diff options
-rw-r--r-- | src/gmdocument.c | 26 | ||||
-rw-r--r-- | src/gmdocument.h | 8 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 103 | ||||
-rw-r--r-- | src/ui/widget.h | 4 | ||||
-rw-r--r-- | src/ui/window.c | 27 |
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 | ||
403 | iRangecc 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 | |||
402 | const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) { | 414 | const 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 | ||
425 | const 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 | |||
413 | const iString *linkUrl_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | 435 | const 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 *); | |||
28 | void render_GmDocument (const iGmDocument *, iRangei visRangeY, iGmDocumentRenderFunc render, void *); | 28 | void render_GmDocument (const iGmDocument *, iRangei visRangeY, iGmDocumentRenderFunc render, void *); |
29 | iInt2 size_GmDocument (const iGmDocument *); | 29 | iInt2 size_GmDocument (const iGmDocument *); |
30 | 30 | ||
31 | const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos); | 31 | iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); |
32 | const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); | 32 | const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos); |
33 | const iString * title_GmDocument (const iGmDocument *); | 33 | const iGmRun * findRunCStr_GmDocument (const iGmDocument *, const char *textCStr); |
34 | const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); | ||
35 | const 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 | ||
217 | static 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 | |||
214 | static iRangecc dirPath_(iRangecc path) { | 222 | static 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 | ||
498 | static void drawRun_DrawContext_(void *context, const iGmRun *run) { | 536 | static 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 { | |||
74 | iDeclareObjectConstruction(Widget) | 74 | iDeclareObjectConstruction(Widget) |
75 | 75 | ||
76 | iLocalDef iWidget *as_Widget(iAnyObject *d) { | 76 | iLocalDef 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 | ||
84 | iLocalDef const iWidget *constAs_Widget(const iAnyObject *d) { | 86 | iLocalDef 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 | ||
120 | static 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 | |||
120 | static void setupUserInterface_Window(iWindow *d) { | 138 | static 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 | ||