summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-02-17 17:01:01 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-02-17 17:01:01 +0200
commitcdb955bfe2be425ed483273d4341f8fd6c8165ca (patch)
tree019ba02b640f59f68cb6413bcfb3667351ddd203
parentc6a3afccfc5ef679a82275c5f045336c2d48f643 (diff)
parentdd90cda682180f6472aa030d01317f70cc3d7282 (diff)
Merge branch 'dev' of skyjake.fi:skyjake/lagrange into dev
-rw-r--r--res/about/version.gmi5
-rw-r--r--src/app.c17
-rw-r--r--src/app.h1
-rw-r--r--src/gmutil.c15
-rw-r--r--src/gmutil.h1
-rw-r--r--src/prefs.c2
-rw-r--r--src/prefs.h1
-rw-r--r--src/ui/documentwidget.c66
-rw-r--r--src/ui/util.c15
-rw-r--r--src/ui/window.c23
10 files changed, 115 insertions, 31 deletions
diff --git a/res/about/version.gmi b/res/about/version.gmi
index dfdf2b97..ba94f7e2 100644
--- a/res/about/version.gmi
+++ b/res/about/version.gmi
@@ -8,11 +8,14 @@
8 8
9## 1.2 9## 1.2
10* Help is opened on first run instead of the "About Lagrange" page to make it easier to discover important Gemini links like the FAQ. 10* Help is opened on first run instead of the "About Lagrange" page to make it easier to discover important Gemini links like the FAQ.
11* Added search engine support: non-URL text entered in the navbar is passed onto the configured search query URL (Preferences > Network).
11* Short pages are centered vertically on actual page content excluding the top banner. 12* Short pages are centered vertically on actual page content excluding the top banner.
13* Added user preference for aligning all pages to the top of the window.
12* Shift+Insert can be used for pasting clipboard contents into input fields. 14* Shift+Insert can be used for pasting clipboard contents into input fields.
13* Added keybinding (F11) for toggling fullscreen mode. On macOS, the shortcut is ⌃⌘F as before. 15* Added keybinding (F11) for toggling fullscreen mode. On macOS, the shortcut is ⌃⌘F as before.
14* Added keybinding for finding text on page. 16* Added keybinding for finding text on page.
15* Windows: Added a custom window frame to replace the default Windows one. This looks nicer but does not behave exactly like a native window frame. Added a setting to Preferences > Window for switching back to the default frame. 17* Scroll position remains fixed while horizontally resizing the window or sidebars.
18* Windows: Added a custom window frame to replace the default Windows one. This looks nicer but does not behave exactly like a native window frame. Added a setting to Preferences for switching back to the default frame.
16* Windows: Fixed a flash of white when the window is first opened. 19* Windows: Fixed a flash of white when the window is first opened.
17 20
18## 1.1.3 21## 1.1.3
diff --git a/src/app.c b/src/app.c
index 3d3dc6f8..b5eb5688 100644
--- a/src/app.c
+++ b/src/app.c
@@ -227,6 +227,7 @@ static iString *serializePrefs_App_(const iApp *d) {
227 appendFormat_String(str, "proxy.gopher address:%s\n", cstr_String(&d->prefs.gopherProxy)); 227 appendFormat_String(str, "proxy.gopher address:%s\n", cstr_String(&d->prefs.gopherProxy));
228 appendFormat_String(str, "proxy.http address:%s\n", cstr_String(&d->prefs.httpProxy)); 228 appendFormat_String(str, "proxy.http address:%s\n", cstr_String(&d->prefs.httpProxy));
229 appendFormat_String(str, "downloads path:%s\n", cstr_String(&d->prefs.downloadDir)); 229 appendFormat_String(str, "downloads path:%s\n", cstr_String(&d->prefs.downloadDir));
230 appendFormat_String(str, "searchurl address:%s\n", cstr_String(&d->prefs.searchUrl));
230 return str; 231 return str;
231} 232}
232 233
@@ -937,6 +938,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
937 isSelected_Widget(findChild_Widget(d, "prefs.ostheme"))); 938 isSelected_Widget(findChild_Widget(d, "prefs.ostheme")));
938 postCommandf_App("decodeurls arg:%d", 939 postCommandf_App("decodeurls arg:%d",
939 isSelected_Widget(findChild_Widget(d, "prefs.decodeurls"))); 940 isSelected_Widget(findChild_Widget(d, "prefs.decodeurls")));
941 postCommandf_App("searchurl address:%s",
942 cstr_String(text_InputWidget(findChild_Widget(d, "prefs.searchurl"))));
940 postCommandf_App("cachesize.set arg:%d", 943 postCommandf_App("cachesize.set arg:%d",
941 toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize")))); 944 toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize"))));
942 postCommandf_App("proxy.gemini address:%s", 945 postCommandf_App("proxy.gemini address:%s",
@@ -1122,6 +1125,15 @@ iBool willUseProxy_App(const iRangecc scheme) {
1122 return schemeProxy_App(scheme) != NULL; 1125 return schemeProxy_App(scheme) != NULL;
1123} 1126}
1124 1127
1128const iString *searchQueryUrl_App(const iString *queryStringUnescaped) {
1129 iApp *d = &app_;
1130 if (isEmpty_String(&d->prefs.searchUrl)) {
1131 return collectNew_String();
1132 }
1133 const iString *escaped = urlEncode_String(queryStringUnescaped);
1134 return collectNewFormat_String("%s?%s", cstr_String(&d->prefs.searchUrl), cstr_String(escaped));
1135}
1136
1125iBool handleCommand_App(const char *cmd) { 1137iBool handleCommand_App(const char *cmd) {
1126 iApp *d = &app_; 1138 iApp *d = &app_;
1127 if (equal_Command(cmd, "config.error")) { 1139 if (equal_Command(cmd, "config.error")) {
@@ -1292,6 +1304,10 @@ iBool handleCommand_App(const char *cmd) {
1292 } 1304 }
1293 return iTrue; 1305 return iTrue;
1294 } 1306 }
1307 else if (equal_Command(cmd, "searchurl")) {
1308 setCStr_String(&d->prefs.searchUrl, suffixPtr_Command(cmd, "address"));
1309 return iTrue;
1310 }
1295 else if (equal_Command(cmd, "proxy.gemini")) { 1311 else if (equal_Command(cmd, "proxy.gemini")) {
1296 setCStr_String(&d->prefs.geminiProxy, suffixPtr_Command(cmd, "address")); 1312 setCStr_String(&d->prefs.geminiProxy, suffixPtr_Command(cmd, "address"));
1297 return iTrue; 1313 return iTrue;
@@ -1474,6 +1490,7 @@ iBool handleCommand_App(const char *cmd) {
1474 setText_InputWidget(findChild_Widget(dlg, "prefs.cachesize"), 1490 setText_InputWidget(findChild_Widget(dlg, "prefs.cachesize"),
1475 collectNewFormat_String("%d", d->prefs.maxCacheSize)); 1491 collectNewFormat_String("%d", d->prefs.maxCacheSize));
1476 setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs); 1492 setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs);
1493 setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.searchUrl);
1477 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gemini"), &d->prefs.geminiProxy); 1494 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gemini"), &d->prefs.geminiProxy);
1478 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gopher"), &d->prefs.gopherProxy); 1495 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gopher"), &d->prefs.gopherProxy);
1479 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.http"), &d->prefs.httpProxy); 1496 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.http"), &d->prefs.httpProxy);
diff --git a/src/app.h b/src/app.h
index efaf0a3e..54c60d10 100644
--- a/src/app.h
+++ b/src/app.h
@@ -75,6 +75,7 @@ iBool forceSoftwareRender_App(void);
75enum iColorTheme colorTheme_App (void); 75enum iColorTheme colorTheme_App (void);
76const iString * schemeProxy_App (iRangecc scheme); 76const iString * schemeProxy_App (iRangecc scheme);
77iBool willUseProxy_App (const iRangecc scheme); 77iBool willUseProxy_App (const iRangecc scheme);
78const iString * searchQueryUrl_App (const iString *queryStringUnescaped);
78 79
79typedef void (*iTickerFunc)(iAny *); 80typedef void (*iTickerFunc)(iAny *);
80 81
diff --git a/src/gmutil.c b/src/gmutil.c
index 0e0cccc5..32bf356f 100644
--- a/src/gmutil.c
+++ b/src/gmutil.c
@@ -272,6 +272,21 @@ const iString *absoluteUrl_String(const iString *d, const iString *urlMaybeRelat
272 return absolute; 272 return absolute;
273} 273}
274 274
275iBool isLikelyUrl_String(const iString *d) {
276 /* Guess whether a human intends the string to be an URL. This is supposed to be fuzzy;
277 not completely per-spec: a) begins with a scheme; b) has something that looks like a
278 hostname */
279 iRegExp *pattern = new_RegExp("^([a-z]+:)?//.*|"
280 "^(//)?([^/?#: ]+)([/?#:].*)$|"
281 "^(\\w+(\\.\\w+)+|localhost)$",
282 caseInsensitive_RegExpOption);
283 iRegExpMatch m;
284 init_RegExpMatch(&m);
285 const iBool likelyUrl = matchString_RegExp(pattern, d, &m);
286 iRelease(pattern);
287 return likelyUrl;
288}
289
275static iBool equalPuny_(const iString *d, iRangecc orig) { 290static iBool equalPuny_(const iString *d, iRangecc orig) {
276 if (!endsWith_String(d, "-")) { 291 if (!endsWith_String(d, "-")) {
277 return iFalse; /* This is a sufficient condition? */ 292 return iFalse; /* This is a sufficient condition? */
diff --git a/src/gmutil.h b/src/gmutil.h
index 1caf2445..26385c02 100644
--- a/src/gmutil.h
+++ b/src/gmutil.h
@@ -104,6 +104,7 @@ void init_Url (iUrl *, const iString *text);
104iRangecc urlScheme_String (const iString *); 104iRangecc urlScheme_String (const iString *);
105iRangecc urlHost_String (const iString *); 105iRangecc urlHost_String (const iString *);
106const iString * absoluteUrl_String (const iString *, const iString *urlMaybeRelative); 106const iString * absoluteUrl_String (const iString *, const iString *urlMaybeRelative);
107iBool isLikelyUrl_String (const iString *);
107void punyEncodeUrlHost_String(iString *); 108void punyEncodeUrlHost_String(iString *);
108void stripDefaultUrlPort_String(iString *); 109void stripDefaultUrlPort_String(iString *);
109const iString * urlFragmentStripped_String(const iString *); 110const iString * urlFragmentStripped_String(const iString *);
diff --git a/src/prefs.c b/src/prefs.c
index a8f34b02..f9186ab3 100644
--- a/src/prefs.c
+++ b/src/prefs.c
@@ -51,9 +51,11 @@ void init_Prefs(iPrefs *d) {
51 init_String(&d->gopherProxy); 51 init_String(&d->gopherProxy);
52 init_String(&d->httpProxy); 52 init_String(&d->httpProxy);
53 init_String(&d->downloadDir); 53 init_String(&d->downloadDir);
54 initCStr_String(&d->searchUrl, "gemini://gus.guru/search");
54} 55}
55 56
56void deinit_Prefs(iPrefs *d) { 57void deinit_Prefs(iPrefs *d) {
58 deinit_String(&d->searchUrl);
57 deinit_String(&d->geminiProxy); 59 deinit_String(&d->geminiProxy);
58 deinit_String(&d->gopherProxy); 60 deinit_String(&d->gopherProxy);
59 deinit_String(&d->httpProxy); 61 deinit_String(&d->httpProxy);
diff --git a/src/prefs.h b/src/prefs.h
index dcda695f..1889c338 100644
--- a/src/prefs.h
+++ b/src/prefs.h
@@ -48,6 +48,7 @@ struct Impl_Prefs {
48 iBool hoverLink; 48 iBool hoverLink;
49 iBool smoothScrolling; 49 iBool smoothScrolling;
50 iBool loadImageInsteadOfScrolling; 50 iBool loadImageInsteadOfScrolling;
51 iString searchUrl;
51 /* Network */ 52 /* Network */
52 iBool decodeUserVisibleURLs; 53 iBool decodeUserVisibleURLs;
53 int maxCacheSize; /* MB */ 54 int maxCacheSize; /* MB */
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 603af076..232b4140 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -133,6 +133,7 @@ enum iDocumentWidgetFlag {
133 showLinkNumbers_DocumentWidgetFlag = iBit(3), 133 showLinkNumbers_DocumentWidgetFlag = iBit(3),
134 setHoverViaKeys_DocumentWidgetFlag = iBit(4), 134 setHoverViaKeys_DocumentWidgetFlag = iBit(4),
135 newTabViaHomeKeys_DocumentWidgetFlag = iBit(5), 135 newTabViaHomeKeys_DocumentWidgetFlag = iBit(5),
136 centerVertically_DocumentWidgetFlag = iBit(6),
136}; 137};
137 138
138enum iDocumentLinkOrdinalMode { 139enum iDocumentLinkOrdinalMode {
@@ -151,6 +152,7 @@ struct Impl_DocumentWidget {
151 iGmRequest * request; 152 iGmRequest * request;
152 iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ 153 iAtomicInt isRequestUpdated; /* request has new content, need to parse it */
153 iObjectList * media; 154 iObjectList * media;
155 enum iGmStatusCode sourceStatus;
154 iString sourceHeader; 156 iString sourceHeader;
155 iString sourceMime; 157 iString sourceMime;
156 iBlock sourceContent; /* original content as received, for saving */ 158 iBlock sourceContent; /* original content as received, for saving */
@@ -231,6 +233,7 @@ void init_DocumentWidget(iDocumentWidget *d) {
231 init_Array(&d->outline, sizeof(iOutlineItem)); 233 init_Array(&d->outline, sizeof(iOutlineItem));
232 init_Anim(&d->sideOpacity, 0); 234 init_Anim(&d->sideOpacity, 0);
233 init_Anim(&d->outlineOpacity, 0); 235 init_Anim(&d->outlineOpacity, 0);
236 d->sourceStatus = none_GmStatusCode;
234 init_String(&d->sourceHeader); 237 init_String(&d->sourceHeader);
235 init_String(&d->sourceMime); 238 init_String(&d->sourceMime);
236 init_Block(&d->sourceContent, 0); 239 init_Block(&d->sourceContent, 0);
@@ -332,7 +335,7 @@ static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) {
332 rect.pos.y += margin; 335 rect.pos.y += margin;
333 rect.size.y -= margin; 336 rect.size.y -= margin;
334 } 337 }
335 if (prefs_App()->centerShortDocs) { 338 if (d->flags & centerVertically_DocumentWidgetFlag) {
336 const iInt2 docSize = size_GmDocument(d->doc); 339 const iInt2 docSize = size_GmDocument(d->doc);
337 if (docSize.y < rect.size.y) { 340 if (docSize.y < rect.size.y) {
338 /* Center vertically if short. There is one empty paragraph line's worth of margin 341 /* Center vertically if short. There is one empty paragraph line's worth of margin
@@ -592,6 +595,10 @@ static iRangecc currentHeading_DocumentWidget_(const iDocumentWidget *d) {
592} 595}
593 596
594static void updateVisible_DocumentWidget_(iDocumentWidget *d) { 597static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
598 iChangeFlags(d->flags,
599 centerVertically_DocumentWidgetFlag,
600 prefs_App()->centerShortDocs || startsWithCase_String(d->mod.url, "about:") ||
601 !isSuccess_GmStatusCode(d->sourceStatus));
595 const iRangei visRange = visibleRange_DocumentWidget_(d); 602 const iRangei visRange = visibleRange_DocumentWidget_(d);
596 const iRect bounds = bounds_Widget(as_Widget(d)); 603 const iRect bounds = bounds_Widget(as_Widget(d));
597 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_DocumentWidget_(d) }); 604 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_DocumentWidget_(d) });
@@ -1038,6 +1045,7 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1038 /* Use the cached response data. */ 1045 /* Use the cached response data. */
1039 updateTrust_DocumentWidget_(d, resp); 1046 updateTrust_DocumentWidget_(d, resp);
1040 d->sourceTime = resp->when; 1047 d->sourceTime = resp->when;
1048 d->sourceStatus = success_GmStatusCode;
1041 format_String(&d->sourceHeader, "(cached content)"); 1049 format_String(&d->sourceHeader, "(cached content)");
1042 updateTimestampBuf_DocumentWidget_(d); 1050 updateTimestampBuf_DocumentWidget_(d);
1043 set_Block(&d->sourceContent, &resp->body); 1051 set_Block(&d->sourceContent, &resp->body);
@@ -1113,6 +1121,9 @@ static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) {
1113} 1121}
1114 1122
1115static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool centered) { 1123static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool centered) {
1124 if (!hasSiteBanner_GmDocument(d->doc)) {
1125 documentY += d->pageMargin * gap_UI;
1126 }
1116 init_Anim(&d->scrollY, 1127 init_Anim(&d->scrollY,
1117 documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2 1128 documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2
1118 : lineHeight_Text(paragraph_FontId))); 1129 : lineHeight_Text(paragraph_FontId)));
@@ -1192,6 +1203,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1192 updateTrust_DocumentWidget_(d, resp); 1203 updateTrust_DocumentWidget_(d, resp);
1193 init_Anim(&d->sideOpacity, 0); 1204 init_Anim(&d->sideOpacity, 0);
1194 format_String(&d->sourceHeader, "%d %s", statusCode, get_GmError(statusCode)->title); 1205 format_String(&d->sourceHeader, "%d %s", statusCode, get_GmError(statusCode)->title);
1206 d->sourceStatus = statusCode;
1195 switch (category_GmStatusCode(statusCode)) { 1207 switch (category_GmStatusCode(statusCode)) {
1196 case categoryInput_GmStatusCode: { 1208 case categoryInput_GmStatusCode: {
1197 iUrl parts; 1209 iUrl parts;
@@ -1538,28 +1550,43 @@ static const int homeRowKeys_[] = {
1538 't', 'y', 1550 't', 'y',
1539}; 1551};
1540 1552
1553static void updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumentWidget *d,
1554 iBool keepCenter) {
1555 /* Font changes (i.e., zooming) will keep the view centered, otherwise keep the top
1556 of the visible area fixed. */
1557 const iGmRun *run = keepCenter ? middleRun_DocumentWidget_(d) : d->firstVisibleRun;
1558 const char * runLoc = (run ? run->text.start : NULL);
1559 int voffset = 0;
1560 if (!keepCenter && run) {
1561 /* Keep the first visible run visible at the same position. */
1562 /* TODO: First *fully* visible run? */
1563 voffset = visibleRange_DocumentWidget_(d).start - top_Rect(run->visBounds);
1564 }
1565 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d));
1566 if (runLoc && !keepCenter) {
1567 run = findRunAtLoc_GmDocument(d->doc, runLoc);
1568 if (run) {
1569 scrollTo_DocumentWidget_(d,
1570 top_Rect(run->visBounds) +
1571 lineHeight_Text(paragraph_FontId) + voffset,
1572 iFalse);
1573 }
1574 }
1575 else if (runLoc && keepCenter) {
1576 run = findRunAtLoc_GmDocument(d->doc, runLoc);
1577 if (run) {
1578 scrollTo_DocumentWidget_(d, mid_Rect(run->bounds).y, iTrue);
1579 }
1580 }
1581}
1582
1541static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { 1583static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
1542 iWidget *w = as_Widget(d); 1584 iWidget *w = as_Widget(d);
1543 if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) { 1585 if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) {
1544 const iBool isVerticalOnly =
1545 !argLabel_Command(cmd, "horiz") && argLabel_Command(cmd, "vert");
1546 /* Alt/Option key may be involved in window size changes. */ 1586 /* Alt/Option key may be involved in window size changes. */
1547 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse); 1587 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);
1548 if (isVerticalOnly) { 1588 const iBool keepCenter = equal_Command(cmd, "font.changed");
1549 scroll_DocumentWidget_(d, 0); /* prevent overscroll */ 1589 updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, keepCenter);
1550 }
1551 else {
1552 const iGmRun *mid = middleRun_DocumentWidget_(d);
1553 const char *midLoc = (mid ? mid->text.start : NULL);
1554 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d));
1555 scroll_DocumentWidget_(d, 0);
1556 if (midLoc) {
1557 mid = findRunAtLoc_GmDocument(d->doc, midLoc);
1558 if (mid) {
1559 scrollTo_DocumentWidget_(d, mid_Rect(mid->bounds).y, iTrue);
1560 }
1561 }
1562 }
1563 updateSideIconBuf_DocumentWidget_(d); 1590 updateSideIconBuf_DocumentWidget_(d);
1564 updateOutline_DocumentWidget_(d); 1591 updateOutline_DocumentWidget_(d);
1565 invalidate_DocumentWidget_(d); 1592 invalidate_DocumentWidget_(d);
@@ -1581,6 +1608,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1581 } 1608 }
1582 else if (equal_Command(cmd, "theme.changed") && document_App() == d) { 1609 else if (equal_Command(cmd, "theme.changed") && document_App() == d) {
1583 updateTheme_DocumentWidget_(d); 1610 updateTheme_DocumentWidget_(d);
1611 updateVisible_DocumentWidget_(d);
1584 updateTrust_DocumentWidget_(d, NULL); 1612 updateTrust_DocumentWidget_(d, NULL);
1585 updateSideIconBuf_DocumentWidget_(d); 1613 updateSideIconBuf_DocumentWidget_(d);
1586 invalidate_DocumentWidget_(d); 1614 invalidate_DocumentWidget_(d);
@@ -3398,7 +3426,7 @@ iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) {
3398} 3426}
3399 3427
3400void updateSize_DocumentWidget(iDocumentWidget *d) { 3428void updateSize_DocumentWidget(iDocumentWidget *d) {
3401 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d)); 3429 updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, iFalse);
3402 resetWideRuns_DocumentWidget_(d); 3430 resetWideRuns_DocumentWidget_(d);
3403 updateSideIconBuf_DocumentWidget_(d); 3431 updateSideIconBuf_DocumentWidget_(d);
3404 updateOutline_DocumentWidget_(d); 3432 updateOutline_DocumentWidget_(d);
diff --git a/src/ui/util.c b/src/ui/util.c
index b52dea2d..553d9078 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -1123,10 +1123,10 @@ iWidget *makePreferences_Widget(void) {
1123 addChild_Widget(headings, iClob(makeHeading_Widget("Saturation:"))); 1123 addChild_Widget(headings, iClob(makeHeading_Widget("Saturation:")));
1124 iWidget *sats = new_Widget(); 1124 iWidget *sats = new_Widget();
1125 /* Saturation levels. */ { 1125 /* Saturation levels. */ {
1126 addRadioButton_(sats, "prefs.saturation.3", "Full", "saturation.set arg:100"); 1126 addRadioButton_(sats, "prefs.saturation.3", "100 %%", "saturation.set arg:100");
1127 addRadioButton_(sats, "prefs.saturation.2", "Reduced", "saturation.set arg:66"); 1127 addRadioButton_(sats, "prefs.saturation.2", "66 %%", "saturation.set arg:66");
1128 addRadioButton_(sats, "prefs.saturation.1", "Minimal", "saturation.set arg:33"); 1128 addRadioButton_(sats, "prefs.saturation.1", "33 %%", "saturation.set arg:33");
1129 addRadioButton_(sats, "prefs.saturation.0", "Monochrome", "saturation.set arg:0"); 1129 addRadioButton_(sats, "prefs.saturation.0", "0 %%", "saturation.set arg:0");
1130 } 1130 }
1131 addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); 1131 addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
1132 } 1132 }
@@ -1175,6 +1175,10 @@ iWidget *makePreferences_Widget(void) {
1175 } 1175 }
1176 /* Network. */ { 1176 /* Network. */ {
1177 appendTwoColumnPage_(tabs, "Network", '5', &headings, &values); 1177 appendTwoColumnPage_(tabs, "Network", '5', &headings, &values);
1178 addChild_Widget(headings, iClob(makeHeading_Widget("Search URL:")));
1179 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.searchurl");
1180 addChild_Widget(headings, iClob(makeHeading_Widget("Decode URLs:")));
1181 addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls")));
1178 addChild_Widget(headings, iClob(makeHeading_Widget("Cache size:"))); 1182 addChild_Widget(headings, iClob(makeHeading_Widget("Cache size:")));
1179 iWidget *cacheGroup = new_Widget(); { 1183 iWidget *cacheGroup = new_Widget(); {
1180 iInputWidget *cache = new_InputWidget(4); 1184 iInputWidget *cache = new_InputWidget(4);
@@ -1183,8 +1187,6 @@ iWidget *makePreferences_Widget(void) {
1183 addChildFlags_Widget(cacheGroup, iClob(new_LabelWidget("MB", NULL)), frameless_WidgetFlag); 1187 addChildFlags_Widget(cacheGroup, iClob(new_LabelWidget("MB", NULL)), frameless_WidgetFlag);
1184 } 1188 }
1185 addChildFlags_Widget(values, iClob(cacheGroup), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); 1189 addChildFlags_Widget(values, iClob(cacheGroup), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
1186 addChild_Widget(headings, iClob(makeHeading_Widget("Decode URLs:")));
1187 addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls")));
1188 makeTwoColumnHeading_("PROXIES", headings, values); 1190 makeTwoColumnHeading_("PROXIES", headings, values);
1189 addChild_Widget(headings, iClob(makeHeading_Widget("Gemini proxy:"))); 1191 addChild_Widget(headings, iClob(makeHeading_Widget("Gemini proxy:")));
1190 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.gemini"); 1192 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.gemini");
@@ -1201,6 +1203,7 @@ iWidget *makePreferences_Widget(void) {
1201 resizeToLargestPage_Widget(tabs); 1203 resizeToLargestPage_Widget(tabs);
1202 arrange_Widget(dlg); 1204 arrange_Widget(dlg);
1203 /* Set input field sizes. */ { 1205 /* Set input field sizes. */ {
1206 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.searchurl"));
1204 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.downloads")); 1207 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.downloads"));
1205 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.gemini")); 1208 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.gemini"));
1206 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.gopher")); 1209 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.gopher"));
diff --git a/src/ui/window.c b/src/ui/window.c
index d6a41d3b..563d57ae 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -47,6 +47,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
47 47
48#include <the_Foundation/file.h> 48#include <the_Foundation/file.h>
49#include <the_Foundation/path.h> 49#include <the_Foundation/path.h>
50#include <the_Foundation/regexp.h>
50#include <SDL_hints.h> 51#include <SDL_hints.h>
51#include <SDL_timer.h> 52#include <SDL_timer.h>
52#include <SDL_syswm.h> 53#include <SDL_syswm.h>
@@ -419,9 +420,14 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
419 !isFocused_Widget(findWidget_App("lookup"))) { 420 !isFocused_Widget(findWidget_App("lookup"))) {
420 iString *newUrl = copy_String(text_InputWidget(url)); 421 iString *newUrl = copy_String(text_InputWidget(url));
421 trim_String(newUrl); 422 trim_String(newUrl);
422 postCommandf_App( 423 if (!isEmpty_String(&prefs_App()->searchUrl) && !isLikelyUrl_String(newUrl)) {
423 "open url:%s", 424 postCommandf_App("open url:%s", cstr_String(searchQueryUrl_App(newUrl)));
424 cstr_String(absoluteUrl_String(&iStringLiteral(""), collect_String(newUrl)))); 425 }
426 else {
427 postCommandf_App(
428 "open url:%s",
429 cstr_String(absoluteUrl_String(&iStringLiteral(""), collect_String(newUrl))));
430 }
425 return iTrue; 431 return iTrue;
426 } 432 }
427 } 433 }
@@ -1028,7 +1034,14 @@ static void invalidate_Window_(iWindow *d) {
1028} 1034}
1029 1035
1030static iBool isNormalPlacement_Window_(const iWindow *d) { 1036static iBool isNormalPlacement_Window_(const iWindow *d) {
1031 if (snap_Window(d) || d->isDrawFrozen) return iFalse; 1037 if (d->isDrawFrozen) return iFalse;
1038#if defined (iPlatformApple)
1039 /* Maximized mode is not special on macOS. */
1040 if (snap_Window(d) == maximized_WindowSnap) {
1041 return iTrue;
1042 }
1043#endif
1044 if (snap_Window(d)) return iFalse;
1032 return !(SDL_GetWindowFlags(d->win) & SDL_WINDOW_MINIMIZED); 1045 return !(SDL_GetWindowFlags(d->win) & SDL_WINDOW_MINIMIZED);
1033} 1046}
1034 1047
@@ -1161,7 +1174,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
1161 //updateRootSize_Window_(d, iTrue); 1174 //updateRootSize_Window_(d, iTrue);
1162 invalidate_Window_(d); 1175 invalidate_Window_(d);
1163 d->isMinimized = iFalse; 1176 d->isMinimized = iFalse;
1164 //printf("restored %d\n", snap_Window(d)); fflush(stdout); 1177 printf("restored %d\n", snap_Window(d)); fflush(stdout);
1165 return iTrue; 1178 return iTrue;
1166 case SDL_WINDOWEVENT_MINIMIZED: 1179 case SDL_WINDOWEVENT_MINIMIZED:
1167 d->isMinimized = iTrue; 1180 d->isMinimized = iTrue;