summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gmdocument.c26
-rw-r--r--src/gmdocument.h8
-rw-r--r--src/ui/documentwidget.c103
-rw-r--r--src/ui/widget.h4
-rw-r--r--src/ui/window.c27
5 files changed, 150 insertions, 18 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c
index f1f63b29..3192acd5 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -232,8 +232,9 @@ static void doLayout_GmDocument_(iGmDocument *d) {
232 if (!isPreformat || (prevType != preformatted_GmLineType)) { 232 if (!isPreformat || (prevType != preformatted_GmLineType)) {
233 int required = 233 int required =
234 iMax(topMargin[type], bottomMargin[prevType]) * lineHeight_Text(paragraph_FontId); 234 iMax(topMargin[type], bottomMargin[prevType]) * lineHeight_Text(paragraph_FontId);
235 if (type == link_GmLineType && prevType == link_GmLineType) { 235 if ((type == link_GmLineType && prevType == link_GmLineType) ||
236 /* No margin between consecutive links. */ 236 (type == quote_GmLineType && prevType == quote_GmLineType)) {
237 /* No margin between consecutive links/quote lines. */
237 required = 0; 238 required = 0;
238 } 239 }
239 if (isEmpty_Array(&d->layout)) { 240 if (isEmpty_Array(&d->layout)) {
@@ -399,6 +400,17 @@ iInt2 size_GmDocument(const iGmDocument *d) {
399 return d->size; 400 return d->size;
400} 401}
401 402
403iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) {
404 const char * src = constBegin_String(&d->source);
405 const size_t startPos = (start ? start - src : 0);
406 const size_t pos =
407 indexOfCStrFromSc_String(&d->source, cstr_String(text), startPos, &iCaseInsensitive);
408 if (pos == iInvalidPos) {
409 return iNullRange;
410 }
411 return (iRangecc){ src + pos, src + pos + size_String(text) };
412}
413
402const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) { 414const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) {
403 /* TODO: Perf optimization likely needed; use a block map? */ 415 /* TODO: Perf optimization likely needed; use a block map? */
404 iConstForEach(Array, i, &d->layout) { 416 iConstForEach(Array, i, &d->layout) {
@@ -410,6 +422,16 @@ const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) {
410 return NULL; 422 return NULL;
411} 423}
412 424
425const iGmRun *findRunCStr_GmDocument(const iGmDocument *d, const char *textCStr) {
426 iConstForEach(Array, i, &d->layout) {
427 const iGmRun *run = i.value;
428 if (contains_Range(&run->text, textCStr) || run->text.start > textCStr /* went past */) {
429 return run;
430 }
431 }
432 return NULL;
433}
434
413const iString *linkUrl_GmDocument(const iGmDocument *d, iGmLinkId linkId) { 435const iString *linkUrl_GmDocument(const iGmDocument *d, iGmLinkId linkId) {
414 if (linkId > 0 && linkId <= size_PtrArray(&d->links)) { 436 if (linkId > 0 && linkId <= size_PtrArray(&d->links)) {
415 const iGmLink *link = constAt_PtrArray(&d->links, linkId - 1); 437 const iGmLink *link = constAt_PtrArray(&d->links, linkId - 1);
diff --git a/src/gmdocument.h b/src/gmdocument.h
index 22e614ea..01ade5d3 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -28,6 +28,8 @@ typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *);
28void render_GmDocument (const iGmDocument *, iRangei visRangeY, iGmDocumentRenderFunc render, void *); 28void render_GmDocument (const iGmDocument *, iRangei visRangeY, iGmDocumentRenderFunc render, void *);
29iInt2 size_GmDocument (const iGmDocument *); 29iInt2 size_GmDocument (const iGmDocument *);
30 30
31const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos); 31iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start);
32const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); 32const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos);
33const iString * title_GmDocument (const iGmDocument *); 33const iGmRun * findRunCStr_GmDocument (const iGmDocument *, const char *textCStr);
34const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId);
35const iString * title_GmDocument (const iGmDocument *);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 5df2b1b9..f7bfa39f 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -32,6 +32,7 @@ struct Impl_DocumentWidget {
32 iGmRequest *request; 32 iGmRequest *request;
33 iAtomicInt isSourcePending; /* request has new content, need to parse it */ 33 iAtomicInt isSourcePending; /* request has new content, need to parse it */
34 iGmDocument *doc; 34 iGmDocument *doc;
35 iRangecc foundMark;
35 int pageMargin; 36 int pageMargin;
36 int scrollY; 37 int scrollY;
37 iPtrArray visibleLinks; 38 iPtrArray visibleLinks;
@@ -47,14 +48,15 @@ void init_DocumentWidget(iDocumentWidget *d) {
47 iWidget *w = as_Widget(d); 48 iWidget *w = as_Widget(d);
48 init_Widget(w); 49 init_Widget(w);
49 setId_Widget(w, "document"); 50 setId_Widget(w, "document");
50 d->state = blank_DocumentState; 51 d->state = blank_DocumentState;
51 d->url = new_String(); 52 d->url = new_String();
52 d->request = NULL; 53 d->request = NULL;
53 d->isSourcePending = iFalse; 54 d->isSourcePending = iFalse;
54 d->doc = new_GmDocument(); 55 d->doc = new_GmDocument();
55 d->pageMargin = 5; 56 d->foundMark = iNullRange;
56 d->scrollY = 0; 57 d->pageMargin = 5;
57 d->hoverLink = NULL; 58 d->scrollY = 0;
59 d->hoverLink = NULL;
58 init_PtrArray(&d->visibleLinks); 60 init_PtrArray(&d->visibleLinks);
59 init_Click(&d->click, d, SDL_BUTTON_LEFT); 61 init_Click(&d->click, d, SDL_BUTTON_LEFT);
60 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); 62 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget()));
@@ -150,10 +152,11 @@ static void updateSource_DocumentWidget_(iDocumentWidget *d) {
150 /* TODO: Do this in the background. However, that requires a text metrics calculator 152 /* TODO: Do this in the background. However, that requires a text metrics calculator
151 that does not try to cache the glyph bitmaps. */ 153 that does not try to cache the glyph bitmaps. */
152 if (status_GmRequest(d->request) != input_GmStatusCode && 154 if (status_GmRequest(d->request) != input_GmStatusCode &&
153 status_GmRequest(d->request) != sensitiveInput_GmStatusCode) { 155 status_GmRequest(d->request) != sensitiveInput_GmStatusCode) {
154 iString str; 156 iString str;
155 initBlock_String(&str, body_GmRequest(d->request)); 157 initBlock_String(&str, body_GmRequest(d->request));
156 setSource_GmDocument(d->doc, &str, documentWidth_DocumentWidget_(d)); 158 setSource_GmDocument(d->doc, &str, documentWidth_DocumentWidget_(d));
159 d->foundMark = iNullRange;
157 updateWindowTitle_DocumentWidget_(d); 160 updateWindowTitle_DocumentWidget_(d);
158 updateVisible_DocumentWidget_(d); 161 updateVisible_DocumentWidget_(d);
159 refresh_Widget(as_Widget(d)); 162 refresh_Widget(as_Widget(d));
@@ -211,6 +214,11 @@ static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) {
211 refresh_Widget(as_Widget(d)); 214 refresh_Widget(as_Widget(d));
212} 215}
213 216
217static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY) {
218 d->scrollY = documentY - documentBounds_DocumentWidget_(d).size.y / 2;
219 scroll_DocumentWidget_(d, 0); /* clamp it */
220}
221
214static iRangecc dirPath_(iRangecc path) { 222static iRangecc dirPath_(iRangecc path) {
215 const size_t pos = lastIndexOfCStr_Rangecc(&path, "/"); 223 const size_t pos = lastIndexOfCStr_Rangecc(&path, "/");
216 if (pos == iInvalidPos) return path; 224 if (pos == iInvalidPos) return path;
@@ -285,6 +293,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
285 default: 293 default:
286 break; 294 break;
287 } 295 }
296 d->foundMark = iNullRange;
288 setSource_GmDocument(d->doc, src, documentWidth_DocumentWidget_(d)); 297 setSource_GmDocument(d->doc, src, documentWidth_DocumentWidget_(d));
289 updateWindowTitle_DocumentWidget_(d); 298 updateWindowTitle_DocumentWidget_(d);
290 updateVisible_DocumentWidget_(d); 299 updateVisible_DocumentWidget_(d);
@@ -408,6 +417,34 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
408 d, arg_Command(command_UserEvent(ev)) * height_Rect(documentBounds_DocumentWidget_(d))); 417 d, arg_Command(command_UserEvent(ev)) * height_Rect(documentBounds_DocumentWidget_(d)));
409 return iTrue; 418 return iTrue;
410 } 419 }
420 else if (isCommand_UserEvent(ev, "find.next")) {
421 iInputWidget *find = findWidget_App("find.input");
422 if (isEmpty_String(text_InputWidget(find))) {
423 d->foundMark = iNullRange;
424 }
425 else {
426 const iBool wrap = d->foundMark.start != NULL;
427 d->foundMark = findText_GmDocument(d->doc, text_InputWidget(find), d->foundMark.end);
428 if (!d->foundMark.start && wrap) {
429 d->foundMark = findText_GmDocument(d->doc, text_InputWidget(find), 0);
430 }
431 if (d->foundMark.start) {
432 iGmRun *run = findRunCStr_GmDocument(d->doc, d->foundMark.start);
433 if (run) {
434 scrollTo_DocumentWidget_(d, mid_Rect(run->bounds).y);
435 }
436 }
437 }
438 refresh_Widget(w);
439 return iTrue;
440 }
441 else if (isCommand_UserEvent(ev, "find.clearmark")) {
442 if (d->foundMark.start) {
443 d->foundMark = iNullRange;
444 refresh_Widget(w);
445 }
446 return iTrue;
447 }
411 if (ev->type == SDL_KEYDOWN) { 448 if (ev->type == SDL_KEYDOWN) {
412 const int mods = keyMods_Sym(ev->key.keysym.mod); 449 const int mods = keyMods_Sym(ev->key.keysym.mod);
413 const int key = ev->key.keysym.sym; 450 const int key = ev->key.keysym.sym;
@@ -493,6 +530,7 @@ struct Impl_DrawContext {
493 const iDocumentWidget *widget; 530 const iDocumentWidget *widget;
494 iRect bounds; 531 iRect bounds;
495 iPaint paint; 532 iPaint paint;
533 int insideMark;
496}; 534};
497 535
498static void drawRun_DrawContext_(void *context, const iGmRun *run) { 536static void drawRun_DrawContext_(void *context, const iGmRun *run) {
@@ -501,10 +539,55 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
501 /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */ 539 /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */
502 initRange_String(&text, run->text); 540 initRange_String(&text, run->text);
503 iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); 541 iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY);
504 drawString_Text(run->font, add_I2(run->bounds.pos, origin), run->color, &text);
505 if (run == d->widget->hoverLink) { 542 if (run == d->widget->hoverLink) {
506 drawRect_Paint(&d->paint, moved_Rect(run->bounds, origin), orange_ColorId); 543 const char *desc = "";
544 const iString *url = linkUrl_GmDocument(d->widget->doc, d->widget->hoverLink->linkId);
545 if (indexOfCStr_String(url, "://") == iInvalidPos) {
546 url = d->widget->url;
547 }
548 iUrl parts;
549 init_Url(&parts, url);
550 desc = cstrFormat_String("\u2192 %s", cstr_String(collect_String(newRange_String(parts.protocol))));
551 int descWidth = measure_Text(default_FontId, desc).x + gap_UI;
552 iRect linkRect = expanded_Rect(moved_Rect(run->bounds, origin), init_I2(gap_UI, 0));
553 linkRect.size.x += descWidth;
554 fillRect_Paint(&d->paint,
555 linkRect,
556 teal_ColorId);
557 drawAlign_Text(default_FontId,
558 addX_I2(topRight_Rect(linkRect), -gap_UI),
559 cyan_ColorId,
560 right_Alignment,
561 "%s",
562 desc);
563 }
564 const iInt2 visPos = add_I2(run->bounds.pos, origin);
565 /* Found text marker. */
566 if ((!d->insideMark && contains_Range(&run->text, d->widget->foundMark.start)) ||
567 d->insideMark) {
568 int x = 0;
569 if (!d->insideMark) {
570 x = advanceRange_Text(run->font, (iRangecc){ run->text.start,
571 d->widget->foundMark.start }).x;
572 }
573 int w = width_Rect(run->bounds) - x;
574 if (contains_Range(&run->text, d->widget->foundMark.end) ||
575 run->text.end == d->widget->foundMark.end) {
576 w = advanceRange_Text(run->font,
577 !d->insideMark
578 ? d->widget->foundMark
579 : (iRangecc){ run->text.start, d->widget->foundMark.end })
580 .x;
581 d->insideMark = iFalse;
582 }
583 else {
584 d->insideMark = iTrue; /* at least until the next run */
585 }
586 fillRect_Paint(&d->paint,
587 (iRect){ addX_I2(visPos, x), init_I2(w, height_Rect(run->bounds)) },
588 teal_ColorId);
507 } 589 }
590 drawString_Text(run->font, visPos, run->color, &text);
508 deinit_String(&text); 591 deinit_String(&text);
509} 592}
510 593
diff --git a/src/ui/widget.h b/src/ui/widget.h
index 3155d6df..48b796bc 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -74,18 +74,22 @@ struct Impl_Widget {
74iDeclareObjectConstruction(Widget) 74iDeclareObjectConstruction(Widget)
75 75
76iLocalDef iWidget *as_Widget(iAnyObject *d) { 76iLocalDef iWidget *as_Widget(iAnyObject *d) {
77#if !defined (NDEBUG)
77 if (d) { 78 if (d) {
78 iAssertIsObject(d); 79 iAssertIsObject(d);
79 iAssert(isInstance_Object(d, &Class_Widget)); 80 iAssert(isInstance_Object(d, &Class_Widget));
80 } 81 }
82#endif
81 return (iWidget *) d; 83 return (iWidget *) d;
82} 84}
83 85
84iLocalDef const iWidget *constAs_Widget(const iAnyObject *d) { 86iLocalDef const iWidget *constAs_Widget(const iAnyObject *d) {
87#if !defined (NDEBUG)
85 if (d) { 88 if (d) {
86 iAssertIsObject(d); 89 iAssertIsObject(d);
87 iAssert(isInstance_Object(d, &Class_Widget)); 90 iAssert(isInstance_Object(d, &Class_Widget));
88 } 91 }
92#endif
89 return (const iWidget *) d; 93 return (const iWidget *) d;
90} 94}
91 95
diff --git a/src/ui/window.c b/src/ui/window.c
index 5293b55b..e340e305 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -117,6 +117,24 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
117 return iFalse; 117 return iFalse;
118} 118}
119 119
120static iBool handleSearchBarCommands_(iWidget *searchBar, const char *cmd) {
121 if (equal_Command(cmd, "input.ended") &&
122 cmp_String(string_Command(cmd, "id"), "find.input") == 0) {
123 if (arg_Command(cmd)) {
124 postCommand_App("find.next");
125 /* Keep focus. */
126 if (!isEmpty_String(text_InputWidget(findChild_Widget(searchBar, "find.input")))) {
127 postCommand_App("focus.set id:find.input");
128 }
129 }
130 else {
131 postCommand_App("find.clearmark");
132 }
133 return iTrue;
134 }
135 return iFalse;
136}
137
120static void setupUserInterface_Window(iWindow *d) { 138static void setupUserInterface_Window(iWindow *d) {
121 /* Children of root cover the entire window. */ 139 /* Children of root cover the entire window. */
122 setFlags_Widget(d->root, resizeChildren_WidgetFlag, iTrue); 140 setFlags_Widget(d->root, resizeChildren_WidgetFlag, iTrue);
@@ -134,8 +152,8 @@ static void setupUserInterface_Window(iWindow *d) {
134 arrangeHorizontal_WidgetFlag, 152 arrangeHorizontal_WidgetFlag,
135 iTrue); 153 iTrue);
136 addChild_Widget(div, iClob(navBar)); 154 addChild_Widget(div, iClob(navBar));
137 setCommandHandler_Widget(navBar, handleNavBarCommands_);
138 setBackgroundColor_Widget(navBar, gray25_ColorId); 155 setBackgroundColor_Widget(navBar, gray25_ColorId);
156 setCommandHandler_Widget(navBar, handleNavBarCommands_);
139 157
140 addChild_Widget(navBar, iClob(new_LabelWidget(" \u25c4 ", 0, 0, "navigate.back"))); 158 addChild_Widget(navBar, iClob(new_LabelWidget(" \u25c4 ", 0, 0, "navigate.back")));
141 addChild_Widget(navBar, iClob(new_LabelWidget(" \u25ba ", 0, 0, "navigate.forward"))); 159 addChild_Widget(navBar, iClob(new_LabelWidget(" \u25ba ", 0, 0, "navigate.forward")));
@@ -160,9 +178,11 @@ static void setupUserInterface_Window(iWindow *d) {
160 iTrue); 178 iTrue);
161 addChild_Widget(div, iClob(searchBar)); 179 addChild_Widget(div, iClob(searchBar));
162 setBackgroundColor_Widget(searchBar, gray25_ColorId); 180 setBackgroundColor_Widget(searchBar, gray25_ColorId);
181 setCommandHandler_Widget(searchBar, handleSearchBarCommands_);
163 182
164 addChild_Widget(searchBar, iClob(new_LabelWidget("\U0001f50d Find:", 0, 0, NULL))); 183 addChild_Widget(searchBar, iClob(new_LabelWidget("\U0001f50d Text", 0, 0, NULL)));
165 addChildFlags_Widget(searchBar, iClob(new_InputWidget(0)), expand_WidgetFlag); 184 setId_Widget(addChildFlags_Widget(searchBar, iClob(new_InputWidget(0)), expand_WidgetFlag),
185 "find.input");
166 addChild_Widget(searchBar, iClob(new_LabelWidget("Next", 0, 0, "find.next"))); 186 addChild_Widget(searchBar, iClob(new_LabelWidget("Next", 0, 0, "find.next")));
167 addChild_Widget(searchBar, iClob(new_LabelWidget("Previous", 0, 0, "find.prev"))); 187 addChild_Widget(searchBar, iClob(new_LabelWidget("Previous", 0, 0, "find.prev")));
168 addChild_Widget(searchBar, iClob(new_LabelWidget("\u00d7", 0, 0, "find.close"))); 188 addChild_Widget(searchBar, iClob(new_LabelWidget("\u00d7", 0, 0, "find.close")));
@@ -320,6 +340,7 @@ static void setupUserInterface_Window(iWindow *d) {
320 /* Glboal keyboard shortcuts. */ { 340 /* Glboal keyboard shortcuts. */ {
321 // addAction_Widget(d->root, SDLK_LEFTBRACKET, KMOD_SHIFT | KMOD_PRIMARY, "tabs.prev"); 341 // addAction_Widget(d->root, SDLK_LEFTBRACKET, KMOD_SHIFT | KMOD_PRIMARY, "tabs.prev");
322 addAction_Widget(d->root, 'l', KMOD_PRIMARY, "focus.set id:url"); 342 addAction_Widget(d->root, 'l', KMOD_PRIMARY, "focus.set id:url");
343 addAction_Widget(d->root, 'f', KMOD_PRIMARY, "focus.set id:find.input");
323 } 344 }
324} 345}
325 346