summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-09-17 07:48:40 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-09-17 07:48:56 +0300
commita0f5bccc709866521fefeb34d6b97cafdbec6fad (patch)
treefefbc4c6cfec66bd84b85ede26a28eb1622a6940 /src
parent115602cf34dfb2f151846673468a41f16712eb49 (diff)
Recognize and visualize "mailto:" links
"mailto:" links now have their own icon and when clicked they open the URL in the default web browser. IssueID #5
Diffstat (limited to 'src')
-rw-r--r--src/app.c5
-rw-r--r--src/gmdocument.c32
-rw-r--r--src/gmdocument.h2
-rw-r--r--src/gmutil.c3
-rw-r--r--src/ui/documentwidget.c41
5 files changed, 49 insertions, 34 deletions
diff --git a/src/app.c b/src/app.c
index 42244c80..c6b9a548 100644
--- a/src/app.c
+++ b/src/app.c
@@ -787,8 +787,9 @@ iBool handleCommand_App(const char *cmd) {
787 const iString *url = collectNewCStr_String(suffixPtr_Command(cmd, "url")); 787 const iString *url = collectNewCStr_String(suffixPtr_Command(cmd, "url"));
788 iUrl parts; 788 iUrl parts;
789 init_Url(&parts, url); 789 init_Url(&parts, url);
790 if (isEmpty_String(&d->httpProxy) && 790 if (equalCase_Rangecc(parts.scheme, "mailto") ||
791 (equalCase_Rangecc(parts.scheme, "http") || equalCase_Rangecc(parts.scheme, "https"))) { 791 (isEmpty_String(&d->httpProxy) && (equalCase_Rangecc(parts.scheme, "http") ||
792 equalCase_Rangecc(parts.scheme, "https")))) {
792 openInDefaultBrowser_App(url); 793 openInDefaultBrowser_App(url);
793 return iTrue; 794 return iTrue;
794 } 795 }
diff --git a/src/gmdocument.c b/src/gmdocument.c
index b2561ee0..f8d5c7cf 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -229,6 +229,9 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li
229 else if (equalCase_Rangecc(parts.scheme, "about")) { 229 else if (equalCase_Rangecc(parts.scheme, "about")) {
230 link->flags |= about_GmLinkFlag; 230 link->flags |= about_GmLinkFlag;
231 } 231 }
232 else if (equalCase_Rangecc(parts.scheme, "mailto")) {
233 link->flags |= mailto_GmLinkFlag;
234 }
232 /* Check the file name extension, if present. */ 235 /* Check the file name extension, if present. */
233 if (!isEmpty_Range(&parts.path)) { 236 if (!isEmpty_Range(&parts.path)) {
234 iString *path = newRange_String(parts.path); 237 iString *path = newRange_String(parts.path);
@@ -258,7 +261,7 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li
258 trim_Rangecc(&desc); 261 trim_Rangecc(&desc);
259 if (!isEmpty_Range(&desc)) { 262 if (!isEmpty_Range(&desc)) {
260 line = desc; /* Just show the description. */ 263 line = desc; /* Just show the description. */
261 link->flags |= userFriendly_GmLinkFlag; 264 link->flags |= humanReadable_GmLinkFlag;
262 } 265 }
263 else { 266 else {
264 line = capturedRange_RegExpMatch(&m, 1); /* Show the URL. */ 267 line = capturedRange_RegExpMatch(&m, 1); /* Show the URL. */
@@ -321,10 +324,11 @@ static void doLayout_GmDocument_(iGmDocument *d) {
321 static const float bottomMargin[max_GmLineType] = { 324 static const float bottomMargin[max_GmLineType] = {
322 0.0f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 1.0f 325 0.0f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 1.0f
323 }; 326 };
324 static const char *arrow = "\u27a4"; // "\u2192"; 327 static const char *arrow = "\u27a4";
325 static const char *bullet = "\u2022"; 328 static const char *envelope = "\U0001f4e7";
326 static const char *folder = "\U0001f4c1"; 329 static const char *bullet = "\u2022";
327 static const char *globe = "\U0001f310"; 330 static const char *folder = "\U0001f4c1";
331 static const char *globe = "\U0001f310";
328 const float midRunSkip = 0; /*0.120f;*/ /* extra space between wrapped text/quote lines */ 332 const float midRunSkip = 0; /*0.120f;*/ /* extra space between wrapped text/quote lines */
329 clear_Array(&d->layout); 333 clear_Array(&d->layout);
330 clearLinks_GmDocument_(d); 334 clearLinks_GmDocument_(d);
@@ -356,6 +360,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
356 run.imageId = 0; 360 run.imageId = 0;
357 enum iGmLineType type; 361 enum iGmLineType type;
358 int indent = 0; 362 int indent = 0;
363 /* Detect the type of the line. */
359 if (!isPreformat) { 364 if (!isPreformat) {
360 type = lineType_GmDocument_(d, line); 365 type = lineType_GmDocument_(d, line);
361 if (contentLine.start == content.start) { 366 if (contentLine.start == content.start) {
@@ -479,7 +484,9 @@ static void doLayout_GmDocument_(iGmDocument *d) {
479 const iGmLink *link = constAt_PtrArray(&d->links, run.linkId - 1); 484 const iGmLink *link = constAt_PtrArray(&d->links, run.linkId - 1);
480 icon.text = range_CStr(link->flags & file_GmLinkFlag 485 icon.text = range_CStr(link->flags & file_GmLinkFlag
481 ? folder 486 ? folder
482 : link->flags & remote_GmLinkFlag ? globe : arrow); 487 : link->flags & mailto_GmLinkFlag
488 ? envelope
489 : link->flags & remote_GmLinkFlag ? globe : arrow);
483 if (link->flags & remote_GmLinkFlag) { 490 if (link->flags & remote_GmLinkFlag) {
484 icon.visBounds.pos.x -= gap_Text / 2; 491 icon.visBounds.pos.x -= gap_Text / 2;
485 } 492 }
@@ -1114,6 +1121,7 @@ uint16_t linkImage_GmDocument(const iGmDocument *d, iGmLinkId linkId) {
1114 1121
1115enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum iGmLinkPart part) { 1122enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum iGmLinkPart part) {
1116 const iGmLink *link = link_GmDocument_(d, linkId); 1123 const iGmLink *link = link_GmDocument_(d, linkId);
1124 const int www_GmLinkFlag = http_GmLinkFlag | mailto_GmLinkFlag;
1117 if (link) { 1125 if (link) {
1118 const iBool isBad = (link->flags & supportedProtocol_GmLinkFlag) == 0; 1126 const iBool isBad = (link->flags & supportedProtocol_GmLinkFlag) == 0;
1119 if (part == icon_GmLinkPart) { 1127 if (part == icon_GmLinkPart) {
@@ -1121,24 +1129,24 @@ enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum
1121 return tmBadLink_ColorId; 1129 return tmBadLink_ColorId;
1122 } 1130 }
1123 if (link->flags & visited_GmLinkFlag) { 1131 if (link->flags & visited_GmLinkFlag) {
1124 return link->flags & http_GmLinkFlag 1132 return link->flags & www_GmLinkFlag
1125 ? tmHypertextLinkIconVisited_ColorId 1133 ? tmHypertextLinkIconVisited_ColorId
1126 : link->flags & gopher_GmLinkFlag ? tmGopherLinkIconVisited_ColorId 1134 : link->flags & gopher_GmLinkFlag ? tmGopherLinkIconVisited_ColorId
1127 : tmLinkIconVisited_ColorId; 1135 : tmLinkIconVisited_ColorId;
1128 } 1136 }
1129 return link->flags & http_GmLinkFlag 1137 return link->flags & www_GmLinkFlag
1130 ? tmHypertextLinkIcon_ColorId 1138 ? tmHypertextLinkIcon_ColorId
1131 : link->flags & gopher_GmLinkFlag ? tmGopherLinkIcon_ColorId 1139 : link->flags & gopher_GmLinkFlag ? tmGopherLinkIcon_ColorId
1132 : tmLinkIcon_ColorId; 1140 : tmLinkIcon_ColorId;
1133 } 1141 }
1134 if (part == text_GmLinkPart) { 1142 if (part == text_GmLinkPart) {
1135 return link->flags & http_GmLinkFlag 1143 return link->flags & www_GmLinkFlag
1136 ? tmHypertextLinkText_ColorId 1144 ? tmHypertextLinkText_ColorId
1137 : link->flags & gopher_GmLinkFlag ? tmGopherLinkText_ColorId 1145 : link->flags & gopher_GmLinkFlag ? tmGopherLinkText_ColorId
1138 : tmLinkText_ColorId; 1146 : tmLinkText_ColorId;
1139 } 1147 }
1140 if (part == textHover_GmLinkPart) { 1148 if (part == textHover_GmLinkPart) {
1141 return link->flags & http_GmLinkFlag 1149 return link->flags & www_GmLinkFlag
1142 ? tmHypertextLinkTextHover_ColorId 1150 ? tmHypertextLinkTextHover_ColorId
1143 : link->flags & gopher_GmLinkFlag ? tmGopherLinkTextHover_ColorId 1151 : link->flags & gopher_GmLinkFlag ? tmGopherLinkTextHover_ColorId
1144 : tmLinkTextHover_ColorId; 1152 : tmLinkTextHover_ColorId;
@@ -1147,13 +1155,13 @@ enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum
1147 if (isBad) { 1155 if (isBad) {
1148 return tmBadLink_ColorId; 1156 return tmBadLink_ColorId;
1149 } 1157 }
1150 return link->flags & http_GmLinkFlag 1158 return link->flags & www_GmLinkFlag
1151 ? tmHypertextLinkDomain_ColorId 1159 ? tmHypertextLinkDomain_ColorId
1152 : link->flags & gopher_GmLinkFlag ? tmGopherLinkDomain_ColorId 1160 : link->flags & gopher_GmLinkFlag ? tmGopherLinkDomain_ColorId
1153 : tmLinkDomain_ColorId; 1161 : tmLinkDomain_ColorId;
1154 } 1162 }
1155 if (part == visited_GmLinkPart) { 1163 if (part == visited_GmLinkPart) {
1156 return link->flags & http_GmLinkFlag 1164 return link->flags & www_GmLinkFlag
1157 ? tmHypertextLinkLastVisitDate_ColorId 1165 ? tmHypertextLinkLastVisitDate_ColorId
1158 : link->flags & gopher_GmLinkFlag ? tmGopherLinkLastVisitDate_ColorId 1166 : link->flags & gopher_GmLinkFlag ? tmGopherLinkLastVisitDate_ColorId
1159 : tmLinkLastVisitDate_ColorId; 1167 : tmLinkLastVisitDate_ColorId;
diff --git a/src/gmdocument.h b/src/gmdocument.h
index 23ce5e8a..b453fba9 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -47,7 +47,7 @@ enum iGmLinkFlags {
47 mailto_GmLinkFlag = iBit(7), 47 mailto_GmLinkFlag = iBit(7),
48 supportedProtocol_GmLinkFlag = 0xff, 48 supportedProtocol_GmLinkFlag = 0xff,
49 remote_GmLinkFlag = iBit(9), 49 remote_GmLinkFlag = iBit(9),
50 userFriendly_GmLinkFlag = iBit(10), 50 humanReadable_GmLinkFlag = iBit(10), /* link has a human-readable description */
51 imageFileExtension_GmLinkFlag = iBit(11), 51 imageFileExtension_GmLinkFlag = iBit(11),
52 audioFileExtension_GmLinkFlag = iBit(12), 52 audioFileExtension_GmLinkFlag = iBit(12),
53 content_GmLinkFlag = iBit(13), /* content visible below */ 53 content_GmLinkFlag = iBit(13), /* content visible below */
diff --git a/src/gmutil.c b/src/gmutil.c
index 131734b2..b2c7e93f 100644
--- a/src/gmutil.c
+++ b/src/gmutil.c
@@ -132,7 +132,8 @@ const iString *absoluteUrl_String(const iString *d, const iString *urlMaybeRelat
132 iUrl rel; 132 iUrl rel;
133 init_Url(&orig, d); 133 init_Url(&orig, d);
134 init_Url(&rel, urlMaybeRelative); 134 init_Url(&rel, urlMaybeRelative);
135 if (equalCase_Rangecc(rel.scheme, "data") || equalCase_Rangecc(rel.scheme, "about")) { 135 if (equalCase_Rangecc(rel.scheme, "data") || equalCase_Rangecc(rel.scheme, "about") ||
136 equalCase_Rangecc(rel.scheme, "mailto")) {
136 /* Special case, the contents should be left unparsed. */ 137 /* Special case, the contents should be left unparsed. */
137 return urlMaybeRelative; 138 return urlMaybeRelative;
138 } 139 }
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 84ad1139..e632a65c 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1715,6 +1715,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1715 topRight_Rect(linkRect), 1715 topRight_Rect(linkRect),
1716 tmInlineContentMetadata_ColorId, 1716 tmInlineContentMetadata_ColorId,
1717 " \u2014 Fetching\u2026"); 1717 " \u2014 Fetching\u2026");
1718 /* TODO: Show amount downloaded so far. */
1718 } 1719 }
1719 } 1720 }
1720 else if (isHover) { 1721 else if (isHover) {
@@ -1723,31 +1724,35 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1723 const int flags = linkFlags_GmDocument(doc, linkId); 1724 const int flags = linkFlags_GmDocument(doc, linkId);
1724 iUrl parts; 1725 iUrl parts;
1725 init_Url(&parts, url); 1726 init_Url(&parts, url);
1726 fg = linkColor_GmDocument(doc, linkId, textHover_GmLinkPart); 1727 fg = linkColor_GmDocument(doc, linkId, textHover_GmLinkPart);
1727 const iBool showHost = (!isEmpty_Range(&parts.host) && flags & userFriendly_GmLinkFlag); 1728 const iBool showHost = (flags & humanReadable_GmLinkFlag &&
1729 (!isEmpty_Range(&parts.host) || flags & mailto_GmLinkFlag));
1728 const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0; 1730 const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0;
1729 const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0; 1731 const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0;
1730 iString str; 1732 iString str;
1731 init_String(&str); 1733 init_String(&str);
1734 /* Show scheme and host. */
1732 if (run->flags & endOfLine_GmRunFlag && 1735 if (run->flags & endOfLine_GmRunFlag &&
1733 (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || 1736 (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) ||
1734 showHost)) { 1737 showHost)) {
1735 format_String( 1738 format_String(&str,
1736 &str, 1739 " \u2014%s%s%s\r%c%s",
1737 " \u2014%s%s%s\r%c%s", 1740 showHost ? " " : "",
1738 showHost ? " " : "", 1741 showHost ? (flags & mailto_GmLinkFlag
1739 showHost ? (!equalCase_Rangecc(parts.scheme, "gemini") 1742 ? cstr_String(url)
1740 ? format_CStr("%s://%s", 1743 : ~flags & gemini_GmLinkFlag
1741 cstr_Rangecc(parts.scheme), 1744 ? format_CStr("%s://%s",
1742 cstr_Rangecc(parts.host)) 1745 cstr_Rangecc(parts.scheme),
1743 : cstr_Rangecc(parts.host)) 1746 cstr_Rangecc(parts.host))
1744 : "", 1747 : cstr_Rangecc(parts.host))
1745 showHost && (showImage || showAudio) ? " \u2014" : "", 1748 : "",
1746 showImage || showAudio 1749 showHost && (showImage || showAudio) ? " \u2014" : "",
1747 ? asciiBase_ColorEscape + fg 1750 showImage || showAudio
1748 : (asciiBase_ColorEscape + linkColor_GmDocument(doc, run->linkId, domain_GmLinkPart)), 1751 ? asciiBase_ColorEscape + fg
1749 showImage ? " View Image \U0001f5bc" 1752 : (asciiBase_ColorEscape +
1750 : showAudio ? " Play Audio \U0001f3b5" : ""); 1753 linkColor_GmDocument(doc, run->linkId, domain_GmLinkPart)),
1754 showImage ? " View Image \U0001f5bc"
1755 : showAudio ? " Play Audio \U0001f3b5" : "");
1751 } 1756 }
1752 if (run->flags & endOfLine_GmRunFlag && flags & visited_GmLinkFlag) { 1757 if (run->flags & endOfLine_GmRunFlag && flags & visited_GmLinkFlag) {
1753 iDate date; 1758 iDate date;