summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/color.c6
-rw-r--r--src/ui/color.h40
-rw-r--r--src/ui/documentwidget.c102
-rw-r--r--src/ui/inputwidget.c4
-rw-r--r--src/ui/inputwidget.h26
-rw-r--r--src/ui/labelwidget.c2
-rw-r--r--src/ui/text.c4
-rw-r--r--src/ui/widget.c1
8 files changed, 104 insertions, 81 deletions
diff --git a/src/ui/color.c b/src/ui/color.c
index a6ba18e4..6c51bc06 100644
--- a/src/ui/color.c
+++ b/src/ui/color.c
@@ -468,12 +468,12 @@ const char *escape_Color(int color) {
468 return esc[color]; 468 return esc[color];
469 } 469 }
470 /* TODO: Conflict with format strings! "%" (37) may be used as the color value. */ 470 /* TODO: Conflict with format strings! "%" (37) may be used as the color value. */
471 /* Double-\r is used for range extension. */ 471 /* Double-\v is used for range extension. */
472 if (color + asciiBase_ColorEscape > 127) { 472 if (color + asciiBase_ColorEscape > 127) {
473 iAssert(color - asciiExtended_ColorEscape + asciiBase_ColorEscape <= 127); 473 iAssert(color - asciiExtended_ColorEscape + asciiBase_ColorEscape <= 127);
474 return format_CStr("\r\r%c", color - asciiExtended_ColorEscape + asciiBase_ColorEscape); 474 return format_CStr("\v\v%c", color - asciiExtended_ColorEscape + asciiBase_ColorEscape);
475 } 475 }
476 return format_CStr("\r%c", color + asciiBase_ColorEscape); 476 return format_CStr("\v%c", color + asciiBase_ColorEscape);
477} 477}
478 478
479iHSLColor setSat_HSLColor(iHSLColor d, float sat) { 479iHSLColor setSat_HSLColor(iHSLColor d, float sat) {
diff --git a/src/ui/color.h b/src/ui/color.h
index d2fa3c00..aafc1794 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -187,26 +187,26 @@ iLocalDef iBool isRegularText_ColorId(enum iColorId d) {
187#define asciiBase_ColorEscape 33 187#define asciiBase_ColorEscape 33
188#define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape) 188#define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape)
189 189
190#define restore_ColorEscape "\r\x24" /* ASCII Cancel */ 190#define restore_ColorEscape "\v\x24" /* ASCII Cancel */
191#define black_ColorEscape "\r!" 191#define black_ColorEscape "\v!"
192#define gray25_ColorEscape "\r\"" 192#define gray25_ColorEscape "\v\""
193#define gray50_ColorEscape "\r#" 193#define gray50_ColorEscape "\v#"
194#define gray75_ColorEscape "\r$" 194#define gray75_ColorEscape "\v$"
195#define white_ColorEscape "\r%" 195#define white_ColorEscape "\v%"
196#define brown_ColorEscape "\r&" 196#define brown_ColorEscape "\v&"
197#define orange_ColorEscape "\r'" 197#define orange_ColorEscape "\v'"
198#define teal_ColorEscape "\r(" 198#define teal_ColorEscape "\v("
199#define cyan_ColorEscape "\r)" 199#define cyan_ColorEscape "\v)"
200#define yellow_ColorEscape "\r*" 200#define yellow_ColorEscape "\v*"
201#define red_ColorEscape "\r+" 201#define red_ColorEscape "\v+"
202#define magenta_ColorEscape "\r," 202#define magenta_ColorEscape "\v,"
203#define blue_ColorEscape "\r-" 203#define blue_ColorEscape "\v-"
204#define green_ColorEscape "\r." 204#define green_ColorEscape "\v."
205#define uiText_ColorEscape "\r4" 205#define uiText_ColorEscape "\v4"
206#define uiTextAction_ColorEscape "\r<" 206#define uiTextAction_ColorEscape "\v<"
207#define uiTextCaution_ColorEscape "\r=" 207#define uiTextCaution_ColorEscape "\v="
208#define uiTextStrong_ColorEscape "\r:" 208#define uiTextStrong_ColorEscape "\v:"
209#define uiHeading_ColorEscape "\rR" 209#define uiHeading_ColorEscape "\vR"
210 210
211iDeclareType(Color) 211iDeclareType(Color)
212iDeclareType(HSLColor) 212iDeclareType(HSLColor)
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 6184a75a..29e264e8 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1016,9 +1016,7 @@ static void showOrHidePinningIndicator_DocumentWidget_(iDocumentWidget *d) {
1016 isPinned_DocumentWidget_(d)); 1016 isPinned_DocumentWidget_(d));
1017} 1017}
1018 1018
1019void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { 1019static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) {
1020 setUrl_GmDocument(d->doc, d->mod.url);
1021 setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d));
1022 documentRunsInvalidated_DocumentWidget_(d); 1020 documentRunsInvalidated_DocumentWidget_(d);
1023 updateWindowTitle_DocumentWidget_(d); 1021 updateWindowTitle_DocumentWidget_(d);
1024 updateVisible_DocumentWidget_(d); 1022 updateVisible_DocumentWidget_(d);
@@ -1034,7 +1032,23 @@ void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
1034 d->flags |= otherRootByDefault_DocumentWidgetFlag; 1032 d->flags |= otherRootByDefault_DocumentWidgetFlag;
1035 } 1033 }
1036 } 1034 }
1037 showOrHidePinningIndicator_DocumentWidget_(d); 1035 showOrHidePinningIndicator_DocumentWidget_(d);
1036}
1037
1038void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
1039 setUrl_GmDocument(d->doc, d->mod.url);
1040 setSource_GmDocument(d->doc,
1041 source,
1042 documentWidth_DocumentWidget_(d),
1043 isFinished_GmRequest(d->request) ? final_GmDocumentUpdate
1044 : partial_GmDocumentUpdate);
1045 documentWasChanged_DocumentWidget_(d);
1046}
1047
1048static void replaceDocument_DocumentWidget_(iDocumentWidget *d, iGmDocument *newDoc) {
1049 iRelease(d->doc);
1050 d->doc = ref_Object(newDoc);
1051 documentWasChanged_DocumentWidget_(d);
1038} 1052}
1039 1053
1040static void updateTheme_DocumentWidget_(iDocumentWidget *d) { 1054static void updateTheme_DocumentWidget_(iDocumentWidget *d) {
@@ -1144,15 +1158,15 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
1144 2); 1158 2);
1145 } 1159 }
1146 setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); 1160 setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner);
1147 setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); 1161 setFormat_GmDocument(d->doc, gemini_SourceFormat);
1148 translate_Lang(src); 1162 translate_Lang(src);
1163 d->state = ready_RequestState;
1149 setSource_DocumentWidget(d, src); 1164 setSource_DocumentWidget(d, src);
1150 updateTheme_DocumentWidget_(d); 1165 updateTheme_DocumentWidget_(d);
1151 reset_SmoothScroll(&d->scrollY); 1166 reset_SmoothScroll(&d->scrollY);
1152 init_Anim(&d->sideOpacity, 0); 1167 init_Anim(&d->sideOpacity, 0);
1153 init_Anim(&d->altTextOpacity, 0); 1168 init_Anim(&d->altTextOpacity, 0);
1154 resetWideRuns_DocumentWidget_(d); 1169 resetWideRuns_DocumentWidget_(d);
1155 d->state = ready_RequestState;
1156} 1170}
1157 1171
1158static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { 1172static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) {
@@ -1264,9 +1278,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool
1264 2); 1278 2);
1265 } 1279 }
1266 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { 1280 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) {
1267 redoLayout_GmDocument(d->doc); 1281 redoLayout_GmDocument(d->doc);
1268 updateVisible_DocumentWidget_(d); 1282 updateVisible_DocumentWidget_(d);
1269 invalidate_DocumentWidget_(d); 1283 invalidate_DocumentWidget_(d);
1270 } 1284 }
1271 } 1285 }
1272 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1286 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {
@@ -1348,7 +1362,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool
1348 } 1362 }
1349} 1363}
1350 1364
1351static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response, 1365static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1366 const iGmResponse *response,
1367 iGmDocument *cachedDoc,
1352 const iBool isInitialUpdate) { 1368 const iBool isInitialUpdate) {
1353 if (d->state == ready_RequestState) { 1369 if (d->state == ready_RequestState) {
1354 return; 1370 return;
@@ -1371,7 +1387,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1371 if (isSuccess_GmStatusCode(statusCode)) { 1387 if (isSuccess_GmStatusCode(statusCode)) {
1372 /* Check the MIME type. */ 1388 /* Check the MIME type. */
1373 iRangecc charset = range_CStr("utf-8"); 1389 iRangecc charset = range_CStr("utf-8");
1374 enum iGmDocumentFormat docFormat = undefined_GmDocumentFormat; 1390 enum iSourceFormat docFormat = undefined_SourceFormat;
1375 const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */ 1391 const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */
1376 set_String(&d->sourceMime, mimeStr); 1392 set_String(&d->sourceMime, mimeStr);
1377 iRangecc mime = range_String(mimeStr); 1393 iRangecc mime = range_String(mimeStr);
@@ -1380,20 +1396,20 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1380 iRangecc param = seg; 1396 iRangecc param = seg;
1381 trim_Rangecc(&param); 1397 trim_Rangecc(&param);
1382 if (equal_Rangecc(param, "text/gemini")) { 1398 if (equal_Rangecc(param, "text/gemini")) {
1383 docFormat = gemini_GmDocumentFormat; 1399 docFormat = gemini_SourceFormat;
1384 setRange_String(&d->sourceMime, param); 1400 setRange_String(&d->sourceMime, param);
1385 } 1401 }
1386 else if (startsWith_Rangecc(param, "text/") || 1402 else if (startsWith_Rangecc(param, "text/") ||
1387 equal_Rangecc(param, "application/json") || 1403 equal_Rangecc(param, "application/json") ||
1388 equal_Rangecc(param, "application/x-pem-file") || 1404 equal_Rangecc(param, "application/x-pem-file") ||
1389 equal_Rangecc(param, "application/pem-certificate-chain")) { 1405 equal_Rangecc(param, "application/pem-certificate-chain")) {
1390 docFormat = plainText_GmDocumentFormat; 1406 docFormat = plainText_SourceFormat;
1391 setRange_String(&d->sourceMime, param); 1407 setRange_String(&d->sourceMime, param);
1392 } 1408 }
1393 else if (equal_Rangecc(param, "application/zip") || 1409 else if (equal_Rangecc(param, "application/zip") ||
1394 (startsWith_Rangecc(param, "application/") && 1410 (startsWith_Rangecc(param, "application/") &&
1395 endsWithCase_Rangecc(param, "+zip"))) { 1411 endsWithCase_Rangecc(param, "+zip"))) {
1396 docFormat = gemini_GmDocumentFormat; 1412 docFormat = gemini_SourceFormat;
1397 setRange_String(&d->sourceMime, param); 1413 setRange_String(&d->sourceMime, param);
1398 iString *key = collectNew_String(); 1414 iString *key = collectNew_String();
1399 toString_Sym(SDLK_s, KMOD_PRIMARY, key); 1415 toString_Sym(SDLK_s, KMOD_PRIMARY, key);
@@ -1420,9 +1436,10 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1420 startsWith_Rangecc(param, "audio/")) { 1436 startsWith_Rangecc(param, "audio/")) {
1421 const iBool isAudio = startsWith_Rangecc(param, "audio/"); 1437 const iBool isAudio = startsWith_Rangecc(param, "audio/");
1422 /* Make a simple document with an image or audio player. */ 1438 /* Make a simple document with an image or audio player. */
1423 docFormat = gemini_GmDocumentFormat; 1439 docFormat = gemini_SourceFormat;
1424 setRange_String(&d->sourceMime, param); 1440 setRange_String(&d->sourceMime, param);
1425 const iGmLinkId imgLinkId = 1; /* there's only the one link */ 1441 const iGmLinkId imgLinkId = 1; /* there's only the one link */
1442 /* TODO: Do the image loading in `postProcessRequestContent_DocumentWidget_()` */
1426 if ((isAudio && isInitialUpdate) || (!isAudio && isRequestFinished)) { 1443 if ((isAudio && isInitialUpdate) || (!isAudio && isRequestFinished)) {
1427 const char *linkTitle = 1444 const char *linkTitle =
1428 startsWith_String(mimeStr, "image/") ? "Image" : "Audio"; 1445 startsWith_String(mimeStr, "image/") ? "Image" : "Audio";
@@ -1464,7 +1481,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1464 } 1481 }
1465 } 1482 }
1466 } 1483 }
1467 if (docFormat == undefined_GmDocumentFormat) { 1484 if (docFormat == undefined_SourceFormat) {
1468 showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode, &response->meta); 1485 showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode, &response->meta);
1469 deinit_String(&str); 1486 deinit_String(&str);
1470 return; 1487 return;
@@ -1476,7 +1493,10 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1476 collect_String(decode_Block(&str.chars, cstr_Rangecc(charset)))); 1493 collect_String(decode_Block(&str.chars, cstr_Rangecc(charset))));
1477 } 1494 }
1478 } 1495 }
1479 if (setSource) { 1496 if (cachedDoc) {
1497 replaceDocument_DocumentWidget_(d, cachedDoc);
1498 }
1499 else if (setSource) {
1480 setSource_DocumentWidget(d, &str); 1500 setSource_DocumentWidget(d, &str);
1481 } 1501 }
1482 deinit_String(&str); 1502 deinit_String(&str);
@@ -1556,14 +1576,15 @@ static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) {
1556} 1576}
1557 1577
1558static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY, 1578static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY,
1559 const iGmResponse *resp) { 1579 const iGmResponse *resp, iGmDocument *cachedDoc) {
1560 setLinkNumberMode_DocumentWidget_(d, iFalse); 1580 setLinkNumberMode_DocumentWidget_(d, iFalse);
1561 clear_ObjectList(d->media); 1581 clear_ObjectList(d->media);
1562 delete_Gempub(d->sourceGempub); 1582 delete_Gempub(d->sourceGempub);
1563 d->sourceGempub = NULL; 1583 d->sourceGempub = NULL;
1564 reset_GmDocument(d->doc); 1584 iRelease(d->doc);
1565 destroy_Widget(d->footerButtons); 1585 destroy_Widget(d->footerButtons);
1566 d->footerButtons = NULL; 1586 d->footerButtons = NULL;
1587 d->doc = new_GmDocument();
1567 resetWideRuns_DocumentWidget_(d); 1588 resetWideRuns_DocumentWidget_(d);
1568 d->state = fetching_RequestState; 1589 d->state = fetching_RequestState;
1569 /* Do the fetch. */ { 1590 /* Do the fetch. */ {
@@ -1574,10 +1595,11 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1574 d->sourceStatus = success_GmStatusCode; 1595 d->sourceStatus = success_GmStatusCode;
1575 format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); 1596 format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached"));
1576 set_Block(&d->sourceContent, &resp->body); 1597 set_Block(&d->sourceContent, &resp->body);
1577 updateDocument_DocumentWidget_(d, resp, iTrue); 1598 updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue);
1578 postProcessRequestContent_DocumentWidget_(d, iTrue); 1599 setCachedDocument_History(d->mod.history, d->doc);
1579 } 1600 }
1580 d->state = ready_RequestState; 1601 d->state = ready_RequestState;
1602 postProcessRequestContent_DocumentWidget_(d, iTrue);
1581 init_Anim(&d->altTextOpacity, 0); 1603 init_Anim(&d->altTextOpacity, 0);
1582 reset_SmoothScroll(&d->scrollY); 1604 reset_SmoothScroll(&d->scrollY);
1583 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); 1605 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y);
@@ -1587,13 +1609,15 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1587 cacheDocumentGlyphs_DocumentWidget_(d); 1609 cacheDocumentGlyphs_DocumentWidget_(d);
1588 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; 1610 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag;
1589 d->flags &= ~urlChanged_DocumentWidgetFlag; 1611 d->flags &= ~urlChanged_DocumentWidgetFlag;
1590 postCommandf_Root(as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); 1612 postCommandf_Root(
1613 as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
1591} 1614}
1592 1615
1593static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { 1616static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1594 const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url)); 1617 const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url));
1595 if (recent && recent->cachedResponse) { 1618 if (recent && recent->cachedResponse) {
1596 updateFromCachedResponse_DocumentWidget_(d, recent->normScrollY, recent->cachedResponse); 1619 updateFromCachedResponse_DocumentWidget_(
1620 d, recent->normScrollY, recent->cachedResponse, recent->cachedDoc);
1597 return iTrue; 1621 return iTrue;
1598 } 1622 }
1599 else if (!isEmpty_String(d->mod.url)) { 1623 else if (!isEmpty_String(d->mod.url)) {
@@ -1843,13 +1867,14 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1843 /* Keep scroll position when reloading the same page. */ 1867 /* Keep scroll position when reloading the same page. */
1844 reset_SmoothScroll(&d->scrollY); 1868 reset_SmoothScroll(&d->scrollY);
1845 } 1869 }
1846 reset_GmDocument(d->doc); /* new content incoming */ 1870 iRelease(d->doc); /* new content incoming */
1871 d->doc = new_GmDocument();
1847 delete_Gempub(d->sourceGempub); 1872 delete_Gempub(d->sourceGempub);
1848 d->sourceGempub = NULL; 1873 d->sourceGempub = NULL;
1849 destroy_Widget(d->footerButtons); 1874 destroy_Widget(d->footerButtons);
1850 d->footerButtons = NULL; 1875 d->footerButtons = NULL;
1851 resetWideRuns_DocumentWidget_(d); 1876 resetWideRuns_DocumentWidget_(d);
1852 updateDocument_DocumentWidget_(d, resp, iTrue); 1877 updateDocument_DocumentWidget_(d, resp, NULL, iTrue);
1853 break; 1878 break;
1854 case categoryRedirect_GmStatusCode: 1879 case categoryRedirect_GmStatusCode:
1855 if (isEmpty_String(&resp->meta)) { 1880 if (isEmpty_String(&resp->meta)) {
@@ -1900,7 +1925,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1900 switch (category_GmStatusCode(statusCode)) { 1925 switch (category_GmStatusCode(statusCode)) {
1901 case categorySuccess_GmStatusCode: 1926 case categorySuccess_GmStatusCode:
1902 /* More content available. */ 1927 /* More content available. */
1903 updateDocument_DocumentWidget_(d, resp, iFalse); 1928 updateDocument_DocumentWidget_(d, resp, NULL, iFalse);
1904 break; 1929 break;
1905 default: 1930 default:
1906 break; 1931 break;
@@ -2085,16 +2110,16 @@ static const iString *saveToDownloads_(const iString *url, const iString *mime,
2085 exportDownloadedFile_iOS(savePath); 2110 exportDownloadedFile_iOS(savePath);
2086#else 2111#else
2087 if (showDialog) { 2112 if (showDialog) {
2088 const iMenuItem items[2] = { 2113 const iMenuItem items[2] = {
2089 { "${dlg.save.opendownload}", 0, 0, 2114 { "${dlg.save.opendownload}", 0, 0,
2090 format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) }, 2115 format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) },
2091 { "${dlg.message.ok}", 0, 0, "message.ok" }, 2116 { "${dlg.message.ok}", 0, 0, "message.ok" },
2092 }; 2117 };
2093 makeMessage_Widget(uiHeading_ColorEscape "${heading.save}", 2118 makeMessage_Widget(uiHeading_ColorEscape "${heading.save}",
2094 format_CStr("%s\n${dlg.save.size} %.3f %s", 2119 format_CStr("%s\n${dlg.save.size} %.3f %s",
2095 cstr_String(path_File(f)), 2120 cstr_String(path_File(f)),
2096 isMega ? size / 1.0e6f : (size / 1.0e3f), 2121 isMega ? size / 1.0e6f : (size / 1.0e3f),
2097 isMega ? "${mb}" : "${kb}"), 2122 isMega ? "${mb}" : "${kb}"),
2098 items, 2123 items,
2099 iElemCount(items)); 2124 iElemCount(items));
2100 } 2125 }
@@ -2520,6 +2545,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2520 (startsWithCase_String(meta_GmRequest(d->request), "text/") || 2545 (startsWithCase_String(meta_GmRequest(d->request), "text/") ||
2521 !cmp_String(&d->sourceMime, mimeType_Gempub))) { 2546 !cmp_String(&d->sourceMime, mimeType_Gempub))) {
2522 setCachedResponse_History(d->mod.history, lockResponse_GmRequest(d->request)); 2547 setCachedResponse_History(d->mod.history, lockResponse_GmRequest(d->request));
2548 setCachedDocument_History(d->mod.history, d->doc); /* keeps a ref */
2523 unlockResponse_GmRequest(d->request); 2549 unlockResponse_GmRequest(d->request);
2524 } 2550 }
2525 } 2551 }
@@ -3875,9 +3901,9 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
3875 int bg = tmBackgroundOpenLink_ColorId; 3901 int bg = tmBackgroundOpenLink_ColorId;
3876 const int frame = tmFrameOpenLink_ColorId; 3902 const int frame = tmFrameOpenLink_ColorId;
3877 iRect wideRect = { init_I2(left_Rect(d->widgetBounds), visPos.y), 3903 iRect wideRect = { init_I2(left_Rect(d->widgetBounds), visPos.y),
3878 init_I2(width_Rect(d->widgetBounds) + 3904 init_I2(width_Rect(d->widgetBounds) +
3879 width_Widget(d->widget->scroll), 3905 width_Widget(d->widget->scroll),
3880 height_Rect(run->visBounds)) }; 3906 height_Rect(run->visBounds)) };
3881 /* The first line is composed of two runs that may be drawn in either order, so 3907 /* The first line is composed of two runs that may be drawn in either order, so
3882 only draw half of the background. */ 3908 only draw half of the background. */
3883 if (run->flags & decoration_GmRunFlag) { 3909 if (run->flags & decoration_GmRunFlag) {
@@ -3940,7 +3966,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
3940 drawCentered_Text(defaultContentSmall_FontId, 3966 drawCentered_Text(defaultContentSmall_FontId,
3941 circleArea, 3967 circleArea,
3942 iTrue, 3968 iTrue,
3943 tmQuote_ColorId, 3969 tmQuote_ColorId,
3944 "%lc", 3970 "%lc",
3945 (int) ordChar); 3971 (int) ordChar);
3946 goto runDrawn; 3972 goto runDrawn;
@@ -4624,8 +4650,8 @@ static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) {
4624 if (!equal_String(d->mod.url, url)) { 4650 if (!equal_String(d->mod.url, url)) {
4625 d->flags |= urlChanged_DocumentWidgetFlag; 4651 d->flags |= urlChanged_DocumentWidgetFlag;
4626 set_String(d->mod.url, url); 4652 set_String(d->mod.url, url);
4627 }
4628} 4653}
4654}
4629 4655
4630void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) { 4656void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) {
4631 setLinkNumberMode_DocumentWidget_(d, iFalse); 4657 setLinkNumberMode_DocumentWidget_(d, iFalse);
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 32fb5ccb..cf128017 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -714,12 +714,12 @@ iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine
714} 714}
715 715
716static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { 716static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) {
717 size_t index = line->offset;
717 if (x <= 0) { 718 if (x <= 0) {
718 return line->offset; 719 return index;
719 } 720 }
720 const char *endPos; 721 const char *endPos;
721 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); 722 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos);
722 size_t index = line->offset;
723 if (endPos == constEnd_String(&line->text)) { 723 if (endPos == constEnd_String(&line->text)) {
724 index += line->len; 724 index += line->len;
725 } 725 }
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h
index 5c39aae0..f8c5bf1e 100644
--- a/src/ui/inputwidget.h
+++ b/src/ui/inputwidget.h
@@ -41,20 +41,20 @@ struct Impl_InputWidgetContentPadding {
41 41
42typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context); 42typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context);
43 43
44void setHint_InputWidget (iInputWidget *, const char *hintText); 44void setHint_InputWidget (iInputWidget *, const char *hintText);
45void setMode_InputWidget (iInputWidget *, enum iInputMode mode); 45void setMode_InputWidget (iInputWidget *, enum iInputMode mode);
46void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); 46void setMaxLen_InputWidget (iInputWidget *, size_t maxLen);
47void setText_InputWidget (iInputWidget *, const iString *text); 47void setText_InputWidget (iInputWidget *, const iString *text);
48void setTextCStr_InputWidget (iInputWidget *, const char *cstr); 48void setTextCStr_InputWidget (iInputWidget *, const char *cstr);
49void setFont_InputWidget (iInputWidget *, int fontId); 49void setFont_InputWidget (iInputWidget *, int fontId);
50void setCursor_InputWidget (iInputWidget *, size_t pos); 50void setCursor_InputWidget (iInputWidget *, size_t pos);
51void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ 51void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */
52void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines); 52void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines);
53void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); 53void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context);
54void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); 54void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled);
55void begin_InputWidget (iInputWidget *); 55void begin_InputWidget (iInputWidget *);
56void end_InputWidget (iInputWidget *, iBool accept); 56void end_InputWidget (iInputWidget *, iBool accept);
57void selectAll_InputWidget (iInputWidget *); 57void selectAll_InputWidget (iInputWidget *);
58 58
59void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); 59void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus);
60void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive); 60void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive);
@@ -62,15 +62,13 @@ void setUrlContent_InputWidget (iInputWidget *, iBool isUrl);
62void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits); 62void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits);
63void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape); 63void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape);
64 64
65const iString * text_InputWidget (const iInputWidget *); 65iInputWidgetContentPadding contentPadding_InputWidget (const iInputWidget *);
66const iString * text_InputWidget (const iInputWidget *);
66 67
67iLocalDef const char *cstrText_InputWidget(const iInputWidget *d) { 68iLocalDef const char *cstrText_InputWidget(const iInputWidget *d) {
68 return cstr_String(text_InputWidget(d)); 69 return cstr_String(text_InputWidget(d));
69} 70}
70 71
71iInputWidgetContentPadding
72 contentPadding_InputWidget (const iInputWidget *);
73
74iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) { 72iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) {
75 iInputWidget *d = new_InputWidget(maxLen); 73 iInputWidget *d = new_InputWidget(maxLen);
76 setHint_InputWidget(d, hint); 74 setHint_InputWidget(d, hint);
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 95f281be..ed023961 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -206,7 +206,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int
206 } 206 }
207 } 207 }
208 int colorEscape = none_ColorId; 208 int colorEscape = none_ColorId;
209 if (startsWith_String(&d->label, "\r")) { 209 if (startsWith_String(&d->label, "\v")) {
210 colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */ 210 colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */
211 } 211 }
212 if (isHover_LabelWidget_(d)) { 212 if (isHover_LabelWidget_(d)) {
diff --git a/src/ui/text.c b/src/ui/text.c
index 55fd4254..ffe08fca 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -1010,10 +1010,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1010 prevCh = 0; 1010 prevCh = 0;
1011 continue; 1011 continue;
1012 } 1012 }
1013 if (ch == '\r') { /* color change */ 1013 if (ch == '\v') { /* color change */
1014 iChar esc = nextChar_(&chPos, args->text.end); 1014 iChar esc = nextChar_(&chPos, args->text.end);
1015 int colorNum = args->color; 1015 int colorNum = args->color;
1016 if (esc == '\r') { /* Extended range. */ 1016 if (esc == '\v') { /* Extended range. */
1017 esc = nextChar_(&chPos, args->text.end) + asciiExtended_ColorEscape; 1017 esc = nextChar_(&chPos, args->text.end) + asciiExtended_ColorEscape;
1018 colorNum = esc - asciiBase_ColorEscape; 1018 colorNum = esc - asciiBase_ColorEscape;
1019 } 1019 }
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 543b8bc9..4eac7ecf 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -321,7 +321,6 @@ static iBool setWidth_Widget_(iWidget *d, int width) {
321 d->rect.size.x = width; 321 d->rect.size.x = width;
322 TRACE(d, "width has changed to %d", width); 322 TRACE(d, "width has changed to %d", width);
323 if (class_Widget(d)->sizeChanged) { 323 if (class_Widget(d)->sizeChanged) {
324 const int oldHeight = d->rect.size.y;
325 class_Widget(d)->sizeChanged(d); 324 class_Widget(d)->sizeChanged(d);
326 } 325 }
327 return iTrue; 326 return iTrue;