summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-07-27 21:48:02 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-07-27 21:48:02 +0300
commit6f18be186a4861a758023ceba92f098273b2dc5c (patch)
tree4fe629e0fa54de8eea5eb2b117efae277287f86b
parentd3b5fe0d8d05d8e76d3f4646f02b0fa78561b1db (diff)
Visualizing links
Show which links are to the same site, and which are HTTP/S, Gopher, or files.
-rw-r--r--src/gmdocument.c89
-rw-r--r--src/gmdocument.h10
-rw-r--r--src/ui/documentwidget.c50
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
11struct Impl_GmLink { 12struct Impl_GmLink {
12 iString url; 13 iString url;
14 int flags;
13}; 15};
14 16
15void init_GmLink(iGmLink *d) { 17void init_GmLink(iGmLink *d) {
16 init_String(&d->url); 18 init_String(&d->url);
19 d->flags = 0;
17} 20}
18 21
19void deinit_GmLink(iGmLink *d) { 22void deinit_GmLink(iGmLink *d) {
@@ -25,6 +28,7 @@ iDefineTypeConstruction(GmLink)
25struct Impl_GmDocument { 28struct 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
306void init_GmDocument(iGmDocument *d) { 351void 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
432void setHost_GmDocument(iGmDocument *d, const iString *host) {
433 set_String(&d->localHost, host);
434}
435
385void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { 436void 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
527int 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
535enum 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
476const iString *title_GmDocument(const iGmDocument *d) { 545const 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
10typedef uint16_t iGmLinkId; 10typedef uint16_t iGmLinkId;
11 11
12enum iGmLinkFlags {
13 remote_GmLinkFlag = 0x1,
14 http_GmLinkFlag = 0x2,
15 gopher_GmLinkFlag = 0x4,
16 file_GmLinkFlag = 0x8,
17};
18
12struct Impl_GmRun { 19struct 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)
24iDeclareObjectConstruction(GmDocument) 31iDeclareObjectConstruction(GmDocument)
25 32
26void setWidth_GmDocument (iGmDocument *, int width); 33void setWidth_GmDocument (iGmDocument *, int width);
34void setHost_GmDocument (iGmDocument *, const iString *host); /* local host name */
27void setSource_GmDocument (iGmDocument *, const iString *source, int width); 35void setSource_GmDocument (iGmDocument *, const iString *source, int width);
28 36
29typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); 37typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *);
@@ -38,4 +46,6 @@ const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos);
38const char * findLoc_GmDocument (const iGmDocument *, iInt2 pos); 46const char * findLoc_GmDocument (const iGmDocument *, iInt2 pos);
39const iGmRun * findRunAtLoc_GmDocument (const iGmDocument *, const char *loc); 47const iGmRun * findRunAtLoc_GmDocument (const iGmDocument *, const char *loc);
40const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); 48const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId);
49int linkFlags_GmDocument (const iGmDocument *, iGmLinkId linkId);
50enum iColorId linkColor_GmDocument (const iGmDocument *, iGmLinkId linkId);
41const iString * title_GmDocument (const iGmDocument *); 51const 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
181static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) { 181static 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
709static void draw_DocumentWidget_(const iDocumentWidget *d) { 719static void draw_DocumentWidget_(const iDocumentWidget *d) {