summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2022-02-20 12:53:12 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2022-02-20 12:53:12 +0200
commit6d7b6122e68c8d53b6e7533a6e5d5f55e33831e1 (patch)
tree80bc2867510e3c9b7bc43578fc416165dae3876c /src/ui
parent2d52f13afb15232cc9c834f0aa14284bf9072a31 (diff)
parent41f378c4b46cb5dd3599d44c81fa51d3183eefee (diff)
Merge branch 'work/v1.11' into dev
# Conflicts: # res/lang/es.bin # res/lang/ie.bin # res/lang/ru.bin # res/lang/sr.bin # res/lang/uk.bin
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/banner.c3
-rw-r--r--src/ui/color.c4
-rw-r--r--src/ui/color.h2
-rw-r--r--src/ui/documentwidget.c135
-rw-r--r--src/ui/inputwidget.c5
-rw-r--r--src/ui/inputwidget.h1
-rw-r--r--src/ui/keys.c2
-rw-r--r--src/ui/labelwidget.c12
-rw-r--r--src/ui/linkinfo.c4
-rw-r--r--src/ui/lookupwidget.c2
-rw-r--r--src/ui/root.c16
-rw-r--r--src/ui/root.h1
-rw-r--r--src/ui/sidebarwidget.c187
-rw-r--r--src/ui/text.c60
-rw-r--r--src/ui/text.h2
-rw-r--r--src/ui/touch.c3
-rw-r--r--src/ui/util.c200
-rw-r--r--src/ui/util.h14
-rw-r--r--src/ui/widget.c3
-rw-r--r--src/ui/window.c75
-rw-r--r--src/ui/window.h3
21 files changed, 564 insertions, 170 deletions
diff --git a/src/ui/banner.c b/src/ui/banner.c
index 11ae1574..79d70039 100644
--- a/src/ui/banner.c
+++ b/src/ui/banner.c
@@ -327,7 +327,8 @@ iBool processEvent_Banner(iBanner *d, const SDL_Event *ev) {
327 else { 327 else {
328 switch (item->code) { 328 switch (item->code) {
329 case missingGlyphs_GmStatusCode: 329 case missingGlyphs_GmStatusCode:
330 postCommandf_App("open newtab:1 url:about:fonts"); 330 //postCommandf_App("open newtab:1 url:about:fonts");
331 makeGlyphFinder_Widget();
331 break; 332 break;
332 case ansiEscapes_GmStatusCode: 333 case ansiEscapes_GmStatusCode:
333 makeQuestion_Widget( 334 makeQuestion_Widget(
diff --git a/src/ui/color.c b/src/ui/color.c
index 824342ae..9cba322d 100644
--- a/src/ui/color.c
+++ b/src/ui/color.c
@@ -522,8 +522,8 @@ iHSLColor setLum_HSLColor(iHSLColor d, float lum) {
522} 522}
523 523
524iHSLColor addSatLum_HSLColor(iHSLColor d, float sat, float lum) { 524iHSLColor addSatLum_HSLColor(iHSLColor d, float sat, float lum) {
525 d.sat = iClamp(d.sat + sat, 0, 1); 525 d.sat = iClamp(d.sat + sat, minSat_HSLColor, 1);
526 d.lum = iClamp(d.lum + lum, 0, 1); 526 d.lum = iClamp(d.lum + lum, minSat_HSLColor, 1);
527 return d; 527 return d;
528} 528}
529 529
diff --git a/src/ui/color.h b/src/ui/color.h
index 24f9e713..f46976d7 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -231,6 +231,8 @@ struct Impl_HSLColor {
231 float hue, sat, lum, a; 231 float hue, sat, lum, a;
232}; 232};
233 233
234#define minSat_HSLColor 0.013f /* Conversion to 8-bit RGB may result in saturation dropping to zero. */
235
234iHSLColor hsl_Color (iColor); 236iHSLColor hsl_Color (iColor);
235iColor rgb_HSLColor (iHSLColor); 237iColor rgb_HSLColor (iHSLColor);
236float luma_Color (iColor); 238float luma_Color (iColor);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index a52e99af..fdc0dd75 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -358,6 +358,7 @@ static void updateSideIconBuf_DocumentWidget_ (const iDocumentWidget *d);
358static void prerender_DocumentWidget_ (iAny *); 358static void prerender_DocumentWidget_ (iAny *);
359static void scrollBegan_DocumentWidget_ (iAnyObject *, int, uint32_t); 359static void scrollBegan_DocumentWidget_ (iAnyObject *, int, uint32_t);
360static void refreshWhileScrolling_DocumentWidget_ (iAny *); 360static void refreshWhileScrolling_DocumentWidget_ (iAny *);
361static iBool requestMedia_DocumentWidget_ (iDocumentWidget *d, iGmLinkId linkId, iBool enableFilters);
361 362
362/* TODO: The following methods are called from DocumentView, which goes the wrong way. */ 363/* TODO: The following methods are called from DocumentView, which goes the wrong way. */
363 364
@@ -1824,7 +1825,7 @@ static void draw_DocumentView_(const iDocumentView *d) {
1824 } 1825 }
1825 if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) { 1826 if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) {
1826 updateSideIconBuf_DocumentView_(d); 1827 updateSideIconBuf_DocumentView_(d);
1827 } 1828 }
1828 const iRect docBounds = documentBounds_DocumentView_(d); 1829 const iRect docBounds = documentBounds_DocumentView_(d);
1829 const iRangei vis = visibleRange_DocumentView_(d); 1830 const iRangei vis = visibleRange_DocumentView_(d);
1830 iDrawContext ctx = { 1831 iDrawContext ctx = {
@@ -2410,6 +2411,20 @@ static const char *zipPageHeading_(const iRangecc mime) {
2410 2411
2411static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) { 2412static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) {
2412 iWidget *w = as_Widget(d); 2413 iWidget *w = as_Widget(d);
2414 /* Embedded images in data links can be shown immediately as they are already fetched
2415 data that is part of the document. */
2416 if (prefs_App()->openDataUrlImagesOnLoad) {
2417 iGmDocument *doc = d->view.doc;
2418 for (size_t linkId = 1; ; linkId++) {
2419 const int linkFlags = linkFlags_GmDocument(doc, linkId);
2420 const iString *linkUrl = linkUrl_GmDocument(doc, linkId);
2421 if (!linkUrl) break;
2422 if (scheme_GmLinkFlag(linkFlags) == data_GmLinkScheme &&
2423 (linkFlags & imageFileExtension_GmLinkFlag)) {
2424 requestMedia_DocumentWidget_(d, linkId, 0);
2425 }
2426 }
2427 }
2413 /* Gempub page behavior and footer actions. */ { 2428 /* Gempub page behavior and footer actions. */ {
2414 /* TODO: move this to gempub.c */ 2429 /* TODO: move this to gempub.c */
2415 delete_Gempub(d->sourceGempub); 2430 delete_Gempub(d->sourceGempub);
@@ -2681,7 +2696,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
2681 if (loadArchive_FontPack(fp, zip)) { 2696 if (loadArchive_FontPack(fp, zip)) {
2682 appendFormat_String(&str, "# " fontpack_Icon "%s\n%s", 2697 appendFormat_String(&str, "# " fontpack_Icon "%s\n%s",
2683 cstr_String(id_FontPack(fp).id), 2698 cstr_String(id_FontPack(fp).id),
2684 cstrCollect_String(infoText_FontPack(fp))); 2699 cstrCollect_String(infoText_FontPack(fp, iTrue)));
2685 } 2700 }
2686 appendCStr_String(&str, "\n"); 2701 appendCStr_String(&str, "\n");
2687 appendCStr_String(&str, cstr_Lang("fontpack.help")); 2702 appendCStr_String(&str, cstr_Lang("fontpack.help"));
@@ -3258,9 +3273,11 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
3258 setTextColor_LabelWidget(menu, uiTextAction_ColorId); 3273 setTextColor_LabelWidget(menu, uiTextAction_ColorId);
3259 } 3274 }
3260 } 3275 }
3261 setValidator_InputWidget(findChild_Widget(dlg, "input"), inputQueryValidator_, d); 3276 iInputWidget *input = findChild_Widget(dlg, "input");
3262 setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"), 3277 setValidator_InputWidget(input, inputQueryValidator_, d);
3263 statusCode == sensitiveInput_GmStatusCode); 3278 setBackupFileName_InputWidget(input, "inputbackup.txt");
3279 setSelectAllOnFocus_InputWidget(input, iTrue);
3280 setSensitiveContent_InputWidget(input, statusCode == sensitiveInput_GmStatusCode);
3264 if (document_App() != d) { 3281 if (document_App() != d) {
3265 postCommandf_App("tabs.switch page:%p", d); 3282 postCommandf_App("tabs.switch page:%p", d);
3266 } 3283 }
@@ -3921,12 +3938,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3921 const char *unchecked = red_ColorEscape "\u2610"; 3938 const char *unchecked = red_ColorEscape "\u2610";
3922 const char *checked = green_ColorEscape "\u2611"; 3939 const char *checked = green_ColorEscape "\u2611";
3923 const iBool haveFingerprint = (d->certFlags & haveFingerprint_GmCertFlag) != 0; 3940 const iBool haveFingerprint = (d->certFlags & haveFingerprint_GmCertFlag) != 0;
3924 const int requiredForTrust = (available_GmCertFlag | haveFingerprint_GmCertFlag | 3941 const int requiredForTrust =
3925 timeVerified_GmCertFlag); 3942 (available_GmCertFlag | haveFingerprint_GmCertFlag | timeVerified_GmCertFlag);
3926 const iBool canTrust = ~d->certFlags & trusted_GmCertFlag && 3943 const iBool canTrust = ~d->certFlags & trusted_GmCertFlag &&
3927 ((d->certFlags & requiredForTrust) == requiredForTrust); 3944 ((d->certFlags & requiredForTrust) == requiredForTrust);
3928 const iRecentUrl *recent = constMostRecentUrl_History(d->mod.history); 3945 const iRecentUrl *recent = constMostRecentUrl_History(d->mod.history);
3929 const iString *meta = &d->sourceMime; 3946 const iString *meta = &d->sourceMime;
3930 if (recent && recent->cachedResponse) { 3947 if (recent && recent->cachedResponse) {
3931 meta = &recent->cachedResponse->meta; 3948 meta = &recent->cachedResponse->meta;
3932 } 3949 }
@@ -3991,6 +4008,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3991 if (haveFingerprint) { 4008 if (haveFingerprint) {
3992 pushBack_Array(items, &(iMenuItem){ "${dlg.cert.fingerprint}", 0, 0, "server.copycert" }); 4009 pushBack_Array(items, &(iMenuItem){ "${dlg.cert.fingerprint}", 0, 0, "server.copycert" });
3993 } 4010 }
4011 const iRangecc root = urlRoot_String(d->mod.url);
4012 if (!isEmpty_Range(&root)) {
4013 pushBack_Array(items, &(iMenuItem){ "${pageinfo.settings}", 0, 0, "document.sitespec" });
4014 }
3994 if (!isEmpty_Array(items)) { 4015 if (!isEmpty_Array(items)) {
3995 pushBack_Array(items, &(iMenuItem){ "---", 0, 0, 0 }); 4016 pushBack_Array(items, &(iMenuItem){ "---", 0, 0, 0 });
3996 } 4017 }
@@ -4014,6 +4035,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
4014 addAction_Widget(dlg, SDLK_SPACE, 0, "message.ok"); 4035 addAction_Widget(dlg, SDLK_SPACE, 0, "message.ok");
4015 return iTrue; 4036 return iTrue;
4016 } 4037 }
4038 else if (equal_Command(cmd, "document.sitespec") && d == document_App()) {
4039 if (!findWidget_App("sitespec.palette")) {
4040 makeSiteSpecificSettings_Widget(d->mod.url);
4041 }
4042 return iTrue;
4043 }
4017 else if (equal_Command(cmd, "server.unexpire") && document_App() == d) { 4044 else if (equal_Command(cmd, "server.unexpire") && document_App() == d) {
4018 const iRangecc host = urlHost_String(d->mod.url); 4045 const iRangecc host = urlHost_String(d->mod.url);
4019 const uint16_t port = urlPort_String(d->mod.url); 4046 const uint16_t port = urlPort_String(d->mod.url);
@@ -4922,7 +4949,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4922 for (size_t i = 0; i < 64; ++i) { 4949 for (size_t i = 0; i < 64; ++i) {
4923 setByte_Block(seed, i, iRandom(0, 256)); 4950 setByte_Block(seed, i, iRandom(0, 256));
4924 } 4951 }
4925 setThemeSeed_GmDocument(view->doc, seed); 4952 setThemeSeed_GmDocument(view->doc, seed, NULL);
4926 delete_Block(seed); 4953 delete_Block(seed);
4927 invalidate_DocumentWidget_(d); 4954 invalidate_DocumentWidget_(d);
4928 refresh_Widget(w); 4955 refresh_Widget(w);
@@ -5044,10 +5071,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
5044 iArray items; 5071 iArray items;
5045 init_Array(&items, sizeof(iMenuItem)); 5072 init_Array(&items, sizeof(iMenuItem));
5046 if (d->contextLink) { 5073 if (d->contextLink) {
5047 /* Context menu for a link. */ 5074 /* Construct the link context menu, depending on what kind of link was clicked. */
5048 interactingWithLink_DocumentWidget_(d, d->contextLink->linkId); /* perhaps will be triggered */ 5075 interactingWithLink_DocumentWidget_(d, d->contextLink->linkId); /* perhaps will be triggered */
5049 const iString *linkUrl = linkUrl_GmDocument(view->doc, d->contextLink->linkId); 5076 const iString *linkUrl = linkUrl_GmDocument(view->doc, d->contextLink->linkId);
5050// const int linkFlags = linkFlags_GmDocument(d->doc, d->contextLink->linkId);
5051 const iRangecc scheme = urlScheme_String(linkUrl); 5077 const iRangecc scheme = urlScheme_String(linkUrl);
5052 const iBool isGemini = equalCase_Rangecc(scheme, "gemini"); 5078 const iBool isGemini = equalCase_Rangecc(scheme, "gemini");
5053 iBool isNative = iFalse; 5079 iBool isNative = iFalse;
@@ -5059,41 +5085,55 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
5059 format_CStr("```%s", cstr_String(infoText)), 5085 format_CStr("```%s", cstr_String(infoText)),
5060 0, 0, NULL }); 5086 0, 0, NULL });
5061 } 5087 }
5062 if (willUseProxy_App(scheme) || isGemini || 5088 if (isGemini ||
5089 willUseProxy_App(scheme) ||
5090 equalCase_Rangecc(scheme, "data") ||
5063 equalCase_Rangecc(scheme, "file") || 5091 equalCase_Rangecc(scheme, "file") ||
5064 equalCase_Rangecc(scheme, "finger") || 5092 equalCase_Rangecc(scheme, "finger") ||
5065 equalCase_Rangecc(scheme, "gopher")) { 5093 equalCase_Rangecc(scheme, "gopher")) {
5066 isNative = iTrue; 5094 isNative = iTrue;
5067 /* Regular links that we can open. */ 5095 /* Regular links that we can open. */
5068 pushBackN_Array( 5096 pushBackN_Array(&items,
5069 &items, 5097 (iMenuItem[]){
5070 (iMenuItem[]){ { openTab_Icon " ${link.newtab}", 5098 { openTab_Icon " ${link.newtab}",
5071 0, 5099 0,
5072 0, 5100 0,
5073 format_CStr("!open newtab:1 origin:%s url:%s", 5101 format_CStr("!open newtab:1 origin:%s url:%s",
5074 cstr_String(id_Widget(w)), 5102 cstr_String(id_Widget(w)),
5075 cstr_String(linkUrl)) }, 5103 cstr_String(linkUrl)) },
5076 { openTabBg_Icon " ${link.newtab.background}", 5104 { openTabBg_Icon " ${link.newtab.background}",
5077 0, 5105 0,
5078 0, 5106 0,
5079 format_CStr("!open newtab:2 origin:%s url:%s", 5107 format_CStr("!open newtab:2 origin:%s url:%s",
5080 cstr_String(id_Widget(w)), 5108 cstr_String(id_Widget(w)),
5081 cstr_String(linkUrl)) }, 5109 cstr_String(linkUrl)) },
5082 { "${link.side}", 5110 { openWindow_Icon " ${link.newwindow}",
5083 0, 5111 0,
5084 0, 5112 0,
5085 format_CStr("!open newtab:4 origin:%s url:%s", 5113 format_CStr("!open newwindow:1 origin:%s url:%s",
5086 cstr_String(id_Widget(w)), 5114 cstr_String(id_Widget(w)),
5087 cstr_String(linkUrl)) }, 5115 cstr_String(linkUrl)) },
5088 { "${link.side.newtab}", 5116 { "${link.side}",
5089 0, 5117 0,
5090 0, 5118 0,
5091 format_CStr("!open newtab:5 origin:%s url:%s", 5119 format_CStr("!open newtab:4 origin:%s url:%s",
5092 cstr_String(id_Widget(w)), 5120 cstr_String(id_Widget(w)),
5093 cstr_String(linkUrl)) } }, 5121 cstr_String(linkUrl)) },
5094 4); 5122 { "${link.side.newtab}",
5123 0,
5124 0,
5125 format_CStr("!open newtab:5 origin:%s url:%s",
5126 cstr_String(id_Widget(w)),
5127 cstr_String(linkUrl)) },
5128 },
5129 5);
5095 if (deviceType_App() == phone_AppDeviceType) { 5130 if (deviceType_App() == phone_AppDeviceType) {
5096 removeN_Array(&items, size_Array(&items) - 2, iInvalidSize); 5131 /* Phones don't do windows or splits. */
5132 removeN_Array(&items, size_Array(&items) - 3, iInvalidSize);
5133 }
5134 else if (deviceType_App() == tablet_AppDeviceType) {
5135 /* Tablets only do splits. */
5136 removeN_Array(&items, size_Array(&items) - 3, 1);
5097 } 5137 }
5098 if (equalCase_Rangecc(scheme, "file")) { 5138 if (equalCase_Rangecc(scheme, "file")) {
5099 pushBack_Array(&items, &(iMenuItem){ "---" }); 5139 pushBack_Array(&items, &(iMenuItem){ "---" });
@@ -5248,6 +5288,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
5248 "document.upload", 5288 "document.upload",
5249 !equalCase_Rangecc(urlScheme_String(d->mod.url), "gemini") && 5289 !equalCase_Rangecc(urlScheme_String(d->mod.url), "gemini") &&
5250 !equalCase_Rangecc(urlScheme_String(d->mod.url), "titan")); 5290 !equalCase_Rangecc(urlScheme_String(d->mod.url), "titan"));
5291 setMenuItemDisabled_Widget(
5292 d->menu,
5293 "document.upload copy:1",
5294 !equalCase_Rangecc(urlScheme_String(d->mod.url), "gemini") &&
5295 !equalCase_Rangecc(urlScheme_String(d->mod.url), "titan"));
5251 } 5296 }
5252 processContextMenuEvent_Widget(d->menu, ev, {}); 5297 processContextMenuEvent_Widget(d->menu, ev, {});
5253 } 5298 }
@@ -5545,12 +5590,12 @@ static void prerender_DocumentWidget_(iAny *context) {
5545 } 5590 }
5546 const iDocumentWidget *d = context; 5591 const iDocumentWidget *d = context;
5547 iDrawContext ctx = { 5592 iDrawContext ctx = {
5548 .view = &d->view, 5593 .view = &d->view,
5549 .docBounds = documentBounds_DocumentView_(&d->view), 5594 .docBounds = documentBounds_DocumentView_(&d->view),
5550 .vis = visibleRange_DocumentView_(&d->view), 5595 .vis = visibleRange_DocumentView_(&d->view),
5551 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0 5596 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0
5552 }; 5597 };
5553 // printf("%u prerendering\n", SDL_GetTicks()); 5598 // printf("%u prerendering\n", SDL_GetTicks());
5554 if (d->view.visBuf->buffers[0].texture) { 5599 if (d->view.visBuf->buffers[0].texture) {
5555 makePaletteGlobal_GmDocument(d->view.doc); 5600 makePaletteGlobal_GmDocument(d->view.doc);
5556 if (render_DocumentView_(&d->view, &ctx, iTrue /* just fill up progressively */)) { 5601 if (render_DocumentView_(&d->view, &ctx, iTrue /* just fill up progressively */)) {
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 6a8d428a..1b68ff57 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -1201,6 +1201,11 @@ void selectAll_InputWidget(iInputWidget *d) {
1201#endif 1201#endif
1202} 1202}
1203 1203
1204void deselect_InputWidget(iInputWidget *d) {
1205 iZap(d->mark);
1206 refresh_Widget(as_Widget(d));
1207}
1208
1204void validate_InputWidget(iInputWidget *d) { 1209void validate_InputWidget(iInputWidget *d) {
1205 if (d->validator) { 1210 if (d->validator) {
1206 d->validator(d, d->validatorContext); /* this may change the contents */ 1211 d->validator(d, d->validatorContext); /* this may change the contents */
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h
index 000fa4b7..832f7853 100644
--- a/src/ui/inputwidget.h
+++ b/src/ui/inputwidget.h
@@ -59,6 +59,7 @@ void setBackupFileName_InputWidget (iInputWidget *, const char *fileName);
59void begin_InputWidget (iInputWidget *); 59void begin_InputWidget (iInputWidget *);
60void end_InputWidget (iInputWidget *, iBool accept); 60void end_InputWidget (iInputWidget *, iBool accept);
61void selectAll_InputWidget (iInputWidget *); 61void selectAll_InputWidget (iInputWidget *);
62void deselect_InputWidget (iInputWidget *);
62void validate_InputWidget (iInputWidget *); 63void validate_InputWidget (iInputWidget *);
63 64
64void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); 65void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus);
diff --git a/src/ui/keys.c b/src/ui/keys.c
index 26a286bc..88efa98b 100644
--- a/src/ui/keys.c
+++ b/src/ui/keys.c
@@ -243,6 +243,8 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] =
243 { 110,{ "${menu.save.downloads}", SDLK_s, KMOD_PRIMARY, "document.save" }, 0 }, 243 { 110,{ "${menu.save.downloads}", SDLK_s, KMOD_PRIMARY, "document.save" }, 0 },
244 { 120,{ "${keys.upload}", SDLK_u, KMOD_PRIMARY, "document.upload" }, 0 }, 244 { 120,{ "${keys.upload}", SDLK_u, KMOD_PRIMARY, "document.upload" }, 0 },
245 { 121,{ "${keys.upload.edit}", SDLK_e, KMOD_PRIMARY, "document.upload copy:1" }, 0 }, 245 { 121,{ "${keys.upload.edit}", SDLK_e, KMOD_PRIMARY, "document.upload copy:1" }, 0 },
246 { 125,{ "${keys.pageinfo}", SDLK_i, KMOD_PRIMARY, "document.info" }, 0 },
247 { 126,{ "${keys.sitespec}", ',', KMOD_PRIMARY | KMOD_SHIFT, "document.sitespec" }, 0 },
246 { 130,{ "${keys.input.precedingline}", SDLK_v, KMOD_PRIMARY | KMOD_SHIFT, "input.precedingline" }, 0 }, 248 { 130,{ "${keys.input.precedingline}", SDLK_v, KMOD_PRIMARY | KMOD_SHIFT, "input.precedingline" }, 0 },
247 /* The following cannot currently be changed (built-in duplicates). */ 249 /* The following cannot currently be changed (built-in duplicates). */
248#if defined (iPlatformApple) 250#if defined (iPlatformApple)
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 3454014a..75cbbf3a 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -231,7 +231,17 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int
231 *bg = uiBackgroundUnfocusedSelection_ColorId; 231 *bg = uiBackgroundUnfocusedSelection_ColorId;
232 } 232 }
233 else { 233 else {
234 *bg = uiBackgroundSelected_ColorId; 234 const enum iGmDocumentTheme docTheme = docTheme_Prefs(prefs_App());
235 if ((docTheme == colorfulLight_GmDocumentTheme || docTheme == sepia_GmDocumentTheme) &&
236 !cmp_String(&d->widget.parent->id, "tabs.buttons")) {
237 *bg = (docTheme == sepia_GmDocumentTheme &&
238 colorTheme_App() == pureWhite_ColorTheme
239 ? tmBackground_ColorId
240 : tmBannerBackground_ColorId);
241 }
242 else {
243 *bg = uiBackgroundSelected_ColorId;
244 }
235 } 245 }
236 if (!isKeyRoot) { 246 if (!isKeyRoot) {
237 *bg = isDark_ColorTheme(colorTheme_App()) ? uiBackgroundUnfocusedSelection_ColorId 247 *bg = isDark_ColorTheme(colorTheme_App()) ? uiBackgroundUnfocusedSelection_ColorId
diff --git a/src/ui/linkinfo.c b/src/ui/linkinfo.c
index 36ab00c8..15aea16e 100644
--- a/src/ui/linkinfo.c
+++ b/src/ui/linkinfo.c
@@ -91,6 +91,10 @@ void infoText_LinkInfo(const iGmDocument *doc, iGmLinkId linkId, iString *text_o
91 appendCStr_String(text_out, "\x1b[0m"); 91 appendCStr_String(text_out, "\x1b[0m");
92 appendRange_String(text_out, (iRangecc){ parts.path.start, constEnd_String(url) }); 92 appendRange_String(text_out, (iRangecc){ parts.path.start, constEnd_String(url) });
93 } 93 }
94 else if (scheme == data_GmLinkScheme) {
95 appendCStr_String(text_out, paperclip_Icon " ");
96 append_String(text_out, prettyDataUrl_String(url, none_ColorId));
97 }
94 else if (scheme != gemini_GmLinkScheme) { 98 else if (scheme != gemini_GmLinkScheme) {
95 const size_t maxDispLen = 300; 99 const size_t maxDispLen = 300;
96 appendCStr_String(text_out, scheme == file_GmLinkScheme ? "" : globe_Icon " "); 100 appendCStr_String(text_out, scheme == file_GmLinkScheme ? "" : globe_Icon " ");
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c
index f14170ad..dc3264a2 100644
--- a/src/ui/lookupwidget.c
+++ b/src/ui/lookupwidget.c
@@ -568,7 +568,7 @@ static void presentResults_LookupWidget_(iLookupWidget *d) {
568 cstr_String(&res->label), 568 cstr_String(&res->label),
569 uiText_ColorEscape, 569 uiText_ColorEscape,
570 cstr_String(&res->meta)); 570 cstr_String(&res->meta));
571 const iString *cmd = feedEntryOpenCommand_String(&res->url, 0); 571 const iString *cmd = feedEntryOpenCommand_String(&res->url, 0, 0);
572 if (cmd) { 572 if (cmd) {
573 set_String(&item->command, cmd); 573 set_String(&item->command, cmd);
574 } 574 }
diff --git a/src/ui/root.c b/src/ui/root.c
index 6e187313..9dee50ae 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -56,7 +56,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
56#if defined (iPlatformPcDesktop) 56#if defined (iPlatformPcDesktop)
57/* TODO: Submenus wouldn't hurt here. */ 57/* TODO: Submenus wouldn't hurt here. */
58static const iMenuItem navMenuItems_[] = { 58static const iMenuItem navMenuItems_[] = {
59 { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, 59 { openWindow_Icon " ${menu.newwindow}", SDLK_n, KMOD_PRIMARY, "window.new" },
60 { add_Icon " ${menu.newtab}", SDLK_t, KMOD_PRIMARY, "tabs.new" },
60 { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, 61 { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" },
61 { "---" }, 62 { "---" },
62 { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" }, 63 { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" },
@@ -468,6 +469,10 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
468 return iFalse; 469 return iFalse;
469 } 470 }
470 else if (equal_Command(cmd, "window.setrect")) { 471 else if (equal_Command(cmd, "window.setrect")) {
472 if (hasLabel_Command(cmd, "index") &&
473 argU32Label_Command(cmd, "index") != windowIndex_Root(root->root)) {
474 return iFalse;
475 }
471 const int snap = argLabel_Command(cmd, "snap"); 476 const int snap = argLabel_Command(cmd, "snap");
472 if (snap) { 477 if (snap) {
473 iMainWindow *window = get_MainWindow(); 478 iMainWindow *window = get_MainWindow();
@@ -1059,6 +1064,8 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
1059 updateNavBarIdentity_(navBar); 1064 updateNavBarIdentity_(navBar);
1060 } 1065 }
1061 setFocus_Widget(NULL); 1066 setFocus_Widget(NULL);
1067 makePaletteGlobal_GmDocument(document_DocumentWidget(doc));
1068 refresh_Widget(findWidget_Root("doctabs"));
1062 } 1069 }
1063 else if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd)) { 1070 else if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd)) {
1064 iWidget *widget = pointer_Command(cmd); 1071 iWidget *widget = pointer_Command(cmd);
@@ -1774,6 +1781,13 @@ void showToolbar_Root(iRoot *d, iBool show) {
1774 } 1781 }
1775} 1782}
1776 1783
1784size_t windowIndex_Root(const iRoot *d) {
1785 if (type_Window(d->window) == main_WindowType) {
1786 return windowIndex_App(as_MainWindow(d->window));
1787 }
1788 return iInvalidPos;
1789}
1790
1777iInt2 size_Root(const iRoot *d) { 1791iInt2 size_Root(const iRoot *d) {
1778 return d && d->widget ? d->widget->rect.size : zero_I2(); 1792 return d && d->widget ? d->widget->rect.size : zero_I2();
1779} 1793}
diff --git a/src/ui/root.h b/src/ui/root.h
index a81ebdf7..3b053c9e 100644
--- a/src/ui/root.h
+++ b/src/ui/root.h
@@ -47,6 +47,7 @@ void showOrHideNewTabButton_Root (iRoot *);
47 47
48void notifyVisualOffsetChange_Root (iRoot *); 48void notifyVisualOffsetChange_Root (iRoot *);
49 49
50size_t windowIndex_Root (const iRoot *);
50iInt2 size_Root (const iRoot *); 51iInt2 size_Root (const iRoot *);
51iRect rect_Root (const iRoot *); 52iRect rect_Root (const iRoot *);
52iRect safeRect_Root (const iRoot *); 53iRect safeRect_Root (const iRoot *);
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index da377ac2..8a96961a 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -412,19 +412,25 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct
412 setOutline_LabelWidget(child_Widget(d->actions, 1), d->feedsMode != all_FeedsMode); 412 setOutline_LabelWidget(child_Widget(d->actions, 1), d->feedsMode != all_FeedsMode);
413 setOutline_LabelWidget(child_Widget(d->actions, 2), d->feedsMode != unread_FeedsMode); 413 setOutline_LabelWidget(child_Widget(d->actions, 2), d->feedsMode != unread_FeedsMode);
414 } 414 }
415 d->menu = makeMenu_Widget( 415 const iMenuItem menuItems[] = {
416 as_Widget(d), 416 { openTab_Icon " ${menu.opentab}", 0, 0, "feed.entry.open newtab:1" },
417 (iMenuItem[]){ { openTab_Icon " ${feeds.entry.newtab}", 0, 0, "feed.entry.opentab" }, 417 { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "feed.entry.open newtab:2" },
418 { circle_Icon " ${feeds.entry.markread}", 0, 0, "feed.entry.toggleread" }, 418#if defined (iPlatformDesktop)
419 { bookmark_Icon " ${feeds.entry.bookmark}", 0, 0, "feed.entry.bookmark" }, 419 { openWindow_Icon " ${menu.openwindow}", 0, 0, "feed.entry.open newwindow:1" },
420 { "---", 0, 0, NULL }, 420#endif
421 { page_Icon " ${feeds.entry.openfeed}", 0, 0, "feed.entry.openfeed" }, 421 { "---", 0, 0, NULL },
422 { edit_Icon " ${feeds.edit}", 0, 0, "feed.entry.edit" }, 422 { circle_Icon " ${feeds.entry.markread}", 0, 0, "feed.entry.toggleread" },
423 { whiteStar_Icon " " uiTextCaution_ColorEscape "${feeds.unsubscribe}", 0, 0, "feed.entry.unsubscribe" }, 423 { bookmark_Icon " ${feeds.entry.bookmark}", 0, 0, "feed.entry.bookmark" },
424 { "---", 0, 0, NULL }, 424 { "${menu.copyurl}", 0, 0, "feed.entry.copy" },
425 { check_Icon " ${feeds.markallread}", SDLK_a, KMOD_SHIFT, "feeds.markallread" }, 425 { "---", 0, 0, NULL },
426 { reload_Icon " ${feeds.refresh}", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" } }, 426 { page_Icon " ${feeds.entry.openfeed}", 0, 0, "feed.entry.openfeed" },
427 10); 427 { edit_Icon " ${feeds.edit}", 0, 0, "feed.entry.edit" },
428 { whiteStar_Icon " " uiTextCaution_ColorEscape "${feeds.unsubscribe}", 0, 0, "feed.entry.unsubscribe" },
429 { "---", 0, 0, NULL },
430 { check_Icon " ${feeds.markallread}", SDLK_a, KMOD_SHIFT, "feeds.markallread" },
431 { reload_Icon " ${feeds.refresh}", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" }
432 };
433 d->menu = makeMenu_Widget(as_Widget(d), menuItems, iElemCount(menuItems));
428 d->modeMenu = makeMenu_Widget( 434 d->modeMenu = makeMenu_Widget(
429 as_Widget(d), 435 as_Widget(d),
430 (iMenuItem[]){ 436 (iMenuItem[]){
@@ -487,26 +493,29 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct
487 addItem_ListWidget(d->list, item); 493 addItem_ListWidget(d->list, item);
488 iRelease(item); 494 iRelease(item);
489 } 495 }
490 d->menu = makeMenu_Widget( 496 const iMenuItem menuItems[] = {
491 as_Widget(d), 497 { openTab_Icon " ${menu.opentab}", 0, 0, "bookmark.open newtab:1" },
492 (iMenuItem[]){ { openTab_Icon " ${menu.opentab}", 0, 0, "bookmark.open newtab:1" }, 498 { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "bookmark.open newtab:2" },
493 { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "bookmark.open newtab:2" }, 499#if defined (iPlatformDesktop)
494 { "---", 0, 0, NULL }, 500 { openWindow_Icon " ${menu.openwindow}", 0, 0, "bookmark.open newwindow:1" },
495 { edit_Icon " ${menu.edit}", 0, 0, "bookmark.edit" }, 501#endif
496 { copy_Icon " ${menu.dup}", 0, 0, "bookmark.dup" }, 502 { "---", 0, 0, NULL },
497 { "${menu.copyurl}", 0, 0, "bookmark.copy" }, 503 { edit_Icon " ${menu.edit}", 0, 0, "bookmark.edit" },
498 { "---", 0, 0, NULL }, 504 { copy_Icon " ${menu.dup}", 0, 0, "bookmark.dup" },
499 { "", 0, 0, "bookmark.tag tag:subscribed" }, 505 { "${menu.copyurl}", 0, 0, "bookmark.copy" },
500 { "", 0, 0, "bookmark.tag tag:homepage" }, 506 { "---", 0, 0, NULL },
501 { "", 0, 0, "bookmark.tag tag:remotesource" }, 507 { "", 0, 0, "bookmark.tag tag:subscribed" },
502 { "---", 0, 0, NULL }, 508 { "", 0, 0, "bookmark.tag tag:homepage" },
503 { delete_Icon " " uiTextCaution_ColorEscape "${bookmark.delete}", 0, 0, "bookmark.delete" }, 509 { "", 0, 0, "bookmark.tag tag:remotesource" },
504 { "---", 0, 0, NULL }, 510 { "---", 0, 0, NULL },
505 { add_Icon " ${menu.newfolder}", 0, 0, "bookmark.addfolder" }, 511 { delete_Icon " " uiTextCaution_ColorEscape "${bookmark.delete}", 0, 0, "bookmark.delete" },
506 { upDownArrow_Icon " ${menu.sort.alpha}", 0, 0, "bookmark.sortfolder" }, 512 { "---", 0, 0, NULL },
507 { "---", 0, 0, NULL }, 513 { folder_Icon " ${menu.newfolder}", 0, 0, "bookmark.addfolder" },
508 { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" } }, 514 { upDownArrow_Icon " ${menu.sort.alpha}", 0, 0, "bookmark.sortfolder" },
509 17); 515 { "---", 0, 0, NULL },
516 { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" }
517 };
518 d->menu = makeMenu_Widget(as_Widget(d), menuItems, iElemCount(menuItems));
510 d->modeMenu = makeMenu_Widget( 519 d->modeMenu = makeMenu_Widget(
511 as_Widget(d), 520 as_Widget(d),
512 (iMenuItem[]){ { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, 521 (iMenuItem[]){ { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" },
@@ -520,7 +529,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct
520 addActionButton_SidebarWidget_(d, "${sidebar.action.bookmarks.newfolder}", 529 addActionButton_SidebarWidget_(d, "${sidebar.action.bookmarks.newfolder}",
521 "bookmarks.addfolder", !d->isEditing ? hidden_WidgetFlag : 0); 530 "bookmarks.addfolder", !d->isEditing ? hidden_WidgetFlag : 0);
522 addChildFlags_Widget(d->actions, iClob(new_Widget()), expand_WidgetFlag); 531 addChildFlags_Widget(d->actions, iClob(new_Widget()), expand_WidgetFlag);
523 iLabelWidget *btn = addActionButton_SidebarWidget_(d, 532 addActionButton_SidebarWidget_(d,
524 d->isEditing ? "${sidebar.close}" : "${sidebar.action.bookmarks.edit}", 533 d->isEditing ? "${sidebar.close}" : "${sidebar.action.bookmarks.edit}",
525 "sidebar.bookmarks.edit", 0); 534 "sidebar.bookmarks.edit", 0);
526 } 535 }
@@ -568,16 +577,21 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct
568 addItem_ListWidget(d->list, item); 577 addItem_ListWidget(d->list, item);
569 iRelease(item); 578 iRelease(item);
570 } 579 }
571 d->menu = makeMenu_Widget( 580 const iMenuItem menuItems[] = {
572 as_Widget(d), 581 { openTab_Icon " ${menu.opentab}", 0, 0, "history.open newtab:1" },
573 (iMenuItem[]){ 582 { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "history.open newtab:2" },
574 { "${menu.copyurl}", 0, 0, "history.copy" }, 583#if defined (iPlatformDesktop)
575 { bookmark_Icon " ${sidebar.entry.bookmark}", 0, 0, "history.addbookmark" }, 584 { openWindow_Icon " ${menu.openwindow}", 0, 0, "history.open newwindow:1" },
576 { "---", 0, 0, NULL }, 585#endif
577 { close_Icon " ${menu.forgeturl}", 0, 0, "history.delete" }, 586 { "---" },
578 { "---", 0, 0, NULL }, 587 { bookmark_Icon " ${sidebar.entry.bookmark}", 0, 0, "history.addbookmark" },
579 { delete_Icon " " uiTextCaution_ColorEscape "${history.clear}", 0, 0, "history.clear confirm:1" }, 588 { "${menu.copyurl}", 0, 0, "history.copy" },
580 }, 6); 589 { "---", 0, 0, NULL },
590 { close_Icon " ${menu.forgeturl}", 0, 0, "history.delete" },
591 { "---", 0, 0, NULL },
592 { delete_Icon " " uiTextCaution_ColorEscape "${history.clear}", 0, 0, "history.clear confirm:1" },
593 };
594 d->menu = makeMenu_Widget(as_Widget(d), menuItems, iElemCount(menuItems));
581 d->modeMenu = makeMenu_Widget( 595 d->modeMenu = makeMenu_Widget(
582 as_Widget(d), 596 as_Widget(d),
583 (iMenuItem[]){ 597 (iMenuItem[]){
@@ -981,7 +995,7 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si
981 } 995 }
982 case feeds_SidebarMode: { 996 case feeds_SidebarMode: {
983 postCommandString_Root(get_Root(), 997 postCommandString_Root(get_Root(),
984 feedEntryOpenCommand_String(&item->url, openTabMode_Sym(modState_Keys()))); 998 feedEntryOpenCommand_String(&item->url, openTabMode_Sym(modState_Keys()), 0));
985 break; 999 break;
986 } 1000 }
987 case bookmarks_SidebarMode: 1001 case bookmarks_SidebarMode:
@@ -1641,11 +1655,20 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1641 else if (startsWith_CStr(cmd, "feed.entry.") && d->mode == feeds_SidebarMode) { 1655 else if (startsWith_CStr(cmd, "feed.entry.") && d->mode == feeds_SidebarMode) {
1642 const iSidebarItem *item = d->contextItem; 1656 const iSidebarItem *item = d->contextItem;
1643 if (item) { 1657 if (item) {
1644 if (isCommand_Widget(w, ev, "feed.entry.opentab")) { 1658 if (isCommand_Widget(w, ev, "feed.entry.open")) {
1645 postCommandString_Root(get_Root(), feedEntryOpenCommand_String(&item->url, 1)); 1659 const char *cmd = command_UserEvent(ev);
1660 postCommandString_Root(
1661 get_Root(),
1662 feedEntryOpenCommand_String(&item->url,
1663 argLabel_Command(cmd, "newtab"),
1664 argLabel_Command(cmd, "newwindow")));
1646 return iTrue; 1665 return iTrue;
1647 } 1666 }
1648 if (isCommand_Widget(w, ev, "feed.entry.toggleread")) { 1667 else if (isCommand_Widget(w, ev, "feed.entry.copy")) {
1668 SDL_SetClipboardText(cstr_String(&item->url));
1669 return iTrue;
1670 }
1671 else if (isCommand_Widget(w, ev, "feed.entry.toggleread")) {
1649 iVisited *vis = visited_App(); 1672 iVisited *vis = visited_App();
1650 const iString *url = urlFragmentStripped_String(&item->url); 1673 const iString *url = urlFragmentStripped_String(&item->url);
1651 if (containsUrl_Visited(vis, url)) { 1674 if (containsUrl_Visited(vis, url)) {
@@ -1657,7 +1680,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1657 postCommand_App("visited.changed"); 1680 postCommand_App("visited.changed");
1658 return iTrue; 1681 return iTrue;
1659 } 1682 }
1660 if (isCommand_Widget(w, ev, "feed.entry.bookmark")) { 1683 else if (isCommand_Widget(w, ev, "feed.entry.bookmark")) {
1661 makeBookmarkCreation_Widget(&item->url, &item->label, item->icon); 1684 makeBookmarkCreation_Widget(&item->url, &item->label, item->icon);
1662 if (deviceType_App() == desktop_AppDeviceType) { 1685 if (deviceType_App() == desktop_AppDeviceType) {
1663 postCommand_App("focus.set id:bmed.title"); 1686 postCommand_App("focus.set id:bmed.title");
@@ -1706,6 +1729,18 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1706 } 1729 }
1707 return iTrue; 1730 return iTrue;
1708 } 1731 }
1732 else if (isCommand_Widget(w, ev, "history.open")) {
1733 const iSidebarItem *item = d->contextItem;
1734 if (item && !isEmpty_String(&item->url)) {
1735 const char *cmd = command_UserEvent(ev);
1736 postCommand_Widget(d,
1737 "!open newtab:%d newwindow:%d url:%s",
1738 argLabel_Command(cmd, "newtab"),
1739 argLabel_Command(cmd, "newwindow"),
1740 cstr_String(&item->url));
1741 }
1742 return iTrue;
1743 }
1709 else if (isCommand_Widget(w, ev, "history.copy")) { 1744 else if (isCommand_Widget(w, ev, "history.copy")) {
1710 const iSidebarItem *item = d->contextItem; 1745 const iSidebarItem *item = d->contextItem;
1711 if (item && !isEmpty_String(&item->url)) { 1746 if (item && !isEmpty_String(&item->url)) {
@@ -2156,28 +2191,38 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
2156 : uiTextDim_ColorId; 2191 : uiTextDim_ColorId;
2157 iUrl parts; 2192 iUrl parts;
2158 init_Url(&parts, &d->label); 2193 init_Url(&parts, &d->label);
2159 const iBool isAbout = equalCase_Rangecc(parts.scheme, "about"); 2194 const iBool isAbout = equalCase_Rangecc(parts.scheme, "about");
2160 const iBool isGemini = equalCase_Rangecc(parts.scheme, "gemini"); 2195 const iBool isGemini = equalCase_Rangecc(parts.scheme, "gemini");
2161 draw_Text(font, 2196 const iBool isData = equalCase_Rangecc(parts.scheme, "data");
2162 add_I2(topLeft_Rect(itemRect), 2197 const int queryColor = isPressing ? uiTextPressed_ColorId
2163 init_I2(3 * gap_UI, (itemHeight - lineHeight_Text(font)) / 2)), 2198 : isHover ? uiText_ColorId
2164 fg, 2199 : uiAnnotation_ColorId;
2165 "%s%s%s%s%s%s%s%s", 2200 const iInt2 textPos =
2166 isGemini ? "" : cstr_Rangecc(parts.scheme), 2201 add_I2(topLeft_Rect(itemRect),
2167 isGemini ? "" 2202 init_I2(3 * gap_UI, (itemHeight - lineHeight_Text(font)) / 2));
2168 : isAbout ? ":" 2203 if (isData) {
2169 : "://", 2204 drawRange_Text(
2170 escape_Color(isHover ? (isPressing ? uiTextPressed_ColorId 2205 font, textPos, fg, range_String(prettyDataUrl_String(&d->label, queryColor)));
2171 : uiTextFramelessHover_ColorId) 2206 }
2172 : uiTextStrong_ColorId), 2207 else {
2173 cstr_Rangecc(parts.host), 2208 draw_Text(
2174 escape_Color(fg), 2209 font,
2175 cstr_Rangecc(parts.path), 2210 textPos,
2176 !isEmpty_Range(&parts.query) ? escape_Color(isPressing ? uiTextPressed_ColorId 2211 fg,
2177 : isHover ? uiText_ColorId 2212 "%s%s%s%s%s%s%s%s",
2178 : uiAnnotation_ColorId) 2213 isGemini ? "" : cstr_Rangecc(parts.scheme),
2179 : "", 2214 isGemini ? ""
2180 !isEmpty_Range(&parts.query) ? cstr_Rangecc(parts.query) : ""); 2215 : isAbout ? ":"
2216 : "://",
2217 escape_Color(isHover ? (isPressing ? uiTextPressed_ColorId
2218 : uiTextFramelessHover_ColorId)
2219 : uiTextStrong_ColorId),
2220 cstr_Rangecc(parts.host),
2221 escape_Color(fg),
2222 cstr_Rangecc(parts.path),
2223 !isEmpty_Range(&parts.query) ? escape_Color(queryColor) : "",
2224 !isEmpty_Range(&parts.query) ? cstr_Rangecc(parts.query) : "");
2225 }
2181 } 2226 }
2182 iEndCollect(); 2227 iEndCollect();
2183 } 2228 }
diff --git a/src/ui/text.c b/src/ui/text.c
index c19aed2f..83e87d0c 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -258,8 +258,6 @@ static int cmp_PrioMapItem_(const void *a, const void *b) {
258} 258}
259 259
260struct Impl_Text { 260struct Impl_Text {
261// enum iTextFont contentFont;
262// enum iTextFont headingFont;
263 float contentFontSize; 261 float contentFontSize;
264 iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */ 262 iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */
265 int overrideFontId; /* always checked for glyphs first, regardless of which font is used */ 263 int overrideFontId; /* always checked for glyphs first, regardless of which font is used */
@@ -276,7 +274,8 @@ struct Impl_Text {
276 int ansiFlags; 274 int ansiFlags;
277 int baseFontId; /* base attributes (for restoring via escapes) */ 275 int baseFontId; /* base attributes (for restoring via escapes) */
278 int baseFgColorId; 276 int baseFgColorId;
279 iBool missingGlyphs; /* true if a glyph couldn't be found */ 277 iBool missingGlyphs; /* true if a glyph couldn't be found */
278 iChar missingChars[20]; /* rotating buffer of the latest missing characters */
280}; 279};
281 280
282iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) 281iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render)
@@ -296,6 +295,7 @@ static void setupFontVariants_Text_(iText *d, const iFontSpec *spec, int baseId)
296 /* This is the highest priority override font. */ 295 /* This is the highest priority override font. */
297 d->overrideFontId = baseId; 296 d->overrideFontId = baseId;
298 } 297 }
298 iAssert(activeText_ == d);
299 pushBack_Array(&d->fontPriorityOrder, &(iPrioMapItem){ spec->priority, baseId }); 299 pushBack_Array(&d->fontPriorityOrder, &(iPrioMapItem){ spec->priority, baseId });
300 for (enum iFontStyle style = 0; style < max_FontStyle; style++) { 300 for (enum iFontStyle style = 0; style < max_FontStyle; style++) {
301 for (enum iFontSize sizeId = 0; sizeId < max_FontSize; sizeId++) { 301 for (enum iFontSize sizeId = 0; sizeId < max_FontSize; sizeId++) {
@@ -357,6 +357,8 @@ static void initFonts_Text_(iText *d) {
357 printf("[Text] %zu font variants ready\n", size_Array(&d->fonts)); 357 printf("[Text] %zu font variants ready\n", size_Array(&d->fonts));
358#endif 358#endif
359 gap_Text = iRound(gap_UI * d->contentFontSize); 359 gap_Text = iRound(gap_UI * d->contentFontSize);
360// d->missingGlyphs = iFalse;
361// iZap(d->missingChars);
360} 362}
361 363
362static void deinitFonts_Text_(iText *d) { 364static void deinitFonts_Text_(iText *d) {
@@ -424,6 +426,7 @@ void init_Text(iText *d, SDL_Renderer *render) {
424 d->baseFontId = -1; 426 d->baseFontId = -1;
425 d->baseFgColorId = -1; 427 d->baseFgColorId = -1;
426 d->missingGlyphs = iFalse; 428 d->missingGlyphs = iFalse;
429 iZap(d->missingChars);
427 d->render = render; 430 d->render = render;
428 /* A grayscale palette for rasterized glyphs. */ { 431 /* A grayscale palette for rasterized glyphs. */ {
429 SDL_Color colors[256]; 432 SDL_Color colors[256];
@@ -497,10 +500,13 @@ static void resetCache_Text_(iText *d) {
497} 500}
498 501
499void resetFonts_Text(iText *d) { 502void resetFonts_Text(iText *d) {
503 iText *oldActive = activeText_;
504 setCurrent_Text(d); /* some routines rely on the global `activeText_` pointer */
500 deinitFonts_Text_(d); 505 deinitFonts_Text_(d);
501 deinitCache_Text_(d); 506 deinitCache_Text_(d);
502 initCache_Text_(d); 507 initCache_Text_(d);
503 initFonts_Text_(d); 508 initFonts_Text_(d);
509 setCurrent_Text(oldActive);
504} 510}
505 511
506static SDL_Palette *glyphPalette_(void) { 512static SDL_Palette *glyphPalette_(void) {
@@ -610,8 +616,23 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
610 } 616 }
611 } 617 }
612 if (!*glyphIndex) { 618 if (!*glyphIndex) {
613 activeText_->missingGlyphs = iTrue; 619 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int) ch); fflush(stderr);
614 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr); 620 iText *tx = activeText_;
621 tx->missingGlyphs = iTrue;
622 /* Remember a few of the latest missing characters. */
623 iBool gotIt = iFalse;
624 for (size_t i = 0; i < iElemCount(tx->missingChars); i++) {
625 if (tx->missingChars[i] == ch) {
626 gotIt = iTrue;
627 break;
628 }
629 }
630 if (!gotIt) {
631 memmove(tx->missingChars + 1,
632 tx->missingChars,
633 sizeof(tx->missingChars) - sizeof(tx->missingChars[0]));
634 tx->missingChars[0] = ch;
635 }
615 } 636 }
616 return d; 637 return d;
617} 638}
@@ -1459,14 +1480,14 @@ static void evenMonospaceAdvances_GlyphBuffer_(iGlyphBuffer *d, iFont *baseFont)
1459} 1480}
1460 1481
1461static iRect run_Font_(iFont *d, const iRunArgs *args) { 1482static iRect run_Font_(iFont *d, const iRunArgs *args) {
1462 const int mode = args->mode; 1483 const int mode = args->mode;
1463 const iInt2 orig = args->pos; 1484 const iInt2 orig = args->pos;
1464 iRect bounds = { orig, init_I2(0, d->height) }; 1485 iRect bounds = { orig, init_I2(0, d->height) };
1465 float xCursor = 0.0f; 1486 float xCursor = 0.0f;
1466 float yCursor = 0.0f; 1487 float yCursor = 0.0f;
1467 float xCursorMax = 0.0f; 1488 float xCursorMax = 0.0f;
1468 const iBool isMonospaced = isMonospaced_Font(d); 1489 const iBool isMonospaced = isMonospaced_Font(d);
1469 iWrapText *wrap = args->wrap; 1490 iWrapText *wrap = args->wrap;
1470 iAssert(args->text.end >= args->text.start); 1491 iAssert(args->text.end >= args->text.start);
1471 /* Split the text into a number of attributed runs that specify exactly which 1492 /* Split the text into a number of attributed runs that specify exactly which
1472 font is used and other attributes such as color. (HarfBuzz shaping is done 1493 font is used and other attributes such as color. (HarfBuzz shaping is done
@@ -2250,6 +2271,19 @@ iBool checkMissing_Text(void) {
2250 return missing; 2271 return missing;
2251} 2272}
2252 2273
2274iChar missing_Text(size_t index) {
2275 const iText *d = activeText_;
2276 if (index >= iElemCount(d->missingChars)) {
2277 return 0;
2278 }
2279 return d->missingChars[index];
2280}
2281
2282void resetMissing_Text(iText *d) {
2283 d->missingGlyphs = iFalse;
2284 iZap(d->missingChars);
2285}
2286
2253SDL_Texture *glyphCache_Text(void) { 2287SDL_Texture *glyphCache_Text(void) {
2254 return activeText_->cache; 2288 return activeText_->cache;
2255} 2289}
diff --git a/src/ui/text.h b/src/ui/text.h
index b952df84..e741880d 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -227,6 +227,8 @@ struct Impl_WrapText {
227iTextMetrics measure_WrapText (iWrapText *, int fontId); 227iTextMetrics measure_WrapText (iWrapText *, int fontId);
228iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); 228iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color);
229 229
230iChar missing_Text (size_t index);
231void resetMissing_Text (iText *);
230iBool checkMissing_Text (void); /* returns the flag, and clears it */ 232iBool checkMissing_Text (void); /* returns the flag, and clears it */
231SDL_Texture * glyphCache_Text (void); 233SDL_Texture * glyphCache_Text (void);
232 234
diff --git a/src/ui/touch.c b/src/ui/touch.c
index a178a913..21a92b80 100644
--- a/src/ui/touch.c
+++ b/src/ui/touch.c
@@ -244,7 +244,8 @@ static void dispatchNotification_Touch_(const iTouch *d, int code) {
244 .timestamp = SDL_GetTicks(), 244 .timestamp = SDL_GetTicks(),
245 .code = code, 245 .code = code,
246 .data1 = d->affinity, 246 .data1 = d->affinity,
247 .data2 = d->affinity->root 247 .data2 = d->affinity->root,
248 .windowID = id_Window(window_Widget(d->affinity)),
248 }); 249 });
249 setCurrent_Root(oldRoot); 250 setCurrent_Root(oldRoot);
250 } 251 }
diff --git a/src/ui/util.c b/src/ui/util.c
index 5dd8a0bd..4f5de7f9 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -35,6 +35,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
35#include "keys.h" 35#include "keys.h"
36#include "labelwidget.h" 36#include "labelwidget.h"
37#include "root.h" 37#include "root.h"
38#include "sitespec.h"
38#include "text.h" 39#include "text.h"
39#include "touch.h" 40#include "touch.h"
40#include "widget.h" 41#include "widget.h"
@@ -903,6 +904,7 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
903#else 904#else
904 /* Non-native custom popup menu. This may still be displayed inside a separate window. */ 905 /* Non-native custom popup menu. This may still be displayed inside a separate window. */
905 setDrawBufferEnabled_Widget(menu, iTrue); 906 setDrawBufferEnabled_Widget(menu, iTrue);
907 setFrameColor_Widget(menu, uiSeparator_ColorId);
906 setBackgroundColor_Widget(menu, uiBackgroundMenu_ColorId); 908 setBackgroundColor_Widget(menu, uiBackgroundMenu_ColorId);
907 if (deviceType_App() != desktop_AppDeviceType) { 909 if (deviceType_App() != desktop_AppDeviceType) {
908 setPadding1_Widget(menu, 2 * gap_UI); 910 setPadding1_Widget(menu, 2 * gap_UI);
@@ -1084,12 +1086,12 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
1084 setFlags_Widget(d, hidden_WidgetFlag, iFalse); 1086 setFlags_Widget(d, hidden_WidgetFlag, iFalse);
1085 setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue); 1087 setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue);
1086 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); 1088 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse);
1087 if (!isPortraitPhone) { 1089// if (!isPortraitPhone) {
1088 setFrameColor_Widget(d, uiBackgroundSelected_ColorId); 1090// setFrameColor_Widget(d, uiSeparator_ColorId);
1089 } 1091// }
1090 else { 1092// else {
1091 setFrameColor_Widget(d, none_ColorId); 1093// setFrameColor_Widget(d, none_ColorId);
1092 } 1094// }
1093 arrange_Widget(d); /* need to know the height */ 1095 arrange_Widget(d); /* need to know the height */
1094 iBool allowOverflow = iFalse; 1096 iBool allowOverflow = iFalse;
1095 /* A vertical offset determined by a possible selected label in the menu. */ 1097 /* A vertical offset determined by a possible selected label in the menu. */
@@ -1327,6 +1329,7 @@ int checkContextMenu_Widget(iWidget *menu, const SDL_Event *ev) {
1327iLabelWidget *makeMenuButton_LabelWidget(const char *label, const iMenuItem *items, size_t n) { 1329iLabelWidget *makeMenuButton_LabelWidget(const char *label, const iMenuItem *items, size_t n) {
1328 iLabelWidget *button = new_LabelWidget(label, "menu.open"); 1330 iLabelWidget *button = new_LabelWidget(label, "menu.open");
1329 iWidget *menu = makeMenu_Widget(as_Widget(button), items, n); 1331 iWidget *menu = makeMenu_Widget(as_Widget(button), items, n);
1332 setFrameColor_Widget(menu, uiBackgroundSelected_ColorId);
1330 setId_Widget(menu, "menu"); 1333 setId_Widget(menu, "menu");
1331 return button; 1334 return button;
1332} 1335}
@@ -1383,6 +1386,9 @@ void updateDropdownSelection_LabelWidget(iLabelWidget *dropButton, const char *s
1383 updateText_LabelWidget(dropButton, 1386 updateText_LabelWidget(dropButton,
1384 replaceNewlinesWithDash_(text_LabelWidget(item))); 1387 replaceNewlinesWithDash_(text_LabelWidget(item)));
1385 checkIcon_LabelWidget(dropButton); 1388 checkIcon_LabelWidget(dropButton);
1389 if (!icon_LabelWidget(dropButton)) {
1390 setIcon_LabelWidget(dropButton, icon_LabelWidget(item));
1391 }
1386 } 1392 }
1387 } 1393 }
1388 } 1394 }
@@ -1709,13 +1715,14 @@ iLabelWidget *addDialogTitle_Widget(iWidget *dlg, const char *text, const char *
1709} 1715}
1710 1716
1711static void acceptValueInput_(iWidget *dlg) { 1717static void acceptValueInput_(iWidget *dlg) {
1712 const iInputWidget *input = findChild_Widget(dlg, "input"); 1718 iInputWidget *input = findChild_Widget(dlg, "input");
1713 if (!isEmpty_String(id_Widget(dlg))) { 1719 if (!isEmpty_String(id_Widget(dlg))) {
1714 const iString *val = text_InputWidget(input); 1720 const iString *val = text_InputWidget(input);
1715 postCommandf_App("%s arg:%d value:%s", 1721 postCommandf_App("%s arg:%d value:%s",
1716 cstr_String(id_Widget(dlg)), 1722 cstr_String(id_Widget(dlg)),
1717 toInt_String(val), 1723 toInt_String(val),
1718 cstr_String(val)); 1724 cstr_String(val));
1725 setBackupFileName_InputWidget(input, NULL);
1719 } 1726 }
1720} 1727}
1721 1728
@@ -1779,6 +1786,7 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) {
1779 else if (equal_Command(cmd, "valueinput.set")) { 1786 else if (equal_Command(cmd, "valueinput.set")) {
1780 iInputWidget *input = findChild_Widget(dlg, "input"); 1787 iInputWidget *input = findChild_Widget(dlg, "input");
1781 setTextUndoableCStr_InputWidget(input, suffixPtr_Command(cmd, "text"), iTrue); 1788 setTextUndoableCStr_InputWidget(input, suffixPtr_Command(cmd, "text"), iTrue);
1789 deselect_InputWidget(input);
1782 validate_InputWidget(input); 1790 validate_InputWidget(input);
1783 return iTrue; 1791 return iTrue;
1784 } 1792 }
@@ -2495,6 +2503,7 @@ iWidget *makePreferences_Widget(void) {
2495 { "input id:prefs.searchurl url:1 noheading:1" }, 2503 { "input id:prefs.searchurl url:1 noheading:1" },
2496 { "padding" }, 2504 { "padding" },
2497 { "toggle id:prefs.bookmarks.addbottom" }, 2505 { "toggle id:prefs.bookmarks.addbottom" },
2506 { "toggle id:prefs.dataurl.openimages" },
2498 { "toggle id:prefs.archive.openindex" }, 2507 { "toggle id:prefs.archive.openindex" },
2499 { "radio device:1 id:prefs.pinsplit", 0, 0, (const void *) pinSplitItems }, 2508 { "radio device:1 id:prefs.pinsplit", 0, 0, (const void *) pinSplitItems },
2500 { "padding" }, 2509 { "padding" },
@@ -2567,6 +2576,7 @@ iWidget *makePreferences_Widget(void) {
2567 const iMenuItem networkPanelItems[] = { 2576 const iMenuItem networkPanelItems[] = {
2568 { "title id:heading.prefs.network" }, 2577 { "title id:heading.prefs.network" },
2569 { "toggle id:prefs.decodeurls" }, 2578 { "toggle id:prefs.decodeurls" },
2579 { "input id:prefs.urlsize maxlen:10 selectall:1" },
2570 { "padding" }, 2580 { "padding" },
2571 { "input id:prefs.cachesize maxlen:4 selectall:1 unit:mb" }, 2581 { "input id:prefs.cachesize maxlen:4 selectall:1 unit:mb" },
2572 { "input id:prefs.memorysize maxlen:4 selectall:1 unit:mb" }, 2582 { "input id:prefs.memorysize maxlen:4 selectall:1 unit:mb" },
@@ -2640,8 +2650,9 @@ iWidget *makePreferences_Widget(void) {
2640 setUrlContent_InputWidget(searchUrl, iTrue); 2650 setUrlContent_InputWidget(searchUrl, iTrue);
2641 addDialogPadding_(headings, values); 2651 addDialogPadding_(headings, values);
2642 addDialogToggle_(headings, values, "${prefs.hoverlink}", "prefs.hoverlink"); 2652 addDialogToggle_(headings, values, "${prefs.hoverlink}", "prefs.hoverlink");
2643 addDialogToggle_(headings, values, "${prefs.bookmarks.addbottom}", "prefs.bookmarks.addbottom"); 2653 addDialogToggle_(headings, values, "${prefs.dataurl.openimages}", "prefs.dataurl.openimages");
2644 addDialogToggle_(headings, values, "${prefs.archive.openindex}", "prefs.archive.openindex"); 2654 addDialogToggle_(headings, values, "${prefs.archive.openindex}", "prefs.archive.openindex");
2655 addDialogToggle_(headings, values, "${prefs.bookmarks.addbottom}", "prefs.bookmarks.addbottom");
2645 if (deviceType_App() != phone_AppDeviceType) { 2656 if (deviceType_App() != phone_AppDeviceType) {
2646 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.pinsplit}"))); 2657 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.pinsplit}")));
2647 iWidget *pinSplit = new_Widget(); 2658 iWidget *pinSplit = new_Widget();
@@ -2900,6 +2911,7 @@ iWidget *makePreferences_Widget(void) {
2900 appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.network}", '6', &headings, &values); 2911 appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.network}", '6', &headings, &values);
2901 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.decodeurls}"))); 2912 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.decodeurls}")));
2902 addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls"))); 2913 addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls")));
2914 addPrefsInputWithHeading_(headings, values, "prefs.urlsize", iClob(new_InputWidget(10)));
2903 /* Cache size. */ { 2915 /* Cache size. */ {
2904 iInputWidget *cache = new_InputWidget(4); 2916 iInputWidget *cache = new_InputWidget(4);
2905 setSelectAllOnFocus_InputWidget(cache, iTrue); 2917 setSelectAllOnFocus_InputWidget(cache, iTrue);
@@ -3120,7 +3132,7 @@ iWidget *makeBookmarkCreation_Widget(const iString *url, const iString *title, i
3120 3132
3121static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { 3133static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) {
3122 if (equal_Command(cmd, "cancel")) { 3134 if (equal_Command(cmd, "cancel")) {
3123 setupSheetTransition_Mobile(dlg, iFalse); 3135 setupSheetTransition_Mobile(dlg, 0);
3124 destroy_Widget(dlg); 3136 destroy_Widget(dlg);
3125 return iTrue; 3137 return iTrue;
3126 } 3138 }
@@ -3163,15 +3175,14 @@ static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) {
3163} 3175}
3164 3176
3165iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { 3177iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) {
3166 const char *headingText = bookmarkId ? uiHeading_ColorEscape "${heading.feedcfg}" 3178 iWidget *dlg;
3167 : uiHeading_ColorEscape "${heading.subscribe}"; 3179 const char *headingText = bookmarkId ? "${heading.feedcfg}" : "${heading.subscribe}";
3168 const iMenuItem actions[] = { { "${cancel}" }, 3180 const iMenuItem actions[] = { { "${cancel}" },
3169 { bookmarkId ? uiTextCaution_ColorEscape "${dlg.feed.save}" 3181 { bookmarkId ? uiTextCaution_ColorEscape "${dlg.feed.save}"
3170 : uiTextCaution_ColorEscape "${dlg.feed.sub}", 3182 : uiTextCaution_ColorEscape "${dlg.feed.sub}",
3171 SDLK_RETURN, 3183 SDLK_RETURN,
3172 KMOD_PRIMARY, 3184 KMOD_PRIMARY,
3173 format_CStr("feedcfg.accept bmid:%d", bookmarkId) } }; 3185 format_CStr("feedcfg.accept bmid:%d", bookmarkId) } };
3174 iWidget *dlg;
3175 if (isUsingPanelLayout_Mobile()) { 3186 if (isUsingPanelLayout_Mobile()) {
3176 const iMenuItem typeItems[] = { 3187 const iMenuItem typeItems[] = {
3177 { "button id:feedcfg.type.gemini label:dlg.feed.type.gemini", 0, 0, "feedcfg.type arg:0" }, 3188 { "button id:feedcfg.type.gemini label:dlg.feed.type.gemini", 0, 0, "feedcfg.type arg:0" },
@@ -3228,6 +3239,111 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) {
3228 return dlg; 3239 return dlg;
3229} 3240}
3230 3241
3242/*----------------------------------------------------------------------------------------------*/
3243
3244static void siteSpecificThemeChanged_(const iWidget *dlg) {
3245 iDocumentWidget *doc = document_App();
3246 setThemeSeed_GmDocument((iGmDocument *) document_DocumentWidget(doc),
3247 urlPaletteSeed_String(url_DocumentWidget(doc)),
3248 urlThemeSeed_String(url_DocumentWidget(doc)));
3249 postCommand_App("theme.changed");
3250}
3251
3252static const iString *siteSpecificRoot_(const iWidget *dlg) {
3253 return collect_String(suffix_Command(cstr_String(id_Widget(dlg)), "site"));
3254}
3255
3256static void updateSiteSpecificTheme_(iInputWidget *palSeed, void *context) {
3257 iWidget *dlg = context;
3258 const iString *siteRoot = siteSpecificRoot_(dlg);
3259 setValueString_SiteSpec(siteRoot, paletteSeed_SiteSpecKey, text_InputWidget(palSeed));
3260 siteSpecificThemeChanged_(dlg);
3261 /* Allow seeing the new theme. */
3262 setFlags_Widget(dlg, noFadeBackground_WidgetFlag, iTrue);
3263}
3264
3265static void closeSiteSpecific_(iWidget *dlg) {
3266 setupSheetTransition_Mobile(dlg, 0);
3267 delete_String(userData_Object(dlg)); /* saved original palette seed */
3268 destroy_Widget(dlg);
3269}
3270
3271static iBool siteSpecificSettingsHandler_(iWidget *dlg, const char *cmd) {
3272 if (equal_Command(cmd, "cancel")) {
3273 const iBool wasNoFade = (flags_Widget(dlg) & noFadeBackground_WidgetFlag) != 0;
3274 iInputWidget *palSeed = findChild_Widget(dlg, "sitespec.palette");
3275 setText_InputWidget(palSeed, userData_Object(dlg));
3276 updateSiteSpecificTheme_(palSeed, dlg);
3277 setFlags_Widget(dlg, noFadeBackground_WidgetFlag, wasNoFade);
3278 closeSiteSpecific_(dlg);
3279 return iTrue;
3280 }
3281 if (startsWith_CStr(cmd, "input.ended id:sitespec.palette")) {
3282 setFlags_Widget(dlg, noFadeBackground_WidgetFlag, iFalse);
3283 refresh_Widget(dlg);
3284 siteSpecificThemeChanged_(dlg);
3285 return iTrue;
3286 }
3287 if (equal_Command(cmd, "sitespec.accept")) {
3288 const iInputWidget *palSeed = findChild_Widget(dlg, "sitespec.palette");
3289 const iBool warnAnsi = isSelected_Widget(findChild_Widget(dlg, "sitespec.ansi"));
3290 const iString *siteRoot = siteSpecificRoot_(dlg);
3291 int dismissed = value_SiteSpec(siteRoot, dismissWarnings_SiteSpecKey);
3292 iChangeFlags(dismissed, ansiEscapes_GmDocumentWarning, !warnAnsi);
3293 setValue_SiteSpec(siteRoot, dismissWarnings_SiteSpecKey, dismissed);
3294 setValueString_SiteSpec(siteRoot, paletteSeed_SiteSpecKey, text_InputWidget(palSeed));
3295 siteSpecificThemeChanged_(dlg);
3296 /* Note: The active DocumentWidget may actually be different than when opening the dialog. */
3297 closeSiteSpecific_(dlg);
3298 return iTrue;
3299 }
3300 return iFalse;
3301}
3302
3303iWidget *makeSiteSpecificSettings_Widget(const iString *url) {
3304 iWidget *dlg;
3305 const iMenuItem actions[] = {
3306 { "${cancel}" },
3307 { "${sitespec.accept}", SDLK_RETURN, KMOD_PRIMARY, "sitespec.accept" }
3308 };
3309 if (isUsingPanelLayout_Mobile()) {
3310 iAssert(iFalse);
3311 }
3312 else {
3313 iWidget *headings, *values;
3314 dlg = makeSheet_Widget(format_CStr("sitespec site:%s", cstr_Rangecc(urlRoot_String(url))));
3315 addDialogTitle_(dlg, "${heading.sitespec}", "heading.sitespec");
3316 addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values)));
3317 iInputWidget *palSeed = new_InputWidget(0);
3318 setHint_InputWidget(palSeed, cstr_Block(urlThemeSeed_String(url)));
3319 addPrefsInputWithHeading_(headings, values, "sitespec.palette", iClob(palSeed));
3320 addDialogToggle_(headings, values, "${sitespec.ansi}", "sitespec.ansi");
3321 addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions))));
3322 addChild_Widget(get_Root()->widget, iClob(dlg));
3323 as_Widget(palSeed)->rect.size.x = 60 * gap_UI;
3324 arrange_Widget(dlg);
3325 }
3326 /* Initialize. */ {
3327 const iString *site = collectNewRange_String(urlRoot_String(url));
3328 setToggle_Widget(findChild_Widget(dlg, "sitespec.ansi"),
3329 ~value_SiteSpec(site, dismissWarnings_SiteSpecKey) & ansiEscapes_GmDocumentWarning);
3330 setText_InputWidget(findChild_Widget(dlg, "sitespec.palette"),
3331 valueString_SiteSpec(site, paletteSeed_SiteSpecKey));
3332 /* Keep a copy of the original palette seed for restoring on cancel. */
3333 setUserData_Object(dlg, copy_String(valueString_SiteSpec(site, paletteSeed_SiteSpecKey)));
3334 if (!isUsingPanelLayout_Mobile()) {
3335 setValidator_InputWidget(findChild_Widget(dlg, "sitespec.palette"),
3336 updateSiteSpecificTheme_, dlg);
3337 }
3338 }
3339 setCommandHandler_Widget(dlg, siteSpecificSettingsHandler_);
3340 setupSheetTransition_Mobile(dlg, incoming_TransitionFlag);
3341 setFocus_Widget(findChild_Widget(dlg, "sitespec.palette"));
3342 return dlg;
3343}
3344
3345/*----------------------------------------------------------------------------------------------*/
3346
3231iWidget *makeIdentityCreation_Widget(void) { 3347iWidget *makeIdentityCreation_Widget(void) {
3232 const iMenuItem actions[] = { { "${dlg.newident.more}", 0, 0, "ident.showmore" }, 3348 const iMenuItem actions[] = { { "${dlg.newident.more}", 0, 0, "ident.showmore" },
3233 { "---" }, 3349 { "---" },
@@ -3451,6 +3567,54 @@ iWidget *makeTranslation_Widget(iWidget *parent) {
3451 return dlg; 3567 return dlg;
3452} 3568}
3453 3569
3570iWidget *makeGlyphFinder_Widget(void) {
3571 iString msg;
3572 iString command;
3573 init_String(&msg);
3574 initCStr_String(&command, "!font.find chars:");
3575 for (size_t i = 0; ; i++) {
3576 iChar ch = missing_Text(i);
3577 if (!ch) break;
3578 appendFormat_String(&msg, " U+%04X", ch);
3579 appendChar_String(&command, ch);
3580 }
3581 iArray items;
3582 init_Array(&items, sizeof(iMenuItem));
3583 if (!isEmpty_String(&msg)) {
3584 prependCStr_String(&msg, "${dlg.glyphfinder.missing} ");
3585 appendCStr_String(&msg, "\n\n${dlg.glyphfinder.help}");
3586 pushBackN_Array(
3587 &items,
3588 (iMenuItem[]){
3589 { "${menu.fonts}", 0, 0, "!open newtab:1 url:about:fonts" },
3590 { "${dlg.glyphfinder.disable}", 0, 0, "prefs.font.warnmissing.changed arg:0" },
3591 { "---" },
3592 { uiTextCaution_ColorEscape magnifyingGlass_Icon " ${dlg.glyphfinder.search}",
3593 0,
3594 0,
3595 cstr_String(&command) },
3596 { "${close}", 0, 0, "cancel" } },
3597 5);
3598 }
3599 else {
3600 setCStr_String(&msg, "${dlg.glyphfinder.help.empty}");
3601 pushBackN_Array(&items,
3602 (iMenuItem[]){ { "${menu.reload}", 0, 0, "navigate.reload" },
3603 { "${close}", 0, 0, "cancel" } },
3604 2);
3605 }
3606 iWidget *dlg = makeQuestion_Widget("${heading.glyphfinder}", cstr_String(&msg),
3607 constData_Array(&items),
3608 size_Array(&items));
3609 arrange_Widget(dlg);
3610 deinit_Array(&items);
3611 deinit_String(&command);
3612 deinit_String(&msg);
3613 return dlg;
3614}
3615
3616/*----------------------------------------------------------------------------------------------*/
3617
3454void init_PerfTimer(iPerfTimer *d) { 3618void init_PerfTimer(iPerfTimer *d) {
3455 d->ticks = SDL_GetPerformanceCounter(); 3619 d->ticks = SDL_GetPerformanceCounter();
3456} 3620}
diff --git a/src/ui/util.h b/src/ui/util.h
index 98ce784c..31c8cedc 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -336,12 +336,14 @@ iWidget * makeQuestion_Widget (const char *title, const char *msg,
336iWidget * makePreferences_Widget (void); 336iWidget * makePreferences_Widget (void);
337void updatePreferencesLayout_Widget (iWidget *prefs); 337void updatePreferencesLayout_Widget (iWidget *prefs);
338 338
339iWidget * makeBookmarkEditor_Widget (void); 339iWidget * makeBookmarkEditor_Widget (void);
340void setBookmarkEditorFolder_Widget(iWidget *editor, uint32_t folderId); 340void setBookmarkEditorFolder_Widget (iWidget *editor, uint32_t folderId);
341iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *title, iChar icon); 341iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *title, iChar icon);
342iWidget * makeIdentityCreation_Widget (void); 342iWidget * makeIdentityCreation_Widget (void);
343iWidget * makeFeedSettings_Widget (uint32_t bookmarkId); 343iWidget * makeFeedSettings_Widget (uint32_t bookmarkId);
344iWidget * makeTranslation_Widget (iWidget *parent); 344iWidget * makeSiteSpecificSettings_Widget (const iString *url);
345iWidget * makeTranslation_Widget (iWidget *parent);
346iWidget * makeGlyphFinder_Widget (void);
345 347
346const char * languageId_String (const iString *menuItemLabel); 348const char * languageId_String (const iString *menuItemLabel);
347int languageIndex_CStr (const char *langId); 349int languageIndex_CStr (const char *langId);
diff --git a/src/ui/widget.c b/src/ui/widget.c
index fc754b7a..2e878878 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -168,7 +168,8 @@ void deinit_Widget(iWidget *d) {
168 if (d->flags & visualOffset_WidgetFlag) { 168 if (d->flags & visualOffset_WidgetFlag) {
169 removeTicker_App(visualOffsetAnimation_Widget_, d); 169 removeTicker_App(visualOffsetAnimation_Widget_, d);
170 } 170 }
171 iWindow *win = get_Window(); 171 iWindow *win = d->root->window;
172 iAssert(win);
172 if (win->lastHover == d) { 173 if (win->lastHover == d) {
173 win->lastHover = NULL; 174 win->lastHover = NULL;
174 } 175 }
diff --git a/src/ui/window.c b/src/ui/window.c
index 47abf878..b0de0557 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -80,6 +80,7 @@ iDefineTypeConstructionArgs(MainWindow, (iRect rect), rect)
80#if defined (iHaveNativeMenus) 80#if defined (iHaveNativeMenus)
81/* Using native menus. */ 81/* Using native menus. */
82static const iMenuItem fileMenuItems_[] = { 82static const iMenuItem fileMenuItems_[] = {
83 { "${menu.newwindow}", SDLK_n, KMOD_PRIMARY, "window.new" },
83 { "${menu.newtab}", SDLK_t, KMOD_PRIMARY, "tabs.new" }, 84 { "${menu.newtab}", SDLK_t, KMOD_PRIMARY, "tabs.new" },
84 { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, 85 { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" },
85 { "---", 0, 0, NULL }, 86 { "---", 0, 0, NULL },
@@ -210,7 +211,9 @@ static void windowSizeChanged_MainWindow_(iMainWindow *d) {
210 211
211static void setupUserInterface_MainWindow(iMainWindow *d) { 212static void setupUserInterface_MainWindow(iMainWindow *d) {
212#if defined (iHaveNativeMenus) 213#if defined (iHaveNativeMenus)
213 insertMacMenus_(); 214 if (numWindows_App() == 0) {
215 insertMacMenus_(); /* TODO: Shouldn't this be in the App? */
216 }
214#endif 217#endif
215 /* One root is created by default. */ 218 /* One root is created by default. */
216 d->base.roots[0] = new_Root(); 219 d->base.roots[0] = new_Root();
@@ -246,6 +249,7 @@ static void updateSize_MainWindow_(iMainWindow *d, iBool notifyAlways) {
246 249
247void drawWhileResizing_MainWindow(iMainWindow *d, int w, int h) { 250void drawWhileResizing_MainWindow(iMainWindow *d, int w, int h) {
248 if (!isDrawing_) { 251 if (!isDrawing_) {
252 setCurrent_Window(d);
249 draw_MainWindow(d); 253 draw_MainWindow(d);
250 } 254 }
251} 255}
@@ -647,6 +651,7 @@ void init_MainWindow(iMainWindow *d, iRect rect) {
647} 651}
648 652
649void deinit_MainWindow(iMainWindow *d) { 653void deinit_MainWindow(iMainWindow *d) {
654 removeWindow_App(d);
650 if (d->backBuf) { 655 if (d->backBuf) {
651 SDL_DestroyTexture(d->backBuf); 656 SDL_DestroyTexture(d->backBuf);
652 } 657 }
@@ -677,6 +682,7 @@ iBool isFullscreen_MainWindow(const iMainWindow *d) {
677} 682}
678 683
679iRoot *findRoot_Window(const iWindow *d, const iWidget *widget) { 684iRoot *findRoot_Window(const iWindow *d, const iWidget *widget) {
685
680 while (widget->parent) { 686 while (widget->parent) {
681 widget = widget->parent; 687 widget = widget->parent;
682 } 688 }
@@ -830,6 +836,9 @@ static void savePlace_MainWindow_(iAny *mainWindow) {
830} 836}
831 837
832static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) { 838static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) {
839 if (ev->windowID != SDL_GetWindowID(d->base.win)) {
840 return iFalse;
841 }
833 switch (ev->event) { 842 switch (ev->event) {
834#if defined(iPlatformDesktop) 843#if defined(iPlatformDesktop)
835 case SDL_WINDOWEVENT_EXPOSED: 844 case SDL_WINDOWEVENT_EXPOSED:
@@ -857,7 +866,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent
857 if (d->base.isMinimized) { 866 if (d->base.isMinimized) {
858 return iFalse; 867 return iFalse;
859 } 868 }
860 closePopups_App(); 869 closePopups_App(iFalse);
861 checkPixelRatioChange_Window_(as_Window(d)); 870 checkPixelRatioChange_Window_(as_Window(d));
862 const iInt2 newPos = init_I2(ev->data1, ev->data2); 871 const iInt2 newPos = init_I2(ev->data1, ev->data2);
863 if (isEqual_I2(newPos, init1_I2(-32000))) { /* magic! */ 872 if (isEqual_I2(newPos, init1_I2(-32000))) { /* magic! */
@@ -907,7 +916,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent
907 // updateSize_Window_(d, iTrue); 916 // updateSize_Window_(d, iTrue);
908 return iTrue; 917 return iTrue;
909 } 918 }
910 closePopups_App(); 919 closePopups_App(iFalse);
911 if (unsnap_MainWindow_(d, NULL)) { 920 if (unsnap_MainWindow_(d, NULL)) {
912 return iTrue; 921 return iTrue;
913 } 922 }
@@ -929,7 +938,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent
929 return iTrue; 938 return iTrue;
930 case SDL_WINDOWEVENT_MINIMIZED: 939 case SDL_WINDOWEVENT_MINIMIZED:
931 d->base.isMinimized = iTrue; 940 d->base.isMinimized = iTrue;
932 closePopups_App(); 941 closePopups_App(iTrue);
933 return iTrue; 942 return iTrue;
934#else /* if defined (!iPlatformDesktop) */ 943#else /* if defined (!iPlatformDesktop) */
935 case SDL_WINDOWEVENT_RESIZED: 944 case SDL_WINDOWEVENT_RESIZED:
@@ -953,6 +962,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent
953 setCapsLockDown_Keys(iFalse); 962 setCapsLockDown_Keys(iFalse);
954 postCommand_App("window.focus.gained"); 963 postCommand_App("window.focus.gained");
955 d->base.isExposed = iTrue; 964 d->base.isExposed = iTrue;
965 setActiveWindow_App(d);
956#if !defined (iPlatformDesktop) 966#if !defined (iPlatformDesktop)
957 /* Returned to foreground, may have lost buffered content. */ 967 /* Returned to foreground, may have lost buffered content. */
958 invalidate_MainWindow_(d, iTrue); 968 invalidate_MainWindow_(d, iTrue);
@@ -964,12 +974,17 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent
964#if !defined (iPlatformDesktop) 974#if !defined (iPlatformDesktop)
965 setFreezeDraw_MainWindow(d, iTrue); 975 setFreezeDraw_MainWindow(d, iTrue);
966#endif 976#endif
967 closePopups_App(); 977 closePopups_App(iTrue);
968 return iFalse; 978 return iFalse;
969 case SDL_WINDOWEVENT_TAKE_FOCUS: 979 case SDL_WINDOWEVENT_TAKE_FOCUS:
970 SDL_SetWindowInputFocus(d->base.win); 980 SDL_SetWindowInputFocus(d->base.win);
971 postRefresh_App(); 981 postRefresh_App();
972 return iTrue; 982 return iTrue;
983 case SDL_WINDOWEVENT_CLOSE:
984 if (numWindows_App() > 1) {
985 closeWindow_App(d);
986 }
987 return iTrue;
973 default: 988 default:
974 break; 989 break;
975 } 990 }
@@ -1009,7 +1024,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
1009 } 1024 }
1010 } 1025 }
1011 case SDL_RENDER_TARGETS_RESET: 1026 case SDL_RENDER_TARGETS_RESET:
1012 case SDL_RENDER_DEVICE_RESET: { 1027 case SDL_RENDER_DEVICE_RESET: {
1013 if (mw) { 1028 if (mw) {
1014 invalidate_MainWindow_(mw, iTrue /* force full reset */); 1029 invalidate_MainWindow_(mw, iTrue /* force full reset */);
1015 } 1030 }
@@ -1095,7 +1110,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
1095 event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) { 1110 event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {
1096 if (mouseGrab_Widget()) { 1111 if (mouseGrab_Widget()) {
1097 iWidget *grabbed = mouseGrab_Widget(); 1112 iWidget *grabbed = mouseGrab_Widget();
1098 setCurrent_Root(findRoot_Window(d, grabbed)); 1113 setCurrent_Root(grabbed->root /* findRoot_Window(d, grabbed)*/);
1099 wasUsed = dispatchEvent_Widget(grabbed, &event); 1114 wasUsed = dispatchEvent_Widget(grabbed, &event);
1100 } 1115 }
1101 } 1116 }
@@ -1164,7 +1179,33 @@ iLocalDef iBool isEscapeKeypress_(const SDL_Event *ev) {
1164 return (ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP) && ev->key.keysym.sym == SDLK_ESCAPE; 1179 return (ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP) && ev->key.keysym.sym == SDLK_ESCAPE;
1165} 1180}
1166 1181
1182static uint32_t windowId_SDLEvent_(const SDL_Event *ev) {
1183 switch (ev->type) {
1184 case SDL_MOUSEBUTTONDOWN:
1185 case SDL_MOUSEBUTTONUP:
1186 return ev->button.windowID;
1187 case SDL_MOUSEMOTION:
1188 return ev->motion.windowID;
1189 case SDL_MOUSEWHEEL:
1190 return ev->wheel.windowID;
1191 case SDL_KEYDOWN:
1192 case SDL_KEYUP:
1193 return ev->key.windowID;
1194 case SDL_TEXTINPUT:
1195 return ev->text.windowID;
1196 case SDL_USEREVENT:
1197 return ev->user.windowID;
1198 default:
1199 return 0;
1200 }
1201}
1202
1167iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) { 1203iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) {
1204 /* For the right window? */
1205 const uint32_t evWin = windowId_SDLEvent_(ev);
1206 if (evWin && evWin != id_Window(d)) {
1207 return iFalse; /* Meant for a different window. */
1208 }
1168 if (ev->type == SDL_MOUSEMOTION) { 1209 if (ev->type == SDL_MOUSEMOTION) {
1169 /* Hover widget may change. */ 1210 /* Hover widget may change. */
1170 setHover_Widget(NULL); 1211 setHover_Widget(NULL);
@@ -1526,6 +1567,23 @@ void setKeyboardHeight_MainWindow(iMainWindow *d, int height) {
1526 } 1567 }
1527} 1568}
1528 1569
1570iObjectList *listDocuments_MainWindow(iMainWindow *d, const iRoot *rootOrNull) {
1571 iObjectList *docs = new_ObjectList();
1572 iForIndices(i, d->base.roots) {
1573 iRoot *root = d->base.roots[i];
1574 if (!root) continue;
1575 if (!rootOrNull || root == rootOrNull) {
1576 const iWidget *tabs = findChild_Widget(root->widget, "doctabs");
1577 iForEach(ObjectList, i, children_Widget(findChild_Widget(tabs, "tabs.pages"))) {
1578 if (isInstance_Object(i.object, &Class_DocumentWidget)) {
1579 pushBack_ObjectList(docs, i.object);
1580 }
1581 }
1582 }
1583 }
1584 return docs;
1585}
1586
1529void checkPendingSplit_MainWindow(iMainWindow *d) { 1587void checkPendingSplit_MainWindow(iMainWindow *d) {
1530 if (d->splitMode != d->pendingSplitMode) { 1588 if (d->splitMode != d->pendingSplitMode) {
1531 setSplitMode_MainWindow(d, d->pendingSplitMode); 1589 setSplitMode_MainWindow(d, d->pendingSplitMode);
@@ -1549,6 +1607,7 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) {
1549 } 1607 }
1550 iWindow *w = as_Window(d); 1608 iWindow *w = as_Window(d);
1551 iAssert(current_Root() == NULL); 1609 iAssert(current_Root() == NULL);
1610 setCurrent_Window(w);
1552 if (d->splitMode != splitMode) { 1611 if (d->splitMode != splitMode) {
1553 int oldCount = numRoots_Window(w); 1612 int oldCount = numRoots_Window(w);
1554 setFreezeDraw_MainWindow(d, iTrue); 1613 setFreezeDraw_MainWindow(d, iTrue);
@@ -1577,8 +1636,8 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) {
1577 /* The last child is the [+] button for adding a tab. */ 1636 /* The last child is the [+] button for adding a tab. */
1578 moveTabButtonToEnd_Widget(findChild_Widget(docTabs, "newtab")); 1637 moveTabButtonToEnd_Widget(findChild_Widget(docTabs, "newtab"));
1579 setFlags_Widget(findWidget_Root("navbar.unsplit"), hidden_WidgetFlag, iTrue); 1638 setFlags_Widget(findWidget_Root("navbar.unsplit"), hidden_WidgetFlag, iTrue);
1580 iRelease(tabs);
1581 postCommandf_App("tabs.switch id:%s", cstr_String(id_Widget(constAs_Widget(curPage)))); 1639 postCommandf_App("tabs.switch id:%s", cstr_String(id_Widget(constAs_Widget(curPage))));
1640 iRelease(tabs);
1582 } 1641 }
1583 else if (oldCount == 1 && splitMode) { 1642 else if (oldCount == 1 && splitMode) {
1584 /* Add a second root. */ 1643 /* Add a second root. */
diff --git a/src/ui/window.h b/src/ui/window.h
index 5abf23eb..c3c34e1b 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -139,7 +139,7 @@ iAnyObject * hitChild_Window (const iWindow *, iInt2 coord);
139uint32_t frameTime_Window (const iWindow *); 139uint32_t frameTime_Window (const iWindow *);
140SDL_Renderer * renderer_Window (const iWindow *); 140SDL_Renderer * renderer_Window (const iWindow *);
141int numRoots_Window (const iWindow *); 141int numRoots_Window (const iWindow *);
142iRoot * findRoot_Window (const iWindow *, const iWidget *widget); 142//iRoot * findRoot_Window (const iWindow *, const iWidget *widget);
143iRoot * otherRoot_Window (const iWindow *, iRoot *root); 143iRoot * otherRoot_Window (const iWindow *, iRoot *root);
144 144
145iBool processEvent_Window (iWindow *, const SDL_Event *); 145iBool processEvent_Window (iWindow *, const SDL_Event *);
@@ -187,6 +187,7 @@ void setTitle_MainWindow (iMainWindow *, const iString *title
187void setSnap_MainWindow (iMainWindow *, int snapMode); 187void setSnap_MainWindow (iMainWindow *, int snapMode);
188void setFreezeDraw_MainWindow (iMainWindow *, iBool freezeDraw); 188void setFreezeDraw_MainWindow (iMainWindow *, iBool freezeDraw);
189void setKeyboardHeight_MainWindow (iMainWindow *, int height); 189void setKeyboardHeight_MainWindow (iMainWindow *, int height);
190iObjectList *listDocuments_MainWindow (iMainWindow *, const iRoot *rootOrNull);
190void setSplitMode_MainWindow (iMainWindow *, int splitMode); 191void setSplitMode_MainWindow (iMainWindow *, int splitMode);
191void checkPendingSplit_MainWindow (iMainWindow *); 192void checkPendingSplit_MainWindow (iMainWindow *);
192void swapRoots_MainWindow (iMainWindow *); 193void swapRoots_MainWindow (iMainWindow *);