From 6f18be186a4861a758023ceba92f098273b2dc5c Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 27 Jul 2020 21:48:02 +0300 Subject: Visualizing links Show which links are to the same site, and which are HTTP/S, Gopher, or files. --- src/gmdocument.c | 89 +++++++++++++++++++++++++++++++++++++++++++------ src/gmdocument.h | 10 ++++++ src/ui/documentwidget.c | 50 ++++++++++++++++----------- 3 files changed, 119 insertions(+), 30 deletions(-) (limited to 'src') 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 @@ #include "gmdocument.h" +#include "gmutil.h" #include "ui/color.h" #include "ui/text.h" #include "ui/metrics.h" @@ -10,10 +11,12 @@ iDeclareType(GmLink) struct Impl_GmLink { iString url; + int flags; }; void init_GmLink(iGmLink *d) { init_String(&d->url); + d->flags = 0; } void deinit_GmLink(iGmLink *d) { @@ -25,6 +28,7 @@ iDefineTypeConstruction(GmLink) struct Impl_GmDocument { iObject object; iString source; + iString localHost; iInt2 size; iArray layout; /* contents of source, laid out in document space */ iPtrArray links; @@ -111,6 +115,26 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li if (matchRange_RegExp(pattern, line, &m)) { iGmLink *link = new_GmLink(); setRange_String(&link->url, capturedRange_RegExpMatch(&m, 1)); + /* Check the host. */ { + iUrl parts; + init_Url(&parts, &link->url); + if (!isEmpty_Range(&parts.host) && + !equalCase_Rangecc(&parts.host, cstr_String(&d->localHost))) { + link->flags |= remote_GmLinkFlag; + } + if (!isEmpty_Range(&parts.protocol) && !equalCase_Rangecc(&parts.protocol, "gemini")) { + link->flags |= remote_GmLinkFlag; + } + if (startsWithCase_Rangecc(&parts.protocol, "http")) { + link->flags |= http_GmLinkFlag; + } + else if (equalCase_Rangecc(&parts.protocol, "gopher")) { + link->flags |= gopher_GmLinkFlag; + } + else if (equalCase_Rangecc(&parts.protocol, "file")) { + link->flags |= file_GmLinkFlag; + } + } pushBack_PtrArray(&d->links, link); *linkId = size_PtrArray(&d->links); /* index + 1 */ iRangecc desc = capturedRange_RegExpMatch(&m, 2); @@ -156,7 +180,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { white_ColorId, }; static const int indents[max_GmLineType] = { - 4, 10, 4, 10, 0, 0, 0, 0 + 5, 10, 5, 10, 0, 0, 0, 5 }; static const float topMargin[max_GmLineType] = { 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) { static const float bottomMargin[max_GmLineType] = { 0.0f, 0.5f, 1.0f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f }; - const float midRunSkip = 0.1f; /* extra space between wrapped text/quote lines */ + static const char *arrow = "\u2192"; static const char *bullet = "\u2022"; + static const char *folder = "\U0001f4c1"; + static const char *globe = "\U0001f310"; + const float midRunSkip = 0.1f; /* extra space between wrapped text/quote lines */ clear_Array(&d->layout); clearLinks_GmDocument_(d); clear_String(&d->title); @@ -236,7 +263,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { if ((type == link_GmLineType && prevType == link_GmLineType) || (type == quote_GmLineType && prevType == quote_GmLineType)) { /* No margin between consecutive links/quote lines. */ - required = 0; + required = + (type == link_GmLineType ? midRunSkip * lineHeight_Text(paragraph_FontId) : 0); } if (isEmpty_Array(&d->layout)) { required = 0; /* top of document */ @@ -246,19 +274,36 @@ static void doLayout_GmDocument_(iGmDocument *d) { pos.y += required - delta; } } - /* Document title. */ + /* Save the document title. */ if (type == header1_GmLineType && isEmpty_String(&d->title)) { setRange_String(&d->title, line); } /* List bullet. */ run.color = colors[type]; if (type == bullet_GmLineType) { - run.bounds.pos = addX_I2(pos, indent * gap_UI); - run.bounds.size = advance_Text(run.font, bullet); - run.bounds.pos.x -= 4 * gap_UI - run.bounds.size.x / 2; - run.text = (iRangecc){ bullet, bullet + strlen(bullet) }; + run.visBounds.pos = addX_I2(pos, indent * gap_UI); + run.visBounds.size = advance_Text(run.font, bullet); + run.visBounds.pos.x -= 4 * gap_UI - width_Rect(run.visBounds) / 2; + run.bounds = zero_Rect(); /* just visual */ + run.text = range_CStr(bullet); + pushBack_Array(&d->layout, &run); + } + /* Link icon. */ + if (type == link_GmLineType) { + run.visBounds.pos = pos; + run.visBounds.size = init_I2(indent * gap_UI, lineHeight_Text(run.font)); + run.bounds = zero_Rect(); /* just visual */ + const iGmLink *link = constAt_PtrArray(&d->links, run.linkId - 1); + run.text = range_CStr(link->flags & file_GmLinkFlag + ? folder + : link->flags & remote_GmLinkFlag ? globe : arrow); + if (link->flags & remote_GmLinkFlag) { + run.visBounds.pos.x -= gap_UI / 2; + } + run.color = linkColor_GmDocument(d, run.linkId); pushBack_Array(&d->layout, &run); } + run.color = colors[type]; /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ if (type == text_GmLineType && isFirstText) { run.font = firstParagraph_FontId; @@ -305,6 +350,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { void init_GmDocument(iGmDocument *d) { init_String(&d->source); + init_String(&d->localHost); d->size = zero_I2(); init_Array(&d->layout, sizeof(iGmRun)); init_PtrArray(&d->links); @@ -316,6 +362,7 @@ void deinit_GmDocument(iGmDocument *d) { clearLinks_GmDocument_(d); deinit_PtrArray(&d->links); deinit_Array(&d->layout); + deinit_String(&d->localHost); deinit_String(&d->source); } @@ -382,6 +429,10 @@ static void normalize_GmDocument(iGmDocument *d) { // printf("normalized:\n%s\n", cstr_String(&d->source)); } +void setHost_GmDocument(iGmDocument *d, const iString *host) { + set_String(&d->localHost, host); +} + void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { set_String(&d->source, source); normalize_GmDocument(d); @@ -396,12 +447,12 @@ void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRende iConstForEach(Array, i, &d->layout) { const iGmRun *run = i.value; if (isInside) { - if (top_Rect(run->bounds) > visRangeY.end) { + if (top_Rect(run->visBounds) > visRangeY.end) { break; } render(context, run); } - else if (bottom_Rect(run->bounds) >= visRangeY.start) { + else if (bottom_Rect(run->visBounds) >= visRangeY.start) { isInside = iTrue; render(context, run); } @@ -473,6 +524,24 @@ const iString *linkUrl_GmDocument(const iGmDocument *d, iGmLinkId linkId) { return NULL; } +int linkFlags_GmDocument(const iGmDocument *d, iGmLinkId linkId) { + if (linkId > 0 && linkId <= size_PtrArray(&d->links)) { + const iGmLink *link = constAt_PtrArray(&d->links, linkId - 1); + return link->flags; + } + return 0; +} + +enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId) { + if (linkId > 0 && linkId <= size_PtrArray(&d->links)) { + const iGmLink *link = constAt_PtrArray(&d->links, linkId - 1); + return link->flags & http_GmLinkFlag + ? orange_ColorId + : link->flags & gopher_GmLinkFlag ? blue_ColorId : cyan_ColorId; + } + return white_ColorId; +} + const iString *title_GmDocument(const iGmDocument *d) { return &d->title; } 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) typedef uint16_t iGmLinkId; +enum iGmLinkFlags { + remote_GmLinkFlag = 0x1, + http_GmLinkFlag = 0x2, + gopher_GmLinkFlag = 0x4, + file_GmLinkFlag = 0x8, +}; + struct Impl_GmRun { iRangecc text; iRect bounds; /* used for hit testing, extends to edge */ @@ -24,6 +31,7 @@ iDeclareClass(GmDocument) iDeclareObjectConstruction(GmDocument) void setWidth_GmDocument (iGmDocument *, int width); +void setHost_GmDocument (iGmDocument *, const iString *host); /* local host name */ void setSource_GmDocument (iGmDocument *, const iString *source, int width); typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); @@ -38,4 +46,6 @@ const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos); const char * findLoc_GmDocument (const iGmDocument *, iInt2 pos); const iGmRun * findRunAtLoc_GmDocument (const iGmDocument *, const char *loc); const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); +int linkFlags_GmDocument (const iGmDocument *, iGmLinkId linkId); +enum iColorId linkColor_GmDocument (const iGmDocument *, iGmLinkId linkId); 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) { } static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) { + iUrl parts; + init_Url(&parts, d->url); + setHost_GmDocument(d->doc, collect_String(newRange_String(parts.host))); setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d)); d->foundMark = iNullRange; d->selectMark = iNullRange; @@ -676,34 +679,41 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */ initRange_String(&text, run->text); iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); + enum iColorId fg = run->color; if (run == d->widget->hoverLink) { - const char *desc = ""; - const iString *url = linkUrl_GmDocument(d->widget->doc, d->widget->hoverLink->linkId); - if (indexOfCStr_String(url, "://") == iInvalidPos) { - url = d->widget->url; - } + //const char *desc = ""; + const iGmDocument *doc = d->widget->doc; + const iGmLinkId linkId = d->widget->hoverLink->linkId; + const iString *url = linkUrl_GmDocument(doc, linkId); +// const int flags = linkFlags_GmDocument(doc, linkId); iUrl parts; init_Url(&parts, url); - desc = cstrFormat_String("\u2192 %s", cstr_String(collect_String(newRange_String(parts.protocol)))); - int descWidth = measure_Text(default_FontId, desc).x + gap_UI; - iRect linkRect = expanded_Rect(moved_Rect(run->bounds, origin), init_I2(gap_UI, 0)); - linkRect.size.x += descWidth; - fillRect_Paint(&d->paint, linkRect, teal_ColorId); - drawAlign_Text(default_FontId, - addX_I2(topRight_Rect(linkRect), -gap_UI), - cyan_ColorId, - right_Alignment, - "%s", - desc); - } - const iInt2 visPos = add_I2(run->bounds.pos, origin); +// desc = cstrFormat_String("\u2192 %s", cstr_String(collect_String(newRange_String(parts.protocol)))); + const iString *host = collect_String(newRange_String(parts.host)); + fg = linkColor_GmDocument(doc, linkId); + if (!isEmpty_String(host)) { +// int descWidth = measure_Text(default_FontId, cstr_String(host)).x + gap_UI; + iRect linkRect = moved_Rect(run->visBounds, origin); +// linkRect.size.x += descWidth; +// fillRect_Paint(&d->paint, linkRect, teal_ColorId); + drawAlign_Text(default_FontId, +// init_I2(right_Rect(d->bounds), top_Rect(linkRect)), + topRight_Rect(linkRect), + fg - 1, + left_Alignment, + " \u2014 %s", + cstr_String(host)); + } + } + const iInt2 visPos = add_I2(run->visBounds.pos, origin); /* Text markers. */ fillRange_DrawContext_(d, run, teal_ColorId, d->widget->foundMark, &d->inFoundMark); fillRange_DrawContext_(d, run, brown_ColorId, d->widget->selectMark, &d->inSelectMark); - drawString_Text(run->font, visPos, run->color, &text); + drawString_Text(run->font, visPos, fg, &text); deinit_String(&text); -// drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, red_ColorId); +// drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); +// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); } static void draw_DocumentWidget_(const iDocumentWidget *d) { -- cgit v1.2.3