diff options
Diffstat (limited to 'src/ui/documentwidget.c')
-rw-r--r-- | src/ui/documentwidget.c | 429 |
1 files changed, 336 insertions, 93 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 94337f8d..29e264e8 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -222,6 +222,7 @@ enum iDocumentWidgetFlag { | |||
222 | movingSelectMarkStart_DocumentWidgetFlag = iBit(10), | 222 | movingSelectMarkStart_DocumentWidgetFlag = iBit(10), |
223 | movingSelectMarkEnd_DocumentWidgetFlag = iBit(11), | 223 | movingSelectMarkEnd_DocumentWidgetFlag = iBit(11), |
224 | otherRootByDefault_DocumentWidgetFlag = iBit(12), /* links open to other root by default */ | 224 | otherRootByDefault_DocumentWidgetFlag = iBit(12), /* links open to other root by default */ |
225 | urlChanged_DocumentWidgetFlag = iBit(13), | ||
225 | }; | 226 | }; |
226 | 227 | ||
227 | enum iDocumentLinkOrdinalMode { | 228 | enum iDocumentLinkOrdinalMode { |
@@ -231,15 +232,41 @@ enum iDocumentLinkOrdinalMode { | |||
231 | 232 | ||
232 | struct Impl_DocumentWidget { | 233 | struct Impl_DocumentWidget { |
233 | iWidget widget; | 234 | iWidget widget; |
234 | enum iRequestState state; | ||
235 | iPersistentDocumentState mod; | ||
236 | int flags; | 235 | int flags; |
236 | |||
237 | /* User interface: */ | ||
237 | enum iDocumentLinkOrdinalMode ordinalMode; | 238 | enum iDocumentLinkOrdinalMode ordinalMode; |
238 | size_t ordinalBase; | 239 | size_t ordinalBase; |
239 | iString * titleUser; | 240 | iRangecc selectMark; |
241 | iRangecc initialSelectMark; /* for word/line selection */ | ||
242 | iRangecc foundMark; | ||
243 | const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ | ||
244 | float grabbedStartVolume; | ||
245 | int mediaTimer; | ||
246 | const iGmRun * hoverPre; /* for clicking */ | ||
247 | const iGmRun * hoverAltPre; /* for drawing alt text */ | ||
248 | const iGmRun * hoverLink; | ||
249 | const iGmRun * contextLink; | ||
250 | iClick click; | ||
251 | iInt2 contextPos; /* coordinates of latest right click */ | ||
252 | int pinchZoomInitial; | ||
253 | int pinchZoomPosted; | ||
254 | iString pendingGotoHeading; | ||
255 | |||
256 | /* Network request: */ | ||
257 | enum iRequestState state; | ||
240 | iGmRequest * request; | 258 | iGmRequest * request; |
241 | iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ | 259 | iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ |
242 | iObjectList * media; | 260 | int certFlags; |
261 | iBlock * certFingerprint; | ||
262 | iDate certExpiry; | ||
263 | iString * certSubject; | ||
264 | int redirectCount; | ||
265 | iObjectList * media; /* inline media requests */ | ||
266 | |||
267 | /* Document: */ | ||
268 | iPersistentDocumentState mod; | ||
269 | iString * titleUser; | ||
243 | enum iGmStatusCode sourceStatus; | 270 | enum iGmStatusCode sourceStatus; |
244 | iString sourceHeader; | 271 | iString sourceHeader; |
245 | iString sourceMime; | 272 | iString sourceMime; |
@@ -247,53 +274,36 @@ struct Impl_DocumentWidget { | |||
247 | iTime sourceTime; | 274 | iTime sourceTime; |
248 | iGempub * sourceGempub; /* NULL unless the page is Gempub content */ | 275 | iGempub * sourceGempub; /* NULL unless the page is Gempub content */ |
249 | iGmDocument * doc; | 276 | iGmDocument * doc; |
250 | int certFlags; | 277 | |
251 | iBlock * certFingerprint; | 278 | /* Rendering: */ |
252 | iDate certExpiry; | ||
253 | iString * certSubject; | ||
254 | int redirectCount; | ||
255 | iRangecc selectMark; | ||
256 | iRangecc initialSelectMark; /* for word/line selection */ | ||
257 | iRangecc foundMark; | ||
258 | int pageMargin; | 279 | int pageMargin; |
280 | float initNormScrollY; | ||
281 | iSmoothScroll scrollY; | ||
282 | iAnim sideOpacity; | ||
283 | iAnim altTextOpacity; | ||
284 | iGmRunRange visibleRuns; | ||
259 | iPtrArray visibleLinks; | 285 | iPtrArray visibleLinks; |
260 | iPtrArray visiblePre; | 286 | iPtrArray visiblePre; |
287 | iPtrArray visibleMedia; /* currently playing audio / ongoing downloads */ | ||
261 | iPtrArray visibleWideRuns; /* scrollable blocks; TODO: merge into `visiblePre` */ | 288 | iPtrArray visibleWideRuns; /* scrollable blocks; TODO: merge into `visiblePre` */ |
262 | iArray wideRunOffsets; | 289 | iArray wideRunOffsets; |
263 | iAnim animWideRunOffset; | 290 | iAnim animWideRunOffset; |
264 | uint16_t animWideRunId; | 291 | uint16_t animWideRunId; |
265 | iGmRunRange animWideRunRange; | 292 | iGmRunRange animWideRunRange; |
266 | iPtrArray visibleMedia; /* currently playing audio / ongoing downloads */ | 293 | iDrawBufs * drawBufs; /* dynamic state for drawing */ |
267 | const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ | 294 | iVisBuf * visBuf; |
268 | float grabbedStartVolume; | 295 | iVisBufMeta * visBufMeta; |
269 | int mediaTimer; | ||
270 | const iGmRun * hoverPre; /* for clicking */ | ||
271 | const iGmRun * hoverAltPre; /* for drawing alt text */ | ||
272 | const iGmRun * hoverLink; | ||
273 | const iGmRun * contextLink; | ||
274 | iGmRunRange visibleRuns; | ||
275 | iGmRunRange renderRuns; | 296 | iGmRunRange renderRuns; |
276 | iClick click; | 297 | iPtrSet * invalidRuns; |
277 | iInt2 contextPos; /* coordinates of latest right click */ | 298 | |
278 | iString pendingGotoHeading; | 299 | /* Widget structure: */ |
279 | float initNormScrollY; | ||
280 | // iAnim scrollY; | ||
281 | // int overscroll; | ||
282 | iSmoothScroll scrollY; | ||
283 | iAnim sideOpacity; | ||
284 | iAnim altTextOpacity; | ||
285 | iScrollWidget *scroll; | 300 | iScrollWidget *scroll; |
301 | iWidget * footerButtons; | ||
286 | iWidget * menu; | 302 | iWidget * menu; |
287 | iWidget * playerMenu; | 303 | iWidget * playerMenu; |
288 | iWidget * copyMenu; | 304 | iWidget * copyMenu; |
289 | iVisBuf * visBuf; | ||
290 | iVisBufMeta * visBufMeta; | ||
291 | iPtrSet * invalidRuns; | ||
292 | iDrawBufs * drawBufs; /* dynamic state for drawing */ | ||
293 | iTranslation * translation; | 305 | iTranslation * translation; |
294 | iWidget * phoneToolbar; | 306 | iWidget * phoneToolbar; |
295 | int pinchZoomInitial; | ||
296 | int pinchZoomPosted; | ||
297 | }; | 307 | }; |
298 | 308 | ||
299 | iDefineObjectConstruction(DocumentWidget) | 309 | iDefineObjectConstruction(DocumentWidget) |
@@ -308,6 +318,7 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
308 | init_PersistentDocumentState(&d->mod); | 318 | init_PersistentDocumentState(&d->mod); |
309 | d->flags = 0; | 319 | d->flags = 0; |
310 | d->phoneToolbar = NULL; | 320 | d->phoneToolbar = NULL; |
321 | d->footerButtons = NULL; | ||
311 | iZap(d->certExpiry); | 322 | iZap(d->certExpiry); |
312 | d->certFingerprint = new_Block(0); | 323 | d->certFingerprint = new_Block(0); |
313 | d->certFlags = 0; | 324 | d->certFlags = 0; |
@@ -321,7 +332,6 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
321 | d->redirectCount = 0; | 332 | d->redirectCount = 0; |
322 | d->ordinalBase = 0; | 333 | d->ordinalBase = 0; |
323 | d->initNormScrollY = 0; | 334 | d->initNormScrollY = 0; |
324 | //init_Anim(&d->scrollY, 0); | ||
325 | init_SmoothScroll(&d->scrollY, w, scrollBegan_DocumentWidget_); | 335 | init_SmoothScroll(&d->scrollY, w, scrollBegan_DocumentWidget_); |
326 | d->animWideRunId = 0; | 336 | d->animWideRunId = 0; |
327 | init_Anim(&d->animWideRunOffset, 0); | 337 | init_Anim(&d->animWideRunOffset, 0); |
@@ -573,7 +583,8 @@ static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) { | |||
573 | static int scrollMax_DocumentWidget_(const iDocumentWidget *d) { | 583 | static int scrollMax_DocumentWidget_(const iDocumentWidget *d) { |
574 | const iWidget *w = constAs_Widget(d); | 584 | const iWidget *w = constAs_Widget(d); |
575 | int sm = size_GmDocument(d->doc).y - height_Rect(bounds_Widget(w)) + | 585 | int sm = size_GmDocument(d->doc).y - height_Rect(bounds_Widget(w)) + |
576 | (hasSiteBanner_GmDocument(d->doc) ? 1 : 2) * d->pageMargin * gap_UI; | 586 | (hasSiteBanner_GmDocument(d->doc) ? 1 : 2) * d->pageMargin * gap_UI + |
587 | height_Widget(d->footerButtons); | ||
577 | if (d->phoneToolbar) { | 588 | if (d->phoneToolbar) { |
578 | sm += size_Root(w->root).y - | 589 | sm += size_Root(w->root).y - |
579 | top_Rect(boundsWithoutVisualOffset_Widget(d->phoneToolbar)); | 590 | top_Rect(boundsWithoutVisualOffset_Widget(d->phoneToolbar)); |
@@ -809,6 +820,13 @@ static iRangecc currentHeading_DocumentWidget_(const iDocumentWidget *d) { | |||
809 | return heading; | 820 | return heading; |
810 | } | 821 | } |
811 | 822 | ||
823 | static int updateScrollMax_DocumentWidget_(iDocumentWidget *d) { | ||
824 | arrange_Widget(d->footerButtons); /* scrollMax depends on footer height */ | ||
825 | const int scrollMax = scrollMax_DocumentWidget_(d); | ||
826 | setMax_SmoothScroll(&d->scrollY, scrollMax); | ||
827 | return scrollMax; | ||
828 | } | ||
829 | |||
812 | static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | 830 | static void updateVisible_DocumentWidget_(iDocumentWidget *d) { |
813 | iChangeFlags(d->flags, | 831 | iChangeFlags(d->flags, |
814 | centerVertically_DocumentWidgetFlag, | 832 | centerVertically_DocumentWidgetFlag, |
@@ -816,8 +834,26 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | |||
816 | !isSuccess_GmStatusCode(d->sourceStatus)); | 834 | !isSuccess_GmStatusCode(d->sourceStatus)); |
817 | const iRangei visRange = visibleRange_DocumentWidget_(d); | 835 | const iRangei visRange = visibleRange_DocumentWidget_(d); |
818 | const iRect bounds = bounds_Widget(as_Widget(d)); | 836 | const iRect bounds = bounds_Widget(as_Widget(d)); |
819 | const int scrollMax = scrollMax_DocumentWidget_(d); | 837 | const int scrollMax = updateScrollMax_DocumentWidget_(d); |
820 | setMax_SmoothScroll(&d->scrollY, scrollMax); | 838 | /* Reposition the footer buttons as appropriate. */ |
839 | /* TODO: You can just position `footerButtons` here completely without having to get | ||
840 | `Widget` involved with the offset in any way. */ | ||
841 | if (d->footerButtons) { | ||
842 | const iRect bounds = bounds_Widget(as_Widget(d)); | ||
843 | const iRect docBounds = documentBounds_DocumentWidget_(d); | ||
844 | const int hPad = (width_Rect(bounds) - iMin(120 * gap_UI, width_Rect(docBounds))) / 2; | ||
845 | const int vPad = 3 * gap_UI; | ||
846 | setPadding_Widget(d->footerButtons, hPad, vPad, hPad, vPad); | ||
847 | d->footerButtons->animOffsetRef = (scrollMax > 0 ? &d->scrollY.pos : NULL); | ||
848 | if (scrollMax <= 0) { | ||
849 | d->footerButtons->animOffsetRef = NULL; | ||
850 | d->footerButtons->rect.pos.y = height_Rect(bounds) - height_Widget(d->footerButtons); | ||
851 | } | ||
852 | else { | ||
853 | d->footerButtons->animOffsetRef = &d->scrollY.pos; | ||
854 | d->footerButtons->rect.pos.y = size_GmDocument(d->doc).y + 2 * gap_UI * d->pageMargin; | ||
855 | } | ||
856 | } | ||
821 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax }); | 857 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax }); |
822 | const int docSize = size_GmDocument(d->doc).y; | 858 | const int docSize = size_GmDocument(d->doc).y; |
823 | setThumb_ScrollWidget(d->scroll, | 859 | setThumb_ScrollWidget(d->scroll, |
@@ -1036,6 +1072,35 @@ static enum iGmDocumentBanner bannerType_DocumentWidget_(const iDocumentWidget * | |||
1036 | return siteDomain_GmDocumentBanner; | 1072 | return siteDomain_GmDocumentBanner; |
1037 | } | 1073 | } |
1038 | 1074 | ||
1075 | static void makeFooterButtons_DocumentWidget_(iDocumentWidget *d, const iMenuItem *items, size_t count) { | ||
1076 | iWidget *w = as_Widget(d); | ||
1077 | destroy_Widget(d->footerButtons); | ||
1078 | d->footerButtons = NULL; | ||
1079 | if (count == 0) { | ||
1080 | return; | ||
1081 | } | ||
1082 | d->footerButtons = new_Widget(); | ||
1083 | setFlags_Widget(d->footerButtons, | ||
1084 | unhittable_WidgetFlag | arrangeVertical_WidgetFlag | | ||
1085 | resizeWidthOfChildren_WidgetFlag | arrangeHeight_WidgetFlag | | ||
1086 | fixedPosition_WidgetFlag | resizeToParentWidth_WidgetFlag, | ||
1087 | iTrue); | ||
1088 | //setBackgroundColor_Widget(d->footerButtons, tmBackground_ColorId); | ||
1089 | for (size_t i = 0; i < count; ++i) { | ||
1090 | iLabelWidget *button = addChildFlags_Widget( | ||
1091 | d->footerButtons, | ||
1092 | iClob(newKeyMods_LabelWidget( | ||
1093 | items[i].label, items[i].key, items[i].kmods, items[i].command)), | ||
1094 | alignLeft_WidgetFlag | drawKey_WidgetFlag); | ||
1095 | checkIcon_LabelWidget(button); | ||
1096 | setFont_LabelWidget(button, uiContent_FontId); | ||
1097 | } | ||
1098 | addChild_Widget(as_Widget(d), iClob(d->footerButtons)); | ||
1099 | arrange_Widget(d->footerButtons); | ||
1100 | arrange_Widget(w); | ||
1101 | updateVisible_DocumentWidget_(d); /* final placement for the buttons */ | ||
1102 | } | ||
1103 | |||
1039 | static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code, | 1104 | static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code, |
1040 | const iString *meta) { | 1105 | const iString *meta) { |
1041 | iString *src = collectNewCStr_String("# "); | 1106 | iString *src = collectNewCStr_String("# "); |
@@ -1061,10 +1126,17 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
1061 | iString *key = collectNew_String(); | 1126 | iString *key = collectNew_String(); |
1062 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); | 1127 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); |
1063 | appendFormat_String(src, "\n```\n%s\n```\n", cstr_String(meta)); | 1128 | appendFormat_String(src, "\n```\n%s\n```\n", cstr_String(meta)); |
1064 | appendFormat_String(src, | 1129 | // appendFormat_String(src, |
1065 | cstr_Lang("error.unsupported.suggestsave"), | 1130 | // cstr_Lang("error.unsupported.suggestsave"), |
1066 | cstr_String(key), | 1131 | // cstr_String(key), |
1067 | saveToDownloads_Label); | 1132 | // saveToDownloads_Label); |
1133 | makeFooterButtons_DocumentWidget_( | ||
1134 | d, | ||
1135 | (iMenuItem[]){ { translateCStr_Lang(download_Icon " " saveToDownloads_Label), | ||
1136 | 0, | ||
1137 | 0, | ||
1138 | "document.save" } }, | ||
1139 | 1); | ||
1068 | break; | 1140 | break; |
1069 | } | 1141 | } |
1070 | case slowDown_GmStatusCode: | 1142 | case slowDown_GmStatusCode: |
@@ -1072,9 +1144,19 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
1072 | cstr_String(meta)); | 1144 | cstr_String(meta)); |
1073 | break; | 1145 | break; |
1074 | default: | 1146 | default: |
1147 | if (!isEmpty_String(meta)) { | ||
1148 | appendFormat_String(src, "\n\n${error.server.msg}\n> %s", cstr_String(meta)); | ||
1149 | } | ||
1075 | break; | 1150 | break; |
1076 | } | 1151 | } |
1077 | } | 1152 | } |
1153 | if (category_GmStatusCode(code) == categoryClientCertificate_GmStatus) { | ||
1154 | makeFooterButtons_DocumentWidget_( | ||
1155 | d, | ||
1156 | (iMenuItem[]){ { leftHalf_Icon " ${menu.show.identities}", '4', KMOD_PRIMARY, "sidebar.mode arg:3 show:1" }, | ||
1157 | { person_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" } }, | ||
1158 | 2); | ||
1159 | } | ||
1078 | setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); | 1160 | setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); |
1079 | setFormat_GmDocument(d->doc, gemini_SourceFormat); | 1161 | setFormat_GmDocument(d->doc, gemini_SourceFormat); |
1080 | translate_Lang(src); | 1162 | translate_Lang(src); |
@@ -1159,11 +1241,102 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool | |||
1159 | } | 1241 | } |
1160 | } | 1242 | } |
1161 | if (d->sourceGempub) { | 1243 | if (d->sourceGempub) { |
1162 | if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub)) && | 1244 | if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) { |
1163 | preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { | 1245 | if (!isRemote_Gempub(d->sourceGempub)) { |
1246 | iArray *items = collectNew_Array(sizeof(iMenuItem)); | ||
1247 | pushBack_Array( | ||
1248 | items, | ||
1249 | &(iMenuItem){ book_Icon " ${gempub.cover.view}", | ||
1250 | 0, | ||
1251 | 0, | ||
1252 | format_CStr("!open url:%s", | ||
1253 | cstr_String(indexPageUrl_Gempub(d->sourceGempub))) }); | ||
1254 | if (navSize_Gempub(d->sourceGempub) > 0) { | ||
1255 | pushBack_Array( | ||
1256 | items, | ||
1257 | &(iMenuItem){ | ||
1258 | format_CStr(forwardArrow_Icon " %s", | ||
1259 | cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))), | ||
1260 | SDLK_RIGHT, | ||
1261 | 0, | ||
1262 | format_CStr("!open url:%s", | ||
1263 | cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) }); | ||
1264 | } | ||
1265 | makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); | ||
1266 | } | ||
1267 | else { | ||
1268 | makeFooterButtons_DocumentWidget_( | ||
1269 | d, | ||
1270 | (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}", | ||
1271 | SDLK_s, | ||
1272 | KMOD_PRIMARY | KMOD_SHIFT, | ||
1273 | "document.save open:1" }, | ||
1274 | { download_Icon " " saveToDownloads_Label, | ||
1275 | SDLK_s, | ||
1276 | KMOD_PRIMARY, | ||
1277 | "document.save" } }, | ||
1278 | 2); | ||
1279 | } | ||
1280 | if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { | ||
1164 | redoLayout_GmDocument(d->doc); | 1281 | redoLayout_GmDocument(d->doc); |
1165 | updateVisible_DocumentWidget_(d); | 1282 | updateVisible_DocumentWidget_(d); |
1166 | invalidate_DocumentWidget_(d); | 1283 | invalidate_DocumentWidget_(d); |
1284 | } | ||
1285 | } | ||
1286 | else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { | ||
1287 | makeFooterButtons_DocumentWidget_( | ||
1288 | d, | ||
1289 | (iMenuItem[]){ { format_CStr(book_Icon " %s", | ||
1290 | cstr_String(property_Gempub(d->sourceGempub, | ||
1291 | title_GempubProperty))), | ||
1292 | SDLK_LEFT, | ||
1293 | 0, | ||
1294 | format_CStr("!open url:%s", | ||
1295 | cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } }, | ||
1296 | 1); | ||
1297 | } | ||
1298 | else { | ||
1299 | /* Navigation buttons. */ | ||
1300 | iArray *items = collectNew_Array(sizeof(iMenuItem)); | ||
1301 | const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url); | ||
1302 | if (navIndex != iInvalidPos) { | ||
1303 | if (navIndex < navSize_Gempub(d->sourceGempub) - 1) { | ||
1304 | pushBack_Array( | ||
1305 | items, | ||
1306 | &(iMenuItem){ | ||
1307 | format_CStr(forwardArrow_Icon " %s", | ||
1308 | cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))), | ||
1309 | SDLK_RIGHT, | ||
1310 | 0, | ||
1311 | format_CStr("!open url:%s", | ||
1312 | cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex + 1))) }); | ||
1313 | } | ||
1314 | if (navIndex > 0) { | ||
1315 | pushBack_Array( | ||
1316 | items, | ||
1317 | &(iMenuItem){ | ||
1318 | format_CStr(backArrow_Icon " %s", | ||
1319 | cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex - 1))), | ||
1320 | SDLK_LEFT, | ||
1321 | 0, | ||
1322 | format_CStr("!open url:%s", | ||
1323 | cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex - 1))) }); | ||
1324 | } | ||
1325 | else if (!equalCase_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { | ||
1326 | pushBack_Array( | ||
1327 | items, | ||
1328 | &(iMenuItem){ | ||
1329 | format_CStr(book_Icon " %s", | ||
1330 | cstr_String(property_Gempub(d->sourceGempub, title_GempubProperty))), | ||
1331 | SDLK_LEFT, | ||
1332 | 0, | ||
1333 | format_CStr("!open url:%s", | ||
1334 | cstr_String(coverPageUrl_Gempub(d->sourceGempub))) }); | ||
1335 | } | ||
1336 | } | ||
1337 | if (!isEmpty_Array(items)) { | ||
1338 | makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); | ||
1339 | } | ||
1167 | } | 1340 | } |
1168 | if (!isCached && prefs_App()->pinSplit && | 1341 | if (!isCached && prefs_App()->pinSplit && |
1169 | equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { | 1342 | equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { |
@@ -1227,7 +1400,9 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, | |||
1227 | setRange_String(&d->sourceMime, param); | 1400 | setRange_String(&d->sourceMime, param); |
1228 | } | 1401 | } |
1229 | else if (startsWith_Rangecc(param, "text/") || | 1402 | else if (startsWith_Rangecc(param, "text/") || |
1230 | equal_Rangecc(param, "application/json")) { | 1403 | equal_Rangecc(param, "application/json") || |
1404 | equal_Rangecc(param, "application/x-pem-file") || | ||
1405 | equal_Rangecc(param, "application/pem-certificate-chain")) { | ||
1231 | docFormat = plainText_SourceFormat; | 1406 | docFormat = plainText_SourceFormat; |
1232 | setRange_String(&d->sourceMime, param); | 1407 | setRange_String(&d->sourceMime, param); |
1233 | } | 1408 | } |
@@ -1400,44 +1575,58 @@ static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) { | |||
1400 | } | 1575 | } |
1401 | } | 1576 | } |
1402 | 1577 | ||
1578 | static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY, | ||
1579 | const iGmResponse *resp, iGmDocument *cachedDoc) { | ||
1580 | setLinkNumberMode_DocumentWidget_(d, iFalse); | ||
1581 | clear_ObjectList(d->media); | ||
1582 | delete_Gempub(d->sourceGempub); | ||
1583 | d->sourceGempub = NULL; | ||
1584 | iRelease(d->doc); | ||
1585 | destroy_Widget(d->footerButtons); | ||
1586 | d->footerButtons = NULL; | ||
1587 | d->doc = new_GmDocument(); | ||
1588 | resetWideRuns_DocumentWidget_(d); | ||
1589 | d->state = fetching_RequestState; | ||
1590 | /* Do the fetch. */ { | ||
1591 | d->initNormScrollY = normScrollY; | ||
1592 | /* Use the cached response data. */ | ||
1593 | updateTrust_DocumentWidget_(d, resp); | ||
1594 | d->sourceTime = resp->when; | ||
1595 | d->sourceStatus = success_GmStatusCode; | ||
1596 | format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); | ||
1597 | set_Block(&d->sourceContent, &resp->body); | ||
1598 | updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue); | ||
1599 | setCachedDocument_History(d->mod.history, d->doc); | ||
1600 | } | ||
1601 | d->state = ready_RequestState; | ||
1602 | postProcessRequestContent_DocumentWidget_(d, iTrue); | ||
1603 | init_Anim(&d->altTextOpacity, 0); | ||
1604 | reset_SmoothScroll(&d->scrollY); | ||
1605 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); | ||
1606 | updateSideOpacity_DocumentWidget_(d, iFalse); | ||
1607 | updateVisible_DocumentWidget_(d); | ||
1608 | moveSpan_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */ | ||
1609 | cacheDocumentGlyphs_DocumentWidget_(d); | ||
1610 | d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; | ||
1611 | d->flags &= ~urlChanged_DocumentWidgetFlag; | ||
1612 | postCommandf_Root( | ||
1613 | as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); | ||
1614 | } | ||
1615 | |||
1403 | static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | 1616 | static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { |
1404 | const iRecentUrl *recent = findUrl_History(d->mod.history, d->mod.url); | 1617 | const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url)); |
1405 | if (recent && recent->cachedResponse) { | 1618 | if (recent && recent->cachedResponse) { |
1406 | const iGmResponse *resp = recent->cachedResponse; | 1619 | updateFromCachedResponse_DocumentWidget_( |
1407 | clear_ObjectList(d->media); | 1620 | d, recent->normScrollY, recent->cachedResponse, recent->cachedDoc); |
1408 | delete_Gempub(d->sourceGempub); | ||
1409 | d->sourceGempub = NULL; | ||
1410 | iRelease(d->doc); | ||
1411 | d->doc = new_GmDocument(); | ||
1412 | resetWideRuns_DocumentWidget_(d); | ||
1413 | d->state = fetching_RequestState; | ||
1414 | /* Do the fetch. */ { | ||
1415 | d->initNormScrollY = recent->normScrollY; | ||
1416 | /* Use the cached response data. */ | ||
1417 | updateTrust_DocumentWidget_(d, resp); | ||
1418 | d->sourceTime = resp->when; | ||
1419 | d->sourceStatus = success_GmStatusCode; | ||
1420 | format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); | ||
1421 | set_Block(&d->sourceContent, &resp->body); | ||
1422 | updateDocument_DocumentWidget_(d, resp, recent->cachedDoc, iTrue); | ||
1423 | setCachedDocument_History(d->mod.history, d->doc); | ||
1424 | } | ||
1425 | d->state = ready_RequestState; | ||
1426 | postProcessRequestContent_DocumentWidget_(d, iTrue); | ||
1427 | init_Anim(&d->altTextOpacity, 0); | ||
1428 | reset_SmoothScroll(&d->scrollY); | ||
1429 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); | ||
1430 | updateSideOpacity_DocumentWidget_(d, iFalse); | ||
1431 | updateVisible_DocumentWidget_(d); | ||
1432 | moveSpan_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */ | ||
1433 | cacheDocumentGlyphs_DocumentWidget_(d); | ||
1434 | d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; | ||
1435 | postCommandf_Root(as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); | ||
1436 | return iTrue; | 1621 | return iTrue; |
1437 | } | 1622 | } |
1438 | else if (!isEmpty_String(d->mod.url)) { | 1623 | else if (!isEmpty_String(d->mod.url)) { |
1439 | fetch_DocumentWidget_(d); | 1624 | fetch_DocumentWidget_(d); |
1440 | } | 1625 | } |
1626 | if (recent) { | ||
1627 | /* Retain scroll position in refetched content as well. */ | ||
1628 | d->initNormScrollY = recent->normScrollY; | ||
1629 | } | ||
1441 | return iFalse; | 1630 | return iFalse; |
1442 | } | 1631 | } |
1443 | 1632 | ||
@@ -1674,11 +1863,16 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
1674 | break; | 1863 | break; |
1675 | } | 1864 | } |
1676 | case categorySuccess_GmStatusCode: | 1865 | case categorySuccess_GmStatusCode: |
1677 | //reset_SmoothScroll(&d->scrollY); | 1866 | if (d->flags & urlChanged_DocumentWidgetFlag) { |
1867 | /* Keep scroll position when reloading the same page. */ | ||
1868 | reset_SmoothScroll(&d->scrollY); | ||
1869 | } | ||
1678 | iRelease(d->doc); /* new content incoming */ | 1870 | iRelease(d->doc); /* new content incoming */ |
1679 | d->doc = new_GmDocument(); | 1871 | d->doc = new_GmDocument(); |
1680 | delete_Gempub(d->sourceGempub); | 1872 | delete_Gempub(d->sourceGempub); |
1681 | d->sourceGempub = NULL; | 1873 | d->sourceGempub = NULL; |
1874 | destroy_Widget(d->footerButtons); | ||
1875 | d->footerButtons = NULL; | ||
1682 | resetWideRuns_DocumentWidget_(d); | 1876 | resetWideRuns_DocumentWidget_(d); |
1683 | updateDocument_DocumentWidget_(d, resp, NULL, iTrue); | 1877 | updateDocument_DocumentWidget_(d, resp, NULL, iTrue); |
1684 | break; | 1878 | break; |
@@ -1902,7 +2096,8 @@ static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) { | |||
1902 | return iFalse; | 2096 | return iFalse; |
1903 | } | 2097 | } |
1904 | 2098 | ||
1905 | static void saveToDownloads_(const iString *url, const iString *mime, const iBlock *content) { | 2099 | static const iString *saveToDownloads_(const iString *url, const iString *mime, const iBlock *content, |
2100 | iBool showDialog) { | ||
1906 | const iString *savePath = downloadPathForUrl_App(url, mime); | 2101 | const iString *savePath = downloadPathForUrl_App(url, mime); |
1907 | /* Write the file. */ { | 2102 | /* Write the file. */ { |
1908 | iFile *f = new_File(savePath); | 2103 | iFile *f = new_File(savePath); |
@@ -1914,17 +2109,22 @@ static void saveToDownloads_(const iString *url, const iString *mime, const iBlo | |||
1914 | #if defined (iPlatformAppleMobile) | 2109 | #if defined (iPlatformAppleMobile) |
1915 | exportDownloadedFile_iOS(savePath); | 2110 | exportDownloadedFile_iOS(savePath); |
1916 | #else | 2111 | #else |
2112 | if (showDialog) { | ||
1917 | const iMenuItem items[2] = { | 2113 | const iMenuItem items[2] = { |
1918 | { "${dlg.save.opendownload}", 0, 0, | 2114 | { "${dlg.save.opendownload}", 0, 0, |
1919 | format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) }, | 2115 | format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) }, |
1920 | { "${dlg.message.ok}", 0, 0, "message.ok" }, | 2116 | { "${dlg.message.ok}", 0, 0, "message.ok" }, |
1921 | }; | 2117 | }; |
1922 | makeMessage_Widget(uiHeading_ColorEscape "${heading.save}", | 2118 | makeMessage_Widget(uiHeading_ColorEscape "${heading.save}", |
1923 | format_CStr("%s\n${dlg.save.size} %.3f %s", cstr_String(path_File(f)), | 2119 | format_CStr("%s\n${dlg.save.size} %.3f %s", |
2120 | cstr_String(path_File(f)), | ||
1924 | isMega ? size / 1.0e6f : (size / 1.0e3f), | 2121 | isMega ? size / 1.0e6f : (size / 1.0e3f), |
1925 | isMega ? "${mb}" : "${kb}"), | 2122 | isMega ? "${mb}" : "${kb}"), |
1926 | items, iElemCount(items)); | 2123 | items, |
2124 | iElemCount(items)); | ||
2125 | } | ||
1927 | #endif | 2126 | #endif |
2127 | return savePath; | ||
1928 | } | 2128 | } |
1929 | else { | 2129 | else { |
1930 | makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.save.error}", | 2130 | makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.save.error}", |
@@ -1932,6 +2132,7 @@ static void saveToDownloads_(const iString *url, const iString *mime, const iBlo | |||
1932 | } | 2132 | } |
1933 | iRelease(f); | 2133 | iRelease(f); |
1934 | } | 2134 | } |
2135 | return collectNew_String(); | ||
1935 | } | 2136 | } |
1936 | 2137 | ||
1937 | static void addAllLinks_(void *context, const iGmRun *run) { | 2138 | static void addAllLinks_(void *context, const iGmRun *run) { |
@@ -2061,6 +2262,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2061 | const iBool keepCenter = equal_Command(cmd, "font.changed"); | 2262 | const iBool keepCenter = equal_Command(cmd, "font.changed"); |
2062 | updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, keepCenter); | 2263 | updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, keepCenter); |
2063 | d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; | 2264 | d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; |
2265 | updateVisible_DocumentWidget_(d); | ||
2064 | invalidate_DocumentWidget_(d); | 2266 | invalidate_DocumentWidget_(d); |
2065 | dealloc_VisBuf(d->visBuf); | 2267 | dealloc_VisBuf(d->visBuf); |
2066 | updateWindowTitle_DocumentWidget_(d); | 2268 | updateWindowTitle_DocumentWidget_(d); |
@@ -2214,7 +2416,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2214 | if (!isEmpty_Array(items)) { | 2416 | if (!isEmpty_Array(items)) { |
2215 | pushBack_Array(items, &(iMenuItem){ "---", 0, 0, 0 }); | 2417 | pushBack_Array(items, &(iMenuItem){ "---", 0, 0, 0 }); |
2216 | } | 2418 | } |
2217 | pushBack_Array(items, &(iMenuItem){ "${dismiss}", 0, 0, "message.ok" }); | 2419 | pushBack_Array(items, &(iMenuItem){ "${close}", 0, 0, "message.ok" }); |
2218 | iWidget *dlg = makeQuestion_Widget(uiHeading_ColorEscape "${heading.pageinfo}", | 2420 | iWidget *dlg = makeQuestion_Widget(uiHeading_ColorEscape "${heading.pageinfo}", |
2219 | cstr_String(msg), | 2421 | cstr_String(msg), |
2220 | data_Array(items), | 2422 | data_Array(items), |
@@ -2334,6 +2536,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2334 | if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { | 2536 | if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { |
2335 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ | 2537 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ |
2336 | } | 2538 | } |
2539 | d->flags &= ~urlChanged_DocumentWidgetFlag; | ||
2337 | d->state = ready_RequestState; | 2540 | d->state = ready_RequestState; |
2338 | postProcessRequestContent_DocumentWidget_(d, iFalse); | 2541 | postProcessRequestContent_DocumentWidget_(d, iFalse); |
2339 | /* The response may be cached. */ | 2542 | /* The response may be cached. */ |
@@ -2409,7 +2612,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2409 | const iMediaRequest *media = findMediaRequest_DocumentWidget_(d, linkId); | 2612 | const iMediaRequest *media = findMediaRequest_DocumentWidget_(d, linkId); |
2410 | if (media) { | 2613 | if (media) { |
2411 | saveToDownloads_(url_GmRequest(media->req), meta_GmRequest(media->req), | 2614 | saveToDownloads_(url_GmRequest(media->req), meta_GmRequest(media->req), |
2412 | body_GmRequest(media->req)); | 2615 | body_GmRequest(media->req), iTrue); |
2413 | } | 2616 | } |
2414 | } | 2617 | } |
2415 | else if (equal_Command(cmd, "document.save") && document_App() == d) { | 2618 | else if (equal_Command(cmd, "document.save") && document_App() == d) { |
@@ -2418,7 +2621,13 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2418 | "${dlg.save.incomplete}"); | 2621 | "${dlg.save.incomplete}"); |
2419 | } | 2622 | } |
2420 | else if (!isEmpty_Block(&d->sourceContent)) { | 2623 | else if (!isEmpty_Block(&d->sourceContent)) { |
2421 | saveToDownloads_(d->mod.url, &d->sourceMime, &d->sourceContent); | 2624 | const iBool doOpen = argLabel_Command(cmd, "open"); |
2625 | const iString *savePath = saveToDownloads_(d->mod.url, &d->sourceMime, | ||
2626 | &d->sourceContent, !doOpen); | ||
2627 | if (!isEmpty_String(savePath) && doOpen) { | ||
2628 | postCommandf_Root( | ||
2629 | w->root, "!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))); | ||
2630 | } | ||
2422 | } | 2631 | } |
2423 | return iTrue; | 2632 | return iTrue; |
2424 | } | 2633 | } |
@@ -2528,6 +2737,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2528 | return iTrue; | 2737 | return iTrue; |
2529 | } | 2738 | } |
2530 | else if (equal_Command(cmd, "scroll.bottom") && document_App() == d) { | 2739 | else if (equal_Command(cmd, "scroll.bottom") && document_App() == d) { |
2740 | updateScrollMax_DocumentWidget_(d); /* scrollY.max might not be fully updated */ | ||
2531 | init_Anim(&d->scrollY.pos, d->scrollY.max); | 2741 | init_Anim(&d->scrollY.pos, d->scrollY.max); |
2532 | invalidate_VisBuf(d->visBuf); | 2742 | invalidate_VisBuf(d->visBuf); |
2533 | clampScroll_DocumentWidget_(d); | 2743 | clampScroll_DocumentWidget_(d); |
@@ -2835,7 +3045,7 @@ static size_t linkOrdinalFromKey_DocumentWidget_(const iDocumentWidget *d, int k | |||
2835 | static iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t ord) { | 3045 | static iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t ord) { |
2836 | if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) { | 3046 | if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) { |
2837 | if (ord < 9) { | 3047 | if (ord < 9) { |
2838 | return 0x278a + ord; | 3048 | return '1' + ord; |
2839 | } | 3049 | } |
2840 | #if defined (iPlatformApple) | 3050 | #if defined (iPlatformApple) |
2841 | if (ord < 9 + 22) { | 3051 | if (ord < 9 + 22) { |
@@ -2844,17 +3054,17 @@ static iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t or | |||
2844 | if (key >= 'm') key++; | 3054 | if (key >= 'm') key++; |
2845 | if (key >= 'q') key++; | 3055 | if (key >= 'q') key++; |
2846 | if (key >= 'w') key++; | 3056 | if (key >= 'w') key++; |
2847 | return 0x24b6 + key - 'a'; | 3057 | return 'A' + key - 'a'; |
2848 | } | 3058 | } |
2849 | #else | 3059 | #else |
2850 | if (ord < 9 + 26) { | 3060 | if (ord < 9 + 26) { |
2851 | return 0x24b6 + ord - 9; | 3061 | return 'A' + ord - 9; |
2852 | } | 3062 | } |
2853 | #endif | 3063 | #endif |
2854 | } | 3064 | } |
2855 | else { | 3065 | else { |
2856 | if (ord < iElemCount(homeRowKeys_)) { | 3066 | if (ord < iElemCount(homeRowKeys_)) { |
2857 | return 0x24b6 + homeRowKeys_[ord] - 'a'; | 3067 | return 'A' + homeRowKeys_[ord] - 'a'; |
2858 | } | 3068 | } |
2859 | } | 3069 | } |
2860 | return 0; | 3070 | return 0; |
@@ -3041,6 +3251,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3041 | iArray items; | 3251 | iArray items; |
3042 | init_Array(&items, sizeof(iMenuItem)); | 3252 | init_Array(&items, sizeof(iMenuItem)); |
3043 | if (d->contextLink) { | 3253 | if (d->contextLink) { |
3254 | /* Context menu for a link. */ | ||
3044 | const iString *linkUrl = linkUrl_GmDocument(d->doc, d->contextLink->linkId); | 3255 | const iString *linkUrl = linkUrl_GmDocument(d->doc, d->contextLink->linkId); |
3045 | // const int linkFlags = linkFlags_GmDocument(d->doc, d->contextLink->linkId); | 3256 | // const int linkFlags = linkFlags_GmDocument(d->doc, d->contextLink->linkId); |
3046 | const iRangecc scheme = urlScheme_String(linkUrl); | 3257 | const iRangecc scheme = urlScheme_String(linkUrl); |
@@ -3675,6 +3886,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
3675 | /* Preformatted runs can be scrolled. */ | 3886 | /* Preformatted runs can be scrolled. */ |
3676 | runOffset_DocumentWidget_(d->widget, run)); | 3887 | runOffset_DocumentWidget_(d->widget, run)); |
3677 | const iRect visRect = { visPos, run->visBounds.size }; | 3888 | const iRect visRect = { visPos, run->visBounds.size }; |
3889 | #if 0 | ||
3678 | if (run->flags & footer_GmRunFlag) { | 3890 | if (run->flags & footer_GmRunFlag) { |
3679 | iRect footerBack = | 3891 | iRect footerBack = |
3680 | (iRect){ visPos, init_I2(width_Rect(d->widgetBounds), run->visBounds.size.y) }; | 3892 | (iRect){ visPos, init_I2(width_Rect(d->widgetBounds), run->visBounds.size.y) }; |
@@ -3682,6 +3894,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
3682 | fillRect_Paint(&d->paint, footerBack, tmBackground_ColorId); | 3894 | fillRect_Paint(&d->paint, footerBack, tmBackground_ColorId); |
3683 | return; | 3895 | return; |
3684 | } | 3896 | } |
3897 | #endif | ||
3685 | /* Fill the background. */ { | 3898 | /* Fill the background. */ { |
3686 | if (run->linkId && linkFlags & isOpen_GmLinkFlag) { | 3899 | if (run->linkId && linkFlags & isOpen_GmLinkFlag) { |
3687 | /* Open links get a highlighted background. */ | 3900 | /* Open links get a highlighted background. */ |
@@ -3743,10 +3956,19 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
3743 | const iChar ordChar = | 3956 | const iChar ordChar = |
3744 | linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase); | 3957 | linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase); |
3745 | if (ordChar) { | 3958 | if (ordChar) { |
3746 | drawString_Text(run->font, | 3959 | const char *circle = "\u25ef"; /* Large Circle */ |
3747 | init_I2(d->viewPos.x - gap_UI / 3, visPos.y), | 3960 | iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y), |
3961 | init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(run->font)) }; | ||
3962 | drawRange_Text( | ||
3963 | run->font, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle)); | ||
3964 | iRect circleArea = visualBounds_Text(run->font, range_CStr(circle)); | ||
3965 | addv_I2(&circleArea.pos, topLeft_Rect(nbArea)); | ||
3966 | drawCentered_Text(defaultContentSmall_FontId, | ||
3967 | circleArea, | ||
3968 | iTrue, | ||
3748 | tmQuote_ColorId, | 3969 | tmQuote_ColorId, |
3749 | collect_String(newUnicodeN_String(&ordChar, 1))); | 3970 | "%lc", |
3971 | (int) ordChar); | ||
3750 | goto runDrawn; | 3972 | goto runDrawn; |
3751 | } | 3973 | } |
3752 | } | 3974 | } |
@@ -4424,9 +4646,16 @@ void deserializeState_DocumentWidget(iDocumentWidget *d, iStream *ins) { | |||
4424 | updateFromHistory_DocumentWidget_(d); | 4646 | updateFromHistory_DocumentWidget_(d); |
4425 | } | 4647 | } |
4426 | 4648 | ||
4649 | static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) { | ||
4650 | if (!equal_String(d->mod.url, url)) { | ||
4651 | d->flags |= urlChanged_DocumentWidgetFlag; | ||
4652 | set_String(d->mod.url, url); | ||
4653 | } | ||
4654 | } | ||
4655 | |||
4427 | void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) { | 4656 | void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) { |
4428 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 4657 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
4429 | set_String(d->mod.url, urlFragmentStripped_String(url)); | 4658 | setUrl_DocumentWidget_(d, urlFragmentStripped_String(url)); |
4430 | /* See if there a username in the URL. */ | 4659 | /* See if there a username in the URL. */ |
4431 | parseUser_DocumentWidget_(d); | 4660 | parseUser_DocumentWidget_(d); |
4432 | if (!isFromCache || !updateFromHistory_DocumentWidget_(d)) { | 4661 | if (!isFromCache || !updateFromHistory_DocumentWidget_(d)) { |
@@ -4434,6 +4663,20 @@ void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBoo | |||
4434 | } | 4663 | } |
4435 | } | 4664 | } |
4436 | 4665 | ||
4666 | void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, const iString *mime, | ||
4667 | const iBlock *source) { | ||
4668 | setLinkNumberMode_DocumentWidget_(d, iFalse); | ||
4669 | setUrl_DocumentWidget_(d, url); | ||
4670 | parseUser_DocumentWidget_(d); | ||
4671 | iGmResponse *resp = new_GmResponse(); | ||
4672 | resp->statusCode = success_GmStatusCode; | ||
4673 | initCurrent_Time(&resp->when); | ||
4674 | set_String(&resp->meta, mime); | ||
4675 | set_Block(&resp->body, source); | ||
4676 | updateFromCachedResponse_DocumentWidget_(d, 0, resp); | ||
4677 | delete_GmResponse(resp); | ||
4678 | } | ||
4679 | |||
4437 | iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) { | 4680 | iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) { |
4438 | iDocumentWidget *d = new_DocumentWidget(); | 4681 | iDocumentWidget *d = new_DocumentWidget(); |
4439 | delete_History(d->mod.history); | 4682 | delete_History(d->mod.history); |