diff options
-rw-r--r-- | src/gmdocument.c | 89 | ||||
-rw-r--r-- | src/gmdocument.h | 10 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 50 |
3 files changed, 119 insertions, 30 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c index 433e8dcc..5389aac7 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -1,4 +1,5 @@ | |||
1 | #include "gmdocument.h" | 1 | #include "gmdocument.h" |
2 | #include "gmutil.h" | ||
2 | #include "ui/color.h" | 3 | #include "ui/color.h" |
3 | #include "ui/text.h" | 4 | #include "ui/text.h" |
4 | #include "ui/metrics.h" | 5 | #include "ui/metrics.h" |
@@ -10,10 +11,12 @@ iDeclareType(GmLink) | |||
10 | 11 | ||
11 | struct Impl_GmLink { | 12 | struct Impl_GmLink { |
12 | iString url; | 13 | iString url; |
14 | int flags; | ||
13 | }; | 15 | }; |
14 | 16 | ||
15 | void init_GmLink(iGmLink *d) { | 17 | void init_GmLink(iGmLink *d) { |
16 | init_String(&d->url); | 18 | init_String(&d->url); |
19 | d->flags = 0; | ||
17 | } | 20 | } |
18 | 21 | ||
19 | void deinit_GmLink(iGmLink *d) { | 22 | void deinit_GmLink(iGmLink *d) { |
@@ -25,6 +28,7 @@ iDefineTypeConstruction(GmLink) | |||
25 | struct Impl_GmDocument { | 28 | struct Impl_GmDocument { |
26 | iObject object; | 29 | iObject object; |
27 | iString source; | 30 | iString source; |
31 | iString localHost; | ||
28 | iInt2 size; | 32 | iInt2 size; |
29 | iArray layout; /* contents of source, laid out in document space */ | 33 | iArray layout; /* contents of source, laid out in document space */ |
30 | iPtrArray links; | 34 | iPtrArray links; |
@@ -111,6 +115,26 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li | |||
111 | if (matchRange_RegExp(pattern, line, &m)) { | 115 | if (matchRange_RegExp(pattern, line, &m)) { |
112 | iGmLink *link = new_GmLink(); | 116 | iGmLink *link = new_GmLink(); |
113 | setRange_String(&link->url, capturedRange_RegExpMatch(&m, 1)); | 117 | setRange_String(&link->url, capturedRange_RegExpMatch(&m, 1)); |
118 | /* Check the host. */ { | ||
119 | iUrl parts; | ||
120 | init_Url(&parts, &link->url); | ||
121 | if (!isEmpty_Range(&parts.host) && | ||
122 | !equalCase_Rangecc(&parts.host, cstr_String(&d->localHost))) { | ||
123 | link->flags |= remote_GmLinkFlag; | ||
124 | } | ||
125 | if (!isEmpty_Range(&parts.protocol) && !equalCase_Rangecc(&parts.protocol, "gemini")) { | ||
126 | link->flags |= remote_GmLinkFlag; | ||
127 | } | ||
128 | if (startsWithCase_Rangecc(&parts.protocol, "http")) { | ||
129 | link->flags |= http_GmLinkFlag; | ||
130 | } | ||
131 | else if (equalCase_Rangecc(&parts.protocol, "gopher")) { | ||
132 | link->flags |= gopher_GmLinkFlag; | ||
133 | } | ||
134 | else if (equalCase_Rangecc(&parts.protocol, "file")) { | ||
135 | link->flags |= file_GmLinkFlag; | ||
136 | } | ||
137 | } | ||
114 | pushBack_PtrArray(&d->links, link); | 138 | pushBack_PtrArray(&d->links, link); |
115 | *linkId = size_PtrArray(&d->links); /* index + 1 */ | 139 | *linkId = size_PtrArray(&d->links); /* index + 1 */ |
116 | iRangecc desc = capturedRange_RegExpMatch(&m, 2); | 140 | iRangecc desc = capturedRange_RegExpMatch(&m, 2); |
@@ -156,7 +180,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
156 | white_ColorId, | 180 | white_ColorId, |
157 | }; | 181 | }; |
158 | static const int indents[max_GmLineType] = { | 182 | static const int indents[max_GmLineType] = { |
159 | 4, 10, 4, 10, 0, 0, 0, 0 | 183 | 5, 10, 5, 10, 0, 0, 0, 5 |
160 | }; | 184 | }; |
161 | static const float topMargin[max_GmLineType] = { | 185 | static const float topMargin[max_GmLineType] = { |
162 | 0.0f, 0.5f, 1.0f, 0.5f, 2.0f, 2.0f, 1.5f, 1.0f | 186 | 0.0f, 0.5f, 1.0f, 0.5f, 2.0f, 2.0f, 1.5f, 1.0f |
@@ -164,8 +188,11 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
164 | static const float bottomMargin[max_GmLineType] = { | 188 | static const float bottomMargin[max_GmLineType] = { |
165 | 0.0f, 0.5f, 1.0f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f | 189 | 0.0f, 0.5f, 1.0f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f |
166 | }; | 190 | }; |
167 | const float midRunSkip = 0.1f; /* extra space between wrapped text/quote lines */ | 191 | static const char *arrow = "\u2192"; |
168 | static const char *bullet = "\u2022"; | 192 | static const char *bullet = "\u2022"; |
193 | static const char *folder = "\U0001f4c1"; | ||
194 | static const char *globe = "\U0001f310"; | ||
195 | const float midRunSkip = 0.1f; /* extra space between wrapped text/quote lines */ | ||
169 | clear_Array(&d->layout); | 196 | clear_Array(&d->layout); |
170 | clearLinks_GmDocument_(d); | 197 | clearLinks_GmDocument_(d); |
171 | clear_String(&d->title); | 198 | clear_String(&d->title); |
@@ -236,7 +263,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
236 | if ((type == link_GmLineType && prevType == link_GmLineType) || | 263 | if ((type == link_GmLineType && prevType == link_GmLineType) || |
237 | (type == quote_GmLineType && prevType == quote_GmLineType)) { | 264 | (type == quote_GmLineType && prevType == quote_GmLineType)) { |
238 | /* No margin between consecutive links/quote lines. */ | 265 | /* No margin between consecutive links/quote lines. */ |
239 | required = 0; | 266 | required = |
267 | (type == link_GmLineType ? midRunSkip * lineHeight_Text(paragraph_FontId) : 0); | ||
240 | } | 268 | } |
241 | if (isEmpty_Array(&d->layout)) { | 269 | if (isEmpty_Array(&d->layout)) { |
242 | required = 0; /* top of document */ | 270 | required = 0; /* top of document */ |
@@ -246,19 +274,36 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
246 | pos.y += required - delta; | 274 | pos.y += required - delta; |
247 | } | 275 | } |
248 | } | 276 | } |
249 | /* Document title. */ | 277 | /* Save the document title. */ |
250 | if (type == header1_GmLineType && isEmpty_String(&d->title)) { | 278 | if (type == header1_GmLineType && isEmpty_String(&d->title)) { |
251 | setRange_String(&d->title, line); | 279 | setRange_String(&d->title, line); |
252 | } | 280 | } |
253 | /* List bullet. */ | 281 | /* List bullet. */ |
254 | run.color = colors[type]; | 282 | run.color = colors[type]; |
255 | if (type == bullet_GmLineType) { | 283 | if (type == bullet_GmLineType) { |
256 | run.bounds.pos = addX_I2(pos, indent * gap_UI); | 284 | run.visBounds.pos = addX_I2(pos, indent * gap_UI); |
257 | run.bounds.size = advance_Text(run.font, bullet); | 285 | run.visBounds.size = advance_Text(run.font, bullet); |
258 | run.bounds.pos.x -= 4 * gap_UI - run.bounds.size.x / 2; | 286 | run.visBounds.pos.x -= 4 * gap_UI - width_Rect(run.visBounds) / 2; |
259 | run.text = (iRangecc){ bullet, bullet + strlen(bullet) }; | 287 | run.bounds = zero_Rect(); /* just visual */ |
288 | run.text = range_CStr(bullet); | ||
289 | pushBack_Array(&d->layout, &run); | ||
290 | } | ||
291 | /* Link icon. */ | ||
292 | if (type == link_GmLineType) { | ||
293 | run.visBounds.pos = pos; | ||
294 | run.visBounds.size = init_I2(indent * gap_UI, lineHeight_Text(run.font)); | ||
295 | run.bounds = zero_Rect(); /* just visual */ | ||
296 | const iGmLink *link = constAt_PtrArray(&d->links, run.linkId - 1); | ||
297 | run.text = range_CStr(link->flags & file_GmLinkFlag | ||
298 | ? folder | ||
299 | : link->flags & remote_GmLinkFlag ? globe : arrow); | ||
300 | if (link->flags & remote_GmLinkFlag) { | ||
301 | run.visBounds.pos.x -= gap_UI / 2; | ||
302 | } | ||
303 | run.color = linkColor_GmDocument(d, run.linkId); | ||
260 | pushBack_Array(&d->layout, &run); | 304 | pushBack_Array(&d->layout, &run); |
261 | } | 305 | } |
306 | run.color = colors[type]; | ||
262 | /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ | 307 | /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ |
263 | if (type == text_GmLineType && isFirstText) { | 308 | if (type == text_GmLineType && isFirstText) { |
264 | run.font = firstParagraph_FontId; | 309 | run.font = firstParagraph_FontId; |
@@ -305,6 +350,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
305 | 350 | ||
306 | void init_GmDocument(iGmDocument *d) { | 351 | void init_GmDocument(iGmDocument *d) { |
307 | init_String(&d->source); | 352 | init_String(&d->source); |
353 | init_String(&d->localHost); | ||
308 | d->size = zero_I2(); | 354 | d->size = zero_I2(); |
309 | init_Array(&d->layout, sizeof(iGmRun)); | 355 | init_Array(&d->layout, sizeof(iGmRun)); |
310 | init_PtrArray(&d->links); | 356 | init_PtrArray(&d->links); |
@@ -316,6 +362,7 @@ void deinit_GmDocument(iGmDocument *d) { | |||
316 | clearLinks_GmDocument_(d); | 362 | clearLinks_GmDocument_(d); |
317 | deinit_PtrArray(&d->links); | 363 | deinit_PtrArray(&d->links); |
318 | deinit_Array(&d->layout); | 364 | deinit_Array(&d->layout); |
365 | deinit_String(&d->localHost); | ||
319 | deinit_String(&d->source); | 366 | deinit_String(&d->source); |
320 | } | 367 | } |
321 | 368 | ||
@@ -382,6 +429,10 @@ static void normalize_GmDocument(iGmDocument *d) { | |||
382 | // printf("normalized:\n%s\n", cstr_String(&d->source)); | 429 | // printf("normalized:\n%s\n", cstr_String(&d->source)); |
383 | } | 430 | } |
384 | 431 | ||
432 | void setHost_GmDocument(iGmDocument *d, const iString *host) { | ||
433 | set_String(&d->localHost, host); | ||
434 | } | ||
435 | |||
385 | void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { | 436 | void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { |
386 | set_String(&d->source, source); | 437 | set_String(&d->source, source); |
387 | normalize_GmDocument(d); | 438 | normalize_GmDocument(d); |
@@ -396,12 +447,12 @@ void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRende | |||
396 | iConstForEach(Array, i, &d->layout) { | 447 | iConstForEach(Array, i, &d->layout) { |
397 | const iGmRun *run = i.value; | 448 | const iGmRun *run = i.value; |
398 | if (isInside) { | 449 | if (isInside) { |
399 | if (top_Rect(run->bounds) > visRangeY.end) { | 450 | if (top_Rect(run->visBounds) > visRangeY.end) { |
400 | break; | 451 | break; |
401 | } | 452 | } |
402 | render(context, run); | 453 | render(context, run); |
403 | } | 454 | } |
404 | else if (bottom_Rect(run->bounds) >= visRangeY.start) { | 455 | else if (bottom_Rect(run->visBounds) >= visRangeY.start) { |
405 | isInside = iTrue; | 456 | isInside = iTrue; |
406 | render(context, run); | 457 | render(context, run); |
407 | } | 458 | } |
@@ -473,6 +524,24 @@ const iString *linkUrl_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | |||
473 | return NULL; | 524 | return NULL; |
474 | } | 525 | } |
475 | 526 | ||
527 | int linkFlags_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | ||
528 | if (linkId > 0 && linkId <= size_PtrArray(&d->links)) { | ||
529 | const iGmLink *link = constAt_PtrArray(&d->links, linkId - 1); | ||
530 | return link->flags; | ||
531 | } | ||
532 | return 0; | ||
533 | } | ||
534 | |||
535 | enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | ||
536 | if (linkId > 0 && linkId <= size_PtrArray(&d->links)) { | ||
537 | const iGmLink *link = constAt_PtrArray(&d->links, linkId - 1); | ||
538 | return link->flags & http_GmLinkFlag | ||
539 | ? orange_ColorId | ||
540 | : link->flags & gopher_GmLinkFlag ? blue_ColorId : cyan_ColorId; | ||
541 | } | ||
542 | return white_ColorId; | ||
543 | } | ||
544 | |||
476 | const iString *title_GmDocument(const iGmDocument *d) { | 545 | const iString *title_GmDocument(const iGmDocument *d) { |
477 | return &d->title; | 546 | return &d->title; |
478 | } | 547 | } |
diff --git a/src/gmdocument.h b/src/gmdocument.h index f392a9f2..ebe0ed50 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -9,6 +9,13 @@ iDeclareType(GmRun) | |||
9 | 9 | ||
10 | typedef uint16_t iGmLinkId; | 10 | typedef uint16_t iGmLinkId; |
11 | 11 | ||
12 | enum iGmLinkFlags { | ||
13 | remote_GmLinkFlag = 0x1, | ||
14 | http_GmLinkFlag = 0x2, | ||
15 | gopher_GmLinkFlag = 0x4, | ||
16 | file_GmLinkFlag = 0x8, | ||
17 | }; | ||
18 | |||
12 | struct Impl_GmRun { | 19 | struct Impl_GmRun { |
13 | iRangecc text; | 20 | iRangecc text; |
14 | iRect bounds; /* used for hit testing, extends to edge */ | 21 | iRect bounds; /* used for hit testing, extends to edge */ |
@@ -24,6 +31,7 @@ iDeclareClass(GmDocument) | |||
24 | iDeclareObjectConstruction(GmDocument) | 31 | iDeclareObjectConstruction(GmDocument) |
25 | 32 | ||
26 | void setWidth_GmDocument (iGmDocument *, int width); | 33 | void setWidth_GmDocument (iGmDocument *, int width); |
34 | void setHost_GmDocument (iGmDocument *, const iString *host); /* local host name */ | ||
27 | void setSource_GmDocument (iGmDocument *, const iString *source, int width); | 35 | void setSource_GmDocument (iGmDocument *, const iString *source, int width); |
28 | 36 | ||
29 | typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); | 37 | typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); |
@@ -38,4 +46,6 @@ const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos); | |||
38 | const char * findLoc_GmDocument (const iGmDocument *, iInt2 pos); | 46 | const char * findLoc_GmDocument (const iGmDocument *, iInt2 pos); |
39 | const iGmRun * findRunAtLoc_GmDocument (const iGmDocument *, const char *loc); | 47 | const iGmRun * findRunAtLoc_GmDocument (const iGmDocument *, const char *loc); |
40 | const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); | 48 | const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); |
49 | int linkFlags_GmDocument (const iGmDocument *, iGmLinkId linkId); | ||
50 | enum iColorId linkColor_GmDocument (const iGmDocument *, iGmLinkId linkId); | ||
41 | const iString * title_GmDocument (const iGmDocument *); | 51 | const iString * title_GmDocument (const iGmDocument *); |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 36ec99ff..a02dc67f 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -179,6 +179,9 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { | |||
179 | } | 179 | } |
180 | 180 | ||
181 | static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) { | 181 | static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) { |
182 | iUrl parts; | ||
183 | init_Url(&parts, d->url); | ||
184 | setHost_GmDocument(d->doc, collect_String(newRange_String(parts.host))); | ||
182 | setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d)); | 185 | setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d)); |
183 | d->foundMark = iNullRange; | 186 | d->foundMark = iNullRange; |
184 | d->selectMark = iNullRange; | 187 | d->selectMark = iNullRange; |
@@ -676,34 +679,41 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
676 | /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */ | 679 | /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */ |
677 | initRange_String(&text, run->text); | 680 | initRange_String(&text, run->text); |
678 | iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); | 681 | iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); |
682 | enum iColorId fg = run->color; | ||
679 | if (run == d->widget->hoverLink) { | 683 | if (run == d->widget->hoverLink) { |
680 | const char *desc = ""; | 684 | //const char *desc = ""; |
681 | const iString *url = linkUrl_GmDocument(d->widget->doc, d->widget->hoverLink->linkId); | 685 | const iGmDocument *doc = d->widget->doc; |
682 | if (indexOfCStr_String(url, "://") == iInvalidPos) { | 686 | const iGmLinkId linkId = d->widget->hoverLink->linkId; |
683 | url = d->widget->url; | 687 | const iString *url = linkUrl_GmDocument(doc, linkId); |
684 | } | 688 | // const int flags = linkFlags_GmDocument(doc, linkId); |
685 | iUrl parts; | 689 | iUrl parts; |
686 | init_Url(&parts, url); | 690 | init_Url(&parts, url); |
687 | desc = cstrFormat_String("\u2192 %s", cstr_String(collect_String(newRange_String(parts.protocol)))); | 691 | // desc = cstrFormat_String("\u2192 %s", cstr_String(collect_String(newRange_String(parts.protocol)))); |
688 | int descWidth = measure_Text(default_FontId, desc).x + gap_UI; | 692 | const iString *host = collect_String(newRange_String(parts.host)); |
689 | iRect linkRect = expanded_Rect(moved_Rect(run->bounds, origin), init_I2(gap_UI, 0)); | 693 | fg = linkColor_GmDocument(doc, linkId); |
690 | linkRect.size.x += descWidth; | 694 | if (!isEmpty_String(host)) { |
691 | fillRect_Paint(&d->paint, linkRect, teal_ColorId); | 695 | // int descWidth = measure_Text(default_FontId, cstr_String(host)).x + gap_UI; |
692 | drawAlign_Text(default_FontId, | 696 | iRect linkRect = moved_Rect(run->visBounds, origin); |
693 | addX_I2(topRight_Rect(linkRect), -gap_UI), | 697 | // linkRect.size.x += descWidth; |
694 | cyan_ColorId, | 698 | // fillRect_Paint(&d->paint, linkRect, teal_ColorId); |
695 | right_Alignment, | 699 | drawAlign_Text(default_FontId, |
696 | "%s", | 700 | // init_I2(right_Rect(d->bounds), top_Rect(linkRect)), |
697 | desc); | 701 | topRight_Rect(linkRect), |
698 | } | 702 | fg - 1, |
699 | const iInt2 visPos = add_I2(run->bounds.pos, origin); | 703 | left_Alignment, |
704 | " \u2014 %s", | ||
705 | cstr_String(host)); | ||
706 | } | ||
707 | } | ||
708 | const iInt2 visPos = add_I2(run->visBounds.pos, origin); | ||
700 | /* Text markers. */ | 709 | /* Text markers. */ |
701 | fillRange_DrawContext_(d, run, teal_ColorId, d->widget->foundMark, &d->inFoundMark); | 710 | fillRange_DrawContext_(d, run, teal_ColorId, d->widget->foundMark, &d->inFoundMark); |
702 | fillRange_DrawContext_(d, run, brown_ColorId, d->widget->selectMark, &d->inSelectMark); | 711 | fillRange_DrawContext_(d, run, brown_ColorId, d->widget->selectMark, &d->inSelectMark); |
703 | drawString_Text(run->font, visPos, run->color, &text); | 712 | drawString_Text(run->font, visPos, fg, &text); |
704 | deinit_String(&text); | 713 | deinit_String(&text); |
705 | 714 | ||
706 | // drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, red_ColorId); | 715 | // drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); |
716 | // drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); | ||
707 | } | 717 | } |
708 | 718 | ||
709 | static void draw_DocumentWidget_(const iDocumentWidget *d) { | 719 | static void draw_DocumentWidget_(const iDocumentWidget *d) { |