diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2022-02-20 12:53:12 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2022-02-20 12:53:12 +0200 |
commit | 6d7b6122e68c8d53b6e7533a6e5d5f55e33831e1 (patch) | |
tree | 80bc2867510e3c9b7bc43578fc416165dae3876c | |
parent | 2d52f13afb15232cc9c834f0aa14284bf9072a31 (diff) | |
parent | 41f378c4b46cb5dd3599d44c81fa51d3183eefee (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
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | Depends.cmake | 2 | ||||
m--------- | lib/the_Foundation | 0 | ||||
-rw-r--r-- | po/en.po | 76 | ||||
-rw-r--r-- | res/about/help.gmi | 34 | ||||
-rw-r--r-- | res/about/version.gmi | 3 | ||||
-rw-r--r-- | res/lang/cs.bin | bin | 32252 -> 33216 bytes | |||
-rw-r--r-- | res/lang/de.bin | bin | 31062 -> 32026 bytes | |||
-rw-r--r-- | res/lang/en.bin | bin | 27131 -> 28095 bytes | |||
-rw-r--r-- | res/lang/eo.bin | bin | 26321 -> 27285 bytes | |||
-rw-r--r-- | res/lang/es_MX.bin | bin | 28201 -> 29165 bytes | |||
-rw-r--r-- | res/lang/fi.bin | bin | 30772 -> 31736 bytes | |||
-rw-r--r-- | res/lang/fr.bin | bin | 31859 -> 32823 bytes | |||
-rw-r--r-- | res/lang/gl.bin | bin | 30135 -> 31099 bytes | |||
-rw-r--r-- | res/lang/hu.bin | bin | 31884 -> 32848 bytes | |||
-rw-r--r-- | res/lang/ia.bin | bin | 29914 -> 30878 bytes | |||
-rw-r--r-- | res/lang/isv.bin | bin | 25852 -> 26816 bytes | |||
-rw-r--r-- | res/lang/nl.bin | bin | 29241 -> 30205 bytes | |||
-rw-r--r-- | res/lang/pl.bin | bin | 30487 -> 31451 bytes | |||
-rw-r--r-- | res/lang/sk.bin | bin | 26188 -> 27152 bytes | |||
-rw-r--r-- | res/lang/tok.bin | bin | 27977 -> 28941 bytes | |||
-rw-r--r-- | res/lang/tr.bin | bin | 30078 -> 31042 bytes | |||
-rw-r--r-- | res/lang/zh_Hans.bin | bin | 26096 -> 27060 bytes | |||
-rw-r--r-- | res/lang/zh_Hant.bin | bin | 26494 -> 27458 bytes | |||
-rw-r--r-- | src/app.c | 553 | ||||
-rw-r--r-- | src/app.h | 13 | ||||
-rw-r--r-- | src/defs.h | 12 | ||||
-rw-r--r-- | src/fontpack.c | 120 | ||||
-rw-r--r-- | src/fontpack.h | 4 | ||||
-rw-r--r-- | src/gmdocument.c | 61 | ||||
-rw-r--r-- | src/gmdocument.h | 4 | ||||
-rw-r--r-- | src/gmutil.c | 87 | ||||
-rw-r--r-- | src/gmutil.h | 9 | ||||
-rw-r--r-- | src/macos.m | 51 | ||||
-rw-r--r-- | src/periodic.c | 19 | ||||
-rw-r--r-- | src/prefs.c | 12 | ||||
-rw-r--r-- | src/prefs.h | 7 | ||||
-rw-r--r-- | src/sitespec.c | 32 | ||||
-rw-r--r-- | src/sitespec.h | 1 | ||||
-rw-r--r-- | src/ui/banner.c | 3 | ||||
-rw-r--r-- | src/ui/color.c | 4 | ||||
-rw-r--r-- | src/ui/color.h | 2 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 135 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 5 | ||||
-rw-r--r-- | src/ui/inputwidget.h | 1 | ||||
-rw-r--r-- | src/ui/keys.c | 2 | ||||
-rw-r--r-- | src/ui/labelwidget.c | 12 | ||||
-rw-r--r-- | src/ui/linkinfo.c | 4 | ||||
-rw-r--r-- | src/ui/lookupwidget.c | 2 | ||||
-rw-r--r-- | src/ui/root.c | 16 | ||||
-rw-r--r-- | src/ui/root.h | 1 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 187 | ||||
-rw-r--r-- | src/ui/text.c | 60 | ||||
-rw-r--r-- | src/ui/text.h | 2 | ||||
-rw-r--r-- | src/ui/touch.c | 3 | ||||
-rw-r--r-- | src/ui/util.c | 200 | ||||
-rw-r--r-- | src/ui/util.h | 14 | ||||
-rw-r--r-- | src/ui/widget.c | 3 | ||||
-rw-r--r-- | src/ui/window.c | 75 | ||||
-rw-r--r-- | src/ui/window.h | 3 |
60 files changed, 1366 insertions, 470 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index a1e4226c..11348fcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -18,7 +18,7 @@ | |||
18 | cmake_minimum_required (VERSION 3.9) | 18 | cmake_minimum_required (VERSION 3.9) |
19 | 19 | ||
20 | project (Lagrange | 20 | project (Lagrange |
21 | VERSION 1.10.6 | 21 | VERSION 1.11.0 |
22 | DESCRIPTION "A Beautiful Gemini Client" | 22 | DESCRIPTION "A Beautiful Gemini Client" |
23 | LANGUAGES C | 23 | LANGUAGES C |
24 | ) | 24 | ) |
diff --git a/Depends.cmake b/Depends.cmake index b7a6cd25..348039b3 100644 --- a/Depends.cmake +++ b/Depends.cmake | |||
@@ -18,7 +18,7 @@ set (_dependsToBuild) | |||
18 | 18 | ||
19 | if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/CMakeLists.txt) | 19 | if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/CMakeLists.txt) |
20 | set (INSTALL_THE_FOUNDATION YES) | 20 | set (INSTALL_THE_FOUNDATION YES) |
21 | find_package (the_Foundation 1.1.0 REQUIRED) | 21 | find_package (the_Foundation 1.2.0 REQUIRED) |
22 | else () | 22 | else () |
23 | if (EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/.git) | 23 | if (EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/.git) |
24 | # the_Foundation is checked out as a submodule, make sure it's up to date. | 24 | # the_Foundation is checked out as a submodule, make sure it's up to date. |
diff --git a/lib/the_Foundation b/lib/the_Foundation | |||
Subproject 453f05f6efb46824ff0dca0174036c7624473e4 | Subproject 11e7a3bdbb4b40ea4417518dac9eba7ad55748d | ||
@@ -148,6 +148,9 @@ msgstr "Identity" | |||
148 | msgid "menu.title.help" | 148 | msgid "menu.title.help" |
149 | msgstr "Help" | 149 | msgstr "Help" |
150 | 150 | ||
151 | msgid "menu.newwindow" | ||
152 | msgstr "New Window" | ||
153 | |||
151 | msgid "menu.newtab" | 154 | msgid "menu.newtab" |
152 | msgstr "New Tab" | 155 | msgstr "New Tab" |
153 | 156 | ||
@@ -519,9 +522,6 @@ msgstr "%b. %d, %Y" | |||
519 | msgid "feeds.today" | 522 | msgid "feeds.today" |
520 | msgstr "Today" | 523 | msgstr "Today" |
521 | 524 | ||
522 | msgid "feeds.entry.newtab" | ||
523 | msgstr "Open Entry in New Tab" | ||
524 | |||
525 | msgid "feeds.entry.markread" | 525 | msgid "feeds.entry.markread" |
526 | msgstr "Mark as Read" | 526 | msgstr "Mark as Read" |
527 | 527 | ||
@@ -558,6 +558,9 @@ msgstr "Open in New Tab" | |||
558 | msgid "menu.opentab.background" | 558 | msgid "menu.opentab.background" |
559 | msgstr "Open in Background Tab" | 559 | msgstr "Open in Background Tab" |
560 | 560 | ||
561 | msgid "menu.openwindow" | ||
562 | msgstr "Open in New Window" | ||
563 | |||
561 | msgid "menu.openfile" | 564 | msgid "menu.openfile" |
562 | msgstr "Open File…" | 565 | msgstr "Open File…" |
563 | 566 | ||
@@ -760,6 +763,27 @@ msgstr "Trust" | |||
760 | msgid "dlg.cert.fingerprint" | 763 | msgid "dlg.cert.fingerprint" |
761 | msgstr "Copy Fingerprint" | 764 | msgstr "Copy Fingerprint" |
762 | 765 | ||
766 | msgid "pageinfo.settings" | ||
767 | msgstr "Settings" | ||
768 | |||
769 | msgid "heading.sitespec" | ||
770 | msgstr "Site-Specific Settings" | ||
771 | |||
772 | msgid "sitespec.ansi" | ||
773 | msgstr "ANSI escape warnings:" | ||
774 | |||
775 | msgid "sitespec.palette" | ||
776 | msgstr "Theme palette seed:" | ||
777 | |||
778 | msgid "sitespec.accept" | ||
779 | msgstr "Save Settings" | ||
780 | |||
781 | msgid "keys.pageinfo" | ||
782 | msgstr "Show page information" | ||
783 | |||
784 | msgid "keys.sitespec" | ||
785 | msgstr "Show site-specific settings" | ||
786 | |||
763 | #, c-format | 787 | #, c-format |
764 | msgid "dlg.input.prompt" | 788 | msgid "dlg.input.prompt" |
765 | msgstr "Please enter input for %s:" | 789 | msgstr "Please enter input for %s:" |
@@ -849,6 +873,9 @@ msgstr "Open Link to the Side" | |||
849 | msgid "link.side.newtab" | 873 | msgid "link.side.newtab" |
850 | msgstr "Open Link in New Tab to the Side" | 874 | msgstr "Open Link in New Tab to the Side" |
851 | 875 | ||
876 | msgid "link.newwindow" | ||
877 | msgstr "Open Link in New Window" | ||
878 | |||
852 | msgid "link.browser" | 879 | msgid "link.browser" |
853 | msgstr "Open Link in Default Browser" | 880 | msgstr "Open Link in Default Browser" |
854 | 881 | ||
@@ -1184,6 +1211,33 @@ msgstr "Russian" | |||
1184 | msgid "lang.es" | 1211 | msgid "lang.es" |
1185 | msgstr "Spanish" | 1212 | msgstr "Spanish" |
1186 | 1213 | ||
1214 | msgid "heading.glyphfinder" | ||
1215 | msgstr "Missing Glyphs" | ||
1216 | |||
1217 | msgid "dlg.glyphfinder.missing" | ||
1218 | msgstr "The following characters could not be shown:" | ||
1219 | |||
1220 | msgid "dlg.glyphfinder.help" | ||
1221 | msgstr "You can try searching the skyjake.fi Font Library for fonts that provide glyphs for these characters, or manually install new TrueType fonts." | ||
1222 | |||
1223 | msgid "dlg.glyphfinder.help.empty" | ||
1224 | msgstr "Please reload the page to check again for missing glyphs." | ||
1225 | |||
1226 | msgid "dlg.glyphfinder.disable" | ||
1227 | msgstr "Disable Warnings" | ||
1228 | |||
1229 | msgid "dlg.glyphfinder.search" | ||
1230 | msgstr "Search Font Library" | ||
1231 | |||
1232 | msgid "heading.glyphfinder.results" | ||
1233 | msgstr "Search Results" | ||
1234 | |||
1235 | msgid "glyphfinder.results" | ||
1236 | msgstr "The following fontpacks provide one or more of the missing glyphs:" | ||
1237 | |||
1238 | msgid "glyphfinder.results.empty" | ||
1239 | msgstr "Sorry, no matching fontpacks were found." | ||
1240 | |||
1187 | msgid "heading.newident" | 1241 | msgid "heading.newident" |
1188 | msgstr "New Identity" | 1242 | msgstr "New Identity" |
1189 | 1243 | ||
@@ -1402,6 +1456,10 @@ msgstr "Collapse preformatted:" | |||
1402 | msgid "prefs.bookmarks.addbottom" | 1456 | msgid "prefs.bookmarks.addbottom" |
1403 | msgstr "Add bookmarks to bottom:" | 1457 | msgstr "Add bookmarks to bottom:" |
1404 | 1458 | ||
1459 | # User preference that controls whether image data embedded in Data URLs gets automatically displayed when a page is loaded. | ||
1460 | msgid "prefs.dataurl.openimages" | ||
1461 | msgstr "Open images in Data URLs:" | ||
1462 | |||
1405 | # User preference that controls whether index.gmi pages get automatically opened when browsing the contents of a directory inside a compressed archive. | 1463 | # User preference that controls whether index.gmi pages get automatically opened when browsing the contents of a directory inside a compressed archive. |
1406 | msgid "prefs.archive.openindex" | 1464 | msgid "prefs.archive.openindex" |
1407 | msgstr "Open archive indices:" | 1465 | msgstr "Open archive indices:" |
@@ -1643,6 +1701,9 @@ msgstr "Wrap plain text:" | |||
1643 | msgid "prefs.decodeurls" | 1701 | msgid "prefs.decodeurls" |
1644 | msgstr "Decode URLs:" | 1702 | msgstr "Decode URLs:" |
1645 | 1703 | ||
1704 | msgid "prefs.urlsize" | ||
1705 | msgstr "Maximum URL size:" | ||
1706 | |||
1646 | msgid "prefs.cachesize" | 1707 | msgid "prefs.cachesize" |
1647 | msgstr "Cache size:" | 1708 | msgstr "Cache size:" |
1648 | 1709 | ||
@@ -2089,12 +2150,3 @@ msgstr "Permanently dismiss warning about terminal emulation on %s?" | |||
2089 | 2150 | ||
2090 | msgid "dlg.dismiss.warning" | 2151 | msgid "dlg.dismiss.warning" |
2091 | msgstr "Dismiss Warning" | 2152 | msgstr "Dismiss Warning" |
2092 | |||
2093 | msgid "heading.fontpack.classic" | ||
2094 | msgstr "Download Fontpack" | ||
2095 | |||
2096 | msgid "dlg.fontpack.classic.msg" | ||
2097 | msgstr "The fonts previously bundled with the app are now available as a separate download. Would you like to download the \"Classic set\" fontpack now?" | ||
2098 | |||
2099 | msgid "dlg.fontpack.classic" | ||
2100 | msgstr "Download Fontpack (25 MB)" | ||
diff --git a/res/about/help.gmi b/res/about/help.gmi index 93bc6a05..cd1a332e 100644 --- a/res/about/help.gmi +++ b/res/about/help.gmi | |||
@@ -106,10 +106,15 @@ The type and destination of a link are indicated by the link's icon and color: | |||
106 | 106 | ||
107 | Link colors remain the same regardless of which color theme is being used for page content. (Color themes are discussed in the Customization section.) | 107 | Link colors remain the same regardless of which color theme is being used for page content. (Color themes are discussed in the Customization section.) |
108 | 108 | ||
109 | When you move the mouse cursor over a link, additional information will appear: the destination domain, with the URL scheme shown for non-Gemini links, and the date of the last visit to the URL. | 109 | The "Show URL on hover" option can be enabled in Preferences to show additional information when you move the mouse cursor over a link: the destination domain, URL scheme for non-Gemini links, date of the last visit to the URL, and the identity that will be used when opening the link. |
110 | 110 | ||
111 | If a link would normally use the default ➤ icon but there is an Emoji at the beginning of the link label, that Emoji is used as the link icon instead. In these cases, you can always assume that the link is a Gemini link whose destination is the same domain that you're currently on. | 111 | If a link would normally use the default ➤ icon but there is an Emoji at the beginning of the link label, that Emoji is used as the link icon instead. In these cases, you can always assume that the link is a Gemini link whose destination is the same domain that you're currently on. |
112 | 112 | ||
113 | The "Network" tab of Preferences has a few settings that affect the presentation of links and URLs in general: | ||
114 | |||
115 | * "Decode URLs" causes percent-coding to be decoded for the user interface, so one can see international characters in URLs. | ||
116 | * "Maximum URL size" sets a limit for how long URLs can be. While Gemini servers are required to enforce a limit of 1024 bytes for URLs, this setting affects all URLs regardless of scheme. Link lines with URLs longer than this will be presented as plain text. | ||
117 | |||
113 | ### 1.1.3 Page caching | 118 | ### 1.1.3 Page caching |
114 | 119 | ||
115 | When navigating to a new page, the old page is cached in memory. If you navigate back, the cached copy of the page is restored. Think of it as rewinding time — you return to a past time as if nothing had happened. The same applies to forward navigation; cached pages are loaded if available. This allows back and forward navigation to happen instantly, without any network requests. | 120 | When navigating to a new page, the old page is cached in memory. If you navigate back, the cached copy of the page is restored. Think of it as rewinding time — you return to a past time as if nothing had happened. The same applies to forward navigation; cached pages are loaded if available. This allows back and forward navigation to happen instantly, without any network requests. |
@@ -355,6 +360,8 @@ You can find a number of settings in Preferences to customize the user interface | |||
355 | 360 | ||
356 | One important characteristic of Gemini is that you remain in control of what gets loaded and when. The browser will not suddenly fetch a ton of images, autoplay videos, or make surreptitious connections to any tracking servers — each network request is purposeful and manually triggered. With this in mind, the "Load image on scroll" option is provided to assist keyboard-only browsing and to facilitate a focused reading experience. When enabled, if there is an image link visible on the page and you press Space or ↓, it will be loaded and shown inline _instead_ of the view scrolling down. This allows you to read and see all the content of the page while only tapping on a single key on the keyboard. | 361 | One important characteristic of Gemini is that you remain in control of what gets loaded and when. The browser will not suddenly fetch a ton of images, autoplay videos, or make surreptitious connections to any tracking servers — each network request is purposeful and manually triggered. With this in mind, the "Load image on scroll" option is provided to assist keyboard-only browsing and to facilitate a focused reading experience. When enabled, if there is an image link visible on the page and you press Space or ↓, it will be loaded and shown inline _instead_ of the view scrolling down. This allows you to read and see all the content of the page while only tapping on a single key on the keyboard. |
357 | 362 | ||
363 | The "Show images in Data URLs" option determines if image data embedded into links using data URLs is automatically shown after the page has finished loading. This only applies to a handful of common image media types: JPEG, PNG, GIF, and WebP. Note that the maximum size of an URL has a limit, so these embedded images have to fit under the configured limit to be recognized as valid links. Image from data URLs are displayed as links' inline content, just like with any other URL scheme. | ||
364 | |||
358 | The "Open archive indices" option controls whether index.gmi pages are automatically opened while browsing the contents of a ZIP archive. The purpose is to simulate the behavior of a Gemini server where opening a directory will by default show its index page. Enabling this option makes navigating an archived copy of a capsule a more streamlined experience. | 365 | The "Open archive indices" option controls whether index.gmi pages are automatically opened while browsing the contents of a ZIP archive. The purpose is to simulate the behavior of a Gemini server where opening a directory will by default show its index page. Enabling this option makes navigating an archived copy of a capsule a more streamlined experience. |
359 | 366 | ||
360 | "Split view pinning" controls which tab links will be opened on when browsing in split view mode. The default mode is "Left Tab", which means that the page in the left tab is pinned (remains unchanged) when clicking on a link. For more information, see section 1.9. | 367 | "Split view pinning" controls which tab links will be opened on when browsing in split view mode. The default mode is "Left Tab", which means that the page in the left tab is pinned (remains unchanged) when clicking on a link. For more information, see section 1.9. |
@@ -388,6 +395,17 @@ Page content color themes are selected on the "Colors" tab of Preferences. The " | |||
388 | * Sepia: Light sepia background with black text. Does not change depending on domain; use this for readability if you prefer a sepia reading experience. | 395 | * Sepia: Light sepia background with black text. Does not change depending on domain; use this for readability if you prefer a sepia reading experience. |
389 | * High Contrast: White background with black text. Does not change depending on domain; use this for readability if you prefer maximum contrast between text and the background. | 396 | * High Contrast: White background with black text. Does not change depending on domain; use this for readability if you prefer maximum contrast between text and the background. |
390 | 397 | ||
398 | ### 2.3.1 Site-specific theme | ||
399 | |||
400 | You can manually customize the color theme of a site. Open "Page Information" and click on "Settings" to open the Site-Specific Settings dialog. The default keyboard shortcut for this is ${SHIFT+}${CTRL+}Comma. | ||
401 | |||
402 | The "Theme palette seed" is the input data given to the theme generator that determines a site's color palette. The theme generator is designed to algorithmically choose a palette whose colors go together. It is not possible to individually pick the colors of a theme. | ||
403 | |||
404 | You can enter any text as the palette seed. By default, the seed is the site's hostname, or a user name found in the URL path (e.g., "gemini://example.com/~User/" → seed "User"). Examples of how to use this: | ||
405 | |||
406 | * Enter a word or phrase, or just random characters, to find a theme that you like. | ||
407 | * To copy the theme of another site, enter the hostname of that site as the seed. | ||
408 | |||
391 | ## 2.4 Fonts | 409 | ## 2.4 Fonts |
392 | 410 | ||
393 | This version of Lagrange supports TrueType fonts. To use a new font, simply view a .ttf file in the app and a page footer action is available for performing the installation. For example, try drag-and-dropping a .ttf file on the window. Alternatively, you can manually copy the font to the "fonts" subdirectory of the user-specific configuration directory (see section 3.5). | 411 | This version of Lagrange supports TrueType fonts. To use a new font, simply view a .ttf file in the app and a page footer action is available for performing the installation. For example, try drag-and-dropping a .ttf file on the window. Alternatively, you can manually copy the font to the "fonts" subdirectory of the user-specific configuration directory (see section 3.5). |
@@ -493,7 +511,19 @@ Gemini allows relaying requests via a proxy server. On the "Network" tab, you ca | |||
493 | 511 | ||
494 | When an HTTP proxy server is configured, HTTP/HTTPS links will no longer open in the system's default web browser but will be loaded via the proxy, expecting it to serve a 'text/gemini' version of the link contents. | 512 | When an HTTP proxy server is configured, HTTP/HTTPS links will no longer open in the system's default web browser but will be loaded via the proxy, expecting it to serve a 'text/gemini' version of the link contents. |
495 | 513 | ||
496 | ## 2.7 Keybindings | 514 | ## 2.7 URL handling |
515 | |||
516 | The "Network" tab of Preferences has a few options related to handling URLs. | ||
517 | |||
518 | Enabling the "Decode URLs" option causes all percent-encoded URLs to be shown in decoded form in the UI. Enabling this option is useful when encountering URLs that contain characters outside the basic Latin (A-Z) alphabet. This does not affect what gets sent to a server when loading pages: Gemini requests are always required to be sent in encoded form. | ||
519 | |||
520 | The "Maximum URL size" setting defines what is considered a valid link in Gemtext. Link lines with URLs longer than this are not considered to be links and are displayed like regular text lines. This only affects links that don't use the "gemini" scheme. The maximum size of a Gemini URL is 1024 bytes. | ||
521 | |||
522 | Having a length limit is necessary due the special case of Data URLs: | ||
523 | => https://datatracker.ietf.org/doc/html/rfc2397 RFC 2397: The "data" URL scheme | ||
524 | With Data URLs, one is able to embed arbitrary data into a Gemtext file using link lines. This makes it possible to circumvent the intentional limitations of Gemini and override the clients' ability to choose which linked resources should be loaded and when. Without a limit, a server could for example send an arbitrarily large image attachment as part of the page, with no regard to the user's or client's preferences or abilities to view images. Also note that like any non-Gemini scheme, "data" is not expected to be supported by Gemini clients. | ||
525 | |||
526 | ## 2.8 Keybindings | ||
497 | 527 | ||
498 | The "Keys" tab lets you change which keys are bound to UI commands. Click on an item with the mouse and then press the new key combination that you want bound to it. Right-click on a binding to erase it or reset it to the default. | 528 | The "Keys" tab lets you change which keys are bound to UI commands. Click on an item with the mouse and then press the new key combination that you want bound to it. Right-click on a binding to erase it or reset it to the default. |
499 | 529 | ||
diff --git a/res/about/version.gmi b/res/about/version.gmi index cf15ba65..8021d9df 100644 --- a/res/about/version.gmi +++ b/res/about/version.gmi | |||
@@ -6,6 +6,9 @@ | |||
6 | ``` | 6 | ``` |
7 | # Release notes | 7 | # Release notes |
8 | 8 | ||
9 | ## 1.11 | ||
10 | ⚠️ Downgrading back to v1.10 causes all site-specific themes to be forgotten. Back up your sitespec.ini beforehand. | ||
11 | |||
9 | ## 1.10.6 | 12 | ## 1.10.6 |
10 | * Added bindings for switching Feeds list to Unread/All mode. | 13 | * Added bindings for switching Feeds list to Unread/All mode. |
11 | * Fixed normalization of empty Gemini URL paths to `/` as per the November 2021 spec update. | 14 | * Fixed normalization of empty Gemini URL paths to `/` as per the November 2021 spec update. |
diff --git a/res/lang/cs.bin b/res/lang/cs.bin index c10c9d89..0cf36a2b 100644 --- a/res/lang/cs.bin +++ b/res/lang/cs.bin | |||
Binary files differ | |||
diff --git a/res/lang/de.bin b/res/lang/de.bin index a7cbadec..ea6e1841 100644 --- a/res/lang/de.bin +++ b/res/lang/de.bin | |||
Binary files differ | |||
diff --git a/res/lang/en.bin b/res/lang/en.bin index e1a3679e..372cef24 100644 --- a/res/lang/en.bin +++ b/res/lang/en.bin | |||
Binary files differ | |||
diff --git a/res/lang/eo.bin b/res/lang/eo.bin index 146cce67..8a9519b6 100644 --- a/res/lang/eo.bin +++ b/res/lang/eo.bin | |||
Binary files differ | |||
diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin index 690f65e5..5c527322 100644 --- a/res/lang/es_MX.bin +++ b/res/lang/es_MX.bin | |||
Binary files differ | |||
diff --git a/res/lang/fi.bin b/res/lang/fi.bin index fe150f13..cdbda504 100644 --- a/res/lang/fi.bin +++ b/res/lang/fi.bin | |||
Binary files differ | |||
diff --git a/res/lang/fr.bin b/res/lang/fr.bin index 9a0ad5b4..3ab07f12 100644 --- a/res/lang/fr.bin +++ b/res/lang/fr.bin | |||
Binary files differ | |||
diff --git a/res/lang/gl.bin b/res/lang/gl.bin index 58518de1..5f5d467b 100644 --- a/res/lang/gl.bin +++ b/res/lang/gl.bin | |||
Binary files differ | |||
diff --git a/res/lang/hu.bin b/res/lang/hu.bin index 94e2dad0..17486efa 100644 --- a/res/lang/hu.bin +++ b/res/lang/hu.bin | |||
Binary files differ | |||
diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 9d22be73..8cae5947 100644 --- a/res/lang/ia.bin +++ b/res/lang/ia.bin | |||
Binary files differ | |||
diff --git a/res/lang/isv.bin b/res/lang/isv.bin index 82242165..81d30963 100644 --- a/res/lang/isv.bin +++ b/res/lang/isv.bin | |||
Binary files differ | |||
diff --git a/res/lang/nl.bin b/res/lang/nl.bin index 86a9e929..e860c109 100644 --- a/res/lang/nl.bin +++ b/res/lang/nl.bin | |||
Binary files differ | |||
diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 98fb053f..b759b1b7 100644 --- a/res/lang/pl.bin +++ b/res/lang/pl.bin | |||
Binary files differ | |||
diff --git a/res/lang/sk.bin b/res/lang/sk.bin index e06344fc..a0197c5b 100644 --- a/res/lang/sk.bin +++ b/res/lang/sk.bin | |||
Binary files differ | |||
diff --git a/res/lang/tok.bin b/res/lang/tok.bin index 6ec80d5a..977454a8 100644 --- a/res/lang/tok.bin +++ b/res/lang/tok.bin | |||
Binary files differ | |||
diff --git a/res/lang/tr.bin b/res/lang/tr.bin index 07351d2a..c131d0ea 100644 --- a/res/lang/tr.bin +++ b/res/lang/tr.bin | |||
Binary files differ | |||
diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 400bb2b7..acaaaa77 100644 --- a/res/lang/zh_Hans.bin +++ b/res/lang/zh_Hans.bin | |||
Binary files differ | |||
diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index ef468edf..cf674d55 100644 --- a/res/lang/zh_Hant.bin +++ b/res/lang/zh_Hant.bin | |||
Binary files differ | |||
@@ -125,7 +125,8 @@ struct Impl_App { | |||
125 | iGmCerts * certs; | 125 | iGmCerts * certs; |
126 | iVisited * visited; | 126 | iVisited * visited; |
127 | iBookmarks * bookmarks; | 127 | iBookmarks * bookmarks; |
128 | iMainWindow *window; | 128 | iMainWindow *window; /* currently active MainWindow */ |
129 | iPtrArray mainWindows; | ||
129 | iPtrArray popupWindows; | 130 | iPtrArray popupWindows; |
130 | iSortedArray tickers; /* per-frame callbacks, used for animations */ | 131 | iSortedArray tickers; /* per-frame callbacks, used for animations */ |
131 | uint32_t lastTickerTime; | 132 | uint32_t lastTickerTime; |
@@ -153,7 +154,8 @@ struct Impl_App { | |||
153 | /* Preferences: */ | 154 | /* Preferences: */ |
154 | iBool commandEcho; /* --echo */ | 155 | iBool commandEcho; /* --echo */ |
155 | iBool forceSoftwareRender; /* --sw */ | 156 | iBool forceSoftwareRender; /* --sw */ |
156 | iRect initialWindowRect; | 157 | //iRect initialWindowRect; |
158 | iArray initialWindowRects; /* one per window */ | ||
157 | iPrefs prefs; | 159 | iPrefs prefs; |
158 | }; | 160 | }; |
159 | 161 | ||
@@ -199,33 +201,43 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
199 | appendFormat_String(str, "window.retain arg:%d\n", d->prefs.retainWindowSize); | 201 | appendFormat_String(str, "window.retain arg:%d\n", d->prefs.retainWindowSize); |
200 | if (d->prefs.retainWindowSize) { | 202 | if (d->prefs.retainWindowSize) { |
201 | int w, h, x, y; | 203 | int w, h, x, y; |
202 | x = d->window->place.normalRect.pos.x; | 204 | iConstForEach(PtrArray, i, &d->mainWindows) { |
203 | y = d->window->place.normalRect.pos.y; | 205 | const iMainWindow *win = i.ptr; |
204 | w = d->window->place.normalRect.size.x; | 206 | const size_t winIndex = index_PtrArrayConstIterator(&i); |
205 | h = d->window->place.normalRect.size.y; | 207 | x = win->place.normalRect.pos.x; |
206 | appendFormat_String(str, "window.setrect width:%d height:%d coord:%d %d\n", w, h, x, y); | 208 | y = win->place.normalRect.pos.y; |
207 | /* On macOS, maximization should be applied at creation time or the window will take | 209 | w = win->place.normalRect.size.x; |
208 | a moment to animate to its maximized size. */ | 210 | h = win->place.normalRect.size.y; |
211 | appendFormat_String(str, | ||
212 | "window.setrect index:%zu width:%d height:%d coord:%d %d\n", | ||
213 | winIndex, | ||
214 | w, | ||
215 | h, | ||
216 | x, | ||
217 | y); | ||
218 | /* On macOS, maximization should be applied at creation time or the window will take | ||
219 | a moment to animate to its maximized size. */ | ||
209 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 220 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
210 | if (snap_MainWindow(d->window)) { | 221 | if (snap_MainWindow(win)) { |
211 | if (snap_MainWindow(d->window) == maximized_WindowSnap) { | 222 | if (snap_MainWindow(win) == maximized_WindowSnap) { |
212 | appendFormat_String(str, "~window.maximize\n"); | 223 | appendFormat_String(str, "~window.maximize index:%zu\n", winIndex); |
213 | } | 224 | } |
214 | else if (~SDL_GetWindowFlags(d->window->base.win) & SDL_WINDOW_MINIMIZED) { | 225 | else if (~SDL_GetWindowFlags(win->base.win) & SDL_WINDOW_MINIMIZED) { |
215 | /* Save the actual visible window position, too, because snapped windows may | 226 | /* Save the actual visible window position, too, because snapped windows may |
216 | still be resized/moved without affecting normalRect. */ | 227 | still be resized/moved without affecting normalRect. */ |
217 | SDL_GetWindowPosition(d->window->base.win, &x, &y); | 228 | SDL_GetWindowPosition(win->base.win, &x, &y); |
218 | SDL_GetWindowSize(d->window->base.win, &w, &h); | 229 | SDL_GetWindowSize(win->base.win, &w, &h); |
219 | appendFormat_String( | 230 | appendFormat_String( |
220 | str, "~window.setrect snap:%d width:%d height:%d coord:%d %d\n", | 231 | str, "~window.setrect index:%zu snap:%d width:%d height:%d coord:%d %d\n", |
221 | snap_MainWindow(d->window), w, h, x, y); | 232 | winIndex, snap_MainWindow(d->window), w, h, x, y); |
233 | } | ||
222 | } | 234 | } |
223 | } | ||
224 | #elif !defined (iPlatformApple) | 235 | #elif !defined (iPlatformApple) |
225 | if (snap_MainWindow(d->window) == maximized_WindowSnap) { | 236 | if (snap_MainWindow(win) == maximized_WindowSnap) { |
226 | appendFormat_String(str, "~window.maximize\n"); | 237 | appendFormat_String(str, "~window.maximize index:%zu\n", winIndex); |
227 | } | 238 | } |
228 | #endif | 239 | #endif |
240 | } | ||
229 | } | 241 | } |
230 | appendFormat_String(str, "uilang id:%s\n", cstr_String(&d->prefs.strings[uiLanguage_PrefsString])); | 242 | appendFormat_String(str, "uilang id:%s\n", cstr_String(&d->prefs.strings[uiLanguage_PrefsString])); |
231 | appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(as_Window(d->window))); | 243 | appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(as_Window(d->window))); |
@@ -244,6 +256,7 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
244 | appendFormat_String(str, "imageloadscroll arg:%d\n", d->prefs.loadImageInsteadOfScrolling); | 256 | appendFormat_String(str, "imageloadscroll arg:%d\n", d->prefs.loadImageInsteadOfScrolling); |
245 | appendFormat_String(str, "cachesize.set arg:%d\n", d->prefs.maxCacheSize); | 257 | appendFormat_String(str, "cachesize.set arg:%d\n", d->prefs.maxCacheSize); |
246 | appendFormat_String(str, "memorysize.set arg:%d\n", d->prefs.maxMemorySize); | 258 | appendFormat_String(str, "memorysize.set arg:%d\n", d->prefs.maxMemorySize); |
259 | appendFormat_String(str, "urlsize.set arg:%d\n", d->prefs.maxUrlSize); | ||
247 | appendFormat_String(str, "decodeurls arg:%d\n", d->prefs.decodeUserVisibleURLs); | 260 | appendFormat_String(str, "decodeurls arg:%d\n", d->prefs.decodeUserVisibleURLs); |
248 | appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); | 261 | appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); |
249 | appendFormat_String(str, "linespacing.set arg:%f\n", d->prefs.lineSpacing); | 262 | appendFormat_String(str, "linespacing.set arg:%f\n", d->prefs.lineSpacing); |
@@ -279,6 +292,7 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
279 | { "prefs.collapsepreonload", &d->prefs.collapsePreOnLoad }, | 292 | { "prefs.collapsepreonload", &d->prefs.collapsePreOnLoad }, |
280 | { "prefs.hoverlink", &d->prefs.hoverLink }, | 293 | { "prefs.hoverlink", &d->prefs.hoverLink }, |
281 | { "prefs.bookmarks.addbottom", &d->prefs.addBookmarksToBottom }, | 294 | { "prefs.bookmarks.addbottom", &d->prefs.addBookmarksToBottom }, |
295 | { "prefs.dataurl.openimages", &d->prefs.openDataUrlImagesOnLoad }, | ||
282 | { "prefs.archive.openindex", &d->prefs.openArchiveIndexPages }, | 296 | { "prefs.archive.openindex", &d->prefs.openArchiveIndexPages }, |
283 | { "prefs.font.warnmissing", &d->prefs.warnAboutMissingGlyphs }, | 297 | { "prefs.font.warnmissing", &d->prefs.warnAboutMissingGlyphs }, |
284 | { "prefs.blink", &d->prefs.blinkingCursor }, | 298 | { "prefs.blink", &d->prefs.blinkingCursor }, |
@@ -401,9 +415,14 @@ static void loadPrefs_App_(iApp *d) { | |||
401 | d->prefs.customFrame = arg_Command(cmd); | 415 | d->prefs.customFrame = arg_Command(cmd); |
402 | } | 416 | } |
403 | else if (equal_Command(cmd, "window.setrect") && !argLabel_Command(cmd, "snap")) { | 417 | else if (equal_Command(cmd, "window.setrect") && !argLabel_Command(cmd, "snap")) { |
404 | const iInt2 pos = coord_Command(cmd); | 418 | const int index = argLabel_Command(cmd, "index"); |
405 | d->initialWindowRect = init_Rect( | 419 | const iInt2 pos = coord_Command(cmd); |
420 | iRect winRect = init_Rect( | ||
406 | pos.x, pos.y, argLabel_Command(cmd, "width"), argLabel_Command(cmd, "height")); | 421 | pos.x, pos.y, argLabel_Command(cmd, "width"), argLabel_Command(cmd, "height")); |
422 | if (index >= 0 && index < 100) { | ||
423 | resize_Array(&d->initialWindowRects, index + 1); | ||
424 | set_Array(&d->initialWindowRects, index, &winRect); | ||
425 | } | ||
407 | } | 426 | } |
408 | else if (equal_Command(cmd, "fontpack.disable")) { | 427 | else if (equal_Command(cmd, "fontpack.disable")) { |
409 | insert_StringSet(d->prefs.disabledFontPacks, | 428 | insert_StringSet(d->prefs.disabledFontPacks, |
@@ -439,6 +458,7 @@ static void loadPrefs_App_(iApp *d) { | |||
439 | } | 458 | } |
440 | iRelease(f); | 459 | iRelease(f); |
441 | /* Upgrade checks. */ | 460 | /* Upgrade checks. */ |
461 | #if 0 /* disabled in v1.11 (font library search) */ | ||
442 | if (cmp_Version(&upgradedFromAppVersion, &(iVersion){ 1, 8, 0 }) < 0) { | 462 | if (cmp_Version(&upgradedFromAppVersion, &(iVersion){ 1, 8, 0 }) < 0) { |
443 | #if !defined (iPlatformAppleMobile) && !defined (iPlatformAndroidMobile) | 463 | #if !defined (iPlatformAppleMobile) && !defined (iPlatformAndroidMobile) |
444 | /* When upgrading to v1.8.0, the old hardcoded font library is gone and that means | 464 | /* When upgrading to v1.8.0, the old hardcoded font library is gone and that means |
@@ -448,6 +468,7 @@ static void loadPrefs_App_(iApp *d) { | |||
448 | postCommand_App("~fontpack.suggest.classic"); | 468 | postCommand_App("~fontpack.suggest.classic"); |
449 | #endif | 469 | #endif |
450 | } | 470 | } |
471 | #endif | ||
451 | #if !defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 472 | #if !defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
452 | d->prefs.customFrame = iFalse; | 473 | d->prefs.customFrame = iFalse; |
453 | #endif | 474 | #endif |
@@ -470,10 +491,36 @@ static const char *magicTabDocument_App_ = "tabd"; | |||
470 | static const char *magicSidebar_App_ = "side"; | 491 | static const char *magicSidebar_App_ = "side"; |
471 | 492 | ||
472 | enum iDocumentStateFlag { | 493 | enum iDocumentStateFlag { |
473 | current_DocumentStateFlag = iBit(1), | 494 | current_DocumentStateFlag = iBit(1), |
474 | rootIndex1_DocumentStateFlag = iBit(2) | 495 | rootIndex1_DocumentStateFlag = iBit(2), |
496 | }; | ||
497 | |||
498 | enum iWindowStateFlag { | ||
499 | current_WindowStateFlag = iBit(9), | ||
475 | }; | 500 | }; |
476 | 501 | ||
502 | static iRect initialWindowRect_App_(const iApp *d, size_t windowIndex) { | ||
503 | if (windowIndex < size_Array(&d->initialWindowRects)) { | ||
504 | return constValue_Array(&d->initialWindowRects, windowIndex, iRect); | ||
505 | } | ||
506 | /* The default window rectangle. */ | ||
507 | iRect rect = init_Rect(-1, -1, 900, 560); | ||
508 | #if defined (iPlatformMsys) | ||
509 | /* Must scale by UI scaling factor. */ | ||
510 | mulfv_I2(&rect.size, desktopDPI_Win32()); | ||
511 | #endif | ||
512 | #if defined (iPlatformLinux) && !defined (iPlatformAndroid) | ||
513 | /* Scale by the primary (?) monitor DPI. */ | ||
514 | if (isRunningUnderWindowSystem_App()) { | ||
515 | float vdpi; | ||
516 | SDL_GetDisplayDPI(0, NULL, NULL, &vdpi); | ||
517 | const float factor = vdpi / 96.0f; | ||
518 | mulfv_I2(&rect.size, iMax(factor, 1.0f)); | ||
519 | } | ||
520 | #endif | ||
521 | return rect; | ||
522 | } | ||
523 | |||
477 | static iBool loadState_App_(iApp *d) { | 524 | static iBool loadState_App_(iApp *d) { |
478 | iUnused(d); | 525 | iUnused(d); |
479 | const char *oldPath = concatPath_CStr(dataDir_App_(), oldStateFileName_App_); | 526 | const char *oldPath = concatPath_CStr(dataDir_App_(), oldStateFileName_App_); |
@@ -494,19 +541,49 @@ static iBool loadState_App_(iApp *d) { | |||
494 | } | 541 | } |
495 | setVersion_Stream(stream_File(f), version); | 542 | setVersion_Stream(stream_File(f), version); |
496 | /* Window state. */ | 543 | /* Window state. */ |
497 | iDocumentWidget *doc = NULL; | 544 | iDeclareType(CurrentTabs); |
498 | iDocumentWidget *current[2] = { NULL, NULL }; | 545 | struct Impl_CurrentTabs { |
499 | iBool isFirstTab[2] = { iTrue, iTrue }; | 546 | iDocumentWidget *currentTab[2]; /* for each root */ |
547 | }; | ||
548 | int numWins = 0; | ||
549 | iMainWindow * win = NULL; | ||
550 | iMainWindow * currentWin = d->window; | ||
551 | iArray * currentTabs; /* two per window (per root per window) */ | ||
552 | iBool isFirstTab[2]; | ||
553 | currentTabs = collectNew_Array(sizeof(iCurrentTabs)); | ||
500 | while (!atEnd_File(f)) { | 554 | while (!atEnd_File(f)) { |
501 | readData_File(f, 4, magic); | 555 | readData_File(f, 4, magic); |
502 | if (!memcmp(magic, magicWindow_App_, 4)) { | 556 | if (!memcmp(magic, magicWindow_App_, 4)) { |
503 | const int splitMode = read32_File(f); | 557 | numWins++; |
504 | const int keyRoot = read32_File(f); | 558 | const int splitMode = read32_File(f); |
505 | d->window->pendingSplitMode = splitMode; | 559 | const int winState = read32_File(f); |
506 | setSplitMode_MainWindow(d->window, splitMode | noEvents_WindowSplit); | 560 | const int keyRoot = (winState & 1); |
507 | d->window->base.keyRoot = d->window->base.roots[keyRoot]; | 561 | const iBool isCurrent = (winState & current_WindowStateFlag) != 0; |
562 | // printf("[State] '%.4s' split:%d state:%x\n", magic, splitMode, winState); | ||
563 | if (numWins == 1) { | ||
564 | win = d->window; | ||
565 | } | ||
566 | else { | ||
567 | win = new_MainWindow(initialWindowRect_App_(d, numWins - 1)); | ||
568 | addWindow_App(win); | ||
569 | } | ||
570 | pushBack_Array(currentTabs, &(iCurrentTabs){ { NULL, NULL } }); | ||
571 | isFirstTab[0] = isFirstTab[1] = iTrue; | ||
572 | if (isCurrent) { | ||
573 | currentWin = win; | ||
574 | } | ||
575 | setCurrent_Window(win); | ||
576 | setCurrent_Root(NULL); | ||
577 | win->pendingSplitMode = splitMode; | ||
578 | setSplitMode_MainWindow(win, splitMode | noEvents_WindowSplit); | ||
579 | win->base.keyRoot = win->base.roots[keyRoot]; | ||
508 | } | 580 | } |
509 | else if (!memcmp(magic, magicSidebar_App_, 4)) { | 581 | else if (!memcmp(magic, magicSidebar_App_, 4)) { |
582 | if (!win) { | ||
583 | printf("%s: missing window\n", cstr_String(path_File(f))); | ||
584 | setCurrent_Root(NULL); | ||
585 | return iFalse; | ||
586 | } | ||
510 | const uint16_t bits = readU16_File(f); | 587 | const uint16_t bits = readU16_File(f); |
511 | const uint8_t modes = readU8_File(f); | 588 | const uint8_t modes = readU8_File(f); |
512 | const float widths[2] = { | 589 | const float widths[2] = { |
@@ -523,7 +600,7 @@ static iBool loadState_App_(iApp *d) { | |||
523 | } | 600 | } |
524 | const uint8_t rootIndex = bits & 0xff; | 601 | const uint8_t rootIndex = bits & 0xff; |
525 | const uint8_t flags = bits >> 8; | 602 | const uint8_t flags = bits >> 8; |
526 | iRoot *root = d->window->base.roots[rootIndex]; | 603 | iRoot *root = win->base.roots[rootIndex]; |
527 | if (root) { | 604 | if (root) { |
528 | iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); | 605 | iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); |
529 | iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); | 606 | iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); |
@@ -546,12 +623,18 @@ static iBool loadState_App_(iApp *d) { | |||
546 | } | 623 | } |
547 | } | 624 | } |
548 | else if (!memcmp(magic, magicTabDocument_App_, 4)) { | 625 | else if (!memcmp(magic, magicTabDocument_App_, 4)) { |
626 | if (!win) { | ||
627 | printf("%s: missing window\n", cstr_String(path_File(f))); | ||
628 | setCurrent_Root(NULL); | ||
629 | return iFalse; | ||
630 | } | ||
549 | const int8_t flags = read8_File(f); | 631 | const int8_t flags = read8_File(f); |
550 | int rootIndex = flags & rootIndex1_DocumentStateFlag ? 1 : 0; | 632 | int rootIndex = flags & rootIndex1_DocumentStateFlag ? 1 : 0; |
551 | if (rootIndex > numRoots_Window(as_Window(d->window)) - 1) { | 633 | if (rootIndex > numRoots_Window(as_Window(win)) - 1) { |
552 | rootIndex = 0; | 634 | rootIndex = 0; |
553 | } | 635 | } |
554 | setCurrent_Root(d->window->base.roots[rootIndex]); | 636 | setCurrent_Root(win->base.roots[rootIndex]); |
637 | iDocumentWidget *doc; | ||
555 | if (isFirstTab[rootIndex]) { | 638 | if (isFirstTab[rootIndex]) { |
556 | isFirstTab[rootIndex] = iFalse; | 639 | isFirstTab[rootIndex] = iFalse; |
557 | /* There is one pre-created tab in each root. */ | 640 | /* There is one pre-created tab in each root. */ |
@@ -561,7 +644,7 @@ static iBool loadState_App_(iApp *d) { | |||
561 | doc = newTab_App(NULL, iFalse /* no switching */); | 644 | doc = newTab_App(NULL, iFalse /* no switching */); |
562 | } | 645 | } |
563 | if (flags & current_DocumentStateFlag) { | 646 | if (flags & current_DocumentStateFlag) { |
564 | current[rootIndex] = doc; | 647 | value_Array(currentTabs, numWins - 1, iCurrentTabs).currentTab[rootIndex] = doc; |
565 | } | 648 | } |
566 | deserializeState_DocumentWidget(doc, stream_File(f)); | 649 | deserializeState_DocumentWidget(doc, stream_File(f)); |
567 | doc = NULL; | 650 | doc = NULL; |
@@ -572,12 +655,23 @@ static iBool loadState_App_(iApp *d) { | |||
572 | return iFalse; | 655 | return iFalse; |
573 | } | 656 | } |
574 | } | 657 | } |
575 | if (d->window->splitMode) { | 658 | iForEach(Array, i, currentTabs) { |
576 | /* Update root placement. */ | 659 | const iCurrentTabs *cur = i.value; |
577 | resize_MainWindow(d->window, -1, -1); | 660 | win = at_PtrArray(&d->mainWindows, index_ArrayIterator(&i)); |
661 | for (size_t j = 0; j < 2; ++j) { | ||
662 | postCommandf_Root(win->base.roots[j], "tabs.switch page:%p", cur->currentTab[j]); | ||
663 | } | ||
664 | if (win->splitMode) { | ||
665 | /* Update root placement. */ | ||
666 | resize_MainWindow(win, -1, -1); | ||
667 | } | ||
668 | // postCommand_Root(win->base.roots[0], "window.unfreeze"); | ||
669 | win->isDrawFrozen = iFalse; | ||
670 | SDL_ShowWindow(win->base.win); | ||
578 | } | 671 | } |
579 | iForIndices(i, current) { | 672 | if (numWindows_App() > 1) { |
580 | postCommandf_Root(NULL, "tabs.switch page:%p", current[i]); | 673 | SDL_RaiseWindow(currentWin->base.win); |
674 | setActiveWindow_App(currentWin); | ||
581 | } | 675 | } |
582 | setCurrent_Root(NULL); | 676 | setCurrent_Root(NULL); |
583 | return iTrue; | 677 | return iTrue; |
@@ -588,7 +682,6 @@ static iBool loadState_App_(iApp *d) { | |||
588 | static void saveState_App_(const iApp *d) { | 682 | static void saveState_App_(const iApp *d) { |
589 | iUnused(d); | 683 | iUnused(d); |
590 | trimCache_App(); | 684 | trimCache_App(); |
591 | iMainWindow *win = d->window; | ||
592 | /* UI state is saved in binary because it is quite complex (e.g., | 685 | /* UI state is saved in binary because it is quite complex (e.g., |
593 | navigation history, cached content) and depends closely on the widget | 686 | navigation history, cached content) and depends closely on the widget |
594 | tree. The data is largely not reorderable and should not be modified | 687 | tree. The data is largely not reorderable and should not be modified |
@@ -597,43 +690,48 @@ static void saveState_App_(const iApp *d) { | |||
597 | if (open_File(f, writeOnly_FileMode)) { | 690 | if (open_File(f, writeOnly_FileMode)) { |
598 | writeData_File(f, magicState_App_, 4); | 691 | writeData_File(f, magicState_App_, 4); |
599 | writeU32_File(f, latest_FileVersion); /* version */ | 692 | writeU32_File(f, latest_FileVersion); /* version */ |
600 | /* Begin with window state. */ { | 693 | iConstForEach(PtrArray, winIter, &d->mainWindows) { |
601 | writeData_File(f, magicWindow_App_, 4); | 694 | const iMainWindow *win = winIter.ptr; |
602 | writeU32_File(f, win->splitMode); | 695 | setCurrent_Window(winIter.ptr); |
603 | writeU32_File(f, win->base.keyRoot == win->base.roots[0] ? 0 : 1); | 696 | /* Window state. */ { |
604 | } | 697 | writeData_File(f, magicWindow_App_, 4); |
605 | /* State of UI elements. */ { | 698 | writeU32_File(f, win->splitMode); |
606 | iForIndices(i, win->base.roots) { | 699 | writeU32_File(f, (win->base.keyRoot == win->base.roots[0] ? 0 : 1) | |
607 | const iRoot *root = win->base.roots[i]; | 700 | (win == d->window ? current_WindowStateFlag : 0)); |
608 | if (root) { | 701 | } |
609 | writeData_File(f, magicSidebar_App_, 4); | 702 | /* State of UI elements. */ { |
610 | const iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); | 703 | iForIndices(i, win->base.roots) { |
611 | const iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); | 704 | const iRoot *root = win->base.roots[i]; |
612 | writeU16_File(f, i | | 705 | if (root) { |
613 | (isVisible_Widget(sidebar) ? 0x100 : 0) | | 706 | writeData_File(f, magicSidebar_App_, 4); |
614 | (isVisible_Widget(sidebar2) ? 0x200 : 0) | | 707 | const iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); |
615 | (feedsMode_SidebarWidget(sidebar) == unread_FeedsMode ? 0x400 : 0) | | 708 | const iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); |
616 | (feedsMode_SidebarWidget(sidebar2) == unread_FeedsMode ? 0x800 : 0)); | 709 | writeU16_File(f, i | |
617 | writeU8_File(f, | 710 | (isVisible_Widget(sidebar) ? 0x100 : 0) | |
618 | mode_SidebarWidget(sidebar) | | 711 | (isVisible_Widget(sidebar2) ? 0x200 : 0) | |
619 | (mode_SidebarWidget(sidebar2) << 4)); | 712 | (feedsMode_SidebarWidget(sidebar) == unread_FeedsMode ? 0x400 : 0) | |
620 | writef_Stream(stream_File(f), width_SidebarWidget(sidebar)); | 713 | (feedsMode_SidebarWidget(sidebar2) == unread_FeedsMode ? 0x800 : 0)); |
621 | writef_Stream(stream_File(f), width_SidebarWidget(sidebar2)); | 714 | writeU8_File(f, |
622 | serialize_IntSet(closedFolders_SidebarWidget(sidebar), stream_File(f)); | 715 | mode_SidebarWidget(sidebar) | |
623 | serialize_IntSet(closedFolders_SidebarWidget(sidebar2), stream_File(f)); | 716 | (mode_SidebarWidget(sidebar2) << 4)); |
717 | writef_Stream(stream_File(f), width_SidebarWidget(sidebar)); | ||
718 | writef_Stream(stream_File(f), width_SidebarWidget(sidebar2)); | ||
719 | serialize_IntSet(closedFolders_SidebarWidget(sidebar), stream_File(f)); | ||
720 | serialize_IntSet(closedFolders_SidebarWidget(sidebar2), stream_File(f)); | ||
721 | } | ||
624 | } | 722 | } |
625 | } | 723 | } |
626 | } | 724 | iConstForEach(ObjectList, i, iClob(listDocuments_App(NULL))) { |
627 | iConstForEach(ObjectList, i, iClob(listDocuments_App(NULL))) { | 725 | iAssert(isInstance_Object(i.object, &Class_DocumentWidget)); |
628 | iAssert(isInstance_Object(i.object, &Class_DocumentWidget)); | 726 | const iWidget *widget = constAs_Widget(i.object); |
629 | const iWidget *widget = constAs_Widget(i.object); | 727 | writeData_File(f, magicTabDocument_App_, 4); |
630 | writeData_File(f, magicTabDocument_App_, 4); | 728 | int8_t flags = (document_Root(widget->root) == i.object ? current_DocumentStateFlag : 0); |
631 | int8_t flags = (document_Root(widget->root) == i.object ? current_DocumentStateFlag : 0); | 729 | if (widget->root == win->base.roots[1]) { |
632 | if (widget->root == win->base.roots[1]) { | 730 | flags |= rootIndex1_DocumentStateFlag; |
633 | flags |= rootIndex1_DocumentStateFlag; | 731 | } |
732 | write8_File(f, flags); | ||
733 | serializeState_DocumentWidget(i.object, stream_File(f)); | ||
634 | } | 734 | } |
635 | write8_File(f, flags); | ||
636 | serializeState_DocumentWidget(i.object, stream_File(f)); | ||
637 | } | 735 | } |
638 | iRelease(f); | 736 | iRelease(f); |
639 | } | 737 | } |
@@ -760,6 +858,7 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
760 | d->isDarkSystemTheme = iTrue; /* will be updated by system later on, if supported */ | 858 | d->isDarkSystemTheme = iTrue; /* will be updated by system later on, if supported */ |
761 | d->isSuspended = iFalse; | 859 | d->isSuspended = iFalse; |
762 | d->tempFilesPendingDeletion = new_StringSet(); | 860 | d->tempFilesPendingDeletion = new_StringSet(); |
861 | init_Array(&d->initialWindowRects, sizeof(iRect)); | ||
763 | init_CommandLine(&d->args, argc, argv); | 862 | init_CommandLine(&d->args, argc, argv); |
764 | /* Where was the app started from? We ask SDL first because the command line alone | 863 | /* Where was the app started from? We ask SDL first because the command line alone |
765 | cannot be relied on (behavior differs depending on OS). */ { | 864 | cannot be relied on (behavior differs depending on OS). */ { |
@@ -894,20 +993,6 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
894 | d->elapsedSinceLastTicker = 0; | 993 | d->elapsedSinceLastTicker = 0; |
895 | d->commandEcho = iClob(checkArgument_CommandLine(&d->args, "echo;E")) != NULL; | 994 | d->commandEcho = iClob(checkArgument_CommandLine(&d->args, "echo;E")) != NULL; |
896 | d->forceSoftwareRender = iClob(checkArgument_CommandLine(&d->args, "sw")) != NULL; | 995 | d->forceSoftwareRender = iClob(checkArgument_CommandLine(&d->args, "sw")) != NULL; |
897 | d->initialWindowRect = init_Rect(-1, -1, 900, 560); | ||
898 | #if defined (iPlatformMsys) | ||
899 | /* Must scale by UI scaling factor. */ | ||
900 | mulfv_I2(&d->initialWindowRect.size, desktopDPI_Win32()); | ||
901 | #endif | ||
902 | #if defined (iPlatformLinux) && !defined (iPlatformAndroid) | ||
903 | /* Scale by the primary (?) monitor DPI. */ | ||
904 | if (isRunningUnderWindowSystem_App()) { | ||
905 | float vdpi; | ||
906 | SDL_GetDisplayDPI(0, NULL, NULL, &vdpi); | ||
907 | const float factor = vdpi / 96.0f; | ||
908 | mulfv_I2(&d->initialWindowRect.size, iMax(factor, 1.0f)); | ||
909 | } | ||
910 | #endif | ||
911 | init_Prefs(&d->prefs); | 996 | init_Prefs(&d->prefs); |
912 | init_SiteSpec(dataDir_App_()); | 997 | init_SiteSpec(dataDir_App_()); |
913 | setCStr_String(&d->prefs.strings[downloadDir_PrefsString], downloadDir_App_()); | 998 | setCStr_String(&d->prefs.strings[downloadDir_PrefsString], downloadDir_App_()); |
@@ -929,21 +1014,30 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
929 | init_Fonts(dataDir_App_()); | 1014 | init_Fonts(dataDir_App_()); |
930 | loadPalette_Color(dataDir_App_()); | 1015 | loadPalette_Color(dataDir_App_()); |
931 | setThemePalette_Color(d->prefs.theme); /* default UI colors */ | 1016 | setThemePalette_Color(d->prefs.theme); /* default UI colors */ |
932 | loadPrefs_App_(d); | 1017 | /* Initial window rectangle of the first window. */ { |
1018 | iAssert(isEmpty_Array(&d->initialWindowRects)); | ||
1019 | const iRect winRect = initialWindowRect_App_(d, 0); /* calculated */ | ||
1020 | resize_Array(&d->initialWindowRects, 1); | ||
1021 | set_Array(&d->initialWindowRects, 0, &winRect); | ||
1022 | } | ||
1023 | loadPrefs_App_(d); | ||
933 | updateActive_Fonts(); | 1024 | updateActive_Fonts(); |
934 | load_Keys(dataDir_App_()); | 1025 | load_Keys(dataDir_App_()); |
1026 | iRect *winRect0 = at_Array(&d->initialWindowRects, 0); | ||
935 | /* See if the user wants to override the window size. */ { | 1027 | /* See if the user wants to override the window size. */ { |
936 | iCommandLineArg *arg = iClob(checkArgument_CommandLine(&d->args, windowWidth_CommandLineOption)); | 1028 | iCommandLineArg *arg = iClob(checkArgument_CommandLine(&d->args, windowWidth_CommandLineOption)); |
937 | if (arg) { | 1029 | if (arg) { |
938 | d->initialWindowRect.size.x = toInt_String(value_CommandLineArg(arg, 0)); | 1030 | winRect0->size.x = toInt_String(value_CommandLineArg(arg, 0)); |
939 | } | 1031 | } |
940 | arg = iClob(checkArgument_CommandLine(&d->args, windowHeight_CommandLineOption)); | 1032 | arg = iClob(checkArgument_CommandLine(&d->args, windowHeight_CommandLineOption)); |
941 | if (arg) { | 1033 | if (arg) { |
942 | d->initialWindowRect.size.y = toInt_String(value_CommandLineArg(arg, 0)); | 1034 | winRect0->size.y = toInt_String(value_CommandLineArg(arg, 0)); |
943 | } | 1035 | } |
944 | } | 1036 | } |
1037 | init_PtrArray(&d->mainWindows); | ||
945 | init_PtrArray(&d->popupWindows); | 1038 | init_PtrArray(&d->popupWindows); |
946 | d->window = new_MainWindow(d->initialWindowRect); | 1039 | d->window = new_MainWindow(*winRect0); /* first window is always created */ |
1040 | addWindow_App(d->window); | ||
947 | load_Visited(d->visited, dataDir_App_()); | 1041 | load_Visited(d->visited, dataDir_App_()); |
948 | load_Bookmarks(d->bookmarks, dataDir_App_()); | 1042 | load_Bookmarks(d->bookmarks, dataDir_App_()); |
949 | load_MimeHooks(d->mimehooks, dataDir_App_()); | 1043 | load_MimeHooks(d->mimehooks, dataDir_App_()); |
@@ -968,8 +1062,8 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
968 | } | 1062 | } |
969 | postCommand_App("~navbar.actions.changed"); | 1063 | postCommand_App("~navbar.actions.changed"); |
970 | postCommand_App("~toolbar.actions.changed"); | 1064 | postCommand_App("~toolbar.actions.changed"); |
971 | postCommand_Root(NULL, "~window.unfreeze"); | 1065 | postCommand_App("~window.unfreeze"); |
972 | postCommand_Root(NULL, "font.reset"); | 1066 | postCommand_App("font.reset"); |
973 | d->autoReloadTimer = SDL_AddTimer(60 * 1000, postAutoReloadCommand_App_, NULL); | 1067 | d->autoReloadTimer = SDL_AddTimer(60 * 1000, postAutoReloadCommand_App_, NULL); |
974 | postCommand_Root(NULL, "document.autoreload"); | 1068 | postCommand_Root(NULL, "document.autoreload"); |
975 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 1069 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
@@ -1008,7 +1102,11 @@ static void deinit_App(iApp *d) { | |||
1008 | SDL_RemoveTimer(d->autoReloadTimer); | 1102 | SDL_RemoveTimer(d->autoReloadTimer); |
1009 | saveState_App_(d); | 1103 | saveState_App_(d); |
1010 | savePrefs_App_(d); | 1104 | savePrefs_App_(d); |
1011 | delete_MainWindow(d->window); | 1105 | iReverseForEach(PtrArray, j, &d->mainWindows) { |
1106 | delete_MainWindow(j.ptr); | ||
1107 | } | ||
1108 | iAssert(isEmpty_PtrArray(&d->mainWindows)); | ||
1109 | deinit_PtrArray(&d->mainWindows); | ||
1012 | d->window = NULL; | 1110 | d->window = NULL; |
1013 | deinit_Feeds(); | 1111 | deinit_Feeds(); |
1014 | save_Keys(dataDir_App_()); | 1112 | save_Keys(dataDir_App_()); |
@@ -1023,7 +1121,6 @@ static void deinit_App(iApp *d) { | |||
1023 | delete_GmCerts(d->certs); | 1121 | delete_GmCerts(d->certs); |
1024 | save_MimeHooks(d->mimehooks); | 1122 | save_MimeHooks(d->mimehooks); |
1025 | delete_MimeHooks(d->mimehooks); | 1123 | delete_MimeHooks(d->mimehooks); |
1026 | d->window = NULL; | ||
1027 | deinit_CommandLine(&d->args); | 1124 | deinit_CommandLine(&d->args); |
1028 | iRelease(d->launchCommands); | 1125 | iRelease(d->launchCommands); |
1029 | delete_String(d->execPath); | 1126 | delete_String(d->execPath); |
@@ -1037,7 +1134,8 @@ static void deinit_App(iApp *d) { | |||
1037 | /* Delete all temporary files created while running. */ | 1134 | /* Delete all temporary files created while running. */ |
1038 | iConstForEach(StringSet, tmp, d->tempFilesPendingDeletion) { | 1135 | iConstForEach(StringSet, tmp, d->tempFilesPendingDeletion) { |
1039 | remove(cstr_String(tmp.value)); | 1136 | remove(cstr_String(tmp.value)); |
1040 | } | 1137 | } |
1138 | deinit_Array(&d->initialWindowRects); | ||
1041 | iRelease(d->tempFilesPendingDeletion); | 1139 | iRelease(d->tempFilesPendingDeletion); |
1042 | } | 1140 | } |
1043 | 1141 | ||
@@ -1282,10 +1380,23 @@ static iPtrArray *listWindows_App_(const iApp *d, iPtrArray *windows) { | |||
1282 | iReverseConstForEach(PtrArray, i, &d->popupWindows) { | 1380 | iReverseConstForEach(PtrArray, i, &d->popupWindows) { |
1283 | pushBack_PtrArray(windows, i.ptr); | 1381 | pushBack_PtrArray(windows, i.ptr); |
1284 | } | 1382 | } |
1285 | pushBack_PtrArray(windows, d->window); | 1383 | if (d->window) { |
1384 | pushBack_PtrArray(windows, d->window); | ||
1385 | } | ||
1386 | iConstForEach(PtrArray, j, &d->mainWindows) { | ||
1387 | if (j.ptr != d->window) { | ||
1388 | pushBack_PtrArray(windows, j.ptr); | ||
1389 | } | ||
1390 | } | ||
1286 | return windows; | 1391 | return windows; |
1287 | } | 1392 | } |
1288 | 1393 | ||
1394 | iPtrArray *listWindows_App(void) { | ||
1395 | iPtrArray *wins = new_PtrArray(); | ||
1396 | listWindows_App_(&app_, wins); | ||
1397 | return wins; | ||
1398 | } | ||
1399 | |||
1289 | void processEvents_App(enum iAppEventMode eventMode) { | 1400 | void processEvents_App(enum iAppEventMode eventMode) { |
1290 | iApp *d = &app_; | 1401 | iApp *d = &app_; |
1291 | iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */ | 1402 | iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */ |
@@ -1365,6 +1476,10 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1365 | dispatchCommands_Periodic(&d->periodic); | 1476 | dispatchCommands_Periodic(&d->periodic); |
1366 | continue; | 1477 | continue; |
1367 | } | 1478 | } |
1479 | if (ev.type == SDL_USEREVENT && ev.user.code == releaseObject_UserEventCode) { | ||
1480 | iRelease(ev.user.data1); | ||
1481 | continue; | ||
1482 | } | ||
1368 | if (ev.type == SDL_USEREVENT && ev.user.code == refresh_UserEventCode) { | 1483 | if (ev.type == SDL_USEREVENT && ev.user.code == refresh_UserEventCode) { |
1369 | gotRefresh = iTrue; | 1484 | gotRefresh = iTrue; |
1370 | continue; | 1485 | continue; |
@@ -1470,6 +1585,7 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1470 | window->lastHover = window->hover; | 1585 | window->lastHover = window->hover; |
1471 | wasUsed = processEvent_Window(window, &ev); | 1586 | wasUsed = processEvent_Window(window, &ev); |
1472 | if (ev.type == SDL_MOUSEMOTION || ev.type == SDL_MOUSEBUTTONDOWN) { | 1587 | if (ev.type == SDL_MOUSEMOTION || ev.type == SDL_MOUSEBUTTONDOWN) { |
1588 | /* Only offered to the frontmost window. */ | ||
1473 | break; | 1589 | break; |
1474 | } | 1590 | } |
1475 | if (wasUsed) break; | 1591 | if (wasUsed) break; |
@@ -1563,6 +1679,9 @@ static void runTickers_App_(iApp *d) { | |||
1563 | iConstForEach(Array, i, &pending->values) { | 1679 | iConstForEach(Array, i, &pending->values) { |
1564 | const iTicker *ticker = i.value; | 1680 | const iTicker *ticker = i.value; |
1565 | if (ticker->callback) { | 1681 | if (ticker->callback) { |
1682 | if (ticker->root) { | ||
1683 | setCurrent_Window(ticker->root->window); | ||
1684 | } | ||
1566 | setCurrent_Root(ticker->root); /* root might be NULL */ | 1685 | setCurrent_Root(ticker->root); /* root might be NULL */ |
1567 | ticker->callback(ticker->context); | 1686 | ticker->callback(ticker->context); |
1568 | } | 1687 | } |
@@ -1755,11 +1874,11 @@ void postCommand_Root(iRoot *d, const char *command) { | |||
1755 | } | 1874 | } |
1756 | SDL_Event ev = { .type = SDL_USEREVENT }; | 1875 | SDL_Event ev = { .type = SDL_USEREVENT }; |
1757 | ev.user.code = command_UserEventCode; | 1876 | ev.user.code = command_UserEventCode; |
1758 | /*ev.user.windowID = id_Window(get_Window());*/ | ||
1759 | ev.user.data1 = strdup(command); | 1877 | ev.user.data1 = strdup(command); |
1760 | ev.user.data2 = d; /* all events are root-specific */ | 1878 | ev.user.data2 = d; /* all events are root-specific */ |
1879 | ev.user.windowID = d ? id_Window(d->window) : 0; /* root-specific means window-specific */ | ||
1761 | SDL_PushEvent(&ev); | 1880 | SDL_PushEvent(&ev); |
1762 | iWindow *win = get_Window(); | 1881 | iWindow *win = d ? d->window : NULL; |
1763 | #if defined (iPlatformAndroid) | 1882 | #if defined (iPlatformAndroid) |
1764 | SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s[command] {%d} %s", | 1883 | SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s[command] {%d} %s", |
1765 | app_.isLoadingPrefs ? "[Prefs] " : "", | 1884 | app_.isLoadingPrefs ? "[Prefs] " : "", |
@@ -1767,10 +1886,17 @@ void postCommand_Root(iRoot *d, const char *command) { | |||
1767 | command); | 1886 | command); |
1768 | #else | 1887 | #else |
1769 | if (app_.commandEcho) { | 1888 | if (app_.commandEcho) { |
1770 | printf("%s[command] {%d} %s\n", | 1889 | const int windowIndex = |
1771 | app_.isLoadingPrefs ? "[Prefs] " : "", | 1890 | win && type_Window(win) == main_WindowType ? windowIndex_App(as_MainWindow(win)) + 1 : 0; |
1772 | (d == NULL || win == NULL ? 0 : d == win->roots[0] ? 1 : 2), | 1891 | printf("%s%s[command] {%d:%d} %s\n", |
1773 | command); fflush(stdout); | 1892 | !app_.isFinishedLaunching ? "<Ln> " : "", |
1893 | app_.isLoadingPrefs ? "<Pr> " : "", | ||
1894 | windowIndex, | ||
1895 | (d == NULL || win == NULL ? 0 | ||
1896 | : d == win->roots[0] ? 1 | ||
1897 | : 2), | ||
1898 | command); | ||
1899 | fflush(stdout); | ||
1774 | } | 1900 | } |
1775 | #endif | 1901 | #endif |
1776 | } | 1902 | } |
@@ -1835,6 +1961,41 @@ void removeTicker_App(iTickerFunc ticker, iAny *context) { | |||
1835 | remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker }); | 1961 | remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker }); |
1836 | } | 1962 | } |
1837 | 1963 | ||
1964 | void addWindow_App(iMainWindow *win) { | ||
1965 | iApp *d = &app_; | ||
1966 | pushBack_PtrArray(&d->mainWindows, win); | ||
1967 | } | ||
1968 | |||
1969 | void removeWindow_App(iMainWindow *win) { | ||
1970 | iApp *d = &app_; | ||
1971 | removeOne_PtrArray(&d->mainWindows, win); | ||
1972 | } | ||
1973 | |||
1974 | size_t numWindows_App(void) { | ||
1975 | return size_PtrArray(&app_.mainWindows); | ||
1976 | } | ||
1977 | |||
1978 | size_t windowIndex_App(const iMainWindow *win) { | ||
1979 | return indexOf_PtrArray(&app_.mainWindows, win); | ||
1980 | } | ||
1981 | |||
1982 | iMainWindow *newMainWindow_App(void) { | ||
1983 | iApp *d = &app_; | ||
1984 | iMainWindow *win = new_MainWindow(initialWindowRect_App_(d, size_PtrArray(&d->mainWindows))); | ||
1985 | addWindow_App(win); | ||
1986 | return win; | ||
1987 | } | ||
1988 | |||
1989 | const iPtrArray *mainWindows_App(void) { | ||
1990 | return &app_.mainWindows; | ||
1991 | } | ||
1992 | |||
1993 | void setActiveWindow_App(iMainWindow *win) { | ||
1994 | iApp *d = &app_; | ||
1995 | d->window = win; | ||
1996 | printf("Active window: %p\n", win); fflush(stdout); | ||
1997 | } | ||
1998 | |||
1838 | void addPopup_App(iWindow *popup) { | 1999 | void addPopup_App(iWindow *popup) { |
1839 | iApp *d = &app_; | 2000 | iApp *d = &app_; |
1840 | pushBack_PtrArray(&d->popupWindows, popup); | 2001 | pushBack_PtrArray(&d->popupWindows, popup); |
@@ -1970,6 +2131,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | |||
1970 | toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize")))); | 2131 | toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize")))); |
1971 | postCommandf_App("memorysize.set arg:%d", | 2132 | postCommandf_App("memorysize.set arg:%d", |
1972 | toInt_String(text_InputWidget(findChild_Widget(d, "prefs.memorysize")))); | 2133 | toInt_String(text_InputWidget(findChild_Widget(d, "prefs.memorysize")))); |
2134 | postCommandf_App("urlsize.set arg:%d", | ||
2135 | toInt_String(text_InputWidget(findChild_Widget(d, "prefs.urlsize")))); | ||
1973 | postCommandf_App("ca.file path:%s", | 2136 | postCommandf_App("ca.file path:%s", |
1974 | cstrText_InputWidget(findChild_Widget(d, "prefs.ca.file"))); | 2137 | cstrText_InputWidget(findChild_Widget(d, "prefs.ca.file"))); |
1975 | postCommandf_App("ca.path path:%s", | 2138 | postCommandf_App("ca.path path:%s", |
@@ -2111,6 +2274,22 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe | |||
2111 | return doc; | 2274 | return doc; |
2112 | } | 2275 | } |
2113 | 2276 | ||
2277 | void closeWindow_App(iMainWindow *win) { | ||
2278 | iApp *d = &app_; | ||
2279 | delete_MainWindow(win); | ||
2280 | if (d->window == win) { | ||
2281 | /* Activate another window. */ | ||
2282 | iForEach(PtrArray, i, &d->mainWindows) { | ||
2283 | if (i.ptr != d->window) { | ||
2284 | SDL_RaiseWindow(i.ptr); | ||
2285 | setActiveWindow_App(i.ptr); | ||
2286 | setCurrent_Window(i.ptr); | ||
2287 | break; | ||
2288 | } | ||
2289 | } | ||
2290 | } | ||
2291 | } | ||
2292 | |||
2114 | static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { | 2293 | static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { |
2115 | iApp *d = &app_; | 2294 | iApp *d = &app_; |
2116 | if (equal_Command(cmd, "ident.showmore")) { | 2295 | if (equal_Command(cmd, "ident.showmore")) { |
@@ -2257,6 +2436,13 @@ void resetFonts_App(void) { | |||
2257 | } | 2436 | } |
2258 | } | 2437 | } |
2259 | 2438 | ||
2439 | void availableFontsChanged_App(void) { | ||
2440 | iApp *d = &app_; | ||
2441 | iConstForEach(PtrArray, win, listWindows_App_(d, collectNew_PtrArray())) { | ||
2442 | resetMissing_Text(text_Window(win.ptr)); | ||
2443 | } | ||
2444 | } | ||
2445 | |||
2260 | static void invalidateCachedDocuments_App_(void) { | 2446 | static void invalidateCachedDocuments_App_(void) { |
2261 | iForEach(ObjectList, i, iClob(listDocuments_App(NULL))) { | 2447 | iForEach(ObjectList, i, iClob(listDocuments_App(NULL))) { |
2262 | invalidateCachedLayout_History(history_DocumentWidget(i.object)); | 2448 | invalidateCachedLayout_History(history_DocumentWidget(i.object)); |
@@ -2274,6 +2460,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2274 | suffixPtr_Command(cmd, "where"))); | 2460 | suffixPtr_Command(cmd, "where"))); |
2275 | return iTrue; | 2461 | return iTrue; |
2276 | } | 2462 | } |
2463 | #if 0 /* disabled in v1.11 */ | ||
2277 | else if (equal_Command(cmd, "fontpack.suggest.classic")) { | 2464 | else if (equal_Command(cmd, "fontpack.suggest.classic")) { |
2278 | /* TODO: Don't use this when system fonts are accessible. */ | 2465 | /* TODO: Don't use this when system fonts are accessible. */ |
2279 | if (!isInstalled_Fonts("classic-set") && !isInstalled_Fonts("cjk")) { | 2466 | if (!isInstalled_Fonts("classic-set") && !isInstalled_Fonts("cjk")) { |
@@ -2290,6 +2477,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2290 | } | 2477 | } |
2291 | return iTrue; | 2478 | return iTrue; |
2292 | } | 2479 | } |
2480 | #endif | ||
2293 | else if (equal_Command(cmd, "prefs.changed")) { | 2481 | else if (equal_Command(cmd, "prefs.changed")) { |
2294 | savePrefs_App_(d); | 2482 | savePrefs_App_(d); |
2295 | return iTrue; | 2483 | return iTrue; |
@@ -2360,12 +2548,16 @@ iBool handleCommand_App(const char *cmd) { | |||
2360 | return iTrue; | 2548 | return iTrue; |
2361 | } | 2549 | } |
2362 | else if (equal_Command(cmd, "window.maximize")) { | 2550 | else if (equal_Command(cmd, "window.maximize")) { |
2363 | if (!argLabel_Command(cmd, "toggle")) { | 2551 | const size_t winIndex = argU32Label_Command(cmd, "index"); |
2364 | setSnap_MainWindow(d->window, maximized_WindowSnap); | 2552 | if (winIndex < size_PtrArray(&d->mainWindows)) { |
2365 | } | 2553 | iMainWindow *win = at_PtrArray(&d->mainWindows, winIndex); |
2366 | else { | 2554 | if (!argLabel_Command(cmd, "toggle")) { |
2367 | setSnap_MainWindow(d->window, snap_MainWindow(d->window) == maximized_WindowSnap ? 0 : | 2555 | setSnap_MainWindow(win, maximized_WindowSnap); |
2368 | maximized_WindowSnap); | 2556 | } |
2557 | else { | ||
2558 | setSnap_MainWindow( | ||
2559 | win, snap_MainWindow(win) == maximized_WindowSnap ? 0 : maximized_WindowSnap); | ||
2560 | } | ||
2369 | } | 2561 | } |
2370 | return iTrue; | 2562 | return iTrue; |
2371 | } | 2563 | } |
@@ -2383,24 +2575,49 @@ iBool handleCommand_App(const char *cmd) { | |||
2383 | reload_Fonts(); /* also does font cache reset, window invalidation */ | 2575 | reload_Fonts(); /* also does font cache reset, window invalidation */ |
2384 | return iTrue; | 2576 | return iTrue; |
2385 | } | 2577 | } |
2386 | #if 0 | 2578 | else if (equal_Command(cmd, "font.find")) { |
2387 | else if (equal_Command(cmd, "font.user")) { | 2579 | searchOnlineLibraryForCharacters_Fonts(string_Command(cmd, "chars")); |
2388 | const char *path = suffixPtr_Command(cmd, "path"); | 2580 | return iTrue; |
2389 | if (cmp_String(&d->prefs.symbolFontPath, path)) { | 2581 | } |
2390 | if (!isFrozen) { | 2582 | else if (equal_Command(cmd, "font.found")) { |
2391 | setFreezeDraw_MainWindow(get_MainWindow(), iTrue); | 2583 | if (hasLabel_Command(cmd, "error")) { |
2392 | } | 2584 | makeSimpleMessage_Widget("${heading.glyphfinder}", |
2393 | setCStr_String(&d->prefs.symbolFontPath, path); | 2585 | format_CStr("%d %s", |
2394 | loadUserFonts_Text(); | 2586 | argLabel_Command(cmd, "error"), |
2395 | resetFonts_App(d); | 2587 | suffixPtr_Command(cmd, "msg"))); |
2396 | if (!isFrozen) { | 2588 | return iTrue; |
2397 | postCommand_App("font.changed"); | ||
2398 | postCommand_App("window.unfreeze"); | ||
2399 | } | ||
2400 | } | 2589 | } |
2590 | iString *src = collectNew_String(); | ||
2591 | setCStr_String(src, "# ${heading.glyphfinder.results}\n\n"); | ||
2592 | iRangecc path = iNullRange; | ||
2593 | iBool isFirst = iTrue; | ||
2594 | while (nextSplit_Rangecc(range_Command(cmd, "packs"), ",", &path)) { | ||
2595 | if (isFirst) { | ||
2596 | appendCStr_String(src, "${glyphfinder.results}\n\n"); | ||
2597 | } | ||
2598 | iRangecc fpath = path; | ||
2599 | iRangecc fsize = path; | ||
2600 | fpath.end = strchr(fpath.start, ';'); | ||
2601 | fsize.start = fpath.end + 1; | ||
2602 | const uint32_t size = strtoul(fsize.start, NULL, 10); | ||
2603 | appendFormat_String(src, "=> gemini://skyjake.fi/fonts/%s %s (%.1f MB)\n", | ||
2604 | cstr_Rangecc(fpath), | ||
2605 | cstr_Rangecc(fpath), | ||
2606 | (double) size / 1.0e6); | ||
2607 | isFirst = iFalse; | ||
2608 | } | ||
2609 | if (isFirst) { | ||
2610 | appendFormat_String(src, "${glyphfinder.results.empty}\n"); | ||
2611 | } | ||
2612 | appendCStr_String(src, "\n=> about:fonts ${menu.fonts}"); | ||
2613 | iDocumentWidget *page = newTab_App(NULL, iTrue); | ||
2614 | translate_Lang(src); | ||
2615 | setUrlAndSource_DocumentWidget(page, | ||
2616 | collectNewCStr_String(""), | ||
2617 | collectNewCStr_String("text/gemini"), | ||
2618 | utf8_String(src)); | ||
2401 | return iTrue; | 2619 | return iTrue; |
2402 | } | 2620 | } |
2403 | #endif | ||
2404 | else if (equal_Command(cmd, "font.set")) { | 2621 | else if (equal_Command(cmd, "font.set")) { |
2405 | if (!isFrozen) { | 2622 | if (!isFrozen) { |
2406 | setFreezeDraw_MainWindow(get_MainWindow(), iTrue); | 2623 | setFreezeDraw_MainWindow(get_MainWindow(), iTrue); |
@@ -2690,6 +2907,10 @@ iBool handleCommand_App(const char *cmd) { | |||
2690 | postRefresh_App(); | 2907 | postRefresh_App(); |
2691 | return iTrue; | 2908 | return iTrue; |
2692 | } | 2909 | } |
2910 | else if (equal_Command(cmd, "prefs.dataurl.openimages.changed")) { | ||
2911 | d->prefs.openDataUrlImagesOnLoad = arg_Command(cmd) != 0; | ||
2912 | return iTrue; | ||
2913 | } | ||
2693 | else if (equal_Command(cmd, "prefs.archive.openindex.changed")) { | 2914 | else if (equal_Command(cmd, "prefs.archive.openindex.changed")) { |
2694 | d->prefs.openArchiveIndexPages = arg_Command(cmd) != 0; | 2915 | d->prefs.openArchiveIndexPages = arg_Command(cmd) != 0; |
2695 | return iTrue; | 2916 | return iTrue; |
@@ -2735,6 +2956,13 @@ iBool handleCommand_App(const char *cmd) { | |||
2735 | } | 2956 | } |
2736 | return iTrue; | 2957 | return iTrue; |
2737 | } | 2958 | } |
2959 | else if (equal_Command(cmd, "urlsize.set")) { | ||
2960 | d->prefs.maxUrlSize = arg_Command(cmd); | ||
2961 | if (d->prefs.maxUrlSize < 1024) { | ||
2962 | d->prefs.maxUrlSize = 1024; /* Gemini protocol requirement */ | ||
2963 | } | ||
2964 | return iTrue; | ||
2965 | } | ||
2738 | else if (equal_Command(cmd, "searchurl")) { | 2966 | else if (equal_Command(cmd, "searchurl")) { |
2739 | iString *url = &d->prefs.strings[searchUrl_PrefsString]; | 2967 | iString *url = &d->prefs.strings[searchUrl_PrefsString]; |
2740 | setCStr_String(url, suffixPtr_Command(cmd, "address")); | 2968 | setCStr_String(url, suffixPtr_Command(cmd, "address")); |
@@ -2817,7 +3045,18 @@ iBool handleCommand_App(const char *cmd) { | |||
2817 | return iTrue; /* invalid command */ | 3045 | return iTrue; /* invalid command */ |
2818 | } | 3046 | } |
2819 | if (findWidget_App("prefs")) { | 3047 | if (findWidget_App("prefs")) { |
2820 | postCommand_App("prefs.dismiss"); | 3048 | postCommand_App("prefs.dismiss"); |
3049 | } | ||
3050 | if (argLabel_Command(cmd, "newwindow")) { | ||
3051 | const iRangecc gotoheading = range_Command(cmd, "gotoheading"); | ||
3052 | const iRangecc gotourlheading = range_Command(cmd, "gotourlheading"); | ||
3053 | postCommandf_Root(get_Root(), "window.new%s%s%s%s url:%s", | ||
3054 | isEmpty_Range(&gotoheading) ? "" : " gotoheading:", | ||
3055 | isEmpty_Range(&gotoheading) ? "" : cstr_Rangecc(gotoheading), | ||
3056 | isEmpty_Range(&gotourlheading) ? "" : " gotourlheading:", | ||
3057 | isEmpty_Range(&gotourlheading) ? "" : cstr_Rangecc(gotourlheading), | ||
3058 | urlArg); | ||
3059 | return iTrue; | ||
2821 | } | 3060 | } |
2822 | iString *url = collectNewCStr_String(urlArg); | 3061 | iString *url = collectNewCStr_String(urlArg); |
2823 | const iBool noProxy = argLabel_Command(cmd, "noproxy") != 0; | 3062 | const iBool noProxy = argLabel_Command(cmd, "noproxy") != 0; |
@@ -2961,6 +3200,20 @@ iBool handleCommand_App(const char *cmd) { | |||
2961 | #endif | 3200 | #endif |
2962 | return iFalse; | 3201 | return iFalse; |
2963 | } | 3202 | } |
3203 | else if (equal_Command(cmd, "window.new")) { | ||
3204 | iMainWindow *newWin = new_MainWindow(initialWindowRect_App_(d, numWindows_App())); | ||
3205 | addWindow_App(newWin); /* takes ownership */ | ||
3206 | SDL_ShowWindow(newWin->base.win); | ||
3207 | setCurrent_Window(newWin); | ||
3208 | if (hasLabel_Command(cmd, "url")) { | ||
3209 | postCommandf_Root(newWin->base.roots[0], "~open %s", cmd + 11 /* all arguments passed on */); | ||
3210 | } | ||
3211 | else { | ||
3212 | postCommand_Root(newWin->base.roots[0], "~navigate.home"); | ||
3213 | } | ||
3214 | postCommand_Root(newWin->base.roots[0], "~window.unfreeze"); | ||
3215 | return iTrue; | ||
3216 | } | ||
2964 | else if (equal_Command(cmd, "tabs.new")) { | 3217 | else if (equal_Command(cmd, "tabs.new")) { |
2965 | const iBool isDuplicate = argLabel_Command(cmd, "duplicate") != 0; | 3218 | const iBool isDuplicate = argLabel_Command(cmd, "duplicate") != 0; |
2966 | newTab_App(isDuplicate ? document_App() : NULL, iTrue); | 3219 | newTab_App(isDuplicate ? document_App() : NULL, iTrue); |
@@ -3021,6 +3274,9 @@ iBool handleCommand_App(const char *cmd) { | |||
3021 | } | 3274 | } |
3022 | } | 3275 | } |
3023 | } | 3276 | } |
3277 | else if (numWindows_App() > 1) { | ||
3278 | closeWindow_App(d->window); | ||
3279 | } | ||
3024 | else { | 3280 | else { |
3025 | postCommand_App("quit"); | 3281 | postCommand_App("quit"); |
3026 | } | 3282 | } |
@@ -3050,6 +3306,7 @@ iBool handleCommand_App(const char *cmd) { | |||
3050 | setToggle_Widget(findChild_Widget(dlg, "prefs.hidetoolbarscroll"), d->prefs.hideToolbarOnScroll); | 3306 | setToggle_Widget(findChild_Widget(dlg, "prefs.hidetoolbarscroll"), d->prefs.hideToolbarOnScroll); |
3051 | setToggle_Widget(findChild_Widget(dlg, "prefs.bookmarks.addbottom"), d->prefs.addBookmarksToBottom); | 3307 | setToggle_Widget(findChild_Widget(dlg, "prefs.bookmarks.addbottom"), d->prefs.addBookmarksToBottom); |
3052 | setToggle_Widget(findChild_Widget(dlg, "prefs.font.warnmissing"), d->prefs.warnAboutMissingGlyphs); | 3308 | setToggle_Widget(findChild_Widget(dlg, "prefs.font.warnmissing"), d->prefs.warnAboutMissingGlyphs); |
3309 | setToggle_Widget(findChild_Widget(dlg, "prefs.dataurl.openimages"), d->prefs.openDataUrlImagesOnLoad); | ||
3053 | setToggle_Widget(findChild_Widget(dlg, "prefs.archive.openindex"), d->prefs.openArchiveIndexPages); | 3310 | setToggle_Widget(findChild_Widget(dlg, "prefs.archive.openindex"), d->prefs.openArchiveIndexPages); |
3054 | setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme); | 3311 | setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme); |
3055 | setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame); | 3312 | setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame); |
@@ -3122,6 +3379,8 @@ iBool handleCommand_App(const char *cmd) { | |||
3122 | collectNewFormat_String("%d", d->prefs.maxCacheSize)); | 3379 | collectNewFormat_String("%d", d->prefs.maxCacheSize)); |
3123 | setText_InputWidget(findChild_Widget(dlg, "prefs.memorysize"), | 3380 | setText_InputWidget(findChild_Widget(dlg, "prefs.memorysize"), |
3124 | collectNewFormat_String("%d", d->prefs.maxMemorySize)); | 3381 | collectNewFormat_String("%d", d->prefs.maxMemorySize)); |
3382 | setText_InputWidget(findChild_Widget(dlg, "prefs.urlsize"), | ||
3383 | collectNewFormat_String("%d", d->prefs.maxUrlSize)); | ||
3125 | setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs); | 3384 | setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs); |
3126 | setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.strings[searchUrl_PrefsString]); | 3385 | setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.strings[searchUrl_PrefsString]); |
3127 | setText_InputWidget(findChild_Widget(dlg, "prefs.ca.file"), &d->prefs.strings[caFile_PrefsString]); | 3386 | setText_InputWidget(findChild_Widget(dlg, "prefs.ca.file"), &d->prefs.strings[caFile_PrefsString]); |
@@ -3509,21 +3768,7 @@ void revealPath_App(const iString *path) { | |||
3509 | } | 3768 | } |
3510 | 3769 | ||
3511 | iObjectList *listDocuments_App(const iRoot *rootOrNull) { | 3770 | iObjectList *listDocuments_App(const iRoot *rootOrNull) { |
3512 | iWindow *win = get_Window(); | 3771 | return listDocuments_MainWindow(get_MainWindow(), rootOrNull); |
3513 | iObjectList *docs = new_ObjectList(); | ||
3514 | iForIndices(i, win->roots) { | ||
3515 | iRoot *root = win->roots[i]; | ||
3516 | if (!root) continue; | ||
3517 | if (!rootOrNull || root == rootOrNull) { | ||
3518 | const iWidget *tabs = findChild_Widget(root->widget, "doctabs"); | ||
3519 | iForEach(ObjectList, i, children_Widget(findChild_Widget(tabs, "tabs.pages"))) { | ||
3520 | if (isInstance_Object(i.object, &Class_DocumentWidget)) { | ||
3521 | pushBack_ObjectList(docs, i.object); | ||
3522 | } | ||
3523 | } | ||
3524 | } | ||
3525 | } | ||
3526 | return docs; | ||
3527 | } | 3772 | } |
3528 | 3773 | ||
3529 | iStringSet *listOpenURLs_App(void) { | 3774 | iStringSet *listOpenURLs_App(void) { |
@@ -3540,12 +3785,16 @@ iMainWindow *mainWindow_App(void) { | |||
3540 | return app_.window; | 3785 | return app_.window; |
3541 | } | 3786 | } |
3542 | 3787 | ||
3543 | void closePopups_App(void) { | 3788 | void closePopups_App(iBool doForce) { |
3544 | iApp *d = &app_; | 3789 | iApp *d = &app_; |
3545 | const uint32_t now = SDL_GetTicks(); | 3790 | const uint32_t now = SDL_GetTicks(); |
3546 | iConstForEach(PtrArray, i, &d->popupWindows) { | 3791 | iForEach(PtrArray, i, &d->popupWindows) { |
3547 | const iWindow *win = i.ptr; | 3792 | iWindow *win = i.ptr; |
3548 | if (now - win->focusGainedAt > 200) { | 3793 | // if (doForce) { |
3794 | // collect_Garbage(win, (iDeleteFunc) delete_Window); | ||
3795 | // } | ||
3796 | // else | ||
3797 | if (now - win->focusGainedAt > 200) { | ||
3549 | postCommand_Root(((const iWindow *) i.ptr)->roots[0], "cancel"); | 3798 | postCommand_Root(((const iWindow *) i.ptr)->roots[0], "cancel"); |
3550 | } | 3799 | } |
3551 | } | 3800 | } |
@@ -67,6 +67,7 @@ enum iUserEventCode { | |||
67 | take, it could turn into a tap-and-hold for example. */ | 67 | take, it could turn into a tap-and-hold for example. */ |
68 | widgetTapBegins_UserEventCode, | 68 | widgetTapBegins_UserEventCode, |
69 | widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */ | 69 | widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */ |
70 | releaseObject_UserEventCode, /* object that needs releasing in the main thread */ | ||
70 | }; | 71 | }; |
71 | 72 | ||
72 | const iString *execPath_App (void); | 73 | const iString *execPath_App (void); |
@@ -97,6 +98,7 @@ iPeriodic * periodic_App (void); | |||
97 | iDocumentWidget * document_App (void); | 98 | iDocumentWidget * document_App (void); |
98 | iObjectList * listDocuments_App (const iRoot *rootOrNull); /* NULL for all roots */ | 99 | iObjectList * listDocuments_App (const iRoot *rootOrNull); /* NULL for all roots */ |
99 | iStringSet * listOpenURLs_App (void); /* all tabs */ | 100 | iStringSet * listOpenURLs_App (void); /* all tabs */ |
101 | iPtrArray * listWindows_App (void); | ||
100 | iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew); | 102 | iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew); |
101 | void trimCache_App (void); | 103 | void trimCache_App (void); |
102 | void trimMemory_App (void); | 104 | void trimMemory_App (void); |
@@ -121,6 +123,14 @@ iAny * findWidget_App (const char *id); | |||
121 | void addTicker_App (iTickerFunc ticker, iAny *context); | 123 | void addTicker_App (iTickerFunc ticker, iAny *context); |
122 | void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context); | 124 | void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context); |
123 | void removeTicker_App (iTickerFunc ticker, iAny *context); | 125 | void removeTicker_App (iTickerFunc ticker, iAny *context); |
126 | void addWindow_App (iMainWindow *win); | ||
127 | void removeWindow_App (iMainWindow *win); | ||
128 | void setActiveWindow_App (iMainWindow *win); | ||
129 | void closeWindow_App (iMainWindow *win); | ||
130 | size_t numWindows_App (void); | ||
131 | size_t windowIndex_App (const iMainWindow *win); | ||
132 | iMainWindow *newMainWindow_App (void); | ||
133 | const iPtrArray *mainWindows_App(void); | ||
124 | void addPopup_App (iWindow *popup); | 134 | void addPopup_App (iWindow *popup); |
125 | void removePopup_App (iWindow *popup); | 135 | void removePopup_App (iWindow *popup); |
126 | void postRefresh_App (void); | 136 | void postRefresh_App (void); |
@@ -142,6 +152,7 @@ iDocumentWidget * document_Command (const char *cmd); | |||
142 | void openInDefaultBrowser_App(const iString *url); | 152 | void openInDefaultBrowser_App(const iString *url); |
143 | void revealPath_App (const iString *path); | 153 | void revealPath_App (const iString *path); |
144 | void resetFonts_App (void); | 154 | void resetFonts_App (void); |
155 | void availableFontsChanged_App(void); | ||
145 | 156 | ||
146 | iMainWindow * mainWindow_App (void); | 157 | iMainWindow * mainWindow_App (void); |
147 | void closePopups_App (void); | 158 | void closePopups_App (iBool doForce); |
@@ -38,9 +38,10 @@ enum iFileVersion { | |||
38 | serializedSidebarState_FileVersion = 3, | 38 | serializedSidebarState_FileVersion = 3, |
39 | addedRecentUrlFlags_FileVersion = 4, | 39 | addedRecentUrlFlags_FileVersion = 4, |
40 | bookmarkFolderState_FileVersion = 5, | 40 | bookmarkFolderState_FileVersion = 5, |
41 | multipleWindows_FileVersion = 6, | ||
41 | /* meta */ | 42 | /* meta */ |
42 | idents_FileVersion = 1, /* version used by GmCerts/idents.lgr */ | 43 | latest_FileVersion = 6, /* used by state.lgr */ |
43 | latest_FileVersion = 5, | 44 | idents_FileVersion = 1, /* used by GmCerts/idents.lgr */ |
44 | }; | 45 | }; |
45 | 46 | ||
46 | enum iImageStyle { | 47 | enum iImageStyle { |
@@ -156,8 +157,9 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { | |||
156 | #define bookmark_Icon "\U0001f516" | 157 | #define bookmark_Icon "\U0001f516" |
157 | #define folder_Icon "\U0001f4c1" | 158 | #define folder_Icon "\U0001f4c1" |
158 | #define file_Icon "\U0001f5ce" | 159 | #define file_Icon "\U0001f5ce" |
159 | #define openTab_Icon "\u2750" | 160 | #define openWindow_Icon "\u2b1a" //"\U0001F5d4" |
160 | #define openTabBg_Icon "\u2b1a" | 161 | #define openTab_Icon add_Icon |
162 | #define openTabBg_Icon "\u2750" //"\u2b1a" | ||
161 | #define openExt_Icon "\u27a0" | 163 | #define openExt_Icon "\u27a0" |
162 | #define add_Icon "\u2795" | 164 | #define add_Icon "\u2795" |
163 | #define page_Icon "\U00010117" | 165 | #define page_Icon "\U00010117" |
@@ -186,6 +188,8 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { | |||
186 | #define downAngle_Icon "\ufe40" | 188 | #define downAngle_Icon "\ufe40" |
187 | #define photo_Icon "\U0001f5bc" | 189 | #define photo_Icon "\U0001f5bc" |
188 | #define fontpack_Icon "\U0001f520" | 190 | #define fontpack_Icon "\U0001f520" |
191 | #define package_Icon "\U0001f4e6" | ||
192 | #define paperclip_Icon "\U0001f4ce" | ||
189 | 193 | ||
190 | #if defined (iPlatformApple) | 194 | #if defined (iPlatformApple) |
191 | # define shift_Icon "\u21e7" | 195 | # define shift_Icon "\u21e7" |
diff --git a/src/fontpack.c b/src/fontpack.c index a440234e..96006226 100644 --- a/src/fontpack.c +++ b/src/fontpack.c | |||
@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
23 | #include "fontpack.h" | 23 | #include "fontpack.h" |
24 | #include "resources.h" | 24 | #include "resources.h" |
25 | #include "ui/window.h" | 25 | #include "ui/window.h" |
26 | #include "gmrequest.h" | ||
26 | #include "app.h" | 27 | #include "app.h" |
27 | 28 | ||
28 | #include <the_Foundation/archive.h> | 29 | #include <the_Foundation/archive.h> |
@@ -748,7 +749,7 @@ const iPtrArray *listSpecsByPriority_Fonts(void) { | |||
748 | return &fonts_.specOrder; | 749 | return &fonts_.specOrder; |
749 | } | 750 | } |
750 | 751 | ||
751 | iString *infoText_FontPack(const iFontPack *d) { | 752 | iString *infoText_FontPack(const iFontPack *d, iBool isFull) { |
752 | const iFontPack *installed = pack_Fonts(cstr_String(&d->id)); | 753 | const iFontPack *installed = pack_Fonts(cstr_String(&d->id)); |
753 | const iBool isInstalled = (installed != NULL); | 754 | const iBool isInstalled = (installed != NULL); |
754 | const int installedVersion = installed ? installed->version : 0; | 755 | const int installedVersion = installed ? installed->version : 0; |
@@ -757,9 +758,17 @@ iString *infoText_FontPack(const iFontPack *d) { | |||
757 | size_t sizeInBytes = 0; | 758 | size_t sizeInBytes = 0; |
758 | iPtrSet *uniqueFiles = new_PtrSet(); | 759 | iPtrSet *uniqueFiles = new_PtrSet(); |
759 | iStringList *names = new_StringList(); | 760 | iStringList *names = new_StringList(); |
761 | size_t numNames = 0; | ||
762 | iBool isAbbreviated = iFalse; | ||
760 | iConstForEach(PtrArray, i, listSpecs_FontPack(d)) { | 763 | iConstForEach(PtrArray, i, listSpecs_FontPack(d)) { |
761 | const iFontSpec *spec = i.ptr; | 764 | const iFontSpec *spec = i.ptr; |
762 | pushBack_StringList(names, &spec->name); | 765 | numNames++; |
766 | if (isFull || size_StringList(names) < 20) { | ||
767 | pushBack_StringList(names, &spec->name); | ||
768 | } | ||
769 | else { | ||
770 | isAbbreviated = iTrue; | ||
771 | } | ||
763 | iForIndices(j, spec->styles) { | 772 | iForIndices(j, spec->styles) { |
764 | insert_PtrSet(uniqueFiles, spec->styles[j]->sourceData.i); | 773 | insert_PtrSet(uniqueFiles, spec->styles[j]->sourceData.i); |
765 | } | 774 | } |
@@ -777,11 +786,12 @@ iString *infoText_FontPack(const iFontPack *d) { | |||
777 | if (!endsWith_String(str, "(")) { | 786 | if (!endsWith_String(str, "(")) { |
778 | appendCStr_String(str, ", "); | 787 | appendCStr_String(str, ", "); |
779 | } | 788 | } |
780 | appendCStr_String(str, formatCStrs_Lang("num.fonts.n", size_StringList(names))); | 789 | appendCStr_String(str, formatCStrs_Lang("num.fonts.n", numNames)); |
781 | } | 790 | } |
782 | appendFormat_String(str, ")"); | 791 | appendFormat_String(str, ")"); |
783 | } | 792 | } |
784 | appendFormat_String(str, " \u2014 %s\n", cstrCollect_String(joinCStr_StringList(names, ", "))); | 793 | appendFormat_String(str, " \u2014 %s%s\n", cstrCollect_String(joinCStr_StringList(names, ", ")), |
794 | isAbbreviated ? ", ..." : ""); | ||
785 | if (isInstalled && installedVersion != d->version) { | 795 | if (isInstalled && installedVersion != d->version) { |
786 | appendCStr_String(str, format_Lang("${fontpack.meta.version}\n", d->version)); | 796 | appendCStr_String(str, format_Lang("${fontpack.meta.version}\n", d->version)); |
787 | } | 797 | } |
@@ -945,7 +955,7 @@ const iString *infoPage_Fonts(iRangecc query) { | |||
945 | appendFormat_String(str, "### %s\n", | 955 | appendFormat_String(str, "### %s\n", |
946 | isEmpty_String(packId) ? "fonts.ini" : | 956 | isEmpty_String(packId) ? "fonts.ini" : |
947 | cstr_String(packId)); | 957 | cstr_String(packId)); |
948 | append_String(str, collect_String(infoText_FontPack(pack))); | 958 | append_String(str, collect_String(infoText_FontPack(pack, iFalse))); |
949 | appendFormat_String(str, "=> %s ${fontpack.meta.viewfile}\n", | 959 | appendFormat_String(str, "=> %s ${fontpack.meta.viewfile}\n", |
950 | cstrCollect_String(makeFileUrl_String(&spec->sourcePath))); | 960 | cstrCollect_String(makeFileUrl_String(&spec->sourcePath))); |
951 | if (pack->isStandalone) { | 961 | if (pack->isStandalone) { |
@@ -1018,6 +1028,7 @@ void install_Fonts(const iString *packId, const iBlock *data) { | |||
1018 | iRelease(f); | 1028 | iRelease(f); |
1019 | /* Newly installed fontpacks may have a higher priority that overrides other fonts. */ | 1029 | /* Newly installed fontpacks may have a higher priority that overrides other fonts. */ |
1020 | reload_Fonts(); | 1030 | reload_Fonts(); |
1031 | availableFontsChanged_App(); | ||
1021 | } | 1032 | } |
1022 | 1033 | ||
1023 | void installFontFile_Fonts(const iString *fileName, const iBlock *data) { | 1034 | void installFontFile_Fonts(const iString *fileName, const iBlock *data) { |
@@ -1028,6 +1039,7 @@ void installFontFile_Fonts(const iString *fileName, const iBlock *data) { | |||
1028 | } | 1039 | } |
1029 | iRelease(f); | 1040 | iRelease(f); |
1030 | reload_Fonts(); | 1041 | reload_Fonts(); |
1042 | availableFontsChanged_App(); | ||
1031 | } | 1043 | } |
1032 | 1044 | ||
1033 | void enablePack_Fonts(const iString *packId, iBool enable) { | 1045 | void enablePack_Fonts(const iString *packId, iBool enable) { |
@@ -1040,6 +1052,7 @@ void enablePack_Fonts(const iString *packId, iBool enable) { | |||
1040 | } | 1052 | } |
1041 | updateActive_Fonts(); | 1053 | updateActive_Fonts(); |
1042 | resetFonts_App(); | 1054 | resetFonts_App(); |
1055 | availableFontsChanged_App(); | ||
1043 | invalidate_Window(get_MainWindow()); | 1056 | invalidate_Window(get_MainWindow()); |
1044 | } | 1057 | } |
1045 | 1058 | ||
@@ -1047,5 +1060,100 @@ void updateActive_Fonts(void) { | |||
1047 | sortSpecs_Fonts_(&fonts_); | 1060 | sortSpecs_Fonts_(&fonts_); |
1048 | } | 1061 | } |
1049 | 1062 | ||
1050 | iDefineClass(FontFile) | 1063 | static void findCharactersInCMap_(iGmRequest *d, iGmRequest *req) { |
1064 | /* Note: Called in background thread. */ | ||
1065 | iUnused(req); | ||
1066 | const iString *missingChars = userData_Object(d); | ||
1067 | if (isSuccess_GmStatusCode(status_GmRequest(d))) { | ||
1068 | iStringList *matchingPacks = new_StringList(); | ||
1069 | iChar needed[20]; | ||
1070 | iChar minChar = UINT32_MAX, maxChar = 0; | ||
1071 | size_t numNeeded = 0; | ||
1072 | iConstForEach(String, ch, missingChars) { | ||
1073 | needed[numNeeded++] = ch.value; | ||
1074 | minChar = iMin(minChar, ch.value); | ||
1075 | maxChar = iMax(maxChar, ch.value); | ||
1076 | if (numNeeded == iElemCount(needed)) { | ||
1077 | /* Shouldn't be that many. */ | ||
1078 | break; | ||
1079 | } | ||
1080 | } | ||
1081 | iBlock *data = decompressGzip_Block(body_GmRequest(d)); | ||
1082 | iRangecc line = iNullRange; | ||
1083 | while (nextSplit_Rangecc(range_Block(data), "\n", &line)) { | ||
1084 | iRangecc fontpackPath = iNullRange; | ||
1085 | for (const char *pos = line.start; pos < line.end; pos++) { | ||
1086 | if (*pos == ':') { | ||
1087 | fontpackPath.start = line.start; | ||
1088 | fontpackPath.end = pos; | ||
1089 | line.start = pos + 1; | ||
1090 | trimStart_Rangecc(&line); | ||
1091 | break; | ||
1092 | } | ||
1093 | } | ||
1094 | if (fontpackPath.start) { | ||
1095 | /* Parse the character ranges and see if any match what we need. */ | ||
1096 | const char *pos = line.start; | ||
1097 | while (pos < line.end) { | ||
1098 | char *endp; | ||
1099 | uint32_t first = strtoul(pos, &endp, 10); | ||
1100 | uint32_t last = first; | ||
1101 | if (*endp == '-') { | ||
1102 | last = strtoul(endp + 1, &endp, 10); | ||
1103 | } | ||
1104 | if (maxChar < first) { | ||
1105 | break; /* The rest are even higher. */ | ||
1106 | } | ||
1107 | if (minChar <= last) { | ||
1108 | for (size_t i = 0; i < numNeeded; i++) { | ||
1109 | if (needed[i] >= first && needed[i] <= last) { | ||
1110 | /* Got it. */ | ||
1111 | pushBackRange_StringList(matchingPacks, fontpackPath); | ||
1112 | break; | ||
1113 | } | ||
1114 | } | ||
1115 | } | ||
1116 | pos = endp + 1; | ||
1117 | } | ||
1118 | } | ||
1119 | } | ||
1120 | delete_Block(data); | ||
1121 | iString result; | ||
1122 | init_String(&result); | ||
1123 | format_String(&result, "font.found chars:%s packs:", cstr_String(missingChars)); | ||
1124 | iConstForEach(StringList, s, matchingPacks) { | ||
1125 | if (s.pos != 0) { | ||
1126 | appendCStr_String(&result, ","); | ||
1127 | } | ||
1128 | append_String(&result, s.value); | ||
1129 | } | ||
1130 | postCommandString_Root(NULL, &result); | ||
1131 | deinit_String(&result); | ||
1132 | iRelease(matchingPacks); | ||
1133 | } | ||
1134 | else { | ||
1135 | /* Report error. */ | ||
1136 | postCommandf_Root(NULL, | ||
1137 | "font.found chars:%s error:%d msg:\x1b[1m%s\x1b[0m\n%s", | ||
1138 | cstr_String(missingChars), | ||
1139 | status_GmRequest(d), | ||
1140 | cstr_String(meta_GmRequest(d)), | ||
1141 | cstr_String(url_GmRequest(d))); | ||
1142 | } | ||
1143 | // fflush(stdout); | ||
1144 | delete_String(userData_Object(d)); | ||
1145 | /* We can't delete ourselves; threads must be joined from another thread. */ | ||
1146 | SDL_PushEvent((SDL_Event *) &(SDL_UserEvent){ | ||
1147 | .type = SDL_USEREVENT, .code = releaseObject_UserEventCode, .data1 = d }); | ||
1148 | } | ||
1051 | 1149 | ||
1150 | void searchOnlineLibraryForCharacters_Fonts(const iString *chars) { | ||
1151 | /* Fetch the character map from skyjake.fi. */ | ||
1152 | iGmRequest *req = new_GmRequest(certs_App()); | ||
1153 | setUrl_GmRequest(req, collectNewCStr_String("gemini://skyjake.fi/fonts/cmap.txt.gz")); | ||
1154 | setUserData_Object(req, copy_String(chars)); | ||
1155 | iConnect(GmRequest, req, finished, req, findCharactersInCMap_); | ||
1156 | submit_GmRequest(req); | ||
1157 | } | ||
1158 | |||
1159 | iDefineClass(FontFile) | ||
diff --git a/src/fontpack.h b/src/fontpack.h index aa6f2b9f..f6d4d483 100644 --- a/src/fontpack.h +++ b/src/fontpack.h | |||
@@ -160,7 +160,7 @@ const iString * loadPath_FontPack (const iFontPack *); /* may return N | |||
160 | iBool isDisabled_FontPack (const iFontPack *); | 160 | iBool isDisabled_FontPack (const iFontPack *); |
161 | iBool isReadOnly_FontPack (const iFontPack *); | 161 | iBool isReadOnly_FontPack (const iFontPack *); |
162 | const iPtrArray * listSpecs_FontPack (const iFontPack *); | 162 | const iPtrArray * listSpecs_FontPack (const iFontPack *); |
163 | iString * infoText_FontPack (const iFontPack *); | 163 | iString * infoText_FontPack (const iFontPack *, iBool isFull); |
164 | const iArray * actions_FontPack (const iFontPack *, iBool showInstalled); | 164 | const iArray * actions_FontPack (const iFontPack *, iBool showInstalled); |
165 | 165 | ||
166 | const iString * idFromUrl_FontPack (const iString *url); | 166 | const iString * idFromUrl_FontPack (const iString *url); |
@@ -186,3 +186,5 @@ void reload_Fonts (void); | |||
186 | iLocalDef iBool isInstalled_Fonts(const char *packId) { | 186 | iLocalDef iBool isInstalled_Fonts(const char *packId) { |
187 | return pack_Fonts(packId) != NULL; | 187 | return pack_Fonts(packId) != NULL; |
188 | } | 188 | } |
189 | |||
190 | void searchOnlineLibraryForCharacters_Fonts (const iString *chars); | ||
diff --git a/src/gmdocument.c b/src/gmdocument.c index 5ff8c72f..a85e4b71 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -333,13 +333,14 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li | |||
333 | link->urlRange = capturedRange_RegExpMatch(&m, 1); | 333 | link->urlRange = capturedRange_RegExpMatch(&m, 1); |
334 | setRange_String(&link->url, link->urlRange); | 334 | setRange_String(&link->url, link->urlRange); |
335 | set_String(&link->url, canonicalUrl_String(absoluteUrl_String(&d->url, &link->url))); | 335 | set_String(&link->url, canonicalUrl_String(absoluteUrl_String(&d->url, &link->url))); |
336 | if (startsWithCase_String(&link->url, "about:command")) { | 336 | /* If invalid, disregard the link. */ |
337 | /* This is a special internal page that allows submitting UI events. */ | 337 | if ((d->format == gemini_SourceFormat && size_String(&link->url) > prefs_App()->maxUrlSize) || |
338 | if (!d->enableCommandLinks) { | 338 | (startsWithCase_String(&link->url, "about:command") |
339 | delete_GmLink(link); | 339 | /* this is a special internal page that allows submitting UI events */ |
340 | *linkId = 0; | 340 | && !d->enableCommandLinks)) { |
341 | return line; | 341 | delete_GmLink(link); |
342 | } | 342 | *linkId = 0; |
343 | return line; | ||
343 | } | 344 | } |
344 | /* Check the URL. */ { | 345 | /* Check the URL. */ { |
345 | iUrl parts; | 346 | iUrl parts; |
@@ -370,6 +371,13 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li | |||
370 | } | 371 | } |
371 | else if (equalCase_Rangecc(parts.scheme, "data")) { | 372 | else if (equalCase_Rangecc(parts.scheme, "data")) { |
372 | setScheme_GmLink_(link, data_GmLinkScheme); | 373 | setScheme_GmLink_(link, data_GmLinkScheme); |
374 | if (startsWith_Rangecc(parts.path, "image/png") || | ||
375 | startsWith_Rangecc(parts.path, "image/jpg") || | ||
376 | startsWith_Rangecc(parts.path, "image/jpeg") || | ||
377 | startsWith_Rangecc(parts.path, "image/webp") || | ||
378 | startsWith_Rangecc(parts.path, "image/gif")) { | ||
379 | link->flags |= imageFileExtension_GmLinkFlag; | ||
380 | } | ||
373 | } | 381 | } |
374 | else if (equalCase_Rangecc(parts.scheme, "about")) { | 382 | else if (equalCase_Rangecc(parts.scheme, "about")) { |
375 | setScheme_GmLink_(link, about_GmLinkScheme); | 383 | setScheme_GmLink_(link, about_GmLinkScheme); |
@@ -382,7 +390,7 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li | |||
382 | iString *path = newRange_String(parts.path); | 390 | iString *path = newRange_String(parts.path); |
383 | if (endsWithCase_String(path, ".gif") || endsWithCase_String(path, ".jpg") || | 391 | if (endsWithCase_String(path, ".gif") || endsWithCase_String(path, ".jpg") || |
384 | endsWithCase_String(path, ".jpeg") || endsWithCase_String(path, ".png") || | 392 | endsWithCase_String(path, ".jpeg") || endsWithCase_String(path, ".png") || |
385 | endsWithCase_String(path, ".tga") || endsWithCase_String(path, ".psd") || | 393 | endsWithCase_String(path, ".tga") || endsWithCase_String(path, ".psd") || |
386 | #if defined (LAGRANGE_ENABLE_WEBP) | 394 | #if defined (LAGRANGE_ENABLE_WEBP) |
387 | endsWithCase_String(path, ".webp") || | 395 | endsWithCase_String(path, ".webp") || |
388 | #endif | 396 | #endif |
@@ -483,8 +491,7 @@ static iBool isNormalized_GmDocument_(const iGmDocument *d) { | |||
483 | } | 491 | } |
484 | 492 | ||
485 | static enum iGmDocumentTheme currentTheme_(void) { | 493 | static enum iGmDocumentTheme currentTheme_(void) { |
486 | return (isDark_ColorTheme(colorTheme_App()) ? prefs_App()->docThemeDark | 494 | return docTheme_Prefs(prefs_App()); |
487 | : prefs_App()->docThemeLight); | ||
488 | } | 495 | } |
489 | 496 | ||
490 | static void alignDecoration_GmRun_(iGmRun *run, iBool isCentered) { | 497 | static void alignDecoration_GmRun_(iGmRun *run, iBool isCentered) { |
@@ -904,6 +911,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
904 | : scheme == titan_GmLinkScheme ? uploadArrow | 911 | : scheme == titan_GmLinkScheme ? uploadArrow |
905 | : scheme == finger_GmLinkScheme ? pointingFinger | 912 | : scheme == finger_GmLinkScheme ? pointingFinger |
906 | : scheme == mailto_GmLinkScheme ? envelope | 913 | : scheme == mailto_GmLinkScheme ? envelope |
914 | : scheme == data_GmLinkScheme ? paperclip_Icon | ||
907 | : link->flags & remote_GmLinkFlag ? globe | 915 | : link->flags & remote_GmLinkFlag ? globe |
908 | : link->flags & imageFileExtension_GmLinkFlag ? image | 916 | : link->flags & imageFileExtension_GmLinkFlag ? image |
909 | : link->flags & fontpackFileExtension_GmLinkFlag ? fontpack_Icon | 917 | : link->flags & fontpackFileExtension_GmLinkFlag ? fontpack_Icon |
@@ -1283,7 +1291,7 @@ static void updateIconBasedOnUrl_GmDocument_(iGmDocument *d) { | |||
1283 | } | 1291 | } |
1284 | } | 1292 | } |
1285 | 1293 | ||
1286 | void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { | 1294 | void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *paletteSeed, const iBlock *iconSeed) { |
1287 | const iPrefs * prefs = prefs_App(); | 1295 | const iPrefs * prefs = prefs_App(); |
1288 | enum iGmDocumentTheme theme = currentTheme_(); | 1296 | enum iGmDocumentTheme theme = currentTheme_(); |
1289 | static const iChar siteIcons[] = { | 1297 | static const iChar siteIcons[] = { |
@@ -1294,6 +1302,16 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { | |||
1294 | 0x1f306, 0x1f308, 0x1f30a, 0x1f319, 0x1f31f, 0x1f320, 0x1f340, 0x1f4cd, 0x1f4e1, 0x1f531, | 1302 | 0x1f306, 0x1f308, 0x1f30a, 0x1f319, 0x1f31f, 0x1f320, 0x1f340, 0x1f4cd, 0x1f4e1, 0x1f531, |
1295 | 0x1f533, 0x1f657, 0x1f659, 0x1f665, 0x1f668, 0x1f66b, 0x1f78b, 0x1f796, 0x1f79c, | 1303 | 0x1f533, 0x1f657, 0x1f659, 0x1f665, 0x1f668, 0x1f66b, 0x1f78b, 0x1f796, 0x1f79c, |
1296 | }; | 1304 | }; |
1305 | if (!iconSeed) { | ||
1306 | iconSeed = paletteSeed; | ||
1307 | } | ||
1308 | if (iconSeed && !isEmpty_Block(iconSeed)) { | ||
1309 | const uint32_t seedHash = themeHash_(iconSeed); | ||
1310 | d->siteIcon = siteIcons[(seedHash >> 7) % iElemCount(siteIcons)]; | ||
1311 | } | ||
1312 | else { | ||
1313 | d->siteIcon = 0; | ||
1314 | } | ||
1297 | /* Default colors. These are used on "about:" pages and local files, for example. */ { | 1315 | /* Default colors. These are used on "about:" pages and local files, for example. */ { |
1298 | /* Link colors are generally the same in all themes. */ | 1316 | /* Link colors are generally the same in all themes. */ |
1299 | set_Color(tmBadLink_ColorId, get_Color(red_ColorId)); | 1317 | set_Color(tmBadLink_ColorId, get_Color(red_ColorId)); |
@@ -1495,13 +1513,11 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { | |||
1495 | } | 1513 | } |
1496 | } | 1514 | } |
1497 | } | 1515 | } |
1498 | if (seed && !isEmpty_Block(seed)) { | 1516 | if (paletteSeed && !isEmpty_Block(paletteSeed)) { |
1499 | d->themeSeed = themeHash_(seed); | 1517 | d->themeSeed = themeHash_(paletteSeed); |
1500 | d->siteIcon = siteIcons[(d->themeSeed >> 7) % iElemCount(siteIcons)]; | ||
1501 | } | 1518 | } |
1502 | else { | 1519 | else { |
1503 | d->themeSeed = 0; | 1520 | d->themeSeed = 0; |
1504 | d->siteIcon = 0; | ||
1505 | } | 1521 | } |
1506 | /* Set up colors. */ | 1522 | /* Set up colors. */ |
1507 | if (d->themeSeed) { | 1523 | if (d->themeSeed) { |
@@ -1552,7 +1568,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { | |||
1552 | 1568 | ||
1553 | if (theme == colorfulDark_GmDocumentTheme) { | 1569 | if (theme == colorfulDark_GmDocumentTheme) { |
1554 | iHSLColor base = { hues[primIndex], | 1570 | iHSLColor base = { hues[primIndex], |
1555 | 0.8f * (d->themeSeed >> 24) / 255.0f, | 1571 | 0.8f * (d->themeSeed >> 24) / 255.0f + minSat_HSLColor, |
1556 | 0.06f + 0.09f * ((d->themeSeed >> 5) & 0x7) / 7.0f, | 1572 | 0.06f + 0.09f * ((d->themeSeed >> 5) & 0x7) / 7.0f, |
1557 | 1.0f }; | 1573 | 1.0f }; |
1558 | iHSLColor altBase = { altHue, base.sat, base.lum, 1 }; | 1574 | iHSLColor altBase = { altHue, base.sat, base.lum, 1 }; |
@@ -1562,13 +1578,13 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { | |||
1562 | setHsl_Color(tmBannerBackground_ColorId, addSatLum_HSLColor(base, 0.1f, 0.04f * (isBannerLighter ? 1 : -1))); | 1578 | setHsl_Color(tmBannerBackground_ColorId, addSatLum_HSLColor(base, 0.1f, 0.04f * (isBannerLighter ? 1 : -1))); |
1563 | setHsl_Color(tmBannerTitle_ColorId, setLum_HSLColor(addSatLum_HSLColor(base, 0.1f, 0), 0.55f)); | 1579 | setHsl_Color(tmBannerTitle_ColorId, setLum_HSLColor(addSatLum_HSLColor(base, 0.1f, 0), 0.55f)); |
1564 | setHsl_Color(tmBannerIcon_ColorId, setLum_HSLColor(addSatLum_HSLColor(base, 0.35f, 0), 0.65f)); | 1580 | setHsl_Color(tmBannerIcon_ColorId, setLum_HSLColor(addSatLum_HSLColor(base, 0.35f, 0), 0.65f)); |
1565 | 1581 | ||
1566 | // printf("primHue: %zu alts: %d %d isDarkBgSat: %d\n", | 1582 | // printf("primHue: %zu alts: %d %d isDarkBgSat: %d\n", |
1567 | // primIndex, | 1583 | // primIndex, |
1568 | // altHues[primIndex].index[altIndex[0]], | 1584 | // altHues[primIndex].index[altIndex[0]], |
1569 | // altHues[primIndex].index[altIndex[1]], | 1585 | // altHues[primIndex].index[altIndex[1]], |
1570 | // isDarkBgSat); | 1586 | // isDarkBgSat); |
1571 | 1587 | ||
1572 | const float titleLum = 0.2f * ((d->themeSeed >> 17) & 0x7) / 7.0f; | 1588 | const float titleLum = 0.2f * ((d->themeSeed >> 17) & 0x7) / 7.0f; |
1573 | setHsl_Color(tmHeading1_ColorId, setLum_HSLColor(altBase, titleLum + 0.80f)); | 1589 | setHsl_Color(tmHeading1_ColorId, setLum_HSLColor(altBase, titleLum + 0.80f)); |
1574 | setHsl_Color(tmHeading2_ColorId, setLum_HSLColor(altBase, titleLum + 0.70f)); | 1590 | setHsl_Color(tmHeading2_ColorId, setLum_HSLColor(altBase, titleLum + 0.70f)); |
@@ -1730,8 +1746,8 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { | |||
1730 | /* Derived colors. */ | 1746 | /* Derived colors. */ |
1731 | setDerivedThemeColors_(theme); | 1747 | setDerivedThemeColors_(theme); |
1732 | /* Special exceptions. */ | 1748 | /* Special exceptions. */ |
1733 | if (seed) { | 1749 | if (iconSeed) { |
1734 | if (equal_CStr(cstr_Block(seed), "gemini.circumlunar.space")) { | 1750 | if (equal_CStr(cstr_Block(iconSeed), "gemini.circumlunar.space")) { |
1735 | d->siteIcon = 0x264a; /* gemini symbol */ | 1751 | d->siteIcon = 0x264a; /* gemini symbol */ |
1736 | } | 1752 | } |
1737 | updateIconBasedOnUrl_GmDocument_(d); | 1753 | updateIconBasedOnUrl_GmDocument_(d); |
@@ -1752,7 +1768,8 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { | |||
1752 | void makePaletteGlobal_GmDocument(const iGmDocument *d) { | 1768 | void makePaletteGlobal_GmDocument(const iGmDocument *d) { |
1753 | if (!d->isPaletteValid) { | 1769 | if (!d->isPaletteValid) { |
1754 | /* Recompute the palette since it's needed now. */ | 1770 | /* Recompute the palette since it's needed now. */ |
1755 | setThemeSeed_GmDocument((iGmDocument *) d, urlThemeSeed_String(&d->url)); | 1771 | setThemeSeed_GmDocument( |
1772 | (iGmDocument *) d, urlPaletteSeed_String(&d->url), urlThemeSeed_String(&d->url)); | ||
1756 | } | 1773 | } |
1757 | iAssert(d->isPaletteValid); | 1774 | iAssert(d->isPaletteValid); |
1758 | memcpy(get_Root()->tmPalette, d->palette, sizeof(d->palette)); | 1775 | memcpy(get_Root()->tmPalette, d->palette, sizeof(d->palette)); |
@@ -1929,7 +1946,7 @@ static void normalize_GmDocument(iGmDocument *d) { | |||
1929 | void setUrl_GmDocument(iGmDocument *d, const iString *url) { | 1946 | void setUrl_GmDocument(iGmDocument *d, const iString *url) { |
1930 | url = canonicalUrl_String(url); | 1947 | url = canonicalUrl_String(url); |
1931 | set_String(&d->url, url); | 1948 | set_String(&d->url, url); |
1932 | setThemeSeed_GmDocument(d, urlThemeSeed_String(url)); | 1949 | setThemeSeed_GmDocument(d, urlPaletteSeed_String(url), urlThemeSeed_String(url)); |
1933 | iUrl parts; | 1950 | iUrl parts; |
1934 | init_Url(&parts, url); | 1951 | init_Url(&parts, url); |
1935 | setRange_String(&d->localHost, parts.host); | 1952 | setRange_String(&d->localHost, parts.host); |
diff --git a/src/gmdocument.h b/src/gmdocument.h index 0969794c..6c25dd6f 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -181,7 +181,9 @@ enum iGmDocumentUpdate { | |||
181 | final_GmDocumentUpdate, /* process all lines, including the last one if not terminated */ | 181 | final_GmDocumentUpdate, /* process all lines, including the last one if not terminated */ |
182 | }; | 182 | }; |
183 | 183 | ||
184 | void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); | 184 | void setThemeSeed_GmDocument (iGmDocument *, |
185 | const iBlock *paletteSeed, | ||
186 | const iBlock *iconSeed); /* seeds may be NULL; NULL iconSeed will use paletteSeed instead */ | ||
185 | void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format); | 187 | void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format); |
186 | void setWidth_GmDocument (iGmDocument *, int width, int canvasWidth); | 188 | void setWidth_GmDocument (iGmDocument *, int width, int canvasWidth); |
187 | iBool updateWidth_GmDocument (iGmDocument *, int width, int canvasWidth); | 189 | iBool updateWidth_GmDocument (iGmDocument *, int width, int canvasWidth); |
diff --git a/src/gmutil.c b/src/gmutil.c index ecfe2128..b32722ac 100644 --- a/src/gmutil.c +++ b/src/gmutil.c | |||
@@ -22,6 +22,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #include "gmutil.h" | 23 | #include "gmutil.h" |
24 | #include "fontpack.h" | 24 | #include "fontpack.h" |
25 | #include "lang.h" | ||
26 | #include "sitespec.h" | ||
27 | #include "ui/color.h" | ||
25 | 28 | ||
26 | #include <the_Foundation/file.h> | 29 | #include <the_Foundation/file.h> |
27 | #include <the_Foundation/fileinfo.h> | 30 | #include <the_Foundation/fileinfo.h> |
@@ -277,6 +280,19 @@ const iBlock *urlThemeSeed_String(const iString *url) { | |||
277 | return collect_Block(newRange_Block(user)); | 280 | return collect_Block(newRange_Block(user)); |
278 | } | 281 | } |
279 | 282 | ||
283 | const iBlock *urlPaletteSeed_String(const iString *url) { | ||
284 | if (equalCase_Rangecc(urlScheme_String(url), "file")) { | ||
285 | return urlThemeSeed_String(url); | ||
286 | } | ||
287 | /* Check for a site-specific setting. */ | ||
288 | const iString *seed = | ||
289 | valueString_SiteSpec(collectNewRange_String(urlRoot_String(url)), paletteSeed_SiteSpecKey); | ||
290 | if (!isEmpty_String(seed)) { | ||
291 | return utf8_String(seed); | ||
292 | } | ||
293 | return urlThemeSeed_String(url); | ||
294 | } | ||
295 | |||
280 | static iBool isAbsolutePath_(iRangecc path) { | 296 | static iBool isAbsolutePath_(iRangecc path) { |
281 | return isAbsolute_Path(collect_String(urlDecode_String(collect_String(newRange_String(path))))); | 297 | return isAbsolute_Path(collect_String(urlDecode_String(collect_String(newRange_String(path))))); |
282 | } | 298 | } |
@@ -735,13 +751,36 @@ const iString *canonicalUrl_String(const iString *d) { | |||
735 | return canon ? collect_String(canon) : d; | 751 | return canon ? collect_String(canon) : d; |
736 | } | 752 | } |
737 | 753 | ||
754 | const iString *prettyDataUrl_String(const iString *d, int contentColor) { | ||
755 | iUrl url; | ||
756 | init_Url(&url, d); | ||
757 | if (!equalCase_Rangecc(url.scheme, "data")) { | ||
758 | return d; | ||
759 | } | ||
760 | iString *pretty = new_String(); | ||
761 | const char *comma = strchr(url.path.start, ','); | ||
762 | if (!comma) { | ||
763 | comma = iMin(constEnd_String(d), constBegin_String(d) + 256); | ||
764 | } | ||
765 | appendRange_String(pretty, (iRangecc){ constBegin_String(d), comma }); | ||
766 | if (size_Range(&url.path)) { | ||
767 | if (contentColor != none_ColorId) { | ||
768 | appendCStr_String(pretty, escape_Color(contentColor)); | ||
769 | } | ||
770 | appendCStr_String(pretty, " ("); | ||
771 | appendCStr_String(pretty, formatCStrs_Lang("num.bytes.n", size_Range(&url.path))); | ||
772 | appendCStr_String(pretty, ")"); | ||
773 | } | ||
774 | return collect_String(pretty); | ||
775 | } | ||
776 | |||
738 | iRangecc mediaTypeWithoutParameters_Rangecc(iRangecc mime) { | 777 | iRangecc mediaTypeWithoutParameters_Rangecc(iRangecc mime) { |
739 | iRangecc part = iNullRange; | 778 | iRangecc part = iNullRange; |
740 | nextSplit_Rangecc(mime, ";", &part); | 779 | nextSplit_Rangecc(mime, ";", &part); |
741 | return part; | 780 | return part; |
742 | } | 781 | } |
743 | 782 | ||
744 | const iString *feedEntryOpenCommand_String(const iString *url, int newTab) { | 783 | const iString *feedEntryOpenCommand_String(const iString *url, int newTab, int newWindow) { |
745 | if (!isEmpty_String(url)) { | 784 | if (!isEmpty_String(url)) { |
746 | iString *cmd = collectNew_String(); | 785 | iString *cmd = collectNew_String(); |
747 | const size_t fragPos = indexOf_String(url, '#'); | 786 | const size_t fragPos = indexOf_String(url, '#'); |
@@ -749,15 +788,20 @@ const iString *feedEntryOpenCommand_String(const iString *url, int newTab) { | |||
749 | iString *head = newRange_String( | 788 | iString *head = newRange_String( |
750 | (iRangecc){ constBegin_String(url) + fragPos + 1, constEnd_String(url) }); | 789 | (iRangecc){ constBegin_String(url) + fragPos + 1, constEnd_String(url) }); |
751 | format_String(cmd, | 790 | format_String(cmd, |
752 | "open fromsidebar:1 newtab:%d gotourlheading:%s url:%s", | 791 | "open fromsidebar:1 newtab:%d newwindow:%d gotourlheading:%s url:%s", |
753 | newTab, | 792 | newTab, |
793 | newWindow, | ||
754 | cstr_String(head), | 794 | cstr_String(head), |
755 | cstr_Rangecc((iRangecc){ constBegin_String(url), | 795 | cstr_Rangecc((iRangecc){ constBegin_String(url), |
756 | constBegin_String(url) + fragPos })); | 796 | constBegin_String(url) + fragPos })); |
757 | delete_String(head); | 797 | delete_String(head); |
758 | } | 798 | } |
759 | else { | 799 | else { |
760 | format_String(cmd, "open fromsidebar:1 newtab:%d url:%s", newTab, cstr_String(url)); | 800 | format_String(cmd, |
801 | "open fromsidebar:1 newtab:%d newwindow:%d url:%s", | ||
802 | newTab, | ||
803 | newWindow, | ||
804 | cstr_String(url)); | ||
761 | } | 805 | } |
762 | return cmd; | 806 | return cmd; |
763 | } | 807 | } |
@@ -901,40 +945,3 @@ const iGmError *get_GmError(enum iGmStatusCode code) { | |||
901 | return &errors_[0].err; /* unknown */ | 945 | return &errors_[0].err; /* unknown */ |
902 | } | 946 | } |
903 | 947 | ||
904 | int replaceRegExp_String(iString *d, const iRegExp *regexp, const char *replacement, | ||
905 | void (*matchHandler)(void *, const iRegExpMatch *), | ||
906 | void *context) { | ||
907 | iRegExpMatch m; | ||
908 | iString result; | ||
909 | int numMatches = 0; | ||
910 | const char *pos = constBegin_String(d); | ||
911 | init_RegExpMatch(&m); | ||
912 | init_String(&result); | ||
913 | while (matchString_RegExp(regexp, d, &m)) { | ||
914 | appendRange_String(&result, (iRangecc){ pos, begin_RegExpMatch(&m) }); | ||
915 | /* Replace any capture group back-references. */ | ||
916 | for (const char *ch = replacement; *ch; ch++) { | ||
917 | if (*ch == '\\') { | ||
918 | ch++; | ||
919 | if (*ch == '\\') { | ||
920 | appendCStr_String(&result, "\\"); | ||
921 | } | ||
922 | else if (*ch >= '0' && *ch <= '9') { | ||
923 | appendRange_String(&result, capturedRange_RegExpMatch(&m, *ch - '0')); | ||
924 | } | ||
925 | } | ||
926 | else { | ||
927 | appendData_Block(&result.chars, ch, 1); | ||
928 | } | ||
929 | } | ||
930 | if (matchHandler) { | ||
931 | matchHandler(context, &m); | ||
932 | } | ||
933 | pos = end_RegExpMatch(&m); | ||
934 | numMatches++; | ||
935 | } | ||
936 | appendRange_String(&result, (iRangecc){ pos, constEnd_String(d) }); | ||
937 | set_String(d, &result); | ||
938 | deinit_String(&result); | ||
939 | return numMatches; | ||
940 | } | ||
diff --git a/src/gmutil.h b/src/gmutil.h index 1594afc4..e4284cfd 100644 --- a/src/gmutil.h +++ b/src/gmutil.h | |||
@@ -120,6 +120,7 @@ uint16_t urlPort_String (const iString *); | |||
120 | iRangecc urlUser_String (const iString *); | 120 | iRangecc urlUser_String (const iString *); |
121 | iRangecc urlRoot_String (const iString *); | 121 | iRangecc urlRoot_String (const iString *); |
122 | const iBlock * urlThemeSeed_String (const iString *); | 122 | const iBlock * urlThemeSeed_String (const iString *); |
123 | const iBlock * urlPaletteSeed_String (const iString *); | ||
123 | 124 | ||
124 | const iString * absoluteUrl_String (const iString *, const iString *urlMaybeRelative); | 125 | const iString * absoluteUrl_String (const iString *, const iString *urlMaybeRelative); |
125 | iBool isLikelyUrl_String (const iString *); | 126 | iBool isLikelyUrl_String (const iString *); |
@@ -141,6 +142,7 @@ void urlEncodeSpaces_String (iString *); | |||
141 | const iString * withSpacesEncoded_String(const iString *); | 142 | const iString * withSpacesEncoded_String(const iString *); |
142 | const iString * withScheme_String (const iString *, const char *scheme); /* replace URI scheme */ | 143 | const iString * withScheme_String (const iString *, const char *scheme); /* replace URI scheme */ |
143 | const iString * canonicalUrl_String (const iString *); | 144 | const iString * canonicalUrl_String (const iString *); |
145 | const iString * prettyDataUrl_String (const iString *, int contentColor); | ||
144 | 146 | ||
145 | const char * mediaType_Path (const iString *path); | 147 | const char * mediaType_Path (const iString *path); |
146 | const char * mediaTypeFromFileExtension_String (const iString *); | 148 | const char * mediaTypeFromFileExtension_String (const iString *); |
@@ -149,9 +151,4 @@ iRangecc mediaTypeWithoutParameters_Rangecc (iRangecc mime); | |||
149 | const iString * findContainerArchive_Path (const iString *path); | 151 | const iString * findContainerArchive_Path (const iString *path); |
150 | 152 | ||
151 | 153 | ||
152 | const iString * feedEntryOpenCommand_String (const iString *url, int newTab); /* checks fragment */ | 154 | const iString * feedEntryOpenCommand_String (const iString *url, int newTab, int newWindow); /* checks fragment */ |
153 | |||
154 | /* TODO: Consider adding this to the_Foundation. */ | ||
155 | int replaceRegExp_String (iString *, const iRegExp *regexp, const char *replacement, | ||
156 | void (*matchHandler)(void *, const iRegExpMatch *), | ||
157 | void *context); | ||
diff --git a/src/macos.m b/src/macos.m index 7b248c3b..5f376874 100644 --- a/src/macos.m +++ b/src/macos.m | |||
@@ -83,7 +83,9 @@ static void ignoreImmediateKeyDownEvents_(void) { | |||
83 | However, we shouldn't double-activate menu items when a shortcut key is used in our | 83 | However, we shouldn't double-activate menu items when a shortcut key is used in our |
84 | widgets. Quite a kludge: take advantage of Window's focus-acquisition threshold to | 84 | widgets. Quite a kludge: take advantage of Window's focus-acquisition threshold to |
85 | ignore the immediately following key down events. */ | 85 | ignore the immediately following key down events. */ |
86 | get_Window()->focusGainedAt = SDL_GetTicks(); | 86 | iForEach(PtrArray, w, collect_PtrArray(listWindows_App())) { |
87 | as_Window(w.ptr)->focusGainedAt = SDL_GetTicks(); | ||
88 | } | ||
87 | } | 89 | } |
88 | 90 | ||
89 | /*----------------------------------------------------------------------------------------------*/ | 91 | /*----------------------------------------------------------------------------------------------*/ |
@@ -435,9 +437,17 @@ static iBool processScrollWheelEvent_(NSEvent *event) { | |||
435 | const iBool isPerPixel = (event.hasPreciseScrollingDeltas != 0); | 437 | const iBool isPerPixel = (event.hasPreciseScrollingDeltas != 0); |
436 | const iBool isInertia = (event.momentumPhase & (NSEventPhaseBegan | NSEventPhaseChanged)) != 0; | 438 | const iBool isInertia = (event.momentumPhase & (NSEventPhaseBegan | NSEventPhaseChanged)) != 0; |
437 | const iBool isEnded = event.scrollingDeltaX == 0.0f && event.scrollingDeltaY == 0.0f && !isInertia; | 439 | const iBool isEnded = event.scrollingDeltaX == 0.0f && event.scrollingDeltaY == 0.0f && !isInertia; |
438 | const iWindow *win = &get_MainWindow()->base; | 440 | const iWindow *win = NULL; //&get_MainWindow()->base; |
439 | if (event.window != nsWindow_(win->win)) { | 441 | /* If this event belongs to one of the MainWindows, handle it and mark it for that window. |
440 | /* Not the main window. */ | 442 | If it's for an auxiliary window, let the system handle it. */ |
443 | iConstForEach(PtrArray, i, mainWindows_App()) { | ||
444 | if (event.window == nsWindow_(as_Window(i.ptr)->win)) { | ||
445 | win = i.ptr; | ||
446 | break; | ||
447 | } | ||
448 | } | ||
449 | if (!win) { //event.window != nsWindow_(win->win)) { | ||
450 | /* Not a main window. */ | ||
441 | return iFalse; | 451 | return iFalse; |
442 | } | 452 | } |
443 | if (isPerPixel) { | 453 | if (isPerPixel) { |
@@ -478,16 +488,18 @@ static iBool processScrollWheelEvent_(NSEvent *event) { | |||
478 | else { | 488 | else { |
479 | SDL_MouseWheelEvent e = { .type = SDL_MOUSEWHEEL }; | 489 | SDL_MouseWheelEvent e = { .type = SDL_MOUSEWHEEL }; |
480 | e.timestamp = SDL_GetTicks(); | 490 | e.timestamp = SDL_GetTicks(); |
491 | e.windowID = id_Window(win); | ||
481 | e.which = 1; /* Distinction between trackpad and regular mouse. */ | 492 | e.which = 1; /* Distinction between trackpad and regular mouse. */ |
482 | /* Disregard any wheel acceleration. */ | 493 | /* Disregard any wheel acceleration. */ |
483 | e.x = event.scrollingDeltaX > 0 ? 1 : event.scrollingDeltaX < 0 ? -1 : 0; | 494 | e.x = event.scrollingDeltaX > 0 ? 1 : event.scrollingDeltaX < 0 ? -1 : 0; |
484 | e.y = event.scrollingDeltaY > 0 ? 1 : event.scrollingDeltaY < 0 ? -1 : 0; | 495 | e.y = event.scrollingDeltaY > 0 ? 1 : event.scrollingDeltaY < 0 ? -1 : 0; |
485 | SDL_PushEvent((SDL_Event *) &e); | 496 | SDL_PushEvent((SDL_Event *) &e); |
486 | return iTrue; | 497 | return iTrue; |
487 | } | 498 | } |
488 | /* Post corresponding MOUSEWHEEL events. */ | 499 | /* Post corresponding MOUSEWHEEL events. */ |
489 | SDL_MouseWheelEvent e = { .type = SDL_MOUSEWHEEL }; | 500 | SDL_MouseWheelEvent e = { .type = SDL_MOUSEWHEEL }; |
490 | e.timestamp = SDL_GetTicks(); | 501 | e.timestamp = SDL_GetTicks(); |
502 | e.windowID = id_Window(win); | ||
491 | e.which = isPerPixel ? 0 : 1; /* Distinction between trackpad and regular mouse. */ | 503 | e.which = isPerPixel ? 0 : 1; /* Distinction between trackpad and regular mouse. */ |
492 | setPerPixel_MouseWheelEvent(&e, isPerPixel); | 504 | setPerPixel_MouseWheelEvent(&e, isPerPixel); |
493 | if (isPerPixel) { | 505 | if (isPerPixel) { |
@@ -517,28 +529,6 @@ static iBool processScrollWheelEvent_(NSEvent *event) { | |||
517 | // printf("#### [%d] dx:%d dy:%d phase:%ld inertia:%d end:%d\n", preventTapGlitch_, e.x, e.y, (long) event.momentumPhase, | 529 | // printf("#### [%d] dx:%d dy:%d phase:%ld inertia:%d end:%d\n", preventTapGlitch_, e.x, e.y, (long) event.momentumPhase, |
518 | // isInertia, isEnded); fflush(stdout); | 530 | // isInertia, isEnded); fflush(stdout); |
519 | SDL_PushEvent((SDL_Event *) &e); | 531 | SDL_PushEvent((SDL_Event *) &e); |
520 | #if 0 | ||
521 | /* On macOS, we handle both trackpad and mouse events. We expect SDL to identify | ||
522 | which device is sending the event. */ | ||
523 | if (ev.wheel.which == 0) { | ||
524 | /* Trackpad with precise scrolling w/inertia (points). */ | ||
525 | setPerPixel_MouseWheelEvent(&ev.wheel, iTrue); | ||
526 | ev.wheel.x *= -d->window->base.pixelRatio; | ||
527 | ev.wheel.y *= d->window->base.pixelRatio; | ||
528 | /* Only scroll on one axis at a time. */ | ||
529 | if (iAbs(ev.wheel.x) > iAbs(ev.wheel.y)) { | ||
530 | ev.wheel.y = 0; | ||
531 | } | ||
532 | else { | ||
533 | ev.wheel.x = 0; | ||
534 | } | ||
535 | } | ||
536 | else { | ||
537 | /* Disregard wheel acceleration applied by the OS. */ | ||
538 | ev.wheel.x = -ev.wheel.x; | ||
539 | ev.wheel.y = iSign(ev.wheel.y); | ||
540 | } | ||
541 | #endif | ||
542 | return iTrue; | 532 | return iTrue; |
543 | } | 533 | } |
544 | 534 | ||
@@ -565,13 +555,6 @@ void setupApplication_MacOS(void) { | |||
565 | windowCloseItem.action = @selector(closeTab); | 555 | windowCloseItem.action = @selector(closeTab); |
566 | [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskScrollWheel | 556 | [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskScrollWheel |
567 | handler:^NSEvent*(NSEvent *event){ | 557 | handler:^NSEvent*(NSEvent *event){ |
568 | // printf("event type: %lu\n", (unsigned long) event.type); | ||
569 | // fflush(stdout); | ||
570 | // if (event.type == NSEventTypeGesture) { | ||
571 | // trackSwipe_(event); | ||
572 | // printf("GESTURE phase:%lu\n", (unsigned long) event.phase); | ||
573 | //fflush(stdout); | ||
574 | // } | ||
575 | if (event.type == NSEventTypeScrollWheel && | 558 | if (event.type == NSEventTypeScrollWheel && |
576 | processScrollWheelEvent_(event)) { | 559 | processScrollWheelEvent_(event)) { |
577 | return nil; /* was eaten */ | 560 | return nil; /* was eaten */ |
diff --git a/src/periodic.c b/src/periodic.c index b4f51ed3..0558ed50 100644 --- a/src/periodic.c +++ b/src/periodic.c | |||
@@ -107,14 +107,17 @@ iBool dispatchCommands_Periodic(iPeriodic *d) { | |||
107 | iConstForEach(Array, i, &d->commands.values) { | 107 | iConstForEach(Array, i, &d->commands.values) { |
108 | const iPeriodicCommand *pc = i.value; | 108 | const iPeriodicCommand *pc = i.value; |
109 | iAssert(isInstance_Object(pc->context, &Class_Widget)); | 109 | iAssert(isInstance_Object(pc->context, &Class_Widget)); |
110 | const SDL_UserEvent ev = { | 110 | iRoot *root = constAs_Widget(pc->context)->root; |
111 | .type = SDL_USEREVENT, | 111 | if (root) { |
112 | .code = command_UserEventCode, | 112 | const SDL_UserEvent ev = { |
113 | .data1 = (void *) cstr_String(&pc->command), | 113 | .type = SDL_USEREVENT, |
114 | .data2 = findRoot_Window(get_Window(), pc->context) | 114 | .code = command_UserEventCode, |
115 | }; | 115 | .data1 = (void *) cstr_String(&pc->command), |
116 | if (ev.data2) { | 116 | .data2 = root, |
117 | setCurrent_Root(ev.data2); | 117 | .windowID = id_Window(root->window), |
118 | }; | ||
119 | setCurrent_Window(root->window); | ||
120 | setCurrent_Root(root); | ||
118 | dispatchEvent_Widget(pc->context, (const SDL_Event *) &ev); | 121 | dispatchEvent_Widget(pc->context, (const SDL_Event *) &ev); |
119 | wasPosted = iTrue; | 122 | wasPosted = iTrue; |
120 | } | 123 | } |
diff --git a/src/prefs.c b/src/prefs.c index 13a1dab7..08355a6a 100644 --- a/src/prefs.c +++ b/src/prefs.c | |||
@@ -66,13 +66,15 @@ void init_Prefs(iPrefs *d) { | |||
66 | d->smoothScrollSpeed[keyboard_ScrollType] = 13; | 66 | d->smoothScrollSpeed[keyboard_ScrollType] = 13; |
67 | d->smoothScrollSpeed[mouse_ScrollType] = 13; | 67 | d->smoothScrollSpeed[mouse_ScrollType] = 13; |
68 | d->loadImageInsteadOfScrolling = iFalse; | 68 | d->loadImageInsteadOfScrolling = iFalse; |
69 | d->collapsePreOnLoad = iFalse; | 69 | d->openDataUrlImagesOnLoad = iFalse; |
70 | d->openArchiveIndexPages = iTrue; | 70 | d->collapsePreOnLoad = iFalse; |
71 | d->addBookmarksToBottom = iTrue; | 71 | d->openArchiveIndexPages = iTrue; |
72 | d->warnAboutMissingGlyphs = iTrue; | 72 | d->addBookmarksToBottom = iTrue; |
73 | d->decodeUserVisibleURLs = iTrue; | 73 | d->warnAboutMissingGlyphs = iTrue; |
74 | d->decodeUserVisibleURLs = iTrue; | ||
74 | d->maxCacheSize = 10; | 75 | d->maxCacheSize = 10; |
75 | d->maxMemorySize = 200; | 76 | d->maxMemorySize = 200; |
77 | d->maxUrlSize = 8192; | ||
76 | setCStr_String(&d->strings[uiFont_PrefsString], "default"); | 78 | setCStr_String(&d->strings[uiFont_PrefsString], "default"); |
77 | setCStr_String(&d->strings[headingFont_PrefsString], "default"); | 79 | setCStr_String(&d->strings[headingFont_PrefsString], "default"); |
78 | setCStr_String(&d->strings[bodyFont_PrefsString], "default"); | 80 | setCStr_String(&d->strings[bodyFont_PrefsString], "default"); |
diff --git a/src/prefs.h b/src/prefs.h index ea864f51..d988399e 100644 --- a/src/prefs.h +++ b/src/prefs.h | |||
@@ -75,6 +75,7 @@ enum iPrefsBool { | |||
75 | hoverLink_PrefsBool, | 75 | hoverLink_PrefsBool, |
76 | smoothScrolling_PrefsBool, | 76 | smoothScrolling_PrefsBool, |
77 | loadImageInsteadOfScrolling_PrefsBool, | 77 | loadImageInsteadOfScrolling_PrefsBool, |
78 | openDataUrlImagesOnLoad_PrefsBool, | ||
78 | collapsePreOnLoad_PrefsBool, | 79 | collapsePreOnLoad_PrefsBool, |
79 | openArchiveIndexPages_PrefsBool, | 80 | openArchiveIndexPages_PrefsBool, |
80 | 81 | ||
@@ -128,6 +129,7 @@ struct Impl_Prefs { | |||
128 | iBool hoverLink; | 129 | iBool hoverLink; |
129 | iBool smoothScrolling; | 130 | iBool smoothScrolling; |
130 | iBool loadImageInsteadOfScrolling; | 131 | iBool loadImageInsteadOfScrolling; |
132 | iBool openDataUrlImagesOnLoad; | ||
131 | iBool collapsePreOnLoad; | 133 | iBool collapsePreOnLoad; |
132 | iBool openArchiveIndexPages; | 134 | iBool openArchiveIndexPages; |
133 | 135 | ||
@@ -172,6 +174,7 @@ struct Impl_Prefs { | |||
172 | /* Network */ | 174 | /* Network */ |
173 | int maxCacheSize; /* MB */ | 175 | int maxCacheSize; /* MB */ |
174 | int maxMemorySize; /* MB */ | 176 | int maxMemorySize; /* MB */ |
177 | int maxUrlSize; /* bytes; longer ones will be disregarded */ | ||
175 | /* Style */ | 178 | /* Style */ |
176 | iStringSet * disabledFontPacks; | 179 | iStringSet * disabledFontPacks; |
177 | int gemtextAnsiEscapes; | 180 | int gemtextAnsiEscapes; |
@@ -190,3 +193,7 @@ iLocalDef float scrollSpeedFactor_Prefs(const iPrefs *d, enum iScrollType type) | |||
190 | iAssert(type >= 0 && type < max_ScrollType); | 193 | iAssert(type >= 0 && type < max_ScrollType); |
191 | return 10.0f / iMax(1, d->smoothScrollSpeed[type]) * (type == mouse_ScrollType ? 0.5f : 1.0f); | 194 | return 10.0f / iMax(1, d->smoothScrollSpeed[type]) * (type == mouse_ScrollType ? 0.5f : 1.0f); |
192 | } | 195 | } |
196 | |||
197 | iLocalDef enum iGmDocumentTheme docTheme_Prefs(const iPrefs *d) { | ||
198 | return isDark_ColorTheme(d->theme) ? d->docThemeDark : d->docThemeLight; | ||
199 | } | ||
diff --git a/src/sitespec.c b/src/sitespec.c index fe80ad13..5133abe5 100644 --- a/src/sitespec.c +++ b/src/sitespec.c | |||
@@ -37,7 +37,8 @@ struct Impl_SiteParams { | |||
37 | iString titanIdentity; /* fingerprint */ | 37 | iString titanIdentity; /* fingerprint */ |
38 | int dismissWarnings; | 38 | int dismissWarnings; |
39 | iStringArray usedIdentities; /* fingerprints; latest ones at the end */ | 39 | iStringArray usedIdentities; /* fingerprints; latest ones at the end */ |
40 | /* TODO: theme seed, style settings */ | 40 | iString paletteSeed; |
41 | /* TODO: style settings */ | ||
41 | }; | 42 | }; |
42 | 43 | ||
43 | void init_SiteParams(iSiteParams *d) { | 44 | void init_SiteParams(iSiteParams *d) { |
@@ -45,9 +46,11 @@ void init_SiteParams(iSiteParams *d) { | |||
45 | init_String(&d->titanIdentity); | 46 | init_String(&d->titanIdentity); |
46 | d->dismissWarnings = 0; | 47 | d->dismissWarnings = 0; |
47 | init_StringArray(&d->usedIdentities); | 48 | init_StringArray(&d->usedIdentities); |
49 | init_String(&d->paletteSeed); | ||
48 | } | 50 | } |
49 | 51 | ||
50 | void deinit_SiteParams(iSiteParams *d) { | 52 | void deinit_SiteParams(iSiteParams *d) { |
53 | deinit_String(&d->paletteSeed); | ||
51 | deinit_StringArray(&d->usedIdentities); | 54 | deinit_StringArray(&d->usedIdentities); |
52 | deinit_String(&d->titanIdentity); | 55 | deinit_String(&d->titanIdentity); |
53 | } | 56 | } |
@@ -149,6 +152,9 @@ static void handleIniKeyValue_SiteSpec_(void *context, const iString *table, con | |||
149 | pushBack_StringArray(&d->loadParams->usedIdentities, collectNewRange_String(seg)); | 152 | pushBack_StringArray(&d->loadParams->usedIdentities, collectNewRange_String(seg)); |
150 | } | 153 | } |
151 | } | 154 | } |
155 | else if (!cmp_String(key, "paletteSeed") && value->type == string_TomlType) { | ||
156 | set_String(&d->loadParams->paletteSeed, value->value.string); | ||
157 | } | ||
152 | } | 158 | } |
153 | 159 | ||
154 | static iBool load_SiteSpec_(iSiteSpec *d) { | 160 | static iBool load_SiteSpec_(iSiteSpec *d) { |
@@ -173,7 +179,7 @@ static void save_SiteSpec_(iSiteSpec *d) { | |||
173 | iBeginCollect(); | 179 | iBeginCollect(); |
174 | const iBlock * key = &i.value->keyBlock; | 180 | const iBlock * key = &i.value->keyBlock; |
175 | const iSiteParams *params = i.value->object; | 181 | const iSiteParams *params = i.value->object; |
176 | format_String(buf, "[%s]\n", cstr_Block(key)); | 182 | clear_String(buf); |
177 | if (params->titanPort) { | 183 | if (params->titanPort) { |
178 | appendFormat_String(buf, "titanPort = %u\n", params->titanPort); | 184 | appendFormat_String(buf, "titanPort = %u\n", params->titanPort); |
179 | } | 185 | } |
@@ -190,8 +196,18 @@ static void save_SiteSpec_(iSiteSpec *d) { | |||
190 | "usedIdentities = \"%s\"\n", | 196 | "usedIdentities = \"%s\"\n", |
191 | cstrCollect_String(joinCStr_StringArray(¶ms->usedIdentities, " "))); | 197 | cstrCollect_String(joinCStr_StringArray(¶ms->usedIdentities, " "))); |
192 | } | 198 | } |
193 | appendCStr_String(buf, "\n"); | 199 | if (!isEmpty_String(¶ms->paletteSeed)) { |
194 | write_File(f, utf8_String(buf)); | 200 | appendCStr_String(buf, "paletteSeed = \""); |
201 | append_String(buf, collect_String(quote_String(¶ms->paletteSeed, iFalse))); | ||
202 | appendCStr_String(buf, "\"\n"); | ||
203 | } | ||
204 | if (!isEmpty_String(buf)) { | ||
205 | writeData_File(f, "[", 1); | ||
206 | writeData_File(f, constData_Block(key), size_Block(key)); | ||
207 | writeData_File(f, "]\n", 2); | ||
208 | appendCStr_String(buf, "\n"); | ||
209 | write_File(f, utf8_String(buf)); | ||
210 | } | ||
195 | iEndCollect(); | 211 | iEndCollect(); |
196 | } | 212 | } |
197 | delete_String(buf); | 213 | delete_String(buf); |
@@ -257,6 +273,12 @@ void setValueString_SiteSpec(const iString *site, enum iSiteSpecKey key, const i | |||
257 | set_String(¶ms->titanIdentity, value); | 273 | set_String(¶ms->titanIdentity, value); |
258 | } | 274 | } |
259 | break; | 275 | break; |
276 | case paletteSeed_SiteSpecKey: | ||
277 | if (!equal_String(¶ms->paletteSeed, value)) { | ||
278 | needSave = iTrue; | ||
279 | set_String(¶ms->paletteSeed, value); | ||
280 | } | ||
281 | break; | ||
260 | default: | 282 | default: |
261 | break; | 283 | break; |
262 | } | 284 | } |
@@ -328,6 +350,8 @@ const iString *valueString_SiteSpec(const iString *site, enum iSiteSpecKey key) | |||
328 | switch (key) { | 350 | switch (key) { |
329 | case titanIdentity_SiteSpecKey: | 351 | case titanIdentity_SiteSpecKey: |
330 | return ¶ms->titanIdentity; | 352 | return ¶ms->titanIdentity; |
353 | case paletteSeed_SiteSpecKey: | ||
354 | return ¶ms->paletteSeed; | ||
331 | default: | 355 | default: |
332 | return collectNew_String(); | 356 | return collectNew_String(); |
333 | } | 357 | } |
diff --git a/src/sitespec.h b/src/sitespec.h index 11c40e3c..372e021e 100644 --- a/src/sitespec.h +++ b/src/sitespec.h | |||
@@ -31,6 +31,7 @@ enum iSiteSpecKey { | |||
31 | titanIdentity_SiteSpecKey, /* String */ | 31 | titanIdentity_SiteSpecKey, /* String */ |
32 | dismissWarnings_SiteSpecKey, /* int */ | 32 | dismissWarnings_SiteSpecKey, /* int */ |
33 | usedIdentities_SiteSpecKey, /* StringArray */ | 33 | usedIdentities_SiteSpecKey, /* StringArray */ |
34 | paletteSeed_SiteSpecKey, /* String */ | ||
34 | }; | 35 | }; |
35 | 36 | ||
36 | void init_SiteSpec (const char *saveDir); | 37 | void init_SiteSpec (const char *saveDir); |
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 | ||
524 | iHSLColor addSatLum_HSLColor(iHSLColor d, float sat, float lum) { | 524 | iHSLColor 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 | |||
234 | iHSLColor hsl_Color (iColor); | 236 | iHSLColor hsl_Color (iColor); |
235 | iColor rgb_HSLColor (iHSLColor); | 237 | iColor rgb_HSLColor (iHSLColor); |
236 | float luma_Color (iColor); | 238 | float 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); | |||
358 | static void prerender_DocumentWidget_ (iAny *); | 358 | static void prerender_DocumentWidget_ (iAny *); |
359 | static void scrollBegan_DocumentWidget_ (iAnyObject *, int, uint32_t); | 359 | static void scrollBegan_DocumentWidget_ (iAnyObject *, int, uint32_t); |
360 | static void refreshWhileScrolling_DocumentWidget_ (iAny *); | 360 | static void refreshWhileScrolling_DocumentWidget_ (iAny *); |
361 | static 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 | ||
2411 | static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) { | 2412 | static 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 | ||
1204 | void deselect_InputWidget(iInputWidget *d) { | ||
1205 | iZap(d->mark); | ||
1206 | refresh_Widget(as_Widget(d)); | ||
1207 | } | ||
1208 | |||
1204 | void validate_InputWidget(iInputWidget *d) { | 1209 | void 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); | |||
59 | void begin_InputWidget (iInputWidget *); | 59 | void begin_InputWidget (iInputWidget *); |
60 | void end_InputWidget (iInputWidget *, iBool accept); | 60 | void end_InputWidget (iInputWidget *, iBool accept); |
61 | void selectAll_InputWidget (iInputWidget *); | 61 | void selectAll_InputWidget (iInputWidget *); |
62 | void deselect_InputWidget (iInputWidget *); | ||
62 | void validate_InputWidget (iInputWidget *); | 63 | void validate_InputWidget (iInputWidget *); |
63 | 64 | ||
64 | void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); | 65 | void 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. */ |
58 | static const iMenuItem navMenuItems_[] = { | 58 | static 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 | ||
1784 | size_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 | |||
1777 | iInt2 size_Root(const iRoot *d) { | 1791 | iInt2 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 | ||
48 | void notifyVisualOffsetChange_Root (iRoot *); | 48 | void notifyVisualOffsetChange_Root (iRoot *); |
49 | 49 | ||
50 | size_t windowIndex_Root (const iRoot *); | ||
50 | iInt2 size_Root (const iRoot *); | 51 | iInt2 size_Root (const iRoot *); |
51 | iRect rect_Root (const iRoot *); | 52 | iRect rect_Root (const iRoot *); |
52 | iRect safeRect_Root (const iRoot *); | 53 | iRect 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 | ||
260 | struct Impl_Text { | 260 | struct 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 | ||
282 | iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) | 281 | iDefineTypeConstructionArgs(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 | ||
362 | static void deinitFonts_Text_(iText *d) { | 364 | static 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 | ||
499 | void resetFonts_Text(iText *d) { | 502 | void 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 | ||
506 | static SDL_Palette *glyphPalette_(void) { | 512 | static 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 | ||
1461 | static iRect run_Font_(iFont *d, const iRunArgs *args) { | 1482 | static 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 | ||
2274 | iChar 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 | |||
2282 | void resetMissing_Text(iText *d) { | ||
2283 | d->missingGlyphs = iFalse; | ||
2284 | iZap(d->missingChars); | ||
2285 | } | ||
2286 | |||
2253 | SDL_Texture *glyphCache_Text(void) { | 2287 | SDL_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 { | |||
227 | iTextMetrics measure_WrapText (iWrapText *, int fontId); | 227 | iTextMetrics measure_WrapText (iWrapText *, int fontId); |
228 | iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); | 228 | iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); |
229 | 229 | ||
230 | iChar missing_Text (size_t index); | ||
231 | void resetMissing_Text (iText *); | ||
230 | iBool checkMissing_Text (void); /* returns the flag, and clears it */ | 232 | iBool checkMissing_Text (void); /* returns the flag, and clears it */ |
231 | SDL_Texture * glyphCache_Text (void); | 233 | SDL_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) { | |||
1327 | iLabelWidget *makeMenuButton_LabelWidget(const char *label, const iMenuItem *items, size_t n) { | 1329 | iLabelWidget *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 | ||
1711 | static void acceptValueInput_(iWidget *dlg) { | 1717 | static 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 | ||
3121 | static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { | 3133 | static 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 | ||
3165 | iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { | 3177 | iWidget *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 | |||
3244 | static 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 | |||
3252 | static const iString *siteSpecificRoot_(const iWidget *dlg) { | ||
3253 | return collect_String(suffix_Command(cstr_String(id_Widget(dlg)), "site")); | ||
3254 | } | ||
3255 | |||
3256 | static 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 | |||
3265 | static 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 | |||
3271 | static 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 | |||
3303 | iWidget *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 | |||
3231 | iWidget *makeIdentityCreation_Widget(void) { | 3347 | iWidget *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 | ||
3570 | iWidget *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 | |||
3454 | void init_PerfTimer(iPerfTimer *d) { | 3618 | void 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, | |||
336 | iWidget * makePreferences_Widget (void); | 336 | iWidget * makePreferences_Widget (void); |
337 | void updatePreferencesLayout_Widget (iWidget *prefs); | 337 | void updatePreferencesLayout_Widget (iWidget *prefs); |
338 | 338 | ||
339 | iWidget * makeBookmarkEditor_Widget (void); | 339 | iWidget * makeBookmarkEditor_Widget (void); |
340 | void setBookmarkEditorFolder_Widget(iWidget *editor, uint32_t folderId); | 340 | void setBookmarkEditorFolder_Widget (iWidget *editor, uint32_t folderId); |
341 | iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *title, iChar icon); | 341 | iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *title, iChar icon); |
342 | iWidget * makeIdentityCreation_Widget (void); | 342 | iWidget * makeIdentityCreation_Widget (void); |
343 | iWidget * makeFeedSettings_Widget (uint32_t bookmarkId); | 343 | iWidget * makeFeedSettings_Widget (uint32_t bookmarkId); |
344 | iWidget * makeTranslation_Widget (iWidget *parent); | 344 | iWidget * makeSiteSpecificSettings_Widget (const iString *url); |
345 | iWidget * makeTranslation_Widget (iWidget *parent); | ||
346 | iWidget * makeGlyphFinder_Widget (void); | ||
345 | 347 | ||
346 | const char * languageId_String (const iString *menuItemLabel); | 348 | const char * languageId_String (const iString *menuItemLabel); |
347 | int languageIndex_CStr (const char *langId); | 349 | int 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. */ |
82 | static const iMenuItem fileMenuItems_[] = { | 82 | static 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 | ||
211 | static void setupUserInterface_MainWindow(iMainWindow *d) { | 212 | static 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 | ||
247 | void drawWhileResizing_MainWindow(iMainWindow *d, int w, int h) { | 250 | void 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 | ||
649 | void deinit_MainWindow(iMainWindow *d) { | 653 | void 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 | ||
679 | iRoot *findRoot_Window(const iWindow *d, const iWidget *widget) { | 684 | iRoot *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 | ||
832 | static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) { | 838 | static 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 | ||
1182 | static 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 | |||
1167 | iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) { | 1203 | iBool 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 | ||
1570 | iObjectList *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 | |||
1529 | void checkPendingSplit_MainWindow(iMainWindow *d) { | 1587 | void 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); | |||
139 | uint32_t frameTime_Window (const iWindow *); | 139 | uint32_t frameTime_Window (const iWindow *); |
140 | SDL_Renderer * renderer_Window (const iWindow *); | 140 | SDL_Renderer * renderer_Window (const iWindow *); |
141 | int numRoots_Window (const iWindow *); | 141 | int numRoots_Window (const iWindow *); |
142 | iRoot * findRoot_Window (const iWindow *, const iWidget *widget); | 142 | //iRoot * findRoot_Window (const iWindow *, const iWidget *widget); |
143 | iRoot * otherRoot_Window (const iWindow *, iRoot *root); | 143 | iRoot * otherRoot_Window (const iWindow *, iRoot *root); |
144 | 144 | ||
145 | iBool processEvent_Window (iWindow *, const SDL_Event *); | 145 | iBool processEvent_Window (iWindow *, const SDL_Event *); |
@@ -187,6 +187,7 @@ void setTitle_MainWindow (iMainWindow *, const iString *title | |||
187 | void setSnap_MainWindow (iMainWindow *, int snapMode); | 187 | void setSnap_MainWindow (iMainWindow *, int snapMode); |
188 | void setFreezeDraw_MainWindow (iMainWindow *, iBool freezeDraw); | 188 | void setFreezeDraw_MainWindow (iMainWindow *, iBool freezeDraw); |
189 | void setKeyboardHeight_MainWindow (iMainWindow *, int height); | 189 | void setKeyboardHeight_MainWindow (iMainWindow *, int height); |
190 | iObjectList *listDocuments_MainWindow (iMainWindow *, const iRoot *rootOrNull); | ||
190 | void setSplitMode_MainWindow (iMainWindow *, int splitMode); | 191 | void setSplitMode_MainWindow (iMainWindow *, int splitMode); |
191 | void checkPendingSplit_MainWindow (iMainWindow *); | 192 | void checkPendingSplit_MainWindow (iMainWindow *); |
192 | void swapRoots_MainWindow (iMainWindow *); | 193 | void swapRoots_MainWindow (iMainWindow *); |