summaryrefslogtreecommitdiff
path: root/src/ui/documentwidget.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/documentwidget.c')
-rw-r--r--src/ui/documentwidget.c374
1 files changed, 316 insertions, 58 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 6184a75a..cb1fde28 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -130,7 +130,8 @@ void deinit_PersistentDocumentState(iPersistentDocumentState *d) {
130 130
131void serialize_PersistentDocumentState(const iPersistentDocumentState *d, iStream *outs) { 131void serialize_PersistentDocumentState(const iPersistentDocumentState *d, iStream *outs) {
132 serialize_String(d->url, outs); 132 serialize_String(d->url, outs);
133 writeU16_Stream(outs, d->reloadInterval & 7); 133 uint16_t params = d->reloadInterval & 7;
134 writeU16_Stream(outs, params);
134 serialize_History(d->history, outs); 135 serialize_History(d->history, outs);
135} 136}
136 137
@@ -223,6 +224,7 @@ enum iDocumentWidgetFlag {
223 movingSelectMarkEnd_DocumentWidgetFlag = iBit(11), 224 movingSelectMarkEnd_DocumentWidgetFlag = iBit(11),
224 otherRootByDefault_DocumentWidgetFlag = iBit(12), /* links open to other root by default */ 225 otherRootByDefault_DocumentWidgetFlag = iBit(12), /* links open to other root by default */
225 urlChanged_DocumentWidgetFlag = iBit(13), 226 urlChanged_DocumentWidgetFlag = iBit(13),
227 openedFromSidebar_DocumentWidgetFlag = iBit(14),
226}; 228};
227 229
228enum iDocumentLinkOrdinalMode { 230enum iDocumentLinkOrdinalMode {
@@ -315,6 +317,10 @@ void init_DocumentWidget(iDocumentWidget *d) {
315 init_Widget(w); 317 init_Widget(w);
316 setId_Widget(w, format_CStr("document%03d", ++docEnum_)); 318 setId_Widget(w, format_CStr("document%03d", ++docEnum_));
317 setFlags_Widget(w, hover_WidgetFlag | noBackground_WidgetFlag, iTrue); 319 setFlags_Widget(w, hover_WidgetFlag | noBackground_WidgetFlag, iTrue);
320 if (deviceType_App() != desktop_AppDeviceType) {
321 setFlags_Widget(w, leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag |
322 horizontalOffset_WidgetFlag, iTrue);
323 }
318 init_PersistentDocumentState(&d->mod); 324 init_PersistentDocumentState(&d->mod);
319 d->flags = 0; 325 d->flags = 0;
320 d->phoneToolbar = NULL; 326 d->phoneToolbar = NULL;
@@ -392,6 +398,7 @@ void init_DocumentWidget(iDocumentWidget *d) {
392} 398}
393 399
394void deinit_DocumentWidget(iDocumentWidget *d) { 400void deinit_DocumentWidget(iDocumentWidget *d) {
401 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue);
395 removeTicker_App(animate_DocumentWidget_, d); 402 removeTicker_App(animate_DocumentWidget_, d);
396 removeTicker_App(prerender_DocumentWidget_, d); 403 removeTicker_App(prerender_DocumentWidget_, d);
397 remove_Periodic(periodic_App(), d); 404 remove_Periodic(periodic_App(), d);
@@ -976,6 +983,9 @@ static void updateTimestampBuf_DocumentWidget_(const iDocumentWidget *d) {
976} 983}
977 984
978static void invalidate_DocumentWidget_(iDocumentWidget *d) { 985static void invalidate_DocumentWidget_(iDocumentWidget *d) {
986 if (flags_Widget(as_Widget(d)) & destroyPending_WidgetFlag) {
987 return;
988 }
979 invalidate_VisBuf(d->visBuf); 989 invalidate_VisBuf(d->visBuf);
980 clear_PtrSet(d->invalidRuns); 990 clear_PtrSet(d->invalidRuns);
981} 991}
@@ -1016,9 +1026,7 @@ static void showOrHidePinningIndicator_DocumentWidget_(iDocumentWidget *d) {
1016 isPinned_DocumentWidget_(d)); 1026 isPinned_DocumentWidget_(d));
1017} 1027}
1018 1028
1019void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { 1029static 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); 1030 documentRunsInvalidated_DocumentWidget_(d);
1023 updateWindowTitle_DocumentWidget_(d); 1031 updateWindowTitle_DocumentWidget_(d);
1024 updateVisible_DocumentWidget_(d); 1032 updateVisible_DocumentWidget_(d);
@@ -1035,6 +1043,26 @@ void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
1035 } 1043 }
1036 } 1044 }
1037 showOrHidePinningIndicator_DocumentWidget_(d); 1045 showOrHidePinningIndicator_DocumentWidget_(d);
1046 setCachedDocument_History(d->mod.history,
1047 d->doc, /* keeps a ref */
1048 (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0);
1049}
1050
1051void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
1052 setUrl_GmDocument(d->doc, d->mod.url);
1053 setSource_GmDocument(d->doc,
1054 source,
1055 documentWidth_DocumentWidget_(d),
1056 isFinished_GmRequest(d->request) ? final_GmDocumentUpdate
1057 : partial_GmDocumentUpdate);
1058 documentWasChanged_DocumentWidget_(d);
1059}
1060
1061static void replaceDocument_DocumentWidget_(iDocumentWidget *d, iGmDocument *newDoc) {
1062 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue);
1063 iRelease(d->doc);
1064 d->doc = ref_Object(newDoc);
1065 documentWasChanged_DocumentWidget_(d);
1038} 1066}
1039 1067
1040static void updateTheme_DocumentWidget_(iDocumentWidget *d) { 1068static void updateTheme_DocumentWidget_(iDocumentWidget *d) {
@@ -1143,16 +1171,22 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
1143 { person_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" } }, 1171 { person_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" } },
1144 2); 1172 2);
1145 } 1173 }
1146 setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); 1174 /* Make a new document for the error page.*/ {
1147 setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); 1175 iGmDocument *errorDoc = new_GmDocument();
1176 setUrl_GmDocument(errorDoc, d->mod.url);
1177 setBanner_GmDocument(errorDoc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner);
1178 setFormat_GmDocument(errorDoc, gemini_SourceFormat);
1179 replaceDocument_DocumentWidget_(d, errorDoc);
1180 iRelease(errorDoc);
1181 }
1148 translate_Lang(src); 1182 translate_Lang(src);
1183 d->state = ready_RequestState;
1149 setSource_DocumentWidget(d, src); 1184 setSource_DocumentWidget(d, src);
1150 updateTheme_DocumentWidget_(d); 1185 updateTheme_DocumentWidget_(d);
1151 reset_SmoothScroll(&d->scrollY); 1186 reset_SmoothScroll(&d->scrollY);
1152 init_Anim(&d->sideOpacity, 0); 1187 init_Anim(&d->sideOpacity, 0);
1153 init_Anim(&d->altTextOpacity, 0); 1188 init_Anim(&d->altTextOpacity, 0);
1154 resetWideRuns_DocumentWidget_(d); 1189 resetWideRuns_DocumentWidget_(d);
1155 d->state = ready_RequestState;
1156} 1190}
1157 1191
1158static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { 1192static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) {
@@ -1264,9 +1298,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool
1264 2); 1298 2);
1265 } 1299 }
1266 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { 1300 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) {
1267 redoLayout_GmDocument(d->doc); 1301 redoLayout_GmDocument(d->doc);
1268 updateVisible_DocumentWidget_(d); 1302 updateVisible_DocumentWidget_(d);
1269 invalidate_DocumentWidget_(d); 1303 invalidate_DocumentWidget_(d);
1270 } 1304 }
1271 } 1305 }
1272 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1306 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {
@@ -1348,7 +1382,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool
1348 } 1382 }
1349} 1383}
1350 1384
1351static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response, 1385static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1386 const iGmResponse *response,
1387 iGmDocument *cachedDoc,
1352 const iBool isInitialUpdate) { 1388 const iBool isInitialUpdate) {
1353 if (d->state == ready_RequestState) { 1389 if (d->state == ready_RequestState) {
1354 return; 1390 return;
@@ -1371,7 +1407,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1371 if (isSuccess_GmStatusCode(statusCode)) { 1407 if (isSuccess_GmStatusCode(statusCode)) {
1372 /* Check the MIME type. */ 1408 /* Check the MIME type. */
1373 iRangecc charset = range_CStr("utf-8"); 1409 iRangecc charset = range_CStr("utf-8");
1374 enum iGmDocumentFormat docFormat = undefined_GmDocumentFormat; 1410 enum iSourceFormat docFormat = undefined_SourceFormat;
1375 const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */ 1411 const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */
1376 set_String(&d->sourceMime, mimeStr); 1412 set_String(&d->sourceMime, mimeStr);
1377 iRangecc mime = range_String(mimeStr); 1413 iRangecc mime = range_String(mimeStr);
@@ -1380,20 +1416,20 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1380 iRangecc param = seg; 1416 iRangecc param = seg;
1381 trim_Rangecc(&param); 1417 trim_Rangecc(&param);
1382 if (equal_Rangecc(param, "text/gemini")) { 1418 if (equal_Rangecc(param, "text/gemini")) {
1383 docFormat = gemini_GmDocumentFormat; 1419 docFormat = gemini_SourceFormat;
1384 setRange_String(&d->sourceMime, param); 1420 setRange_String(&d->sourceMime, param);
1385 } 1421 }
1386 else if (startsWith_Rangecc(param, "text/") || 1422 else if (startsWith_Rangecc(param, "text/") ||
1387 equal_Rangecc(param, "application/json") || 1423 equal_Rangecc(param, "application/json") ||
1388 equal_Rangecc(param, "application/x-pem-file") || 1424 equal_Rangecc(param, "application/x-pem-file") ||
1389 equal_Rangecc(param, "application/pem-certificate-chain")) { 1425 equal_Rangecc(param, "application/pem-certificate-chain")) {
1390 docFormat = plainText_GmDocumentFormat; 1426 docFormat = plainText_SourceFormat;
1391 setRange_String(&d->sourceMime, param); 1427 setRange_String(&d->sourceMime, param);
1392 } 1428 }
1393 else if (equal_Rangecc(param, "application/zip") || 1429 else if (equal_Rangecc(param, "application/zip") ||
1394 (startsWith_Rangecc(param, "application/") && 1430 (startsWith_Rangecc(param, "application/") &&
1395 endsWithCase_Rangecc(param, "+zip"))) { 1431 endsWithCase_Rangecc(param, "+zip"))) {
1396 docFormat = gemini_GmDocumentFormat; 1432 docFormat = gemini_SourceFormat;
1397 setRange_String(&d->sourceMime, param); 1433 setRange_String(&d->sourceMime, param);
1398 iString *key = collectNew_String(); 1434 iString *key = collectNew_String();
1399 toString_Sym(SDLK_s, KMOD_PRIMARY, key); 1435 toString_Sym(SDLK_s, KMOD_PRIMARY, key);
@@ -1420,9 +1456,10 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1420 startsWith_Rangecc(param, "audio/")) { 1456 startsWith_Rangecc(param, "audio/")) {
1421 const iBool isAudio = startsWith_Rangecc(param, "audio/"); 1457 const iBool isAudio = startsWith_Rangecc(param, "audio/");
1422 /* Make a simple document with an image or audio player. */ 1458 /* Make a simple document with an image or audio player. */
1423 docFormat = gemini_GmDocumentFormat; 1459 docFormat = gemini_SourceFormat;
1424 setRange_String(&d->sourceMime, param); 1460 setRange_String(&d->sourceMime, param);
1425 const iGmLinkId imgLinkId = 1; /* there's only the one link */ 1461 const iGmLinkId imgLinkId = 1; /* there's only the one link */
1462 /* TODO: Do the image loading in `postProcessRequestContent_DocumentWidget_()` */
1426 if ((isAudio && isInitialUpdate) || (!isAudio && isRequestFinished)) { 1463 if ((isAudio && isInitialUpdate) || (!isAudio && isRequestFinished)) {
1427 const char *linkTitle = 1464 const char *linkTitle =
1428 startsWith_String(mimeStr, "image/") ? "Image" : "Audio"; 1465 startsWith_String(mimeStr, "image/") ? "Image" : "Audio";
@@ -1432,7 +1469,9 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1432 linkTitle = 1469 linkTitle =
1433 baseName_Path(collect_String(newRange_String(parts.path))).start; 1470 baseName_Path(collect_String(newRange_String(parts.path))).start;
1434 } 1471 }
1435 format_String(&str, "=> %s %s\n", cstr_String(d->mod.url), linkTitle); 1472 format_String(&str, "=> %s %s\n",
1473 cstr_String(withSpacesEncoded_String(d->mod.url)),
1474 linkTitle);
1436 setData_Media(media_GmDocument(d->doc), 1475 setData_Media(media_GmDocument(d->doc),
1437 imgLinkId, 1476 imgLinkId,
1438 mimeStr, 1477 mimeStr,
@@ -1464,7 +1503,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1464 } 1503 }
1465 } 1504 }
1466 } 1505 }
1467 if (docFormat == undefined_GmDocumentFormat) { 1506 if (docFormat == undefined_SourceFormat) {
1468 showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode, &response->meta); 1507 showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode, &response->meta);
1469 deinit_String(&str); 1508 deinit_String(&str);
1470 return; 1509 return;
@@ -1476,7 +1515,10 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1476 collect_String(decode_Block(&str.chars, cstr_Rangecc(charset)))); 1515 collect_String(decode_Block(&str.chars, cstr_Rangecc(charset))));
1477 } 1516 }
1478 } 1517 }
1479 if (setSource) { 1518 if (cachedDoc) {
1519 replaceDocument_DocumentWidget_(d, cachedDoc);
1520 }
1521 else if (setSource) {
1480 setSource_DocumentWidget(d, &str); 1522 setSource_DocumentWidget(d, &str);
1481 } 1523 }
1482 deinit_String(&str); 1524 deinit_String(&str);
@@ -1556,14 +1598,16 @@ static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) {
1556} 1598}
1557 1599
1558static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY, 1600static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY,
1559 const iGmResponse *resp) { 1601 const iGmResponse *resp, iGmDocument *cachedDoc) {
1560 setLinkNumberMode_DocumentWidget_(d, iFalse); 1602 setLinkNumberMode_DocumentWidget_(d, iFalse);
1561 clear_ObjectList(d->media); 1603 clear_ObjectList(d->media);
1562 delete_Gempub(d->sourceGempub); 1604 delete_Gempub(d->sourceGempub);
1563 d->sourceGempub = NULL; 1605 d->sourceGempub = NULL;
1564 reset_GmDocument(d->doc); 1606 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue);
1607 iRelease(d->doc);
1565 destroy_Widget(d->footerButtons); 1608 destroy_Widget(d->footerButtons);
1566 d->footerButtons = NULL; 1609 d->footerButtons = NULL;
1610 d->doc = new_GmDocument();
1567 resetWideRuns_DocumentWidget_(d); 1611 resetWideRuns_DocumentWidget_(d);
1568 d->state = fetching_RequestState; 1612 d->state = fetching_RequestState;
1569 /* Do the fetch. */ { 1613 /* Do the fetch. */ {
@@ -1574,10 +1618,12 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1574 d->sourceStatus = success_GmStatusCode; 1618 d->sourceStatus = success_GmStatusCode;
1575 format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); 1619 format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached"));
1576 set_Block(&d->sourceContent, &resp->body); 1620 set_Block(&d->sourceContent, &resp->body);
1577 updateDocument_DocumentWidget_(d, resp, iTrue); 1621 updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue);
1578 postProcessRequestContent_DocumentWidget_(d, iTrue); 1622// setCachedDocument_History(d->mod.history, d->doc,
1623// (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0);
1579 } 1624 }
1580 d->state = ready_RequestState; 1625 d->state = ready_RequestState;
1626 postProcessRequestContent_DocumentWidget_(d, iTrue);
1581 init_Anim(&d->altTextOpacity, 0); 1627 init_Anim(&d->altTextOpacity, 0);
1582 reset_SmoothScroll(&d->scrollY); 1628 reset_SmoothScroll(&d->scrollY);
1583 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); 1629 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y);
@@ -1587,13 +1633,18 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1587 cacheDocumentGlyphs_DocumentWidget_(d); 1633 cacheDocumentGlyphs_DocumentWidget_(d);
1588 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; 1634 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag;
1589 d->flags &= ~urlChanged_DocumentWidgetFlag; 1635 d->flags &= ~urlChanged_DocumentWidgetFlag;
1590 postCommandf_Root(as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); 1636 postCommandf_Root(
1637 as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
1591} 1638}
1592 1639
1593static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { 1640static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1594 const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url)); 1641 const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url));
1595 if (recent && recent->cachedResponse) { 1642 if (recent && recent->cachedResponse) {
1596 updateFromCachedResponse_DocumentWidget_(d, recent->normScrollY, recent->cachedResponse); 1643 iChangeFlags(d->flags,
1644 openedFromSidebar_DocumentWidgetFlag,
1645 recent->flags.openedFromSidebar);
1646 updateFromCachedResponse_DocumentWidget_(
1647 d, recent->normScrollY, recent->cachedResponse, recent->cachedDoc);
1597 return iTrue; 1648 return iTrue;
1598 } 1649 }
1599 else if (!isEmpty_String(d->mod.url)) { 1650 else if (!isEmpty_String(d->mod.url)) {
@@ -1634,7 +1685,7 @@ static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t du
1634 if (deviceType_App() == phone_AppDeviceType) { 1685 if (deviceType_App() == phone_AppDeviceType) {
1635 const float normPos = normScrollPos_DocumentWidget_(d); 1686 const float normPos = normScrollPos_DocumentWidget_(d);
1636 if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 && normPos >= 0) { 1687 if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 && normPos >= 0) {
1637 showToolbars_Root(as_Widget(d)->root, offset < 0); 1688 showToolbar_Root(as_Widget(d)->root, offset < 0);
1638 } 1689 }
1639 } 1690 }
1640 updateVisible_DocumentWidget_(d); 1691 updateVisible_DocumentWidget_(d);
@@ -1843,13 +1894,15 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1843 /* Keep scroll position when reloading the same page. */ 1894 /* Keep scroll position when reloading the same page. */
1844 reset_SmoothScroll(&d->scrollY); 1895 reset_SmoothScroll(&d->scrollY);
1845 } 1896 }
1846 reset_GmDocument(d->doc); /* new content incoming */ 1897 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue);
1898 iRelease(d->doc); /* new content incoming */
1899 d->doc = new_GmDocument();
1847 delete_Gempub(d->sourceGempub); 1900 delete_Gempub(d->sourceGempub);
1848 d->sourceGempub = NULL; 1901 d->sourceGempub = NULL;
1849 destroy_Widget(d->footerButtons); 1902 destroy_Widget(d->footerButtons);
1850 d->footerButtons = NULL; 1903 d->footerButtons = NULL;
1851 resetWideRuns_DocumentWidget_(d); 1904 resetWideRuns_DocumentWidget_(d);
1852 updateDocument_DocumentWidget_(d, resp, iTrue); 1905 updateDocument_DocumentWidget_(d, resp, NULL, iTrue);
1853 break; 1906 break;
1854 case categoryRedirect_GmStatusCode: 1907 case categoryRedirect_GmStatusCode:
1855 if (isEmpty_String(&resp->meta)) { 1908 if (isEmpty_String(&resp->meta)) {
@@ -1900,7 +1953,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1900 switch (category_GmStatusCode(statusCode)) { 1953 switch (category_GmStatusCode(statusCode)) {
1901 case categorySuccess_GmStatusCode: 1954 case categorySuccess_GmStatusCode:
1902 /* More content available. */ 1955 /* More content available. */
1903 updateDocument_DocumentWidget_(d, resp, iFalse); 1956 updateDocument_DocumentWidget_(d, resp, NULL, iFalse);
1904 break; 1957 break;
1905 default: 1958 default:
1906 break; 1959 break;
@@ -2085,16 +2138,16 @@ static const iString *saveToDownloads_(const iString *url, const iString *mime,
2085 exportDownloadedFile_iOS(savePath); 2138 exportDownloadedFile_iOS(savePath);
2086#else 2139#else
2087 if (showDialog) { 2140 if (showDialog) {
2088 const iMenuItem items[2] = { 2141 const iMenuItem items[2] = {
2089 { "${dlg.save.opendownload}", 0, 0, 2142 { "${dlg.save.opendownload}", 0, 0,
2090 format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) }, 2143 format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) },
2091 { "${dlg.message.ok}", 0, 0, "message.ok" }, 2144 { "${dlg.message.ok}", 0, 0, "message.ok" },
2092 }; 2145 };
2093 makeMessage_Widget(uiHeading_ColorEscape "${heading.save}", 2146 makeMessage_Widget(uiHeading_ColorEscape "${heading.save}",
2094 format_CStr("%s\n${dlg.save.size} %.3f %s", 2147 format_CStr("%s\n${dlg.save.size} %.3f %s",
2095 cstr_String(path_File(f)), 2148 cstr_String(path_File(f)),
2096 isMega ? size / 1.0e6f : (size / 1.0e3f), 2149 isMega ? size / 1.0e6f : (size / 1.0e3f),
2097 isMega ? "${mb}" : "${kb}"), 2150 isMega ? "${mb}" : "${kb}"),
2098 items, 2151 items,
2099 iElemCount(items)); 2152 iElemCount(items));
2100 } 2153 }
@@ -2208,6 +2261,147 @@ static iBool handlePinch_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
2208 return iTrue; 2261 return iTrue;
2209} 2262}
2210 2263
2264static void swap_DocumentWidget_(iDocumentWidget *d, iGmDocument *doc,
2265 iDocumentWidget *swapBuffersWith) {
2266 if (doc) {
2267 iAssert(isInstance_Object(doc, &Class_GmDocument));
2268 iGmDocument *copy = ref_Object(doc);
2269 iRelease(d->doc);
2270 d->doc = copy;
2271 d->scrollY = swapBuffersWith->scrollY;
2272 updateVisible_DocumentWidget_(d);
2273 iSwap(iVisBuf *, d->visBuf, swapBuffersWith->visBuf);
2274 iSwap(iVisBufMeta *, d->visBufMeta, swapBuffersWith->visBufMeta);
2275 iSwap(iDrawBufs *, d->drawBufs, swapBuffersWith->drawBufs);
2276 invalidate_DocumentWidget_(swapBuffersWith);
2277 }
2278}
2279
2280static iWidget *swipeParent_DocumentWidget_(iDocumentWidget *d) {
2281 return findChild_Widget(as_Widget(d)->root->widget, "doctabs");
2282}
2283
2284static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
2285 iWidget *w = as_Widget(d);
2286 /* Swipe animations are rather complex and utilize both cached GmDocument content
2287 and temporary DocumentWidgets. Depending on the swipe direction, this DocumentWidget
2288 may wait until the finger is released to actually perform the navigation action. */
2289 if (equal_Command(cmd, "edgeswipe.moved")) {
2290 //printf("[%p] responds to edgeswipe.moved\n", d);
2291 as_Widget(d)->offsetRef = NULL;
2292 const int side = argLabel_Command(cmd, "side");
2293 const int offset = arg_Command(cmd);
2294 if (side == 1) { /* left edge */
2295 if (atOldest_History(d->mod.history)) {
2296 return iTrue;
2297 }
2298 iWidget *swipeParent = swipeParent_DocumentWidget_(d);
2299 /* The temporary "swipeIn" will display the previous page until the finger is lifted. */
2300 iDocumentWidget *swipeIn = findChild_Widget(swipeParent, "swipein");
2301 if (!swipeIn) {
2302 const iBool sidebarSwipe = (isPortraitPhone_App() &&
2303 d->flags & openedFromSidebar_DocumentWidgetFlag &&
2304 !isVisible_Widget(findWidget_App("sidebar")));
2305 swipeIn = new_DocumentWidget();
2306 setId_Widget(as_Widget(swipeIn), "swipein");
2307 setFlags_Widget(as_Widget(swipeIn),
2308 disabled_WidgetFlag | refChildrenOffset_WidgetFlag |
2309 fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue);
2310 swipeIn->widget.rect.pos = windowToInner_Widget(swipeParent, localToWindow_Widget(w, w->rect.pos));
2311 swipeIn->widget.rect.size = d->widget.rect.size;
2312 swipeIn->widget.offsetRef = parent_Widget(w);
2313 if (!sidebarSwipe) {
2314 iRecentUrl *recent = new_RecentUrl();
2315 preceding_History(d->mod.history, recent);
2316 if (recent->cachedDoc) {
2317 iChangeRef(swipeIn->doc, recent->cachedDoc);
2318 updateScrollMax_DocumentWidget_(d);
2319 setValue_Anim(&swipeIn->scrollY.pos, size_GmDocument(swipeIn->doc).y * recent->normScrollY, 0);
2320 updateVisible_DocumentWidget_(swipeIn);
2321 swipeIn->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag;
2322 }
2323 delete_RecentUrl(recent);
2324 }
2325 addChildPos_Widget(swipeParent, iClob(swipeIn), front_WidgetAddPos);
2326 }
2327 }
2328 if (side == 2) { /* right edge */
2329 if (offset < -get_Window()->pixelRatio * 10) {
2330 int animSpan = 10;
2331 if (!atLatest_History(d->mod.history) &&
2332 ~flags_Widget(w) & dragged_WidgetFlag) {
2333 animSpan = 0;
2334 postCommand_Widget(d, "navigate.forward");
2335 setFlags_Widget(w, dragged_WidgetFlag, iTrue);
2336 /* Set up the swipe dummy. */
2337 iWidget *swipeParent = swipeParent_DocumentWidget_(d);
2338 iDocumentWidget *target = new_DocumentWidget();
2339 setId_Widget(as_Widget(target), "swipeout");
2340 /* The target takes the old document and jumps on top. */
2341 target->widget.rect.pos = windowToInner_Widget(swipeParent, localToWindow_Widget(w, w->rect.pos));
2342 target->widget.rect.size = d->widget.rect.size;
2343 setFlags_Widget(as_Widget(target), fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue);
2344 swap_DocumentWidget_(target, d->doc, d);
2345 addChildPos_Widget(swipeParent, iClob(target), front_WidgetAddPos);
2346 setFlags_Widget(as_Widget(target), refChildrenOffset_WidgetFlag, iTrue);
2347 as_Widget(target)->offsetRef = parent_Widget(w);
2348 destroy_Widget(as_Widget(target)); /* will be actually deleted after animation finishes */
2349 }
2350 if (flags_Widget(w) & dragged_WidgetFlag) {
2351 setVisualOffset_Widget(w, width_Widget(w) +
2352 width_Widget(d) * offset / size_Root(w->root).x,
2353 animSpan, 0);
2354 }
2355 else {
2356 setVisualOffset_Widget(w, offset / 4, animSpan, 0);
2357 }
2358 }
2359 return iTrue;
2360 }
2361 }
2362 if (equal_Command(cmd, "edgeswipe.ended") && argLabel_Command(cmd, "side") == 2) {
2363 if (argLabel_Command(cmd, "abort") && flags_Widget(w) & dragged_WidgetFlag) {
2364 postCommand_Widget(d, "navigate.back");
2365 }
2366 setFlags_Widget(w, dragged_WidgetFlag, iFalse);
2367 setVisualOffset_Widget(w, 0, 100, 0);
2368 return iTrue;
2369 }
2370 if (equal_Command(cmd, "edgeswipe.ended") && argLabel_Command(cmd, "side") == 1) {
2371 iWidget *swipeParent = swipeParent_DocumentWidget_(d);
2372 iWidget *swipeIn = findChild_Widget(swipeParent, "swipein");
2373 if (swipeIn) {
2374 swipeIn->offsetRef = NULL;
2375 destroy_Widget(swipeIn);
2376 }
2377 }
2378 if (equal_Command(cmd, "swipe.back")) {
2379 if (atOldest_History(d->mod.history)) {
2380 setVisualOffset_Widget(w, 0, 100, 0);
2381 return iTrue;
2382 }
2383 iWidget *swipeParent = swipeParent_DocumentWidget_(d);
2384 iDocumentWidget *target = new_DocumentWidget();
2385 setId_Widget(as_Widget(target), "swipeout");
2386 /* The target takes the old document and jumps on top. */
2387 target->widget.rect.pos = windowToInner_Widget(swipeParent, innerToWindow_Widget(w, zero_I2()));
2388 /* Note: `innerToWindow_Widget` does not apply visual offset. */
2389 target->widget.rect.size = w->rect.size;
2390 setFlags_Widget(as_Widget(target), fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue);
2391 swap_DocumentWidget_(target, d->doc, d);
2392 addChildPos_Widget(swipeParent, iClob(target), back_WidgetAddPos);
2393 setFlags_Widget(as_Widget(d), refChildrenOffset_WidgetFlag, iTrue);
2394 as_Widget(d)->offsetRef = swipeParent;
2395 setVisualOffset_Widget(as_Widget(target), value_Anim(&w->visualOffset), 0, 0);
2396 setVisualOffset_Widget(as_Widget(target), width_Widget(target), 150, 0);
2397 destroy_Widget(as_Widget(target)); /* will be actually deleted after animation finishes */
2398 setVisualOffset_Widget(w, 0, 0, 0);
2399 postCommand_Widget(d, "navigate.back");
2400 return iTrue;
2401 }
2402 return iFalse;
2403}
2404
2211static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { 2405static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
2212 iWidget *w = as_Widget(d); 2406 iWidget *w = as_Widget(d);
2213 if (equal_Command(cmd, "document.openurls.changed")) { 2407 if (equal_Command(cmd, "document.openurls.changed")) {
@@ -2256,6 +2450,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2256 return iFalse; 2450 return iFalse;
2257 } 2451 }
2258 else if (equal_Command(cmd, "theme.changed") && document_App() == d) { 2452 else if (equal_Command(cmd, "theme.changed") && document_App() == d) {
2453// invalidateTheme_History(d->mod.history); /* cached colors */
2259 updateTheme_DocumentWidget_(d); 2454 updateTheme_DocumentWidget_(d);
2260 updateVisible_DocumentWidget_(d); 2455 updateVisible_DocumentWidget_(d);
2261 updateTrust_DocumentWidget_(d, NULL); 2456 updateTrust_DocumentWidget_(d, NULL);
@@ -2345,6 +2540,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2345 msg, "%s\n", formatCStrs_Lang("num.bytes.n", size_Block(&d->sourceContent))); 2540 msg, "%s\n", formatCStrs_Lang("num.bytes.n", size_Block(&d->sourceContent)));
2346 } 2541 }
2347 } 2542 }
2543 /* TODO: On mobile, omit the CA status. */
2348 appendFormat_String( 2544 appendFormat_String(
2349 msg, 2545 msg,
2350 "\n%s${pageinfo.cert.status}\n" 2546 "\n%s${pageinfo.cert.status}\n"
@@ -2649,6 +2845,18 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2649 return iTrue; 2845 return iTrue;
2650 } 2846 }
2651 else if (equal_Command(cmd, "navigate.back") && document_App() == d) { 2847 else if (equal_Command(cmd, "navigate.back") && document_App() == d) {
2848 if (isPortraitPhone_App()) {
2849 if (d->flags & openedFromSidebar_DocumentWidgetFlag &&
2850 !isVisible_Widget(findWidget_App("sidebar"))) {
2851 postCommand_App("sidebar.toggle");
2852 showToolbar_Root(get_Root(), iTrue);
2853#if defined (iPlatformAppleMobile)
2854 playHapticEffect_iOS(gentleTap_HapticEffect);
2855#endif
2856 return iTrue;
2857 }
2858 d->flags &= ~openedFromSidebar_DocumentWidgetFlag;
2859 }
2652 if (d->request) { 2860 if (d->request) {
2653 postCommandf_Root(w->root, 2861 postCommandf_Root(w->root,
2654 "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url)); 2862 "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url));
@@ -2811,7 +3019,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2811 uiHeading_ColorEscape "${heading.import.bookmarks}", 3019 uiHeading_ColorEscape "${heading.import.bookmarks}",
2812 formatCStrs_Lang("dlg.import.found.n", count), 3020 formatCStrs_Lang("dlg.import.found.n", count),
2813 (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, 3021 (iMenuItem[]){ { "${cancel}", 0, 0, NULL },
2814 { format_CStr(cstrCount_Lang("dlg.import.add.n", count), 3022 { format_CStr(cstrCount_Lang("dlg.import.add.n", (int) count),
2815 uiTextAction_ColorEscape, 3023 uiTextAction_ColorEscape,
2816 count), 3024 count),
2817 0, 3025 0,
@@ -2870,6 +3078,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2870 else if (startsWith_CStr(cmd, "pinch.") && document_Command(cmd) == d) { 3078 else if (startsWith_CStr(cmd, "pinch.") && document_Command(cmd) == d) {
2871 return handlePinch_DocumentWidget_(d, cmd); 3079 return handlePinch_DocumentWidget_(d, cmd);
2872 } 3080 }
3081 else if ((startsWith_CStr(cmd, "edgeswipe.") || startsWith_CStr(cmd, "swipe.")) &&
3082 document_App() == d) {
3083 return handleSwipe_DocumentWidget_(d, cmd);
3084 }
2873 return iFalse; 3085 return iFalse;
2874} 3086}
2875 3087
@@ -3318,6 +3530,18 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3318 d->contextLink->linkId) }); 3530 d->contextLink->linkId) });
3319 } 3531 }
3320 } 3532 }
3533 if (equalCase_Rangecc(scheme, "file")) {
3534 /* Local files may be deleted. */
3535 pushBack_Array(
3536 &items,
3537 &(iMenuItem){ delete_Icon " " uiTextCaution_ColorEscape
3538 "${link.file.delete}",
3539 0,
3540 0,
3541 format_CStr("!file.delete confirm:1 path:%s",
3542 cstrCollect_String(
3543 localFilePathFromUrl_String(linkUrl))) });
3544 }
3321 } 3545 }
3322 else if (deviceType_App() == desktop_AppDeviceType) { 3546 else if (deviceType_App() == desktop_AppDeviceType) {
3323 if (!isEmpty_Range(&d->selectMark)) { 3547 if (!isEmpty_Range(&d->selectMark)) {
@@ -3870,14 +4094,14 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
3870 } 4094 }
3871#endif 4095#endif
3872 /* Fill the background. */ { 4096 /* Fill the background. */ {
3873 if (run->linkId && linkFlags & isOpen_GmLinkFlag) { 4097 if (run->linkId && linkFlags & isOpen_GmLinkFlag && ~linkFlags & content_GmLinkFlag) {
3874 /* Open links get a highlighted background. */ 4098 /* Open links get a highlighted background. */
3875 int bg = tmBackgroundOpenLink_ColorId; 4099 int bg = tmBackgroundOpenLink_ColorId;
3876 const int frame = tmFrameOpenLink_ColorId; 4100 const int frame = tmFrameOpenLink_ColorId;
3877 iRect wideRect = { init_I2(left_Rect(d->widgetBounds), visPos.y), 4101 iRect wideRect = { init_I2(left_Rect(d->widgetBounds), visPos.y),
3878 init_I2(width_Rect(d->widgetBounds) + 4102 init_I2(width_Rect(d->widgetBounds) +
3879 width_Widget(d->widget->scroll), 4103 width_Widget(d->widget->scroll),
3880 height_Rect(run->visBounds)) }; 4104 height_Rect(run->visBounds)) };
3881 /* The first line is composed of two runs that may be drawn in either order, so 4105 /* The first line is composed of two runs that may be drawn in either order, so
3882 only draw half of the background. */ 4106 only draw half of the background. */
3883 if (run->flags & decoration_GmRunFlag) { 4107 if (run->flags & decoration_GmRunFlag) {
@@ -3931,16 +4155,17 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
3931 linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase); 4155 linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase);
3932 if (ordChar) { 4156 if (ordChar) {
3933 const char *circle = "\u25ef"; /* Large Circle */ 4157 const char *circle = "\u25ef"; /* Large Circle */
4158 const int circleFont = defaultContentRegular_FontId;
3934 iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y), 4159 iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y),
3935 init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(run->font)) }; 4160 init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(circleFont)) };
3936 drawRange_Text( 4161 drawRange_Text(
3937 run->font, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle)); 4162 circleFont, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle));
3938 iRect circleArea = visualBounds_Text(run->font, range_CStr(circle)); 4163 iRect circleArea = visualBounds_Text(circleFont, range_CStr(circle));
3939 addv_I2(&circleArea.pos, topLeft_Rect(nbArea)); 4164 addv_I2(&circleArea.pos, topLeft_Rect(nbArea));
3940 drawCentered_Text(defaultContentSmall_FontId, 4165 drawCentered_Text(defaultContentSmall_FontId,
3941 circleArea, 4166 circleArea,
3942 iTrue, 4167 iTrue,
3943 tmQuote_ColorId, 4168 tmQuote_ColorId,
3944 "%lc", 4169 "%lc",
3945 (int) ordChar); 4170 (int) ordChar);
3946 goto runDrawn; 4171 goto runDrawn;
@@ -4178,7 +4403,7 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
4178 iDrawBufs * dbuf = d->drawBufs; 4403 iDrawBufs * dbuf = d->drawBufs;
4179 iPaint p; 4404 iPaint p;
4180 init_Paint(&p); 4405 init_Paint(&p);
4181 setClip_Paint(&p, bounds); 4406 setClip_Paint(&p, boundsWithoutVisualOffset_Widget(w));
4182 /* Side icon and current heading. */ 4407 /* Side icon and current heading. */
4183 if (prefs_App()->sideIcon && opacity > 0 && dbuf->sideIconBuf) { 4408 if (prefs_App()->sideIcon && opacity > 0 && dbuf->sideIconBuf) {
4184 const iInt2 texSize = size_SDLTexture(dbuf->sideIconBuf); 4409 const iInt2 texSize = size_SDLTexture(dbuf->sideIconBuf);
@@ -4263,7 +4488,8 @@ static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx,
4263 /* Swap buffers around to have room available both before and after the visible region. */ 4488 /* Swap buffers around to have room available both before and after the visible region. */
4264 allocVisBuffer_DocumentWidget_(d); 4489 allocVisBuffer_DocumentWidget_(d);
4265 reposition_VisBuf(visBuf, vis); 4490 reposition_VisBuf(visBuf, vis);
4266 /* Redraw the invalid ranges. */ { 4491 /* Redraw the invalid ranges. */
4492 if (~flags_Widget(constAs_Widget(d)) & destroyPending_WidgetFlag) {
4267 iPaint *p = &ctx->paint; 4493 iPaint *p = &ctx->paint;
4268 init_Paint(p); 4494 init_Paint(p);
4269 iForIndices(i, visBuf->buffers) { 4495 iForIndices(i, visBuf->buffers) {
@@ -4427,12 +4653,17 @@ static void prerender_DocumentWidget_(iAny *context) {
4427} 4653}
4428 4654
4429static void draw_DocumentWidget_(const iDocumentWidget *d) { 4655static void draw_DocumentWidget_(const iDocumentWidget *d) {
4430 const iWidget *w = constAs_Widget(d); 4656 const iWidget *w = constAs_Widget(d);
4431 const iRect bounds = bounds_Widget(w); 4657 const iRect bounds = bounds_Widget(w);
4658 const iRect boundsWithoutVisOff = boundsWithoutVisualOffset_Widget(w);
4659 const iRect clipBounds = intersect_Rect(bounds, boundsWithoutVisOff);
4432 if (width_Rect(bounds) <= 0) { 4660 if (width_Rect(bounds) <= 0) {
4433 return; 4661 return;
4434 } 4662 }
4435// draw_Widget(w); 4663 /* TODO: Come up with a better palette caching system.
4664 It should be able to recompute cached colors in `History` when the theme has changed.
4665 Cache the theme seed in `GmDocument`? */
4666// makePaletteGlobal_GmDocument(d->doc);
4436 if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { 4667 if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) {
4437 updateTimestampBuf_DocumentWidget_(d); 4668 updateTimestampBuf_DocumentWidget_(d);
4438 } 4669 }
@@ -4447,14 +4678,15 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4447 .vis = vis, 4678 .vis = vis,
4448 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0, 4679 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0,
4449 }; 4680 };
4681 init_Paint(&ctx.paint);
4450 render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); 4682 render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */);
4451 setClip_Paint(&ctx.paint, bounds); 4683 setClip_Paint(&ctx.paint, clipBounds);
4452 int yTop = docBounds.pos.y - pos_SmoothScroll(&d->scrollY); 4684 int yTop = docBounds.pos.y - pos_SmoothScroll(&d->scrollY);
4453 draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds)); 4685 draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds));
4454 /* Text markers. */ 4686 /* Text markers. */
4455 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; 4687 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0;
4456 if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { 4688 if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) {
4457 SDL_Renderer *render = renderer_Window(get_Window()); 4689 SDL_Renderer *render = renderer_Window(get_Window());
4458 ctx.firstMarkRect = zero_Rect(); 4690 ctx.firstMarkRect = zero_Rect();
4459 ctx.lastMarkRect = zero_Rect(); 4691 ctx.lastMarkRect = zero_Rect();
4460 SDL_SetRenderDrawBlendMode(render, 4692 SDL_SetRenderDrawBlendMode(render,
@@ -4483,7 +4715,6 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4483 } 4715 }
4484 } 4716 }
4485 drawMedia_DocumentWidget_(d, &ctx.paint); 4717 drawMedia_DocumentWidget_(d, &ctx.paint);
4486 unsetClip_Paint(&ctx.paint);
4487 /* Fill the top and bottom, in case the document is short. */ 4718 /* Fill the top and bottom, in case the document is short. */
4488 if (yTop > top_Rect(bounds)) { 4719 if (yTop > top_Rect(bounds)) {
4489 fillRect_Paint(&ctx.paint, 4720 fillRect_Paint(&ctx.paint,
@@ -4497,6 +4728,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4497 init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom), 4728 init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom),
4498 tmBackground_ColorId); 4729 tmBackground_ColorId);
4499 } 4730 }
4731 unsetClip_Paint(&ctx.paint);
4500 drawSideElements_DocumentWidget_(d); 4732 drawSideElements_DocumentWidget_(d);
4501 if (prefs_App()->hoverLink && d->hoverLink) { 4733 if (prefs_App()->hoverLink && d->hoverLink) {
4502 const int font = uiLabel_FontId; 4734 const int font = uiLabel_FontId;
@@ -4558,6 +4790,23 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4558 drawCentered_Text(uiLabelBold_FontId, rect, iFalse, uiBackground_ColorId, "%zu bytes selected", 4790 drawCentered_Text(uiLabelBold_FontId, rect, iFalse, uiBackground_ColorId, "%zu bytes selected",
4559 size_Range(&mark)); 4791 size_Range(&mark));
4560 } 4792 }
4793 if (w->offsetRef) {
4794 const int offX = visualOffsetByReference_Widget(w);
4795 if (offX) {
4796 setClip_Paint(&ctx.paint, clipBounds);
4797 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
4798 ctx.paint.alpha = iAbs(offX) / (float) get_Window()->size.x * 300;
4799 fillRect_Paint(&ctx.paint, bounds, backgroundFadeColor_Widget());
4800 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
4801 unsetClip_Paint(&ctx.paint);
4802 }
4803 else {
4804 /* TODO: Should have a better place to do this; drawing is supposed to be immutable. */
4805 iWidget *mut = iConstCast(iWidget *, w);
4806 mut->offsetRef = NULL;
4807 mut->flags &= ~refChildrenOffset_WidgetFlag;
4808 }
4809 }
4561} 4810}
4562 4811
4563/*----------------------------------------------------------------------------------------------*/ 4812/*----------------------------------------------------------------------------------------------*/
@@ -4625,9 +4874,12 @@ static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) {
4625 d->flags |= urlChanged_DocumentWidgetFlag; 4874 d->flags |= urlChanged_DocumentWidgetFlag;
4626 set_String(d->mod.url, url); 4875 set_String(d->mod.url, url);
4627 } 4876 }
4628} 4877}
4629 4878
4630void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) { 4879void setUrlFlags_DocumentWidget(iDocumentWidget *d, const iString *url, int setUrlFlags) {
4880 iChangeFlags(d->flags, openedFromSidebar_DocumentWidgetFlag,
4881 (setUrlFlags & openedFromSidebar_DocumentWidgetSetUrlFlag) != 0);
4882 const iBool isFromCache = (setUrlFlags & useCachedContentIfAvailable_DocumentWidgetSetUrlFlag) != 0;
4631 setLinkNumberMode_DocumentWidget_(d, iFalse); 4883 setLinkNumberMode_DocumentWidget_(d, iFalse);
4632 setUrl_DocumentWidget_(d, urlFragmentStripped_String(url)); 4884 setUrl_DocumentWidget_(d, urlFragmentStripped_String(url));
4633 /* See if there a username in the URL. */ 4885 /* See if there a username in the URL. */
@@ -4639,6 +4891,7 @@ void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBoo
4639 4891
4640void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, const iString *mime, 4892void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, const iString *mime,
4641 const iBlock *source) { 4893 const iBlock *source) {
4894 d->flags &= ~openedFromSidebar_DocumentWidgetFlag;
4642 setLinkNumberMode_DocumentWidget_(d, iFalse); 4895 setLinkNumberMode_DocumentWidget_(d, iFalse);
4643 setUrl_DocumentWidget_(d, url); 4896 setUrl_DocumentWidget_(d, url);
4644 parseUser_DocumentWidget_(d); 4897 parseUser_DocumentWidget_(d);
@@ -4647,7 +4900,7 @@ void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, cons
4647 initCurrent_Time(&resp->when); 4900 initCurrent_Time(&resp->when);
4648 set_String(&resp->meta, mime); 4901 set_String(&resp->meta, mime);
4649 set_Block(&resp->body, source); 4902 set_Block(&resp->body, source);
4650 updateFromCachedResponse_DocumentWidget_(d, 0, resp); 4903 updateFromCachedResponse_DocumentWidget_(d, 0, resp, NULL);
4651 delete_GmResponse(resp); 4904 delete_GmResponse(resp);
4652} 4905}
4653 4906
@@ -4656,12 +4909,12 @@ iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) {
4656 delete_History(d->mod.history); 4909 delete_History(d->mod.history);
4657 d->initNormScrollY = normScrollPos_DocumentWidget_(d); 4910 d->initNormScrollY = normScrollPos_DocumentWidget_(d);
4658 d->mod.history = copy_History(orig->mod.history); 4911 d->mod.history = copy_History(orig->mod.history);
4659 setUrlFromCache_DocumentWidget(d, orig->mod.url, iTrue); 4912 setUrlFlags_DocumentWidget(d, orig->mod.url, useCachedContentIfAvailable_DocumentWidgetSetUrlFlag);
4660 return d; 4913 return d;
4661} 4914}
4662 4915
4663void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { 4916void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) {
4664 setUrlFromCache_DocumentWidget(d, url, iFalse); 4917 setUrlFlags_DocumentWidget(d, url, 0);
4665} 4918}
4666 4919
4667void setInitialScroll_DocumentWidget(iDocumentWidget *d, float normScrollY) { 4920void setInitialScroll_DocumentWidget(iDocumentWidget *d, float normScrollY) {
@@ -4672,6 +4925,11 @@ void setRedirectCount_DocumentWidget(iDocumentWidget *d, int count) {
4672 d->redirectCount = count; 4925 d->redirectCount = count;
4673} 4926}
4674 4927
4928void setOpenedFromSidebar_DocumentWidget(iDocumentWidget *d, iBool fromSidebar) {
4929 iChangeFlags(d->flags, openedFromSidebar_DocumentWidgetFlag, fromSidebar);
4930// setCachedDocument_History(d->mod.history, d->doc, fromSidebar);
4931}
4932
4675iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { 4933iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) {
4676 return d->request != NULL; 4934 return d->request != NULL;
4677} 4935}