diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2022-01-20 12:06:39 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2022-01-20 12:06:39 +0200 |
commit | 6da3bdb612e0ead07ff93f4f6dc5aef46c7b9c9e (patch) | |
tree | 4378984d744dc67b7453573524d5839b9ce7f9d5 | |
parent | d5169339b3454c80a6f2ed5f8cb937e5d5613fc0 (diff) | |
parent | 33816278c84fd7ac7e895f4111229c4ff4436b53 (diff) |
Merge branch 'dev' of skyjake.fi:gemini/lagrange into dev
-rw-r--r-- | AUTHORS.md | 29 | ||||
-rw-r--r-- | CMakeLists.txt | 69 | ||||
-rw-r--r-- | Depends-Android.cmake | 11 | ||||
-rw-r--r-- | Depends-iOS.cmake | 2 | ||||
-rw-r--r-- | Depends.cmake | 10 | ||||
-rw-r--r-- | README.md | 2 | ||||
m--------- | lib/the_Foundation | 0 | ||||
-rw-r--r-- | po/cs.po | 208 | ||||
-rw-r--r-- | po/de.po | 33 | ||||
-rw-r--r-- | po/en.po | 190 | ||||
-rw-r--r-- | po/eo.po | 87 | ||||
-rw-r--r-- | po/es.po | 192 | ||||
-rw-r--r-- | po/fi.po | 190 | ||||
-rw-r--r-- | po/gl.po | 190 | ||||
-rw-r--r-- | po/ia.po | 394 | ||||
-rw-r--r-- | po/ru.po | 190 | ||||
-rw-r--r-- | po/sr.po | 194 | ||||
-rw-r--r-- | po/tok.po | 147 | ||||
-rw-r--r-- | po/tr.po | 200 | ||||
-rw-r--r-- | po/uk.po | 194 | ||||
-rw-r--r-- | res/about/android-help.gmi | 173 | ||||
-rw-r--r-- | res/about/android-version.gmi | 25 | ||||
-rw-r--r-- | res/about/help.gmi | 6 | ||||
-rw-r--r-- | res/about/ios-help.gmi | 204 | ||||
-rw-r--r-- | res/about/ios-version.gmi | 83 | ||||
-rw-r--r-- | res/about/version.gmi | 58 | ||||
-rw-r--r-- | res/iOSBundleInfo.plist.in | 5 | ||||
-rw-r--r-- | res/lang/cs.bin | bin | 30937 -> 32086 bytes | |||
-rw-r--r-- | res/lang/de.bin | bin | 29906 -> 30896 bytes | |||
-rw-r--r-- | res/lang/en.bin | bin | 26003 -> 26965 bytes | |||
-rw-r--r-- | res/lang/eo.bin | bin | 24963 -> 26163 bytes | |||
-rw-r--r-- | res/lang/es.bin | bin | 29735 -> 30808 bytes | |||
-rw-r--r-- | res/lang/es_MX.bin | bin | 27062 -> 28035 bytes | |||
-rw-r--r-- | res/lang/fi.bin | bin | 29573 -> 30606 bytes | |||
-rw-r--r-- | res/lang/fr.bin | bin | 30710 -> 31693 bytes | |||
-rw-r--r-- | res/lang/gl.bin | bin | 28913 -> 29969 bytes | |||
-rw-r--r-- | res/lang/hu.bin | bin | 30735 -> 31718 bytes | |||
-rw-r--r-- | res/lang/ia.bin | bin | 28062 -> 29748 bytes | |||
-rw-r--r-- | res/lang/ie.bin | bin | 28672 -> 29655 bytes | |||
-rw-r--r-- | res/lang/isv.bin | bin | 24723 -> 25686 bytes | |||
-rw-r--r-- | res/lang/nl.bin | bin | 28092 -> 29075 bytes | |||
-rw-r--r-- | res/lang/pl.bin | bin | 29338 -> 30321 bytes | |||
-rw-r--r-- | res/lang/ru.bin | bin | 44148 -> 45668 bytes | |||
-rw-r--r-- | res/lang/sk.bin | bin | 25059 -> 26022 bytes | |||
-rw-r--r-- | res/lang/sr.bin | bin | 43555 -> 44964 bytes | |||
-rw-r--r-- | res/lang/tok.bin | bin | 26777 -> 27801 bytes | |||
-rw-r--r-- | res/lang/tr.bin | bin | 28928 -> 29912 bytes | |||
-rw-r--r-- | res/lang/uk.bin | bin | 43480 -> 45045 bytes | |||
-rw-r--r-- | res/lang/zh_Hans.bin | bin | 24957 -> 25930 bytes | |||
-rw-r--r-- | res/lang/zh_Hant.bin | bin | 25345 -> 26328 bytes | |||
-rw-r--r-- | sdl2.0.18-macos-ios.diff | 129 | ||||
-rw-r--r-- | src/app.c | 397 | ||||
-rw-r--r-- | src/app.h | 3 | ||||
-rw-r--r-- | src/audio/player.c | 25 | ||||
-rw-r--r-- | src/bookmarks.c | 132 | ||||
-rw-r--r-- | src/bookmarks.h | 77 | ||||
-rw-r--r-- | src/defs.h | 29 | ||||
-rw-r--r-- | src/feeds.c | 22 | ||||
-rw-r--r-- | src/fontpack.c | 2 | ||||
-rw-r--r-- | src/gmcerts.c | 29 | ||||
-rw-r--r-- | src/gmcerts.h | 5 | ||||
-rw-r--r-- | src/gmdocument.c | 84 | ||||
-rw-r--r-- | src/gmdocument.h | 2 | ||||
-rw-r--r-- | src/gmrequest.c | 13 | ||||
-rw-r--r-- | src/gmutil.c | 43 | ||||
-rw-r--r-- | src/gmutil.h | 10 | ||||
-rw-r--r-- | src/history.c | 80 | ||||
-rw-r--r-- | src/history.h | 16 | ||||
-rw-r--r-- | src/ios.h | 29 | ||||
-rw-r--r-- | src/ios.m | 319 | ||||
-rw-r--r-- | src/macos.h | 2 | ||||
-rw-r--r-- | src/macos.m | 233 | ||||
-rw-r--r-- | src/main.c | 4 | ||||
-rw-r--r-- | src/media.c | 13 | ||||
-rw-r--r-- | src/media.h | 3 | ||||
-rw-r--r-- | src/mimehooks.c | 3 | ||||
-rw-r--r-- | src/periodic.c | 26 | ||||
-rw-r--r-- | src/periodic.h | 1 | ||||
-rw-r--r-- | src/prefs.c | 16 | ||||
-rw-r--r-- | src/prefs.h | 138 | ||||
-rw-r--r-- | src/resources.c | 12 | ||||
-rw-r--r-- | src/sitespec.c | 83 | ||||
-rw-r--r-- | src/sitespec.h | 20 | ||||
-rw-r--r-- | src/ui/banner.c | 8 | ||||
-rw-r--r-- | src/ui/bindingswidget.c | 6 | ||||
-rw-r--r-- | src/ui/certimportwidget.c | 5 | ||||
-rw-r--r-- | src/ui/certlistwidget.c | 490 | ||||
-rw-r--r-- | src/ui/certlistwidget.h | 40 | ||||
-rw-r--r-- | src/ui/color.c | 6 | ||||
-rw-r--r-- | src/ui/color.h | 2 | ||||
-rw-r--r-- | src/ui/command.c | 77 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 4489 | ||||
-rw-r--r-- | src/ui/documentwidget.h | 6 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 809 | ||||
-rw-r--r-- | src/ui/inputwidget.h | 1 | ||||
-rw-r--r-- | src/ui/keys.c | 1 | ||||
-rw-r--r-- | src/ui/labelwidget.c | 108 | ||||
-rw-r--r-- | src/ui/labelwidget.h | 2 | ||||
-rw-r--r-- | src/ui/linkinfo.c | 176 | ||||
-rw-r--r-- | src/ui/linkinfo.h | 47 | ||||
-rw-r--r-- | src/ui/listwidget.c | 82 | ||||
-rw-r--r-- | src/ui/listwidget.h | 31 | ||||
-rw-r--r-- | src/ui/lookupwidget.c | 28 | ||||
-rw-r--r-- | src/ui/mediaui.c | 52 | ||||
-rw-r--r-- | src/ui/mobile.c | 146 | ||||
-rw-r--r-- | src/ui/mobile.h | 14 | ||||
-rw-r--r-- | src/ui/paint.c | 11 | ||||
-rw-r--r-- | src/ui/paint.h | 1 | ||||
-rw-r--r-- | src/ui/root.c | 511 | ||||
-rw-r--r-- | src/ui/root.h | 8 | ||||
-rw-r--r-- | src/ui/scrollwidget.c | 4 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 925 | ||||
-rw-r--r-- | src/ui/sidebarwidget.h | 1 | ||||
-rw-r--r-- | src/ui/text.c | 193 | ||||
-rw-r--r-- | src/ui/text.h | 11 | ||||
-rw-r--r-- | src/ui/text_simple.c | 5 | ||||
-rw-r--r-- | src/ui/touch.c | 55 | ||||
-rw-r--r-- | src/ui/touch.h | 4 | ||||
-rw-r--r-- | src/ui/uploadwidget.c | 200 | ||||
-rw-r--r-- | src/ui/uploadwidget.h | 1 | ||||
-rw-r--r-- | src/ui/util.c | 444 | ||||
-rw-r--r-- | src/ui/util.h | 31 | ||||
-rw-r--r-- | src/ui/visbuf.c | 3 | ||||
-rw-r--r-- | src/ui/widget.c | 102 | ||||
-rw-r--r-- | src/ui/widget.h | 28 | ||||
-rw-r--r-- | src/ui/window.c | 48 | ||||
-rw-r--r-- | src/ui/window.h | 8 | ||||
-rw-r--r-- | src/visited.c | 2 |
128 files changed, 10195 insertions, 4113 deletions
@@ -5,27 +5,30 @@ Lagrange was created by Jaakko Keränen (<jaakko.keranen@iki.fi>) in July 2020. | |||
5 | Legend: `C` code, `T` translation | 5 | Legend: `C` code, `T` translation |
6 | 6 | ||
7 | ``` | 7 | ``` |
8 | C 2665 Jaakko Keränen <jaakko.keranen@iki.fi> | 8 | C 3009 Jaakko Keränen <jaakko.keranen@iki.fi> |
9 | CT 39 Nikolay Korotkiy <sikmir@gmail.com> | 9 | CT 43 Nikolay Korotkiy <sikmir@gmail.com> |
10 | T 39 Olga Smirnova <mistresssilvara@hotmail.com> | 10 | T 42 Alyssa Liddell <e-liss@tuta.io> |
11 | T 28 Anna “CyberTailor” <cyber@sysrq.in> | 11 | T 41 Olga Smirnova <mistresssilvara@hotmail.com> |
12 | T 27 Alyssa Liddell <e-liss@tuta.io> | 12 | T 30 Anna “CyberTailor” <cyber@sysrq.in> |
13 | T 25 Shibo Lyu <github@of.sb> | 13 | T 27 Страхиња Радић <contact@strahinja.org> |
14 | T 22 Страхиња Радић <contact@strahinja.org> | 14 | T 26 Shibo Lyu <github@of.sb> |
15 | T 17 MCMic <come@chilliet.eu> | 15 | T 19 Wally Hackenslacker <mastor89@protonmail.com> |
16 | T 15 Wally Hackenslacker <mastor89@protonmail.com> | 16 | T 18 MCMic <come@chilliet.eu> |
17 | T 10 Tadeš Erban <tadysekerbosek@gmail.com> | 17 | T 14 Xos M <correoxm@disroot.org> |
18 | T 10 Xos M <correoxm@disroot.org> | 18 | T 13 Tadeš Erban <tadysekerbosek@gmail.com> |
19 | T 9 Aaron Fischer <mail@aaron-fischer.net> | 19 | T 9 Aaron Fischer <mail@aaron-fischer.net> |
20 | T 8 El Mau <public@correolibre.net> | 20 | T 8 El Mau <public@correolibre.net> |
21 | T 7 Waterrail <maksymiliankrol03@gmail.com> | 21 | T 7 Waterrail <maksymiliankrol03@gmail.com> |
22 | T 5 Emir <emir_sari@msn.com> | ||
22 | T 5 roy niang <roy@royniang.com> | 23 | T 5 roy niang <roy@royniang.com> |
23 | T 3 Alex Schroeder <alex@alexschroeder.ch> | 24 | T 3 Alex Schroeder <alex@alexschroeder.ch> |
24 | T 3 Botond Balázs <balazsbotond@gmail.com> | 25 | T 3 Botond Balázs <balazsbotond@gmail.com> |
25 | C 2 Alyssa Rosenzweig <alyssa@rosenzweig.io> | 26 | C 2 Alyssa Rosenzweig <alyssa@rosenzweig.io> |
27 | T 2 Andrij Mizyk <andmizyk@gmail.com> | ||
26 | T 2 Arns Udovič <zordsdavini@arns.lt> | 28 | T 2 Arns Udovič <zordsdavini@arns.lt> |
27 | C 2 Br0000k <77938600+Br0000k@users.noreply.github.com> | 29 | C 2 Br0000k <77938600+Br0000k@users.noreply.github.com> |
28 | T 2 Gabriel de Oliveira Ferreira Machado <goliv04053@tutanota.com> | 30 | T 2 Gabriel de Oliveira Ferreira Machado <goliv04053@tutanota.com> |
31 | T 2 Jop Vernooy <jop@jopv.net> | ||
29 | C 2 Manos Pitsidianakis <el13635@mail.ntua.gr> | 32 | C 2 Manos Pitsidianakis <el13635@mail.ntua.gr> |
30 | T 2 methbkts <methbkts@gmail.com> | 33 | T 2 methbkts <methbkts@gmail.com> |
31 | T 2 tbodt <tblodt@icloud.com> | 34 | T 2 tbodt <tblodt@icloud.com> |
@@ -33,15 +36,17 @@ C 1 Adam Mizerski <adam@mizerski.pl> | |||
33 | C 1 Charles <charles@cdaniels.net> | 36 | C 1 Charles <charles@cdaniels.net> |
34 | C 1 Dario Vladovic <d.vladimyr@gmail.com> | 37 | C 1 Dario Vladovic <d.vladimyr@gmail.com> |
35 | C 1 David Gillies <dave.gillies@gmail.com> | 38 | C 1 David Gillies <dave.gillies@gmail.com> |
36 | T 1 Emir <emir_sari@msn.com> | 39 | T 1 Emir SARI <emir_sari@icloud.com> |
37 | T 1 Eric <spice2wolf@gmail.com> | 40 | T 1 Eric <spice2wolf@gmail.com> |
38 | T 1 Marek Ľach <mareklachbc@tutanota.com> | 41 | T 1 Marek Ľach <mareklachbc@tutanota.com> |
39 | C 1 Raph M <raph@raphm.com> | 42 | C 1 Raph M <raph@raphm.com> |
40 | C 1 SolidHal <hal@halemmerich.com> | 43 | C 1 SolidHal <hal@halemmerich.com> |
41 | C 1 Thomas Adam <thomas.adam@smoothwall.net> | 44 | C 1 Thomas Adam <thomas.adam@smoothwall.net> |
45 | T 1 Timothée Goguely <timothee@goguely.com> | ||
42 | C 1 Waweic <waweic@activ.ism.rocks> | 46 | C 1 Waweic <waweic@activ.ism.rocks> |
43 | C 1 Waweic <waweic@protonmail.com> | 47 | C 1 Waweic <waweic@protonmail.com> |
44 | C 1 Zach DeCook <zachdecook@librem.one> | 48 | C 1 Zach DeCook <zachdecook@librem.one> |
49 | C 1 jcromero <jcromero+github@disconfor.me> | ||
45 | C 1 zocker <zocker@10zen.eu> | 50 | C 1 zocker <zocker@10zen.eu> |
46 | ``` | 51 | ``` |
47 | 52 | ||
diff --git a/CMakeLists.txt b/CMakeLists.txt index e060736c..4b1dfef1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -18,22 +18,31 @@ | |||
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.9.4 | 21 | VERSION 1.10.1 |
22 | DESCRIPTION "A Beautiful Gemini Client" | 22 | DESCRIPTION "A Beautiful Gemini Client" |
23 | LANGUAGES C | 23 | LANGUAGES C |
24 | ) | 24 | ) |
25 | set (COPYRIGHT_YEAR 2021) | 25 | set (COPYRIGHT_YEAR 2022) |
26 | if (IOS) | 26 | if (IOS) |
27 | set (PROJECT_VERSION 1.7) | 27 | set (PROJECT_VERSION 1.10) |
28 | set (IOS_BUNDLE_VERSION 23) | 28 | set (IOS_BUNDLE_VERSION 9) |
29 | set (IOS_BUILD_DATE "2021-10-23") | 29 | set (IOS_BUILD_DATE "2022-01-01") |
30 | endif () | ||
31 | if (ANDROID) | ||
32 | set (PROJECT_VERSION 1.10) | ||
33 | set (ANDROID_BUILD_VERSION a3) # remember to update Gradle, AndroidManifest.xml | ||
34 | set (ANDROID_BUILD_DATE "2022-01-03") | ||
30 | endif () | 35 | endif () |
31 | 36 | ||
32 | # Defaults that depend on environment. | 37 | # Defaults that depend on environment. |
33 | set (DEFAULT_RESIZE_DRAW ON) | 38 | set (DEFAULT_RESIZE_DRAW ON) |
34 | if (HAIKU) | 39 | if (HAIKU OR ANDROID) |
35 | set (DEFAULT_RESIZE_DRAW OFF) | 40 | set (DEFAULT_RESIZE_DRAW OFF) |
36 | endif () | 41 | endif () |
42 | set (DEFAULT_IDLE_SLEEP OFF) | ||
43 | if (IOS) | ||
44 | set (DEFAULT_IDLE_SLEEP ON) | ||
45 | endif () | ||
37 | 46 | ||
38 | # Build configuration. | 47 | # Build configuration. |
39 | option (ENABLE_CUSTOM_FRAME "Draw a custom window frame (Windows)" OFF) | 48 | option (ENABLE_CUSTOM_FRAME "Draw a custom window frame (Windows)" OFF) |
@@ -42,7 +51,7 @@ option (ENABLE_FRIBIDI "Use the GNU FriBidi library for bidirectional t | |||
42 | option (ENABLE_FRIBIDI_BUILD "Build the GNU FriBidi library (if OFF, try pkg-config)" OFF) | 51 | option (ENABLE_FRIBIDI_BUILD "Build the GNU FriBidi library (if OFF, try pkg-config)" OFF) |
43 | option (ENABLE_HARFBUZZ "Use the HarfBuzz library to shape text" ON) | 52 | option (ENABLE_HARFBUZZ "Use the HarfBuzz library to shape text" ON) |
44 | option (ENABLE_HARFBUZZ_MINIMAL "Build the HarfBuzz library with minimal dependencies (if OFF, try pkg-config)" OFF) | 53 | option (ENABLE_HARFBUZZ_MINIMAL "Build the HarfBuzz library with minimal dependencies (if OFF, try pkg-config)" OFF) |
45 | option (ENABLE_IDLE_SLEEP "While idle, sleep in the main thread instead of waiting for events" ON) | 54 | option (ENABLE_IDLE_SLEEP "While idle, sleep in the main thread instead of waiting for events" ${DEFAULT_IDLE_SLEEP}) |
46 | option (ENABLE_IPC "Use IPC to communicate between running instances" ON) | 55 | option (ENABLE_IPC "Use IPC to communicate between running instances" ON) |
47 | option (ENABLE_KERNING "Enable kerning in font renderer (slower)" ON) | 56 | option (ENABLE_KERNING "Enable kerning in font renderer (slower)" ON) |
48 | option (ENABLE_MAC_MENUS "Use native context menus (macOS)" ON) | 57 | option (ENABLE_MAC_MENUS "Use native context menus (macOS)" ON) |
@@ -66,11 +75,8 @@ include (Depends.cmake) | |||
66 | message (STATUS "Preparing resources...") | 75 | message (STATUS "Preparing resources...") |
67 | set (RESOURCES | 76 | set (RESOURCES |
68 | res/about/about.gmi | 77 | res/about/about.gmi |
69 | res/about/help.gmi | ||
70 | res/about/lagrange.gmi | 78 | res/about/lagrange.gmi |
71 | res/about/license.gmi | 79 | res/about/license.gmi |
72 | res/about/version.gmi | ||
73 | res/arg-help.txt | ||
74 | res/lang/cs.bin | 80 | res/lang/cs.bin |
75 | res/lang/de.bin | 81 | res/lang/de.bin |
76 | res/lang/en.bin | 82 | res/lang/en.bin |
@@ -99,12 +105,38 @@ set (RESOURCES | |||
99 | ) | 105 | ) |
100 | file (GLOB FONTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} res/fonts/*) | 106 | file (GLOB FONTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} res/fonts/*) |
101 | list (APPEND RESOURCES ${FONTS}) | 107 | list (APPEND RESOURCES ${FONTS}) |
108 | if (IOS) | ||
109 | list (APPEND RESOURCES | ||
110 | res/about/ios-help.gmi | ||
111 | res/about/ios-version.gmi | ||
112 | ) | ||
113 | elseif (ANDROID) | ||
114 | list (APPEND RESOURCES | ||
115 | res/about/android-help.gmi | ||
116 | res/about/android-version.gmi | ||
117 | ) | ||
118 | else () | ||
119 | list (APPEND RESOURCES | ||
120 | res/about/help.gmi | ||
121 | res/about/version.gmi | ||
122 | res/arg-help.txt | ||
123 | ) | ||
124 | endif () | ||
102 | if ((UNIX AND NOT APPLE) OR MSYS) | 125 | if ((UNIX AND NOT APPLE) OR MSYS) |
103 | list (APPEND RESOURCES res/lagrange-64.png) | 126 | list (APPEND RESOURCES res/lagrange-64.png) |
104 | endif () | 127 | endif () |
105 | set (EMB_BIN ${CMAKE_CURRENT_BINARY_DIR}/resources.lgr) | 128 | set (EMB_BIN ${CMAKE_CURRENT_BINARY_DIR}/resources.lgr) |
106 | make_resources (${EMB_BIN} ${RESOURCES}) | 129 | make_resources (${EMB_BIN} ${RESOURCES}) |
107 | set_source_files_properties (${EMB_BIN} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) | 130 | set_source_files_properties (${EMB_BIN} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) |
131 | if (IOS) | ||
132 | set (EMB_FONTS | ||
133 | res/fonts/SourceSans3-Regular.ttf | ||
134 | res/fonts/IosevkaTerm-Extended.ttf | ||
135 | ) | ||
136 | set_source_files_properties (${EMB_FONTS} | ||
137 | PROPERTIES MACOSX_PACKAGE_LOCATION Resources | ||
138 | ) | ||
139 | endif () | ||
108 | 140 | ||
109 | # Source files. | 141 | # Source files. |
110 | set (SOURCES | 142 | set (SOURCES |
@@ -169,6 +201,8 @@ set (SOURCES | |||
169 | src/ui/bindingswidget.h | 201 | src/ui/bindingswidget.h |
170 | src/ui/certimportwidget.c | 202 | src/ui/certimportwidget.c |
171 | src/ui/certimportwidget.h | 203 | src/ui/certimportwidget.h |
204 | src/ui/certlistwidget.c | ||
205 | src/ui/certlistwidget.h | ||
172 | src/ui/color.c | 206 | src/ui/color.c |
173 | src/ui/color.h | 207 | src/ui/color.h |
174 | src/ui/command.c | 208 | src/ui/command.c |
@@ -177,6 +211,8 @@ set (SOURCES | |||
177 | src/ui/documentwidget.h | 211 | src/ui/documentwidget.h |
178 | src/ui/indicatorwidget.c | 212 | src/ui/indicatorwidget.c |
179 | src/ui/indicatorwidget.h | 213 | src/ui/indicatorwidget.h |
214 | src/ui/linkinfo.c | ||
215 | src/ui/linkinfo.h | ||
180 | src/ui/listwidget.c | 216 | src/ui/listwidget.c |
181 | src/ui/listwidget.h | 217 | src/ui/listwidget.h |
182 | src/ui/lookupwidget.c | 218 | src/ui/lookupwidget.c |
@@ -226,7 +262,7 @@ set (SOURCES | |||
226 | res/about/version.gmi | 262 | res/about/version.gmi |
227 | ${EMB_BIN} | 263 | ${EMB_BIN} |
228 | ) | 264 | ) |
229 | if (NOT APPLE) # macos.m has Sparkle updater | 265 | if (IOS OR NOT APPLE) # macos.m has Sparkle updater |
230 | list (APPEND SOURCES src/updater.c) | 266 | list (APPEND SOURCES src/updater.c) |
231 | endif () | 267 | endif () |
232 | if (ENABLE_IPC) | 268 | if (ENABLE_IPC) |
@@ -258,6 +294,7 @@ elseif (APPLE) | |||
258 | add_definitions (-DiPlatformAppleDesktop=1) | 294 | add_definitions (-DiPlatformAppleDesktop=1) |
259 | list (APPEND SOURCES src/macos.m src/macos.h) | 295 | list (APPEND SOURCES src/macos.m src/macos.h) |
260 | list (APPEND RESOURCES "res/Lagrange.icns") | 296 | list (APPEND RESOURCES "res/Lagrange.icns") |
297 | set_source_files_properties ("res/Lagrange.icns" PROPERTIES MACOSX_PACKAGE_LOCATION Resources) | ||
261 | endif () | 298 | endif () |
262 | if (MSYS) | 299 | if (MSYS) |
263 | set (WINRC_FILE_VERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0) | 300 | set (WINRC_FILE_VERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0) |
@@ -265,7 +302,6 @@ if (MSYS) | |||
265 | configure_file (res/lagrange.rc.in ${CMAKE_CURRENT_BINARY_DIR}/lagrange.rc NEWLINE_STYLE WIN32) | 302 | configure_file (res/lagrange.rc.in ${CMAKE_CURRENT_BINARY_DIR}/lagrange.rc NEWLINE_STYLE WIN32) |
266 | list (APPEND SOURCES src/win32.c src/win32.h ${CMAKE_CURRENT_BINARY_DIR}/lagrange.rc) | 303 | list (APPEND SOURCES src/win32.c src/win32.h ${CMAKE_CURRENT_BINARY_DIR}/lagrange.rc) |
267 | endif () | 304 | endif () |
268 | set_source_files_properties (${RESOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) | ||
269 | if (MSYS OR (APPLE AND NOT MOBILE) OR (UNIX AND NOT MOBILE)) | 305 | if (MSYS OR (APPLE AND NOT MOBILE) OR (UNIX AND NOT MOBILE)) |
270 | add_definitions (-DiPlatformPcDesktop=1) | 306 | add_definitions (-DiPlatformPcDesktop=1) |
271 | endif () | 307 | endif () |
@@ -287,7 +323,7 @@ endif () | |||
287 | 323 | ||
288 | # Target. | 324 | # Target. |
289 | if (NOT ANDROID) | 325 | if (NOT ANDROID) |
290 | add_executable (app ${SOURCES} ${RESOURCES}) | 326 | add_executable (app ${SOURCES} ${RESOURCES} ${EMB_FONTS}) |
291 | else () | 327 | else () |
292 | # The whole app becomes one shared library, based on this static one. | 328 | # The whole app becomes one shared library, based on this static one. |
293 | add_library (app SHARED ${SOURCES}) | 329 | add_library (app SHARED ${SOURCES}) |
@@ -305,6 +341,7 @@ target_include_directories (app PUBLIC | |||
305 | target_compile_options (app PUBLIC | 341 | target_compile_options (app PUBLIC |
306 | -Werror=implicit-function-declaration | 342 | -Werror=implicit-function-declaration |
307 | -Werror=incompatible-pointer-types | 343 | -Werror=incompatible-pointer-types |
344 | -Wno-deprecated-declarations | ||
308 | ${SDL2_CFLAGS} | 345 | ${SDL2_CFLAGS} |
309 | -DSTB_VORBIS_NO_STDIO=1 | 346 | -DSTB_VORBIS_NO_STDIO=1 |
310 | -DSTB_VORBIS_NO_INTEGER_CONVERSION=1 | 347 | -DSTB_VORBIS_NO_INTEGER_CONVERSION=1 |
@@ -418,6 +455,12 @@ if (APPLE) | |||
418 | ) | 455 | ) |
419 | endif () | 456 | endif () |
420 | endif () | 457 | endif () |
458 | if (ANDROID) | ||
459 | target_compile_definitions (app PUBLIC | ||
460 | LAGRANGE_ANDROID_VERSION="${ANDROID_BUILD_VERSION}" | ||
461 | LAGRANGE_ANDROID_BUILD_DATE="${ANDROID_BUILD_DATE}" | ||
462 | ) | ||
463 | endif () | ||
421 | if (MSYS) | 464 | if (MSYS) |
422 | target_link_libraries (app PUBLIC d2d1 uuid dwmapi) # querying DPI | 465 | target_link_libraries (app PUBLIC d2d1 uuid dwmapi) # querying DPI |
423 | if (ENABLE_WINSPARKLE) | 466 | if (ENABLE_WINSPARKLE) |
diff --git a/Depends-Android.cmake b/Depends-Android.cmake index 74635620..cffcf139 100644 --- a/Depends-Android.cmake +++ b/Depends-Android.cmake | |||
@@ -21,6 +21,7 @@ set (TFDN_STATIC_LIBRARY ON CACHE BOOL "") | |||
21 | set (TFDN_ENABLE_INSTALL OFF CACHE BOOL "") | 21 | set (TFDN_ENABLE_INSTALL OFF CACHE BOOL "") |
22 | set (TFDN_ENABLE_TESTS OFF CACHE BOOL "") | 22 | set (TFDN_ENABLE_TESTS OFF CACHE BOOL "") |
23 | set (TFDN_ENABLE_WEBREQUEST OFF CACHE BOOL "") | 23 | set (TFDN_ENABLE_WEBREQUEST OFF CACHE BOOL "") |
24 | set (TFDN_ENABLE_SSE41 OFF CACHE BOOL "") | ||
24 | add_subdirectory (lib/the_Foundation) | 25 | add_subdirectory (lib/the_Foundation) |
25 | add_library (the_Foundation::the_Foundation ALIAS the_Foundation) | 26 | add_library (the_Foundation::the_Foundation ALIAS the_Foundation) |
26 | if (NOT OPENSSL_FOUND) | 27 | if (NOT OPENSSL_FOUND) |
@@ -28,4 +29,12 @@ if (NOT OPENSSL_FOUND) | |||
28 | endif () | 29 | endif () |
29 | if (NOT ZLIB_FOUND) | 30 | if (NOT ZLIB_FOUND) |
30 | message (FATAL_ERROR "Lagrange requires zlib for reading compressed archives. Please check if pkg-config can find 'zlib'.") | 31 | message (FATAL_ERROR "Lagrange requires zlib for reading compressed archives. Please check if pkg-config can find 'zlib'.") |
31 | endif () \ No newline at end of file | 32 | endif () |
33 | |||
34 | set (FRIBIDI_FOUND YES) | ||
35 | set (FRIBIDI_LIBRARIES ${ANDROID_DIR}/fribidi-android/${ANDROID_ABI}/lib/libfribidi.a) | ||
36 | set (FRIBIDI_INCLUDE_DIRS ${ANDROID_DIR}/fribidi-android/${ANDROID_ABI}/include) | ||
37 | |||
38 | set (HARFBUZZ_FOUND YES) | ||
39 | set (HARFBUZZ_LIBRARIES ${ANDROID_DIR}/harfbuzz-android/${ANDROID_ABI}/lib/libharfbuzz.a) | ||
40 | set (HARFBUZZ_INCLUDE_DIRS ${ANDROID_DIR}/harfbuzz-android/${ANDROID_ABI}/include/harfbuzz) | ||
diff --git a/Depends-iOS.cmake b/Depends-iOS.cmake index 96a2f584..912251ca 100644 --- a/Depends-iOS.cmake +++ b/Depends-iOS.cmake | |||
@@ -22,6 +22,8 @@ set (SDL2_LDFLAGS | |||
22 | "-framework UIKit" | 22 | "-framework UIKit" |
23 | ) | 23 | ) |
24 | 24 | ||
25 | pkg_check_modules (WEBP IMPORTED_TARGET libwebpdecoder) | ||
26 | |||
25 | set (FRIBIDI_FOUND YES) | 27 | set (FRIBIDI_FOUND YES) |
26 | set (FRIBIDI_LIBRARIES ${IOS_DIR}/lib/libfribidi.a) | 28 | set (FRIBIDI_LIBRARIES ${IOS_DIR}/lib/libfribidi.a) |
27 | 29 | ||
diff --git a/Depends.cmake b/Depends.cmake index 246f3398..b7a6cd25 100644 --- a/Depends.cmake +++ b/Depends.cmake | |||
@@ -1,3 +1,5 @@ | |||
1 | find_package (PkgConfig) | ||
2 | |||
1 | if (IOS) | 3 | if (IOS) |
2 | include (Depends-iOS.cmake) | 4 | include (Depends-iOS.cmake) |
3 | return () | 5 | return () |
@@ -16,7 +18,7 @@ set (_dependsToBuild) | |||
16 | 18 | ||
17 | if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/CMakeLists.txt) | 19 | if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/CMakeLists.txt) |
18 | set (INSTALL_THE_FOUNDATION YES) | 20 | set (INSTALL_THE_FOUNDATION YES) |
19 | find_package (the_Foundation 1.0.1 REQUIRED) | 21 | find_package (the_Foundation 1.1.0 REQUIRED) |
20 | else () | 22 | else () |
21 | if (EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/.git) | 23 | if (EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/.git) |
22 | # 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. |
@@ -49,7 +51,7 @@ else () | |||
49 | endif () | 51 | endif () |
50 | 52 | ||
51 | if (APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET) | 53 | if (APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET) |
52 | set (_dependMacOpts | 54 | set (_dependMacOpts |
53 | -Dc_args=-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET} | 55 | -Dc_args=-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET} |
54 | -Dc_link_args=-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET} | 56 | -Dc_link_args=-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET} |
55 | -Dcpp_args=-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET} | 57 | -Dcpp_args=-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET} |
@@ -62,8 +64,8 @@ if (ENABLE_HARFBUZZ) | |||
62 | if (NOT ENABLE_HARFBUZZ_MINIMAL AND PKG_CONFIG_FOUND) | 64 | if (NOT ENABLE_HARFBUZZ_MINIMAL AND PKG_CONFIG_FOUND) |
63 | pkg_check_modules (HARFBUZZ IMPORTED_TARGET harfbuzz) | 65 | pkg_check_modules (HARFBUZZ IMPORTED_TARGET harfbuzz) |
64 | endif () | 66 | endif () |
65 | if (EXISTS ${CMAKE_SOURCE_DIR}/lib/harfbuzz/CMakeLists.txt AND | 67 | if (EXISTS ${CMAKE_SOURCE_DIR}/lib/harfbuzz/CMakeLists.txt AND |
66 | (ENABLE_HARFBUZZ_MINIMAL OR NOT HARFBUZZ_FOUND)) | 68 | (ENABLE_HARFBUZZ_MINIMAL OR NOT HARFBUZZ_FOUND)) |
67 | # Build HarfBuzz with minimal dependencies. | 69 | # Build HarfBuzz with minimal dependencies. |
68 | if (MESON_EXECUTABLE AND NINJA_EXECUTABLE) | 70 | if (MESON_EXECUTABLE AND NINJA_EXECUTABLE) |
69 | set (_dst ${CMAKE_BINARY_DIR}/lib/harfbuzz) | 71 | set (_dst ${CMAKE_BINARY_DIR}/lib/harfbuzz) |
@@ -76,7 +76,7 @@ Note that the `install` target also deploys an XDG .desktop file for launching t | |||
76 | | `ENABLE_BINCAT_SH` | Merge resource files (fonts, etc.) together using a Bash shell script. By default this is **OFF**, so _res/bincat.c_ is compiled as a native executable for this purpose. However, when cross-compiling, native binaries built during the CMake run may be targeted for the wrong architecture. Set this to **ON** if you are having problems with bincat while running CMake. | | 76 | | `ENABLE_BINCAT_SH` | Merge resource files (fonts, etc.) together using a Bash shell script. By default this is **OFF**, so _res/bincat.c_ is compiled as a native executable for this purpose. However, when cross-compiling, native binaries built during the CMake run may be targeted for the wrong architecture. Set this to **ON** if you are having problems with bincat while running CMake. | |
77 | | `ENABLE_CUSTOM_FRAME` | Draw a custom window frame. (Only on Microsoft Windows.) The custom frame is more in line with the visual style of the rest of the UI, but does not implement all of the native window behaviors (e.g., snapping, system menu). | | 77 | | `ENABLE_CUSTOM_FRAME` | Draw a custom window frame. (Only on Microsoft Windows.) The custom frame is more in line with the visual style of the rest of the UI, but does not implement all of the native window behaviors (e.g., snapping, system menu). | |
78 | | `ENABLE_DOWNLOAD_EDIT` | Allow changing the Downloads directory via the Preferences dialog. This should be set to **OFF** in sandboxed environments where downloaded files must be saved into a specific place. | | 78 | | `ENABLE_DOWNLOAD_EDIT` | Allow changing the Downloads directory via the Preferences dialog. This should be set to **OFF** in sandboxed environments where downloaded files must be saved into a specific place. | |
79 | | `ENABLE_IDLE_SLEEP` | Sleep in the main thread instead of waiting for events. On some platforms, `SDL_WaitEvent()` may have a relatively high CPU usage. Setting this to **ON** polls for events periodically but otherwise keeps the main thread sleeping, reducing CPU usage. The drawback is that there is a slightly increased latency reacting to new events after idle mode ends. | | 79 | | `ENABLE_IDLE_SLEEP` | Sleep in the main thread instead of waiting for events. On some platforms, when using SDL 2.0.16 or earlier, `SDL_WaitEvent()` may have a relatively high CPU usage. Setting this to **ON** polls for events periodically but otherwise keeps the main thread sleeping, reducing CPU usage. The drawback is that there is a slightly increased latency reacting to new events after idle mode ends. | |
80 | | `ENABLE_FRIBIDI` | Use the GNU FriBidi library for processing bidirectional text. FriBidi implements the Unicode Bidirectional Algorithm to determine text directions. | | 80 | | `ENABLE_FRIBIDI` | Use the GNU FriBidi library for processing bidirectional text. FriBidi implements the Unicode Bidirectional Algorithm to determine text directions. | |
81 | | `ENABLE_FRIBIDI_BUILD` | Compile the GNU FriBidi library as part of the build. If set to **OFF**, `pkg-config` is used instead to locate the library. | | 81 | | `ENABLE_FRIBIDI_BUILD` | Compile the GNU FriBidi library as part of the build. If set to **OFF**, `pkg-config` is used instead to locate the library. | |
82 | | `ENABLE_HARFBUZZ` | Use the HarfBuzz library for shaping Unicode text. This is required for correctly rendering complex scripts and combining glyphs. If disabled, a simplified text shaping algorithm is used that only works for non-complex languages like English. | | 82 | | `ENABLE_HARFBUZZ` | Use the HarfBuzz library for shaping Unicode text. This is required for correctly rendering complex scripts and combining glyphs. If disabled, a simplified text shaping algorithm is used that only works for non-complex languages like English. | |
diff --git a/lib/the_Foundation b/lib/the_Foundation | |||
Subproject cd0a22f5f003b723ecc038f287a320fb9b7c1d8 | Subproject 453f05f6efb46824ff0dca0174036c7624473e4 | ||
@@ -1,7 +1,7 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
4 | "PO-Revision-Date: 2021-12-01 16:50+0000\n" | 4 | "PO-Revision-Date: 2022-01-20 04:05+0000\n" |
5 | "Last-Translator: Tadeáš Erban <tadysekerbosek@gmail.com>\n" | 5 | "Last-Translator: Tadeáš Erban <tadysekerbosek@gmail.com>\n" |
6 | "Language-Team: Czech <http://weblate.skyjake.fi/projects/lagrange/ui/cs/>\n" | 6 | "Language-Team: Czech <http://weblate.skyjake.fi/projects/lagrange/ui/cs/>\n" |
7 | "Language: cs\n" | 7 | "Language: cs\n" |
@@ -13,7 +13,7 @@ msgstr "" | |||
13 | 13 | ||
14 | # Link download progress message. | 14 | # Link download progress message. |
15 | msgid "doc.fetching" | 15 | msgid "doc.fetching" |
16 | msgstr "Stahování" | 16 | msgstr "Načtní" |
17 | 17 | ||
18 | #, c-format | 18 | #, c-format |
19 | msgid "doc.archive" | 19 | msgid "doc.archive" |
@@ -504,13 +504,13 @@ msgid "menu.copyurl" | |||
504 | msgstr "Kopírovat adresu URL" | 504 | msgstr "Kopírovat adresu URL" |
505 | 505 | ||
506 | msgid "heading.history.clear" | 506 | msgid "heading.history.clear" |
507 | msgstr "VYMAZAT HISTORII" | 507 | msgstr "Vymazat historii" |
508 | 508 | ||
509 | msgid "dlg.history.clear" | 509 | msgid "dlg.history.clear" |
510 | msgstr "Vymazat historii" | 510 | msgstr "Vymazat historii" |
511 | 511 | ||
512 | msgid "heading.confirm.bookmarks.delete" | 512 | msgid "heading.confirm.bookmarks.delete" |
513 | msgstr "ODSTRANIT ZLOKY" | 513 | msgstr "Odstranit zloky" |
514 | 514 | ||
515 | #, c-format | 515 | #, c-format |
516 | msgid "dlg.bookmarks.delete" | 516 | msgid "dlg.bookmarks.delete" |
@@ -640,7 +640,7 @@ msgid "dlg.input.send" | |||
640 | msgstr "Odeslat" | 640 | msgstr "Odeslat" |
641 | 641 | ||
642 | msgid "heading.save" | 642 | msgid "heading.save" |
643 | msgstr "SOUBOR ULOEN" | 643 | msgstr "Soubor uloen" |
644 | 644 | ||
645 | msgid "dlg.save.opendownload" | 645 | msgid "dlg.save.opendownload" |
646 | msgstr "Otevřít stažený soubor" | 646 | msgstr "Otevřít stažený soubor" |
@@ -728,19 +728,19 @@ msgid "dlg.save.incomplete" | |||
728 | msgstr "Obsah této stránky se ještě stahuje." | 728 | msgstr "Obsah této stránky se ještě stahuje." |
729 | 729 | ||
730 | msgid "heading.autoreload" | 730 | msgid "heading.autoreload" |
731 | msgstr "AUTOMATICK OBNOVOVN" | 731 | msgstr "Automatick obnovovn" |
732 | 732 | ||
733 | msgid "dlg.autoreload" | 733 | msgid "dlg.autoreload" |
734 | msgstr "Vyberte interval automatických aktualizací pro tuto kartu." | 734 | msgstr "Vyberte interval automatických aktualizací pro tuto kartu." |
735 | 735 | ||
736 | msgid "heading.file.delete" | 736 | msgid "heading.file.delete" |
737 | msgstr "ODSTRANIT SOUBOR" | 737 | msgstr "Odstranit soubor" |
738 | 738 | ||
739 | msgid "dlg.certwarn.different" | 739 | msgid "dlg.certwarn.different" |
740 | msgstr "Přijatý certifikát je platný, ale je jiný než ten, kterému důvěřujeme." | 740 | msgstr "Přijatý certifikát je platný, ale je jiný než ten, kterému důvěřujeme." |
741 | 741 | ||
742 | msgid "link.hint.image" | 742 | msgid "link.hint.image" |
743 | msgstr "Zobrazit obrázek" | 743 | msgstr "Obrázek" |
744 | 744 | ||
745 | msgid "bookmark.export.format.sub" | 745 | msgid "bookmark.export.format.sub" |
746 | msgstr "" | 746 | msgstr "" |
@@ -787,7 +787,7 @@ msgid "lang.es.mx" | |||
787 | msgstr "Španělština (Mexická)" | 787 | msgstr "Španělština (Mexická)" |
788 | 788 | ||
789 | msgid "heading.newident" | 789 | msgid "heading.newident" |
790 | msgstr "NOV TOTONOST" | 790 | msgstr "Nov totonost" |
791 | 791 | ||
792 | msgid "lang.uk" | 792 | msgid "lang.uk" |
793 | msgstr "Ukrajinština" | 793 | msgstr "Ukrajinština" |
@@ -826,19 +826,19 @@ msgid "ident.export" | |||
826 | msgstr "Exportovat" | 826 | msgstr "Exportovat" |
827 | 827 | ||
828 | msgid "heading.ident.use" | 828 | msgid "heading.ident.use" |
829 | msgstr "POUIT TOTONOSTI" | 829 | msgstr "Pouit totonosti" |
830 | 830 | ||
831 | msgid "menu.edit.notes" | 831 | msgid "menu.edit.notes" |
832 | msgstr "Upravit poznámky…" | 832 | msgstr "Upravit poznámky…" |
833 | 833 | ||
834 | msgid "heading.ident.notes" | 834 | msgid "heading.ident.notes" |
835 | msgstr "POZNMKY TOTONOSTI" | 835 | msgstr "Poznmky k totonosti" |
836 | 836 | ||
837 | msgid "ident.fingerprint" | 837 | msgid "ident.fingerprint" |
838 | msgstr "Zkopírovat otisk" | 838 | msgstr "Zkopírovat otisk" |
839 | 839 | ||
840 | msgid "heading.unsub" | 840 | msgid "heading.unsub" |
841 | msgstr "UKONIT ODBR" | 841 | msgstr "Ukonit odbr" |
842 | 842 | ||
843 | #, c-format | 843 | #, c-format |
844 | msgid "dlg.confirm.unsub" | 844 | msgid "dlg.confirm.unsub" |
@@ -857,7 +857,7 @@ msgid "error.server.msg" | |||
857 | msgstr "Server odpověděl s následující zprávou:" | 857 | msgstr "Server odpověděl s následující zprávou:" |
858 | 858 | ||
859 | msgid "heading.pageinfo" | 859 | msgid "heading.pageinfo" |
860 | msgstr "INFORMACE O STRNCE" | 860 | msgstr "Informace o strnce" |
861 | 861 | ||
862 | msgid "pageinfo.cert.status" | 862 | msgid "pageinfo.cert.status" |
863 | msgstr "Stav certifikátu:" | 863 | msgstr "Stav certifikátu:" |
@@ -869,7 +869,7 @@ msgid "ident.delete" | |||
869 | msgstr "Odstranit totožnost…" | 869 | msgstr "Odstranit totožnost…" |
870 | 870 | ||
871 | msgid "heading.ident.delete" | 871 | msgid "heading.ident.delete" |
872 | msgstr "ODSTRANIT TOTONOST" | 872 | msgstr "Odstranit totonost" |
873 | 873 | ||
874 | msgid "dlg.ident.delete" | 874 | msgid "dlg.ident.delete" |
875 | msgstr "Odstranit totožnost a její soubory" | 875 | msgstr "Odstranit totožnost a její soubory" |
@@ -903,16 +903,16 @@ msgid "dlg.input.linebreak" | |||
903 | msgstr "Zalomení řádku" | 903 | msgstr "Zalomení řádku" |
904 | 904 | ||
905 | msgid "heading.save.incomplete" | 905 | msgid "heading.save.incomplete" |
906 | msgstr "STRNKA NEPLN" | 906 | msgstr "Strnka nepln" |
907 | 907 | ||
908 | msgid "dlg.save.size" | 908 | msgid "dlg.save.size" |
909 | msgstr "Velikost:" | 909 | msgstr "Velikost:" |
910 | 910 | ||
911 | msgid "heading.save.error" | 911 | msgid "heading.save.error" |
912 | msgstr "CHYBA PI UKLDN SOUBORU" | 912 | msgstr "Chyba pi ukldn souboru" |
913 | 913 | ||
914 | msgid "heading.import.bookmarks" | 914 | msgid "heading.import.bookmarks" |
915 | msgstr "NAHRT ZLOKY" | 915 | msgstr "Nahrt zloky" |
916 | 916 | ||
917 | #, c-format | 917 | #, c-format |
918 | msgid "dlg.import.add" | 918 | msgid "dlg.import.add" |
@@ -964,7 +964,7 @@ msgid "link.browser" | |||
964 | msgstr "Otevřít odkaz ve výchozím prohlížeči" | 964 | msgstr "Otevřít odkaz ve výchozím prohlížeči" |
965 | 965 | ||
966 | msgid "link.noproxy" | 966 | msgid "link.noproxy" |
967 | msgstr "Otevřít bez použití proxy" | 967 | msgstr "Otevřít bez použití prostředníka" |
968 | 968 | ||
969 | msgid "link.copy" | 969 | msgid "link.copy" |
970 | msgstr "Zkopírovat odkaz" | 970 | msgstr "Zkopírovat odkaz" |
@@ -979,7 +979,7 @@ msgid "dlg.file.delete" | |||
979 | msgstr "Odstranit" | 979 | msgstr "Odstranit" |
980 | 980 | ||
981 | msgid "heading.openlink" | 981 | msgid "heading.openlink" |
982 | msgstr "OTEVT ODKAZ" | 982 | msgstr "Otevt odkaz" |
983 | 983 | ||
984 | #, c-format | 984 | #, c-format |
985 | msgid "dlg.openlink.confirm" | 985 | msgid "dlg.openlink.confirm" |
@@ -1014,16 +1014,16 @@ msgid "dlg.certimport.notfound" | |||
1014 | msgstr "Žádný certifikát nebo soukromý klíč nebyl nalezen." | 1014 | msgstr "Žádný certifikát nebo soukromý klíč nebyl nalezen." |
1015 | 1015 | ||
1016 | msgid "heading.certimport.pasted" | 1016 | msgid "heading.certimport.pasted" |
1017 | msgstr "VLOENO ZE SCHRNKY" | 1017 | msgstr "Vloeno ze schrnky" |
1018 | 1018 | ||
1019 | msgid "heading.certimport.dropped" | 1019 | msgid "heading.certimport.dropped" |
1020 | msgstr "PETHNUT SOUBOR" | 1020 | msgstr "Pethnut soubor" |
1021 | 1021 | ||
1022 | msgid "dlg.certimport.import" | 1022 | msgid "dlg.certimport.import" |
1023 | msgstr "Nahrát" | 1023 | msgstr "Nahrát" |
1024 | 1024 | ||
1025 | msgid "heading.certimport" | 1025 | msgid "heading.certimport" |
1026 | msgstr "NAHRT TOTONOST" | 1026 | msgstr "Nahrt totonost" |
1027 | 1027 | ||
1028 | msgid "dlg.certimport.notes" | 1028 | msgid "dlg.certimport.notes" |
1029 | msgstr "Poznámky:" | 1029 | msgstr "Poznámky:" |
@@ -1038,7 +1038,7 @@ msgid "dlg.certimport.nokey" | |||
1038 | msgstr "Žádný soukromý klíč" | 1038 | msgstr "Žádný soukromý klíč" |
1039 | 1039 | ||
1040 | msgid "link.hint.audio" | 1040 | msgid "link.hint.audio" |
1041 | msgstr "Přehrát zvuk" | 1041 | msgstr "Zvuk" |
1042 | 1042 | ||
1043 | msgid "bookmark.title.blank" | 1043 | msgid "bookmark.title.blank" |
1044 | msgstr "Prázdná stránka" | 1044 | msgstr "Prázdná stránka" |
@@ -1088,10 +1088,10 @@ msgid "hint.upload.text" | |||
1088 | msgstr "vložte text k odeslání" | 1088 | msgstr "vložte text k odeslání" |
1089 | 1089 | ||
1090 | msgid "menu.page.upload" | 1090 | msgid "menu.page.upload" |
1091 | msgstr "Nahrát stránku na server pomocí Titanu…" | 1091 | msgstr "Nahrát na server pomocí Titanu…" |
1092 | 1092 | ||
1093 | msgid "heading.upload" | 1093 | msgid "heading.upload" |
1094 | msgstr "NAHRT NA SERVER POMOC TITANU" | 1094 | msgstr "Nahrt pomoc titanu" |
1095 | 1095 | ||
1096 | msgid "upload.file.name" | 1096 | msgid "upload.file.name" |
1097 | msgstr "Jméno souboru:" | 1097 | msgstr "Jméno souboru:" |
@@ -1127,7 +1127,7 @@ msgid "dlg.upload.pickfile" | |||
1127 | msgstr "Vybrat soubor" | 1127 | msgstr "Vybrat soubor" |
1128 | 1128 | ||
1129 | msgid "heading.translate" | 1129 | msgid "heading.translate" |
1130 | msgstr "PELOIT STRNKU" | 1130 | msgstr "Peloit strnku" |
1131 | 1131 | ||
1132 | msgid "dlg.translate.unavail" | 1132 | msgid "dlg.translate.unavail" |
1133 | msgstr "Služba nedostupná" | 1133 | msgstr "Služba nedostupná" |
@@ -1190,23 +1190,23 @@ msgid "lang.sk" | |||
1190 | msgstr "Slovenština" | 1190 | msgstr "Slovenština" |
1191 | 1191 | ||
1192 | msgid "keys.upload" | 1192 | msgid "keys.upload" |
1193 | msgstr "Nahrát stránku pomocí Titanu" | 1193 | msgstr "Nahrát pomocí Titanu" |
1194 | 1194 | ||
1195 | msgid "error.proxy.msg" | 1195 | msgid "error.proxy.msg" |
1196 | msgstr "" | 1196 | msgstr "" |
1197 | "Požadavek pomocí proxy selhal, protože server nebyl schopen utvořit spojení " | 1197 | "Požadavek pomocí prostředníka selhal, protože server nebyl schopen utvořit " |
1198 | "s vzdáleným hostitelem. Je možné, že existují problémy s konektivitou." | 1198 | "spojení s vzdáleným hostitelem. Je možné, že existují problémy s připojením." |
1199 | 1199 | ||
1200 | msgid "error.proxyrefusal.msg" | 1200 | msgid "error.proxyrefusal.msg" |
1201 | msgstr "" | 1201 | msgstr "" |
1202 | "Váš požadavek byl pro zdroj na doméně, která není obsluhována tímto serverem " | 1202 | "Váš požadavek byl pro zdroj na doméně, která není obsluhována tímto serverem " |
1203 | "a ten server nepřijímá proxy požadavky." | 1203 | "a ten server nepřijímá požadavky přes prostředníky." |
1204 | 1204 | ||
1205 | msgid "heading.bookmark.add" | 1205 | msgid "heading.bookmark.add" |
1206 | msgstr "PIDAT ZLOKU" | 1206 | msgstr "Pidat zloku" |
1207 | 1207 | ||
1208 | msgid "heading.bookmark.edit" | 1208 | msgid "heading.bookmark.edit" |
1209 | msgstr "UPRAVIT ZLOKU" | 1209 | msgstr "Upravit zloku" |
1210 | 1210 | ||
1211 | msgid "dlg.bookmark.save" | 1211 | msgid "dlg.bookmark.save" |
1212 | msgstr "Uložit záložku" | 1212 | msgstr "Uložit záložku" |
@@ -1227,20 +1227,20 @@ msgid "dlg.addfolder.prompt" | |||
1227 | msgstr "Zadejte jméno nové složky:" | 1227 | msgstr "Zadejte jméno nové složky:" |
1228 | 1228 | ||
1229 | msgid "heading.addfolder" | 1229 | msgid "heading.addfolder" |
1230 | msgstr "PIDAT SLOKU" | 1230 | msgstr "Pidat sloku" |
1231 | 1231 | ||
1232 | msgid "dlg.bookmark.url" | 1232 | msgid "dlg.bookmark.url" |
1233 | msgstr "URL:" | 1233 | msgstr "URL:" |
1234 | 1234 | ||
1235 | msgid "heading.prefs.certs" | 1235 | msgid "heading.prefs.certs" |
1236 | msgstr "CERTIFIKTY" | 1236 | msgstr "Certifikty" |
1237 | 1237 | ||
1238 | # used on mobile | 1238 | # used on mobile |
1239 | msgid "heading.settings" | 1239 | msgid "heading.settings" |
1240 | msgstr "NASTAVEN" | 1240 | msgstr "Nastaven" |
1241 | 1241 | ||
1242 | msgid "heading.prefs.uitheme" | 1242 | msgid "heading.prefs.uitheme" |
1243 | msgstr "BARVY ROZHRAN" | 1243 | msgstr "Barvy rozhran" |
1244 | 1244 | ||
1245 | msgid "prefs.searchurl" | 1245 | msgid "prefs.searchurl" |
1246 | msgstr "URL vyhledávání:" | 1246 | msgstr "URL vyhledávání:" |
@@ -1289,7 +1289,7 @@ msgid "error.cgi" | |||
1289 | msgstr "CGI chyba" | 1289 | msgstr "CGI chyba" |
1290 | 1290 | ||
1291 | msgid "error.proxy" | 1291 | msgid "error.proxy" |
1292 | msgstr "Chyba proxy" | 1292 | msgstr "Chyba prostředníka" |
1293 | 1293 | ||
1294 | msgid "error.permanent" | 1294 | msgid "error.permanent" |
1295 | msgstr "Trvalé selhání" | 1295 | msgstr "Trvalé selhání" |
@@ -1386,13 +1386,13 @@ msgid "dlg.newident.more" | |||
1386 | msgstr "Více…" | 1386 | msgstr "Více…" |
1387 | 1387 | ||
1388 | msgid "heading.newident.missing" | 1388 | msgid "heading.newident.missing" |
1389 | msgstr "CHYBJC INFORMACE" | 1389 | msgstr "Chybjc informace" |
1390 | 1390 | ||
1391 | msgid "dlg.newindent.missing.commonname" | 1391 | msgid "dlg.newindent.missing.commonname" |
1392 | msgstr "\"Obecné jméno\" musí být specifikováno." | 1392 | msgstr "\"Obecné jméno\" musí být specifikováno." |
1393 | 1393 | ||
1394 | msgid "heading.newident.date.bad" | 1394 | msgid "heading.newident.date.bad" |
1395 | msgstr "NEPLATN DATUM" | 1395 | msgstr "Neplatn datum" |
1396 | 1396 | ||
1397 | msgid "dlg.newident.date.past" | 1397 | msgid "dlg.newident.date.past" |
1398 | msgstr "Datum vypršení platnosti musí být v budoucnosti." | 1398 | msgstr "Datum vypršení platnosti musí být v budoucnosti." |
@@ -1408,10 +1408,10 @@ msgid "keys.split.menu" | |||
1408 | msgstr "Nastavit režim rozpůleného okna" | 1408 | msgstr "Nastavit režim rozpůleného okna" |
1409 | 1409 | ||
1410 | msgid "heading.feedcfg" | 1410 | msgid "heading.feedcfg" |
1411 | msgstr "NASTAVEN KANLU" | 1411 | msgstr "Nastaven kanlu" |
1412 | 1412 | ||
1413 | msgid "heading.subscribe" | 1413 | msgid "heading.subscribe" |
1414 | msgstr "ODEBRAT STRNKU" | 1414 | msgstr "Odebrat strnku" |
1415 | 1415 | ||
1416 | msgid "dlg.feed.title" | 1416 | msgid "dlg.feed.title" |
1417 | msgstr "Název:" | 1417 | msgstr "Název:" |
@@ -1438,22 +1438,22 @@ msgid "heading.prefs.network" | |||
1438 | msgstr "Síť" | 1438 | msgstr "Síť" |
1439 | 1439 | ||
1440 | msgid "heading.prefs.paragraph" | 1440 | msgid "heading.prefs.paragraph" |
1441 | msgstr "ODSTAVEC" | 1441 | msgstr "Odstavec" |
1442 | 1442 | ||
1443 | msgid "heading.prefs.pagecontent" | 1443 | msgid "heading.prefs.pagecontent" |
1444 | msgstr "BARVY STRNKY" | 1444 | msgstr "Barvy strnky" |
1445 | 1445 | ||
1446 | msgid "heading.prefs.proxies" | 1446 | msgid "heading.prefs.proxies" |
1447 | msgstr "PROXY" | 1447 | msgstr "Prostředníci" |
1448 | 1448 | ||
1449 | msgid "heading.prefs.scrolling" | 1449 | msgid "heading.prefs.scrolling" |
1450 | msgstr "POSOUVN STRNKY" | 1450 | msgstr "Posouvn strnky" |
1451 | 1451 | ||
1452 | msgid "heading.prefs.sizing" | 1452 | msgid "heading.prefs.sizing" |
1453 | msgstr "DIMENZOVN" | 1453 | msgstr "Dimenzovn" |
1454 | 1454 | ||
1455 | msgid "heading.prefs.widelayout" | 1455 | msgid "heading.prefs.widelayout" |
1456 | msgstr "ŠIROK ROZLOEN" | 1456 | msgstr "Širok rozloen" |
1457 | 1457 | ||
1458 | # tab button | 1458 | # tab button |
1459 | msgid "heading.prefs.style" | 1459 | msgid "heading.prefs.style" |
@@ -1598,13 +1598,13 @@ msgid "prefs.ca.path" | |||
1598 | msgstr "Cesta k CA:" | 1598 | msgstr "Cesta k CA:" |
1599 | 1599 | ||
1600 | msgid "prefs.proxy.gemini" | 1600 | msgid "prefs.proxy.gemini" |
1601 | msgstr "Proxy pro Gemini:" | 1601 | msgstr "Prostředník pro Gemini:" |
1602 | 1602 | ||
1603 | msgid "prefs.proxy.gopher" | 1603 | msgid "prefs.proxy.gopher" |
1604 | msgstr "Proxy pro Gopher:" | 1604 | msgstr "Prostředník pro Gopher:" |
1605 | 1605 | ||
1606 | msgid "prefs.proxy.http" | 1606 | msgid "prefs.proxy.http" |
1607 | msgstr "Proxy pro HTTP:" | 1607 | msgstr "Prostředník pro HTTP:" |
1608 | 1608 | ||
1609 | msgid "menu.binding.reset" | 1609 | msgid "menu.binding.reset" |
1610 | msgstr "Obnovit výchozí" | 1610 | msgstr "Obnovit výchozí" |
@@ -1720,7 +1720,7 @@ msgid "error.gone.msg" | |||
1720 | msgstr "Požadovaný zdroj již není dostupný." | 1720 | msgstr "Požadovaný zdroj již není dostupný." |
1721 | 1721 | ||
1722 | msgid "error.proxyrefusal" | 1722 | msgid "error.proxyrefusal" |
1723 | msgstr "Proxy požadavek byl odmítnut" | 1723 | msgstr "Požadavek na prostředníka byl odmítnut" |
1724 | 1724 | ||
1725 | msgid "error.badheader.msg" | 1725 | msgid "error.badheader.msg" |
1726 | msgstr "" | 1726 | msgstr "" |
@@ -1769,13 +1769,13 @@ msgid "dlg.bookmark.icon" | |||
1769 | msgstr "Ikona:" | 1769 | msgstr "Ikona:" |
1770 | 1770 | ||
1771 | msgid "heading.bookmark.tags" | 1771 | msgid "heading.bookmark.tags" |
1772 | msgstr "SPECILN ZNAEN" | 1772 | msgstr "Speciln značení" |
1773 | 1773 | ||
1774 | msgid "dlg.addfolder" | 1774 | msgid "dlg.addfolder" |
1775 | msgstr "Přidat složku" | 1775 | msgstr "Přidat složku" |
1776 | 1776 | ||
1777 | msgid "heading.prefs" | 1777 | msgid "heading.prefs" |
1778 | msgstr "PEDVOLBY" | 1778 | msgstr "Pedvolby" |
1779 | 1779 | ||
1780 | # tab button | 1780 | # tab button |
1781 | msgid "heading.prefs.colors" | 1781 | msgid "heading.prefs.colors" |
@@ -2009,7 +2009,7 @@ msgid "fontpack.delete" | |||
2009 | msgstr "Trvale odstranit \"%s\"" | 2009 | msgstr "Trvale odstranit \"%s\"" |
2010 | 2010 | ||
2011 | msgid "heading.fontpack.delete" | 2011 | msgid "heading.fontpack.delete" |
2012 | msgstr "ODSTRANIT PSMOV BALEK" | 2012 | msgstr "Odstranit psmov balek" |
2013 | 2013 | ||
2014 | #, c-format | 2014 | #, c-format |
2015 | msgid "dlg.fontpack.delete.confirm" | 2015 | msgid "dlg.fontpack.delete.confirm" |
@@ -2030,7 +2030,7 @@ msgstr "" | |||
2030 | "zkopírovány do složky uživatelských písem." | 2030 | "zkopírovány do složky uživatelských písem." |
2031 | 2031 | ||
2032 | msgid "heading.dismiss.warning" | 2032 | msgid "heading.dismiss.warning" |
2033 | msgstr "PŘESTAT UKAZOVAT VAROVN?" | 2033 | msgstr "Skrýt varovn?" |
2034 | 2034 | ||
2035 | #, c-format | 2035 | #, c-format |
2036 | msgid "dlg.dismiss.ansi" | 2036 | msgid "dlg.dismiss.ansi" |
@@ -2040,7 +2040,7 @@ msgid "dlg.dismiss.warning" | |||
2040 | msgstr "Přestat ukazovat varování" | 2040 | msgstr "Přestat ukazovat varování" |
2041 | 2041 | ||
2042 | msgid "heading.fontpack.classic" | 2042 | msgid "heading.fontpack.classic" |
2043 | msgstr "STHNOUT PSMOV BALEK" | 2043 | msgstr "Sthnout psmov balek" |
2044 | 2044 | ||
2045 | msgid "dlg.fontpack.classic.msg" | 2045 | msgid "dlg.fontpack.classic.msg" |
2046 | msgstr "" | 2046 | msgstr "" |
@@ -2152,3 +2152,99 @@ msgstr "Zkontrolovat dostupné aktualizace…" | |||
2152 | # This label should be fairly short so it fits in a button in the sidebar. | 2152 | # This label should be fairly short so it fits in a button in the sidebar. |
2153 | msgid "sidebar.action.feeds.markallread" | 2153 | msgid "sidebar.action.feeds.markallread" |
2154 | msgstr "Označit vše jako přečtené" | 2154 | msgstr "Označit vše jako přečtené" |
2155 | |||
2156 | msgid "menu.open.external" | ||
2157 | msgstr "Otevřít v jiné aplikaci" | ||
2158 | |||
2159 | # Active identity toolbar menu. | ||
2160 | msgid "menu.hide.identities" | ||
2161 | msgstr "Skrýt totožnosti" | ||
2162 | |||
2163 | msgid "menu.home" | ||
2164 | msgstr "Jít domů" | ||
2165 | |||
2166 | msgid "menu.identities" | ||
2167 | msgstr "Spravovat totožnosti" | ||
2168 | |||
2169 | msgid "sidebar.close" | ||
2170 | msgstr "Hotovo" | ||
2171 | |||
2172 | msgid "sidebar.action.bookmarks.newfolder" | ||
2173 | msgstr "Nová složka" | ||
2174 | |||
2175 | msgid "sidebar.action.bookmarks.edit" | ||
2176 | msgstr "Upravit" | ||
2177 | |||
2178 | msgid "sidebar.action.history.clear" | ||
2179 | msgstr "Vymazat" | ||
2180 | |||
2181 | # The %s represents the name of an identity. | ||
2182 | #, c-format | ||
2183 | msgid "ident.switch" | ||
2184 | msgstr "Používat %s" | ||
2185 | |||
2186 | msgid "sidebar.empty.unread" | ||
2187 | msgstr "Žádné nepřečtené záznamy" | ||
2188 | |||
2189 | # Paste the line preceding the clicked link into the input prompt. | ||
2190 | msgid "menu.input.precedingline" | ||
2191 | msgstr "Vložit předcházející řádek" | ||
2192 | |||
2193 | # Shows where a local file is using the Finder. | ||
2194 | msgid "menu.reveal.macos" | ||
2195 | msgstr "Ukázat ve Finderu" | ||
2196 | |||
2197 | msgid "menu.share" | ||
2198 | msgstr "Sdílet" | ||
2199 | |||
2200 | msgid "menu.page.upload.edit" | ||
2201 | msgstr "Upravit pomocí Titanu…" | ||
2202 | |||
2203 | msgid "heading.upload.id" | ||
2204 | msgstr "Oprávnění" | ||
2205 | |||
2206 | msgid "menu.upload.export" | ||
2207 | msgstr "Exportovat text" | ||
2208 | |||
2209 | msgid "menu.upload.delete" | ||
2210 | msgstr "Odstranit vše" | ||
2211 | |||
2212 | msgid "menu.upload.delete.confirm" | ||
2213 | msgstr "Opravdu odstranit vše (není cesty zpět)" | ||
2214 | |||
2215 | # Mobile subheading in the Upload dialog. | ||
2216 | msgid "upload.url" | ||
2217 | msgstr "URL" | ||
2218 | |||
2219 | # Mobile subheading: buttons for entering uploaded data. | ||
2220 | msgid "upload.content" | ||
2221 | msgstr "Obsah" | ||
2222 | |||
2223 | msgid "hint.upload.path" | ||
2224 | msgstr "Cesta URL" | ||
2225 | |||
2226 | msgid "hint.upload.token.long" | ||
2227 | msgstr "token - podívejte se na instrukce serveru" | ||
2228 | |||
2229 | msgid "heading.prefs.toolbaractions" | ||
2230 | msgstr "Akce panelu nástrojů" | ||
2231 | |||
2232 | msgid "prefs.toolbaraction1" | ||
2233 | msgstr "Tlačítko 1" | ||
2234 | |||
2235 | msgid "prefs.toolbaraction2" | ||
2236 | msgstr "Tlačítko 2" | ||
2237 | |||
2238 | msgid "prefs.blink" | ||
2239 | msgstr "Blikající kurzor:" | ||
2240 | |||
2241 | msgid "keys.upload.edit" | ||
2242 | msgstr "Upravit pomocí Titanu" | ||
2243 | |||
2244 | # Menu heading shown when customizing navbar button actions. | ||
2245 | msgid "menu.toolbar.setaction" | ||
2246 | msgstr "Nastavit akci:" | ||
2247 | |||
2248 | # Shows where a local file is using the File Manager. | ||
2249 | msgid "menu.reveal.filemgr" | ||
2250 | msgstr "Otevřít v průzkumníku souborů" | ||
@@ -1,7 +1,7 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
4 | "PO-Revision-Date: 2021-11-27 04:58+0000\n" | 4 | "PO-Revision-Date: 2022-01-08 14:50+0000\n" |
5 | "Last-Translator: Alyssa Liddell <e-liss@tuta.io>\n" | 5 | "Last-Translator: Alyssa Liddell <e-liss@tuta.io>\n" |
6 | "Language-Team: German <http://weblate.skyjake.fi/projects/lagrange/ui/de/>\n" | 6 | "Language-Team: German <http://weblate.skyjake.fi/projects/lagrange/ui/de/>\n" |
7 | "Language: de\n" | 7 | "Language: de\n" |
@@ -1167,7 +1167,7 @@ msgid "dlg.certimport.nokey" | |||
1167 | msgstr "Kein privater Schlüssel" | 1167 | msgstr "Kein privater Schlüssel" |
1168 | 1168 | ||
1169 | msgid "link.hint.audio" | 1169 | msgid "link.hint.audio" |
1170 | msgstr "Audio abspielen" | 1170 | msgstr "Audio" |
1171 | 1171 | ||
1172 | # tab button | 1172 | # tab button |
1173 | msgid "heading.prefs.userinterface" | 1173 | msgid "heading.prefs.userinterface" |
@@ -2125,3 +2125,32 @@ msgstr "Hintergrundfarbe" | |||
2125 | 2125 | ||
2126 | msgid "sidebar.action.show" | 2126 | msgid "sidebar.action.show" |
2127 | msgstr "Anzeigen:" | 2127 | msgstr "Anzeigen:" |
2128 | |||
2129 | msgid "sidebar.close" | ||
2130 | msgstr "Fertig" | ||
2131 | |||
2132 | msgid "sidebar.action.bookmarks.newfolder" | ||
2133 | msgstr "Neuer Ordner" | ||
2134 | |||
2135 | msgid "sidebar.action.bookmarks.edit" | ||
2136 | msgstr "Bearbeiten" | ||
2137 | |||
2138 | msgid "menu.upload.delete" | ||
2139 | msgstr "Alles löschen" | ||
2140 | |||
2141 | # Mobile subheading in the Upload dialog. | ||
2142 | msgid "upload.url" | ||
2143 | msgstr "URL" | ||
2144 | |||
2145 | # Mobile subheading: buttons for entering uploaded data. | ||
2146 | msgid "upload.content" | ||
2147 | msgstr "Inhalt" | ||
2148 | |||
2149 | msgid "menu.page.upload.edit" | ||
2150 | msgstr "Seite mit Titan bearbeiten…" | ||
2151 | |||
2152 | msgid "prefs.toolbaraction1" | ||
2153 | msgstr "Taste 1" | ||
2154 | |||
2155 | msgid "prefs.toolbaraction2" | ||
2156 | msgstr "Taste 2" | ||
@@ -3,7 +3,7 @@ msgstr "Preformatted text without a caption" | |||
3 | 3 | ||
4 | # Link download progress message. | 4 | # Link download progress message. |
5 | msgid "doc.fetching" | 5 | msgid "doc.fetching" |
6 | msgstr "Fetching" | 6 | msgstr "Loading" |
7 | 7 | ||
8 | #, c-format | 8 | #, c-format |
9 | msgid "doc.archive" | 9 | msgid "doc.archive" |
@@ -196,6 +196,9 @@ msgstr "Find on Page" | |||
196 | msgid "macos.menu.find" | 196 | msgid "macos.menu.find" |
197 | msgstr "Find" | 197 | msgstr "Find" |
198 | 198 | ||
199 | msgid "menu.open.external" | ||
200 | msgstr "Open in Another App" | ||
201 | |||
199 | # Used on iOS. "Files" refers to Apple's iOS app where you can pick an iCloud folder. | 202 | # Used on iOS. "Files" refers to Apple's iOS app where you can pick an iCloud folder. |
200 | msgid "menu.save.files" | 203 | msgid "menu.save.files" |
201 | msgstr "Save to Files" | 204 | msgstr "Save to Files" |
@@ -321,6 +324,10 @@ msgstr "Show History" | |||
321 | msgid "menu.show.identities" | 324 | msgid "menu.show.identities" |
322 | msgstr "Show Identities" | 325 | msgstr "Show Identities" |
323 | 326 | ||
327 | # Active identity toolbar menu. | ||
328 | msgid "menu.hide.identities" | ||
329 | msgstr "Hide Identities" | ||
330 | "" | ||
324 | # Used in the View menu on macOS. Shows sidebar and switches sidebar tab. | 331 | # Used in the View menu on macOS. Shows sidebar and switches sidebar tab. |
325 | msgid "menu.show.outline" | 332 | msgid "menu.show.outline" |
326 | msgstr "Show Page Outline" | 333 | msgstr "Show Page Outline" |
@@ -337,6 +344,9 @@ msgstr "Go to Parent" | |||
337 | msgid "menu.root" | 344 | msgid "menu.root" |
338 | msgstr "Go to Root" | 345 | msgstr "Go to Root" |
339 | 346 | ||
347 | msgid "menu.home" | ||
348 | msgstr "Go Home" | ||
349 | |||
340 | msgid "menu.reload" | 350 | msgid "menu.reload" |
341 | msgstr "Reload Page" | 351 | msgstr "Reload Page" |
342 | 352 | ||
@@ -391,9 +401,19 @@ msgstr "New Identity…" | |||
391 | msgid "menu.identity.import" | 401 | msgid "menu.identity.import" |
392 | msgstr "Import…" | 402 | msgstr "Import…" |
393 | 403 | ||
404 | msgid "menu.identities" | ||
405 | msgstr "Manage Identities" | ||
406 | |||
394 | msgid "menu.identity.notactive" | 407 | msgid "menu.identity.notactive" |
395 | msgstr "No Active Identity" | 408 | msgstr "No Active Identity" |
396 | 409 | ||
410 | # Menu heading shown when customizing navbar button actions. | ||
411 | msgid "menu.toolbar.setaction" | ||
412 | msgstr "Set Action:" | ||
413 | |||
414 | msgid "sidebar.close" | ||
415 | msgstr "Done" | ||
416 | |||
397 | msgid "sidebar.bookmarks" | 417 | msgid "sidebar.bookmarks" |
398 | msgstr "Bookmarks" | 418 | msgstr "Bookmarks" |
399 | 419 | ||
@@ -409,6 +429,12 @@ msgstr "Identities" | |||
409 | msgid "sidebar.outline" | 429 | msgid "sidebar.outline" |
410 | msgstr "Outline" | 430 | msgstr "Outline" |
411 | 431 | ||
432 | msgid "sidebar.action.bookmarks.newfolder" | ||
433 | msgstr "New Folder" | ||
434 | |||
435 | msgid "sidebar.action.bookmarks.edit" | ||
436 | msgstr "Edit" | ||
437 | |||
412 | # This label should be fairly short so it fits in a button in the sidebar. | 438 | # This label should be fairly short so it fits in a button in the sidebar. |
413 | msgid "sidebar.action.feeds.markallread" | 439 | msgid "sidebar.action.feeds.markallread" |
414 | msgstr "Read All" | 440 | msgstr "Read All" |
@@ -428,6 +454,9 @@ msgstr "New…" | |||
428 | msgid "sidebar.action.ident.import" | 454 | msgid "sidebar.action.ident.import" |
429 | msgstr "Import…" | 455 | msgstr "Import…" |
430 | 456 | ||
457 | msgid "sidebar.action.history.clear" | ||
458 | msgstr "Clear" | ||
459 | |||
431 | # Usage: "(count) Unread" in the sidebar tab title, referring to feed entries. | 460 | # Usage: "(count) Unread" in the sidebar tab title, referring to feed entries. |
432 | msgid "sidebar.unread" | 461 | msgid "sidebar.unread" |
433 | msgid_plural "sidebar.unread.n" | 462 | msgid_plural "sidebar.unread.n" |
@@ -548,7 +577,7 @@ msgid "history.clear" | |||
548 | msgstr "Clear History…" | 577 | msgstr "Clear History…" |
549 | 578 | ||
550 | msgid "heading.history.clear" | 579 | msgid "heading.history.clear" |
551 | msgstr "CLEAR HISTORY" | 580 | msgstr "Clear History" |
552 | 581 | ||
553 | msgid "dlg.confirm.history.clear" | 582 | msgid "dlg.confirm.history.clear" |
554 | msgstr "Do you really want to erase the history of all visited pages?" | 583 | msgstr "Do you really want to erase the history of all visited pages?" |
@@ -557,7 +586,7 @@ msgid "dlg.history.clear" | |||
557 | msgstr "Clear History" | 586 | msgstr "Clear History" |
558 | 587 | ||
559 | msgid "heading.confirm.bookmarks.delete" | 588 | msgid "heading.confirm.bookmarks.delete" |
560 | msgstr "DELETE BOOKMARKS" | 589 | msgstr "Delete Bookmarks" |
561 | 590 | ||
562 | #, c-format | 591 | #, c-format |
563 | msgid "dlg.confirm.bookmarks.delete" | 592 | msgid "dlg.confirm.bookmarks.delete" |
@@ -629,14 +658,19 @@ msgstr "Stop Using Everywhere" | |||
629 | msgid "ident.export" | 658 | msgid "ident.export" |
630 | msgstr "Export" | 659 | msgstr "Export" |
631 | 660 | ||
661 | # The %s represents the name of an identity. | ||
662 | #, c-format | ||
663 | msgid "ident.switch" | ||
664 | msgstr "Use %s" | ||
665 | |||
632 | msgid "heading.ident.use" | 666 | msgid "heading.ident.use" |
633 | msgstr "IDENTITY USAGE" | 667 | msgstr "Identity Usage" |
634 | 668 | ||
635 | msgid "menu.edit.notes" | 669 | msgid "menu.edit.notes" |
636 | msgstr "Edit Notes…" | 670 | msgstr "Edit Notes…" |
637 | 671 | ||
638 | msgid "heading.ident.notes" | 672 | msgid "heading.ident.notes" |
639 | msgstr "IDENTITY NOTES" | 673 | msgstr "Identity Notes" |
640 | 674 | ||
641 | # %s refers to name of an identity. | 675 | # %s refers to name of an identity. |
642 | #, c-format | 676 | #, c-format |
@@ -650,7 +684,7 @@ msgid "ident.delete" | |||
650 | msgstr "Delete Identity…" | 684 | msgstr "Delete Identity…" |
651 | 685 | ||
652 | msgid "heading.ident.delete" | 686 | msgid "heading.ident.delete" |
653 | msgstr "DELETE IDENTITY" | 687 | msgstr "Delete Identity" |
654 | 688 | ||
655 | #, c-format | 689 | #, c-format |
656 | msgid "dlg.confirm.ident.delete" | 690 | msgid "dlg.confirm.ident.delete" |
@@ -662,13 +696,16 @@ msgstr "Delete Identity and Files" | |||
662 | msgid "sidebar.empty.idents" | 696 | msgid "sidebar.empty.idents" |
663 | msgstr "No Identities" | 697 | msgstr "No Identities" |
664 | 698 | ||
699 | msgid "sidebar.empty.unread" | ||
700 | msgstr "No Unread Entries" | ||
701 | |||
665 | # The %s format characters are used to highlight the word "Help" and must be used in the translation in same way as here. | 702 | # The %s format characters are used to highlight the word "Help" and must be used in the translation in same way as here. |
666 | #, c-format | 703 | #, c-format |
667 | msgid "ident.gotohelp" | 704 | msgid "ident.gotohelp" |
668 | msgstr "See %sHelp%s for more information about TLS client certificates." | 705 | msgstr "See %sHelp%s for more information about TLS client certificates." |
669 | 706 | ||
670 | msgid "heading.unsub" | 707 | msgid "heading.unsub" |
671 | msgstr "UNSUBSCRIBE" | 708 | msgstr "Unsubscribe" |
672 | 709 | ||
673 | #, c-format | 710 | #, c-format |
674 | msgid "dlg.confirm.unsub" | 711 | msgid "dlg.confirm.unsub" |
@@ -685,7 +722,7 @@ msgid "error.server.msg" | |||
685 | msgstr "Server responded with the message:" | 722 | msgstr "Server responded with the message:" |
686 | 723 | ||
687 | msgid "heading.pageinfo" | 724 | msgid "heading.pageinfo" |
688 | msgstr "PAGE INFORMATION" | 725 | msgstr "Page Information" |
689 | 726 | ||
690 | msgid "pageinfo.header.cached" | 727 | msgid "pageinfo.header.cached" |
691 | msgstr "(cached content)" | 728 | msgstr "(cached content)" |
@@ -733,11 +770,15 @@ msgstr "Line break" | |||
733 | msgid "dlg.input.send" | 770 | msgid "dlg.input.send" |
734 | msgstr "Send" | 771 | msgstr "Send" |
735 | 772 | ||
773 | # Paste the line preceding the clicked link into the input prompt. | ||
774 | msgid "menu.input.precedingline" | ||
775 | msgstr "Paste Preceding Line" | ||
776 | |||
736 | msgid "heading.save" | 777 | msgid "heading.save" |
737 | msgstr "FILE SAVED" | 778 | msgstr "File Saved" |
738 | 779 | ||
739 | msgid "heading.save.incomplete" | 780 | msgid "heading.save.incomplete" |
740 | msgstr "PAGE INCOMPLETE" | 781 | msgstr "Page Incomplete" |
741 | 782 | ||
742 | msgid "dlg.save.incomplete" | 783 | msgid "dlg.save.incomplete" |
743 | msgstr "The page contents are still being downloaded." | 784 | msgstr "The page contents are still being downloaded." |
@@ -749,10 +790,10 @@ msgid "dlg.save.opendownload" | |||
749 | msgstr "Open Downloaded File" | 790 | msgstr "Open Downloaded File" |
750 | 791 | ||
751 | msgid "heading.save.error" | 792 | msgid "heading.save.error" |
752 | msgstr "ERROR SAVING FILE" | 793 | msgstr "Error Saving File" |
753 | 794 | ||
754 | msgid "heading.import.bookmarks" | 795 | msgid "heading.import.bookmarks" |
755 | msgstr "IMPORT BOOKMARKS" | 796 | msgstr "Import Bookmarks" |
756 | 797 | ||
757 | #, c-format | 798 | #, c-format |
758 | msgid "dlg.import.found" | 799 | msgid "dlg.import.found" |
@@ -770,7 +811,7 @@ msgid "dlg.import.notnew" | |||
770 | msgstr "All links on this page are already bookmarked." | 811 | msgstr "All links on this page are already bookmarked." |
771 | 812 | ||
772 | msgid "heading.autoreload" | 813 | msgid "heading.autoreload" |
773 | msgstr "AUTO-RELOAD" | 814 | msgstr "Auto-Reload" |
774 | 815 | ||
775 | msgid "dlg.autoreload" | 816 | msgid "dlg.autoreload" |
776 | msgstr "Select the auto-reload interval for this tab." | 817 | msgstr "Select the auto-reload interval for this tab." |
@@ -820,11 +861,22 @@ msgstr "Bookmark Link…" | |||
820 | msgid "link.download" | 861 | msgid "link.download" |
821 | msgstr "Download Linked File" | 862 | msgstr "Download Linked File" |
822 | 863 | ||
864 | # Shows where a local file is using the Finder. | ||
865 | msgid "menu.reveal.macos" | ||
866 | msgstr "Show in Finder" | ||
867 | |||
868 | # Shows where a local file is using the File Manager. | ||
869 | msgid "menu.reveal.filemgr" | ||
870 | msgstr "Show in File Manager" | ||
871 | |||
872 | msgid "menu.share" | ||
873 | msgstr "Share" | ||
874 | |||
823 | msgid "link.file.delete" | 875 | msgid "link.file.delete" |
824 | msgstr "Delete File" | 876 | msgstr "Delete File" |
825 | 877 | ||
826 | msgid "heading.file.delete" | 878 | msgid "heading.file.delete" |
827 | msgstr "DELETE FILE" | 879 | msgstr "Delete File" |
828 | 880 | ||
829 | msgid "dlg.file.delete.confirm" | 881 | msgid "dlg.file.delete.confirm" |
830 | msgstr "Are you sure you want to delete this file?" | 882 | msgstr "Are you sure you want to delete this file?" |
@@ -833,7 +885,7 @@ msgid "dlg.file.delete" | |||
833 | msgstr "Delete" | 885 | msgstr "Delete" |
834 | 886 | ||
835 | msgid "heading.openlink" | 887 | msgid "heading.openlink" |
836 | msgstr "OPEN LINK" | 888 | msgstr "Open Link" |
837 | 889 | ||
838 | #, c-format | 890 | #, c-format |
839 | msgid "dlg.openlink.confirm" | 891 | msgid "dlg.openlink.confirm" |
@@ -865,7 +917,7 @@ msgid "dlg.certwarn.domain.expired" | |||
865 | msgstr "The received certificate is expired AND for the wrong domain." | 917 | msgstr "The received certificate is expired AND for the wrong domain." |
866 | 918 | ||
867 | msgid "heading.certimport" | 919 | msgid "heading.certimport" |
868 | msgstr "IMPORT IDENTITY" | 920 | msgstr "Import Identity" |
869 | 921 | ||
870 | msgid "dlg.certimport.help" | 922 | msgid "dlg.certimport.help" |
871 | msgstr "Paste a PEM-encoded certificate and/or private key,\nor drop a .crt/.key file on the window." | 923 | msgstr "Paste a PEM-encoded certificate and/or private key,\nor drop a .crt/.key file on the window." |
@@ -877,10 +929,10 @@ msgid "dlg.certimport.notfound.page" | |||
877 | msgstr "No certificate/key found on the current page." | 929 | msgstr "No certificate/key found on the current page." |
878 | 930 | ||
879 | msgid "heading.certimport.pasted" | 931 | msgid "heading.certimport.pasted" |
880 | msgstr "PASTED FROM CLIPBOARD" | 932 | msgstr "Pasted from Clipboard" |
881 | 933 | ||
882 | msgid "heading.certimport.dropped" | 934 | msgid "heading.certimport.dropped" |
883 | msgstr "DROPPED FILE" | 935 | msgstr "Dropped File" |
884 | 936 | ||
885 | # button in the mobile New Identity dialog | 937 | # button in the mobile New Identity dialog |
886 | msgid "dlg.certimport.pickfile" | 938 | msgid "dlg.certimport.pickfile" |
@@ -902,10 +954,10 @@ msgid "dlg.certimport.nokey" | |||
902 | msgstr "No Private Key" | 954 | msgstr "No Private Key" |
903 | 955 | ||
904 | msgid "link.hint.audio" | 956 | msgid "link.hint.audio" |
905 | msgstr "Play Audio" | 957 | msgstr "Audio" |
906 | 958 | ||
907 | msgid "link.hint.image" | 959 | msgid "link.hint.image" |
908 | msgstr "View Image" | 960 | msgstr "Image" |
909 | 961 | ||
910 | msgid "bookmark.title.blank" | 962 | msgid "bookmark.title.blank" |
911 | msgstr "Blank Page" | 963 | msgstr "Blank Page" |
@@ -970,14 +1022,20 @@ msgid "heading.lookup.other" | |||
970 | msgstr "OTHER" | 1022 | msgstr "OTHER" |
971 | 1023 | ||
972 | msgid "menu.page.upload" | 1024 | msgid "menu.page.upload" |
973 | msgstr "Upload Page with Titan…" | 1025 | msgstr "Upload with Titan…" |
1026 | |||
1027 | msgid "menu.page.upload.edit" | ||
1028 | msgstr "Edit Page with Titan…" | ||
974 | 1029 | ||
975 | msgid "heading.upload" | 1030 | msgid "heading.upload" |
976 | msgstr "UPLOAD WITH TITAN" | 1031 | msgstr "Upload with Titan" |
977 | 1032 | ||
978 | msgid "upload.id" | 1033 | msgid "upload.id" |
979 | msgstr "Identity:" | 1034 | msgstr "Identity:" |
980 | 1035 | ||
1036 | msgid "heading.upload.id" | ||
1037 | msgstr "Authorization" | ||
1038 | |||
981 | msgid "dlg.upload.id.none" | 1039 | msgid "dlg.upload.id.none" |
982 | msgstr "None" | 1040 | msgstr "None" |
983 | 1041 | ||
@@ -987,6 +1045,15 @@ msgstr "Default" | |||
987 | msgid "heading.upload.text" | 1045 | msgid "heading.upload.text" |
988 | msgstr "Text" | 1046 | msgstr "Text" |
989 | 1047 | ||
1048 | msgid "menu.upload.export" | ||
1049 | msgstr "Export Text" | ||
1050 | |||
1051 | msgid "menu.upload.delete" | ||
1052 | msgstr "Delete All" | ||
1053 | |||
1054 | msgid "menu.upload.delete.confirm" | ||
1055 | msgstr "Really Delete All (No Undo)" | ||
1056 | |||
990 | msgid "hint.upload.text" | 1057 | msgid "hint.upload.text" |
991 | msgstr "enter text to upload" | 1058 | msgstr "enter text to upload" |
992 | 1059 | ||
@@ -1008,9 +1075,23 @@ msgstr "MIME type:" | |||
1008 | msgid "upload.token" | 1075 | msgid "upload.token" |
1009 | msgstr "Token:" | 1076 | msgstr "Token:" |
1010 | 1077 | ||
1078 | # Mobile subheading in the Upload dialog. | ||
1079 | msgid "upload.url" | ||
1080 | msgstr "URL" | ||
1081 | |||
1082 | # Mobile subheading: buttons for entering uploaded data. | ||
1083 | msgid "upload.content" | ||
1084 | msgstr "Content" | ||
1085 | |||
1086 | msgid "hint.upload.path" | ||
1087 | msgstr "URL path" | ||
1088 | |||
1011 | msgid "hint.upload.token" | 1089 | msgid "hint.upload.token" |
1012 | msgstr "see server's instructions" | 1090 | msgstr "see server's instructions" |
1013 | 1091 | ||
1092 | msgid "hint.upload.token.long" | ||
1093 | msgstr "token — see server's instructions" | ||
1094 | |||
1014 | msgid "dlg.upload.send" | 1095 | msgid "dlg.upload.send" |
1015 | msgstr "Upload" | 1096 | msgstr "Upload" |
1016 | 1097 | ||
@@ -1018,7 +1099,7 @@ msgid "upload.port" | |||
1018 | msgstr "Port…" | 1099 | msgstr "Port…" |
1019 | 1100 | ||
1020 | msgid "heading.uploadport" | 1101 | msgid "heading.uploadport" |
1021 | msgstr "TITAN UPLOAD PORT" | 1102 | msgstr "Titan Upload Port" |
1022 | 1103 | ||
1023 | msgid "dlg.uploadport.msg" | 1104 | msgid "dlg.uploadport.msg" |
1024 | msgstr "Set the Titan server port to use for this URL. The port is saved in the site-specific configuration." | 1105 | msgstr "Set the Titan server port to use for this URL. The port is saved in the site-specific configuration." |
@@ -1039,7 +1120,7 @@ msgid "dlg.upload.pickfile" | |||
1039 | msgstr "Select File" | 1120 | msgstr "Select File" |
1040 | 1121 | ||
1041 | msgid "heading.translate" | 1122 | msgid "heading.translate" |
1042 | msgstr "TRANSLATE PAGE" | 1123 | msgstr "Translate Page" |
1043 | 1124 | ||
1044 | msgid "dlg.translate.unavail" | 1125 | msgid "dlg.translate.unavail" |
1045 | msgstr "Service Unavailable" | 1126 | msgstr "Service Unavailable" |
@@ -1101,7 +1182,7 @@ msgid "lang.es" | |||
1101 | msgstr "Spanish" | 1182 | msgstr "Spanish" |
1102 | 1183 | ||
1103 | msgid "heading.newident" | 1184 | msgid "heading.newident" |
1104 | msgstr "NEW IDENTITY" | 1185 | msgstr "New Identity" |
1105 | 1186 | ||
1106 | msgid "dlg.newident.rsa.selfsign" | 1187 | msgid "dlg.newident.rsa.selfsign" |
1107 | msgstr "Creating a self-signed 2048-bit RSA certificate." | 1188 | msgstr "Creating a self-signed 2048-bit RSA certificate." |
@@ -1158,13 +1239,13 @@ msgid "dlg.newident.create" | |||
1158 | msgstr "Create Identity" | 1239 | msgstr "Create Identity" |
1159 | 1240 | ||
1160 | msgid "heading.newident.missing" | 1241 | msgid "heading.newident.missing" |
1161 | msgstr "MISSING INFO" | 1242 | msgstr "Missing Info" |
1162 | 1243 | ||
1163 | msgid "dlg.newindent.missing.commonname" | 1244 | msgid "dlg.newindent.missing.commonname" |
1164 | msgstr "A \"Common name\" must be specified." | 1245 | msgstr "A \"Common name\" must be specified." |
1165 | 1246 | ||
1166 | msgid "heading.newident.date.bad" | 1247 | msgid "heading.newident.date.bad" |
1167 | msgstr "INVALID DATE" | 1248 | msgstr "Invalid Date" |
1168 | 1249 | ||
1169 | msgid "dlg.newident.date.past" | 1250 | msgid "dlg.newident.date.past" |
1170 | msgstr "Expiration date must be in the future." | 1251 | msgstr "Expiration date must be in the future." |
@@ -1173,10 +1254,10 @@ msgid "dlg.newident.date.example" | |||
1173 | msgstr "Please check the \"Valid until\" date. Examples:\n• 2030\n• 2025-06-30\n• 2021-12-31 23:59:59" | 1254 | msgstr "Please check the \"Valid until\" date. Examples:\n• 2030\n• 2025-06-30\n• 2021-12-31 23:59:59" |
1174 | 1255 | ||
1175 | msgid "heading.feedcfg" | 1256 | msgid "heading.feedcfg" |
1176 | msgstr "FEED SETTINGS" | 1257 | msgstr "Feed Settings" |
1177 | 1258 | ||
1178 | msgid "heading.subscribe" | 1259 | msgid "heading.subscribe" |
1179 | msgstr "SUBSCRIBE TO PAGE" | 1260 | msgstr "Subscribe to Page" |
1180 | 1261 | ||
1181 | msgid "dlg.feed.title" | 1262 | msgid "dlg.feed.title" |
1182 | msgstr "Title:" | 1263 | msgstr "Title:" |
@@ -1200,10 +1281,10 @@ msgid "dlg.feed.sub" | |||
1200 | msgstr "Subscribe" | 1281 | msgstr "Subscribe" |
1201 | 1282 | ||
1202 | msgid "heading.bookmark.add" | 1283 | msgid "heading.bookmark.add" |
1203 | msgstr "ADD BOOKMARK" | 1284 | msgstr "Add Bookmark" |
1204 | 1285 | ||
1205 | msgid "heading.bookmark.edit" | 1286 | msgid "heading.bookmark.edit" |
1206 | msgstr "EDIT BOOKMARK" | 1287 | msgstr "Edit Bookmark" |
1207 | 1288 | ||
1208 | msgid "dlg.bookmark.save" | 1289 | msgid "dlg.bookmark.save" |
1209 | msgstr "Save Bookmark" | 1290 | msgstr "Save Bookmark" |
@@ -1224,10 +1305,10 @@ msgid "dlg.bookmark.icon" | |||
1224 | msgstr "Icon:" | 1305 | msgstr "Icon:" |
1225 | 1306 | ||
1226 | msgid "heading.bookmark.tags" | 1307 | msgid "heading.bookmark.tags" |
1227 | msgstr "SPECIAL TAGS" | 1308 | msgstr "Special Tags" |
1228 | 1309 | ||
1229 | msgid "heading.addfolder" | 1310 | msgid "heading.addfolder" |
1230 | msgstr "ADD FOLDER" | 1311 | msgstr "Add Folder" |
1231 | 1312 | ||
1232 | msgid "dlg.addfolder.defaulttitle" | 1313 | msgid "dlg.addfolder.defaulttitle" |
1233 | msgstr "New Folder" | 1314 | msgstr "New Folder" |
@@ -1239,14 +1320,14 @@ msgid "dlg.addfolder" | |||
1239 | msgstr "Add Folder" | 1320 | msgstr "Add Folder" |
1240 | 1321 | ||
1241 | msgid "heading.prefs" | 1322 | msgid "heading.prefs" |
1242 | msgstr "PREFERENCES" | 1323 | msgstr "Preferences" |
1243 | 1324 | ||
1244 | # used on mobile | 1325 | # used on mobile |
1245 | msgid "heading.settings" | 1326 | msgid "heading.settings" |
1246 | msgstr "SETTINGS" | 1327 | msgstr "Settings" |
1247 | 1328 | ||
1248 | msgid "heading.prefs.certs" | 1329 | msgid "heading.prefs.certs" |
1249 | msgstr "CERTIFICATES" | 1330 | msgstr "Certificates" |
1250 | 1331 | ||
1251 | # tab button | 1332 | # tab button |
1252 | msgid "heading.prefs.colors" | 1333 | msgid "heading.prefs.colors" |
@@ -1272,25 +1353,25 @@ msgid "heading.prefs.network" | |||
1272 | msgstr "Network" | 1353 | msgstr "Network" |
1273 | 1354 | ||
1274 | msgid "heading.prefs.paragraph" | 1355 | msgid "heading.prefs.paragraph" |
1275 | msgstr "PARAGRAPH" | 1356 | msgstr "Paragraph" |
1276 | 1357 | ||
1277 | msgid "heading.prefs.uitheme" | 1358 | msgid "heading.prefs.uitheme" |
1278 | msgstr "UI COLORS" | 1359 | msgstr "UI Colors" |
1279 | 1360 | ||
1280 | msgid "heading.prefs.pagecontent" | 1361 | msgid "heading.prefs.pagecontent" |
1281 | msgstr "PAGE COLORS" | 1362 | msgstr "Page Colors" |
1282 | 1363 | ||
1283 | msgid "heading.prefs.proxies" | 1364 | msgid "heading.prefs.proxies" |
1284 | msgstr "PROXIES" | 1365 | msgstr "Proxies" |
1285 | 1366 | ||
1286 | msgid "heading.prefs.scrolling" | 1367 | msgid "heading.prefs.scrolling" |
1287 | msgstr "SCROLLING" | 1368 | msgstr "Scrolling" |
1288 | 1369 | ||
1289 | msgid "heading.prefs.sizing" | 1370 | msgid "heading.prefs.sizing" |
1290 | msgstr "SIZING" | 1371 | msgstr "Sizing" |
1291 | 1372 | ||
1292 | msgid "heading.prefs.widelayout" | 1373 | msgid "heading.prefs.widelayout" |
1293 | msgstr "WIDE LAYOUT" | 1374 | msgstr "Wide Layout" |
1294 | 1375 | ||
1295 | # tab button | 1376 | # tab button |
1296 | msgid "heading.prefs.style" | 1377 | msgid "heading.prefs.style" |
@@ -1349,6 +1430,15 @@ msgstr "Load image on scroll:" | |||
1349 | msgid "prefs.hidetoolbarscroll" | 1430 | msgid "prefs.hidetoolbarscroll" |
1350 | msgstr "Hide toolbar on scroll:" | 1431 | msgstr "Hide toolbar on scroll:" |
1351 | 1432 | ||
1433 | msgid "heading.prefs.toolbaractions" | ||
1434 | msgstr "Toolbar Actions" | ||
1435 | |||
1436 | msgid "prefs.toolbaraction1" | ||
1437 | msgstr "Button 1" | ||
1438 | |||
1439 | msgid "prefs.toolbaraction2" | ||
1440 | msgstr "Button 2" | ||
1441 | |||
1352 | msgid "prefs.ostheme" | 1442 | msgid "prefs.ostheme" |
1353 | msgstr "Use system theme:" | 1443 | msgstr "Use system theme:" |
1354 | 1444 | ||
@@ -1388,6 +1478,9 @@ msgstr "UI scale factor:" | |||
1388 | msgid "prefs.customframe" | 1478 | msgid "prefs.customframe" |
1389 | msgstr "Custom window frame:" | 1479 | msgstr "Custom window frame:" |
1390 | 1480 | ||
1481 | msgid "prefs.blink" | ||
1482 | msgstr "Blinking cursor:" | ||
1483 | |||
1391 | msgid "prefs.returnkey" | 1484 | msgid "prefs.returnkey" |
1392 | msgstr "Return key behavior:" | 1485 | msgstr "Return key behavior:" |
1393 | 1486 | ||
@@ -1698,7 +1791,10 @@ msgid "keys.hoverurl" | |||
1698 | msgstr "Toggle show URL on hover" | 1791 | msgstr "Toggle show URL on hover" |
1699 | 1792 | ||
1700 | msgid "keys.upload" | 1793 | msgid "keys.upload" |
1701 | msgstr "Upload page with Titan" | 1794 | msgstr "Upload with Titan" |
1795 | |||
1796 | msgid "keys.upload.edit" | ||
1797 | msgstr "Edit Page with Titan" | ||
1702 | 1798 | ||
1703 | msgid "error.badstatus" | 1799 | msgid "error.badstatus" |
1704 | msgstr "Unknown Status Code" | 1800 | msgstr "Unknown Status Code" |
@@ -1948,7 +2044,7 @@ msgid "fontpack.delete" | |||
1948 | msgstr "Permanently delete \"%s\"" | 2044 | msgstr "Permanently delete \"%s\"" |
1949 | 2045 | ||
1950 | msgid "heading.fontpack.delete" | 2046 | msgid "heading.fontpack.delete" |
1951 | msgstr "DELETE FONTPACK" | 2047 | msgstr "Delete Fontpack" |
1952 | 2048 | ||
1953 | #, c-format | 2049 | #, c-format |
1954 | msgid "dlg.fontpack.delete.confirm" | 2050 | msgid "dlg.fontpack.delete.confirm" |
@@ -1976,7 +2072,7 @@ msgid "truetype.help.installed" | |||
1976 | msgstr "This font is installed in the user fonts directory." | 2072 | msgstr "This font is installed in the user fonts directory." |
1977 | 2073 | ||
1978 | msgid "heading.dismiss.warning" | 2074 | msgid "heading.dismiss.warning" |
1979 | msgstr "DISMISS WARNING?" | 2075 | msgstr "Dismiss Warning?" |
1980 | 2076 | ||
1981 | #, c-format | 2077 | #, c-format |
1982 | msgid "dlg.dismiss.ansi" | 2078 | msgid "dlg.dismiss.ansi" |
@@ -1986,7 +2082,7 @@ msgid "dlg.dismiss.warning" | |||
1986 | msgstr "Dismiss Warning" | 2082 | msgstr "Dismiss Warning" |
1987 | 2083 | ||
1988 | msgid "heading.fontpack.classic" | 2084 | msgid "heading.fontpack.classic" |
1989 | msgstr "DOWNLOAD FONTPACK" | 2085 | msgstr "Download Fontpack" |
1990 | 2086 | ||
1991 | msgid "dlg.fontpack.classic.msg" | 2087 | msgid "dlg.fontpack.classic.msg" |
1992 | 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?" | 2088 | 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?" |
@@ -1,7 +1,7 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
4 | "PO-Revision-Date: 2021-12-08 14:50+0000\n" | 4 | "PO-Revision-Date: 2022-01-16 17:31+0000\n" |
5 | "Last-Translator: Nikolay Korotkiy <sikmir@gmail.com>\n" | 5 | "Last-Translator: Nikolay Korotkiy <sikmir@gmail.com>\n" |
6 | "Language-Team: Esperanto <http://weblate.skyjake.fi/projects/lagrange/ui/eo/>" | 6 | "Language-Team: Esperanto <http://weblate.skyjake.fi/projects/lagrange/ui/eo/>" |
7 | "\n" | 7 | "\n" |
@@ -227,7 +227,7 @@ msgid "feeds.entry.markunread" | |||
227 | msgstr "Marki nelegita" | 227 | msgstr "Marki nelegita" |
228 | 228 | ||
229 | msgid "heading.history.clear" | 229 | msgid "heading.history.clear" |
230 | msgstr "MALPLENIGI HISTORION" | 230 | msgstr "Malplenigi historion" |
231 | 231 | ||
232 | msgid "ident.delete" | 232 | msgid "ident.delete" |
233 | msgstr "Forigi identecon…" | 233 | msgstr "Forigi identecon…" |
@@ -236,7 +236,7 @@ msgid "ident.fingerprint" | |||
236 | msgstr "Kopii fingropremaĵon" | 236 | msgstr "Kopii fingropremaĵon" |
237 | 237 | ||
238 | msgid "heading.ident.use" | 238 | msgid "heading.ident.use" |
239 | msgstr "IDENTECA UZADO" | 239 | msgstr "Identeca uzado" |
240 | 240 | ||
241 | msgid "menu.edit.notes" | 241 | msgid "menu.edit.notes" |
242 | msgstr "Redakti notojn…" | 242 | msgstr "Redakti notojn…" |
@@ -259,10 +259,10 @@ msgid "dlg.input.send" | |||
259 | msgstr "Sendi" | 259 | msgstr "Sendi" |
260 | 260 | ||
261 | msgid "heading.unsub" | 261 | msgid "heading.unsub" |
262 | msgstr "MALABONO" | 262 | msgstr "Malabono" |
263 | 263 | ||
264 | msgid "heading.pageinfo" | 264 | msgid "heading.pageinfo" |
265 | msgstr "PAINFORMOJ" | 265 | msgstr "Painformoj" |
266 | 266 | ||
267 | msgid "dlg.cert.fingerprint" | 267 | msgid "dlg.cert.fingerprint" |
268 | msgstr "Kopii fingropremaĵon" | 268 | msgstr "Kopii fingropremaĵon" |
@@ -301,10 +301,10 @@ msgstr[0] "%d horo" | |||
301 | msgstr[1] "%d horoj" | 301 | msgstr[1] "%d horoj" |
302 | 302 | ||
303 | msgid "heading.openlink" | 303 | msgid "heading.openlink" |
304 | msgstr "MALFERMI LIGILON" | 304 | msgstr "Malfermi ligilon" |
305 | 305 | ||
306 | msgid "heading.certimport" | 306 | msgid "heading.certimport" |
307 | msgstr "IMPORTI IDENTECON" | 307 | msgstr "Importi identecon" |
308 | 308 | ||
309 | msgid "hint.certimport.description" | 309 | msgid "hint.certimport.description" |
310 | msgstr "priskribo" | 310 | msgstr "priskribo" |
@@ -332,7 +332,7 @@ msgid "link.hint.audio" | |||
332 | msgstr "Ludi la sondosieron" | 332 | msgstr "Ludi la sondosieron" |
333 | 333 | ||
334 | msgid "link.hint.image" | 334 | msgid "link.hint.image" |
335 | msgstr "Montri la bildon" | 335 | msgstr "Bildo" |
336 | 336 | ||
337 | msgid "lang.ar" | 337 | msgid "lang.ar" |
338 | msgstr "Araba" | 338 | msgstr "Araba" |
@@ -380,7 +380,7 @@ msgid "dlg.newident.country" | |||
380 | msgstr "Lando:" | 380 | msgstr "Lando:" |
381 | 381 | ||
382 | msgid "heading.newident" | 382 | msgid "heading.newident" |
383 | msgstr "NOVA IDENTECO" | 383 | msgstr "Nova identeco" |
384 | 384 | ||
385 | msgid "dlg.newident.domain" | 385 | msgid "dlg.newident.domain" |
386 | msgstr "Domajno:" | 386 | msgstr "Domajno:" |
@@ -411,7 +411,7 @@ msgid "dlg.bookmark.icon" | |||
411 | msgstr "Bildsimbolo:" | 411 | msgstr "Bildsimbolo:" |
412 | 412 | ||
413 | msgid "heading.prefs" | 413 | msgid "heading.prefs" |
414 | msgstr "AGORDOJ" | 414 | msgstr "Agordoj" |
415 | 415 | ||
416 | msgid "heading.prefs.fonts" | 416 | msgid "heading.prefs.fonts" |
417 | msgstr "Tiparoj" | 417 | msgstr "Tiparoj" |
@@ -421,10 +421,10 @@ msgid "heading.prefs.network" | |||
421 | msgstr "Reto" | 421 | msgstr "Reto" |
422 | 422 | ||
423 | msgid "heading.prefs.paragraph" | 423 | msgid "heading.prefs.paragraph" |
424 | msgstr "ALINEO" | 424 | msgstr "Alineo" |
425 | 425 | ||
426 | msgid "heading.prefs.scrolling" | 426 | msgid "heading.prefs.scrolling" |
427 | msgstr "RULUMADO" | 427 | msgstr "Rulumado" |
428 | 428 | ||
429 | msgid "prefs.theme" | 429 | msgid "prefs.theme" |
430 | msgstr "Temo:" | 430 | msgstr "Temo:" |
@@ -559,7 +559,7 @@ msgid "dlg.newident.temp" | |||
559 | msgstr "Provizora:" | 559 | msgstr "Provizora:" |
560 | 560 | ||
561 | msgid "heading.prefs.certs" | 561 | msgid "heading.prefs.certs" |
562 | msgstr "ATESTILOJ" | 562 | msgstr "Atestiloj" |
563 | 563 | ||
564 | #, c-format | 564 | #, c-format |
565 | msgid "num.minutes" | 565 | msgid "num.minutes" |
@@ -580,7 +580,7 @@ msgid "keys.fullscreen" | |||
580 | msgstr "Baskuligi plenekranan reĝimon" | 580 | msgstr "Baskuligi plenekranan reĝimon" |
581 | 581 | ||
582 | msgid "heading.import.bookmarks" | 582 | msgid "heading.import.bookmarks" |
583 | msgstr "IMPORTI LEGOSIGNOJN" | 583 | msgstr "Importi legosignojn" |
584 | 584 | ||
585 | msgid "lang.sr" | 585 | msgid "lang.sr" |
586 | msgstr "Serba" | 586 | msgstr "Serba" |
@@ -679,7 +679,7 @@ msgid "heading.prefs.interface" | |||
679 | msgstr "Fasado" | 679 | msgstr "Fasado" |
680 | 680 | ||
681 | msgid "heading.prefs.proxies" | 681 | msgid "heading.prefs.proxies" |
682 | msgstr "PROKURILOJ" | 682 | msgstr "Prokuriloj" |
683 | 683 | ||
684 | msgid "gempub.meta.lang" | 684 | msgid "gempub.meta.lang" |
685 | msgstr "Lingvo" | 685 | msgstr "Lingvo" |
@@ -746,3 +746,60 @@ msgstr "Tiparoj" | |||
746 | 746 | ||
747 | msgid "dlg.dismiss.warning" | 747 | msgid "dlg.dismiss.warning" |
748 | msgstr "Ignori averton" | 748 | msgstr "Ignori averton" |
749 | |||
750 | msgid "menu.newfolder" | ||
751 | msgstr "Nova dosierujo…" | ||
752 | |||
753 | # keep this short (3x1 horiz layout) | ||
754 | msgid "menu.selectall" | ||
755 | msgstr "Elekti ĉiujn" | ||
756 | |||
757 | msgid "menu.copyurl" | ||
758 | msgstr "Kopii URL" | ||
759 | |||
760 | #, c-format | ||
761 | msgid "minutes.ago" | ||
762 | msgid_plural "minutes.ago.n" | ||
763 | msgstr[0] "antaŭ %d minuto" | ||
764 | msgstr[1] "antaŭ %d minutoj" | ||
765 | |||
766 | #, c-format | ||
767 | msgid "hours.ago" | ||
768 | msgid_plural "hours.ago.n" | ||
769 | msgstr[0] "antaŭ %d horo" | ||
770 | msgstr[1] "antaŭ %d horoj" | ||
771 | |||
772 | # used for Preferences on mobile | ||
773 | msgid "menu.settings" | ||
774 | msgstr "Agordoj" | ||
775 | |||
776 | # keep this short (3x1 horiz layout) | ||
777 | msgid "menu.delete" | ||
778 | msgstr "Forigi" | ||
779 | |||
780 | msgid "sidebar.action.bookmarks.newfolder" | ||
781 | msgstr "Nova dosierujo" | ||
782 | |||
783 | msgid "sidebar.action.bookmarks.edit" | ||
784 | msgstr "Redakti" | ||
785 | |||
786 | msgid "num.files" | ||
787 | msgid_plural "num.files.n" | ||
788 | msgstr[0] "%u dosiero" | ||
789 | msgstr[1] "%u dosieroj" | ||
790 | |||
791 | msgid "ident.export" | ||
792 | msgstr "Eksporti" | ||
793 | |||
794 | #, c-format | ||
795 | msgid "days.ago" | ||
796 | msgid_plural "days.ago.n" | ||
797 | msgstr[0] "antaŭ %d tago" | ||
798 | msgstr[1] "antaŭ %d tagoj" | ||
799 | |||
800 | # keep this short (3x1 horiz layout) | ||
801 | msgid "menu.undo" | ||
802 | msgstr "Malfari" | ||
803 | |||
804 | msgid "sidebar.action.show" | ||
805 | msgstr "Montri:" | ||
@@ -1,7 +1,7 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
4 | "PO-Revision-Date: 2021-12-07 04:50+0000\n" | 4 | "PO-Revision-Date: 2022-01-18 17:50+0000\n" |
5 | "Last-Translator: Wally Hackenslacker <mastor89@protonmail.com>\n" | 5 | "Last-Translator: Wally Hackenslacker <mastor89@protonmail.com>\n" |
6 | "Language-Team: Spanish <http://weblate.skyjake.fi/projects/lagrange/ui/es/>\n" | 6 | "Language-Team: Spanish <http://weblate.skyjake.fi/projects/lagrange/ui/es/>\n" |
7 | "Language: es\n" | 7 | "Language: es\n" |
@@ -18,13 +18,13 @@ msgid "dlg.newident.rsa.selfsign" | |||
18 | msgstr "Creando un certificado RSA autofirmado de 2048 bits." | 18 | msgstr "Creando un certificado RSA autofirmado de 2048 bits." |
19 | 19 | ||
20 | msgid "heading.subscribe" | 20 | msgid "heading.subscribe" |
21 | msgstr "SUSCRIBIR A PGINA" | 21 | msgstr "Suscribir a Pgina" |
22 | 22 | ||
23 | msgid "dlg.bookmark.save" | 23 | msgid "dlg.bookmark.save" |
24 | msgstr "Guardar Marcador" | 24 | msgstr "Guardar Marcador" |
25 | 25 | ||
26 | msgid "heading.prefs.sizing" | 26 | msgid "heading.prefs.sizing" |
27 | msgstr "TAMAOS" | 27 | msgstr "Tamaos" |
28 | 28 | ||
29 | msgid "prefs.uiscale" | 29 | msgid "prefs.uiscale" |
30 | msgstr "Factor de escalamiento de la Interfaz:" | 30 | msgstr "Factor de escalamiento de la Interfaz:" |
@@ -135,7 +135,7 @@ msgid "dlg.feed.title" | |||
135 | msgstr "Título:" | 135 | msgstr "Título:" |
136 | 136 | ||
137 | msgid "heading.bookmark.add" | 137 | msgid "heading.bookmark.add" |
138 | msgstr "AGREGAR MARCADOR" | 138 | msgstr "Agregar Marcador" |
139 | 139 | ||
140 | # tab button | 140 | # tab button |
141 | msgid "heading.prefs.colors" | 141 | msgid "heading.prefs.colors" |
@@ -479,7 +479,7 @@ msgid "history.clear" | |||
479 | msgstr "Limpiar Historial…" | 479 | msgstr "Limpiar Historial…" |
480 | 480 | ||
481 | msgid "heading.history.clear" | 481 | msgid "heading.history.clear" |
482 | msgstr "LIMPIAR HISTORIAL" | 482 | msgstr "Limpiar Historial" |
483 | 483 | ||
484 | msgid "dlg.history.clear" | 484 | msgid "dlg.history.clear" |
485 | msgstr "Limpiar Historial" | 485 | msgstr "Limpiar Historial" |
@@ -535,7 +535,7 @@ msgid "menu.edit.notes" | |||
535 | msgstr "Editar Notas…" | 535 | msgstr "Editar Notas…" |
536 | 536 | ||
537 | msgid "heading.ident.notes" | 537 | msgid "heading.ident.notes" |
538 | msgstr "NOTAS SOBRE IDENTIDAD" | 538 | msgstr "Notas Sobre Identidades" |
539 | 539 | ||
540 | msgid "ident.fingerprint" | 540 | msgid "ident.fingerprint" |
541 | msgstr "Copiar Huella Digital" | 541 | msgstr "Copiar Huella Digital" |
@@ -544,7 +544,7 @@ msgid "ident.delete" | |||
544 | msgstr "Borrar Identidad…" | 544 | msgstr "Borrar Identidad…" |
545 | 545 | ||
546 | msgid "heading.ident.delete" | 546 | msgid "heading.ident.delete" |
547 | msgstr "BORRAR IDENTIDAD" | 547 | msgstr "Borrar Identidad" |
548 | 548 | ||
549 | msgid "dlg.ident.delete" | 549 | msgid "dlg.ident.delete" |
550 | msgstr "Borrar Identidad y Archivos" | 550 | msgstr "Borrar Identidad y Archivos" |
@@ -559,13 +559,13 @@ msgstr "" | |||
559 | "Vea la %sAyuda%s para más información sobre los certificados TLS de cliente." | 559 | "Vea la %sAyuda%s para más información sobre los certificados TLS de cliente." |
560 | 560 | ||
561 | msgid "heading.unsub" | 561 | msgid "heading.unsub" |
562 | msgstr "ELIMINAR SUSCRIPCIN" | 562 | msgstr "Eliminar Suscripcin" |
563 | 563 | ||
564 | msgid "dlg.unsub" | 564 | msgid "dlg.unsub" |
565 | msgstr "Eliminar suscripción" | 565 | msgstr "Eliminar suscripción" |
566 | 566 | ||
567 | msgid "heading.pageinfo" | 567 | msgid "heading.pageinfo" |
568 | msgstr "INFORMACIN DE PGINA" | 568 | msgstr "Informacin de Pgina" |
569 | 569 | ||
570 | msgid "pageinfo.header.cached" | 570 | msgid "pageinfo.header.cached" |
571 | msgstr "(contenido en caché)" | 571 | msgstr "(contenido en caché)" |
@@ -611,26 +611,26 @@ msgid "dlg.input.send" | |||
611 | msgstr "Enviar" | 611 | msgstr "Enviar" |
612 | 612 | ||
613 | msgid "heading.save" | 613 | msgid "heading.save" |
614 | msgstr "ARCHIVO GUARDADO" | 614 | msgstr "Archivo Guardado" |
615 | 615 | ||
616 | msgid "heading.save.incomplete" | 616 | msgid "heading.save.incomplete" |
617 | msgstr "PGINA INCOMPLETA" | 617 | msgstr "Pgina Incompleta" |
618 | 618 | ||
619 | msgid "dlg.save.size" | 619 | msgid "dlg.save.size" |
620 | msgstr "Tamaño:" | 620 | msgstr "Tamaño:" |
621 | 621 | ||
622 | msgid "heading.save.error" | 622 | msgid "heading.save.error" |
623 | msgstr "ERROR GUARDANDO ARCHIVO" | 623 | msgstr "Error Guardando Archivo" |
624 | 624 | ||
625 | msgid "heading.import.bookmarks" | 625 | msgid "heading.import.bookmarks" |
626 | msgstr "IMPORTAR MARCADORES" | 626 | msgstr "Importar Marcadores" |
627 | 627 | ||
628 | msgid "dlg.import.notnew" | 628 | msgid "dlg.import.notnew" |
629 | msgstr "" | 629 | msgstr "" |
630 | "Todos los enlaces de esta página ya se encuentran guardados en marcadores." | 630 | "Todos los enlaces de esta página ya se encuentran guardados en marcadores." |
631 | 631 | ||
632 | msgid "heading.autoreload" | 632 | msgid "heading.autoreload" |
633 | msgstr "AUTO-RECARGAR" | 633 | msgstr "Auto-Recargar" |
634 | 634 | ||
635 | msgid "link.newtab" | 635 | msgid "link.newtab" |
636 | msgstr "Abrir Enlace en Nueva Pestaña" | 636 | msgstr "Abrir Enlace en Nueva Pestaña" |
@@ -651,7 +651,7 @@ msgid "link.bookmark" | |||
651 | msgstr "Guardar Enlace en Marcadores…" | 651 | msgstr "Guardar Enlace en Marcadores…" |
652 | 652 | ||
653 | msgid "heading.openlink" | 653 | msgid "heading.openlink" |
654 | msgstr "ABRIR ENLACE" | 654 | msgstr "Abrir Enlace" |
655 | 655 | ||
656 | #, c-format | 656 | #, c-format |
657 | msgid "dlg.openlink.confirm" | 657 | msgid "dlg.openlink.confirm" |
@@ -670,7 +670,7 @@ msgid "bookmark.title.blank" | |||
670 | msgstr "Página en Blanco" | 670 | msgstr "Página en Blanco" |
671 | 671 | ||
672 | msgid "heading.translate" | 672 | msgid "heading.translate" |
673 | msgstr "TRADUCIR PGINA" | 673 | msgstr "Traducir Pgina" |
674 | 674 | ||
675 | msgid "dlg.translate.unavail" | 675 | msgid "dlg.translate.unavail" |
676 | msgstr "Servicio no Disponible" | 676 | msgstr "Servicio no Disponible" |
@@ -719,7 +719,7 @@ msgid "dlg.certwarn.domain.expired" | |||
719 | msgstr "El certificado recibido ha expirado Y es para el dominio incorrecto." | 719 | msgstr "El certificado recibido ha expirado Y es para el dominio incorrecto." |
720 | 720 | ||
721 | msgid "heading.certimport" | 721 | msgid "heading.certimport" |
722 | msgstr "IMPORTAR IDENTIDAD" | 722 | msgstr "Importar Identidad" |
723 | 723 | ||
724 | msgid "dlg.certimport.help" | 724 | msgid "dlg.certimport.help" |
725 | msgstr "" | 725 | msgstr "" |
@@ -733,10 +733,10 @@ msgid "dlg.certimport.notfound.page" | |||
733 | msgstr "No se encontró un certificado/clave en la página actual." | 733 | msgstr "No se encontró un certificado/clave en la página actual." |
734 | 734 | ||
735 | msgid "heading.certimport.pasted" | 735 | msgid "heading.certimport.pasted" |
736 | msgstr "PEGADO DESDE EL PORTAPAPELES" | 736 | msgstr "Pegado del Portapapeles" |
737 | 737 | ||
738 | msgid "heading.certimport.dropped" | 738 | msgid "heading.certimport.dropped" |
739 | msgstr "ARCHIVO ARRASTRADO Y SOLTADO" | 739 | msgstr "Archivo Soltado" |
740 | 740 | ||
741 | msgid "dlg.certimport.import" | 741 | msgid "dlg.certimport.import" |
742 | msgstr "Importar" | 742 | msgstr "Importar" |
@@ -754,7 +754,7 @@ msgid "dlg.certimport.nokey" | |||
754 | msgstr "Sin Clave Privada" | 754 | msgstr "Sin Clave Privada" |
755 | 755 | ||
756 | msgid "link.hint.image" | 756 | msgid "link.hint.image" |
757 | msgstr "Ver Imagen" | 757 | msgstr "Imagen" |
758 | 758 | ||
759 | msgid "keys.subscribe" | 759 | msgid "keys.subscribe" |
760 | msgstr "Suscribirse a página" | 760 | msgstr "Suscribirse a página" |
@@ -861,7 +861,7 @@ msgid "ident.temporary" | |||
861 | msgstr "Temporal" | 861 | msgstr "Temporal" |
862 | 862 | ||
863 | msgid "heading.ident.use" | 863 | msgid "heading.ident.use" |
864 | msgstr "USO DE IDENTIDADES" | 864 | msgstr "Uso de Identidades" |
865 | 865 | ||
866 | # %s refers to name of an identity. | 866 | # %s refers to name of an identity. |
867 | #, c-format | 867 | #, c-format |
@@ -911,7 +911,7 @@ msgid "lang.ru" | |||
911 | msgstr "Ruso" | 911 | msgstr "Ruso" |
912 | 912 | ||
913 | msgid "heading.newident" | 913 | msgid "heading.newident" |
914 | msgstr "NUEVA IDENTIDAD" | 914 | msgstr "Nueva Identidad" |
915 | 915 | ||
916 | msgid "dlg.newident.until" | 916 | msgid "dlg.newident.until" |
917 | msgstr "Válido hasta:" | 917 | msgstr "Válido hasta:" |
@@ -944,7 +944,7 @@ msgid "dlg.newident.create" | |||
944 | msgstr "Crear Identidad" | 944 | msgstr "Crear Identidad" |
945 | 945 | ||
946 | msgid "heading.feedcfg" | 946 | msgid "heading.feedcfg" |
947 | msgstr "CONFIGURACIN DE SUSCRIPCIONES" | 947 | msgstr "Configuracin de Suscripciones" |
948 | 948 | ||
949 | msgid "dlg.feed.entrytype" | 949 | msgid "dlg.feed.entrytype" |
950 | msgstr "Tipo de entrada:" | 950 | msgstr "Tipo de entrada:" |
@@ -962,7 +962,7 @@ msgid "dlg.feed.sub" | |||
962 | msgstr "Suscribir" | 962 | msgstr "Suscribir" |
963 | 963 | ||
964 | msgid "heading.bookmark.edit" | 964 | msgid "heading.bookmark.edit" |
965 | msgstr "EDITAR MARCADOR" | 965 | msgstr "Editar Marcador" |
966 | 966 | ||
967 | msgid "dlg.bookmark.title" | 967 | msgid "dlg.bookmark.title" |
968 | msgstr "Título:" | 968 | msgstr "Título:" |
@@ -977,25 +977,25 @@ msgid "dlg.bookmark.icon" | |||
977 | msgstr "Ícono:" | 977 | msgstr "Ícono:" |
978 | 978 | ||
979 | msgid "heading.prefs" | 979 | msgid "heading.prefs" |
980 | msgstr "PREFERENCIAS" | 980 | msgstr "Preferencias" |
981 | 981 | ||
982 | msgid "heading.prefs.certs" | 982 | msgid "heading.prefs.certs" |
983 | msgstr "CERTIFICADOS" | 983 | msgstr "Certificados" |
984 | 984 | ||
985 | msgid "heading.prefs.paragraph" | 985 | msgid "heading.prefs.paragraph" |
986 | msgstr "PRRAFO" | 986 | msgstr "Prrafo" |
987 | 987 | ||
988 | msgid "heading.prefs.pagecontent" | 988 | msgid "heading.prefs.pagecontent" |
989 | msgstr "COLORES DE LA PGINA" | 989 | msgstr "Colores de la Pgina" |
990 | 990 | ||
991 | msgid "heading.prefs.proxies" | 991 | msgid "heading.prefs.proxies" |
992 | msgstr "PROXIES" | 992 | msgstr "Proxies" |
993 | 993 | ||
994 | msgid "heading.prefs.scrolling" | 994 | msgid "heading.prefs.scrolling" |
995 | msgstr "DESPLAZAMIENTO" | 995 | msgstr "Desplazamiento" |
996 | 996 | ||
997 | msgid "heading.prefs.widelayout" | 997 | msgid "heading.prefs.widelayout" |
998 | msgstr "DISPOSICIN AMPLIA" | 998 | msgstr "Disposicin Amplia" |
999 | 999 | ||
1000 | msgid "prefs.smoothscroll" | 1000 | msgid "prefs.smoothscroll" |
1001 | msgstr "Desplazamiento suave:" | 1001 | msgstr "Desplazamiento suave:" |
@@ -1313,7 +1313,7 @@ msgid "prefs.doctheme.name.highcontrast" | |||
1313 | msgstr "Alto Contraste" | 1313 | msgstr "Alto Contraste" |
1314 | 1314 | ||
1315 | msgid "link.hint.audio" | 1315 | msgid "link.hint.audio" |
1316 | msgstr "Reproducir Audio" | 1316 | msgstr "Audio" |
1317 | 1317 | ||
1318 | #, c-format | 1318 | #, c-format |
1319 | msgid "num.minutes" | 1319 | msgid "num.minutes" |
@@ -1358,7 +1358,7 @@ msgstr "En Tema Claro" | |||
1358 | 1358 | ||
1359 | # Link download progress message. | 1359 | # Link download progress message. |
1360 | msgid "doc.fetching" | 1360 | msgid "doc.fetching" |
1361 | msgstr "Recibiendo" | 1361 | msgstr "Cargando" |
1362 | 1362 | ||
1363 | # Inline download status message. | 1363 | # Inline download status message. |
1364 | msgid "media.download.warnclose" | 1364 | msgid "media.download.warnclose" |
@@ -1492,13 +1492,13 @@ msgid "lang.ia" | |||
1492 | msgstr "Interlingua" | 1492 | msgstr "Interlingua" |
1493 | 1493 | ||
1494 | msgid "heading.newident.missing" | 1494 | msgid "heading.newident.missing" |
1495 | msgstr "INFORMACIN FALTANTE" | 1495 | msgstr "Informacin Faltante" |
1496 | 1496 | ||
1497 | msgid "dlg.newindent.missing.commonname" | 1497 | msgid "dlg.newindent.missing.commonname" |
1498 | msgstr "Debe especificar un \"nombre común\"." | 1498 | msgstr "Debe especificar un \"nombre común\"." |
1499 | 1499 | ||
1500 | msgid "heading.newident.date.bad" | 1500 | msgid "heading.newident.date.bad" |
1501 | msgstr "FECHA NO VLIDA" | 1501 | msgstr "Fecha no Vlida" |
1502 | 1502 | ||
1503 | msgid "dlg.newident.date.past" | 1503 | msgid "dlg.newident.date.past" |
1504 | msgstr "La fecha de vencimiento debe ser en el futuro." | 1504 | msgstr "La fecha de vencimiento debe ser en el futuro." |
@@ -1661,7 +1661,7 @@ msgid "upload.port" | |||
1661 | msgstr "Puerto…" | 1661 | msgstr "Puerto…" |
1662 | 1662 | ||
1663 | msgid "heading.uploadport" | 1663 | msgid "heading.uploadport" |
1664 | msgstr "PUERTO DE CARGA POR TITAN" | 1664 | msgstr "Puerto de Carga con Titan" |
1665 | 1665 | ||
1666 | msgid "dlg.uploadport.msg" | 1666 | msgid "dlg.uploadport.msg" |
1667 | msgstr "" | 1667 | msgstr "" |
@@ -1678,7 +1678,7 @@ msgid "prefs.linespacing" | |||
1678 | msgstr "Espaciado de linea:" | 1678 | msgstr "Espaciado de linea:" |
1679 | 1679 | ||
1680 | msgid "keys.upload" | 1680 | msgid "keys.upload" |
1681 | msgstr "Cargar página con Titan" | 1681 | msgstr "Cargar con Titan" |
1682 | 1682 | ||
1683 | msgid "error.certexpired" | 1683 | msgid "error.certexpired" |
1684 | msgstr "Certificado Expirado" | 1684 | msgstr "Certificado Expirado" |
@@ -1698,7 +1698,7 @@ msgid "link.file.delete" | |||
1698 | msgstr "Borrar Archivo" | 1698 | msgstr "Borrar Archivo" |
1699 | 1699 | ||
1700 | msgid "heading.file.delete" | 1700 | msgid "heading.file.delete" |
1701 | msgstr "BORRAR ARCHIVO" | 1701 | msgstr "Borrar Archivo" |
1702 | 1702 | ||
1703 | msgid "dlg.file.delete.confirm" | 1703 | msgid "dlg.file.delete.confirm" |
1704 | msgstr "Está seguro de que quiere borrar este archivo?" | 1704 | msgstr "Está seguro de que quiere borrar este archivo?" |
@@ -1707,10 +1707,10 @@ msgid "dlg.file.delete" | |||
1707 | msgstr "Borrar" | 1707 | msgstr "Borrar" |
1708 | 1708 | ||
1709 | msgid "menu.page.upload" | 1709 | msgid "menu.page.upload" |
1710 | msgstr "Cargar Página con Titan…" | 1710 | msgstr "Cargar con Titan…" |
1711 | 1711 | ||
1712 | msgid "heading.upload" | 1712 | msgid "heading.upload" |
1713 | msgstr "CARGAR PÁGINA CON TITAN" | 1713 | msgstr "Cargar con Titan" |
1714 | 1714 | ||
1715 | msgid "heading.upload.text" | 1715 | msgid "heading.upload.text" |
1716 | msgstr "Texto" | 1716 | msgstr "Texto" |
@@ -1770,7 +1770,7 @@ msgid "menu.openfile" | |||
1770 | msgstr "Abrir Archivo…" | 1770 | msgstr "Abrir Archivo…" |
1771 | 1771 | ||
1772 | msgid "heading.prefs.uitheme" | 1772 | msgid "heading.prefs.uitheme" |
1773 | msgstr "COLORES DE IU" | 1773 | msgstr "Colores de la IU" |
1774 | 1774 | ||
1775 | msgid "media.untitled.image" | 1775 | msgid "media.untitled.image" |
1776 | msgstr "Imagen" | 1776 | msgstr "Imagen" |
@@ -1830,7 +1830,7 @@ msgid "menu.newfolder" | |||
1830 | msgstr "Nueva Carpeta…" | 1830 | msgstr "Nueva Carpeta…" |
1831 | 1831 | ||
1832 | msgid "heading.confirm.bookmarks.delete" | 1832 | msgid "heading.confirm.bookmarks.delete" |
1833 | msgstr "BORRAR MARCADORES" | 1833 | msgstr "Borrar Marcadores" |
1834 | 1834 | ||
1835 | #, c-format | 1835 | #, c-format |
1836 | msgid "dlg.confirm.bookmarks.delete" | 1836 | msgid "dlg.confirm.bookmarks.delete" |
@@ -1849,7 +1849,7 @@ msgid "dlg.upload.id.none" | |||
1849 | msgstr "Ninguno" | 1849 | msgstr "Ninguno" |
1850 | 1850 | ||
1851 | msgid "heading.bookmark.tags" | 1851 | msgid "heading.bookmark.tags" |
1852 | msgstr "ETIQUETAS ESPECIALES" | 1852 | msgstr "Etiquetas Especiales" |
1853 | 1853 | ||
1854 | msgid "dlg.addfolder.prompt" | 1854 | msgid "dlg.addfolder.prompt" |
1855 | msgstr "Nombre de la nueva carpeta:" | 1855 | msgstr "Nombre de la nueva carpeta:" |
@@ -1898,7 +1898,7 @@ msgid "dlg.upload.pickfile" | |||
1898 | msgstr "Escoger Archivo" | 1898 | msgstr "Escoger Archivo" |
1899 | 1899 | ||
1900 | msgid "heading.addfolder" | 1900 | msgid "heading.addfolder" |
1901 | msgstr "AGREGAR CARPETA" | 1901 | msgstr "Agregar Carpeta" |
1902 | 1902 | ||
1903 | msgid "dlg.addfolder.defaulttitle" | 1903 | msgid "dlg.addfolder.defaulttitle" |
1904 | msgstr "Nueva Carpeta" | 1904 | msgstr "Nueva Carpeta" |
@@ -1908,7 +1908,7 @@ msgstr "Agregar Carpeta" | |||
1908 | 1908 | ||
1909 | # used on mobile | 1909 | # used on mobile |
1910 | msgid "heading.settings" | 1910 | msgid "heading.settings" |
1911 | msgstr "CONFIGURACIN" | 1911 | msgstr "Configuracin" |
1912 | 1912 | ||
1913 | msgid "prefs.imagestyle" | 1913 | msgid "prefs.imagestyle" |
1914 | msgstr "Colorear imágenes:" | 1914 | msgstr "Colorear imágenes:" |
@@ -1966,7 +1966,7 @@ msgid "fontpack.export" | |||
1966 | msgstr "Ver plantilla fontpack.ini" | 1966 | msgstr "Ver plantilla fontpack.ini" |
1967 | 1967 | ||
1968 | msgid "heading.fontpack.classic" | 1968 | msgid "heading.fontpack.classic" |
1969 | msgstr "DESCARGAR PAQUETE DE FUENTES" | 1969 | msgstr "Descargar Paquete de Fuentes" |
1970 | 1970 | ||
1971 | msgid "dlg.fontpack.classic.msg" | 1971 | msgid "dlg.fontpack.classic.msg" |
1972 | msgstr "" | 1972 | msgstr "" |
@@ -2041,7 +2041,7 @@ msgid "truetype.help.installed" | |||
2041 | msgstr "Esta fuente está instalada en el directorio de fuentes del usuario." | 2041 | msgstr "Esta fuente está instalada en el directorio de fuentes del usuario." |
2042 | 2042 | ||
2043 | msgid "heading.dismiss.warning" | 2043 | msgid "heading.dismiss.warning" |
2044 | msgstr "DESCARTAR ADVERTENCIA?" | 2044 | msgstr "¿Descartar Advertencia?" |
2045 | 2045 | ||
2046 | #, c-format | 2046 | #, c-format |
2047 | msgid "dlg.dismiss.ansi" | 2047 | msgid "dlg.dismiss.ansi" |
@@ -2125,7 +2125,7 @@ msgid "fontpack.delete" | |||
2125 | msgstr "Borrar \"%s\" permanentemente" | 2125 | msgstr "Borrar \"%s\" permanentemente" |
2126 | 2126 | ||
2127 | msgid "heading.fontpack.delete" | 2127 | msgid "heading.fontpack.delete" |
2128 | msgstr "BORRAR PAQUETE DE FUENTES" | 2128 | msgstr "Borrar Paquete de Fuentes" |
2129 | 2129 | ||
2130 | msgid "lang.uk" | 2130 | msgid "lang.uk" |
2131 | msgstr "Ucraniano" | 2131 | msgstr "Ucraniano" |
@@ -2155,3 +2155,99 @@ msgstr "Hora de 24 Horas" | |||
2155 | # This label should be fairly short so it fits in a button in the sidebar. | 2155 | # This label should be fairly short so it fits in a button in the sidebar. |
2156 | msgid "sidebar.action.feeds.markallread" | 2156 | msgid "sidebar.action.feeds.markallread" |
2157 | msgstr "Leer Todo" | 2157 | msgstr "Leer Todo" |
2158 | |||
2159 | msgid "menu.open.external" | ||
2160 | msgstr "Abrir en Otra App" | ||
2161 | |||
2162 | # Active identity toolbar menu. | ||
2163 | msgid "menu.hide.identities" | ||
2164 | msgstr "Ocultar Identidades" | ||
2165 | |||
2166 | msgid "menu.home" | ||
2167 | msgstr "Ir a la Página Principal" | ||
2168 | |||
2169 | msgid "menu.identities" | ||
2170 | msgstr "Gestionar Identidades" | ||
2171 | |||
2172 | msgid "sidebar.close" | ||
2173 | msgstr "Listo" | ||
2174 | |||
2175 | msgid "sidebar.action.bookmarks.newfolder" | ||
2176 | msgstr "Nueva Carpeta" | ||
2177 | |||
2178 | msgid "sidebar.action.bookmarks.edit" | ||
2179 | msgstr "Editar" | ||
2180 | |||
2181 | msgid "sidebar.action.history.clear" | ||
2182 | msgstr "Limpiar" | ||
2183 | |||
2184 | # The %s represents the name of an identity. | ||
2185 | #, c-format | ||
2186 | msgid "ident.switch" | ||
2187 | msgstr "Usar %s" | ||
2188 | |||
2189 | msgid "sidebar.empty.unread" | ||
2190 | msgstr "Sin Entradas no Leídas" | ||
2191 | |||
2192 | # Paste the line preceding the clicked link into the input prompt. | ||
2193 | msgid "menu.input.precedingline" | ||
2194 | msgstr "Pegar la Linea Precedente" | ||
2195 | |||
2196 | # Shows where a local file is using the Finder. | ||
2197 | msgid "menu.reveal.macos" | ||
2198 | msgstr "Mostrar en el Finder" | ||
2199 | |||
2200 | msgid "menu.share" | ||
2201 | msgstr "Compartir" | ||
2202 | |||
2203 | msgid "menu.page.upload.edit" | ||
2204 | msgstr "Editar Página con Titan…" | ||
2205 | |||
2206 | msgid "heading.upload.id" | ||
2207 | msgstr "Autorización" | ||
2208 | |||
2209 | msgid "menu.upload.export" | ||
2210 | msgstr "Exportar Texto" | ||
2211 | |||
2212 | msgid "menu.upload.delete" | ||
2213 | msgstr "Borrar Todo" | ||
2214 | |||
2215 | msgid "menu.upload.delete.confirm" | ||
2216 | msgstr "De Verdad Borrar Todo (No se Puede Deshacer)" | ||
2217 | |||
2218 | # Mobile subheading in the Upload dialog. | ||
2219 | msgid "upload.url" | ||
2220 | msgstr "URL" | ||
2221 | |||
2222 | # Mobile subheading: buttons for entering uploaded data. | ||
2223 | msgid "upload.content" | ||
2224 | msgstr "Contenido" | ||
2225 | |||
2226 | msgid "hint.upload.path" | ||
2227 | msgstr "Ruta del URL" | ||
2228 | |||
2229 | msgid "hint.upload.token.long" | ||
2230 | msgstr "Token — ver instrucciones del servidor" | ||
2231 | |||
2232 | msgid "heading.prefs.toolbaractions" | ||
2233 | msgstr "Acciones de la Barra de Herramientas" | ||
2234 | |||
2235 | msgid "prefs.toolbaraction1" | ||
2236 | msgstr "Botón 1" | ||
2237 | |||
2238 | msgid "prefs.toolbaraction2" | ||
2239 | msgstr "Botón 2" | ||
2240 | |||
2241 | msgid "prefs.blink" | ||
2242 | msgstr "Cursor parpadeante:" | ||
2243 | |||
2244 | msgid "keys.upload.edit" | ||
2245 | msgstr "Editar Página con Titan" | ||
2246 | |||
2247 | # Menu heading shown when customizing navbar button actions. | ||
2248 | msgid "menu.toolbar.setaction" | ||
2249 | msgstr "Establecer Acción:" | ||
2250 | |||
2251 | # Shows where a local file is using the File Manager. | ||
2252 | msgid "menu.reveal.filemgr" | ||
2253 | msgstr "Mostrar en Administrador de Archivos" | ||
@@ -3,7 +3,7 @@ msgstr "" | |||
3 | "Project-Id-Version: PACKAGE VERSION\n" | 3 | "Project-Id-Version: PACKAGE VERSION\n" |
4 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 4 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
5 | "POT-Creation-Date: 2021-03-23 09:09+0000\n" | 5 | "POT-Creation-Date: 2021-03-23 09:09+0000\n" |
6 | "PO-Revision-Date: 2021-11-30 05:35+0000\n" | 6 | "PO-Revision-Date: 2022-01-17 09:43+0000\n" |
7 | "Last-Translator: Weblate Admin <jaakko.keranen@iki.fi>\n" | 7 | "Last-Translator: Weblate Admin <jaakko.keranen@iki.fi>\n" |
8 | "Language-Team: Finnish <http://weblate.skyjake.fi/projects/lagrange/ui/fi/>\n" | 8 | "Language-Team: Finnish <http://weblate.skyjake.fi/projects/lagrange/ui/fi/>\n" |
9 | "Language: fi\n" | 9 | "Language: fi\n" |
@@ -348,7 +348,7 @@ msgid "history.clear" | |||
348 | msgstr "Tyhjennä historia…" | 348 | msgstr "Tyhjennä historia…" |
349 | 349 | ||
350 | msgid "heading.history.clear" | 350 | msgid "heading.history.clear" |
351 | msgstr "TYHJENN HISTORIA" | 351 | msgstr "Tyhjenn historia" |
352 | 352 | ||
353 | msgid "dlg.confirm.history.clear" | 353 | msgid "dlg.confirm.history.clear" |
354 | msgstr "Haluatko varmasti tyhjentää selaushistorian?" | 354 | msgstr "Haluatko varmasti tyhjentää selaushistorian?" |
@@ -410,13 +410,13 @@ msgid "ident.showuse" | |||
410 | msgstr "Näytä käyttö" | 410 | msgstr "Näytä käyttö" |
411 | 411 | ||
412 | msgid "heading.ident.use" | 412 | msgid "heading.ident.use" |
413 | msgstr "IDENTITEETIN KYTT" | 413 | msgstr "Identiteetin kytt" |
414 | 414 | ||
415 | msgid "menu.edit.notes" | 415 | msgid "menu.edit.notes" |
416 | msgstr "Muokkaa muistiinpanoja…" | 416 | msgstr "Muokkaa muistiinpanoja…" |
417 | 417 | ||
418 | msgid "heading.ident.notes" | 418 | msgid "heading.ident.notes" |
419 | msgstr "IDENTITEETIN MUISTIINPANOT" | 419 | msgstr "Identiteetin muistiinpanot" |
420 | 420 | ||
421 | msgid "dlg.ident.notes" | 421 | msgid "dlg.ident.notes" |
422 | msgstr "Muistiinpanot liittyen identiteettiin %s:" | 422 | msgstr "Muistiinpanot liittyen identiteettiin %s:" |
@@ -428,7 +428,7 @@ msgid "ident.delete" | |||
428 | msgstr "Poista identiteetti…" | 428 | msgstr "Poista identiteetti…" |
429 | 429 | ||
430 | msgid "heading.ident.delete" | 430 | msgid "heading.ident.delete" |
431 | msgstr "POISTA IDENTITEETTI" | 431 | msgstr "Poista identiteetti" |
432 | 432 | ||
433 | msgid "dlg.confirm.ident.delete" | 433 | msgid "dlg.confirm.ident.delete" |
434 | msgstr "" | 434 | msgstr "" |
@@ -445,7 +445,7 @@ msgid "ident.gotohelp" | |||
445 | msgstr "Katso %sOhjeesta%s lisätietoja TLS-asiakassertifikaateista." | 445 | msgstr "Katso %sOhjeesta%s lisätietoja TLS-asiakassertifikaateista." |
446 | 446 | ||
447 | msgid "heading.unsub" | 447 | msgid "heading.unsub" |
448 | msgstr "POISTA TILAUS" | 448 | msgstr "Poista tilaus" |
449 | 449 | ||
450 | msgid "dlg.confirm.unsub" | 450 | msgid "dlg.confirm.unsub" |
451 | msgstr "Haluatko varmasti poistaa syötteen \"%s\" tilauksen?" | 451 | msgstr "Haluatko varmasti poistaa syötteen \"%s\" tilauksen?" |
@@ -458,7 +458,7 @@ msgstr "" | |||
458 | "Voit tallentaa sen latauskansioon: paina %s tai valitse \"%s\" valikosta." | 458 | "Voit tallentaa sen latauskansioon: paina %s tai valitse \"%s\" valikosta." |
459 | 459 | ||
460 | msgid "heading.pageinfo" | 460 | msgid "heading.pageinfo" |
461 | msgstr "SIVUN TIEDOT" | 461 | msgstr "Sivun tiedot" |
462 | 462 | ||
463 | msgid "pageinfo.header.cached" | 463 | msgid "pageinfo.header.cached" |
464 | msgstr "(sisältö välimuistista)" | 464 | msgstr "(sisältö välimuistista)" |
@@ -503,10 +503,10 @@ msgid "dlg.input.send" | |||
503 | msgstr "Lähetä" | 503 | msgstr "Lähetä" |
504 | 504 | ||
505 | msgid "heading.save" | 505 | msgid "heading.save" |
506 | msgstr "TIEDOSTO TALLENNETTU" | 506 | msgstr "Tiedosto tallennettu" |
507 | 507 | ||
508 | msgid "heading.save.incomplete" | 508 | msgid "heading.save.incomplete" |
509 | msgstr "SIVUN LATAUS KESKEN" | 509 | msgstr "Sivun lataus kesken" |
510 | 510 | ||
511 | msgid "dlg.save.incomplete" | 511 | msgid "dlg.save.incomplete" |
512 | msgstr "Sivun sisällön lataus ei ole vielä päättynyt." | 512 | msgstr "Sivun sisällön lataus ei ole vielä päättynyt." |
@@ -515,10 +515,10 @@ msgid "dlg.save.size" | |||
515 | msgstr "Koko:" | 515 | msgstr "Koko:" |
516 | 516 | ||
517 | msgid "heading.save.error" | 517 | msgid "heading.save.error" |
518 | msgstr "VIRHE TALLENTAESSA" | 518 | msgstr "Virhe tallentaessa" |
519 | 519 | ||
520 | msgid "heading.import.bookmarks" | 520 | msgid "heading.import.bookmarks" |
521 | msgstr "TUO KIRJANMERKKEJ" | 521 | msgstr "Tuo kirjanmerkkej" |
522 | 522 | ||
523 | msgid "dlg.import.found" | 523 | msgid "dlg.import.found" |
524 | msgid_plural "dlg.import.found.n" | 524 | msgid_plural "dlg.import.found.n" |
@@ -534,7 +534,7 @@ msgid "dlg.import.notnew" | |||
534 | msgstr "Kaikki sivun linkit on jo kirjanmerkkeinä." | 534 | msgstr "Kaikki sivun linkit on jo kirjanmerkkeinä." |
535 | 535 | ||
536 | msgid "heading.autoreload" | 536 | msgid "heading.autoreload" |
537 | msgstr "AUTOMAATTINEN LATAUS" | 537 | msgstr "Automaattinen lataus" |
538 | 538 | ||
539 | msgid "dlg.autoreload" | 539 | msgid "dlg.autoreload" |
540 | msgstr "Valitse välilehden uudelleenlataustiheys." | 540 | msgstr "Valitse välilehden uudelleenlataustiheys." |
@@ -561,7 +561,7 @@ msgid "link.download" | |||
561 | msgstr "Lataa kohde tiedostoon" | 561 | msgstr "Lataa kohde tiedostoon" |
562 | 562 | ||
563 | msgid "heading.openlink" | 563 | msgid "heading.openlink" |
564 | msgstr "AVAA LINKKI" | 564 | msgstr "Avaa linkki" |
565 | 565 | ||
566 | msgid "dlg.openlink.confirm" | 566 | msgid "dlg.openlink.confirm" |
567 | msgstr "" | 567 | msgstr "" |
@@ -604,16 +604,16 @@ msgstr "" | |||
604 | "verkkotunnukselle." | 604 | "verkkotunnukselle." |
605 | 605 | ||
606 | msgid "link.hint.audio" | 606 | msgid "link.hint.audio" |
607 | msgstr "Toista änitiedosto" | 607 | msgstr "äni" |
608 | 608 | ||
609 | msgid "link.hint.image" | 609 | msgid "link.hint.image" |
610 | msgstr "Katso kuva" | 610 | msgstr "Kuva" |
611 | 611 | ||
612 | msgid "bookmark.title.blank" | 612 | msgid "bookmark.title.blank" |
613 | msgstr "Tyhjä sivu" | 613 | msgstr "Tyhjä sivu" |
614 | 614 | ||
615 | msgid "heading.translate" | 615 | msgid "heading.translate" |
616 | msgstr "KNN SIVU" | 616 | msgstr "Knn sivu" |
617 | 617 | ||
618 | msgid "dlg.translate.unavail" | 618 | msgid "dlg.translate.unavail" |
619 | msgstr "Palvelu ei ole toiminnassa" | 619 | msgstr "Palvelu ei ole toiminnassa" |
@@ -664,7 +664,7 @@ msgid "lang.es" | |||
664 | msgstr "Espanja" | 664 | msgstr "Espanja" |
665 | 665 | ||
666 | msgid "heading.newident" | 666 | msgid "heading.newident" |
667 | msgstr "UUSI IDENTITEETTI" | 667 | msgstr "Uusi identiteetti" |
668 | 668 | ||
669 | msgid "dlg.newident.rsa.selfsign" | 669 | msgid "dlg.newident.rsa.selfsign" |
670 | msgstr "Luodaan itse allekirjoitettu 2048-bittinen RSA-sertifikaatti." | 670 | msgstr "Luodaan itse allekirjoitettu 2048-bittinen RSA-sertifikaatti." |
@@ -706,10 +706,10 @@ msgid "dlg.newident.create" | |||
706 | msgstr "Luo identiteetti" | 706 | msgstr "Luo identiteetti" |
707 | 707 | ||
708 | msgid "heading.feedcfg" | 708 | msgid "heading.feedcfg" |
709 | msgstr "SYTTEEN ASETUKSET" | 709 | msgstr "Sytteen asetukset" |
710 | 710 | ||
711 | msgid "heading.subscribe" | 711 | msgid "heading.subscribe" |
712 | msgstr "TILAA SIVU SYTTEEN" | 712 | msgstr "Tilaa sivu sytteen" |
713 | 713 | ||
714 | msgid "dlg.feed.title" | 714 | msgid "dlg.feed.title" |
715 | msgstr "Otsikko:" | 715 | msgstr "Otsikko:" |
@@ -730,10 +730,10 @@ msgid "dlg.feed.sub" | |||
730 | msgstr "Tilaa" | 730 | msgstr "Tilaa" |
731 | 731 | ||
732 | msgid "heading.bookmark.add" | 732 | msgid "heading.bookmark.add" |
733 | msgstr "LIS KIRJANMERKKI" | 733 | msgstr "Lis kirjanmerkki" |
734 | 734 | ||
735 | msgid "heading.bookmark.edit" | 735 | msgid "heading.bookmark.edit" |
736 | msgstr "MUOKKAA KIRJANMERKKI" | 736 | msgstr "Muokkaa kirjanmerkki" |
737 | 737 | ||
738 | msgid "dlg.bookmark.save" | 738 | msgid "dlg.bookmark.save" |
739 | msgstr "Talleta kirjanmerkki" | 739 | msgstr "Talleta kirjanmerkki" |
@@ -751,10 +751,10 @@ msgid "dlg.bookmark.icon" | |||
751 | msgstr "Kuvake:" | 751 | msgstr "Kuvake:" |
752 | 752 | ||
753 | msgid "heading.prefs" | 753 | msgid "heading.prefs" |
754 | msgstr "ASETUKSET" | 754 | msgstr "Asetukset" |
755 | 755 | ||
756 | msgid "heading.prefs.certs" | 756 | msgid "heading.prefs.certs" |
757 | msgstr "SERTIFIKAATIT" | 757 | msgstr "Sertifikaatit" |
758 | 758 | ||
759 | msgid "heading.prefs.colors" | 759 | msgid "heading.prefs.colors" |
760 | msgstr "Värit" | 760 | msgstr "Värit" |
@@ -775,22 +775,22 @@ msgid "heading.prefs.network" | |||
775 | msgstr "Verkko" | 775 | msgstr "Verkko" |
776 | 776 | ||
777 | msgid "heading.prefs.paragraph" | 777 | msgid "heading.prefs.paragraph" |
778 | msgstr "KAPPALE" | 778 | msgstr "Kappale" |
779 | 779 | ||
780 | msgid "heading.prefs.pagecontent" | 780 | msgid "heading.prefs.pagecontent" |
781 | msgstr "SIVUN VRIT" | 781 | msgstr "Sivun vrit" |
782 | 782 | ||
783 | msgid "heading.prefs.proxies" | 783 | msgid "heading.prefs.proxies" |
784 | msgstr "VLIPALVELIMET" | 784 | msgstr "Vlipalvelimet" |
785 | 785 | ||
786 | msgid "heading.prefs.scrolling" | 786 | msgid "heading.prefs.scrolling" |
787 | msgstr "VIERITYS" | 787 | msgstr "Vieritys" |
788 | 788 | ||
789 | msgid "heading.prefs.sizing" | 789 | msgid "heading.prefs.sizing" |
790 | msgstr "KOKO" | 790 | msgstr "Koko" |
791 | 791 | ||
792 | msgid "heading.prefs.widelayout" | 792 | msgid "heading.prefs.widelayout" |
793 | msgstr "LEVE ULKOASU" | 793 | msgstr "Leve ulkoasu" |
794 | 794 | ||
795 | msgid "heading.prefs.style" | 795 | msgid "heading.prefs.style" |
796 | msgstr "Tyyli" | 796 | msgstr "Tyyli" |
@@ -1042,7 +1042,7 @@ msgid "keys.hoverurl" | |||
1042 | msgstr "Näytä URL kursorin alla" | 1042 | msgstr "Näytä URL kursorin alla" |
1043 | 1043 | ||
1044 | msgid "heading.certimport" | 1044 | msgid "heading.certimport" |
1045 | msgstr "TUO IDENTITEETTI" | 1045 | msgstr "Tuo identiteetti" |
1046 | 1046 | ||
1047 | msgid "dlg.certimport.help" | 1047 | msgid "dlg.certimport.help" |
1048 | msgstr "" | 1048 | msgstr "" |
@@ -1059,10 +1059,10 @@ msgid "dlg.certimport.notfound.page" | |||
1059 | msgstr "Sivulta ei löytynyt sertifikaattia tai avainta." | 1059 | msgstr "Sivulta ei löytynyt sertifikaattia tai avainta." |
1060 | 1060 | ||
1061 | msgid "heading.certimport.pasted" | 1061 | msgid "heading.certimport.pasted" |
1062 | msgstr "LIIMATTU LEIKEPYDLT" | 1062 | msgstr "Liimattu leikepydlt" |
1063 | 1063 | ||
1064 | msgid "heading.certimport.dropped" | 1064 | msgid "heading.certimport.dropped" |
1065 | msgstr "PUDOTETTU TIEDOSTO" | 1065 | msgstr "Pudotettu tiedosto" |
1066 | 1066 | ||
1067 | msgid "dlg.certimport.import" | 1067 | msgid "dlg.certimport.import" |
1068 | msgstr "Tuo" | 1068 | msgstr "Tuo" |
@@ -1508,13 +1508,13 @@ msgid "lang.tok" | |||
1508 | msgstr "Toki Pona" | 1508 | msgstr "Toki Pona" |
1509 | 1509 | ||
1510 | msgid "heading.newident.missing" | 1510 | msgid "heading.newident.missing" |
1511 | msgstr "PUUTTUVA TIETO" | 1511 | msgstr "Puuttuva tieto" |
1512 | 1512 | ||
1513 | msgid "dlg.newindent.missing.commonname" | 1513 | msgid "dlg.newindent.missing.commonname" |
1514 | msgstr "\"Yleisnimi\" on pakollinen tieto." | 1514 | msgstr "\"Yleisnimi\" on pakollinen tieto." |
1515 | 1515 | ||
1516 | msgid "heading.newident.date.bad" | 1516 | msgid "heading.newident.date.bad" |
1517 | msgstr "VIRHEELLINEN PIVMR" | 1517 | msgstr "Virheellinen pivmr" |
1518 | 1518 | ||
1519 | msgid "dlg.newident.date.past" | 1519 | msgid "dlg.newident.date.past" |
1520 | msgstr "Voimassaolon täytyy päättyä tulevaisuudessa." | 1520 | msgstr "Voimassaolon täytyy päättyä tulevaisuudessa." |
@@ -1657,7 +1657,7 @@ msgid "link.file.delete" | |||
1657 | msgstr "Poista tiedosto" | 1657 | msgstr "Poista tiedosto" |
1658 | 1658 | ||
1659 | msgid "heading.file.delete" | 1659 | msgid "heading.file.delete" |
1660 | msgstr "POISTA TIEDOSTO" | 1660 | msgstr "Poista tiedosto" |
1661 | 1661 | ||
1662 | msgid "dlg.file.delete.confirm" | 1662 | msgid "dlg.file.delete.confirm" |
1663 | msgstr "Oletko varma, että haluat poistaa tämän tiedoston?" | 1663 | msgstr "Oletko varma, että haluat poistaa tämän tiedoston?" |
@@ -1666,7 +1666,7 @@ msgid "dlg.file.delete" | |||
1666 | msgstr "Poista" | 1666 | msgstr "Poista" |
1667 | 1667 | ||
1668 | msgid "heading.prefs.uitheme" | 1668 | msgid "heading.prefs.uitheme" |
1669 | msgstr "KYTTLIITTYMN VRIT" | 1669 | msgstr "Kyttliittymn vrit" |
1670 | 1670 | ||
1671 | msgid "prefs.scrollspeed.keyboard" | 1671 | msgid "prefs.scrollspeed.keyboard" |
1672 | msgstr "Näppäimistön nopeus:" | 1672 | msgstr "Näppäimistön nopeus:" |
@@ -1690,10 +1690,10 @@ msgid "menu.unexpire" | |||
1690 | msgstr "Jatka lataamista vanhentumisesta huolimatta" | 1690 | msgstr "Jatka lataamista vanhentumisesta huolimatta" |
1691 | 1691 | ||
1692 | msgid "menu.page.upload" | 1692 | msgid "menu.page.upload" |
1693 | msgstr "Lähetä sivu Titanilla…" | 1693 | msgstr "Lähetä Titanilla…" |
1694 | 1694 | ||
1695 | msgid "heading.upload" | 1695 | msgid "heading.upload" |
1696 | msgstr "TITAN-LHETYS" | 1696 | msgstr "Titan-lhetys" |
1697 | 1697 | ||
1698 | msgid "heading.upload.text" | 1698 | msgid "heading.upload.text" |
1699 | msgstr "Teksti" | 1699 | msgstr "Teksti" |
@@ -1735,7 +1735,7 @@ msgid "prefs.returnkey" | |||
1735 | msgstr "Palautusnäppäin:" | 1735 | msgstr "Palautusnäppäin:" |
1736 | 1736 | ||
1737 | msgid "keys.upload" | 1737 | msgid "keys.upload" |
1738 | msgstr "Lähetä sivu Titanilla" | 1738 | msgstr "Lähetä Titanilla" |
1739 | 1739 | ||
1740 | msgid "error.certexpired" | 1740 | msgid "error.certexpired" |
1741 | msgstr "Vanhentunut sertifikaatti" | 1741 | msgstr "Vanhentunut sertifikaatti" |
@@ -1749,7 +1749,7 @@ msgid "upload.port" | |||
1749 | msgstr "Portti…" | 1749 | msgstr "Portti…" |
1750 | 1750 | ||
1751 | msgid "heading.uploadport" | 1751 | msgid "heading.uploadport" |
1752 | msgstr "TITAN-LHETYSPORTTI" | 1752 | msgstr "Titan-lhetysportti" |
1753 | 1753 | ||
1754 | msgid "dlg.uploadport.msg" | 1754 | msgid "dlg.uploadport.msg" |
1755 | msgstr "" | 1755 | msgstr "" |
@@ -1780,7 +1780,7 @@ msgid "menu.sort.alpha" | |||
1780 | msgstr "Järjestä aakkosjärjestykseen" | 1780 | msgstr "Järjestä aakkosjärjestykseen" |
1781 | 1781 | ||
1782 | msgid "heading.confirm.bookmarks.delete" | 1782 | msgid "heading.confirm.bookmarks.delete" |
1783 | msgstr "TUHOA KIRJANMERKIT" | 1783 | msgstr "Tuhoa kirjanmerkit" |
1784 | 1784 | ||
1785 | #, c-format | 1785 | #, c-format |
1786 | msgid "dlg.confirm.bookmarks.delete" | 1786 | msgid "dlg.confirm.bookmarks.delete" |
@@ -1803,7 +1803,7 @@ msgstr "Lähetä tekstiä" | |||
1803 | 1803 | ||
1804 | # used on mobile | 1804 | # used on mobile |
1805 | msgid "heading.settings" | 1805 | msgid "heading.settings" |
1806 | msgstr "ASETUKSET" | 1806 | msgstr "Asetukset" |
1807 | 1807 | ||
1808 | msgid "prefs.imagestyle" | 1808 | msgid "prefs.imagestyle" |
1809 | msgstr "Kuvien väritys:" | 1809 | msgstr "Kuvien väritys:" |
@@ -1862,10 +1862,10 @@ msgid "dlg.upload.pickfile" | |||
1862 | msgstr "Valitse tiedosto" | 1862 | msgstr "Valitse tiedosto" |
1863 | 1863 | ||
1864 | msgid "heading.bookmark.tags" | 1864 | msgid "heading.bookmark.tags" |
1865 | msgstr "ERIKOISAVAINSANAT" | 1865 | msgstr "Erikoisavainsanat" |
1866 | 1866 | ||
1867 | msgid "heading.addfolder" | 1867 | msgid "heading.addfolder" |
1868 | msgstr "LIS KANSIO" | 1868 | msgstr "Lis kansio" |
1869 | 1869 | ||
1870 | msgid "dlg.addfolder.defaulttitle" | 1870 | msgid "dlg.addfolder.defaulttitle" |
1871 | msgstr "Uusi kansio" | 1871 | msgstr "Uusi kansio" |
@@ -2074,7 +2074,7 @@ msgid "fontpack.delete" | |||
2074 | msgstr "Poista ja tuhoa \"%s\"" | 2074 | msgstr "Poista ja tuhoa \"%s\"" |
2075 | 2075 | ||
2076 | msgid "heading.fontpack.delete" | 2076 | msgid "heading.fontpack.delete" |
2077 | msgstr "POISTA FONTTIPAKETTI" | 2077 | msgstr "Poista fonttipaketti" |
2078 | 2078 | ||
2079 | msgid "dlg.fontpack.delete" | 2079 | msgid "dlg.fontpack.delete" |
2080 | msgstr "Tuhoa fonttipaketti" | 2080 | msgstr "Tuhoa fonttipaketti" |
@@ -2092,7 +2092,7 @@ msgid "truetype.help.installed" | |||
2092 | msgstr "Tämä fontti on asennettu käyttäjän fonttihakemistoon." | 2092 | msgstr "Tämä fontti on asennettu käyttäjän fonttihakemistoon." |
2093 | 2093 | ||
2094 | msgid "heading.dismiss.warning" | 2094 | msgid "heading.dismiss.warning" |
2095 | msgstr "POISTA VAROITUS?" | 2095 | msgstr "Poista varoitus?" |
2096 | 2096 | ||
2097 | #, c-format | 2097 | #, c-format |
2098 | msgid "dlg.dismiss.ansi" | 2098 | msgid "dlg.dismiss.ansi" |
@@ -2105,7 +2105,7 @@ msgid "dlg.fontpack.classic" | |||
2105 | msgstr "Lataa fonttipaketti (25 Mt)" | 2105 | msgstr "Lataa fonttipaketti (25 Mt)" |
2106 | 2106 | ||
2107 | msgid "heading.fontpack.classic" | 2107 | msgid "heading.fontpack.classic" |
2108 | msgstr "LATAA FONTTIPAKETTI" | 2108 | msgstr "Lataa fonttipaketti" |
2109 | 2109 | ||
2110 | msgid "dlg.fontpack.classic.msg" | 2110 | msgid "dlg.fontpack.classic.msg" |
2111 | msgstr "" | 2111 | msgstr "" |
@@ -2140,3 +2140,99 @@ msgstr "24 tunnin kellonajat" | |||
2140 | # This label should be fairly short so it fits in a button in the sidebar. | 2140 | # This label should be fairly short so it fits in a button in the sidebar. |
2141 | msgid "sidebar.action.feeds.markallread" | 2141 | msgid "sidebar.action.feeds.markallread" |
2142 | msgstr "Lue kaikki" | 2142 | msgstr "Lue kaikki" |
2143 | |||
2144 | msgid "menu.page.upload.edit" | ||
2145 | msgstr "Muokkaa sivua Titanilla…" | ||
2146 | |||
2147 | msgid "heading.upload.id" | ||
2148 | msgstr "Valtuudet" | ||
2149 | |||
2150 | msgid "menu.upload.export" | ||
2151 | msgstr "Vie teksti" | ||
2152 | |||
2153 | msgid "menu.upload.delete" | ||
2154 | msgstr "Poista kaikki" | ||
2155 | |||
2156 | # Mobile subheading in the Upload dialog. | ||
2157 | msgid "upload.url" | ||
2158 | msgstr "URL" | ||
2159 | |||
2160 | # Mobile subheading: buttons for entering uploaded data. | ||
2161 | msgid "upload.content" | ||
2162 | msgstr "Sisältö" | ||
2163 | |||
2164 | msgid "hint.upload.path" | ||
2165 | msgstr "Polku" | ||
2166 | |||
2167 | msgid "hint.upload.token.long" | ||
2168 | msgstr "avainsana — katso palvelimen ohjeet" | ||
2169 | |||
2170 | msgid "prefs.toolbaraction1" | ||
2171 | msgstr "Painike 1" | ||
2172 | |||
2173 | msgid "prefs.toolbaraction2" | ||
2174 | msgstr "Painike 2" | ||
2175 | |||
2176 | msgid "keys.upload.edit" | ||
2177 | msgstr "Muokkaa sivua Titanilla" | ||
2178 | |||
2179 | # The %s represents the name of an identity. | ||
2180 | #, c-format | ||
2181 | msgid "ident.switch" | ||
2182 | msgstr "Ota käyttöön %s" | ||
2183 | |||
2184 | msgid "menu.upload.delete.confirm" | ||
2185 | msgstr "Poista kaikki (ei voi peruuttaa)" | ||
2186 | |||
2187 | msgid "heading.prefs.toolbaractions" | ||
2188 | msgstr "Työkalupalkin toiminnot" | ||
2189 | |||
2190 | msgid "prefs.blink" | ||
2191 | msgstr "Vilkkuva kursori:" | ||
2192 | |||
2193 | msgid "menu.open.external" | ||
2194 | msgstr "Avaa toisessa sovelluksessa" | ||
2195 | |||
2196 | # Active identity toolbar menu. | ||
2197 | msgid "menu.hide.identities" | ||
2198 | msgstr "Piilota identiteetit" | ||
2199 | |||
2200 | msgid "menu.home" | ||
2201 | msgstr "Mene kotiin" | ||
2202 | |||
2203 | msgid "menu.identities" | ||
2204 | msgstr "Hallinnoi identiteettejä" | ||
2205 | |||
2206 | msgid "sidebar.close" | ||
2207 | msgstr "Valmis" | ||
2208 | |||
2209 | msgid "sidebar.action.bookmarks.newfolder" | ||
2210 | msgstr "Uusi kansio" | ||
2211 | |||
2212 | msgid "sidebar.action.bookmarks.edit" | ||
2213 | msgstr "Muokkaa" | ||
2214 | |||
2215 | msgid "sidebar.action.history.clear" | ||
2216 | msgstr "Tyhjennä" | ||
2217 | |||
2218 | msgid "sidebar.empty.unread" | ||
2219 | msgstr "Kaikki luettu" | ||
2220 | |||
2221 | # Paste the line preceding the clicked link into the input prompt. | ||
2222 | msgid "menu.input.precedingline" | ||
2223 | msgstr "Liimaa edeltävä rivi" | ||
2224 | |||
2225 | # Shows where a local file is using the Finder. | ||
2226 | msgid "menu.reveal.macos" | ||
2227 | msgstr "Näytä Finderissa" | ||
2228 | |||
2229 | msgid "menu.share" | ||
2230 | msgstr "Jaa" | ||
2231 | |||
2232 | # Shows where a local file is using the File Manager. | ||
2233 | msgid "menu.reveal.filemgr" | ||
2234 | msgstr "Näytä tiedostonhallinnassa" | ||
2235 | |||
2236 | # Menu heading shown when customizing navbar button actions. | ||
2237 | msgid "menu.toolbar.setaction" | ||
2238 | msgstr "Aseta toiminto:" | ||
@@ -1,7 +1,7 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
4 | "PO-Revision-Date: 2021-12-19 06:50+0000\n" | 4 | "PO-Revision-Date: 2022-01-20 06:20+0000\n" |
5 | "Last-Translator: Xosé M. <correoxm@disroot.org>\n" | 5 | "Last-Translator: Xosé M. <correoxm@disroot.org>\n" |
6 | "Language-Team: Galician <http://weblate.skyjake.fi/projects/lagrange/ui/gl/>" | 6 | "Language-Team: Galician <http://weblate.skyjake.fi/projects/lagrange/ui/gl/>" |
7 | "\n" | 7 | "\n" |
@@ -122,13 +122,13 @@ msgstr "" | |||
122 | "• 2021-12-31 23:59:59" | 122 | "• 2021-12-31 23:59:59" |
123 | 123 | ||
124 | msgid "heading.prefs.pagecontent" | 124 | msgid "heading.prefs.pagecontent" |
125 | msgstr "CORES DA PXINA" | 125 | msgstr "Cores da pxina" |
126 | 126 | ||
127 | msgid "heading.prefs.sizing" | 127 | msgid "heading.prefs.sizing" |
128 | msgstr "TAMAO" | 128 | msgstr "Tamao" |
129 | 129 | ||
130 | msgid "heading.prefs.widelayout" | 130 | msgid "heading.prefs.widelayout" |
131 | msgstr "ANCHO DA DISPOSICIN" | 131 | msgstr "Ancho da disposicin" |
132 | 132 | ||
133 | msgid "prefs.collapsepreonload" | 133 | msgid "prefs.collapsepreonload" |
134 | msgstr "Pregar texto preformatado:" | 134 | msgstr "Pregar texto preformatado:" |
@@ -572,7 +572,7 @@ msgid "history.clear" | |||
572 | msgstr "Limpar historial…" | 572 | msgstr "Limpar historial…" |
573 | 573 | ||
574 | msgid "heading.history.clear" | 574 | msgid "heading.history.clear" |
575 | msgstr "LIMPAR HISTORIAL" | 575 | msgstr "Limpar historial" |
576 | 576 | ||
577 | msgid "dlg.history.clear" | 577 | msgid "dlg.history.clear" |
578 | msgstr "Limpar historial" | 578 | msgstr "Limpar historial" |
@@ -631,13 +631,13 @@ msgid "ident.export" | |||
631 | msgstr "Exportar" | 631 | msgstr "Exportar" |
632 | 632 | ||
633 | msgid "heading.ident.use" | 633 | msgid "heading.ident.use" |
634 | msgstr "USO DA IDENTIDADE" | 634 | msgstr "Uso de identidade" |
635 | 635 | ||
636 | msgid "menu.edit.notes" | 636 | msgid "menu.edit.notes" |
637 | msgstr "Editar notas…" | 637 | msgstr "Editar notas…" |
638 | 638 | ||
639 | msgid "heading.ident.notes" | 639 | msgid "heading.ident.notes" |
640 | msgstr "NOTAS DA IDENTIDADE" | 640 | msgstr "Notas da identidade" |
641 | 641 | ||
642 | msgid "ident.fingerprint" | 642 | msgid "ident.fingerprint" |
643 | msgstr "Copiar impresión dixital" | 643 | msgstr "Copiar impresión dixital" |
@@ -646,7 +646,7 @@ msgid "ident.delete" | |||
646 | msgstr "Eliiminar identidade…" | 646 | msgstr "Eliiminar identidade…" |
647 | 647 | ||
648 | msgid "heading.ident.delete" | 648 | msgid "heading.ident.delete" |
649 | msgstr "ELIMINAR IDENTIDADE" | 649 | msgstr "Eliminar identidade" |
650 | 650 | ||
651 | msgid "dlg.ident.delete" | 651 | msgid "dlg.ident.delete" |
652 | msgstr "Eliminar Identidade e Ficheiros" | 652 | msgstr "Eliminar Identidade e Ficheiros" |
@@ -655,7 +655,7 @@ msgid "sidebar.empty.idents" | |||
655 | msgstr "Sen identidades" | 655 | msgstr "Sen identidades" |
656 | 656 | ||
657 | msgid "heading.unsub" | 657 | msgid "heading.unsub" |
658 | msgstr "RETIRAR SUBSCRICIN" | 658 | msgstr "Retirar subscricin" |
659 | 659 | ||
660 | #, c-format | 660 | #, c-format |
661 | msgid "dlg.confirm.unsub" | 661 | msgid "dlg.confirm.unsub" |
@@ -674,7 +674,7 @@ msgid "error.server.msg" | |||
674 | msgstr "O servidor respondeu coa mensaxe:" | 674 | msgstr "O servidor respondeu coa mensaxe:" |
675 | 675 | ||
676 | msgid "heading.pageinfo" | 676 | msgid "heading.pageinfo" |
677 | msgstr "INFORMACIN DA PXINA" | 677 | msgstr "Informacin da pxina" |
678 | 678 | ||
679 | msgid "pageinfo.header.cached" | 679 | msgid "pageinfo.header.cached" |
680 | msgstr "(contido na caché)" | 680 | msgstr "(contido na caché)" |
@@ -723,10 +723,10 @@ msgid "dlg.input.send" | |||
723 | msgstr "Enviar" | 723 | msgstr "Enviar" |
724 | 724 | ||
725 | msgid "heading.save" | 725 | msgid "heading.save" |
726 | msgstr "FICHEIRO GARDADO" | 726 | msgstr "Ficheiro gardado" |
727 | 727 | ||
728 | msgid "heading.save.incomplete" | 728 | msgid "heading.save.incomplete" |
729 | msgstr "PXINA INCOMPLETA" | 729 | msgstr "Pxina incompleta" |
730 | 730 | ||
731 | msgid "dlg.save.size" | 731 | msgid "dlg.save.size" |
732 | msgstr "Tamaño:" | 732 | msgstr "Tamaño:" |
@@ -735,10 +735,10 @@ msgid "dlg.save.opendownload" | |||
735 | msgstr "Abrir ficheiro descargado" | 735 | msgstr "Abrir ficheiro descargado" |
736 | 736 | ||
737 | msgid "heading.save.error" | 737 | msgid "heading.save.error" |
738 | msgstr "ERRO AO GARDAR O FICHEIRO" | 738 | msgstr "Erro ao gardar o ficheiro" |
739 | 739 | ||
740 | msgid "heading.import.bookmarks" | 740 | msgid "heading.import.bookmarks" |
741 | msgstr "IMPORTAR MARCADORES" | 741 | msgstr "Importar marcadores" |
742 | 742 | ||
743 | #, c-format | 743 | #, c-format |
744 | msgid "dlg.import.found" | 744 | msgid "dlg.import.found" |
@@ -756,7 +756,7 @@ msgid "dlg.import.notnew" | |||
756 | msgstr "Xa están marcadas tódalas ligazóns desta páxina." | 756 | msgstr "Xa están marcadas tódalas ligazóns desta páxina." |
757 | 757 | ||
758 | msgid "heading.autoreload" | 758 | msgid "heading.autoreload" |
759 | msgstr "AUTO-RECARGA" | 759 | msgstr "Auto-Recarga" |
760 | 760 | ||
761 | msgid "dlg.autoreload" | 761 | msgid "dlg.autoreload" |
762 | msgstr "Elixe o intervalo para a auto-recarga nesta lapela." | 762 | msgstr "Elixe o intervalo para a auto-recarga nesta lapela." |
@@ -798,13 +798,13 @@ msgid "link.file.delete" | |||
798 | msgstr "Eliminar ficheiro" | 798 | msgstr "Eliminar ficheiro" |
799 | 799 | ||
800 | msgid "heading.file.delete" | 800 | msgid "heading.file.delete" |
801 | msgstr "ELIMINAR FICHEIRO" | 801 | msgstr "Eliminar ficheiro" |
802 | 802 | ||
803 | msgid "dlg.file.delete" | 803 | msgid "dlg.file.delete" |
804 | msgstr "Eliminar" | 804 | msgstr "Eliminar" |
805 | 805 | ||
806 | msgid "heading.openlink" | 806 | msgid "heading.openlink" |
807 | msgstr "ABRIR LIGAZN" | 807 | msgstr "Abrir Ligazn" |
808 | 808 | ||
809 | #, c-format | 809 | #, c-format |
810 | msgid "dlg.openlink.confirm" | 810 | msgid "dlg.openlink.confirm" |
@@ -827,7 +827,7 @@ msgid "dlg.certwarn.domain.expired" | |||
827 | msgstr "O certificado recibido está caducado E era para un dominio incorrecto." | 827 | msgstr "O certificado recibido está caducado E era para un dominio incorrecto." |
828 | 828 | ||
829 | msgid "heading.certimport" | 829 | msgid "heading.certimport" |
830 | msgstr "IMPORTAR IDENTIDADE" | 830 | msgstr "Importar identidade" |
831 | 831 | ||
832 | msgid "dlg.certimport.help" | 832 | msgid "dlg.certimport.help" |
833 | msgstr "" | 833 | msgstr "" |
@@ -838,10 +838,10 @@ msgid "dlg.certimport.notfound" | |||
838 | msgstr "Non se atopa certificado ou chave privada." | 838 | msgstr "Non se atopa certificado ou chave privada." |
839 | 839 | ||
840 | msgid "heading.certimport.pasted" | 840 | msgid "heading.certimport.pasted" |
841 | msgstr "PEGADO DESDE PORTAPAPEIS" | 841 | msgstr "Pegado desde portapapeis" |
842 | 842 | ||
843 | msgid "heading.certimport.dropped" | 843 | msgid "heading.certimport.dropped" |
844 | msgstr "FICHEIRO APORTADO" | 844 | msgstr "Ficheiro aportado" |
845 | 845 | ||
846 | msgid "dlg.certimport.import" | 846 | msgid "dlg.certimport.import" |
847 | msgstr "Importar" | 847 | msgstr "Importar" |
@@ -859,10 +859,10 @@ msgid "dlg.certimport.nokey" | |||
859 | msgstr "Sen chave privada" | 859 | msgstr "Sen chave privada" |
860 | 860 | ||
861 | msgid "link.hint.audio" | 861 | msgid "link.hint.audio" |
862 | msgstr "Reproducir" | 862 | msgstr "Audio" |
863 | 863 | ||
864 | msgid "link.hint.image" | 864 | msgid "link.hint.image" |
865 | msgstr "Ver imaxe" | 865 | msgstr "Imaxe" |
866 | 866 | ||
867 | msgid "bookmark.title.blank" | 867 | msgid "bookmark.title.blank" |
868 | msgstr "Páxina baleira" | 868 | msgstr "Páxina baleira" |
@@ -912,10 +912,10 @@ msgid "heading.lookup.other" | |||
912 | msgstr "OUTRO" | 912 | msgstr "OUTRO" |
913 | 913 | ||
914 | msgid "menu.page.upload" | 914 | msgid "menu.page.upload" |
915 | msgstr "Subir páxina con Titan…" | 915 | msgstr "Subir con Titan…" |
916 | 916 | ||
917 | msgid "heading.upload" | 917 | msgid "heading.upload" |
918 | msgstr "SUBIR CON TITAN" | 918 | msgstr "Subir con Titan" |
919 | 919 | ||
920 | msgid "heading.upload.text" | 920 | msgid "heading.upload.text" |
921 | msgstr "Texto" | 921 | msgstr "Texto" |
@@ -951,13 +951,13 @@ msgid "upload.port" | |||
951 | msgstr "Porto…" | 951 | msgstr "Porto…" |
952 | 952 | ||
953 | msgid "heading.uploadport" | 953 | msgid "heading.uploadport" |
954 | msgstr "PORTO TITAN PARA SUBIDAS" | 954 | msgstr "Porto de subida Titan" |
955 | 955 | ||
956 | msgid "dlg.uploadport.set" | 956 | msgid "dlg.uploadport.set" |
957 | msgstr "Establecer porto" | 957 | msgstr "Establecer porto" |
958 | 958 | ||
959 | msgid "heading.translate" | 959 | msgid "heading.translate" |
960 | msgstr "TRADUCIR PXINA" | 960 | msgstr "Traducir pxina" |
961 | 961 | ||
962 | msgid "dlg.translate.unavail" | 962 | msgid "dlg.translate.unavail" |
963 | msgstr "Servizo non dispoñible" | 963 | msgstr "Servizo non dispoñible" |
@@ -1029,7 +1029,7 @@ msgid "lang.tok" | |||
1029 | msgstr "Toki Pona" | 1029 | msgstr "Toki Pona" |
1030 | 1030 | ||
1031 | msgid "heading.newident" | 1031 | msgid "heading.newident" |
1032 | msgstr "NOVA IDENTIDADE" | 1032 | msgstr "Nova Identidade" |
1033 | 1033 | ||
1034 | msgid "dlg.newident.until" | 1034 | msgid "dlg.newident.until" |
1035 | msgstr "Válido ata:" | 1035 | msgstr "Válido ata:" |
@@ -1080,19 +1080,19 @@ msgid "dlg.newident.create" | |||
1080 | msgstr "Crear identidade" | 1080 | msgstr "Crear identidade" |
1081 | 1081 | ||
1082 | msgid "heading.newident.missing" | 1082 | msgid "heading.newident.missing" |
1083 | msgstr "INFO QUE FALTA" | 1083 | msgstr "Info que falta" |
1084 | 1084 | ||
1085 | msgid "heading.newident.date.bad" | 1085 | msgid "heading.newident.date.bad" |
1086 | msgstr "DATA NON VLIDA" | 1086 | msgstr "Data non vlida" |
1087 | 1087 | ||
1088 | msgid "dlg.newident.date.past" | 1088 | msgid "dlg.newident.date.past" |
1089 | msgstr "A data de caducidade ten que estar no futuro." | 1089 | msgstr "A data de caducidade ten que estar no futuro." |
1090 | 1090 | ||
1091 | msgid "heading.feedcfg" | 1091 | msgid "heading.feedcfg" |
1092 | msgstr "AXUSTES DA FONTE" | 1092 | msgstr "Axustes da Fonte" |
1093 | 1093 | ||
1094 | msgid "heading.subscribe" | 1094 | msgid "heading.subscribe" |
1095 | msgstr "SUBSCRIBIRSE A PXINA" | 1095 | msgstr "Subscribirse á Pxina" |
1096 | 1096 | ||
1097 | msgid "dlg.feed.title" | 1097 | msgid "dlg.feed.title" |
1098 | msgstr "Título:" | 1098 | msgstr "Título:" |
@@ -1113,10 +1113,10 @@ msgid "dlg.feed.sub" | |||
1113 | msgstr "Subscribirse" | 1113 | msgstr "Subscribirse" |
1114 | 1114 | ||
1115 | msgid "heading.bookmark.add" | 1115 | msgid "heading.bookmark.add" |
1116 | msgstr "ENGADIR MARCADOR" | 1116 | msgstr "Engadir Marcador" |
1117 | 1117 | ||
1118 | msgid "heading.bookmark.edit" | 1118 | msgid "heading.bookmark.edit" |
1119 | msgstr "EDITAR MARCADOR" | 1119 | msgstr "Editar Marcador" |
1120 | 1120 | ||
1121 | msgid "dlg.bookmark.save" | 1121 | msgid "dlg.bookmark.save" |
1122 | msgstr "Gardar marcador" | 1122 | msgstr "Gardar marcador" |
@@ -1134,10 +1134,10 @@ msgid "dlg.bookmark.icon" | |||
1134 | msgstr "Icona:" | 1134 | msgstr "Icona:" |
1135 | 1135 | ||
1136 | msgid "heading.prefs" | 1136 | msgid "heading.prefs" |
1137 | msgstr "PREFERENCIAS" | 1137 | msgstr "Preferencias" |
1138 | 1138 | ||
1139 | msgid "heading.prefs.certs" | 1139 | msgid "heading.prefs.certs" |
1140 | msgstr "CERTIFICADOS" | 1140 | msgstr "Certificados" |
1141 | 1141 | ||
1142 | # tab button | 1142 | # tab button |
1143 | msgid "heading.prefs.colors" | 1143 | msgid "heading.prefs.colors" |
@@ -1163,16 +1163,16 @@ msgid "heading.prefs.network" | |||
1163 | msgstr "Rede" | 1163 | msgstr "Rede" |
1164 | 1164 | ||
1165 | msgid "heading.prefs.paragraph" | 1165 | msgid "heading.prefs.paragraph" |
1166 | msgstr "PARGRAFO" | 1166 | msgstr "Pargrafo" |
1167 | 1167 | ||
1168 | msgid "heading.prefs.uitheme" | 1168 | msgid "heading.prefs.uitheme" |
1169 | msgstr "CORES DA IU" | 1169 | msgstr "Cores da IU" |
1170 | 1170 | ||
1171 | msgid "heading.prefs.proxies" | 1171 | msgid "heading.prefs.proxies" |
1172 | msgstr "PROXIES" | 1172 | msgstr "Proxies" |
1173 | 1173 | ||
1174 | msgid "heading.prefs.scrolling" | 1174 | msgid "heading.prefs.scrolling" |
1175 | msgstr "DESPRAZAMENTO" | 1175 | msgstr "Desprazamento" |
1176 | 1176 | ||
1177 | # tab button | 1177 | # tab button |
1178 | msgid "heading.prefs.style" | 1178 | msgid "heading.prefs.style" |
@@ -1480,7 +1480,7 @@ msgid "keys.tab.close.other" | |||
1480 | msgstr "Pechar outras lapelas" | 1480 | msgstr "Pechar outras lapelas" |
1481 | 1481 | ||
1482 | msgid "keys.upload" | 1482 | msgid "keys.upload" |
1483 | msgstr "Subir páxina con Titan" | 1483 | msgstr "Subir con Titan" |
1484 | 1484 | ||
1485 | msgid "error.badstatus.msg" | 1485 | msgid "error.badstatus.msg" |
1486 | msgstr "" | 1486 | msgstr "" |
@@ -1825,7 +1825,7 @@ msgid "menu.undo" | |||
1825 | msgstr "Desfacer" | 1825 | msgstr "Desfacer" |
1826 | 1826 | ||
1827 | msgid "heading.confirm.bookmarks.delete" | 1827 | msgid "heading.confirm.bookmarks.delete" |
1828 | msgstr "ELIMINAR MARCADORES" | 1828 | msgstr "Eliminar marcadores" |
1829 | 1829 | ||
1830 | #, c-format | 1830 | #, c-format |
1831 | msgid "dlg.confirm.bookmarks.delete" | 1831 | msgid "dlg.confirm.bookmarks.delete" |
@@ -1840,7 +1840,7 @@ msgid "dlg.upload.id.none" | |||
1840 | msgstr "Ningunha" | 1840 | msgstr "Ningunha" |
1841 | 1841 | ||
1842 | msgid "heading.bookmark.tags" | 1842 | msgid "heading.bookmark.tags" |
1843 | msgstr "ETIQUETAS ESPECIAIS" | 1843 | msgstr "Etiquetas especiais" |
1844 | 1844 | ||
1845 | msgid "dlg.addfolder.defaulttitle" | 1845 | msgid "dlg.addfolder.defaulttitle" |
1846 | msgstr "Novo cartafol" | 1846 | msgstr "Novo cartafol" |
@@ -1890,7 +1890,7 @@ msgid "dlg.upload.pickfile" | |||
1890 | msgstr "Elexir ficheiro" | 1890 | msgstr "Elexir ficheiro" |
1891 | 1891 | ||
1892 | msgid "heading.addfolder" | 1892 | msgid "heading.addfolder" |
1893 | msgstr "ENGADIR CARTAFOL" | 1893 | msgstr "Engadir Cartafol" |
1894 | 1894 | ||
1895 | msgid "dlg.addfolder.prompt" | 1895 | msgid "dlg.addfolder.prompt" |
1896 | msgstr "Escribe o nome do novo cartafol:" | 1896 | msgstr "Escribe o nome do novo cartafol:" |
@@ -1900,7 +1900,7 @@ msgstr "Engadir cartafol" | |||
1900 | 1900 | ||
1901 | # used on mobile | 1901 | # used on mobile |
1902 | msgid "heading.settings" | 1902 | msgid "heading.settings" |
1903 | msgstr "AXUSTES" | 1903 | msgstr "Axustes" |
1904 | 1904 | ||
1905 | msgid "prefs.imagestyle" | 1905 | msgid "prefs.imagestyle" |
1906 | msgstr "Imaxes coloridas:" | 1906 | msgstr "Imaxes coloridas:" |
@@ -1946,7 +1946,7 @@ msgstr[0] "%u tipografía" | |||
1946 | msgstr[1] "%u tipografías" | 1946 | msgstr[1] "%u tipografías" |
1947 | 1947 | ||
1948 | msgid "heading.fontpack.classic" | 1948 | msgid "heading.fontpack.classic" |
1949 | msgstr "DESCARGAR PAQUETE TIPOGRAFA" | 1949 | msgstr "Descargar paquete de tipografa" |
1950 | 1950 | ||
1951 | msgid "dlg.fontpack.classic" | 1951 | msgid "dlg.fontpack.classic" |
1952 | msgstr "Descargar Paquete de tipografía (25 MB)" | 1952 | msgstr "Descargar Paquete de tipografía (25 MB)" |
@@ -2084,7 +2084,7 @@ msgid "fontpack.delete" | |||
2084 | msgstr "Eliminar permanentemente \"%s\"" | 2084 | msgstr "Eliminar permanentemente \"%s\"" |
2085 | 2085 | ||
2086 | msgid "heading.fontpack.delete" | 2086 | msgid "heading.fontpack.delete" |
2087 | msgstr "ELIMINAR TIPOGRAFA" | 2087 | msgstr "Eliminar tipografa" |
2088 | 2088 | ||
2089 | msgid "dlg.fontpack.delete" | 2089 | msgid "dlg.fontpack.delete" |
2090 | msgstr "Eliminar tipografía" | 2090 | msgstr "Eliminar tipografía" |
@@ -2107,7 +2107,7 @@ msgid "truetype.help.installed" | |||
2107 | msgstr "Esta tipografía está instalada no directorio da usuaria." | 2107 | msgstr "Esta tipografía está instalada no directorio da usuaria." |
2108 | 2108 | ||
2109 | msgid "heading.dismiss.warning" | 2109 | msgid "heading.dismiss.warning" |
2110 | msgstr "DESBOTAR AVISO?" | 2110 | msgstr "Desbotar aviso?" |
2111 | 2111 | ||
2112 | #, c-format | 2112 | #, c-format |
2113 | msgid "dlg.dismiss.ansi" | 2113 | msgid "dlg.dismiss.ansi" |
@@ -2145,3 +2145,99 @@ msgstr "Ler todo" | |||
2145 | 2145 | ||
2146 | msgid "menu.update" | 2146 | msgid "menu.update" |
2147 | msgstr "Comprobar actualización…" | 2147 | msgstr "Comprobar actualización…" |
2148 | |||
2149 | msgid "menu.open.external" | ||
2150 | msgstr "Abrir noutra app" | ||
2151 | |||
2152 | # Active identity toolbar menu. | ||
2153 | msgid "menu.hide.identities" | ||
2154 | msgstr "Agochar identidades" | ||
2155 | |||
2156 | msgid "menu.home" | ||
2157 | msgstr "Ir a Inicio" | ||
2158 | |||
2159 | msgid "menu.identities" | ||
2160 | msgstr "Xestión Identidades" | ||
2161 | |||
2162 | msgid "sidebar.close" | ||
2163 | msgstr "Feito" | ||
2164 | |||
2165 | msgid "sidebar.action.bookmarks.newfolder" | ||
2166 | msgstr "Novo cartafol" | ||
2167 | |||
2168 | msgid "sidebar.action.bookmarks.edit" | ||
2169 | msgstr "Editar" | ||
2170 | |||
2171 | msgid "sidebar.action.history.clear" | ||
2172 | msgstr "Limpar" | ||
2173 | |||
2174 | # The %s represents the name of an identity. | ||
2175 | #, c-format | ||
2176 | msgid "ident.switch" | ||
2177 | msgstr "Usar %s" | ||
2178 | |||
2179 | msgid "sidebar.empty.unread" | ||
2180 | msgstr "Sen entradas non lidas" | ||
2181 | |||
2182 | # Paste the line preceding the clicked link into the input prompt. | ||
2183 | msgid "menu.input.precedingline" | ||
2184 | msgstr "Pegar a liña anterior" | ||
2185 | |||
2186 | # Shows where a local file is using the Finder. | ||
2187 | msgid "menu.reveal.macos" | ||
2188 | msgstr "Mostrar no buscador" | ||
2189 | |||
2190 | msgid "menu.share" | ||
2191 | msgstr "Compartir" | ||
2192 | |||
2193 | msgid "menu.page.upload.edit" | ||
2194 | msgstr "Editar páxina con Titan…" | ||
2195 | |||
2196 | msgid "heading.upload.id" | ||
2197 | msgstr "Autorización" | ||
2198 | |||
2199 | msgid "menu.upload.export" | ||
2200 | msgstr "Exportar texto" | ||
2201 | |||
2202 | msgid "menu.upload.delete" | ||
2203 | msgstr "Eliminar todo" | ||
2204 | |||
2205 | msgid "menu.upload.delete.confirm" | ||
2206 | msgstr "Quero Eliminar Todo (non ten volta)" | ||
2207 | |||
2208 | # Mobile subheading in the Upload dialog. | ||
2209 | msgid "upload.url" | ||
2210 | msgstr "URL" | ||
2211 | |||
2212 | # Mobile subheading: buttons for entering uploaded data. | ||
2213 | msgid "upload.content" | ||
2214 | msgstr "Contido" | ||
2215 | |||
2216 | msgid "hint.upload.path" | ||
2217 | msgstr "Ruta URL" | ||
2218 | |||
2219 | msgid "hint.upload.token.long" | ||
2220 | msgstr "token — mira instruccións no servidor" | ||
2221 | |||
2222 | msgid "heading.prefs.toolbaractions" | ||
2223 | msgstr "Accións da barra de ferramentas" | ||
2224 | |||
2225 | msgid "prefs.toolbaraction1" | ||
2226 | msgstr "Botón 1" | ||
2227 | |||
2228 | msgid "prefs.toolbaraction2" | ||
2229 | msgstr "Botón 2" | ||
2230 | |||
2231 | msgid "prefs.blink" | ||
2232 | msgstr "Cursor intermitente:" | ||
2233 | |||
2234 | msgid "keys.upload.edit" | ||
2235 | msgstr "Editar páxina con Titan" | ||
2236 | |||
2237 | # Shows where a local file is using the File Manager. | ||
2238 | msgid "menu.reveal.filemgr" | ||
2239 | msgstr "Mostrar no Xestor de ficheiros" | ||
2240 | |||
2241 | # Menu heading shown when customizing navbar button actions. | ||
2242 | msgid "menu.toolbar.setaction" | ||
2243 | msgstr "Establece acción:" | ||
@@ -1,7 +1,7 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
4 | "PO-Revision-Date: 2021-07-19 19:06+0000\n" | 4 | "PO-Revision-Date: 2022-01-02 15:50+0000\n" |
5 | "Last-Translator: Olga Smirnova <mistresssilvara@hotmail.com>\n" | 5 | "Last-Translator: Olga Smirnova <mistresssilvara@hotmail.com>\n" |
6 | "Language-Team: Interlingua <http://weblate.skyjake.fi/projects/lagrange/ui/" | 6 | "Language-Team: Interlingua <http://weblate.skyjake.fi/projects/lagrange/ui/" |
7 | "ia/>\n" | 7 | "ia/>\n" |
@@ -133,7 +133,7 @@ msgid "toolbar.outline" | |||
133 | msgstr "Structura del pagina" | 133 | msgstr "Structura del pagina" |
134 | 134 | ||
135 | msgid "hint.findtext" | 135 | msgid "hint.findtext" |
136 | msgstr "Trovar texto in le pagina" | 136 | msgstr "trovar texto in le pagina" |
137 | 137 | ||
138 | msgid "status.feeds" | 138 | msgid "status.feeds" |
139 | msgstr "Actualisation del syndicationes" | 139 | msgstr "Actualisation del syndicationes" |
@@ -760,8 +760,8 @@ msgstr "Certificato non valide" | |||
760 | #, c-format | 760 | #, c-format |
761 | msgid "ident.usedonurls" | 761 | msgid "ident.usedonurls" |
762 | msgid_plural "ident.usedonurls.n" | 762 | msgid_plural "ident.usedonurls.n" |
763 | msgstr[0] "Usate por %zu URL" | 763 | msgstr[0] "Usate por %u URL" |
764 | msgstr[1] "Usate por %zu URLs" | 764 | msgstr[1] "Usate por %u URLs" |
765 | 765 | ||
766 | #, c-format | 766 | #, c-format |
767 | msgid "dlg.import.add" | 767 | msgid "dlg.import.add" |
@@ -926,9 +926,7 @@ msgstr "Vide %sAdjuta%s por plus de information re certificatos de cliente TLS." | |||
926 | 926 | ||
927 | #, c-format | 927 | #, c-format |
928 | msgid "dlg.confirm.unsub" | 928 | msgid "dlg.confirm.unsub" |
929 | msgstr "" | 929 | msgstr "Disabonar le syndication «%s»?" |
930 | "Disabonar le syndication\n" | ||
931 | "«%s»?" | ||
932 | 930 | ||
933 | msgid "heading.pageinfo" | 931 | msgid "heading.pageinfo" |
934 | msgstr "INFORMATION RE LE PAGINA" | 932 | msgstr "INFORMATION RE LE PAGINA" |
@@ -1001,7 +999,7 @@ msgid "error.tls" | |||
1001 | msgstr "Fallimento del rete o TLS" | 999 | msgstr "Fallimento del rete o TLS" |
1002 | 1000 | ||
1003 | msgid "error.tls.msg" | 1001 | msgid "error.tls.msg" |
1004 | msgstr "Fallite a communicar con le servitor. Ecce le message de error:" | 1002 | msgstr "Fallite a communicar con le servitor." |
1005 | 1003 | ||
1006 | msgid "error.slowdown" | 1004 | msgid "error.slowdown" |
1007 | msgstr "Relentamento" | 1005 | msgstr "Relentamento" |
@@ -1020,8 +1018,8 @@ msgstr "Monstrar tote" | |||
1020 | 1018 | ||
1021 | msgid "num.bytes" | 1019 | msgid "num.bytes" |
1022 | msgid_plural "num.bytes.n" | 1020 | msgid_plural "num.bytes.n" |
1023 | msgstr[0] "%zu byte" | 1021 | msgstr[0] "%u byte" |
1024 | msgstr[1] "%zu bytes" | 1022 | msgstr[1] "%u bytes" |
1025 | 1023 | ||
1026 | msgid "prefs.doctheme.name.colorfuldark" | 1024 | msgid "prefs.doctheme.name.colorfuldark" |
1027 | msgstr "Colorate obscur" | 1025 | msgstr "Colorate obscur" |
@@ -1211,9 +1209,8 @@ msgstr "Actualisar marcapaginas remote" | |||
1211 | #, c-format | 1209 | #, c-format |
1212 | msgid "dlg.confirm.ident.delete" | 1210 | msgid "dlg.confirm.ident.delete" |
1213 | msgstr "" | 1211 | msgstr "" |
1214 | "Vole tu vermente deler le identitate\n" | 1212 | "Vole tu vermente deler le identitate %s«%s»%s con su certificato e clave " |
1215 | "%s%s%s\n" | 1213 | "private?" |
1216 | "con su certificato e clave private?" | ||
1217 | 1214 | ||
1218 | msgid "lang.ie" | 1215 | msgid "lang.ie" |
1219 | msgstr "Interlingue" | 1216 | msgstr "Interlingue" |
@@ -1241,14 +1238,14 @@ msgstr "Le contento del pagina se carga ancora." | |||
1241 | #, c-format | 1238 | #, c-format |
1242 | msgid "feeds.list.counts" | 1239 | msgid "feeds.list.counts" |
1243 | msgid_plural "feeds.list.counts.n" | 1240 | msgid_plural "feeds.list.counts.n" |
1244 | msgstr[0] "%zu abonate syndication que contine %%s.\n" | 1241 | msgstr[0] "%u abonate syndication que contine %%s.\n" |
1245 | msgstr[1] "%zu abonate syndicationes que contine %%s.\n" | 1242 | msgstr[1] "%u abonate syndicationes que contine %%s.\n" |
1246 | 1243 | ||
1247 | #, c-format | 1244 | #, c-format |
1248 | msgid "feeds.list.entrycount" | 1245 | msgid "feeds.list.entrycount" |
1249 | msgid_plural "feeds.list.entrycount.n" | 1246 | msgid_plural "feeds.list.entrycount.n" |
1250 | msgstr[0] "%zu elemento in toto" | 1247 | msgstr[0] "%u elemento in toto" |
1251 | msgstr[1] "%zu elementos in toto" | 1248 | msgstr[1] "%u elementos in toto" |
1252 | 1249 | ||
1253 | msgid "dlg.autoreload" | 1250 | msgid "dlg.autoreload" |
1254 | msgstr "Interstitio de recarga automatic por iste scheda." | 1251 | msgstr "Interstitio de recarga automatic por iste scheda." |
@@ -1458,7 +1455,7 @@ msgid "menu.split.vertical" | |||
1458 | msgstr "Vertical" | 1455 | msgstr "Vertical" |
1459 | 1456 | ||
1460 | msgid "menu.view.split" | 1457 | msgid "menu.view.split" |
1461 | msgstr "Finder le vista..." | 1458 | msgstr "Finder le vista…" |
1462 | 1459 | ||
1463 | msgid "bookmark.tag.linksplit" | 1460 | msgid "bookmark.tag.linksplit" |
1464 | msgstr "Ligamines que aperi al latere" | 1461 | msgstr "Ligamines que aperi al latere" |
@@ -1507,9 +1504,8 @@ msgstr "Sortir le archivo" | |||
1507 | #, c-format | 1504 | #, c-format |
1508 | msgid "archive.summary" | 1505 | msgid "archive.summary" |
1509 | msgid_plural "archive.summary.n" | 1506 | msgid_plural "archive.summary.n" |
1510 | msgstr[0] "Iste archivo contine %zu elemento e su dimension comprimite es %.1f MB." | 1507 | msgstr[0] "Iste archivo contine %u elemento e su dimension comprimite es %.1f MB." |
1511 | msgstr[1] "" | 1508 | msgstr[1] "Iste archivo contine %u elementos e su dimension comprimite es %.1f MB." |
1512 | "Iste archivo contine %zu elementos e su dimension comprimite es %.1f MB." | ||
1513 | 1509 | ||
1514 | msgid "dir.empty" | 1510 | msgid "dir.empty" |
1515 | msgstr "Iste directorio es vacue." | 1511 | msgstr "Iste directorio es vacue." |
@@ -1517,8 +1513,8 @@ msgstr "Iste directorio es vacue." | |||
1517 | #, c-format | 1513 | #, c-format |
1518 | msgid "dir.summary" | 1514 | msgid "dir.summary" |
1519 | msgid_plural "dir.summary.n" | 1515 | msgid_plural "dir.summary.n" |
1520 | msgstr[0] "Iste directorio contine %zu elemento." | 1516 | msgstr[0] "Iste directorio contine %u elemento." |
1521 | msgstr[1] "Iste directorio contine %zu elementos." | 1517 | msgstr[1] "Iste directorio contine %u elementos." |
1522 | 1518 | ||
1523 | msgid "keys.tab.close.other" | 1519 | msgid "keys.tab.close.other" |
1524 | msgstr "Clauder le altere schedas" | 1520 | msgstr "Clauder le altere schedas" |
@@ -1579,3 +1575,355 @@ msgstr "DELER LE FILE" | |||
1579 | 1575 | ||
1580 | msgid "dlg.file.delete" | 1576 | msgid "dlg.file.delete" |
1581 | msgstr "Deler" | 1577 | msgstr "Deler" |
1578 | |||
1579 | msgid "upload.file.name" | ||
1580 | msgstr "Nomine de file:" | ||
1581 | |||
1582 | msgid "upload.file.drophere" | ||
1583 | msgstr "(trahe e depone un file al fenestra)" | ||
1584 | |||
1585 | msgid "upload.mime" | ||
1586 | msgstr "Typo MIME:" | ||
1587 | |||
1588 | msgid "upload.token" | ||
1589 | msgstr "Token:" | ||
1590 | |||
1591 | msgid "prefs.scrollspeed.mouse" | ||
1592 | msgstr "Velocitate del mus:" | ||
1593 | |||
1594 | msgid "dlg.newident.more" | ||
1595 | msgstr "Plus…" | ||
1596 | |||
1597 | msgid "dlg.fontpack.classic" | ||
1598 | msgstr "Discargar le fontpack (25 MB)" | ||
1599 | |||
1600 | msgid "prefs.returnkey.linebreak" | ||
1601 | msgstr "Ruptura de linea" | ||
1602 | |||
1603 | msgid "error.certverify" | ||
1604 | msgstr "Servitor non fidabile" | ||
1605 | |||
1606 | msgid "prefs.imagestyle" | ||
1607 | msgstr "Colorisar imagines:" | ||
1608 | |||
1609 | msgid "prefs.font.mono" | ||
1610 | msgstr "Preformattate:" | ||
1611 | |||
1612 | # Font to use for headings and body when Monospace body is enabled. | ||
1613 | msgid "prefs.font.monodoc" | ||
1614 | msgstr "Typo de characteres mono-spatio:" | ||
1615 | |||
1616 | msgid "upload.port" | ||
1617 | msgstr "Porta…" | ||
1618 | |||
1619 | msgid "heading.uploadport" | ||
1620 | msgstr "PORTA DE INCARGAMENTO DE TITAN" | ||
1621 | |||
1622 | msgid "prefs.returnkey.accept" | ||
1623 | msgstr "Acceptar" | ||
1624 | |||
1625 | msgid "prefs.returnkey" | ||
1626 | msgstr "Comportamento del clave Enter:" | ||
1627 | |||
1628 | msgid "prefs.time.24h" | ||
1629 | msgstr "Tempore a 24-horas" | ||
1630 | |||
1631 | msgid "prefs.imagestyle.original" | ||
1632 | msgstr "Nulle" | ||
1633 | |||
1634 | msgid "prefs.imagestyle.text" | ||
1635 | msgstr "Color del texto" | ||
1636 | |||
1637 | msgid "prefs.gemtext.ansi" | ||
1638 | msgstr "Escappamentos ANSI:" | ||
1639 | |||
1640 | # Color of text background. | ||
1641 | msgid "prefs.gemtext.ansi.bg" | ||
1642 | msgstr "Color de fundo" | ||
1643 | |||
1644 | msgid "prefs.gemtext.ansi.fontstyle" | ||
1645 | msgstr "Stilo del characteres" | ||
1646 | |||
1647 | msgid "prefs.memorysize" | ||
1648 | msgstr "Dimension de memoria:" | ||
1649 | |||
1650 | msgid "keys.upload" | ||
1651 | msgstr "Incargar un pagina via Titan" | ||
1652 | |||
1653 | msgid "error.server.msg" | ||
1654 | msgstr "Le servitor ha respondite:" | ||
1655 | |||
1656 | # Action label | ||
1657 | msgid "fontpack.meta.viewfile" | ||
1658 | msgstr "Vider le file" | ||
1659 | |||
1660 | msgid "dlg.newident.scope" | ||
1661 | msgstr "Usar pro:" | ||
1662 | |||
1663 | msgid "menu.pageinfo" | ||
1664 | msgstr "Monstrar information re le pagina" | ||
1665 | |||
1666 | msgid "menu.save.downloads.open" | ||
1667 | msgstr "Salvar a Discargas e aperir le file" | ||
1668 | |||
1669 | msgid "prefs.scrollspeed.keyboard" | ||
1670 | msgstr "Velocitate de claviero:" | ||
1671 | |||
1672 | msgid "num.files" | ||
1673 | msgid_plural "num.files.n" | ||
1674 | msgstr[0] "%u dossier" | ||
1675 | msgstr[1] "%u dossiers" | ||
1676 | |||
1677 | msgid "prefs.font.ui" | ||
1678 | msgstr "IdU:" | ||
1679 | |||
1680 | msgid "fontpack.meta.disabled" | ||
1681 | msgstr ", inactive" | ||
1682 | |||
1683 | #, c-format | ||
1684 | msgid "fontpack.enable" | ||
1685 | msgstr "Activar «%s»" | ||
1686 | |||
1687 | #, c-format | ||
1688 | msgid "fontpack.disable" | ||
1689 | msgstr "Disactivar «%s»" | ||
1690 | |||
1691 | msgid "fontpack.export" | ||
1692 | msgstr "Vider un patrono de fontpack.ini" | ||
1693 | |||
1694 | #, c-format | ||
1695 | msgid "fontpack.install" | ||
1696 | msgstr "Installar «%s»" | ||
1697 | |||
1698 | #, c-format | ||
1699 | msgid "fontpack.upgrade" | ||
1700 | msgstr "Promover «%s» al version %d" | ||
1701 | |||
1702 | msgid "media.untitled.image" | ||
1703 | msgstr "Imagine" | ||
1704 | |||
1705 | msgid "media.untitled.audio" | ||
1706 | msgstr "Audio" | ||
1707 | |||
1708 | msgid "dlg.fontpack.delete" | ||
1709 | msgstr "Deler le fontpack" | ||
1710 | |||
1711 | msgid "prefs.font.body" | ||
1712 | msgstr "Corpore:" | ||
1713 | |||
1714 | msgid "heading.fontpack.meta.enabled" | ||
1715 | msgstr "Fontpacks active" | ||
1716 | |||
1717 | msgid "heading.fontpack.meta.disabled" | ||
1718 | msgstr "Fontpacks inactive" | ||
1719 | |||
1720 | msgid "menu.newfolder" | ||
1721 | msgstr "Nove dossier…" | ||
1722 | |||
1723 | # used for Preferences on mobile | ||
1724 | msgid "menu.settings" | ||
1725 | msgstr "Parametros" | ||
1726 | |||
1727 | # keep this short (3x1 horiz layout) | ||
1728 | msgid "menu.selectall" | ||
1729 | msgstr "Seliger toto" | ||
1730 | |||
1731 | # keep this short (3x1 horiz layout) | ||
1732 | msgid "menu.delete" | ||
1733 | msgstr "Deler" | ||
1734 | |||
1735 | #, c-format | ||
1736 | msgid "dlg.bookmarks.delete" | ||
1737 | msgid_plural "dlg.bookmarks.delete.n" | ||
1738 | msgstr[0] "Deler le marcapagina" | ||
1739 | msgstr[1] "Deler %u marcapaginas" | ||
1740 | |||
1741 | msgid "bookmark.export.count" | ||
1742 | msgid_plural "bookmark.export.count.n" | ||
1743 | msgstr[0] "Vos have %d marcapagina." | ||
1744 | msgstr[1] "Vos have %d marcapaginas." | ||
1745 | |||
1746 | msgid "sidebar.action.show" | ||
1747 | msgstr "Monstrar:" | ||
1748 | |||
1749 | msgid "heading.confirm.bookmarks.delete" | ||
1750 | msgstr "DELER MARCAPAGINAS" | ||
1751 | |||
1752 | msgid "menu.website" | ||
1753 | msgstr "Sito web del projecto…" | ||
1754 | |||
1755 | # keep this short (3x1 horiz layout) | ||
1756 | msgid "menu.undo" | ||
1757 | msgstr "Disfacer" | ||
1758 | |||
1759 | msgid "fontpack.open.fontsdir" | ||
1760 | msgstr "Aperir le directorio del typos del usator" | ||
1761 | |||
1762 | msgid "fontpack.open.aboutfonts" | ||
1763 | msgstr "Monstrar le typos de character installate" | ||
1764 | |||
1765 | msgid "menu.fonts" | ||
1766 | msgstr "Gerer typos de litteras…" | ||
1767 | |||
1768 | # This label should be fairly short so it fits in a button in the sidebar. | ||
1769 | msgid "sidebar.action.feeds.markallread" | ||
1770 | msgstr "Toto legite" | ||
1771 | |||
1772 | msgid "num.fonts" | ||
1773 | msgid_plural "num.fonts.n" | ||
1774 | msgstr[0] "%u typo" | ||
1775 | msgstr[1] "%u typos" | ||
1776 | |||
1777 | msgid "dlg.file.delete.confirm" | ||
1778 | msgstr "Desira tu vermente deler iste file?" | ||
1779 | |||
1780 | msgid "bookmark.export.format.linklines" | ||
1781 | msgstr "Cata ligamine representa un marcapagina." | ||
1782 | |||
1783 | msgid "menu.page.upload" | ||
1784 | msgstr "Incargar un pagina via Titan…" | ||
1785 | |||
1786 | msgid "heading.upload" | ||
1787 | msgstr "INCARGAR VIA TITAN" | ||
1788 | |||
1789 | msgid "upload.id" | ||
1790 | msgstr "Identitate:" | ||
1791 | |||
1792 | msgid "dlg.upload.id.none" | ||
1793 | msgstr "Nulle" | ||
1794 | |||
1795 | msgid "heading.upload.file" | ||
1796 | msgstr "File" | ||
1797 | |||
1798 | msgid "upload.file.size" | ||
1799 | msgstr "Dimension de file:" | ||
1800 | |||
1801 | msgid "dlg.upload.send" | ||
1802 | msgstr "Incargar" | ||
1803 | |||
1804 | # used on mobile | ||
1805 | msgid "dlg.upload.text" | ||
1806 | msgstr "Incargar un texto simple" | ||
1807 | |||
1808 | # used on mobile | ||
1809 | msgid "dlg.upload.file" | ||
1810 | msgstr "Incargar un file" | ||
1811 | |||
1812 | # used on mobile | ||
1813 | msgid "dlg.upload.pickfile" | ||
1814 | msgstr "Seliger un file" | ||
1815 | |||
1816 | msgid "dlg.newident.scope.domain" | ||
1817 | msgstr "Dominio actual" | ||
1818 | |||
1819 | msgid "dlg.newident.scope.page" | ||
1820 | msgstr "Pagina actual" | ||
1821 | |||
1822 | msgid "dlg.newident.scope.none" | ||
1823 | msgstr "Non usate" | ||
1824 | |||
1825 | msgid "dlg.feed.ignoreweb" | ||
1826 | msgstr "Ignorar ligamines HTTP(S):" | ||
1827 | |||
1828 | msgid "dlg.bookmark.folder" | ||
1829 | msgstr "Dossier:" | ||
1830 | |||
1831 | msgid "heading.bookmark.tags" | ||
1832 | msgstr "ETIQUETTAS SPECIAL" | ||
1833 | |||
1834 | msgid "heading.addfolder" | ||
1835 | msgstr "ADDER UN DOSSIER" | ||
1836 | |||
1837 | msgid "dlg.addfolder.defaulttitle" | ||
1838 | msgstr "Nove dossier" | ||
1839 | |||
1840 | msgid "menu.update" | ||
1841 | msgstr "Controlar pro le actualisationes…" | ||
1842 | |||
1843 | msgid "status.query.tight" | ||
1844 | msgstr "Requesta" | ||
1845 | |||
1846 | msgid "dlg.addfolder" | ||
1847 | msgstr "Adder un dossier" | ||
1848 | |||
1849 | # used on mobile | ||
1850 | msgid "heading.settings" | ||
1851 | msgstr "PARAMETROS" | ||
1852 | |||
1853 | msgid "prefs.font.heading" | ||
1854 | msgstr "Capites:" | ||
1855 | |||
1856 | msgid "prefs.boldlink.visited" | ||
1857 | msgstr "Visitate" | ||
1858 | |||
1859 | msgid "prefs.font.warnmissing" | ||
1860 | msgstr "Advertimentos de glyphos:" | ||
1861 | |||
1862 | msgid "prefs.linespacing" | ||
1863 | msgstr "Spatiamento inter lineas:" | ||
1864 | |||
1865 | msgid "error.certexpired" | ||
1866 | msgstr "Certificato perimite" | ||
1867 | |||
1868 | msgid "error.glyphs" | ||
1869 | msgstr "Glyphos mancante" | ||
1870 | |||
1871 | msgid "heading.fontpack.meta" | ||
1872 | msgstr "Typos de character" | ||
1873 | |||
1874 | #, c-format | ||
1875 | msgid "fontpack.meta.version" | ||
1876 | msgstr "Version %d" | ||
1877 | |||
1878 | msgid "fontpack.meta.installed" | ||
1879 | msgstr "Installate" | ||
1880 | |||
1881 | msgid "fontpack.meta.notinstalled" | ||
1882 | msgstr "Non installate" | ||
1883 | |||
1884 | msgid "heading.fontpack.delete" | ||
1885 | msgstr "DELER UN FONTPACK" | ||
1886 | |||
1887 | msgid "fontpack.install.ttf" | ||
1888 | msgstr "Installar un typo de litteras TrueType" | ||
1889 | |||
1890 | msgid "dlg.dismiss.warning" | ||
1891 | msgstr "Dimitter le advertimento" | ||
1892 | |||
1893 | msgid "heading.fontpack.classic" | ||
1894 | msgstr "DISCARGAR UN FONTPACK" | ||
1895 | |||
1896 | msgid "prefs.imagestyle.preformat" | ||
1897 | msgstr "Color de preformattate" | ||
1898 | |||
1899 | #, c-format | ||
1900 | msgid "fontpack.delete" | ||
1901 | msgstr "Deler permanentemente «%s»" | ||
1902 | |||
1903 | msgid "dlg.certwarn.title" | ||
1904 | msgstr "Problema de securitate" | ||
1905 | |||
1906 | # button in the mobile New Identity dialog | ||
1907 | msgid "dlg.certimport.pickfile" | ||
1908 | msgstr "Importar un certificato o un file clave" | ||
1909 | |||
1910 | msgid "bookmark.export.title.folder" | ||
1911 | msgstr "Marcapaginas" | ||
1912 | |||
1913 | msgid "bookmark.export.title.tag" | ||
1914 | msgstr "Etiquettas de marcapaginas" | ||
1915 | |||
1916 | msgid "prefs.imagestyle.grayscale" | ||
1917 | msgstr "Scala de gris" | ||
1918 | |||
1919 | msgid "error.ansi" | ||
1920 | msgstr "Emulation de terminal" | ||
1921 | |||
1922 | msgid "dlg.upload.id.default" | ||
1923 | msgstr "Predefinite" | ||
1924 | |||
1925 | msgid "heading.upload.text" | ||
1926 | msgstr "Texto" | ||
1927 | |||
1928 | msgid "hint.upload.text" | ||
1929 | msgstr "Insere texto a incargar" | ||
@@ -3,7 +3,7 @@ msgstr "" | |||
3 | "Project-Id-Version: PACKAGE VERSION\n" | 3 | "Project-Id-Version: PACKAGE VERSION\n" |
4 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 4 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
5 | "POT-Creation-Date: 2021-03-23 19:02+0000\n" | 5 | "POT-Creation-Date: 2021-03-23 19:02+0000\n" |
6 | "PO-Revision-Date: 2021-12-08 14:50+0000\n" | 6 | "PO-Revision-Date: 2022-01-18 17:50+0000\n" |
7 | "Last-Translator: Nikolay Korotkiy <sikmir@gmail.com>\n" | 7 | "Last-Translator: Nikolay Korotkiy <sikmir@gmail.com>\n" |
8 | "Language-Team: Russian <http://weblate.skyjake.fi/projects/lagrange/ui/ru/>\n" | 8 | "Language-Team: Russian <http://weblate.skyjake.fi/projects/lagrange/ui/ru/>\n" |
9 | "Language: ru\n" | 9 | "Language: ru\n" |
@@ -350,7 +350,7 @@ msgid "history.clear" | |||
350 | msgstr "Очистить историю…" | 350 | msgstr "Очистить историю…" |
351 | 351 | ||
352 | msgid "heading.history.clear" | 352 | msgid "heading.history.clear" |
353 | msgstr "ОТКА РИИ" | 353 | msgstr "Очст стр" |
354 | 354 | ||
355 | msgid "dlg.confirm.history.clear" | 355 | msgid "dlg.confirm.history.clear" |
356 | msgstr "Вы действительно хотите стереть историю посещений страниц?" | 356 | msgstr "Вы действительно хотите стереть историю посещений страниц?" |
@@ -413,13 +413,13 @@ msgid "ident.showuse" | |||
413 | msgstr "Показать использование" | 413 | msgstr "Показать использование" |
414 | 414 | ||
415 | msgid "heading.ident.use" | 415 | msgid "heading.ident.use" |
416 | msgstr "ИИЕ ГО ИКАТА" | 416 | msgstr "Ись тс сртфт" |
417 | 417 | ||
418 | msgid "menu.edit.notes" | 418 | msgid "menu.edit.notes" |
419 | msgstr "Редактировать заметки…" | 419 | msgstr "Редактировать заметки…" |
420 | 420 | ||
421 | msgid "heading.ident.notes" | 421 | msgid "heading.ident.notes" |
422 | msgstr "ЗИ ГО ИКАТА" | 422 | msgstr "Зт тс сртфт" |
423 | 423 | ||
424 | msgid "dlg.ident.notes" | 424 | msgid "dlg.ident.notes" |
425 | msgstr "Заметки о %s:" | 425 | msgstr "Заметки о %s:" |
@@ -431,7 +431,7 @@ msgid "ident.delete" | |||
431 | msgstr "Удалить клиентский сертификат…" | 431 | msgstr "Удалить клиентский сертификат…" |
432 | 432 | ||
433 | msgid "heading.ident.delete" | 433 | msgid "heading.ident.delete" |
434 | msgstr "У ГО ИКАТА" | 434 | msgstr "У тс сртфт" |
435 | 435 | ||
436 | msgid "dlg.confirm.ident.delete" | 436 | msgid "dlg.confirm.ident.delete" |
437 | msgstr "" | 437 | msgstr "" |
@@ -450,7 +450,7 @@ msgstr "" | |||
450 | "сертификатах." | 450 | "сертификатах." |
451 | 451 | ||
452 | msgid "heading.unsub" | 452 | msgid "heading.unsub" |
453 | msgstr "ОКА" | 453 | msgstr "Отс" |
454 | 454 | ||
455 | msgid "dlg.confirm.unsub" | 455 | msgid "dlg.confirm.unsub" |
456 | msgstr "Действительно отписаться от ленты «%s»?" | 456 | msgstr "Действительно отписаться от ленты «%s»?" |
@@ -464,7 +464,7 @@ msgstr "" | |||
464 | "«%s»." | 464 | "«%s»." |
465 | 465 | ||
466 | msgid "heading.pageinfo" | 466 | msgid "heading.pageinfo" |
467 | msgstr "ИАЦИЯ НИЦЕ" | 467 | msgstr "Ифрця стрц" |
468 | 468 | ||
469 | msgid "pageinfo.header.cached" | 469 | msgid "pageinfo.header.cached" |
470 | msgstr "(кешированный контент)" | 470 | msgstr "(кешированный контент)" |
@@ -509,10 +509,10 @@ msgid "dlg.input.send" | |||
509 | msgstr "Отправить" | 509 | msgstr "Отправить" |
510 | 510 | ||
511 | msgid "heading.save" | 511 | msgid "heading.save" |
512 | msgstr "Ф АНЁН" | 512 | msgstr "Ф схрё" |
513 | 513 | ||
514 | msgid "heading.save.incomplete" | 514 | msgid "heading.save.incomplete" |
515 | msgstr "СИЦА ЕССЕ КИ" | 515 | msgstr "Стрц рцсс ру" |
516 | 516 | ||
517 | msgid "dlg.save.incomplete" | 517 | msgid "dlg.save.incomplete" |
518 | msgstr "Содержимое страницы всё еще загружается." | 518 | msgstr "Содержимое страницы всё еще загружается." |
@@ -521,10 +521,10 @@ msgid "dlg.save.size" | |||
521 | msgstr "Размер:" | 521 | msgstr "Размер:" |
522 | 522 | ||
523 | msgid "heading.save.error" | 523 | msgid "heading.save.error" |
524 | msgstr "ОА ЕНИЯ А" | 524 | msgstr "Ош схря ф" |
525 | 525 | ||
526 | msgid "heading.import.bookmarks" | 526 | msgid "heading.import.bookmarks" |
527 | msgstr "ИРТ " | 527 | msgstr "Ирт " |
528 | 528 | ||
529 | msgid "dlg.import.found" | 529 | msgid "dlg.import.found" |
530 | msgid_plural "dlg.import.found.n" | 530 | msgid_plural "dlg.import.found.n" |
@@ -542,7 +542,7 @@ msgid "dlg.import.notnew" | |||
542 | msgstr "Все ссылки на этой странице уже добавлены в закладки." | 542 | msgstr "Все ссылки на этой странице уже добавлены в закладки." |
543 | 543 | ||
544 | msgid "heading.autoreload" | 544 | msgid "heading.autoreload" |
545 | msgstr "АЗКА" | 545 | msgstr "Атру" |
546 | 546 | ||
547 | msgid "dlg.autoreload" | 547 | msgid "dlg.autoreload" |
548 | msgstr "Выберите интервал автоматической перезагрузки для этой вкладки." | 548 | msgstr "Выберите интервал автоматической перезагрузки для этой вкладки." |
@@ -569,7 +569,7 @@ msgid "link.download" | |||
569 | msgstr "Скачать файл" | 569 | msgstr "Скачать файл" |
570 | 570 | ||
571 | msgid "heading.openlink" | 571 | msgid "heading.openlink" |
572 | msgstr "ОКРЫТЬ ЫЛКУ" | 572 | msgstr "Отрыть ссыу" |
573 | 573 | ||
574 | msgid "dlg.openlink.confirm" | 574 | msgid "dlg.openlink.confirm" |
575 | msgstr "" | 575 | msgstr "" |
@@ -616,16 +616,16 @@ msgstr "" | |||
616 | "домену." | 616 | "домену." |
617 | 617 | ||
618 | msgid "link.hint.audio" | 618 | msgid "link.hint.audio" |
619 | msgstr "опроизвести адио" | 619 | msgstr "удио" |
620 | 620 | ||
621 | msgid "link.hint.image" | 621 | msgid "link.hint.image" |
622 | msgstr "кать избражение" | 622 | msgstr "зображение" |
623 | 623 | ||
624 | msgid "bookmark.title.blank" | 624 | msgid "bookmark.title.blank" |
625 | msgstr "Пустая страница" | 625 | msgstr "Пустая страница" |
626 | 626 | ||
627 | msgid "heading.translate" | 627 | msgid "heading.translate" |
628 | msgstr "ПД АНИЦЫ" | 628 | msgstr "Пр стрцы" |
629 | 629 | ||
630 | msgid "dlg.translate.unavail" | 630 | msgid "dlg.translate.unavail" |
631 | msgstr "Сервис недоступен" | 631 | msgstr "Сервис недоступен" |
@@ -676,7 +676,7 @@ msgid "lang.es" | |||
676 | msgstr "Испанский" | 676 | msgstr "Испанский" |
677 | 677 | ||
678 | msgid "heading.newident" | 678 | msgid "heading.newident" |
679 | msgstr "НЙ ИЙ ФИКАТ" | 679 | msgstr "Ны тс сртфт" |
680 | 680 | ||
681 | msgid "dlg.newident.rsa.selfsign" | 681 | msgid "dlg.newident.rsa.selfsign" |
682 | msgstr "Создание самоподписанного 2048-битного RSA-сертификата." | 682 | msgstr "Создание самоподписанного 2048-битного RSA-сертификата." |
@@ -718,10 +718,10 @@ msgid "dlg.newident.create" | |||
718 | msgstr "Создать сертификат" | 718 | msgstr "Создать сертификат" |
719 | 719 | ||
720 | msgid "heading.feedcfg" | 720 | msgid "heading.feedcfg" |
721 | msgstr "НЙКИ ТЫ" | 721 | msgstr "Нстр ты" |
722 | 722 | ||
723 | msgid "heading.subscribe" | 723 | msgid "heading.subscribe" |
724 | msgstr "ПА АНИЦУ" | 724 | msgstr "Пс стрцу" |
725 | 725 | ||
726 | msgid "dlg.feed.title" | 726 | msgid "dlg.feed.title" |
727 | msgstr "Заголовок:" | 727 | msgstr "Заголовок:" |
@@ -742,10 +742,10 @@ msgid "dlg.feed.sub" | |||
742 | msgstr "Подписаться" | 742 | msgstr "Подписаться" |
743 | 743 | ||
744 | msgid "heading.bookmark.add" | 744 | msgid "heading.bookmark.add" |
745 | msgstr "ДТЬ У" | 745 | msgstr "Дть у" |
746 | 746 | ||
747 | msgid "heading.bookmark.edit" | 747 | msgid "heading.bookmark.edit" |
748 | msgstr "РИЕ " | 748 | msgstr "Ртр " |
749 | 749 | ||
750 | msgid "dlg.bookmark.save" | 750 | msgid "dlg.bookmark.save" |
751 | msgstr "Сохранить закладку" | 751 | msgstr "Сохранить закладку" |
@@ -763,10 +763,10 @@ msgid "dlg.bookmark.icon" | |||
763 | msgstr "Иконка:" | 763 | msgstr "Иконка:" |
764 | 764 | ||
765 | msgid "heading.prefs" | 765 | msgid "heading.prefs" |
766 | msgstr "НЙКИ" | 766 | msgstr "Нстр" |
767 | 767 | ||
768 | msgid "heading.prefs.certs" | 768 | msgid "heading.prefs.certs" |
769 | msgstr "СИКАТЫ" | 769 | msgstr "Сртфты" |
770 | 770 | ||
771 | msgid "heading.prefs.colors" | 771 | msgid "heading.prefs.colors" |
772 | msgstr "Цвета" | 772 | msgstr "Цвета" |
@@ -787,22 +787,22 @@ msgid "heading.prefs.network" | |||
787 | msgstr "Сеть" | 787 | msgstr "Сеть" |
788 | 788 | ||
789 | msgid "heading.prefs.paragraph" | 789 | msgid "heading.prefs.paragraph" |
790 | msgstr "ПРАФ" | 790 | msgstr "Пррф" |
791 | 791 | ||
792 | msgid "heading.prefs.pagecontent" | 792 | msgid "heading.prefs.pagecontent" |
793 | msgstr "ЦА АНИЦЫ" | 793 | msgstr "Цт стрцы" |
794 | 794 | ||
795 | msgid "heading.prefs.proxies" | 795 | msgid "heading.prefs.proxies" |
796 | msgstr "ПСИ" | 796 | msgstr "Прс" |
797 | 797 | ||
798 | msgid "heading.prefs.scrolling" | 798 | msgid "heading.prefs.scrolling" |
799 | msgstr "ПУТКА" | 799 | msgstr "Пррут" |
800 | 800 | ||
801 | msgid "heading.prefs.sizing" | 801 | msgid "heading.prefs.sizing" |
802 | msgstr "РР" | 802 | msgstr "Рр" |
803 | 803 | ||
804 | msgid "heading.prefs.widelayout" | 804 | msgid "heading.prefs.widelayout" |
805 | msgstr "ШЙ Т" | 805 | msgstr "Шр т" |
806 | 806 | ||
807 | msgid "heading.prefs.style" | 807 | msgid "heading.prefs.style" |
808 | msgstr "Стиль" | 808 | msgstr "Стиль" |
@@ -1060,7 +1060,7 @@ msgid "prefs.uilang" | |||
1060 | msgstr "Язык:" | 1060 | msgstr "Язык:" |
1061 | 1061 | ||
1062 | msgid "heading.certimport" | 1062 | msgid "heading.certimport" |
1063 | msgstr "ИРТ ГО ИКАТА" | 1063 | msgstr "Ирт тс сртфт" |
1064 | 1064 | ||
1065 | msgid "dlg.certimport.notfound" | 1065 | msgid "dlg.certimport.notfound" |
1066 | msgstr "Сертификат или закрытый ключ не найдены." | 1066 | msgstr "Сертификат или закрытый ключ не найдены." |
@@ -1075,7 +1075,7 @@ msgid "lang.fi" | |||
1075 | msgstr "Финский" | 1075 | msgstr "Финский" |
1076 | 1076 | ||
1077 | msgid "heading.certimport.pasted" | 1077 | msgid "heading.certimport.pasted" |
1078 | msgstr "ВНО ЕРА " | 1078 | msgstr "Вст уфр " |
1079 | 1079 | ||
1080 | msgid "dlg.certimport.import" | 1080 | msgid "dlg.certimport.import" |
1081 | msgstr "Импортировать" | 1081 | msgstr "Импортировать" |
@@ -1252,7 +1252,7 @@ msgid "keys.subscribe" | |||
1252 | msgstr "Подписаться на страницу" | 1252 | msgstr "Подписаться на страницу" |
1253 | 1253 | ||
1254 | msgid "heading.certimport.dropped" | 1254 | msgid "heading.certimport.dropped" |
1255 | msgstr "ПЩЕН Л" | 1255 | msgstr "Пртщ ф" |
1256 | 1256 | ||
1257 | msgid "error.permanent.msg" | 1257 | msgid "error.permanent.msg" |
1258 | msgstr "" | 1258 | msgstr "" |
@@ -1498,10 +1498,10 @@ msgid "dlg.newident.date.past" | |||
1498 | msgstr "Срок действия не должен заканчиваться в прошлом." | 1498 | msgstr "Срок действия не должен заканчиваться в прошлом." |
1499 | 1499 | ||
1500 | msgid "heading.newident.date.bad" | 1500 | msgid "heading.newident.date.bad" |
1501 | msgstr "ННАЯ А" | 1501 | msgstr "Нрья т" |
1502 | 1502 | ||
1503 | msgid "heading.newident.missing" | 1503 | msgid "heading.newident.missing" |
1504 | msgstr "ТУЕТСЯ АЦИЯ" | 1504 | msgstr "Трутся фрця" |
1505 | 1505 | ||
1506 | msgid "keys.split.menu" | 1506 | msgid "keys.split.menu" |
1507 | msgstr "Управлять разделением окна" | 1507 | msgstr "Управлять разделением окна" |
@@ -1648,7 +1648,7 @@ msgid "link.file.delete" | |||
1648 | msgstr "Удалить файл" | 1648 | msgstr "Удалить файл" |
1649 | 1649 | ||
1650 | msgid "heading.file.delete" | 1650 | msgid "heading.file.delete" |
1651 | msgstr "УТЬ Л" | 1651 | msgstr "Уть ф" |
1652 | 1652 | ||
1653 | msgid "dlg.file.delete.confirm" | 1653 | msgid "dlg.file.delete.confirm" |
1654 | msgstr "Вы действительно хотите удалить этот файл?" | 1654 | msgstr "Вы действительно хотите удалить этот файл?" |
@@ -1657,7 +1657,7 @@ msgid "dlg.file.delete" | |||
1657 | msgstr "Удалить" | 1657 | msgstr "Удалить" |
1658 | 1658 | ||
1659 | msgid "heading.prefs.uitheme" | 1659 | msgid "heading.prefs.uitheme" |
1660 | msgstr "ЦА ЕЙСА" | 1660 | msgstr "Цт трфс" |
1661 | 1661 | ||
1662 | msgid "prefs.scrollspeed.keyboard" | 1662 | msgid "prefs.scrollspeed.keyboard" |
1663 | msgstr "Скорость клавиатуры:" | 1663 | msgstr "Скорость клавиатуры:" |
@@ -1681,10 +1681,10 @@ msgid "menu.unexpire" | |||
1681 | msgstr "Все равно загрузить" | 1681 | msgstr "Все равно загрузить" |
1682 | 1682 | ||
1683 | msgid "menu.page.upload" | 1683 | msgid "menu.page.upload" |
1684 | msgstr "Загрузить страницу (Titan)…" | 1684 | msgstr "Загрузить (Titan)…" |
1685 | 1685 | ||
1686 | msgid "heading.upload" | 1686 | msgid "heading.upload" |
1687 | msgstr "ЗКА ΤΙΤΑΝ" | 1687 | msgstr "Зру Titan" |
1688 | 1688 | ||
1689 | msgid "heading.upload.text" | 1689 | msgid "heading.upload.text" |
1690 | msgstr "Текст" | 1690 | msgstr "Текст" |
@@ -1723,7 +1723,7 @@ msgid "prefs.returnkey.accept" | |||
1723 | msgstr "Подтверждение" | 1723 | msgstr "Подтверждение" |
1724 | 1724 | ||
1725 | msgid "keys.upload" | 1725 | msgid "keys.upload" |
1726 | msgstr "Загрузить страницу (Titan)" | 1726 | msgstr "Загрузить (Titan)" |
1727 | 1727 | ||
1728 | msgid "error.certexpired" | 1728 | msgid "error.certexpired" |
1729 | msgstr "Сертификат просрочен" | 1729 | msgstr "Сертификат просрочен" |
@@ -1736,7 +1736,7 @@ msgid "upload.port" | |||
1736 | msgstr "Порт…" | 1736 | msgstr "Порт…" |
1737 | 1737 | ||
1738 | msgid "heading.uploadport" | 1738 | msgid "heading.uploadport" |
1739 | msgstr "ПРТ ОК TITAN" | 1739 | msgstr "Прт ру Titan" |
1740 | 1740 | ||
1741 | msgid "dlg.uploadport.msg" | 1741 | msgid "dlg.uploadport.msg" |
1742 | msgstr "" | 1742 | msgstr "" |
@@ -1845,10 +1845,10 @@ msgid "lang.isv" | |||
1845 | msgstr "Междуславянский" | 1845 | msgstr "Междуславянский" |
1846 | 1846 | ||
1847 | msgid "heading.bookmark.tags" | 1847 | msgid "heading.bookmark.tags" |
1848 | msgstr "ОЫЕ И" | 1848 | msgstr "Осы т" |
1849 | 1849 | ||
1850 | msgid "heading.addfolder" | 1850 | msgid "heading.addfolder" |
1851 | msgstr "ДТЬ У" | 1851 | msgstr "Дть у" |
1852 | 1852 | ||
1853 | msgid "dlg.addfolder.defaulttitle" | 1853 | msgid "dlg.addfolder.defaulttitle" |
1854 | msgstr "Новая папка" | 1854 | msgstr "Новая папка" |
@@ -1861,7 +1861,7 @@ msgstr "Добавить папку" | |||
1861 | 1861 | ||
1862 | # used on mobile | 1862 | # used on mobile |
1863 | msgid "heading.settings" | 1863 | msgid "heading.settings" |
1864 | msgstr "НЙКИ" | 1864 | msgstr "Нстр" |
1865 | 1865 | ||
1866 | msgid "prefs.imagestyle" | 1866 | msgid "prefs.imagestyle" |
1867 | msgstr "Окрашивать изображения:" | 1867 | msgstr "Окрашивать изображения:" |
@@ -1898,7 +1898,7 @@ msgid "menu.undo" | |||
1898 | msgstr "Отмена" | 1898 | msgstr "Отмена" |
1899 | 1899 | ||
1900 | msgid "heading.confirm.bookmarks.delete" | 1900 | msgid "heading.confirm.bookmarks.delete" |
1901 | msgstr "УТЬ " | 1901 | msgstr "Уть " |
1902 | 1902 | ||
1903 | # button in the mobile New Identity dialog | 1903 | # button in the mobile New Identity dialog |
1904 | msgid "dlg.certimport.pickfile" | 1904 | msgid "dlg.certimport.pickfile" |
@@ -1941,7 +1941,7 @@ msgstr[1] "%u шрифта" | |||
1941 | msgstr[2] "%u шрифтов" | 1941 | msgstr[2] "%u шрифтов" |
1942 | 1942 | ||
1943 | msgid "heading.fontpack.classic" | 1943 | msgid "heading.fontpack.classic" |
1944 | msgstr "ЗЗИТЬ Т ФТОВ" | 1944 | msgstr "Зруть т шрфт" |
1945 | 1945 | ||
1946 | msgid "dlg.fontpack.classic.msg" | 1946 | msgid "dlg.fontpack.classic.msg" |
1947 | msgstr "" | 1947 | msgstr "" |
@@ -2050,7 +2050,7 @@ msgid "fontpack.delete" | |||
2050 | msgstr "Безвозвратно удалить «%s»" | 2050 | msgstr "Безвозвратно удалить «%s»" |
2051 | 2051 | ||
2052 | msgid "heading.fontpack.delete" | 2052 | msgid "heading.fontpack.delete" |
2053 | msgstr "УТЬ Т ФТОВ" | 2053 | msgstr "Уть т шрфт" |
2054 | 2054 | ||
2055 | msgid "dlg.fontpack.delete" | 2055 | msgid "dlg.fontpack.delete" |
2056 | msgstr "Удалить пакет шрифтов" | 2056 | msgstr "Удалить пакет шрифтов" |
@@ -2105,7 +2105,7 @@ msgid "truetype.help.installed" | |||
2105 | msgstr "Этот шрифт установлен в каталог пользовательских шрифтов." | 2105 | msgstr "Этот шрифт установлен в каталог пользовательских шрифтов." |
2106 | 2106 | ||
2107 | msgid "heading.dismiss.warning" | 2107 | msgid "heading.dismiss.warning" |
2108 | msgstr "СРЫТЬ НИЕ?" | 2108 | msgstr "Срыть рур?" |
2109 | 2109 | ||
2110 | msgid "dlg.dismiss.warning" | 2110 | msgid "dlg.dismiss.warning" |
2111 | msgstr "Скрыть предупреждение" | 2111 | msgstr "Скрыть предупреждение" |
@@ -2142,3 +2142,99 @@ msgstr "24-часовой формат времени" | |||
2142 | # This label should be fairly short so it fits in a button in the sidebar. | 2142 | # This label should be fairly short so it fits in a button in the sidebar. |
2143 | msgid "sidebar.action.feeds.markallread" | 2143 | msgid "sidebar.action.feeds.markallread" |
2144 | msgstr "Прочитать все" | 2144 | msgstr "Прочитать все" |
2145 | |||
2146 | msgid "menu.open.external" | ||
2147 | msgstr "Открыть в другом приложении" | ||
2148 | |||
2149 | msgid "menu.identities" | ||
2150 | msgstr "Управление клиентскими сертификатами" | ||
2151 | |||
2152 | # The %s represents the name of an identity. | ||
2153 | #, c-format | ||
2154 | msgid "ident.switch" | ||
2155 | msgstr "Использовать %s" | ||
2156 | |||
2157 | # Paste the line preceding the clicked link into the input prompt. | ||
2158 | msgid "menu.input.precedingline" | ||
2159 | msgstr "Вставить предыдущую строку" | ||
2160 | |||
2161 | msgid "menu.page.upload.edit" | ||
2162 | msgstr "Редактировать страницу (Titan)…" | ||
2163 | |||
2164 | msgid "menu.upload.export" | ||
2165 | msgstr "Экспортировать текст" | ||
2166 | |||
2167 | # Mobile subheading: buttons for entering uploaded data. | ||
2168 | msgid "upload.content" | ||
2169 | msgstr "Содержимое" | ||
2170 | |||
2171 | msgid "prefs.blink" | ||
2172 | msgstr "Мигание курсора мыши:" | ||
2173 | |||
2174 | # Active identity toolbar menu. | ||
2175 | msgid "menu.hide.identities" | ||
2176 | msgstr "Скрыть панель клиентских сертификатов" | ||
2177 | |||
2178 | msgid "menu.home" | ||
2179 | msgstr "Домой" | ||
2180 | |||
2181 | msgid "sidebar.close" | ||
2182 | msgstr "Готово" | ||
2183 | |||
2184 | msgid "sidebar.action.bookmarks.newfolder" | ||
2185 | msgstr "Новая папка" | ||
2186 | |||
2187 | msgid "sidebar.action.bookmarks.edit" | ||
2188 | msgstr "Редактировать" | ||
2189 | |||
2190 | msgid "sidebar.action.history.clear" | ||
2191 | msgstr "Очистить" | ||
2192 | |||
2193 | msgid "sidebar.empty.unread" | ||
2194 | msgstr "Непрочитанных записей нет" | ||
2195 | |||
2196 | # Shows where a local file is using the Finder. | ||
2197 | msgid "menu.reveal.macos" | ||
2198 | msgstr "Показать в Finder" | ||
2199 | |||
2200 | msgid "menu.share" | ||
2201 | msgstr "Поделиться" | ||
2202 | |||
2203 | msgid "heading.upload.id" | ||
2204 | msgstr "Авторизация" | ||
2205 | |||
2206 | msgid "menu.upload.delete" | ||
2207 | msgstr "Удалить всё" | ||
2208 | |||
2209 | msgid "menu.upload.delete.confirm" | ||
2210 | msgstr "Действительно удалить всё (безвозвратно)" | ||
2211 | |||
2212 | # Mobile subheading in the Upload dialog. | ||
2213 | msgid "upload.url" | ||
2214 | msgstr "URL" | ||
2215 | |||
2216 | msgid "hint.upload.path" | ||
2217 | msgstr "Путь URL" | ||
2218 | |||
2219 | msgid "hint.upload.token.long" | ||
2220 | msgstr "токен — см. инструкции сервера" | ||
2221 | |||
2222 | msgid "heading.prefs.toolbaractions" | ||
2223 | msgstr "Панель инструментов" | ||
2224 | |||
2225 | msgid "prefs.toolbaraction1" | ||
2226 | msgstr "Кнопка 1" | ||
2227 | |||
2228 | msgid "prefs.toolbaraction2" | ||
2229 | msgstr "Кнопка 2" | ||
2230 | |||
2231 | msgid "keys.upload.edit" | ||
2232 | msgstr "Редактировать страницу (Titan)" | ||
2233 | |||
2234 | # Shows where a local file is using the File Manager. | ||
2235 | msgid "menu.reveal.filemgr" | ||
2236 | msgstr "Показать в файловом менеджере" | ||
2237 | |||
2238 | # Menu heading shown when customizing navbar button actions. | ||
2239 | msgid "menu.toolbar.setaction" | ||
2240 | msgstr "Установить действие:" | ||
@@ -1,7 +1,7 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
4 | "PO-Revision-Date: 2021-12-01 16:50+0000\n" | 4 | "PO-Revision-Date: 2022-01-18 17:50+0000\n" |
5 | "Last-Translator: Страхиња Радић <contact@strahinja.org>\n" | 5 | "Last-Translator: Страхиња Радић <contact@strahinja.org>\n" |
6 | "Language-Team: Serbian <http://weblate.skyjake.fi/projects/lagrange/ui/sr/>\n" | 6 | "Language-Team: Serbian <http://weblate.skyjake.fi/projects/lagrange/ui/sr/>\n" |
7 | "Language: sr\n" | 7 | "Language: sr\n" |
@@ -289,7 +289,7 @@ msgid "dlg.unsub" | |||
289 | msgstr "Уклонити претплату" | 289 | msgstr "Уклонити претплату" |
290 | 290 | ||
291 | msgid "heading.pageinfo" | 291 | msgid "heading.pageinfo" |
292 | msgstr "ПИ НИЦИ" | 292 | msgstr "Пц стрц" |
293 | 293 | ||
294 | msgid "pageinfo.header.cached" | 294 | msgid "pageinfo.header.cached" |
295 | msgstr "(кеширани садржај)" | 295 | msgstr "(кеширани садржај)" |
@@ -316,7 +316,7 @@ msgid "pageinfo.cert.untrusted" | |||
316 | msgstr "Без поверења" | 316 | msgstr "Без поверења" |
317 | 317 | ||
318 | msgid "heading.unsub" | 318 | msgid "heading.unsub" |
319 | msgstr "УЊЕ АТЕ" | 319 | msgstr "Уњњ ртт" |
320 | 320 | ||
321 | msgid "pageinfo.domain.mismatch" | 321 | msgid "pageinfo.domain.mismatch" |
322 | msgstr "Назив домена не одговара" | 322 | msgstr "Назив домена не одговара" |
@@ -331,10 +331,10 @@ msgid "dlg.input.send" | |||
331 | msgstr "Пошаљи" | 331 | msgstr "Пошаљи" |
332 | 332 | ||
333 | msgid "heading.save" | 333 | msgid "heading.save" |
334 | msgstr "ДКА НА" | 334 | msgstr "Дтт ј счу" |
335 | 335 | ||
336 | msgid "heading.save.incomplete" | 336 | msgid "heading.save.incomplete" |
337 | msgstr "ННА НИЦА" | 337 | msgstr "Нту стрц" |
338 | 338 | ||
339 | msgid "dlg.save.incomplete" | 339 | msgid "dlg.save.incomplete" |
340 | msgstr "Садржај странице се и даље преузима." | 340 | msgstr "Садржај странице се и даље преузима." |
@@ -343,7 +343,7 @@ msgid "dlg.save.size" | |||
343 | msgstr "Величина:" | 343 | msgstr "Величина:" |
344 | 344 | ||
345 | msgid "heading.import.bookmarks" | 345 | msgid "heading.import.bookmarks" |
346 | msgstr "У А" | 346 | msgstr "У ч" |
347 | 347 | ||
348 | #, c-format | 348 | #, c-format |
349 | msgid "dlg.import.add" | 349 | msgid "dlg.import.add" |
@@ -356,7 +356,7 @@ msgid "dlg.import.notnew" | |||
356 | msgstr "Све везе на овој страници су већ додате у обележиваче." | 356 | msgstr "Све везе на овој страници су већ додате у обележиваче." |
357 | 357 | ||
358 | msgid "heading.autoreload" | 358 | msgid "heading.autoreload" |
359 | msgstr "АТСКО Е" | 359 | msgstr "Ауттс сњ" |
360 | 360 | ||
361 | msgid "reload.never" | 361 | msgid "reload.never" |
362 | msgstr "Никад" | 362 | msgstr "Никад" |
@@ -394,7 +394,7 @@ msgid "link.bookmark" | |||
394 | msgstr "Додај везу у обележиваче…" | 394 | msgstr "Додај везу у обележиваче…" |
395 | 395 | ||
396 | msgid "heading.openlink" | 396 | msgid "heading.openlink" |
397 | msgstr "ОАЊЕ " | 397 | msgstr "Отрњ " |
398 | 398 | ||
399 | msgid "dlg.openlink" | 399 | msgid "dlg.openlink" |
400 | msgstr "Отвори везу" | 400 | msgstr "Отвори везу" |
@@ -403,10 +403,10 @@ msgid "dlg.certimport.notfound.page" | |||
403 | msgstr "Ниједан сертификат нити кључ није пронађен на текућој страници." | 403 | msgstr "Ниједан сертификат нити кључ није пронађен на текућој страници." |
404 | 404 | ||
405 | msgid "heading.certimport.pasted" | 405 | msgid "heading.certimport.pasted" |
406 | msgstr "НО А" | 406 | msgstr "Нљ р" |
407 | 407 | ||
408 | msgid "heading.certimport.dropped" | 408 | msgid "heading.certimport.dropped" |
409 | msgstr "ПЕНА КА" | 409 | msgstr "Пруч тт" |
410 | 410 | ||
411 | msgid "dlg.certimport.import" | 411 | msgid "dlg.certimport.import" |
412 | msgstr "Увези" | 412 | msgstr "Увези" |
@@ -421,7 +421,7 @@ msgid "dlg.certimport.nocert" | |||
421 | msgstr "Без сертификата" | 421 | msgstr "Без сертификата" |
422 | 422 | ||
423 | msgid "link.hint.audio" | 423 | msgid "link.hint.audio" |
424 | msgstr "уст ауио" | 424 | msgstr "удио" |
425 | 425 | ||
426 | msgid "bookmark.title.blank" | 426 | msgid "bookmark.title.blank" |
427 | msgstr "Празна страница" | 427 | msgstr "Празна страница" |
@@ -447,7 +447,7 @@ msgid "heading.lookup.other" | |||
447 | msgstr "ОСТАЛО" | 447 | msgstr "ОСТАЛО" |
448 | 448 | ||
449 | msgid "heading.translate" | 449 | msgid "heading.translate" |
450 | msgstr "ПД НИЦЕ" | 450 | msgstr "Пр стрц" |
451 | 451 | ||
452 | msgid "dlg.translate.unavail" | 452 | msgid "dlg.translate.unavail" |
453 | msgstr "Сервис недоступан" | 453 | msgstr "Сервис недоступан" |
@@ -495,7 +495,7 @@ msgid "lang.es" | |||
495 | msgstr "Шпански" | 495 | msgstr "Шпански" |
496 | 496 | ||
497 | msgid "heading.newident" | 497 | msgid "heading.newident" |
498 | msgstr "Н ТЕТ" | 498 | msgstr "Н ттт" |
499 | 499 | ||
500 | msgid "dlg.newident.until" | 500 | msgid "dlg.newident.until" |
501 | msgstr "Важи до:" | 501 | msgstr "Важи до:" |
@@ -534,7 +534,7 @@ msgid "dlg.feed.sub" | |||
534 | msgstr "Претплати се" | 534 | msgstr "Претплати се" |
535 | 535 | ||
536 | msgid "heading.bookmark.add" | 536 | msgid "heading.bookmark.add" |
537 | msgstr "ДЕ А" | 537 | msgstr "Дњ ч" |
538 | 538 | ||
539 | msgid "dlg.bookmark.save" | 539 | msgid "dlg.bookmark.save" |
540 | msgstr "Сачувај обележивач" | 540 | msgstr "Сачувај обележивач" |
@@ -552,10 +552,10 @@ msgid "dlg.bookmark.icon" | |||
552 | msgstr "Икона:" | 552 | msgstr "Икона:" |
553 | 553 | ||
554 | msgid "heading.prefs" | 554 | msgid "heading.prefs" |
555 | msgstr "ПЊА" | 555 | msgstr "Пшњ" |
556 | 556 | ||
557 | msgid "heading.prefs.certs" | 557 | msgid "heading.prefs.certs" |
558 | msgstr "СКАТИ" | 558 | msgstr "Сртфт" |
559 | 559 | ||
560 | msgid "heading.prefs.fonts" | 560 | msgid "heading.prefs.fonts" |
561 | msgstr "Фонтови" | 561 | msgstr "Фонтови" |
@@ -573,16 +573,16 @@ msgid "heading.prefs.network" | |||
573 | msgstr "Мрежа" | 573 | msgstr "Мрежа" |
574 | 574 | ||
575 | msgid "heading.prefs.paragraph" | 575 | msgid "heading.prefs.paragraph" |
576 | msgstr "ПСУС" | 576 | msgstr "Псус" |
577 | 577 | ||
578 | msgid "heading.prefs.pagecontent" | 578 | msgid "heading.prefs.pagecontent" |
579 | msgstr "БЕ НИЦЕ" | 579 | msgstr "Бј стрц" |
580 | 580 | ||
581 | msgid "heading.prefs.sizing" | 581 | msgid "heading.prefs.sizing" |
582 | msgstr "ВА" | 582 | msgstr "Вч" |
583 | 583 | ||
584 | msgid "heading.prefs.widelayout" | 584 | msgid "heading.prefs.widelayout" |
585 | msgstr "ШИ " | 585 | msgstr "Шр " |
586 | 586 | ||
587 | # tab button | 587 | # tab button |
588 | msgid "heading.prefs.style" | 588 | msgid "heading.prefs.style" |
@@ -890,7 +890,7 @@ msgid "about.tagline" | |||
890 | msgstr "Прелепи Џемини клијент" | 890 | msgstr "Прелепи Џемини клијент" |
891 | 891 | ||
892 | msgid "heading.history.clear" | 892 | msgid "heading.history.clear" |
893 | msgstr "ЧЕЊЕ ОРЕ" | 893 | msgstr "Чшћњ стр" |
894 | 894 | ||
895 | msgid "error.unavail.msg" | 895 | msgid "error.unavail.msg" |
896 | msgstr "" | 896 | msgstr "" |
@@ -915,7 +915,7 @@ msgid "menu.title.file" | |||
915 | msgstr "Датотека" | 915 | msgstr "Датотека" |
916 | 916 | ||
917 | msgid "heading.certimport" | 917 | msgid "heading.certimport" |
918 | msgstr "У ЕТА" | 918 | msgstr "У ттт" |
919 | 919 | ||
920 | msgid "error.proxy" | 920 | msgid "error.proxy" |
921 | msgstr "Грешка проксија" | 921 | msgstr "Грешка проксија" |
@@ -949,7 +949,7 @@ msgid "dlg.certimport.nokey" | |||
949 | msgstr "Без приватног кључа" | 949 | msgstr "Без приватног кључа" |
950 | 950 | ||
951 | msgid "link.hint.image" | 951 | msgid "link.hint.image" |
952 | msgstr "ркажи сику" | 952 | msgstr "лика" |
953 | 953 | ||
954 | msgid "menu.closetab.other" | 954 | msgid "menu.closetab.other" |
955 | msgstr "Затвори остале картице" | 955 | msgstr "Затвори остале картице" |
@@ -1021,13 +1021,13 @@ msgid "menu.show.outline" | |||
1021 | msgstr "Прикажи структуру странице" | 1021 | msgstr "Прикажи структуру странице" |
1022 | 1022 | ||
1023 | msgid "heading.feedcfg" | 1023 | msgid "heading.feedcfg" |
1024 | msgstr "ПЊА А" | 1024 | msgstr "Пшњ ф" |
1025 | 1025 | ||
1026 | msgid "menu.autoreload" | 1026 | msgid "menu.autoreload" |
1027 | msgstr "Постави аутоматско учитавање…" | 1027 | msgstr "Постави аутоматско учитавање…" |
1028 | 1028 | ||
1029 | msgid "heading.subscribe" | 1029 | msgid "heading.subscribe" |
1030 | msgstr "ПАТА АНИЦУ" | 1030 | msgstr "Пртт стрцу" |
1031 | 1031 | ||
1032 | msgid "menu.page.subscribe" | 1032 | msgid "menu.page.subscribe" |
1033 | msgstr "Претплати се на страницу…" | 1033 | msgstr "Претплати се на страницу…" |
@@ -1045,7 +1045,7 @@ msgid "menu.bookmarks.refresh" | |||
1045 | msgstr "Освежи удаљене обележиваче" | 1045 | msgstr "Освежи удаљене обележиваче" |
1046 | 1046 | ||
1047 | msgid "heading.bookmark.edit" | 1047 | msgid "heading.bookmark.edit" |
1048 | msgstr "УЕЂИ " | 1048 | msgstr "Урњ ч" |
1049 | 1049 | ||
1050 | msgid "sidebar.feeds" | 1050 | msgid "sidebar.feeds" |
1051 | msgstr "Фидови" | 1051 | msgstr "Фидови" |
@@ -1065,7 +1065,7 @@ msgid "sidebar.identities" | |||
1065 | msgstr "Идентитети" | 1065 | msgstr "Идентитети" |
1066 | 1066 | ||
1067 | msgid "heading.prefs.proxies" | 1067 | msgid "heading.prefs.proxies" |
1068 | msgstr "ПСИ" | 1068 | msgstr "Прс" |
1069 | 1069 | ||
1070 | # Usage: "(count) Unread" in the sidebar tab title, referring to feed entries. | 1070 | # Usage: "(count) Unread" in the sidebar tab title, referring to feed entries. |
1071 | msgid "sidebar.unread" | 1071 | msgid "sidebar.unread" |
@@ -1075,7 +1075,7 @@ msgstr[1] "непрочитана" | |||
1075 | msgstr[2] "непрочитаних" | 1075 | msgstr[2] "непрочитаних" |
1076 | 1076 | ||
1077 | msgid "heading.prefs.scrolling" | 1077 | msgid "heading.prefs.scrolling" |
1078 | msgstr "СЊЕ" | 1078 | msgstr "Срњ" |
1079 | 1079 | ||
1080 | msgid "status.query" | 1080 | msgid "status.query" |
1081 | msgstr "Упит за претрагу" | 1081 | msgstr "Упит за претрагу" |
@@ -1205,7 +1205,7 @@ msgid "prefs.proxy.gopher" | |||
1205 | msgstr "Прокси за Gopher:" | 1205 | msgstr "Прокси за Gopher:" |
1206 | 1206 | ||
1207 | msgid "heading.ident.use" | 1207 | msgid "heading.ident.use" |
1208 | msgstr "УБА ЕТА" | 1208 | msgstr "Утр ттт" |
1209 | 1209 | ||
1210 | msgid "keys.scroll.halfpage.up" | 1210 | msgid "keys.scroll.halfpage.up" |
1211 | msgstr "Скроловање нагоре за пола странице" | 1211 | msgstr "Скроловање нагоре за пола странице" |
@@ -1217,13 +1217,13 @@ msgid "keys.scroll.page.down" | |||
1217 | msgstr "Скроловање надоле за једну страницу" | 1217 | msgstr "Скроловање надоле за једну страницу" |
1218 | 1218 | ||
1219 | msgid "heading.ident.notes" | 1219 | msgid "heading.ident.notes" |
1220 | msgstr "ЗЕ ТЕТУ" | 1220 | msgstr "Зш ттту" |
1221 | 1221 | ||
1222 | msgid "keys.link.modkey" | 1222 | msgid "keys.link.modkey" |
1223 | msgstr "Отварање везе преко тастера модификатора" | 1223 | msgstr "Отварање везе преко тастера модификатора" |
1224 | 1224 | ||
1225 | msgid "heading.ident.delete" | 1225 | msgid "heading.ident.delete" |
1226 | msgstr "БАЊЕ ЕТА" | 1226 | msgstr "Брсњ ттт" |
1227 | 1227 | ||
1228 | msgid "keys.link.homerow.newtab" | 1228 | msgid "keys.link.homerow.newtab" |
1229 | msgstr "Отварање везе у новој картици преко тастера основног реда" | 1229 | msgstr "Отварање везе у новој картици преко тастера основног реда" |
@@ -1277,7 +1277,7 @@ msgid "keys.hoverurl" | |||
1277 | msgstr "Смена приказивања УРЛ-ова при надношењу показивача" | 1277 | msgstr "Смена приказивања УРЛ-ова при надношењу показивача" |
1278 | 1278 | ||
1279 | msgid "heading.save.error" | 1279 | msgid "heading.save.error" |
1280 | msgstr "ГКА И ВАЊУ КЕ" | 1280 | msgstr "Грш р чуњу тт" |
1281 | 1281 | ||
1282 | msgid "error.badstatus.msg" | 1282 | msgid "error.badstatus.msg" |
1283 | msgstr "" | 1283 | msgstr "" |
@@ -1390,7 +1390,7 @@ msgstr[2] "пре %d дана" | |||
1390 | 1390 | ||
1391 | # Link download progress message. | 1391 | # Link download progress message. |
1392 | msgid "doc.fetching" | 1392 | msgid "doc.fetching" |
1393 | msgstr "пање" | 1393 | msgstr "чање" |
1394 | 1394 | ||
1395 | # Inline download status message. | 1395 | # Inline download status message. |
1396 | msgid "media.download.warnclose" | 1396 | msgid "media.download.warnclose" |
@@ -1521,10 +1521,10 @@ msgid "link.side" | |||
1521 | msgstr "Отвори везу са стране" | 1521 | msgstr "Отвори везу са стране" |
1522 | 1522 | ||
1523 | msgid "heading.newident.missing" | 1523 | msgid "heading.newident.missing" |
1524 | msgstr "Н АЦА" | 1524 | msgstr "Н фрц" |
1525 | 1525 | ||
1526 | msgid "heading.newident.date.bad" | 1526 | msgid "heading.newident.date.bad" |
1527 | msgstr "НАН УМ" | 1527 | msgstr "Нср ту" |
1528 | 1528 | ||
1529 | msgid "dlg.newident.date.past" | 1529 | msgid "dlg.newident.date.past" |
1530 | msgstr "Рок трајања мора бити у будућности." | 1530 | msgstr "Рок трајања мора бити у будућности." |
@@ -1696,7 +1696,7 @@ msgid "link.file.delete" | |||
1696 | msgstr "Обриши датотеку" | 1696 | msgstr "Обриши датотеку" |
1697 | 1697 | ||
1698 | msgid "heading.file.delete" | 1698 | msgid "heading.file.delete" |
1699 | msgstr "БАЊЕ КЕ" | 1699 | msgstr "Брсњ тт" |
1700 | 1700 | ||
1701 | msgid "dlg.file.delete.confirm" | 1701 | msgid "dlg.file.delete.confirm" |
1702 | msgstr "Да ли сте сигурни да желите да обришете ову датотеку?" | 1702 | msgstr "Да ли сте сигурни да желите да обришете ову датотеку?" |
@@ -1705,7 +1705,7 @@ msgid "dlg.file.delete" | |||
1705 | msgstr "Обриши" | 1705 | msgstr "Обриши" |
1706 | 1706 | ||
1707 | msgid "heading.prefs.uitheme" | 1707 | msgid "heading.prefs.uitheme" |
1708 | msgstr "БЈЕ ЈСА" | 1708 | msgstr "Б трфјс" |
1709 | 1709 | ||
1710 | msgid "prefs.scrollspeed.keyboard" | 1710 | msgid "prefs.scrollspeed.keyboard" |
1711 | msgstr "Брзина тастатуре:" | 1711 | msgstr "Брзина тастатуре:" |
@@ -1742,7 +1742,7 @@ msgid "upload.port" | |||
1742 | msgstr "Порт…" | 1742 | msgstr "Порт…" |
1743 | 1743 | ||
1744 | msgid "heading.uploadport" | 1744 | msgid "heading.uploadport" |
1745 | msgstr "ПРТ ЊЕ О ТА" | 1745 | msgstr "Прт сњ р Тт" |
1746 | 1746 | ||
1747 | msgid "dlg.uploadport.msg" | 1747 | msgid "dlg.uploadport.msg" |
1748 | msgstr "" | 1748 | msgstr "" |
@@ -1761,10 +1761,10 @@ msgid "prefs.returnkey.accept" | |||
1761 | msgstr "Прихватање" | 1761 | msgstr "Прихватање" |
1762 | 1762 | ||
1763 | msgid "menu.page.upload" | 1763 | msgid "menu.page.upload" |
1764 | msgstr "Пошаљи стрницу реко Титана…" | 1764 | msgstr "Пошаљи преко Титана…" |
1765 | 1765 | ||
1766 | msgid "heading.upload" | 1766 | msgid "heading.upload" |
1767 | msgstr "СЕ О ТА" | 1767 | msgstr "Сњ р Тт" |
1768 | 1768 | ||
1769 | msgid "heading.upload.text" | 1769 | msgid "heading.upload.text" |
1770 | msgstr "Текст" | 1770 | msgstr "Текст" |
@@ -1801,7 +1801,7 @@ msgstr "" | |||
1801 | "Повезивање са сервером је поништено јер је његов TLS сертификат истекао." | 1801 | "Повезивање са сервером је поништено јер је његов TLS сертификат истекао." |
1802 | 1802 | ||
1803 | msgid "keys.upload" | 1803 | msgid "keys.upload" |
1804 | msgstr "Слање стрнице реко Титана" | 1804 | msgstr "Слање преко Титана" |
1805 | 1805 | ||
1806 | msgid "media.untitled.image" | 1806 | msgid "media.untitled.image" |
1807 | msgstr "Слика" | 1807 | msgstr "Слика" |
@@ -1907,7 +1907,7 @@ msgid "menu.undo" | |||
1907 | msgstr "Опозови" | 1907 | msgstr "Опозови" |
1908 | 1908 | ||
1909 | msgid "heading.confirm.bookmarks.delete" | 1909 | msgid "heading.confirm.bookmarks.delete" |
1910 | msgstr "БАЊЕ А" | 1910 | msgstr "Брсњ ч" |
1911 | 1911 | ||
1912 | #, c-format | 1912 | #, c-format |
1913 | msgid "dlg.bookmarks.delete" | 1913 | msgid "dlg.bookmarks.delete" |
@@ -1934,10 +1934,10 @@ msgid "dlg.upload.pickfile" | |||
1934 | msgstr "Изабери датотеку" | 1934 | msgstr "Изабери датотеку" |
1935 | 1935 | ||
1936 | msgid "heading.bookmark.tags" | 1936 | msgid "heading.bookmark.tags" |
1937 | msgstr "ПЕ " | 1937 | msgstr "Пс " |
1938 | 1938 | ||
1939 | msgid "heading.addfolder" | 1939 | msgid "heading.addfolder" |
1940 | msgstr "ДЕ КЛЕ" | 1940 | msgstr "Дњ фсц" |
1941 | 1941 | ||
1942 | msgid "dlg.addfolder.defaulttitle" | 1942 | msgid "dlg.addfolder.defaulttitle" |
1943 | msgstr "Нова фасцикла" | 1943 | msgstr "Нова фасцикла" |
@@ -1947,7 +1947,7 @@ msgstr "Додавање фасцикле" | |||
1947 | 1947 | ||
1948 | # used on mobile | 1948 | # used on mobile |
1949 | msgid "heading.settings" | 1949 | msgid "heading.settings" |
1950 | msgstr "ПЊА" | 1950 | msgstr "Пшњ" |
1951 | 1951 | ||
1952 | msgid "prefs.imagestyle.original" | 1952 | msgid "prefs.imagestyle.original" |
1953 | msgstr "Ниједно" | 1953 | msgstr "Ниједно" |
@@ -1980,7 +1980,7 @@ msgid "lang.isv" | |||
1980 | msgstr "Међусловенски" | 1980 | msgstr "Међусловенски" |
1981 | 1981 | ||
1982 | msgid "heading.fontpack.classic" | 1982 | msgid "heading.fontpack.classic" |
1983 | msgstr "ПАЊЕ А ВА" | 1983 | msgstr "Пруњ т фт" |
1984 | 1984 | ||
1985 | msgid "prefs.gemtext.ansi.fontstyle" | 1985 | msgid "prefs.gemtext.ansi.fontstyle" |
1986 | msgstr "Стил фонта" | 1986 | msgstr "Стил фонта" |
@@ -2087,7 +2087,7 @@ msgid "fontpack.install" | |||
2087 | msgstr "Инсталирај „%s“" | 2087 | msgstr "Инсталирај „%s“" |
2088 | 2088 | ||
2089 | msgid "heading.fontpack.delete" | 2089 | msgid "heading.fontpack.delete" |
2090 | msgstr "БАЊЕ А ВА" | 2090 | msgstr "Брсњ т фт" |
2091 | 2091 | ||
2092 | msgid "dlg.fontpack.delete" | 2092 | msgid "dlg.fontpack.delete" |
2093 | msgstr "Обриши пакет" | 2093 | msgstr "Обриши пакет" |
@@ -2102,7 +2102,7 @@ msgid "truetype.help.installed" | |||
2102 | msgstr "Фонт је инсталиран у корисничком директоријуму са фонтовима." | 2102 | msgstr "Фонт је инсталиран у корисничком директоријуму са фонтовима." |
2103 | 2103 | ||
2104 | msgid "heading.dismiss.warning" | 2104 | msgid "heading.dismiss.warning" |
2105 | msgstr "ОТИ ЕЊЕ?" | 2105 | msgstr "Отт урњ?" |
2106 | 2106 | ||
2107 | msgid "dlg.dismiss.warning" | 2107 | msgid "dlg.dismiss.warning" |
2108 | msgstr "Отклони упозорење" | 2108 | msgstr "Отклони упозорење" |
@@ -2123,7 +2123,7 @@ msgstr "" | |||
2123 | 2123 | ||
2124 | msgid "error.glyphs.msg" | 2124 | msgid "error.glyphs.msg" |
2125 | msgstr "" | 2125 | msgstr "" |
2126 | "Ова страница се не може потпуно приказати јер недостају неки знакови. Можете " | 2126 | "Ова страница се не може потпуно приказати јер недостају неки знаци. Можете " |
2127 | "инсталирати додатне фонтове да бисте то исправили." | 2127 | "инсталирати додатне фонтове да бисте то исправили." |
2128 | 2128 | ||
2129 | # Action label | 2129 | # Action label |
@@ -2189,3 +2189,99 @@ msgstr "24-часовно време" | |||
2189 | # This label should be fairly short so it fits in a button in the sidebar. | 2189 | # This label should be fairly short so it fits in a button in the sidebar. |
2190 | msgid "sidebar.action.feeds.markallread" | 2190 | msgid "sidebar.action.feeds.markallread" |
2191 | msgstr "Прочитај све" | 2191 | msgstr "Прочитај све" |
2192 | |||
2193 | msgid "menu.open.external" | ||
2194 | msgstr "Отвори у другој апликацији" | ||
2195 | |||
2196 | # Active identity toolbar menu. | ||
2197 | msgid "menu.hide.identities" | ||
2198 | msgstr "Сакриј идентитете" | ||
2199 | |||
2200 | msgid "menu.identities" | ||
2201 | msgstr "Управљај идентитетима" | ||
2202 | |||
2203 | msgid "sidebar.action.history.clear" | ||
2204 | msgstr "Очисти" | ||
2205 | |||
2206 | # Paste the line preceding the clicked link into the input prompt. | ||
2207 | msgid "menu.input.precedingline" | ||
2208 | msgstr "Уметни претходни ред" | ||
2209 | |||
2210 | msgid "menu.upload.delete.confirm" | ||
2211 | msgstr "Заиста обриши све (неповратно)" | ||
2212 | |||
2213 | # Mobile subheading: buttons for entering uploaded data. | ||
2214 | msgid "upload.content" | ||
2215 | msgstr "Садржај" | ||
2216 | |||
2217 | msgid "hint.upload.token.long" | ||
2218 | msgstr "токен — видети инструкције сервера" | ||
2219 | |||
2220 | msgid "menu.home" | ||
2221 | msgstr "Иди на почетну" | ||
2222 | |||
2223 | msgid "sidebar.close" | ||
2224 | msgstr "Готово" | ||
2225 | |||
2226 | msgid "sidebar.action.bookmarks.newfolder" | ||
2227 | msgstr "Нова фасцикла" | ||
2228 | |||
2229 | msgid "sidebar.action.bookmarks.edit" | ||
2230 | msgstr "Уреди" | ||
2231 | |||
2232 | # The %s represents the name of an identity. | ||
2233 | #, c-format | ||
2234 | msgid "ident.switch" | ||
2235 | msgstr "Користити %s" | ||
2236 | |||
2237 | msgid "sidebar.empty.unread" | ||
2238 | msgstr "Нема непрочитаних ставки" | ||
2239 | |||
2240 | # Shows where a local file is using the Finder. | ||
2241 | msgid "menu.reveal.macos" | ||
2242 | msgstr "Прикажи у Finder-у" | ||
2243 | |||
2244 | msgid "menu.share" | ||
2245 | msgstr "Подели" | ||
2246 | |||
2247 | msgid "menu.page.upload.edit" | ||
2248 | msgstr "Уреди страницу преко Титана…" | ||
2249 | |||
2250 | msgid "heading.upload.id" | ||
2251 | msgstr "Ауторизација" | ||
2252 | |||
2253 | msgid "menu.upload.export" | ||
2254 | msgstr "Извези текст" | ||
2255 | |||
2256 | msgid "menu.upload.delete" | ||
2257 | msgstr "Обриши све" | ||
2258 | |||
2259 | # Mobile subheading in the Upload dialog. | ||
2260 | msgid "upload.url" | ||
2261 | msgstr "УРЛ" | ||
2262 | |||
2263 | msgid "hint.upload.path" | ||
2264 | msgstr "УРЛ путања" | ||
2265 | |||
2266 | msgid "heading.prefs.toolbaractions" | ||
2267 | msgstr "Акције палете алатки" | ||
2268 | |||
2269 | msgid "prefs.toolbaraction1" | ||
2270 | msgstr "Дугме 1" | ||
2271 | |||
2272 | msgid "prefs.toolbaraction2" | ||
2273 | msgstr "Дугме 2" | ||
2274 | |||
2275 | msgid "prefs.blink" | ||
2276 | msgstr "Трепћући курсор:" | ||
2277 | |||
2278 | msgid "keys.upload.edit" | ||
2279 | msgstr "Уређивање странице преко Титана" | ||
2280 | |||
2281 | # Shows where a local file is using the File Manager. | ||
2282 | msgid "menu.reveal.filemgr" | ||
2283 | msgstr "Прикажи у Менаџеру датотека" | ||
2284 | |||
2285 | # Menu heading shown when customizing navbar button actions. | ||
2286 | msgid "menu.toolbar.setaction" | ||
2287 | msgstr "Постави акцију:" | ||
@@ -1,7 +1,7 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
4 | "PO-Revision-Date: 2021-12-08 14:50+0000\n" | 4 | "PO-Revision-Date: 2022-01-08 14:50+0000\n" |
5 | "Last-Translator: jan Anja <cyber@sysrq.in>\n" | 5 | "Last-Translator: jan Anja <cyber@sysrq.in>\n" |
6 | "Language-Team: Toki Pona <http://weblate.skyjake.fi/projects/lagrange/ui/tok/" | 6 | "Language-Team: Toki Pona <http://weblate.skyjake.fi/projects/lagrange/ui/tok/" |
7 | ">\n" | 7 | ">\n" |
@@ -29,7 +29,7 @@ msgstr "sitelen namako li jo ala e sitelen pona" | |||
29 | 29 | ||
30 | # Link download progress message. | 30 | # Link download progress message. |
31 | msgid "doc.fetching" | 31 | msgid "doc.fetching" |
32 | msgstr "mi kama jo" | 32 | msgstr "lipu li kama" |
33 | 33 | ||
34 | # Inline download status message. | 34 | # Inline download status message. |
35 | msgid "media.download.complete" | 35 | msgid "media.download.complete" |
@@ -389,7 +389,7 @@ msgid "history.clear" | |||
389 | msgstr "o kama sin e sona pi tenpo pini…" | 389 | msgstr "o kama sin e sona pi tenpo pini…" |
390 | 390 | ||
391 | msgid "heading.history.clear" | 391 | msgid "heading.history.clear" |
392 | msgstr "SONA PI TENPO PINI LI WILE KAMA SIN" | 392 | msgstr "o kama sin e sona pi tenpo pini" |
393 | 393 | ||
394 | msgid "ident.using" | 394 | msgid "ident.using" |
395 | msgstr "lipu ni li kepeken e ona" | 395 | msgstr "lipu ni li kepeken e ona" |
@@ -428,25 +428,25 @@ msgid "dlg.ident.notes" | |||
428 | msgstr "toki lili pi nimi %s:" | 428 | msgstr "toki lili pi nimi %s:" |
429 | 429 | ||
430 | msgid "heading.ident.use" | 430 | msgid "heading.ident.use" |
431 | msgstr "LIPU NI LI KEPEKEN E ONA" | 431 | msgstr "lipu ni li kepeken e ona" |
432 | 432 | ||
433 | msgid "menu.edit.notes" | 433 | msgid "menu.edit.notes" |
434 | msgstr "o ante e toki lili…" | 434 | msgstr "o ante e toki lili…" |
435 | 435 | ||
436 | msgid "heading.ident.notes" | 436 | msgid "heading.ident.notes" |
437 | msgstr "TOKI LILI NIMI" | 437 | msgstr "toki lili nimi" |
438 | 438 | ||
439 | msgid "ident.delete" | 439 | msgid "ident.delete" |
440 | msgstr "o weka e ona…" | 440 | msgstr "o weka e ona…" |
441 | 441 | ||
442 | msgid "heading.ident.delete" | 442 | msgid "heading.ident.delete" |
443 | msgstr "NIMI LI WILE TAWA WEKA" | 443 | msgstr "o weka e nimi" |
444 | 444 | ||
445 | msgid "ident.fingerprint" | 445 | msgid "ident.fingerprint" |
446 | msgstr "o kama sona e sitelen len lili" | 446 | msgstr "o kama sona e sitelen len lili" |
447 | 447 | ||
448 | msgid "heading.unsub" | 448 | msgid "heading.unsub" |
449 | msgstr "O PINI LUKIN" | 449 | msgstr "o pini lukin" |
450 | 450 | ||
451 | #, c-format | 451 | #, c-format |
452 | msgid "dlg.confirm.unsub" | 452 | msgid "dlg.confirm.unsub" |
@@ -499,10 +499,10 @@ msgid "dlg.cert.trust" | |||
499 | msgstr "o kepeken" | 499 | msgstr "o kepeken" |
500 | 500 | ||
501 | msgid "heading.save" | 501 | msgid "heading.save" |
502 | msgstr "IJO LI AWEN" | 502 | msgstr "ijo li awen" |
503 | 503 | ||
504 | msgid "heading.save.incomplete" | 504 | msgid "heading.save.incomplete" |
505 | msgstr "LIPU LI WAN ALA" | 505 | msgstr "lipu li wan ala" |
506 | 506 | ||
507 | msgid "dlg.save.incomplete" | 507 | msgid "dlg.save.incomplete" |
508 | msgstr "mi kin kama jo e lipu ni." | 508 | msgstr "mi kin kama jo e lipu ni." |
@@ -511,10 +511,10 @@ msgid "dlg.save.size" | |||
511 | msgstr "suli:" | 511 | msgstr "suli:" |
512 | 512 | ||
513 | msgid "heading.save.error" | 513 | msgid "heading.save.error" |
514 | msgstr "PAKALA! MI AWEN ALA E IJO" | 514 | msgstr "pakala! mi awen ala e ijo" |
515 | 515 | ||
516 | msgid "heading.import.bookmarks" | 516 | msgid "heading.import.bookmarks" |
517 | msgstr "O KAMA JO E LIPU AWEN" | 517 | msgstr "o kama jo e lipu awen" |
518 | 518 | ||
519 | msgid "reload.onceperday" | 519 | msgid "reload.onceperday" |
520 | msgstr "tenpo wan pi tenpo suno" | 520 | msgstr "tenpo wan pi tenpo suno" |
@@ -532,7 +532,7 @@ msgid "link.bookmark" | |||
532 | msgstr "o awen e nimi tawa…" | 532 | msgstr "o awen e nimi tawa…" |
533 | 533 | ||
534 | msgid "heading.openlink" | 534 | msgid "heading.openlink" |
535 | msgstr "O OPEN E NIMI TAWA" | 535 | msgstr "o open e nimi tawa" |
536 | 536 | ||
537 | #, c-format | 537 | #, c-format |
538 | msgid "dlg.import.add" | 538 | msgid "dlg.import.add" |
@@ -546,7 +546,7 @@ msgid "link.noproxy" | |||
546 | msgstr "o open kepeken ala lupa" | 546 | msgstr "o open kepeken ala lupa" |
547 | 547 | ||
548 | msgid "heading.autoreload" | 548 | msgid "heading.autoreload" |
549 | msgstr "O SIN E LIPU" | 549 | msgstr "o sin e lipu" |
550 | 550 | ||
551 | #, c-format | 551 | #, c-format |
552 | msgid "num.minutes" | 552 | msgid "num.minutes" |
@@ -586,16 +586,16 @@ msgid "dlg.certimport.notfound.page" | |||
586 | msgstr "sitelen len en ijo len li lon ala lon lipu ni." | 586 | msgstr "sitelen len en ijo len li lon ala lon lipu ni." |
587 | 587 | ||
588 | msgid "heading.certimport" | 588 | msgid "heading.certimport" |
589 | msgstr "O KAMA JO E NIMI" | 589 | msgstr "o kama jo e nimi" |
590 | 590 | ||
591 | msgid "heading.certimport.pasted" | 591 | msgid "heading.certimport.pasted" |
592 | msgstr "PANA TAN SONA" | 592 | msgstr "pana tan sona" |
593 | 593 | ||
594 | msgid "bookmark.title.blank" | 594 | msgid "bookmark.title.blank" |
595 | msgstr "lipu li jo e ala insa" | 595 | msgstr "lipu li jo e ala insa" |
596 | 596 | ||
597 | msgid "heading.certimport.dropped" | 597 | msgid "heading.certimport.dropped" |
598 | msgstr "TAWA TAN IJO" | 598 | msgstr "tawa tan ijo" |
599 | 599 | ||
600 | msgid "dlg.certimport.import" | 600 | msgid "dlg.certimport.import" |
601 | msgstr "o kama jo" | 601 | msgstr "o kama jo" |
@@ -610,10 +610,10 @@ msgid "dlg.certimport.nokey" | |||
610 | msgstr "ijo len li lon ala" | 610 | msgstr "ijo len li lon ala" |
611 | 611 | ||
612 | msgid "link.hint.audio" | 612 | msgid "link.hint.audio" |
613 | msgstr "o kute e kalama musi" | 613 | msgstr "kalama musi" |
614 | 614 | ||
615 | msgid "link.hint.image" | 615 | msgid "link.hint.image" |
616 | msgstr "o lukin e sitelen" | 616 | msgstr "sitelen" |
617 | 617 | ||
618 | # Interpret as "Results from bookmarks..." | 618 | # Interpret as "Results from bookmarks..." |
619 | msgid "heading.lookup.bookmarks" | 619 | msgid "heading.lookup.bookmarks" |
@@ -632,7 +632,7 @@ msgid "heading.lookup.other" | |||
632 | msgstr "TAN IJO ANTE" | 632 | msgstr "TAN IJO ANTE" |
633 | 633 | ||
634 | msgid "heading.translate" | 634 | msgid "heading.translate" |
635 | msgstr "O TOKI ANTE E LIPU" | 635 | msgstr "o toki ante e lipu" |
636 | 636 | ||
637 | msgid "dlg.translate.fail" | 637 | msgid "dlg.translate.fail" |
638 | msgstr "toki li pakala" | 638 | msgstr "toki li pakala" |
@@ -719,7 +719,7 @@ msgid "dlg.newident.create" | |||
719 | msgstr "o pali e nimi" | 719 | msgstr "o pali e nimi" |
720 | 720 | ||
721 | msgid "heading.subscribe" | 721 | msgid "heading.subscribe" |
722 | msgstr "O LUKIN E IJO SIN LON LIPU" | 722 | msgstr "o lukin e ijo sin lon lipu" |
723 | 723 | ||
724 | msgid "dlg.feed.title" | 724 | msgid "dlg.feed.title" |
725 | msgstr "nimi:" | 725 | msgstr "nimi:" |
@@ -740,10 +740,10 @@ msgid "dlg.feed.sub" | |||
740 | msgstr "o lukin" | 740 | msgstr "o lukin" |
741 | 741 | ||
742 | msgid "heading.bookmark.add" | 742 | msgid "heading.bookmark.add" |
743 | msgstr "O AWEN E LIPU" | 743 | msgstr "o awen e lipu" |
744 | 744 | ||
745 | msgid "heading.bookmark.edit" | 745 | msgid "heading.bookmark.edit" |
746 | msgstr "O ANTE E LIPU AWEN" | 746 | msgstr "o ante e lipu awen" |
747 | 747 | ||
748 | msgid "dlg.bookmark.save" | 748 | msgid "dlg.bookmark.save" |
749 | msgstr "o awen" | 749 | msgstr "o awen" |
@@ -758,7 +758,7 @@ msgid "dlg.bookmark.icon" | |||
758 | msgstr "sitelen:" | 758 | msgstr "sitelen:" |
759 | 759 | ||
760 | msgid "heading.prefs" | 760 | msgid "heading.prefs" |
761 | msgstr "IJO ANTE" | 761 | msgstr "ijo ante" |
762 | 762 | ||
763 | # tab button | 763 | # tab button |
764 | msgid "heading.prefs.colors" | 764 | msgid "heading.prefs.colors" |
@@ -780,7 +780,7 @@ msgid "heading.prefs.network" | |||
780 | msgstr "kon" | 780 | msgstr "kon" |
781 | 781 | ||
782 | msgid "heading.prefs.paragraph" | 782 | msgid "heading.prefs.paragraph" |
783 | msgstr "WAN SITELEN" | 783 | msgstr "wan sitelen" |
784 | 784 | ||
785 | msgid "prefs.searchurl" | 785 | msgid "prefs.searchurl" |
786 | msgstr "nimi tawa lukin e nimi:" | 786 | msgstr "nimi tawa lukin e nimi:" |
@@ -801,19 +801,19 @@ msgid "prefs.hidetoolbarscroll" | |||
801 | msgstr "tawa la mi len e ijo sewi:" | 801 | msgstr "tawa la mi len e ijo sewi:" |
802 | 802 | ||
803 | msgid "heading.prefs.pagecontent" | 803 | msgid "heading.prefs.pagecontent" |
804 | msgstr "KULE LIPU" | 804 | msgstr "kule lipu" |
805 | 805 | ||
806 | msgid "heading.prefs.proxies" | 806 | msgid "heading.prefs.proxies" |
807 | msgstr "LUPA" | 807 | msgstr "lupa" |
808 | 808 | ||
809 | msgid "heading.prefs.scrolling" | 809 | msgid "heading.prefs.scrolling" |
810 | msgstr "TAWA" | 810 | msgstr "tawa" |
811 | 811 | ||
812 | msgid "heading.prefs.sizing" | 812 | msgid "heading.prefs.sizing" |
813 | msgstr "SULI" | 813 | msgstr "suli" |
814 | 814 | ||
815 | msgid "heading.prefs.widelayout" | 815 | msgid "heading.prefs.widelayout" |
816 | msgstr "SULI MUTE" | 816 | msgstr "suli mute" |
817 | 817 | ||
818 | # tab button | 818 | # tab button |
819 | msgid "heading.prefs.style" | 819 | msgid "heading.prefs.style" |
@@ -1288,7 +1288,7 @@ msgid "ident.gotohelp" | |||
1288 | msgstr "sina wile kama sona e sitelen jan pi ilo TLS la o lukin e %ssona%s." | 1288 | msgstr "sina wile kama sona e sitelen jan pi ilo TLS la o lukin e %ssona%s." |
1289 | 1289 | ||
1290 | msgid "heading.pageinfo" | 1290 | msgid "heading.pageinfo" |
1291 | msgstr "SONA PI LIPU NI" | 1291 | msgstr "sona pi lipu ni" |
1292 | 1292 | ||
1293 | msgid "pageinfo.header.cached" | 1293 | msgid "pageinfo.header.cached" |
1294 | msgstr "(ijo lon sona pi tenpo pini lili)" | 1294 | msgstr "(ijo lon sona pi tenpo pini lili)" |
@@ -1317,7 +1317,7 @@ msgid "dlg.newident.rsa.selfsign" | |||
1317 | msgstr "sina ken pali e sitelen len kepeken e sitelen RSA pi tu 2048." | 1317 | msgstr "sina ken pali e sitelen len kepeken e sitelen RSA pi tu 2048." |
1318 | 1318 | ||
1319 | msgid "heading.prefs.certs" | 1319 | msgid "heading.prefs.certs" |
1320 | msgstr "SITELEN LEN MUTE" | 1320 | msgstr "sitelen len mute" |
1321 | 1321 | ||
1322 | msgid "error.cert.needed" | 1322 | msgid "error.cert.needed" |
1323 | msgstr "sina wile e sitelen len" | 1323 | msgstr "sina wile e sitelen len" |
@@ -1348,7 +1348,7 @@ msgid "lang.hi" | |||
1348 | msgstr "toki Insi" | 1348 | msgstr "toki Insi" |
1349 | 1349 | ||
1350 | msgid "heading.newident" | 1350 | msgid "heading.newident" |
1351 | msgstr "NIMI SIN" | 1351 | msgstr "nimi sin" |
1352 | 1352 | ||
1353 | msgid "dlg.newident.domain" | 1353 | msgid "dlg.newident.domain" |
1354 | msgstr "nimi pi ilo sona:" | 1354 | msgstr "nimi pi ilo sona:" |
@@ -1357,7 +1357,7 @@ msgid "dlg.newident.country" | |||
1357 | msgstr "ma:" | 1357 | msgstr "ma:" |
1358 | 1358 | ||
1359 | msgid "heading.feedcfg" | 1359 | msgid "heading.feedcfg" |
1360 | msgstr "IJO ANTE PI LIPU LUKIN" | 1360 | msgstr "ijo ante pi lipu lukin" |
1361 | 1361 | ||
1362 | msgid "dlg.bookmark.tags" | 1362 | msgid "dlg.bookmark.tags" |
1363 | msgstr "nimi lili:" | 1363 | msgstr "nimi lili:" |
@@ -1448,7 +1448,7 @@ msgid "dlg.save.opendownload" | |||
1448 | msgstr "o open e ijo" | 1448 | msgstr "o open e ijo" |
1449 | 1449 | ||
1450 | msgid "heading.newident.date.bad" | 1450 | msgid "heading.newident.date.bad" |
1451 | msgstr "TENPO LI PAKALA" | 1451 | msgstr "tenpo li pakala" |
1452 | 1452 | ||
1453 | msgid "gempub.cover.aboutbook" | 1453 | msgid "gempub.cover.aboutbook" |
1454 | msgstr "sona lipu" | 1454 | msgstr "sona lipu" |
@@ -1476,7 +1476,7 @@ msgid "lang.ia" | |||
1476 | msgstr "toki Intelinwa" | 1476 | msgstr "toki Intelinwa" |
1477 | 1477 | ||
1478 | msgid "heading.newident.missing" | 1478 | msgid "heading.newident.missing" |
1479 | msgstr "NIMI LI LON ALA" | 1479 | msgstr "nimi li lon ala" |
1480 | 1480 | ||
1481 | msgid "link.side" | 1481 | msgid "link.side" |
1482 | msgstr "o open e nimi tawa lon poki ante" | 1482 | msgstr "o open e nimi tawa lon poki ante" |
@@ -1611,7 +1611,7 @@ msgid "dlg.newident.scope" | |||
1611 | msgstr "o kepeken e ona lon:" | 1611 | msgstr "o kepeken e ona lon:" |
1612 | 1612 | ||
1613 | msgid "keys.upload" | 1613 | msgid "keys.upload" |
1614 | msgstr "o pana e lipu kepeken kon Tetan" | 1614 | msgstr "o pana kepeken kon Tetan" |
1615 | 1615 | ||
1616 | msgid "menu.pageinfo" | 1616 | msgid "menu.pageinfo" |
1617 | msgstr "o lukin e sona lipu" | 1617 | msgstr "o lukin e sona lipu" |
@@ -1629,7 +1629,7 @@ msgid "upload.port" | |||
1629 | msgstr "nanpa kon…" | 1629 | msgstr "nanpa kon…" |
1630 | 1630 | ||
1631 | msgid "heading.uploadport" | 1631 | msgid "heading.uploadport" |
1632 | msgstr "NANPA PI KON TETAN" | 1632 | msgstr "nanpa pi kon Tetan" |
1633 | 1633 | ||
1634 | msgid "dlg.uploadport.set" | 1634 | msgid "dlg.uploadport.set" |
1635 | msgstr "o ante" | 1635 | msgstr "o ante" |
@@ -1640,13 +1640,13 @@ msgstr "" | |||
1640 | "wile awen." | 1640 | "wile awen." |
1641 | 1641 | ||
1642 | msgid "heading.prefs.uitheme" | 1642 | msgid "heading.prefs.uitheme" |
1643 | msgstr "KULE PI ILO LAKELAN" | 1643 | msgstr "kule pi ilo Lakelan" |
1644 | 1644 | ||
1645 | msgid "link.file.delete" | 1645 | msgid "link.file.delete" |
1646 | msgstr "o weka e ijo" | 1646 | msgstr "o weka e ijo" |
1647 | 1647 | ||
1648 | msgid "heading.file.delete" | 1648 | msgid "heading.file.delete" |
1649 | msgstr "O WEKA E IJO" | 1649 | msgstr "o weka e ijo" |
1650 | 1650 | ||
1651 | msgid "dlg.file.delete.confirm" | 1651 | msgid "dlg.file.delete.confirm" |
1652 | msgstr "sina wile ala wile weka e ijo ni?" | 1652 | msgstr "sina wile ala wile weka e ijo ni?" |
@@ -1655,10 +1655,10 @@ msgid "dlg.file.delete" | |||
1655 | msgstr "o weka" | 1655 | msgstr "o weka" |
1656 | 1656 | ||
1657 | msgid "heading.upload" | 1657 | msgid "heading.upload" |
1658 | msgstr "O PANA KEPEKEN KON TETAN" | 1658 | msgstr "o pana kepeken kon Tetan" |
1659 | 1659 | ||
1660 | msgid "menu.page.upload" | 1660 | msgid "menu.page.upload" |
1661 | msgstr "o pana e lipu kepeken kon Tetan…" | 1661 | msgstr "o pana kepeken kon Tetan…" |
1662 | 1662 | ||
1663 | msgid "heading.upload.text" | 1663 | msgid "heading.upload.text" |
1664 | msgstr "lipu" | 1664 | msgstr "lipu" |
@@ -1768,7 +1768,7 @@ msgid "dlg.upload.pickfile" | |||
1768 | msgstr "open e ijo" | 1768 | msgstr "open e ijo" |
1769 | 1769 | ||
1770 | msgid "heading.addfolder" | 1770 | msgid "heading.addfolder" |
1771 | msgstr "O PALI E POKI" | 1771 | msgstr "o pali e poki" |
1772 | 1772 | ||
1773 | msgid "dlg.addfolder.defaulttitle" | 1773 | msgid "dlg.addfolder.defaulttitle" |
1774 | msgstr "poki sin" | 1774 | msgstr "poki sin" |
@@ -1781,7 +1781,7 @@ msgstr "o pali e poki" | |||
1781 | 1781 | ||
1782 | # used on mobile | 1782 | # used on mobile |
1783 | msgid "heading.settings" | 1783 | msgid "heading.settings" |
1784 | msgstr "IJO ANTE" | 1784 | msgstr "ijo ante" |
1785 | 1785 | ||
1786 | msgid "prefs.imagestyle" | 1786 | msgid "prefs.imagestyle" |
1787 | msgstr "o kule e sitelen:" | 1787 | msgstr "o kule e sitelen:" |
@@ -1806,7 +1806,7 @@ msgid "keys.bookmark.addfolder" | |||
1806 | msgstr "o pali e poki tawa lipu awen" | 1806 | msgstr "o pali e poki tawa lipu awen" |
1807 | 1807 | ||
1808 | msgid "heading.bookmark.tags" | 1808 | msgid "heading.bookmark.tags" |
1809 | msgstr "NIMI LILI NAMAKO" | 1809 | msgstr "nimi lili namako" |
1810 | 1810 | ||
1811 | msgid "media.untitled.image" | 1811 | msgid "media.untitled.image" |
1812 | msgstr "sitelen" | 1812 | msgstr "sitelen" |
@@ -1834,7 +1834,7 @@ msgid "menu.undo" | |||
1834 | msgstr "o ante monsi" | 1834 | msgstr "o ante monsi" |
1835 | 1835 | ||
1836 | msgid "heading.confirm.bookmarks.delete" | 1836 | msgid "heading.confirm.bookmarks.delete" |
1837 | msgstr "LIPU AWEN LI WILE KAMA WEKA" | 1837 | msgstr "o weka e lipu awen" |
1838 | 1838 | ||
1839 | #, c-format | 1839 | #, c-format |
1840 | msgid "dlg.confirm.bookmarks.delete" | 1840 | msgid "dlg.confirm.bookmarks.delete" |
@@ -1854,7 +1854,7 @@ msgid "dlg.fontpack.classic" | |||
1854 | msgstr "o kama jo e kulupu sitelen (tu suli meka 25)" | 1854 | msgstr "o kama jo e kulupu sitelen (tu suli meka 25)" |
1855 | 1855 | ||
1856 | msgid "heading.fontpack.classic" | 1856 | msgid "heading.fontpack.classic" |
1857 | msgstr "O KAMA JO E KULUPU SITELEN" | 1857 | msgstr "o kama jo e kulupu sitelen" |
1858 | 1858 | ||
1859 | #, c-format | 1859 | #, c-format |
1860 | msgid "fontpack.install" | 1860 | msgid "fontpack.install" |
@@ -1972,7 +1972,7 @@ msgid "fontpack.delete" | |||
1972 | msgstr "o weka e kulupu sitelen \"%s\"" | 1972 | msgstr "o weka e kulupu sitelen \"%s\"" |
1973 | 1973 | ||
1974 | msgid "heading.fontpack.delete" | 1974 | msgid "heading.fontpack.delete" |
1975 | msgstr "KULUPU SITELEN LI KAMA WEKA" | 1975 | msgstr "o weka e kulupu sitelen" |
1976 | 1976 | ||
1977 | #, c-format | 1977 | #, c-format |
1978 | msgid "dlg.fontpack.delete.confirm" | 1978 | msgid "dlg.fontpack.delete.confirm" |
@@ -2068,3 +2068,56 @@ msgstr "poki:" | |||
2068 | 2068 | ||
2069 | msgid "sidebar.action.show" | 2069 | msgid "sidebar.action.show" |
2070 | msgstr "o len ala:" | 2070 | msgstr "o len ala:" |
2071 | |||
2072 | msgid "menu.open.external" | ||
2073 | msgstr "o open kepeken ilo ante" | ||
2074 | |||
2075 | msgid "sidebar.action.history.clear" | ||
2076 | msgstr "o weka" | ||
2077 | |||
2078 | msgid "keys.upload.edit" | ||
2079 | msgstr "o ante e lipu kepeken kon Tetan" | ||
2080 | |||
2081 | # Active identity toolbar menu. | ||
2082 | msgid "menu.hide.identities" | ||
2083 | msgstr "o len e nimi" | ||
2084 | |||
2085 | msgid "menu.home" | ||
2086 | msgstr "o tawa lipu tomo" | ||
2087 | |||
2088 | msgid "menu.identities" | ||
2089 | msgstr "o ante e nimi" | ||
2090 | |||
2091 | msgid "sidebar.close" | ||
2092 | msgstr "awen" | ||
2093 | |||
2094 | msgid "sidebar.action.bookmarks.newfolder" | ||
2095 | msgstr "poki sin" | ||
2096 | |||
2097 | msgid "sidebar.action.bookmarks.edit" | ||
2098 | msgstr "o ante" | ||
2099 | |||
2100 | # The %s represents the name of an identity. | ||
2101 | #, c-format | ||
2102 | msgid "ident.switch" | ||
2103 | msgstr "o kepeken e nimi %s" | ||
2104 | |||
2105 | msgid "menu.share" | ||
2106 | msgstr "o pana" | ||
2107 | |||
2108 | msgid "menu.page.upload.edit" | ||
2109 | msgstr "o ante e lipu kepeken kon Tetan…" | ||
2110 | |||
2111 | # Shows where a local file is using the Finder. | ||
2112 | msgid "menu.reveal.macos" | ||
2113 | msgstr "o lukin kepeken ilo Finder" | ||
2114 | |||
2115 | msgid "menu.upload.delete" | ||
2116 | msgstr "o weka e ijo ale" | ||
2117 | |||
2118 | msgid "menu.upload.delete.confirm" | ||
2119 | msgstr "sina wile ala wile weka e ijo ale? sina ken ala ante monsi" | ||
2120 | |||
2121 | # Mobile subheading in the Upload dialog. | ||
2122 | msgid "upload.url" | ||
2123 | msgstr "nimi tawa" | ||
@@ -1,8 +1,8 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
4 | "PO-Revision-Date: 2021-12-04 10:50+0000\n" | 4 | "PO-Revision-Date: 2022-01-20 04:05+0000\n" |
5 | "Last-Translator: Emir <emir_sari@msn.com>\n" | 5 | "Last-Translator: Emir SARI <emir_sari@icloud.com>\n" |
6 | "Language-Team: Turkish <http://weblate.skyjake.fi/projects/lagrange/ui/tr/>\n" | 6 | "Language-Team: Turkish <http://weblate.skyjake.fi/projects/lagrange/ui/tr/>\n" |
7 | "Language: tr\n" | 7 | "Language: tr\n" |
8 | "MIME-Version: 1.0\n" | 8 | "MIME-Version: 1.0\n" |
@@ -13,7 +13,7 @@ msgstr "" | |||
13 | 13 | ||
14 | # Link download progress message. | 14 | # Link download progress message. |
15 | msgid "doc.fetching" | 15 | msgid "doc.fetching" |
16 | msgstr "Getiriliyor" | 16 | msgstr "Yükleniyor" |
17 | 17 | ||
18 | msgid "doc.archive.view" | 18 | msgid "doc.archive.view" |
19 | msgstr "Arşiv içeriğini görüntüle" | 19 | msgstr "Arşiv içeriğini görüntüle" |
@@ -392,7 +392,7 @@ msgid "history.clear" | |||
392 | msgstr "Geçmişi temizle…" | 392 | msgstr "Geçmişi temizle…" |
393 | 393 | ||
394 | msgid "heading.history.clear" | 394 | msgid "heading.history.clear" |
395 | msgstr "GEMİİ TEMİZLE" | 395 | msgstr "Gemii temizle" |
396 | 396 | ||
397 | msgid "dlg.history.clear" | 397 | msgid "dlg.history.clear" |
398 | msgstr "Geçmişi temizle" | 398 | msgstr "Geçmişi temizle" |
@@ -425,7 +425,7 @@ msgid "bookmarks.reload" | |||
425 | msgstr "Uzak konum kaynaklarını yenile" | 425 | msgstr "Uzak konum kaynaklarını yenile" |
426 | 426 | ||
427 | msgid "ident.using" | 427 | msgid "ident.using" |
428 | msgstr "Bu sayfada kullanılıyor" | 428 | msgstr "Bu sayfada kullan" |
429 | 429 | ||
430 | msgid "ident.notused" | 430 | msgid "ident.notused" |
431 | msgstr "Kullanılmıyor" | 431 | msgstr "Kullanılmıyor" |
@@ -454,13 +454,13 @@ msgid "ident.export" | |||
454 | msgstr "Dışa aktar" | 454 | msgstr "Dışa aktar" |
455 | 455 | ||
456 | msgid "heading.ident.use" | 456 | msgid "heading.ident.use" |
457 | msgstr "KMLK KULLANIMI" | 457 | msgstr "Kimlik kullanm" |
458 | 458 | ||
459 | msgid "menu.edit.notes" | 459 | msgid "menu.edit.notes" |
460 | msgstr "Notları düzenle…" | 460 | msgstr "Notları düzenle…" |
461 | 461 | ||
462 | msgid "heading.ident.notes" | 462 | msgid "heading.ident.notes" |
463 | msgstr "KİMLİK NOTLARI" | 463 | msgstr "Kimlik notlar" |
464 | 464 | ||
465 | msgid "ident.fingerprint" | 465 | msgid "ident.fingerprint" |
466 | msgstr "Parmak izini kopyala" | 466 | msgstr "Parmak izini kopyala" |
@@ -469,7 +469,7 @@ msgid "ident.delete" | |||
469 | msgstr "Kimliği sil…" | 469 | msgstr "Kimliği sil…" |
470 | 470 | ||
471 | msgid "heading.ident.delete" | 471 | msgid "heading.ident.delete" |
472 | msgstr "KMLİĞİ SİL" | 472 | msgstr "Kimlii sil" |
473 | 473 | ||
474 | #, c-format | 474 | #, c-format |
475 | msgid "dlg.confirm.ident.delete" | 475 | msgid "dlg.confirm.ident.delete" |
@@ -484,7 +484,7 @@ msgid "sidebar.empty.idents" | |||
484 | msgstr "Kimlik yok" | 484 | msgstr "Kimlik yok" |
485 | 485 | ||
486 | msgid "heading.unsub" | 486 | msgid "heading.unsub" |
487 | msgstr "ABONELİKTEN IK" | 487 | msgstr "Abonelikten ık" |
488 | 488 | ||
489 | #, c-format | 489 | #, c-format |
490 | msgid "dlg.confirm.unsub" | 490 | msgid "dlg.confirm.unsub" |
@@ -494,7 +494,7 @@ msgid "dlg.unsub" | |||
494 | msgstr "Abonelikten çık" | 494 | msgstr "Abonelikten çık" |
495 | 495 | ||
496 | msgid "heading.pageinfo" | 496 | msgid "heading.pageinfo" |
497 | msgstr "SAYFA BİLGİSİ" | 497 | msgstr "Sayfa bilgisi" |
498 | 498 | ||
499 | msgid "pageinfo.header.cached" | 499 | msgid "pageinfo.header.cached" |
500 | msgstr "(önbelleklenmiş içerik)" | 500 | msgstr "(önbelleklenmiş içerik)" |
@@ -534,7 +534,7 @@ msgid "dlg.input.linebreak" | |||
534 | msgstr "Satır sonu" | 534 | msgstr "Satır sonu" |
535 | 535 | ||
536 | msgid "heading.save" | 536 | msgid "heading.save" |
537 | msgstr "DOSYA KAYDEDİLDİ" | 537 | msgstr "Dosya kaydedildi" |
538 | 538 | ||
539 | msgid "dlg.save.incomplete" | 539 | msgid "dlg.save.incomplete" |
540 | msgstr "Sayfa içeriği hâlâ indiriliyor." | 540 | msgstr "Sayfa içeriği hâlâ indiriliyor." |
@@ -546,7 +546,7 @@ msgid "dlg.save.opendownload" | |||
546 | msgstr "İndirilen dosyayı aç" | 546 | msgstr "İndirilen dosyayı aç" |
547 | 547 | ||
548 | msgid "heading.import.bookmarks" | 548 | msgid "heading.import.bookmarks" |
549 | msgstr "YER İMLERİNİ İE AKTAR" | 549 | msgstr "Yer imlerini ie aktar" |
550 | 550 | ||
551 | #, c-format | 551 | #, c-format |
552 | msgid "dlg.import.add" | 552 | msgid "dlg.import.add" |
@@ -555,7 +555,7 @@ msgstr[0] "%sYer imi ekle" | |||
555 | msgstr[1] "%s%d yer imi ekle" | 555 | msgstr[1] "%s%d yer imi ekle" |
556 | 556 | ||
557 | msgid "heading.autoreload" | 557 | msgid "heading.autoreload" |
558 | msgstr "OTOMATİK YENİDEN YKLEME" | 558 | msgstr "Otomatik yeniden ykleme" |
559 | 559 | ||
560 | msgid "reload.never" | 560 | msgid "reload.never" |
561 | msgstr "Hiçbir zaman" | 561 | msgstr "Hiçbir zaman" |
@@ -597,10 +597,10 @@ msgid "dlg.file.delete" | |||
597 | msgstr "Sil" | 597 | msgstr "Sil" |
598 | 598 | ||
599 | msgid "heading.openlink" | 599 | msgid "heading.openlink" |
600 | msgstr "BALANTI A" | 600 | msgstr "Balantı a" |
601 | 601 | ||
602 | msgid "heading.file.delete" | 602 | msgid "heading.file.delete" |
603 | msgstr "DOSYA SL" | 603 | msgstr "Dosyay sil" |
604 | 604 | ||
605 | #, c-format | 605 | #, c-format |
606 | msgid "dlg.openlink.confirm" | 606 | msgid "dlg.openlink.confirm" |
@@ -623,7 +623,7 @@ msgstr "" | |||
623 | "sorunu olabilir." | 623 | "sorunu olabilir." |
624 | 624 | ||
625 | msgid "heading.certimport" | 625 | msgid "heading.certimport" |
626 | msgstr "KİMLİK İE AKTAR" | 626 | msgstr "Kimlik ie aktar" |
627 | 627 | ||
628 | msgid "dlg.certimport.notfound" | 628 | msgid "dlg.certimport.notfound" |
629 | msgstr "Bir sertifika veya gizli anahtar bulunamadı." | 629 | msgstr "Bir sertifika veya gizli anahtar bulunamadı." |
@@ -632,10 +632,10 @@ msgid "dlg.certimport.notfound.page" | |||
632 | msgstr "Geçerli sayfada bir sertifika/anahtar bulunamadı." | 632 | msgstr "Geçerli sayfada bir sertifika/anahtar bulunamadı." |
633 | 633 | ||
634 | msgid "heading.certimport.pasted" | 634 | msgid "heading.certimport.pasted" |
635 | msgstr "PANODAN YAPITIRILMI" | 635 | msgstr "Panodan yapıtırılmı" |
636 | 636 | ||
637 | msgid "heading.certimport.dropped" | 637 | msgid "heading.certimport.dropped" |
638 | msgstr "BIRAKILAN DOSYA" | 638 | msgstr "Bırakılan dosya" |
639 | 639 | ||
640 | msgid "dlg.certimport.import" | 640 | msgid "dlg.certimport.import" |
641 | msgstr "İçe aktar" | 641 | msgstr "İçe aktar" |
@@ -650,10 +650,10 @@ msgid "dlg.certimport.nokey" | |||
650 | msgstr "Gizli anahtar yok" | 650 | msgstr "Gizli anahtar yok" |
651 | 651 | ||
652 | msgid "link.hint.audio" | 652 | msgid "link.hint.audio" |
653 | msgstr "Ses çal" | 653 | msgstr "Ses" |
654 | 654 | ||
655 | msgid "link.hint.image" | 655 | msgid "link.hint.image" |
656 | msgstr "Görsel görüntüle" | 656 | msgstr "Görsel" |
657 | 657 | ||
658 | msgid "bookmark.title.blank" | 658 | msgid "bookmark.title.blank" |
659 | msgstr "Boş sayfa" | 659 | msgstr "Boş sayfa" |
@@ -701,7 +701,7 @@ msgid "heading.lookup.other" | |||
701 | msgstr "DİĞER" | 701 | msgstr "DİĞER" |
702 | 702 | ||
703 | msgid "heading.upload" | 703 | msgid "heading.upload" |
704 | msgstr "TITAN İLE GNDER" | 704 | msgstr "Titan ile gnder" |
705 | 705 | ||
706 | msgid "upload.id" | 706 | msgid "upload.id" |
707 | msgstr "Kimlik:" | 707 | msgstr "Kimlik:" |
@@ -737,7 +737,7 @@ msgid "upload.port" | |||
737 | msgstr "Kapı…" | 737 | msgstr "Kapı…" |
738 | 738 | ||
739 | msgid "heading.uploadport" | 739 | msgid "heading.uploadport" |
740 | msgstr "TITAN GNDERME KAPISI" | 740 | msgstr "Titan gnderim kapısı" |
741 | 741 | ||
742 | msgid "dlg.uploadport.set" | 742 | msgid "dlg.uploadport.set" |
743 | msgstr "Kapı ayarla" | 743 | msgstr "Kapı ayarla" |
@@ -755,7 +755,7 @@ msgid "dlg.upload.pickfile" | |||
755 | msgstr "Dosya seç" | 755 | msgstr "Dosya seç" |
756 | 756 | ||
757 | msgid "heading.translate" | 757 | msgid "heading.translate" |
758 | msgstr "SAYFAYI EVİR" | 758 | msgstr "Sayfayı evir" |
759 | 759 | ||
760 | msgid "dlg.translate.fail" | 760 | msgid "dlg.translate.fail" |
761 | msgstr "İstek başarısız oldu" | 761 | msgstr "İstek başarısız oldu" |
@@ -798,7 +798,7 @@ msgid "lang.ru" | |||
798 | msgstr "Rusça" | 798 | msgstr "Rusça" |
799 | 799 | ||
800 | msgid "heading.newident" | 800 | msgid "heading.newident" |
801 | msgstr "YENİ KİMLİK" | 801 | msgstr "Yeni kimlik" |
802 | 802 | ||
803 | msgid "dlg.newident.until" | 803 | msgid "dlg.newident.until" |
804 | msgstr "Şu tarihe kadar geçerli:" | 804 | msgstr "Şu tarihe kadar geçerli:" |
@@ -837,19 +837,19 @@ msgid "dlg.newident.more" | |||
837 | msgstr "Daha fazla…" | 837 | msgstr "Daha fazla…" |
838 | 838 | ||
839 | msgid "heading.newident.missing" | 839 | msgid "heading.newident.missing" |
840 | msgstr "EKSİK BİLGİ" | 840 | msgstr "Bilgi eksik" |
841 | 841 | ||
842 | msgid "dlg.newindent.missing.commonname" | 842 | msgid "dlg.newindent.missing.commonname" |
843 | msgstr "Bir \"Ortak ad\" belirtilmelidir." | 843 | msgstr "Bir \"Ortak ad\" belirtilmelidir." |
844 | 844 | ||
845 | msgid "heading.newident.date.bad" | 845 | msgid "heading.newident.date.bad" |
846 | msgstr "GEERSİZ TARİH" | 846 | msgstr "Geersiz tarih" |
847 | 847 | ||
848 | msgid "heading.feedcfg" | 848 | msgid "heading.feedcfg" |
849 | msgstr "BESLEME AYARLARI" | 849 | msgstr "Besleme ayarları" |
850 | 850 | ||
851 | msgid "heading.subscribe" | 851 | msgid "heading.subscribe" |
852 | msgstr "SAYFAYA ABONE OL" | 852 | msgstr "Sayfaya abone ol" |
853 | 853 | ||
854 | msgid "dlg.feed.title" | 854 | msgid "dlg.feed.title" |
855 | msgstr "Başlık:" | 855 | msgstr "Başlık:" |
@@ -870,10 +870,10 @@ msgid "dlg.feed.sub" | |||
870 | msgstr "Abone ol" | 870 | msgstr "Abone ol" |
871 | 871 | ||
872 | msgid "heading.bookmark.add" | 872 | msgid "heading.bookmark.add" |
873 | msgstr "YER İMİ EKLE" | 873 | msgstr "Yer imi ekle" |
874 | 874 | ||
875 | msgid "heading.bookmark.edit" | 875 | msgid "heading.bookmark.edit" |
876 | msgstr "YER İMİNİ DZENLE" | 876 | msgstr "Yer imini dzenle" |
877 | 877 | ||
878 | msgid "dlg.bookmark.title" | 878 | msgid "dlg.bookmark.title" |
879 | msgstr "Başlık:" | 879 | msgstr "Başlık:" |
@@ -888,10 +888,10 @@ msgid "dlg.bookmark.icon" | |||
888 | msgstr "Simge:" | 888 | msgstr "Simge:" |
889 | 889 | ||
890 | msgid "heading.bookmark.tags" | 890 | msgid "heading.bookmark.tags" |
891 | msgstr "ÖZEL ETİKETLER" | 891 | msgstr "Özel etiketler" |
892 | 892 | ||
893 | msgid "heading.addfolder" | 893 | msgid "heading.addfolder" |
894 | msgstr "KLASR EKLE" | 894 | msgstr "Klasr ekle" |
895 | 895 | ||
896 | msgid "dlg.addfolder.prompt" | 896 | msgid "dlg.addfolder.prompt" |
897 | msgstr "Yeni klasörün adını girin:" | 897 | msgstr "Yeni klasörün adını girin:" |
@@ -900,14 +900,14 @@ msgid "dlg.addfolder" | |||
900 | msgstr "Klasör ekle" | 900 | msgstr "Klasör ekle" |
901 | 901 | ||
902 | msgid "heading.prefs" | 902 | msgid "heading.prefs" |
903 | msgstr "TERCİHLER" | 903 | msgstr "Tercihler" |
904 | 904 | ||
905 | # used on mobile | 905 | # used on mobile |
906 | msgid "heading.settings" | 906 | msgid "heading.settings" |
907 | msgstr "AYARLAR" | 907 | msgstr "Ayarlar" |
908 | 908 | ||
909 | msgid "heading.prefs.certs" | 909 | msgid "heading.prefs.certs" |
910 | msgstr "SERTİFİKALAR" | 910 | msgstr "Sertifikalar" |
911 | 911 | ||
912 | msgid "heading.prefs.fonts" | 912 | msgid "heading.prefs.fonts" |
913 | msgstr "Yazıtipleri" | 913 | msgstr "Yazıtipleri" |
@@ -929,19 +929,19 @@ msgid "heading.prefs.network" | |||
929 | msgstr "Ağ" | 929 | msgstr "Ağ" |
930 | 930 | ||
931 | msgid "heading.prefs.paragraph" | 931 | msgid "heading.prefs.paragraph" |
932 | msgstr "PARAGRAF" | 932 | msgstr "Paragraf" |
933 | 933 | ||
934 | msgid "heading.prefs.pagecontent" | 934 | msgid "heading.prefs.pagecontent" |
935 | msgstr "SAYFA RENKLERİ" | 935 | msgstr "Sayfa renkleri" |
936 | 936 | ||
937 | msgid "heading.prefs.proxies" | 937 | msgid "heading.prefs.proxies" |
938 | msgstr "VEKİLLER" | 938 | msgstr "Vekiller" |
939 | 939 | ||
940 | msgid "heading.prefs.scrolling" | 940 | msgid "heading.prefs.scrolling" |
941 | msgstr "KAYDIRMA" | 941 | msgstr "Kaydırma" |
942 | 942 | ||
943 | msgid "heading.prefs.sizing" | 943 | msgid "heading.prefs.sizing" |
944 | msgstr "BOYUTLANDIRMA" | 944 | msgstr "Boyutlandırma" |
945 | 945 | ||
946 | # tab button | 946 | # tab button |
947 | msgid "heading.prefs.style" | 947 | msgid "heading.prefs.style" |
@@ -1193,7 +1193,7 @@ msgid "keys.split.next" | |||
1193 | msgstr "Odağı bir sonraki bölüntüye taşı" | 1193 | msgstr "Odağı bir sonraki bölüntüye taşı" |
1194 | 1194 | ||
1195 | msgid "keys.upload" | 1195 | msgid "keys.upload" |
1196 | msgstr "Sayfayı Titan ile gönder" | 1196 | msgstr "Titan ile gönder" |
1197 | 1197 | ||
1198 | msgid "error.openfile.msg" | 1198 | msgid "error.openfile.msg" |
1199 | msgstr "" | 1199 | msgstr "" |
@@ -1412,7 +1412,7 @@ msgid "fontpack.delete" | |||
1412 | msgstr "\"%s\" ögesini kalıcı olarak sil" | 1412 | msgstr "\"%s\" ögesini kalıcı olarak sil" |
1413 | 1413 | ||
1414 | msgid "heading.fontpack.delete" | 1414 | msgid "heading.fontpack.delete" |
1415 | msgstr "YAZITPİ PAKETİNİ SİL" | 1415 | msgstr "Yaztipi paketini sil" |
1416 | 1416 | ||
1417 | msgid "dlg.fontpack.delete" | 1417 | msgid "dlg.fontpack.delete" |
1418 | msgstr "Yazıtipi paketini sil" | 1418 | msgstr "Yazıtipi paketini sil" |
@@ -1435,7 +1435,7 @@ msgid "truetype.help.installed" | |||
1435 | msgstr "Bu yazıtipi, kullanıcı yazıtipleri dizinine yüklenmiş." | 1435 | msgstr "Bu yazıtipi, kullanıcı yazıtipleri dizinine yüklenmiş." |
1436 | 1436 | ||
1437 | msgid "heading.dismiss.warning" | 1437 | msgid "heading.dismiss.warning" |
1438 | msgstr "UYARIYI ATLA?" | 1438 | msgstr "Uyarı atlansın mı?" |
1439 | 1439 | ||
1440 | #, c-format | 1440 | #, c-format |
1441 | msgid "dlg.dismiss.ansi" | 1441 | msgid "dlg.dismiss.ansi" |
@@ -1445,7 +1445,7 @@ msgid "dlg.dismiss.warning" | |||
1445 | msgstr "Uyarıyı atla" | 1445 | msgstr "Uyarıyı atla" |
1446 | 1446 | ||
1447 | msgid "heading.fontpack.classic" | 1447 | msgid "heading.fontpack.classic" |
1448 | msgstr "YAZITPİ PAKETİNİ İNDİR" | 1448 | msgstr "Yaztipi paketini indir" |
1449 | 1449 | ||
1450 | msgid "dlg.fontpack.classic" | 1450 | msgid "dlg.fontpack.classic" |
1451 | msgstr "Yazıtipi paketini indir (25 MB)" | 1451 | msgstr "Yazıtipi paketini indir (25 MB)" |
@@ -1596,8 +1596,8 @@ msgstr "KB" | |||
1596 | #, c-format | 1596 | #, c-format |
1597 | msgid "page.timestamp" | 1597 | msgid "page.timestamp" |
1598 | msgstr "" | 1598 | msgstr "" |
1599 | "Alınma tarihi ve zamanı: %I.%M %p,\n" | 1599 | "Alındı: %d %b %Y,\n" |
1600 | "%d %b %Y" | 1600 | "%I.%M %p" |
1601 | 1601 | ||
1602 | msgid "feeds.entry.markread" | 1602 | msgid "feeds.entry.markread" |
1603 | msgstr "Okundu olarak imle" | 1603 | msgstr "Okundu olarak imle" |
@@ -1621,7 +1621,7 @@ msgstr "" | |||
1621 | "Tüm ziyaret edilen sayfalar geçmişini temizlemek istediğinizden emin misiniz?" | 1621 | "Tüm ziyaret edilen sayfalar geçmişini temizlemek istediğinizden emin misiniz?" |
1622 | 1622 | ||
1623 | msgid "heading.confirm.bookmarks.delete" | 1623 | msgid "heading.confirm.bookmarks.delete" |
1624 | msgstr "YER İMLERİNİ SİL" | 1624 | msgstr "Yer imlerini sil" |
1625 | 1625 | ||
1626 | #, c-format | 1626 | #, c-format |
1627 | msgid "dlg.confirm.bookmarks.delete" | 1627 | msgid "dlg.confirm.bookmarks.delete" |
@@ -1669,10 +1669,10 @@ msgid "dlg.input.send" | |||
1669 | msgstr "Gönder" | 1669 | msgstr "Gönder" |
1670 | 1670 | ||
1671 | msgid "heading.save.incomplete" | 1671 | msgid "heading.save.incomplete" |
1672 | msgstr "SAYFA TAM DEİL" | 1672 | msgstr "Sayfa tam deil" |
1673 | 1673 | ||
1674 | msgid "heading.save.error" | 1674 | msgid "heading.save.error" |
1675 | msgstr "DOSYA KAYDEDİLİRKEN HATA" | 1675 | msgstr "Dosya kaydedilirken hata" |
1676 | 1676 | ||
1677 | #, c-format | 1677 | #, c-format |
1678 | msgid "dlg.import.found" | 1678 | msgid "dlg.import.found" |
@@ -1760,7 +1760,7 @@ msgid "heading.lookup.pagecontent" | |||
1760 | msgstr "SAYFA İÇERİĞİ" | 1760 | msgstr "SAYFA İÇERİĞİ" |
1761 | 1761 | ||
1762 | msgid "menu.page.upload" | 1762 | msgid "menu.page.upload" |
1763 | msgstr "Sayfayı Titan ile gönder…" | 1763 | msgstr "Titan ile gönder…" |
1764 | 1764 | ||
1765 | msgid "hint.upload.text" | 1765 | msgid "hint.upload.text" |
1766 | msgstr "gönderilecek metni girin" | 1766 | msgstr "gönderilecek metni girin" |
@@ -1837,10 +1837,10 @@ msgid "heading.prefs.colors" | |||
1837 | msgstr "Renkler" | 1837 | msgstr "Renkler" |
1838 | 1838 | ||
1839 | msgid "heading.prefs.uitheme" | 1839 | msgid "heading.prefs.uitheme" |
1840 | msgstr "KULLANICI ARABİRİMİ RENKLERİ" | 1840 | msgstr "Arabirim renkleri" |
1841 | 1841 | ||
1842 | msgid "heading.prefs.widelayout" | 1842 | msgid "heading.prefs.widelayout" |
1843 | msgstr "GENİ DİZİLİM" | 1843 | msgstr "Geni dizilim" |
1844 | 1844 | ||
1845 | msgid "prefs.collapsepreonload" | 1845 | msgid "prefs.collapsepreonload" |
1846 | msgstr "Önceden biçimlendirilenleri daralt:" | 1846 | msgstr "Önceden biçimlendirilenleri daralt:" |
@@ -2094,3 +2094,99 @@ msgstr "24 saat biçimi" | |||
2094 | # This label should be fairly short so it fits in a button in the sidebar. | 2094 | # This label should be fairly short so it fits in a button in the sidebar. |
2095 | msgid "sidebar.action.feeds.markallread" | 2095 | msgid "sidebar.action.feeds.markallread" |
2096 | msgstr "Tümünü oku" | 2096 | msgstr "Tümünü oku" |
2097 | |||
2098 | msgid "menu.home" | ||
2099 | msgstr "Ana sayfaya git" | ||
2100 | |||
2101 | msgid "menu.identities" | ||
2102 | msgstr "Kimlikleri yönet" | ||
2103 | |||
2104 | msgid "prefs.toolbaraction1" | ||
2105 | msgstr "Düğme 1" | ||
2106 | |||
2107 | msgid "prefs.toolbaraction2" | ||
2108 | msgstr "Düğme 2" | ||
2109 | |||
2110 | msgid "prefs.blink" | ||
2111 | msgstr "Yanıp sönen imleç:" | ||
2112 | |||
2113 | msgid "sidebar.close" | ||
2114 | msgstr "Tamam" | ||
2115 | |||
2116 | # The %s represents the name of an identity. | ||
2117 | #, c-format | ||
2118 | msgid "ident.switch" | ||
2119 | msgstr "%s kullan" | ||
2120 | |||
2121 | msgid "menu.upload.delete.confirm" | ||
2122 | msgstr "Tümünü gerçekten sil (geri alınamaz)" | ||
2123 | |||
2124 | msgid "keys.upload.edit" | ||
2125 | msgstr "Sayfayı Titan ile düzenle" | ||
2126 | |||
2127 | msgid "menu.open.external" | ||
2128 | msgstr "Başka bir uygulamada aç" | ||
2129 | |||
2130 | # Active identity toolbar menu. | ||
2131 | msgid "menu.hide.identities" | ||
2132 | msgstr "Kimlikleri gizle" | ||
2133 | |||
2134 | msgid "sidebar.action.bookmarks.newfolder" | ||
2135 | msgstr "Yeni klasör" | ||
2136 | |||
2137 | msgid "sidebar.action.bookmarks.edit" | ||
2138 | msgstr "Düzenle" | ||
2139 | |||
2140 | msgid "sidebar.action.history.clear" | ||
2141 | msgstr "Temizle" | ||
2142 | |||
2143 | msgid "sidebar.empty.unread" | ||
2144 | msgstr "Okunmamış girdi yok" | ||
2145 | |||
2146 | # Paste the line preceding the clicked link into the input prompt. | ||
2147 | msgid "menu.input.precedingline" | ||
2148 | msgstr "Önceki satırı yapıştır" | ||
2149 | |||
2150 | # Shows where a local file is using the Finder. | ||
2151 | msgid "menu.reveal.macos" | ||
2152 | msgstr "Finder'da göster" | ||
2153 | |||
2154 | msgid "menu.share" | ||
2155 | msgstr "Paylaş" | ||
2156 | |||
2157 | msgid "menu.page.upload.edit" | ||
2158 | msgstr "Sayfayı Titan ile düzenle…" | ||
2159 | |||
2160 | msgid "heading.upload.id" | ||
2161 | msgstr "Kimlik doğrulama" | ||
2162 | |||
2163 | msgid "menu.upload.export" | ||
2164 | msgstr "Metni dışa aktar" | ||
2165 | |||
2166 | msgid "menu.upload.delete" | ||
2167 | msgstr "Tümünü sil" | ||
2168 | |||
2169 | # Mobile subheading in the Upload dialog. | ||
2170 | msgid "upload.url" | ||
2171 | msgstr "URL" | ||
2172 | |||
2173 | # Mobile subheading: buttons for entering uploaded data. | ||
2174 | msgid "upload.content" | ||
2175 | msgstr "İçerik" | ||
2176 | |||
2177 | msgid "hint.upload.path" | ||
2178 | msgstr "URL yolu" | ||
2179 | |||
2180 | msgid "hint.upload.token.long" | ||
2181 | msgstr "jeton — sunucunun yönergelerine bakın" | ||
2182 | |||
2183 | msgid "heading.prefs.toolbaractions" | ||
2184 | msgstr "Araç çubuğu eylemleri" | ||
2185 | |||
2186 | # Menu heading shown when customizing navbar button actions. | ||
2187 | msgid "menu.toolbar.setaction" | ||
2188 | msgstr "Eylem Ayarla:" | ||
2189 | |||
2190 | # Shows where a local file is using the File Manager. | ||
2191 | msgid "menu.reveal.filemgr" | ||
2192 | msgstr "Dosya Yöneticisinde Göster" | ||
@@ -1,7 +1,7 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" | 3 | "Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" |
4 | "PO-Revision-Date: 2021-12-23 11:10+0000\n" | 4 | "PO-Revision-Date: 2022-01-18 17:50+0000\n" |
5 | "Last-Translator: Alyssa Liddell <e-liss@tuta.io>\n" | 5 | "Last-Translator: Alyssa Liddell <e-liss@tuta.io>\n" |
6 | "Language-Team: Ukrainian <http://weblate.skyjake.fi/projects/lagrange/ui/uk/>" | 6 | "Language-Team: Ukrainian <http://weblate.skyjake.fi/projects/lagrange/ui/uk/>" |
7 | "\n" | 7 | "\n" |
@@ -524,13 +524,13 @@ msgid "pageinfo.domain.match" | |||
524 | msgstr "Доменне ім'я збігається" | 524 | msgstr "Доменне ім'я збігається" |
525 | 525 | ||
526 | msgid "heading.save" | 526 | msgid "heading.save" |
527 | msgstr "Ф ЗО" | 527 | msgstr "Ф Зр" |
528 | 528 | ||
529 | msgid "heading.save.incomplete" | 529 | msgid "heading.save.incomplete" |
530 | msgstr "СНКА " | 530 | msgstr "Стрі " |
531 | 531 | ||
532 | msgid "dlg.save.incomplete" | 532 | msgid "dlg.save.incomplete" |
533 | msgstr "ЗНЯ ЇЇ ІСТУ ДОВЖУЄТЬСЯ." | 533 | msgstr "Зтя її істу щ тр." |
534 | 534 | ||
535 | #, c-format | 535 | #, c-format |
536 | msgid "dlg.import.found" | 536 | msgid "dlg.import.found" |
@@ -556,7 +556,7 @@ msgid "link.browser" | |||
556 | msgstr "Відкрити посилання у основному переглядачі" | 556 | msgstr "Відкрити посилання у основному переглядачі" |
557 | 557 | ||
558 | msgid "heading.file.delete" | 558 | msgid "heading.file.delete" |
559 | msgstr "ВИ Л" | 559 | msgstr "Вт ф" |
560 | 560 | ||
561 | msgid "dlg.file.delete.confirm" | 561 | msgid "dlg.file.delete.confirm" |
562 | msgstr "Справді бажаєте видалити цей файл?" | 562 | msgstr "Справді бажаєте видалити цей файл?" |
@@ -631,13 +631,13 @@ msgid "history.clear" | |||
631 | msgstr "Очистити історію…" | 631 | msgstr "Очистити історію…" |
632 | 632 | ||
633 | msgid "heading.history.clear" | 633 | msgid "heading.history.clear" |
634 | msgstr "ЧНЯ СТОРІЇ" | 634 | msgstr "Чщя істрії" |
635 | 635 | ||
636 | msgid "dlg.history.clear" | 636 | msgid "dlg.history.clear" |
637 | msgstr "Очистити історію" | 637 | msgstr "Очистити історію" |
638 | 638 | ||
639 | msgid "heading.confirm.bookmarks.delete" | 639 | msgid "heading.confirm.bookmarks.delete" |
640 | msgstr "В " | 640 | msgstr "Вя " |
641 | 641 | ||
642 | #, c-format | 642 | #, c-format |
643 | msgid "dlg.bookmarks.delete" | 643 | msgid "dlg.bookmarks.delete" |
@@ -688,10 +688,10 @@ msgid "ident.export" | |||
688 | msgstr "Експортувати" | 688 | msgstr "Експортувати" |
689 | 689 | ||
690 | msgid "heading.ident.use" | 690 | msgid "heading.ident.use" |
691 | msgstr "ВАННЯ ЧНОСТІ" | 691 | msgstr "Врстя ітчсті" |
692 | 692 | ||
693 | msgid "heading.ident.notes" | 693 | msgid "heading.ident.notes" |
694 | msgstr "ПТКИ НОСТІ" | 694 | msgstr "Пріт р ітчість" |
695 | 695 | ||
696 | # %s refers to name of an identity. | 696 | # %s refers to name of an identity. |
697 | #, c-format | 697 | #, c-format |
@@ -711,7 +711,7 @@ msgid "error.server.msg" | |||
711 | msgstr "Сервер повернув таке повідомлення:" | 711 | msgstr "Сервер повернув таке повідомлення:" |
712 | 712 | ||
713 | msgid "heading.pageinfo" | 713 | msgid "heading.pageinfo" |
714 | msgstr "ІМАЦЯ ПО СІНКУ" | 714 | msgstr "Іфрія Пр Стріу" |
715 | 715 | ||
716 | msgid "pageinfo.cert.ca.verified" | 716 | msgid "pageinfo.cert.ca.verified" |
717 | msgstr "Підтверджено ЦС" | 717 | msgstr "Підтверджено ЦС" |
@@ -754,16 +754,16 @@ msgid "dlg.save.opendownload" | |||
754 | msgstr "Відкрити завантажений файл" | 754 | msgstr "Відкрити завантажений файл" |
755 | 755 | ||
756 | msgid "heading.save.error" | 756 | msgid "heading.save.error" |
757 | msgstr "П НЯ ЛУ" | 757 | msgstr "П ря фу" |
758 | 758 | ||
759 | msgid "heading.import.bookmarks" | 759 | msgid "heading.import.bookmarks" |
760 | msgstr "ІРТ " | 760 | msgstr "Ірт " |
761 | 761 | ||
762 | msgid "dlg.import.notnew" | 762 | msgid "dlg.import.notnew" |
763 | msgstr "Усі посилання на сторінці вже додані до закладок." | 763 | msgstr "Усі посилання на сторінці вже додані до закладок." |
764 | 764 | ||
765 | msgid "heading.autoreload" | 765 | msgid "heading.autoreload" |
766 | msgstr "АНЯ" | 766 | msgstr "Атя" |
767 | 767 | ||
768 | msgid "reload.never" | 768 | msgid "reload.never" |
769 | msgstr "Ніколи" | 769 | msgstr "Ніколи" |
@@ -813,7 +813,7 @@ msgid "dlg.file.delete" | |||
813 | msgstr "Видалити" | 813 | msgstr "Видалити" |
814 | 814 | ||
815 | msgid "heading.openlink" | 815 | msgid "heading.openlink" |
816 | msgstr "ВИТИ НЯ" | 816 | msgstr "Вірт ся" |
817 | 817 | ||
818 | #, c-format | 818 | #, c-format |
819 | msgid "dlg.openlink.confirm" | 819 | msgid "dlg.openlink.confirm" |
@@ -839,16 +839,16 @@ msgid "dlg.certwarn.domain.expired" | |||
839 | msgstr "Отриманий сертифікат протермінований ТА належить іншому домену." | 839 | msgstr "Отриманий сертифікат протермінований ТА належить іншому домену." |
840 | 840 | ||
841 | msgid "heading.certimport" | 841 | msgid "heading.certimport" |
842 | msgstr "ІРТ ІНОСТІ" | 842 | msgstr "Ірт Ітчсті" |
843 | 843 | ||
844 | msgid "dlg.certimport.notfound" | 844 | msgid "dlg.certimport.notfound" |
845 | msgstr "Не вдалося знайти сертифікат або закритий ключ." | 845 | msgstr "Не вдалося знайти сертифікат або закритий ключ." |
846 | 846 | ||
847 | msgid "heading.certimport.pasted" | 847 | msgid "heading.certimport.pasted" |
848 | msgstr "ВНО ЕРА НУ" | 848 | msgstr "Вст уфр іу" |
849 | 849 | ||
850 | msgid "heading.certimport.dropped" | 850 | msgid "heading.certimport.dropped" |
851 | msgstr "ПГНУТО Л" | 851 | msgstr "Пртяут ф" |
852 | 852 | ||
853 | # button in the mobile New Identity dialog | 853 | # button in the mobile New Identity dialog |
854 | msgid "dlg.certimport.pickfile" | 854 | msgid "dlg.certimport.pickfile" |
@@ -870,10 +870,10 @@ msgid "dlg.certimport.nokey" | |||
870 | msgstr "Закритий ключ відсутній" | 870 | msgstr "Закритий ключ відсутній" |
871 | 871 | ||
872 | msgid "link.hint.audio" | 872 | msgid "link.hint.audio" |
873 | msgstr "дрт уі" | 873 | msgstr "доас" |
874 | 874 | ||
875 | msgid "link.hint.image" | 875 | msgid "link.hint.image" |
876 | msgstr "оазати зораження" | 876 | msgstr "ображення" |
877 | 877 | ||
878 | msgid "bookmark.title.blank" | 878 | msgid "bookmark.title.blank" |
879 | msgstr "Порожня сторінка" | 879 | msgstr "Порожня сторінка" |
@@ -926,7 +926,7 @@ msgid "ident.delete" | |||
926 | msgstr "Видалити ідентичність…" | 926 | msgstr "Видалити ідентичність…" |
927 | 927 | ||
928 | msgid "heading.ident.delete" | 928 | msgid "heading.ident.delete" |
929 | msgstr "ВЯ ЧНОСТІ" | 929 | msgstr "Вя ітчсті" |
930 | 930 | ||
931 | #, c-format | 931 | #, c-format |
932 | msgid "dlg.confirm.ident.delete" | 932 | msgid "dlg.confirm.ident.delete" |
@@ -942,7 +942,7 @@ msgstr "" | |||
942 | "сертифікати." | 942 | "сертифікати." |
943 | 943 | ||
944 | msgid "heading.unsub" | 944 | msgid "heading.unsub" |
945 | msgstr "ВКА" | 945 | msgstr "Віс" |
946 | 946 | ||
947 | #, c-format | 947 | #, c-format |
948 | msgid "dlg.confirm.unsub" | 948 | msgid "dlg.confirm.unsub" |
@@ -997,7 +997,7 @@ msgid "heading.lookup.other" | |||
997 | msgstr "ІНШІ" | 997 | msgstr "ІНШІ" |
998 | 998 | ||
999 | msgid "heading.upload" | 999 | msgid "heading.upload" |
1000 | msgstr "ПЧА Ю TITAN" | 1000 | msgstr "Прч ю Titan" |
1001 | 1001 | ||
1002 | msgid "upload.id" | 1002 | msgid "upload.id" |
1003 | msgstr "Ідентичність:" | 1003 | msgstr "Ідентичність:" |
@@ -1026,7 +1026,7 @@ msgid "heading.lookup.pagecontent" | |||
1026 | msgstr "ВМІСТ СТОРІНКИ" | 1026 | msgstr "ВМІСТ СТОРІНКИ" |
1027 | 1027 | ||
1028 | msgid "menu.page.upload" | 1028 | msgid "menu.page.upload" |
1029 | msgstr "Передати сторіу Titan…" | 1029 | msgstr "Передати а догою Titan…" |
1030 | 1030 | ||
1031 | msgid "hint.upload.text" | 1031 | msgid "hint.upload.text" |
1032 | msgstr "Введіть текст для передачі" | 1032 | msgstr "Введіть текст для передачі" |
@@ -1050,7 +1050,7 @@ msgid "upload.port" | |||
1050 | msgstr "Порт…" | 1050 | msgstr "Порт…" |
1051 | 1051 | ||
1052 | msgid "heading.uploadport" | 1052 | msgid "heading.uploadport" |
1053 | msgstr "ПРТ АЧІ TITAN" | 1053 | msgstr "Прт рчі Titan" |
1054 | 1054 | ||
1055 | msgid "dlg.uploadport.set" | 1055 | msgid "dlg.uploadport.set" |
1056 | msgstr "Вказати порт" | 1056 | msgstr "Вказати порт" |
@@ -1060,7 +1060,7 @@ msgid "dlg.upload.pickfile" | |||
1060 | msgstr "Вибрати файл" | 1060 | msgstr "Вибрати файл" |
1061 | 1061 | ||
1062 | msgid "heading.translate" | 1062 | msgid "heading.translate" |
1063 | msgstr "ПД ІНКИ" | 1063 | msgstr "Пр стрі" |
1064 | 1064 | ||
1065 | msgid "heading.upload.file" | 1065 | msgid "heading.upload.file" |
1066 | msgstr "Файл" | 1066 | msgstr "Файл" |
@@ -1172,7 +1172,7 @@ msgid "lang.tok" | |||
1172 | msgstr "Токі-пона" | 1172 | msgstr "Токі-пона" |
1173 | 1173 | ||
1174 | msgid "heading.newident" | 1174 | msgid "heading.newident" |
1175 | msgstr "Н ИЧНІСТЬ" | 1175 | msgstr "Н ітчість" |
1176 | 1176 | ||
1177 | msgid "dlg.newident.rsa.selfsign" | 1177 | msgid "dlg.newident.rsa.selfsign" |
1178 | msgstr "Створення самопідписаного 2048-розрядного RSA сертифікату." | 1178 | msgstr "Створення самопідписаного 2048-розрядного RSA сертифікату." |
@@ -1223,7 +1223,7 @@ msgid "dlg.newident.more" | |||
1223 | msgstr "Ще…" | 1223 | msgstr "Ще…" |
1224 | 1224 | ||
1225 | msgid "heading.newident.missing" | 1225 | msgid "heading.newident.missing" |
1226 | msgstr "ВСУТНЯ РМАЦЯ" | 1226 | msgstr "Вісутя іфрія" |
1227 | 1227 | ||
1228 | msgid "dlg.newindent.missing.commonname" | 1228 | msgid "dlg.newindent.missing.commonname" |
1229 | msgstr "Потрібно вказати звичайне ім'я." | 1229 | msgstr "Потрібно вказати звичайне ім'я." |
@@ -1236,10 +1236,10 @@ msgstr "" | |||
1236 | "• 2021-12-31 23:59:59" | 1236 | "• 2021-12-31 23:59:59" |
1237 | 1237 | ||
1238 | msgid "heading.feedcfg" | 1238 | msgid "heading.feedcfg" |
1239 | msgstr "НАННЯ РІЧКИ" | 1239 | msgstr "Нштуя стіч" |
1240 | 1240 | ||
1241 | msgid "heading.subscribe" | 1241 | msgid "heading.subscribe" |
1242 | msgstr "ПКА РІНКУ" | 1242 | msgstr "Піс стріу" |
1243 | 1243 | ||
1244 | msgid "dlg.feed.title" | 1244 | msgid "dlg.feed.title" |
1245 | msgstr "Заголовок:" | 1245 | msgstr "Заголовок:" |
@@ -1254,7 +1254,7 @@ msgid "dlg.feed.sub" | |||
1254 | msgstr "Підписатися" | 1254 | msgstr "Підписатися" |
1255 | 1255 | ||
1256 | msgid "heading.bookmark.add" | 1256 | msgid "heading.bookmark.add" |
1257 | msgstr "ДИ У" | 1257 | msgstr "Дт у" |
1258 | 1258 | ||
1259 | msgid "dlg.bookmark.save" | 1259 | msgid "dlg.bookmark.save" |
1260 | msgstr "Збереги закладку" | 1260 | msgstr "Збереги закладку" |
@@ -1269,7 +1269,7 @@ msgid "dlg.newident.create" | |||
1269 | msgstr "Створити ідентичність" | 1269 | msgstr "Створити ідентичність" |
1270 | 1270 | ||
1271 | msgid "heading.newident.date.bad" | 1271 | msgid "heading.newident.date.bad" |
1272 | msgstr "ННА А" | 1272 | msgstr "Ніс т" |
1273 | 1273 | ||
1274 | msgid "dlg.newident.date.past" | 1274 | msgid "dlg.newident.date.past" |
1275 | msgstr "Термін дії має закінчуватися у майбутньому." | 1275 | msgstr "Термін дії має закінчуватися у майбутньому." |
@@ -1281,7 +1281,7 @@ msgid "dlg.feed.type.headings" | |||
1281 | msgstr "Нові заголовки" | 1281 | msgstr "Нові заголовки" |
1282 | 1282 | ||
1283 | msgid "heading.bookmark.edit" | 1283 | msgid "heading.bookmark.edit" |
1284 | msgstr "РНЯ " | 1284 | msgstr "Руя " |
1285 | 1285 | ||
1286 | msgid "dlg.bookmark.url" | 1286 | msgid "dlg.bookmark.url" |
1287 | msgstr "URL:" | 1287 | msgstr "URL:" |
@@ -1293,10 +1293,10 @@ msgid "dlg.bookmark.icon" | |||
1293 | msgstr "Значок:" | 1293 | msgstr "Значок:" |
1294 | 1294 | ||
1295 | msgid "heading.bookmark.tags" | 1295 | msgid "heading.bookmark.tags" |
1296 | msgstr "ОВІ КИ" | 1296 | msgstr "Осі іт" |
1297 | 1297 | ||
1298 | msgid "heading.addfolder" | 1298 | msgid "heading.addfolder" |
1299 | msgstr "ДИ КУ" | 1299 | msgstr "Дт ту" |
1300 | 1300 | ||
1301 | msgid "dlg.addfolder.defaulttitle" | 1301 | msgid "dlg.addfolder.defaulttitle" |
1302 | msgstr "Нова тека" | 1302 | msgstr "Нова тека" |
@@ -1305,7 +1305,7 @@ msgid "dlg.addfolder.prompt" | |||
1305 | msgstr "Назва створюваної теки:" | 1305 | msgstr "Назва створюваної теки:" |
1306 | 1306 | ||
1307 | msgid "heading.prefs.certs" | 1307 | msgid "heading.prefs.certs" |
1308 | msgstr "СІКАТИ" | 1308 | msgstr "Сртфіт" |
1309 | 1309 | ||
1310 | # tab button | 1310 | # tab button |
1311 | msgid "heading.prefs.colors" | 1311 | msgid "heading.prefs.colors" |
@@ -1318,11 +1318,11 @@ msgid "dlg.addfolder" | |||
1318 | msgstr "Додати теку" | 1318 | msgstr "Додати теку" |
1319 | 1319 | ||
1320 | msgid "heading.prefs" | 1320 | msgid "heading.prefs" |
1321 | msgstr "НАННЯ" | 1321 | msgstr "Нштуя" |
1322 | 1322 | ||
1323 | # used on mobile | 1323 | # used on mobile |
1324 | msgid "heading.settings" | 1324 | msgid "heading.settings" |
1325 | msgstr "НАННЯ" | 1325 | msgstr "Нштуя" |
1326 | 1326 | ||
1327 | # tab button | 1327 | # tab button |
1328 | msgid "heading.prefs.general" | 1328 | msgid "heading.prefs.general" |
@@ -1341,22 +1341,22 @@ msgid "heading.prefs.network" | |||
1341 | msgstr "Мережа" | 1341 | msgstr "Мережа" |
1342 | 1342 | ||
1343 | msgid "heading.prefs.paragraph" | 1343 | msgid "heading.prefs.paragraph" |
1344 | msgstr "АЦ" | 1344 | msgstr "Ац" |
1345 | 1345 | ||
1346 | msgid "heading.prefs.uitheme" | 1346 | msgid "heading.prefs.uitheme" |
1347 | msgstr "КРИ ІК" | 1347 | msgstr "Кьр ІК" |
1348 | 1348 | ||
1349 | msgid "heading.prefs.pagecontent" | 1349 | msgid "heading.prefs.pagecontent" |
1350 | msgstr "КРИ ІНКИ" | 1350 | msgstr "Кьр стрі" |
1351 | 1351 | ||
1352 | msgid "heading.prefs.proxies" | 1352 | msgid "heading.prefs.proxies" |
1353 | msgstr "ПКСІ" | 1353 | msgstr "Прсі" |
1354 | 1354 | ||
1355 | msgid "heading.prefs.scrolling" | 1355 | msgid "heading.prefs.scrolling" |
1356 | msgstr "ПУВАННЯ" | 1356 | msgstr "Прручуя" |
1357 | 1357 | ||
1358 | msgid "heading.prefs.sizing" | 1358 | msgid "heading.prefs.sizing" |
1359 | msgstr "РРИ" | 1359 | msgstr "Рір" |
1360 | 1360 | ||
1361 | # tab button | 1361 | # tab button |
1362 | msgid "heading.prefs.style" | 1362 | msgid "heading.prefs.style" |
@@ -1373,7 +1373,7 @@ msgid "prefs.hoverlink" | |||
1373 | msgstr "Показувати URL при наведенні:" | 1373 | msgstr "Показувати URL при наведенні:" |
1374 | 1374 | ||
1375 | msgid "heading.prefs.widelayout" | 1375 | msgid "heading.prefs.widelayout" |
1376 | msgstr "ШЙ Т" | 1376 | msgstr "Шр т" |
1377 | 1377 | ||
1378 | msgid "prefs.searchurl" | 1378 | msgid "prefs.searchurl" |
1379 | msgstr "URL пошуку:" | 1379 | msgstr "URL пошуку:" |
@@ -1807,7 +1807,7 @@ msgid "keys.split.item" | |||
1807 | msgstr "Меню розділеного перегляду:" | 1807 | msgstr "Меню розділеного перегляду:" |
1808 | 1808 | ||
1809 | msgid "keys.upload" | 1809 | msgid "keys.upload" |
1810 | msgstr "Передати сторіу Titan" | 1810 | msgstr "Передати а догою Titan" |
1811 | 1811 | ||
1812 | msgid "error.badstatus" | 1812 | msgid "error.badstatus" |
1813 | msgstr "Невідомий код стану" | 1813 | msgstr "Невідомий код стану" |
@@ -2016,7 +2016,7 @@ msgid "prefs.bookmarks.addbottom" | |||
2016 | msgstr "Додати закладки у кінець списку:" | 2016 | msgstr "Додати закладки у кінець списку:" |
2017 | 2017 | ||
2018 | msgid "prefs.font.ui" | 2018 | msgid "prefs.font.ui" |
2019 | msgstr "ІК:" | 2019 | msgstr "Ітерфейс ористувача:" |
2020 | 2020 | ||
2021 | msgid "prefs.font.heading" | 2021 | msgid "prefs.font.heading" |
2022 | msgstr "Заголовки:" | 2022 | msgstr "Заголовки:" |
@@ -2103,7 +2103,7 @@ msgid "fontpack.delete" | |||
2103 | msgstr "Назавжди видалити \"%s\"" | 2103 | msgstr "Назавжди видалити \"%s\"" |
2104 | 2104 | ||
2105 | msgid "heading.fontpack.delete" | 2105 | msgid "heading.fontpack.delete" |
2106 | msgstr "ВЯ ТУ ИФТІВ" | 2106 | msgstr "Вя ту шрфті" |
2107 | 2107 | ||
2108 | msgid "dlg.fontpack.delete" | 2108 | msgid "dlg.fontpack.delete" |
2109 | msgstr "Видалити пакет шрифту" | 2109 | msgstr "Видалити пакет шрифту" |
@@ -2129,13 +2129,13 @@ msgid "dlg.dismiss.warning" | |||
2129 | msgstr "Усунути попередження" | 2129 | msgstr "Усунути попередження" |
2130 | 2130 | ||
2131 | msgid "heading.dismiss.warning" | 2131 | msgid "heading.dismiss.warning" |
2132 | msgstr "УНУТИ НЯ?" | 2132 | msgstr "Усуут ря?" |
2133 | 2133 | ||
2134 | msgid "dlg.fontpack.classic" | 2134 | msgid "dlg.fontpack.classic" |
2135 | msgstr "Завантажити пакет шрифтів (25 МБ)" | 2135 | msgstr "Завантажити пакет шрифтів (25 МБ)" |
2136 | 2136 | ||
2137 | msgid "heading.fontpack.classic" | 2137 | msgid "heading.fontpack.classic" |
2138 | msgstr "ЗТИ Т ИФТІВ" | 2138 | msgstr "Зтт т шрфті" |
2139 | 2139 | ||
2140 | msgid "dlg.fontpack.classic.msg" | 2140 | msgid "dlg.fontpack.classic.msg" |
2141 | msgstr "" | 2141 | msgstr "" |
@@ -2170,3 +2170,99 @@ msgstr "24-годинний час" | |||
2170 | # This label should be fairly short so it fits in a button in the sidebar. | 2170 | # This label should be fairly short so it fits in a button in the sidebar. |
2171 | msgid "sidebar.action.feeds.markallread" | 2171 | msgid "sidebar.action.feeds.markallread" |
2172 | msgstr "Прочитати всі" | 2172 | msgstr "Прочитати всі" |
2173 | |||
2174 | msgid "heading.prefs.toolbaractions" | ||
2175 | msgstr "Панель інструментів" | ||
2176 | |||
2177 | msgid "prefs.toolbaraction1" | ||
2178 | msgstr "Кнопка 1" | ||
2179 | |||
2180 | msgid "prefs.toolbaraction2" | ||
2181 | msgstr "Кнопка 2" | ||
2182 | |||
2183 | msgid "prefs.blink" | ||
2184 | msgstr "Моргання курсору:" | ||
2185 | |||
2186 | msgid "keys.upload.edit" | ||
2187 | msgstr "Редагувати сторінку за дпомогою Titan" | ||
2188 | |||
2189 | msgid "menu.open.external" | ||
2190 | msgstr "Відкрити в іншому додатку" | ||
2191 | |||
2192 | msgid "sidebar.action.history.clear" | ||
2193 | msgstr "Очистити" | ||
2194 | |||
2195 | # Active identity toolbar menu. | ||
2196 | msgid "menu.hide.identities" | ||
2197 | msgstr "Приховати панель ідентичностей" | ||
2198 | |||
2199 | msgid "menu.home" | ||
2200 | msgstr "Додому" | ||
2201 | |||
2202 | msgid "menu.identities" | ||
2203 | msgstr "Керувати ідентичностями" | ||
2204 | |||
2205 | msgid "sidebar.close" | ||
2206 | msgstr "Готово" | ||
2207 | |||
2208 | msgid "sidebar.action.bookmarks.newfolder" | ||
2209 | msgstr "Нова тека" | ||
2210 | |||
2211 | msgid "sidebar.action.bookmarks.edit" | ||
2212 | msgstr "Редагувати" | ||
2213 | |||
2214 | # The %s represents the name of an identity. | ||
2215 | #, c-format | ||
2216 | msgid "ident.switch" | ||
2217 | msgstr "Використовувати %s" | ||
2218 | |||
2219 | msgid "sidebar.empty.unread" | ||
2220 | msgstr "Непрочитані дописи відсутні" | ||
2221 | |||
2222 | # Paste the line preceding the clicked link into the input prompt. | ||
2223 | msgid "menu.input.precedingline" | ||
2224 | msgstr "Вставити попередній рядок" | ||
2225 | |||
2226 | # Shows where a local file is using the Finder. | ||
2227 | msgid "menu.reveal.macos" | ||
2228 | msgstr "Показати в Finder" | ||
2229 | |||
2230 | msgid "menu.share" | ||
2231 | msgstr "Поділитися" | ||
2232 | |||
2233 | msgid "menu.page.upload.edit" | ||
2234 | msgstr "Редагувати сторінку за допомогою Titan…" | ||
2235 | |||
2236 | msgid "heading.upload.id" | ||
2237 | msgstr "Авторизація" | ||
2238 | |||
2239 | msgid "menu.upload.export" | ||
2240 | msgstr "Експортувати текст" | ||
2241 | |||
2242 | msgid "menu.upload.delete" | ||
2243 | msgstr "Видалити все" | ||
2244 | |||
2245 | msgid "menu.upload.delete.confirm" | ||
2246 | msgstr "Справді видалити все (незворотньо)" | ||
2247 | |||
2248 | # Mobile subheading in the Upload dialog. | ||
2249 | msgid "upload.url" | ||
2250 | msgstr "URL" | ||
2251 | |||
2252 | # Mobile subheading: buttons for entering uploaded data. | ||
2253 | msgid "upload.content" | ||
2254 | msgstr "Вміст" | ||
2255 | |||
2256 | msgid "hint.upload.path" | ||
2257 | msgstr "Шлях URL" | ||
2258 | |||
2259 | msgid "hint.upload.token.long" | ||
2260 | msgstr "токен — див. вказівки сервера" | ||
2261 | |||
2262 | # Shows where a local file is using the File Manager. | ||
2263 | msgid "menu.reveal.filemgr" | ||
2264 | msgstr "Показати у файловому менеджері" | ||
2265 | |||
2266 | # Menu heading shown when customizing navbar button actions. | ||
2267 | msgid "menu.toolbar.setaction" | ||
2268 | msgstr "Призначити дію:" | ||
diff --git a/res/about/android-help.gmi b/res/about/android-help.gmi new file mode 100644 index 00000000..48835967 --- /dev/null +++ b/res/about/android-help.gmi | |||
@@ -0,0 +1,173 @@ | |||
1 | ```LAGRANGE | ||
2 | __ __ __ ___ | ||
3 | | /\ / _` |__) /\ |\ | / _` |__ | ||
4 | |___ /~~\ \__> | \ /~~\ | \| \__> |___ | ||
5 | |||
6 | ``` | ||
7 | # Help | ||
8 | |||
9 | ## What is Gemini | ||
10 | |||
11 | Gemini is a simple protocol for serving content over the internet. It specifies a Markdown inspired format allowing basic plain text document markup. Compared to HTTP and HTML, Gemini is vastly simpler and easier to work with. | ||
12 | |||
13 | => gemini://gemini.circumlunar.space/docs/faq.gmi Project Gemini FAQ | ||
14 | => gemini://gemini.circumlunar.space/docs/specification.gmi Protocol and 'text/gemini' specification | ||
15 | |||
16 | ## What is Lagrange | ||
17 | |||
18 | Lagrange is a GUI client for browsing Geminispace. It offers modern conveniences familiar from web browsers, such as smooth scrolling, inline image viewing, multiple tabs, visual themes, Unicode fonts, bookmarks, history, and page outlines. | ||
19 | |||
20 | Like Gemini, Lagrange has been designed with minimalism in mind. It depends on a small number of essential libraries. It is written in C and uses SDL for hardware-accelerated graphics. OpenSSL is used for secure communications. | ||
21 | |||
22 | => about:lagrange About Lagrange | ||
23 | => https://www.libsdl.org SDL: Simple DirectMedia Layer | ||
24 | => https://www.openssl.org OpenSSL: Cryptography and SSL/TLS Toolkit | ||
25 | |||
26 | ### Features | ||
27 | |||
28 | * Beautiful typography with full Unicode support | ||
29 | * Autogenerated page style and symbol for each Gemini domain | ||
30 | * Light and dark color themes | ||
31 | * Scaling page content (50%...200%) | ||
32 | * Freely adjustable scaling factor for the UI | ||
33 | * Sidebar for managing bookmarks, subscribed feeds, and viewing browsing history and the page outline | ||
34 | * Multiple tabs | ||
35 | * Find text on the page | ||
36 | * Open image and audio links inline on the same page | ||
37 | * Smart suggestions when typing an URL — search bookmarks, history, identities | ||
38 | * Search engine integration | ||
39 | * Identity management — create and use TLS client certificates | ||
40 | * Subscribe to Gemini and Atom feeds | ||
41 | * Use Gemini pages as a source of bookmarks | ||
42 | * Audio playback: MP3, Ogg Vorbis, WAV | ||
43 | * Read Gempub books and view ZIP archive contents | ||
44 | * Built-in support for Gopher | ||
45 | * Built-in support for uploading data using the Titan protocol | ||
46 | * Use proxy servers for HTTP, Gopher, or Gemini content | ||
47 | |||
48 | # Work in progress! | ||
49 | |||
50 | This documentation is a work in progress. You may find it useful to alse see the desktop Help page: | ||
51 | => gemini://git.skyjake.fi:1968/lagrange/release/res/about/help.gmi Help page (desktop version, latest release) | ||
52 | |||
53 | # 1 User interface | ||
54 | |||
55 | ## 1.6 Identities (client certificates) | ||
56 | |||
57 | Gemini uses TLS client certificates for user/session identification purposes. Unlike on the web where user identity tracking is covert and automatic, client certificates must be manually taken into use, and you are able to define how long each certificate remains valid. The term "Identity" is used in Lagrange to refer to client certificates. | ||
58 | |||
59 | The Identities sidebar tab shows all identities known to Lagrange. This includes identities created in Lagrange and any identities based on imported X.509 certificates. | ||
60 | |||
61 | ### 1.6.1 Creating a new identity | ||
62 | |||
63 | Click on the 👤 button in the toolbar and select "New Identity...". | ||
64 | |||
65 | Consider any information you enter in the certificate as public — only the Common Name is required to be non-empty. The generated certificate will use the Common Name as the issuer and subject of the certificate, making it clear that the certificate is self-signed. The other required field is the expiration date in "Valid until". Entering a year is sufficient, and means that the certificate is valid until the end of that year. | ||
66 | |||
67 | ### 1.6.2 Using an identity | ||
68 | |||
69 | You will need to select an identity when you encounter this error message: | ||
70 | |||
71 | > 🔑 Certificate Required | ||
72 | |||
73 | Go to Settings > Identities and tap on an identity to toggle it on/off for the currently open URL. On subsequent page loads, the certificate will then be sent to the server when the URL or any URL under it is fetched. You can click on the 👤 button in the navigation bar to see which identity is being used for the current page. | ||
74 | |||
75 | As the sidebar is not keyboard-navigable, note that identities can also be accessed via lookup results. Identities matching the search terms are shown as the last category in the lookup results list. From there, one can toggle an identity for the current page, or stop using it on all pages. | ||
76 | |||
77 | ### 1.6.3 Importing existing certificates | ||
78 | |||
79 | To import an existing X.509 certificate as an identity, click on 👤 and select "Import...". | ||
80 | |||
81 | When the Import Identity dialog opens, it checks the currently open page and the system clipboard for any certificates and private keys in PEM format. If found, these are automatically loaded in. | ||
82 | |||
83 | ## 1.8 Uploads (with Titan) | ||
84 | |||
85 | Titan is a sister protocol to Gemini that enables sending arbitrary amounts of data from a client to a server. The Gemini protocol itself only enables sending up to 1024 bytes of data in a request. Furthermore, the request URL also counts against that limit, and the sent data must be percent-encoded so it can be parsed as a valid URL. Consequently, Gemini clients can only send very limited amounts of data to a server. Titan solves this by expanding the request so that the request URL is followed by a payload field. When it comes to TLS, Titan is equivalent to Gemini, so the same server and client certificates can be used with both. | ||
86 | |||
87 | => gemini://transjovian.org/titan Titan Protocol (by Alex Schroeder) | ||
88 | |||
89 | While Titan and Gemini are related, Titan is a separate protocol and regular Gemini servers are not expected to support it. Whether it makes sense to allow clients to upload large amounts of data is a service-specific question. For example, a server that hosts a gemlog could enable Titan uploads for submitting new posts, or editing existing posts by uploading a revised version. | ||
90 | |||
91 | As far as Lagrange is concerned, Titan is just one of the supported URL schemes. Whenever you try to open a "titan://" URL, no matter if it is manually entered into the URL field, or by clicking on a link, opening a bookmark, feed entry, or via a redirect, a dialog will open where you can specify the data to upload. | ||
92 | |||
93 | The Titan upload dialog supports two ways to enter the data: | ||
94 | |||
95 | * You can type text in the input field on the "Text" tab. It will be sent to the server using 'text/plain' as the media type. | ||
96 | * You can drag and drop a file on the dialog. The details of the file to be uploaded are visible on the "File" tab. The media type can be specified manually if Lagrange does not correctly detect it. | ||
97 | |||
98 | The upload token is a feature of Titan where servers can require a certain token text/passphrase for uploads. It is up to the server how this is interpreted. It could be used as a simple password, or even a command to further instruct the server about what to do with the uploaded data. Please refer to the server's instructions about what to enter here. The token may also be left empty. | ||
99 | |||
100 | The text entered into the upload dialog's main text field is protected against accidental closing of the dialog or the application, or a crash. The previous text is restored when the dialog is reopened. The text field contents are only cleared when the submitted Titan request has been successfully completed. | ||
101 | |||
102 | # 2 Customization | ||
103 | |||
104 | ## 2.1 Browsing behavior | ||
105 | |||
106 | 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. | ||
107 | |||
108 | ## 2.4 Fonts | ||
109 | |||
110 | This version of Lagrange supports TrueType fonts. To use a new font, simply view a .ttf (or .fontpack) file in the app and a page footer action is available for performing the installation. | ||
111 | |||
112 | The "Fonts" page of Settings contains options that affect the appearance of text in page content, the user interface, and text rendering in general. | ||
113 | |||
114 | To be precise, on this tab you can select which _typefaces_ to use for certain elements of the page and the UI; picking a particular font (including its size, weight, style, etc.) for specific elements is not possible. Lagrange controls the size and styling of text by choosing fonts according to your theme and typeface preferences. The term "font" is often used to mean "typeface" in this document and in the app UI. | ||
115 | |||
116 | The fonts for document headings, body text, preformatted blocks, and the user interface can be chosen separately. There is also a separate font for monospaced body text, used when the protocol-specific "Monospace body" option is enabled. | ||
117 | |||
118 | There are two built-in fonts available: | ||
119 | |||
120 | * "Source Sans" is the default UI font that is also used for page content. | ||
121 | * "Iosevka" is the monospaced font. | ||
122 | |||
123 | There is also an "Iosevka (compact)" variant that actually uses the font's original line spacing. It is the default for preformatted blocks to avoid gaps between lines of ASCII art, but not for body text for better legibility. | ||
124 | |||
125 | See sections 2.4.4 and 5 for more details about font management, compatibility, and configuration. | ||
126 | |||
127 | ### 2.4.1 Monospace body | ||
128 | |||
129 | The "Monospace body" option causes all pages to be displayed in a monospace font. For example, most Gopher content has been written with the assumption of a monospace font, so it may provide a better reading experience to enable this for Gopher pages. The selected "Monospace font" is applied to the entire document. | ||
130 | |||
131 | Handling of whitespace in page content also changes when this option is enabled: spaces are no longer normalized so if the source uses multiple spaces somewhere, those are shown as-is. | ||
132 | |||
133 | ### 2.4.2 ANSI escapes | ||
134 | |||
135 | => https://en.wikipedia.org/wiki/ANSI_escape_code ANSI escape code (Wikipedia): | ||
136 | > ANSI escape sequences are a standard for in-band signaling to control cursor location, color, font styling, and other options on video text terminals and terminal emulators. | ||
137 | |||
138 | Sometimes these codes are used for things like colored ASCII art. However, Lagrange is not a terminal emulator so only a very minimal set of codes have any effect; the rest are ignored. | ||
139 | |||
140 | The "ANSI escapes" setting controls which ANSI escape codes are enabled: | ||
141 | |||
142 | * "FG Color" enables changes to text foreground color. | ||
143 | * "BG Color" enables changes to text background color. | ||
144 | * "Font Style" enables changing the font style: bold, italic, or monospace. (These correspond to codes 1, 3, and 11.) | ||
145 | |||
146 | A warning banner is displayed if any codes are detected on a page. This helps you be aware of potential visual artifacts, like text color that is being forced to black regardless of the page background color. Click on the warning to dismiss it on a per-site basis. | ||
147 | |||
148 | If you serve content via Gemini, please be aware that ANSI escapes may not be supported by clients, and may in fact disrupt the behavior of a client in unexpected ways. For instance, consider a screen reader or a web proxy that doesn't filter out the codes. Only employ ANSI escapes when the user has somehow indicated that is their preference. | ||
149 | |||
150 | ### 2.4.3 Other font options | ||
151 | |||
152 | The "Glyph warnings" option controls whether a warning will be shown when a page is missing one or more characters. This is typically due to the page being in a language that is not covered by any of the installed fonts. Clicking on the glyph warning banner opens the font management page. | ||
153 | |||
154 | "Smoothing" enables or disables glyph antialiasing. In this version, it is recommended that smoothing is always enabled. This is because the text renderer does not support hinting of any kind, which means that without smoothing, glyph shapes will be severely distorted. You may still want to try disabling this option if antialiasing causes eye strain for you. | ||
155 | |||
156 | ### 2.4.4 Managing fonts | ||
157 | |||
158 | Open "about:fonts" to see all the installed fonts, and enable, disable or uninstall individual fontpacks. At the top of the page, there is a link to the skyjake.fi Font Library: a curated collection of fontpacks that can be freely distributed. From there you can conveniently install more fonts specifically tuned for Lagrange. | ||
159 | |||
160 | The rest of the page is divided to two sections: enabled and disabled fontpacks. Individual packs can be disabled, so they remain installed but not in use. (Tip: Use the Outline sidebar to see a list of all packs, if there are many.) | ||
161 | |||
162 | Click on the "View file" links to see what each fontpack contains. The fontpack format is described in section 5: it is simply a ZIP archive with some metadata and a bunch of font files. When you install a TrueType font (i.e., copy it to the user's fonts directory), it is treated as a fontpack containing a single file, although no ZIP archive is created for it. Use the "View fontpack.ini template" link to see the generated metadata that can be used as a basis for creating an actual fontpack. | ||
163 | |||
164 | About compatibility: fonts are complex, and while the TrueType format is not the most advanced one, it still has features that are not supported in Lagrange. The technical reason is that glyphs are rasterized using stb_truetype, a rather simple TrueType font library. (It's nice and small, though.) You may find that some fonts simply fail to load correctly, or look wrong in the app. | ||
165 | |||
166 | * Hinting is not supported. | ||
167 | * Bitmap glyphs are not supported. | ||
168 | * Multi-color glyphs are not supported. | ||
169 | |||
170 | TrueType Collections (.ttc) can be used, with the restriction that each declared font uses a single index from the collection. The index must be appended to the file name: | ||
171 | ```.ttc example | ||
172 | regular = "NotoSansCJK-Regular.ttc:2" | ||
173 | ``` | ||
diff --git a/res/about/android-version.gmi b/res/about/android-version.gmi new file mode 100644 index 00000000..fc7b444b --- /dev/null +++ b/res/about/android-version.gmi | |||
@@ -0,0 +1,25 @@ | |||
1 | ```LAGRANGE | ||
2 | __ __ __ ___ | ||
3 | | /\ / _` |__) /\ |\ | / _` |__ | ||
4 | |___ /~~\ \__> | \ /~~\ | \| \__> |___ | ||
5 | |||
6 | ``` | ||
7 | # Release notes | ||
8 | |||
9 | ## 1.10 (Alpha 3) | ||
10 | * Added Android-specific release notes. | ||
11 | * Added Settings > UI > Toolbar Actions: customize the two leftmost phone toolbar buttons. | ||
12 | * Show build version in Settings > About. | ||
13 | * Changed return key behavior to insert newlines where possible. | ||
14 | * Fixed sizing of the UI when the device has on-screen system keys. | ||
15 | * Fixed the copy/paste menu staying hidden behind the keyboard. | ||
16 | * Fixed system Auto-Rotate setting not locking the screen orientation. | ||
17 | |||
18 | ## 1.10 (Alpha 2) | ||
19 | * Upgraded SDL to 2.0.18, which fixed a lot of stability issues. | ||
20 | * Added native binaries for arm64, x86, and x86_64. | ||
21 | * Enabled bidirectional text and complex scripts (with HarfBuzz and FriBidi). | ||
22 | * Enabled switching to landscape orientation. | ||
23 | |||
24 | ## 1.10 (Alpha 1) | ||
25 | Initial test build with SDL 2.0.5 and 32-bit ARM binaries. It works, but barely. \ No newline at end of file | ||
diff --git a/res/about/help.gmi b/res/about/help.gmi index 8a63152e..93bc6a05 100644 --- a/res/about/help.gmi +++ b/res/about/help.gmi | |||
@@ -253,7 +253,7 @@ You will need to select an identity when you encounter this error message: | |||
253 | 253 | ||
254 | > 🔑 Certificate Required | 254 | > 🔑 Certificate Required |
255 | 255 | ||
256 | Clicking on an identity in the sidebar will toggle it on/off for the currently open URL. On subsequent page loads, the certificate will then be sent to the server when the URL or any URL under it is fetched. You can click on the 👤 button in the navigation bar to see which identity is being used for the current page. | 256 | Clicking on an identity in the sidebar shows a menu where you can control usage of the identity for the currently open URL. On subsequent page loads, the certificate will then be sent to the server when the URL or any URL under it is fetched. You can click on the 👤 button in the navigation bar to see which identity is being used for the current page. |
257 | 257 | ||
258 | As the sidebar is not keyboard-navigable, note that identities can also be accessed via lookup results. Identities matching the search terms are shown as the last category in the lookup results list. From there, one can toggle an identity for the current page, or stop using it on all pages. | 258 | As the sidebar is not keyboard-navigable, note that identities can also be accessed via lookup results. Identities matching the search terms are shown as the last category in the lookup results list. From there, one can toggle an identity for the current page, or stop using it on all pages. |
259 | 259 | ||
@@ -390,7 +390,7 @@ Page content color themes are selected on the "Colors" tab of Preferences. The " | |||
390 | 390 | ||
391 | ## 2.4 Fonts | 391 | ## 2.4 Fonts |
392 | 392 | ||
393 | This version of Lagrange supports TrueType fonts. To use a new font in the app, simply view the .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). | 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). |
394 | 394 | ||
395 | The "Fonts" tab of the Preferences dialog contains options that affect the appearance of text in page content, the user interface, and text rendering in general. | 395 | The "Fonts" tab of the Preferences dialog contains options that affect the appearance of text in page content, the user interface, and text rendering in general. |
396 | 396 | ||
@@ -424,7 +424,7 @@ The "ANSI escapes" setting controls which ANSI escape codes are enabled: | |||
424 | 424 | ||
425 | * "FG Color" enables changes to text foreground color. | 425 | * "FG Color" enables changes to text foreground color. |
426 | * "BG Color" enables changes to text background color. | 426 | * "BG Color" enables changes to text background color. |
427 | * "Font Style" enables changing the font style: bold, italic, or monospace. (These correspond to codes 1, 3, and 11.) | 427 | * "Font Style" enables changing the font style: bold, light, italic, regular, or monospace. (These correspond to codes 1, 2, 3, 10, and 11.) |
428 | 428 | ||
429 | A warning banner is displayed if any codes are detected on a page. This helps you be aware of potential visual artifacts, like text color that is being forced to black regardless of the page background color. Click on the warning to dismiss it on a per-site basis. | 429 | A warning banner is displayed if any codes are detected on a page. This helps you be aware of potential visual artifacts, like text color that is being forced to black regardless of the page background color. Click on the warning to dismiss it on a per-site basis. |
430 | 430 | ||
diff --git a/res/about/ios-help.gmi b/res/about/ios-help.gmi new file mode 100644 index 00000000..41a8445b --- /dev/null +++ b/res/about/ios-help.gmi | |||
@@ -0,0 +1,204 @@ | |||
1 | ```LAGRANGE | ||
2 | __ __ __ ___ | ||
3 | | /\ / _` |__) /\ |\ | / _` |__ | ||
4 | |___ /~~\ \__> | \ /~~\ | \| \__> |___ | ||
5 | |||
6 | ``` | ||
7 | # Help | ||
8 | |||
9 | ## What is Gemini | ||
10 | |||
11 | Gemini is a simple protocol for serving content over the internet. It specifies a Markdown inspired format allowing basic plain text document markup. Compared to HTTP and HTML, Gemini is vastly simpler and easier to work with. | ||
12 | |||
13 | => gemini://gemini.circumlunar.space/docs/faq.gmi Project Gemini FAQ | ||
14 | => gemini://gemini.circumlunar.space/docs/specification.gmi Protocol and 'text/gemini' specification | ||
15 | |||
16 | ## What is Lagrange | ||
17 | |||
18 | Lagrange is a GUI client for browsing Geminispace. It offers modern conveniences familiar from web browsers, such as smooth scrolling, inline image viewing, multiple tabs, visual themes, Unicode fonts, bookmarks, history, and page outlines. | ||
19 | |||
20 | Like Gemini, Lagrange has been designed with minimalism in mind. It depends on a small number of essential libraries. It is written in C and uses SDL for hardware-accelerated graphics. OpenSSL is used for secure communications. | ||
21 | |||
22 | => about:lagrange About Lagrange | ||
23 | => https://www.libsdl.org SDL: Simple DirectMedia Layer | ||
24 | => https://www.openssl.org OpenSSL: Cryptography and SSL/TLS Toolkit | ||
25 | |||
26 | ### Features | ||
27 | |||
28 | * Beautiful typography with full Unicode support | ||
29 | * Autogenerated page style and symbol for each Gemini domain | ||
30 | * Light and dark color themes | ||
31 | * Scaling page content (50%...200%) | ||
32 | * Freely adjustable scaling factor for the UI | ||
33 | * Sidebar for managing bookmarks, subscribed feeds, and viewing browsing history and the page outline | ||
34 | * Multiple tabs | ||
35 | * Split view for browsing two pages at once (iPad only) | ||
36 | * Find text on the page | ||
37 | * Open image and audio links inline on the same page | ||
38 | * Smart suggestions when typing an URL — search bookmarks, history, identities | ||
39 | * Search engine integration | ||
40 | * Identity management — create and use TLS client certificates | ||
41 | * Subscribe to Gemini and Atom feeds | ||
42 | * Use Gemini pages as a source of bookmarks | ||
43 | * Audio playback: MP3, Ogg Vorbis, WAV | ||
44 | * Read Gempub books and view ZIP archive contents | ||
45 | * Built-in support for Gopher | ||
46 | * Built-in support for uploading data using the Titan protocol | ||
47 | * Use proxy servers for HTTP, Gopher, or Gemini content | ||
48 | |||
49 | # Work in progress! | ||
50 | |||
51 | This documentation is a work in progress. You may find it useful to alse see the desktop Help page: | ||
52 | => gemini://git.skyjake.fi:1968/lagrange/release/res/about/help.gmi Help page (desktop version, latest release) | ||
53 | |||
54 | # 1 User interface | ||
55 | |||
56 | ## 1.6 Identities (client certificates) | ||
57 | |||
58 | Gemini uses TLS client certificates for user/session identification purposes. Unlike on the web where user identity tracking is covert and automatic, client certificates must be manually taken into use, and you are able to define how long each certificate remains valid. The term "Identity" is used in Lagrange to refer to client certificates. | ||
59 | |||
60 | The Identities sidebar tab shows all identities known to Lagrange. This includes identities created in Lagrange and any identities based on imported X.509 certificates. | ||
61 | |||
62 | ### 1.6.1 Creating a new identity | ||
63 | |||
64 | Click on the 👤 button in the toolbar and select "New Identity...". | ||
65 | |||
66 | Consider any information you enter in the certificate as public — only the Common Name is required to be non-empty. The generated certificate will use the Common Name as the issuer and subject of the certificate, making it clear that the certificate is self-signed. The other required field is the expiration date in "Valid until". Entering a year is sufficient, and means that the certificate is valid until the end of that year. | ||
67 | |||
68 | ### 1.6.2 Using an identity | ||
69 | |||
70 | You will need to select an identity when you encounter this error message: | ||
71 | |||
72 | > 🔑 Certificate Required | ||
73 | |||
74 | Go to Settings > Identities and tap on an identity to toggle it on/off for the currently open URL. On subsequent page loads, the certificate will then be sent to the server when the URL or any URL under it is fetched. You can click on the 👤 button in the navigation bar to see which identity is being used for the current page. | ||
75 | |||
76 | As the sidebar is not keyboard-navigable, note that identities can also be accessed via lookup results. Identities matching the search terms are shown as the last category in the lookup results list. From there, one can toggle an identity for the current page, or stop using it on all pages. | ||
77 | |||
78 | ### 1.6.3 Importing existing certificates | ||
79 | |||
80 | To import an existing X.509 certificate as an identity, click on 👤 and select "Import...". | ||
81 | |||
82 | When the Import Identity dialog opens, it checks the currently open page and the system clipboard for any certificates and private keys in PEM format. If found, these are automatically loaded in. | ||
83 | |||
84 | ## 1.8 Uploads (with Titan) | ||
85 | |||
86 | Titan is a sister protocol to Gemini that enables sending arbitrary amounts of data from a client to a server. The Gemini protocol itself only enables sending up to 1024 bytes of data in a request. Furthermore, the request URL also counts against that limit, and the sent data must be percent-encoded so it can be parsed as a valid URL. Consequently, Gemini clients can only send very limited amounts of data to a server. Titan solves this by expanding the request so that the request URL is followed by a payload field. When it comes to TLS, Titan is equivalent to Gemini, so the same server and client certificates can be used with both. | ||
87 | |||
88 | => gemini://transjovian.org/titan Titan Protocol (by Alex Schroeder) | ||
89 | |||
90 | While Titan and Gemini are related, Titan is a separate protocol and regular Gemini servers are not expected to support it. Whether it makes sense to allow clients to upload large amounts of data is a service-specific question. For example, a server that hosts a gemlog could enable Titan uploads for submitting new posts, or editing existing posts by uploading a revised version. | ||
91 | |||
92 | As far as Lagrange is concerned, Titan is just one of the supported URL schemes. Whenever you try to open a "titan://" URL, no matter if it is manually entered into the URL field, or by clicking on a link, opening a bookmark, feed entry, or via a redirect, a dialog will open where you can specify the data to upload. | ||
93 | |||
94 | The Titan upload dialog supports two ways to enter the data: | ||
95 | |||
96 | * You can type text in the input field on the "Text" tab. It will be sent to the server using 'text/plain' as the media type. | ||
97 | * You can drag and drop a file on the dialog. The details of the file to be uploaded are visible on the "File" tab. The media type can be specified manually if Lagrange does not correctly detect it. | ||
98 | |||
99 | The upload token is a feature of Titan where servers can require a certain token text/passphrase for uploads. It is up to the server how this is interpreted. It could be used as a simple password, or even a command to further instruct the server about what to do with the uploaded data. Please refer to the server's instructions about what to enter here. The token may also be left empty. | ||
100 | |||
101 | The text entered into the upload dialog's main text field is protected against accidental closing of the dialog or the application, or a crash. The previous text is restored when the dialog is reopened. The text field contents are only cleared when the submitted Titan request has been successfully completed. | ||
102 | |||
103 | ## 1.9 Split view mode (iPad only) | ||
104 | |||
105 | By default, only one tab is visible at a time in the application window. However, sometimes it is beneficial to see two pages at once. For example, many capsules have top-level menus or lists of articles, and keeping the menu/index visible on the side makes navigation less cumbersome. | ||
106 | |||
107 | Split view mode divides the UI into two equivalent parts. You can have multiple tabs in each split. Closing all tabs on one side will remove the split and return back to the normal unsplit mode. | ||
108 | |||
109 | Each split has its own sidebars, which means that in split view mode you can have a total of four sidebars open at the same time. | ||
110 | |||
111 | ### 1.9.1 Switching focus | ||
112 | |||
113 | At any given time, one of the splits has input focus. This is indicated by a colored line at the top of the section, and some UI elements will be dimmed out on the unfocused side. | ||
114 | |||
115 | If a keyboard is connected, you can use the Next/Previous Tab keybindings or Ctrl+Tab to switch keyboard focus between the sections. Next/Previous Tab is particularly convenient as it cycles through all open tabs, jumping to the other side of the split when appropriate. You may also press Tab to cycle input focus between all the URL input fields. | ||
116 | |||
117 | ### 1.9.2 Pinning | ||
118 | |||
119 | While it is sometimes useful to simply have two independent browsers open side by side, by default view splitting is meant to assist in navigating hierarchies and lists. In the typical use case, you'll have a menu or an index page on the left, and a content page open on the right. Links clicked on the left will automatically open on the right. | ||
120 | |||
121 | This is called "pinning" and the behavior can be configured in Settings. The "Split view pinning" setting on the "General" page of Settings controls where links get opened in a split view. There are three modes available: | ||
122 | |||
123 | * "None" causes links to open in the tab where the link is clicked. In this mode, both sides of the split can be navigated independently. | ||
124 | * "Left Tab" causes links clicked on the left tab to open on the right side. The page open in the left tab is therefore "pinned" and does not change unless you enter a new URL or navigate to the parent or root. | ||
125 | * "Right Tab" is the same but works the other way around. The page open in the right tab is pinned and clicked links open on the left. | ||
126 | |||
127 | The default pinning mode is "Left Tab". | ||
128 | |||
129 | The ◧ indicator is shown in the URL input field when the current tab is pinned. | ||
130 | |||
131 | # 2 Customization | ||
132 | |||
133 | ## 2.1 Browsing behavior | ||
134 | |||
135 | 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. | ||
136 | |||
137 | "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. | ||
138 | |||
139 | ## 2.4 Fonts | ||
140 | |||
141 | This version of Lagrange supports TrueType fonts. To use a new font, simply view a .ttf (or .fontpack) file in the app and a page footer action is available for performing the installation. | ||
142 | |||
143 | The "Fonts" page of Settings contains options that affect the appearance of text in page content, the user interface, and text rendering in general. | ||
144 | |||
145 | To be precise, on this tab you can select which _typefaces_ to use for certain elements of the page and the UI; picking a particular font (including its size, weight, style, etc.) for specific elements is not possible. Lagrange controls the size and styling of text by choosing fonts according to your theme and typeface preferences. The term "font" is often used to mean "typeface" in this document and in the app UI. | ||
146 | |||
147 | The fonts for document headings, body text, preformatted blocks, and the user interface can be chosen separately. There is also a separate font for monospaced body text, used when the protocol-specific "Monospace body" option is enabled. | ||
148 | |||
149 | There are two built-in fonts available: | ||
150 | |||
151 | * "Source Sans" is the default UI font that is also used for page content. | ||
152 | * "Iosevka" is the monospaced font. | ||
153 | |||
154 | There is also an "Iosevka (compact)" variant that actually uses the font's original line spacing. It is the default for preformatted blocks to avoid gaps between lines of ASCII art, but not for body text for better legibility. | ||
155 | |||
156 | See sections 2.4.4 and 5 for more details about font management, compatibility, and configuration. | ||
157 | |||
158 | ### 2.4.1 Monospace body | ||
159 | |||
160 | The "Monospace body" option causes all pages to be displayed in a monospace font. For example, most Gopher content has been written with the assumption of a monospace font, so it may provide a better reading experience to enable this for Gopher pages. The selected "Monospace font" is applied to the entire document. | ||
161 | |||
162 | Handling of whitespace in page content also changes when this option is enabled: spaces are no longer normalized so if the source uses multiple spaces somewhere, those are shown as-is. | ||
163 | |||
164 | ### 2.4.2 ANSI escapes | ||
165 | |||
166 | => https://en.wikipedia.org/wiki/ANSI_escape_code ANSI escape code (Wikipedia): | ||
167 | > ANSI escape sequences are a standard for in-band signaling to control cursor location, color, font styling, and other options on video text terminals and terminal emulators. | ||
168 | |||
169 | Sometimes these codes are used for things like colored ASCII art. However, Lagrange is not a terminal emulator so only a very minimal set of codes have any effect; the rest are ignored. | ||
170 | |||
171 | The "ANSI escapes" setting controls which ANSI escape codes are enabled: | ||
172 | |||
173 | * "FG Color" enables changes to text foreground color. | ||
174 | * "BG Color" enables changes to text background color. | ||
175 | * "Font Style" enables changing the font style: bold, italic, or monospace. (These correspond to codes 1, 3, and 11.) | ||
176 | |||
177 | A warning banner is displayed if any codes are detected on a page. This helps you be aware of potential visual artifacts, like text color that is being forced to black regardless of the page background color. Click on the warning to dismiss it on a per-site basis. | ||
178 | |||
179 | If you serve content via Gemini, please be aware that ANSI escapes may not be supported by clients, and may in fact disrupt the behavior of a client in unexpected ways. For instance, consider a screen reader or a web proxy that doesn't filter out the codes. Only employ ANSI escapes when the user has somehow indicated that is their preference. | ||
180 | |||
181 | ### 2.4.3 Other font options | ||
182 | |||
183 | The "Glyph warnings" option controls whether a warning will be shown when a page is missing one or more characters. This is typically due to the page being in a language that is not covered by any of the installed fonts. Clicking on the glyph warning banner opens the font management page. | ||
184 | |||
185 | "Smoothing" enables or disables glyph antialiasing. In this version, it is recommended that smoothing is always enabled. This is because the text renderer does not support hinting of any kind, which means that without smoothing, glyph shapes will be severely distorted. You may still want to try disabling this option if antialiasing causes eye strain for you. | ||
186 | |||
187 | ### 2.4.4 Managing fonts | ||
188 | |||
189 | Open "about:fonts" to see all the installed fonts, and enable, disable or uninstall individual fontpacks. At the top of the page, there is a link to the skyjake.fi Font Library: a curated collection of fontpacks that can be freely distributed. From there you can conveniently install more fonts specifically tuned for Lagrange. | ||
190 | |||
191 | The rest of the page is divided to two sections: enabled and disabled fontpacks. Individual packs can be disabled, so they remain installed but not in use. (Tip: Use the Outline sidebar to see a list of all packs, if there are many.) | ||
192 | |||
193 | Click on the "View file" links to see what each fontpack contains. The fontpack format is described in section 5: it is simply a ZIP archive with some metadata and a bunch of font files. When you install a TrueType font (i.e., copy it to the user's fonts directory), it is treated as a fontpack containing a single file, although no ZIP archive is created for it. Use the "View fontpack.ini template" link to see the generated metadata that can be used as a basis for creating an actual fontpack. | ||
194 | |||
195 | About compatibility: fonts are complex, and while the TrueType format is not the most advanced one, it still has features that are not supported in Lagrange. The technical reason is that glyphs are rasterized using stb_truetype, a rather simple TrueType font library. (It's nice and small, though.) You may find that some fonts simply fail to load correctly, or look wrong in the app. | ||
196 | |||
197 | * Hinting is not supported. | ||
198 | * Bitmap glyphs are not supported. | ||
199 | * Multi-color glyphs are not supported. | ||
200 | |||
201 | TrueType Collections (.ttc) can be used, with the restriction that each declared font uses a single index from the collection. The index must be appended to the file name: | ||
202 | ```.ttc example | ||
203 | regular = "NotoSansCJK-Regular.ttc:2" | ||
204 | ``` | ||
diff --git a/res/about/ios-version.gmi b/res/about/ios-version.gmi new file mode 100644 index 00000000..f3122dbe --- /dev/null +++ b/res/about/ios-version.gmi | |||
@@ -0,0 +1,83 @@ | |||
1 | ```LAGRANGE | ||
2 | __ __ __ ___ | ||
3 | | /\ / _` |__) /\ |\ | / _` |__ | ||
4 | |___ /~~\ \__> | \ /~~\ | \| \__> |___ | ||
5 | |||
6 | ``` | ||
7 | # Release notes | ||
8 | |||
9 | ## 1.10 (9) | ||
10 | * Added "Share" actions for downloaded files (via "Download Linked File") and selected text on page. | ||
11 | * Added "Edit Page with Titan": opens the Upload dialog with the page contents prefilled (previous contents are lost!). | ||
12 | * Inlining `image/*` responses into the current document regardless of file extension in URL. | ||
13 | * Fixed glitch with banner not being visible until page has finished loading. | ||
14 | * Fixed possible crash when creating a bookmark. | ||
15 | |||
16 | ## 1.10 (7) | ||
17 | * Link context menu shows used identity and date of last visit in addition to the URL. | ||
18 | * Removed the "Show URL on hover" setting. URLs are shown in the link context menu. | ||
19 | * Inline image metadata goes under the image instead of possibly overlapping the image caption text. | ||
20 | * Improved appearance of hover/open link highlighting. It no longer goes under the side elements on the page. | ||
21 | * Gempub: Open books in 1:2 split mode instead of 1:1. | ||
22 | * Fixed opening links in split view mode. Links would open on the wrong side. | ||
23 | * Upgraded SDL to version 2.0.18. | ||
24 | |||
25 | ## 1.10 (6) | ||
26 | * Added "Paste Preceding Line" in the input prompt dialog. Potential use cases include user-editable sections on a page and suggested/example input values. | ||
27 | * Fixed UI glitches and a potential memory leak when navigating via swipes. Sometimes swipe navigation would stop working because animation placeholders were not destroyed. | ||
28 | * Fixed Settings background fade. | ||
29 | * Fixed sidebar background fade in portrait phone layout. | ||
30 | |||
31 | ## 1.10 (5) | ||
32 | * Fixed positioning of native UI controls in non-animated input widgets. | ||
33 | * Fixed input widgets not reacting to keyboard being dismissed by system. | ||
34 | * iPad: Faster back swipe animation. | ||
35 | |||
36 | ## 1.10 (4) | ||
37 | * Fixed crash when closing sidebar in bookmark edit mode. | ||
38 | * Fixed incorrect initial height of an unfocused multiline input widget. | ||
39 | * Fixed lookup results having too narrow a width. | ||
40 | * Fixed padding at bottom of the upload text editor. | ||
41 | * Fixed missing "Folder" field in the bookmark editor. Edited bookmarks would be moved to the root folder. | ||
42 | * Fixed missing "Glyph Warnings" toggle in Settings. | ||
43 | * Removed "Return Key Behavior" from Settings since it has no effect. | ||
44 | * Minor improvements in page caching. | ||
45 | * Tuned page swipe animations. | ||
46 | * Optimized UI layout updates. | ||
47 | * Audio subsystem is only initialized when actually needed. | ||
48 | * Prevent state file corruption if the app happens to get killed while state is being saved. | ||
49 | |||
50 | ## 1.10 (3) | ||
51 | * Revised layout of the Upload dialog. There is a new edit field that lets you modify the file path of the URL. Identity names are in bold. | ||
52 | * Added an edit actions menu in the Upload text editor (only in portrait phone layout for now). | ||
53 | * Fixed persistent storage of the Upload text field contents. | ||
54 | * Fixed input widget positioning and behavior in the Upload dialog. | ||
55 | * Fixed minimum width of main panel in dialogs. | ||
56 | * Fixed use of cached content when the same URL was found multiple times in history. Previously, only the latest page was used. | ||
57 | * Changed input field tint color to match chosen UI accent color. | ||
58 | * Optimized memory use during UI event processing. | ||
59 | |||
60 | ## 1.10 (2) | ||
61 | * Fixed pull-to-refresh on short pages. | ||
62 | * Fixed URL field contents not being clipped to widget bounds. | ||
63 | * Fixed major glitches in back/forward swipe navigation, e.g., top banner showing incorrect contents, incorrect theme colors, scroll position jumping. | ||
64 | * Fixed major and minor UI element positioning glitches, e.g., native text fields, maximum height of input fields, input length counter, Translate dialog. | ||
65 | * Fixed inappropriate font sizes, e.g., sidebar action labels. | ||
66 | * Fixed color issues: tint color of native input, and footer buttons not being prominent enough. | ||
67 | * Bookmarks: In Edit mode, folders still get (un)folded when tapped. | ||
68 | * Feeds: Added "No Unread Entries" message. | ||
69 | * Identities: Show the current page's URL in the context menu to make it clearer what "This Page" refers to. | ||
70 | |||
71 | ## 1.10 (1) | ||
72 | * Use native iOS text input UI controls for entering text. | ||
73 | * Pull-to-refresh on pages. | ||
74 | * Redesigned phone sidebar that mimics Safari on iPhone. Opens half-height for better reachability of tab buttons, can be swiped up and down. | ||
75 | * Bookmark edit mode for reordering and organizing bookmarks into folders. | ||
76 | * Sidebar has new action buttons for Feeds and History. | ||
77 | * Identity toolbar button has been revised for quickly switching between alternate identities for current site, and opening Settings > Identities. | ||
78 | * Settings: Managing identities. | ||
79 | * Settings: Improved widgets used in button/radio groups to closer match iOS design. | ||
80 | * Settings: Added new options from desktop version. | ||
81 | * Mobile-friendly layout for value input dialogs. | ||
82 | * Various animation issues fixes, e.g., context menu animations on iPad. | ||
83 | * Various layout issues fixed, e.g., related to safe area insets. | ||
diff --git a/res/about/version.gmi b/res/about/version.gmi index b9ea37db..ea420916 100644 --- a/res/about/version.gmi +++ b/res/about/version.gmi | |||
@@ -6,8 +6,66 @@ | |||
6 | ``` | 6 | ``` |
7 | # Release notes | 7 | # Release notes |
8 | 8 | ||
9 | ## 1.10.1 | ||
10 | * Fixed bottom actions of the Feeds sidebar getting hidden when all entries are read. This prevented switching between Unread/All filter modes. | ||
11 | * Fixed potential crash when downloading a large file (e.g., a fontpack). | ||
12 | * Linux: SDL event handling workaround adjusted to only apply to 2.0.18+. | ||
13 | * Updated UI translations. | ||
14 | |||
15 | ## 1.10 | ||
16 | New features: | ||
17 | * macOS: Trackpad swipe navigation. | ||
18 | * Customizable navbar actions. Right-click on a button to change its action. (Identity and hamburger buttons cannot be changed.) | ||
19 | * Identity toolbar menu can be used to switch between alternate identities. If you have used multiple identities on one site, this makes it more convenient to switch between them. | ||
20 | * Added "Edit Page with Titan": opens the upload dialog with current page's content prefilled. | ||
21 | * Added "Paste Preceding Line" in the input prompt dialog. Potential use cases include user-editable sections on a page and suggested/example input values. | ||
22 | * Added footer action to open file in another app when the media type is unsupported. | ||
23 | * Added option to disable cursor blinking in input fields. | ||
24 | * Added ANSI SGR codes for light and regular font weights (2, 10). | ||
25 | * macOS: Added "Show in Finder" in the Identities sidebar. | ||
26 | |||
27 | Changes and enhancements: | ||
28 | * Improved image inlining: all responses with an image media type can get inlined, regardless of the file extension in the URL. | ||
29 | * Inline image metadata goes under the image instead of possibly overlapping the label text. | ||
30 | * Inline downloads have a context menu for relevant actions, and clicking on the download opens the file. | ||
31 | * Improved highlighting of open pages. The highlight no longer goes under the side elements on the page. | ||
32 | * Entry dates in feed links are de-emphasized for improved readability. | ||
33 | * Revised link hover popup. None of the information appears on the same line any more (which was problematic if there wasn't enough space). Instead, everything is shown in a popup at the bottom/top of the view, including the identity that will be used when opening the link and the date of last visit. | ||
34 | * Revised layout of the Upload dialog. There is a new edit field that lets you modify the file path of the URL. Identity names are in bold. | ||
35 | * Optimized UI layout procedure and memory use during UI event processing. | ||
36 | * Audio subsystem is only initialized when actually needed. | ||
37 | * Prevent state file corruption if the app happens to get killed while state is being saved. | ||
38 | * Gempub: Open books in 1:2 split mode instead of 1:1. | ||
39 | * Minor improvements in page caching. | ||
40 | * Detect when text is Bengali, Devanagari, Oriya, or Tamil. | ||
41 | |||
42 | Fixes: | ||
43 | * Fixed a history caching issue: if there were multiple instances of the same URL in history, only the latest one's content would be used when navigating back/forward. | ||
44 | * Fixed handling of reserved characters in URLs (cf. RFC 3986, section 2.2). | ||
45 | * Fixed the copy/paste context menu not showing in input fields. | ||
46 | * Fixed duplicated warnings showing in the page banner. | ||
47 | * Fixed very narrow input fields causing the app to hang. | ||
48 | * Fixed initial scroll position in multiline input fields. | ||
49 | * Fixed layout issues in the sidebar on empty Feeds and Identities tabs. | ||
50 | * Fixed lookup results list becoming too narrow. | ||
51 | * Fixed glitches when a widget has multiple simultanous animations. | ||
52 | * Fixed mixed-language CJK word wrapping. | ||
53 | * Fixed parsing Atom feed dates with leading whitespace. | ||
54 | * Windows: Fixed installing individual TrueType fonts via drag and drop. | ||
55 | * macOS: Fixed high CPU usage during audio playback and UI animations. | ||
56 | * macOS: Line breaks inside menu items (e.g., info about current identity). | ||
57 | |||
58 | ## 1.9.5 | ||
59 | * Fixed misshapen button borders (SDL 2.0.16 line drawing workaround). | ||
60 | * Fixed actions being triggered when navigating via home row keys. | ||
61 | * macOS: Fixed native menu items being triggered when navigating via home row keys. | ||
62 | * macOS: Fixed native menu items triggering when changing key bindings. | ||
63 | |||
9 | ## 1.9.4 | 64 | ## 1.9.4 |
10 | * Fixed crash when a link is missing both URL and label (just a `=>`). | 65 | * Fixed crash when a link is missing both URL and label (just a `=>`). |
66 | * Fixed handling of foreground color escapes in the simple text renderer. | ||
67 | * Updated UI translations. | ||
68 | * Upgraded SDL to 2.0.18. | ||
11 | 69 | ||
12 | ## 1.9.3 | 70 | ## 1.9.3 |
13 | * Added UI language for Dutch. | 71 | * Added UI language for Dutch. |
diff --git a/res/iOSBundleInfo.plist.in b/res/iOSBundleInfo.plist.in index 09af4abf..c0c1a288 100644 --- a/res/iOSBundleInfo.plist.in +++ b/res/iOSBundleInfo.plist.in | |||
@@ -49,6 +49,11 @@ | |||
49 | </array> | 49 | </array> |
50 | <key>UILaunchStoryboardName</key> | 50 | <key>UILaunchStoryboardName</key> |
51 | <string>LaunchScreen</string> | 51 | <string>LaunchScreen</string> |
52 | <key>UIAppFonts</key> | ||
53 | <array> | ||
54 | <string>IosevkaTerm-Extended.ttf</string> | ||
55 | <string>SourceSans3-Regular.ttf</string> | ||
56 | </array> | ||
52 | <key>UIBackgroundModes</key> | 57 | <key>UIBackgroundModes</key> |
53 | <array> | 58 | <array> |
54 | <string>audio</string> | 59 | <string>audio</string> |
diff --git a/res/lang/cs.bin b/res/lang/cs.bin index 22043934..6624fbd4 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 b2bb35a0..9c3d4541 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 fbf4c73c..5f649846 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 d7f9cebf..ace592df 100644 --- a/res/lang/eo.bin +++ b/res/lang/eo.bin | |||
Binary files differ | |||
diff --git a/res/lang/es.bin b/res/lang/es.bin index 749645bf..09b7151b 100644 --- a/res/lang/es.bin +++ b/res/lang/es.bin | |||
Binary files differ | |||
diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin index 078de89d..dfcd6306 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 dee3561f..44d2741d 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 96c1148e..ad16b2e7 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 02482f3a..925994f9 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 7b7edb50..c070dd49 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 4750b545..34633dd1 100644 --- a/res/lang/ia.bin +++ b/res/lang/ia.bin | |||
Binary files differ | |||
diff --git a/res/lang/ie.bin b/res/lang/ie.bin index 14f16e5d..722ee20d 100644 --- a/res/lang/ie.bin +++ b/res/lang/ie.bin | |||
Binary files differ | |||
diff --git a/res/lang/isv.bin b/res/lang/isv.bin index 80754fc5..3be01643 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 6ae01202..dcc9fe97 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 c0affedf..a32e9d10 100644 --- a/res/lang/pl.bin +++ b/res/lang/pl.bin | |||
Binary files differ | |||
diff --git a/res/lang/ru.bin b/res/lang/ru.bin index f0762eea..6a9b8e67 100644 --- a/res/lang/ru.bin +++ b/res/lang/ru.bin | |||
Binary files differ | |||
diff --git a/res/lang/sk.bin b/res/lang/sk.bin index deda3b69..833872c9 100644 --- a/res/lang/sk.bin +++ b/res/lang/sk.bin | |||
Binary files differ | |||
diff --git a/res/lang/sr.bin b/res/lang/sr.bin index dc13d2e1..a1e87e95 100644 --- a/res/lang/sr.bin +++ b/res/lang/sr.bin | |||
Binary files differ | |||
diff --git a/res/lang/tok.bin b/res/lang/tok.bin index 6a93803c..d17f075e 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 1092938f..981ced04 100644 --- a/res/lang/tr.bin +++ b/res/lang/tr.bin | |||
Binary files differ | |||
diff --git a/res/lang/uk.bin b/res/lang/uk.bin index 4154c354..d5e78f6f 100644 --- a/res/lang/uk.bin +++ b/res/lang/uk.bin | |||
Binary files differ | |||
diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 3a83dd40..246b7c42 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 ffad7072..0e94f66a 100644 --- a/res/lang/zh_Hant.bin +++ b/res/lang/zh_Hant.bin | |||
Binary files differ | |||
diff --git a/sdl2.0.18-macos-ios.diff b/sdl2.0.18-macos-ios.diff new file mode 100644 index 00000000..8c143226 --- /dev/null +++ b/sdl2.0.18-macos-ios.diff | |||
@@ -0,0 +1,129 @@ | |||
1 | diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c | ||
2 | index 539f17138..a5a5b00fd 100644 | ||
3 | --- a/src/events/SDL_mouse.c | ||
4 | +++ b/src/events/SDL_mouse.c | ||
5 | @@ -709,8 +709,8 @@ SDL_SendMouseWheel(SDL_Window * window, SDL_MouseID mouseID, float x, float y, S | ||
6 | event.type = SDL_MOUSEWHEEL; | ||
7 | event.wheel.windowID = mouse->focus ? mouse->focus->id : 0; | ||
8 | event.wheel.which = mouseID; | ||
9 | - event.wheel.x = integral_x; | ||
10 | - event.wheel.y = integral_y; | ||
11 | + event.wheel.x = x; //integral_x; | ||
12 | + event.wheel.y = y; //integral_y; | ||
13 | event.wheel.preciseX = x; | ||
14 | event.wheel.preciseY = y; | ||
15 | event.wheel.direction = (Uint32)direction; | ||
16 | diff --git a/src/video/cocoa/SDL_cocoamouse.m b/src/video/cocoa/SDL_cocoamouse.m | ||
17 | index e9d832d64..4cfa3624b 100644 | ||
18 | --- a/src/video/cocoa/SDL_cocoamouse.m | ||
19 | +++ b/src/video/cocoa/SDL_cocoamouse.m | ||
20 | @@ -463,10 +463,16 @@ + (NSCursor *)invisibleCursor | ||
21 | } | ||
22 | |||
23 | SDL_MouseID mouseID = mouse->mouseID; | ||
24 | - CGFloat x = -[event deltaX]; | ||
25 | - CGFloat y = [event deltaY]; | ||
26 | + CGFloat x = -[event scrollingDeltaX]; | ||
27 | + CGFloat y = [event scrollingDeltaY]; | ||
28 | SDL_MouseWheelDirection direction = SDL_MOUSEWHEEL_NORMAL; | ||
29 | |||
30 | + /* HACK: Make a distinction between precise and imprecise scrolling. | ||
31 | + Trackpad seems to be mouseID 0. */ | ||
32 | + if (![event hasPreciseScrollingDeltas]) { | ||
33 | + mouseID = 1; | ||
34 | + } | ||
35 | + | ||
36 | if ([event respondsToSelector:@selector(isDirectionInvertedFromDevice)]) { | ||
37 | if ([event isDirectionInvertedFromDevice] == YES) { | ||
38 | direction = SDL_MOUSEWHEEL_FLIPPED; | ||
39 | diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h | ||
40 | index 489db169f..79a41dd50 100644 | ||
41 | --- a/src/video/cocoa/SDL_cocoawindow.h | ||
42 | +++ b/src/video/cocoa/SDL_cocoawindow.h | ||
43 | @@ -110,6 +110,8 @@ typedef enum | ||
44 | /* Touch event handling */ | ||
45 | -(void) handleTouches:(NSTouchPhase) phase withEvent:(NSEvent*) theEvent; | ||
46 | |||
47 | +-(void) syncMouseButtonAndKeyboardModifierState; | ||
48 | + | ||
49 | @end | ||
50 | /* *INDENT-ON* */ | ||
51 | |||
52 | diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m | ||
53 | index f09088c45..7379c2269 100644 | ||
54 | --- a/src/video/cocoa/SDL_cocoawindow.m | ||
55 | +++ b/src/video/cocoa/SDL_cocoawindow.m | ||
56 | @@ -1236,6 +1236,25 @@ - (void)otherMouseDown:(NSEvent *)theEvent | ||
57 | [self mouseDown:theEvent]; | ||
58 | } | ||
59 | |||
60 | +- (void)syncMouseButtonAndKeyboardModifierState { | ||
61 | + SDL_Mouse *mouse = SDL_GetMouse(); | ||
62 | + if (mouse) { | ||
63 | + for (int i = 0; i < mouse->num_sources; i++) { | ||
64 | + if (mouse->sources[i].mouseID == mouse->mouseID) { | ||
65 | + mouse->sources[i].buttonstate = 0; | ||
66 | + } | ||
67 | + } | ||
68 | + } | ||
69 | + SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LGUI); | ||
70 | + SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RGUI); | ||
71 | + SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT); | ||
72 | + SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RSHIFT); | ||
73 | + SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LCTRL); | ||
74 | + SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RCTRL); | ||
75 | + SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LALT); | ||
76 | + SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_RALT); | ||
77 | +} | ||
78 | + | ||
79 | - (void)mouseUp:(NSEvent *)theEvent | ||
80 | { | ||
81 | SDL_Mouse *mouse = SDL_GetMouse(); | ||
82 | diff --git a/src/video/uikit/SDL_uikitviewcontroller.h b/src/video/uikit/SDL_uikitviewcontroller.h | ||
83 | index f7f4c9de6..50c72aad0 100644 | ||
84 | --- a/src/video/uikit/SDL_uikitviewcontroller.h | ||
85 | +++ b/src/video/uikit/SDL_uikitviewcontroller.h | ||
86 | @@ -58,10 +58,13 @@ | ||
87 | #if !TARGET_OS_TV | ||
88 | - (NSUInteger)supportedInterfaceOrientations; | ||
89 | - (BOOL)prefersStatusBarHidden; | ||
90 | +- (void)setStatusStyle:(UIStatusBarStyle)style; | ||
91 | +- (UIStatusBarStyle)preferredStatusBarStyle; | ||
92 | - (BOOL)prefersHomeIndicatorAutoHidden; | ||
93 | - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures; | ||
94 | |||
95 | @property (nonatomic, assign) int homeIndicatorHidden; | ||
96 | +@property (nonatomic, assign) UIStatusBarStyle statusBarStyle; | ||
97 | #endif | ||
98 | |||
99 | #if SDL_IPHONE_KEYBOARD | ||
100 | diff --git a/src/video/uikit/SDL_uikitviewcontroller.m b/src/video/uikit/SDL_uikitviewcontroller.m | ||
101 | index b2db4037d..d39f8085c 100644 | ||
102 | --- a/src/video/uikit/SDL_uikitviewcontroller.m | ||
103 | +++ b/src/video/uikit/SDL_uikitviewcontroller.m | ||
104 | @@ -105,6 +105,7 @@ - (instancetype)initWithSDLWindow:(SDL_Window *)_window | ||
105 | #endif | ||
106 | |||
107 | #if !TARGET_OS_TV | ||
108 | + self.statusBarStyle = UIStatusBarStyleDefault; | ||
109 | SDL_AddHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR, | ||
110 | SDL_HideHomeIndicatorHintChanged, | ||
111 | (__bridge void *) self); | ||
112 | @@ -230,6 +231,17 @@ - (BOOL)prefersHomeIndicatorAutoHidden | ||
113 | return hidden; | ||
114 | } | ||
115 | |||
116 | +- (void)setStatusStyle:(UIStatusBarStyle)style | ||
117 | +{ | ||
118 | + self.statusBarStyle = style; | ||
119 | + [self setNeedsStatusBarAppearanceUpdate]; | ||
120 | +} | ||
121 | + | ||
122 | +- (UIStatusBarStyle)preferredStatusBarStyle | ||
123 | +{ | ||
124 | + return self.statusBarStyle; | ||
125 | +} | ||
126 | + | ||
127 | - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures | ||
128 | { | ||
129 | if (self.homeIndicatorHidden >= 0) { | ||
@@ -55,6 +55,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
55 | #include <the_Foundation/path.h> | 55 | #include <the_Foundation/path.h> |
56 | #include <the_Foundation/process.h> | 56 | #include <the_Foundation/process.h> |
57 | #include <the_Foundation/sortedarray.h> | 57 | #include <the_Foundation/sortedarray.h> |
58 | #include <the_Foundation/stringset.h> | ||
58 | #include <the_Foundation/time.h> | 59 | #include <the_Foundation/time.h> |
59 | #include <the_Foundation/version.h> | 60 | #include <the_Foundation/version.h> |
60 | #include <SDL.h> | 61 | #include <SDL.h> |
@@ -71,6 +72,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
71 | #if defined (iPlatformAppleMobile) | 72 | #if defined (iPlatformAppleMobile) |
72 | # include "ios.h" | 73 | # include "ios.h" |
73 | #endif | 74 | #endif |
75 | #if defined (iPlatformAndroidMobile) | ||
76 | #include <SDL_log.h> | ||
77 | #endif | ||
74 | #if defined (iPlatformMsys) | 78 | #if defined (iPlatformMsys) |
75 | # include "win32.h" | 79 | # include "win32.h" |
76 | #endif | 80 | #endif |
@@ -108,6 +112,7 @@ static const char *defaultDataDir_App_ = "~/config/settings/lagrange"; | |||
108 | static const char *prefsFileName_App_ = "prefs.cfg"; | 112 | static const char *prefsFileName_App_ = "prefs.cfg"; |
109 | static const char *oldStateFileName_App_ = "state.binary"; | 113 | static const char *oldStateFileName_App_ = "state.binary"; |
110 | static const char *stateFileName_App_ = "state.lgr"; | 114 | static const char *stateFileName_App_ = "state.lgr"; |
115 | static const char *tempStateFileName_App_ = "state.lgr.tmp"; | ||
111 | static const char *defaultDownloadDir_App_ = "~/Downloads"; | 116 | static const char *defaultDownloadDir_App_ = "~/Downloads"; |
112 | 117 | ||
113 | static const int idleThreshold_App_ = 1000; /* ms */ | 118 | static const int idleThreshold_App_ = 1000; /* ms */ |
@@ -115,6 +120,7 @@ static const int idleThreshold_App_ = 1000; /* ms */ | |||
115 | struct Impl_App { | 120 | struct Impl_App { |
116 | iCommandLine args; | 121 | iCommandLine args; |
117 | iString * execPath; | 122 | iString * execPath; |
123 | iStringSet * tempFilesPendingDeletion; | ||
118 | iMimeHooks * mimehooks; | 124 | iMimeHooks * mimehooks; |
119 | iGmCerts * certs; | 125 | iGmCerts * certs; |
120 | iVisited * visited; | 126 | iVisited * visited; |
@@ -127,6 +133,7 @@ struct Impl_App { | |||
127 | iBool isRunning; | 133 | iBool isRunning; |
128 | iBool isRunningUnderWindowSystem; | 134 | iBool isRunningUnderWindowSystem; |
129 | iBool isDarkSystemTheme; | 135 | iBool isDarkSystemTheme; |
136 | iBool isSuspended; | ||
130 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 137 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
131 | iBool isIdling; | 138 | iBool isIdling; |
132 | uint32_t lastEventTime; | 139 | uint32_t lastEventTime; |
@@ -164,7 +171,11 @@ struct Impl_Ticker { | |||
164 | 171 | ||
165 | static int cmp_Ticker_(const void *a, const void *b) { | 172 | static int cmp_Ticker_(const void *a, const void *b) { |
166 | const iTicker *elems[2] = { a, b }; | 173 | const iTicker *elems[2] = { a, b }; |
167 | return iCmp(elems[0]->context, elems[1]->context); | 174 | const int cmp = iCmp(elems[0]->context, elems[1]->context); |
175 | if (cmp) { | ||
176 | return cmp; | ||
177 | } | ||
178 | return iCmp((void *) elems[0]->callback, (void *) elems[1]->callback); | ||
168 | } | 179 | } |
169 | 180 | ||
170 | /*----------------------------------------------------------------------------------------------*/ | 181 | /*----------------------------------------------------------------------------------------------*/ |
@@ -237,6 +248,13 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
237 | appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); | 248 | appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); |
238 | appendFormat_String(str, "linespacing.set arg:%f\n", d->prefs.lineSpacing); | 249 | appendFormat_String(str, "linespacing.set arg:%f\n", d->prefs.lineSpacing); |
239 | appendFormat_String(str, "returnkey.set arg:%d\n", d->prefs.returnKey); | 250 | appendFormat_String(str, "returnkey.set arg:%d\n", d->prefs.returnKey); |
251 | for (size_t i = 0; i < iElemCount(d->prefs.navbarActions); i++) { | ||
252 | appendFormat_String(str, "navbar.action.set arg:%d button:%d\n", d->prefs.navbarActions[i], i); | ||
253 | } | ||
254 | #if defined (iPlatformMobile) | ||
255 | appendFormat_String(str, "toolbar.action.set arg:%d button:0\n", d->prefs.toolbarActions[0]); | ||
256 | appendFormat_String(str, "toolbar.action.set arg:%d button:1\n", d->prefs.toolbarActions[1]); | ||
257 | #endif | ||
240 | iConstForEach(StringSet, fp, d->prefs.disabledFontPacks) { | 258 | iConstForEach(StringSet, fp, d->prefs.disabledFontPacks) { |
241 | appendFormat_String(str, "fontpack.disable id:%s\n", cstr_String(fp.value)); | 259 | appendFormat_String(str, "fontpack.disable id:%s\n", cstr_String(fp.value)); |
242 | } | 260 | } |
@@ -263,6 +281,7 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
263 | { "prefs.bookmarks.addbottom", &d->prefs.addBookmarksToBottom }, | 281 | { "prefs.bookmarks.addbottom", &d->prefs.addBookmarksToBottom }, |
264 | { "prefs.archive.openindex", &d->prefs.openArchiveIndexPages }, | 282 | { "prefs.archive.openindex", &d->prefs.openArchiveIndexPages }, |
265 | { "prefs.font.warnmissing", &d->prefs.warnAboutMissingGlyphs }, | 283 | { "prefs.font.warnmissing", &d->prefs.warnAboutMissingGlyphs }, |
284 | { "prefs.blink", &d->prefs.blinkingCursor }, | ||
266 | }; | 285 | }; |
267 | iForIndices(i, boolPrefs) { | 286 | iForIndices(i, boolPrefs) { |
268 | appendFormat_String(str, "%s.changed arg:%d\n", boolPrefs[i].id, *boolPrefs[i].value); | 287 | appendFormat_String(str, "%s.changed arg:%d\n", boolPrefs[i].id, *boolPrefs[i].value); |
@@ -313,6 +332,9 @@ static const char *dataDir_App_(void) { | |||
313 | } | 332 | } |
314 | 333 | ||
315 | static const char *downloadDir_App_(void) { | 334 | static const char *downloadDir_App_(void) { |
335 | #if defined (iPlatformAndroidMobile) | ||
336 | return concatPath_CStr(SDL_AndroidGetInternalStoragePath(), "Downloads"); | ||
337 | #endif | ||
316 | #if defined (iPlatformLinux) || defined (iPlatformOther) | 338 | #if defined (iPlatformLinux) || defined (iPlatformOther) |
317 | /* Parse user-dirs.dirs using the `xdg-user-dir` tool. */ | 339 | /* Parse user-dirs.dirs using the `xdg-user-dir` tool. */ |
318 | iProcess *proc = iClob(new_Process()); | 340 | iProcess *proc = iClob(new_Process()); |
@@ -363,7 +385,7 @@ static void loadPrefs_App_(iApp *d) { | |||
363 | setUiScale_Window(get_Window(), argf_Command(cmd)); | 385 | setUiScale_Window(get_Window(), argf_Command(cmd)); |
364 | } | 386 | } |
365 | else if (equal_Command(cmd, "uilang")) { | 387 | else if (equal_Command(cmd, "uilang")) { |
366 | const char *id = cstr_Rangecc(range_Command(cmd, "id")); | 388 | const char *id = cstr_Command(cmd, "id"); |
367 | setCStr_String(&d->prefs.strings[uiLanguage_PrefsString], id); | 389 | setCStr_String(&d->prefs.strings[uiLanguage_PrefsString], id); |
368 | setCurrent_Lang(id); | 390 | setCurrent_Lang(id); |
369 | } | 391 | } |
@@ -385,6 +407,12 @@ static void loadPrefs_App_(iApp *d) { | |||
385 | insert_StringSet(d->prefs.disabledFontPacks, | 407 | insert_StringSet(d->prefs.disabledFontPacks, |
386 | collect_String(suffix_Command(cmd, "id"))); | 408 | collect_String(suffix_Command(cmd, "id"))); |
387 | } | 409 | } |
410 | #if defined (iPlatformAndroidMobile) | ||
411 | else if (equal_Command(cmd, "returnkey.set")) { | ||
412 | /* Hardcoded to avoid accidental presses of the virtual Return key. */ | ||
413 | d->prefs.returnKey = default_ReturnKeyBehavior; | ||
414 | } | ||
415 | #endif | ||
388 | #if !defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) | 416 | #if !defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) |
389 | else if (equal_Command(cmd, "downloads")) { | 417 | else if (equal_Command(cmd, "downloads")) { |
390 | continue; /* can't change downloads directory */ | 418 | continue; /* can't change downloads directory */ |
@@ -410,11 +438,13 @@ static void loadPrefs_App_(iApp *d) { | |||
410 | iRelease(f); | 438 | iRelease(f); |
411 | /* Upgrade checks. */ | 439 | /* Upgrade checks. */ |
412 | if (cmp_Version(&upgradedFromAppVersion, &(iVersion){ 1, 8, 0 }) < 0) { | 440 | if (cmp_Version(&upgradedFromAppVersion, &(iVersion){ 1, 8, 0 }) < 0) { |
441 | #if !defined (iPlatformAppleMobile) && !defined (iPlatformAndroidMobile) | ||
413 | /* When upgrading to v1.8.0, the old hardcoded font library is gone and that means | 442 | /* When upgrading to v1.8.0, the old hardcoded font library is gone and that means |
414 | UI strings may not have the right fonts available for the UI to remain | 443 | UI strings may not have the right fonts available for the UI to remain |
415 | usable. */ | 444 | usable. */ |
416 | postCommandf_App("uilang id:en"); | 445 | postCommandf_App("uilang id:en"); |
417 | postCommand_App("~fontpack.suggest.classic"); | 446 | postCommand_App("~fontpack.suggest.classic"); |
447 | #endif | ||
418 | } | 448 | } |
419 | #if !defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 449 | #if !defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
420 | d->prefs.customFrame = iFalse; | 450 | d->prefs.customFrame = iFalse; |
@@ -505,7 +535,7 @@ static iBool loadState_App_(iApp *d) { | |||
505 | if (flags & 8) { | 535 | if (flags & 8) { |
506 | postCommand_Widget(sidebar2, "feeds.mode arg:%d", unread_FeedsMode); | 536 | postCommand_Widget(sidebar2, "feeds.mode arg:%d", unread_FeedsMode); |
507 | } | 537 | } |
508 | if (deviceType_App() != phone_AppDeviceType) { | 538 | if (deviceType_App() == desktop_AppDeviceType) { |
509 | setWidth_SidebarWidget(sidebar, widths[0]); | 539 | setWidth_SidebarWidget(sidebar, widths[0]); |
510 | setWidth_SidebarWidget(sidebar2, widths[1]); | 540 | setWidth_SidebarWidget(sidebar2, widths[1]); |
511 | if (flags & 1) postCommand_Root(root, "sidebar.toggle noanim:1"); | 541 | if (flags & 1) postCommand_Root(root, "sidebar.toggle noanim:1"); |
@@ -561,7 +591,7 @@ static void saveState_App_(const iApp *d) { | |||
561 | navigation history, cached content) and depends closely on the widget | 591 | navigation history, cached content) and depends closely on the widget |
562 | tree. The data is largely not reorderable and should not be modified | 592 | tree. The data is largely not reorderable and should not be modified |
563 | by the user manually. */ | 593 | by the user manually. */ |
564 | iFile *f = newCStr_File(concatPath_CStr(dataDir_App_(), stateFileName_App_)); | 594 | iFile *f = newCStr_File(concatPath_CStr(dataDir_App_(), tempStateFileName_App_)); |
565 | if (open_File(f, writeOnly_FileMode)) { | 595 | if (open_File(f, writeOnly_FileMode)) { |
566 | writeData_File(f, magicState_App_, 4); | 596 | writeData_File(f, magicState_App_, 4); |
567 | writeU32_File(f, latest_FileVersion); /* version */ | 597 | writeU32_File(f, latest_FileVersion); /* version */ |
@@ -603,11 +633,19 @@ static void saveState_App_(const iApp *d) { | |||
603 | write8_File(f, flags); | 633 | write8_File(f, flags); |
604 | serializeState_DocumentWidget(i.object, stream_File(f)); | 634 | serializeState_DocumentWidget(i.object, stream_File(f)); |
605 | } | 635 | } |
636 | iRelease(f); | ||
606 | } | 637 | } |
607 | else { | 638 | else { |
639 | iRelease(f); | ||
608 | fprintf(stderr, "[App] failed to save state: %s\n", strerror(errno)); | 640 | fprintf(stderr, "[App] failed to save state: %s\n", strerror(errno)); |
641 | return; | ||
609 | } | 642 | } |
610 | iRelease(f); | 643 | /* Copy it over to the real file. This avoids truncation if the app for any reason crashes |
644 | before the state file is fully written. */ | ||
645 | const char *tempName = concatPath_CStr(dataDir_App_(), tempStateFileName_App_); | ||
646 | const char *finalName = concatPath_CStr(dataDir_App_(), stateFileName_App_); | ||
647 | remove(finalName); | ||
648 | rename(tempName, finalName); | ||
611 | } | 649 | } |
612 | 650 | ||
613 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 651 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
@@ -678,18 +716,22 @@ static void communicateWithRunningInstance_App_(iApp *d, iProcessId instance, | |||
678 | appendCStr_String(cmds, "tabs.new\n"); | 716 | appendCStr_String(cmds, "tabs.new\n"); |
679 | requestRaise = iTrue; | 717 | requestRaise = iTrue; |
680 | } | 718 | } |
719 | iBool gotResult = iFalse; | ||
681 | if (!isEmpty_String(cmds)) { | 720 | if (!isEmpty_String(cmds)) { |
682 | iString *result = communicate_Ipc(cmds, requestRaise); | 721 | iString *result = communicate_Ipc(cmds, requestRaise); |
683 | if (result) { | 722 | if (result) { |
684 | fwrite(cstr_String(result), 1, size_String(result), stdout); | 723 | fwrite(cstr_String(result), 1, size_String(result), stdout); |
685 | fflush(stdout); | 724 | fflush(stdout); |
725 | if (!isEmpty_String(result)) { | ||
726 | gotResult = iTrue; | ||
727 | } | ||
686 | } | 728 | } |
687 | delete_String(result); | 729 | delete_String(result); |
688 | } | 730 | } |
689 | iUnused(instance); | 731 | iUnused(instance); |
690 | // else { | 732 | if (!gotResult) { |
691 | // printf("Lagrange already running (PID %d)\n", instance); | 733 | printf("Commands sent to Lagrange process %d\n", instance); |
692 | // } | 734 | } |
693 | terminate_App_(0); | 735 | terminate_App_(0); |
694 | } | 736 | } |
695 | #endif /* defined (LAGRANGE_ENABLE_IPC) */ | 737 | #endif /* defined (LAGRANGE_ENABLE_IPC) */ |
@@ -714,6 +756,8 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
714 | d->isRunningUnderWindowSystem = iTrue; | 756 | d->isRunningUnderWindowSystem = iTrue; |
715 | #endif | 757 | #endif |
716 | d->isDarkSystemTheme = iTrue; /* will be updated by system later on, if supported */ | 758 | d->isDarkSystemTheme = iTrue; /* will be updated by system later on, if supported */ |
759 | d->isSuspended = iFalse; | ||
760 | d->tempFilesPendingDeletion = new_StringSet(); | ||
717 | init_CommandLine(&d->args, argc, argv); | 761 | init_CommandLine(&d->args, argc, argv); |
718 | /* Where was the app started from? We ask SDL first because the command line alone | 762 | /* Where was the app started from? We ask SDL first because the command line alone |
719 | cannot be relied on (behavior differs depending on OS). */ { | 763 | cannot be relied on (behavior differs depending on OS). */ { |
@@ -920,6 +964,8 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
920 | if (!loadState_App_(d)) { | 964 | if (!loadState_App_(d)) { |
921 | postCommand_Root(NULL, "open url:about:help"); | 965 | postCommand_Root(NULL, "open url:about:help"); |
922 | } | 966 | } |
967 | postCommand_App("~navbar.actions.changed"); | ||
968 | postCommand_App("~toolbar.actions.changed"); | ||
923 | postCommand_Root(NULL, "~window.unfreeze"); | 969 | postCommand_Root(NULL, "~window.unfreeze"); |
924 | postCommand_Root(NULL, "font.reset"); | 970 | postCommand_Root(NULL, "font.reset"); |
925 | d->autoReloadTimer = SDL_AddTimer(60 * 1000, postAutoReloadCommand_App_, NULL); | 971 | d->autoReloadTimer = SDL_AddTimer(60 * 1000, postAutoReloadCommand_App_, NULL); |
@@ -986,6 +1032,11 @@ static void deinit_App(iApp *d) { | |||
986 | deinit_Periodic(&d->periodic); | 1032 | deinit_Periodic(&d->periodic); |
987 | deinit_Lang(); | 1033 | deinit_Lang(); |
988 | iRecycle(); | 1034 | iRecycle(); |
1035 | /* Delete all temporary files created while running. */ | ||
1036 | iConstForEach(StringSet, tmp, d->tempFilesPendingDeletion) { | ||
1037 | remove(cstr_String(tmp.value)); | ||
1038 | } | ||
1039 | iRelease(d->tempFilesPendingDeletion); | ||
989 | } | 1040 | } |
990 | 1041 | ||
991 | const iString *execPath_App(void) { | 1042 | const iString *execPath_App(void) { |
@@ -1000,7 +1051,7 @@ const iString *downloadDir_App(void) { | |||
1000 | return collect_String(cleaned_Path(&app_.prefs.strings[downloadDir_PrefsString])); | 1051 | return collect_String(cleaned_Path(&app_.prefs.strings[downloadDir_PrefsString])); |
1001 | } | 1052 | } |
1002 | 1053 | ||
1003 | const iString *downloadPathForUrl_App(const iString *url, const iString *mime) { | 1054 | const iString *fileNameForUrl_App(const iString *url, const iString *mime) { |
1004 | /* Figure out a file name from the URL. */ | 1055 | /* Figure out a file name from the URL. */ |
1005 | iUrl parts; | 1056 | iUrl parts; |
1006 | init_Url(&parts, url); | 1057 | init_Url(&parts, url); |
@@ -1026,22 +1077,27 @@ const iString *downloadPathForUrl_App(const iString *url, const iString *mime) { | |||
1026 | } | 1077 | } |
1027 | } | 1078 | } |
1028 | if (startsWith_String(name, "~")) { | 1079 | if (startsWith_String(name, "~")) { |
1029 | /* This would be interpreted as a reference to a home directory. */ | 1080 | /* This might be interpreted as a reference to a home directory. */ |
1030 | remove_Block(&name->chars, 0, 1); | 1081 | remove_Block(&name->chars, 0, 1); |
1031 | } | 1082 | } |
1032 | iString *savePath = concat_Path(downloadDir_App(), name); | 1083 | if (lastIndexOfCStr_String(name, ".") == iInvalidPos) { |
1033 | if (lastIndexOfCStr_String(savePath, ".") == iInvalidPos) { | 1084 | /* TODO: Needs the inverse of `mediaTypeFromFileExtension_String()`. */ |
1034 | /* No extension specified in URL. */ | 1085 | /* No extension specified in URL. */ |
1035 | if (startsWith_String(mime, "text/gemini")) { | 1086 | if (startsWith_String(mime, "text/gemini")) { |
1036 | appendCStr_String(savePath, ".gmi"); | 1087 | appendCStr_String(name, ".gmi"); |
1037 | } | 1088 | } |
1038 | else if (startsWith_String(mime, "text/")) { | 1089 | else if (startsWith_String(mime, "text/")) { |
1039 | appendCStr_String(savePath, ".txt"); | 1090 | appendCStr_String(name, ".txt"); |
1040 | } | 1091 | } |
1041 | else if (startsWith_String(mime, "image/")) { | 1092 | else if (startsWith_String(mime, "image/")) { |
1042 | appendCStr_String(savePath, cstr_String(mime) + 6); | 1093 | appendCStr_String(name, cstr_String(mime) + 6); |
1043 | } | 1094 | } |
1044 | } | 1095 | } |
1096 | return name; | ||
1097 | } | ||
1098 | |||
1099 | const iString *downloadPathForUrl_App(const iString *url, const iString *mime) { | ||
1100 | iString *savePath = concat_Path(downloadDir_App(), fileNameForUrl_App(url, mime)); | ||
1045 | if (fileExists_FileInfo(savePath)) { | 1101 | if (fileExists_FileInfo(savePath)) { |
1046 | /* Make it unique. */ | 1102 | /* Make it unique. */ |
1047 | iDate now; | 1103 | iDate now; |
@@ -1056,6 +1112,22 @@ const iString *downloadPathForUrl_App(const iString *url, const iString *mime) { | |||
1056 | return collect_String(savePath); | 1112 | return collect_String(savePath); |
1057 | } | 1113 | } |
1058 | 1114 | ||
1115 | const iString *temporaryPathForUrl_App(const iString *url, const iString *mime) { | ||
1116 | iApp *d = &app_; | ||
1117 | #if defined (P_tmpdir) | ||
1118 | iString * tmpPath = collectNew_String(); | ||
1119 | const iRangecc tmpDir = range_CStr(P_tmpdir); | ||
1120 | #else | ||
1121 | iString * tmpPath = collectNewCStr_String(tmpnam(NULL)); | ||
1122 | const iRangecc tmpDir = dirName_Path(tmpPath); | ||
1123 | #endif | ||
1124 | set_String( | ||
1125 | tmpPath, | ||
1126 | collect_String(concat_Path(collectNewRange_String(tmpDir), fileNameForUrl_App(url, mime)))); | ||
1127 | insert_StringSet(d->tempFilesPendingDeletion, tmpPath); /* deleted in `deinit_App` */ | ||
1128 | return tmpPath; | ||
1129 | } | ||
1130 | |||
1059 | const iString *debugInfo_App(void) { | 1131 | const iString *debugInfo_App(void) { |
1060 | extern char **environ; /* The environment variables. */ | 1132 | extern char **environ; /* The environment variables. */ |
1061 | iApp *d = &app_; | 1133 | iApp *d = &app_; |
@@ -1175,9 +1247,6 @@ iBool findCachedContent_App(const iString *url, iString *mime_out, iBlock *data_ | |||
1175 | #endif | 1247 | #endif |
1176 | 1248 | ||
1177 | iLocalDef iBool isWaitingAllowed_App_(iApp *d) { | 1249 | iLocalDef iBool isWaitingAllowed_App_(iApp *d) { |
1178 | if (!isEmpty_Periodic(&d->periodic)) { | ||
1179 | return iFalse; | ||
1180 | } | ||
1181 | if (d->warmupFrames > 0) { | 1250 | if (d->warmupFrames > 0) { |
1182 | return iFalse; | 1251 | return iFalse; |
1183 | } | 1252 | } |
@@ -1191,16 +1260,19 @@ iLocalDef iBool isWaitingAllowed_App_(iApp *d) { | |||
1191 | 1260 | ||
1192 | static iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *event) { | 1261 | static iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *event) { |
1193 | if (eventMode == waitForNewEvents_AppEventMode && isWaitingAllowed_App_(d)) { | 1262 | if (eventMode == waitForNewEvents_AppEventMode && isWaitingAllowed_App_(d)) { |
1194 | /* If there are periodic commands pending, wait only for a short while. */ | ||
1195 | if (!isEmpty_Periodic(&d->periodic)) { | ||
1196 | return SDL_WaitEventTimeout(event, 500); | ||
1197 | } | ||
1198 | /* We may be allowed to block here until an event comes in. */ | 1263 | /* We may be allowed to block here until an event comes in. */ |
1199 | if (isWaitingAllowed_App_(d)) { | 1264 | if (isWaitingAllowed_App_(d)) { |
1200 | return SDL_WaitEvent(event); | 1265 | return SDL_WaitEvent(event); |
1201 | } | 1266 | } |
1202 | } | 1267 | } |
1268 | /* SDL regression circa 2.0.18? SDL_PollEvent() doesn't always return | ||
1269 | events posted immediately beforehand. Waiting with a very short timeout | ||
1270 | seems to work better. */ | ||
1271 | #if defined (iPlatformLinux) && SDL_VERSION_ATLEAST(2, 0, 18) | ||
1272 | return SDL_WaitEventTimeout(event, 1); | ||
1273 | #else | ||
1203 | return SDL_PollEvent(event); | 1274 | return SDL_PollEvent(event); |
1275 | #endif | ||
1204 | } | 1276 | } |
1205 | 1277 | ||
1206 | static iPtrArray *listWindows_App_(const iApp *d, iPtrArray *windows) { | 1278 | static iPtrArray *listWindows_App_(const iApp *d, iPtrArray *windows) { |
@@ -1239,6 +1311,7 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1239 | break; | 1311 | break; |
1240 | case SDL_APP_WILLENTERFOREGROUND: | 1312 | case SDL_APP_WILLENTERFOREGROUND: |
1241 | invalidate_Window(as_Window(d->window)); | 1313 | invalidate_Window(as_Window(d->window)); |
1314 | d->isSuspended = iFalse; | ||
1242 | break; | 1315 | break; |
1243 | case SDL_APP_DIDENTERFOREGROUND: | 1316 | case SDL_APP_DIDENTERFOREGROUND: |
1244 | gotEvents = iTrue; | 1317 | gotEvents = iTrue; |
@@ -1256,6 +1329,7 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1256 | setFreezeDraw_MainWindow(d->window, iTrue); | 1329 | setFreezeDraw_MainWindow(d->window, iTrue); |
1257 | savePrefs_App_(d); | 1330 | savePrefs_App_(d); |
1258 | saveState_App_(d); | 1331 | saveState_App_(d); |
1332 | d->isSuspended = iTrue; | ||
1259 | break; | 1333 | break; |
1260 | case SDL_APP_TERMINATING: | 1334 | case SDL_APP_TERMINATING: |
1261 | setFreezeDraw_MainWindow(d->window, iTrue); | 1335 | setFreezeDraw_MainWindow(d->window, iTrue); |
@@ -1284,6 +1358,10 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1284 | break; | 1358 | break; |
1285 | } | 1359 | } |
1286 | default: { | 1360 | default: { |
1361 | if (ev.type == SDL_USEREVENT && ev.user.code == periodic_UserEventCode) { | ||
1362 | dispatchCommands_Periodic(&d->periodic); | ||
1363 | continue; | ||
1364 | } | ||
1287 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 1365 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
1288 | if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) { | 1366 | if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) { |
1289 | if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_ && | 1367 | if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_ && |
@@ -1323,28 +1401,6 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1323 | #endif | 1401 | #endif |
1324 | /* Scroll events may be per-pixel or mouse wheel steps. */ | 1402 | /* Scroll events may be per-pixel or mouse wheel steps. */ |
1325 | if (ev.type == SDL_MOUSEWHEEL) { | 1403 | if (ev.type == SDL_MOUSEWHEEL) { |
1326 | #if defined (iPlatformAppleDesktop) | ||
1327 | /* On macOS, we handle both trackpad and mouse events. We expect SDL to identify | ||
1328 | which device is sending the event. */ | ||
1329 | if (ev.wheel.which == 0) { | ||
1330 | /* Trackpad with precise scrolling w/inertia (points). */ | ||
1331 | setPerPixel_MouseWheelEvent(&ev.wheel, iTrue); | ||
1332 | ev.wheel.x *= -d->window->base.pixelRatio; | ||
1333 | ev.wheel.y *= d->window->base.pixelRatio; | ||
1334 | /* Only scroll on one axis at a time. */ | ||
1335 | if (iAbs(ev.wheel.x) > iAbs(ev.wheel.y)) { | ||
1336 | ev.wheel.y = 0; | ||
1337 | } | ||
1338 | else { | ||
1339 | ev.wheel.x = 0; | ||
1340 | } | ||
1341 | } | ||
1342 | else { | ||
1343 | /* Disregard wheel acceleration applied by the OS. */ | ||
1344 | ev.wheel.x = -ev.wheel.x; | ||
1345 | ev.wheel.y = iSign(ev.wheel.y); | ||
1346 | } | ||
1347 | #endif | ||
1348 | #if defined (iPlatformMsys) | 1404 | #if defined (iPlatformMsys) |
1349 | ev.wheel.x = -ev.wheel.x; | 1405 | ev.wheel.x = -ev.wheel.x; |
1350 | #endif | 1406 | #endif |
@@ -1460,10 +1516,10 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1460 | deinit_PtrArray(&windows); | 1516 | deinit_PtrArray(&windows); |
1461 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 1517 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
1462 | if (d->isIdling && !gotEvents) { | 1518 | if (d->isIdling && !gotEvents) { |
1463 | /* This is where we spend most of our time when idle. 60 Hz still quite a lot but we | 1519 | /* This is where we spend most of our time when idle. 30 Hz still quite a lot but we |
1464 | can't wait too long after the user tries to interact again with the app. In any | 1520 | can't wait too long after the user tries to interact again with the app. In any |
1465 | case, on macOS SDL_WaitEvent() seems to use 10x more CPU time than sleeping. */ | 1521 | case, on iOS SDL_WaitEvent() seems to use 10x more CPU time than sleeping (2.0.18). */ |
1466 | SDL_Delay(1000 / 60); | 1522 | SDL_Delay(1000 / 30); |
1467 | } | 1523 | } |
1468 | #endif | 1524 | #endif |
1469 | backToMainLoop:; | 1525 | backToMainLoop:; |
@@ -1478,6 +1534,12 @@ static void runTickers_App_(iApp *d) { | |||
1478 | d->lastTickerTime = 0; | 1534 | d->lastTickerTime = 0; |
1479 | return; | 1535 | return; |
1480 | } | 1536 | } |
1537 | iForIndices(i, d->window->base.roots) { | ||
1538 | iRoot *root = d->window->base.roots[i]; | ||
1539 | if (root) { | ||
1540 | root->didAnimateVisualOffsets = iFalse; | ||
1541 | } | ||
1542 | } | ||
1481 | /* Tickers may add themselves again, so we'll run off a copy. */ | 1543 | /* Tickers may add themselves again, so we'll run off a copy. */ |
1482 | iSortedArray *pending = copy_SortedArray(&d->tickers); | 1544 | iSortedArray *pending = copy_SortedArray(&d->tickers); |
1483 | clear_SortedArray(&d->tickers); | 1545 | clear_SortedArray(&d->tickers); |
@@ -1494,6 +1556,12 @@ static void runTickers_App_(iApp *d) { | |||
1494 | if (isEmpty_SortedArray(&d->tickers)) { | 1556 | if (isEmpty_SortedArray(&d->tickers)) { |
1495 | d->lastTickerTime = 0; | 1557 | d->lastTickerTime = 0; |
1496 | } | 1558 | } |
1559 | // iForIndices(i, d->window->base.roots) { | ||
1560 | // iRoot *root = d->window->base.roots[i]; | ||
1561 | // if (root) { | ||
1562 | // notifyVisualOffsetChange_Root(root); | ||
1563 | // } | ||
1564 | // } | ||
1497 | } | 1565 | } |
1498 | 1566 | ||
1499 | static int resizeWatcher_(void *user, SDL_Event *event) { | 1567 | static int resizeWatcher_(void *user, SDL_Event *event) { |
@@ -1526,7 +1594,6 @@ static int run_App_(iApp *d) { | |||
1526 | SDL_AddEventWatch(resizeWatcher_, d); /* redraw window during resizing */ | 1594 | SDL_AddEventWatch(resizeWatcher_, d); /* redraw window during resizing */ |
1527 | #endif | 1595 | #endif |
1528 | while (d->isRunning) { | 1596 | while (d->isRunning) { |
1529 | dispatchCommands_Periodic(&d->periodic); | ||
1530 | processEvents_App(waitForNewEvents_AppEventMode); | 1597 | processEvents_App(waitForNewEvents_AppEventMode); |
1531 | runTickers_App_(d); | 1598 | runTickers_App_(d); |
1532 | refresh_App(); | 1599 | refresh_App(); |
@@ -1680,13 +1747,20 @@ void postCommand_Root(iRoot *d, const char *command) { | |||
1680 | ev.user.data1 = strdup(command); | 1747 | ev.user.data1 = strdup(command); |
1681 | ev.user.data2 = d; /* all events are root-specific */ | 1748 | ev.user.data2 = d; /* all events are root-specific */ |
1682 | SDL_PushEvent(&ev); | 1749 | SDL_PushEvent(&ev); |
1750 | iWindow *win = get_Window(); | ||
1751 | #if defined (iPlatformAndroid) | ||
1752 | SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s[command] {%d} %s", | ||
1753 | app_.isLoadingPrefs ? "[Prefs] " : "", | ||
1754 | (d == NULL || win == NULL ? 0 : d == win->roots[0] ? 1 : 2), | ||
1755 | command); | ||
1756 | #else | ||
1683 | if (app_.commandEcho) { | 1757 | if (app_.commandEcho) { |
1684 | iWindow *win = get_Window(); | ||
1685 | printf("%s[command] {%d} %s\n", | 1758 | printf("%s[command] {%d} %s\n", |
1686 | app_.isLoadingPrefs ? "[Prefs] " : "", | 1759 | app_.isLoadingPrefs ? "[Prefs] " : "", |
1687 | (d == NULL || win == NULL ? 0 : d == win->roots[0] ? 1 : 2), | 1760 | (d == NULL || win == NULL ? 0 : d == win->roots[0] ? 1 : 2), |
1688 | command); fflush(stdout); | 1761 | command); fflush(stdout); |
1689 | } | 1762 | } |
1763 | #endif | ||
1690 | } | 1764 | } |
1691 | 1765 | ||
1692 | void postCommandf_Root(iRoot *d, const char *command, ...) { | 1766 | void postCommandf_Root(iRoot *d, const char *command, ...) { |
@@ -1823,6 +1897,12 @@ static void updatePrefsPinSplitButtons_(iWidget *d, int value) { | |||
1823 | } | 1897 | } |
1824 | } | 1898 | } |
1825 | 1899 | ||
1900 | static void updatePrefsToolBarActionButton_(iWidget *prefs, int buttonIndex, int action) { | ||
1901 | updateDropdownSelection_LabelWidget( | ||
1902 | findChild_Widget(prefs, format_CStr("prefs.toolbaraction%d", buttonIndex + 1)), | ||
1903 | format_CStr(" arg:%d button:%d", action, buttonIndex)); | ||
1904 | } | ||
1905 | |||
1826 | static void updateScrollSpeedButtons_(iWidget *d, enum iScrollType type, const int value) { | 1906 | static void updateScrollSpeedButtons_(iWidget *d, enum iScrollType type, const int value) { |
1827 | const char *typeStr = (type == mouse_ScrollType ? "mouse" : "keyboard"); | 1907 | const char *typeStr = (type == mouse_ScrollType ? "mouse" : "keyboard"); |
1828 | for (int i = 0; i <= 40; i++) { | 1908 | for (int i = 0; i <= 40; i++) { |
@@ -1913,6 +1993,10 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | |||
1913 | format_CStr("returnkey.set arg:%d", arg_Command(cmd))); | 1993 | format_CStr("returnkey.set arg:%d", arg_Command(cmd))); |
1914 | return iFalse; | 1994 | return iFalse; |
1915 | } | 1995 | } |
1996 | else if (equal_Command(cmd, "toolbar.action.set")) { | ||
1997 | updatePrefsToolBarActionButton_(d, argLabel_Command(cmd, "button"), arg_Command(cmd)); | ||
1998 | return iFalse; | ||
1999 | } | ||
1916 | else if (equal_Command(cmd, "pinsplit.set")) { | 2000 | else if (equal_Command(cmd, "pinsplit.set")) { |
1917 | updatePrefsPinSplitButtons_(d, arg_Command(cmd)); | 2001 | updatePrefsPinSplitButtons_(d, arg_Command(cmd)); |
1918 | return iFalse; | 2002 | return iFalse; |
@@ -1957,8 +2041,11 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | |||
1957 | } | 2041 | } |
1958 | } | 2042 | } |
1959 | else if (equalWidget_Command(cmd, d, "input.resized")) { | 2043 | else if (equalWidget_Command(cmd, d, "input.resized")) { |
1960 | updatePreferencesLayout_Widget(d); | 2044 | if (!d->root->pendingArrange) { |
1961 | return iFalse; | 2045 | d->root->pendingArrange = iTrue; |
2046 | postCommand_Root(d->root, "root.arrange"); | ||
2047 | } | ||
2048 | return iTrue; | ||
1962 | } | 2049 | } |
1963 | return iFalse; | 2050 | return iFalse; |
1964 | } | 2051 | } |
@@ -2176,6 +2263,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2176 | return iTrue; | 2263 | return iTrue; |
2177 | } | 2264 | } |
2178 | else if (equal_Command(cmd, "fontpack.suggest.classic")) { | 2265 | else if (equal_Command(cmd, "fontpack.suggest.classic")) { |
2266 | /* TODO: Don't use this when system fonts are accessible. */ | ||
2179 | if (!isInstalled_Fonts("classic-set") && !isInstalled_Fonts("cjk")) { | 2267 | if (!isInstalled_Fonts("classic-set") && !isInstalled_Fonts("cjk")) { |
2180 | makeQuestion_Widget( | 2268 | makeQuestion_Widget( |
2181 | uiHeading_ColorEscape "${heading.fontpack.classic}", | 2269 | uiHeading_ColorEscape "${heading.fontpack.classic}", |
@@ -2208,6 +2296,22 @@ iBool handleCommand_App(const char *cmd) { | |||
2208 | } | 2296 | } |
2209 | return iTrue; | 2297 | return iTrue; |
2210 | } | 2298 | } |
2299 | else if (equal_Command(cmd, "navbar.action.set")) { | ||
2300 | d->prefs.navbarActions[iClamp(argLabel_Command(cmd, "button"), 0, maxNavbarActions_Prefs - 1)] = | ||
2301 | iClamp(arg_Command(cmd), 0, max_ToolbarAction - 1); | ||
2302 | if (!isFrozen) { | ||
2303 | postCommand_App("~navbar.actions.changed"); | ||
2304 | } | ||
2305 | return iTrue; | ||
2306 | } | ||
2307 | else if (equal_Command(cmd, "toolbar.action.set")) { | ||
2308 | d->prefs.toolbarActions[iClamp(argLabel_Command(cmd, "button"), 0, 1)] = | ||
2309 | iClamp(arg_Command(cmd), 0, max_ToolbarAction - 1); | ||
2310 | if (!isFrozen) { | ||
2311 | postCommand_App("~toolbar.actions.changed"); | ||
2312 | } | ||
2313 | return iTrue; | ||
2314 | } | ||
2211 | else if (equal_Command(cmd, "translation.languages")) { | 2315 | else if (equal_Command(cmd, "translation.languages")) { |
2212 | d->prefs.langFrom = argLabel_Command(cmd, "from"); | 2316 | d->prefs.langFrom = argLabel_Command(cmd, "from"); |
2213 | d->prefs.langTo = argLabel_Command(cmd, "to"); | 2317 | d->prefs.langTo = argLabel_Command(cmd, "to"); |
@@ -2229,6 +2333,9 @@ iBool handleCommand_App(const char *cmd) { | |||
2229 | (argLabel_Command(cmd, "axis") ? vertical_WindowSplit : 0) | (arg_Command(cmd) << 1); | 2333 | (argLabel_Command(cmd, "axis") ? vertical_WindowSplit : 0) | (arg_Command(cmd) << 1); |
2230 | const char *url = suffixPtr_Command(cmd, "url"); | 2334 | const char *url = suffixPtr_Command(cmd, "url"); |
2231 | setCStr_String(d->window->pendingSplitUrl, url ? url : ""); | 2335 | setCStr_String(d->window->pendingSplitUrl, url ? url : ""); |
2336 | if (hasLabel_Command(cmd, "origin")) { | ||
2337 | set_String(d->window->pendingSplitOrigin, string_Command(cmd, "origin")); | ||
2338 | } | ||
2232 | postRefresh_App(); | 2339 | postRefresh_App(); |
2233 | return iTrue; | 2340 | return iTrue; |
2234 | } | 2341 | } |
@@ -2587,6 +2694,10 @@ iBool handleCommand_App(const char *cmd) { | |||
2587 | d->prefs.uiAnimations = arg_Command(cmd) != 0; | 2694 | d->prefs.uiAnimations = arg_Command(cmd) != 0; |
2588 | return iTrue; | 2695 | return iTrue; |
2589 | } | 2696 | } |
2697 | else if (equal_Command(cmd, "prefs.blink.changed")) { | ||
2698 | d->prefs.blinkingCursor = arg_Command(cmd) != 0; | ||
2699 | return iTrue; | ||
2700 | } | ||
2590 | else if (equal_Command(cmd, "prefs.time.24h.changed")) { | 2701 | else if (equal_Command(cmd, "prefs.time.24h.changed")) { |
2591 | d->prefs.time24h = arg_Command(cmd) != 0; | 2702 | d->prefs.time24h = arg_Command(cmd) != 0; |
2592 | return iTrue; | 2703 | return iTrue; |
@@ -2642,7 +2753,9 @@ iBool handleCommand_App(const char *cmd) { | |||
2642 | } | 2753 | } |
2643 | #endif | 2754 | #endif |
2644 | else if (equal_Command(cmd, "downloads.open")) { | 2755 | else if (equal_Command(cmd, "downloads.open")) { |
2645 | postCommandf_App("open url:%s", cstrCollect_String(makeFileUrl_String(downloadDir_App()))); | 2756 | postCommandf_App("open newtab:%d url:%s", |
2757 | argLabel_Command(cmd, "newtab"), | ||
2758 | cstrCollect_String(makeFileUrl_String(downloadDir_App()))); | ||
2646 | return iTrue; | 2759 | return iTrue; |
2647 | } | 2760 | } |
2648 | else if (equal_Command(cmd, "ca.file")) { | 2761 | else if (equal_Command(cmd, "ca.file")) { |
@@ -2673,14 +2786,29 @@ iBool handleCommand_App(const char *cmd) { | |||
2673 | } | 2786 | } |
2674 | return iTrue; | 2787 | return iTrue; |
2675 | } | 2788 | } |
2789 | else if (equal_Command(cmd, "reveal")) { | ||
2790 | const iString *path = NULL; | ||
2791 | if (hasLabel_Command(cmd, "path")) { | ||
2792 | path = suffix_Command(cmd, "path"); | ||
2793 | } | ||
2794 | else if (hasLabel_Command(cmd, "url")) { | ||
2795 | path = collect_String(localFilePathFromUrl_String(suffix_Command(cmd, "url"))); | ||
2796 | } | ||
2797 | if (path) { | ||
2798 | revealPath_App(path); | ||
2799 | } | ||
2800 | return iTrue; | ||
2801 | } | ||
2676 | else if (equal_Command(cmd, "open")) { | 2802 | else if (equal_Command(cmd, "open")) { |
2677 | const char *urlArg = suffixPtr_Command(cmd, "url"); | 2803 | const char *urlArg = suffixPtr_Command(cmd, "url"); |
2678 | if (!urlArg) { | 2804 | if (!urlArg) { |
2679 | return iTrue; /* invalid command */ | 2805 | return iTrue; /* invalid command */ |
2680 | } | 2806 | } |
2681 | iString *url = collectNewCStr_String(urlArg); | 2807 | if (findWidget_App("prefs")) { |
2682 | const iBool noProxy = argLabel_Command(cmd, "noproxy") != 0; | 2808 | postCommand_App("prefs.dismiss"); |
2683 | const iBool fromSidebar = argLabel_Command(cmd, "fromsidebar") != 0; | 2809 | } |
2810 | iString *url = collectNewCStr_String(urlArg); | ||
2811 | const iBool noProxy = argLabel_Command(cmd, "noproxy") != 0; | ||
2684 | iUrl parts; | 2812 | iUrl parts; |
2685 | init_Url(&parts, url); | 2813 | init_Url(&parts, url); |
2686 | if (equal_Rangecc(parts.scheme, "about") && equal_Rangecc(parts.path, "command") && | 2814 | if (equal_Rangecc(parts.scheme, "about") && equal_Rangecc(parts.path, "command") && |
@@ -2711,12 +2839,23 @@ iBool handleCommand_App(const char *cmd) { | |||
2711 | openInDefaultBrowser_App(url); | 2839 | openInDefaultBrowser_App(url); |
2712 | return iTrue; | 2840 | return iTrue; |
2713 | } | 2841 | } |
2842 | iDocumentWidget *doc = document_Command(cmd); | ||
2843 | iDocumentWidget *origin = doc; | ||
2844 | if (hasLabel_Command(cmd, "origin")) { | ||
2845 | iDocumentWidget *cmdOrig = findWidget_App(cstr_Command(cmd, "origin")); | ||
2846 | if (cmdOrig) { | ||
2847 | origin = cmdOrig; | ||
2848 | } | ||
2849 | } | ||
2714 | const int newTab = argLabel_Command(cmd, "newtab"); | 2850 | const int newTab = argLabel_Command(cmd, "newtab"); |
2715 | if (newTab & otherRoot_OpenTabFlag && numRoots_Window(get_Window()) == 1) { | 2851 | if (newTab & otherRoot_OpenTabFlag && numRoots_Window(get_Window()) == 1) { |
2716 | /* Need to split first. */ | 2852 | /* Need to split first. */ |
2717 | const iInt2 winSize = get_Window()->size; | 2853 | const iInt2 winSize = get_Window()->size; |
2718 | postCommandf_App("ui.split arg:3 axis:%d newtab:%d url:%s", | 2854 | const int splitMode = argLabel_Command(cmd, "splitmode"); |
2855 | postCommandf_App("ui.split arg:%d axis:%d origin:%s newtab:%d url:%s", | ||
2856 | splitMode ? splitMode : 3, | ||
2719 | (float) winSize.x / (float) winSize.y < 0.7f ? 1 : 0, | 2857 | (float) winSize.x / (float) winSize.y < 0.7f ? 1 : 0, |
2858 | cstr_String(id_Widget(as_Widget(origin))), | ||
2720 | newTab & ~otherRoot_OpenTabFlag, | 2859 | newTab & ~otherRoot_OpenTabFlag, |
2721 | cstr_String(url)); | 2860 | cstr_String(url)); |
2722 | return iTrue; | 2861 | return iTrue; |
@@ -2727,15 +2866,16 @@ iBool handleCommand_App(const char *cmd) { | |||
2727 | root = otherRoot_Window(as_Window(d->window), root); | 2866 | root = otherRoot_Window(as_Window(d->window), root); |
2728 | setKeyRoot_Window(as_Window(d->window), root); | 2867 | setKeyRoot_Window(as_Window(d->window), root); |
2729 | setCurrent_Root(root); /* need to change for widget creation */ | 2868 | setCurrent_Root(root); /* need to change for widget creation */ |
2869 | doc = document_Command(cmd); /* may be different */ | ||
2730 | } | 2870 | } |
2731 | iDocumentWidget *doc = document_Command(cmd); | ||
2732 | if (newTab & (new_OpenTabFlag | newBackground_OpenTabFlag)) { | 2871 | if (newTab & (new_OpenTabFlag | newBackground_OpenTabFlag)) { |
2733 | doc = newTab_App(NULL, (newTab & new_OpenTabFlag) != 0); /* `newtab:2` to open in background */ | 2872 | doc = newTab_App(NULL, (newTab & new_OpenTabFlag) != 0); /* `newtab:2` to open in background */ |
2734 | } | 2873 | } |
2735 | iHistory *history = history_DocumentWidget(doc); | 2874 | iHistory *history = history_DocumentWidget(doc); |
2736 | const iBool isHistory = argLabel_Command(cmd, "history") != 0; | 2875 | const iBool isHistory = argLabel_Command(cmd, "history") != 0; |
2737 | int redirectCount = argLabel_Command(cmd, "redirect"); | 2876 | int redirectCount = argLabel_Command(cmd, "redirect"); |
2738 | if (!isHistory) { | 2877 | if (!isHistory) { |
2878 | /* TODO: Shouldn't DocumentWidget manage history on its own? */ | ||
2739 | if (redirectCount) { | 2879 | if (redirectCount) { |
2740 | replace_History(history, url); | 2880 | replace_History(history, url); |
2741 | } | 2881 | } |
@@ -2745,16 +2885,10 @@ iBool handleCommand_App(const char *cmd) { | |||
2745 | } | 2885 | } |
2746 | setInitialScroll_DocumentWidget(doc, argfLabel_Command(cmd, "scroll")); | 2886 | setInitialScroll_DocumentWidget(doc, argfLabel_Command(cmd, "scroll")); |
2747 | setRedirectCount_DocumentWidget(doc, redirectCount); | 2887 | setRedirectCount_DocumentWidget(doc, redirectCount); |
2888 | setOrigin_DocumentWidget(doc, origin); | ||
2748 | showCollapsed_Widget(findWidget_App("document.progress"), iFalse); | 2889 | showCollapsed_Widget(findWidget_App("document.progress"), iFalse); |
2749 | if (prefs_App()->decodeUserVisibleURLs) { | ||
2750 | urlDecodePath_String(url); | ||
2751 | } | ||
2752 | else { | ||
2753 | urlEncodePath_String(url); | ||
2754 | } | ||
2755 | setUrlFlags_DocumentWidget(doc, url, | 2890 | setUrlFlags_DocumentWidget(doc, url, |
2756 | (isHistory ? useCachedContentIfAvailable_DocumentWidgetSetUrlFlag : 0) | | 2891 | isHistory ? useCachedContentIfAvailable_DocumentWidgetSetUrlFlag : 0); |
2757 | (fromSidebar ? openedFromSidebar_DocumentWidgetSetUrlFlag : 0)); | ||
2758 | /* Optionally, jump to a text in the document. This will only work if the document | 2892 | /* Optionally, jump to a text in the document. This will only work if the document |
2759 | is already available, e.g., it's from "about:" or restored from cache. */ | 2893 | is already available, e.g., it's from "about:" or restored from cache. */ |
2760 | const iRangecc gotoHeading = range_Command(cmd, "gotoheading"); | 2894 | const iRangecc gotoHeading = range_Command(cmd, "gotoheading"); |
@@ -2895,6 +3029,8 @@ iBool handleCommand_App(const char *cmd) { | |||
2895 | iWidget *dlg = makePreferences_Widget(); | 3029 | iWidget *dlg = makePreferences_Widget(); |
2896 | updatePrefsThemeButtons_(dlg); | 3030 | updatePrefsThemeButtons_(dlg); |
2897 | setText_InputWidget(findChild_Widget(dlg, "prefs.downloads"), &d->prefs.strings[downloadDir_PrefsString]); | 3031 | setText_InputWidget(findChild_Widget(dlg, "prefs.downloads"), &d->prefs.strings[downloadDir_PrefsString]); |
3032 | /* TODO: Use a common table in Prefs to do this more conviently. | ||
3033 | Also see `serializePrefs_App_()`. */ | ||
2898 | setToggle_Widget(findChild_Widget(dlg, "prefs.hoverlink"), d->prefs.hoverLink); | 3034 | setToggle_Widget(findChild_Widget(dlg, "prefs.hoverlink"), d->prefs.hoverLink); |
2899 | setToggle_Widget(findChild_Widget(dlg, "prefs.smoothscroll"), d->prefs.smoothScrolling); | 3035 | setToggle_Widget(findChild_Widget(dlg, "prefs.smoothscroll"), d->prefs.smoothScrolling); |
2900 | setToggle_Widget(findChild_Widget(dlg, "prefs.imageloadscroll"), d->prefs.loadImageInsteadOfScrolling); | 3036 | setToggle_Widget(findChild_Widget(dlg, "prefs.imageloadscroll"), d->prefs.loadImageInsteadOfScrolling); |
@@ -2905,7 +3041,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2905 | setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme); | 3041 | setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme); |
2906 | setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame); | 3042 | setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame); |
2907 | setToggle_Widget(findChild_Widget(dlg, "prefs.animate"), d->prefs.uiAnimations); | 3043 | setToggle_Widget(findChild_Widget(dlg, "prefs.animate"), d->prefs.uiAnimations); |
2908 | // setText_InputWidget(findChild_Widget(dlg, "prefs.userfont"), &d->prefs.symbolFontPath); | 3044 | setToggle_Widget(findChild_Widget(dlg, "prefs.blink"), d->prefs.blinkingCursor); |
2909 | updatePrefsPinSplitButtons_(dlg, d->prefs.pinSplit); | 3045 | updatePrefsPinSplitButtons_(dlg, d->prefs.pinSplit); |
2910 | updateScrollSpeedButtons_(dlg, mouse_ScrollType, d->prefs.smoothScrollSpeed[mouse_ScrollType]); | 3046 | updateScrollSpeedButtons_(dlg, mouse_ScrollType, d->prefs.smoothScrollSpeed[mouse_ScrollType]); |
2911 | updateScrollSpeedButtons_(dlg, keyboard_ScrollType, d->prefs.smoothScrollSpeed[keyboard_ScrollType]); | 3047 | updateScrollSpeedButtons_(dlg, keyboard_ScrollType, d->prefs.smoothScrollSpeed[keyboard_ScrollType]); |
@@ -2914,16 +3050,11 @@ iBool handleCommand_App(const char *cmd) { | |||
2914 | updateDropdownSelection_LabelWidget( | 3050 | updateDropdownSelection_LabelWidget( |
2915 | findChild_Widget(dlg, "prefs.returnkey"), | 3051 | findChild_Widget(dlg, "prefs.returnkey"), |
2916 | format_CStr("returnkey.set arg:%d", d->prefs.returnKey)); | 3052 | format_CStr("returnkey.set arg:%d", d->prefs.returnKey)); |
3053 | updatePrefsToolBarActionButton_(dlg, 0, d->prefs.toolbarActions[0]); | ||
3054 | updatePrefsToolBarActionButton_(dlg, 1, d->prefs.toolbarActions[1]); | ||
2917 | setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); | 3055 | setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); |
2918 | setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), | 3056 | setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), |
2919 | collectNewFormat_String("%g", uiScale_Window(as_Window(d->window)))); | 3057 | collectNewFormat_String("%g", uiScale_Window(as_Window(d->window)))); |
2920 | // setFlags_Widget(findChild_Widget(dlg, format_CStr("prefs.font.%d", d->prefs.font)), | ||
2921 | // selected_WidgetFlag, | ||
2922 | // iTrue); | ||
2923 | // setFlags_Widget( | ||
2924 | // findChild_Widget(dlg, format_CStr("prefs.headingfont.%d", d->prefs.headingFont)), | ||
2925 | // selected_WidgetFlag, | ||
2926 | // iTrue); | ||
2927 | setFlags_Widget(findChild_Widget(dlg, "prefs.mono.gemini"), | 3058 | setFlags_Widget(findChild_Widget(dlg, "prefs.mono.gemini"), |
2928 | selected_WidgetFlag, | 3059 | selected_WidgetFlag, |
2929 | d->prefs.monospaceGemini); | 3060 | d->prefs.monospaceGemini); |
@@ -2990,13 +3121,16 @@ iBool handleCommand_App(const char *cmd) { | |||
2990 | showTabPage_Widget(tabs, tabPage_Widget(tabs, d->prefs.dialogTab)); | 3121 | showTabPage_Widget(tabs, tabPage_Widget(tabs, d->prefs.dialogTab)); |
2991 | } | 3122 | } |
2992 | setCommandHandler_Widget(dlg, handlePrefsCommands_); | 3123 | setCommandHandler_Widget(dlg, handlePrefsCommands_); |
3124 | if (argLabel_Command(cmd, "idents") && deviceType_App() != desktop_AppDeviceType) { | ||
3125 | iWidget *idPanel = panel_Mobile(dlg, 2); | ||
3126 | iWidget *button = findUserData_Widget(findChild_Widget(dlg, "panel.top"), idPanel); | ||
3127 | postCommand_Widget(button, "panel.open"); | ||
3128 | } | ||
2993 | } | 3129 | } |
2994 | else if (equal_Command(cmd, "navigate.home")) { | 3130 | else if (equal_Command(cmd, "navigate.home")) { |
2995 | /* Look for bookmarks tagged "homepage". */ | 3131 | /* Look for bookmarks tagged "homepage". */ |
2996 | iRegExp *pattern = iClob(new_RegExp("\\b" homepage_BookmarkTag "\\b", | ||
2997 | caseInsensitive_RegExpOption)); | ||
2998 | const iPtrArray *homepages = | 3132 | const iPtrArray *homepages = |
2999 | list_Bookmarks(d->bookmarks, NULL, filterTagsRegExp_Bookmarks, pattern); | 3133 | list_Bookmarks(d->bookmarks, NULL, filterHomepage_Bookmark, NULL); |
3000 | if (isEmpty_PtrArray(homepages)) { | 3134 | if (isEmpty_PtrArray(homepages)) { |
3001 | postCommand_Root(get_Root(), "open url:about:lagrange"); | 3135 | postCommand_Root(get_Root(), "open url:about:lagrange"); |
3002 | } | 3136 | } |
@@ -3147,6 +3281,7 @@ iBool handleCommand_App(const char *cmd) { | |||
3147 | d->certs, | 3281 | d->certs, |
3148 | findIdentity_GmCerts(d->certs, collect_Block(hexDecode_Rangecc(range_Command(cmd, "ident")))), | 3282 | findIdentity_GmCerts(d->certs, collect_Block(hexDecode_Rangecc(range_Command(cmd, "ident")))), |
3149 | url); | 3283 | url); |
3284 | postCommand_App("navigate.reload"); | ||
3150 | postCommand_App("idents.changed"); | 3285 | postCommand_App("idents.changed"); |
3151 | return iTrue; | 3286 | return iTrue; |
3152 | } | 3287 | } |
@@ -3159,9 +3294,29 @@ iBool handleCommand_App(const char *cmd) { | |||
3159 | else { | 3294 | else { |
3160 | setUse_GmIdentity(ident, collect_String(suffix_Command(cmd, "url")), iFalse); | 3295 | setUse_GmIdentity(ident, collect_String(suffix_Command(cmd, "url")), iFalse); |
3161 | } | 3296 | } |
3297 | postCommand_App("navigate.reload"); | ||
3162 | postCommand_App("idents.changed"); | 3298 | postCommand_App("idents.changed"); |
3163 | return iTrue; | 3299 | return iTrue; |
3164 | } | 3300 | } |
3301 | else if (equal_Command(cmd, "ident.switch")) { | ||
3302 | /* This is different than "ident.signin" in that the currently used identity's activation | ||
3303 | URL is used instead of the current one. */ | ||
3304 | const iString *docUrl = url_DocumentWidget(document_App()); | ||
3305 | const iGmIdentity *cur = identityForUrl_GmCerts(d->certs, docUrl); | ||
3306 | iGmIdentity *dst = findIdentity_GmCerts( | ||
3307 | d->certs, collect_Block(hexDecode_Rangecc(range_Command(cmd, "fp")))); | ||
3308 | if (dst && cur != dst) { | ||
3309 | iString *useUrl = copy_String(findUse_GmIdentity(cur, docUrl)); | ||
3310 | if (isEmpty_String(useUrl)) { | ||
3311 | useUrl = copy_String(docUrl); | ||
3312 | } | ||
3313 | signIn_GmCerts(d->certs, dst, useUrl); | ||
3314 | postCommand_App("idents.changed"); | ||
3315 | postCommand_App("navigate.reload"); | ||
3316 | delete_String(useUrl); | ||
3317 | } | ||
3318 | return iTrue; | ||
3319 | } | ||
3165 | else if (equal_Command(cmd, "idents.changed")) { | 3320 | else if (equal_Command(cmd, "idents.changed")) { |
3166 | saveIdentities_GmCerts(d->certs); | 3321 | saveIdentities_GmCerts(d->certs); |
3167 | return iFalse; | 3322 | return iFalse; |
@@ -3259,50 +3414,69 @@ void openInDefaultBrowser_App(const iString *url) { | |||
3259 | return; | 3414 | return; |
3260 | } | 3415 | } |
3261 | #endif | 3416 | #endif |
3262 | #if !defined (iPlatformAppleMobile) | 3417 | #if defined (iPlatformAppleMobile) |
3418 | if (equalCase_Rangecc(urlScheme_String(url), "file")) { | ||
3419 | revealPath_App(collect_String(localFilePathFromUrl_String(url))); | ||
3420 | } | ||
3421 | return; | ||
3422 | #endif | ||
3263 | iProcess *proc = new_Process(); | 3423 | iProcess *proc = new_Process(); |
3264 | setArguments_Process(proc, | 3424 | setArguments_Process(proc, iClob(newStringsCStr_StringList( |
3265 | #if defined (iPlatformAppleDesktop) | 3425 | #if defined (iPlatformAppleDesktop) |
3266 | iClob(newStringsCStr_StringList("/usr/bin/env", "open", cstr_String(url), NULL)) | 3426 | "/usr/bin/env", |
3427 | "open", | ||
3428 | cstr_String(url), | ||
3267 | #elif defined (iPlatformLinux) || defined (iPlatformOther) || defined (iPlatformHaiku) | 3429 | #elif defined (iPlatformLinux) || defined (iPlatformOther) || defined (iPlatformHaiku) |
3268 | iClob(newStringsCStr_StringList("/usr/bin/env", "xdg-open", cstr_String(url), NULL)) | 3430 | "/usr/bin/env", |
3431 | "xdg-open", | ||
3432 | cstr_String(url), | ||
3269 | #elif defined (iPlatformMsys) | 3433 | #elif defined (iPlatformMsys) |
3270 | iClob(newStringsCStr_StringList( | 3434 | concatPath_CStr(cstr_String(execPath_App()), "../urlopen.bat"), |
3271 | concatPath_CStr(cstr_String(execPath_App()), "../urlopen.bat"), | 3435 | cstr_String(url), |
3272 | cstr_String(url), | ||
3273 | NULL)) | ||
3274 | /* TODO: The prompt window is shown momentarily... */ | 3436 | /* TODO: The prompt window is shown momentarily... */ |
3275 | #endif | 3437 | #endif |
3438 | NULL)) | ||
3276 | ); | 3439 | ); |
3277 | start_Process(proc); | 3440 | start_Process(proc); |
3278 | waitForFinished_Process(proc); /* TODO: test on Windows */ | 3441 | waitForFinished_Process(proc); |
3279 | iRelease(proc); | 3442 | iRelease(proc); |
3280 | #endif | ||
3281 | } | 3443 | } |
3282 | 3444 | ||
3445 | #include <the_Foundation/thread.h> | ||
3446 | |||
3283 | void revealPath_App(const iString *path) { | 3447 | void revealPath_App(const iString *path) { |
3284 | #if defined (iPlatformAppleDesktop) | 3448 | #if defined (iPlatformAppleDesktop) |
3285 | const char *scriptPath = concatPath_CStr(dataDir_App_(), "revealfile.scpt"); | 3449 | iProcess *proc = new_Process(); |
3286 | iFile *f = newCStr_File(scriptPath); | 3450 | setArguments_Process( |
3287 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { | 3451 | proc, iClob(newStringsCStr_StringList("/usr/bin/open", "-R", cstr_String(path), NULL))); |
3288 | /* AppleScript to select a specific file. */ | 3452 | start_Process(proc); |
3289 | write_File(f, collect_Block(newCStr_Block("on run argv\n" | 3453 | iRelease(proc); |
3290 | " tell application \"Finder\"\n" | 3454 | #elif defined (iPlatformAppleMobile) |
3291 | " activate\n" | 3455 | /* Use a share sheet. */ |
3292 | " reveal POSIX file (item 1 of argv) as text\n" | 3456 | openFileActivityView_iOS(path); |
3293 | " end tell\n" | 3457 | #elif defined (iPlatformLinux) || defined (iPlatformHaiku) |
3294 | "end run\n"))); | 3458 | iProcess *proc = NULL; |
3295 | close_File(f); | 3459 | /* Try with `dbus-send` first. */ { |
3296 | iProcess *proc = new_Process(); | 3460 | proc = new_Process(); |
3297 | setArguments_Process( | 3461 | setArguments_Process( |
3298 | proc, | 3462 | proc, |
3299 | iClob(newStringsCStr_StringList( | 3463 | iClob(newStringsCStr_StringList( |
3300 | "/usr/bin/osascript", scriptPath, cstr_String(path), NULL))); | 3464 | "/usr/bin/dbus-send", |
3465 | "--print-reply", | ||
3466 | "--dest=org.freedesktop.FileManager1", | ||
3467 | "/org/freedesktop/FileManager1", | ||
3468 | "org.freedesktop.FileManager1.ShowItems", | ||
3469 | format_CStr("array:string:%s", makeFileUrl_CStr(cstr_String(path))), | ||
3470 | "string:", | ||
3471 | NULL))); | ||
3301 | start_Process(proc); | 3472 | start_Process(proc); |
3473 | waitForFinished_Process(proc); | ||
3474 | const iBool dbusDidSucceed = (exitStatus_Process(proc) == 0); | ||
3302 | iRelease(proc); | 3475 | iRelease(proc); |
3476 | if (dbusDidSucceed) { | ||
3477 | return; | ||
3478 | } | ||
3303 | } | 3479 | } |
3304 | iRelease(f); | ||
3305 | #elif defined (iPlatformLinux) || defined (iPlatformHaiku) | ||
3306 | iFileInfo *inf = iClob(new_FileInfo(path)); | 3480 | iFileInfo *inf = iClob(new_FileInfo(path)); |
3307 | iRangecc target; | 3481 | iRangecc target; |
3308 | if (isDirectory_FileInfo(inf)) { | 3482 | if (isDirectory_FileInfo(inf)) { |
@@ -3311,7 +3485,7 @@ void revealPath_App(const iString *path) { | |||
3311 | else { | 3485 | else { |
3312 | target = dirName_Path(path); | 3486 | target = dirName_Path(path); |
3313 | } | 3487 | } |
3314 | iProcess *proc = new_Process(); | 3488 | proc = new_Process(); |
3315 | setArguments_Process( | 3489 | setArguments_Process( |
3316 | proc, iClob(newStringsCStr_StringList("/usr/bin/env", "xdg-open", cstr_Rangecc(target), NULL))); | 3490 | proc, iClob(newStringsCStr_StringList("/usr/bin/env", "xdg-open", cstr_Rangecc(target), NULL))); |
3317 | start_Process(proc); | 3491 | start_Process(proc); |
@@ -3365,8 +3539,21 @@ void closePopups_App(void) { | |||
3365 | } | 3539 | } |
3366 | 3540 | ||
3367 | #if defined (iPlatformAndroidMobile) | 3541 | #if defined (iPlatformAndroidMobile) |
3542 | |||
3368 | float displayDensity_Android(void) { | 3543 | float displayDensity_Android(void) { |
3369 | iApp *d = &app_; | 3544 | iApp *d = &app_; |
3370 | return toFloat_String(at_CommandLine(&d->args, 1)); | 3545 | return toFloat_String(at_CommandLine(&d->args, 1)); |
3371 | } | 3546 | } |
3547 | |||
3548 | #include <jni.h> | ||
3549 | |||
3550 | JNIEXPORT void JNICALL Java_fi_skyjake_lagrange_LagrangeActivity_postAppCommand( | ||
3551 | JNIEnv* env, jclass jcls, | ||
3552 | jstring command) | ||
3553 | { | ||
3554 | const char *cmd = (*env)->GetStringUTFChars(env, command, NULL); | ||
3555 | postCommand_Root(NULL, cmd); | ||
3556 | (*env)->ReleaseStringUTFChars(env, command, cmd); | ||
3557 | } | ||
3558 | |||
3372 | #endif | 3559 | #endif |
@@ -61,6 +61,7 @@ enum iUserEventCode { | |||
61 | command_UserEventCode = 1, | 61 | command_UserEventCode = 1, |
62 | refresh_UserEventCode, | 62 | refresh_UserEventCode, |
63 | asleep_UserEventCode, | 63 | asleep_UserEventCode, |
64 | periodic_UserEventCode, | ||
64 | /* The start of a potential touch tap event is notified via a custom event because | 65 | /* The start of a potential touch tap event is notified via a custom event because |
65 | sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will | 66 | sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will |
66 | take, it could turn into a tap-and-hold for example. */ | 67 | take, it could turn into a tap-and-hold for example. */ |
@@ -110,6 +111,8 @@ enum iColorTheme colorTheme_App (void); | |||
110 | const iString * schemeProxy_App (iRangecc scheme); | 111 | const iString * schemeProxy_App (iRangecc scheme); |
111 | iBool willUseProxy_App (const iRangecc scheme); | 112 | iBool willUseProxy_App (const iRangecc scheme); |
112 | const iString * searchQueryUrl_App (const iString *queryStringUnescaped); | 113 | const iString * searchQueryUrl_App (const iString *queryStringUnescaped); |
114 | const iString * fileNameForUrl_App (const iString *url, const iString *mime); | ||
115 | const iString * temporaryPathForUrl_App(const iString *url, const iString *mime); /* deleted before quitting */ | ||
113 | const iString * downloadPathForUrl_App(const iString *url, const iString *mime); | 116 | const iString * downloadPathForUrl_App(const iString *url, const iString *mime); |
114 | 117 | ||
115 | typedef void (*iTickerFunc)(iAny *); | 118 | typedef void (*iTickerFunc)(iAny *); |
diff --git a/src/audio/player.c b/src/audio/player.c index 94bcd065..de430b17 100644 --- a/src/audio/player.c +++ b/src/audio/player.c | |||
@@ -31,6 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
31 | #include <the_Foundation/thread.h> | 31 | #include <the_Foundation/thread.h> |
32 | #include <SDL_audio.h> | 32 | #include <SDL_audio.h> |
33 | #include <SDL_timer.h> | 33 | #include <SDL_timer.h> |
34 | #include <SDL.h> | ||
34 | 35 | ||
35 | #if defined (LAGRANGE_ENABLE_MPG123) | 36 | #if defined (LAGRANGE_ENABLE_MPG123) |
36 | # include <mpg123.h> | 37 | # include <mpg123.h> |
@@ -191,7 +192,7 @@ static enum iDecoderStatus decodeVorbis_Decoder_(iDecoder *d) { | |||
191 | int error; | 192 | int error; |
192 | int consumed; | 193 | int consumed; |
193 | d->vorbis = stb_vorbis_open_pushdata( | 194 | d->vorbis = stb_vorbis_open_pushdata( |
194 | constData_Block(input), size_Block(input), &consumed, &error, NULL); | 195 | constData_Block(input), (int) size_Block(input), &consumed, &error, NULL); |
195 | if (!d->vorbis) { | 196 | if (!d->vorbis) { |
196 | return needMoreInput_DecoderStatus; | 197 | return needMoreInput_DecoderStatus; |
197 | } | 198 | } |
@@ -224,7 +225,7 @@ static enum iDecoderStatus decodeVorbis_Decoder_(iDecoder *d) { | |||
224 | lock_Mutex(&d->input->mtx); | 225 | lock_Mutex(&d->input->mtx); |
225 | d->totalInputSize = size_Block(input); | 226 | d->totalInputSize = size_Block(input); |
226 | int error = 0; | 227 | int error = 0; |
227 | stb_vorbis *vrb = stb_vorbis_open_memory(constData_Block(input), size_Block(input), | 228 | stb_vorbis *vrb = stb_vorbis_open_memory(constData_Block(input), (int) size_Block(input), |
228 | &error, NULL); | 229 | &error, NULL); |
229 | if (vrb) { | 230 | if (vrb) { |
230 | d->totalSamples = stb_vorbis_stream_length_in_samples(vrb); | 231 | d->totalSamples = stb_vorbis_stream_length_in_samples(vrb); |
@@ -739,6 +740,22 @@ size_t sourceDataSize_Player(const iPlayer *d) { | |||
739 | return size; | 740 | return size; |
740 | } | 741 | } |
741 | 742 | ||
743 | static iBool setupSDLAudio_(iBool init) { | ||
744 | static iBool isAudioInited_ = iFalse; | ||
745 | if (init) { | ||
746 | if (SDL_InitSubSystem(SDL_INIT_AUDIO)) { | ||
747 | fprintf(stderr, "[SDL] audio init failed: %s\n", SDL_GetError()); | ||
748 | return iFalse; | ||
749 | } | ||
750 | isAudioInited_ = iTrue; | ||
751 | } | ||
752 | else if (isAudioInited_) { | ||
753 | SDL_QuitSubSystem(SDL_INIT_AUDIO); | ||
754 | isAudioInited_ = iFalse; | ||
755 | } | ||
756 | return isAudioInited_; | ||
757 | } | ||
758 | |||
742 | iBool start_Player(iPlayer *d) { | 759 | iBool start_Player(iPlayer *d) { |
743 | if (isStarted_Player(d)) { | 760 | if (isStarted_Player(d)) { |
744 | return iFalse; | 761 | return iFalse; |
@@ -757,6 +774,9 @@ iBool start_Player(iPlayer *d) { | |||
757 | } | 774 | } |
758 | content.output.callback = writeOutputSamples_Player_; | 775 | content.output.callback = writeOutputSamples_Player_; |
759 | content.output.userdata = d; | 776 | content.output.userdata = d; |
777 | if (!setupSDLAudio_(iTrue)) { | ||
778 | return iFalse; | ||
779 | } | ||
760 | d->device = SDL_OpenAudioDevice(NULL, SDL_FALSE /* playback */, &content.output, &d->spec, 0); | 780 | d->device = SDL_OpenAudioDevice(NULL, SDL_FALSE /* playback */, &content.output, &d->spec, 0); |
761 | if (!d->device) { | 781 | if (!d->device) { |
762 | return iFalse; | 782 | return iFalse; |
@@ -796,6 +816,7 @@ void stop_Player(iPlayer *d) { | |||
796 | d->device = 0; | 816 | d->device = 0; |
797 | delete_Decoder(d->decoder); | 817 | delete_Decoder(d->decoder); |
798 | d->decoder = NULL; | 818 | d->decoder = NULL; |
819 | setupSDLAudio_(iFalse); | ||
799 | } | 820 | } |
800 | } | 821 | } |
801 | 822 | ||
diff --git a/src/bookmarks.c b/src/bookmarks.c index 5e943387..500caa38 100644 --- a/src/bookmarks.c +++ b/src/bookmarks.c | |||
@@ -37,6 +37,7 @@ void init_Bookmark(iBookmark *d) { | |||
37 | init_String(&d->url); | 37 | init_String(&d->url); |
38 | init_String(&d->title); | 38 | init_String(&d->title); |
39 | init_String(&d->tags); | 39 | init_String(&d->tags); |
40 | iZap(d->flags); | ||
40 | iZap(d->when); | 41 | iZap(d->when); |
41 | d->parentId = 0; | 42 | d->parentId = 0; |
42 | d->order = 0; | 43 | d->order = 0; |
@@ -48,6 +49,7 @@ void deinit_Bookmark(iBookmark *d) { | |||
48 | deinit_String(&d->url); | 49 | deinit_String(&d->url); |
49 | } | 50 | } |
50 | 51 | ||
52 | #if 0 | ||
51 | iBool hasTag_Bookmark(const iBookmark *d, const char *tag) { | 53 | iBool hasTag_Bookmark(const iBookmark *d, const char *tag) { |
52 | if (!d) return iFalse; | 54 | if (!d) return iFalse; |
53 | iRegExp *pattern = new_RegExp(format_CStr("\\b%s\\b", tag), caseSensitive_RegExpOption); | 55 | iRegExp *pattern = new_RegExp(format_CStr("\\b%s\\b", tag), caseSensitive_RegExpOption); |
@@ -60,7 +62,7 @@ iBool hasTag_Bookmark(const iBookmark *d, const char *tag) { | |||
60 | 62 | ||
61 | void addTag_Bookmark(iBookmark *d, const char *tag) { | 63 | void addTag_Bookmark(iBookmark *d, const char *tag) { |
62 | if (!isEmpty_String(&d->tags)) { | 64 | if (!isEmpty_String(&d->tags)) { |
63 | appendChar_String(&d->tags, ' '); | 65 | appendCStr_String(&d->tags, " "); |
64 | } | 66 | } |
65 | appendCStr_String(&d->tags, tag); | 67 | appendCStr_String(&d->tags, tag); |
66 | } | 68 | } |
@@ -72,6 +74,93 @@ void removeTag_Bookmark(iBookmark *d, const char *tag) { | |||
72 | trim_String(&d->tags); | 74 | trim_String(&d->tags); |
73 | } | 75 | } |
74 | } | 76 | } |
77 | #endif | ||
78 | |||
79 | static struct { | ||
80 | uint32_t bit; | ||
81 | const char *tag; | ||
82 | iRegExp * pattern; | ||
83 | iRegExp * oldPattern; | ||
84 | } | ||
85 | specialTags_[] = { | ||
86 | { homepage_BookmarkFlag, ".homepage" }, | ||
87 | { remoteSource_BookmarkFlag, ".remotesource" }, | ||
88 | { linkSplit_BookmarkFlag, ".linksplit" }, | ||
89 | { userIcon_BookmarkFlag, ".usericon" }, | ||
90 | { subscribed_BookmarkFlag, ".subscribed" }, | ||
91 | { headings_BookmarkFlag, ".headings" }, | ||
92 | { ignoreWeb_BookmarkFlag, ".ignoreweb" }, | ||
93 | /* `remote_BookmarkFlag` not included because it's runtime only */ | ||
94 | }; | ||
95 | |||
96 | static void updatePatterns_(size_t index) { | ||
97 | if (!specialTags_[index].pattern) { | ||
98 | specialTags_[index].pattern = new_RegExp(format_CStr("(?<!\\w)\\%s\\b(?!\\w)", | ||
99 | specialTags_[index].tag), | ||
100 | caseSensitive_RegExpOption); /* never released */ | ||
101 | } | ||
102 | if (!specialTags_[index].oldPattern) { | ||
103 | /* TODO: Get rid of these when compatibility with v1.9 or older is not important. */ | ||
104 | specialTags_[index].oldPattern = | ||
105 | new_RegExp(format_CStr("\\b%s\\b", specialTags_[index].tag + 1), /* dotless */ | ||
106 | caseSensitive_RegExpOption); /* never released */ | ||
107 | } | ||
108 | } | ||
109 | |||
110 | static void normalizeSpacesInTags_(iString *tags) { | ||
111 | iBool wasSpace = iFalse; | ||
112 | iString out; | ||
113 | init_String(&out); | ||
114 | for (const char *ch = constBegin_String(tags); ch != constEnd_String(tags); ch++) { | ||
115 | if (*ch == ' ') { | ||
116 | if (!wasSpace) { | ||
117 | wasSpace = iTrue; | ||
118 | } | ||
119 | else { | ||
120 | continue; | ||
121 | } | ||
122 | } | ||
123 | else { | ||
124 | wasSpace = iFalse; | ||
125 | } | ||
126 | appendData_Block(&out.chars, ch, 1); | ||
127 | } | ||
128 | trim_String(&out); | ||
129 | set_String(tags, &out); | ||
130 | deinit_String(&out); | ||
131 | } | ||
132 | |||
133 | static void unpackDotTags_Bookmark_(iBookmark *d) { | ||
134 | iZap(d->flags); | ||
135 | iForIndices(i, specialTags_) { | ||
136 | updatePatterns_(i); | ||
137 | iRegExpMatch m; | ||
138 | init_RegExpMatch(&m); | ||
139 | iBool isSet = matchString_RegExp(specialTags_[i].pattern, &d->tags, &m); | ||
140 | if (!isSet) { | ||
141 | init_RegExpMatch(&m); | ||
142 | isSet = matchString_RegExp(specialTags_[i].oldPattern, &d->tags, &m); | ||
143 | } | ||
144 | iChangeFlags(d->flags, specialTags_[i].bit, isSet); | ||
145 | if (isSet) { | ||
146 | remove_Block(&d->tags.chars, m.range.start, size_Range(&m.range)); | ||
147 | } | ||
148 | } | ||
149 | normalizeSpacesInTags_(&d->tags); | ||
150 | } | ||
151 | |||
152 | static iString *packedDotTags_Bookmark_(const iBookmark *d) { | ||
153 | iString *withDot = copy_String(&d->tags); | ||
154 | iForIndices(i, specialTags_) { | ||
155 | if (d->flags & specialTags_[i].bit) { | ||
156 | if (!isEmpty_String(withDot)) { | ||
157 | appendCStr_String(withDot, " "); | ||
158 | } | ||
159 | appendCStr_String(withDot, specialTags_[i].tag); | ||
160 | } | ||
161 | } | ||
162 | return withDot; | ||
163 | } | ||
75 | 164 | ||
76 | iDefineTypeConstruction(Bookmark) | 165 | iDefineTypeConstruction(Bookmark) |
77 | 166 | ||
@@ -176,6 +265,7 @@ static void loadOldFormat_Bookmarks(iBookmarks *d, const char *dirPath) { | |||
176 | setRange_String(&bm->title, line); | 265 | setRange_String(&bm->title, line); |
177 | nextSplit_Rangecc(src, "\n", &line); | 266 | nextSplit_Rangecc(src, "\n", &line); |
178 | setRange_String(&bm->tags, line); | 267 | setRange_String(&bm->tags, line); |
268 | unpackDotTags_Bookmark_(bm); | ||
179 | insert_Bookmarks_(d, bm); | 269 | insert_Bookmarks_(d, bm); |
180 | } | 270 | } |
181 | } | 271 | } |
@@ -220,6 +310,7 @@ static void handleKeyValue_BookmarkLoader_(void *context, const iString *table, | |||
220 | } | 310 | } |
221 | else if (!cmp_String(key, "tags") && tv->type == string_TomlType) { | 311 | else if (!cmp_String(key, "tags") && tv->type == string_TomlType) { |
222 | set_String(&bm->tags, tv->value.string); | 312 | set_String(&bm->tags, tv->value.string); |
313 | unpackDotTags_Bookmark_(bm); | ||
223 | } | 314 | } |
224 | else if (!cmp_String(key, "icon") && tv->type == int64_TomlType) { | 315 | else if (!cmp_String(key, "icon") && tv->type == int64_TomlType) { |
225 | bm->icon = (iChar) tv->value.int64; | 316 | bm->icon = (iChar) tv->value.int64; |
@@ -292,7 +383,6 @@ void load_Bookmarks(iBookmarks *d, const char *dirPath) { | |||
292 | 383 | ||
293 | void save_Bookmarks(const iBookmarks *d, const char *dirPath) { | 384 | void save_Bookmarks(const iBookmarks *d, const char *dirPath) { |
294 | lock_Mutex(d->mtx); | 385 | lock_Mutex(d->mtx); |
295 | iRegExp *remotePattern = iClob(new_RegExp("\\bremote\\b", caseSensitive_RegExpOption)); | ||
296 | iFile *f = newCStr_File(concatPath_CStr(dirPath, fileName_Bookmarks_)); | 386 | iFile *f = newCStr_File(concatPath_CStr(dirPath, fileName_Bookmarks_)); |
297 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { | 387 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { |
298 | iString *str = collectNew_String(); | 388 | iString *str = collectNew_String(); |
@@ -300,13 +390,12 @@ void save_Bookmarks(const iBookmarks *d, const char *dirPath) { | |||
300 | writeData_File(f, cstr_String(str), size_String(str)); | 390 | writeData_File(f, cstr_String(str), size_String(str)); |
301 | iConstForEach(Hash, i, &d->bookmarks) { | 391 | iConstForEach(Hash, i, &d->bookmarks) { |
302 | const iBookmark *bm = (const iBookmark *) i.value; | 392 | const iBookmark *bm = (const iBookmark *) i.value; |
303 | iRegExpMatch m; | 393 | if (bm->flags & remote_BookmarkFlag) { |
304 | init_RegExpMatch(&m); | ||
305 | if (matchString_RegExp(remotePattern, &bm->tags, &m)) { | ||
306 | /* Remote bookmarks are not saved. */ | 394 | /* Remote bookmarks are not saved. */ |
307 | continue; | 395 | continue; |
308 | } | 396 | } |
309 | iBeginCollect(); | 397 | iBeginCollect(); |
398 | const iString *packedTags = collect_String(packedDotTags_Bookmark_(bm)); | ||
310 | format_String(str, | 399 | format_String(str, |
311 | "[%d]\n" | 400 | "[%d]\n" |
312 | "url = \"%s\"\n" | 401 | "url = \"%s\"\n" |
@@ -317,7 +406,7 @@ void save_Bookmarks(const iBookmarks *d, const char *dirPath) { | |||
317 | id_Bookmark(bm), | 406 | id_Bookmark(bm), |
318 | cstrCollect_String(quote_String(&bm->url, iFalse)), | 407 | cstrCollect_String(quote_String(&bm->url, iFalse)), |
319 | cstrCollect_String(quote_String(&bm->title, iFalse)), | 408 | cstrCollect_String(quote_String(&bm->title, iFalse)), |
320 | cstrCollect_String(quote_String(&bm->tags, iFalse)), | 409 | cstrCollect_String(quote_String(packedTags, iFalse)), |
321 | bm->icon, | 410 | bm->icon, |
322 | seconds_Time(&bm->when), | 411 | seconds_Time(&bm->when), |
323 | cstrCollect_String(format_Time(&bm->when, "%Y-%m-%d"))); | 412 | cstrCollect_String(format_Time(&bm->when, "%Y-%m-%d"))); |
@@ -397,7 +486,7 @@ iBool updateBookmarkIcon_Bookmarks(iBookmarks *d, const iString *url, iChar icon | |||
397 | const uint32_t id = findUrl_Bookmarks(d, url); | 486 | const uint32_t id = findUrl_Bookmarks(d, url); |
398 | if (id) { | 487 | if (id) { |
399 | iBookmark *bm = get_Bookmarks(d, id); | 488 | iBookmark *bm = get_Bookmarks(d, id); |
400 | if (!hasTag_Bookmark(bm, remote_BookmarkTag) && !hasTag_Bookmark(bm, userIcon_BookmarkTag)) { | 489 | if (~bm->flags & remote_BookmarkFlag && ~bm->flags & userIcon_BookmarkFlag) { |
401 | if (icon != bm->icon) { | 490 | if (icon != bm->icon) { |
402 | bm->icon = icon; | 491 | bm->icon = icon; |
403 | changed = iTrue; | 492 | changed = iTrue; |
@@ -422,19 +511,13 @@ iChar siteIcon_Bookmarks(const iBookmarks *d, const iString *url) { | |||
422 | if (isEmpty_String(url)) { | 511 | if (isEmpty_String(url)) { |
423 | return 0; | 512 | return 0; |
424 | } | 513 | } |
425 | static iRegExp *tagPattern_; | ||
426 | if (!tagPattern_) { | ||
427 | tagPattern_ = new_RegExp("\\b" userIcon_BookmarkTag "\\b", caseSensitive_RegExpOption); | ||
428 | } | ||
429 | const iRangecc urlRoot = urlRoot_String(url); | 514 | const iRangecc urlRoot = urlRoot_String(url); |
430 | size_t matchingSize = iInvalidSize; /* we'll pick the shortest matching */ | 515 | size_t matchingSize = iInvalidSize; /* we'll pick the shortest matching */ |
431 | iChar icon = 0; | 516 | iChar icon = 0; |
432 | lock_Mutex(d->mtx); | 517 | lock_Mutex(d->mtx); |
433 | iConstForEach(Hash, i, &d->bookmarks) { | 518 | iConstForEach(Hash, i, &d->bookmarks) { |
434 | const iBookmark *bm = (const iBookmark *) i.value; | 519 | const iBookmark *bm = (const iBookmark *) i.value; |
435 | iRegExpMatch m; | 520 | if (bm->icon && bm->flags & userIcon_BookmarkFlag) { |
436 | init_RegExpMatch(&m); | ||
437 | if (bm->icon && matchString_RegExp(tagPattern_, &bm->tags, &m)) { | ||
438 | const iRangecc bmRoot = urlRoot_String(&bm->url); | 521 | const iRangecc bmRoot = urlRoot_String(&bm->url); |
439 | if (equalRangeCase_Rangecc(urlRoot, bmRoot)) { | 522 | if (equalRangeCase_Rangecc(urlRoot, bmRoot)) { |
440 | const size_t n = size_String(&bm->url); | 523 | const size_t n = size_String(&bm->url); |
@@ -467,10 +550,15 @@ void reorder_Bookmarks(iBookmarks *d, uint32_t id, int newOrder) { | |||
467 | unlock_Mutex(d->mtx); | 550 | unlock_Mutex(d->mtx); |
468 | } | 551 | } |
469 | 552 | ||
470 | iBool filterTagsRegExp_Bookmarks(void *regExp, const iBookmark *bm) { | 553 | //iBool filterTagsRegExp_Bookmarks(void *regExp, const iBookmark *bm) { |
471 | iRegExpMatch m; | 554 | // iRegExpMatch m; |
472 | init_RegExpMatch(&m); | 555 | // init_RegExpMatch(&m); |
473 | return matchString_RegExp(regExp, &bm->tags, &m); | 556 | // return matchString_RegExp(regExp, &bm->tags, &m); |
557 | //} | ||
558 | |||
559 | iBool filterHomepage_Bookmark(void *d, const iBookmark *bm) { | ||
560 | iUnused(d); | ||
561 | return (bm->flags & homepage_BookmarkFlag) != 0; | ||
474 | } | 562 | } |
475 | 563 | ||
476 | static iBool matchUrl_(void *url, const iBookmark *bm) { | 564 | static iBool matchUrl_(void *url, const iBookmark *bm) { |
@@ -618,7 +706,7 @@ const iString *bookmarkListPage_Bookmarks(const iBookmarks *d, enum iBookmarkLis | |||
618 | 706 | ||
619 | static iBool isRemoteSource_Bookmark_(void *context, const iBookmark *d) { | 707 | static iBool isRemoteSource_Bookmark_(void *context, const iBookmark *d) { |
620 | iUnused(context); | 708 | iUnused(context); |
621 | return hasTag_Bookmark(d, remoteSource_BookmarkTag); | 709 | return (d->flags & remoteSource_BookmarkFlag) != 0; |
622 | } | 710 | } |
623 | 711 | ||
624 | void remoteRequestFinished_Bookmarks_(iBookmarks *d, iGmRequest *req) { | 712 | void remoteRequestFinished_Bookmarks_(iBookmarks *d, iGmRequest *req) { |
@@ -642,7 +730,6 @@ void requestFinished_Bookmarks(iBookmarks *d, iGmRequest *req) { | |||
642 | initCurrent_Time(&now); | 730 | initCurrent_Time(&now); |
643 | iRegExp *linkPattern = new_RegExp("^=>\\s*([^\\s]+)(\\s+(.*))?", 0); | 731 | iRegExp *linkPattern = new_RegExp("^=>\\s*([^\\s]+)(\\s+(.*))?", 0); |
644 | iString src; | 732 | iString src; |
645 | const iString *remoteTag = collectNewCStr_String("remote"); | ||
646 | initBlock_String(&src, body_GmRequest(req)); | 733 | initBlock_String(&src, body_GmRequest(req)); |
647 | iRangecc srcLine = iNullRange; | 734 | iRangecc srcLine = iNullRange; |
648 | while (nextSplit_Rangecc(range_String(&src), "\n", &srcLine)) { | 735 | while (nextSplit_Rangecc(range_String(&src), "\n", &srcLine)) { |
@@ -660,8 +747,9 @@ void requestFinished_Bookmarks(iBookmarks *d, iGmRequest *req) { | |||
660 | if (isEmpty_String(titleStr)) { | 747 | if (isEmpty_String(titleStr)) { |
661 | setRange_String(titleStr, urlHost_String(urlStr)); | 748 | setRange_String(titleStr, urlHost_String(urlStr)); |
662 | } | 749 | } |
663 | const uint32_t bmId = add_Bookmarks(d, absUrl, titleStr, remoteTag, 0x2913); | 750 | const uint32_t bmId = add_Bookmarks(d, absUrl, titleStr, NULL, 0x2913); |
664 | iBookmark *bm = get_Bookmarks(d, bmId); | 751 | iBookmark *bm = get_Bookmarks(d, bmId); |
752 | bm->flags |= remote_BookmarkFlag; | ||
665 | bm->parentId = *(uint32_t *) userData_Object(req); | 753 | bm->parentId = *(uint32_t *) userData_Object(req); |
666 | delete_String(titleStr); | 754 | delete_String(titleStr); |
667 | } | 755 | } |
@@ -690,7 +778,7 @@ void fetchRemote_Bookmarks(iBookmarks *d) { | |||
690 | size_t numRemoved = 0; | 778 | size_t numRemoved = 0; |
691 | iForEach(Hash, i, &d->bookmarks) { | 779 | iForEach(Hash, i, &d->bookmarks) { |
692 | iBookmark *bm = (iBookmark *) i.value; | 780 | iBookmark *bm = (iBookmark *) i.value; |
693 | if (hasTag_Bookmark(bm, remote_BookmarkTag)) { | 781 | if (bm->flags & remote_BookmarkFlag) { |
694 | remove_HashIterator(&i); | 782 | remove_HashIterator(&i); |
695 | delete_Bookmark(bm); | 783 | delete_Bookmark(bm); |
696 | numRemoved++; | 784 | numRemoved++; |
diff --git a/src/bookmarks.h b/src/bookmarks.h index 6cb5c8a9..08afdd8b 100644 --- a/src/bookmarks.h +++ b/src/bookmarks.h | |||
@@ -31,27 +31,30 @@ iDeclareType(GmRequest) | |||
31 | 31 | ||
32 | iDeclareType(Bookmark) | 32 | iDeclareType(Bookmark) |
33 | iDeclareTypeConstruction(Bookmark) | 33 | iDeclareTypeConstruction(Bookmark) |
34 | 34 | ||
35 | /* TODO: Make the special internal tags a bitfield, separate from user's tags. */ | 35 | /* These values are not serialized as-is in bookmarks.ini. Instead, they are included in `tags` |
36 | 36 | with a dot prefix. This helps retain backwards and forwards compatibility. */ | |
37 | #define headings_BookmarkTag "headings" | 37 | enum iBookmarkFlags { |
38 | #define ignoreWeb_BookmarkTag "ignoreweb" | 38 | homepage_BookmarkFlag = iBit(1), |
39 | #define homepage_BookmarkTag "homepage" | 39 | remoteSource_BookmarkFlag = iBit(2), |
40 | #define linkSplit_BookmarkTag "linksplit" | 40 | linkSplit_BookmarkFlag = iBit(3), |
41 | #define remote_BookmarkTag "remote" | 41 | userIcon_BookmarkFlag = iBit(4), |
42 | #define remoteSource_BookmarkTag "remotesource" | 42 | subscribed_BookmarkFlag = iBit(17), |
43 | #define subscribed_BookmarkTag "subscribed" | 43 | headings_BookmarkFlag = iBit(18), |
44 | #define userIcon_BookmarkTag "usericon" | 44 | ignoreWeb_BookmarkFlag = iBit(19), |
45 | remote_BookmarkFlag = iBit(31), | ||
46 | }; | ||
45 | 47 | ||
46 | struct Impl_Bookmark { | 48 | struct Impl_Bookmark { |
47 | iHashNode node; | 49 | iHashNode node; |
48 | iString url; | 50 | iString url; |
49 | iString title; | 51 | iString title; |
50 | iString tags; | 52 | iString tags; |
51 | iChar icon; | 53 | uint32_t flags; |
52 | iTime when; | 54 | iChar icon; |
53 | uint32_t parentId; /* remote source or folder */ | 55 | iTime when; |
54 | int order; /* sort order */ | 56 | uint32_t parentId; /* remote source or folder */ |
57 | int order; /* sort order */ | ||
55 | }; | 58 | }; |
56 | 59 | ||
57 | iLocalDef uint32_t id_Bookmark (const iBookmark *d) { return d->node.key; } | 60 | iLocalDef uint32_t id_Bookmark (const iBookmark *d) { return d->node.key; } |
@@ -59,23 +62,24 @@ iLocalDef iBool isFolder_Bookmark (const iBookmark *d) { return isEmpty_St | |||
59 | 62 | ||
60 | iBool hasParent_Bookmark (const iBookmark *, uint32_t parentId); | 63 | iBool hasParent_Bookmark (const iBookmark *, uint32_t parentId); |
61 | int depth_Bookmark (const iBookmark *); | 64 | int depth_Bookmark (const iBookmark *); |
62 | iBool hasTag_Bookmark (const iBookmark *, const char *tag); | 65 | |
63 | void addTag_Bookmark (iBookmark *, const char *tag); | 66 | //iBool hasTag_Bookmark (const iBookmark *, const char *tag); |
64 | void removeTag_Bookmark (iBookmark *, const char *tag); | 67 | //void addTag_Bookmark (iBookmark *, const char *tag); |
65 | 68 | //void removeTag_Bookmark (iBookmark *, const char *tag); | |
66 | iLocalDef void addTagIfMissing_Bookmark(iBookmark *d, const char *tag) { | 69 | |
67 | if (!hasTag_Bookmark(d, tag)) { | 70 | //iLocalDef void addTagIfMissing_Bookmark(iBookmark *d, const char *tag) { |
68 | addTag_Bookmark(d, tag); | 71 | // if (!hasTag_Bookmark(d, tag)) { |
69 | } | 72 | // addTag_Bookmark(d, tag); |
70 | } | 73 | // } |
71 | iLocalDef void addOrRemoveTag_Bookmark(iBookmark *d, const char *tag, iBool add) { | 74 | //} |
72 | if (add) { | 75 | //iLocalDef void addOrRemoveTag_Bookmark(iBookmark *d, const char *tag, iBool add) { |
73 | addTagIfMissing_Bookmark(d, tag); | 76 | // if (add) { |
74 | } | 77 | // addTagIfMissing_Bookmark(d, tag); |
75 | else { | 78 | // } |
76 | removeTag_Bookmark(d, tag); | 79 | // else { |
77 | } | 80 | // removeTag_Bookmark(d, tag); |
78 | } | 81 | // } |
82 | //} | ||
79 | 83 | ||
80 | int cmpTitleAscending_Bookmark (const iBookmark **, const iBookmark **); | 84 | int cmpTitleAscending_Bookmark (const iBookmark **, const iBookmark **); |
81 | int cmpTree_Bookmark (const iBookmark **, const iBookmark **); | 85 | int cmpTree_Bookmark (const iBookmark **, const iBookmark **); |
@@ -109,7 +113,8 @@ iChar siteIcon_Bookmarks (const iBookmarks *, const iString *url) | |||
109 | uint32_t findUrl_Bookmarks (const iBookmarks *, const iString *url); /* O(n) */ | 113 | uint32_t findUrl_Bookmarks (const iBookmarks *, const iString *url); /* O(n) */ |
110 | uint32_t recentFolder_Bookmarks (const iBookmarks *); | 114 | uint32_t recentFolder_Bookmarks (const iBookmarks *); |
111 | 115 | ||
112 | iBool filterTagsRegExp_Bookmarks (void *regExp, const iBookmark *); | 116 | //iBool filterTagsRegExp_Bookmarks (void *regExp, const iBookmark *); |
117 | iBool filterHomepage_Bookmark (void *, const iBookmark *); | ||
113 | 118 | ||
114 | /** | 119 | /** |
115 | * Lists all or a subset of the bookmarks in a sorted array of Bookmark pointers. | 120 | * Lists all or a subset of the bookmarks in a sorted array of Bookmark pointers. |
@@ -66,10 +66,28 @@ enum iReturnKeyFlag { | |||
66 | accept_ReturnKeyFlag = 4, /* shift */ | 66 | accept_ReturnKeyFlag = 4, /* shift */ |
67 | }; | 67 | }; |
68 | 68 | ||
69 | enum iToolbarAction { | ||
70 | back_ToolbarAction = 0, | ||
71 | forward_ToolbarAction = 1, | ||
72 | home_ToolbarAction = 2, | ||
73 | parent_ToolbarAction = 3, | ||
74 | reload_ToolbarAction = 4, | ||
75 | newTab_ToolbarAction = 5, | ||
76 | closeTab_ToolbarAction = 6, | ||
77 | addBookmark_ToolbarAction = 7, | ||
78 | translate_ToolbarAction = 8, | ||
79 | upload_ToolbarAction = 9, | ||
80 | editPage_ToolbarAction = 10, | ||
81 | findText_ToolbarAction = 11, | ||
82 | settings_ToolbarAction = 12, | ||
83 | sidebar_ToolbarAction = 13, /* desktop only */ | ||
84 | max_ToolbarAction | ||
85 | }; | ||
86 | |||
69 | /* Return key behavior is not handled via normal bindings because only certain combinations | 87 | /* Return key behavior is not handled via normal bindings because only certain combinations |
70 | are valid. */ | 88 | are valid. */ |
71 | enum iReturnKeyBehavior { | 89 | enum iReturnKeyBehavior { |
72 | default_ReturnKeyBehavior = | 90 | acceptWithoutMod_ReturnKeyBehavior = |
73 | shiftReturn_ReturnKeyFlag | (return_ReturnKeyFlag << accept_ReturnKeyFlag), | 91 | shiftReturn_ReturnKeyFlag | (return_ReturnKeyFlag << accept_ReturnKeyFlag), |
74 | acceptWithShift_ReturnKeyBehavior = | 92 | acceptWithShift_ReturnKeyBehavior = |
75 | return_ReturnKeyFlag | (shiftReturn_ReturnKeyFlag << accept_ReturnKeyFlag), | 93 | return_ReturnKeyFlag | (shiftReturn_ReturnKeyFlag << accept_ReturnKeyFlag), |
@@ -79,6 +97,11 @@ enum iReturnKeyBehavior { | |||
79 | #else | 97 | #else |
80 | return_ReturnKeyFlag | (controlReturn_ReturnKeyFlag << accept_ReturnKeyFlag), | 98 | return_ReturnKeyFlag | (controlReturn_ReturnKeyFlag << accept_ReturnKeyFlag), |
81 | #endif | 99 | #endif |
100 | #if defined (iPlatformAndroidMobile) | ||
101 | default_ReturnKeyBehavior = acceptWithShift_ReturnKeyBehavior, | ||
102 | #else | ||
103 | default_ReturnKeyBehavior = acceptWithoutMod_ReturnKeyBehavior, | ||
104 | #endif | ||
82 | }; | 105 | }; |
83 | 106 | ||
84 | int keyMod_ReturnKeyFlag (int flag); | 107 | int keyMod_ReturnKeyFlag (int flag); |
@@ -94,7 +117,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { | |||
94 | 117 | ||
95 | #define menu_Icon "\U0001d362" | 118 | #define menu_Icon "\U0001d362" |
96 | #define rightArrowhead_Icon "\u27a4" | 119 | #define rightArrowhead_Icon "\u27a4" |
97 | #define leftArrowhead_Icon "\u27a4" | 120 | #define leftArrowhead_Icon "\u2b9c" |
98 | #define warning_Icon "\u26a0" | 121 | #define warning_Icon "\u26a0" |
99 | #define openLock_Icon "\U0001f513" | 122 | #define openLock_Icon "\U0001f513" |
100 | #define closedLock_Icon "\U0001f512" | 123 | #define closedLock_Icon "\U0001f512" |
@@ -102,7 +125,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { | |||
102 | #define reload_Icon "\U0001f503" | 125 | #define reload_Icon "\U0001f503" |
103 | #define backArrow_Icon "\U0001f870" | 126 | #define backArrow_Icon "\U0001f870" |
104 | #define forwardArrow_Icon "\U0001f872" | 127 | #define forwardArrow_Icon "\U0001f872" |
105 | #define upArrow_Icon "\u2191" | 128 | #define upArrow_Icon "\U0001f871" |
106 | #define upArrowBar_Icon "\u2912" | 129 | #define upArrowBar_Icon "\u2912" |
107 | #define downArrowBar_Icon "\u2913" | 130 | #define downArrowBar_Icon "\u2913" |
108 | #define rightArrowWhite_Icon "\u21e8" | 131 | #define rightArrowWhite_Icon "\u21e8" |
diff --git a/src/feeds.c b/src/feeds.c index 26b3d6db..a8cbf47a 100644 --- a/src/feeds.c +++ b/src/feeds.c | |||
@@ -65,7 +65,9 @@ const iString *url_FeedEntry(const iFeedEntry *d) { | |||
65 | iBool isUnread_FeedEntry(const iFeedEntry *d) { | 65 | iBool isUnread_FeedEntry(const iFeedEntry *d) { |
66 | const size_t fragPos = indexOf_String(&d->url, '#'); | 66 | const size_t fragPos = indexOf_String(&d->url, '#'); |
67 | if (fragPos != iInvalidPos) { | 67 | if (fragPos != iInvalidPos) { |
68 | /* Check if the entry is newer than the latest visit. */ | 68 | /* Check if the entry is newer than the latest visit. If the URL has not been visited, |
69 | `urlVisitTime_Visited` returns a zero timestamp that is always earlier than | ||
70 | `posted`. */ | ||
69 | const iTime visTime = urlVisitTime_Visited(visited_App(), url_FeedEntry(d)); | 71 | const iTime visTime = urlVisitTime_Visited(visited_App(), url_FeedEntry(d)); |
70 | return cmp_Time(&visTime, &d->posted) < 0; | 72 | return cmp_Time(&visTime, &d->posted) < 0; |
71 | } | 73 | } |
@@ -97,8 +99,8 @@ static void init_FeedJob(iFeedJob *d, const iBookmark *bookmark) { | |||
97 | init_PtrArray(&d->results); | 99 | init_PtrArray(&d->results); |
98 | iZap(d->startTime); | 100 | iZap(d->startTime); |
99 | d->isFirstUpdate = iFalse; | 101 | d->isFirstUpdate = iFalse; |
100 | d->checkHeadings = hasTag_Bookmark(bookmark, headings_BookmarkTag); | 102 | d->checkHeadings = (bookmark->flags & headings_BookmarkFlag) != 0; |
101 | d->ignoreWeb = hasTag_Bookmark(bookmark, ignoreWeb_BookmarkTag); | 103 | d->ignoreWeb = (bookmark->flags & ignoreWeb_BookmarkFlag) != 0; |
102 | } | 104 | } |
103 | 105 | ||
104 | static void deinit_FeedJob(iFeedJob *d) { | 106 | static void deinit_FeedJob(iFeedJob *d) { |
@@ -146,13 +148,7 @@ static void submit_FeedJob_(iFeedJob *d) { | |||
146 | 148 | ||
147 | static iBool isSubscribed_(void *context, const iBookmark *bm) { | 149 | static iBool isSubscribed_(void *context, const iBookmark *bm) { |
148 | iUnused(context); | 150 | iUnused(context); |
149 | static iRegExp *pattern_ = NULL; | 151 | return (bm->flags & subscribed_BookmarkFlag) != 0; |
150 | if (!pattern_) { | ||
151 | pattern_ = new_RegExp("\\bsubscribed\\b", caseSensitive_RegExpOption); | ||
152 | } | ||
153 | iRegExpMatch m; | ||
154 | init_RegExpMatch(&m); | ||
155 | return matchString_RegExp(pattern_, &bm->tags, &m); | ||
156 | } | 152 | } |
157 | 153 | ||
158 | static const iPtrArray *listSubscriptions_(void) { | 154 | static const iPtrArray *listSubscriptions_(void) { |
@@ -443,7 +439,7 @@ static iThreadResult fetch_Feeds_(iThread *thread) { | |||
443 | iZap(work); | 439 | iZap(work); |
444 | iBool gotNew = iFalse; | 440 | iBool gotNew = iFalse; |
445 | postCommand_App("feeds.update.started"); | 441 | postCommand_App("feeds.update.started"); |
446 | const int totalJobs = size_PtrArray(&d->jobs); | 442 | const size_t totalJobs = size_PtrArray(&d->jobs); |
447 | int numFinishedJobs = 0; | 443 | int numFinishedJobs = 0; |
448 | while (!d->stopWorker) { | 444 | while (!d->stopWorker) { |
449 | /* Start new jobs. */ | 445 | /* Start new jobs. */ |
@@ -482,7 +478,7 @@ static iThreadResult fetch_Feeds_(iThread *thread) { | |||
482 | } | 478 | } |
483 | } | 479 | } |
484 | if (doNotify) { | 480 | if (doNotify) { |
485 | postCommandf_App("feeds.update.progress arg:%d total:%d", numFinishedJobs, totalJobs); | 481 | postCommandf_App("feeds.update.progress arg:%d total:%zu", numFinishedJobs, totalJobs); |
486 | } | 482 | } |
487 | /* Stop if everything has finished. */ | 483 | /* Stop if everything has finished. */ |
488 | if (ongoing == 0 && isEmpty_PtrArray(&d->jobs)) { | 484 | if (ongoing == 0 && isEmpty_PtrArray(&d->jobs)) { |
@@ -620,7 +616,7 @@ static void load_Feeds_(iFeeds *d) { | |||
620 | /* TODO: Cleanup needed... | 616 | /* TODO: Cleanup needed... |
621 | All right, this could maybe use a bit more robust, structured format. | 617 | All right, this could maybe use a bit more robust, structured format. |
622 | The code below is messy. */ | 618 | The code below is messy. */ |
623 | const uint32_t feedId = strtoul(line.start, NULL, 16); | 619 | const uint32_t feedId = (uint32_t) strtoul(line.start, NULL, 16); |
624 | if (!nextSplit_Rangecc(range_Block(src), "\n", &line)) { | 620 | if (!nextSplit_Rangecc(range_Block(src), "\n", &line)) { |
625 | goto aborted; | 621 | goto aborted; |
626 | } | 622 | } |
diff --git a/src/fontpack.c b/src/fontpack.c index 79f35526..a440234e 100644 --- a/src/fontpack.c +++ b/src/fontpack.c | |||
@@ -818,7 +818,7 @@ const iArray *actions_FontPack(const iFontPack *d, iBool showInstalled) { | |||
818 | pushBack_Array( | 818 | pushBack_Array( |
819 | items, | 819 | items, |
820 | &(iMenuItem){ format_Lang(isEnabled ? close_Icon " ${fontpack.disable}" | 820 | &(iMenuItem){ format_Lang(isEnabled ? close_Icon " ${fontpack.disable}" |
821 | : leftArrowhead_Icon " ${fontpack.enable}", | 821 | : "${fontpack.enable}", |
822 | fpId), | 822 | fpId), |
823 | 0, | 823 | 0, |
824 | 0, | 824 | 0, |
diff --git a/src/gmcerts.c b/src/gmcerts.c index f95fea7d..7b05103b 100644 --- a/src/gmcerts.c +++ b/src/gmcerts.c | |||
@@ -90,7 +90,7 @@ void serialize_GmIdentity(const iGmIdentity *d, iStream *outs) { | |||
90 | writeU32_Stream(outs, d->icon); | 90 | writeU32_Stream(outs, d->icon); |
91 | serialize_String(&d->notes, outs); | 91 | serialize_String(&d->notes, outs); |
92 | write32_Stream(outs, d->flags); | 92 | write32_Stream(outs, d->flags); |
93 | writeU32_Stream(outs, size_StringSet(d->useUrls)); | 93 | writeU32_Stream(outs, (uint32_t) size_StringSet(d->useUrls)); |
94 | iConstForEach(StringSet, i, d->useUrls) { | 94 | iConstForEach(StringSet, i, d->useUrls) { |
95 | serialize_String(i.value, outs); | 95 | serialize_String(i.value, outs); |
96 | } | 96 | } |
@@ -146,6 +146,7 @@ iBool isUsed_GmIdentity(const iGmIdentity *d) { | |||
146 | } | 146 | } |
147 | 147 | ||
148 | iBool isUsedOn_GmIdentity(const iGmIdentity *d, const iString *url) { | 148 | iBool isUsedOn_GmIdentity(const iGmIdentity *d, const iString *url) { |
149 | #if 0 | ||
149 | size_t pos = iInvalidPos; | 150 | size_t pos = iInvalidPos; |
150 | locate_StringSet(d->useUrls, url, &pos); | 151 | locate_StringSet(d->useUrls, url, &pos); |
151 | if (pos < size_StringSet(d->useUrls)) { | 152 | if (pos < size_StringSet(d->useUrls)) { |
@@ -159,6 +160,12 @@ iBool isUsedOn_GmIdentity(const iGmIdentity *d, const iString *url) { | |||
159 | return iTrue; | 160 | return iTrue; |
160 | } | 161 | } |
161 | } | 162 | } |
163 | #endif | ||
164 | iConstForEach(StringSet, i, d->useUrls) { | ||
165 | if (startsWithCase_String(url, cstr_String(i.value))) { | ||
166 | return iTrue; | ||
167 | } | ||
168 | } | ||
162 | return iFalse; | 169 | return iFalse; |
163 | } | 170 | } |
164 | 171 | ||
@@ -193,7 +200,13 @@ void setUse_GmIdentity(iGmIdentity *d, const iString *url, iBool use) { | |||
193 | iAssert(wasInserted); | 200 | iAssert(wasInserted); |
194 | } | 201 | } |
195 | else { | 202 | else { |
196 | remove_StringSet(d->useUrls, url); | 203 | iForEach(Array, i, &d->useUrls->strings.values) { |
204 | iString *used = i.value; | ||
205 | if (startsWithCase_String(url, cstr_String(used))) { | ||
206 | deinit_String(used); | ||
207 | remove_ArrayIterator(&i); | ||
208 | } | ||
209 | } | ||
197 | } | 210 | } |
198 | } | 211 | } |
199 | 212 | ||
@@ -201,6 +214,16 @@ void clearUse_GmIdentity(iGmIdentity *d) { | |||
201 | clear_StringSet(d->useUrls); | 214 | clear_StringSet(d->useUrls); |
202 | } | 215 | } |
203 | 216 | ||
217 | const iString *findUse_GmIdentity(const iGmIdentity *d, const iString *url) { | ||
218 | if (!d) return NULL; | ||
219 | iConstForEach(StringSet, using, d->useUrls) { | ||
220 | if (startsWith_String(url, cstr_String(using.value))) { | ||
221 | return using.value; | ||
222 | } | ||
223 | } | ||
224 | return NULL; | ||
225 | } | ||
226 | |||
204 | const iString *name_GmIdentity(const iGmIdentity *d) { | 227 | const iString *name_GmIdentity(const iGmIdentity *d) { |
205 | iString *name = collect_String(subject_TlsCertificate(d->cert)); | 228 | iString *name = collect_String(subject_TlsCertificate(d->cert)); |
206 | if (startsWith_String(name, "CN = ")) { | 229 | if (startsWith_String(name, "CN = ")) { |
@@ -631,7 +654,7 @@ void importIdentity_GmCerts(iGmCerts *d, iTlsCertificate *cert, const iString *n | |||
631 | } | 654 | } |
632 | 655 | ||
633 | static const char *certPath_GmCerts_(const iGmCerts *d, const iGmIdentity *identity) { | 656 | static const char *certPath_GmCerts_(const iGmCerts *d, const iGmIdentity *identity) { |
634 | if (!(identity->flags & (temporary_GmIdentityFlag | imported_GmIdentityFlag))) { | 657 | if (!(identity->flags & temporary_GmIdentityFlag)) { |
635 | const char *finger = cstrCollect_String(hexEncode_Block(&identity->fingerprint)); | 658 | const char *finger = cstrCollect_String(hexEncode_Block(&identity->fingerprint)); |
636 | return concatPath_CStr(cstr_String(&d->saveDir), format_CStr("idents/%s", finger)); | 659 | return concatPath_CStr(cstr_String(&d->saveDir), format_CStr("idents/%s", finger)); |
637 | } | 660 | } |
diff --git a/src/gmcerts.h b/src/gmcerts.h index 02a41c14..6ece1954 100644 --- a/src/gmcerts.h +++ b/src/gmcerts.h | |||
@@ -48,8 +48,9 @@ iBool isUsed_GmIdentity (const iGmIdentity *); | |||
48 | iBool isUsedOn_GmIdentity (const iGmIdentity *, const iString *url); | 48 | iBool isUsedOn_GmIdentity (const iGmIdentity *, const iString *url); |
49 | iBool isUsedOnDomain_GmIdentity (const iGmIdentity *, const iRangecc domain); | 49 | iBool isUsedOnDomain_GmIdentity (const iGmIdentity *, const iRangecc domain); |
50 | 50 | ||
51 | void setUse_GmIdentity (iGmIdentity *, const iString *url, iBool use); | 51 | void setUse_GmIdentity (iGmIdentity *, const iString *url, iBool use); |
52 | void clearUse_GmIdentity (iGmIdentity *); | 52 | void clearUse_GmIdentity (iGmIdentity *); |
53 | const iString *findUse_GmIdentity (const iGmIdentity *, const iString *url); | ||
53 | 54 | ||
54 | const iString *name_GmIdentity(const iGmIdentity *); | 55 | const iString *name_GmIdentity(const iGmIdentity *); |
55 | 56 | ||
diff --git a/src/gmdocument.c b/src/gmdocument.c index 9d79830b..19230392 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
37 | #include <the_Foundation/intset.h> | 37 | #include <the_Foundation/intset.h> |
38 | #include <the_Foundation/ptrarray.h> | 38 | #include <the_Foundation/ptrarray.h> |
39 | #include <the_Foundation/regexp.h> | 39 | #include <the_Foundation/regexp.h> |
40 | #include <the_Foundation/stringarray.h> | ||
40 | #include <the_Foundation/stringset.h> | 41 | #include <the_Foundation/stringset.h> |
41 | 42 | ||
42 | #include <ctype.h> | 43 | #include <ctype.h> |
@@ -163,6 +164,7 @@ struct Impl_GmDocument { | |||
163 | iBool enableCommandLinks; /* `about:command?` only allowed on selected pages */ | 164 | iBool enableCommandLinks; /* `about:command?` only allowed on selected pages */ |
164 | iBool isLayoutInvalidated; | 165 | iBool isLayoutInvalidated; |
165 | iArray layout; /* contents of source, laid out in document space */ | 166 | iArray layout; /* contents of source, laid out in document space */ |
167 | iStringArray auxText; /* generated text that appears on the page but is not part of the source */ | ||
166 | iPtrArray links; | 168 | iPtrArray links; |
167 | iString title; /* the first top-level title */ | 169 | iString title; /* the first top-level title */ |
168 | iArray headings; | 170 | iArray headings; |
@@ -554,16 +556,22 @@ static const int maxLedeLines_ = 10; | |||
554 | 556 | ||
555 | static void applyAttributes_RunTypesetter_(iRunTypesetter *d, iTextAttrib attrib) { | 557 | static void applyAttributes_RunTypesetter_(iRunTypesetter *d, iTextAttrib attrib) { |
556 | /* WARNING: This is duplicated in run_Font_(). Make sure they behave identically. */ | 558 | /* WARNING: This is duplicated in run_Font_(). Make sure they behave identically. */ |
557 | if (attrib.bold) { | 559 | if (attrib.monospace) { |
558 | d->run.font = fontWithStyle_Text(d->baseFont, bold_FontStyle); | 560 | d->run.font = fontWithFamily_Text(d->baseFont, monospace_FontId); |
559 | d->run.color = tmFirstParagraph_ColorId; | 561 | d->run.color = tmPreformatted_ColorId; |
560 | } | 562 | } |
561 | else if (attrib.italic) { | 563 | else if (attrib.italic) { |
562 | d->run.font = fontWithStyle_Text(d->baseFont, italic_FontStyle); | 564 | d->run.font = fontWithStyle_Text(d->baseFont, italic_FontStyle); |
563 | } | 565 | } |
564 | else if (attrib.monospace) { | 566 | else if (attrib.regular) { |
565 | d->run.font = fontWithFamily_Text(d->baseFont, monospace_FontId); | 567 | d->run.font = fontWithStyle_Text(d->baseFont, regular_FontStyle); |
566 | d->run.color = tmPreformatted_ColorId; | 568 | } |
569 | else if (attrib.bold) { | ||
570 | d->run.font = fontWithStyle_Text(d->baseFont, bold_FontStyle); | ||
571 | d->run.color = tmFirstParagraph_ColorId; | ||
572 | } | ||
573 | else if (attrib.light) { | ||
574 | d->run.font = fontWithStyle_Text(d->baseFont, light_FontStyle); | ||
567 | } | 575 | } |
568 | else { | 576 | else { |
569 | d->run.font = d->baseFont; | 577 | d->run.font = d->baseFont; |
@@ -643,6 +651,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
643 | static const char *uploadArrow = upload_Icon; | 651 | static const char *uploadArrow = upload_Icon; |
644 | static const char *image = photo_Icon; | 652 | static const char *image = photo_Icon; |
645 | clear_Array(&d->layout); | 653 | clear_Array(&d->layout); |
654 | clear_StringArray(&d->auxText); | ||
646 | clearLinks_GmDocument_(d); | 655 | clearLinks_GmDocument_(d); |
647 | clear_Array(&d->headings); | 656 | clear_Array(&d->headings); |
648 | const iArray *oldPreMeta = collect_Array(copy_Array(&d->preMeta)); /* remember fold states */ | 657 | const iArray *oldPreMeta = collect_Array(copy_Array(&d->preMeta)); /* remember fold states */ |
@@ -927,7 +936,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
927 | : paragraph_FontId; | 936 | : paragraph_FontId; |
928 | alignDecoration_GmRun_(&icon, iFalse); | 937 | alignDecoration_GmRun_(&icon, iFalse); |
929 | icon.color = linkColor_GmDocument(d, run.linkId, icon_GmLinkPart); | 938 | icon.color = linkColor_GmDocument(d, run.linkId, icon_GmLinkPart); |
930 | icon.flags |= decoration_GmRunFlag; | 939 | icon.flags |= decoration_GmRunFlag | startOfLine_GmRunFlag; |
931 | pushBack_Array(&d->layout, &icon); | 940 | pushBack_Array(&d->layout, &icon); |
932 | } | 941 | } |
933 | run.lineType = type; | 942 | run.lineType = type; |
@@ -1041,7 +1050,12 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
1041 | deinit_RunTypesetter_(&rts); | 1050 | deinit_RunTypesetter_(&rts); |
1042 | } | 1051 | } |
1043 | /* Flag the end of line, too. */ | 1052 | /* Flag the end of line, too. */ |
1044 | ((iGmRun *) back_Array(&d->layout))->flags |= endOfLine_GmRunFlag; | 1053 | iGmRun *lastRun = back_Array(&d->layout); |
1054 | lastRun->flags |= endOfLine_GmRunFlag; | ||
1055 | if (lastRun->linkId && lastRun->flags & startOfLine_GmRunFlag) { | ||
1056 | /* Single-run link: the icon should also be marked endOfLine. */ | ||
1057 | lastRun[-1].flags |= endOfLine_GmRunFlag; | ||
1058 | } | ||
1045 | /* Image or audio content. */ | 1059 | /* Image or audio content. */ |
1046 | if (type == link_GmLineType) { | 1060 | if (type == link_GmLineType) { |
1047 | /* TODO: Cleanup here? Move to a function of its own. */ | 1061 | /* TODO: Cleanup here? Move to a function of its own. */ |
@@ -1075,7 +1089,9 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
1075 | run.bounds.pos.x -= d->outsideMargin; | 1089 | run.bounds.pos.x -= d->outsideMargin; |
1076 | } | 1090 | } |
1077 | run.visBounds = run.bounds; | 1091 | run.visBounds = run.bounds; |
1078 | const iInt2 maxSize = mulf_I2(imgSize, get_Window()->pixelRatio); | 1092 | const iInt2 maxSize = mulf_I2( |
1093 | imgSize, | ||
1094 | get_Window()->pixelRatio * iMax(1.0f, (prefs_App()->zoomPercent / 100.0f))); | ||
1079 | if (width_Rect(run.visBounds) > maxSize.x) { | 1095 | if (width_Rect(run.visBounds) > maxSize.x) { |
1080 | /* Don't scale the image up. */ | 1096 | /* Don't scale the image up. */ |
1081 | run.visBounds.size.y = | 1097 | run.visBounds.size.y = |
@@ -1085,6 +1101,34 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
1085 | run.bounds.size.y = run.visBounds.size.y; | 1101 | run.bounds.size.y = run.visBounds.size.y; |
1086 | } | 1102 | } |
1087 | pushBack_Array(&d->layout, &run); | 1103 | pushBack_Array(&d->layout, &run); |
1104 | pos.y += run.bounds.size.y + margin / 2; | ||
1105 | /* Image metadata caption. */ { | ||
1106 | run.font = FONT_ID(documentBody_FontId, semiBold_FontStyle, contentSmall_FontSize); | ||
1107 | run.color = tmQuoteIcon_ColorId; | ||
1108 | run.flags = decoration_GmRunFlag; | ||
1109 | run.mediaId = 0; | ||
1110 | run.mediaType = 0; | ||
1111 | run.visBounds.pos.y = pos.y; | ||
1112 | run.visBounds.size.y = lineHeight_Text(run.font); | ||
1113 | run.bounds = zero_Rect(); | ||
1114 | iString caption; | ||
1115 | init_String(&caption); | ||
1116 | format_String(&caption, | ||
1117 | "%s \u2014 %d x %d \u2014 %.1f%s", | ||
1118 | info.type, | ||
1119 | imgSize.x, | ||
1120 | imgSize.y, | ||
1121 | info.numBytes / 1.0e6f, | ||
1122 | cstr_Lang("mb")); | ||
1123 | pushBack_StringArray(&d->auxText, &caption); | ||
1124 | run.text = range_String(&caption); | ||
1125 | /* Center it. */ | ||
1126 | run.visBounds.size.x = measureRange_Text(run.font, range_String(&caption)).bounds.size.x; | ||
1127 | run.visBounds.pos.x = d->size.x / 2 - run.visBounds.size.x / 2; | ||
1128 | deinit_String(&caption); | ||
1129 | pushBack_Array(&d->layout, &run); | ||
1130 | pos.y += run.visBounds.size.y + margin; | ||
1131 | } | ||
1088 | break; | 1132 | break; |
1089 | } | 1133 | } |
1090 | case audio_MediaType: { | 1134 | case audio_MediaType: { |
@@ -1157,6 +1201,7 @@ void init_GmDocument(iGmDocument *d) { | |||
1157 | d->enableCommandLinks = iFalse; | 1201 | d->enableCommandLinks = iFalse; |
1158 | d->isLayoutInvalidated = iFalse; | 1202 | d->isLayoutInvalidated = iFalse; |
1159 | init_Array(&d->layout, sizeof(iGmRun)); | 1203 | init_Array(&d->layout, sizeof(iGmRun)); |
1204 | init_StringArray(&d->auxText); | ||
1160 | init_PtrArray(&d->links); | 1205 | init_PtrArray(&d->links); |
1161 | init_String(&d->title); | 1206 | init_String(&d->title); |
1162 | init_Array(&d->headings, sizeof(iGmHeading)); | 1207 | init_Array(&d->headings, sizeof(iGmHeading)); |
@@ -1178,6 +1223,7 @@ void deinit_GmDocument(iGmDocument *d) { | |||
1178 | deinit_PtrArray(&d->links); | 1223 | deinit_PtrArray(&d->links); |
1179 | deinit_Array(&d->preMeta); | 1224 | deinit_Array(&d->preMeta); |
1180 | deinit_Array(&d->headings); | 1225 | deinit_Array(&d->headings); |
1226 | deinit_StringArray(&d->auxText); | ||
1181 | deinit_Array(&d->layout); | 1227 | deinit_Array(&d->layout); |
1182 | deinit_String(&d->localHost); | 1228 | deinit_String(&d->localHost); |
1183 | deinit_String(&d->url); | 1229 | deinit_String(&d->url); |
@@ -1228,8 +1274,8 @@ static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { | |||
1228 | mix_Color(get_Color(tmQuoteIcon_ColorId), get_Color(tmBackground_ColorId), 0.4f)); | 1274 | mix_Color(get_Color(tmQuoteIcon_ColorId), get_Color(tmBackground_ColorId), 0.4f)); |
1229 | set_Color(tmBackgroundOpenLink_ColorId, | 1275 | set_Color(tmBackgroundOpenLink_ColorId, |
1230 | mix_Color(get_Color(tmLinkText_ColorId), get_Color(tmBackground_ColorId), 0.90f)); | 1276 | mix_Color(get_Color(tmLinkText_ColorId), get_Color(tmBackground_ColorId), 0.90f)); |
1231 | set_Color(tmFrameOpenLink_ColorId, | 1277 | set_Color(tmLinkFeedEntryDate_ColorId, |
1232 | mix_Color(get_Color(tmLinkText_ColorId), get_Color(tmBackground_ColorId), 0.75f)); | 1278 | mix_Color(get_Color(tmLinkText_ColorId), get_Color(tmBackground_ColorId), 0.25f)); |
1233 | if (theme == colorfulDark_GmDocumentTheme) { | 1279 | if (theme == colorfulDark_GmDocumentTheme) { |
1234 | /* Ensure paragraph text and link text aren't too similarly colored. */ | 1280 | /* Ensure paragraph text and link text aren't too similarly colored. */ |
1235 | if (delta_Color(get_Color(tmLinkText_ColorId), get_Color(tmParagraph_ColorId)) < 100) { | 1281 | if (delta_Color(get_Color(tmLinkText_ColorId), get_Color(tmParagraph_ColorId)) < 100) { |
@@ -1715,9 +1761,12 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { | |||
1715 | } | 1761 | } |
1716 | 1762 | ||
1717 | void makePaletteGlobal_GmDocument(const iGmDocument *d) { | 1763 | void makePaletteGlobal_GmDocument(const iGmDocument *d) { |
1718 | if (d->isPaletteValid) { | 1764 | if (!d->isPaletteValid) { |
1719 | memcpy(get_Root()->tmPalette, d->palette, sizeof(d->palette)); | 1765 | /* Recompute the palette since it's needed now. */ |
1766 | setThemeSeed_GmDocument((iGmDocument *) d, urlThemeSeed_String(&d->url)); | ||
1720 | } | 1767 | } |
1768 | iAssert(d->isPaletteValid); | ||
1769 | memcpy(get_Root()->tmPalette, d->palette, sizeof(d->palette)); | ||
1721 | } | 1770 | } |
1722 | 1771 | ||
1723 | void invalidatePalette_GmDocument(iGmDocument *d) { | 1772 | void invalidatePalette_GmDocument(iGmDocument *d) { |
@@ -1754,6 +1803,7 @@ static void markLinkRunsVisited_GmDocument_(iGmDocument *d, const iIntSet *linkI | |||
1754 | iForEach(Array, r, &d->layout) { | 1803 | iForEach(Array, r, &d->layout) { |
1755 | iGmRun *run = r.value; | 1804 | iGmRun *run = r.value; |
1756 | if (run->linkId && !run->mediaId && contains_IntSet(linkIds, run->linkId)) { | 1805 | if (run->linkId && !run->mediaId && contains_IntSet(linkIds, run->linkId)) { |
1806 | /* TODO: Does this even work? The font IDs may be different. */ | ||
1757 | if (run->font == bold_FontId) { | 1807 | if (run->font == bold_FontId) { |
1758 | run->font = paragraph_FontId; | 1808 | run->font = paragraph_FontId; |
1759 | } | 1809 | } |
@@ -1890,6 +1940,7 @@ static void normalize_GmDocument(iGmDocument *d) { | |||
1890 | void setUrl_GmDocument(iGmDocument *d, const iString *url) { | 1940 | void setUrl_GmDocument(iGmDocument *d, const iString *url) { |
1891 | url = canonicalUrl_String(url); | 1941 | url = canonicalUrl_String(url); |
1892 | set_String(&d->url, url); | 1942 | set_String(&d->url, url); |
1943 | setThemeSeed_GmDocument(d, urlThemeSeed_String(url)); | ||
1893 | iUrl parts; | 1944 | iUrl parts; |
1894 | init_Url(&parts, url); | 1945 | init_Url(&parts, url); |
1895 | setRange_String(&d->localHost, parts.host); | 1946 | setRange_String(&d->localHost, parts.host); |
@@ -1900,9 +1951,9 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) { | |||
1900 | } | 1951 | } |
1901 | } | 1952 | } |
1902 | 1953 | ||
1903 | static int replaceRegExp_String(iString *d, const iRegExp *regexp, const char *replacement, | 1954 | int replaceRegExp_String(iString *d, const iRegExp *regexp, const char *replacement, |
1904 | void (*matchHandler)(void *, const iRegExpMatch *), | 1955 | void (*matchHandler)(void *, const iRegExpMatch *), |
1905 | void *context) { | 1956 | void *context) { |
1906 | iRegExpMatch m; | 1957 | iRegExpMatch m; |
1907 | iString result; | 1958 | iString result; |
1908 | int numMatches = 0; | 1959 | int numMatches = 0; |
@@ -2103,6 +2154,7 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int | |||
2103 | if (size_String(source) == size_String(&d->unormSource)) { | 2154 | if (size_String(source) == size_String(&d->unormSource)) { |
2104 | iAssert(equal_String(source, &d->unormSource)); | 2155 | iAssert(equal_String(source, &d->unormSource)); |
2105 | // printf("[GmDocument] source is unchanged!\n"); | 2156 | // printf("[GmDocument] source is unchanged!\n"); |
2157 | updateWidth_GmDocument(d, width, canvasWidth); | ||
2106 | return; /* Nothing to do. */ | 2158 | return; /* Nothing to do. */ |
2107 | } | 2159 | } |
2108 | /* Normalize and convert to Gemtext if needed. */ | 2160 | /* Normalize and convert to Gemtext if needed. */ |
diff --git a/src/gmdocument.h b/src/gmdocument.h index 58fc3db3..eb02a26c 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -139,7 +139,7 @@ struct Impl_GmRun { | |||
139 | 139 | ||
140 | uint32_t font : 14; | 140 | uint32_t font : 14; |
141 | uint32_t mediaType : 3; /* note: max_MediaType means preformatted block */ | 141 | uint32_t mediaType : 3; /* note: max_MediaType means preformatted block */ |
142 | uint32_t mediaId : 11; /* zero if not an image */ | 142 | uint32_t mediaId : 11; |
143 | uint32_t lineType : 3; | 143 | uint32_t lineType : 3; |
144 | uint32_t isLede : 1; | 144 | uint32_t isLede : 1; |
145 | }; | 145 | }; |
diff --git a/src/gmrequest.c b/src/gmrequest.c index 23845475..3d5a4aef 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -585,8 +585,10 @@ void setUrl_GmRequest(iGmRequest *d, const iString *url) { | |||
585 | /* TODO: Gemini spec allows UTF-8 encoded URLs, but still need to percent-encode non-ASCII | 585 | /* TODO: Gemini spec allows UTF-8 encoded URLs, but still need to percent-encode non-ASCII |
586 | characters? Could be a server-side issue, e.g., if they're using a URL parser meant for | 586 | characters? Could be a server-side issue, e.g., if they're using a URL parser meant for |
587 | the web. */ | 587 | the web. */ |
588 | urlEncodePath_String(&d->url); | 588 | /* Encode everything except already-percent encoded characters. */ |
589 | urlEncodeSpaces_String(&d->url); | 589 | iString *enc = urlEncodeExclude_String(&d->url, "%" URL_RESERVED_CHARS); |
590 | set_String(&d->url, enc); | ||
591 | delete_String(enc); | ||
590 | d->identity = identityForUrl_GmCerts(d->certs, &d->url); | 592 | d->identity = identityForUrl_GmCerts(d->certs, &d->url); |
591 | } | 593 | } |
592 | 594 | ||
@@ -692,9 +694,11 @@ void submit_GmRequest(iGmRequest *d) { | |||
692 | setCStr_String(&resp->meta, "text/gemini"); | 694 | setCStr_String(&resp->meta, "text/gemini"); |
693 | iString *page = collectNew_String(); | 695 | iString *page = collectNew_String(); |
694 | iString *parentDir = collectNewRange_String(dirName_Path(path)); | 696 | iString *parentDir = collectNewRange_String(dirName_Path(path)); |
697 | #if !defined (iPlatformMobile) | ||
695 | appendFormat_String(page, "=> %s " upArrow_Icon " %s" iPathSeparator "\n\n", | 698 | appendFormat_String(page, "=> %s " upArrow_Icon " %s" iPathSeparator "\n\n", |
696 | cstrCollect_String(makeFileUrl_String(parentDir)), | 699 | cstrCollect_String(makeFileUrl_String(parentDir)), |
697 | cstr_String(parentDir)); | 700 | cstr_String(parentDir)); |
701 | #endif | ||
698 | appendFormat_String(page, "# %s\n", cstr_Rangecc(baseName_Path(path))); | 702 | appendFormat_String(page, "# %s\n", cstr_Rangecc(baseName_Path(path))); |
699 | /* Make a directory index page. */ | 703 | /* Make a directory index page. */ |
700 | iPtrArray *sortedInfo = collectNew_PtrArray(); | 704 | iPtrArray *sortedInfo = collectNew_PtrArray(); |
@@ -790,7 +794,8 @@ void submit_GmRequest(iGmRequest *d) { | |||
790 | cstr_String(containerUrl)); | 794 | cstr_String(containerUrl)); |
791 | appendFormat_String(page, "# %s\n\n", cstr_Rangecc(containerName)); | 795 | appendFormat_String(page, "# %s\n\n", cstr_Rangecc(containerName)); |
792 | appendFormat_String(page, | 796 | appendFormat_String(page, |
793 | cstrCount_Lang("archive.summary.n", numEntries_Archive(arch)), | 797 | cstrCount_Lang("archive.summary.n", |
798 | (int) numEntries_Archive(arch)), | ||
794 | numEntries_Archive(arch), | 799 | numEntries_Archive(arch), |
795 | (double) sourceSize_Archive(arch) / 1.0e6); | 800 | (double) sourceSize_Archive(arch) / 1.0e6); |
796 | appendCStr_String(page, "\n\n"); | 801 | appendCStr_String(page, "\n\n"); |
@@ -802,7 +807,7 @@ void submit_GmRequest(iGmRequest *d) { | |||
802 | } | 807 | } |
803 | else if (size_StringSet(contents) > 1) { | 808 | else if (size_StringSet(contents) > 1) { |
804 | appendFormat_String(page, cstrCount_Lang("dir.summary.n", | 809 | appendFormat_String(page, cstrCount_Lang("dir.summary.n", |
805 | size_StringSet(contents)), | 810 | (int) size_StringSet(contents)), |
806 | size_StringSet(contents)); | 811 | size_StringSet(contents)); |
807 | appendCStr_String(page, "\n\n"); | 812 | appendCStr_String(page, "\n\n"); |
808 | } | 813 | } |
diff --git a/src/gmutil.c b/src/gmutil.c index 70a3608e..98e4d4d6 100644 --- a/src/gmutil.c +++ b/src/gmutil.c | |||
@@ -253,6 +253,17 @@ iRangecc urlRoot_String(const iString *d) { | |||
253 | return (iRangecc){ constBegin_String(d), rootEnd }; | 253 | return (iRangecc){ constBegin_String(d), rootEnd }; |
254 | } | 254 | } |
255 | 255 | ||
256 | const iBlock *urlThemeSeed_String(const iString *url) { | ||
257 | if (equalCase_Rangecc(urlScheme_String(url), "file")) { | ||
258 | return collect_Block(new_Block(0)); | ||
259 | } | ||
260 | const iRangecc user = urlUser_String(url); | ||
261 | if (isEmpty_Range(&user)) { | ||
262 | return collect_Block(newRange_Block(urlHost_String(url))); | ||
263 | } | ||
264 | return collect_Block(newRange_Block(user)); | ||
265 | } | ||
266 | |||
256 | static iBool isAbsolutePath_(iRangecc path) { | 267 | static iBool isAbsolutePath_(iRangecc path) { |
257 | return isAbsolute_Path(collect_String(urlDecode_String(collect_String(newRange_String(path))))); | 268 | return isAbsolute_Path(collect_String(urlDecode_String(collect_String(newRange_String(path))))); |
258 | } | 269 | } |
@@ -319,6 +330,28 @@ void urlEncodePath_String(iString *d) { | |||
319 | delete_String(encoded); | 330 | delete_String(encoded); |
320 | } | 331 | } |
321 | 332 | ||
333 | void urlEncodeQuery_String(iString *d) { | ||
334 | iUrl url; | ||
335 | init_Url(&url, d); | ||
336 | if (isEmpty_Range(&url.query)) { | ||
337 | return; | ||
338 | } | ||
339 | iString encoded; | ||
340 | init_String(&encoded); | ||
341 | appendRange_String(&encoded, (iRangecc){ constBegin_String(d), url.query.start }); | ||
342 | iString query; | ||
343 | url.query.start++; /* omit the question mark */ | ||
344 | initRange_String(&query, url.query); | ||
345 | iString *encQuery = urlEncode_String(&query); /* fully encoded */ | ||
346 | appendCStr_String(&encoded, "?"); | ||
347 | append_String(&encoded, encQuery); | ||
348 | delete_String(encQuery); | ||
349 | deinit_String(&query); | ||
350 | appendRange_String(&encoded, (iRangecc){ url.query.end, constEnd_String(d) }); | ||
351 | set_String(d, &encoded); | ||
352 | deinit_String(&encoded); | ||
353 | } | ||
354 | |||
322 | iBool isKnownScheme_Rangecc(iRangecc scheme) { | 355 | iBool isKnownScheme_Rangecc(iRangecc scheme) { |
323 | if (isKnownUrlScheme_Rangecc(scheme)) { | 356 | if (isKnownUrlScheme_Rangecc(scheme)) { |
324 | return iTrue; | 357 | return iTrue; |
@@ -651,25 +684,25 @@ const iString *withSpacesEncoded_String(const iString *d) { | |||
651 | const iString *canonicalUrl_String(const iString *d) { | 684 | const iString *canonicalUrl_String(const iString *d) { |
652 | /* The "canonical" form, used for internal storage and comparisons, is: | 685 | /* The "canonical" form, used for internal storage and comparisons, is: |
653 | - all non-reserved characters decoded (i.e., it's an IRI) | 686 | - all non-reserved characters decoded (i.e., it's an IRI) |
654 | - expect for spaces, which are always `%20` | 687 | - except spaces, which are always `%20` |
655 | This means a canonical URL can be used on a gemtext link line without modifications. */ | 688 | This means a canonical URL can be used on a gemtext link line without modifications. */ |
656 | iString *canon = NULL; | 689 | iString *canon = NULL; |
657 | iUrl parts; | 690 | iUrl parts; |
658 | init_Url(&parts, d); | 691 | init_Url(&parts, d); |
659 | /* Colons are in decoded form in the URL path. */ | 692 | /* Colons (0x3a) are in decoded form in the URL path. */ |
660 | if (iStrStrN(parts.path.start, "%3A", size_Range(&parts.path)) || | 693 | if (iStrStrN(parts.path.start, "%3A", size_Range(&parts.path)) || |
661 | iStrStrN(parts.path.start, "%3a", size_Range(&parts.path))) { | 694 | iStrStrN(parts.path.start, "%3a", size_Range(&parts.path))) { |
662 | /* This is done separately to avoid the copy if %3A is not present; it's rare. */ | 695 | /* This is done separately to avoid the copy if %3A is not present; it's rare. */ |
663 | canon = copy_String(d); | 696 | canon = copy_String(d); |
664 | urlDecodePath_String(canon); | 697 | urlDecodePath_String(canon); |
665 | iString *dec = maybeUrlDecodeExclude_String(canon, "%/?:;#&+= "); /* decode everything else in all parts */ | 698 | iString *dec = maybeUrlDecodeExclude_String(canon, "% " URL_RESERVED_CHARS); /* decode everything else in all parts */ |
666 | if (dec) { | 699 | if (dec) { |
667 | set_String(canon, dec); | 700 | set_String(canon, dec); |
668 | delete_String(dec); | 701 | delete_String(dec); |
669 | } | 702 | } |
670 | } | 703 | } |
671 | else { | 704 | else { |
672 | canon = maybeUrlDecodeExclude_String(d, "%/?:;#&+= "); | 705 | canon = maybeUrlDecodeExclude_String(d, "% " URL_RESERVED_CHARS); |
673 | } | 706 | } |
674 | /* `canon` may now be NULL if nothing was decoded. */ | 707 | /* `canon` may now be NULL if nothing was decoded. */ |
675 | if (indexOfCStr_String(canon ? canon : d, " ") != iInvalidPos || | 708 | if (indexOfCStr_String(canon ? canon : d, " ") != iInvalidPos || |
@@ -678,7 +711,7 @@ const iString *canonicalUrl_String(const iString *d) { | |||
678 | canon = copy_String(d); | 711 | canon = copy_String(d); |
679 | } | 712 | } |
680 | urlEncodeSpaces_String(canon); | 713 | urlEncodeSpaces_String(canon); |
681 | } | 714 | } |
682 | return canon ? collect_String(canon) : d; | 715 | return canon ? collect_String(canon) : d; |
683 | } | 716 | } |
684 | 717 | ||
diff --git a/src/gmutil.h b/src/gmutil.h index 6b10b444..15bb7b2e 100644 --- a/src/gmutil.h +++ b/src/gmutil.h | |||
@@ -27,6 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
27 | 27 | ||
28 | iDeclareType(GmError) | 28 | iDeclareType(GmError) |
29 | iDeclareType(RegExp) | 29 | iDeclareType(RegExp) |
30 | iDeclareType(RegExpMatch) | ||
30 | iDeclareType(Url) | 31 | iDeclareType(Url) |
31 | 32 | ||
32 | /* Response status codes. */ | 33 | /* Response status codes. */ |
@@ -99,6 +100,7 @@ iRegExp * newGemtextLink_RegExp (void); | |||
99 | 100 | ||
100 | #define GEMINI_DEFAULT_PORT ((uint16_t) 1965) | 101 | #define GEMINI_DEFAULT_PORT ((uint16_t) 1965) |
101 | #define GEMINI_DEFAULT_PORT_CSTR "1965" | 102 | #define GEMINI_DEFAULT_PORT_CSTR "1965" |
103 | #define URL_RESERVED_CHARS ":/?#[]@!$&'()*+,;=" /* RFC 3986 */ | ||
102 | 104 | ||
103 | struct Impl_Url { | 105 | struct Impl_Url { |
104 | iRangecc scheme; | 106 | iRangecc scheme; |
@@ -117,6 +119,8 @@ iRangecc urlHost_String (const iString *); | |||
117 | uint16_t urlPort_String (const iString *); | 119 | uint16_t urlPort_String (const iString *); |
118 | iRangecc urlUser_String (const iString *); | 120 | iRangecc urlUser_String (const iString *); |
119 | iRangecc urlRoot_String (const iString *); | 121 | iRangecc urlRoot_String (const iString *); |
122 | const iBlock * urlThemeSeed_String (const iString *); | ||
123 | |||
120 | const iString * absoluteUrl_String (const iString *, const iString *urlMaybeRelative); | 124 | const iString * absoluteUrl_String (const iString *, const iString *urlMaybeRelative); |
121 | iBool isLikelyUrl_String (const iString *); | 125 | iBool isLikelyUrl_String (const iString *); |
122 | iBool isKnownScheme_Rangecc (iRangecc scheme); /* any URI scheme */ | 126 | iBool isKnownScheme_Rangecc (iRangecc scheme); /* any URI scheme */ |
@@ -128,6 +132,7 @@ const iString * urlFragmentStripped_String(const iString *); | |||
128 | const iString * urlQueryStripped_String (const iString *); | 132 | const iString * urlQueryStripped_String (const iString *); |
129 | void urlDecodePath_String (iString *); | 133 | void urlDecodePath_String (iString *); |
130 | void urlEncodePath_String (iString *); | 134 | void urlEncodePath_String (iString *); |
135 | void urlEncodeQuery_String (iString *); | ||
131 | iString * makeFileUrl_String (const iString *localFilePath); | 136 | iString * makeFileUrl_String (const iString *localFilePath); |
132 | const char * makeFileUrl_CStr (const char *localFilePath); | 137 | const char * makeFileUrl_CStr (const char *localFilePath); |
133 | iString * localFilePathFromUrl_String(const iString *); | 138 | iString * localFilePathFromUrl_String(const iString *); |
@@ -143,3 +148,8 @@ const iString * findContainerArchive_Path (const iString *path); | |||
143 | 148 | ||
144 | 149 | ||
145 | const iString * feedEntryOpenCommand_String (const iString *url, int newTab); /* checks fragment */ | 150 | const iString * feedEntryOpenCommand_String (const iString *url, int newTab); /* checks fragment */ |
151 | |||
152 | /* TODO: Consider adding this to the_Foundation. */ | ||
153 | int replaceRegExp_String (iString *, const iRegExp *regexp, const char *replacement, | ||
154 | void (*matchHandler)(void *, const iRegExpMatch *), | ||
155 | void *context); | ||
diff --git a/src/history.c b/src/history.c index 7185912f..56454009 100644 --- a/src/history.c +++ b/src/history.c | |||
@@ -37,7 +37,7 @@ void init_RecentUrl(iRecentUrl *d) { | |||
37 | d->normScrollY = 0; | 37 | d->normScrollY = 0; |
38 | d->cachedResponse = NULL; | 38 | d->cachedResponse = NULL; |
39 | d->cachedDoc = NULL; | 39 | d->cachedDoc = NULL; |
40 | d->flags.openedFromSidebar = iFalse; | 40 | d->flags = 0; |
41 | } | 41 | } |
42 | 42 | ||
43 | void deinit_RecentUrl(iRecentUrl *d) { | 43 | void deinit_RecentUrl(iRecentUrl *d) { |
@@ -110,6 +110,14 @@ iHistory *copy_History(const iHistory *d) { | |||
110 | return copy; | 110 | return copy; |
111 | } | 111 | } |
112 | 112 | ||
113 | void lock_History(iHistory *d) { | ||
114 | lock_Mutex(d->mtx); | ||
115 | } | ||
116 | |||
117 | void unlock_History(iHistory *d) { | ||
118 | unlock_Mutex(d->mtx); | ||
119 | } | ||
120 | |||
113 | iMemInfo memoryUsage_History(const iHistory *d) { | 121 | iMemInfo memoryUsage_History(const iHistory *d) { |
114 | iMemInfo mem = { 0, 0 }; | 122 | iMemInfo mem = { 0, 0 }; |
115 | iConstForEach(Array, i, &d->recent) { | 123 | iConstForEach(Array, i, &d->recent) { |
@@ -173,7 +181,7 @@ void serialize_History(const iHistory *d, iStream *outs) { | |||
173 | const iRecentUrl *item = i.value; | 181 | const iRecentUrl *item = i.value; |
174 | serialize_String(&item->url, outs); | 182 | serialize_String(&item->url, outs); |
175 | write32_Stream(outs, item->normScrollY * 1.0e6f); | 183 | write32_Stream(outs, item->normScrollY * 1.0e6f); |
176 | writeU16_Stream(outs, item->flags.openedFromSidebar ? iBit(1) : 0); | 184 | writeU16_Stream(outs, item->flags); |
177 | if (item->cachedResponse) { | 185 | if (item->cachedResponse) { |
178 | write8_Stream(outs, 1); | 186 | write8_Stream(outs, 1); |
179 | serialize_GmResponse(item->cachedResponse, outs); | 187 | serialize_GmResponse(item->cachedResponse, outs); |
@@ -197,10 +205,7 @@ void deserialize_History(iHistory *d, iStream *ins) { | |||
197 | set_String(&item.url, canonicalUrl_String(&item.url)); | 205 | set_String(&item.url, canonicalUrl_String(&item.url)); |
198 | item.normScrollY = (float) read32_Stream(ins) / 1.0e6f; | 206 | item.normScrollY = (float) read32_Stream(ins) / 1.0e6f; |
199 | if (version_Stream(ins) >= addedRecentUrlFlags_FileVersion) { | 207 | if (version_Stream(ins) >= addedRecentUrlFlags_FileVersion) { |
200 | uint16_t flags = readU16_Stream(ins); | 208 | item.flags = readU16_Stream(ins); |
201 | if (flags & iBit(1)) { | ||
202 | item.flags.openedFromSidebar = iTrue; | ||
203 | } | ||
204 | } | 209 | } |
205 | if (read8_Stream(ins)) { | 210 | if (read8_Stream(ins)) { |
206 | item.cachedResponse = new_GmResponse(); | 211 | item.cachedResponse = new_GmResponse(); |
@@ -246,18 +251,26 @@ const iString *url_History(const iHistory *d, size_t pos) { | |||
246 | return collectNew_String(); | 251 | return collectNew_String(); |
247 | } | 252 | } |
248 | 253 | ||
249 | iRecentUrl *findUrl_History(iHistory *d, const iString *url) { | 254 | #if 0 |
255 | iRecentUrl *findUrl_History(iHistory *d, const iString *url, int timeDir) { | ||
250 | url = canonicalUrl_String(url); | 256 | url = canonicalUrl_String(url); |
257 | // if (!timeDir) { | ||
258 | // timeDir = -1; | ||
259 | // } | ||
251 | lock_Mutex(d->mtx); | 260 | lock_Mutex(d->mtx); |
252 | iReverseForEach(Array, i, &d->recent) { | 261 | for (size_t i = size_Array(&d->recent) - 1 - d->recentPos; i < size_Array(&d->recent); |
253 | if (cmpStringCase_String(url, &((iRecentUrl *) i.value)->url) == 0) { | 262 | i += timeDir) { |
263 | iRecentUrl *item = at_Array(&d->recent, i); | ||
264 | if (cmpStringCase_String(url, &item->url) == 0) { | ||
254 | unlock_Mutex(d->mtx); | 265 | unlock_Mutex(d->mtx); |
255 | return i.value; | 266 | return item; /* FIXME: Returning an internal pointer; should remain locked. */ |
256 | } | 267 | } |
268 | if (!timeDir) break; | ||
257 | } | 269 | } |
258 | unlock_Mutex(d->mtx); | 270 | unlock_Mutex(d->mtx); |
259 | return NULL; | 271 | return NULL; |
260 | } | 272 | } |
273 | #endif | ||
261 | 274 | ||
262 | void replace_History(iHistory *d, const iString *url) { | 275 | void replace_History(iHistory *d, const iString *url) { |
263 | url = canonicalUrl_String(url); | 276 | url = canonicalUrl_String(url); |
@@ -297,20 +310,31 @@ void add_History(iHistory *d, const iString *url) { | |||
297 | unlock_Mutex(d->mtx); | 310 | unlock_Mutex(d->mtx); |
298 | } | 311 | } |
299 | 312 | ||
300 | iBool preceding_History(iHistory *d, iRecentUrl *recent_out) { | 313 | void undo_History(iHistory *d) { |
301 | iBool ok = iFalse; | ||
302 | lock_Mutex(d->mtx); | 314 | lock_Mutex(d->mtx); |
303 | if (!isEmpty_Array(&d->recent) && d->recentPos < size_Array(&d->recent) - 1) { | 315 | if (!isEmpty_Array(&d->recent) || d->recentPos != 0) { |
304 | const iRecentUrl *recent = constAt_Array(&d->recent, size_Array(&d->recent) - 1 - | 316 | deinit_RecentUrl(back_Array(&d->recent)); |
305 | (d->recentPos + 1)); | 317 | popBack_Array(&d->recent); |
306 | set_String(&recent_out->url, &recent->url); | 318 | } |
307 | recent_out->normScrollY = recent->normScrollY; | 319 | unlock_Mutex(d->mtx); |
308 | iChangeRef(recent_out->cachedDoc, recent->cachedDoc); | 320 | } |
321 | |||
322 | iRecentUrl *precedingLocked_History(iHistory *d) { | ||
323 | /* NOTE: Manual lock and unlock are required when using this; returning an internal pointer. */ | ||
324 | iBool ok = iFalse; | ||
325 | //lock_Mutex(d->mtx); | ||
326 | const size_t lastIndex = size_Array(&d->recent) - 1; | ||
327 | if (!isEmpty_Array(&d->recent) && d->recentPos < lastIndex) { | ||
328 | return at_Array(&d->recent, lastIndex - (d->recentPos + 1)); | ||
329 | // set_String(&recent_out->url, &recent->url); | ||
330 | // recent_out->normScrollY = recent->normScrollY; | ||
331 | // iChangeRef(recent_out->cachedDoc, recent->cachedDoc); | ||
309 | /* Cached response is not returned, would involve a deep copy. */ | 332 | /* Cached response is not returned, would involve a deep copy. */ |
310 | ok = iTrue; | 333 | // ok = iTrue; |
311 | } | 334 | } |
312 | unlock_Mutex(d->mtx); | 335 | //unlock_Mutex(d->mtx); |
313 | return ok; | 336 | // return ok; |
337 | return NULL; | ||
314 | } | 338 | } |
315 | 339 | ||
316 | #if 0 | 340 | #if 0 |
@@ -360,7 +384,7 @@ iBool goForward_History(iHistory *d) { | |||
360 | return iFalse; | 384 | return iFalse; |
361 | } | 385 | } |
362 | 386 | ||
363 | iBool atLatest_History(const iHistory *d) { | 387 | iBool atNewest_History(const iHistory *d) { |
364 | iBool isLatest; | 388 | iBool isLatest; |
365 | iGuardMutex(d->mtx, isLatest = (d->recentPos == 0)); | 389 | iGuardMutex(d->mtx, isLatest = (d->recentPos == 0)); |
366 | return isLatest; | 390 | return isLatest; |
@@ -391,12 +415,18 @@ void setCachedResponse_History(iHistory *d, const iGmResponse *response) { | |||
391 | unlock_Mutex(d->mtx); | 415 | unlock_Mutex(d->mtx); |
392 | } | 416 | } |
393 | 417 | ||
394 | void setCachedDocument_History(iHistory *d, iGmDocument *doc, iBool openedFromSidebar) { | 418 | void setCachedDocument_History(iHistory *d, iGmDocument *doc) { |
395 | lock_Mutex(d->mtx); | 419 | lock_Mutex(d->mtx); |
396 | iRecentUrl *item = mostRecentUrl_History(d); | 420 | iRecentUrl *item = mostRecentUrl_History(d); |
421 | iAssert(size_GmDocument(doc).x > 0); | ||
397 | if (item) { | 422 | if (item) { |
398 | iAssert(equal_String(url_GmDocument(doc), &item->url)); | 423 | #if !defined (NDEBUG) |
399 | item->flags.openedFromSidebar = openedFromSidebar; | 424 | if (!equal_String(url_GmDocument(doc), &item->url)) { |
425 | printf("[History] Cache mismatch! Expecting data for item {%s} but document URL is {%s}\n", | ||
426 | cstr_String(&item->url), | ||
427 | cstr_String(url_GmDocument(doc))); | ||
428 | } | ||
429 | #endif | ||
400 | if (item->cachedDoc != doc) { | 430 | if (item->cachedDoc != doc) { |
401 | iRelease(item->cachedDoc); | 431 | iRelease(item->cachedDoc); |
402 | item->cachedDoc = ref_Object(doc); | 432 | item->cachedDoc = ref_Object(doc); |
diff --git a/src/history.h b/src/history.h index d3daae80..7959187d 100644 --- a/src/history.h +++ b/src/history.h | |||
@@ -39,9 +39,7 @@ struct Impl_RecentUrl { | |||
39 | float normScrollY; /* normalized to document height */ | 39 | float normScrollY; /* normalized to document height */ |
40 | iGmResponse *cachedResponse; /* kept in memory for quicker back navigation */ | 40 | iGmResponse *cachedResponse; /* kept in memory for quicker back navigation */ |
41 | iGmDocument *cachedDoc; /* cached copy of the presentation: layout and media (not serialized) */ | 41 | iGmDocument *cachedDoc; /* cached copy of the presentation: layout and media (not serialized) */ |
42 | struct { | 42 | uint16_t flags; |
43 | uint8_t openedFromSidebar : 1; | ||
44 | } flags; | ||
45 | }; | 43 | }; |
46 | 44 | ||
47 | iDeclareType(MemInfo) | 45 | iDeclareType(MemInfo) |
@@ -58,19 +56,21 @@ iDeclareTypeConstruction(History) | |||
58 | iDeclareTypeSerialization(History) | 56 | iDeclareTypeSerialization(History) |
59 | 57 | ||
60 | iHistory * copy_History (const iHistory *); | 58 | iHistory * copy_History (const iHistory *); |
59 | void lock_History (iHistory *); | ||
60 | void unlock_History (iHistory *); | ||
61 | 61 | ||
62 | void clear_History (iHistory *); | 62 | void clear_History (iHistory *); |
63 | void add_History (iHistory *, const iString *url); | 63 | void add_History (iHistory *, const iString *url); |
64 | void undo_History (iHistory *); /* removes the most recent URL */ | ||
64 | void replace_History (iHistory *, const iString *url); | 65 | void replace_History (iHistory *, const iString *url); |
65 | void setCachedResponse_History (iHistory *, const iGmResponse *response); | 66 | void setCachedResponse_History (iHistory *, const iGmResponse *response); |
66 | void setCachedDocument_History (iHistory *, iGmDocument *doc, iBool openedFromSidebar); | 67 | void setCachedDocument_History (iHistory *, iGmDocument *doc); |
67 | iBool goBack_History (iHistory *); | 68 | iBool goBack_History (iHistory *); |
68 | iBool goForward_History (iHistory *); | 69 | iBool goForward_History (iHistory *); |
69 | iBool preceding_History (iHistory *d, iRecentUrl *recent_out); | 70 | iRecentUrl *precedingLocked_History (iHistory *); /* requires manual lock/unlock! */ |
70 | //iBool following_History (iHistory *d, iRecentUrl *recent_out); | ||
71 | iRecentUrl *recentUrl_History (iHistory *, size_t pos); | 71 | iRecentUrl *recentUrl_History (iHistory *, size_t pos); |
72 | iRecentUrl *mostRecentUrl_History (iHistory *); | 72 | iRecentUrl *mostRecentUrl_History (iHistory *); |
73 | iRecentUrl *findUrl_History (iHistory *, const iString *url); | 73 | //iRecentUrl *findUrl_History (iHistory *, const iString *url, int timeDir); |
74 | 74 | ||
75 | void clearCache_History (iHistory *); | 75 | void clearCache_History (iHistory *); |
76 | size_t pruneLeastImportant_History (iHistory *); | 76 | size_t pruneLeastImportant_History (iHistory *); |
@@ -78,7 +78,7 @@ size_t pruneLeastImportantMemory_History (iHistory *); | |||
78 | void invalidateTheme_History (iHistory *); /* theme has changed, cached contents need updating */ | 78 | void invalidateTheme_History (iHistory *); /* theme has changed, cached contents need updating */ |
79 | void invalidateCachedLayout_History (iHistory *); | 79 | void invalidateCachedLayout_History (iHistory *); |
80 | 80 | ||
81 | iBool atLatest_History (const iHistory *); | 81 | iBool atNewest_History (const iHistory *); |
82 | iBool atOldest_History (const iHistory *); | 82 | iBool atOldest_History (const iHistory *); |
83 | 83 | ||
84 | const iStringArray * searchContents_History (const iHistory *, const iRegExp *pattern); /* chronologically ascending */ | 84 | const iStringArray * searchContents_History (const iHistory *, const iRegExp *pattern); /* chronologically ascending */ |
@@ -38,6 +38,8 @@ void playHapticEffect_iOS (enum iHapticEffect effect); | |||
38 | void exportDownloadedFile_iOS(const iString *path); | 38 | void exportDownloadedFile_iOS(const iString *path); |
39 | void pickFileForOpening_iOS (void); | 39 | void pickFileForOpening_iOS (void); |
40 | void pickFile_iOS (const char *command); /* ` path:%s` will be appended */ | 40 | void pickFile_iOS (const char *command); /* ` path:%s` will be appended */ |
41 | void openTextActivityView_iOS(const iString *text); | ||
42 | void openFileActivityView_iOS(const iString *path); | ||
41 | 43 | ||
42 | iBool isPhone_iOS (void); | 44 | iBool isPhone_iOS (void); |
43 | void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom); | 45 | void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom); |
@@ -61,3 +63,30 @@ iBool isPaused_AVFAudioPlayer (const iAVFAudioPlayer *); | |||
61 | 63 | ||
62 | void clearNowPlayingInfo_iOS (void); | 64 | void clearNowPlayingInfo_iOS (void); |
63 | void updateNowPlayingInfo_iOS (void); | 65 | void updateNowPlayingInfo_iOS (void); |
66 | |||
67 | /*----------------------------------------------------------------------------------------------*/ | ||
68 | |||
69 | enum iSystemTextInputFlags { | ||
70 | selectAll_SystemTextInputFlags = iBit(1), | ||
71 | multiLine_SystemTextInputFlags = iBit(2), | ||
72 | returnGo_SystemTextInputFlags = iBit(3), | ||
73 | returnSend_SystemTextInputFlags = iBit(4), | ||
74 | disableAutocorrect_SystemTextInputFlag = iBit(5), | ||
75 | disableAutocapitalize_SystemTextInputFlag = iBit(6), | ||
76 | alignRight_SystemTextInputFlag = iBit(7), | ||
77 | insertNewlines_SystemTextInputFlag = iBit(8), | ||
78 | extraPadding_SystemTextInputFlag = iBit(9), | ||
79 | }; | ||
80 | |||
81 | iDeclareType(SystemTextInput) | ||
82 | iDeclareTypeConstructionArgs(SystemTextInput, iRect rect, int flags) | ||
83 | |||
84 | void setRect_SystemTextInput (iSystemTextInput *, iRect rect); | ||
85 | void setText_SystemTextInput (iSystemTextInput *, const iString *text, iBool allowUndo); | ||
86 | void setFont_SystemTextInput (iSystemTextInput *, int fontId); | ||
87 | void setTextChangedFunc_SystemTextInput | ||
88 | (iSystemTextInput *, void (*textChangedFunc)(iSystemTextInput *, void *), void *); | ||
89 | void selectAll_SystemTextInput(iSystemTextInput *); | ||
90 | |||
91 | const iString * text_SystemTextInput (const iSystemTextInput *); | ||
92 | int preferredHeight_SystemTextInput (const iSystemTextInput *); | ||
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
25 | #include "audio/player.h" | 25 | #include "audio/player.h" |
26 | #include "ui/command.h" | 26 | #include "ui/command.h" |
27 | #include "ui/window.h" | 27 | #include "ui/window.h" |
28 | #include "ui/touch.h" | ||
28 | 29 | ||
29 | #include <the_Foundation/file.h> | 30 | #include <the_Foundation/file.h> |
30 | #include <the_Foundation/fileinfo.h> | 31 | #include <the_Foundation/fileinfo.h> |
@@ -60,6 +61,9 @@ static UIViewController *viewController_(iWindow *window) { | |||
60 | return NULL; | 61 | return NULL; |
61 | } | 62 | } |
62 | 63 | ||
64 | static void notifyChange_SystemTextInput_(iSystemTextInput *); | ||
65 | static BOOL isNewlineAllowed_SystemTextInput_(const iSystemTextInput *); | ||
66 | |||
63 | /*----------------------------------------------------------------------------------------------*/ | 67 | /*----------------------------------------------------------------------------------------------*/ |
64 | 68 | ||
65 | API_AVAILABLE(ios(13.0)) | 69 | API_AVAILABLE(ios(13.0)) |
@@ -159,9 +163,10 @@ API_AVAILABLE(ios(13.0)) | |||
159 | 163 | ||
160 | /*----------------------------------------------------------------------------------------------*/ | 164 | /*----------------------------------------------------------------------------------------------*/ |
161 | 165 | ||
162 | @interface AppState : NSObject<UIDocumentPickerDelegate> { | 166 | @interface AppState : NSObject<UIDocumentPickerDelegate, UITextFieldDelegate, UITextViewDelegate> { |
163 | iString *fileBeingSaved; | 167 | iString *fileBeingSaved; |
164 | iString *pickFileCommand; | 168 | iString *pickFileCommand; |
169 | iSystemTextInput *sysCtrl; | ||
165 | } | 170 | } |
166 | @property (nonatomic, assign) BOOL isHapticsAvailable; | 171 | @property (nonatomic, assign) BOOL isHapticsAvailable; |
167 | @property (nonatomic, strong) NSObject *haptic; | 172 | @property (nonatomic, strong) NSObject *haptic; |
@@ -175,9 +180,18 @@ static AppState *appState_; | |||
175 | self = [super init]; | 180 | self = [super init]; |
176 | fileBeingSaved = NULL; | 181 | fileBeingSaved = NULL; |
177 | pickFileCommand = NULL; | 182 | pickFileCommand = NULL; |
183 | sysCtrl = NULL; | ||
178 | return self; | 184 | return self; |
179 | } | 185 | } |
180 | 186 | ||
187 | -(void)setSystemTextInput:(iSystemTextInput *)sys { | ||
188 | sysCtrl = sys; | ||
189 | } | ||
190 | |||
191 | -(iSystemTextInput *)systemTextInput { | ||
192 | return sysCtrl; | ||
193 | } | ||
194 | |||
181 | -(void)setPickFileCommand:(const char *)command { | 195 | -(void)setPickFileCommand:(const char *)command { |
182 | if (!pickFileCommand) { | 196 | if (!pickFileCommand) { |
183 | pickFileCommand = new_String(); | 197 | pickFileCommand = new_String(); |
@@ -256,6 +270,46 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls { | |||
256 | -(void)keyboardOffScreen:(NSNotification *)notification { | 270 | -(void)keyboardOffScreen:(NSNotification *)notification { |
257 | setKeyboardHeight_MainWindow(get_MainWindow(), 0); | 271 | setKeyboardHeight_MainWindow(get_MainWindow(), 0); |
258 | } | 272 | } |
273 | |||
274 | static void sendReturnKeyPress_(void) { | ||
275 | SDL_Event ev = { .type = SDL_KEYDOWN }; | ||
276 | ev.key.timestamp = SDL_GetTicks(); | ||
277 | ev.key.keysym.sym = SDLK_RETURN; | ||
278 | ev.key.state = SDL_PRESSED; | ||
279 | SDL_PushEvent(&ev); | ||
280 | ev.type = SDL_KEYUP; | ||
281 | ev.key.state = SDL_RELEASED; | ||
282 | SDL_PushEvent(&ev); | ||
283 | } | ||
284 | |||
285 | - (BOOL)textFieldShouldReturn:(UITextField *)textField { | ||
286 | sendReturnKeyPress_(); | ||
287 | return NO; | ||
288 | } | ||
289 | |||
290 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range | ||
291 | replacementString:(NSString *)string { | ||
292 | iSystemTextInput *sysCtrl = [appState_ systemTextInput]; | ||
293 | notifyChange_SystemTextInput_(sysCtrl); | ||
294 | return YES; | ||
295 | } | ||
296 | |||
297 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range | ||
298 | replacementText:(NSString *)text { | ||
299 | if ([text isEqualToString:@"\n"]) { | ||
300 | if (!isNewlineAllowed_SystemTextInput_([appState_ systemTextInput])) { | ||
301 | sendReturnKeyPress_(); | ||
302 | return NO; | ||
303 | } | ||
304 | } | ||
305 | return YES; | ||
306 | } | ||
307 | |||
308 | - (void)textViewDidChange:(UITextView *)textView { | ||
309 | iSystemTextInput *sysCtrl = [appState_ systemTextInput]; | ||
310 | notifyChange_SystemTextInput_(sysCtrl); | ||
311 | } | ||
312 | |||
259 | @end | 313 | @end |
260 | 314 | ||
261 | static void enableMouse_(iBool yes) { | 315 | static void enableMouse_(iBool yes) { |
@@ -483,6 +537,35 @@ void pickFile_iOS(const char *command) { | |||
483 | [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil]; | 537 | [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil]; |
484 | } | 538 | } |
485 | 539 | ||
540 | static void openActivityView_(NSArray *activityItems) { | ||
541 | UIActivityViewController *actView = | ||
542 | [[UIActivityViewController alloc] | ||
543 | initWithActivityItems:activityItems | ||
544 | applicationActivities:nil]; | ||
545 | iWindow *win = get_Window(); | ||
546 | UIViewController *viewCtl = viewController_(win); | ||
547 | UIPopoverPresentationController *popover = [actView popoverPresentationController]; | ||
548 | if (popover) { | ||
549 | [popover setSourceView:[viewCtl view]]; | ||
550 | iInt2 tapPos = latestTapPosition_Touch(); | ||
551 | tapPos.x /= win->pixelRatio; | ||
552 | tapPos.y /= win->pixelRatio; | ||
553 | [popover setSourceRect:(CGRect){{tapPos.x - 10, tapPos.y - 10}, {20, 20}}]; | ||
554 | [popover setCanOverlapSourceViewRect:YES]; | ||
555 | } | ||
556 | [viewCtl presentViewController:actView animated:YES completion:nil]; | ||
557 | } | ||
558 | |||
559 | void openTextActivityView_iOS(const iString *text) { | ||
560 | openActivityView_(@[[NSString stringWithUTF8String:cstr_String(text)]]); | ||
561 | } | ||
562 | |||
563 | void openFileActivityView_iOS(const iString *path) { | ||
564 | NSURL *url = [NSURL fileURLWithPath:[[NSString alloc] initWithCString:cstr_String(path) | ||
565 | encoding:NSUTF8StringEncoding]]; | ||
566 | openActivityView_(@[url]); | ||
567 | } | ||
568 | |||
486 | /*----------------------------------------------------------------------------------------------*/ | 569 | /*----------------------------------------------------------------------------------------------*/ |
487 | 570 | ||
488 | enum iAVFAudioPlayerState { | 571 | enum iAVFAudioPlayerState { |
@@ -511,6 +594,10 @@ void init_AVFAudioPlayer(iAVFAudioPlayer *d) { | |||
511 | 594 | ||
512 | void deinit_AVFAudioPlayer(iAVFAudioPlayer *d) { | 595 | void deinit_AVFAudioPlayer(iAVFAudioPlayer *d) { |
513 | setInput_AVFAudioPlayer(d, NULL, NULL); | 596 | setInput_AVFAudioPlayer(d, NULL, NULL); |
597 | if (d->player) { | ||
598 | CFBridgingRelease(d->player); | ||
599 | d->player = nil; | ||
600 | } | ||
514 | } | 601 | } |
515 | 602 | ||
516 | static const char *cacheDir_ = "~/Library/Caches/Audio"; | 603 | static const char *cacheDir_ = "~/Library/Caches/Audio"; |
@@ -607,3 +694,233 @@ iBool isStarted_AVFAudioPlayer(const iAVFAudioPlayer *d) { | |||
607 | iBool isPaused_AVFAudioPlayer(const iAVFAudioPlayer *d) { | 694 | iBool isPaused_AVFAudioPlayer(const iAVFAudioPlayer *d) { |
608 | return d->state == paused_AVFAudioPlayerState; | 695 | return d->state == paused_AVFAudioPlayerState; |
609 | } | 696 | } |
697 | |||
698 | /*----------------------------------------------------------------------------------------------*/ | ||
699 | |||
700 | struct Impl_SystemTextInput { | ||
701 | int flags; | ||
702 | void *field; /* single-line text field */ | ||
703 | void *view; /* multi-line text view */ | ||
704 | void (*textChangedFunc)(iSystemTextInput *, void *); | ||
705 | void *textChangedContext; | ||
706 | }; | ||
707 | |||
708 | iDefineTypeConstructionArgs(SystemTextInput, (iRect rect, int flags), rect, flags) | ||
709 | |||
710 | #define REF_d_field (__bridge UITextField *)d->field | ||
711 | #define REF_d_view (__bridge UITextView *)d->view | ||
712 | |||
713 | static CGRect convertToCGRect_(const iRect *rect, iBool expanded) { | ||
714 | const iWindow *win = get_Window(); | ||
715 | CGRect frame; | ||
716 | // TODO: Convert coordinates properly! | ||
717 | frame.origin.x = rect->pos.x / win->pixelRatio; | ||
718 | frame.origin.y = (rect->pos.y - gap_UI + 1) / win->pixelRatio; | ||
719 | frame.size.width = rect->size.x / win->pixelRatio; | ||
720 | frame.size.height = rect->size.y / win->pixelRatio; | ||
721 | /* Some padding to account for insets. If we just zero out the insets, the insertion point | ||
722 | may be clipped at the edges. */ | ||
723 | if (expanded) { | ||
724 | const float inset = gap_UI / get_Window()->pixelRatio; | ||
725 | frame.origin.x -= inset + 1; | ||
726 | frame.origin.y -= inset + 1; | ||
727 | frame.size.width += 2 * inset + 2; | ||
728 | frame.size.height += inset + 1 + inset; | ||
729 | } | ||
730 | return frame; | ||
731 | } | ||
732 | |||
733 | static UIColor *makeUIColor_(enum iColorId colorId) { | ||
734 | iColor color = get_Color(colorId); | ||
735 | return [UIColor colorWithRed:color.r / 255.0 | ||
736 | green:color.g / 255.0 | ||
737 | blue:color.b / 255.0 | ||
738 | alpha:color.a / 255.0]; | ||
739 | } | ||
740 | |||
741 | void init_SystemTextInput(iSystemTextInput *d, iRect rect, int flags) { | ||
742 | d->flags = flags; | ||
743 | d->field = NULL; | ||
744 | d->view = NULL; | ||
745 | CGRect frame = convertToCGRect_(&rect, (flags & multiLine_SystemTextInputFlags) != 0); | ||
746 | if (flags & multiLine_SystemTextInputFlags) { | ||
747 | d->view = (void *) CFBridgingRetain([[UITextView alloc] initWithFrame:frame textContainer:nil]); | ||
748 | [[viewController_(get_Window()) view] addSubview:REF_d_view]; | ||
749 | } | ||
750 | else { | ||
751 | d->field = (void *) CFBridgingRetain([[UITextField alloc] initWithFrame:frame]); | ||
752 | [[viewController_(get_Window()) view] addSubview:REF_d_field]; | ||
753 | } | ||
754 | UIControl<UITextInputTraits> *traits = (UIControl<UITextInputTraits> *) (d->view ? REF_d_view : REF_d_field); | ||
755 | if (~flags & insertNewlines_SystemTextInputFlag) { | ||
756 | [traits setReturnKeyType:UIReturnKeyDone]; | ||
757 | } | ||
758 | if (flags & returnGo_SystemTextInputFlags) { | ||
759 | [traits setReturnKeyType:UIReturnKeyGo]; | ||
760 | } | ||
761 | if (flags & returnSend_SystemTextInputFlags) { | ||
762 | [traits setReturnKeyType:UIReturnKeySend]; | ||
763 | } | ||
764 | if (flags & disableAutocorrect_SystemTextInputFlag) { | ||
765 | [traits setAutocorrectionType:UITextAutocorrectionTypeNo]; | ||
766 | [traits setSpellCheckingType:UITextSpellCheckingTypeNo]; | ||
767 | } | ||
768 | if (flags & disableAutocapitalize_SystemTextInputFlag) { | ||
769 | [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone]; | ||
770 | } | ||
771 | if (flags & alignRight_SystemTextInputFlag) { | ||
772 | if (d->field) { | ||
773 | [REF_d_field setTextAlignment:NSTextAlignmentRight]; | ||
774 | } | ||
775 | if (d->view) { | ||
776 | [REF_d_view setTextAlignment:NSTextAlignmentRight]; | ||
777 | } | ||
778 | } | ||
779 | UIColor *textColor = makeUIColor_(uiInputTextFocused_ColorId); | ||
780 | UIColor *backgroundColor = makeUIColor_(uiInputBackgroundFocused_ColorId); | ||
781 | UIColor *tintColor = makeUIColor_(uiInputFrameHover_ColorId); /* use the accent color */ //uiInputCursor_ColorId); | ||
782 | [appState_ setSystemTextInput:d]; | ||
783 | if (d->field) { | ||
784 | UITextField *field = REF_d_field; | ||
785 | [field setTextColor:textColor]; | ||
786 | [field setTintColor:tintColor]; | ||
787 | [field setDelegate:appState_]; | ||
788 | [field becomeFirstResponder]; | ||
789 | } | ||
790 | else { | ||
791 | UITextView *view = REF_d_view; | ||
792 | [view setBackgroundColor:[UIColor colorWithWhite:1.0f alpha:0.0f]]; | ||
793 | [view setTextColor:textColor]; | ||
794 | [view setTintColor:tintColor]; | ||
795 | if (flags & extraPadding_SystemTextInputFlag) { | ||
796 | [view setContentInset:(UIEdgeInsets){ 0, 0, 3 * gap_UI / get_Window()->pixelRatio, 0}]; | ||
797 | } | ||
798 | [view setEditable:YES]; | ||
799 | [view setDelegate:appState_]; | ||
800 | [view becomeFirstResponder]; | ||
801 | } | ||
802 | d->textChangedFunc = NULL; | ||
803 | d->textChangedContext = NULL; | ||
804 | } | ||
805 | |||
806 | void deinit_SystemTextInput(iSystemTextInput *d) { | ||
807 | [appState_ setSystemTextInput:nil]; | ||
808 | if (d->field) { | ||
809 | [REF_d_field removeFromSuperview]; | ||
810 | CFBridgingRelease(d->field); | ||
811 | d->field = nil; | ||
812 | } | ||
813 | if (d->view) { | ||
814 | [REF_d_view removeFromSuperview]; | ||
815 | CFBridgingRelease(d->view); | ||
816 | d->view = nil; | ||
817 | } | ||
818 | } | ||
819 | |||
820 | void selectAll_SystemTextInput(iSystemTextInput *d) { | ||
821 | if (d->field) { | ||
822 | [REF_d_field selectAll:nil]; | ||
823 | } | ||
824 | if (d->view) { | ||
825 | [REF_d_view selectAll:nil]; | ||
826 | } | ||
827 | } | ||
828 | |||
829 | void setText_SystemTextInput(iSystemTextInput *d, const iString *text, iBool allowUndo) { | ||
830 | NSString *str = [NSString stringWithUTF8String:cstr_String(text)]; | ||
831 | if (d->field) { | ||
832 | [REF_d_field setText:str]; | ||
833 | if (d->flags & selectAll_SystemTextInputFlags) { | ||
834 | [REF_d_field selectAll:nil]; | ||
835 | } | ||
836 | } | ||
837 | else { | ||
838 | UITextView *view = REF_d_view; | ||
839 | // if (allowUndo) { | ||
840 | // [view selectAll:nil]; | ||
841 | // if ([view shouldChangeTextInRange:[view selectedTextRange] replacementText:@""]) { | ||
842 | // [[view textStorage] beginEditing]; | ||
843 | // [[view textStorage] replaceCharactersInRange:[view selectedRange] withString:@""]; | ||
844 | // [[view textStorage] endEditing]; | ||
845 | // } | ||
846 | // } | ||
847 | // else { | ||
848 | // TODO: How to implement `allowUndo`, given that UITextView does not exist when unfocused? | ||
849 | // Maybe keep the UITextStorage (if it has the undo?)? | ||
850 | [view setText:str]; | ||
851 | // } | ||
852 | if (d->flags & selectAll_SystemTextInputFlags) { | ||
853 | [view selectAll:nil]; | ||
854 | } | ||
855 | } | ||
856 | } | ||
857 | |||
858 | int preferredHeight_SystemTextInput(const iSystemTextInput *d) { | ||
859 | if (d->view) { | ||
860 | CGRect usedRect = [[REF_d_view layoutManager] usedRectForTextContainer:[REF_d_view textContainer]]; | ||
861 | return usedRect.size.height * get_Window()->pixelRatio; | ||
862 | } | ||
863 | return 0; | ||
864 | } | ||
865 | |||
866 | void setFont_SystemTextInput(iSystemTextInput *d, int fontId) { | ||
867 | float height = lineHeight_Text(fontId) / get_Window()->pixelRatio; | ||
868 | UIFont *font; | ||
869 | // for (NSString *name in [UIFont familyNames]) { | ||
870 | // printf("family: %s\n", [name cStringUsingEncoding:NSUTF8StringEncoding]); | ||
871 | // } | ||
872 | if (fontId / maxVariants_Fonts * maxVariants_Fonts == monospace_FontId) { | ||
873 | // font = [UIFont monospacedSystemFontOfSize:0.8f * height weight:UIFontWeightRegular]; | ||
874 | // for (NSString *name in [UIFont fontNamesForFamilyName:@"Iosevka Term"]) { | ||
875 | // printf("fontname: %s\n", [name cStringUsingEncoding:NSUTF8StringEncoding]); | ||
876 | // } | ||
877 | font = [UIFont fontWithName:@"Iosevka-Term-Extended" size:height * 0.82f]; | ||
878 | } | ||
879 | else { | ||
880 | // font = [UIFont systemFontOfSize:0.65f * height]; | ||
881 | font = [UIFont fontWithName:@"SourceSans3-Regular" size:height * 0.7f]; | ||
882 | } | ||
883 | if (d->field) { | ||
884 | [REF_d_field setFont:font]; | ||
885 | } | ||
886 | if (d->view) { | ||
887 | [REF_d_view setFont:font]; | ||
888 | } | ||
889 | } | ||
890 | |||
891 | const iString *text_SystemTextInput(const iSystemTextInput *d) { | ||
892 | if (d->field) { | ||
893 | return collectNewCStr_String([[REF_d_field text] cStringUsingEncoding:NSUTF8StringEncoding]); | ||
894 | } | ||
895 | if (d->view) { | ||
896 | return collectNewCStr_String([[REF_d_view text] cStringUsingEncoding:NSUTF8StringEncoding]); | ||
897 | } | ||
898 | return NULL; | ||
899 | } | ||
900 | |||
901 | void setRect_SystemTextInput(iSystemTextInput *d, iRect rect) { | ||
902 | CGRect frame = convertToCGRect_(&rect, (d->flags & multiLine_SystemTextInputFlags) != 0); | ||
903 | if (d->field) { | ||
904 | [REF_d_field setFrame:frame]; | ||
905 | } | ||
906 | else { | ||
907 | [REF_d_view setFrame:frame]; | ||
908 | } | ||
909 | } | ||
910 | |||
911 | void setTextChangedFunc_SystemTextInput(iSystemTextInput *d, | ||
912 | void (*textChangedFunc)(iSystemTextInput *, void *), | ||
913 | void *context) { | ||
914 | d->textChangedFunc = textChangedFunc; | ||
915 | d->textChangedContext = context; | ||
916 | } | ||
917 | |||
918 | static void notifyChange_SystemTextInput_(iSystemTextInput *d) { | ||
919 | if (d && d->textChangedFunc) { | ||
920 | d->textChangedFunc(d, d->textChangedContext); | ||
921 | } | ||
922 | } | ||
923 | |||
924 | static BOOL isNewlineAllowed_SystemTextInput_(const iSystemTextInput *d) { | ||
925 | return (d->flags & insertNewlines_SystemTextInputFlag) != 0; | ||
926 | } | ||
diff --git a/src/macos.h b/src/macos.h index 22a6dfff..10cbba81 100644 --- a/src/macos.h +++ b/src/macos.h | |||
@@ -39,8 +39,10 @@ void hideTitleBar_MacOS (iWindow *window); | |||
39 | void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count); | 39 | void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count); |
40 | void removeMenu_MacOS (int atIndex); | 40 | void removeMenu_MacOS (int atIndex); |
41 | void enableMenu_MacOS (const char *menuLabel, iBool enable); | 41 | void enableMenu_MacOS (const char *menuLabel, iBool enable); |
42 | void enableMenuIndex_MacOS (int index, iBool enable); | ||
42 | void enableMenuItem_MacOS (const char *menuItemCommand, iBool enable); | 43 | void enableMenuItem_MacOS (const char *menuItemCommand, iBool enable); |
43 | void enableMenuItemsByKey_MacOS (int key, int kmods, iBool enable); | 44 | void enableMenuItemsByKey_MacOS (int key, int kmods, iBool enable); |
45 | void enableMenuItemsOnHomeRow_MacOS(iBool enable); | ||
44 | void handleCommand_MacOS (const char *cmd); | 46 | void handleCommand_MacOS (const char *cmd); |
45 | 47 | ||
46 | void showPopupMenu_MacOS (iWidget *source, iInt2 windowCoord, const iMenuItem *items, size_t n); | 48 | void showPopupMenu_MacOS (iWidget *source, iInt2 windowCoord, const iMenuItem *items, size_t n); |
diff --git a/src/macos.m b/src/macos.m index cfbca488..4ad267c1 100644 --- a/src/macos.m +++ b/src/macos.m | |||
@@ -31,6 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
31 | 31 | ||
32 | #include <SDL_timer.h> | 32 | #include <SDL_timer.h> |
33 | #include <SDL_syswm.h> | 33 | #include <SDL_syswm.h> |
34 | #include <the_Foundation/stringset.h> | ||
34 | 35 | ||
35 | #import <AppKit/AppKit.h> | 36 | #import <AppKit/AppKit.h> |
36 | 37 | ||
@@ -110,7 +111,7 @@ static void ignoreImmediateKeyDownEvents_(void) { | |||
110 | - (id)initWithIdentifier:(NSTouchBarItemIdentifier)identifier | 111 | - (id)initWithIdentifier:(NSTouchBarItemIdentifier)identifier |
111 | title:(NSString *)title | 112 | title:(NSString *)title |
112 | command:(NSString *)cmd { | 113 | command:(NSString *)cmd { |
113 | [super initWithIdentifier:identifier]; | 114 | self = [super initWithIdentifier:identifier]; |
114 | self.view = [NSButton buttonWithTitle:title target:self action:@selector(buttonPressed)]; | 115 | self.view = [NSButton buttonWithTitle:title target:self action:@selector(buttonPressed)]; |
115 | command = cmd; | 116 | command = cmd; |
116 | return self; | 117 | return self; |
@@ -120,7 +121,7 @@ static void ignoreImmediateKeyDownEvents_(void) { | |||
120 | image:(NSImage *)image | 121 | image:(NSImage *)image |
121 | widget:(iWidget *)widget | 122 | widget:(iWidget *)widget |
122 | command:(NSString *)cmd { | 123 | command:(NSString *)cmd { |
123 | [super initWithIdentifier:identifier]; | 124 | self = [super initWithIdentifier:identifier]; |
124 | self.view = [NSButton buttonWithImage:image target:self action:@selector(buttonPressed)]; | 125 | self.view = [NSButton buttonWithImage:image target:self action:@selector(buttonPressed)]; |
125 | command = cmd; | 126 | command = cmd; |
126 | return self; | 127 | return self; |
@@ -163,12 +164,13 @@ static void ignoreImmediateKeyDownEvents_(void) { | |||
163 | @implementation MenuCommands | 164 | @implementation MenuCommands |
164 | 165 | ||
165 | - (id)init { | 166 | - (id)init { |
167 | self = [super init]; | ||
166 | commands = [[NSMutableDictionary<NSString *, NSString *> alloc] init]; | 168 | commands = [[NSMutableDictionary<NSString *, NSString *> alloc] init]; |
167 | source = NULL; | 169 | source = NULL; |
168 | return self; | 170 | return self; |
169 | } | 171 | } |
170 | 172 | ||
171 | - (void)setCommand:(NSString *)command forMenuItem:(NSMenuItem *)menuItem { | 173 | - (void)setCommand:(NSString * __nonnull)command forMenuItem:(NSMenuItem * __nonnull)menuItem { |
172 | [commands setObject:command forKey:[menuItem title]]; | 174 | [commands setObject:command forKey:[menuItem title]]; |
173 | } | 175 | } |
174 | 176 | ||
@@ -220,7 +222,7 @@ static void ignoreImmediateKeyDownEvents_(void) { | |||
220 | @implementation MyDelegate | 222 | @implementation MyDelegate |
221 | 223 | ||
222 | - (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl { | 224 | - (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl { |
223 | [super init]; | 225 | self = [super init]; |
224 | currentAppearanceName = nil; | 226 | currentAppearanceName = nil; |
225 | menuCommands = [[MenuCommands alloc] init]; | 227 | menuCommands = [[MenuCommands alloc] init]; |
226 | touchBarVariant = default_TouchBarVariant; | 228 | touchBarVariant = default_TouchBarVariant; |
@@ -402,6 +404,131 @@ void registerURLHandler_MacOS(void) { | |||
402 | [handler release]; | 404 | [handler release]; |
403 | } | 405 | } |
404 | 406 | ||
407 | #if 0 | ||
408 | static iBool isTracking_; | ||
409 | |||
410 | static void trackSwipe_(NSEvent *event) { | ||
411 | if (isTracking_) { | ||
412 | return; | ||
413 | } | ||
414 | isTracking_ = iTrue; | ||
415 | [event trackSwipeEventWithOptions:NSEventSwipeTrackingLockDirection | ||
416 | dampenAmountThresholdMin:-1.0 | ||
417 | max:1.0 | ||
418 | usingHandler:^(CGFloat gestureAmount, NSEventPhase phase, | ||
419 | BOOL isComplete, BOOL *stop) { | ||
420 | printf("TRACK: amount:%f phase:%lu complete:%d\n", | ||
421 | gestureAmount, (unsigned long) phase, isComplete); | ||
422 | fflush(stdout); | ||
423 | if (isComplete) { | ||
424 | isTracking_ = iFalse; | ||
425 | } | ||
426 | } | ||
427 | ]; | ||
428 | } | ||
429 | #endif | ||
430 | |||
431 | static int swipeDir_ = 0; | ||
432 | static int preventTapGlitch_ = 0; | ||
433 | |||
434 | static iBool processScrollWheelEvent_(NSEvent *event) { | ||
435 | const iBool isPerPixel = (event.hasPreciseScrollingDeltas != 0); | ||
436 | const iBool isInertia = (event.momentumPhase & (NSEventPhaseBegan | NSEventPhaseChanged)) != 0; | ||
437 | const iBool isEnded = event.scrollingDeltaX == 0.0f && event.scrollingDeltaY == 0.0f && !isInertia; | ||
438 | const iWindow *win = &get_MainWindow()->base; | ||
439 | if (isPerPixel) { | ||
440 | /* On macOS 12.1, stopping ongoing inertia scroll with a tap seems to sometimes produce | ||
441 | spurious large scroll events. */ | ||
442 | switch (preventTapGlitch_) { | ||
443 | case 0: | ||
444 | if (isInertia && event.momentumPhase == NSEventPhaseChanged) { | ||
445 | preventTapGlitch_++; | ||
446 | } | ||
447 | else { | ||
448 | preventTapGlitch_ = 0; | ||
449 | } | ||
450 | break; | ||
451 | case 1: | ||
452 | if (event.scrollingDeltaY == 0 && event.momentumPhase == NSEventPhaseEnded) { | ||
453 | preventTapGlitch_++; | ||
454 | } | ||
455 | break; | ||
456 | case 2: | ||
457 | if (event.scrollingDeltaY == 0 && event.momentumPhase == 0 && isEnded) { | ||
458 | preventTapGlitch_++; | ||
459 | } | ||
460 | else { | ||
461 | preventTapGlitch_ = 0; | ||
462 | } | ||
463 | break; | ||
464 | case 3: | ||
465 | if (event.scrollingDeltaY != 0 && event.momentumPhase == 0 && !isInertia) { | ||
466 | preventTapGlitch_ = 0; | ||
467 | // printf("SPURIOUS\n"); fflush(stdout); | ||
468 | return iTrue; | ||
469 | } | ||
470 | preventTapGlitch_ = 0; | ||
471 | break; | ||
472 | } | ||
473 | } | ||
474 | /* Post corresponding MOUSEWHEEL events. */ | ||
475 | SDL_MouseWheelEvent e = { .type = SDL_MOUSEWHEEL }; | ||
476 | e.timestamp = SDL_GetTicks(); | ||
477 | e.which = isPerPixel ? 0 : 1; /* Distinction between trackpad and regular mouse. TODO: Still needed? */ | ||
478 | setPerPixel_MouseWheelEvent(&e, isPerPixel); | ||
479 | if (isPerPixel) { | ||
480 | setInertia_MouseWheelEvent(&e, isInertia); | ||
481 | setScrollFinished_MouseWheelEvent(&e, isEnded); | ||
482 | e.x = event.scrollingDeltaX * win->pixelRatio; | ||
483 | e.y = event.scrollingDeltaY * win->pixelRatio; | ||
484 | /* Only scroll on one axis at a time. */ | ||
485 | if (swipeDir_ == 0) { | ||
486 | swipeDir_ = iAbs(e.x) > iAbs(e.y) ? 1 : 2; | ||
487 | } | ||
488 | if (swipeDir_ == 1) { | ||
489 | e.y = 0; | ||
490 | } | ||
491 | else if (swipeDir_ == 2) { | ||
492 | e.x = 0; | ||
493 | } | ||
494 | if (isEnded) { | ||
495 | swipeDir_ = 0; | ||
496 | } | ||
497 | } | ||
498 | else { | ||
499 | /* Disregard wheel acceleration applied by the OS. */ | ||
500 | e.x = -event.scrollingDeltaX; | ||
501 | e.y = iSign(event.scrollingDeltaY); | ||
502 | } | ||
503 | // printf("#### [%d] dx:%d dy:%d phase:%ld inertia:%d end:%d\n", preventTapGlitch_, e.x, e.y, (long) event.momentumPhase, | ||
504 | // isInertia, isEnded); fflush(stdout); | ||
505 | SDL_PushEvent((SDL_Event *) &e); | ||
506 | #if 0 | ||
507 | /* On macOS, we handle both trackpad and mouse events. We expect SDL to identify | ||
508 | which device is sending the event. */ | ||
509 | if (ev.wheel.which == 0) { | ||
510 | /* Trackpad with precise scrolling w/inertia (points). */ | ||
511 | setPerPixel_MouseWheelEvent(&ev.wheel, iTrue); | ||
512 | ev.wheel.x *= -d->window->base.pixelRatio; | ||
513 | ev.wheel.y *= d->window->base.pixelRatio; | ||
514 | /* Only scroll on one axis at a time. */ | ||
515 | if (iAbs(ev.wheel.x) > iAbs(ev.wheel.y)) { | ||
516 | ev.wheel.y = 0; | ||
517 | } | ||
518 | else { | ||
519 | ev.wheel.x = 0; | ||
520 | } | ||
521 | } | ||
522 | else { | ||
523 | /* Disregard wheel acceleration applied by the OS. */ | ||
524 | ev.wheel.x = -ev.wheel.x; | ||
525 | ev.wheel.y = iSign(ev.wheel.y); | ||
526 | } | ||
527 | #endif | ||
528 | |||
529 | return iTrue; | ||
530 | } | ||
531 | |||
405 | void setupApplication_MacOS(void) { | 532 | void setupApplication_MacOS(void) { |
406 | NSApplication *app = [NSApplication sharedApplication]; | 533 | NSApplication *app = [NSApplication sharedApplication]; |
407 | [app setActivationPolicy:NSApplicationActivationPolicyRegular]; | 534 | [app setActivationPolicy:NSApplicationActivationPolicyRegular]; |
@@ -423,6 +550,21 @@ void setupApplication_MacOS(void) { | |||
423 | NSMenuItem *windowCloseItem = [windowMenu itemWithTitle:@"Close"]; | 550 | NSMenuItem *windowCloseItem = [windowMenu itemWithTitle:@"Close"]; |
424 | windowCloseItem.target = myDel; | 551 | windowCloseItem.target = myDel; |
425 | windowCloseItem.action = @selector(closeTab); | 552 | windowCloseItem.action = @selector(closeTab); |
553 | [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskScrollWheel | ||
554 | handler:^NSEvent*(NSEvent *event){ | ||
555 | // printf("event type: %lu\n", (unsigned long) event.type); | ||
556 | // fflush(stdout); | ||
557 | // if (event.type == NSEventTypeGesture) { | ||
558 | // trackSwipe_(event); | ||
559 | // printf("GESTURE phase:%lu\n", (unsigned long) event.phase); | ||
560 | //fflush(stdout); | ||
561 | // } | ||
562 | if (event.type == NSEventTypeScrollWheel && | ||
563 | processScrollWheelEvent_(event)) { | ||
564 | return nil; /* was eaten */ | ||
565 | } | ||
566 | return event; | ||
567 | }]; | ||
426 | } | 568 | } |
427 | 569 | ||
428 | void hideTitleBar_MacOS(iWindow *window) { | 570 | void hideTitleBar_MacOS(iWindow *window) { |
@@ -439,6 +581,13 @@ void enableMenu_MacOS(const char *menuLabel, iBool enable) { | |||
439 | [menuItem setEnabled:enable]; | 581 | [menuItem setEnabled:enable]; |
440 | } | 582 | } |
441 | 583 | ||
584 | void enableMenuIndex_MacOS(int index, iBool enable) { | ||
585 | NSApplication *app = [NSApplication sharedApplication]; | ||
586 | NSMenu *appMenu = [app mainMenu]; | ||
587 | NSMenuItem *menuItem = [appMenu itemAtIndex:index]; | ||
588 | [menuItem setEnabled:enable]; | ||
589 | } | ||
590 | |||
442 | void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) { | 591 | void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) { |
443 | NSApplication *app = [NSApplication sharedApplication]; | 592 | NSApplication *app = [NSApplication sharedApplication]; |
444 | NSMenu *appMenu = [app mainMenu]; | 593 | NSMenu *appMenu = [app mainMenu]; |
@@ -513,6 +662,47 @@ void enableMenuItemsByKey_MacOS(int key, int kmods, iBool enable) { | |||
513 | delete_String(keyEquiv); | 662 | delete_String(keyEquiv); |
514 | } | 663 | } |
515 | 664 | ||
665 | void enableMenuItemsOnHomeRow_MacOS(iBool enable) { | ||
666 | iStringSet *homeRowKeys = new_StringSet(); | ||
667 | const char *keys[] = { /* Note: another array in documentwidget.c */ | ||
668 | "f", "d", "s", "a", | ||
669 | "j", "k", "l", | ||
670 | "r", "e", "w", "q", | ||
671 | "u", "i", "o", "p", | ||
672 | "v", "c", "x", "z", | ||
673 | "m", "n", | ||
674 | "g", "h", | ||
675 | "b", | ||
676 | "t", "y" | ||
677 | }; | ||
678 | iForIndices(i, keys) { | ||
679 | iString str; | ||
680 | initCStr_String(&str, keys[i]); | ||
681 | insert_StringSet(homeRowKeys, &str); | ||
682 | deinit_String(&str); | ||
683 | } | ||
684 | NSApplication *app = [NSApplication sharedApplication]; | ||
685 | NSMenu *appMenu = [app mainMenu]; | ||
686 | for (NSMenuItem *mainMenuItem in appMenu.itemArray) { | ||
687 | NSMenu *menu = mainMenuItem.submenu; | ||
688 | if (menu) { | ||
689 | for (NSMenuItem *menuItem in menu.itemArray) { | ||
690 | if (menuItem.keyEquivalentModifierMask == 0) { | ||
691 | iString equiv; | ||
692 | initCStr_String(&equiv, [menuItem.keyEquivalent | ||
693 | cStringUsingEncoding:NSUTF8StringEncoding]); | ||
694 | if (contains_StringSet(homeRowKeys, &equiv)) { | ||
695 | [menuItem setEnabled:enable]; | ||
696 | [menu setAutoenablesItems:NO]; | ||
697 | } | ||
698 | deinit_String(&equiv); | ||
699 | } | ||
700 | } | ||
701 | } | ||
702 | } | ||
703 | iRelease(homeRowKeys); | ||
704 | } | ||
705 | |||
516 | static void setShortcut_NSMenuItem_(NSMenuItem *item, int key, int kmods) { | 706 | static void setShortcut_NSMenuItem_(NSMenuItem *item, int key, int kmods) { |
517 | NSEventModifierFlags modMask; | 707 | NSEventModifierFlags modMask; |
518 | iString *str = composeKeyEquivalent_(key, kmods, &modMask); | 708 | iString *str = composeKeyEquivalent_(key, kmods, &modMask); |
@@ -541,6 +731,29 @@ enum iColorId removeColorEscapes_String(iString *d) { | |||
541 | return color; | 731 | return color; |
542 | } | 732 | } |
543 | 733 | ||
734 | static NSString *cleanString_(const iString *ansiEscapedText) { | ||
735 | iString mod; | ||
736 | initCopy_String(&mod, ansiEscapedText); | ||
737 | iRegExp *ansi = makeAnsiEscapePattern_Text(); | ||
738 | replaceRegExp_String(&mod, ansi, "", NULL, NULL); | ||
739 | iRelease(ansi); | ||
740 | NSString *clean = [NSString stringWithUTF8String:cstr_String(&mod)]; | ||
741 | deinit_String(&mod); | ||
742 | return clean; | ||
743 | } | ||
744 | |||
745 | #if 0 | ||
746 | static NSAttributedString *makeAttributedString_(const iString *ansiEscapedText) { | ||
747 | iString mod; | ||
748 | initCopy_String(&mod, ansiEscapedText); | ||
749 | NSData *data = [NSData dataWithBytesNoCopy:data_Block(&mod.chars) length:size_String(&mod)]; | ||
750 | NSAttributedString *as = [[NSAttributedString alloc] initWithHTML:data | ||
751 | documentAttributes:nil]; | ||
752 | deinit_String(&mod); | ||
753 | return as; | ||
754 | } | ||
755 | #endif | ||
756 | |||
544 | /* returns the selected item, if any */ | 757 | /* returns the selected item, if any */ |
545 | static NSMenuItem *makeMenuItems_(NSMenu *menu, MenuCommands *commands, const iMenuItem *items, size_t n) { | 758 | static NSMenuItem *makeMenuItems_(NSMenu *menu, MenuCommands *commands, const iMenuItem *items, size_t n) { |
546 | NSMenuItem *selectedItem = nil; | 759 | NSMenuItem *selectedItem = nil; |
@@ -557,7 +770,7 @@ static NSMenuItem *makeMenuItems_(NSMenu *menu, MenuCommands *commands, const iM | |||
557 | isChecked = iTrue; | 770 | isChecked = iTrue; |
558 | label += 3; | 771 | label += 3; |
559 | } | 772 | } |
560 | else if (startsWith_CStr(label, "///")) { | 773 | else if (startsWith_CStr(label, "///") || startsWith_CStr(label, "```")) { |
561 | isDisabled = iTrue; | 774 | isDisabled = iTrue; |
562 | label += 3; | 775 | label += 3; |
563 | } | 776 | } |
@@ -567,9 +780,13 @@ static NSMenuItem *makeMenuItems_(NSMenu *menu, MenuCommands *commands, const iM | |||
567 | if (removeColorEscapes_String(&itemTitle) == uiTextCaution_ColorId) { | 780 | if (removeColorEscapes_String(&itemTitle) == uiTextCaution_ColorId) { |
568 | // prependCStr_String(&itemTitle, "\u26a0\ufe0f "); | 781 | // prependCStr_String(&itemTitle, "\u26a0\ufe0f "); |
569 | } | 782 | } |
570 | NSMenuItem *item = [menu addItemWithTitle:[NSString stringWithUTF8String:cstr_String(&itemTitle)] | 783 | NSMenuItem *item = [[NSMenuItem alloc] init]; |
571 | action:(hasCommand ? @selector(postMenuItemCommand:) : nil) | 784 | /* Use attributed string to allow newlines. */ |
572 | keyEquivalent:@""]; | 785 | NSAttributedString *title = [[NSAttributedString alloc] initWithString:cleanString_(&itemTitle)]; |
786 | item.attributedTitle = title; | ||
787 | [title release]; | ||
788 | item.action = (hasCommand ? @selector(postMenuItemCommand:) : nil); | ||
789 | [menu addItem:item]; | ||
573 | deinit_String(&itemTitle); | 790 | deinit_String(&itemTitle); |
574 | [item setTarget:commands]; | 791 | [item setTarget:commands]; |
575 | if (isChecked) { | 792 | if (isChecked) { |
@@ -67,6 +67,7 @@ int main(int argc, char **argv) { | |||
67 | "ECDHE-RSA-AES128-GCM-SHA256:" | 67 | "ECDHE-RSA-AES128-GCM-SHA256:" |
68 | "DHE-RSA-AES256-GCM-SHA384"); | 68 | "DHE-RSA-AES256-GCM-SHA384"); |
69 | SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); | 69 | SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); |
70 | SDL_EnableScreenSaver(); | ||
70 | SDL_SetHint(SDL_HINT_MAC_BACKGROUND_APP, "1"); | 71 | SDL_SetHint(SDL_HINT_MAC_BACKGROUND_APP, "1"); |
71 | SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1"); | 72 | SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1"); |
72 | #if SDL_VERSION_ATLEAST(2, 0, 8) | 73 | #if SDL_VERSION_ATLEAST(2, 0, 8) |
@@ -82,9 +83,6 @@ int main(int argc, char **argv) { | |||
82 | fprintf(stderr, "[SDL] init failed: %s\n", SDL_GetError()); | 83 | fprintf(stderr, "[SDL] init failed: %s\n", SDL_GetError()); |
83 | return -1; | 84 | return -1; |
84 | } | 85 | } |
85 | if (SDL_Init(SDL_INIT_AUDIO)) { | ||
86 | fprintf(stderr, "[SDL] audio init failed: %s\n", SDL_GetError()); | ||
87 | } | ||
88 | init_Updater(); | 86 | init_Updater(); |
89 | run_App(argc, argv); | 87 | run_App(argc, argv); |
90 | SDL_Quit(); | 88 | SDL_Quit(); |
diff --git a/src/media.c b/src/media.c index a3f381ec..4940c13e 100644 --- a/src/media.c +++ b/src/media.c | |||
@@ -144,7 +144,7 @@ void makeTexture_GmImage(iGmImage *d) { | |||
144 | } | 144 | } |
145 | else { | 145 | else { |
146 | imgData = stbi_load_from_memory( | 146 | imgData = stbi_load_from_memory( |
147 | constData_Block(data), size_Block(data), &d->size.x, &d->size.y, NULL, 4); | 147 | constData_Block(data), (int) size_Block(data), &d->size.x, &d->size.y, NULL, 4); |
148 | if (!imgData) { | 148 | if (!imgData) { |
149 | fprintf(stderr, "[media] image load failed: %s\n", stbi_failure_reason()); | 149 | fprintf(stderr, "[media] image load failed: %s\n", stbi_failure_reason()); |
150 | } | 150 | } |
@@ -629,6 +629,17 @@ void deinit_MediaRequest(iMediaRequest *d) { | |||
629 | iRelease(d->req); | 629 | iRelease(d->req); |
630 | } | 630 | } |
631 | 631 | ||
632 | iMediaRequest *newReused_MediaRequest(iDocumentWidget *doc, unsigned int linkId, | ||
633 | iGmRequest *request) { | ||
634 | iMediaRequest *d = new_Object(&Class_MediaRequest); | ||
635 | d->doc = doc; | ||
636 | d->linkId = linkId; | ||
637 | d->req = request; /* takes ownership */ | ||
638 | iConnect(GmRequest, d->req, updated, d, updated_MediaRequest_); | ||
639 | iConnect(GmRequest, d->req, finished, d, finished_MediaRequest_); | ||
640 | return d; | ||
641 | } | ||
642 | |||
632 | iDefineObjectConstructionArgs(MediaRequest, | 643 | iDefineObjectConstructionArgs(MediaRequest, |
633 | (iDocumentWidget *doc, unsigned int linkId, const iString *url, | 644 | (iDocumentWidget *doc, unsigned int linkId, const iString *url, |
634 | iBool enableFilters), | 645 | iBool enableFilters), |
diff --git a/src/media.h b/src/media.h index 3b329716..584c77eb 100644 --- a/src/media.h +++ b/src/media.h | |||
@@ -123,3 +123,6 @@ struct Impl_MediaRequest { | |||
123 | 123 | ||
124 | iDeclareObjectConstructionArgs(MediaRequest, iDocumentWidget *doc, unsigned int linkId, | 124 | iDeclareObjectConstructionArgs(MediaRequest, iDocumentWidget *doc, unsigned int linkId, |
125 | const iString *url, iBool enableFilters) | 125 | const iString *url, iBool enableFilters) |
126 | |||
127 | iMediaRequest * newReused_MediaRequest (iDocumentWidget *doc, unsigned int linkId, | ||
128 | iGmRequest *request); | ||
diff --git a/src/mimehooks.c b/src/mimehooks.c index c097bd1f..eb379106 100644 --- a/src/mimehooks.c +++ b/src/mimehooks.c | |||
@@ -142,7 +142,8 @@ static iBlock *translateAtomXmlToGeminiFeed_(const iString *mime, const iBlock * | |||
142 | appendCStr_String(&out, cstr_Lang("feeds.atom.translated")); | 142 | appendCStr_String(&out, cstr_Lang("feeds.atom.translated")); |
143 | appendCStr_String(&out, "\n\n"); | 143 | appendCStr_String(&out, "\n\n"); |
144 | iRegExp *datePattern = | 144 | iRegExp *datePattern = |
145 | iClob(new_RegExp("^([0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9])(T|\\s).*", caseSensitive_RegExpOption)); | 145 | iClob(new_RegExp("^\\s*([0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9])(T|\\s).*", |
146 | caseSensitive_RegExpOption)); | ||
146 | iBeginCollect(); | 147 | iBeginCollect(); |
147 | iConstForEach(PtrArray, i, &feed->children) { | 148 | iConstForEach(PtrArray, i, &feed->children) { |
148 | iEndCollect(); | 149 | iEndCollect(); |
diff --git a/src/periodic.c b/src/periodic.c index ef3d8033..b4f51ed3 100644 --- a/src/periodic.c +++ b/src/periodic.c | |||
@@ -57,6 +57,25 @@ iDefineTypeConstructionArgs(PeriodicCommand, (iAny *ctx, const char *cmd), ctx, | |||
57 | 57 | ||
58 | static const uint32_t postingInterval_Periodic_ = 500; | 58 | static const uint32_t postingInterval_Periodic_ = 500; |
59 | 59 | ||
60 | static uint32_t postEvent_Periodic_(uint32_t interval, void *context) { | ||
61 | iUnused(context); | ||
62 | SDL_UserEvent ev = { .type = SDL_USEREVENT, | ||
63 | .timestamp = SDL_GetTicks(), | ||
64 | .code = periodic_UserEventCode }; | ||
65 | SDL_PushEvent((SDL_Event *) &ev); | ||
66 | return interval; | ||
67 | } | ||
68 | |||
69 | static void startOrStopWakeupTimer_Periodic_(iPeriodic *d, iBool start) { | ||
70 | if (start && !d->wakeupTimer) { | ||
71 | d->wakeupTimer = SDL_AddTimer(postingInterval_Periodic_, postEvent_Periodic_, d); | ||
72 | } | ||
73 | else if (!start && d->wakeupTimer) { | ||
74 | SDL_RemoveTimer(d->wakeupTimer); | ||
75 | d->wakeupTimer = 0; | ||
76 | } | ||
77 | } | ||
78 | |||
60 | static void removePending_Periodic_(iPeriodic *d) { | 79 | static void removePending_Periodic_(iPeriodic *d) { |
61 | iForEach(PtrSet, i, &d->pendingRemoval) { | 80 | iForEach(PtrSet, i, &d->pendingRemoval) { |
62 | size_t pos; | 81 | size_t pos; |
@@ -68,6 +87,9 @@ static void removePending_Periodic_(iPeriodic *d) { | |||
68 | } | 87 | } |
69 | } | 88 | } |
70 | clear_PtrSet(&d->pendingRemoval); | 89 | clear_PtrSet(&d->pendingRemoval); |
90 | if (isEmpty_SortedArray(&d->commands)) { | ||
91 | startOrStopWakeupTimer_Periodic_(d, iFalse); | ||
92 | } | ||
71 | } | 93 | } |
72 | 94 | ||
73 | static iBool isDispatching_; | 95 | static iBool isDispatching_; |
@@ -109,9 +131,11 @@ void init_Periodic(iPeriodic *d) { | |||
109 | init_SortedArray(&d->commands, sizeof(iPeriodicCommand), cmp_PeriodicCommand_); | 131 | init_SortedArray(&d->commands, sizeof(iPeriodicCommand), cmp_PeriodicCommand_); |
110 | d->lastPostTime = 0; | 132 | d->lastPostTime = 0; |
111 | init_PtrSet(&d->pendingRemoval); | 133 | init_PtrSet(&d->pendingRemoval); |
134 | d->wakeupTimer = 0; | ||
112 | } | 135 | } |
113 | 136 | ||
114 | void deinit_Periodic(iPeriodic *d) { | 137 | void deinit_Periodic(iPeriodic *d) { |
138 | startOrStopWakeupTimer_Periodic_(d, iFalse); | ||
115 | deinit_PtrSet(&d->pendingRemoval); | 139 | deinit_PtrSet(&d->pendingRemoval); |
116 | iForEach(Array, i, &d->commands.values) { | 140 | iForEach(Array, i, &d->commands.values) { |
117 | deinit_PeriodicCommand(i.value); | 141 | deinit_PeriodicCommand(i.value); |
@@ -121,6 +145,7 @@ void deinit_Periodic(iPeriodic *d) { | |||
121 | } | 145 | } |
122 | 146 | ||
123 | void add_Periodic(iPeriodic *d, iAny *context, const char *command) { | 147 | void add_Periodic(iPeriodic *d, iAny *context, const char *command) { |
148 | iAssert(isInstance_Object(context, &Class_Widget)); | ||
124 | lock_Mutex(d->mutex); | 149 | lock_Mutex(d->mutex); |
125 | size_t pos; | 150 | size_t pos; |
126 | iPeriodicCommand key = { .context = context }; | 151 | iPeriodicCommand key = { .context = context }; |
@@ -133,6 +158,7 @@ void add_Periodic(iPeriodic *d, iAny *context, const char *command) { | |||
133 | init_PeriodicCommand(&pc, context, command); | 158 | init_PeriodicCommand(&pc, context, command); |
134 | insert_SortedArray(&d->commands, &pc); | 159 | insert_SortedArray(&d->commands, &pc); |
135 | } | 160 | } |
161 | startOrStopWakeupTimer_Periodic_(d, iTrue); | ||
136 | unlock_Mutex(d->mutex); | 162 | unlock_Mutex(d->mutex); |
137 | } | 163 | } |
138 | 164 | ||
diff --git a/src/periodic.h b/src/periodic.h index a56310a8..f65a4299 100644 --- a/src/periodic.h +++ b/src/periodic.h | |||
@@ -35,6 +35,7 @@ struct Impl_Periodic { | |||
35 | iSortedArray commands; | 35 | iSortedArray commands; |
36 | uint32_t lastPostTime; | 36 | uint32_t lastPostTime; |
37 | iPtrSet pendingRemoval; /* contexts */ | 37 | iPtrSet pendingRemoval; /* contexts */ |
38 | int wakeupTimer; /* running while there are pending periodic commands */ | ||
38 | }; | 39 | }; |
39 | 40 | ||
40 | void init_Periodic (iPeriodic *); | 41 | void init_Periodic (iPeriodic *); |
diff --git a/src/prefs.c b/src/prefs.c index 10df9ade..6164ca25 100644 --- a/src/prefs.c +++ b/src/prefs.c | |||
@@ -23,6 +23,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
23 | #include "prefs.h" | 23 | #include "prefs.h" |
24 | 24 | ||
25 | #include <the_Foundation/fileinfo.h> | 25 | #include <the_Foundation/fileinfo.h> |
26 | #include <assert.h> | ||
27 | |||
28 | static_assert(offsetof(iPrefs, plainTextWrap) == offsetof(iPrefs, bools[plainTextWrap_PrefsBool]), | ||
29 | "memory layout mismatch (needs struct packing?)"); | ||
26 | 30 | ||
27 | void init_Prefs(iPrefs *d) { | 31 | void init_Prefs(iPrefs *d) { |
28 | iForIndices(i, d->strings) { | 32 | iForIndices(i, d->strings) { |
@@ -40,8 +44,20 @@ void init_Prefs(iPrefs *d) { | |||
40 | d->uiAnimations = iTrue; | 44 | d->uiAnimations = iTrue; |
41 | d->uiScale = 1.0f; /* default set elsewhere */ | 45 | d->uiScale = 1.0f; /* default set elsewhere */ |
42 | d->zoomPercent = 100; | 46 | d->zoomPercent = 100; |
47 | d->navbarActions[0] = back_ToolbarAction; | ||
48 | d->navbarActions[1] = forward_ToolbarAction; | ||
49 | d->navbarActions[2] = sidebar_ToolbarAction; | ||
50 | d->navbarActions[3] = home_ToolbarAction; | ||
51 | #if defined (iPlatformAndroidMobile) | ||
52 | /* Android has a system-wide back button so no need to have a duplicate. */ | ||
53 | d->toolbarActions[0] = closeTab_ToolbarAction; | ||
54 | #else | ||
55 | d->toolbarActions[0] = back_ToolbarAction; | ||
56 | #endif | ||
57 | d->toolbarActions[1] = forward_ToolbarAction; | ||
43 | d->sideIcon = iTrue; | 58 | d->sideIcon = iTrue; |
44 | d->hideToolbarOnScroll = iTrue; | 59 | d->hideToolbarOnScroll = iTrue; |
60 | d->blinkingCursor = iTrue; | ||
45 | d->pinSplit = 1; | 61 | d->pinSplit = 1; |
46 | d->time24h = iTrue; | 62 | d->time24h = iTrue; |
47 | d->returnKey = default_ReturnKeyBehavior; | 63 | d->returnKey = default_ReturnKeyBehavior; |
diff --git a/src/prefs.h b/src/prefs.h index 2fbff9de..ea864f51 100644 --- a/src/prefs.h +++ b/src/prefs.h | |||
@@ -33,76 +33,150 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
33 | iDeclareType(Prefs) | 33 | iDeclareType(Prefs) |
34 | 34 | ||
35 | enum iPrefsString { | 35 | enum iPrefsString { |
36 | /* General */ | ||
36 | uiLanguage_PrefsString, | 37 | uiLanguage_PrefsString, |
37 | downloadDir_PrefsString, | 38 | downloadDir_PrefsString, |
38 | searchUrl_PrefsString, | 39 | searchUrl_PrefsString, |
40 | |||
39 | /* Network */ | 41 | /* Network */ |
40 | caFile_PrefsString, | 42 | caFile_PrefsString, |
41 | caPath_PrefsString, | 43 | caPath_PrefsString, |
42 | geminiProxy_PrefsString, | 44 | geminiProxy_PrefsString, |
43 | gopherProxy_PrefsString, | 45 | gopherProxy_PrefsString, |
44 | httpProxy_PrefsString, | 46 | httpProxy_PrefsString, |
47 | |||
45 | /* Style */ | 48 | /* Style */ |
46 | uiFont_PrefsString, | 49 | uiFont_PrefsString, |
47 | headingFont_PrefsString, | 50 | headingFont_PrefsString, |
48 | bodyFont_PrefsString, | 51 | bodyFont_PrefsString, |
49 | monospaceFont_PrefsString, | 52 | monospaceFont_PrefsString, |
50 | monospaceDocumentFont_PrefsString, | 53 | monospaceDocumentFont_PrefsString, |
54 | |||
55 | /* Meta */ | ||
51 | max_PrefsString | 56 | max_PrefsString |
52 | }; | 57 | }; |
53 | 58 | ||
54 | /* TODO: Refactor at least the boolean values into an array for easier manipulation. | 59 | /* Note: These match match the array/struct in Prefs. */ |
55 | Then they can be (de)serialized as a group. Need to use a systematic command naming | 60 | enum iPrefsBool { |
56 | convention for notifications. */ | 61 | /* Window and User Interface */ |
62 | useSystemTheme_PrefsBool, | ||
63 | customFrame_PrefsBool, | ||
64 | retainWindowSize_PrefsBool, | ||
65 | uiAnimations_PrefsBool, | ||
66 | hideToolbarOnScroll_PrefsBool, | ||
67 | |||
68 | blinkingCursor_PrefsBool, | ||
69 | |||
70 | /* Document presentation */ | ||
71 | sideIcon_PrefsBool, | ||
72 | time24h_PrefsBool, | ||
73 | |||
74 | /* Behavior */ | ||
75 | hoverLink_PrefsBool, | ||
76 | smoothScrolling_PrefsBool, | ||
77 | loadImageInsteadOfScrolling_PrefsBool, | ||
78 | collapsePreOnLoad_PrefsBool, | ||
79 | openArchiveIndexPages_PrefsBool, | ||
80 | |||
81 | addBookmarksToBottom_PrefsBool, | ||
82 | warnAboutMissingGlyphs_PrefsBool, | ||
83 | |||
84 | /* Network */ | ||
85 | decodeUserVisibleURLs_PrefsBool, | ||
86 | |||
87 | /* Style */ | ||
88 | monospaceGemini_PrefsBool, | ||
89 | monospaceGopher_PrefsBool, | ||
90 | boldLinkVisited_PrefsBool, | ||
91 | boldLinkDark_PrefsBool, | ||
92 | boldLinkLight_PrefsBool, | ||
93 | |||
94 | fontSmoothing_PrefsBool, | ||
95 | bigFirstParagraph_PrefsBool, | ||
96 | quoteIcon_PrefsBool, | ||
97 | centerShortDocs_PrefsBool, | ||
98 | plainTextWrap_PrefsBool, | ||
99 | |||
100 | /* Meta */ | ||
101 | max_PrefsBool | ||
102 | }; | ||
103 | |||
104 | #define maxNavbarActions_Prefs 4 | ||
105 | |||
106 | /* TODO: Use a systematic command naming convention for notifications. */ | ||
107 | |||
57 | struct Impl_Prefs { | 108 | struct Impl_Prefs { |
58 | iString strings[max_PrefsString]; | 109 | iString strings[max_PrefsString]; |
59 | /* UI state */ | 110 | union { |
111 | iBool bools[max_PrefsBool]; | ||
112 | /* For convenience, contents of the array are accessible also via these members. */ | ||
113 | struct { | ||
114 | /* Window and User Interface */ | ||
115 | iBool useSystemTheme; | ||
116 | iBool customFrame; /* when LAGRANGE_ENABLE_CUSTOM_FRAME is defined */ | ||
117 | iBool retainWindowSize; | ||
118 | iBool uiAnimations; | ||
119 | iBool hideToolbarOnScroll; | ||
120 | |||
121 | iBool blinkingCursor; | ||
122 | |||
123 | /* Document presentation */ | ||
124 | iBool sideIcon; | ||
125 | iBool time24h; | ||
126 | |||
127 | /* Behavior */ | ||
128 | iBool hoverLink; | ||
129 | iBool smoothScrolling; | ||
130 | iBool loadImageInsteadOfScrolling; | ||
131 | iBool collapsePreOnLoad; | ||
132 | iBool openArchiveIndexPages; | ||
133 | |||
134 | iBool addBookmarksToBottom; | ||
135 | iBool warnAboutMissingGlyphs; | ||
136 | |||
137 | /* Network */ | ||
138 | iBool decodeUserVisibleURLs; | ||
139 | |||
140 | /* Style */ | ||
141 | iBool monospaceGemini; | ||
142 | iBool monospaceGopher; | ||
143 | iBool boldLinkVisited; | ||
144 | iBool boldLinkDark; | ||
145 | iBool boldLinkLight; | ||
146 | |||
147 | iBool fontSmoothing; | ||
148 | iBool bigFirstParagraph; | ||
149 | iBool quoteIcon; | ||
150 | iBool centerShortDocs; | ||
151 | iBool plainTextWrap; | ||
152 | }; | ||
153 | }; | ||
154 | /* UI state (belongs to state.lgr...) */ | ||
60 | int dialogTab; | 155 | int dialogTab; |
61 | int langFrom; | 156 | int langFrom; |
62 | int langTo; | 157 | int langTo; |
63 | /* Window */ | 158 | /* Colors */ |
64 | iBool useSystemTheme; | ||
65 | enum iColorTheme systemPreferredColorTheme[2]; /* dark, light */ | 159 | enum iColorTheme systemPreferredColorTheme[2]; /* dark, light */ |
66 | enum iColorTheme theme; | 160 | enum iColorTheme theme; |
67 | enum iColorAccent accent; | 161 | enum iColorAccent accent; |
68 | iBool customFrame; /* when LAGRANGE_ENABLE_CUSTOM_FRAME is defined */ | 162 | /* Window and User Interface */ |
69 | iBool retainWindowSize; | ||
70 | iBool uiAnimations; | ||
71 | float uiScale; | 163 | float uiScale; |
164 | enum iToolbarAction navbarActions[maxNavbarActions_Prefs]; | ||
165 | enum iToolbarAction toolbarActions[2]; | ||
166 | /* Document presentation */ | ||
72 | int zoomPercent; | 167 | int zoomPercent; |
73 | iBool sideIcon; | ||
74 | iBool hideToolbarOnScroll; | ||
75 | int pinSplit; /* 0: no pinning, 1: left doc, 2: right doc */ | ||
76 | iBool time24h; | ||
77 | /* Behavior */ | 168 | /* Behavior */ |
169 | int pinSplit; /* 0: no pinning, 1: left doc, 2: right doc */ | ||
78 | int returnKey; | 170 | int returnKey; |
79 | iBool hoverLink; | ||
80 | iBool smoothScrolling; | ||
81 | int smoothScrollSpeed[max_ScrollType]; | 171 | int smoothScrollSpeed[max_ScrollType]; |
82 | iBool loadImageInsteadOfScrolling; | ||
83 | iBool collapsePreOnLoad; | ||
84 | iBool openArchiveIndexPages; | ||
85 | iBool addBookmarksToBottom; | ||
86 | iBool warnAboutMissingGlyphs; | ||
87 | /* Network */ | 172 | /* Network */ |
88 | iBool decodeUserVisibleURLs; | ||
89 | int maxCacheSize; /* MB */ | 173 | int maxCacheSize; /* MB */ |
90 | int maxMemorySize; /* MB */ | 174 | int maxMemorySize; /* MB */ |
91 | /* Style */ | 175 | /* Style */ |
92 | iStringSet * disabledFontPacks; | 176 | iStringSet * disabledFontPacks; |
93 | iBool fontSmoothing; | ||
94 | int gemtextAnsiEscapes; | 177 | int gemtextAnsiEscapes; |
95 | iBool monospaceGemini; | ||
96 | iBool monospaceGopher; | ||
97 | iBool boldLinkVisited; | ||
98 | iBool boldLinkDark; | ||
99 | iBool boldLinkLight; | ||
100 | int lineWidth; | 178 | int lineWidth; |
101 | float lineSpacing; | 179 | float lineSpacing; |
102 | iBool bigFirstParagraph; | ||
103 | iBool quoteIcon; | ||
104 | iBool centerShortDocs; | ||
105 | iBool plainTextWrap; | ||
106 | enum iImageStyle imageStyle; | 180 | enum iImageStyle imageStyle; |
107 | /* Colors */ | 181 | /* Colors */ |
108 | enum iGmDocumentTheme docThemeDark; | 182 | enum iGmDocumentTheme docThemeDark; |
diff --git a/src/resources.c b/src/resources.c index 03ca7cbb..ae85463a 100644 --- a/src/resources.c +++ b/src/resources.c | |||
@@ -28,7 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
28 | #include <SDL_rwops.h> | 28 | #include <SDL_rwops.h> |
29 | 29 | ||
30 | static iArchive *archive_; | 30 | static iArchive *archive_; |
31 | 31 | ||
32 | iBlock blobAbout_Resources; | 32 | iBlock blobAbout_Resources; |
33 | iBlock blobHelp_Resources; | 33 | iBlock blobHelp_Resources; |
34 | iBlock blobLagrange_Resources; | 34 | iBlock blobLagrange_Resources; |
@@ -66,10 +66,18 @@ static struct { | |||
66 | const char *archivePath; | 66 | const char *archivePath; |
67 | } entries_[] = { | 67 | } entries_[] = { |
68 | { &blobAbout_Resources, "about/about.gmi" }, | 68 | { &blobAbout_Resources, "about/about.gmi" }, |
69 | { &blobHelp_Resources, "about/help.gmi" }, | ||
70 | { &blobLagrange_Resources, "about/lagrange.gmi" }, | 69 | { &blobLagrange_Resources, "about/lagrange.gmi" }, |
71 | { &blobLicense_Resources, "about/license.gmi" }, | 70 | { &blobLicense_Resources, "about/license.gmi" }, |
71 | #if defined (iPlatformAppleMobile) | ||
72 | { &blobHelp_Resources, "about/ios-help.gmi" }, | ||
73 | { &blobVersion_Resources, "about/ios-version.gmi" }, | ||
74 | #elif defined (iPlatformAndroidMobile) | ||
75 | { &blobHelp_Resources, "about/android-help.gmi" }, | ||
76 | { &blobVersion_Resources, "about/android-version.gmi" }, | ||
77 | #else | ||
78 | { &blobHelp_Resources, "about/help.gmi" }, | ||
72 | { &blobVersion_Resources, "about/version.gmi" }, | 79 | { &blobVersion_Resources, "about/version.gmi" }, |
80 | #endif | ||
73 | { &blobArghelp_Resources, "arg-help.txt" }, | 81 | { &blobArghelp_Resources, "arg-help.txt" }, |
74 | { &blobCs_Resources, "lang/cs.bin" }, | 82 | { &blobCs_Resources, "lang/cs.bin" }, |
75 | { &blobDe_Resources, "lang/de.bin" }, | 83 | { &blobDe_Resources, "lang/de.bin" }, |
diff --git a/src/sitespec.c b/src/sitespec.c index 6f4546f0..fe80ad13 100644 --- a/src/sitespec.c +++ b/src/sitespec.c | |||
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
25 | #include <the_Foundation/file.h> | 25 | #include <the_Foundation/file.h> |
26 | #include <the_Foundation/path.h> | 26 | #include <the_Foundation/path.h> |
27 | #include <the_Foundation/stringhash.h> | 27 | #include <the_Foundation/stringhash.h> |
28 | #include <the_Foundation/stringarray.h> | ||
28 | #include <the_Foundation/toml.h> | 29 | #include <the_Foundation/toml.h> |
29 | 30 | ||
30 | iDeclareClass(SiteParams) | 31 | iDeclareClass(SiteParams) |
@@ -35,6 +36,7 @@ struct Impl_SiteParams { | |||
35 | uint16_t titanPort; | 36 | uint16_t titanPort; |
36 | iString titanIdentity; /* fingerprint */ | 37 | iString titanIdentity; /* fingerprint */ |
37 | int dismissWarnings; | 38 | int dismissWarnings; |
39 | iStringArray usedIdentities; /* fingerprints; latest ones at the end */ | ||
38 | /* TODO: theme seed, style settings */ | 40 | /* TODO: theme seed, style settings */ |
39 | }; | 41 | }; |
40 | 42 | ||
@@ -42,12 +44,23 @@ void init_SiteParams(iSiteParams *d) { | |||
42 | d->titanPort = 0; /* undefined */ | 44 | d->titanPort = 0; /* undefined */ |
43 | init_String(&d->titanIdentity); | 45 | init_String(&d->titanIdentity); |
44 | d->dismissWarnings = 0; | 46 | d->dismissWarnings = 0; |
47 | init_StringArray(&d->usedIdentities); | ||
45 | } | 48 | } |
46 | 49 | ||
47 | void deinit_SiteParams(iSiteParams *d) { | 50 | void deinit_SiteParams(iSiteParams *d) { |
51 | deinit_StringArray(&d->usedIdentities); | ||
48 | deinit_String(&d->titanIdentity); | 52 | deinit_String(&d->titanIdentity); |
49 | } | 53 | } |
50 | 54 | ||
55 | static size_t findUsedIdentity_SiteParams_(const iSiteParams *d, const iString *fingerprint) { | ||
56 | iConstForEach(StringArray, i, &d->usedIdentities) { | ||
57 | if (equal_String(i.value, fingerprint)) { | ||
58 | return index_StringArrayConstIterator(&i); | ||
59 | } | ||
60 | } | ||
61 | return iInvalidPos; | ||
62 | } | ||
63 | |||
51 | iDefineClass(SiteParams) | 64 | iDefineClass(SiteParams) |
52 | iDefineObjectConstruction(SiteParams) | 65 | iDefineObjectConstruction(SiteParams) |
53 | 66 | ||
@@ -128,7 +141,13 @@ static void handleIniKeyValue_SiteSpec_(void *context, const iString *table, con | |||
128 | set_String(&d->loadParams->titanIdentity, value->value.string); | 141 | set_String(&d->loadParams->titanIdentity, value->value.string); |
129 | } | 142 | } |
130 | else if (!cmp_String(key, "dismissWarnings") && value->type == int64_TomlType) { | 143 | else if (!cmp_String(key, "dismissWarnings") && value->type == int64_TomlType) { |
131 | d->loadParams->dismissWarnings = value->value.int64; | 144 | d->loadParams->dismissWarnings = (int) value->value.int64; |
145 | } | ||
146 | else if (!cmp_String(key, "usedIdentities") && value->type == string_TomlType) { | ||
147 | iRangecc seg = iNullRange; | ||
148 | while (nextSplit_Rangecc(range_String(value->value.string), " ", &seg)) { | ||
149 | pushBack_StringArray(&d->loadParams->usedIdentities, collectNewRange_String(seg)); | ||
150 | } | ||
132 | } | 151 | } |
133 | } | 152 | } |
134 | 153 | ||
@@ -151,6 +170,7 @@ static void save_SiteSpec_(iSiteSpec *d) { | |||
151 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { | 170 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { |
152 | iString *buf = new_String(); | 171 | iString *buf = new_String(); |
153 | iConstForEach(StringHash, i, &d->sites) { | 172 | iConstForEach(StringHash, i, &d->sites) { |
173 | iBeginCollect(); | ||
154 | const iBlock * key = &i.value->keyBlock; | 174 | const iBlock * key = &i.value->keyBlock; |
155 | const iSiteParams *params = i.value->object; | 175 | const iSiteParams *params = i.value->object; |
156 | format_String(buf, "[%s]\n", cstr_Block(key)); | 176 | format_String(buf, "[%s]\n", cstr_Block(key)); |
@@ -164,8 +184,15 @@ static void save_SiteSpec_(iSiteSpec *d) { | |||
164 | if (params->dismissWarnings) { | 184 | if (params->dismissWarnings) { |
165 | appendFormat_String(buf, "dismissWarnings = 0x%x\n", params->dismissWarnings); | 185 | appendFormat_String(buf, "dismissWarnings = 0x%x\n", params->dismissWarnings); |
166 | } | 186 | } |
187 | if (!isEmpty_StringArray(¶ms->usedIdentities)) { | ||
188 | appendFormat_String( | ||
189 | buf, | ||
190 | "usedIdentities = \"%s\"\n", | ||
191 | cstrCollect_String(joinCStr_StringArray(¶ms->usedIdentities, " "))); | ||
192 | } | ||
167 | appendCStr_String(buf, "\n"); | 193 | appendCStr_String(buf, "\n"); |
168 | write_File(f, utf8_String(buf)); | 194 | write_File(f, utf8_String(buf)); |
195 | iEndCollect(); | ||
169 | } | 196 | } |
170 | delete_String(buf); | 197 | delete_String(buf); |
171 | } | 198 | } |
@@ -188,14 +215,19 @@ void deinit_SiteSpec(void) { | |||
188 | deinit_String(&d->saveDir); | 215 | deinit_String(&d->saveDir); |
189 | } | 216 | } |
190 | 217 | ||
191 | void setValue_SiteSpec(const iString *site, enum iSiteSpecKey key, int value) { | 218 | static iSiteParams *findParams_SiteSpec_(iSiteSpec *d, const iString *site) { |
192 | iSiteSpec *d = &siteSpec_; | ||
193 | const iString *hashKey = collect_String(lower_String(site)); | 219 | const iString *hashKey = collect_String(lower_String(site)); |
194 | iSiteParams *params = value_StringHash(&d->sites, hashKey); | 220 | iSiteParams *params = value_StringHash(&d->sites, hashKey); |
195 | if (!params) { | 221 | if (!params) { |
196 | params = new_SiteParams(); | 222 | params = new_SiteParams(); |
197 | insert_StringHash(&d->sites, hashKey, params); | 223 | insert_StringHash(&d->sites, hashKey, params); |
198 | } | 224 | } |
225 | return params; | ||
226 | } | ||
227 | |||
228 | void setValue_SiteSpec(const iString *site, enum iSiteSpecKey key, int value) { | ||
229 | iSiteSpec *d = &siteSpec_; | ||
230 | iSiteParams *params = findParams_SiteSpec_(d, site); | ||
199 | iBool needSave = iFalse; | 231 | iBool needSave = iFalse; |
200 | switch (key) { | 232 | switch (key) { |
201 | case titanPort_SiteSpecKey: | 233 | case titanPort_SiteSpecKey: |
@@ -216,12 +248,7 @@ void setValue_SiteSpec(const iString *site, enum iSiteSpecKey key, int value) { | |||
216 | 248 | ||
217 | void setValueString_SiteSpec(const iString *site, enum iSiteSpecKey key, const iString *value) { | 249 | void setValueString_SiteSpec(const iString *site, enum iSiteSpecKey key, const iString *value) { |
218 | iSiteSpec *d = &siteSpec_; | 250 | iSiteSpec *d = &siteSpec_; |
219 | const iString *hashKey = collect_String(lower_String(site)); | 251 | iSiteParams *params = findParams_SiteSpec_(d, site); |
220 | iSiteParams *params = value_StringHash(&d->sites, hashKey); | ||
221 | if (!params) { | ||
222 | params = new_SiteParams(); | ||
223 | insert_StringHash(&d->sites, hashKey, params); | ||
224 | } | ||
225 | iBool needSave = iFalse; | 252 | iBool needSave = iFalse; |
226 | switch (key) { | 253 | switch (key) { |
227 | case titanIdentity_SiteSpecKey: | 254 | case titanIdentity_SiteSpecKey: |
@@ -238,6 +265,44 @@ void setValueString_SiteSpec(const iString *site, enum iSiteSpecKey key, const i | |||
238 | } | 265 | } |
239 | } | 266 | } |
240 | 267 | ||
268 | static void insertOrRemoveString_SiteSpec_(iSiteSpec *d, const iString *site, enum iSiteSpecKey key, | ||
269 | const iString *value, iBool doInsert) { | ||
270 | iSiteParams *params = findParams_SiteSpec_(d, site); | ||
271 | iBool needSave = iFalse; | ||
272 | switch (key) { | ||
273 | case usedIdentities_SiteSpecKey: { | ||
274 | const size_t index = findUsedIdentity_SiteParams_(params, value); | ||
275 | if (doInsert && index == iInvalidPos) { | ||
276 | pushBack_StringArray(¶ms->usedIdentities, value); | ||
277 | needSave = iTrue; | ||
278 | } | ||
279 | else if (!doInsert && index != iInvalidPos) { | ||
280 | remove_StringArray(¶ms->usedIdentities, index); | ||
281 | needSave = iTrue; | ||
282 | } | ||
283 | break; | ||
284 | } | ||
285 | default: | ||
286 | break; | ||
287 | } | ||
288 | if (needSave) { | ||
289 | save_SiteSpec_(d); | ||
290 | } | ||
291 | } | ||
292 | |||
293 | void insertString_SiteSpec(const iString *site, enum iSiteSpecKey key, const iString *value) { | ||
294 | insertOrRemoveString_SiteSpec_(&siteSpec_, site, key, value, iTrue); | ||
295 | } | ||
296 | |||
297 | void removeString_SiteSpec(const iString *site, enum iSiteSpecKey key, const iString *value) { | ||
298 | insertOrRemoveString_SiteSpec_(&siteSpec_, site, key, value, iFalse); | ||
299 | } | ||
300 | |||
301 | const iStringArray *strings_SiteSpec(const iString *site, enum iSiteSpecKey key) { | ||
302 | const iSiteParams *params = findParams_SiteSpec_(&siteSpec_, site); | ||
303 | return ¶ms->usedIdentities; | ||
304 | } | ||
305 | |||
241 | int value_SiteSpec(const iString *site, enum iSiteSpecKey key) { | 306 | int value_SiteSpec(const iString *site, enum iSiteSpecKey key) { |
242 | iSiteSpec *d = &siteSpec_; | 307 | iSiteSpec *d = &siteSpec_; |
243 | const iSiteParams *params = constValue_StringHash(&d->sites, collect_String(lower_String(site))); | 308 | const iSiteParams *params = constValue_StringHash(&d->sites, collect_String(lower_String(site))); |
diff --git a/src/sitespec.h b/src/sitespec.h index 5adaeb8c..11c40e3c 100644 --- a/src/sitespec.h +++ b/src/sitespec.h | |||
@@ -22,22 +22,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #pragma once | 23 | #pragma once |
24 | 24 | ||
25 | #include <the_Foundation/string.h> | 25 | #include <the_Foundation/stringarray.h> |
26 | 26 | ||
27 | iDeclareType(SiteSpec) | 27 | iDeclareType(SiteSpec) |
28 | 28 | ||
29 | enum iSiteSpecKey { | 29 | enum iSiteSpecKey { |
30 | titanPort_SiteSpecKey, | 30 | titanPort_SiteSpecKey, /* int */ |
31 | titanIdentity_SiteSpecKey, | 31 | titanIdentity_SiteSpecKey, /* String */ |
32 | dismissWarnings_SiteSpecKey, | 32 | dismissWarnings_SiteSpecKey, /* int */ |
33 | usedIdentities_SiteSpecKey, /* StringArray */ | ||
33 | }; | 34 | }; |
34 | 35 | ||
35 | void init_SiteSpec (const char *saveDir); | 36 | void init_SiteSpec (const char *saveDir); |
36 | void deinit_SiteSpec (void); | 37 | void deinit_SiteSpec (void); |
37 | 38 | ||
38 | /* changes saved immediately */ | 39 | /* changes saved immediately */ |
39 | void setValue_SiteSpec (const iString *site, enum iSiteSpecKey key, int value); | 40 | void setValue_SiteSpec (const iString *site, enum iSiteSpecKey key, int value); |
40 | void setValueString_SiteSpec (const iString *site, enum iSiteSpecKey key, const iString *value); | 41 | void setValueString_SiteSpec (const iString *site, enum iSiteSpecKey key, const iString *value); |
42 | void insertString_SiteSpec (const iString *site, enum iSiteSpecKey key, const iString *value); | ||
43 | void removeString_SiteSpec (const iString *site, enum iSiteSpecKey key, const iString *value); | ||
41 | 44 | ||
42 | int value_SiteSpec (const iString *site, enum iSiteSpecKey key); | 45 | int value_SiteSpec (const iString *site, enum iSiteSpecKey key); |
43 | const iString * valueString_SiteSpec (const iString *site, enum iSiteSpecKey key); | 46 | const iString * valueString_SiteSpec (const iString *site, enum iSiteSpecKey key); |
47 | const iStringArray *strings_SiteSpec (const iString *site, enum iSiteSpecKey key); | ||
diff --git a/src/ui/banner.c b/src/ui/banner.c index 7ec189a4..11ae1574 100644 --- a/src/ui/banner.c +++ b/src/ui/banner.c | |||
@@ -76,7 +76,6 @@ static void updateHeight_Banner_(iBanner *d) { | |||
76 | } | 76 | } |
77 | const size_t numItems = size_Array(&d->items); | 77 | const size_t numItems = size_Array(&d->items); |
78 | if (numItems) { | 78 | if (numItems) { |
79 | const int innerPad = gap_UI; | ||
80 | iConstForEach(Array, i, &d->items) { | 79 | iConstForEach(Array, i, &d->items) { |
81 | const iBannerItem *item = i.value; | 80 | const iBannerItem *item = i.value; |
82 | d->rect.size.y += item->height; | 81 | d->rect.size.y += item->height; |
@@ -161,6 +160,13 @@ void setSite_Banner(iBanner *d, iRangecc site, iChar icon) { | |||
161 | 160 | ||
162 | void add_Banner(iBanner *d, enum iBannerType type, enum iGmStatusCode code, | 161 | void add_Banner(iBanner *d, enum iBannerType type, enum iGmStatusCode code, |
163 | const iString *message, const iString *details) { | 162 | const iString *message, const iString *details) { |
163 | /* If there already is a matching item, don't add a second one. */ | ||
164 | iConstForEach(Array, i, &d->items) { | ||
165 | const iBannerItem *item = i.value; | ||
166 | if (item->type == type && item->code == code) { | ||
167 | return; | ||
168 | } | ||
169 | } | ||
164 | iBannerItem item; | 170 | iBannerItem item; |
165 | init_BannerItem(&item); | 171 | init_BannerItem(&item); |
166 | item.type = type; | 172 | item.type = type; |
diff --git a/src/ui/bindingswidget.c b/src/ui/bindingswidget.c index 4cf8df8e..13f9434e 100644 --- a/src/ui/bindingswidget.c +++ b/src/ui/bindingswidget.c | |||
@@ -143,12 +143,16 @@ static void setActiveItem_BindingsWidget_(iBindingsWidget *d, size_t pos) { | |||
143 | item->isWaitingForEvent = iTrue; | 143 | item->isWaitingForEvent = iTrue; |
144 | invalidateItem_ListWidget(d->list, d->activePos); | 144 | invalidateItem_ListWidget(d->list, d->activePos); |
145 | } | 145 | } |
146 | #if defined (iPlatformAppleDesktop) | 146 | #if defined (iPlatformAppleDesktop) && defined (iHaveNativeContextMenus) |
147 | /* Native menus must be disabled while grabbing keys so the shortcuts don't trigger. */ | 147 | /* Native menus must be disabled while grabbing keys so the shortcuts don't trigger. */ |
148 | const iBool enableNativeMenus = (d->activePos == iInvalidPos); | 148 | const iBool enableNativeMenus = (d->activePos == iInvalidPos); |
149 | enableMenu_MacOS("${menu.title.file}", enableNativeMenus); | ||
149 | enableMenu_MacOS("${menu.title.edit}", enableNativeMenus); | 150 | enableMenu_MacOS("${menu.title.edit}", enableNativeMenus); |
150 | enableMenu_MacOS("${menu.title.view}", enableNativeMenus); | 151 | enableMenu_MacOS("${menu.title.view}", enableNativeMenus); |
152 | enableMenu_MacOS("${menu.title.bookmarks}", enableNativeMenus); | ||
151 | enableMenu_MacOS("${menu.title.identity}", enableNativeMenus); | 153 | enableMenu_MacOS("${menu.title.identity}", enableNativeMenus); |
154 | enableMenuIndex_MacOS(6, enableNativeMenus); | ||
155 | enableMenuIndex_MacOS(7, enableNativeMenus); | ||
152 | #endif | 156 | #endif |
153 | } | 157 | } |
154 | 158 | ||
diff --git a/src/ui/certimportwidget.c b/src/ui/certimportwidget.c index f4dfdefa..e4e461e0 100644 --- a/src/ui/certimportwidget.c +++ b/src/ui/certimportwidget.c | |||
@@ -145,10 +145,7 @@ void init_CertImportWidget(iCertImportWidget *d) { | |||
145 | else { | 145 | else { |
146 | /* This should behave similar to sheets. */ | 146 | /* This should behave similar to sheets. */ |
147 | useSheetStyle_Widget(w); | 147 | useSheetStyle_Widget(w); |
148 | addChildFlags_Widget( | 148 | addDialogTitle_Widget(w, "${heading.certimport}", NULL); |
149 | w, | ||
150 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.certimport}", NULL)), | ||
151 | frameless_WidgetFlag); | ||
152 | d->info = addChildFlags_Widget(w, iClob(new_LabelWidget(infoText_, NULL)), frameless_WidgetFlag); | 149 | d->info = addChildFlags_Widget(w, iClob(new_LabelWidget(infoText_, NULL)), frameless_WidgetFlag); |
153 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | 150 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); |
154 | d->crtLabel = new_LabelWidget("", NULL); { | 151 | d->crtLabel = new_LabelWidget("", NULL); { |
diff --git a/src/ui/certlistwidget.c b/src/ui/certlistwidget.c new file mode 100644 index 00000000..2a7562d8 --- /dev/null +++ b/src/ui/certlistwidget.c | |||
@@ -0,0 +1,490 @@ | |||
1 | /* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #include "certlistwidget.h" | ||
24 | |||
25 | #include "documentwidget.h" | ||
26 | #include "command.h" | ||
27 | #include "labelwidget.h" | ||
28 | #include "listwidget.h" | ||
29 | #include "../gmcerts.h" | ||
30 | #include "../app.h" | ||
31 | |||
32 | #include <SDL_clipboard.h> | ||
33 | |||
34 | iDeclareType(CertItem) | ||
35 | typedef iListItemClass iCertItemClass; | ||
36 | |||
37 | struct Impl_CertItem { | ||
38 | iListItem listItem; | ||
39 | uint32_t id; | ||
40 | int indent; | ||
41 | iChar icon; | ||
42 | iBool isBold; | ||
43 | iString label; | ||
44 | iString meta; | ||
45 | // iString url; | ||
46 | }; | ||
47 | |||
48 | void init_CertItem(iCertItem *d) { | ||
49 | init_ListItem(&d->listItem); | ||
50 | d->id = 0; | ||
51 | d->indent = 0; | ||
52 | d->icon = 0; | ||
53 | d->isBold = iFalse; | ||
54 | init_String(&d->label); | ||
55 | init_String(&d->meta); | ||
56 | // init_String(&d->url); | ||
57 | } | ||
58 | |||
59 | void deinit_CertItem(iCertItem *d) { | ||
60 | // deinit_String(&d->url); | ||
61 | deinit_String(&d->meta); | ||
62 | deinit_String(&d->label); | ||
63 | } | ||
64 | |||
65 | static void draw_CertItem_(const iCertItem *d, iPaint *p, iRect itemRect, const iListWidget *list); | ||
66 | |||
67 | iBeginDefineSubclass(CertItem, ListItem) | ||
68 | .draw = (iAny *) draw_CertItem_, | ||
69 | iEndDefineSubclass(CertItem) | ||
70 | |||
71 | iDefineObjectConstruction(CertItem) | ||
72 | |||
73 | /*----------------------------------------------------------------------------------------------*/ | ||
74 | |||
75 | struct Impl_CertListWidget { | ||
76 | iListWidget list; | ||
77 | int itemFonts[2]; | ||
78 | iWidget *menu; /* context menu for an item */ | ||
79 | iCertItem *contextItem; /* list item accessed in the context menu */ | ||
80 | size_t contextIndex; /* index of list item accessed in the context menu */ | ||
81 | }; | ||
82 | |||
83 | iDefineObjectConstruction(CertListWidget) | ||
84 | |||
85 | static iGmIdentity *menuIdentity_CertListWidget_(const iCertListWidget *d) { | ||
86 | if (d->contextItem) { | ||
87 | return identity_GmCerts(certs_App(), d->contextItem->id); | ||
88 | } | ||
89 | return NULL; | ||
90 | } | ||
91 | |||
92 | static void updateContextMenu_CertListWidget_(iCertListWidget *d) { | ||
93 | iArray *items = collectNew_Array(sizeof(iMenuItem)); | ||
94 | const iString *docUrl = url_DocumentWidget(document_App()); | ||
95 | size_t firstIndex = 0; | ||
96 | if (deviceType_App() != desktop_AppDeviceType && !isEmpty_String(docUrl)) { | ||
97 | pushBack_Array(items, &(iMenuItem){ format_CStr("```%s", cstr_String(docUrl)) }); | ||
98 | firstIndex = 1; | ||
99 | } | ||
100 | const iMenuItem ctxItems[] = { | ||
101 | { person_Icon " ${ident.use}", 0, 0, "ident.use arg:1" }, | ||
102 | { close_Icon " ${ident.stopuse}", 0, 0, "ident.use arg:0" }, | ||
103 | { close_Icon " ${ident.stopuse.all}", 0, 0, "ident.use arg:0 clear:1" }, | ||
104 | { "---", 0, 0, NULL }, | ||
105 | { edit_Icon " ${menu.edit.notes}", 0, 0, "ident.edit" }, | ||
106 | { "${ident.fingerprint}", 0, 0, "ident.fingerprint" }, | ||
107 | #if defined (iPlatformAppleDesktop) | ||
108 | { magnifyingGlass_Icon " ${menu.reveal.macos}", 0, 0, "ident.reveal" }, | ||
109 | #endif | ||
110 | #if defined (iPlatformLinux) | ||
111 | { magnifyingGlass_Icon " ${menu.reveal.filemgr}", 0, 0, "ident.reveal" }, | ||
112 | #endif | ||
113 | { export_Icon " ${ident.export}", 0, 0, "ident.export" }, | ||
114 | { "---", 0, 0, NULL }, | ||
115 | { delete_Icon " " uiTextCaution_ColorEscape "${ident.delete}", 0, 0, "ident.delete confirm:1" }, | ||
116 | }; | ||
117 | pushBackN_Array(items, ctxItems, iElemCount(ctxItems)); | ||
118 | /* Used URLs. */ | ||
119 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
120 | if (ident) { | ||
121 | size_t insertPos = firstIndex + 3; | ||
122 | if (!isEmpty_StringSet(ident->useUrls)) { | ||
123 | insert_Array(items, insertPos++, &(iMenuItem){ "---", 0, 0, NULL }); | ||
124 | } | ||
125 | iBool usedOnCurrentPage = iFalse; | ||
126 | iConstForEach(StringSet, i, ident->useUrls) { | ||
127 | const iString *url = i.value; | ||
128 | usedOnCurrentPage |= startsWithCase_String(docUrl, cstr_String(url)); | ||
129 | iRangecc urlStr = range_String(url); | ||
130 | if (startsWith_Rangecc(urlStr, "gemini://")) { | ||
131 | urlStr.start += 9; /* omit the default scheme */ | ||
132 | } | ||
133 | insert_Array(items, | ||
134 | insertPos++, | ||
135 | &(iMenuItem){ format_CStr(globe_Icon " %s", cstr_Rangecc(urlStr)), | ||
136 | 0, | ||
137 | 0, | ||
138 | format_CStr("!open url:%s", cstr_String(url)) }); | ||
139 | } | ||
140 | if (!usedOnCurrentPage) { | ||
141 | remove_Array(items, firstIndex + 1); | ||
142 | } | ||
143 | else { | ||
144 | remove_Array(items, firstIndex); | ||
145 | } | ||
146 | } | ||
147 | destroy_Widget(d->menu); | ||
148 | d->menu = makeMenu_Widget(as_Widget(d), data_Array(items), size_Array(items)); | ||
149 | } | ||
150 | |||
151 | static void itemClicked_CertListWidget_(iCertListWidget *d, iCertItem *item, size_t itemIndex) { | ||
152 | iWidget *w = as_Widget(d); | ||
153 | setFocus_Widget(NULL); | ||
154 | d->contextItem = item; | ||
155 | if (d->contextIndex != iInvalidPos) { | ||
156 | invalidateItem_ListWidget(&d->list, d->contextIndex); | ||
157 | } | ||
158 | d->contextIndex = itemIndex; | ||
159 | if (itemIndex < numItems_ListWidget(&d->list)) { | ||
160 | updateContextMenu_CertListWidget_(d); | ||
161 | arrange_Widget(d->menu); | ||
162 | openMenu_Widget(d->menu, | ||
163 | bounds_Widget(w).pos.x < mid_Rect(rect_Root(w->root)).x | ||
164 | ? topRight_Rect(itemRect_ListWidget(&d->list, itemIndex)) | ||
165 | : addX_I2(topLeft_Rect(itemRect_ListWidget(&d->list, itemIndex)), | ||
166 | -width_Widget(d->menu))); | ||
167 | } | ||
168 | } | ||
169 | |||
170 | static iBool processEvent_CertListWidget_(iCertListWidget *d, const SDL_Event *ev) { | ||
171 | iWidget *w = as_Widget(d); | ||
172 | /* Handle commands. */ | ||
173 | if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { | ||
174 | const char *cmd = command_UserEvent(ev); | ||
175 | if (equal_Command(cmd, "idents.changed")) { | ||
176 | updateItems_CertListWidget(d); | ||
177 | } | ||
178 | else if (isCommand_Widget(w, ev, "list.clicked")) { | ||
179 | itemClicked_CertListWidget_( | ||
180 | d, pointerLabel_Command(cmd, "item"), argU32Label_Command(cmd, "arg")); | ||
181 | return iTrue; | ||
182 | } | ||
183 | else if (isCommand_Widget(w, ev, "ident.use")) { | ||
184 | iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
185 | const iString *tabUrl = urlQueryStripped_String(url_DocumentWidget(document_App())); | ||
186 | if (ident) { | ||
187 | if (argLabel_Command(cmd, "clear")) { | ||
188 | clearUse_GmIdentity(ident); | ||
189 | } | ||
190 | else if (arg_Command(cmd)) { | ||
191 | signIn_GmCerts(certs_App(), ident, tabUrl); | ||
192 | postCommand_App("navigate.reload"); | ||
193 | } | ||
194 | else { | ||
195 | signOut_GmCerts(certs_App(), tabUrl); | ||
196 | postCommand_App("navigate.reload"); | ||
197 | } | ||
198 | saveIdentities_GmCerts(certs_App()); | ||
199 | updateItems_CertListWidget(d); | ||
200 | } | ||
201 | return iTrue; | ||
202 | } | ||
203 | else if (isCommand_Widget(w, ev, "ident.edit")) { | ||
204 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
205 | if (ident) { | ||
206 | makeValueInput_Widget(get_Root()->widget, | ||
207 | &ident->notes, | ||
208 | uiHeading_ColorEscape "${heading.ident.notes}", | ||
209 | format_CStr(cstr_Lang("dlg.ident.notes"), cstr_String(name_GmIdentity(ident))), | ||
210 | uiTextAction_ColorEscape "${dlg.default}", | ||
211 | format_CStr("!ident.setnotes ident:%p ptr:%p", ident, d)); | ||
212 | } | ||
213 | return iTrue; | ||
214 | } | ||
215 | else if (isCommand_Widget(w, ev, "ident.fingerprint")) { | ||
216 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
217 | if (ident) { | ||
218 | const iString *fps = collect_String( | ||
219 | hexEncode_Block(collect_Block(fingerprint_TlsCertificate(ident->cert)))); | ||
220 | SDL_SetClipboardText(cstr_String(fps)); | ||
221 | } | ||
222 | return iTrue; | ||
223 | } | ||
224 | else if (isCommand_Widget(w, ev, "ident.export")) { | ||
225 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
226 | if (ident) { | ||
227 | iString *pem = collect_String(pem_TlsCertificate(ident->cert)); | ||
228 | append_String(pem, collect_String(privateKeyPem_TlsCertificate(ident->cert))); | ||
229 | iDocumentWidget *expTab = newTab_App(NULL, iTrue); | ||
230 | setUrlAndSource_DocumentWidget( | ||
231 | expTab, | ||
232 | collectNewFormat_String("file:%s.pem", cstr_String(name_GmIdentity(ident))), | ||
233 | collectNewCStr_String("text/plain"), | ||
234 | utf8_String(pem)); | ||
235 | } | ||
236 | return iTrue; | ||
237 | } | ||
238 | else if (isCommand_Widget(w, ev, "ident.setnotes")) { | ||
239 | iGmIdentity *ident = pointerLabel_Command(cmd, "ident"); | ||
240 | if (ident) { | ||
241 | setCStr_String(&ident->notes, suffixPtr_Command(cmd, "value")); | ||
242 | updateItems_CertListWidget(d); | ||
243 | } | ||
244 | return iTrue; | ||
245 | } | ||
246 | else if (isCommand_Widget(w, ev, "ident.pickicon")) { | ||
247 | return iTrue; | ||
248 | } | ||
249 | else if (isCommand_Widget(w, ev, "ident.reveal")) { | ||
250 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
251 | if (ident) { | ||
252 | const iString *crtPath = certificatePath_GmCerts(certs_App(), ident); | ||
253 | if (crtPath) { | ||
254 | postCommandf_App("reveal path:%s", cstr_String(crtPath)); | ||
255 | } | ||
256 | } | ||
257 | return iTrue; | ||
258 | } | ||
259 | else if (isCommand_Widget(w, ev, "ident.delete")) { | ||
260 | iCertItem *item = d->contextItem; | ||
261 | if (argLabel_Command(cmd, "confirm")) { | ||
262 | makeQuestion_Widget( | ||
263 | uiTextCaution_ColorEscape "${heading.ident.delete}", | ||
264 | format_CStr(cstr_Lang("dlg.confirm.ident.delete"), | ||
265 | uiTextAction_ColorEscape, | ||
266 | cstr_String(&item->label), | ||
267 | uiText_ColorEscape), | ||
268 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, | ||
269 | { uiTextCaution_ColorEscape "${dlg.ident.delete}", | ||
270 | 0, | ||
271 | 0, | ||
272 | format_CStr("!ident.delete confirm:0 ptr:%p", d) } }, | ||
273 | 2); | ||
274 | return iTrue; | ||
275 | } | ||
276 | deleteIdentity_GmCerts(certs_App(), menuIdentity_CertListWidget_(d)); | ||
277 | postCommand_App("idents.changed"); | ||
278 | return iTrue; | ||
279 | } | ||
280 | } | ||
281 | if (ev->type == SDL_MOUSEMOTION && !isVisible_Widget(d->menu)) { | ||
282 | const iInt2 mouse = init_I2(ev->motion.x, ev->motion.y); | ||
283 | /* Update cursor. */ | ||
284 | if (contains_Widget(w, mouse)) { | ||
285 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); | ||
286 | } | ||
287 | else if (d->contextIndex != iInvalidPos) { | ||
288 | invalidateItem_ListWidget(&d->list, d->contextIndex); | ||
289 | d->contextIndex = iInvalidPos; | ||
290 | } | ||
291 | } | ||
292 | /* Update context menu items. */ | ||
293 | if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT) { | ||
294 | d->contextItem = NULL; | ||
295 | if (!isVisible_Widget(d->menu)) { | ||
296 | updateMouseHover_ListWidget(&d->list); | ||
297 | } | ||
298 | if (constHoverItem_ListWidget(&d->list) || isVisible_Widget(d->menu)) { | ||
299 | d->contextItem = hoverItem_ListWidget(&d->list); | ||
300 | /* Context is drawn in hover state. */ | ||
301 | if (d->contextIndex != iInvalidPos) { | ||
302 | invalidateItem_ListWidget(&d->list, d->contextIndex); | ||
303 | } | ||
304 | d->contextIndex = hoverItemIndex_ListWidget(&d->list); | ||
305 | updateContextMenu_CertListWidget_(d); | ||
306 | /* TODO: Some callback-based mechanism would be nice for updating menus right | ||
307 | before they open? At least move these to `updateContextMenu_ */ | ||
308 | const iGmIdentity *ident = constHoverIdentity_CertListWidget(d); | ||
309 | const iString * docUrl = url_DocumentWidget(document_App()); | ||
310 | iForEach(ObjectList, i, children_Widget(d->menu)) { | ||
311 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
312 | iLabelWidget *menuItem = i.object; | ||
313 | const char * cmdItem = cstr_String(command_LabelWidget(menuItem)); | ||
314 | if (equal_Command(cmdItem, "ident.use")) { | ||
315 | const iBool cmdUse = arg_Command(cmdItem) != 0; | ||
316 | const iBool cmdClear = argLabel_Command(cmdItem, "clear") != 0; | ||
317 | setFlags_Widget( | ||
318 | as_Widget(menuItem), | ||
319 | disabled_WidgetFlag, | ||
320 | (cmdClear && !isUsed_GmIdentity(ident)) || | ||
321 | (!cmdClear && cmdUse && isUsedOn_GmIdentity(ident, docUrl)) || | ||
322 | (!cmdClear && !cmdUse && !isUsedOn_GmIdentity(ident, docUrl))); | ||
323 | } | ||
324 | } | ||
325 | } | ||
326 | } | ||
327 | if (hoverItem_ListWidget(&d->list) || isVisible_Widget(d->menu)) { | ||
328 | processContextMenuEvent_Widget(d->menu, ev, {}); | ||
329 | } | ||
330 | } | ||
331 | return ((iWidgetClass *) class_Widget(w)->super)->processEvent(w, ev); | ||
332 | } | ||
333 | |||
334 | static void draw_CertListWidget_(const iCertListWidget *d) { | ||
335 | const iWidget *w = constAs_Widget(d); | ||
336 | ((iWidgetClass *) class_Widget(w)->super)->draw(w); | ||
337 | } | ||
338 | |||
339 | static void draw_CertItem_(const iCertItem *d, iPaint *p, iRect itemRect, | ||
340 | const iListWidget *list) { | ||
341 | const iCertListWidget *certList = (const iCertListWidget *) list; | ||
342 | const iBool isMenuVisible = isVisible_Widget(certList->menu); | ||
343 | const iBool isDragging = constDragItem_ListWidget(list) == d; | ||
344 | const iBool isPressing = isMouseDown_ListWidget(list) && !isDragging; | ||
345 | const iBool isHover = | ||
346 | (!isMenuVisible && | ||
347 | isHover_Widget(constAs_Widget(list)) && | ||
348 | constHoverItem_ListWidget(list) == d) || | ||
349 | (isMenuVisible && certList->contextItem == d) || | ||
350 | isDragging; | ||
351 | const int itemHeight = height_Rect(itemRect); | ||
352 | const int iconColor = isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId) | ||
353 | : uiIcon_ColorId; | ||
354 | const int altIconColor = isPressing ? uiTextPressed_ColorId : uiTextCaution_ColorId; | ||
355 | const int font = certList->itemFonts[d->isBold ? 1 : 0]; | ||
356 | int bg = uiBackgroundSidebar_ColorId; | ||
357 | if (isHover) { | ||
358 | bg = isPressing ? uiBackgroundPressed_ColorId | ||
359 | : uiBackgroundFramelessHover_ColorId; | ||
360 | fillRect_Paint(p, itemRect, bg); | ||
361 | } | ||
362 | else if (d->listItem.isSelected) { | ||
363 | bg = uiBackgroundUnfocusedSelection_ColorId; | ||
364 | fillRect_Paint(p, itemRect, bg); | ||
365 | } | ||
366 | // iInt2 pos = itemRect.pos; | ||
367 | const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) | ||
368 | : uiTextStrong_ColorId; | ||
369 | const iBool isUsedOnDomain = (d->indent != 0); | ||
370 | iString icon; | ||
371 | initUnicodeN_String(&icon, &d->icon, 1); | ||
372 | iInt2 cPos = topLeft_Rect(itemRect); | ||
373 | const int indent = 1.4f * lineHeight_Text(font); | ||
374 | addv_I2(&cPos, | ||
375 | init_I2(3 * gap_UI, | ||
376 | (itemHeight - lineHeight_Text(uiLabel_FontId) * 2 - lineHeight_Text(font)) / | ||
377 | 2)); | ||
378 | const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId | ||
379 | : uiTextFramelessHover_ColorId) | ||
380 | : uiTextDim_ColorId; | ||
381 | if (!d->listItem.isSelected && !isUsedOnDomain) { | ||
382 | drawOutline_Text(font, cPos, metaFg, none_ColorId, range_String(&icon)); | ||
383 | } | ||
384 | drawRange_Text(font, | ||
385 | cPos, | ||
386 | d->listItem.isSelected ? iconColor | ||
387 | : isUsedOnDomain ? altIconColor | ||
388 | : uiBackgroundSidebar_ColorId, | ||
389 | range_String(&icon)); | ||
390 | deinit_String(&icon); | ||
391 | drawRange_Text(d->listItem.isSelected ? certList->itemFonts[1] : font, | ||
392 | add_I2(cPos, init_I2(indent, 0)), | ||
393 | fg, | ||
394 | range_String(&d->label)); | ||
395 | drawRange_Text(uiLabel_FontId, | ||
396 | add_I2(cPos, init_I2(indent, lineHeight_Text(font))), | ||
397 | metaFg, | ||
398 | range_String(&d->meta)); | ||
399 | } | ||
400 | |||
401 | void init_CertListWidget(iCertListWidget *d) { | ||
402 | iWidget *w = as_Widget(d); | ||
403 | init_ListWidget(&d->list); | ||
404 | setId_Widget(w, "certlist"); | ||
405 | setBackgroundColor_Widget(w, none_ColorId); | ||
406 | d->itemFonts[0] = uiContent_FontId; | ||
407 | d->itemFonts[1] = uiContentBold_FontId; | ||
408 | #if defined (iPlatformMobile) | ||
409 | if (deviceType_App() == phone_AppDeviceType) { | ||
410 | d->itemFonts[0] = uiLabelBig_FontId; | ||
411 | d->itemFonts[1] = uiLabelBigBold_FontId; | ||
412 | } | ||
413 | #endif | ||
414 | updateItemHeight_CertListWidget(d); | ||
415 | d->menu = NULL; | ||
416 | d->contextItem = NULL; | ||
417 | d->contextIndex = iInvalidPos; | ||
418 | } | ||
419 | |||
420 | void updateItemHeight_CertListWidget(iCertListWidget *d) { | ||
421 | setItemHeight_ListWidget(&d->list, 3.5f * lineHeight_Text(d->itemFonts[0])); | ||
422 | } | ||
423 | |||
424 | iBool updateItems_CertListWidget(iCertListWidget *d) { | ||
425 | clear_ListWidget(&d->list); | ||
426 | destroy_Widget(d->menu); | ||
427 | d->menu = NULL; | ||
428 | const iString *tabUrl = url_DocumentWidget(document_App()); | ||
429 | const iRangecc tabHost = urlHost_String(tabUrl); | ||
430 | iBool haveItems = iFalse; | ||
431 | iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) { | ||
432 | const iGmIdentity *ident = i.ptr; | ||
433 | iCertItem *item = new_CertItem(); | ||
434 | item->id = (uint32_t) index_PtrArrayConstIterator(&i); | ||
435 | item->icon = 0x1f464; /* person */ | ||
436 | set_String(&item->label, name_GmIdentity(ident)); | ||
437 | iDate until; | ||
438 | validUntil_TlsCertificate(ident->cert, &until); | ||
439 | const iBool isActive = isUsedOn_GmIdentity(ident, tabUrl); | ||
440 | format_String(&item->meta, | ||
441 | "%s", | ||
442 | isActive ? cstr_Lang("ident.using") | ||
443 | : isUsed_GmIdentity(ident) | ||
444 | ? formatCStrs_Lang("ident.usedonurls.n", size_StringSet(ident->useUrls)) | ||
445 | : cstr_Lang("ident.notused")); | ||
446 | const char *expiry = | ||
447 | ident->flags & temporary_GmIdentityFlag | ||
448 | ? cstr_Lang("ident.temporary") | ||
449 | : cstrCollect_String(format_Date(&until, cstr_Lang("ident.expiry"))); | ||
450 | if (isEmpty_String(&ident->notes)) { | ||
451 | appendFormat_String(&item->meta, "\n%s", expiry); | ||
452 | } | ||
453 | else { | ||
454 | appendFormat_String(&item->meta, | ||
455 | " \u2014 %s\n%s%s", | ||
456 | expiry, | ||
457 | escape_Color(uiHeading_ColorId), | ||
458 | cstr_String(&ident->notes)); | ||
459 | } | ||
460 | item->listItem.isSelected = isActive; | ||
461 | if (!isActive && isUsedOnDomain_GmIdentity(ident, tabHost)) { | ||
462 | item->indent = 1; /* will be highlighted */ | ||
463 | } | ||
464 | addItem_ListWidget(&d->list, item); | ||
465 | haveItems = iTrue; | ||
466 | iRelease(item); | ||
467 | } | ||
468 | return haveItems; | ||
469 | } | ||
470 | |||
471 | void deinit_CertListWidget(iCertListWidget *d) { | ||
472 | iUnused(d); | ||
473 | } | ||
474 | |||
475 | const iGmIdentity *constHoverIdentity_CertListWidget(const iCertListWidget *d) { | ||
476 | const iCertItem *hoverItem = constHoverItem_ListWidget(&d->list); | ||
477 | if (hoverItem) { | ||
478 | return identity_GmCerts(certs_App(), hoverItem->id); | ||
479 | } | ||
480 | return NULL; | ||
481 | } | ||
482 | |||
483 | iGmIdentity *hoverIdentity_CertListWidget(const iCertListWidget *d) { | ||
484 | return iConstCast(iGmIdentity *, constHoverIdentity_CertListWidget(d)); | ||
485 | } | ||
486 | |||
487 | iBeginDefineSubclass(CertListWidget, ListWidget) | ||
488 | .processEvent = (iAny *) processEvent_CertListWidget_, | ||
489 | .draw = (iAny *) draw_CertListWidget_, | ||
490 | iEndDefineSubclass(CertListWidget) | ||
diff --git a/src/ui/certlistwidget.h b/src/ui/certlistwidget.h new file mode 100644 index 00000000..2e5f6247 --- /dev/null +++ b/src/ui/certlistwidget.h | |||
@@ -0,0 +1,40 @@ | |||
1 | /* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #pragma once | ||
24 | |||
25 | #include "listwidget.h" | ||
26 | |||
27 | iDeclareType(CertListWidget) | ||
28 | |||
29 | typedef iListWidgetClass iCertListWidgetClass; | ||
30 | extern iCertListWidgetClass Class_CertListWidget; | ||
31 | |||
32 | iDeclareObjectConstruction(CertListWidget) | ||
33 | |||
34 | iDeclareType(GmIdentity) | ||
35 | |||
36 | const iGmIdentity * constHoverIdentity_CertListWidget(const iCertListWidget *); | ||
37 | iGmIdentity * hoverIdentity_CertListWidget (const iCertListWidget *); | ||
38 | |||
39 | iBool updateItems_CertListWidget (iCertListWidget *); /* returns False is empty */ | ||
40 | void updateItemHeight_CertListWidget (iCertListWidget *); | ||
diff --git a/src/ui/color.c b/src/ui/color.c index 3ea98d8c..3c2f0339 100644 --- a/src/ui/color.c +++ b/src/ui/color.c | |||
@@ -90,8 +90,8 @@ void setThemePalette_Color(enum iColorTheme theme) { | |||
90 | const int accentLo = (prefs->accent == cyan_ColorAccent ? teal_ColorId : brown_ColorId); | 90 | const int accentLo = (prefs->accent == cyan_ColorAccent ? teal_ColorId : brown_ColorId); |
91 | const int altAccentHi = (prefs->accent == cyan_ColorAccent ? orange_ColorId : cyan_ColorId); | 91 | const int altAccentHi = (prefs->accent == cyan_ColorAccent ? orange_ColorId : cyan_ColorId); |
92 | const int altAccentLo = (prefs->accent == cyan_ColorAccent ? brown_ColorId : teal_ColorId); | 92 | const int altAccentLo = (prefs->accent == cyan_ColorAccent ? brown_ColorId : teal_ColorId); |
93 | const iColor accentMid = mix_Color(get_Color(accentHi), get_Color(accentLo), 0.5f); | 93 | //const iColor accentMid = mix_Color(get_Color(accentHi), get_Color(accentLo), 0.5f); |
94 | const iColor altAccentMid = mix_Color(get_Color(altAccentHi), get_Color(altAccentLo), 0.5f); | 94 | //const iColor altAccentMid = mix_Color(get_Color(altAccentHi), get_Color(altAccentLo), 0.5f); |
95 | switch (theme) { | 95 | switch (theme) { |
96 | case pureBlack_ColorTheme: { | 96 | case pureBlack_ColorTheme: { |
97 | copy_(uiBackground_ColorId, black_ColorId); | 97 | copy_(uiBackground_ColorId, black_ColorId); |
@@ -832,7 +832,7 @@ void ansiColors_Color(iRangecc escapeSequence, int fgDefault, int bgDefault, | |||
832 | int rgb[3] = { 0, 0, 0 }; | 832 | int rgb[3] = { 0, 0, 0 }; |
833 | iForIndices(i, rgb) { | 833 | iForIndices(i, rgb) { |
834 | if (ch >= escapeSequence.end) break; | 834 | if (ch >= escapeSequence.end) break; |
835 | rgb[i] = strtoul(ch + 1, &endPtr, 10); | 835 | rgb[i] = (int) strtoul(ch + 1, &endPtr, 10); |
836 | ch = endPtr; | 836 | ch = endPtr; |
837 | } | 837 | } |
838 | dst->r = iClamp(rgb[0], 0, 255); | 838 | dst->r = iClamp(rgb[0], 0, 255); |
diff --git a/src/ui/color.h b/src/ui/color.h index 0b3f8bed..24f9e713 100644 --- a/src/ui/color.h +++ b/src/ui/color.h | |||
@@ -136,7 +136,7 @@ enum iColorId { | |||
136 | tmBackgroundAltText_ColorId, /* derived from other theme colors */ | 136 | tmBackgroundAltText_ColorId, /* derived from other theme colors */ |
137 | tmFrameAltText_ColorId, /* derived from other theme colors */ | 137 | tmFrameAltText_ColorId, /* derived from other theme colors */ |
138 | tmBackgroundOpenLink_ColorId, /* derived from other theme colors */ | 138 | tmBackgroundOpenLink_ColorId, /* derived from other theme colors */ |
139 | tmFrameOpenLink_ColorId, /* derived from other theme colors */ | 139 | tmLinkFeedEntryDate_ColorId, /* derived from other theme colors */ |
140 | tmLinkCustomIconVisited_ColorId, /* derived from other theme colors */ | 140 | tmLinkCustomIconVisited_ColorId, /* derived from other theme colors */ |
141 | tmBadLink_ColorId, | 141 | tmBadLink_ColorId, |
142 | 142 | ||
diff --git a/src/ui/command.c b/src/ui/command.c index 3ae0f0c9..a4868ca9 100644 --- a/src/ui/command.c +++ b/src/ui/command.c | |||
@@ -26,6 +26,34 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
26 | #include <the_Foundation/string.h> | 26 | #include <the_Foundation/string.h> |
27 | #include <ctype.h> | 27 | #include <ctype.h> |
28 | 28 | ||
29 | iDeclareType(Token) | ||
30 | |||
31 | #define maxLen_Token 64 | ||
32 | |||
33 | struct Impl_Token { | ||
34 | char buf[64]; | ||
35 | size_t size; | ||
36 | }; | ||
37 | |||
38 | static void init_Token(iToken *d, const char *label) { | ||
39 | const size_t len = strlen(label); | ||
40 | iAssert(len < sizeof(d->buf) - 3); | ||
41 | d->buf[0] = ' '; | ||
42 | memcpy(d->buf + 1, label, len); | ||
43 | d->buf[1 + len] = ':'; | ||
44 | d->buf[1 + len + 1] = 0; | ||
45 | d->size = len + 2; | ||
46 | } | ||
47 | |||
48 | static iRangecc find_Token(const iToken *d, const char *cmd) { | ||
49 | iRangecc range = iNullRange; | ||
50 | range.start = strstr(cmd, d->buf); | ||
51 | if (range.start) { | ||
52 | range.end = range.start + d->size; | ||
53 | } | ||
54 | return range; | ||
55 | } | ||
56 | |||
29 | iBool equal_Command(const char *cmdWithArgs, const char *cmd) { | 57 | iBool equal_Command(const char *cmdWithArgs, const char *cmd) { |
30 | if (strchr(cmdWithArgs, ':')) { | 58 | if (strchr(cmdWithArgs, ':')) { |
31 | return startsWith_CStr(cmdWithArgs, cmd) && cmdWithArgs[strlen(cmd)] == ' '; | 59 | return startsWith_CStr(cmdWithArgs, cmd) && cmdWithArgs[strlen(cmd)] == ' '; |
@@ -33,15 +61,12 @@ iBool equal_Command(const char *cmdWithArgs, const char *cmd) { | |||
33 | return equal_CStr(cmdWithArgs, cmd); | 61 | return equal_CStr(cmdWithArgs, cmd); |
34 | } | 62 | } |
35 | 63 | ||
36 | static const iString *tokenString_(const char *label) { | ||
37 | return collectNewFormat_String(" %s:", label); | ||
38 | } | ||
39 | |||
40 | int argLabel_Command(const char *cmd, const char *label) { | 64 | int argLabel_Command(const char *cmd, const char *label) { |
41 | const iString *tok = tokenString_(label); | 65 | iToken tok; |
42 | const char *ptr = strstr(cmd, cstr_String(tok)); | 66 | init_Token(&tok, label); |
43 | if (ptr) { | 67 | iRangecc ptr = find_Token(&tok, cmd); |
44 | return atoi(ptr + size_String(tok)); | 68 | if (ptr.start) { |
69 | return atoi(ptr.end); | ||
45 | } | 70 | } |
46 | return 0; | 71 | return 0; |
47 | } | 72 | } |
@@ -51,19 +76,21 @@ int arg_Command(const char *cmd) { | |||
51 | } | 76 | } |
52 | 77 | ||
53 | uint32_t argU32Label_Command(const char *cmd, const char *label) { | 78 | uint32_t argU32Label_Command(const char *cmd, const char *label) { |
54 | const iString *tok = tokenString_(label); | 79 | iToken tok; |
55 | const char *ptr = strstr(cmd, cstr_String(tok)); | 80 | init_Token(&tok, label); |
56 | if (ptr) { | 81 | const iRangecc ptr = find_Token(&tok, cmd); |
57 | return strtoul(ptr + size_String(tok), NULL, 10); | 82 | if (ptr.start) { |
83 | return (uint32_t) strtoul(ptr.end, NULL, 10); | ||
58 | } | 84 | } |
59 | return 0; | 85 | return 0; |
60 | } | 86 | } |
61 | 87 | ||
62 | float argfLabel_Command(const char *cmd, const char *label) { | 88 | float argfLabel_Command(const char *cmd, const char *label) { |
63 | const iString *tok = tokenString_(label); | 89 | iToken tok; |
64 | const char *ptr = strstr(cmd, cstr_String(tok)); | 90 | init_Token(&tok, label); |
65 | if (ptr) { | 91 | const iRangecc ptr = find_Token(&tok, cmd); |
66 | return strtof(ptr + size_String(tok), NULL); | 92 | if (ptr.start) { |
93 | return strtof(ptr.end, NULL); | ||
67 | } | 94 | } |
68 | return 0.0f; | 95 | return 0.0f; |
69 | } | 96 | } |
@@ -77,11 +104,12 @@ float argf_Command(const char *cmd) { | |||
77 | } | 104 | } |
78 | 105 | ||
79 | void *pointerLabel_Command(const char *cmd, const char *label) { | 106 | void *pointerLabel_Command(const char *cmd, const char *label) { |
80 | const iString *tok = tokenString_(label); | 107 | iToken tok; |
81 | const char *ptr = strstr(cmd, cstr_String(tok)); | 108 | init_Token(&tok, label); |
82 | if (ptr) { | 109 | const iRangecc ptr = find_Token(&tok, cmd); |
110 | if (ptr.start) { | ||
83 | void *val = NULL; | 111 | void *val = NULL; |
84 | sscanf(ptr + size_String(tok), "%p", &val); | 112 | sscanf(ptr.end, "%p", &val); |
85 | return val; | 113 | return val; |
86 | } | 114 | } |
87 | return NULL; | 115 | return NULL; |
@@ -92,10 +120,11 @@ void *pointer_Command(const char *cmd) { | |||
92 | } | 120 | } |
93 | 121 | ||
94 | const char *suffixPtr_Command(const char *cmd, const char *label) { | 122 | const char *suffixPtr_Command(const char *cmd, const char *label) { |
95 | const iString *tok = tokenString_(label); | 123 | iToken tok; |
96 | const char *ptr = strstr(cmd, cstr_String(tok)); | 124 | init_Token(&tok, label); |
97 | if (ptr) { | 125 | const iRangecc ptr = find_Token(&tok, cmd); |
98 | return ptr + size_String(tok); | 126 | if (ptr.start) { |
127 | return ptr.end; | ||
99 | } | 128 | } |
100 | return NULL; | 129 | return NULL; |
101 | } | 130 | } |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 46af5fcd..fdb55232 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -20,8 +20,8 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
22 | 22 | ||
23 | /* TODO: This file is a little (!) too large. DocumentWidget could be split into | 23 | /* TODO: Move DocumentView into a source file of its own. Consider cleaning up the network |
24 | a couple of smaller objects. One for rendering the document, for instance. */ | 24 | request handling. */ |
25 | 25 | ||
26 | #include "documentwidget.h" | 26 | #include "documentwidget.h" |
27 | 27 | ||
@@ -41,6 +41,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
41 | #include "inputwidget.h" | 41 | #include "inputwidget.h" |
42 | #include "keys.h" | 42 | #include "keys.h" |
43 | #include "labelwidget.h" | 43 | #include "labelwidget.h" |
44 | #include "linkinfo.h" | ||
44 | #include "media.h" | 45 | #include "media.h" |
45 | #include "paint.h" | 46 | #include "paint.h" |
46 | #include "periodic.h" | 47 | #include "periodic.h" |
@@ -55,6 +56,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
55 | #include "visbuf.h" | 56 | #include "visbuf.h" |
56 | #include "visited.h" | 57 | #include "visited.h" |
57 | 58 | ||
59 | #if defined (iPlatformAppleDesktop) | ||
60 | # include "macos.h" | ||
61 | #endif | ||
58 | #if defined (iPlatformAppleMobile) | 62 | #if defined (iPlatformAppleMobile) |
59 | # include "ios.h" | 63 | # include "ios.h" |
60 | #endif | 64 | #endif |
@@ -161,10 +165,10 @@ enum iDrawBufsFlag { | |||
161 | }; | 165 | }; |
162 | 166 | ||
163 | struct Impl_DrawBufs { | 167 | struct Impl_DrawBufs { |
164 | int flags; | 168 | int flags; |
165 | SDL_Texture * sideIconBuf; | 169 | SDL_Texture *sideIconBuf; |
166 | iTextBuf * timestampBuf; | 170 | iTextBuf *timestampBuf; |
167 | uint32_t lastRenderTime; | 171 | uint32_t lastRenderTime; |
168 | }; | 172 | }; |
169 | 173 | ||
170 | static void init_DrawBufs(iDrawBufs *d) { | 174 | static void init_DrawBufs(iDrawBufs *d) { |
@@ -198,16 +202,6 @@ static void visBufInvalidated_(iVisBuf *d, size_t index) { | |||
198 | 202 | ||
199 | /*----------------------------------------------------------------------------------------------*/ | 203 | /*----------------------------------------------------------------------------------------------*/ |
200 | 204 | ||
201 | static void animate_DocumentWidget_ (void *ticker); | ||
202 | static void animateMedia_DocumentWidget_ (iDocumentWidget *d); | ||
203 | static void updateSideIconBuf_DocumentWidget_ (const iDocumentWidget *d); | ||
204 | static void prerender_DocumentWidget_ (iAny *); | ||
205 | static void scrollBegan_DocumentWidget_ (iAnyObject *, int, uint32_t); | ||
206 | |||
207 | static const int smoothDuration_DocumentWidget_(enum iScrollType type) { | ||
208 | return 600 /* milliseconds */ * scrollSpeedFactor_Prefs(prefs_App(), type); | ||
209 | } | ||
210 | |||
211 | enum iRequestState { | 205 | enum iRequestState { |
212 | blank_RequestState, | 206 | blank_RequestState, |
213 | fetching_RequestState, | 207 | fetching_RequestState, |
@@ -229,8 +223,14 @@ enum iDocumentWidgetFlag { | |||
229 | movingSelectMarkEnd_DocumentWidgetFlag = iBit(11), | 223 | movingSelectMarkEnd_DocumentWidgetFlag = iBit(11), |
230 | otherRootByDefault_DocumentWidgetFlag = iBit(12), /* links open to other root by default */ | 224 | otherRootByDefault_DocumentWidgetFlag = iBit(12), /* links open to other root by default */ |
231 | urlChanged_DocumentWidgetFlag = iBit(13), | 225 | urlChanged_DocumentWidgetFlag = iBit(13), |
232 | openedFromSidebar_DocumentWidgetFlag = iBit(14), | 226 | drawDownloadCounter_DocumentWidgetFlag = iBit(14), |
233 | drawDownloadCounter_DocumentWidgetFlag = iBit(15), | 227 | fromCache_DocumentWidgetFlag = iBit(15), /* don't write anything to cache */ |
228 | animationPlaceholder_DocumentWidgetFlag = iBit(16), /* avoid slow operations */ | ||
229 | invalidationPending_DocumentWidgetFlag = iBit(17), /* invalidate as soon as convenient */ | ||
230 | leftWheelSwipe_DocumentWidgetFlag = iBit(18), /* swipe state flags are used on desktop */ | ||
231 | rightWheelSwipe_DocumentWidgetFlag = iBit(19), | ||
232 | eitherWheelSwipe_DocumentWidgetFlag = leftWheelSwipe_DocumentWidgetFlag | | ||
233 | rightWheelSwipe_DocumentWidgetFlag, | ||
234 | }; | 234 | }; |
235 | 235 | ||
236 | enum iDocumentLinkOrdinalMode { | 236 | enum iDocumentLinkOrdinalMode { |
@@ -238,10 +238,44 @@ enum iDocumentLinkOrdinalMode { | |||
238 | homeRow_DocumentLinkOrdinalMode, | 238 | homeRow_DocumentLinkOrdinalMode, |
239 | }; | 239 | }; |
240 | 240 | ||
241 | enum iWheelSwipeState { | ||
242 | none_WheelSwipeState, | ||
243 | direct_WheelSwipeState, | ||
244 | }; | ||
245 | |||
246 | /* TODO: DocumentView is supposed to be useful on its own; move to a separate source file. */ | ||
247 | iDeclareType(DocumentView) | ||
248 | |||
249 | struct Impl_DocumentView { | ||
250 | iDocumentWidget *owner; /* TODO: Convert to an abstract provider of metrics? */ | ||
251 | iGmDocument * doc; | ||
252 | int pageMargin; | ||
253 | iSmoothScroll scrollY; | ||
254 | iAnim sideOpacity; | ||
255 | iAnim altTextOpacity; | ||
256 | iGmRunRange visibleRuns; | ||
257 | iPtrArray visibleLinks; | ||
258 | iPtrArray visiblePre; | ||
259 | iPtrArray visibleMedia; /* currently playing audio / ongoing downloads */ | ||
260 | iPtrArray visibleWideRuns; /* scrollable blocks; TODO: merge into `visiblePre` */ | ||
261 | const iGmRun * hoverPre; /* for clicking */ | ||
262 | const iGmRun * hoverAltPre; /* for drawing alt text */ | ||
263 | const iGmRun * hoverLink; | ||
264 | iArray wideRunOffsets; | ||
265 | iAnim animWideRunOffset; | ||
266 | uint16_t animWideRunId; | ||
267 | iGmRunRange animWideRunRange; | ||
268 | iDrawBufs * drawBufs; /* dynamic state for drawing */ | ||
269 | iVisBuf * visBuf; | ||
270 | iVisBufMeta * visBufMeta; | ||
271 | iGmRunRange renderRuns; | ||
272 | iPtrSet * invalidRuns; | ||
273 | }; | ||
274 | |||
241 | struct Impl_DocumentWidget { | 275 | struct Impl_DocumentWidget { |
242 | iWidget widget; | 276 | iWidget widget; |
243 | int flags; /* internal behavior, see enum iDocumentWidgetFlag */ | 277 | int flags; /* internal behavior, see enum iDocumentWidgetFlag */ |
244 | 278 | ||
245 | /* User interface: */ | 279 | /* User interface: */ |
246 | enum iDocumentLinkOrdinalMode ordinalMode; | 280 | enum iDocumentLinkOrdinalMode ordinalMode; |
247 | size_t ordinalBase; | 281 | size_t ordinalBase; |
@@ -251,19 +285,22 @@ struct Impl_DocumentWidget { | |||
251 | const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ | 285 | const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ |
252 | float grabbedStartVolume; | 286 | float grabbedStartVolume; |
253 | int mediaTimer; | 287 | int mediaTimer; |
254 | const iGmRun * hoverPre; /* for clicking */ | ||
255 | const iGmRun * hoverAltPre; /* for drawing alt text */ | ||
256 | const iGmRun * hoverLink; | ||
257 | const iGmRun * contextLink; | 288 | const iGmRun * contextLink; |
258 | iClick click; | 289 | iClick click; |
259 | iInt2 contextPos; /* coordinates of latest right click */ | 290 | iInt2 contextPos; /* coordinates of latest right click */ |
260 | int pinchZoomInitial; | 291 | int pinchZoomInitial; |
261 | int pinchZoomPosted; | 292 | int pinchZoomPosted; |
293 | float swipeSpeed; /* points/sec */ | ||
294 | uint32_t lastSwipeTime; | ||
295 | int wheelSwipeDistance; | ||
296 | enum iWheelSwipeState wheelSwipeState; | ||
262 | iString pendingGotoHeading; | 297 | iString pendingGotoHeading; |
263 | 298 | iString linePrecedingLink; | |
299 | |||
264 | /* Network request: */ | 300 | /* Network request: */ |
265 | enum iRequestState state; | 301 | enum iRequestState state; |
266 | iGmRequest * request; | 302 | iGmRequest * request; |
303 | iGmLinkId requestLinkId; /* ID of the link that initiated the current request */ | ||
267 | iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ | 304 | iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ |
268 | int certFlags; | 305 | int certFlags; |
269 | iBlock * certFingerprint; | 306 | iBlock * certFingerprint; |
@@ -271,7 +308,7 @@ struct Impl_DocumentWidget { | |||
271 | iString * certSubject; | 308 | iString * certSubject; |
272 | int redirectCount; | 309 | int redirectCount; |
273 | iObjectList * media; /* inline media requests */ | 310 | iObjectList * media; /* inline media requests */ |
274 | 311 | ||
275 | /* Document: */ | 312 | /* Document: */ |
276 | iPersistentDocumentState mod; | 313 | iPersistentDocumentState mod; |
277 | iString * titleUser; | 314 | iString * titleUser; |
@@ -281,31 +318,14 @@ struct Impl_DocumentWidget { | |||
281 | iBlock sourceContent; /* original content as received, for saving; set on request finish */ | 318 | iBlock sourceContent; /* original content as received, for saving; set on request finish */ |
282 | iTime sourceTime; | 319 | iTime sourceTime; |
283 | iGempub * sourceGempub; /* NULL unless the page is Gempub content */ | 320 | iGempub * sourceGempub; /* NULL unless the page is Gempub content */ |
284 | iGmDocument * doc; | ||
285 | iBanner * banner; | 321 | iBanner * banner; |
286 | |||
287 | /* Rendering: */ | ||
288 | int pageMargin; | ||
289 | float initNormScrollY; | 322 | float initNormScrollY; |
290 | iSmoothScroll scrollY; | 323 | |
291 | iAnim sideOpacity; | 324 | /* Rendering: */ |
292 | iAnim altTextOpacity; | 325 | iDocumentView view; |
293 | iGmRunRange visibleRuns; | 326 | iLinkInfo * linkInfo; |
294 | iPtrArray visibleLinks; | 327 | |
295 | iPtrArray visiblePre; | 328 | /* Widget structure: */ |
296 | iPtrArray visibleMedia; /* currently playing audio / ongoing downloads */ | ||
297 | iPtrArray visibleWideRuns; /* scrollable blocks; TODO: merge into `visiblePre` */ | ||
298 | iArray wideRunOffsets; | ||
299 | iAnim animWideRunOffset; | ||
300 | uint16_t animWideRunId; | ||
301 | iGmRunRange animWideRunRange; | ||
302 | iDrawBufs * drawBufs; /* dynamic state for drawing */ | ||
303 | iVisBuf * visBuf; | ||
304 | iVisBufMeta * visBufMeta; | ||
305 | iGmRunRange renderRuns; | ||
306 | iPtrSet * invalidRuns; | ||
307 | |||
308 | /* Widget structure: */ | ||
309 | iScrollWidget *scroll; | 329 | iScrollWidget *scroll; |
310 | iWidget * footerButtons; | 330 | iWidget * footerButtons; |
311 | iWidget * menu; | 331 | iWidget * menu; |
@@ -317,46 +337,159 @@ struct Impl_DocumentWidget { | |||
317 | 337 | ||
318 | iDefineObjectConstruction(DocumentWidget) | 338 | iDefineObjectConstruction(DocumentWidget) |
319 | 339 | ||
340 | /* Sorted by proximity to F and J. */ | ||
341 | static const int homeRowKeys_[] = { | ||
342 | 'f', 'd', 's', 'a', | ||
343 | 'j', 'k', 'l', | ||
344 | 'r', 'e', 'w', 'q', | ||
345 | 'u', 'i', 'o', 'p', | ||
346 | 'v', 'c', 'x', 'z', | ||
347 | 'm', 'n', | ||
348 | 'g', 'h', | ||
349 | 'b', | ||
350 | 't', 'y', | ||
351 | }; | ||
320 | static int docEnum_ = 0; | 352 | static int docEnum_ = 0; |
321 | 353 | ||
322 | void init_DocumentWidget(iDocumentWidget *d) { | 354 | static void animate_DocumentWidget_ (void *ticker); |
323 | iWidget *w = as_Widget(d); | 355 | static void animateMedia_DocumentWidget_ (iDocumentWidget *d); |
324 | init_Widget(w); | 356 | static void updateSideIconBuf_DocumentWidget_ (const iDocumentWidget *d); |
325 | setId_Widget(w, format_CStr("document%03d", ++docEnum_)); | 357 | static void prerender_DocumentWidget_ (iAny *); |
326 | setFlags_Widget(w, hover_WidgetFlag | noBackground_WidgetFlag, iTrue); | 358 | static void scrollBegan_DocumentWidget_ (iAnyObject *, int, uint32_t); |
327 | if (deviceType_App() != desktop_AppDeviceType) { | 359 | static void refreshWhileScrolling_DocumentWidget_ (iAny *); |
328 | setFlags_Widget(w, leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag | | 360 | |
329 | horizontalOffset_WidgetFlag, iTrue); | 361 | /* TODO: The following methods are called from DocumentView, which goes the wrong way. */ |
362 | |||
363 | static iRangecc selectMark_DocumentWidget_(const iDocumentWidget *d) { | ||
364 | /* Normalize so start < end. */ | ||
365 | iRangecc norm = d->selectMark; | ||
366 | if (norm.start > norm.end) { | ||
367 | iSwap(const char *, norm.start, norm.end); | ||
330 | } | 368 | } |
331 | init_PersistentDocumentState(&d->mod); | 369 | return norm; |
332 | d->flags = 0; | 370 | } |
333 | d->phoneToolbar = NULL; | 371 | |
334 | d->footerButtons = NULL; | 372 | static int phoneToolbarHeight_DocumentWidget_(const iDocumentWidget *d) { |
335 | iZap(d->certExpiry); | 373 | if (!d->phoneToolbar) { |
336 | d->certFingerprint = new_Block(0); | 374 | return 0; |
337 | d->certFlags = 0; | 375 | } |
338 | d->certSubject = new_String(); | 376 | const iWidget *w = constAs_Widget(d); |
339 | d->state = blank_RequestState; | 377 | return bottom_Rect(rect_Root(w->root)) - top_Rect(boundsWithoutVisualOffset_Widget(d->phoneToolbar)); |
340 | d->titleUser = new_String(); | 378 | } |
341 | d->request = NULL; | 379 | |
342 | d->isRequestUpdated = iFalse; | 380 | static int footerHeight_DocumentWidget_(const iDocumentWidget *d) { |
343 | d->media = new_ObjectList(); | 381 | int hgt = height_Widget(d->footerButtons); |
382 | if (isPortraitPhone_App()) { | ||
383 | hgt += phoneToolbarHeight_DocumentWidget_(d); | ||
384 | } | ||
385 | return hgt; | ||
386 | } | ||
387 | |||
388 | static iBool isHoverAllowed_DocumentWidget_(const iDocumentWidget *d) { | ||
389 | if (!isHover_Widget(d)) { | ||
390 | return iFalse; | ||
391 | } | ||
392 | if (!(d->state == ready_RequestState || d->state == receivedPartialResponse_RequestState)) { | ||
393 | return iFalse; | ||
394 | } | ||
395 | if (d->flags & (noHoverWhileScrolling_DocumentWidgetFlag | | ||
396 | drawDownloadCounter_DocumentWidgetFlag)) { | ||
397 | return iFalse; | ||
398 | } | ||
399 | if (d->flags & pinchZoom_DocumentWidgetFlag) { | ||
400 | return iFalse; | ||
401 | } | ||
402 | if (flags_Widget(constAs_Widget(d)) & touchDrag_WidgetFlag) { | ||
403 | return iFalse; | ||
404 | } | ||
405 | if (flags_Widget(constAs_Widget(d->scroll)) & pressed_WidgetFlag) { | ||
406 | return iFalse; | ||
407 | } | ||
408 | return iTrue; | ||
409 | } | ||
410 | |||
411 | static iMediaRequest *findMediaRequest_DocumentWidget_(const iDocumentWidget *d, iGmLinkId linkId) { | ||
412 | iConstForEach(ObjectList, i, d->media) { | ||
413 | const iMediaRequest *req = (const iMediaRequest *) i.object; | ||
414 | if (req->linkId == linkId) { | ||
415 | return iConstCast(iMediaRequest *, req); | ||
416 | } | ||
417 | } | ||
418 | return NULL; | ||
419 | } | ||
420 | |||
421 | static size_t linkOrdinalFromKey_DocumentWidget_(const iDocumentWidget *d, int key) { | ||
422 | size_t ord = iInvalidPos; | ||
423 | if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) { | ||
424 | if (key >= '1' && key <= '9') { | ||
425 | return key - '1'; | ||
426 | } | ||
427 | if (key < 'a' || key > 'z') { | ||
428 | return iInvalidPos; | ||
429 | } | ||
430 | ord = key - 'a' + 9; | ||
431 | #if defined (iPlatformApple) | ||
432 | /* Skip keys that would conflict with default system shortcuts: hide, minimize, quit, close. */ | ||
433 | if (key == 'h' || key == 'm' || key == 'q' || key == 'w') { | ||
434 | return iInvalidPos; | ||
435 | } | ||
436 | if (key > 'h') ord--; | ||
437 | if (key > 'm') ord--; | ||
438 | if (key > 'q') ord--; | ||
439 | if (key > 'w') ord--; | ||
440 | #endif | ||
441 | } | ||
442 | else { | ||
443 | iForIndices(i, homeRowKeys_) { | ||
444 | if (homeRowKeys_[i] == key) { | ||
445 | return i; | ||
446 | } | ||
447 | } | ||
448 | } | ||
449 | return ord; | ||
450 | } | ||
451 | |||
452 | static iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t ord) { | ||
453 | if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) { | ||
454 | if (ord < 9) { | ||
455 | return '1' + ord; | ||
456 | } | ||
457 | #if defined (iPlatformApple) | ||
458 | if (ord < 9 + 22) { | ||
459 | int key = 'a' + ord - 9; | ||
460 | if (key >= 'h') key++; | ||
461 | if (key >= 'm') key++; | ||
462 | if (key >= 'q') key++; | ||
463 | if (key >= 'w') key++; | ||
464 | return 'A' + key - 'a'; | ||
465 | } | ||
466 | #else | ||
467 | if (ord < 9 + 26) { | ||
468 | return 'A' + ord - 9; | ||
469 | } | ||
470 | #endif | ||
471 | } | ||
472 | else { | ||
473 | if (ord < iElemCount(homeRowKeys_)) { | ||
474 | return 'A' + homeRowKeys_[ord] - 'a'; | ||
475 | } | ||
476 | } | ||
477 | return 0; | ||
478 | } | ||
479 | |||
480 | /*----------------------------------------------------------------------------------------------*/ | ||
481 | |||
482 | void init_DocumentView(iDocumentView *d) { | ||
483 | d->owner = NULL; | ||
344 | d->doc = new_GmDocument(); | 484 | d->doc = new_GmDocument(); |
345 | d->banner = new_Banner(); | 485 | d->invalidRuns = new_PtrSet(); |
346 | setOwner_Banner(d->banner, d); | 486 | d->drawBufs = new_DrawBufs(); |
347 | d->redirectCount = 0; | 487 | d->pageMargin = 5; |
348 | d->ordinalBase = 0; | 488 | d->hoverPre = NULL; |
349 | d->initNormScrollY = 0; | 489 | d->hoverAltPre = NULL; |
350 | init_SmoothScroll(&d->scrollY, w, scrollBegan_DocumentWidget_); | 490 | d->hoverLink = NULL; |
351 | d->animWideRunId = 0; | 491 | d->animWideRunId = 0; |
352 | init_Anim(&d->animWideRunOffset, 0); | 492 | init_Anim(&d->animWideRunOffset, 0); |
353 | d->selectMark = iNullRange; | ||
354 | d->foundMark = iNullRange; | ||
355 | d->pageMargin = 5; | ||
356 | d->hoverPre = NULL; | ||
357 | d->hoverAltPre = NULL; | ||
358 | d->hoverLink = NULL; | ||
359 | d->contextLink = NULL; | ||
360 | iZap(d->renderRuns); | 493 | iZap(d->renderRuns); |
361 | iZap(d->visibleRuns); | 494 | iZap(d->visibleRuns); |
362 | d->visBuf = new_VisBuf(); { | 495 | d->visBuf = new_VisBuf(); { |
@@ -367,212 +500,105 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
367 | d->visBuf->buffers[i].user = d->visBufMeta + i; | 500 | d->visBuf->buffers[i].user = d->visBufMeta + i; |
368 | } | 501 | } |
369 | } | 502 | } |
370 | d->invalidRuns = new_PtrSet(); | ||
371 | init_Anim(&d->sideOpacity, 0); | 503 | init_Anim(&d->sideOpacity, 0); |
372 | init_Anim(&d->altTextOpacity, 0); | 504 | init_Anim(&d->altTextOpacity, 0); |
373 | d->sourceStatus = none_GmStatusCode; | ||
374 | init_String(&d->sourceHeader); | ||
375 | init_String(&d->sourceMime); | ||
376 | init_Block(&d->sourceContent, 0); | ||
377 | iZap(d->sourceTime); | ||
378 | d->sourceGempub = NULL; | ||
379 | init_PtrArray(&d->visibleLinks); | 505 | init_PtrArray(&d->visibleLinks); |
380 | init_PtrArray(&d->visiblePre); | 506 | init_PtrArray(&d->visiblePre); |
381 | init_PtrArray(&d->visibleWideRuns); | 507 | init_PtrArray(&d->visibleWideRuns); |
382 | init_Array(&d->wideRunOffsets, sizeof(int)); | 508 | init_Array(&d->wideRunOffsets, sizeof(int)); |
383 | init_PtrArray(&d->visibleMedia); | 509 | init_PtrArray(&d->visibleMedia); |
384 | d->grabbedPlayer = NULL; | ||
385 | d->mediaTimer = 0; | ||
386 | init_String(&d->pendingGotoHeading); | ||
387 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | ||
388 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); | ||
389 | d->menu = NULL; /* created when clicking */ | ||
390 | d->playerMenu = NULL; | ||
391 | d->copyMenu = NULL; | ||
392 | d->drawBufs = new_DrawBufs(); | ||
393 | d->translation = NULL; | ||
394 | addChildFlags_Widget(w, | ||
395 | iClob(new_IndicatorWidget()), | ||
396 | resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag); | ||
397 | #if !defined (iPlatformAppleDesktop) /* in system menu */ | ||
398 | addAction_Widget(w, reload_KeyShortcut, "navigate.reload"); | ||
399 | addAction_Widget(w, closeTab_KeyShortcut, "tabs.close"); | ||
400 | addAction_Widget(w, SDLK_d, KMOD_PRIMARY, "bookmark.add"); | ||
401 | addAction_Widget(w, subscribeToPage_KeyModifier, "feeds.subscribe"); | ||
402 | #endif | ||
403 | addAction_Widget(w, navigateBack_KeyShortcut, "navigate.back"); | ||
404 | addAction_Widget(w, navigateForward_KeyShortcut, "navigate.forward"); | ||
405 | addAction_Widget(w, navigateParent_KeyShortcut, "navigate.parent"); | ||
406 | addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root"); | ||
407 | } | 510 | } |
408 | 511 | ||
409 | void cancelAllRequests_DocumentWidget(iDocumentWidget *d) { | 512 | void deinit_DocumentView(iDocumentView *d) { |
410 | iForEach(ObjectList, i, d->media) { | ||
411 | iMediaRequest *mr = i.object; | ||
412 | cancel_GmRequest(mr->req); | ||
413 | } | ||
414 | if (d->request) { | ||
415 | cancel_GmRequest(d->request); | ||
416 | } | ||
417 | } | ||
418 | |||
419 | void deinit_DocumentWidget(iDocumentWidget *d) { | ||
420 | cancelAllRequests_DocumentWidget(d); | ||
421 | pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); | ||
422 | removeTicker_App(animate_DocumentWidget_, d); | ||
423 | removeTicker_App(prerender_DocumentWidget_, d); | ||
424 | remove_Periodic(periodic_App(), d); | ||
425 | delete_Translation(d->translation); | ||
426 | delete_DrawBufs(d->drawBufs); | 513 | delete_DrawBufs(d->drawBufs); |
427 | delete_VisBuf(d->visBuf); | 514 | delete_VisBuf(d->visBuf); |
428 | free(d->visBufMeta); | 515 | free(d->visBufMeta); |
429 | delete_PtrSet(d->invalidRuns); | 516 | delete_PtrSet(d->invalidRuns); |
430 | iRelease(d->media); | ||
431 | iRelease(d->request); | ||
432 | delete_Gempub(d->sourceGempub); | ||
433 | deinit_String(&d->pendingGotoHeading); | ||
434 | deinit_Block(&d->sourceContent); | ||
435 | deinit_String(&d->sourceMime); | ||
436 | deinit_String(&d->sourceHeader); | ||
437 | delete_Banner(d->banner); | ||
438 | iRelease(d->doc); | ||
439 | if (d->mediaTimer) { | ||
440 | SDL_RemoveTimer(d->mediaTimer); | ||
441 | } | ||
442 | deinit_Array(&d->wideRunOffsets); | 517 | deinit_Array(&d->wideRunOffsets); |
443 | deinit_PtrArray(&d->visibleMedia); | 518 | deinit_PtrArray(&d->visibleMedia); |
444 | deinit_PtrArray(&d->visibleWideRuns); | 519 | deinit_PtrArray(&d->visibleWideRuns); |
445 | deinit_PtrArray(&d->visiblePre); | 520 | deinit_PtrArray(&d->visiblePre); |
446 | deinit_PtrArray(&d->visibleLinks); | 521 | deinit_PtrArray(&d->visibleLinks); |
447 | delete_Block(d->certFingerprint); | 522 | iReleasePtr(&d->doc); |
448 | delete_String(d->certSubject); | ||
449 | delete_String(d->titleUser); | ||
450 | deinit_PersistentDocumentState(&d->mod); | ||
451 | } | ||
452 | |||
453 | static iRangecc selectMark_DocumentWidget_(const iDocumentWidget *d) { | ||
454 | /* Normalize so start < end. */ | ||
455 | iRangecc norm = d->selectMark; | ||
456 | if (norm.start > norm.end) { | ||
457 | iSwap(const char *, norm.start, norm.end); | ||
458 | } | ||
459 | return norm; | ||
460 | } | 523 | } |
461 | 524 | ||
462 | static void enableActions_DocumentWidget_(iDocumentWidget *d, iBool enable) { | 525 | static void setOwner_DocumentView_(iDocumentView *d, iDocumentWidget *doc) { |
463 | /* Actions are invisible child widgets of the DocumentWidget. */ | 526 | d->owner = doc; |
464 | iForEach(ObjectList, i, children_Widget(d)) { | 527 | init_SmoothScroll(&d->scrollY, as_Widget(doc), scrollBegan_DocumentWidget_); |
465 | if (isAction_Widget(i.object)) { | 528 | if (deviceType_App() != desktop_AppDeviceType) { |
466 | setFlags_Widget(i.object, disabled_WidgetFlag, !enable); | 529 | d->scrollY.flags |= pullDownAction_SmoothScrollFlag; /* pull to refresh */ |
467 | } | ||
468 | } | ||
469 | } | ||
470 | |||
471 | static void setLinkNumberMode_DocumentWidget_(iDocumentWidget *d, iBool set) { | ||
472 | iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, set); | ||
473 | /* Children have priority when handling events. */ | ||
474 | enableActions_DocumentWidget_(d, !set); | ||
475 | if (d->menu) { | ||
476 | setFlags_Widget(d->menu, disabled_WidgetFlag, set); | ||
477 | } | 530 | } |
478 | } | 531 | } |
479 | 532 | ||
480 | static void resetWideRuns_DocumentWidget_(iDocumentWidget *d) { | 533 | static void resetWideRuns_DocumentView_(iDocumentView *d) { |
481 | clear_Array(&d->wideRunOffsets); | 534 | clear_Array(&d->wideRunOffsets); |
482 | d->animWideRunId = 0; | 535 | d->animWideRunId = 0; |
483 | init_Anim(&d->animWideRunOffset, 0); | 536 | init_Anim(&d->animWideRunOffset, 0); |
484 | iZap(d->animWideRunRange); | 537 | iZap(d->animWideRunRange); |
485 | } | 538 | } |
486 | 539 | ||
487 | static void requestUpdated_DocumentWidget_(iAnyObject *obj) { | 540 | static int documentWidth_DocumentView_(const iDocumentView *d) { |
488 | iDocumentWidget *d = obj; | 541 | const iWidget *w = constAs_Widget(d->owner); |
489 | const int wasUpdated = exchange_Atomic(&d->isRequestUpdated, iTrue); | ||
490 | if (!wasUpdated) { | ||
491 | postCommand_Widget(obj, | ||
492 | "document.request.updated doc:%p reqid:%u request:%p", | ||
493 | d, | ||
494 | id_GmRequest(d->request), | ||
495 | d->request); | ||
496 | } | ||
497 | } | ||
498 | |||
499 | static void requestFinished_DocumentWidget_(iAnyObject *obj) { | ||
500 | iDocumentWidget *d = obj; | ||
501 | postCommand_Widget(obj, | ||
502 | "document.request.finished doc:%p reqid:%u request:%p", | ||
503 | d, | ||
504 | id_GmRequest(d->request), | ||
505 | d->request); | ||
506 | } | ||
507 | |||
508 | static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { | ||
509 | const iWidget *w = constAs_Widget(d); | ||
510 | const iRect bounds = bounds_Widget(w); | 542 | const iRect bounds = bounds_Widget(w); |
511 | const iPrefs * prefs = prefs_App(); | 543 | const iPrefs * prefs = prefs_App(); |
512 | const int minWidth = 50 * gap_UI; /* lines must fit a word at least */ | 544 | const int minWidth = 50 * gap_UI; /* lines must fit a word at least */ |
513 | const float adjust = iClamp((float) bounds.size.x / gap_UI / 11 - 12, | 545 | const float adjust = iClamp((float) bounds.size.x / gap_UI / 11 - 12, |
514 | -1.0f, 10.0f); /* adapt to width */ | 546 | -1.0f, 10.0f); /* adapt to width */ |
515 | //printf("%f\n", adjust); fflush(stdout); | 547 | //printf("%f\n", adjust); fflush(stdout); |
516 | return iMini(iMax(minWidth, bounds.size.x - gap_UI * (d->pageMargin + adjust) * 2), | 548 | return iMini(iMax(minWidth, bounds.size.x - gap_UI * (d->pageMargin + adjust) * 2), |
517 | fontSize_UI * //emRatio_Text(paragraph_FontId) * /* dependent on avg. glyph width */ | 549 | fontSize_UI * //emRatio_Text(paragraph_FontId) * /* dependent on avg. glyph width */ |
518 | prefs->lineWidth * prefs->zoomPercent / 100); | 550 | prefs->lineWidth * prefs->zoomPercent / 100); |
519 | } | 551 | } |
520 | 552 | ||
521 | static int documentTopPad_DocumentWidget_(const iDocumentWidget *d) { | 553 | static int documentTopPad_DocumentView_(const iDocumentView *d) { |
522 | /* Amount of space between banner and top of the document. */ | 554 | /* Amount of space between banner and top of the document. */ |
523 | return isEmpty_Banner(d->banner) ? 0 : lineHeight_Text(paragraph_FontId); | 555 | return isEmpty_Banner(d->owner->banner) ? 0 : lineHeight_Text(paragraph_FontId); |
524 | } | ||
525 | |||
526 | static int documentTopMargin_DocumentWidget_(const iDocumentWidget *d) { | ||
527 | return (isEmpty_Banner(d->banner) ? d->pageMargin * gap_UI : height_Banner(d->banner)) + | ||
528 | documentTopPad_DocumentWidget_(d); | ||
529 | } | 556 | } |
530 | 557 | ||
531 | static int pageHeight_DocumentWidget_(const iDocumentWidget *d) { | 558 | static int documentTopMargin_DocumentView_(const iDocumentView *d) { |
532 | return height_Banner(d->banner) + documentTopPad_DocumentWidget_(d) + size_GmDocument(d->doc).y; | 559 | return (isEmpty_Banner(d->owner->banner) ? d->pageMargin * gap_UI : height_Banner(d->owner->banner)) + |
560 | documentTopPad_DocumentView_(d); | ||
533 | } | 561 | } |
534 | 562 | ||
535 | static int footerButtonsHeight_DocumentWidget_(const iDocumentWidget *d) { | 563 | static int pageHeight_DocumentView_(const iDocumentView *d) { |
536 | int height = height_Widget(d->footerButtons); | 564 | return height_Banner(d->owner->banner) + documentTopPad_DocumentView_(d) + size_GmDocument(d->doc).y; |
537 | // if (height) { | ||
538 | // height += 3 * gap_UI; /* padding */ | ||
539 | // } | ||
540 | return height; | ||
541 | } | 565 | } |
542 | 566 | ||
543 | static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) { | 567 | static iRect documentBounds_DocumentView_(const iDocumentView *d) { |
544 | const iRect bounds = bounds_Widget(constAs_Widget(d)); | 568 | const iRect bounds = bounds_Widget(constAs_Widget(d->owner)); |
545 | const int margin = gap_UI * d->pageMargin; | 569 | const int margin = gap_UI * d->pageMargin; |
546 | iRect rect; | 570 | iRect rect; |
547 | rect.size.x = documentWidth_DocumentWidget_(d); | 571 | rect.size.x = documentWidth_DocumentView_(d); |
548 | rect.pos.x = mid_Rect(bounds).x - rect.size.x / 2; | 572 | rect.pos.x = mid_Rect(bounds).x - rect.size.x / 2; |
549 | rect.pos.y = top_Rect(bounds) + margin; | 573 | rect.pos.y = top_Rect(bounds) + margin; |
550 | rect.size.y = height_Rect(bounds) - margin; | 574 | rect.size.y = height_Rect(bounds) - margin; |
551 | iBool wasCentered = iFalse; | 575 | iBool wasCentered = iFalse; |
552 | if (d->flags & centerVertically_DocumentWidgetFlag) { | 576 | /* TODO: Further separation of View and Widget: configure header and footer heights |
577 | without involving the widget here. */ | ||
578 | if (d->owner->flags & centerVertically_DocumentWidgetFlag) { | ||
553 | const int docSize = size_GmDocument(d->doc).y + | 579 | const int docSize = size_GmDocument(d->doc).y + |
554 | documentTopMargin_DocumentWidget_(d); | 580 | documentTopMargin_DocumentView_(d); |
555 | if (size_GmDocument(d->doc).y == 0) { | 581 | if (size_GmDocument(d->doc).y == 0) { |
556 | /* Document is empty; maybe just showing an error banner. */ | 582 | /* Document is empty; maybe just showing an error banner. */ |
557 | rect.pos.y = top_Rect(bounds) + height_Rect(bounds) / 2 - | 583 | rect.pos.y = top_Rect(bounds) + height_Rect(bounds) / 2 - |
558 | documentTopPad_DocumentWidget_(d) - height_Banner(d->banner) / 2; | 584 | documentTopPad_DocumentView_(d) - height_Banner(d->owner->banner) / 2; |
559 | rect.size.y = 0; | 585 | rect.size.y = 0; |
560 | wasCentered = iTrue; | 586 | wasCentered = iTrue; |
561 | } | 587 | } |
562 | else if (docSize < rect.size.y - footerButtonsHeight_DocumentWidget_(d)) { | 588 | else if (docSize < rect.size.y - footerHeight_DocumentWidget_(d->owner)) { |
563 | /* TODO: Phone toolbar? */ | 589 | /* TODO: Phone toolbar? */ |
564 | /* Center vertically when the document is short. */ | 590 | /* Center vertically when the document is short. */ |
565 | const int relMidY = (height_Rect(bounds) - footerButtonsHeight_DocumentWidget_(d)) / 2; | 591 | const int relMidY = (height_Rect(bounds) - footerHeight_DocumentWidget_(d->owner)) / 2; |
566 | const int visHeight = size_GmDocument(d->doc).y; | 592 | const int visHeight = size_GmDocument(d->doc).y; |
567 | const int offset = -height_Banner(d->banner) - documentTopPad_DocumentWidget_(d); | 593 | const int offset = -height_Banner(d->owner->banner) - documentTopPad_DocumentView_(d); |
568 | rect.pos.y = top_Rect(bounds) + iMaxi(0, relMidY - visHeight / 2 + offset); | 594 | rect.pos.y = top_Rect(bounds) + iMaxi(0, relMidY - visHeight / 2 + offset); |
569 | rect.size.y = size_GmDocument(d->doc).y + documentTopMargin_DocumentWidget_(d); | 595 | rect.size.y = size_GmDocument(d->doc).y + documentTopMargin_DocumentView_(d); |
570 | wasCentered = iTrue; | 596 | wasCentered = iTrue; |
571 | } | 597 | } |
572 | } | 598 | } |
573 | if (!wasCentered) { | 599 | if (!wasCentered) { |
574 | /* The banner overtakes the top margin. */ | 600 | /* The banner overtakes the top margin. */ |
575 | if (!isEmpty_Banner(d->banner)) { | 601 | if (!isEmpty_Banner(d->owner->banner)) { |
576 | rect.pos.y -= margin; | 602 | rect.pos.y -= margin; |
577 | } | 603 | } |
578 | else { | 604 | else { |
@@ -582,38 +608,28 @@ static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) { | |||
582 | return rect; | 608 | return rect; |
583 | } | 609 | } |
584 | 610 | ||
585 | static int viewPos_DocumentWidget_(const iDocumentWidget *d) { | 611 | static int viewPos_DocumentView_(const iDocumentView *d) { |
586 | return height_Banner(d->banner) + documentTopPad_DocumentWidget_(d) - pos_SmoothScroll(&d->scrollY); | 612 | return height_Banner(d->owner->banner) + documentTopPad_DocumentView_(d) - |
587 | } | 613 | pos_SmoothScroll(&d->scrollY); |
588 | |||
589 | #if 0 | ||
590 | static iRect siteBannerRect_DocumentWidget_(const iDocumentWidget *d) { | ||
591 | const iGmRun *banner = siteBanner_GmDocument(d->doc); | ||
592 | if (!banner) { | ||
593 | return zero_Rect(); | ||
594 | } | ||
595 | const iRect docBounds = documentBounds_DocumentWidget_(d); | ||
596 | const iInt2 origin = addY_I2(topLeft_Rect(docBounds), -pos_SmoothScroll(&d->scrollY)); | ||
597 | return moved_Rect(banner->visBounds, origin); | ||
598 | } | 614 | } |
599 | #endif | ||
600 | 615 | ||
601 | static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { | 616 | static iInt2 documentPos_DocumentView_(const iDocumentView *d, iInt2 pos) { |
602 | return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), | 617 | return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentView_(d))), |
603 | -viewPos_DocumentWidget_(d)); | 618 | -viewPos_DocumentView_(d)); |
604 | } | 619 | } |
605 | 620 | ||
606 | static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) { | 621 | static iRangei visibleRange_DocumentView_(const iDocumentView *d) { |
607 | int top = pos_SmoothScroll(&d->scrollY) - height_Banner(d->banner) - documentTopPad_DocumentWidget_(d); | 622 | int top = pos_SmoothScroll(&d->scrollY) - height_Banner(d->owner->banner) - |
608 | if (isEmpty_Banner(d->banner)) { | 623 | documentTopPad_DocumentView_(d); |
624 | if (isEmpty_Banner(d->owner->banner)) { | ||
609 | /* Top padding is not collapsed. */ | 625 | /* Top padding is not collapsed. */ |
610 | top -= d->pageMargin * gap_UI; | 626 | top -= d->pageMargin * gap_UI; |
611 | } | 627 | } |
612 | return (iRangei){ top, top + height_Rect(bounds_Widget(constAs_Widget(d))) }; | 628 | return (iRangei){ top, top + height_Rect(bounds_Widget(constAs_Widget(d->owner))) }; |
613 | } | 629 | } |
614 | 630 | ||
615 | static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { | 631 | static void addVisible_DocumentView_(void *context, const iGmRun *run) { |
616 | iDocumentWidget *d = context; | 632 | iDocumentView *d = context; |
617 | if (~run->flags & decoration_GmRunFlag && !run->mediaId) { | 633 | if (~run->flags & decoration_GmRunFlag && !run->mediaId) { |
618 | if (!d->visibleRuns.start) { | 634 | if (!d->visibleRuns.start) { |
619 | d->visibleRuns.start = run; | 635 | d->visibleRuns.start = run; |
@@ -636,7 +652,7 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { | |||
636 | } | 652 | } |
637 | } | 653 | } |
638 | 654 | ||
639 | static const iGmRun *lastVisibleLink_DocumentWidget_(const iDocumentWidget *d) { | 655 | static const iGmRun *lastVisibleLink_DocumentView_(const iDocumentView *d) { |
640 | iReverseConstForEach(PtrArray, i, &d->visibleLinks) { | 656 | iReverseConstForEach(PtrArray, i, &d->visibleLinks) { |
641 | const iGmRun *run = i.ptr; | 657 | const iGmRun *run = i.ptr; |
642 | if (run->flags & decoration_GmRunFlag && run->linkId) { | 658 | if (run->flags & decoration_GmRunFlag && run->linkId) { |
@@ -646,23 +662,24 @@ static const iGmRun *lastVisibleLink_DocumentWidget_(const iDocumentWidget *d) { | |||
646 | return NULL; | 662 | return NULL; |
647 | } | 663 | } |
648 | 664 | ||
649 | static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) { | 665 | static float normScrollPos_DocumentView_(const iDocumentView *d) { |
650 | const int docSize = pageHeight_DocumentWidget_(d); // size_GmDocument(d->doc).y; | 666 | const int docSize = pageHeight_DocumentView_(d); |
651 | if (docSize) { | 667 | if (docSize) { |
652 | return pos_SmoothScroll(&d->scrollY) / (float) docSize; | 668 | float pos = pos_SmoothScroll(&d->scrollY) / (float) docSize; |
669 | return iMax(pos, 0.0f); | ||
653 | } | 670 | } |
654 | return 0; | 671 | return 0; |
655 | } | 672 | } |
656 | 673 | ||
657 | static int scrollMax_DocumentWidget_(const iDocumentWidget *d) { | 674 | static int scrollMax_DocumentView_(const iDocumentView *d) { |
658 | const iWidget *w = constAs_Widget(d); | 675 | const iWidget *w = constAs_Widget(d->owner); |
659 | int sm = pageHeight_DocumentWidget_(d) - height_Rect(bounds_Widget(w)) + | 676 | int sm = pageHeight_DocumentView_(d) + |
660 | (isEmpty_Banner(d->banner) ? 2 : 1) * d->pageMargin * gap_UI + /* top and bottom margins */ | 677 | (isEmpty_Banner(d->owner->banner) ? 2 : 1) * d->pageMargin * gap_UI + /* top and bottom margins */ |
661 | iMax(height_Widget(d->phoneToolbar), height_Widget(d->footerButtons)); | 678 | footerHeight_DocumentWidget_(d->owner) - height_Rect(bounds_Widget(w)); |
662 | return sm; | 679 | return sm; |
663 | } | 680 | } |
664 | 681 | ||
665 | static void invalidateLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) { | 682 | static void invalidateLink_DocumentView_(iDocumentView *d, iGmLinkId id) { |
666 | /* A link has multiple runs associated with it. */ | 683 | /* A link has multiple runs associated with it. */ |
667 | iConstForEach(PtrArray, i, &d->visibleLinks) { | 684 | iConstForEach(PtrArray, i, &d->visibleLinks) { |
668 | const iGmRun *run = i.ptr; | 685 | const iGmRun *run = i.ptr; |
@@ -672,7 +689,7 @@ static void invalidateLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) { | |||
672 | } | 689 | } |
673 | } | 690 | } |
674 | 691 | ||
675 | static void invalidateVisibleLinks_DocumentWidget_(iDocumentWidget *d) { | 692 | static void invalidateVisibleLinks_DocumentView_(iDocumentView *d) { |
676 | iConstForEach(PtrArray, i, &d->visibleLinks) { | 693 | iConstForEach(PtrArray, i, &d->visibleLinks) { |
677 | const iGmRun *run = i.ptr; | 694 | const iGmRun *run = i.ptr; |
678 | if (run->linkId) { | 695 | if (run->linkId) { |
@@ -681,7 +698,7 @@ static void invalidateVisibleLinks_DocumentWidget_(iDocumentWidget *d) { | |||
681 | } | 698 | } |
682 | } | 699 | } |
683 | 700 | ||
684 | static int runOffset_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { | 701 | static int runOffset_DocumentView_(const iDocumentView *d, const iGmRun *run) { |
685 | if (preId_GmRun(run) && run->flags & wide_GmRunFlag) { | 702 | if (preId_GmRun(run) && run->flags & wide_GmRunFlag) { |
686 | if (d->animWideRunId == preId_GmRun(run)) { | 703 | if (d->animWideRunId == preId_GmRun(run)) { |
687 | return -value_Anim(&d->animWideRunOffset); | 704 | return -value_Anim(&d->animWideRunOffset); |
@@ -695,56 +712,24 @@ static int runOffset_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run | |||
695 | return 0; | 712 | return 0; |
696 | } | 713 | } |
697 | 714 | ||
698 | static void invalidateWideRunsWithNonzeroOffset_DocumentWidget_(iDocumentWidget *d) { | 715 | static void invalidateWideRunsWithNonzeroOffset_DocumentView_(iDocumentView *d) { |
699 | iConstForEach(PtrArray, i, &d->visibleWideRuns) { | 716 | iConstForEach(PtrArray, i, &d->visibleWideRuns) { |
700 | const iGmRun *run = i.ptr; | 717 | const iGmRun *run = i.ptr; |
701 | if (runOffset_DocumentWidget_(d, run)) { | 718 | if (runOffset_DocumentView_(d, run)) { |
702 | insert_PtrSet(d->invalidRuns, run); | 719 | insert_PtrSet(d->invalidRuns, run); |
703 | } | 720 | } |
704 | } | 721 | } |
705 | } | 722 | } |
706 | 723 | ||
707 | static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse); | 724 | static void updateHover_DocumentView_(iDocumentView *d, iInt2 mouse) { |
708 | 725 | const iWidget *w = constAs_Widget(d->owner); | |
709 | static void animate_DocumentWidget_(void *ticker) { | 726 | const iRect docBounds = documentBounds_DocumentView_(d); |
710 | iDocumentWidget *d = ticker; | ||
711 | refresh_Widget(d); | ||
712 | if (!isFinished_Anim(&d->sideOpacity) || !isFinished_Anim(&d->altTextOpacity)) { | ||
713 | addTicker_App(animate_DocumentWidget_, d); | ||
714 | } | ||
715 | } | ||
716 | |||
717 | static iBool isHoverAllowed_DocumentWidget_(const iDocumentWidget *d) { | ||
718 | if (!isHover_Widget(d)) { | ||
719 | return iFalse; | ||
720 | } | ||
721 | if (!(d->state == ready_RequestState || d->state == receivedPartialResponse_RequestState)) { | ||
722 | return iFalse; | ||
723 | } | ||
724 | if (d->flags & noHoverWhileScrolling_DocumentWidgetFlag) { | ||
725 | return iFalse; | ||
726 | } | ||
727 | if (d->flags & pinchZoom_DocumentWidgetFlag) { | ||
728 | return iFalse; | ||
729 | } | ||
730 | if (flags_Widget(constAs_Widget(d)) & touchDrag_WidgetFlag) { | ||
731 | return iFalse; | ||
732 | } | ||
733 | if (flags_Widget(constAs_Widget(d->scroll)) & pressed_WidgetFlag) { | ||
734 | return iFalse; | ||
735 | } | ||
736 | return iTrue; | ||
737 | } | ||
738 | |||
739 | static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { | ||
740 | const iWidget *w = constAs_Widget(d); | ||
741 | const iRect docBounds = documentBounds_DocumentWidget_(d); | ||
742 | const iGmRun * oldHoverLink = d->hoverLink; | 727 | const iGmRun * oldHoverLink = d->hoverLink; |
743 | d->hoverPre = NULL; | 728 | d->hoverPre = NULL; |
744 | d->hoverLink = NULL; | 729 | d->hoverLink = NULL; |
745 | const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)), | 730 | const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)), |
746 | -viewPos_DocumentWidget_(d)); | 731 | -viewPos_DocumentView_(d)); |
747 | if (isHoverAllowed_DocumentWidget_(d)) { | 732 | if (isHoverAllowed_DocumentWidget_(d->owner)) { |
748 | iConstForEach(PtrArray, i, &d->visibleLinks) { | 733 | iConstForEach(PtrArray, i, &d->visibleLinks) { |
749 | const iGmRun *run = i.ptr; | 734 | const iGmRun *run = i.ptr; |
750 | /* Click targets are slightly expanded so there are no gaps between links. */ | 735 | /* Click targets are slightly expanded so there are no gaps between links. */ |
@@ -756,15 +741,21 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { | |||
756 | } | 741 | } |
757 | if (d->hoverLink != oldHoverLink) { | 742 | if (d->hoverLink != oldHoverLink) { |
758 | if (oldHoverLink) { | 743 | if (oldHoverLink) { |
759 | invalidateLink_DocumentWidget_(d, oldHoverLink->linkId); | 744 | invalidateLink_DocumentView_(d, oldHoverLink->linkId); |
760 | } | 745 | } |
761 | if (d->hoverLink) { | 746 | if (d->hoverLink) { |
762 | invalidateLink_DocumentWidget_(d, d->hoverLink->linkId); | 747 | invalidateLink_DocumentView_(d, d->hoverLink->linkId); |
748 | } | ||
749 | if (update_LinkInfo(d->owner->linkInfo, | ||
750 | d->doc, | ||
751 | d->hoverLink ? d->hoverLink->linkId : 0, | ||
752 | width_Widget(w))) { | ||
753 | animate_DocumentWidget_(d->owner); | ||
763 | } | 754 | } |
764 | refresh_Widget(w); | 755 | refresh_Widget(w); |
765 | } | 756 | } |
766 | /* Hovering over preformatted blocks. */ | 757 | /* Hovering over preformatted blocks. */ |
767 | if (isHoverAllowed_DocumentWidget_(d)) { | 758 | if (isHoverAllowed_DocumentWidget_(d->owner)) { |
768 | iConstForEach(PtrArray, j, &d->visiblePre) { | 759 | iConstForEach(PtrArray, j, &d->visiblePre) { |
769 | const iGmRun *run = j.ptr; | 760 | const iGmRun *run = j.ptr; |
770 | if (contains_Rect(run->bounds, hoverPos)) { | 761 | if (contains_Rect(run->bounds, hoverPos)) { |
@@ -777,18 +768,18 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { | |||
777 | if (!d->hoverPre) { | 768 | if (!d->hoverPre) { |
778 | setValueSpeed_Anim(&d->altTextOpacity, 0.0f, 1.5f); | 769 | setValueSpeed_Anim(&d->altTextOpacity, 0.0f, 1.5f); |
779 | if (!isFinished_Anim(&d->altTextOpacity)) { | 770 | if (!isFinished_Anim(&d->altTextOpacity)) { |
780 | animate_DocumentWidget_(d); | 771 | animate_DocumentWidget_(d->owner); |
781 | } | 772 | } |
782 | } | 773 | } |
783 | else if (d->hoverPre && | 774 | else if (d->hoverPre && |
784 | preHasAltText_GmDocument(d->doc, preId_GmRun(d->hoverPre)) && | 775 | preHasAltText_GmDocument(d->doc, preId_GmRun(d->hoverPre)) && |
785 | ~d->flags & noHoverWhileScrolling_DocumentWidgetFlag) { | 776 | ~d->owner->flags & noHoverWhileScrolling_DocumentWidgetFlag) { |
786 | setValueSpeed_Anim(&d->altTextOpacity, 1.0f, 1.5f); | 777 | setValueSpeed_Anim(&d->altTextOpacity, 1.0f, 1.5f); |
787 | if (!isFinished_Anim(&d->altTextOpacity)) { | 778 | if (!isFinished_Anim(&d->altTextOpacity)) { |
788 | animate_DocumentWidget_(d); | 779 | animate_DocumentWidget_(d->owner); |
789 | } | 780 | } |
790 | } | 781 | } |
791 | if (isHover_Widget(w) && !contains_Widget(constAs_Widget(d->scroll), mouse)) { | 782 | if (isHover_Widget(w) && !contains_Widget(constAs_Widget(d->owner->scroll), mouse)) { |
792 | setCursor_Window(get_Window(), | 783 | setCursor_Window(get_Window(), |
793 | d->hoverLink || d->hoverPre ? SDL_SYSTEM_CURSOR_HAND | 784 | d->hoverLink || d->hoverPre ? SDL_SYSTEM_CURSOR_HAND |
794 | : SDL_SYSTEM_CURSOR_IBEAM); | 785 | : SDL_SYSTEM_CURSOR_IBEAM); |
@@ -799,15 +790,1198 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { | |||
799 | } | 790 | } |
800 | } | 791 | } |
801 | 792 | ||
802 | static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d, iBool isAnimated) { | 793 | static void updateSideOpacity_DocumentView_(iDocumentView *d, iBool isAnimated) { |
803 | float opacity = 0.0f; | 794 | float opacity = 0.0f; |
804 | // const iGmRun *banner = siteBanner_GmDocument(d->doc); | 795 | if (!isEmpty_Banner(d->owner->banner) && |
805 | if (!isEmpty_Banner(d->banner) && height_Banner(d->banner) < pos_SmoothScroll(&d->scrollY)) { | 796 | height_Banner(d->owner->banner) < pos_SmoothScroll(&d->scrollY)) { |
806 | // if (banner && bottom_Rect(banner->visBounds) < pos_SmoothScroll(&d->scrollY)) { | ||
807 | opacity = 1.0f; | 797 | opacity = 1.0f; |
808 | } | 798 | } |
809 | setValue_Anim(&d->sideOpacity, opacity, isAnimated ? (opacity < 0.5f ? 100 : 200) : 0); | 799 | setValue_Anim(&d->sideOpacity, opacity, isAnimated ? (opacity < 0.5f ? 100 : 200) : 0); |
810 | animate_DocumentWidget_(d); | 800 | animate_DocumentWidget_(d->owner); |
801 | } | ||
802 | |||
803 | static iRangecc currentHeading_DocumentView_(const iDocumentView *d) { | ||
804 | iRangecc heading = iNullRange; | ||
805 | if (d->visibleRuns.start) { | ||
806 | iConstForEach(Array, i, headings_GmDocument(d->doc)) { | ||
807 | const iGmHeading *head = i.value; | ||
808 | if (head->level == 0) { | ||
809 | if (head->text.start <= d->visibleRuns.start->text.start) { | ||
810 | heading = head->text; | ||
811 | } | ||
812 | if (d->visibleRuns.end && head->text.start > d->visibleRuns.end->text.start) { | ||
813 | break; | ||
814 | } | ||
815 | } | ||
816 | } | ||
817 | } | ||
818 | return heading; | ||
819 | } | ||
820 | |||
821 | static int updateScrollMax_DocumentView_(iDocumentView *d) { | ||
822 | arrange_Widget(d->owner->footerButtons); /* scrollMax depends on footer height */ | ||
823 | const int scrollMax = scrollMax_DocumentView_(d); | ||
824 | setMax_SmoothScroll(&d->scrollY, scrollMax); | ||
825 | return scrollMax; | ||
826 | } | ||
827 | |||
828 | static void updateVisible_DocumentView_(iDocumentView *d) { | ||
829 | /* TODO: The concerns of Widget and View are too tangled together here. */ | ||
830 | iChangeFlags(d->owner->flags, | ||
831 | centerVertically_DocumentWidgetFlag, | ||
832 | prefs_App()->centerShortDocs || startsWithCase_String(d->owner->mod.url, "about:") || | ||
833 | !isSuccess_GmStatusCode(d->owner->sourceStatus)); | ||
834 | iScrollWidget *scrollBar = d->owner->scroll; | ||
835 | const iRangei visRange = visibleRange_DocumentView_(d); | ||
836 | // printf("visRange: %d...%d\n", visRange.start, visRange.end); | ||
837 | const iRect bounds = bounds_Widget(as_Widget(d->owner)); | ||
838 | const int scrollMax = updateScrollMax_DocumentView_(d); | ||
839 | /* Reposition the footer buttons as appropriate. */ | ||
840 | setRange_ScrollWidget(scrollBar, (iRangei){ 0, scrollMax }); | ||
841 | const int docSize = pageHeight_DocumentView_(d) + footerHeight_DocumentWidget_(d->owner); | ||
842 | const float scrollPos = pos_SmoothScroll(&d->scrollY); | ||
843 | setThumb_ScrollWidget(scrollBar, | ||
844 | pos_SmoothScroll(&d->scrollY), | ||
845 | docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); | ||
846 | if (d->owner->footerButtons) { | ||
847 | const iRect bounds = bounds_Widget(as_Widget(d->owner)); | ||
848 | const iRect docBounds = documentBounds_DocumentView_(d); | ||
849 | const int hPad = (width_Rect(bounds) - iMin(120 * gap_UI, width_Rect(docBounds))) / 2; | ||
850 | const int vPad = 3 * gap_UI; | ||
851 | setPadding_Widget(d->owner->footerButtons, hPad, 0, hPad, vPad); | ||
852 | d->owner->footerButtons->rect.pos.y = height_Rect(bounds) - | ||
853 | footerHeight_DocumentWidget_(d->owner) + | ||
854 | (scrollMax > 0 ? scrollMax - scrollPos : 0); | ||
855 | } | ||
856 | clear_PtrArray(&d->visibleLinks); | ||
857 | clear_PtrArray(&d->visibleWideRuns); | ||
858 | clear_PtrArray(&d->visiblePre); | ||
859 | clear_PtrArray(&d->visibleMedia); | ||
860 | const iRangecc oldHeading = currentHeading_DocumentView_(d); | ||
861 | /* Scan for visible runs. */ { | ||
862 | iZap(d->visibleRuns); | ||
863 | render_GmDocument(d->doc, visRange, addVisible_DocumentView_, d); | ||
864 | } | ||
865 | const iRangecc newHeading = currentHeading_DocumentView_(d); | ||
866 | if (memcmp(&oldHeading, &newHeading, sizeof(oldHeading))) { | ||
867 | d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; | ||
868 | } | ||
869 | updateHover_DocumentView_(d, mouseCoord_Window(get_Window(), 0)); | ||
870 | updateSideOpacity_DocumentView_(d, iTrue); | ||
871 | animateMedia_DocumentWidget_(d->owner); | ||
872 | /* Remember scroll positions of recently visited pages. */ { | ||
873 | iRecentUrl *recent = mostRecentUrl_History(d->owner->mod.history); | ||
874 | if (recent && docSize && d->owner->state == ready_RequestState && | ||
875 | equal_String(&recent->url, d->owner->mod.url)) { | ||
876 | recent->normScrollY = normScrollPos_DocumentView_(d); | ||
877 | } | ||
878 | } | ||
879 | /* After scrolling/resizing stops, begin pre-rendering the visbuf contents. */ { | ||
880 | removeTicker_App(prerender_DocumentWidget_, d->owner); | ||
881 | remove_Periodic(periodic_App(), d); | ||
882 | add_Periodic(periodic_App(), d->owner, "document.render"); | ||
883 | } | ||
884 | } | ||
885 | |||
886 | static void swap_DocumentView_(iDocumentView *d, iDocumentView *swapBuffersWith) { | ||
887 | d->scrollY = swapBuffersWith->scrollY; | ||
888 | d->scrollY.widget = as_Widget(d->owner); | ||
889 | iSwap(iVisBuf *, d->visBuf, swapBuffersWith->visBuf); | ||
890 | iSwap(iVisBufMeta *, d->visBufMeta, swapBuffersWith->visBufMeta); | ||
891 | iSwap(iDrawBufs *, d->drawBufs, swapBuffersWith->drawBufs); | ||
892 | updateVisible_DocumentView_(d); | ||
893 | updateVisible_DocumentView_(swapBuffersWith); | ||
894 | } | ||
895 | |||
896 | static void updateTimestampBuf_DocumentView_(const iDocumentView *d) { | ||
897 | if (!isExposed_Window(get_Window())) { | ||
898 | return; | ||
899 | } | ||
900 | if (d->drawBufs->timestampBuf) { | ||
901 | delete_TextBuf(d->drawBufs->timestampBuf); | ||
902 | d->drawBufs->timestampBuf = NULL; | ||
903 | } | ||
904 | if (isValid_Time(&d->owner->sourceTime)) { | ||
905 | iString *fmt = timeFormatHourPreference_Lang("page.timestamp"); | ||
906 | d->drawBufs->timestampBuf = newRange_TextBuf( | ||
907 | uiLabel_FontId, | ||
908 | white_ColorId, | ||
909 | range_String(collect_String(format_Time(&d->owner->sourceTime, cstr_String(fmt))))); | ||
910 | delete_String(fmt); | ||
911 | } | ||
912 | d->drawBufs->flags &= ~updateTimestampBuf_DrawBufsFlag; | ||
913 | } | ||
914 | |||
915 | static void invalidate_DocumentView_(iDocumentView *d) { | ||
916 | invalidate_VisBuf(d->visBuf); | ||
917 | clear_PtrSet(d->invalidRuns); | ||
918 | } | ||
919 | |||
920 | static void documentRunsInvalidated_DocumentView_(iDocumentView *d) { | ||
921 | d->hoverPre = NULL; | ||
922 | d->hoverAltPre = NULL; | ||
923 | d->hoverLink = NULL; | ||
924 | iZap(d->visibleRuns); | ||
925 | iZap(d->renderRuns); | ||
926 | } | ||
927 | |||
928 | static void resetScroll_DocumentView_(iDocumentView *d) { | ||
929 | reset_SmoothScroll(&d->scrollY); | ||
930 | init_Anim(&d->sideOpacity, 0); | ||
931 | init_Anim(&d->altTextOpacity, 0); | ||
932 | resetWideRuns_DocumentView_(d); | ||
933 | } | ||
934 | |||
935 | static void updateWidth_DocumentView_(iDocumentView *d) { | ||
936 | updateWidth_GmDocument(d->doc, documentWidth_DocumentView_(d), width_Widget(d->owner)); | ||
937 | } | ||
938 | |||
939 | static void updateWidthAndRedoLayout_DocumentView_(iDocumentView *d) { | ||
940 | setWidth_GmDocument(d->doc, documentWidth_DocumentView_(d), width_Widget(d->owner)); | ||
941 | } | ||
942 | |||
943 | static void clampScroll_DocumentView_(iDocumentView *d) { | ||
944 | move_SmoothScroll(&d->scrollY, 0); | ||
945 | } | ||
946 | |||
947 | static void immediateScroll_DocumentView_(iDocumentView *d, int offset) { | ||
948 | move_SmoothScroll(&d->scrollY, offset); | ||
949 | } | ||
950 | |||
951 | static void smoothScroll_DocumentView_(iDocumentView *d, int offset, int duration) { | ||
952 | moveSpan_SmoothScroll(&d->scrollY, offset, duration); | ||
953 | } | ||
954 | |||
955 | static void scrollTo_DocumentView_(iDocumentView *d, int documentY, iBool centered) { | ||
956 | if (!isEmpty_Banner(d->owner->banner)) { | ||
957 | documentY += height_Banner(d->owner->banner) + documentTopPad_DocumentView_(d); | ||
958 | } | ||
959 | else { | ||
960 | documentY += documentTopPad_DocumentView_(d) + d->pageMargin * gap_UI; | ||
961 | } | ||
962 | init_Anim(&d->scrollY.pos, | ||
963 | documentY - (centered ? documentBounds_DocumentView_(d).size.y / 2 | ||
964 | : lineHeight_Text(paragraph_FontId))); | ||
965 | clampScroll_DocumentView_(d); | ||
966 | } | ||
967 | |||
968 | static void scrollToHeading_DocumentView_(iDocumentView *d, const char *heading) { | ||
969 | iConstForEach(Array, h, headings_GmDocument(d->doc)) { | ||
970 | const iGmHeading *head = h.value; | ||
971 | if (startsWithCase_Rangecc(head->text, heading)) { | ||
972 | postCommandf_Root(as_Widget(d->owner)->root, "document.goto loc:%p", head->text.start); | ||
973 | break; | ||
974 | } | ||
975 | } | ||
976 | } | ||
977 | |||
978 | static iBool scrollWideBlock_DocumentView_(iDocumentView *d, iInt2 mousePos, int delta, | ||
979 | int duration) { | ||
980 | if (delta == 0 || d->owner->flags & eitherWheelSwipe_DocumentWidgetFlag) { | ||
981 | return iFalse; | ||
982 | } | ||
983 | const iInt2 docPos = documentPos_DocumentView_(d, mousePos); | ||
984 | iConstForEach(PtrArray, i, &d->visibleWideRuns) { | ||
985 | const iGmRun *run = i.ptr; | ||
986 | if (docPos.y >= top_Rect(run->bounds) && docPos.y <= bottom_Rect(run->bounds)) { | ||
987 | /* We can scroll this run. First find out how much is allowed. */ | ||
988 | const iGmRunRange range = findPreformattedRange_GmDocument(d->doc, run); | ||
989 | int maxWidth = 0; | ||
990 | for (const iGmRun *r = range.start; r != range.end; r++) { | ||
991 | maxWidth = iMax(maxWidth, width_Rect(r->visBounds)); | ||
992 | } | ||
993 | const int maxOffset = maxWidth - documentWidth_DocumentView_(d) + d->pageMargin * gap_UI; | ||
994 | if (size_Array(&d->wideRunOffsets) <= preId_GmRun(run)) { | ||
995 | resize_Array(&d->wideRunOffsets, preId_GmRun(run) + 1); | ||
996 | } | ||
997 | int *offset = at_Array(&d->wideRunOffsets, preId_GmRun(run) - 1); | ||
998 | const int oldOffset = *offset; | ||
999 | *offset = iClamp(*offset + delta, 0, maxOffset); | ||
1000 | /* Make sure the whole block gets redraw. */ | ||
1001 | if (oldOffset != *offset) { | ||
1002 | for (const iGmRun *r = range.start; r != range.end; r++) { | ||
1003 | insert_PtrSet(d->invalidRuns, r); | ||
1004 | } | ||
1005 | refresh_Widget(d->owner); | ||
1006 | d->owner->selectMark = iNullRange; | ||
1007 | d->owner->foundMark = iNullRange; | ||
1008 | } | ||
1009 | if (duration) { | ||
1010 | if (d->animWideRunId != preId_GmRun(run) || isFinished_Anim(&d->animWideRunOffset)) { | ||
1011 | d->animWideRunId = preId_GmRun(run); | ||
1012 | init_Anim(&d->animWideRunOffset, oldOffset); | ||
1013 | } | ||
1014 | setValueEased_Anim(&d->animWideRunOffset, *offset, duration); | ||
1015 | d->animWideRunRange = range; | ||
1016 | addTicker_App(refreshWhileScrolling_DocumentWidget_, d->owner); | ||
1017 | } | ||
1018 | else { | ||
1019 | d->animWideRunId = 0; | ||
1020 | init_Anim(&d->animWideRunOffset, 0); | ||
1021 | } | ||
1022 | return iTrue; | ||
1023 | } | ||
1024 | } | ||
1025 | return iFalse; | ||
1026 | } | ||
1027 | |||
1028 | static iRangecc sourceLoc_DocumentView_(const iDocumentView *d, iInt2 pos) { | ||
1029 | return findLoc_GmDocument(d->doc, documentPos_DocumentView_(d, pos)); | ||
1030 | } | ||
1031 | |||
1032 | iDeclareType(MiddleRunParams) | ||
1033 | |||
1034 | struct Impl_MiddleRunParams { | ||
1035 | int midY; | ||
1036 | const iGmRun *closest; | ||
1037 | int distance; | ||
1038 | }; | ||
1039 | |||
1040 | static void find_MiddleRunParams_(void *params, const iGmRun *run) { | ||
1041 | iMiddleRunParams *d = params; | ||
1042 | if (isEmpty_Rect(run->bounds)) { | ||
1043 | return; | ||
1044 | } | ||
1045 | const int distance = iAbs(mid_Rect(run->bounds).y - d->midY); | ||
1046 | if (!d->closest || distance < d->distance) { | ||
1047 | d->closest = run; | ||
1048 | d->distance = distance; | ||
1049 | } | ||
1050 | } | ||
1051 | |||
1052 | static const iGmRun *middleRun_DocumentView_(const iDocumentView *d) { | ||
1053 | iRangei visRange = visibleRange_DocumentView_(d); | ||
1054 | iMiddleRunParams params = { (visRange.start + visRange.end) / 2, NULL, 0 }; | ||
1055 | render_GmDocument(d->doc, visRange, find_MiddleRunParams_, ¶ms); | ||
1056 | return params.closest; | ||
1057 | } | ||
1058 | |||
1059 | static void allocVisBuffer_DocumentView_(const iDocumentView *d) { | ||
1060 | const iWidget *w = constAs_Widget(d->owner); | ||
1061 | const iBool isVisible = isVisible_Widget(w); | ||
1062 | const iInt2 size = bounds_Widget(w).size; | ||
1063 | if (isVisible) { | ||
1064 | alloc_VisBuf(d->visBuf, size, 1); | ||
1065 | } | ||
1066 | else { | ||
1067 | dealloc_VisBuf(d->visBuf); | ||
1068 | } | ||
1069 | } | ||
1070 | |||
1071 | static size_t visibleLinkOrdinal_DocumentView_(const iDocumentView *d, iGmLinkId linkId) { | ||
1072 | size_t ord = 0; | ||
1073 | const iRangei visRange = visibleRange_DocumentView_(d); | ||
1074 | iConstForEach(PtrArray, i, &d->visibleLinks) { | ||
1075 | const iGmRun *run = i.ptr; | ||
1076 | if (top_Rect(run->visBounds) >= visRange.start + gap_UI * d->pageMargin * 4 / 5) { | ||
1077 | if (run->flags & decoration_GmRunFlag && run->linkId) { | ||
1078 | if (run->linkId == linkId) return ord; | ||
1079 | ord++; | ||
1080 | } | ||
1081 | } | ||
1082 | } | ||
1083 | return iInvalidPos; | ||
1084 | } | ||
1085 | |||
1086 | static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) { | ||
1087 | d->foundMark = iNullRange; | ||
1088 | d->selectMark = iNullRange; | ||
1089 | d->contextLink = NULL; | ||
1090 | documentRunsInvalidated_DocumentView_(&d->view); | ||
1091 | } | ||
1092 | |||
1093 | static iBool updateDocumentWidthRetainingScrollPosition_DocumentView_(iDocumentView *d, | ||
1094 | iBool keepCenter) { | ||
1095 | const int newWidth = documentWidth_DocumentView_(d); | ||
1096 | if (newWidth == size_GmDocument(d->doc).x && !keepCenter /* not a font change */) { | ||
1097 | return iFalse; | ||
1098 | } | ||
1099 | /* Font changes (i.e., zooming) will keep the view centered, otherwise keep the top | ||
1100 | of the visible area fixed. */ | ||
1101 | const iGmRun *run = keepCenter ? middleRun_DocumentView_(d) : d->visibleRuns.start; | ||
1102 | const char * runLoc = (run ? run->text.start : NULL); | ||
1103 | int voffset = 0; | ||
1104 | if (!keepCenter && run) { | ||
1105 | /* Keep the first visible run visible at the same position. */ | ||
1106 | /* TODO: First *fully* visible run? */ | ||
1107 | voffset = visibleRange_DocumentView_(d).start - top_Rect(run->visBounds); | ||
1108 | } | ||
1109 | setWidth_GmDocument(d->doc, newWidth, width_Widget(d->owner)); | ||
1110 | setWidth_Banner(d->owner->banner, newWidth); | ||
1111 | documentRunsInvalidated_DocumentWidget_(d->owner); | ||
1112 | if (runLoc && !keepCenter) { | ||
1113 | run = findRunAtLoc_GmDocument(d->doc, runLoc); | ||
1114 | if (run) { | ||
1115 | scrollTo_DocumentView_( | ||
1116 | d, top_Rect(run->visBounds) + lineHeight_Text(paragraph_FontId) + voffset, iFalse); | ||
1117 | } | ||
1118 | } | ||
1119 | else if (runLoc && keepCenter) { | ||
1120 | run = findRunAtLoc_GmDocument(d->doc, runLoc); | ||
1121 | if (run) { | ||
1122 | scrollTo_DocumentView_(d, mid_Rect(run->bounds).y, iTrue); | ||
1123 | } | ||
1124 | } | ||
1125 | return iTrue; | ||
1126 | } | ||
1127 | |||
1128 | static iRect runRect_DocumentView_(const iDocumentView *d, const iGmRun *run) { | ||
1129 | const iRect docBounds = documentBounds_DocumentView_(d); | ||
1130 | return moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), viewPos_DocumentView_(d))); | ||
1131 | } | ||
1132 | |||
1133 | iDeclareType(DrawContext) | ||
1134 | |||
1135 | struct Impl_DrawContext { | ||
1136 | const iDocumentView *view; | ||
1137 | iRect widgetBounds; | ||
1138 | iRect docBounds; | ||
1139 | iRangei vis; | ||
1140 | iInt2 viewPos; /* document area origin */ | ||
1141 | iPaint paint; | ||
1142 | iBool inSelectMark; | ||
1143 | iBool inFoundMark; | ||
1144 | iBool showLinkNumbers; | ||
1145 | iRect firstMarkRect; | ||
1146 | iRect lastMarkRect; | ||
1147 | iGmRunRange runsDrawn; | ||
1148 | }; | ||
1149 | |||
1150 | static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color, | ||
1151 | iRangecc mark, iBool *isInside) { | ||
1152 | if (mark.start > mark.end) { | ||
1153 | /* Selection may be done in either direction. */ | ||
1154 | iSwap(const char *, mark.start, mark.end); | ||
1155 | } | ||
1156 | if (*isInside || (contains_Range(&run->text, mark.start) || | ||
1157 | contains_Range(&mark, run->text.start))) { | ||
1158 | int x = 0; | ||
1159 | if (!*isInside) { | ||
1160 | x = measureRange_Text(run->font, | ||
1161 | (iRangecc){ run->text.start, iMax(run->text.start, mark.start) }) | ||
1162 | .advance.x; | ||
1163 | } | ||
1164 | int w = width_Rect(run->visBounds) - x; | ||
1165 | if (contains_Range(&run->text, mark.end) || mark.end < run->text.start) { | ||
1166 | iRangecc mk = !*isInside ? mark | ||
1167 | : (iRangecc){ run->text.start, iMax(run->text.start, mark.end) }; | ||
1168 | mk.start = iMax(mk.start, run->text.start); | ||
1169 | w = measureRange_Text(run->font, mk).advance.x; | ||
1170 | *isInside = iFalse; | ||
1171 | } | ||
1172 | else { | ||
1173 | *isInside = iTrue; /* at least until the next run */ | ||
1174 | } | ||
1175 | if (w > width_Rect(run->visBounds) - x) { | ||
1176 | w = width_Rect(run->visBounds) - x; | ||
1177 | } | ||
1178 | if (~run->flags & decoration_GmRunFlag) { | ||
1179 | const iInt2 visPos = | ||
1180 | add_I2(run->bounds.pos, addY_I2(d->viewPos, viewPos_DocumentView_(d->view))); | ||
1181 | const iRect rangeRect = { addX_I2(visPos, x), init_I2(w, height_Rect(run->bounds)) }; | ||
1182 | if (rangeRect.size.x) { | ||
1183 | fillRect_Paint(&d->paint, rangeRect, color); | ||
1184 | /* Keep track of the first and last marked rects. */ | ||
1185 | if (d->firstMarkRect.size.x == 0) { | ||
1186 | d->firstMarkRect = rangeRect; | ||
1187 | } | ||
1188 | d->lastMarkRect = rangeRect; | ||
1189 | } | ||
1190 | } | ||
1191 | } | ||
1192 | /* Link URLs are not part of the visible document, so they are ignored above. Handle | ||
1193 | these ranges as a special case. */ | ||
1194 | if (run->linkId && run->flags & decoration_GmRunFlag) { | ||
1195 | const iRangecc url = linkUrlRange_GmDocument(d->view->doc, run->linkId); | ||
1196 | if (contains_Range(&url, mark.start) && | ||
1197 | (contains_Range(&url, mark.end) || url.end == mark.end)) { | ||
1198 | fillRect_Paint( | ||
1199 | &d->paint, | ||
1200 | moved_Rect(run->visBounds, addY_I2(d->viewPos, viewPos_DocumentView_(d->view))), | ||
1201 | color); | ||
1202 | } | ||
1203 | } | ||
1204 | } | ||
1205 | |||
1206 | static void drawMark_DrawContext_(void *context, const iGmRun *run) { | ||
1207 | iDrawContext *d = context; | ||
1208 | if (!isMedia_GmRun(run)) { | ||
1209 | fillRange_DrawContext_(d, run, uiMatching_ColorId, d->view->owner->foundMark, &d->inFoundMark); | ||
1210 | fillRange_DrawContext_(d, run, uiMarked_ColorId, d->view->owner->selectMark, &d->inSelectMark); | ||
1211 | } | ||
1212 | } | ||
1213 | |||
1214 | static void drawRun_DrawContext_(void *context, const iGmRun *run) { | ||
1215 | iDrawContext *d = context; | ||
1216 | const iInt2 origin = d->viewPos; | ||
1217 | /* Keep track of the drawn visible runs. */ { | ||
1218 | if (!d->runsDrawn.start || run < d->runsDrawn.start) { | ||
1219 | d->runsDrawn.start = run; | ||
1220 | } | ||
1221 | if (!d->runsDrawn.end || run > d->runsDrawn.end) { | ||
1222 | d->runsDrawn.end = run; | ||
1223 | } | ||
1224 | } | ||
1225 | if (run->mediaType == image_MediaType) { | ||
1226 | SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->view->doc), mediaId_GmRun(run)); | ||
1227 | const iRect dst = moved_Rect(run->visBounds, origin); | ||
1228 | if (tex) { | ||
1229 | fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */ | ||
1230 | SDL_RenderCopy(d->paint.dst->render, tex, NULL, | ||
1231 | &(SDL_Rect){ dst.pos.x, dst.pos.y, dst.size.x, dst.size.y }); | ||
1232 | } | ||
1233 | else { | ||
1234 | drawRect_Paint(&d->paint, dst, tmQuoteIcon_ColorId); | ||
1235 | drawCentered_Text(uiLabel_FontId, | ||
1236 | dst, | ||
1237 | iFalse, | ||
1238 | tmQuote_ColorId, | ||
1239 | explosion_Icon " Error Loading Image"); | ||
1240 | } | ||
1241 | return; | ||
1242 | } | ||
1243 | else if (isMedia_GmRun(run)) { | ||
1244 | /* Media UIs are drawn afterwards as a dynamic overlay. */ | ||
1245 | return; | ||
1246 | } | ||
1247 | enum iColorId fg = run->color; | ||
1248 | const iGmDocument *doc = d->view->doc; | ||
1249 | const int linkFlags = linkFlags_GmDocument(doc, run->linkId); | ||
1250 | /* Hover state of a link. */ | ||
1251 | const iBool isPartOfHover = (run->linkId && d->view->hoverLink && | ||
1252 | run->linkId == d->view->hoverLink->linkId); | ||
1253 | iBool isHover = (isPartOfHover && ~run->flags & decoration_GmRunFlag); | ||
1254 | /* Visible (scrolled) position of the run. */ | ||
1255 | const iInt2 visPos = addX_I2(add_I2(run->visBounds.pos, origin), | ||
1256 | /* Preformatted runs can be scrolled. */ | ||
1257 | runOffset_DocumentView_(d->view, run)); | ||
1258 | const iRect visRect = { visPos, run->visBounds.size }; | ||
1259 | /* Fill the background. */ { | ||
1260 | #if 0 | ||
1261 | iBool isInlineImageCaption = run->linkId && linkFlags & content_GmLinkFlag && | ||
1262 | ~linkFlags & permanent_GmLinkFlag; | ||
1263 | if (run->flags & decoration_GmRunFlag && ~run->flags & startOfLine_GmRunFlag) { | ||
1264 | /* This is the metadata. */ | ||
1265 | isInlineImageCaption = iFalse; | ||
1266 | } | ||
1267 | #endif | ||
1268 | iBool isMobileHover = deviceType_App() != desktop_AppDeviceType && | ||
1269 | (isPartOfHover || contains_PtrSet(d->view->invalidRuns, run)) && | ||
1270 | (~run->flags & decoration_GmRunFlag || run->flags & startOfLine_GmRunFlag | ||
1271 | /* highlight link icon but not image captions */); | ||
1272 | /* While this is consistent, it's a bit excessive to indicate that an inlined image | ||
1273 | is open: the image itself is the indication. */ | ||
1274 | const iBool isInlineImageCaption = iFalse; | ||
1275 | if (run->linkId && (linkFlags & isOpen_GmLinkFlag || isInlineImageCaption || isMobileHover)) { | ||
1276 | /* Open links get a highlighted background. */ | ||
1277 | int bg = tmBackgroundOpenLink_ColorId; | ||
1278 | if (isMobileHover && !isPartOfHover) { | ||
1279 | bg = tmBackground_ColorId; /* hover ended and was invalidated */ | ||
1280 | } | ||
1281 | // const int frame = tmFrameOpenLink_ColorId; | ||
1282 | const int pad = gap_Text; | ||
1283 | iRect wideRect = { init_I2(origin.x - pad, visPos.y), | ||
1284 | init_I2(d->docBounds.size.x + 2 * pad, | ||
1285 | height_Rect(run->visBounds)) }; | ||
1286 | adjustEdges_Rect(&wideRect, | ||
1287 | run->flags & startOfLine_GmRunFlag ? -pad * 3 / 4 : 0, 0, | ||
1288 | run->flags & endOfLine_GmRunFlag ? pad * 3 / 4 : 0, 0); | ||
1289 | /* The first line is composed of two runs that may be drawn in either order, so | ||
1290 | only draw half of the background. */ | ||
1291 | if (run->flags & decoration_GmRunFlag) { | ||
1292 | wideRect.size.x = right_Rect(visRect) - left_Rect(wideRect); | ||
1293 | } | ||
1294 | else if (run->flags & startOfLine_GmRunFlag) { | ||
1295 | wideRect.size.x = right_Rect(wideRect) - left_Rect(visRect); | ||
1296 | wideRect.pos.x = left_Rect(visRect); | ||
1297 | } | ||
1298 | fillRect_Paint(&d->paint, wideRect, bg); | ||
1299 | } | ||
1300 | else { | ||
1301 | /* Normal background for other runs. There are cases when runs get drawn multiple times, | ||
1302 | e.g., at the buffer boundary, and there are slightly overlapping characters in | ||
1303 | monospace blocks. Clearing the background here ensures a cleaner visual appearance | ||
1304 | since only one glyph is visible at any given point. */ | ||
1305 | fillRect_Paint(&d->paint, visRect, tmBackground_ColorId); | ||
1306 | } | ||
1307 | } | ||
1308 | if (run->linkId) { | ||
1309 | if (run->flags & decoration_GmRunFlag && run->flags & startOfLine_GmRunFlag) { | ||
1310 | /* Link icon. */ | ||
1311 | if (linkFlags & content_GmLinkFlag) { | ||
1312 | fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); | ||
1313 | } | ||
1314 | } | ||
1315 | else if (~run->flags & decoration_GmRunFlag) { | ||
1316 | fg = linkColor_GmDocument(doc, run->linkId, isHover ? textHover_GmLinkPart : text_GmLinkPart); | ||
1317 | if (linkFlags & content_GmLinkFlag) { | ||
1318 | fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); /* link is inactive */ | ||
1319 | } | ||
1320 | } | ||
1321 | } | ||
1322 | if (run->flags & altText_GmRunFlag) { | ||
1323 | const iInt2 margin = preRunMargin_GmDocument(doc, preId_GmRun(run)); | ||
1324 | fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId); | ||
1325 | drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmFrameAltText_ColorId); | ||
1326 | drawWrapRange_Text(run->font, | ||
1327 | add_I2(visPos, margin), | ||
1328 | run->visBounds.size.x - 2 * margin.x, | ||
1329 | run->color, | ||
1330 | run->text); | ||
1331 | } | ||
1332 | else { | ||
1333 | if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { | ||
1334 | const size_t ord = visibleLinkOrdinal_DocumentView_(d->view, run->linkId); | ||
1335 | if (ord >= d->view->owner->ordinalBase) { | ||
1336 | const iChar ordChar = | ||
1337 | linkOrdinalChar_DocumentWidget_(d->view->owner, ord - d->view->owner->ordinalBase); | ||
1338 | if (ordChar) { | ||
1339 | const char *circle = "\u25ef"; /* Large Circle */ | ||
1340 | const int circleFont = FONT_ID(default_FontId, regular_FontStyle, contentRegular_FontSize); | ||
1341 | iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y), | ||
1342 | init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(circleFont)) }; | ||
1343 | drawRange_Text( | ||
1344 | circleFont, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle)); | ||
1345 | iRect circleArea = visualBounds_Text(circleFont, range_CStr(circle)); | ||
1346 | addv_I2(&circleArea.pos, topLeft_Rect(nbArea)); | ||
1347 | drawCentered_Text(FONT_ID(default_FontId, regular_FontStyle, contentSmall_FontSize), | ||
1348 | circleArea, | ||
1349 | iTrue, | ||
1350 | tmQuote_ColorId, | ||
1351 | "%lc", | ||
1352 | (int) ordChar); | ||
1353 | goto runDrawn; | ||
1354 | } | ||
1355 | } | ||
1356 | } | ||
1357 | if (run->flags & quoteBorder_GmRunFlag) { | ||
1358 | drawVLine_Paint(&d->paint, | ||
1359 | addX_I2(visPos, | ||
1360 | !run->isRTL | ||
1361 | ? -gap_Text * 5 / 2 | ||
1362 | : (width_Rect(run->visBounds) + gap_Text * 5 / 2)), | ||
1363 | height_Rect(run->visBounds), | ||
1364 | tmQuoteIcon_ColorId); | ||
1365 | } | ||
1366 | /* Base attributes. */ { | ||
1367 | int f, c; | ||
1368 | runBaseAttributes_GmDocument(doc, run, &f, &c); | ||
1369 | setBaseAttributes_Text(f, c); | ||
1370 | } | ||
1371 | /* Fancy date in Gemini feed links. */ { | ||
1372 | if (run->linkId && run->flags & startOfLine_GmRunFlag && ~run->flags & decoration_GmRunFlag) { | ||
1373 | static iRegExp *datePattern_; | ||
1374 | if (!datePattern_) { | ||
1375 | datePattern_ = new_RegExp("^[12][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9]\\s", 0); | ||
1376 | } | ||
1377 | iRegExpMatch m; | ||
1378 | init_RegExpMatch(&m); | ||
1379 | if (matchRange_RegExp(datePattern_, run->text, &m)) { | ||
1380 | /* The date uses regular weight and a dimmed color. */ | ||
1381 | iString styled; | ||
1382 | initRange_String(&styled, run->text); | ||
1383 | insertData_Block(&styled.chars, 10, "\x1b[0m", 4); /* restore */ | ||
1384 | iBlock buf; | ||
1385 | init_Block(&buf, 0); | ||
1386 | appendCStr_Block(&buf, "\x1b[10m"); /* regular font weight */ | ||
1387 | appendCStr_Block(&buf, escape_Color(isHover ? fg : tmLinkFeedEntryDate_ColorId)); | ||
1388 | insertData_Block(&styled.chars, 0, constData_Block(&buf), size_Block(&buf)); | ||
1389 | deinit_Block(&buf); | ||
1390 | const int oldAnsi = ansiFlags_Text(); | ||
1391 | setAnsiFlags_Text(oldAnsi | allowFontStyle_AnsiFlag); | ||
1392 | setBaseAttributes_Text(run->font, fg); | ||
1393 | drawBoundRange_Text(run->font, | ||
1394 | visPos, | ||
1395 | (run->isRTL ? -1 : 1) * width_Rect(run->visBounds), | ||
1396 | fg, | ||
1397 | range_String(&styled)); | ||
1398 | setAnsiFlags_Text(oldAnsi); | ||
1399 | deinit_String(&styled); | ||
1400 | goto runDrawn; | ||
1401 | } | ||
1402 | } | ||
1403 | } | ||
1404 | drawBoundRange_Text(run->font, | ||
1405 | visPos, | ||
1406 | (run->isRTL ? -1 : 1) * width_Rect(run->visBounds), | ||
1407 | fg, | ||
1408 | run->text); | ||
1409 | runDrawn:; | ||
1410 | setBaseAttributes_Text(-1, -1); | ||
1411 | } | ||
1412 | /* Presentation of links. */ | ||
1413 | if (run->linkId && ~run->flags & decoration_GmRunFlag) { | ||
1414 | const int metaFont = paragraph_FontId; | ||
1415 | /* TODO: Show status of an ongoing media request. */ | ||
1416 | const int flags = linkFlags; | ||
1417 | const iRect linkRect = moved_Rect(run->visBounds, origin); | ||
1418 | iMediaRequest *mr = NULL; | ||
1419 | /* Show metadata about inline content. */ | ||
1420 | if (flags & content_GmLinkFlag && run->flags & endOfLine_GmRunFlag) { | ||
1421 | fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); | ||
1422 | iString text; | ||
1423 | init_String(&text); | ||
1424 | const iMediaId linkMedia = findMediaForLink_Media(constMedia_GmDocument(doc), | ||
1425 | run->linkId, none_MediaType); | ||
1426 | iAssert(linkMedia.type != none_MediaType); | ||
1427 | iGmMediaInfo info; | ||
1428 | info_Media(constMedia_GmDocument(doc), linkMedia, &info); | ||
1429 | switch (linkMedia.type) { | ||
1430 | case image_MediaType: { | ||
1431 | /* There's a separate decorative GmRun for the metadata. */ | ||
1432 | break; | ||
1433 | } | ||
1434 | case audio_MediaType: | ||
1435 | format_String(&text, "%s", info.type); | ||
1436 | break; | ||
1437 | case download_MediaType: | ||
1438 | format_String(&text, "%s", info.type); | ||
1439 | break; | ||
1440 | default: | ||
1441 | break; | ||
1442 | } | ||
1443 | if (linkMedia.type != download_MediaType && /* can't cancel downloads currently */ | ||
1444 | linkMedia.type != image_MediaType && | ||
1445 | findMediaRequest_DocumentWidget_(d->view->owner, run->linkId)) { | ||
1446 | appendFormat_String( | ||
1447 | &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : ""); | ||
1448 | } | ||
1449 | const iInt2 size = measureRange_Text(metaFont, range_String(&text)).bounds.size; | ||
1450 | if (size.x) { | ||
1451 | fillRect_Paint( | ||
1452 | &d->paint, | ||
1453 | (iRect){ add_I2(origin, addX_I2(topRight_Rect(run->bounds), -size.x - gap_UI)), | ||
1454 | addX_I2(size, 2 * gap_UI) }, | ||
1455 | tmBackground_ColorId); | ||
1456 | drawAlign_Text(metaFont, | ||
1457 | add_I2(topRight_Rect(run->bounds), origin), | ||
1458 | fg, | ||
1459 | right_Alignment, | ||
1460 | "%s", cstr_String(&text)); | ||
1461 | } | ||
1462 | deinit_String(&text); | ||
1463 | } | ||
1464 | else if (run->flags & endOfLine_GmRunFlag && | ||
1465 | (mr = findMediaRequest_DocumentWidget_(d->view->owner, run->linkId)) != NULL) { | ||
1466 | if (!isFinished_GmRequest(mr->req)) { | ||
1467 | draw_Text(metaFont, | ||
1468 | topRight_Rect(linkRect), | ||
1469 | tmInlineContentMetadata_ColorId, | ||
1470 | translateCStr_Lang(" \u2014 ${doc.fetching}\u2026 (%.1f ${mb})"), | ||
1471 | (float) bodySize_GmRequest(mr->req) / 1.0e6f); | ||
1472 | } | ||
1473 | } | ||
1474 | } | ||
1475 | if (0) { | ||
1476 | drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); | ||
1477 | drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); | ||
1478 | } | ||
1479 | } | ||
1480 | |||
1481 | static int drawSideRect_(iPaint *p, iRect rect) { | ||
1482 | int bg = tmBannerBackground_ColorId; | ||
1483 | int fg = tmBannerIcon_ColorId; | ||
1484 | if (equal_Color(get_Color(bg), get_Color(tmBackground_ColorId))) { | ||
1485 | bg = tmBannerIcon_ColorId; | ||
1486 | fg = tmBannerBackground_ColorId; | ||
1487 | } | ||
1488 | fillRect_Paint(p, rect, bg); | ||
1489 | return fg; | ||
1490 | } | ||
1491 | |||
1492 | static int sideElementAvailWidth_DocumentView_(const iDocumentView *d) { | ||
1493 | return left_Rect(documentBounds_DocumentView_(d)) - | ||
1494 | left_Rect(bounds_Widget(constAs_Widget(d->owner))) - 2 * d->pageMargin * gap_UI; | ||
1495 | } | ||
1496 | |||
1497 | static iBool isSideHeadingVisible_DocumentView_(const iDocumentView *d) { | ||
1498 | return sideElementAvailWidth_DocumentView_(d) >= lineHeight_Text(banner_FontId) * 4.5f; | ||
1499 | } | ||
1500 | |||
1501 | static void updateSideIconBuf_DocumentView_(const iDocumentView *d) { | ||
1502 | if (!isExposed_Window(get_Window())) { | ||
1503 | return; | ||
1504 | } | ||
1505 | iDrawBufs *dbuf = d->drawBufs; | ||
1506 | dbuf->flags &= ~updateSideBuf_DrawBufsFlag; | ||
1507 | if (dbuf->sideIconBuf) { | ||
1508 | SDL_DestroyTexture(dbuf->sideIconBuf); | ||
1509 | dbuf->sideIconBuf = NULL; | ||
1510 | } | ||
1511 | // const iGmRun *banner = siteBanner_GmDocument(d->doc); | ||
1512 | if (isEmpty_Banner(d->owner->banner)) { | ||
1513 | return; | ||
1514 | } | ||
1515 | const int margin = gap_UI * d->pageMargin; | ||
1516 | const int minBannerSize = lineHeight_Text(banner_FontId) * 2; | ||
1517 | const iChar icon = siteIcon_GmDocument(d->doc); | ||
1518 | const int avail = sideElementAvailWidth_DocumentView_(d) - margin; | ||
1519 | iBool isHeadingVisible = isSideHeadingVisible_DocumentView_(d); | ||
1520 | /* Determine the required size. */ | ||
1521 | iInt2 bufSize = init1_I2(minBannerSize); | ||
1522 | const int sideHeadingFont = FONT_ID(documentHeading_FontId, regular_FontStyle, contentBig_FontSize); | ||
1523 | if (isHeadingVisible) { | ||
1524 | const iInt2 headingSize = measureWrapRange_Text(sideHeadingFont, avail, | ||
1525 | currentHeading_DocumentView_(d)).bounds.size; | ||
1526 | if (headingSize.x > 0) { | ||
1527 | bufSize.y += gap_Text + headingSize.y; | ||
1528 | bufSize.x = iMax(bufSize.x, headingSize.x); | ||
1529 | } | ||
1530 | else { | ||
1531 | isHeadingVisible = iFalse; | ||
1532 | } | ||
1533 | } | ||
1534 | SDL_Renderer *render = renderer_Window(get_Window()); | ||
1535 | dbuf->sideIconBuf = SDL_CreateTexture(render, | ||
1536 | SDL_PIXELFORMAT_RGBA4444, | ||
1537 | SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, | ||
1538 | bufSize.x, bufSize.y); | ||
1539 | iPaint p; | ||
1540 | init_Paint(&p); | ||
1541 | beginTarget_Paint(&p, dbuf->sideIconBuf); | ||
1542 | const iColor back = get_Color(tmBannerSideTitle_ColorId); | ||
1543 | SDL_SetRenderDrawColor(render, back.r, back.g, back.b, 0); /* better blending of the edge */ | ||
1544 | SDL_RenderClear(render); | ||
1545 | const iRect iconRect = { zero_I2(), init1_I2(minBannerSize) }; | ||
1546 | int fg = drawSideRect_(&p, iconRect); | ||
1547 | iString str; | ||
1548 | initUnicodeN_String(&str, &icon, 1); | ||
1549 | drawCentered_Text(banner_FontId, iconRect, iTrue, fg, "%s", cstr_String(&str)); | ||
1550 | deinit_String(&str); | ||
1551 | if (isHeadingVisible) { | ||
1552 | iRangecc text = currentHeading_DocumentView_(d); | ||
1553 | iInt2 pos = addY_I2(bottomLeft_Rect(iconRect), gap_Text); | ||
1554 | const int font = sideHeadingFont; | ||
1555 | drawWrapRange_Text(font, pos, avail, tmBannerSideTitle_ColorId, text); | ||
1556 | } | ||
1557 | endTarget_Paint(&p); | ||
1558 | SDL_SetTextureBlendMode(dbuf->sideIconBuf, SDL_BLENDMODE_BLEND); | ||
1559 | } | ||
1560 | |||
1561 | static void drawSideElements_DocumentView_(const iDocumentView *d) { | ||
1562 | const iWidget *w = constAs_Widget(d->owner); | ||
1563 | const iRect bounds = bounds_Widget(w); | ||
1564 | const iRect docBounds = documentBounds_DocumentView_(d); | ||
1565 | const int margin = gap_UI * d->pageMargin; | ||
1566 | float opacity = value_Anim(&d->sideOpacity); | ||
1567 | const int avail = left_Rect(docBounds) - left_Rect(bounds) - 2 * margin; | ||
1568 | iDrawBufs * dbuf = d->drawBufs; | ||
1569 | iPaint p; | ||
1570 | init_Paint(&p); | ||
1571 | setClip_Paint(&p, boundsWithoutVisualOffset_Widget(w)); | ||
1572 | /* Side icon and current heading. */ | ||
1573 | if (prefs_App()->sideIcon && opacity > 0 && dbuf->sideIconBuf) { | ||
1574 | const iInt2 texSize = size_SDLTexture(dbuf->sideIconBuf); | ||
1575 | if (avail > texSize.x) { | ||
1576 | const int minBannerSize = lineHeight_Text(banner_FontId) * 2; | ||
1577 | iInt2 pos = addY_I2(add_I2(topLeft_Rect(bounds), init_I2(margin, 0)), | ||
1578 | height_Rect(bounds) / 2 - minBannerSize / 2 - | ||
1579 | (texSize.y > minBannerSize | ||
1580 | ? (gap_Text + lineHeight_Text(heading3_FontId)) / 2 | ||
1581 | : 0)); | ||
1582 | SDL_SetTextureAlphaMod(dbuf->sideIconBuf, 255 * opacity); | ||
1583 | SDL_RenderCopy(renderer_Window(get_Window()), | ||
1584 | dbuf->sideIconBuf, NULL, | ||
1585 | &(SDL_Rect){ pos.x, pos.y, texSize.x, texSize.y }); | ||
1586 | } | ||
1587 | } | ||
1588 | /* Reception timestamp. */ | ||
1589 | if (dbuf->timestampBuf && dbuf->timestampBuf->size.x <= avail) { | ||
1590 | draw_TextBuf( | ||
1591 | dbuf->timestampBuf, | ||
1592 | add_I2( | ||
1593 | bottomLeft_Rect(bounds), | ||
1594 | init_I2(margin, | ||
1595 | -margin + -dbuf->timestampBuf->size.y + | ||
1596 | iMax(0, d->scrollY.max - pos_SmoothScroll(&d->scrollY)))), | ||
1597 | tmQuoteIcon_ColorId); | ||
1598 | } | ||
1599 | unsetClip_Paint(&p); | ||
1600 | } | ||
1601 | |||
1602 | static void drawMedia_DocumentView_(const iDocumentView *d, iPaint *p) { | ||
1603 | iConstForEach(PtrArray, i, &d->visibleMedia) { | ||
1604 | const iGmRun * run = i.ptr; | ||
1605 | if (run->mediaType == audio_MediaType) { | ||
1606 | iPlayerUI ui; | ||
1607 | init_PlayerUI(&ui, | ||
1608 | audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)), | ||
1609 | runRect_DocumentView_(d, run)); | ||
1610 | draw_PlayerUI(&ui, p); | ||
1611 | } | ||
1612 | else if (run->mediaType == download_MediaType) { | ||
1613 | iDownloadUI ui; | ||
1614 | init_DownloadUI(&ui, constMedia_GmDocument(d->doc), run->mediaId, | ||
1615 | runRect_DocumentView_(d, run)); | ||
1616 | draw_DownloadUI(&ui, p); | ||
1617 | } | ||
1618 | } | ||
1619 | } | ||
1620 | |||
1621 | static void extend_GmRunRange_(iGmRunRange *runs) { | ||
1622 | if (runs->start) { | ||
1623 | runs->start--; | ||
1624 | runs->end++; | ||
1625 | } | ||
1626 | } | ||
1627 | |||
1628 | static iBool render_DocumentView_(const iDocumentView *d, iDrawContext *ctx, iBool prerenderExtra) { | ||
1629 | iBool didDraw = iFalse; | ||
1630 | const iRect bounds = bounds_Widget(constAs_Widget(d->owner)); | ||
1631 | const iRect ctxWidgetBounds = | ||
1632 | init_Rect(0, | ||
1633 | 0, | ||
1634 | width_Rect(bounds) - constAs_Widget(d->owner->scroll)->rect.size.x, | ||
1635 | height_Rect(bounds)); | ||
1636 | const iRangei full = { 0, size_GmDocument(d->doc).y }; | ||
1637 | const iRangei vis = ctx->vis; | ||
1638 | iVisBuf *visBuf = d->visBuf; /* will be updated now */ | ||
1639 | d->drawBufs->lastRenderTime = SDL_GetTicks(); | ||
1640 | /* Swap buffers around to have room available both before and after the visible region. */ | ||
1641 | allocVisBuffer_DocumentView_(d); | ||
1642 | reposition_VisBuf(visBuf, vis); | ||
1643 | /* Redraw the invalid ranges. */ | ||
1644 | if (~flags_Widget(constAs_Widget(d->owner)) & destroyPending_WidgetFlag) { | ||
1645 | iPaint *p = &ctx->paint; | ||
1646 | init_Paint(p); | ||
1647 | iForIndices(i, visBuf->buffers) { | ||
1648 | iVisBufTexture *buf = &visBuf->buffers[i]; | ||
1649 | iVisBufMeta *meta = buf->user; | ||
1650 | const iRangei bufRange = intersect_Rangei(bufferRange_VisBuf(visBuf, i), full); | ||
1651 | const iRangei bufVisRange = intersect_Rangei(bufRange, vis); | ||
1652 | ctx->widgetBounds = moved_Rect(ctxWidgetBounds, init_I2(0, -buf->origin)); | ||
1653 | ctx->viewPos = init_I2(left_Rect(ctx->docBounds) - left_Rect(bounds), -buf->origin); | ||
1654 | // printf(" buffer %zu: buf vis range %d...%d\n", i, bufVisRange.start, bufVisRange.end); | ||
1655 | if (!prerenderExtra && !isEmpty_Range(&bufVisRange)) { | ||
1656 | didDraw = iTrue; | ||
1657 | if (isEmpty_Rangei(buf->validRange)) { | ||
1658 | /* Fill the required currently visible range (vis). */ | ||
1659 | const iRangei bufVisRange = intersect_Rangei(bufRange, vis); | ||
1660 | if (!isEmpty_Range(&bufVisRange)) { | ||
1661 | beginTarget_Paint(p, buf->texture); | ||
1662 | fillRect_Paint(p, (iRect){ zero_I2(), visBuf->texSize }, tmBackground_ColorId); | ||
1663 | iZap(ctx->runsDrawn); | ||
1664 | render_GmDocument(d->doc, bufVisRange, drawRun_DrawContext_, ctx); | ||
1665 | meta->runsDrawn = ctx->runsDrawn; | ||
1666 | extend_GmRunRange_(&meta->runsDrawn); | ||
1667 | buf->validRange = bufVisRange; | ||
1668 | // printf(" buffer %zu valid %d...%d\n", i, bufRange.start, bufRange.end); | ||
1669 | } | ||
1670 | } | ||
1671 | else { | ||
1672 | /* Progressively fill the required runs. */ | ||
1673 | if (meta->runsDrawn.start) { | ||
1674 | beginTarget_Paint(p, buf->texture); | ||
1675 | meta->runsDrawn.start = renderProgressive_GmDocument(d->doc, meta->runsDrawn.start, | ||
1676 | -1, iInvalidSize, | ||
1677 | bufVisRange, | ||
1678 | drawRun_DrawContext_, | ||
1679 | ctx); | ||
1680 | buf->validRange.start = bufVisRange.start; | ||
1681 | } | ||
1682 | if (meta->runsDrawn.end) { | ||
1683 | beginTarget_Paint(p, buf->texture); | ||
1684 | meta->runsDrawn.end = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end, | ||
1685 | +1, iInvalidSize, | ||
1686 | bufVisRange, | ||
1687 | drawRun_DrawContext_, | ||
1688 | ctx); | ||
1689 | buf->validRange.end = bufVisRange.end; | ||
1690 | } | ||
1691 | } | ||
1692 | } | ||
1693 | /* Progressively draw the rest of the buffer if it isn't fully valid. */ | ||
1694 | if (prerenderExtra && !equal_Rangei(bufRange, buf->validRange)) { | ||
1695 | const iGmRun *next; | ||
1696 | // printf("%zu: prerenderExtra (start:%p end:%p)\n", i, meta->runsDrawn.start, meta->runsDrawn.end); | ||
1697 | if (meta->runsDrawn.start == NULL) { | ||
1698 | /* Haven't drawn anything yet in this buffer, so let's try seeding it. */ | ||
1699 | const int rh = lineHeight_Text(paragraph_FontId); | ||
1700 | const int y = i >= iElemCount(visBuf->buffers) / 2 ? bufRange.start : (bufRange.end - rh); | ||
1701 | beginTarget_Paint(p, buf->texture); | ||
1702 | fillRect_Paint(p, (iRect){ zero_I2(), visBuf->texSize }, tmBackground_ColorId); | ||
1703 | buf->validRange = (iRangei){ y, y + rh }; | ||
1704 | iZap(ctx->runsDrawn); | ||
1705 | render_GmDocument(d->doc, buf->validRange, drawRun_DrawContext_, ctx); | ||
1706 | meta->runsDrawn = ctx->runsDrawn; | ||
1707 | extend_GmRunRange_(&meta->runsDrawn); | ||
1708 | // printf("%zu: seeded, next %p:%p\n", i, meta->runsDrawn.start, meta->runsDrawn.end); | ||
1709 | didDraw = iTrue; | ||
1710 | } | ||
1711 | else { | ||
1712 | if (meta->runsDrawn.start) { | ||
1713 | const iRangei upper = intersect_Rangei(bufRange, (iRangei){ full.start, buf->validRange.start }); | ||
1714 | if (upper.end > upper.start) { | ||
1715 | beginTarget_Paint(p, buf->texture); | ||
1716 | next = renderProgressive_GmDocument(d->doc, meta->runsDrawn.start, | ||
1717 | -1, 1, upper, | ||
1718 | drawRun_DrawContext_, | ||
1719 | ctx); | ||
1720 | if (next && meta->runsDrawn.start != next) { | ||
1721 | meta->runsDrawn.start = next; | ||
1722 | buf->validRange.start = bottom_Rect(next->visBounds); | ||
1723 | didDraw = iTrue; | ||
1724 | } | ||
1725 | else { | ||
1726 | buf->validRange.start = bufRange.start; | ||
1727 | } | ||
1728 | } | ||
1729 | } | ||
1730 | if (!didDraw && meta->runsDrawn.end) { | ||
1731 | const iRangei lower = intersect_Rangei(bufRange, (iRangei){ buf->validRange.end, full.end }); | ||
1732 | if (lower.end > lower.start) { | ||
1733 | beginTarget_Paint(p, buf->texture); | ||
1734 | next = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end, | ||
1735 | +1, 1, lower, | ||
1736 | drawRun_DrawContext_, | ||
1737 | ctx); | ||
1738 | if (next && meta->runsDrawn.end != next) { | ||
1739 | meta->runsDrawn.end = next; | ||
1740 | buf->validRange.end = top_Rect(next->visBounds); | ||
1741 | didDraw = iTrue; | ||
1742 | } | ||
1743 | else { | ||
1744 | buf->validRange.end = bufRange.end; | ||
1745 | } | ||
1746 | } | ||
1747 | } | ||
1748 | } | ||
1749 | } | ||
1750 | /* Draw any invalidated runs that fall within this buffer. */ | ||
1751 | if (!prerenderExtra) { | ||
1752 | const iRangei bufRange = { buf->origin, buf->origin + visBuf->texSize.y }; | ||
1753 | /* Clear full-width backgrounds first in case there are any dynamic elements. */ { | ||
1754 | iConstForEach(PtrSet, r, d->invalidRuns) { | ||
1755 | const iGmRun *run = *r.value; | ||
1756 | if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { | ||
1757 | beginTarget_Paint(p, buf->texture); | ||
1758 | fillRect_Paint(p, | ||
1759 | init_Rect(0, | ||
1760 | run->visBounds.pos.y - buf->origin, | ||
1761 | visBuf->texSize.x, | ||
1762 | run->visBounds.size.y), | ||
1763 | tmBackground_ColorId); | ||
1764 | } | ||
1765 | } | ||
1766 | } | ||
1767 | setAnsiFlags_Text(ansiEscapes_GmDocument(d->doc)); | ||
1768 | iConstForEach(PtrSet, r, d->invalidRuns) { | ||
1769 | const iGmRun *run = *r.value; | ||
1770 | if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { | ||
1771 | beginTarget_Paint(p, buf->texture); | ||
1772 | drawRun_DrawContext_(ctx, run); | ||
1773 | } | ||
1774 | } | ||
1775 | setAnsiFlags_Text(allowAll_AnsiFlag); | ||
1776 | } | ||
1777 | endTarget_Paint(p); | ||
1778 | if (prerenderExtra && didDraw) { | ||
1779 | /* Just a run at a time. */ | ||
1780 | break; | ||
1781 | } | ||
1782 | } | ||
1783 | if (!prerenderExtra) { | ||
1784 | clear_PtrSet(d->invalidRuns); | ||
1785 | } | ||
1786 | } | ||
1787 | return didDraw; | ||
1788 | } | ||
1789 | |||
1790 | static void draw_DocumentView_(const iDocumentView *d) { | ||
1791 | const iWidget *w = constAs_Widget(d->owner); | ||
1792 | const iRect bounds = bounds_Widget(w); | ||
1793 | const iRect boundsWithoutVisOff = boundsWithoutVisualOffset_Widget(w); | ||
1794 | const iRect clipBounds = intersect_Rect(bounds, boundsWithoutVisOff); | ||
1795 | /* Each document has its own palette, but the drawing routines rely on a global one. | ||
1796 | As we're now drawing a document, ensure that the right palette is in effect. | ||
1797 | Document theme colors can be used elsewhere, too, but first a document's palette | ||
1798 | must be made global. */ | ||
1799 | makePaletteGlobal_GmDocument(d->doc); | ||
1800 | if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { | ||
1801 | updateTimestampBuf_DocumentView_(d); | ||
1802 | } | ||
1803 | if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) { | ||
1804 | updateSideIconBuf_DocumentView_(d); | ||
1805 | } | ||
1806 | const iRect docBounds = documentBounds_DocumentView_(d); | ||
1807 | const iRangei vis = visibleRange_DocumentView_(d); | ||
1808 | iDrawContext ctx = { | ||
1809 | .view = d, | ||
1810 | .docBounds = docBounds, | ||
1811 | .vis = vis, | ||
1812 | .showLinkNumbers = (d->owner->flags & showLinkNumbers_DocumentWidgetFlag) != 0, | ||
1813 | }; | ||
1814 | init_Paint(&ctx.paint); | ||
1815 | render_DocumentView_(d, &ctx, iFalse /* just the mandatory parts */); | ||
1816 | iBanner *banner = d->owner->banner; | ||
1817 | int yTop = docBounds.pos.y + viewPos_DocumentView_(d); | ||
1818 | const iBool isDocEmpty = size_GmDocument(d->doc).y == 0; | ||
1819 | const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; | ||
1820 | if (!isDocEmpty || !isEmpty_Banner(banner)) { | ||
1821 | const int docBgColor = isDocEmpty ? tmBannerBackground_ColorId : tmBackground_ColorId; | ||
1822 | setClip_Paint(&ctx.paint, clipBounds); | ||
1823 | if (!isDocEmpty) { | ||
1824 | draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds)); | ||
1825 | } | ||
1826 | /* Text markers. */ | ||
1827 | if (!isEmpty_Range(&d->owner->foundMark) || !isEmpty_Range(&d->owner->selectMark)) { | ||
1828 | SDL_Renderer *render = renderer_Window(get_Window()); | ||
1829 | ctx.firstMarkRect = zero_Rect(); | ||
1830 | ctx.lastMarkRect = zero_Rect(); | ||
1831 | SDL_SetRenderDrawBlendMode(render, | ||
1832 | isDark_ColorTheme(colorTheme_App()) ? SDL_BLENDMODE_ADD | ||
1833 | : SDL_BLENDMODE_BLEND); | ||
1834 | ctx.viewPos = topLeft_Rect(docBounds); | ||
1835 | /* Marker starting outside the visible range? */ | ||
1836 | if (d->visibleRuns.start) { | ||
1837 | if (!isEmpty_Range(&d->owner->selectMark) && | ||
1838 | d->owner->selectMark.start < d->visibleRuns.start->text.start && | ||
1839 | d->owner->selectMark.end > d->visibleRuns.start->text.start) { | ||
1840 | ctx.inSelectMark = iTrue; | ||
1841 | } | ||
1842 | if (isEmpty_Range(&d->owner->foundMark) && | ||
1843 | d->owner->foundMark.start < d->visibleRuns.start->text.start && | ||
1844 | d->owner->foundMark.end > d->visibleRuns.start->text.start) { | ||
1845 | ctx.inFoundMark = iTrue; | ||
1846 | } | ||
1847 | } | ||
1848 | render_GmDocument(d->doc, vis, drawMark_DrawContext_, &ctx); | ||
1849 | SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); | ||
1850 | /* Selection range pins. */ | ||
1851 | if (isTouchSelecting) { | ||
1852 | drawPin_Paint(&ctx.paint, ctx.firstMarkRect, 0, tmQuote_ColorId); | ||
1853 | drawPin_Paint(&ctx.paint, ctx.lastMarkRect, 1, tmQuote_ColorId); | ||
1854 | } | ||
1855 | } | ||
1856 | drawMedia_DocumentView_(d, &ctx.paint); | ||
1857 | /* Fill the top and bottom, in case the document is short. */ | ||
1858 | if (yTop > top_Rect(bounds)) { | ||
1859 | fillRect_Paint(&ctx.paint, | ||
1860 | (iRect){ bounds.pos, init_I2(bounds.size.x, yTop - top_Rect(bounds)) }, | ||
1861 | !isEmpty_Banner(banner) ? tmBannerBackground_ColorId | ||
1862 | : docBgColor); | ||
1863 | } | ||
1864 | /* Banner. */ | ||
1865 | if (!isDocEmpty || numItems_Banner(banner) > 0) { | ||
1866 | /* Fill the part between the banner and the top of the document. */ | ||
1867 | fillRect_Paint(&ctx.paint, | ||
1868 | (iRect){ init_I2(left_Rect(bounds), | ||
1869 | top_Rect(docBounds) + viewPos_DocumentView_(d) - | ||
1870 | documentTopPad_DocumentView_(d)), | ||
1871 | init_I2(bounds.size.x, documentTopPad_DocumentView_(d)) }, | ||
1872 | docBgColor); | ||
1873 | setPos_Banner(banner, addY_I2(topLeft_Rect(docBounds), | ||
1874 | -pos_SmoothScroll(&d->scrollY))); | ||
1875 | draw_Banner(banner); | ||
1876 | } | ||
1877 | const int yBottom = yTop + size_GmDocument(d->doc).y; | ||
1878 | if (yBottom < bottom_Rect(bounds)) { | ||
1879 | fillRect_Paint(&ctx.paint, | ||
1880 | init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom), | ||
1881 | !isDocEmpty ? docBgColor : tmBannerBackground_ColorId); | ||
1882 | } | ||
1883 | unsetClip_Paint(&ctx.paint); | ||
1884 | drawSideElements_DocumentView_(d); | ||
1885 | /* Alt text. */ | ||
1886 | const float altTextOpacity = value_Anim(&d->altTextOpacity) * 6 - 5; | ||
1887 | if (d->hoverAltPre && altTextOpacity > 0) { | ||
1888 | const iGmPreMeta *meta = preMeta_GmDocument(d->doc, preId_GmRun(d->hoverAltPre)); | ||
1889 | if (meta->flags & topLeft_GmPreMetaFlag && ~meta->flags & decoration_GmRunFlag && | ||
1890 | !isEmpty_Range(&meta->altText)) { | ||
1891 | const int margin = 3 * gap_UI / 2; | ||
1892 | const int altFont = uiLabel_FontId; | ||
1893 | const int wrap = docBounds.size.x - 2 * margin; | ||
1894 | iInt2 pos = addY_I2(add_I2(docBounds.pos, meta->pixelRect.pos), | ||
1895 | viewPos_DocumentView_(d)); | ||
1896 | const iInt2 textSize = measureWrapRange_Text(altFont, wrap, meta->altText).bounds.size; | ||
1897 | pos.y -= textSize.y + gap_UI; | ||
1898 | pos.y = iMax(pos.y, top_Rect(bounds)); | ||
1899 | const iRect altRect = { pos, init_I2(docBounds.size.x, textSize.y) }; | ||
1900 | ctx.paint.alpha = altTextOpacity * 255; | ||
1901 | if (altTextOpacity < 1) { | ||
1902 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | ||
1903 | } | ||
1904 | fillRect_Paint(&ctx.paint, altRect, tmBackgroundAltText_ColorId); | ||
1905 | drawRect_Paint(&ctx.paint, altRect, tmFrameAltText_ColorId); | ||
1906 | setOpacity_Text(altTextOpacity); | ||
1907 | drawWrapRange_Text(altFont, addX_I2(pos, margin), wrap, | ||
1908 | tmQuote_ColorId, meta->altText); | ||
1909 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | ||
1910 | setOpacity_Text(1.0f); | ||
1911 | } | ||
1912 | } | ||
1913 | /* Touch selection indicator. */ | ||
1914 | if (isTouchSelecting) { | ||
1915 | iRect rect = { topLeft_Rect(bounds), | ||
1916 | init_I2(width_Rect(bounds), lineHeight_Text(uiLabelBold_FontId)) }; | ||
1917 | fillRect_Paint(&ctx.paint, rect, uiTextAction_ColorId); | ||
1918 | const iRangecc mark = selectMark_DocumentWidget_(d->owner); | ||
1919 | drawCentered_Text(uiLabelBold_FontId, | ||
1920 | rect, | ||
1921 | iFalse, | ||
1922 | uiBackground_ColorId, | ||
1923 | "%zu bytes selected", /* TODO: i18n */ | ||
1924 | size_Range(&mark)); | ||
1925 | } | ||
1926 | } | ||
1927 | } | ||
1928 | |||
1929 | /*----------------------------------------------------------------------------------------------*/ | ||
1930 | |||
1931 | static void enableActions_DocumentWidget_(iDocumentWidget *d, iBool enable) { | ||
1932 | /* Actions are invisible child widgets of the DocumentWidget. */ | ||
1933 | iForEach(ObjectList, i, children_Widget(d)) { | ||
1934 | if (isAction_Widget(i.object)) { | ||
1935 | setFlags_Widget(i.object, disabled_WidgetFlag, !enable); | ||
1936 | } | ||
1937 | } | ||
1938 | } | ||
1939 | |||
1940 | static void setLinkNumberMode_DocumentWidget_(iDocumentWidget *d, iBool set) { | ||
1941 | if (((d->flags & showLinkNumbers_DocumentWidgetFlag) != 0) != set) { | ||
1942 | iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, set); | ||
1943 | /* Children have priority when handling events. */ | ||
1944 | enableActions_DocumentWidget_(d, !set); | ||
1945 | #if defined (iPlatformAppleDesktop) | ||
1946 | enableMenuItemsOnHomeRow_MacOS(!set); | ||
1947 | #endif | ||
1948 | /* Ensure all keyboard events come here first. */ | ||
1949 | setKeyboardGrab_Widget(set ? as_Widget(d) : NULL); | ||
1950 | if (d->menu) { | ||
1951 | setFlags_Widget(d->menu, disabled_WidgetFlag, set); | ||
1952 | } | ||
1953 | } | ||
1954 | } | ||
1955 | |||
1956 | static void requestUpdated_DocumentWidget_(iAnyObject *obj) { | ||
1957 | iDocumentWidget *d = obj; | ||
1958 | const int wasUpdated = exchange_Atomic(&d->isRequestUpdated, iTrue); | ||
1959 | if (!wasUpdated) { | ||
1960 | postCommand_Widget(obj, | ||
1961 | "document.request.updated doc:%p reqid:%u request:%p", | ||
1962 | d, | ||
1963 | id_GmRequest(d->request), | ||
1964 | d->request); | ||
1965 | } | ||
1966 | } | ||
1967 | |||
1968 | static void requestFinished_DocumentWidget_(iAnyObject *obj) { | ||
1969 | iDocumentWidget *d = obj; | ||
1970 | postCommand_Widget(obj, | ||
1971 | "document.request.finished doc:%p reqid:%u request:%p", | ||
1972 | d, | ||
1973 | id_GmRequest(d->request), | ||
1974 | d->request); | ||
1975 | } | ||
1976 | |||
1977 | static void animate_DocumentWidget_(void *ticker) { | ||
1978 | iDocumentWidget *d = ticker; | ||
1979 | iAssert(isInstance_Object(d, &Class_DocumentWidget)); | ||
1980 | refresh_Widget(d); | ||
1981 | if (!isFinished_Anim(&d->view.sideOpacity) || !isFinished_Anim(&d->view.altTextOpacity) || | ||
1982 | (d->linkInfo && !isFinished_Anim(&d->linkInfo->opacity))) { | ||
1983 | addTicker_App(animate_DocumentWidget_, d); | ||
1984 | } | ||
811 | } | 1985 | } |
812 | 1986 | ||
813 | static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) { | 1987 | static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) { |
@@ -819,10 +1993,10 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) { | |||
819 | } | 1993 | } |
820 | static const uint32_t invalidInterval_ = ~0u; | 1994 | static const uint32_t invalidInterval_ = ~0u; |
821 | uint32_t interval = invalidInterval_; | 1995 | uint32_t interval = invalidInterval_; |
822 | iConstForEach(PtrArray, i, &d->visibleMedia) { | 1996 | iConstForEach(PtrArray, i, &d->view.visibleMedia) { |
823 | const iGmRun *run = i.ptr; | 1997 | const iGmRun *run = i.ptr; |
824 | if (run->mediaType == audio_MediaType) { | 1998 | if (run->mediaType == audio_MediaType) { |
825 | iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); | 1999 | iPlayer *plr = audioPlayer_Media(media_GmDocument(d->view.doc), mediaId_GmRun(run)); |
826 | if (flags_Player(plr) & adjustingVolume_PlayerFlag || | 2000 | if (flags_Player(plr) & adjustingVolume_PlayerFlag || |
827 | (isStarted_Player(plr) && !isPaused_Player(plr))) { | 2001 | (isStarted_Player(plr) && !isPaused_Player(plr))) { |
828 | interval = iMin(interval, 1000 / 15); | 2002 | interval = iMin(interval, 1000 / 15); |
@@ -845,10 +2019,10 @@ static uint32_t postMediaUpdate_DocumentWidget_(uint32_t interval, void *context | |||
845 | static void updateMedia_DocumentWidget_(iDocumentWidget *d) { | 2019 | static void updateMedia_DocumentWidget_(iDocumentWidget *d) { |
846 | if (document_App() == d) { | 2020 | if (document_App() == d) { |
847 | refresh_Widget(d); | 2021 | refresh_Widget(d); |
848 | iConstForEach(PtrArray, i, &d->visibleMedia) { | 2022 | iConstForEach(PtrArray, i, &d->view.visibleMedia) { |
849 | const iGmRun *run = i.ptr; | 2023 | const iGmRun *run = i.ptr; |
850 | if (run->mediaType == audio_MediaType) { | 2024 | if (run->mediaType == audio_MediaType) { |
851 | iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); | 2025 | iPlayer *plr = audioPlayer_Media(media_GmDocument(d->view.doc), mediaId_GmRun(run)); |
852 | if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag && | 2026 | if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag && |
853 | flags_Player(plr) & adjustingVolume_PlayerFlag) { | 2027 | flags_Player(plr) & adjustingVolume_PlayerFlag) { |
854 | setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse); | 2028 | setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse); |
@@ -876,88 +2050,6 @@ static void animateMedia_DocumentWidget_(iDocumentWidget *d) { | |||
876 | } | 2050 | } |
877 | } | 2051 | } |
878 | 2052 | ||
879 | static iRangecc currentHeading_DocumentWidget_(const iDocumentWidget *d) { | ||
880 | iRangecc heading = iNullRange; | ||
881 | if (d->visibleRuns.start) { | ||
882 | iConstForEach(Array, i, headings_GmDocument(d->doc)) { | ||
883 | const iGmHeading *head = i.value; | ||
884 | if (head->level == 0) { | ||
885 | if (head->text.start <= d->visibleRuns.start->text.start) { | ||
886 | heading = head->text; | ||
887 | } | ||
888 | if (d->visibleRuns.end && head->text.start > d->visibleRuns.end->text.start) { | ||
889 | break; | ||
890 | } | ||
891 | } | ||
892 | } | ||
893 | } | ||
894 | return heading; | ||
895 | } | ||
896 | |||
897 | static int updateScrollMax_DocumentWidget_(iDocumentWidget *d) { | ||
898 | arrange_Widget(d->footerButtons); /* scrollMax depends on footer height */ | ||
899 | const int scrollMax = scrollMax_DocumentWidget_(d); | ||
900 | setMax_SmoothScroll(&d->scrollY, scrollMax); | ||
901 | return scrollMax; | ||
902 | } | ||
903 | |||
904 | static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | ||
905 | iChangeFlags(d->flags, | ||
906 | centerVertically_DocumentWidgetFlag, | ||
907 | prefs_App()->centerShortDocs || startsWithCase_String(d->mod.url, "about:") || | ||
908 | !isSuccess_GmStatusCode(d->sourceStatus)); | ||
909 | const iRangei visRange = visibleRange_DocumentWidget_(d); | ||
910 | // printf("visRange: %d...%d\n", visRange.start, visRange.end); | ||
911 | const iRect bounds = bounds_Widget(as_Widget(d)); | ||
912 | const int scrollMax = updateScrollMax_DocumentWidget_(d); | ||
913 | /* Reposition the footer buttons as appropriate. */ | ||
914 | /* TODO: You can just position `footerButtons` here completely without having to get | ||
915 | `Widget` involved with the offset in any way. */ | ||
916 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax }); | ||
917 | const int docSize = pageHeight_DocumentWidget_(d) + iMax(height_Widget(d->phoneToolbar), | ||
918 | height_Widget(d->footerButtons)); | ||
919 | const float scrollPos = pos_SmoothScroll(&d->scrollY); | ||
920 | setThumb_ScrollWidget(d->scroll, | ||
921 | pos_SmoothScroll(&d->scrollY), | ||
922 | docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); | ||
923 | if (d->footerButtons) { | ||
924 | const iRect bounds = bounds_Widget(as_Widget(d)); | ||
925 | const iRect docBounds = documentBounds_DocumentWidget_(d); | ||
926 | const int hPad = (width_Rect(bounds) - iMin(120 * gap_UI, width_Rect(docBounds))) / 2; | ||
927 | const int vPad = 3 * gap_UI; | ||
928 | setPadding_Widget(d->footerButtons, hPad, 0, hPad, vPad); | ||
929 | d->footerButtons->rect.pos.y = height_Rect(bounds) - height_Widget(d->footerButtons) + | ||
930 | (scrollMax > 0 ? scrollMax - scrollPos : 0); | ||
931 | } | ||
932 | clear_PtrArray(&d->visibleLinks); | ||
933 | clear_PtrArray(&d->visibleWideRuns); | ||
934 | clear_PtrArray(&d->visiblePre); | ||
935 | clear_PtrArray(&d->visibleMedia); | ||
936 | const iRangecc oldHeading = currentHeading_DocumentWidget_(d); | ||
937 | /* Scan for visible runs. */ { | ||
938 | iZap(d->visibleRuns); | ||
939 | render_GmDocument(d->doc, visRange, addVisible_DocumentWidget_, d); | ||
940 | } | ||
941 | const iRangecc newHeading = currentHeading_DocumentWidget_(d); | ||
942 | if (memcmp(&oldHeading, &newHeading, sizeof(oldHeading))) { | ||
943 | d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; | ||
944 | } | ||
945 | updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window(), 0)); | ||
946 | updateSideOpacity_DocumentWidget_(d, iTrue); | ||
947 | animateMedia_DocumentWidget_(d); | ||
948 | /* Remember scroll positions of recently visited pages. */ { | ||
949 | iRecentUrl *recent = mostRecentUrl_History(d->mod.history); | ||
950 | if (recent && docSize && d->state == ready_RequestState) { | ||
951 | recent->normScrollY = normScrollPos_DocumentWidget_(d); | ||
952 | } | ||
953 | } | ||
954 | /* After scrolling/resizing stops, begin pre-rendering the visbuf contents. */ { | ||
955 | removeTicker_App(prerender_DocumentWidget_, d); | ||
956 | remove_Periodic(periodic_App(), d); | ||
957 | add_Periodic(periodic_App(), d, "document.render"); | ||
958 | } | ||
959 | } | ||
960 | |||
961 | static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { | 2053 | static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { |
962 | iLabelWidget *tabButton = tabPageButton_Widget(findChild_Widget(root_Widget(constAs_Widget(d)), | 2054 | iLabelWidget *tabButton = tabPageButton_Widget(findChild_Widget(root_Widget(constAs_Widget(d)), |
963 | "doctabs"), d); | 2055 | "doctabs"), d); |
@@ -966,8 +2058,8 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { | |||
966 | return; | 2058 | return; |
967 | } | 2059 | } |
968 | iStringArray *title = iClob(new_StringArray()); | 2060 | iStringArray *title = iClob(new_StringArray()); |
969 | if (!isEmpty_String(title_GmDocument(d->doc))) { | 2061 | if (!isEmpty_String(title_GmDocument(d->view.doc))) { |
970 | pushBack_StringArray(title, title_GmDocument(d->doc)); | 2062 | pushBack_StringArray(title, title_GmDocument(d->view.doc)); |
971 | } | 2063 | } |
972 | if (!isEmpty_String(d->titleUser)) { | 2064 | if (!isEmpty_String(d->titleUser)) { |
973 | pushBack_StringArray(title, d->titleUser); | 2065 | pushBack_StringArray(title, d->titleUser); |
@@ -998,7 +2090,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { | |||
998 | setTitle_MainWindow(get_MainWindow(), text); | 2090 | setTitle_MainWindow(get_MainWindow(), text); |
999 | setWindow = iFalse; | 2091 | setWindow = iFalse; |
1000 | } | 2092 | } |
1001 | const iChar siteIcon = siteIcon_GmDocument(d->doc); | 2093 | const iChar siteIcon = siteIcon_GmDocument(d->view.doc); |
1002 | if (siteIcon) { | 2094 | if (siteIcon) { |
1003 | if (!isEmpty_String(text)) { | 2095 | if (!isEmpty_String(text)) { |
1004 | prependCStr_String(text, " " restore_ColorEscape); | 2096 | prependCStr_String(text, " " restore_ColorEscape); |
@@ -1038,50 +2130,28 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { | |||
1038 | } | 2130 | } |
1039 | } | 2131 | } |
1040 | 2132 | ||
1041 | static void updateTimestampBuf_DocumentWidget_(const iDocumentWidget *d) { | 2133 | static void invalidate_DocumentWidget_(iDocumentWidget *d) { |
1042 | if (!isExposed_Window(get_Window())) { | 2134 | if (flags_Widget(as_Widget(d)) & destroyPending_WidgetFlag) { |
1043 | return; | 2135 | return; |
1044 | } | 2136 | } |
1045 | if (d->drawBufs->timestampBuf) { | 2137 | if (d->flags & invalidationPending_DocumentWidgetFlag) { |
1046 | delete_TextBuf(d->drawBufs->timestampBuf); | 2138 | return; |
1047 | d->drawBufs->timestampBuf = NULL; | ||
1048 | } | ||
1049 | if (isValid_Time(&d->sourceTime)) { | ||
1050 | iString *fmt = timeFormatHourPreference_Lang("page.timestamp"); | ||
1051 | d->drawBufs->timestampBuf = newRange_TextBuf( | ||
1052 | uiLabel_FontId, | ||
1053 | white_ColorId, | ||
1054 | range_String(collect_String(format_Time(&d->sourceTime, cstr_String(fmt))))); | ||
1055 | delete_String(fmt); | ||
1056 | } | 2139 | } |
1057 | d->drawBufs->flags &= ~updateTimestampBuf_DrawBufsFlag; | 2140 | if (isAffectedByVisualOffset_Widget(as_Widget(d))) { |
1058 | } | 2141 | d->flags |= invalidationPending_DocumentWidgetFlag; |
1059 | |||
1060 | static void invalidate_DocumentWidget_(iDocumentWidget *d) { | ||
1061 | if (flags_Widget(as_Widget(d)) & destroyPending_WidgetFlag) { | ||
1062 | return; | 2142 | return; |
1063 | } | 2143 | } |
1064 | invalidate_VisBuf(d->visBuf); | 2144 | d->flags &= ~invalidationPending_DocumentWidgetFlag; |
1065 | clear_PtrSet(d->invalidRuns); | 2145 | invalidate_DocumentView_(&d->view); |
2146 | // printf("[%p] '%s' invalidated\n", d, cstr_String(id_Widget(as_Widget(d)))); | ||
1066 | } | 2147 | } |
1067 | 2148 | ||
1068 | static iRangecc siteText_DocumentWidget_(const iDocumentWidget *d) { | 2149 | static iRangecc siteText_DocumentWidget_(const iDocumentWidget *d) { |
1069 | return isEmpty_String(d->titleUser) ? urlHost_String(d->mod.url) | 2150 | return isEmpty_String(d->titleUser) ? urlHost_String(d->mod.url) |
1070 | : range_String(d->titleUser); | 2151 | : range_String(d->titleUser); |
1071 | } | 2152 | } |
1072 | 2153 | ||
1073 | static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) { | 2154 | static iBool isPinned_DocumentWidget_(const iDocumentWidget *d) { |
1074 | d->foundMark = iNullRange; | ||
1075 | d->selectMark = iNullRange; | ||
1076 | d->hoverPre = NULL; | ||
1077 | d->hoverAltPre = NULL; | ||
1078 | d->hoverLink = NULL; | ||
1079 | d->contextLink = NULL; | ||
1080 | iZap(d->visibleRuns); | ||
1081 | iZap(d->renderRuns); | ||
1082 | } | ||
1083 | |||
1084 | iBool isPinned_DocumentWidget_(const iDocumentWidget *d) { | ||
1085 | if (deviceType_App() == phone_AppDeviceType) { | 2155 | if (deviceType_App() == phone_AppDeviceType) { |
1086 | return iFalse; | 2156 | return iFalse; |
1087 | } | 2157 | } |
@@ -1104,12 +2174,20 @@ static void showOrHidePinningIndicator_DocumentWidget_(iDocumentWidget *d) { | |||
1104 | isPinned_DocumentWidget_(d)); | 2174 | isPinned_DocumentWidget_(d)); |
1105 | } | 2175 | } |
1106 | 2176 | ||
2177 | static void updateBanner_DocumentWidget_(iDocumentWidget *d) { | ||
2178 | setSite_Banner(d->banner, siteText_DocumentWidget_(d), siteIcon_GmDocument(d->view.doc)); | ||
2179 | } | ||
2180 | |||
1107 | static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) { | 2181 | static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) { |
1108 | updateVisitedLinks_GmDocument(d->doc); | 2182 | iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iFalse); |
2183 | setFlags_Widget(as_Widget(d), touchDrag_WidgetFlag, iFalse); | ||
2184 | d->requestLinkId = 0; | ||
2185 | updateVisitedLinks_GmDocument(d->view.doc); | ||
1109 | documentRunsInvalidated_DocumentWidget_(d); | 2186 | documentRunsInvalidated_DocumentWidget_(d); |
1110 | updateWindowTitle_DocumentWidget_(d); | 2187 | updateWindowTitle_DocumentWidget_(d); |
1111 | updateVisible_DocumentWidget_(d); | 2188 | updateBanner_DocumentWidget_(d); |
1112 | d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; | 2189 | updateVisible_DocumentView_(&d->view); |
2190 | d->view.drawBufs->flags |= updateSideBuf_DrawBufsFlag; | ||
1113 | invalidate_DocumentWidget_(d); | 2191 | invalidate_DocumentWidget_(d); |
1114 | refresh_Widget(as_Widget(d)); | 2192 | refresh_Widget(as_Widget(d)); |
1115 | /* Check for special bookmark tags. */ | 2193 | /* Check for special bookmark tags. */ |
@@ -1117,59 +2195,28 @@ static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) { | |||
1117 | const uint16_t bmid = findUrl_Bookmarks(bookmarks_App(), d->mod.url); | 2195 | const uint16_t bmid = findUrl_Bookmarks(bookmarks_App(), d->mod.url); |
1118 | if (bmid) { | 2196 | if (bmid) { |
1119 | const iBookmark *bm = get_Bookmarks(bookmarks_App(), bmid); | 2197 | const iBookmark *bm = get_Bookmarks(bookmarks_App(), bmid); |
1120 | if (hasTag_Bookmark(bm, linkSplit_BookmarkTag)) { | 2198 | if (bm->flags & linkSplit_BookmarkFlag) { |
1121 | d->flags |= otherRootByDefault_DocumentWidgetFlag; | 2199 | d->flags |= otherRootByDefault_DocumentWidgetFlag; |
1122 | } | 2200 | } |
1123 | } | 2201 | } |
1124 | showOrHidePinningIndicator_DocumentWidget_(d); | 2202 | showOrHidePinningIndicator_DocumentWidget_(d); |
1125 | setCachedDocument_History(d->mod.history, | 2203 | if (~d->flags & fromCache_DocumentWidgetFlag) { |
1126 | d->doc, /* keeps a ref */ | 2204 | setCachedDocument_History(d->mod.history, d->view.doc /* keeps a ref */); |
1127 | (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0); | 2205 | } |
1128 | } | ||
1129 | |||
1130 | void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { | ||
1131 | setUrl_GmDocument(d->doc, d->mod.url); | ||
1132 | const int docWidth = documentWidth_DocumentWidget_(d); | ||
1133 | setSource_GmDocument(d->doc, | ||
1134 | source, | ||
1135 | docWidth, | ||
1136 | width_Widget(d), | ||
1137 | isFinished_GmRequest(d->request) ? final_GmDocumentUpdate | ||
1138 | : partial_GmDocumentUpdate); | ||
1139 | setWidth_Banner(d->banner, docWidth); | ||
1140 | documentWasChanged_DocumentWidget_(d); | ||
1141 | } | 2206 | } |
1142 | 2207 | ||
1143 | static void replaceDocument_DocumentWidget_(iDocumentWidget *d, iGmDocument *newDoc) { | 2208 | static void replaceDocument_DocumentWidget_(iDocumentWidget *d, iGmDocument *newDoc) { |
1144 | pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); | 2209 | pauseAllPlayers_Media(media_GmDocument(d->view.doc), iTrue); |
1145 | iRelease(d->doc); | 2210 | iRelease(d->view.doc); |
1146 | d->doc = ref_Object(newDoc); | 2211 | d->view.doc = ref_Object(newDoc); |
1147 | documentWasChanged_DocumentWidget_(d); | 2212 | documentWasChanged_DocumentWidget_(d); |
1148 | } | 2213 | } |
1149 | 2214 | ||
1150 | static void updateBanner_DocumentWidget_(iDocumentWidget *d) { | ||
1151 | /* TODO: Set width. */ | ||
1152 | setSite_Banner(d->banner, siteText_DocumentWidget_(d), siteIcon_GmDocument(d->doc)); | ||
1153 | } | ||
1154 | |||
1155 | static void updateTheme_DocumentWidget_(iDocumentWidget *d) { | 2215 | static void updateTheme_DocumentWidget_(iDocumentWidget *d) { |
1156 | if (document_App() != d || category_GmStatusCode(d->sourceStatus) == categoryInput_GmStatusCode) { | 2216 | if (document_App() != d || category_GmStatusCode(d->sourceStatus) == categoryInput_GmStatusCode) { |
1157 | return; | 2217 | return; |
1158 | } | 2218 | } |
1159 | if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { | 2219 | d->view.drawBufs->flags |= updateTimestampBuf_DrawBufsFlag; |
1160 | iBlock empty; | ||
1161 | init_Block(&empty, 0); | ||
1162 | setThemeSeed_GmDocument(d->doc, &empty); | ||
1163 | deinit_Block(&empty); | ||
1164 | } | ||
1165 | else if (isEmpty_String(d->titleUser)) { | ||
1166 | setThemeSeed_GmDocument(d->doc, | ||
1167 | collect_Block(newRange_Block(urlHost_String(d->mod.url)))); | ||
1168 | } | ||
1169 | else { | ||
1170 | setThemeSeed_GmDocument(d->doc, &d->titleUser->chars); | ||
1171 | } | ||
1172 | d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag; | ||
1173 | updateBanner_DocumentWidget_(d); | 2220 | updateBanner_DocumentWidget_(d); |
1174 | } | 2221 | } |
1175 | 2222 | ||
@@ -1186,7 +2233,6 @@ static void makeFooterButtons_DocumentWidget_(iDocumentWidget *d, const iMenuIte | |||
1186 | resizeWidthOfChildren_WidgetFlag | arrangeHeight_WidgetFlag | | 2233 | resizeWidthOfChildren_WidgetFlag | arrangeHeight_WidgetFlag | |
1187 | fixedPosition_WidgetFlag | resizeToParentWidth_WidgetFlag, | 2234 | fixedPosition_WidgetFlag | resizeToParentWidth_WidgetFlag, |
1188 | iTrue); | 2235 | iTrue); |
1189 | //setBackgroundColor_Widget(d->footerButtons, tmBackground_ColorId); | ||
1190 | for (size_t i = 0; i < count; ++i) { | 2236 | for (size_t i = 0; i < count; ++i) { |
1191 | iLabelWidget *button = addChildFlags_Widget( | 2237 | iLabelWidget *button = addChildFlags_Widget( |
1192 | d->footerButtons, | 2238 | d->footerButtons, |
@@ -1196,25 +2242,18 @@ static void makeFooterButtons_DocumentWidget_(iDocumentWidget *d, const iMenuIte | |||
1196 | setPadding1_Widget(as_Widget(button), gap_UI / 2); | 2242 | setPadding1_Widget(as_Widget(button), gap_UI / 2); |
1197 | checkIcon_LabelWidget(button); | 2243 | checkIcon_LabelWidget(button); |
1198 | setFont_LabelWidget(button, uiContent_FontId); | 2244 | setFont_LabelWidget(button, uiContent_FontId); |
1199 | } | 2245 | setBackgroundColor_Widget(as_Widget(button), uiBackgroundSidebar_ColorId); |
1200 | if (deviceType_App() == phone_AppDeviceType) { | ||
1201 | /* Footer buttons shouldn't be under the toolbar. */ | ||
1202 | addChild_Widget(d->footerButtons, iClob(makePadding_Widget(height_Widget(d->phoneToolbar)))); | ||
1203 | } | 2246 | } |
1204 | addChild_Widget(as_Widget(d), iClob(d->footerButtons)); | 2247 | addChild_Widget(as_Widget(d), iClob(d->footerButtons)); |
1205 | arrange_Widget(d->footerButtons); | 2248 | arrange_Widget(d->footerButtons); |
1206 | arrange_Widget(w); | 2249 | arrange_Widget(w); |
1207 | updateVisible_DocumentWidget_(d); /* final placement for the buttons */ | 2250 | updateVisible_DocumentView_(&d->view); /* final placement for the buttons */ |
1208 | } | 2251 | } |
1209 | 2252 | ||
1210 | static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code, | 2253 | static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code, |
1211 | const iString *meta) { | 2254 | const iString *meta) { |
1212 | /* TODO: No such thing as an "error page". It should be an empty page with an error banner. */ | 2255 | iString *src = collectNew_String(); |
1213 | iString *src = collectNew_String(); | ||
1214 | const iGmError *msg = get_GmError(code); | 2256 | const iGmError *msg = get_GmError(code); |
1215 | // appendChar_String(src, msg->icon ? msg->icon : 0x2327); /* X in a box */ | ||
1216 | //appendFormat_String(src, " %s\n%s", msg->title, msg->info); | ||
1217 | // iBool useBanner = iTrue; | ||
1218 | destroy_Widget(d->footerButtons); | 2257 | destroy_Widget(d->footerButtons); |
1219 | d->footerButtons = NULL; | 2258 | d->footerButtons = NULL; |
1220 | const iString *serverErrorMsg = NULL; | 2259 | const iString *serverErrorMsg = NULL; |
@@ -1264,6 +2303,11 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
1264 | 0, | 2303 | 0, |
1265 | format_CStr("document.setmediatype mime:%s", mtype) }); | 2304 | format_CStr("document.setmediatype mime:%s", mtype) }); |
1266 | } | 2305 | } |
2306 | pushBack_Array(&items, | ||
2307 | &(iMenuItem){ export_Icon " ${menu.open.external}", | ||
2308 | SDLK_RETURN, | ||
2309 | KMOD_PRIMARY, | ||
2310 | "document.save extview:1" }); | ||
1267 | pushBack_Array( | 2311 | pushBack_Array( |
1268 | &items, | 2312 | &items, |
1269 | &(iMenuItem){ translateCStr_Lang(download_Icon " " saveToDownloads_Label), | 2313 | &(iMenuItem){ translateCStr_Lang(download_Icon " " saveToDownloads_Label), |
@@ -1285,12 +2329,18 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
1285 | if (category_GmStatusCode(code) == categoryClientCertificate_GmStatus) { | 2329 | if (category_GmStatusCode(code) == categoryClientCertificate_GmStatus) { |
1286 | makeFooterButtons_DocumentWidget_( | 2330 | makeFooterButtons_DocumentWidget_( |
1287 | d, | 2331 | d, |
1288 | (iMenuItem[]){ { leftHalf_Icon " ${menu.show.identities}", '4', KMOD_PRIMARY, "sidebar.mode arg:3 show:1" }, | 2332 | (iMenuItem[]){ |
1289 | { person_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" } }, | 2333 | { leftHalf_Icon " ${menu.show.identities}", |
2334 | '4', | ||
2335 | KMOD_PRIMARY, | ||
2336 | deviceType_App() == desktop_AppDeviceType ? "sidebar.mode arg:3 show:1" | ||
2337 | : "preferences idents:1" }, | ||
2338 | { person_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" } }, | ||
1290 | 2); | 2339 | 2); |
1291 | } | 2340 | } |
1292 | /* Make a new document for the error page.*/ | 2341 | /* Make a new document for the error page.*/ |
1293 | iGmDocument *errorDoc = new_GmDocument(); | 2342 | iGmDocument *errorDoc = new_GmDocument(); |
2343 | setWidth_GmDocument(errorDoc, documentWidth_DocumentView_(&d->view), width_Widget(d)); | ||
1294 | setUrl_GmDocument(errorDoc, d->mod.url); | 2344 | setUrl_GmDocument(errorDoc, d->mod.url); |
1295 | setFormat_GmDocument(errorDoc, gemini_SourceFormat); | 2345 | setFormat_GmDocument(errorDoc, gemini_SourceFormat); |
1296 | replaceDocument_DocumentWidget_(d, errorDoc); | 2346 | replaceDocument_DocumentWidget_(d, errorDoc); |
@@ -1300,10 +2350,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
1300 | d->state = ready_RequestState; | 2350 | d->state = ready_RequestState; |
1301 | setSource_DocumentWidget(d, src); | 2351 | setSource_DocumentWidget(d, src); |
1302 | updateTheme_DocumentWidget_(d); | 2352 | updateTheme_DocumentWidget_(d); |
1303 | reset_SmoothScroll(&d->scrollY); | 2353 | resetScroll_DocumentView_(&d->view); |
1304 | init_Anim(&d->sideOpacity, 0); | ||
1305 | init_Anim(&d->altTextOpacity, 0); | ||
1306 | resetWideRuns_DocumentWidget_(d); | ||
1307 | } | 2354 | } |
1308 | 2355 | ||
1309 | static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { | 2356 | static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { |
@@ -1419,9 +2466,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool | |||
1419 | "document.save" } }, | 2466 | "document.save" } }, |
1420 | 2); | 2467 | 2); |
1421 | } | 2468 | } |
1422 | if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { | 2469 | if (preloadCoverImage_Gempub(d->sourceGempub, d->view.doc)) { |
1423 | redoLayout_GmDocument(d->doc); | 2470 | redoLayout_GmDocument(d->view.doc); |
1424 | updateVisible_DocumentWidget_(d); | 2471 | updateVisible_DocumentView_(&d->view); |
1425 | invalidate_DocumentWidget_(d); | 2472 | invalidate_DocumentWidget_(d); |
1426 | } | 2473 | } |
1427 | } | 2474 | } |
@@ -1497,7 +2544,7 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool | |||
1497 | } | 2544 | } |
1498 | else { | 2545 | else { |
1499 | postCommandf_App( | 2546 | postCommandf_App( |
1500 | "open newtab:%d url:%s", otherRoot_OpenTabFlag, cstr_String(navStart)); | 2547 | "open splitmode:1 newtab:%d url:%s", otherRoot_OpenTabFlag, cstr_String(navStart)); |
1501 | } | 2548 | } |
1502 | } | 2549 | } |
1503 | } | 2550 | } |
@@ -1525,7 +2572,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, | |||
1525 | } | 2572 | } |
1526 | clear_String(&d->sourceMime); | 2573 | clear_String(&d->sourceMime); |
1527 | d->sourceTime = response->when; | 2574 | d->sourceTime = response->when; |
1528 | d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag; | 2575 | d->view.drawBufs->flags |= updateTimestampBuf_DrawBufsFlag; |
1529 | initBlock_String(&str, &response->body); /* Note: Body may be megabytes in size. */ | 2576 | initBlock_String(&str, &response->body); /* Note: Body may be megabytes in size. */ |
1530 | if (isSuccess_GmStatusCode(statusCode)) { | 2577 | if (isSuccess_GmStatusCode(statusCode)) { |
1531 | /* Check the MIME type. */ | 2578 | /* Check the MIME type. */ |
@@ -1565,7 +2612,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, | |||
1565 | setRange_String(&d->sourceMime, param); | 2612 | setRange_String(&d->sourceMime, param); |
1566 | format_String(&str, "# TrueType Font\n"); | 2613 | format_String(&str, "# TrueType Font\n"); |
1567 | iString *decUrl = collect_String(urlDecode_String(d->mod.url)); | 2614 | iString *decUrl = collect_String(urlDecode_String(d->mod.url)); |
1568 | iRangecc name = baseName_Path(decUrl); | 2615 | iRangecc name = baseNameSep_Path(decUrl, "/"); |
1569 | iBool isInstalled = iFalse; | 2616 | iBool isInstalled = iFalse; |
1570 | if (startsWith_String(collect_String(localFilePathFromUrl_String(d->mod.url)), | 2617 | if (startsWith_String(collect_String(localFilePathFromUrl_String(d->mod.url)), |
1571 | cstr_String(dataDir_App()))) { | 2618 | cstr_String(dataDir_App()))) { |
@@ -1628,7 +2675,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, | |||
1628 | appendFormat_String(&str, | 2675 | appendFormat_String(&str, |
1629 | cstr_Lang("doc.archive"), | 2676 | cstr_Lang("doc.archive"), |
1630 | cstr_Rangecc(baseName_Path(d->mod.url))); | 2677 | cstr_Rangecc(baseName_Path(d->mod.url))); |
1631 | appendCStr_String(&str, "\n"); | 2678 | appendCStr_String(&str, "\n"); |
1632 | } | 2679 | } |
1633 | appendCStr_String(&str, "\n"); | 2680 | appendCStr_String(&str, "\n"); |
1634 | iString *localPath = localFilePathFromUrl_String(d->mod.url); | 2681 | iString *localPath = localFilePathFromUrl_String(d->mod.url); |
@@ -1669,16 +2716,16 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, | |||
1669 | format_String(&str, "=> %s %s\n", | 2716 | format_String(&str, "=> %s %s\n", |
1670 | cstr_String(canonicalUrl_String(d->mod.url)), | 2717 | cstr_String(canonicalUrl_String(d->mod.url)), |
1671 | linkTitle); | 2718 | linkTitle); |
1672 | setData_Media(media_GmDocument(d->doc), | 2719 | setData_Media(media_GmDocument(d->view.doc), |
1673 | imgLinkId, | 2720 | imgLinkId, |
1674 | mimeStr, | 2721 | mimeStr, |
1675 | &response->body, | 2722 | &response->body, |
1676 | !isRequestFinished ? partialData_MediaFlag : 0); | 2723 | !isRequestFinished ? partialData_MediaFlag : 0); |
1677 | redoLayout_GmDocument(d->doc); | 2724 | redoLayout_GmDocument(d->view.doc); |
1678 | } | 2725 | } |
1679 | else if (isAudio && !isInitialUpdate) { | 2726 | else if (isAudio && !isInitialUpdate) { |
1680 | /* Update the audio content. */ | 2727 | /* Update the audio content. */ |
1681 | setData_Media(media_GmDocument(d->doc), | 2728 | setData_Media(media_GmDocument(d->view.doc), |
1682 | imgLinkId, | 2729 | imgLinkId, |
1683 | mimeStr, | 2730 | mimeStr, |
1684 | &response->body, | 2731 | &response->body, |
@@ -1708,11 +2755,12 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, | |||
1708 | return; | 2755 | return; |
1709 | } | 2756 | } |
1710 | d->flags |= drawDownloadCounter_DocumentWidgetFlag; | 2757 | d->flags |= drawDownloadCounter_DocumentWidgetFlag; |
1711 | clear_PtrSet(d->invalidRuns); | 2758 | clear_PtrSet(d->view.invalidRuns); |
2759 | documentRunsInvalidated_DocumentWidget_(d); | ||
1712 | deinit_String(&str); | 2760 | deinit_String(&str); |
1713 | return; | 2761 | return; |
1714 | } | 2762 | } |
1715 | setFormat_GmDocument(d->doc, docFormat); | 2763 | setFormat_GmDocument(d->view.doc, docFormat); |
1716 | /* Convert the source to UTF-8 if needed. */ | 2764 | /* Convert the source to UTF-8 if needed. */ |
1717 | if (!equalCase_Rangecc(charset, "utf-8")) { | 2765 | if (!equalCase_Rangecc(charset, "utf-8")) { |
1718 | set_String(&str, | 2766 | set_String(&str, |
@@ -1721,7 +2769,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, | |||
1721 | } | 2769 | } |
1722 | if (cachedDoc) { | 2770 | if (cachedDoc) { |
1723 | replaceDocument_DocumentWidget_(d, cachedDoc); | 2771 | replaceDocument_DocumentWidget_(d, cachedDoc); |
1724 | updateWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d), width_Widget(d)); | 2772 | updateWidth_DocumentView_(&d->view); |
1725 | } | 2773 | } |
1726 | else if (setSource) { | 2774 | else if (setSource) { |
1727 | setSource_DocumentWidget(d, &str); | 2775 | setSource_DocumentWidget(d, &str); |
@@ -1731,6 +2779,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, | |||
1731 | } | 2779 | } |
1732 | 2780 | ||
1733 | static void fetch_DocumentWidget_(iDocumentWidget *d) { | 2781 | static void fetch_DocumentWidget_(iDocumentWidget *d) { |
2782 | iAssert(~d->flags & animationPlaceholder_DocumentWidgetFlag); | ||
1734 | /* Forget the previous request. */ | 2783 | /* Forget the previous request. */ |
1735 | if (d->request) { | 2784 | if (d->request) { |
1736 | iRelease(d->request); | 2785 | iRelease(d->request); |
@@ -1740,8 +2789,6 @@ static void fetch_DocumentWidget_(iDocumentWidget *d) { | |||
1740 | "document.request.started doc:%p url:%s", | 2789 | "document.request.started doc:%p url:%s", |
1741 | d, | 2790 | d, |
1742 | cstr_String(d->mod.url)); | 2791 | cstr_String(d->mod.url)); |
1743 | clear_ObjectList(d->media); | ||
1744 | d->certFlags = 0; | ||
1745 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 2792 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
1746 | d->flags &= ~drawDownloadCounter_DocumentWidgetFlag; | 2793 | d->flags &= ~drawDownloadCounter_DocumentWidgetFlag; |
1747 | d->state = fetching_RequestState; | 2794 | d->state = fetching_RequestState; |
@@ -1774,7 +2821,7 @@ static void updateTrust_DocumentWidget_(iDocumentWidget *d, const iGmResponse *r | |||
1774 | } | 2821 | } |
1775 | else if (~d->certFlags & timeVerified_GmCertFlag) { | 2822 | else if (~d->certFlags & timeVerified_GmCertFlag) { |
1776 | updateTextCStr_LabelWidget(lock, isDarkMode ? orange_ColorEscape warning_Icon | 2823 | updateTextCStr_LabelWidget(lock, isDarkMode ? orange_ColorEscape warning_Icon |
1777 | : black_ColorEscape warning_Icon); | 2824 | : black_ColorEscape warning_Icon); |
1778 | } | 2825 | } |
1779 | else { | 2826 | else { |
1780 | updateTextCStr_LabelWidget(lock, green_ColorEscape closedLock_Icon); | 2827 | updateTextCStr_LabelWidget(lock, green_ColorEscape closedLock_Icon); |
@@ -1793,17 +2840,24 @@ static void cacheRunGlyphs_(void *data, const iGmRun *run) { | |||
1793 | } | 2840 | } |
1794 | 2841 | ||
1795 | static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) { | 2842 | static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) { |
1796 | if (isFinishedLaunching_App() && isExposed_Window(get_Window())) { | 2843 | if (isFinishedLaunching_App() && isExposed_Window(get_Window()) && |
2844 | ~d->flags & animationPlaceholder_DocumentWidgetFlag) { | ||
1797 | /* Just cache the top of the document, since this is what we usually need. */ | 2845 | /* Just cache the top of the document, since this is what we usually need. */ |
1798 | int maxY = height_Widget(&d->widget) * 2; | 2846 | int maxY = height_Widget(&d->widget) * 2; |
1799 | if (maxY == 0) { | 2847 | if (maxY == 0) { |
1800 | maxY = size_GmDocument(d->doc).y; | 2848 | maxY = size_GmDocument(d->view.doc).y; |
1801 | } | 2849 | } |
1802 | render_GmDocument(d->doc, (iRangei){ 0, maxY }, cacheRunGlyphs_, NULL); | 2850 | render_GmDocument(d->view.doc, (iRangei){ 0, maxY }, cacheRunGlyphs_, NULL); |
1803 | } | 2851 | } |
1804 | } | 2852 | } |
1805 | 2853 | ||
1806 | static void addBannerWarnings_DocumentWidget_(iDocumentWidget *d) { | 2854 | static void addBannerWarnings_DocumentWidget_(iDocumentWidget *d) { |
2855 | updateBanner_DocumentWidget_(d); | ||
2856 | /* Warnings are not shown on internal pages. */ | ||
2857 | if (equalCase_Rangecc(urlScheme_String(d->mod.url), "about")) { | ||
2858 | clear_Banner(d->banner); | ||
2859 | return; | ||
2860 | } | ||
1807 | /* Warnings related to certificates and trust. */ | 2861 | /* Warnings related to certificates and trust. */ |
1808 | const int certFlags = d->certFlags; | 2862 | const int certFlags = d->certFlags; |
1809 | const int req = timeVerified_GmCertFlag | domainVerified_GmCertFlag | trusted_GmCertFlag; | 2863 | const int req = timeVerified_GmCertFlag | domainVerified_GmCertFlag | trusted_GmCertFlag; |
@@ -1850,7 +2904,7 @@ static void addBannerWarnings_DocumentWidget_(iDocumentWidget *d) { | |||
1850 | value_SiteSpec(collectNewRange_String(urlRoot_String(d->mod.url)), | 2904 | value_SiteSpec(collectNewRange_String(urlRoot_String(d->mod.url)), |
1851 | dismissWarnings_SiteSpecKey) | | 2905 | dismissWarnings_SiteSpecKey) | |
1852 | (!prefs_App()->warnAboutMissingGlyphs ? missingGlyphs_GmDocumentWarning : 0); | 2906 | (!prefs_App()->warnAboutMissingGlyphs ? missingGlyphs_GmDocumentWarning : 0); |
1853 | const int warnings = warnings_GmDocument(d->doc) & ~dismissed; | 2907 | const int warnings = warnings_GmDocument(d->view.doc) & ~dismissed; |
1854 | if (warnings & missingGlyphs_GmDocumentWarning) { | 2908 | if (warnings & missingGlyphs_GmDocumentWarning) { |
1855 | add_Banner(d->banner, warning_BannerType, missingGlyphs_GmStatusCode, NULL, NULL); | 2909 | add_Banner(d->banner, warning_BannerType, missingGlyphs_GmStatusCode, NULL, NULL); |
1856 | /* TODO: List one or more of the missing characters and/or their Unicode blocks? */ | 2910 | /* TODO: List one or more of the missing characters and/or their Unicode blocks? */ |
@@ -1862,17 +2916,18 @@ static void addBannerWarnings_DocumentWidget_(iDocumentWidget *d) { | |||
1862 | 2916 | ||
1863 | static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY, | 2917 | static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY, |
1864 | const iGmResponse *resp, iGmDocument *cachedDoc) { | 2918 | const iGmResponse *resp, iGmDocument *cachedDoc) { |
2919 | // iAssert(width_Widget(d) > 0); /* must be laid out by now */ | ||
1865 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 2920 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
1866 | clear_ObjectList(d->media); | 2921 | clear_ObjectList(d->media); |
1867 | delete_Gempub(d->sourceGempub); | 2922 | delete_Gempub(d->sourceGempub); |
1868 | d->sourceGempub = NULL; | 2923 | d->sourceGempub = NULL; |
1869 | pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); | 2924 | pauseAllPlayers_Media(media_GmDocument(d->view.doc), iTrue); |
1870 | iRelease(d->doc); | ||
1871 | destroy_Widget(d->footerButtons); | 2925 | destroy_Widget(d->footerButtons); |
1872 | d->footerButtons = NULL; | 2926 | d->footerButtons = NULL; |
1873 | d->doc = new_GmDocument(); | 2927 | iRelease(d->view.doc); |
1874 | resetWideRuns_DocumentWidget_(d); | 2928 | d->view.doc = new_GmDocument(); |
1875 | d->state = fetching_RequestState; | 2929 | d->state = fetching_RequestState; |
2930 | d->flags |= fromCache_DocumentWidgetFlag; | ||
1876 | /* Do the fetch. */ { | 2931 | /* Do the fetch. */ { |
1877 | d->initNormScrollY = normScrollY; | 2932 | d->initNormScrollY = normScrollY; |
1878 | /* Use the cached response data. */ | 2933 | /* Use the cached response data. */ |
@@ -1881,36 +2936,37 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n | |||
1881 | d->sourceStatus = success_GmStatusCode; | 2936 | d->sourceStatus = success_GmStatusCode; |
1882 | format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); | 2937 | format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); |
1883 | set_Block(&d->sourceContent, &resp->body); | 2938 | set_Block(&d->sourceContent, &resp->body); |
2939 | if (!cachedDoc) { | ||
2940 | updateWidthAndRedoLayout_DocumentView_(&d->view); | ||
2941 | } | ||
1884 | updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue); | 2942 | updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue); |
1885 | // setCachedDocument_History(d->mod.history, d->doc, | ||
1886 | // (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0); | ||
1887 | clear_Banner(d->banner); | 2943 | clear_Banner(d->banner); |
1888 | updateBanner_DocumentWidget_(d); | 2944 | updateBanner_DocumentWidget_(d); |
1889 | addBannerWarnings_DocumentWidget_(d); | 2945 | addBannerWarnings_DocumentWidget_(d); |
1890 | } | 2946 | } |
1891 | d->state = ready_RequestState; | 2947 | d->state = ready_RequestState; |
1892 | postProcessRequestContent_DocumentWidget_(d, iTrue); | 2948 | postProcessRequestContent_DocumentWidget_(d, iTrue); |
1893 | init_Anim(&d->altTextOpacity, 0); | 2949 | resetScroll_DocumentView_(&d->view); |
1894 | reset_SmoothScroll(&d->scrollY); | 2950 | init_Anim(&d->view.scrollY.pos, d->initNormScrollY * pageHeight_DocumentView_(&d->view)); |
1895 | init_Anim(&d->scrollY.pos, d->initNormScrollY * pageHeight_DocumentWidget_(d)); | 2951 | updateVisible_DocumentView_(&d->view); |
1896 | updateSideOpacity_DocumentWidget_(d, iFalse); | 2952 | moveSpan_SmoothScroll(&d->view.scrollY, 0, 0); /* clamp position to new max */ |
1897 | updateVisible_DocumentWidget_(d); | 2953 | updateSideOpacity_DocumentView_(&d->view, iFalse); |
1898 | moveSpan_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */ | ||
1899 | cacheDocumentGlyphs_DocumentWidget_(d); | 2954 | cacheDocumentGlyphs_DocumentWidget_(d); |
1900 | d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; | 2955 | d->view.drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; |
1901 | d->flags &= ~(urlChanged_DocumentWidgetFlag | drawDownloadCounter_DocumentWidgetFlag); | 2956 | d->flags &= ~(urlChanged_DocumentWidgetFlag | drawDownloadCounter_DocumentWidgetFlag); |
1902 | postCommandf_Root( | 2957 | postCommandf_Root( |
1903 | as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); | 2958 | as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); |
1904 | } | 2959 | } |
1905 | 2960 | ||
1906 | static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | 2961 | static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { |
1907 | const iRecentUrl *recent = findUrl_History(d->mod.history, d->mod.url); | 2962 | const iRecentUrl *recent = constMostRecentUrl_History(d->mod.history); |
1908 | if (recent && recent->cachedResponse) { | 2963 | if (recent && recent->cachedResponse && equalCase_String(&recent->url, d->mod.url)) { |
1909 | iChangeFlags(d->flags, | ||
1910 | openedFromSidebar_DocumentWidgetFlag, | ||
1911 | recent->flags.openedFromSidebar); | ||
1912 | updateFromCachedResponse_DocumentWidget_( | 2964 | updateFromCachedResponse_DocumentWidget_( |
1913 | d, recent->normScrollY, recent->cachedResponse, recent->cachedDoc); | 2965 | d, recent->normScrollY, recent->cachedResponse, recent->cachedDoc); |
2966 | if (!recent->cachedDoc) { | ||
2967 | /* We have a cached copy now. */ | ||
2968 | setCachedDocument_History(d->mod.history, d->view.doc); | ||
2969 | } | ||
1914 | return iTrue; | 2970 | return iTrue; |
1915 | } | 2971 | } |
1916 | else if (!isEmpty_String(d->mod.url)) { | 2972 | else if (!isEmpty_String(d->mod.url)) { |
@@ -1924,23 +2980,25 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | |||
1924 | } | 2980 | } |
1925 | 2981 | ||
1926 | static void refreshWhileScrolling_DocumentWidget_(iAny *ptr) { | 2982 | static void refreshWhileScrolling_DocumentWidget_(iAny *ptr) { |
2983 | iAssert(isInstance_Object(ptr, &Class_DocumentWidget)); | ||
1927 | iDocumentWidget *d = ptr; | 2984 | iDocumentWidget *d = ptr; |
1928 | updateVisible_DocumentWidget_(d); | 2985 | iDocumentView *view = &d->view; |
2986 | updateVisible_DocumentView_(view); | ||
1929 | refresh_Widget(d); | 2987 | refresh_Widget(d); |
1930 | if (d->animWideRunId) { | 2988 | if (view->animWideRunId) { |
1931 | for (const iGmRun *r = d->animWideRunRange.start; r != d->animWideRunRange.end; r++) { | 2989 | for (const iGmRun *r = view->animWideRunRange.start; r != view->animWideRunRange.end; r++) { |
1932 | insert_PtrSet(d->invalidRuns, r); | 2990 | insert_PtrSet(view->invalidRuns, r); |
1933 | } | 2991 | } |
1934 | } | 2992 | } |
1935 | if (isFinished_Anim(&d->animWideRunOffset)) { | 2993 | if (isFinished_Anim(&view->animWideRunOffset)) { |
1936 | d->animWideRunId = 0; | 2994 | view->animWideRunId = 0; |
1937 | } | 2995 | } |
1938 | if (!isFinished_SmoothScroll(&d->scrollY) || !isFinished_Anim(&d->animWideRunOffset)) { | 2996 | if (!isFinished_SmoothScroll(&view->scrollY) || !isFinished_Anim(&view->animWideRunOffset)) { |
1939 | addTicker_App(refreshWhileScrolling_DocumentWidget_, d); | 2997 | addTicker_App(refreshWhileScrolling_DocumentWidget_, d); |
1940 | } | 2998 | } |
1941 | if (isFinished_SmoothScroll(&d->scrollY)) { | 2999 | if (isFinished_SmoothScroll(&view->scrollY)) { |
1942 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iFalse); | 3000 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iFalse); |
1943 | updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window(), 0)); | 3001 | updateHover_DocumentView_(view, mouseCoord_Window(get_Window(), 0)); |
1944 | } | 3002 | } |
1945 | } | 3003 | } |
1946 | 3004 | ||
@@ -1949,16 +3007,16 @@ static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t du | |||
1949 | /* Get rid of link numbers when scrolling. */ | 3007 | /* Get rid of link numbers when scrolling. */ |
1950 | if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) { | 3008 | if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) { |
1951 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 3009 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
1952 | invalidateVisibleLinks_DocumentWidget_(d); | 3010 | invalidateVisibleLinks_DocumentView_(&d->view); |
1953 | } | 3011 | } |
1954 | /* Show and hide toolbar on scroll. */ | 3012 | /* Show and hide toolbar on scroll. */ |
1955 | if (deviceType_App() == phone_AppDeviceType) { | 3013 | if (deviceType_App() == phone_AppDeviceType) { |
1956 | const float normPos = normScrollPos_DocumentWidget_(d); | 3014 | const float normPos = normScrollPos_DocumentView_(&d->view); |
1957 | if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 && normPos >= 0) { | 3015 | if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 && normPos >= 0) { |
1958 | showToolbar_Root(as_Widget(d)->root, offset < 0); | 3016 | showToolbar_Root(as_Widget(d)->root, offset < 0); |
1959 | } | 3017 | } |
1960 | } | 3018 | } |
1961 | updateVisible_DocumentWidget_(d); | 3019 | updateVisible_DocumentView_(&d->view); |
1962 | refresh_Widget(as_Widget(d)); | 3020 | refresh_Widget(as_Widget(d)); |
1963 | if (duration > 0) { | 3021 | if (duration > 0) { |
1964 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); | 3022 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); |
@@ -1966,98 +3024,14 @@ static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t du | |||
1966 | } | 3024 | } |
1967 | } | 3025 | } |
1968 | 3026 | ||
1969 | static void clampScroll_DocumentWidget_(iDocumentWidget *d) { | ||
1970 | move_SmoothScroll(&d->scrollY, 0); | ||
1971 | } | ||
1972 | |||
1973 | static void immediateScroll_DocumentWidget_(iDocumentWidget *d, int offset) { | ||
1974 | move_SmoothScroll(&d->scrollY, offset); | ||
1975 | } | ||
1976 | |||
1977 | static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int duration) { | ||
1978 | moveSpan_SmoothScroll(&d->scrollY, offset, duration); | ||
1979 | } | ||
1980 | |||
1981 | static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool centered) { | ||
1982 | if (!isEmpty_Banner(d->banner)) { | ||
1983 | documentY += height_Banner(d->banner) + documentTopPad_DocumentWidget_(d); | ||
1984 | } | ||
1985 | else { | ||
1986 | documentY += documentTopPad_DocumentWidget_(d) + d->pageMargin * gap_UI; | ||
1987 | } | ||
1988 | init_Anim(&d->scrollY.pos, | ||
1989 | documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2 | ||
1990 | : lineHeight_Text(paragraph_FontId))); | ||
1991 | clampScroll_DocumentWidget_(d); | ||
1992 | } | ||
1993 | |||
1994 | static void scrollToHeading_DocumentWidget_(iDocumentWidget *d, const char *heading) { | ||
1995 | iConstForEach(Array, h, headings_GmDocument(d->doc)) { | ||
1996 | const iGmHeading *head = h.value; | ||
1997 | if (startsWithCase_Rangecc(head->text, heading)) { | ||
1998 | postCommandf_Root(as_Widget(d)->root, "document.goto loc:%p", head->text.start); | ||
1999 | break; | ||
2000 | } | ||
2001 | } | ||
2002 | } | ||
2003 | |||
2004 | static void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos, int delta, | ||
2005 | int duration) { | ||
2006 | if (delta == 0) { | ||
2007 | return; | ||
2008 | } | ||
2009 | const iInt2 docPos = documentPos_DocumentWidget_(d, mousePos); | ||
2010 | iConstForEach(PtrArray, i, &d->visibleWideRuns) { | ||
2011 | const iGmRun *run = i.ptr; | ||
2012 | if (docPos.y >= top_Rect(run->bounds) && docPos.y <= bottom_Rect(run->bounds)) { | ||
2013 | /* We can scroll this run. First find out how much is allowed. */ | ||
2014 | const iGmRunRange range = findPreformattedRange_GmDocument(d->doc, run); | ||
2015 | int maxWidth = 0; | ||
2016 | for (const iGmRun *r = range.start; r != range.end; r++) { | ||
2017 | maxWidth = iMax(maxWidth, width_Rect(r->visBounds)); | ||
2018 | } | ||
2019 | const int maxOffset = maxWidth - documentWidth_DocumentWidget_(d) + d->pageMargin * gap_UI; | ||
2020 | if (size_Array(&d->wideRunOffsets) <= preId_GmRun(run)) { | ||
2021 | resize_Array(&d->wideRunOffsets, preId_GmRun(run) + 1); | ||
2022 | } | ||
2023 | int *offset = at_Array(&d->wideRunOffsets, preId_GmRun(run) - 1); | ||
2024 | const int oldOffset = *offset; | ||
2025 | *offset = iClamp(*offset + delta, 0, maxOffset); | ||
2026 | /* Make sure the whole block gets redraw. */ | ||
2027 | if (oldOffset != *offset) { | ||
2028 | for (const iGmRun *r = range.start; r != range.end; r++) { | ||
2029 | insert_PtrSet(d->invalidRuns, r); | ||
2030 | } | ||
2031 | refresh_Widget(d); | ||
2032 | d->selectMark = iNullRange; | ||
2033 | d->foundMark = iNullRange; | ||
2034 | } | ||
2035 | if (duration) { | ||
2036 | if (d->animWideRunId != preId_GmRun(run) || isFinished_Anim(&d->animWideRunOffset)) { | ||
2037 | d->animWideRunId = preId_GmRun(run); | ||
2038 | init_Anim(&d->animWideRunOffset, oldOffset); | ||
2039 | } | ||
2040 | setValueEased_Anim(&d->animWideRunOffset, *offset, duration); | ||
2041 | d->animWideRunRange = range; | ||
2042 | addTicker_App(refreshWhileScrolling_DocumentWidget_, d); | ||
2043 | } | ||
2044 | else { | ||
2045 | d->animWideRunId = 0; | ||
2046 | init_Anim(&d->animWideRunOffset, 0); | ||
2047 | } | ||
2048 | break; | ||
2049 | } | ||
2050 | } | ||
2051 | } | ||
2052 | |||
2053 | static void togglePreFold_DocumentWidget_(iDocumentWidget *d, uint16_t preId) { | 3027 | static void togglePreFold_DocumentWidget_(iDocumentWidget *d, uint16_t preId) { |
2054 | d->hoverPre = NULL; | 3028 | d->view.hoverPre = NULL; |
2055 | d->hoverAltPre = NULL; | 3029 | d->view.hoverAltPre = NULL; |
2056 | d->selectMark = iNullRange; | 3030 | d->selectMark = iNullRange; |
2057 | foldPre_GmDocument(d->doc, preId); | 3031 | foldPre_GmDocument(d->view.doc, preId); |
2058 | redoLayout_GmDocument(d->doc); | 3032 | redoLayout_GmDocument(d->view.doc); |
2059 | clampScroll_DocumentWidget_(d); | 3033 | clampScroll_DocumentView_(&d->view); |
2060 | updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window(), 0)); | 3034 | updateHover_DocumentView_(&d->view, mouseCoord_Window(get_Window(), 0)); |
2061 | invalidate_DocumentWidget_(d); | 3035 | invalidate_DocumentWidget_(d); |
2062 | refresh_Widget(as_Widget(d)); | 3036 | refresh_Widget(as_Widget(d)); |
2063 | } | 3037 | } |
@@ -2071,7 +3045,15 @@ static iString *makeQueryUrl_DocumentWidget_(const iDocumentWidget *d, | |||
2071 | remove_Block(&url->chars, qPos, iInvalidSize); | 3045 | remove_Block(&url->chars, qPos, iInvalidSize); |
2072 | } | 3046 | } |
2073 | appendCStr_String(url, "?"); | 3047 | appendCStr_String(url, "?"); |
2074 | append_String(url, collect_String(urlEncode_String(userEnteredText))); | 3048 | iString *cleaned = copy_String(userEnteredText); |
3049 | if (deviceType_App() != desktop_AppDeviceType) { | ||
3050 | trimEnd_String(cleaned); /* autocorrect may insert an extra space */ | ||
3051 | if (isEmpty_String(cleaned)) { | ||
3052 | set_String(cleaned, userEnteredText); /* user wanted just spaces? */ | ||
3053 | } | ||
3054 | } | ||
3055 | append_String(url, collect_String(urlEncode_String(cleaned))); | ||
3056 | delete_String(cleaned); | ||
2075 | return url; | 3057 | return url; |
2076 | } | 3058 | } |
2077 | 3059 | ||
@@ -2107,6 +3089,14 @@ static const char *humanReadableStatusCode_(enum iGmStatusCode code) { | |||
2107 | return format_CStr("%d ", code); | 3089 | return format_CStr("%d ", code); |
2108 | } | 3090 | } |
2109 | 3091 | ||
3092 | static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) { | ||
3093 | url = canonicalUrl_String(url); | ||
3094 | if (!equal_String(d->mod.url, url)) { | ||
3095 | d->flags |= urlChanged_DocumentWidgetFlag; | ||
3096 | set_String(d->mod.url, url); | ||
3097 | } | ||
3098 | } | ||
3099 | |||
2110 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | 3100 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { |
2111 | if (!d->request) { | 3101 | if (!d->request) { |
2112 | return; | 3102 | return; |
@@ -2117,7 +3107,35 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
2117 | } | 3107 | } |
2118 | iGmResponse *resp = lockResponse_GmRequest(d->request); | 3108 | iGmResponse *resp = lockResponse_GmRequest(d->request); |
2119 | if (d->state == fetching_RequestState) { | 3109 | if (d->state == fetching_RequestState) { |
3110 | /* Under certain conditions, inline any image response into the current document. */ | ||
3111 | if (d->requestLinkId && | ||
3112 | isSuccess_GmStatusCode(d->sourceStatus) && | ||
3113 | startsWithCase_String(&d->sourceMime, "text/gemini") && | ||
3114 | isSuccess_GmStatusCode(statusCode) && | ||
3115 | startsWithCase_String(&resp->meta, "image/")) { | ||
3116 | /* This request is turned into a new media request in the current document. */ | ||
3117 | iDisconnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_); | ||
3118 | iDisconnect(GmRequest, d->request, finished, d, requestFinished_DocumentWidget_); | ||
3119 | iMediaRequest *mr = newReused_MediaRequest(d, d->requestLinkId, d->request); | ||
3120 | unlockResponse_GmRequest(d->request); | ||
3121 | d->request = NULL; /* ownership moved */ | ||
3122 | postCommand_Widget(d, "document.request.cancelled doc:%p", d); | ||
3123 | pushBack_ObjectList(d->media, mr); | ||
3124 | iRelease(mr); | ||
3125 | /* Reset the fetch state, returning to the originating page. */ | ||
3126 | d->state = ready_RequestState; | ||
3127 | if (equal_String(&mostRecentUrl_History(d->mod.history)->url, url_GmRequest(mr->req))) { | ||
3128 | undo_History(d->mod.history); | ||
3129 | } | ||
3130 | setUrl_DocumentWidget_(d, url_GmDocument(d->view.doc)); | ||
3131 | updateFetchProgress_DocumentWidget_(d); | ||
3132 | postCommand_Widget(d, "media.updated link:%u request:%p", d->requestLinkId, mr); | ||
3133 | return; | ||
3134 | } | ||
3135 | /* Get ready for the incoming new document. */ | ||
2120 | d->state = receivedPartialResponse_RequestState; | 3136 | d->state = receivedPartialResponse_RequestState; |
3137 | d->flags &= ~fromCache_DocumentWidgetFlag; | ||
3138 | clear_ObjectList(d->media); | ||
2121 | updateTrust_DocumentWidget_(d, resp); | 3139 | updateTrust_DocumentWidget_(d, resp); |
2122 | if (isSuccess_GmStatusCode(statusCode)) { | 3140 | if (isSuccess_GmStatusCode(statusCode)) { |
2123 | clear_Banner(d->banner); | 3141 | clear_Banner(d->banner); |
@@ -2128,8 +3146,8 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
2128 | equalCase_Rangecc(urlScheme_String(d->mod.url), "gemini")) { | 3146 | equalCase_Rangecc(urlScheme_String(d->mod.url), "gemini")) { |
2129 | statusCode = tlsServerCertificateNotVerified_GmStatusCode; | 3147 | statusCode = tlsServerCertificateNotVerified_GmStatusCode; |
2130 | } | 3148 | } |
2131 | init_Anim(&d->sideOpacity, 0); | 3149 | init_Anim(&d->view.sideOpacity, 0); |
2132 | init_Anim(&d->altTextOpacity, 0); | 3150 | init_Anim(&d->view.altTextOpacity, 0); |
2133 | format_String(&d->sourceHeader, | 3151 | format_String(&d->sourceHeader, |
2134 | "%s%s", | 3152 | "%s%s", |
2135 | humanReadableStatusCode_(statusCode), | 3153 | humanReadableStatusCode_(statusCode), |
@@ -2143,7 +3161,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
2143 | it is only displayed as an input dialog. */ | 3161 | it is only displayed as an input dialog. */ |
2144 | visitUrl_Visited(visited_App(), d->mod.url, transient_VisitedUrlFlag); | 3162 | visitUrl_Visited(visited_App(), d->mod.url, transient_VisitedUrlFlag); |
2145 | iUrl parts; | 3163 | iUrl parts; |
2146 | init_Url(&parts, d->mod.url); | 3164 | init_Url(&parts, d->mod.url); |
2147 | iWidget *dlg = makeValueInput_Widget( | 3165 | iWidget *dlg = makeValueInput_Widget( |
2148 | as_Widget(d), | 3166 | as_Widget(d), |
2149 | NULL, | 3167 | NULL, |
@@ -2169,19 +3187,41 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
2169 | NULL); | 3187 | NULL); |
2170 | insertChildAfter_Widget(buttons, iClob(lineBreak), 0); | 3188 | insertChildAfter_Widget(buttons, iClob(lineBreak), 0); |
2171 | } | 3189 | } |
2172 | else { | 3190 | if (lineBreak) { |
2173 | lineBreak = new_LabelWidget("${dlg.input.linebreak}", "text.insert arg:10"); | 3191 | setFlags_Widget(as_Widget(lineBreak), frameless_WidgetFlag, iTrue); |
3192 | setTextColor_LabelWidget(lineBreak, uiTextDim_ColorId); | ||
2174 | } | 3193 | } |
2175 | setFlags_Widget(as_Widget(lineBreak), frameless_WidgetFlag, iTrue); | ||
2176 | setTextColor_LabelWidget(lineBreak, uiTextDim_ColorId); | ||
2177 | } | 3194 | } |
2178 | setId_Widget(addChildPosFlags_Widget(buttons, | 3195 | iWidget *counter = (iWidget *) new_LabelWidget("", NULL); |
2179 | iClob(new_LabelWidget("", NULL)), | 3196 | setId_Widget(counter, "valueinput.counter"); |
2180 | front_WidgetAddPos, frameless_WidgetFlag), | 3197 | setFlags_Widget(counter, frameless_WidgetFlag | resizeToParentHeight_WidgetFlag, iTrue); |
2181 | "valueinput.counter"); | 3198 | if (deviceType_App() == desktop_AppDeviceType) { |
3199 | addChildPos_Widget(buttons, iClob(counter), front_WidgetAddPos); | ||
3200 | } | ||
3201 | else { | ||
3202 | insertChildAfter_Widget(buttons, iClob(counter), 1); | ||
3203 | } | ||
2182 | if (lineBreak && deviceType_App() != desktop_AppDeviceType) { | 3204 | if (lineBreak && deviceType_App() != desktop_AppDeviceType) { |
2183 | addChildPos_Widget(buttons, iClob(lineBreak), front_WidgetAddPos); | 3205 | addChildPos_Widget(buttons, iClob(lineBreak), front_WidgetAddPos); |
2184 | } | 3206 | } |
3207 | /* Menu for additional actions, past entries. */ { | ||
3208 | iMenuItem items[] = { { "${menu.input.precedingline}", | ||
3209 | SDLK_v, | ||
3210 | KMOD_PRIMARY | KMOD_SHIFT, | ||
3211 | format_CStr("!valueinput.set ptr:%p text:%s", | ||
3212 | buttons, | ||
3213 | cstr_String(&d->linePrecedingLink)) } }; | ||
3214 | iLabelWidget *menu = makeMenuButton_LabelWidget(midEllipsis_Icon, items, 1); | ||
3215 | if (deviceType_App() == desktop_AppDeviceType) { | ||
3216 | addChildPos_Widget(buttons, iClob(menu), front_WidgetAddPos); | ||
3217 | } | ||
3218 | else { | ||
3219 | insertChildAfterFlags_Widget(buttons, iClob(menu), 0, | ||
3220 | frameless_WidgetFlag | noBackground_WidgetFlag); | ||
3221 | setFont_LabelWidget(menu, font_LabelWidget((iLabelWidget *) lastChild_Widget(buttons))); | ||
3222 | setTextColor_LabelWidget(menu, uiTextAction_ColorId); | ||
3223 | } | ||
3224 | } | ||
2185 | setValidator_InputWidget(findChild_Widget(dlg, "input"), inputQueryValidator_, d); | 3225 | setValidator_InputWidget(findChild_Widget(dlg, "input"), inputQueryValidator_, d); |
2186 | setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"), | 3226 | setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"), |
2187 | statusCode == sensitiveInput_GmStatusCode); | 3227 | statusCode == sensitiveInput_GmStatusCode); |
@@ -2196,16 +3236,17 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
2196 | case categorySuccess_GmStatusCode: | 3236 | case categorySuccess_GmStatusCode: |
2197 | if (d->flags & urlChanged_DocumentWidgetFlag) { | 3237 | if (d->flags & urlChanged_DocumentWidgetFlag) { |
2198 | /* Keep scroll position when reloading the same page. */ | 3238 | /* Keep scroll position when reloading the same page. */ |
2199 | reset_SmoothScroll(&d->scrollY); | 3239 | resetScroll_DocumentView_(&d->view); |
2200 | } | 3240 | } |
2201 | pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); | 3241 | d->view.scrollY.pullActionTriggered = 0; |
2202 | iRelease(d->doc); /* new content incoming */ | 3242 | pauseAllPlayers_Media(media_GmDocument(d->view.doc), iTrue); |
2203 | d->doc = new_GmDocument(); | 3243 | iReleasePtr(&d->view.doc); /* new content incoming */ |
2204 | delete_Gempub(d->sourceGempub); | 3244 | delete_Gempub(d->sourceGempub); |
2205 | d->sourceGempub = NULL; | 3245 | d->sourceGempub = NULL; |
2206 | destroy_Widget(d->footerButtons); | 3246 | destroy_Widget(d->footerButtons); |
2207 | d->footerButtons = NULL; | 3247 | d->footerButtons = NULL; |
2208 | resetWideRuns_DocumentWidget_(d); | 3248 | d->view.doc = new_GmDocument(); |
3249 | resetWideRuns_DocumentView_(&d->view); | ||
2209 | updateDocument_DocumentWidget_(d, resp, NULL, iTrue); | 3250 | updateDocument_DocumentWidget_(d, resp, NULL, iTrue); |
2210 | break; | 3251 | break; |
2211 | case categoryRedirect_GmStatusCode: | 3252 | case categoryRedirect_GmStatusCode: |
@@ -2260,6 +3301,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
2260 | } | 3301 | } |
2261 | } | 3302 | } |
2262 | else if (d->state == receivedPartialResponse_RequestState) { | 3303 | else if (d->state == receivedPartialResponse_RequestState) { |
3304 | d->flags &= ~fromCache_DocumentWidgetFlag; | ||
2263 | switch (category_GmStatusCode(statusCode)) { | 3305 | switch (category_GmStatusCode(statusCode)) { |
2264 | case categorySuccess_GmStatusCode: | 3306 | case categorySuccess_GmStatusCode: |
2265 | /* More content available. */ | 3307 | /* More content available. */ |
@@ -2272,37 +3314,6 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
2272 | unlockResponse_GmRequest(d->request); | 3314 | unlockResponse_GmRequest(d->request); |
2273 | } | 3315 | } |
2274 | 3316 | ||
2275 | static iRangecc sourceLoc_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { | ||
2276 | return findLoc_GmDocument(d->doc, documentPos_DocumentWidget_(d, pos)); | ||
2277 | } | ||
2278 | |||
2279 | iDeclareType(MiddleRunParams) | ||
2280 | |||
2281 | struct Impl_MiddleRunParams { | ||
2282 | int midY; | ||
2283 | const iGmRun *closest; | ||
2284 | int distance; | ||
2285 | }; | ||
2286 | |||
2287 | static void find_MiddleRunParams_(void *params, const iGmRun *run) { | ||
2288 | iMiddleRunParams *d = params; | ||
2289 | if (isEmpty_Rect(run->bounds)) { | ||
2290 | return; | ||
2291 | } | ||
2292 | const int distance = iAbs(mid_Rect(run->bounds).y - d->midY); | ||
2293 | if (!d->closest || distance < d->distance) { | ||
2294 | d->closest = run; | ||
2295 | d->distance = distance; | ||
2296 | } | ||
2297 | } | ||
2298 | |||
2299 | static const iGmRun *middleRun_DocumentWidget_(const iDocumentWidget *d) { | ||
2300 | iRangei visRange = visibleRange_DocumentWidget_(d); | ||
2301 | iMiddleRunParams params = { (visRange.start + visRange.end) / 2, NULL, 0 }; | ||
2302 | render_GmDocument(d->doc, visRange, find_MiddleRunParams_, ¶ms); | ||
2303 | return params.closest; | ||
2304 | } | ||
2305 | |||
2306 | static void removeMediaRequest_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId) { | 3317 | static void removeMediaRequest_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId) { |
2307 | iForEach(ObjectList, i, d->media) { | 3318 | iForEach(ObjectList, i, d->media) { |
2308 | iMediaRequest *req = (iMediaRequest *) i.object; | 3319 | iMediaRequest *req = (iMediaRequest *) i.object; |
@@ -2313,19 +3324,9 @@ static void removeMediaRequest_DocumentWidget_(iDocumentWidget *d, iGmLinkId lin | |||
2313 | } | 3324 | } |
2314 | } | 3325 | } |
2315 | 3326 | ||
2316 | static iMediaRequest *findMediaRequest_DocumentWidget_(const iDocumentWidget *d, iGmLinkId linkId) { | ||
2317 | iConstForEach(ObjectList, i, d->media) { | ||
2318 | const iMediaRequest *req = (const iMediaRequest *) i.object; | ||
2319 | if (req->linkId == linkId) { | ||
2320 | return iConstCast(iMediaRequest *, req); | ||
2321 | } | ||
2322 | } | ||
2323 | return NULL; | ||
2324 | } | ||
2325 | |||
2326 | static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId, iBool enableFilters) { | 3327 | static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId, iBool enableFilters) { |
2327 | if (!findMediaRequest_DocumentWidget_(d, linkId)) { | 3328 | if (!findMediaRequest_DocumentWidget_(d, linkId)) { |
2328 | const iString *mediaUrl = absoluteUrl_String(d->mod.url, linkUrl_GmDocument(d->doc, linkId)); | 3329 | const iString *mediaUrl = absoluteUrl_String(d->mod.url, linkUrl_GmDocument(d->view.doc, linkId)); |
2329 | pushBack_ObjectList(d->media, iClob(new_MediaRequest(d, linkId, mediaUrl, enableFilters))); | 3330 | pushBack_ObjectList(d->media, iClob(new_MediaRequest(d, linkId, mediaUrl, enableFilters))); |
2330 | invalidate_DocumentWidget_(d); | 3331 | invalidate_DocumentWidget_(d); |
2331 | return iTrue; | 3332 | return iTrue; |
@@ -2334,7 +3335,7 @@ static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId, | |||
2334 | } | 3335 | } |
2335 | 3336 | ||
2336 | static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) { | 3337 | static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) { |
2337 | return findMediaForLink_Media(constMedia_GmDocument(d->doc), req->linkId, download_MediaType).type != 0; | 3338 | return findMediaForLink_Media(constMedia_GmDocument(d->view.doc), req->linkId, download_MediaType).type != 0; |
2338 | } | 3339 | } |
2339 | 3340 | ||
2340 | static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | 3341 | static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { |
@@ -2358,21 +3359,21 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char * | |||
2358 | if (isDownloadRequest_DocumentWidget(d, req) || | 3359 | if (isDownloadRequest_DocumentWidget(d, req) || |
2359 | startsWith_String(&resp->meta, "audio/")) { | 3360 | startsWith_String(&resp->meta, "audio/")) { |
2360 | /* TODO: Use a helper? This is same as below except for the partialData flag. */ | 3361 | /* TODO: Use a helper? This is same as below except for the partialData flag. */ |
2361 | if (setData_Media(media_GmDocument(d->doc), | 3362 | if (setData_Media(media_GmDocument(d->view.doc), |
2362 | req->linkId, | 3363 | req->linkId, |
2363 | &resp->meta, | 3364 | &resp->meta, |
2364 | &resp->body, | 3365 | &resp->body, |
2365 | partialData_MediaFlag | allowHide_MediaFlag)) { | 3366 | partialData_MediaFlag | allowHide_MediaFlag)) { |
2366 | redoLayout_GmDocument(d->doc); | 3367 | redoLayout_GmDocument(d->view.doc); |
2367 | } | 3368 | } |
2368 | updateVisible_DocumentWidget_(d); | 3369 | updateVisible_DocumentView_(&d->view); |
2369 | invalidate_DocumentWidget_(d); | 3370 | invalidate_DocumentWidget_(d); |
2370 | refresh_Widget(as_Widget(d)); | 3371 | refresh_Widget(as_Widget(d)); |
2371 | } | 3372 | } |
2372 | unlockResponse_GmRequest(req->req); | 3373 | unlockResponse_GmRequest(req->req); |
2373 | } | 3374 | } |
2374 | /* Update the link's progress. */ | 3375 | /* Update the link's progress. */ |
2375 | invalidateLink_DocumentWidget_(d, req->linkId); | 3376 | invalidateLink_DocumentView_(&d->view, req->linkId); |
2376 | refresh_Widget(d); | 3377 | refresh_Widget(d); |
2377 | return iTrue; | 3378 | return iTrue; |
2378 | } | 3379 | } |
@@ -2383,14 +3384,14 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char * | |||
2383 | if (isDownloadRequest_DocumentWidget(d, req) || | 3384 | if (isDownloadRequest_DocumentWidget(d, req) || |
2384 | startsWith_String(meta_GmRequest(req->req), "image/") || | 3385 | startsWith_String(meta_GmRequest(req->req), "image/") || |
2385 | startsWith_String(meta_GmRequest(req->req), "audio/")) { | 3386 | startsWith_String(meta_GmRequest(req->req), "audio/")) { |
2386 | setData_Media(media_GmDocument(d->doc), | 3387 | setData_Media(media_GmDocument(d->view.doc), |
2387 | req->linkId, | 3388 | req->linkId, |
2388 | meta_GmRequest(req->req), | 3389 | meta_GmRequest(req->req), |
2389 | body_GmRequest(req->req), | 3390 | body_GmRequest(req->req), |
2390 | allowHide_MediaFlag); | 3391 | allowHide_MediaFlag); |
2391 | redoLayout_GmDocument(d->doc); | 3392 | redoLayout_GmDocument(d->view.doc); |
2392 | iZap(d->visibleRuns); /* pointers invalidated */ | 3393 | iZap(d->view.visibleRuns); /* pointers invalidated */ |
2393 | updateVisible_DocumentWidget_(d); | 3394 | updateVisible_DocumentView_(&d->view); |
2394 | invalidate_DocumentWidget_(d); | 3395 | invalidate_DocumentWidget_(d); |
2395 | refresh_Widget(as_Widget(d)); | 3396 | refresh_Widget(as_Widget(d)); |
2396 | } | 3397 | } |
@@ -2405,25 +3406,13 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char * | |||
2405 | return iFalse; | 3406 | return iFalse; |
2406 | } | 3407 | } |
2407 | 3408 | ||
2408 | static void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) { | ||
2409 | const iWidget *w = constAs_Widget(d); | ||
2410 | const iBool isVisible = isVisible_Widget(w); | ||
2411 | const iInt2 size = bounds_Widget(w).size; | ||
2412 | if (isVisible) { | ||
2413 | alloc_VisBuf(d->visBuf, size, 1); | ||
2414 | } | ||
2415 | else { | ||
2416 | dealloc_VisBuf(d->visBuf); | ||
2417 | } | ||
2418 | } | ||
2419 | |||
2420 | static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) { | 3409 | static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) { |
2421 | iConstForEach(PtrArray, i, &d->visibleLinks) { | 3410 | iConstForEach(PtrArray, i, &d->view.visibleLinks) { |
2422 | const iGmRun *run = i.ptr; | 3411 | const iGmRun *run = i.ptr; |
2423 | if (run->linkId && run->mediaType == none_MediaType && | 3412 | if (run->linkId && run->mediaType == none_MediaType && |
2424 | ~run->flags & decoration_GmRunFlag) { | 3413 | ~run->flags & decoration_GmRunFlag) { |
2425 | const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId); | 3414 | const int linkFlags = linkFlags_GmDocument(d->view.doc, run->linkId); |
2426 | if (isMediaLink_GmDocument(d->doc, run->linkId) && | 3415 | if (isMediaLink_GmDocument(d->view.doc, run->linkId) && |
2427 | linkFlags & imageFileExtension_GmLinkFlag && | 3416 | linkFlags & imageFileExtension_GmLinkFlag && |
2428 | ~linkFlags & content_GmLinkFlag && ~linkFlags & permanent_GmLinkFlag ) { | 3417 | ~linkFlags & content_GmLinkFlag && ~linkFlags & permanent_GmLinkFlag ) { |
2429 | if (requestMedia_DocumentWidget_(d, run->linkId, iTrue)) { | 3418 | if (requestMedia_DocumentWidget_(d, run->linkId, iTrue)) { |
@@ -2435,9 +3424,8 @@ static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) { | |||
2435 | return iFalse; | 3424 | return iFalse; |
2436 | } | 3425 | } |
2437 | 3426 | ||
2438 | static const iString *saveToDownloads_(const iString *url, const iString *mime, const iBlock *content, | 3427 | static iBool saveToFile_(const iString *savePath, const iBlock *content, iBool showDialog) { |
2439 | iBool showDialog) { | 3428 | iBool ok = iFalse; |
2440 | const iString *savePath = downloadPathForUrl_App(url, mime); | ||
2441 | /* Write the file. */ { | 3429 | /* Write the file. */ { |
2442 | iFile *f = new_File(savePath); | 3430 | iFile *f = new_File(savePath); |
2443 | if (open_File(f, writeOnly_FileMode)) { | 3431 | if (open_File(f, writeOnly_FileMode)) { |
@@ -2449,21 +3437,21 @@ static const iString *saveToDownloads_(const iString *url, const iString *mime, | |||
2449 | exportDownloadedFile_iOS(savePath); | 3437 | exportDownloadedFile_iOS(savePath); |
2450 | #else | 3438 | #else |
2451 | if (showDialog) { | 3439 | if (showDialog) { |
2452 | const iMenuItem items[2] = { | 3440 | const iMenuItem items[2] = { |
2453 | { "${dlg.save.opendownload}", 0, 0, | 3441 | { "${dlg.save.opendownload}", 0, 0, |
2454 | format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) }, | 3442 | format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) }, |
2455 | { "${dlg.message.ok}", 0, 0, "message.ok" }, | 3443 | { "${dlg.message.ok}", 0, 0, "message.ok" }, |
2456 | }; | 3444 | }; |
2457 | makeMessage_Widget(uiHeading_ColorEscape "${heading.save}", | 3445 | makeMessage_Widget(uiHeading_ColorEscape "${heading.save}", |
2458 | format_CStr("%s\n${dlg.save.size} %.3f %s", | 3446 | format_CStr("%s\n${dlg.save.size} %.3f %s", |
2459 | cstr_String(path_File(f)), | 3447 | cstr_String(path_File(f)), |
2460 | isMega ? size / 1.0e6f : (size / 1.0e3f), | 3448 | isMega ? size / 1.0e6f : (size / 1.0e3f), |
2461 | isMega ? "${mb}" : "${kb}"), | 3449 | isMega ? "${mb}" : "${kb}"), |
2462 | items, | 3450 | items, |
2463 | iElemCount(items)); | 3451 | iElemCount(items)); |
2464 | } | 3452 | } |
2465 | #endif | 3453 | #endif |
2466 | return savePath; | 3454 | ok = iTrue; |
2467 | } | 3455 | } |
2468 | else { | 3456 | else { |
2469 | makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.save.error}", | 3457 | makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.save.error}", |
@@ -2471,7 +3459,16 @@ static const iString *saveToDownloads_(const iString *url, const iString *mime, | |||
2471 | } | 3459 | } |
2472 | iRelease(f); | 3460 | iRelease(f); |
2473 | } | 3461 | } |
2474 | return collectNew_String(); | 3462 | return ok; |
3463 | } | ||
3464 | |||
3465 | static const iString *saveToDownloads_(const iString *url, const iString *mime, const iBlock *content, | ||
3466 | iBool showDialog) { | ||
3467 | const iString *savePath = downloadPathForUrl_App(url, mime); | ||
3468 | if (!saveToFile_(savePath, content, showDialog)) { | ||
3469 | return collectNew_String(); | ||
3470 | } | ||
3471 | return savePath; | ||
2475 | } | 3472 | } |
2476 | 3473 | ||
2477 | static void addAllLinks_(void *context, const iGmRun *run) { | 3474 | static void addAllLinks_(void *context, const iGmRun *run) { |
@@ -2481,71 +3478,6 @@ static void addAllLinks_(void *context, const iGmRun *run) { | |||
2481 | } | 3478 | } |
2482 | } | 3479 | } |
2483 | 3480 | ||
2484 | static size_t visibleLinkOrdinal_DocumentWidget_(const iDocumentWidget *d, iGmLinkId linkId) { | ||
2485 | size_t ord = 0; | ||
2486 | const iRangei visRange = visibleRange_DocumentWidget_(d); | ||
2487 | iConstForEach(PtrArray, i, &d->visibleLinks) { | ||
2488 | const iGmRun *run = i.ptr; | ||
2489 | if (top_Rect(run->visBounds) >= visRange.start + gap_UI * d->pageMargin * 4 / 5) { | ||
2490 | if (run->flags & decoration_GmRunFlag && run->linkId) { | ||
2491 | if (run->linkId == linkId) return ord; | ||
2492 | ord++; | ||
2493 | } | ||
2494 | } | ||
2495 | } | ||
2496 | return iInvalidPos; | ||
2497 | } | ||
2498 | |||
2499 | /* Sorted by proximity to F and J. */ | ||
2500 | static const int homeRowKeys_[] = { | ||
2501 | 'f', 'd', 's', 'a', | ||
2502 | 'j', 'k', 'l', | ||
2503 | 'r', 'e', 'w', 'q', | ||
2504 | 'u', 'i', 'o', 'p', | ||
2505 | 'v', 'c', 'x', 'z', | ||
2506 | 'm', 'n', | ||
2507 | 'g', 'h', | ||
2508 | 'b', | ||
2509 | 't', 'y', | ||
2510 | }; | ||
2511 | |||
2512 | static iBool updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumentWidget *d, | ||
2513 | iBool keepCenter) { | ||
2514 | const int newWidth = documentWidth_DocumentWidget_(d); | ||
2515 | if (newWidth == size_GmDocument(d->doc).x && !keepCenter /* not a font change */) { | ||
2516 | return iFalse; | ||
2517 | } | ||
2518 | /* Font changes (i.e., zooming) will keep the view centered, otherwise keep the top | ||
2519 | of the visible area fixed. */ | ||
2520 | const iGmRun *run = keepCenter ? middleRun_DocumentWidget_(d) : d->visibleRuns.start; | ||
2521 | const char * runLoc = (run ? run->text.start : NULL); | ||
2522 | int voffset = 0; | ||
2523 | if (!keepCenter && run) { | ||
2524 | /* Keep the first visible run visible at the same position. */ | ||
2525 | /* TODO: First *fully* visible run? */ | ||
2526 | voffset = visibleRange_DocumentWidget_(d).start - top_Rect(run->visBounds); | ||
2527 | } | ||
2528 | setWidth_GmDocument(d->doc, newWidth, width_Widget(d)); | ||
2529 | setWidth_Banner(d->banner, newWidth); | ||
2530 | documentRunsInvalidated_DocumentWidget_(d); | ||
2531 | if (runLoc && !keepCenter) { | ||
2532 | run = findRunAtLoc_GmDocument(d->doc, runLoc); | ||
2533 | if (run) { | ||
2534 | scrollTo_DocumentWidget_(d, | ||
2535 | top_Rect(run->visBounds) + | ||
2536 | lineHeight_Text(paragraph_FontId) + voffset, | ||
2537 | iFalse); | ||
2538 | } | ||
2539 | } | ||
2540 | else if (runLoc && keepCenter) { | ||
2541 | run = findRunAtLoc_GmDocument(d->doc, runLoc); | ||
2542 | if (run) { | ||
2543 | scrollTo_DocumentWidget_(d, mid_Rect(run->bounds).y, iTrue); | ||
2544 | } | ||
2545 | } | ||
2546 | return iTrue; | ||
2547 | } | ||
2548 | |||
2549 | static iBool handlePinch_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | 3481 | static iBool handlePinch_DocumentWidget_(iDocumentWidget *d, const char *cmd) { |
2550 | if (equal_Command(cmd, "pinch.began")) { | 3482 | if (equal_Command(cmd, "pinch.began")) { |
2551 | d->pinchZoomInitial = d->pinchZoomPosted = prefs_App()->zoomPercent; | 3483 | d->pinchZoomInitial = d->pinchZoomPosted = prefs_App()->zoomPercent; |
@@ -2577,15 +3509,12 @@ static void swap_DocumentWidget_(iDocumentWidget *d, iGmDocument *doc, | |||
2577 | iDocumentWidget *swapBuffersWith) { | 3509 | iDocumentWidget *swapBuffersWith) { |
2578 | if (doc) { | 3510 | if (doc) { |
2579 | iAssert(isInstance_Object(doc, &Class_GmDocument)); | 3511 | iAssert(isInstance_Object(doc, &Class_GmDocument)); |
2580 | iGmDocument *copy = ref_Object(doc); | 3512 | replaceDocument_DocumentWidget_(d, doc); |
2581 | iRelease(d->doc); | 3513 | iSwap(iBanner *, d->banner, swapBuffersWith->banner); |
2582 | d->doc = copy; | 3514 | setOwner_Banner(d->banner, d); |
2583 | d->scrollY = swapBuffersWith->scrollY; | 3515 | setOwner_Banner(swapBuffersWith->banner, swapBuffersWith); |
2584 | updateVisible_DocumentWidget_(d); | 3516 | swap_DocumentView_(&d->view, &swapBuffersWith->view); |
2585 | iSwap(iVisBuf *, d->visBuf, swapBuffersWith->visBuf); | 3517 | // invalidate_DocumentWidget_(swapBuffersWith); |
2586 | iSwap(iVisBufMeta *, d->visBufMeta, swapBuffersWith->visBufMeta); | ||
2587 | iSwap(iDrawBufs *, d->drawBufs, swapBuffersWith->drawBufs); | ||
2588 | invalidate_DocumentWidget_(swapBuffersWith); | ||
2589 | } | 3518 | } |
2590 | } | 3519 | } |
2591 | 3520 | ||
@@ -2593,11 +3522,49 @@ static iWidget *swipeParent_DocumentWidget_(iDocumentWidget *d) { | |||
2593 | return findChild_Widget(as_Widget(d)->root->widget, "doctabs"); | 3522 | return findChild_Widget(as_Widget(d)->root->widget, "doctabs"); |
2594 | } | 3523 | } |
2595 | 3524 | ||
3525 | static void setupSwipeOverlay_DocumentWidget_(iDocumentWidget *d, iWidget *overlay) { | ||
3526 | iWidget *w = as_Widget(d); | ||
3527 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); | ||
3528 | /* The target takes the old document and jumps on top. */ | ||
3529 | overlay->rect.pos = windowToInner_Widget(swipeParent, innerToWindow_Widget(w, zero_I2())); | ||
3530 | /* Note: `innerToWindow_Widget` does not apply visual offset. */ | ||
3531 | overlay->rect.size = w->rect.size; | ||
3532 | setFlags_Widget(overlay, fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue); | ||
3533 | // swap_DocumentWidget_(target, d->doc, d); | ||
3534 | setFlags_Widget(as_Widget(d), refChildrenOffset_WidgetFlag, iTrue); | ||
3535 | as_Widget(d)->offsetRef = swipeParent; | ||
3536 | /* `overlay` animates off the screen to the right. */ | ||
3537 | const int fromPos = value_Anim(&w->visualOffset); | ||
3538 | const int toPos = width_Widget(overlay); | ||
3539 | setVisualOffset_Widget(overlay, fromPos, 0, 0); | ||
3540 | /* Bigger screen, faster swipes. */ | ||
3541 | if (deviceType_App() == desktop_AppDeviceType) { | ||
3542 | setVisualOffset_Widget(overlay, toPos, 250, easeOut_AnimFlag | softer_AnimFlag); | ||
3543 | } | ||
3544 | else { | ||
3545 | const float devFactor = (deviceType_App() == phone_AppDeviceType ? 1.0f : 2.0f); | ||
3546 | float swipe = iClamp(d->swipeSpeed, devFactor * 400, devFactor * 1000) * gap_UI; | ||
3547 | uint32_t span = ((toPos - fromPos) / swipe) * 1000; | ||
3548 | // printf("from:%d to:%d swipe:%f span:%u\n", fromPos, toPos, d->swipeSpeed, span); | ||
3549 | setVisualOffset_Widget(overlay, toPos, span, deviceType_App() == tablet_AppDeviceType ? | ||
3550 | easeOut_AnimFlag : 0); | ||
3551 | } | ||
3552 | setVisualOffset_Widget(w, 0, 0, 0); | ||
3553 | } | ||
3554 | |||
2596 | static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | 3555 | static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) { |
3556 | /* TODO: Cleanup | ||
3557 | If DocumentWidget is refactored to split the document presentation from state | ||
3558 | and request management (a new DocumentView class), plain views could be used for this | ||
3559 | animation without having to mess with the complete state of the DocumentWidget. That | ||
3560 | seems like a less error-prone approach -- the current implementation will likely break | ||
3561 | down (again) if anything is changed in the document internals. | ||
3562 | */ | ||
2597 | iWidget *w = as_Widget(d); | 3563 | iWidget *w = as_Widget(d); |
2598 | /* Swipe animations are rather complex and utilize both cached GmDocument content | 3564 | /* The swipe animation is implemented in a rather complex way. It utilizes both cached |
2599 | and temporary DocumentWidgets. Depending on the swipe direction, this DocumentWidget | 3565 | GmDocument content and temporary underlay/overlay DocumentWidgets. Depending on the |
2600 | may wait until the finger is released to actually perform the navigation action. */ | 3566 | swipe direction, the DocumentWidget `d` may wait until the finger is released to actually |
3567 | perform the navigation action. */ | ||
2601 | if (equal_Command(cmd, "edgeswipe.moved")) { | 3568 | if (equal_Command(cmd, "edgeswipe.moved")) { |
2602 | //printf("[%p] responds to edgeswipe.moved\n", d); | 3569 | //printf("[%p] responds to edgeswipe.moved\n", d); |
2603 | as_Widget(d)->offsetRef = NULL; | 3570 | as_Widget(d)->offsetRef = NULL; |
@@ -2608,32 +3575,40 @@ static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | |||
2608 | return iTrue; | 3575 | return iTrue; |
2609 | } | 3576 | } |
2610 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); | 3577 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); |
2611 | /* The temporary "swipeIn" will display the previous page until the finger is lifted. */ | 3578 | if (findChild_Widget(swipeParent, "swipeout")) { |
3579 | return iTrue; /* too fast, previous animation hasn't finished */ | ||
3580 | } | ||
3581 | /* The temporary "swipein" will display the previous page until the finger is lifted. */ | ||
2612 | iDocumentWidget *swipeIn = findChild_Widget(swipeParent, "swipein"); | 3582 | iDocumentWidget *swipeIn = findChild_Widget(swipeParent, "swipein"); |
2613 | if (!swipeIn) { | 3583 | if (!swipeIn) { |
2614 | const iBool sidebarSwipe = (isPortraitPhone_App() && | ||
2615 | d->flags & openedFromSidebar_DocumentWidgetFlag && | ||
2616 | !isVisible_Widget(findWidget_App("sidebar"))); | ||
2617 | swipeIn = new_DocumentWidget(); | 3584 | swipeIn = new_DocumentWidget(); |
3585 | swipeIn->flags |= animationPlaceholder_DocumentWidgetFlag; | ||
2618 | setId_Widget(as_Widget(swipeIn), "swipein"); | 3586 | setId_Widget(as_Widget(swipeIn), "swipein"); |
2619 | setFlags_Widget(as_Widget(swipeIn), | 3587 | setFlags_Widget(as_Widget(swipeIn), |
2620 | disabled_WidgetFlag | refChildrenOffset_WidgetFlag | | 3588 | disabled_WidgetFlag | refChildrenOffset_WidgetFlag | |
2621 | fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue); | 3589 | fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue); |
3590 | setFlags_Widget(findChild_Widget(as_Widget(swipeIn), "scroll"), hidden_WidgetFlag, iTrue); | ||
2622 | swipeIn->widget.rect.pos = windowToInner_Widget(swipeParent, localToWindow_Widget(w, w->rect.pos)); | 3591 | swipeIn->widget.rect.pos = windowToInner_Widget(swipeParent, localToWindow_Widget(w, w->rect.pos)); |
2623 | swipeIn->widget.rect.size = d->widget.rect.size; | 3592 | swipeIn->widget.rect.size = d->widget.rect.size; |
2624 | swipeIn->widget.offsetRef = parent_Widget(w); | 3593 | swipeIn->widget.offsetRef = parent_Widget(w); |
2625 | if (!sidebarSwipe) { | 3594 | /* Use a cached document for the layer underneath. */ { |
2626 | iRecentUrl *recent = new_RecentUrl(); | 3595 | lock_History(d->mod.history); |
2627 | preceding_History(d->mod.history, recent); | 3596 | iRecentUrl *recent = precedingLocked_History(d->mod.history); |
2628 | if (recent->cachedDoc) { | 3597 | if (recent && recent->cachedResponse) { |
2629 | iChangeRef(swipeIn->doc, recent->cachedDoc); | 3598 | setUrl_DocumentWidget_(swipeIn, &recent->url); |
2630 | updateScrollMax_DocumentWidget_(d); | 3599 | updateFromCachedResponse_DocumentWidget_(swipeIn, |
2631 | setValue_Anim(&swipeIn->scrollY.pos, | 3600 | recent->normScrollY, |
2632 | pageHeight_DocumentWidget_(d) * recent->normScrollY, 0); | 3601 | recent->cachedResponse, |
2633 | updateVisible_DocumentWidget_(swipeIn); | 3602 | recent->cachedDoc); |
2634 | swipeIn->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; | 3603 | parseUser_DocumentWidget_(swipeIn); |
3604 | updateBanner_DocumentWidget_(swipeIn); | ||
2635 | } | 3605 | } |
2636 | delete_RecentUrl(recent); | 3606 | else { |
3607 | setUrlAndSource_DocumentWidget(swipeIn, &recent->url, | ||
3608 | collectNewCStr_String("text/gemini"), | ||
3609 | collect_Block(new_Block(0))); | ||
3610 | } | ||
3611 | unlock_History(d->mod.history); | ||
2637 | } | 3612 | } |
2638 | addChildPos_Widget(swipeParent, iClob(swipeIn), front_WidgetAddPos); | 3613 | addChildPos_Widget(swipeParent, iClob(swipeIn), front_WidgetAddPos); |
2639 | } | 3614 | } |
@@ -2641,24 +3616,36 @@ static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | |||
2641 | if (side == 2) { /* right edge */ | 3616 | if (side == 2) { /* right edge */ |
2642 | if (offset < -get_Window()->pixelRatio * 10) { | 3617 | if (offset < -get_Window()->pixelRatio * 10) { |
2643 | int animSpan = 10; | 3618 | int animSpan = 10; |
2644 | if (!atLatest_History(d->mod.history) && | 3619 | if (!atNewest_History(d->mod.history) && ~flags_Widget(w) & dragged_WidgetFlag) { |
2645 | ~flags_Widget(w) & dragged_WidgetFlag) { | 3620 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); |
3621 | if (findChild_Widget(swipeParent, "swipeout")) { | ||
3622 | return iTrue; /* too fast, previous animation hasn't finished */ | ||
3623 | } | ||
3624 | /* Setup the drag. `d` will be moving with the finger. */ | ||
2646 | animSpan = 0; | 3625 | animSpan = 0; |
2647 | postCommand_Widget(d, "navigate.forward"); | 3626 | postCommand_Widget(d, "navigate.forward"); |
2648 | setFlags_Widget(w, dragged_WidgetFlag, iTrue); | 3627 | setFlags_Widget(w, dragged_WidgetFlag, iTrue); |
2649 | /* Set up the swipe dummy. */ | 3628 | /* Set up the swipe dummy. */ |
2650 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); | ||
2651 | iDocumentWidget *target = new_DocumentWidget(); | 3629 | iDocumentWidget *target = new_DocumentWidget(); |
3630 | target->flags |= animationPlaceholder_DocumentWidgetFlag; | ||
2652 | setId_Widget(as_Widget(target), "swipeout"); | 3631 | setId_Widget(as_Widget(target), "swipeout"); |
2653 | /* The target takes the old document and jumps on top. */ | 3632 | /* "swipeout" takes `d`'s document and goes underneath. */ |
2654 | target->widget.rect.pos = windowToInner_Widget(swipeParent, localToWindow_Widget(w, w->rect.pos)); | 3633 | target->widget.rect.pos = windowToInner_Widget(swipeParent, localToWindow_Widget(w, w->rect.pos)); |
2655 | target->widget.rect.size = d->widget.rect.size; | 3634 | target->widget.rect.size = d->widget.rect.size; |
2656 | setFlags_Widget(as_Widget(target), fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue); | 3635 | setFlags_Widget(as_Widget(target), fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue); |
2657 | swap_DocumentWidget_(target, d->doc, d); | 3636 | swap_DocumentWidget_(target, d->view.doc, d); |
2658 | addChildPos_Widget(swipeParent, iClob(target), front_WidgetAddPos); | 3637 | addChildPos_Widget(swipeParent, iClob(target), front_WidgetAddPos); |
2659 | setFlags_Widget(as_Widget(target), refChildrenOffset_WidgetFlag, iTrue); | 3638 | setFlags_Widget(as_Widget(target), refChildrenOffset_WidgetFlag, iTrue); |
2660 | as_Widget(target)->offsetRef = parent_Widget(w); | 3639 | as_Widget(target)->offsetRef = parent_Widget(w); |
2661 | destroy_Widget(as_Widget(target)); /* will be actually deleted after animation finishes */ | 3640 | /* Mark it for deletion after animation finishes. */ |
3641 | destroy_Widget(as_Widget(target)); | ||
3642 | /* The `d` document will now navigate forward and be replaced with a cached | ||
3643 | copy. However, if a cached response isn't available, we'll need to show a | ||
3644 | blank page. */ | ||
3645 | setUrlAndSource_DocumentWidget(d, | ||
3646 | collectNewCStr_String("about:blank"), | ||
3647 | collectNewCStr_String("text/gemini"), | ||
3648 | collect_Block(new_Block(0))); | ||
2662 | } | 3649 | } |
2663 | if (flags_Widget(w) & dragged_WidgetFlag) { | 3650 | if (flags_Widget(w) & dragged_WidgetFlag) { |
2664 | setVisualOffset_Widget(w, width_Widget(w) + | 3651 | setVisualOffset_Widget(w, width_Widget(w) + |
@@ -2674,41 +3661,68 @@ static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | |||
2674 | } | 3661 | } |
2675 | if (equal_Command(cmd, "edgeswipe.ended") && argLabel_Command(cmd, "side") == 2) { | 3662 | if (equal_Command(cmd, "edgeswipe.ended") && argLabel_Command(cmd, "side") == 2) { |
2676 | if (argLabel_Command(cmd, "abort") && flags_Widget(w) & dragged_WidgetFlag) { | 3663 | if (argLabel_Command(cmd, "abort") && flags_Widget(w) & dragged_WidgetFlag) { |
3664 | setFlags_Widget(w, dragged_WidgetFlag, iFalse); | ||
2677 | postCommand_Widget(d, "navigate.back"); | 3665 | postCommand_Widget(d, "navigate.back"); |
3666 | /* We must now undo the swap that was done when the drag started. */ | ||
3667 | /* TODO: Currently not animated! What exactly is the appropriate thing to do here? */ | ||
3668 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); | ||
3669 | iDocumentWidget *swipeOut = findChild_Widget(swipeParent, "swipeout"); | ||
3670 | swap_DocumentWidget_(d, swipeOut->view.doc, swipeOut); | ||
3671 | // const int visOff = visualOffsetByReference_Widget(w); | ||
3672 | w->offsetRef = NULL; | ||
3673 | // setVisualOffset_Widget(w, visOff, 0, 0); | ||
3674 | // setVisualOffset_Widget(w, 0, 150, 0); | ||
3675 | setVisualOffset_Widget(w, 0, 0, 0); | ||
3676 | /* Make it an overlay instead. */ | ||
3677 | // removeChild_Widget(swipeParent, swipeOut); | ||
3678 | // addChildPos_Widget(swipeParent, iClob(swipeOut), back_WidgetAddPos); | ||
3679 | // setupSwipeOverlay_DocumentWidget_(d, as_Widget(swipeOut)); | ||
3680 | return iTrue; | ||
2678 | } | 3681 | } |
3682 | iAssert(~d->flags & animationPlaceholder_DocumentWidgetFlag); | ||
2679 | setFlags_Widget(w, dragged_WidgetFlag, iFalse); | 3683 | setFlags_Widget(w, dragged_WidgetFlag, iFalse); |
2680 | setVisualOffset_Widget(w, 0, 100, 0); | 3684 | setVisualOffset_Widget(w, 0, 250, easeOut_AnimFlag | softer_AnimFlag); |
2681 | return iTrue; | 3685 | return iTrue; |
2682 | } | 3686 | } |
2683 | if (equal_Command(cmd, "edgeswipe.ended") && argLabel_Command(cmd, "side") == 1) { | 3687 | if (equal_Command(cmd, "edgeswipe.ended") && argLabel_Command(cmd, "side") == 1) { |
2684 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); | 3688 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); |
2685 | iWidget *swipeIn = findChild_Widget(swipeParent, "swipein"); | 3689 | iDocumentWidget *swipeIn = findChild_Widget(swipeParent, "swipein"); |
3690 | d->swipeSpeed = argLabel_Command(cmd, "speed") / gap_UI; | ||
3691 | /* "swipe.back" will soon follow. The `d` document will do the actual back navigation, | ||
3692 | switching immediately to a cached page. However, if one is not available, we'll need | ||
3693 | to show a blank page for a while. */ | ||
2686 | if (swipeIn) { | 3694 | if (swipeIn) { |
2687 | swipeIn->offsetRef = NULL; | 3695 | if (!argLabel_Command(cmd, "abort")) { |
2688 | destroy_Widget(swipeIn); | 3696 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); |
3697 | /* What was being shown in the `d` document is now being swapped to | ||
3698 | the outgoing page animation. */ | ||
3699 | iDocumentWidget *target = new_DocumentWidget(); | ||
3700 | target->flags |= animationPlaceholder_DocumentWidgetFlag; | ||
3701 | addChildPos_Widget(swipeParent, iClob(target), back_WidgetAddPos); | ||
3702 | setId_Widget(as_Widget(target), "swipeout"); | ||
3703 | setFlags_Widget(as_Widget(target), disabled_WidgetFlag, iTrue); | ||
3704 | swap_DocumentWidget_(target, d->view.doc, d); | ||
3705 | setUrlAndSource_DocumentWidget(d, | ||
3706 | swipeIn->mod.url, | ||
3707 | collectNewCStr_String("text/gemini"), | ||
3708 | collect_Block(new_Block(0))); | ||
3709 | as_Widget(swipeIn)->offsetRef = NULL; | ||
3710 | } | ||
3711 | destroy_Widget(as_Widget(swipeIn)); | ||
2689 | } | 3712 | } |
2690 | } | 3713 | } |
2691 | if (equal_Command(cmd, "swipe.back")) { | 3714 | if (equal_Command(cmd, "swipe.back")) { |
3715 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); | ||
3716 | iDocumentWidget *target = findChild_Widget(swipeParent, "swipeout"); | ||
2692 | if (atOldest_History(d->mod.history)) { | 3717 | if (atOldest_History(d->mod.history)) { |
2693 | setVisualOffset_Widget(w, 0, 100, 0); | 3718 | setVisualOffset_Widget(w, 0, 100, 0); |
3719 | if (target) { | ||
3720 | destroy_Widget(as_Widget(target)); /* didn't need it after all */ | ||
3721 | } | ||
2694 | return iTrue; | 3722 | return iTrue; |
2695 | } | 3723 | } |
2696 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); | 3724 | setupSwipeOverlay_DocumentWidget_(d, as_Widget(target)); |
2697 | iDocumentWidget *target = new_DocumentWidget(); | ||
2698 | setId_Widget(as_Widget(target), "swipeout"); | ||
2699 | /* The target takes the old document and jumps on top. */ | ||
2700 | target->widget.rect.pos = windowToInner_Widget(swipeParent, innerToWindow_Widget(w, zero_I2())); | ||
2701 | /* Note: `innerToWindow_Widget` does not apply visual offset. */ | ||
2702 | target->widget.rect.size = w->rect.size; | ||
2703 | setFlags_Widget(as_Widget(target), fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue); | ||
2704 | swap_DocumentWidget_(target, d->doc, d); | ||
2705 | addChildPos_Widget(swipeParent, iClob(target), back_WidgetAddPos); | ||
2706 | setFlags_Widget(as_Widget(d), refChildrenOffset_WidgetFlag, iTrue); | ||
2707 | as_Widget(d)->offsetRef = swipeParent; | ||
2708 | setVisualOffset_Widget(as_Widget(target), value_Anim(&w->visualOffset), 0, 0); | ||
2709 | setVisualOffset_Widget(as_Widget(target), width_Widget(target), 150, 0); | ||
2710 | destroy_Widget(as_Widget(target)); /* will be actually deleted after animation finishes */ | 3725 | destroy_Widget(as_Widget(target)); /* will be actually deleted after animation finishes */ |
2711 | setVisualOffset_Widget(w, 0, 0, 0); | ||
2712 | postCommand_Widget(d, "navigate.back"); | 3726 | postCommand_Widget(d, "navigate.back"); |
2713 | return iTrue; | 3727 | return iTrue; |
2714 | } | 3728 | } |
@@ -2733,27 +3747,34 @@ static iBool cancelRequest_DocumentWidget_(iDocumentWidget *d, iBool postBack) { | |||
2733 | return iFalse; | 3747 | return iFalse; |
2734 | } | 3748 | } |
2735 | 3749 | ||
3750 | static const int smoothDuration_DocumentWidget_(enum iScrollType type) { | ||
3751 | return 600 /* milliseconds */ * scrollSpeedFactor_Prefs(prefs_App(), type); | ||
3752 | } | ||
3753 | |||
2736 | static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | 3754 | static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { |
2737 | iWidget *w = as_Widget(d); | 3755 | iWidget *w = as_Widget(d); |
2738 | if (equal_Command(cmd, "document.openurls.changed")) { | 3756 | if (equal_Command(cmd, "document.openurls.changed")) { |
3757 | if (d->flags & animationPlaceholder_DocumentWidgetFlag) { | ||
3758 | return iFalse; | ||
3759 | } | ||
2739 | /* When any tab changes its document URL, update the open link indicators. */ | 3760 | /* When any tab changes its document URL, update the open link indicators. */ |
2740 | if (updateOpenURLs_GmDocument(d->doc)) { | 3761 | if (updateOpenURLs_GmDocument(d->view.doc)) { |
2741 | invalidate_DocumentWidget_(d); | 3762 | invalidate_DocumentWidget_(d); |
2742 | refresh_Widget(d); | 3763 | refresh_Widget(d); |
2743 | } | 3764 | } |
2744 | return iFalse; | 3765 | return iFalse; |
2745 | } | 3766 | } |
2746 | if (equal_Command(cmd, "visited.changed")) { | 3767 | if (equal_Command(cmd, "visited.changed")) { |
2747 | updateVisitedLinks_GmDocument(d->doc); | 3768 | updateVisitedLinks_GmDocument(d->view.doc); |
2748 | invalidateVisibleLinks_DocumentWidget_(d); | 3769 | invalidateVisibleLinks_DocumentView_(&d->view); |
2749 | return iFalse; | 3770 | return iFalse; |
2750 | } | 3771 | } |
2751 | if (equal_Command(cmd, "document.render")) /* `Periodic` makes direct dispatch to here */ { | 3772 | if (equal_Command(cmd, "document.render")) /* `Periodic` makes direct dispatch to here */ { |
2752 | // printf("%u: document.render\n", SDL_GetTicks()); | 3773 | // printf("%u: document.render\n", SDL_GetTicks()); |
2753 | if (SDL_GetTicks() - d->drawBufs->lastRenderTime > 150) { | 3774 | if (SDL_GetTicks() - d->view.drawBufs->lastRenderTime > 150) { |
2754 | remove_Periodic(periodic_App(), d); | 3775 | remove_Periodic(periodic_App(), d); |
2755 | /* Scrolling has stopped, begin filling up the buffer. */ | 3776 | /* Scrolling has stopped, begin filling up the buffer. */ |
2756 | if (d->visBuf->buffers[0].texture) { | 3777 | if (d->view.visBuf->buffers[0].texture) { |
2757 | addTicker_App(prerender_DocumentWidget_, d); | 3778 | addTicker_App(prerender_DocumentWidget_, d); |
2758 | } | 3779 | } |
2759 | } | 3780 | } |
@@ -2762,17 +3783,17 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2762 | else if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed") || | 3783 | else if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed") || |
2763 | equal_Command(cmd, "keyroot.changed")) { | 3784 | equal_Command(cmd, "keyroot.changed")) { |
2764 | if (equal_Command(cmd, "font.changed")) { | 3785 | if (equal_Command(cmd, "font.changed")) { |
2765 | invalidateCachedLayout_History(d->mod.history); | 3786 | invalidateCachedLayout_History(d->mod.history); |
2766 | } | 3787 | } |
2767 | /* Alt/Option key may be involved in window size changes. */ | 3788 | /* Alt/Option key may be involved in window size changes. */ |
2768 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 3789 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
2769 | d->phoneToolbar = findWidget_App("toolbar"); | 3790 | d->phoneToolbar = findWidget_App("toolbar"); |
2770 | const iBool keepCenter = equal_Command(cmd, "font.changed"); | 3791 | const iBool keepCenter = equal_Command(cmd, "font.changed"); |
2771 | updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, keepCenter); | 3792 | updateDocumentWidthRetainingScrollPosition_DocumentView_(&d->view, keepCenter); |
2772 | d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; | 3793 | d->view.drawBufs->flags |= updateSideBuf_DrawBufsFlag; |
2773 | updateVisible_DocumentWidget_(d); | 3794 | updateVisible_DocumentView_(&d->view); |
2774 | invalidate_DocumentWidget_(d); | 3795 | invalidate_DocumentWidget_(d); |
2775 | dealloc_VisBuf(d->visBuf); | 3796 | dealloc_VisBuf(d->view.visBuf); |
2776 | updateWindowTitle_DocumentWidget_(d); | 3797 | updateWindowTitle_DocumentWidget_(d); |
2777 | showOrHidePinningIndicator_DocumentWidget_(d); | 3798 | showOrHidePinningIndicator_DocumentWidget_(d); |
2778 | refresh_Widget(w); | 3799 | refresh_Widget(w); |
@@ -2780,7 +3801,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2780 | else if (equal_Command(cmd, "window.focus.lost")) { | 3801 | else if (equal_Command(cmd, "window.focus.lost")) { |
2781 | if (d->flags & showLinkNumbers_DocumentWidgetFlag) { | 3802 | if (d->flags & showLinkNumbers_DocumentWidgetFlag) { |
2782 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 3803 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
2783 | invalidateVisibleLinks_DocumentWidget_(d); | 3804 | invalidateVisibleLinks_DocumentView_(&d->view); |
2784 | refresh_Widget(w); | 3805 | refresh_Widget(w); |
2785 | } | 3806 | } |
2786 | return iFalse; | 3807 | return iFalse; |
@@ -2788,18 +3809,21 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2788 | else if (equal_Command(cmd, "window.mouse.exited")) { | 3809 | else if (equal_Command(cmd, "window.mouse.exited")) { |
2789 | return iFalse; | 3810 | return iFalse; |
2790 | } | 3811 | } |
2791 | else if (equal_Command(cmd, "theme.changed") && document_App() == d) { | 3812 | else if (equal_Command(cmd, "theme.changed")) { |
2792 | // invalidateTheme_History(d->mod.history); /* cached colors */ | 3813 | invalidatePalette_GmDocument(d->view.doc); |
2793 | updateTheme_DocumentWidget_(d); | 3814 | invalidateTheme_History(d->mod.history); /* forget cached color palettes */ |
2794 | updateVisible_DocumentWidget_(d); | 3815 | if (document_App() == d) { |
2795 | updateTrust_DocumentWidget_(d, NULL); | 3816 | updateTheme_DocumentWidget_(d); |
2796 | d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; | 3817 | updateVisible_DocumentView_(&d->view); |
2797 | invalidate_DocumentWidget_(d); | 3818 | updateTrust_DocumentWidget_(d, NULL); |
2798 | refresh_Widget(w); | 3819 | d->view.drawBufs->flags |= updateSideBuf_DrawBufsFlag; |
3820 | invalidate_DocumentWidget_(d); | ||
3821 | refresh_Widget(w); | ||
3822 | } | ||
2799 | } | 3823 | } |
2800 | else if (equal_Command(cmd, "document.layout.changed") && document_Root(get_Root()) == d) { | 3824 | else if (equal_Command(cmd, "document.layout.changed") && document_Root(get_Root()) == d) { |
2801 | if (argLabel_Command(cmd, "redo")) { | 3825 | if (argLabel_Command(cmd, "redo")) { |
2802 | redoLayout_GmDocument(d->doc); | 3826 | redoLayout_GmDocument(d->view.doc); |
2803 | } | 3827 | } |
2804 | updateSize_DocumentWidget(d); | 3828 | updateSize_DocumentWidget(d); |
2805 | } | 3829 | } |
@@ -2822,11 +3846,11 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2822 | updateFetchProgress_DocumentWidget_(d); | 3846 | updateFetchProgress_DocumentWidget_(d); |
2823 | updateHover_Window(window_Widget(w)); | 3847 | updateHover_Window(window_Widget(w)); |
2824 | } | 3848 | } |
2825 | init_Anim(&d->sideOpacity, 0); | 3849 | init_Anim(&d->view.sideOpacity, 0); |
2826 | init_Anim(&d->altTextOpacity, 0); | 3850 | init_Anim(&d->view.altTextOpacity, 0); |
2827 | updateSideOpacity_DocumentWidget_(d, iFalse); | 3851 | updateSideOpacity_DocumentView_(&d->view, iFalse); |
2828 | updateWindowTitle_DocumentWidget_(d); | 3852 | updateWindowTitle_DocumentWidget_(d); |
2829 | allocVisBuffer_DocumentWidget_(d); | 3853 | allocVisBuffer_DocumentView_(&d->view); |
2830 | animateMedia_DocumentWidget_(d); | 3854 | animateMedia_DocumentWidget_(d); |
2831 | remove_Periodic(periodic_App(), d); | 3855 | remove_Periodic(periodic_App(), d); |
2832 | removeTicker_App(prerender_DocumentWidget_, d); | 3856 | removeTicker_App(prerender_DocumentWidget_, d); |
@@ -2850,8 +3874,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2850 | selectWords_DocumentWidgetFlag; /* finger-based selection is imprecise */ | 3874 | selectWords_DocumentWidgetFlag; /* finger-based selection is imprecise */ |
2851 | d->flags &= ~selectLines_DocumentWidgetFlag; | 3875 | d->flags &= ~selectLines_DocumentWidgetFlag; |
2852 | setFadeEnabled_ScrollWidget(d->scroll, iFalse); | 3876 | setFadeEnabled_ScrollWidget(d->scroll, iFalse); |
2853 | d->selectMark = sourceLoc_DocumentWidget_(d, d->contextPos); | 3877 | d->selectMark = sourceLoc_DocumentView_(&d->view, d->contextPos); |
2854 | extendRange_Rangecc(&d->selectMark, range_String(source_GmDocument(d->doc)), | 3878 | extendRange_Rangecc(&d->selectMark, range_String(source_GmDocument(d->view.doc)), |
2855 | word_RangeExtension | bothStartAndEnd_RangeExtension); | 3879 | word_RangeExtension | bothStartAndEnd_RangeExtension); |
2856 | d->initialSelectMark = d->selectMark; | 3880 | d->initialSelectMark = d->selectMark; |
2857 | } | 3881 | } |
@@ -2865,7 +3889,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2865 | timeVerified_GmCertFlag); | 3889 | timeVerified_GmCertFlag); |
2866 | const iBool canTrust = ~d->certFlags & trusted_GmCertFlag && | 3890 | const iBool canTrust = ~d->certFlags & trusted_GmCertFlag && |
2867 | ((d->certFlags & requiredForTrust) == requiredForTrust); | 3891 | ((d->certFlags & requiredForTrust) == requiredForTrust); |
2868 | const iRecentUrl *recent = findUrl_History(d->mod.history, d->mod.url); | 3892 | const iRecentUrl *recent = constMostRecentUrl_History(d->mod.history); |
2869 | const iString *meta = &d->sourceMime; | 3893 | const iString *meta = &d->sourceMime; |
2870 | if (recent && recent->cachedResponse) { | 3894 | if (recent && recent->cachedResponse) { |
2871 | meta = &recent->cachedResponse->meta; | 3895 | meta = &recent->cachedResponse->meta; |
@@ -2945,7 +3969,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2945 | // setFixedSize_Widget(sizer, init_I2(gap_UI * 65, 1)); | 3969 | // setFixedSize_Widget(sizer, init_I2(gap_UI * 65, 1)); |
2946 | // addChildFlags_Widget(dlg, iClob(sizer), frameless_WidgetFlag); | 3970 | // addChildFlags_Widget(dlg, iClob(sizer), frameless_WidgetFlag); |
2947 | // setFlags_Widget(dlg, centerHorizontal_WidgetFlag, iFalse); | 3971 | // setFlags_Widget(dlg, centerHorizontal_WidgetFlag, iFalse); |
2948 | if (deviceType_App() != phone_AppDeviceType) { | 3972 | if (deviceType_App() == desktop_AppDeviceType) { |
2949 | const iWidget *lockButton = findWidget_Root("navbar.lock"); | 3973 | const iWidget *lockButton = findWidget_Root("navbar.lock"); |
2950 | setPos_Widget(dlg, windowToLocal_Widget(dlg, bottomLeft_Rect(bounds_Widget(lockButton)))); | 3974 | setPos_Widget(dlg, windowToLocal_Widget(dlg, bottomLeft_Rect(bounds_Widget(lockButton)))); |
2951 | } | 3975 | } |
@@ -2994,9 +4018,16 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2994 | } | 4018 | } |
2995 | else { | 4019 | else { |
2996 | /* Full document. */ | 4020 | /* Full document. */ |
2997 | copied = copy_String(source_GmDocument(d->doc)); | 4021 | copied = copy_String(source_GmDocument(d->view.doc)); |
4022 | } | ||
4023 | if (argLabel_Command(cmd, "share")) { | ||
4024 | #if defined (iPlatformAppleMobile) | ||
4025 | openTextActivityView_iOS(copied); | ||
4026 | #endif | ||
4027 | } | ||
4028 | else { | ||
4029 | SDL_SetClipboardText(cstr_String(copied)); | ||
2998 | } | 4030 | } |
2999 | SDL_SetClipboardText(cstr_String(copied)); | ||
3000 | delete_String(copied); | 4031 | delete_String(copied); |
3001 | if (flags_Widget(w) & touchDrag_WidgetFlag) { | 4032 | if (flags_Widget(w) & touchDrag_WidgetFlag) { |
3002 | postCommand_Widget(w, "document.select arg:0"); | 4033 | postCommand_Widget(w, "document.select arg:0"); |
@@ -3006,7 +4037,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3006 | else if (equal_Command(cmd, "document.copylink") && document_App() == d) { | 4037 | else if (equal_Command(cmd, "document.copylink") && document_App() == d) { |
3007 | if (d->contextLink) { | 4038 | if (d->contextLink) { |
3008 | SDL_SetClipboardText(cstr_String(canonicalUrl_String(absoluteUrl_String( | 4039 | SDL_SetClipboardText(cstr_String(canonicalUrl_String(absoluteUrl_String( |
3009 | d->mod.url, linkUrl_GmDocument(d->doc, d->contextLink->linkId))))); | 4040 | d->mod.url, linkUrl_GmDocument(d->view.doc, d->contextLink->linkId))))); |
3010 | } | 4041 | } |
3011 | else { | 4042 | else { |
3012 | SDL_SetClipboardText(cstr_String(canonicalUrl_String(d->mod.url))); | 4043 | SDL_SetClipboardText(cstr_String(canonicalUrl_String(d->mod.url))); |
@@ -3016,13 +4047,13 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3016 | else if (equalWidget_Command(cmd, w, "document.downloadlink")) { | 4047 | else if (equalWidget_Command(cmd, w, "document.downloadlink")) { |
3017 | if (d->contextLink) { | 4048 | if (d->contextLink) { |
3018 | const iGmLinkId linkId = d->contextLink->linkId; | 4049 | const iGmLinkId linkId = d->contextLink->linkId; |
3019 | setUrl_Media(media_GmDocument(d->doc), | 4050 | setUrl_Media(media_GmDocument(d->view.doc), |
3020 | linkId, | 4051 | linkId, |
3021 | download_MediaType, | 4052 | download_MediaType, |
3022 | linkUrl_GmDocument(d->doc, linkId)); | 4053 | linkUrl_GmDocument(d->view.doc, linkId)); |
3023 | requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */); | 4054 | requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */); |
3024 | redoLayout_GmDocument(d->doc); /* inline downloader becomes visible */ | 4055 | redoLayout_GmDocument(d->view.doc); /* inline downloader becomes visible */ |
3025 | updateVisible_DocumentWidget_(d); | 4056 | updateVisible_DocumentView_(&d->view); |
3026 | invalidate_DocumentWidget_(d); | 4057 | invalidate_DocumentWidget_(d); |
3027 | refresh_Widget(w); | 4058 | refresh_Widget(w); |
3028 | } | 4059 | } |
@@ -3055,6 +4086,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3055 | } | 4086 | } |
3056 | else if (equalWidget_Command(cmd, w, "document.request.finished") && | 4087 | else if (equalWidget_Command(cmd, w, "document.request.finished") && |
3057 | id_GmRequest(d->request) == argU32Label_Command(cmd, "reqid")) { | 4088 | id_GmRequest(d->request) == argU32Label_Command(cmd, "reqid")) { |
4089 | d->flags &= ~fromCache_DocumentWidgetFlag; | ||
3058 | set_Block(&d->sourceContent, body_GmRequest(d->request)); | 4090 | set_Block(&d->sourceContent, body_GmRequest(d->request)); |
3059 | if (!isSuccess_GmStatusCode(status_GmRequest(d->request))) { | 4091 | if (!isSuccess_GmStatusCode(status_GmRequest(d->request))) { |
3060 | /* TODO: Why is this here? Can it be removed? */ | 4092 | /* TODO: Why is this here? Can it be removed? */ |
@@ -3066,7 +4098,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3066 | updateFetchProgress_DocumentWidget_(d); | 4098 | updateFetchProgress_DocumentWidget_(d); |
3067 | checkResponse_DocumentWidget_(d); | 4099 | checkResponse_DocumentWidget_(d); |
3068 | if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { | 4100 | if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { |
3069 | init_Anim(&d->scrollY.pos, d->initNormScrollY * pageHeight_DocumentWidget_(d)); /* TODO: unless user already scrolled! */ | 4101 | init_Anim(&d->view.scrollY.pos, d->initNormScrollY * pageHeight_DocumentView_(&d->view)); |
4102 | /* TODO: unless user already scrolled! */ | ||
3070 | } | 4103 | } |
3071 | addBannerWarnings_DocumentWidget_(d); | 4104 | addBannerWarnings_DocumentWidget_(d); |
3072 | iChangeFlags(d->flags, | 4105 | iChangeFlags(d->flags, |
@@ -3076,6 +4109,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3076 | postProcessRequestContent_DocumentWidget_(d, iFalse); | 4109 | postProcessRequestContent_DocumentWidget_(d, iFalse); |
3077 | /* The response may be cached. */ | 4110 | /* The response may be cached. */ |
3078 | if (d->request) { | 4111 | if (d->request) { |
4112 | iAssert(~d->flags & animationPlaceholder_DocumentWidgetFlag); | ||
4113 | iAssert(~d->flags & fromCache_DocumentWidgetFlag); | ||
3079 | if (!equal_Rangecc(urlScheme_String(d->mod.url), "about") && | 4114 | if (!equal_Rangecc(urlScheme_String(d->mod.url), "about") && |
3080 | (startsWithCase_String(meta_GmRequest(d->request), "text/") || | 4115 | (startsWithCase_String(meta_GmRequest(d->request), "text/") || |
3081 | !cmp_String(&d->sourceMime, mimeType_Gempub))) { | 4116 | !cmp_String(&d->sourceMime, mimeType_Gempub))) { |
@@ -3084,8 +4119,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3084 | } | 4119 | } |
3085 | } | 4120 | } |
3086 | iReleasePtr(&d->request); | 4121 | iReleasePtr(&d->request); |
3087 | updateVisible_DocumentWidget_(d); | 4122 | updateVisible_DocumentView_(&d->view); |
3088 | d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; | 4123 | d->view.drawBufs->flags |= updateSideBuf_DrawBufsFlag; |
3089 | postCommandf_Root(w->root, | 4124 | postCommandf_Root(w->root, |
3090 | "document.changed doc:%p status:%d url:%s", | 4125 | "document.changed doc:%p status:%d url:%s", |
3091 | d, | 4126 | d, |
@@ -3093,7 +4128,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3093 | cstr_String(d->mod.url)); | 4128 | cstr_String(d->mod.url)); |
3094 | /* Check for a pending goto. */ | 4129 | /* Check for a pending goto. */ |
3095 | if (!isEmpty_String(&d->pendingGotoHeading)) { | 4130 | if (!isEmpty_String(&d->pendingGotoHeading)) { |
3096 | scrollToHeading_DocumentWidget_(d, cstr_String(&d->pendingGotoHeading)); | 4131 | scrollToHeading_DocumentView_(&d->view, cstr_String(&d->pendingGotoHeading)); |
3097 | clear_String(&d->pendingGotoHeading); | 4132 | clear_String(&d->pendingGotoHeading); |
3098 | } | 4133 | } |
3099 | cacheDocumentGlyphs_DocumentWidget_(d); | 4134 | cacheDocumentGlyphs_DocumentWidget_(d); |
@@ -3102,6 +4137,11 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3102 | else if (equal_Command(cmd, "document.translate") && d == document_App()) { | 4137 | else if (equal_Command(cmd, "document.translate") && d == document_App()) { |
3103 | if (!d->translation) { | 4138 | if (!d->translation) { |
3104 | d->translation = new_Translation(d); | 4139 | d->translation = new_Translation(d); |
4140 | if (isUsingPanelLayout_Mobile()) { | ||
4141 | const iRect safe = safeRect_Root(w->root); | ||
4142 | d->translation->dlg->rect.pos = windowToLocal_Widget(w, zero_I2()); | ||
4143 | d->translation->dlg->rect.size = safe.size; | ||
4144 | } | ||
3105 | } | 4145 | } |
3106 | return iTrue; | 4146 | return iTrue; |
3107 | } | 4147 | } |
@@ -3117,14 +4157,19 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3117 | if (findChild_Widget(root_Widget(w), "upload")) { | 4157 | if (findChild_Widget(root_Widget(w), "upload")) { |
3118 | return iTrue; /* already open */ | 4158 | return iTrue; /* already open */ |
3119 | } | 4159 | } |
3120 | if (equalCase_Rangecc(urlScheme_String(d->mod.url), "gemini") || | 4160 | const iBool isGemini = equalCase_Rangecc(urlScheme_String(d->mod.url), "gemini"); |
3121 | equalCase_Rangecc(urlScheme_String(d->mod.url), "titan")) { | 4161 | if (isGemini || equalCase_Rangecc(urlScheme_String(d->mod.url), "titan")) { |
3122 | iUploadWidget *upload = new_UploadWidget(); | 4162 | iUploadWidget *upload = new_UploadWidget(); |
3123 | setUrl_UploadWidget(upload, d->mod.url); | 4163 | setUrl_UploadWidget(upload, d->mod.url); |
3124 | setResponseViewer_UploadWidget(upload, d); | 4164 | setResponseViewer_UploadWidget(upload, d); |
3125 | addChild_Widget(get_Root()->widget, iClob(upload)); | 4165 | addChild_Widget(get_Root()->widget, iClob(upload)); |
3126 | // finalizeSheet_Mobile(as_Widget(upload)); | ||
3127 | setupSheetTransition_Mobile(as_Widget(upload), iTrue); | 4166 | setupSheetTransition_Mobile(as_Widget(upload), iTrue); |
4167 | if (argLabel_Command(cmd, "copy") && isUtf8_Rangecc(range_Block(&d->sourceContent))) { | ||
4168 | iString text; | ||
4169 | initBlock_String(&text, &d->sourceContent); | ||
4170 | setText_UploadWidget(upload, &text); | ||
4171 | deinit_String(&text); | ||
4172 | } | ||
3128 | postRefresh_App(); | 4173 | postRefresh_App(); |
3129 | } | 4174 | } |
3130 | return iTrue; | 4175 | return iTrue; |
@@ -3135,7 +4180,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3135 | else if (equal_Command(cmd, "media.player.started")) { | 4180 | else if (equal_Command(cmd, "media.player.started")) { |
3136 | /* When one media player starts, pause the others that may be playing. */ | 4181 | /* When one media player starts, pause the others that may be playing. */ |
3137 | const iPlayer *startedPlr = pointerLabel_Command(cmd, "player"); | 4182 | const iPlayer *startedPlr = pointerLabel_Command(cmd, "player"); |
3138 | const iMedia * media = media_GmDocument(d->doc); | 4183 | const iMedia * media = media_GmDocument(d->view.doc); |
3139 | const size_t num = numAudio_Media(media); | 4184 | const size_t num = numAudio_Media(media); |
3140 | for (size_t id = 1; id <= num; id++) { | 4185 | for (size_t id = 1; id <= num; id++) { |
3141 | iPlayer *plr = audioPlayer_Media(media, (iMediaId){ audio_MediaType, id }); | 4186 | iPlayer *plr = audioPlayer_Media(media, (iMediaId){ audio_MediaType, id }); |
@@ -3167,22 +4212,37 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3167 | "${dlg.save.incomplete}"); | 4212 | "${dlg.save.incomplete}"); |
3168 | } | 4213 | } |
3169 | else if (!isEmpty_Block(&d->sourceContent)) { | 4214 | else if (!isEmpty_Block(&d->sourceContent)) { |
3170 | const iBool doOpen = argLabel_Command(cmd, "open"); | 4215 | if (argLabel_Command(cmd, "extview")) { |
3171 | const iString *savePath = saveToDownloads_(d->mod.url, &d->sourceMime, | 4216 | if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { |
3172 | &d->sourceContent, !doOpen); | 4217 | /* Already a file so just open it directly. */ |
3173 | if (!isEmpty_String(savePath) && doOpen) { | 4218 | postCommandf_Root(w->root, "!open default:1 url:%s", cstr_String(d->mod.url)); |
3174 | postCommandf_Root( | 4219 | } |
3175 | w->root, "!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))); | 4220 | else { |
4221 | const iString *tmpPath = temporaryPathForUrl_App(d->mod.url, &d->sourceMime); | ||
4222 | if (saveToFile_(tmpPath, &d->sourceContent, iFalse)) { | ||
4223 | postCommandf_Root(w->root, "!open default:1 url:%s", | ||
4224 | cstrCollect_String(makeFileUrl_String(tmpPath))); | ||
4225 | } | ||
4226 | } | ||
4227 | } | ||
4228 | else { | ||
4229 | const iBool doOpen = argLabel_Command(cmd, "open"); | ||
4230 | const iString *savePath = saveToDownloads_(d->mod.url, &d->sourceMime, | ||
4231 | &d->sourceContent, !doOpen); | ||
4232 | if (!isEmpty_String(savePath) && doOpen) { | ||
4233 | postCommandf_Root( | ||
4234 | w->root, "!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))); | ||
4235 | } | ||
3176 | } | 4236 | } |
3177 | } | 4237 | } |
3178 | return iTrue; | 4238 | return iTrue; |
3179 | } | 4239 | } |
3180 | else if (equal_Command(cmd, "document.reload") && document_Command(cmd) == d) { | 4240 | else if (equal_Command(cmd, "document.reload") && document_Command(cmd) == d) { |
3181 | d->initNormScrollY = normScrollPos_DocumentWidget_(d); | 4241 | d->initNormScrollY = normScrollPos_DocumentView_(&d->view); |
3182 | if (equalCase_Rangecc(urlScheme_String(d->mod.url), "titan")) { | 4242 | if (equalCase_Rangecc(urlScheme_String(d->mod.url), "titan")) { |
3183 | /* Reopen so the Upload dialog gets shown. */ | 4243 | /* Reopen so the Upload dialog gets shown. */ |
3184 | postCommandf_App("open url:%s", cstr_String(d->mod.url)); | 4244 | postCommandf_App("open url:%s", cstr_String(d->mod.url)); |
3185 | return iTrue; | 4245 | return iTrue; |
3186 | } | 4246 | } |
3187 | fetch_DocumentWidget_(d); | 4247 | fetch_DocumentWidget_(d); |
3188 | return iTrue; | 4248 | return iTrue; |
@@ -3195,13 +4255,13 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3195 | if (d->flags & showLinkNumbers_DocumentWidgetFlag && | 4255 | if (d->flags & showLinkNumbers_DocumentWidgetFlag && |
3196 | d->ordinalMode == homeRow_DocumentLinkOrdinalMode) { | 4256 | d->ordinalMode == homeRow_DocumentLinkOrdinalMode) { |
3197 | const size_t numKeys = iElemCount(homeRowKeys_); | 4257 | const size_t numKeys = iElemCount(homeRowKeys_); |
3198 | const iGmRun *last = lastVisibleLink_DocumentWidget_(d); | 4258 | const iGmRun *last = lastVisibleLink_DocumentView_(&d->view); |
3199 | if (!last) { | 4259 | if (!last) { |
3200 | d->ordinalBase = 0; | 4260 | d->ordinalBase = 0; |
3201 | } | 4261 | } |
3202 | else { | 4262 | else { |
3203 | d->ordinalBase += numKeys; | 4263 | d->ordinalBase += numKeys; |
3204 | if (visibleLinkOrdinal_DocumentWidget_(d, last->linkId) < d->ordinalBase) { | 4264 | if (visibleLinkOrdinal_DocumentView_(&d->view, last->linkId) < d->ordinalBase) { |
3205 | d->ordinalBase = 0; | 4265 | d->ordinalBase = 0; |
3206 | } | 4266 | } |
3207 | } | 4267 | } |
@@ -3221,23 +4281,11 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3221 | iChangeFlags(d->flags, newTabViaHomeKeys_DocumentWidgetFlag, | 4281 | iChangeFlags(d->flags, newTabViaHomeKeys_DocumentWidgetFlag, |
3222 | argLabel_Command(cmd, "newtab") != 0); | 4282 | argLabel_Command(cmd, "newtab") != 0); |
3223 | } | 4283 | } |
3224 | invalidateVisibleLinks_DocumentWidget_(d); | 4284 | invalidateVisibleLinks_DocumentView_(&d->view); |
3225 | refresh_Widget(d); | 4285 | refresh_Widget(d); |
3226 | return iTrue; | 4286 | return iTrue; |
3227 | } | 4287 | } |
3228 | else if (equal_Command(cmd, "navigate.back") && document_App() == d) { | 4288 | else if (equal_Command(cmd, "navigate.back") && document_App() == d) { |
3229 | if (isPortraitPhone_App()) { | ||
3230 | if (d->flags & openedFromSidebar_DocumentWidgetFlag && | ||
3231 | !isVisible_Widget(findWidget_App("sidebar"))) { | ||
3232 | postCommand_App("sidebar.toggle"); | ||
3233 | showToolbar_Root(get_Root(), iTrue); | ||
3234 | #if defined (iPlatformAppleMobile) | ||
3235 | playHapticEffect_iOS(gentleTap_HapticEffect); | ||
3236 | #endif | ||
3237 | return iTrue; | ||
3238 | } | ||
3239 | d->flags &= ~openedFromSidebar_DocumentWidgetFlag; | ||
3240 | } | ||
3241 | if (d->request) { | 4289 | if (d->request) { |
3242 | postCommandf_Root(w->root, | 4290 | postCommandf_Root(w->root, |
3243 | "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url)); | 4291 | "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url)); |
@@ -3274,8 +4322,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3274 | return iTrue; | 4322 | return iTrue; |
3275 | } | 4323 | } |
3276 | else if (equalWidget_Command(cmd, w, "scroll.moved")) { | 4324 | else if (equalWidget_Command(cmd, w, "scroll.moved")) { |
3277 | init_Anim(&d->scrollY.pos, arg_Command(cmd)); | 4325 | init_Anim(&d->view.scrollY.pos, arg_Command(cmd)); |
3278 | updateVisible_DocumentWidget_(d); | 4326 | updateVisible_DocumentView_(&d->view); |
3279 | return iTrue; | 4327 | return iTrue; |
3280 | } | 4328 | } |
3281 | else if (equal_Command(cmd, "scroll.page") && document_App() == d) { | 4329 | else if (equal_Command(cmd, "scroll.page") && document_App() == d) { |
@@ -3286,25 +4334,26 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3286 | return iTrue; | 4334 | return iTrue; |
3287 | } | 4335 | } |
3288 | const float amount = argLabel_Command(cmd, "full") != 0 ? 1.0f : 0.5f; | 4336 | const float amount = argLabel_Command(cmd, "full") != 0 ? 1.0f : 0.5f; |
3289 | smoothScroll_DocumentWidget_(d, | 4337 | smoothScroll_DocumentView_(&d->view, |
3290 | dir * amount * height_Rect(documentBounds_DocumentWidget_(d)), | 4338 | dir * amount * |
3291 | smoothDuration_DocumentWidget_(keyboard_ScrollType)); | 4339 | height_Rect(documentBounds_DocumentView_(&d->view)), |
4340 | smoothDuration_DocumentWidget_(keyboard_ScrollType)); | ||
3292 | return iTrue; | 4341 | return iTrue; |
3293 | } | 4342 | } |
3294 | else if (equal_Command(cmd, "scroll.top") && document_App() == d) { | 4343 | else if (equal_Command(cmd, "scroll.top") && document_App() == d) { |
3295 | init_Anim(&d->scrollY.pos, 0); | 4344 | init_Anim(&d->view.scrollY.pos, 0); |
3296 | invalidate_VisBuf(d->visBuf); | 4345 | invalidate_VisBuf(d->view.visBuf); |
3297 | clampScroll_DocumentWidget_(d); | 4346 | clampScroll_DocumentView_(&d->view); |
3298 | updateVisible_DocumentWidget_(d); | 4347 | updateVisible_DocumentView_(&d->view); |
3299 | refresh_Widget(w); | 4348 | refresh_Widget(w); |
3300 | return iTrue; | 4349 | return iTrue; |
3301 | } | 4350 | } |
3302 | else if (equal_Command(cmd, "scroll.bottom") && document_App() == d) { | 4351 | else if (equal_Command(cmd, "scroll.bottom") && document_App() == d) { |
3303 | updateScrollMax_DocumentWidget_(d); /* scrollY.max might not be fully updated */ | 4352 | updateScrollMax_DocumentView_(&d->view); /* scrollY.max might not be fully updated */ |
3304 | init_Anim(&d->scrollY.pos, d->scrollY.max); | 4353 | init_Anim(&d->view.scrollY.pos, d->view.scrollY.max); |
3305 | invalidate_VisBuf(d->visBuf); | 4354 | invalidate_VisBuf(d->view.visBuf); |
3306 | clampScroll_DocumentWidget_(d); | 4355 | clampScroll_DocumentView_(&d->view); |
3307 | updateVisible_DocumentWidget_(d); | 4356 | updateVisible_DocumentView_(&d->view); |
3308 | refresh_Widget(w); | 4357 | refresh_Widget(w); |
3309 | return iTrue; | 4358 | return iTrue; |
3310 | } | 4359 | } |
@@ -3315,9 +4364,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3315 | fetchNextUnfetchedImage_DocumentWidget_(d)) { | 4364 | fetchNextUnfetchedImage_DocumentWidget_(d)) { |
3316 | return iTrue; | 4365 | return iTrue; |
3317 | } | 4366 | } |
3318 | smoothScroll_DocumentWidget_(d, | 4367 | smoothScroll_DocumentView_(&d->view, |
3319 | 3 * lineHeight_Text(paragraph_FontId) * dir, | 4368 | 3 * lineHeight_Text(paragraph_FontId) * dir, |
3320 | smoothDuration_DocumentWidget_(keyboard_ScrollType)); | 4369 | smoothDuration_DocumentWidget_(keyboard_ScrollType)); |
3321 | return iTrue; | 4370 | return iTrue; |
3322 | } | 4371 | } |
3323 | else if (equal_Command(cmd, "document.goto") && document_App() == d) { | 4372 | else if (equal_Command(cmd, "document.goto") && document_App() == d) { |
@@ -3328,13 +4377,13 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3328 | setCStr_String(&d->pendingGotoHeading, heading); | 4377 | setCStr_String(&d->pendingGotoHeading, heading); |
3329 | return iTrue; | 4378 | return iTrue; |
3330 | } | 4379 | } |
3331 | scrollToHeading_DocumentWidget_(d, heading); | 4380 | scrollToHeading_DocumentView_(&d->view, heading); |
3332 | return iTrue; | 4381 | return iTrue; |
3333 | } | 4382 | } |
3334 | const char *loc = pointerLabel_Command(cmd, "loc"); | 4383 | const char *loc = pointerLabel_Command(cmd, "loc"); |
3335 | const iGmRun *run = findRunAtLoc_GmDocument(d->doc, loc); | 4384 | const iGmRun *run = findRunAtLoc_GmDocument(d->view.doc, loc); |
3336 | if (run) { | 4385 | if (run) { |
3337 | scrollTo_DocumentWidget_(d, run->visBounds.pos.y, iFalse); | 4386 | scrollTo_DocumentView_(&d->view, run->visBounds.pos.y, iFalse); |
3338 | } | 4387 | } |
3339 | return iTrue; | 4388 | return iTrue; |
3340 | } | 4389 | } |
@@ -3349,24 +4398,24 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3349 | } | 4398 | } |
3350 | else { | 4399 | else { |
3351 | const iBool wrap = d->foundMark.start != NULL; | 4400 | const iBool wrap = d->foundMark.start != NULL; |
3352 | d->foundMark = finder(d->doc, text_InputWidget(find), dir > 0 ? d->foundMark.end | 4401 | d->foundMark = finder(d->view.doc, text_InputWidget(find), dir > 0 ? d->foundMark.end |
3353 | : d->foundMark.start); | 4402 | : d->foundMark.start); |
3354 | if (!d->foundMark.start && wrap) { | 4403 | if (!d->foundMark.start && wrap) { |
3355 | /* Wrap around. */ | 4404 | /* Wrap around. */ |
3356 | d->foundMark = finder(d->doc, text_InputWidget(find), NULL); | 4405 | d->foundMark = finder(d->view.doc, text_InputWidget(find), NULL); |
3357 | } | 4406 | } |
3358 | if (d->foundMark.start) { | 4407 | if (d->foundMark.start) { |
3359 | const iGmRun *found; | 4408 | const iGmRun *found; |
3360 | if ((found = findRunAtLoc_GmDocument(d->doc, d->foundMark.start)) != NULL) { | 4409 | if ((found = findRunAtLoc_GmDocument(d->view.doc, d->foundMark.start)) != NULL) { |
3361 | scrollTo_DocumentWidget_(d, mid_Rect(found->bounds).y, iTrue); | 4410 | scrollTo_DocumentView_(&d->view, mid_Rect(found->bounds).y, iTrue); |
3362 | } | 4411 | } |
3363 | } | 4412 | } |
3364 | } | 4413 | } |
3365 | if (flags_Widget(w) & touchDrag_WidgetFlag) { | 4414 | if (flags_Widget(w) & touchDrag_WidgetFlag) { |
3366 | postCommand_Root(w->root, "document.select arg:0"); /* we can't handle both at the same time */ | 4415 | postCommand_Root(w->root, "document.select arg:0"); /* we can't handle both at the same time */ |
3367 | } | 4416 | } |
3368 | invalidateWideRunsWithNonzeroOffset_DocumentWidget_(d); /* markers don't support offsets */ | 4417 | invalidateWideRunsWithNonzeroOffset_DocumentView_(&d->view); /* markers don't support offsets */ |
3369 | resetWideRuns_DocumentWidget_(d); | 4418 | resetWideRuns_DocumentView_(&d->view); |
3370 | refresh_Widget(w); | 4419 | refresh_Widget(w); |
3371 | return iTrue; | 4420 | return iTrue; |
3372 | } | 4421 | } |
@@ -3379,16 +4428,16 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3379 | } | 4428 | } |
3380 | else if (equal_Command(cmd, "bookmark.links") && document_App() == d) { | 4429 | else if (equal_Command(cmd, "bookmark.links") && document_App() == d) { |
3381 | iPtrArray *links = collectNew_PtrArray(); | 4430 | iPtrArray *links = collectNew_PtrArray(); |
3382 | render_GmDocument(d->doc, (iRangei){ 0, size_GmDocument(d->doc).y }, addAllLinks_, links); | 4431 | render_GmDocument(d->view.doc, (iRangei){ 0, size_GmDocument(d->view.doc).y }, addAllLinks_, links); |
3383 | /* Find links that aren't already bookmarked. */ | 4432 | /* Find links that aren't already bookmarked. */ |
3384 | iForEach(PtrArray, i, links) { | 4433 | iForEach(PtrArray, i, links) { |
3385 | const iGmRun *run = i.ptr; | 4434 | const iGmRun *run = i.ptr; |
3386 | uint32_t bmid; | 4435 | uint32_t bmid; |
3387 | if ((bmid = findUrl_Bookmarks(bookmarks_App(), | 4436 | if ((bmid = findUrl_Bookmarks(bookmarks_App(), |
3388 | linkUrl_GmDocument(d->doc, run->linkId))) != 0) { | 4437 | linkUrl_GmDocument(d->view.doc, run->linkId))) != 0) { |
3389 | const iBookmark *bm = get_Bookmarks(bookmarks_App(), bmid); | 4438 | const iBookmark *bm = get_Bookmarks(bookmarks_App(), bmid); |
3390 | /* We can import local copies of remote bookmarks. */ | 4439 | /* We can import local copies of remote bookmarks. */ |
3391 | if (!hasTag_Bookmark(bm, remote_BookmarkTag)) { | 4440 | if (~bm->flags & remote_BookmarkFlag) { |
3392 | remove_PtrArrayIterator(&i); | 4441 | remove_PtrArrayIterator(&i); |
3393 | } | 4442 | } |
3394 | } | 4443 | } |
@@ -3412,7 +4461,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3412 | iConstForEach(PtrArray, j, links) { | 4461 | iConstForEach(PtrArray, j, links) { |
3413 | const iGmRun *run = j.ptr; | 4462 | const iGmRun *run = j.ptr; |
3414 | add_Bookmarks(bookmarks_App(), | 4463 | add_Bookmarks(bookmarks_App(), |
3415 | linkUrl_GmDocument(d->doc, run->linkId), | 4464 | linkUrl_GmDocument(d->view.doc, run->linkId), |
3416 | collect_String(newRange_String(run->text)), | 4465 | collect_String(newRange_String(run->text)), |
3417 | NULL, | 4466 | NULL, |
3418 | 0x1f588 /* pin */); | 4467 | 0x1f588 /* pin */); |
@@ -3427,7 +4476,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3427 | return iTrue; | 4476 | return iTrue; |
3428 | } | 4477 | } |
3429 | else if (equalWidget_Command(cmd, w, "menu.closed")) { | 4478 | else if (equalWidget_Command(cmd, w, "menu.closed")) { |
3430 | updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window(), 0)); | 4479 | updateHover_DocumentView_(&d->view, mouseCoord_Window(get_Window(), 0)); |
3431 | } | 4480 | } |
3432 | else if (equal_Command(cmd, "document.autoreload")) { | 4481 | else if (equal_Command(cmd, "document.autoreload")) { |
3433 | if (d->mod.reloadInterval) { | 4482 | if (d->mod.reloadInterval) { |
@@ -3485,7 +4534,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3485 | if (argLabel_Command(cmd, "ttf")) { | 4534 | if (argLabel_Command(cmd, "ttf")) { |
3486 | iAssert(!cmp_String(&d->sourceMime, "font/ttf")); | 4535 | iAssert(!cmp_String(&d->sourceMime, "font/ttf")); |
3487 | installFontFile_Fonts(collect_String(suffix_Command(cmd, "name")), &d->sourceContent); | 4536 | installFontFile_Fonts(collect_String(suffix_Command(cmd, "name")), &d->sourceContent); |
3488 | postCommand_App("open url:about:fonts"); | 4537 | postCommand_App("open url:about:fonts"); |
3489 | } | 4538 | } |
3490 | else { | 4539 | else { |
3491 | const iString *id = idFromUrl_FontPack(d->mod.url); | 4540 | const iString *id = idFromUrl_FontPack(d->mod.url); |
@@ -3497,14 +4546,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3497 | return iFalse; | 4546 | return iFalse; |
3498 | } | 4547 | } |
3499 | 4548 | ||
3500 | static iRect runRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { | ||
3501 | const iRect docBounds = documentBounds_DocumentWidget_(d); | ||
3502 | return moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), viewPos_DocumentWidget_(d))); | ||
3503 | } | ||
3504 | |||
3505 | static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { | 4549 | static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { |
3506 | if (run && run->mediaType == audio_MediaType) { | 4550 | if (run && run->mediaType == audio_MediaType) { |
3507 | iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); | 4551 | iPlayer *plr = audioPlayer_Media(media_GmDocument(d->view.doc), mediaId_GmRun(run)); |
3508 | setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue); | 4552 | setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue); |
3509 | d->grabbedStartVolume = volume_Player(plr); | 4553 | d->grabbedStartVolume = volume_Player(plr); |
3510 | d->grabbedPlayer = run; | 4554 | d->grabbedPlayer = run; |
@@ -3512,7 +4556,7 @@ static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *r | |||
3512 | } | 4556 | } |
3513 | else if (d->grabbedPlayer) { | 4557 | else if (d->grabbedPlayer) { |
3514 | setFlags_Player( | 4558 | setFlags_Player( |
3515 | audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer)), | 4559 | audioPlayer_Media(media_GmDocument(d->view.doc), mediaId_GmRun(d->grabbedPlayer)), |
3516 | volumeGrabbed_PlayerFlag, | 4560 | volumeGrabbed_PlayerFlag, |
3517 | iFalse); | 4561 | iFalse); |
3518 | d->grabbedPlayer = NULL; | 4562 | d->grabbedPlayer = NULL; |
@@ -3528,24 +4572,33 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev | |||
3528 | ev->type != SDL_MOUSEMOTION) { | 4572 | ev->type != SDL_MOUSEMOTION) { |
3529 | return iFalse; | 4573 | return iFalse; |
3530 | } | 4574 | } |
3531 | if (ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) { | ||
3532 | if (ev->button.button != SDL_BUTTON_LEFT) { | ||
3533 | return iFalse; | ||
3534 | } | ||
3535 | } | ||
3536 | if (d->grabbedPlayer) { | 4575 | if (d->grabbedPlayer) { |
3537 | /* Updated in the drag. */ | 4576 | /* Updated in the drag. */ |
3538 | return iFalse; | 4577 | return iFalse; |
3539 | } | 4578 | } |
3540 | const iInt2 mouse = init_I2(ev->button.x, ev->button.y); | 4579 | const iInt2 mouse = init_I2(ev->button.x, ev->button.y); |
3541 | iConstForEach(PtrArray, i, &d->visibleMedia) { | 4580 | iConstForEach(PtrArray, i, &d->view.visibleMedia) { |
3542 | const iGmRun *run = i.ptr; | 4581 | const iGmRun *run = i.ptr; |
4582 | if (run->mediaType == download_MediaType) { | ||
4583 | iDownloadUI ui; | ||
4584 | init_DownloadUI(&ui, media_GmDocument(d->view.doc), mediaId_GmRun(run).id, | ||
4585 | runRect_DocumentView_(&d->view, run)); | ||
4586 | if (processEvent_DownloadUI(&ui, ev)) { | ||
4587 | return iTrue; | ||
4588 | } | ||
4589 | continue; | ||
4590 | } | ||
3543 | if (run->mediaType != audio_MediaType) { | 4591 | if (run->mediaType != audio_MediaType) { |
3544 | continue; | 4592 | continue; |
3545 | } | 4593 | } |
4594 | if (ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) { | ||
4595 | if (ev->button.button != SDL_BUTTON_LEFT) { | ||
4596 | return iFalse; | ||
4597 | } | ||
4598 | } | ||
3546 | /* TODO: move this to mediaui.c */ | 4599 | /* TODO: move this to mediaui.c */ |
3547 | const iRect rect = runRect_DocumentWidget_(d, run); | 4600 | const iRect rect = runRect_DocumentView_(&d->view, run); |
3548 | iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); | 4601 | iPlayer * plr = audioPlayer_Media(media_GmDocument(d->view.doc), mediaId_GmRun(run)); |
3549 | if (contains_Rect(rect, mouse)) { | 4602 | if (contains_Rect(rect, mouse)) { |
3550 | iPlayerUI ui; | 4603 | iPlayerUI ui; |
3551 | init_PlayerUI(&ui, plr, rect); | 4604 | init_PlayerUI(&ui, plr, rect); |
@@ -3610,83 +4663,140 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev | |||
3610 | return iFalse; | 4663 | return iFalse; |
3611 | } | 4664 | } |
3612 | 4665 | ||
3613 | static size_t linkOrdinalFromKey_DocumentWidget_(const iDocumentWidget *d, int key) { | 4666 | static void beginMarkingSelection_DocumentWidget_(iDocumentWidget *d, iInt2 pos) { |
3614 | size_t ord = iInvalidPos; | 4667 | setFocus_Widget(NULL); /* TODO: Focus this document? */ |
3615 | if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) { | 4668 | invalidateWideRunsWithNonzeroOffset_DocumentView_(&d->view); |
3616 | if (key >= '1' && key <= '9') { | 4669 | resetWideRuns_DocumentView_(&d->view); /* Selections don't support horizontal scrolling. */ |
3617 | return key - '1'; | 4670 | iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iTrue); |
3618 | } | 4671 | d->initialSelectMark = d->selectMark = sourceLoc_DocumentView_(&d->view, pos); |
3619 | if (key < 'a' || key > 'z') { | 4672 | refresh_Widget(as_Widget(d)); |
3620 | return iInvalidPos; | 4673 | } |
3621 | } | 4674 | |
3622 | ord = key - 'a' + 9; | 4675 | static void interactingWithLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) { |
3623 | #if defined (iPlatformApple) | 4676 | iRangecc loc = linkUrlRange_GmDocument(d->view.doc, id); |
3624 | /* Skip keys that would conflict with default system shortcuts: hide, minimize, quit, close. */ | 4677 | if (!loc.start) { |
3625 | if (key == 'h' || key == 'm' || key == 'q' || key == 'w') { | 4678 | clear_String(&d->linePrecedingLink); |
3626 | return iInvalidPos; | 4679 | return; |
3627 | } | ||
3628 | if (key > 'h') ord--; | ||
3629 | if (key > 'm') ord--; | ||
3630 | if (key > 'q') ord--; | ||
3631 | if (key > 'w') ord--; | ||
3632 | #endif | ||
3633 | } | 4680 | } |
3634 | else { | 4681 | d->requestLinkId = id; |
3635 | iForIndices(i, homeRowKeys_) { | 4682 | const char *start = range_String(source_GmDocument(d->view.doc)).start; |
3636 | if (homeRowKeys_[i] == key) { | 4683 | /* Find the preceding line. This is offered as a prefill option for a possible input query. */ |
3637 | return i; | 4684 | while (loc.start > start && *loc.start != '\n') { |
3638 | } | 4685 | loc.start--; |
3639 | } | ||
3640 | } | 4686 | } |
3641 | return ord; | 4687 | loc.end = loc.start; /* End of the preceding line. */ |
4688 | if (loc.start > start) { | ||
4689 | loc.start--; | ||
4690 | } | ||
4691 | while (loc.start > start && *loc.start != '\n') { | ||
4692 | loc.start--; | ||
4693 | } | ||
4694 | if (*loc.start == '\n') { | ||
4695 | loc.start++; /* Start of the preceding line. */ | ||
4696 | } | ||
4697 | setRange_String(&d->linePrecedingLink, loc); | ||
3642 | } | 4698 | } |
3643 | 4699 | ||
3644 | static iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t ord) { | 4700 | iLocalDef int wheelSwipeSide_DocumentWidget_(const iDocumentWidget *d) { |
3645 | if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) { | 4701 | return (d->flags & rightWheelSwipe_DocumentWidgetFlag ? 2 |
3646 | if (ord < 9) { | 4702 | : d->flags & leftWheelSwipe_DocumentWidgetFlag ? 1 |
3647 | return '1' + ord; | 4703 | : 0); |
3648 | } | 4704 | } |
3649 | #if defined (iPlatformApple) | 4705 | |
3650 | if (ord < 9 + 22) { | 4706 | static void finishWheelSwipe_DocumentWidget_(iDocumentWidget *d) { |
3651 | int key = 'a' + ord - 9; | 4707 | if (d->flags & eitherWheelSwipe_DocumentWidgetFlag && |
3652 | if (key >= 'h') key++; | 4708 | d->wheelSwipeState == direct_WheelSwipeState) { |
3653 | if (key >= 'm') key++; | 4709 | const int side = wheelSwipeSide_DocumentWidget_(d); |
3654 | if (key >= 'q') key++; | 4710 | int abort = ((side == 1 && d->swipeSpeed < 0) || (side == 2 && d->swipeSpeed > 0)); |
3655 | if (key >= 'w') key++; | 4711 | if (iAbs(d->wheelSwipeDistance) < width_Widget(d) / 4 && iAbs(d->swipeSpeed) < 4 * gap_UI) { |
3656 | return 'A' + key - 'a'; | 4712 | abort = 1; |
3657 | } | ||
3658 | #else | ||
3659 | if (ord < 9 + 26) { | ||
3660 | return 'A' + ord - 9; | ||
3661 | } | ||
3662 | #endif | ||
3663 | } | ||
3664 | else { | ||
3665 | if (ord < iElemCount(homeRowKeys_)) { | ||
3666 | return 'A' + homeRowKeys_[ord] - 'a'; | ||
3667 | } | 4713 | } |
4714 | postCommand_Widget(d, "edgeswipe.ended side:%d abort:%d", side, abort); | ||
4715 | d->flags &= ~eitherWheelSwipe_DocumentWidgetFlag; | ||
3668 | } | 4716 | } |
3669 | return 0; | ||
3670 | } | 4717 | } |
3671 | 4718 | ||
3672 | static void beginMarkingSelection_DocumentWidget_(iDocumentWidget *d, iInt2 pos) { | 4719 | static iBool handleWheelSwipe_DocumentWidget_(iDocumentWidget *d, const SDL_MouseWheelEvent *ev) { |
3673 | setFocus_Widget(NULL); /* TODO: Focus this document? */ | 4720 | iWidget *w = as_Widget(d); |
3674 | invalidateWideRunsWithNonzeroOffset_DocumentWidget_(d); | 4721 | if (deviceType_App() != desktop_AppDeviceType) { |
3675 | resetWideRuns_DocumentWidget_(d); /* Selections don't support horizontal scrolling. */ | 4722 | return iFalse; |
3676 | iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iTrue); | 4723 | } |
3677 | d->initialSelectMark = d->selectMark = sourceLoc_DocumentWidget_(d, pos); | 4724 | if (~flags_Widget(w) & horizontalOffset_WidgetFlag) { |
3678 | refresh_Widget(as_Widget(d)); | 4725 | return iFalse; |
4726 | } | ||
4727 | iAssert(~d->flags & animationPlaceholder_DocumentWidgetFlag); | ||
4728 | // printf("STATE:%d wheel x:%d inert:%d end:%d\n", d->wheelSwipeState, | ||
4729 | // ev->x, isInertia_MouseWheelEvent(ev), | ||
4730 | // isScrollFinished_MouseWheelEvent(ev)); | ||
4731 | // fflush(stdout); | ||
4732 | switch (d->wheelSwipeState) { | ||
4733 | case none_WheelSwipeState: | ||
4734 | /* A new swipe starts. */ | ||
4735 | if (!isInertia_MouseWheelEvent(ev) && !isScrollFinished_MouseWheelEvent(ev)) { | ||
4736 | int side = ev->x > 0 ? 1 : 2; | ||
4737 | d->wheelSwipeDistance = ev->x * 2; | ||
4738 | d->flags &= ~eitherWheelSwipe_DocumentWidgetFlag; | ||
4739 | d->flags |= (side == 1 ? leftWheelSwipe_DocumentWidgetFlag | ||
4740 | : rightWheelSwipe_DocumentWidgetFlag); | ||
4741 | // printf("swipe starts at %d, side %d\n", d->wheelSwipeDistance, side); | ||
4742 | d->wheelSwipeState = direct_WheelSwipeState; | ||
4743 | d->swipeSpeed = 0; | ||
4744 | postCommand_Widget(d, "edgeswipe.moved arg:%d side:%d", d->wheelSwipeDistance, side); | ||
4745 | return iTrue; | ||
4746 | } | ||
4747 | break; | ||
4748 | case direct_WheelSwipeState: | ||
4749 | if (isInertia_MouseWheelEvent(ev) || isScrollFinished_MouseWheelEvent(ev)) { | ||
4750 | finishWheelSwipe_DocumentWidget_(d); | ||
4751 | d->wheelSwipeState = none_WheelSwipeState; | ||
4752 | } | ||
4753 | else { | ||
4754 | int step = ev->x * 2; | ||
4755 | d->wheelSwipeDistance += step; | ||
4756 | /* Remember the maximum speed. */ | ||
4757 | if (d->swipeSpeed < 0 && step < 0) { | ||
4758 | d->swipeSpeed = iMin(d->swipeSpeed, step); | ||
4759 | } | ||
4760 | else if (d->swipeSpeed > 0 && step > 0) { | ||
4761 | d->swipeSpeed = iMax(d->swipeSpeed, step); | ||
4762 | } | ||
4763 | else { | ||
4764 | d->swipeSpeed = step; | ||
4765 | } | ||
4766 | switch (wheelSwipeSide_DocumentWidget_(d)) { | ||
4767 | case 1: | ||
4768 | d->wheelSwipeDistance = iMax(0, d->wheelSwipeDistance); | ||
4769 | d->wheelSwipeDistance = iMin(width_Widget(d), d->wheelSwipeDistance); | ||
4770 | break; | ||
4771 | case 2: | ||
4772 | d->wheelSwipeDistance = iMin(0, d->wheelSwipeDistance); | ||
4773 | d->wheelSwipeDistance = iMax(-width_Widget(d), d->wheelSwipeDistance); | ||
4774 | break; | ||
4775 | } | ||
4776 | /* TODO: calculate speed, rememeber direction */ | ||
4777 | //printf("swipe moved to %d, side %d\n", d->wheelSwipeDistance, side); | ||
4778 | postCommand_Widget(d, "edgeswipe.moved arg:%d side:%d", d->wheelSwipeDistance, | ||
4779 | wheelSwipeSide_DocumentWidget_(d)); | ||
4780 | } | ||
4781 | return iTrue; | ||
4782 | } | ||
4783 | return iFalse; | ||
3679 | } | 4784 | } |
3680 | 4785 | ||
3681 | static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { | 4786 | static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { |
3682 | iWidget *w = as_Widget(d); | 4787 | iWidget *w = as_Widget(d); |
4788 | iDocumentView *view = &d->view; | ||
3683 | if (isMetricsChange_UserEvent(ev)) { | 4789 | if (isMetricsChange_UserEvent(ev)) { |
3684 | updateSize_DocumentWidget(d); | 4790 | updateSize_DocumentWidget(d); |
3685 | } | 4791 | } |
3686 | else if (processEvent_SmoothScroll(&d->scrollY, ev)) { | 4792 | else if (processEvent_SmoothScroll(&d->view.scrollY, ev)) { |
3687 | return iTrue; | 4793 | return iTrue; |
3688 | } | 4794 | } |
3689 | else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { | 4795 | else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { |
4796 | if (isCommand_Widget(w, ev, "pullaction")) { | ||
4797 | postCommand_Widget(w, "navigate.reload"); | ||
4798 | return iTrue; | ||
4799 | } | ||
3690 | if (!handleCommand_DocumentWidget_(d, command_UserEvent(ev))) { | 4800 | if (!handleCommand_DocumentWidget_(d, command_UserEvent(ev))) { |
3691 | /* Base class commands. */ | 4801 | /* Base class commands. */ |
3692 | return processEvent_Widget(w, ev); | 4802 | return processEvent_Widget(w, ev); |
@@ -3698,27 +4808,28 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3698 | if ((d->flags & showLinkNumbers_DocumentWidgetFlag) && | 4808 | if ((d->flags & showLinkNumbers_DocumentWidgetFlag) && |
3699 | ((key >= '1' && key <= '9') || (key >= 'a' && key <= 'z'))) { | 4809 | ((key >= '1' && key <= '9') || (key >= 'a' && key <= 'z'))) { |
3700 | const size_t ord = linkOrdinalFromKey_DocumentWidget_(d, key) + d->ordinalBase; | 4810 | const size_t ord = linkOrdinalFromKey_DocumentWidget_(d, key) + d->ordinalBase; |
3701 | iConstForEach(PtrArray, i, &d->visibleLinks) { | 4811 | iConstForEach(PtrArray, i, &d->view.visibleLinks) { |
3702 | if (ord == iInvalidPos) break; | 4812 | if (ord == iInvalidPos) break; |
3703 | const iGmRun *run = i.ptr; | 4813 | const iGmRun *run = i.ptr; |
3704 | if (run->flags & decoration_GmRunFlag && | 4814 | if (run->flags & decoration_GmRunFlag && |
3705 | visibleLinkOrdinal_DocumentWidget_(d, run->linkId) == ord) { | 4815 | visibleLinkOrdinal_DocumentView_(view, run->linkId) == ord) { |
3706 | if (d->flags & setHoverViaKeys_DocumentWidgetFlag) { | 4816 | if (d->flags & setHoverViaKeys_DocumentWidgetFlag) { |
3707 | d->hoverLink = run; | 4817 | view->hoverLink = run; |
3708 | } | 4818 | } |
3709 | else { | 4819 | else { |
3710 | postCommandf_Root(w->root, | 4820 | postCommandf_Root( |
3711 | "open newtab:%d url:%s", | 4821 | w->root, |
3712 | (isPinned_DocumentWidget_(d) ? otherRoot_OpenTabFlag : 0) ^ | 4822 | "open newtab:%d url:%s", |
3713 | (d->ordinalMode == | 4823 | (isPinned_DocumentWidget_(d) ? otherRoot_OpenTabFlag : 0) ^ |
3714 | numbersAndAlphabet_DocumentLinkOrdinalMode | 4824 | (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode |
3715 | ? openTabMode_Sym(modState_Keys()) | 4825 | ? openTabMode_Sym(modState_Keys()) |
3716 | : (d->flags & newTabViaHomeKeys_DocumentWidgetFlag ? 1 : 0)), | 4826 | : (d->flags & newTabViaHomeKeys_DocumentWidgetFlag ? 1 : 0)), |
3717 | cstr_String(absoluteUrl_String( | 4827 | cstr_String(absoluteUrl_String( |
3718 | d->mod.url, linkUrl_GmDocument(d->doc, run->linkId)))); | 4828 | d->mod.url, linkUrl_GmDocument(view->doc, run->linkId)))); |
4829 | interactingWithLink_DocumentWidget_(d, run->linkId); | ||
3719 | } | 4830 | } |
3720 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 4831 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
3721 | invalidateVisibleLinks_DocumentWidget_(d); | 4832 | invalidateVisibleLinks_DocumentView_(view); |
3722 | refresh_Widget(d); | 4833 | refresh_Widget(d); |
3723 | return iTrue; | 4834 | return iTrue; |
3724 | } | 4835 | } |
@@ -3728,7 +4839,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3728 | case SDLK_ESCAPE: | 4839 | case SDLK_ESCAPE: |
3729 | if (d->flags & showLinkNumbers_DocumentWidgetFlag && document_App() == d) { | 4840 | if (d->flags & showLinkNumbers_DocumentWidgetFlag && document_App() == d) { |
3730 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 4841 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
3731 | invalidateVisibleLinks_DocumentWidget_(d); | 4842 | invalidateVisibleLinks_DocumentView_(view); |
3732 | refresh_Widget(d); | 4843 | refresh_Widget(d); |
3733 | return iTrue; | 4844 | return iTrue; |
3734 | } | 4845 | } |
@@ -3740,7 +4851,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3740 | for (size_t i = 0; i < 64; ++i) { | 4851 | for (size_t i = 0; i < 64; ++i) { |
3741 | setByte_Block(seed, i, iRandom(0, 256)); | 4852 | setByte_Block(seed, i, iRandom(0, 256)); |
3742 | } | 4853 | } |
3743 | setThemeSeed_GmDocument(d->doc, seed); | 4854 | setThemeSeed_GmDocument(view->doc, seed); |
3744 | delete_Block(seed); | 4855 | delete_Block(seed); |
3745 | invalidate_DocumentWidget_(d); | 4856 | invalidate_DocumentWidget_(d); |
3746 | refresh_Widget(w); | 4857 | refresh_Widget(w); |
@@ -3770,13 +4881,24 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3770 | #endif | 4881 | #endif |
3771 | } | 4882 | } |
3772 | } | 4883 | } |
4884 | #if defined (iPlatformAppleDesktop) | ||
4885 | else if (ev->type == SDL_MOUSEWHEEL && | ||
4886 | ev->wheel.y == 0 && | ||
4887 | d->wheelSwipeState == direct_WheelSwipeState && | ||
4888 | handleWheelSwipe_DocumentWidget_(d, &ev->wheel)) { | ||
4889 | return iTrue; | ||
4890 | } | ||
4891 | #endif | ||
3773 | else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { | 4892 | else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { |
3774 | const iInt2 mouseCoord = coord_MouseWheelEvent(&ev->wheel); | 4893 | const iInt2 mouseCoord = coord_MouseWheelEvent(&ev->wheel); |
3775 | if (isPerPixel_MouseWheelEvent(&ev->wheel)) { | 4894 | if (isPerPixel_MouseWheelEvent(&ev->wheel)) { |
3776 | const iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y); | 4895 | const iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y); |
3777 | stop_Anim(&d->scrollY.pos); | 4896 | stop_Anim(&d->view.scrollY.pos); |
3778 | immediateScroll_DocumentWidget_(d, -wheel.y); | 4897 | immediateScroll_DocumentView_(view, -wheel.y); |
3779 | scrollWideBlock_DocumentWidget_(d, mouseCoord, -wheel.x, 0); | 4898 | if (!scrollWideBlock_DocumentView_(view, mouseCoord, -wheel.x, 0) && |
4899 | wheel.x) { | ||
4900 | handleWheelSwipe_DocumentWidget_(d, &ev->wheel); | ||
4901 | } | ||
3780 | } | 4902 | } |
3781 | else { | 4903 | else { |
3782 | /* Traditional mouse wheel. */ | 4904 | /* Traditional mouse wheel. */ |
@@ -3785,16 +4907,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3785 | postCommandf_App("zoom.delta arg:%d", amount > 0 ? 10 : -10); | 4907 | postCommandf_App("zoom.delta arg:%d", amount > 0 ? 10 : -10); |
3786 | return iTrue; | 4908 | return iTrue; |
3787 | } | 4909 | } |
3788 | smoothScroll_DocumentWidget_( | 4910 | smoothScroll_DocumentView_(view, |
3789 | d, | 4911 | -3 * amount * lineHeight_Text(paragraph_FontId), |
3790 | -3 * amount * lineHeight_Text(paragraph_FontId), | 4912 | smoothDuration_DocumentWidget_(mouse_ScrollType)); |
3791 | smoothDuration_DocumentWidget_(mouse_ScrollType)); | 4913 | scrollWideBlock_DocumentView_( |
3792 | /* accelerated speed for repeated wheelings */ | 4914 | view, mouseCoord, -3 * ev->wheel.x * lineHeight_Text(paragraph_FontId), 167); |
3793 | // * (!isFinished_SmoothScroll(&d->scrollY) && pos_Anim(&d->scrollY.pos) < 0.25f | ||
3794 | // ? 0.5f | ||
3795 | // : 1.0f)); | ||
3796 | scrollWideBlock_DocumentWidget_( | ||
3797 | d, mouseCoord, -3 * ev->wheel.x * lineHeight_Text(paragraph_FontId), 167); | ||
3798 | } | 4915 | } |
3799 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); | 4916 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); |
3800 | return iTrue; | 4917 | return iTrue; |
@@ -3813,16 +4930,19 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3813 | } | 4930 | } |
3814 | #endif | 4931 | #endif |
3815 | else { | 4932 | else { |
3816 | if (value_Anim(&d->altTextOpacity) < 0.833f) { | 4933 | if (value_Anim(&view->altTextOpacity) < 0.833f) { |
3817 | setValue_Anim(&d->altTextOpacity, 0, 0); /* keep it hidden while moving */ | 4934 | setValue_Anim(&view->altTextOpacity, 0, 0); /* keep it hidden while moving */ |
3818 | } | 4935 | } |
3819 | updateHover_DocumentWidget_(d, mpos); | 4936 | updateHover_DocumentView_(view, mpos); |
3820 | } | 4937 | } |
3821 | } | 4938 | } |
3822 | if (ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) { | 4939 | if (ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) { |
3823 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iFalse); | 4940 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iFalse); |
3824 | return iTrue; | 4941 | return iTrue; |
3825 | } | 4942 | } |
4943 | if (processMediaEvents_DocumentWidget_(d, ev)) { | ||
4944 | return iTrue; | ||
4945 | } | ||
3826 | if (ev->type == SDL_MOUSEBUTTONDOWN) { | 4946 | if (ev->type == SDL_MOUSEBUTTONDOWN) { |
3827 | if (ev->button.button == SDL_BUTTON_X1) { | 4947 | if (ev->button.button == SDL_BUTTON_X1) { |
3828 | postCommand_Root(w->root, "navigate.back"); | 4948 | postCommand_Root(w->root, "navigate.back"); |
@@ -3832,17 +4952,18 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3832 | postCommand_Root(w->root, "navigate.forward"); | 4952 | postCommand_Root(w->root, "navigate.forward"); |
3833 | return iTrue; | 4953 | return iTrue; |
3834 | } | 4954 | } |
3835 | if (ev->button.button == SDL_BUTTON_MIDDLE && d->hoverLink) { | 4955 | if (ev->button.button == SDL_BUTTON_MIDDLE && view->hoverLink) { |
4956 | interactingWithLink_DocumentWidget_(d, view->hoverLink->linkId); | ||
3836 | postCommandf_Root(w->root, "open newtab:%d url:%s", | 4957 | postCommandf_Root(w->root, "open newtab:%d url:%s", |
3837 | (isPinned_DocumentWidget_(d) ? otherRoot_OpenTabFlag : 0) | | 4958 | (isPinned_DocumentWidget_(d) ? otherRoot_OpenTabFlag : 0) | |
3838 | (modState_Keys() & KMOD_SHIFT ? new_OpenTabFlag : newBackground_OpenTabFlag), | 4959 | (modState_Keys() & KMOD_SHIFT ? new_OpenTabFlag : newBackground_OpenTabFlag), |
3839 | cstr_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId))); | 4960 | cstr_String(linkUrl_GmDocument(view->doc, view->hoverLink->linkId))); |
3840 | return iTrue; | 4961 | return iTrue; |
3841 | } | 4962 | } |
3842 | if (ev->button.button == SDL_BUTTON_RIGHT && | 4963 | if (ev->button.button == SDL_BUTTON_RIGHT && |
3843 | contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { | 4964 | contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { |
3844 | if (!isVisible_Widget(d->menu)) { | 4965 | if (!isVisible_Widget(d->menu)) { |
3845 | d->contextLink = d->hoverLink; | 4966 | d->contextLink = view->hoverLink; |
3846 | d->contextPos = init_I2(ev->button.x, ev->button.y); | 4967 | d->contextPos = init_I2(ev->button.x, ev->button.y); |
3847 | if (d->menu) { | 4968 | if (d->menu) { |
3848 | destroy_Widget(d->menu); | 4969 | destroy_Widget(d->menu); |
@@ -3853,15 +4974,18 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3853 | init_Array(&items, sizeof(iMenuItem)); | 4974 | init_Array(&items, sizeof(iMenuItem)); |
3854 | if (d->contextLink) { | 4975 | if (d->contextLink) { |
3855 | /* Context menu for a link. */ | 4976 | /* Context menu for a link. */ |
3856 | const iString *linkUrl = linkUrl_GmDocument(d->doc, d->contextLink->linkId); | 4977 | interactingWithLink_DocumentWidget_(d, d->contextLink->linkId); /* perhaps will be triggered */ |
4978 | const iString *linkUrl = linkUrl_GmDocument(view->doc, d->contextLink->linkId); | ||
3857 | // const int linkFlags = linkFlags_GmDocument(d->doc, d->contextLink->linkId); | 4979 | // const int linkFlags = linkFlags_GmDocument(d->doc, d->contextLink->linkId); |
3858 | const iRangecc scheme = urlScheme_String(linkUrl); | 4980 | const iRangecc scheme = urlScheme_String(linkUrl); |
3859 | const iBool isGemini = equalCase_Rangecc(scheme, "gemini"); | 4981 | const iBool isGemini = equalCase_Rangecc(scheme, "gemini"); |
3860 | iBool isNative = iFalse; | 4982 | iBool isNative = iFalse; |
3861 | if (deviceType_App() != desktop_AppDeviceType) { | 4983 | if (deviceType_App() != desktop_AppDeviceType) { |
3862 | /* Show the link as the first, non-interactive item. */ | 4984 | /* Show the link as the first, non-interactive item. */ |
4985 | iString *infoText = collectNew_String(); | ||
4986 | infoText_LinkInfo(d->view.doc, d->contextLink->linkId, infoText); | ||
3863 | pushBack_Array(&items, &(iMenuItem){ | 4987 | pushBack_Array(&items, &(iMenuItem){ |
3864 | format_CStr("```%s", cstr_String(linkUrl)), | 4988 | format_CStr("```%s", cstr_String(infoText)), |
3865 | 0, 0, NULL }); | 4989 | 0, 0, NULL }); |
3866 | } | 4990 | } |
3867 | if (willUseProxy_App(scheme) || isGemini || | 4991 | if (willUseProxy_App(scheme) || isGemini || |
@@ -3872,27 +4996,59 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3872 | /* Regular links that we can open. */ | 4996 | /* Regular links that we can open. */ |
3873 | pushBackN_Array( | 4997 | pushBackN_Array( |
3874 | &items, | 4998 | &items, |
3875 | (iMenuItem[]){ | 4999 | (iMenuItem[]){ { openTab_Icon " ${link.newtab}", |
3876 | { openTab_Icon " ${link.newtab}", | 5000 | 0, |
3877 | 0, | 5001 | 0, |
3878 | 0, | 5002 | format_CStr("!open newtab:1 origin:%s url:%s", |
3879 | format_CStr("!open newtab:1 url:%s", cstr_String(linkUrl)) }, | 5003 | cstr_String(id_Widget(w)), |
3880 | { openTabBg_Icon " ${link.newtab.background}", | 5004 | cstr_String(linkUrl)) }, |
3881 | 0, | 5005 | { openTabBg_Icon " ${link.newtab.background}", |
3882 | 0, | 5006 | 0, |
3883 | format_CStr("!open newtab:2 url:%s", cstr_String(linkUrl)) }, | 5007 | 0, |
3884 | { "${link.side}", | 5008 | format_CStr("!open newtab:2 origin:%s url:%s", |
3885 | 0, | 5009 | cstr_String(id_Widget(w)), |
3886 | 0, | 5010 | cstr_String(linkUrl)) }, |
3887 | format_CStr("!open newtab:4 url:%s", cstr_String(linkUrl)) }, | 5011 | { "${link.side}", |
3888 | { "${link.side.newtab}", | 5012 | 0, |
3889 | 0, | 5013 | 0, |
3890 | 0, | 5014 | format_CStr("!open newtab:4 origin:%s url:%s", |
3891 | format_CStr("!open newtab:5 url:%s", cstr_String(linkUrl)) } }, | 5015 | cstr_String(id_Widget(w)), |
5016 | cstr_String(linkUrl)) }, | ||
5017 | { "${link.side.newtab}", | ||
5018 | 0, | ||
5019 | 0, | ||
5020 | format_CStr("!open newtab:5 origin:%s url:%s", | ||
5021 | cstr_String(id_Widget(w)), | ||
5022 | cstr_String(linkUrl)) } }, | ||
3892 | 4); | 5023 | 4); |
3893 | if (deviceType_App() == phone_AppDeviceType) { | 5024 | if (deviceType_App() == phone_AppDeviceType) { |
3894 | removeN_Array(&items, size_Array(&items) - 2, iInvalidSize); | 5025 | removeN_Array(&items, size_Array(&items) - 2, iInvalidSize); |
3895 | } | 5026 | } |
5027 | if (equalCase_Rangecc(scheme, "file")) { | ||
5028 | pushBack_Array(&items, &(iMenuItem){ "---" }); | ||
5029 | pushBack_Array(&items, | ||
5030 | &(iMenuItem){ export_Icon " ${menu.open.external}", | ||
5031 | 0, | ||
5032 | 0, | ||
5033 | format_CStr("!open default:1 url:%s", | ||
5034 | cstr_String(linkUrl)) }); | ||
5035 | #if defined (iPlatformAppleDesktop) | ||
5036 | pushBack_Array(&items, | ||
5037 | &(iMenuItem){ "${menu.reveal.macos}", | ||
5038 | 0, | ||
5039 | 0, | ||
5040 | format_CStr("!reveal url:%s", | ||
5041 | cstr_String(linkUrl)) }); | ||
5042 | #endif | ||
5043 | #if defined (iPlatformLinux) | ||
5044 | pushBack_Array(&items, | ||
5045 | &(iMenuItem){ "${menu.reveal.filemgr}", | ||
5046 | 0, | ||
5047 | 0, | ||
5048 | format_CStr("!reveal url:%s", | ||
5049 | cstr_String(linkUrl)) }); | ||
5050 | #endif | ||
5051 | } | ||
3896 | } | 5052 | } |
3897 | else if (!willUseProxy_App(scheme)) { | 5053 | else if (!willUseProxy_App(scheme)) { |
3898 | pushBack_Array( | 5054 | pushBack_Array( |
@@ -3910,11 +5066,13 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3910 | { isGemini ? "${link.noproxy}" : openExt_Icon " ${link.browser}", | 5066 | { isGemini ? "${link.noproxy}" : openExt_Icon " ${link.browser}", |
3911 | 0, | 5067 | 0, |
3912 | 0, | 5068 | 0, |
3913 | format_CStr("!open noproxy:1 url:%s", cstr_String(linkUrl)) } }, | 5069 | format_CStr("!open origin:%s noproxy:1 url:%s", |
5070 | cstr_String(id_Widget(w)), | ||
5071 | cstr_String(linkUrl)) } }, | ||
3914 | 2); | 5072 | 2); |
3915 | } | 5073 | } |
3916 | iString *linkLabel = collectNewRange_String( | 5074 | iString *linkLabel = collectNewRange_String( |
3917 | linkLabel_GmDocument(d->doc, d->contextLink->linkId)); | 5075 | linkLabel_GmDocument(view->doc, d->contextLink->linkId)); |
3918 | urlEncodeSpaces_String(linkLabel); | 5076 | urlEncodeSpaces_String(linkLabel); |
3919 | pushBackN_Array(&items, | 5077 | pushBackN_Array(&items, |
3920 | (iMenuItem[]){ { "---" }, | 5078 | (iMenuItem[]){ { "---" }, |
@@ -3927,7 +5085,8 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3927 | cstr_String(linkUrl)) }, | 5085 | cstr_String(linkUrl)) }, |
3928 | }, | 5086 | }, |
3929 | 3); | 5087 | 3); |
3930 | if (isNative && d->contextLink->mediaType != download_MediaType) { | 5088 | if (isNative && d->contextLink->mediaType != download_MediaType && |
5089 | !equalCase_Rangecc(scheme, "file")) { | ||
3931 | pushBackN_Array(&items, (iMenuItem[]){ | 5090 | pushBackN_Array(&items, (iMenuItem[]){ |
3932 | { "---" }, | 5091 | { "---" }, |
3933 | { download_Icon " ${link.download}", 0, 0, "document.downloadlink" }, | 5092 | { download_Icon " ${link.download}", 0, 0, "document.downloadlink" }, |
@@ -3947,6 +5106,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3947 | } | 5106 | } |
3948 | if (equalCase_Rangecc(scheme, "file")) { | 5107 | if (equalCase_Rangecc(scheme, "file")) { |
3949 | /* Local files may be deleted. */ | 5108 | /* Local files may be deleted. */ |
5109 | pushBack_Array(&items, &(iMenuItem){ "---" }); | ||
3950 | pushBack_Array( | 5110 | pushBack_Array( |
3951 | &items, | 5111 | &items, |
3952 | &(iMenuItem){ delete_Icon " " uiTextCaution_ColorEscape | 5112 | &(iMenuItem){ delete_Icon " " uiTextCaution_ColorEscape |
@@ -3982,9 +5142,10 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3982 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | 5142 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, |
3983 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, | 5143 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, |
3984 | { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, | 5144 | { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, |
5145 | { "${menu.page.upload.edit}", 0, 0, "document.upload copy:1" }, | ||
3985 | { "---" }, | 5146 | { "---" }, |
3986 | { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, | 5147 | { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, |
3987 | 16); | 5148 | 17); |
3988 | if (isEmpty_Range(&d->selectMark)) { | 5149 | if (isEmpty_Range(&d->selectMark)) { |
3989 | pushBackN_Array( | 5150 | pushBackN_Array( |
3990 | &items, | 5151 | &items, |
@@ -4020,9 +5181,6 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4020 | processContextMenuEvent_Widget(d->menu, ev, {}); | 5181 | processContextMenuEvent_Widget(d->menu, ev, {}); |
4021 | } | 5182 | } |
4022 | } | 5183 | } |
4023 | if (processMediaEvents_DocumentWidget_(d, ev)) { | ||
4024 | return iTrue; | ||
4025 | } | ||
4026 | if (processEvent_Banner(d->banner, ev)) { | 5184 | if (processEvent_Banner(d->banner, ev)) { |
4027 | return iTrue; | 5185 | return iTrue; |
4028 | } | 5186 | } |
@@ -4035,7 +5193,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4035 | /* Enable hover state now that scrolling has surely finished. */ | 5193 | /* Enable hover state now that scrolling has surely finished. */ |
4036 | if (d->flags & noHoverWhileScrolling_DocumentWidgetFlag) { | 5194 | if (d->flags & noHoverWhileScrolling_DocumentWidgetFlag) { |
4037 | d->flags &= ~noHoverWhileScrolling_DocumentWidgetFlag; | 5195 | d->flags &= ~noHoverWhileScrolling_DocumentWidgetFlag; |
4038 | updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window(), ev->button.which)); | 5196 | updateHover_DocumentView_(view, mouseCoord_Window(get_Window(), ev->button.which)); |
4039 | } | 5197 | } |
4040 | if (~flags_Widget(w) & touchDrag_WidgetFlag) { | 5198 | if (~flags_Widget(w) & touchDrag_WidgetFlag) { |
4041 | iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iFalse); | 5199 | iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iFalse); |
@@ -4046,7 +5204,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4046 | beginMarkingSelection_DocumentWidget_(d, d->click.startPos); | 5204 | beginMarkingSelection_DocumentWidget_(d, d->click.startPos); |
4047 | extendRange_Rangecc( | 5205 | extendRange_Rangecc( |
4048 | &d->selectMark, | 5206 | &d->selectMark, |
4049 | range_String(source_GmDocument(d->doc)), | 5207 | range_String(source_GmDocument(view->doc)), |
4050 | bothStartAndEnd_RangeExtension | | 5208 | bothStartAndEnd_RangeExtension | |
4051 | (d->click.count == 2 ? word_RangeExtension : line_RangeExtension)); | 5209 | (d->click.count == 2 ? word_RangeExtension : line_RangeExtension)); |
4052 | d->initialSelectMark = d->selectMark; | 5210 | d->initialSelectMark = d->selectMark; |
@@ -4060,24 +5218,24 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4060 | case drag_ClickResult: { | 5218 | case drag_ClickResult: { |
4061 | if (d->grabbedPlayer) { | 5219 | if (d->grabbedPlayer) { |
4062 | iPlayer *plr = | 5220 | iPlayer *plr = |
4063 | audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer)); | 5221 | audioPlayer_Media(media_GmDocument(view->doc), mediaId_GmRun(d->grabbedPlayer)); |
4064 | iPlayerUI ui; | 5222 | iPlayerUI ui; |
4065 | init_PlayerUI(&ui, plr, runRect_DocumentWidget_(d, d->grabbedPlayer)); | 5223 | init_PlayerUI(&ui, plr, runRect_DocumentView_(view, d->grabbedPlayer)); |
4066 | float off = (float) delta_Click(&d->click).x / (float) width_Rect(ui.volumeSlider); | 5224 | float off = (float) delta_Click(&d->click).x / (float) width_Rect(ui.volumeSlider); |
4067 | setVolume_Player(plr, d->grabbedStartVolume + off); | 5225 | setVolume_Player(plr, d->grabbedStartVolume + off); |
4068 | refresh_Widget(w); | 5226 | refresh_Widget(w); |
4069 | return iTrue; | 5227 | return iTrue; |
4070 | } | 5228 | } |
4071 | /* Fold/unfold a preformatted block. */ | 5229 | /* Fold/unfold a preformatted block. */ |
4072 | if (~d->flags & selecting_DocumentWidgetFlag && d->hoverPre && | 5230 | if (~d->flags & selecting_DocumentWidgetFlag && view->hoverPre && |
4073 | preIsFolded_GmDocument(d->doc, preId_GmRun(d->hoverPre))) { | 5231 | preIsFolded_GmDocument(view->doc, preId_GmRun(view->hoverPre))) { |
4074 | return iTrue; | 5232 | return iTrue; |
4075 | } | 5233 | } |
4076 | /* Begin selecting a range of text. */ | 5234 | /* Begin selecting a range of text. */ |
4077 | if (~d->flags & selecting_DocumentWidgetFlag) { | 5235 | if (~d->flags & selecting_DocumentWidgetFlag) { |
4078 | beginMarkingSelection_DocumentWidget_(d, d->click.startPos); | 5236 | beginMarkingSelection_DocumentWidget_(d, d->click.startPos); |
4079 | } | 5237 | } |
4080 | iRangecc loc = sourceLoc_DocumentWidget_(d, pos_Click(&d->click)); | 5238 | iRangecc loc = sourceLoc_DocumentView_(view, pos_Click(&d->click)); |
4081 | if (d->selectMark.start == NULL) { | 5239 | if (d->selectMark.start == NULL) { |
4082 | d->selectMark = loc; | 5240 | d->selectMark = loc; |
4083 | } | 5241 | } |
@@ -4088,7 +5246,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4088 | movingSelectMarkEnd_DocumentWidgetFlag))) { | 5246 | movingSelectMarkEnd_DocumentWidgetFlag))) { |
4089 | const iRangecc mark = selectMark_DocumentWidget_(d); | 5247 | const iRangecc mark = selectMark_DocumentWidget_(d); |
4090 | const char * midMark = mark.start + size_Range(&mark) / 2; | 5248 | const char * midMark = mark.start + size_Range(&mark) / 2; |
4091 | const iRangecc loc = sourceLoc_DocumentWidget_(d, pos_Click(&d->click)); | 5249 | const iRangecc loc = sourceLoc_DocumentView_(view, pos_Click(&d->click)); |
4092 | const iBool isCloserToStart = d->selectMark.start > d->selectMark.end ? | 5250 | const iBool isCloserToStart = d->selectMark.start > d->selectMark.end ? |
4093 | (loc.start > midMark) : (loc.start < midMark); | 5251 | (loc.start > midMark) : (loc.start < midMark); |
4094 | iChangeFlags(d->flags, movingSelectMarkStart_DocumentWidgetFlag, isCloserToStart); | 5252 | iChangeFlags(d->flags, movingSelectMarkStart_DocumentWidgetFlag, isCloserToStart); |
@@ -4118,7 +5276,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4118 | if (d->flags & (selectWords_DocumentWidgetFlag | selectLines_DocumentWidgetFlag)) { | 5276 | if (d->flags & (selectWords_DocumentWidgetFlag | selectLines_DocumentWidgetFlag)) { |
4119 | extendRange_Rangecc( | 5277 | extendRange_Rangecc( |
4120 | &d->selectMark, | 5278 | &d->selectMark, |
4121 | range_String(source_GmDocument(d->doc)), | 5279 | range_String(source_GmDocument(view->doc)), |
4122 | (d->flags & movingSelectMarkStart_DocumentWidgetFlag ? moveStart_RangeExtension | 5280 | (d->flags & movingSelectMarkStart_DocumentWidgetFlag ? moveStart_RangeExtension |
4123 | : moveEnd_RangeExtension) | | 5281 | : moveEnd_RangeExtension) | |
4124 | (d->flags & selectWords_DocumentWidgetFlag ? word_RangeExtension | 5282 | (d->flags & selectWords_DocumentWidgetFlag ? word_RangeExtension |
@@ -4156,7 +5314,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4156 | setFocus_Widget(NULL); | 5314 | setFocus_Widget(NULL); |
4157 | /* Tap in tap selection mode. */ | 5315 | /* Tap in tap selection mode. */ |
4158 | if (flags_Widget(w) & touchDrag_WidgetFlag) { | 5316 | if (flags_Widget(w) & touchDrag_WidgetFlag) { |
4159 | const iRangecc tapLoc = sourceLoc_DocumentWidget_(d, pos_Click(&d->click)); | 5317 | const iRangecc tapLoc = sourceLoc_DocumentView_(view, pos_Click(&d->click)); |
4160 | /* Tapping on the selection will show a menu. */ | 5318 | /* Tapping on the selection will show a menu. */ |
4161 | const iRangecc mark = selectMark_DocumentWidget_(d); | 5319 | const iRangecc mark = selectMark_DocumentWidget_(d); |
4162 | if (tapLoc.start >= mark.start && tapLoc.end <= mark.end) { | 5320 | if (tapLoc.start >= mark.start && tapLoc.end <= mark.end) { |
@@ -4165,11 +5323,15 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4165 | destroy_Widget(d->copyMenu); | 5323 | destroy_Widget(d->copyMenu); |
4166 | d->copyMenu = NULL; | 5324 | d->copyMenu = NULL; |
4167 | } | 5325 | } |
4168 | d->copyMenu = makeMenu_Widget(w, (iMenuItem[]){ | 5326 | const iMenuItem items[] = { |
4169 | { clipCopy_Icon " ${menu.copy}", 0, 0, "copy" }, | 5327 | { clipCopy_Icon " ${menu.copy}", 0, 0, "copy" }, |
5328 | #if defined (iPlatformAppleMobile) | ||
5329 | { export_Icon " ${menu.share}", 0, 0, "copy share:1" }, | ||
5330 | #endif | ||
4170 | { "---" }, | 5331 | { "---" }, |
4171 | { close_Icon " ${menu.select.clear}", 0, 0, "document.select arg:0" }, | 5332 | { close_Icon " ${menu.select.clear}", 0, 0, "document.select arg:0" }, |
4172 | }, 3); | 5333 | }; |
5334 | d->copyMenu = makeMenu_Widget(w, items, iElemCount(items)); | ||
4173 | setFlags_Widget(d->copyMenu, noFadeBackground_WidgetFlag, iTrue); | 5335 | setFlags_Widget(d->copyMenu, noFadeBackground_WidgetFlag, iTrue); |
4174 | openMenu_Widget(d->copyMenu, pos_Click(&d->click)); | 5336 | openMenu_Widget(d->copyMenu, pos_Click(&d->click)); |
4175 | return iTrue; | 5337 | return iTrue; |
@@ -4180,18 +5342,18 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4180 | return iTrue; | 5342 | return iTrue; |
4181 | } | 5343 | } |
4182 | } | 5344 | } |
4183 | if (d->hoverPre) { | 5345 | if (view->hoverPre) { |
4184 | togglePreFold_DocumentWidget_(d, preId_GmRun(d->hoverPre)); | 5346 | togglePreFold_DocumentWidget_(d, preId_GmRun(view->hoverPre)); |
4185 | return iTrue; | 5347 | return iTrue; |
4186 | } | 5348 | } |
4187 | if (d->hoverLink) { | 5349 | if (view->hoverLink) { |
4188 | /* TODO: Move this to a method. */ | 5350 | /* TODO: Move this to a method. */ |
4189 | const iGmLinkId linkId = d->hoverLink->linkId; | 5351 | const iGmLinkId linkId = view->hoverLink->linkId; |
4190 | const iMediaId linkMedia = mediaId_GmRun(d->hoverLink); | 5352 | const iMediaId linkMedia = mediaId_GmRun(view->hoverLink); |
4191 | const int linkFlags = linkFlags_GmDocument(d->doc, linkId); | 5353 | const int linkFlags = linkFlags_GmDocument(view->doc, linkId); |
4192 | iAssert(linkId); | 5354 | iAssert(linkId); |
4193 | /* Media links are opened inline by default. */ | 5355 | /* Media links are opened inline by default. */ |
4194 | if (isMediaLink_GmDocument(d->doc, linkId)) { | 5356 | if (isMediaLink_GmDocument(view->doc, linkId)) { |
4195 | if (linkFlags & content_GmLinkFlag && linkFlags & permanent_GmLinkFlag) { | 5357 | if (linkFlags & content_GmLinkFlag && linkFlags & permanent_GmLinkFlag) { |
4196 | /* We have the content and it cannot be dismissed, so nothing | 5358 | /* We have the content and it cannot be dismissed, so nothing |
4197 | further to do. */ | 5359 | further to do. */ |
@@ -4200,7 +5362,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4200 | if (!requestMedia_DocumentWidget_(d, linkId, iTrue)) { | 5362 | if (!requestMedia_DocumentWidget_(d, linkId, iTrue)) { |
4201 | if (linkFlags & content_GmLinkFlag) { | 5363 | if (linkFlags & content_GmLinkFlag) { |
4202 | /* Dismiss shown content on click. */ | 5364 | /* Dismiss shown content on click. */ |
4203 | setData_Media(media_GmDocument(d->doc), | 5365 | setData_Media(media_GmDocument(view->doc), |
4204 | linkId, | 5366 | linkId, |
4205 | NULL, | 5367 | NULL, |
4206 | NULL, | 5368 | NULL, |
@@ -4214,10 +5376,10 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4214 | be redone. */ | 5376 | be redone. */ |
4215 | } | 5377 | } |
4216 | } | 5378 | } |
4217 | redoLayout_GmDocument(d->doc); | 5379 | redoLayout_GmDocument(view->doc); |
4218 | d->hoverLink = NULL; | 5380 | view->hoverLink = NULL; |
4219 | clampScroll_DocumentWidget_(d); | 5381 | clampScroll_DocumentView_(view); |
4220 | updateVisible_DocumentWidget_(d); | 5382 | updateVisible_DocumentView_(view); |
4221 | invalidate_DocumentWidget_(d); | 5383 | invalidate_DocumentWidget_(d); |
4222 | refresh_Widget(w); | 5384 | refresh_Widget(w); |
4223 | return iTrue; | 5385 | return iTrue; |
@@ -4226,13 +5388,13 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4226 | /* Show the existing content again if we have it. */ | 5388 | /* Show the existing content again if we have it. */ |
4227 | iMediaRequest *req = findMediaRequest_DocumentWidget_(d, linkId); | 5389 | iMediaRequest *req = findMediaRequest_DocumentWidget_(d, linkId); |
4228 | if (req) { | 5390 | if (req) { |
4229 | setData_Media(media_GmDocument(d->doc), | 5391 | setData_Media(media_GmDocument(view->doc), |
4230 | linkId, | 5392 | linkId, |
4231 | meta_GmRequest(req->req), | 5393 | meta_GmRequest(req->req), |
4232 | body_GmRequest(req->req), | 5394 | body_GmRequest(req->req), |
4233 | allowHide_MediaFlag); | 5395 | allowHide_MediaFlag); |
4234 | redoLayout_GmDocument(d->doc); | 5396 | redoLayout_GmDocument(view->doc); |
4235 | updateVisible_DocumentWidget_(d); | 5397 | updateVisible_DocumentView_(view); |
4236 | invalidate_DocumentWidget_(d); | 5398 | invalidate_DocumentWidget_(d); |
4237 | refresh_Widget(w); | 5399 | refresh_Widget(w); |
4238 | return iTrue; | 5400 | return iTrue; |
@@ -4252,14 +5414,15 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4252 | if (isPinned_DocumentWidget_(d)) { | 5414 | if (isPinned_DocumentWidget_(d)) { |
4253 | tabMode ^= otherRoot_OpenTabFlag; | 5415 | tabMode ^= otherRoot_OpenTabFlag; |
4254 | } | 5416 | } |
5417 | interactingWithLink_DocumentWidget_(d, linkId); | ||
4255 | postCommandf_Root(w->root, "open newtab:%d url:%s", | 5418 | postCommandf_Root(w->root, "open newtab:%d url:%s", |
4256 | tabMode, | 5419 | tabMode, |
4257 | cstr_String(absoluteUrl_String( | 5420 | cstr_String(absoluteUrl_String( |
4258 | d->mod.url, linkUrl_GmDocument(d->doc, linkId)))); | 5421 | d->mod.url, linkUrl_GmDocument(view->doc, linkId)))); |
4259 | } | 5422 | } |
4260 | else { | 5423 | else { |
4261 | const iString *url = absoluteUrl_String( | 5424 | const iString *url = absoluteUrl_String( |
4262 | d->mod.url, linkUrl_GmDocument(d->doc, linkId)); | 5425 | d->mod.url, linkUrl_GmDocument(view->doc, linkId)); |
4263 | makeQuestion_Widget( | 5426 | makeQuestion_Widget( |
4264 | uiTextCaution_ColorEscape "${heading.openlink}", | 5427 | uiTextCaution_ColorEscape "${heading.openlink}", |
4265 | format_CStr( | 5428 | format_CStr( |
@@ -4292,705 +5455,18 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
4292 | return processEvent_Widget(w, ev); | 5455 | return processEvent_Widget(w, ev); |
4293 | } | 5456 | } |
4294 | 5457 | ||
4295 | iDeclareType(DrawContext) | 5458 | static void checkPendingInvalidation_DocumentWidget_(const iDocumentWidget *d) { |
4296 | 5459 | if (d->flags & invalidationPending_DocumentWidgetFlag && | |
4297 | struct Impl_DrawContext { | 5460 | !isAffectedByVisualOffset_Widget(constAs_Widget(d))) { |
4298 | const iDocumentWidget *widget; | 5461 | // printf("%p visoff: %d\n", d, left_Rect(bounds_Widget(w)) - left_Rect(boundsWithoutVisualOffset_Widget(w))); |
4299 | iRect widgetBounds; | 5462 | iDocumentWidget *m = (iDocumentWidget *) d; /* Hrrm, not const... */ |
4300 | iRect docBounds; | 5463 | m->flags &= ~invalidationPending_DocumentWidgetFlag; |
4301 | iRangei vis; | 5464 | invalidate_DocumentWidget_(m); |
4302 | iInt2 viewPos; /* document area origin */ | ||
4303 | iPaint paint; | ||
4304 | iBool inSelectMark; | ||
4305 | iBool inFoundMark; | ||
4306 | iBool showLinkNumbers; | ||
4307 | iRect firstMarkRect; | ||
4308 | iRect lastMarkRect; | ||
4309 | iGmRunRange runsDrawn; | ||
4310 | }; | ||
4311 | |||
4312 | static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color, | ||
4313 | iRangecc mark, iBool *isInside) { | ||
4314 | if (mark.start > mark.end) { | ||
4315 | /* Selection may be done in either direction. */ | ||
4316 | iSwap(const char *, mark.start, mark.end); | ||
4317 | } | ||
4318 | if (*isInside || (contains_Range(&run->text, mark.start) || | ||
4319 | contains_Range(&mark, run->text.start))) { | ||
4320 | int x = 0; | ||
4321 | if (!*isInside) { | ||
4322 | x = measureRange_Text(run->font, | ||
4323 | (iRangecc){ run->text.start, iMax(run->text.start, mark.start) }) | ||
4324 | .advance.x; | ||
4325 | } | ||
4326 | int w = width_Rect(run->visBounds) - x; | ||
4327 | if (contains_Range(&run->text, mark.end) || mark.end < run->text.start) { | ||
4328 | iRangecc mk = !*isInside ? mark | ||
4329 | : (iRangecc){ run->text.start, iMax(run->text.start, mark.end) }; | ||
4330 | mk.start = iMax(mk.start, run->text.start); | ||
4331 | w = measureRange_Text(run->font, mk).advance.x; | ||
4332 | *isInside = iFalse; | ||
4333 | } | ||
4334 | else { | ||
4335 | *isInside = iTrue; /* at least until the next run */ | ||
4336 | } | ||
4337 | if (w > width_Rect(run->visBounds) - x) { | ||
4338 | w = width_Rect(run->visBounds) - x; | ||
4339 | } | ||
4340 | if (~run->flags & decoration_GmRunFlag) { | ||
4341 | const iInt2 visPos = | ||
4342 | add_I2(run->bounds.pos, addY_I2(d->viewPos, viewPos_DocumentWidget_(d->widget))); | ||
4343 | const iRect rangeRect = { addX_I2(visPos, x), init_I2(w, height_Rect(run->bounds)) }; | ||
4344 | if (rangeRect.size.x) { | ||
4345 | fillRect_Paint(&d->paint, rangeRect, color); | ||
4346 | /* Keep track of the first and last marked rects. */ | ||
4347 | if (d->firstMarkRect.size.x == 0) { | ||
4348 | d->firstMarkRect = rangeRect; | ||
4349 | } | ||
4350 | d->lastMarkRect = rangeRect; | ||
4351 | } | ||
4352 | } | ||
4353 | } | ||
4354 | /* Link URLs are not part of the visible document, so they are ignored above. Handle | ||
4355 | these ranges as a special case. */ | ||
4356 | if (run->linkId && run->flags & decoration_GmRunFlag) { | ||
4357 | const iRangecc url = linkUrlRange_GmDocument(d->widget->doc, run->linkId); | ||
4358 | if (contains_Range(&url, mark.start) && | ||
4359 | (contains_Range(&url, mark.end) || url.end == mark.end)) { | ||
4360 | fillRect_Paint( | ||
4361 | &d->paint, | ||
4362 | moved_Rect(run->visBounds, addY_I2(d->viewPos, viewPos_DocumentWidget_(d->widget))), | ||
4363 | color); | ||
4364 | } | ||
4365 | } | ||
4366 | } | ||
4367 | |||
4368 | static void drawMark_DrawContext_(void *context, const iGmRun *run) { | ||
4369 | iDrawContext *d = context; | ||
4370 | if (!isMedia_GmRun(run)) { | ||
4371 | fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark); | ||
4372 | fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark); | ||
4373 | } | ||
4374 | } | ||
4375 | |||
4376 | static void drawRun_DrawContext_(void *context, const iGmRun *run) { | ||
4377 | iDrawContext *d = context; | ||
4378 | const iInt2 origin = d->viewPos; | ||
4379 | /* Keep track of the drawn visible runs. */ { | ||
4380 | if (!d->runsDrawn.start || run < d->runsDrawn.start) { | ||
4381 | d->runsDrawn.start = run; | ||
4382 | } | ||
4383 | if (!d->runsDrawn.end || run > d->runsDrawn.end) { | ||
4384 | d->runsDrawn.end = run; | ||
4385 | } | ||
4386 | } | ||
4387 | if (run->mediaType == image_MediaType) { | ||
4388 | SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), mediaId_GmRun(run)); | ||
4389 | const iRect dst = moved_Rect(run->visBounds, origin); | ||
4390 | if (tex) { | ||
4391 | fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */ | ||
4392 | SDL_RenderCopy(d->paint.dst->render, tex, NULL, | ||
4393 | &(SDL_Rect){ dst.pos.x, dst.pos.y, dst.size.x, dst.size.y }); | ||
4394 | } | ||
4395 | else { | ||
4396 | drawRect_Paint(&d->paint, dst, tmQuoteIcon_ColorId); | ||
4397 | drawCentered_Text(uiLabel_FontId, | ||
4398 | dst, | ||
4399 | iFalse, | ||
4400 | tmQuote_ColorId, | ||
4401 | explosion_Icon " Error Loading Image"); | ||
4402 | } | ||
4403 | return; | ||
4404 | } | ||
4405 | else if (isMedia_GmRun(run)) { | ||
4406 | /* Media UIs are drawn afterwards as a dynamic overlay. */ | ||
4407 | return; | ||
4408 | } | ||
4409 | enum iColorId fg = run->color; | ||
4410 | const iGmDocument *doc = d->widget->doc; | ||
4411 | const int linkFlags = linkFlags_GmDocument(doc, run->linkId); | ||
4412 | /* Hover state of a link. */ | ||
4413 | iBool isHover = | ||
4414 | (run->linkId && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId && | ||
4415 | ~run->flags & decoration_GmRunFlag); | ||
4416 | /* Visible (scrolled) position of the run. */ | ||
4417 | const iInt2 visPos = addX_I2(add_I2(run->visBounds.pos, origin), | ||
4418 | /* Preformatted runs can be scrolled. */ | ||
4419 | runOffset_DocumentWidget_(d->widget, run)); | ||
4420 | const iRect visRect = { visPos, run->visBounds.size }; | ||
4421 | #if 0 | ||
4422 | if (run->flags & footer_GmRunFlag) { | ||
4423 | iRect footerBack = | ||
4424 | (iRect){ visPos, init_I2(width_Rect(d->widgetBounds), run->visBounds.size.y) }; | ||
4425 | footerBack.pos.x = left_Rect(d->widgetBounds); | ||
4426 | fillRect_Paint(&d->paint, footerBack, tmBackground_ColorId); | ||
4427 | return; | ||
4428 | } | ||
4429 | #endif | ||
4430 | /* Fill the background. */ { | ||
4431 | if (run->linkId && linkFlags & isOpen_GmLinkFlag && ~linkFlags & content_GmLinkFlag) { | ||
4432 | /* Open links get a highlighted background. */ | ||
4433 | int bg = tmBackgroundOpenLink_ColorId; | ||
4434 | const int frame = tmFrameOpenLink_ColorId; | ||
4435 | iRect wideRect = { init_I2(left_Rect(d->widgetBounds), visPos.y), | ||
4436 | init_I2(width_Rect(d->widgetBounds) + | ||
4437 | width_Widget(d->widget->scroll), | ||
4438 | height_Rect(run->visBounds)) }; | ||
4439 | /* The first line is composed of two runs that may be drawn in either order, so | ||
4440 | only draw half of the background. */ | ||
4441 | if (run->flags & decoration_GmRunFlag) { | ||
4442 | wideRect.size.x = right_Rect(visRect) - left_Rect(wideRect); | ||
4443 | } | ||
4444 | else if (run->flags & startOfLine_GmRunFlag) { | ||
4445 | wideRect.size.x = right_Rect(wideRect) - left_Rect(visRect); | ||
4446 | wideRect.pos.x = left_Rect(visRect); | ||
4447 | } | ||
4448 | fillRect_Paint(&d->paint, wideRect, bg); | ||
4449 | if (run->flags & (startOfLine_GmRunFlag | decoration_GmRunFlag)) { | ||
4450 | drawHLine_Paint(&d->paint, topLeft_Rect(wideRect), width_Rect(wideRect), frame); | ||
4451 | } | ||
4452 | /* TODO: The decoration is not marked as endOfLine, so it lacks the bottom line. */ | ||
4453 | // if (run->flags & endOfLine_GmRunFlag) { | ||
4454 | // drawHLine_Paint( | ||
4455 | // &d->paint, addY_I2(bottomLeft_Rect(wideRect), -1), width_Rect(wideRect), frame); | ||
4456 | // } | ||
4457 | } | ||
4458 | else { | ||
4459 | /* Normal background for other runs. There are cases when runs get drawn multiple times, | ||
4460 | e.g., at the buffer boundary, and there are slightly overlapping characters in | ||
4461 | monospace blocks. Clearing the background here ensures a cleaner visual appearance | ||
4462 | since only one glyph is visible at any given point. */ | ||
4463 | fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackground_ColorId); | ||
4464 | } | ||
4465 | } | ||
4466 | if (run->linkId && ~run->flags & decoration_GmRunFlag) { | ||
4467 | fg = linkColor_GmDocument(doc, run->linkId, isHover ? textHover_GmLinkPart : text_GmLinkPart); | ||
4468 | if (linkFlags & content_GmLinkFlag) { | ||
4469 | fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); /* link is inactive */ | ||
4470 | } | ||
4471 | } | ||
4472 | if (run->flags & altText_GmRunFlag) { | ||
4473 | const iInt2 margin = preRunMargin_GmDocument(doc, preId_GmRun(run)); | ||
4474 | fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId); | ||
4475 | drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmFrameAltText_ColorId); | ||
4476 | drawWrapRange_Text(run->font, | ||
4477 | add_I2(visPos, margin), | ||
4478 | run->visBounds.size.x - 2 * margin.x, | ||
4479 | run->color, | ||
4480 | run->text); | ||
4481 | } | ||
4482 | #if 0 | ||
4483 | else if (run->flags & siteBanner_GmRunFlag) { | ||
4484 | /* Banner background. */ | ||
4485 | iRect bannerBack = initCorners_Rect(topLeft_Rect(d->widgetBounds), | ||
4486 | init_I2(right_Rect(bounds_Widget(constAs_Widget(d->widget))), | ||
4487 | visPos.y + height_Rect(run->visBounds))); | ||
4488 | fillRect_Paint(&d->paint, bannerBack, tmBannerBackground_ColorId); | ||
4489 | drawBannerRun_DrawContext_(d, run, visPos); | ||
4490 | } | ||
4491 | #endif | ||
4492 | else { | ||
4493 | if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { | ||
4494 | const size_t ord = visibleLinkOrdinal_DocumentWidget_(d->widget, run->linkId); | ||
4495 | if (ord >= d->widget->ordinalBase) { | ||
4496 | const iChar ordChar = | ||
4497 | linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase); | ||
4498 | if (ordChar) { | ||
4499 | const char *circle = "\u25ef"; /* Large Circle */ | ||
4500 | const int circleFont = FONT_ID(default_FontId, regular_FontStyle, contentRegular_FontSize); | ||
4501 | iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y), | ||
4502 | init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(circleFont)) }; | ||
4503 | drawRange_Text( | ||
4504 | circleFont, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle)); | ||
4505 | iRect circleArea = visualBounds_Text(circleFont, range_CStr(circle)); | ||
4506 | addv_I2(&circleArea.pos, topLeft_Rect(nbArea)); | ||
4507 | drawCentered_Text(FONT_ID(default_FontId, regular_FontStyle, contentSmall_FontSize), | ||
4508 | circleArea, | ||
4509 | iTrue, | ||
4510 | tmQuote_ColorId, | ||
4511 | "%lc", | ||
4512 | (int) ordChar); | ||
4513 | goto runDrawn; | ||
4514 | } | ||
4515 | } | ||
4516 | } | ||
4517 | if (run->flags & quoteBorder_GmRunFlag) { | ||
4518 | drawVLine_Paint(&d->paint, | ||
4519 | addX_I2(visPos, | ||
4520 | !run->isRTL | ||
4521 | ? -gap_Text * 5 / 2 | ||
4522 | : (width_Rect(run->visBounds) + gap_Text * 5 / 2)), | ||
4523 | height_Rect(run->visBounds), | ||
4524 | tmQuoteIcon_ColorId); | ||
4525 | } | ||
4526 | /* Base attributes. */ { | ||
4527 | int f, c; | ||
4528 | runBaseAttributes_GmDocument(doc, run, &f, &c); | ||
4529 | setBaseAttributes_Text(f, c); | ||
4530 | } | ||
4531 | drawBoundRange_Text(run->font, | ||
4532 | visPos, | ||
4533 | (run->isRTL ? -1 : 1) * width_Rect(run->visBounds), | ||
4534 | fg, | ||
4535 | run->text); | ||
4536 | setBaseAttributes_Text(-1, -1); | ||
4537 | runDrawn:; | ||
4538 | } | ||
4539 | /* Presentation of links. */ | ||
4540 | if (run->linkId && ~run->flags & decoration_GmRunFlag) { | ||
4541 | const int metaFont = paragraph_FontId; | ||
4542 | /* TODO: Show status of an ongoing media request. */ | ||
4543 | const int flags = linkFlags; | ||
4544 | const iRect linkRect = moved_Rect(run->visBounds, origin); | ||
4545 | iMediaRequest *mr = NULL; | ||
4546 | /* Show metadata about inline content. */ | ||
4547 | if (flags & content_GmLinkFlag && run->flags & endOfLine_GmRunFlag) { | ||
4548 | fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); | ||
4549 | iString text; | ||
4550 | init_String(&text); | ||
4551 | const iMediaId linkMedia = findMediaForLink_Media(constMedia_GmDocument(doc), | ||
4552 | run->linkId, none_MediaType); | ||
4553 | iAssert(linkMedia.type != none_MediaType); | ||
4554 | iGmMediaInfo info; | ||
4555 | info_Media(constMedia_GmDocument(doc), linkMedia, &info); | ||
4556 | switch (linkMedia.type) { | ||
4557 | case image_MediaType: { | ||
4558 | iAssert(!isEmpty_Rect(run->bounds)); | ||
4559 | const iInt2 imgSize = imageSize_Media(constMedia_GmDocument(doc), linkMedia); | ||
4560 | format_String(&text, "%s \u2014 %d x %d \u2014 %.1f%s", | ||
4561 | info.type, imgSize.x, imgSize.y, info.numBytes / 1.0e6f, | ||
4562 | cstr_Lang("mb")); | ||
4563 | break; | ||
4564 | } | ||
4565 | case audio_MediaType: | ||
4566 | format_String(&text, "%s", info.type); | ||
4567 | break; | ||
4568 | case download_MediaType: | ||
4569 | format_String(&text, "%s", info.type); | ||
4570 | break; | ||
4571 | default: | ||
4572 | break; | ||
4573 | } | ||
4574 | if (linkMedia.type != download_MediaType && /* can't cancel downloads currently */ | ||
4575 | findMediaRequest_DocumentWidget_(d->widget, run->linkId)) { | ||
4576 | appendFormat_String( | ||
4577 | &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : ""); | ||
4578 | } | ||
4579 | const iInt2 size = measureRange_Text(metaFont, range_String(&text)).bounds.size; | ||
4580 | fillRect_Paint( | ||
4581 | &d->paint, | ||
4582 | (iRect){ add_I2(origin, addX_I2(topRight_Rect(run->bounds), -size.x - gap_UI)), | ||
4583 | addX_I2(size, 2 * gap_UI) }, | ||
4584 | tmBackground_ColorId); | ||
4585 | drawAlign_Text(metaFont, | ||
4586 | add_I2(topRight_Rect(run->bounds), origin), | ||
4587 | fg, | ||
4588 | right_Alignment, | ||
4589 | "%s", cstr_String(&text)); | ||
4590 | deinit_String(&text); | ||
4591 | } | ||
4592 | else if (run->flags & endOfLine_GmRunFlag && | ||
4593 | (mr = findMediaRequest_DocumentWidget_(d->widget, run->linkId)) != NULL) { | ||
4594 | if (!isFinished_GmRequest(mr->req)) { | ||
4595 | draw_Text(metaFont, | ||
4596 | topRight_Rect(linkRect), | ||
4597 | tmInlineContentMetadata_ColorId, | ||
4598 | translateCStr_Lang(" \u2014 ${doc.fetching}\u2026 (%.1f ${mb})"), | ||
4599 | (float) bodySize_GmRequest(mr->req) / 1.0e6f); | ||
4600 | } | ||
4601 | } | ||
4602 | else if (isHover) { | ||
4603 | const iGmLinkId linkId = d->widget->hoverLink->linkId; | ||
4604 | const iString * url = linkUrl_GmDocument(doc, linkId); | ||
4605 | const int flags = linkFlags; | ||
4606 | iUrl parts; | ||
4607 | init_Url(&parts, url); | ||
4608 | fg = linkColor_GmDocument(doc, linkId, textHover_GmLinkPart); | ||
4609 | const enum iGmLinkScheme scheme = scheme_GmLinkFlag(flags); | ||
4610 | const iBool showHost = (flags & humanReadable_GmLinkFlag && | ||
4611 | (!isEmpty_Range(&parts.host) || | ||
4612 | scheme == mailto_GmLinkScheme)); | ||
4613 | const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0; | ||
4614 | const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0; | ||
4615 | iString str; | ||
4616 | init_String(&str); | ||
4617 | /* Show scheme and host. */ | ||
4618 | if (run->flags & endOfLine_GmRunFlag && | ||
4619 | (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || | ||
4620 | showHost)) { | ||
4621 | format_String( | ||
4622 | &str, | ||
4623 | "%s%s%s%s%s", | ||
4624 | showHost ? "" : "", | ||
4625 | showHost | ||
4626 | ? (scheme == mailto_GmLinkScheme ? cstr_String(url) | ||
4627 | : scheme != gemini_GmLinkScheme ? format_CStr("%s://%s", | ||
4628 | cstr_Rangecc(parts.scheme), | ||
4629 | cstr_Rangecc(parts.host)) | ||
4630 | : cstr_Rangecc(parts.host)) | ||
4631 | : "", | ||
4632 | showHost && (showImage || showAudio) ? " \u2014" : "", | ||
4633 | showImage || showAudio | ||
4634 | ? escape_Color(fg) | ||
4635 | : escape_Color(linkColor_GmDocument(doc, run->linkId, domain_GmLinkPart)), | ||
4636 | showImage || showAudio | ||
4637 | ? format_CStr(showImage ? " %s " photo_Icon : " %s \U0001f3b5", | ||
4638 | cstr_Lang(showImage ? "link.hint.image" : "link.hint.audio")) | ||
4639 | : ""); | ||
4640 | } | ||
4641 | if (run->flags & endOfLine_GmRunFlag && flags & visited_GmLinkFlag) { | ||
4642 | iDate date; | ||
4643 | init_Date(&date, linkTime_GmDocument(doc, run->linkId)); | ||
4644 | appendCStr_String(&str, " \u2014 "); | ||
4645 | appendCStr_String( | ||
4646 | &str, escape_Color(linkColor_GmDocument(doc, run->linkId, visited_GmLinkPart))); | ||
4647 | iString *dateStr = format_Date(&date, "%b %d"); | ||
4648 | append_String(&str, dateStr); | ||
4649 | delete_String(dateStr); | ||
4650 | } | ||
4651 | if (!isEmpty_String(&str)) { | ||
4652 | if (run->isRTL) { | ||
4653 | appendCStr_String(&str, " \u2014 "); | ||
4654 | } | ||
4655 | else { | ||
4656 | prependCStr_String(&str, " \u2014 "); | ||
4657 | } | ||
4658 | const iInt2 textSize = measure_Text(metaFont, cstr_String(&str)).bounds.size; | ||
4659 | int tx = topRight_Rect(linkRect).x; | ||
4660 | const char *msg = cstr_String(&str); | ||
4661 | if (run->isRTL) { | ||
4662 | tx = topLeft_Rect(linkRect).x - textSize.x; | ||
4663 | } | ||
4664 | if (tx + textSize.x > right_Rect(d->widgetBounds)) { | ||
4665 | tx = right_Rect(d->widgetBounds) - textSize.x; | ||
4666 | fillRect_Paint(&d->paint, (iRect){ init_I2(tx, top_Rect(linkRect)), textSize }, | ||
4667 | uiBackground_ColorId); | ||
4668 | msg += 4; /* skip the space and dash */ | ||
4669 | tx += measure_Text(metaFont, " \u2014").advance.x / 2; | ||
4670 | } | ||
4671 | drawAlign_Text(metaFont, | ||
4672 | init_I2(tx, top_Rect(linkRect)), | ||
4673 | linkColor_GmDocument(doc, run->linkId, domain_GmLinkPart), | ||
4674 | left_Alignment, | ||
4675 | "%s", | ||
4676 | msg); | ||
4677 | deinit_String(&str); | ||
4678 | } | ||
4679 | } | ||
4680 | } | ||
4681 | if (0) { | ||
4682 | drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); | ||
4683 | drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); | ||
4684 | } | ||
4685 | } | ||
4686 | |||
4687 | static int drawSideRect_(iPaint *p, iRect rect) { | ||
4688 | int bg = tmBannerBackground_ColorId; | ||
4689 | int fg = tmBannerIcon_ColorId; | ||
4690 | if (equal_Color(get_Color(bg), get_Color(tmBackground_ColorId))) { | ||
4691 | bg = tmBannerIcon_ColorId; | ||
4692 | fg = tmBannerBackground_ColorId; | ||
4693 | } | ||
4694 | fillRect_Paint(p, rect, bg); | ||
4695 | return fg; | ||
4696 | } | ||
4697 | |||
4698 | static int sideElementAvailWidth_DocumentWidget_(const iDocumentWidget *d) { | ||
4699 | return left_Rect(documentBounds_DocumentWidget_(d)) - | ||
4700 | left_Rect(bounds_Widget(constAs_Widget(d))) - 2 * d->pageMargin * gap_UI; | ||
4701 | } | ||
4702 | |||
4703 | static iBool isSideHeadingVisible_DocumentWidget_(const iDocumentWidget *d) { | ||
4704 | return sideElementAvailWidth_DocumentWidget_(d) >= lineHeight_Text(banner_FontId) * 4.5f; | ||
4705 | } | ||
4706 | |||
4707 | static void updateSideIconBuf_DocumentWidget_(const iDocumentWidget *d) { | ||
4708 | if (!isExposed_Window(get_Window())) { | ||
4709 | return; | ||
4710 | } | ||
4711 | iDrawBufs *dbuf = d->drawBufs; | ||
4712 | dbuf->flags &= ~updateSideBuf_DrawBufsFlag; | ||
4713 | if (dbuf->sideIconBuf) { | ||
4714 | SDL_DestroyTexture(dbuf->sideIconBuf); | ||
4715 | dbuf->sideIconBuf = NULL; | ||
4716 | } | ||
4717 | // const iGmRun *banner = siteBanner_GmDocument(d->doc); | ||
4718 | if (isEmpty_Banner(d->banner)) { | ||
4719 | return; | ||
4720 | } | ||
4721 | const int margin = gap_UI * d->pageMargin; | ||
4722 | const int minBannerSize = lineHeight_Text(banner_FontId) * 2; | ||
4723 | const iChar icon = siteIcon_GmDocument(d->doc); | ||
4724 | const int avail = sideElementAvailWidth_DocumentWidget_(d) - margin; | ||
4725 | iBool isHeadingVisible = isSideHeadingVisible_DocumentWidget_(d); | ||
4726 | /* Determine the required size. */ | ||
4727 | iInt2 bufSize = init1_I2(minBannerSize); | ||
4728 | const int sideHeadingFont = FONT_ID(documentHeading_FontId, regular_FontStyle, contentBig_FontSize); | ||
4729 | if (isHeadingVisible) { | ||
4730 | const iInt2 headingSize = measureWrapRange_Text(sideHeadingFont, avail, | ||
4731 | currentHeading_DocumentWidget_(d)).bounds.size; | ||
4732 | if (headingSize.x > 0) { | ||
4733 | bufSize.y += gap_Text + headingSize.y; | ||
4734 | bufSize.x = iMax(bufSize.x, headingSize.x); | ||
4735 | } | ||
4736 | else { | ||
4737 | isHeadingVisible = iFalse; | ||
4738 | } | ||
4739 | } | ||
4740 | SDL_Renderer *render = renderer_Window(get_Window()); | ||
4741 | dbuf->sideIconBuf = SDL_CreateTexture(render, | ||
4742 | SDL_PIXELFORMAT_RGBA4444, | ||
4743 | SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, | ||
4744 | bufSize.x, bufSize.y); | ||
4745 | iPaint p; | ||
4746 | init_Paint(&p); | ||
4747 | beginTarget_Paint(&p, dbuf->sideIconBuf); | ||
4748 | const iColor back = get_Color(tmBannerSideTitle_ColorId); | ||
4749 | SDL_SetRenderDrawColor(render, back.r, back.g, back.b, 0); /* better blending of the edge */ | ||
4750 | SDL_RenderClear(render); | ||
4751 | const iRect iconRect = { zero_I2(), init1_I2(minBannerSize) }; | ||
4752 | int fg = drawSideRect_(&p, iconRect); | ||
4753 | iString str; | ||
4754 | initUnicodeN_String(&str, &icon, 1); | ||
4755 | drawCentered_Text(banner_FontId, iconRect, iTrue, fg, "%s", cstr_String(&str)); | ||
4756 | deinit_String(&str); | ||
4757 | if (isHeadingVisible) { | ||
4758 | iRangecc text = currentHeading_DocumentWidget_(d); | ||
4759 | iInt2 pos = addY_I2(bottomLeft_Rect(iconRect), gap_Text); | ||
4760 | const int font = sideHeadingFont; | ||
4761 | drawWrapRange_Text(font, pos, avail, tmBannerSideTitle_ColorId, text); | ||
4762 | } | ||
4763 | endTarget_Paint(&p); | ||
4764 | SDL_SetTextureBlendMode(dbuf->sideIconBuf, SDL_BLENDMODE_BLEND); | ||
4765 | } | ||
4766 | |||
4767 | static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) { | ||
4768 | const iWidget *w = constAs_Widget(d); | ||
4769 | const iRect bounds = bounds_Widget(w); | ||
4770 | const iRect docBounds = documentBounds_DocumentWidget_(d); | ||
4771 | const int margin = gap_UI * d->pageMargin; | ||
4772 | float opacity = value_Anim(&d->sideOpacity); | ||
4773 | const int avail = left_Rect(docBounds) - left_Rect(bounds) - 2 * margin; | ||
4774 | iDrawBufs * dbuf = d->drawBufs; | ||
4775 | iPaint p; | ||
4776 | init_Paint(&p); | ||
4777 | setClip_Paint(&p, boundsWithoutVisualOffset_Widget(w)); | ||
4778 | /* Side icon and current heading. */ | ||
4779 | if (prefs_App()->sideIcon && opacity > 0 && dbuf->sideIconBuf) { | ||
4780 | const iInt2 texSize = size_SDLTexture(dbuf->sideIconBuf); | ||
4781 | if (avail > texSize.x) { | ||
4782 | const int minBannerSize = lineHeight_Text(banner_FontId) * 2; | ||
4783 | iInt2 pos = addY_I2(add_I2(topLeft_Rect(bounds), init_I2(margin, 0)), | ||
4784 | height_Rect(bounds) / 2 - minBannerSize / 2 - | ||
4785 | (texSize.y > minBannerSize | ||
4786 | ? (gap_Text + lineHeight_Text(heading3_FontId)) / 2 | ||
4787 | : 0)); | ||
4788 | SDL_SetTextureAlphaMod(dbuf->sideIconBuf, 255 * opacity); | ||
4789 | SDL_RenderCopy(renderer_Window(get_Window()), | ||
4790 | dbuf->sideIconBuf, NULL, | ||
4791 | &(SDL_Rect){ pos.x, pos.y, texSize.x, texSize.y }); | ||
4792 | } | ||
4793 | } | ||
4794 | /* Reception timestamp. */ | ||
4795 | if (dbuf->timestampBuf && dbuf->timestampBuf->size.x <= avail) { | ||
4796 | draw_TextBuf( | ||
4797 | dbuf->timestampBuf, | ||
4798 | add_I2( | ||
4799 | bottomLeft_Rect(bounds), | ||
4800 | init_I2(margin, | ||
4801 | -margin + -dbuf->timestampBuf->size.y + | ||
4802 | iMax(0, d->scrollY.max - pos_SmoothScroll(&d->scrollY)))), | ||
4803 | tmQuoteIcon_ColorId); | ||
4804 | } | ||
4805 | unsetClip_Paint(&p); | ||
4806 | } | ||
4807 | |||
4808 | static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { | ||
4809 | iConstForEach(PtrArray, i, &d->visibleMedia) { | ||
4810 | const iGmRun * run = i.ptr; | ||
4811 | if (run->mediaType == audio_MediaType) { | ||
4812 | iPlayerUI ui; | ||
4813 | init_PlayerUI(&ui, | ||
4814 | audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)), | ||
4815 | runRect_DocumentWidget_(d, run)); | ||
4816 | draw_PlayerUI(&ui, p); | ||
4817 | } | ||
4818 | else if (run->mediaType == download_MediaType) { | ||
4819 | iDownloadUI ui; | ||
4820 | init_DownloadUI(&ui, constMedia_GmDocument(d->doc), run->mediaId, | ||
4821 | runRect_DocumentWidget_(d, run)); | ||
4822 | draw_DownloadUI(&ui, p); | ||
4823 | } | ||
4824 | } | ||
4825 | } | ||
4826 | |||
4827 | static void extend_GmRunRange_(iGmRunRange *runs) { | ||
4828 | if (runs->start) { | ||
4829 | runs->start--; | ||
4830 | runs->end++; | ||
4831 | } | 5465 | } |
4832 | } | 5466 | } |
4833 | 5467 | ||
4834 | static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx, iBool prerenderExtra) { | ||
4835 | iBool didDraw = iFalse; | ||
4836 | const iRect bounds = bounds_Widget(constAs_Widget(d)); | ||
4837 | const iRect ctxWidgetBounds = init_Rect( | ||
4838 | 0, 0, width_Rect(bounds) - constAs_Widget(d->scroll)->rect.size.x, height_Rect(bounds)); | ||
4839 | const iRangei full = { 0, size_GmDocument(d->doc).y }; | ||
4840 | const iRangei vis = ctx->vis; | ||
4841 | iVisBuf *visBuf = d->visBuf; /* will be updated now */ | ||
4842 | d->drawBufs->lastRenderTime = SDL_GetTicks(); | ||
4843 | /* Swap buffers around to have room available both before and after the visible region. */ | ||
4844 | allocVisBuffer_DocumentWidget_(d); | ||
4845 | reposition_VisBuf(visBuf, vis); | ||
4846 | /* Redraw the invalid ranges. */ | ||
4847 | if (~flags_Widget(constAs_Widget(d)) & destroyPending_WidgetFlag) { | ||
4848 | iPaint *p = &ctx->paint; | ||
4849 | init_Paint(p); | ||
4850 | iForIndices(i, visBuf->buffers) { | ||
4851 | iVisBufTexture *buf = &visBuf->buffers[i]; | ||
4852 | iVisBufMeta *meta = buf->user; | ||
4853 | const iRangei bufRange = intersect_Rangei(bufferRange_VisBuf(visBuf, i), full); | ||
4854 | const iRangei bufVisRange = intersect_Rangei(bufRange, vis); | ||
4855 | ctx->widgetBounds = moved_Rect(ctxWidgetBounds, init_I2(0, -buf->origin)); | ||
4856 | ctx->viewPos = init_I2(left_Rect(ctx->docBounds) - left_Rect(bounds), -buf->origin); | ||
4857 | // printf(" buffer %zu: buf vis range %d...%d\n", i, bufVisRange.start, bufVisRange.end); | ||
4858 | if (!prerenderExtra && !isEmpty_Range(&bufVisRange)) { | ||
4859 | didDraw = iTrue; | ||
4860 | if (isEmpty_Rangei(buf->validRange)) { | ||
4861 | /* Fill the required currently visible range (vis). */ | ||
4862 | const iRangei bufVisRange = intersect_Rangei(bufRange, vis); | ||
4863 | if (!isEmpty_Range(&bufVisRange)) { | ||
4864 | beginTarget_Paint(p, buf->texture); | ||
4865 | fillRect_Paint(p, (iRect){ zero_I2(), visBuf->texSize }, tmBackground_ColorId); | ||
4866 | iZap(ctx->runsDrawn); | ||
4867 | render_GmDocument(d->doc, bufVisRange, drawRun_DrawContext_, ctx); | ||
4868 | meta->runsDrawn = ctx->runsDrawn; | ||
4869 | extend_GmRunRange_(&meta->runsDrawn); | ||
4870 | buf->validRange = bufVisRange; | ||
4871 | // printf(" buffer %zu valid %d...%d\n", i, bufRange.start, bufRange.end); | ||
4872 | } | ||
4873 | } | ||
4874 | else { | ||
4875 | /* Progressively fill the required runs. */ | ||
4876 | if (meta->runsDrawn.start) { | ||
4877 | beginTarget_Paint(p, buf->texture); | ||
4878 | meta->runsDrawn.start = renderProgressive_GmDocument(d->doc, meta->runsDrawn.start, | ||
4879 | -1, iInvalidSize, | ||
4880 | bufVisRange, | ||
4881 | drawRun_DrawContext_, | ||
4882 | ctx); | ||
4883 | buf->validRange.start = bufVisRange.start; | ||
4884 | } | ||
4885 | if (meta->runsDrawn.end) { | ||
4886 | beginTarget_Paint(p, buf->texture); | ||
4887 | meta->runsDrawn.end = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end, | ||
4888 | +1, iInvalidSize, | ||
4889 | bufVisRange, | ||
4890 | drawRun_DrawContext_, | ||
4891 | ctx); | ||
4892 | buf->validRange.end = bufVisRange.end; | ||
4893 | } | ||
4894 | } | ||
4895 | } | ||
4896 | /* Progressively draw the rest of the buffer if it isn't fully valid. */ | ||
4897 | if (prerenderExtra && !equal_Rangei(bufRange, buf->validRange)) { | ||
4898 | const iGmRun *next; | ||
4899 | // printf("%zu: prerenderExtra (start:%p end:%p)\n", i, meta->runsDrawn.start, meta->runsDrawn.end); | ||
4900 | if (meta->runsDrawn.start == NULL) { | ||
4901 | /* Haven't drawn anything yet in this buffer, so let's try seeding it. */ | ||
4902 | const int rh = lineHeight_Text(paragraph_FontId); | ||
4903 | const int y = i >= iElemCount(visBuf->buffers) / 2 ? bufRange.start : (bufRange.end - rh); | ||
4904 | beginTarget_Paint(p, buf->texture); | ||
4905 | fillRect_Paint(p, (iRect){ zero_I2(), visBuf->texSize }, tmBackground_ColorId); | ||
4906 | buf->validRange = (iRangei){ y, y + rh }; | ||
4907 | iZap(ctx->runsDrawn); | ||
4908 | render_GmDocument(d->doc, buf->validRange, drawRun_DrawContext_, ctx); | ||
4909 | meta->runsDrawn = ctx->runsDrawn; | ||
4910 | extend_GmRunRange_(&meta->runsDrawn); | ||
4911 | // printf("%zu: seeded, next %p:%p\n", i, meta->runsDrawn.start, meta->runsDrawn.end); | ||
4912 | didDraw = iTrue; | ||
4913 | } | ||
4914 | else { | ||
4915 | if (meta->runsDrawn.start) { | ||
4916 | const iRangei upper = intersect_Rangei(bufRange, (iRangei){ full.start, buf->validRange.start }); | ||
4917 | if (upper.end > upper.start) { | ||
4918 | beginTarget_Paint(p, buf->texture); | ||
4919 | next = renderProgressive_GmDocument(d->doc, meta->runsDrawn.start, | ||
4920 | -1, 1, upper, | ||
4921 | drawRun_DrawContext_, | ||
4922 | ctx); | ||
4923 | if (next && meta->runsDrawn.start != next) { | ||
4924 | meta->runsDrawn.start = next; | ||
4925 | buf->validRange.start = bottom_Rect(next->visBounds); | ||
4926 | didDraw = iTrue; | ||
4927 | } | ||
4928 | else { | ||
4929 | buf->validRange.start = bufRange.start; | ||
4930 | } | ||
4931 | } | ||
4932 | } | ||
4933 | if (!didDraw && meta->runsDrawn.end) { | ||
4934 | const iRangei lower = intersect_Rangei(bufRange, (iRangei){ buf->validRange.end, full.end }); | ||
4935 | if (lower.end > lower.start) { | ||
4936 | beginTarget_Paint(p, buf->texture); | ||
4937 | next = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end, | ||
4938 | +1, 1, lower, | ||
4939 | drawRun_DrawContext_, | ||
4940 | ctx); | ||
4941 | if (next && meta->runsDrawn.end != next) { | ||
4942 | meta->runsDrawn.end = next; | ||
4943 | buf->validRange.end = top_Rect(next->visBounds); | ||
4944 | didDraw = iTrue; | ||
4945 | } | ||
4946 | else { | ||
4947 | buf->validRange.end = bufRange.end; | ||
4948 | } | ||
4949 | } | ||
4950 | } | ||
4951 | } | ||
4952 | } | ||
4953 | /* Draw any invalidated runs that fall within this buffer. */ | ||
4954 | if (!prerenderExtra) { | ||
4955 | const iRangei bufRange = { buf->origin, buf->origin + visBuf->texSize.y }; | ||
4956 | /* Clear full-width backgrounds first in case there are any dynamic elements. */ { | ||
4957 | iConstForEach(PtrSet, r, d->invalidRuns) { | ||
4958 | const iGmRun *run = *r.value; | ||
4959 | if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { | ||
4960 | beginTarget_Paint(p, buf->texture); | ||
4961 | fillRect_Paint(p, | ||
4962 | init_Rect(0, | ||
4963 | run->visBounds.pos.y - buf->origin, | ||
4964 | visBuf->texSize.x, | ||
4965 | run->visBounds.size.y), | ||
4966 | tmBackground_ColorId); | ||
4967 | } | ||
4968 | } | ||
4969 | } | ||
4970 | setAnsiFlags_Text(ansiEscapes_GmDocument(d->doc)); | ||
4971 | iConstForEach(PtrSet, r, d->invalidRuns) { | ||
4972 | const iGmRun *run = *r.value; | ||
4973 | if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { | ||
4974 | beginTarget_Paint(p, buf->texture); | ||
4975 | drawRun_DrawContext_(ctx, run); | ||
4976 | } | ||
4977 | } | ||
4978 | setAnsiFlags_Text(allowAll_AnsiFlag); | ||
4979 | } | ||
4980 | endTarget_Paint(p); | ||
4981 | if (prerenderExtra && didDraw) { | ||
4982 | /* Just a run at a time. */ | ||
4983 | break; | ||
4984 | } | ||
4985 | } | ||
4986 | if (!prerenderExtra) { | ||
4987 | clear_PtrSet(d->invalidRuns); | ||
4988 | } | ||
4989 | } | ||
4990 | return didDraw; | ||
4991 | } | ||
4992 | |||
4993 | static void prerender_DocumentWidget_(iAny *context) { | 5468 | static void prerender_DocumentWidget_(iAny *context) { |
5469 | iAssert(isInstance_Object(context, &Class_DocumentWidget)); | ||
4994 | if (current_Root() == NULL) { | 5470 | if (current_Root() == NULL) { |
4995 | /* The widget has probably been removed from the widget tree, pending destruction. | 5471 | /* The widget has probably been removed from the widget tree, pending destruction. |
4996 | Tickers are not cancelled until the widget is actually destroyed. */ | 5472 | Tickers are not cancelled until the widget is actually destroyed. */ |
@@ -4998,15 +5474,16 @@ static void prerender_DocumentWidget_(iAny *context) { | |||
4998 | } | 5474 | } |
4999 | const iDocumentWidget *d = context; | 5475 | const iDocumentWidget *d = context; |
5000 | iDrawContext ctx = { | 5476 | iDrawContext ctx = { |
5001 | .widget = d, | 5477 | .view = &d->view, |
5002 | .docBounds = documentBounds_DocumentWidget_(d), | 5478 | .docBounds = documentBounds_DocumentView_(&d->view), |
5003 | .vis = visibleRange_DocumentWidget_(d), | 5479 | .vis = visibleRange_DocumentView_(&d->view), |
5004 | .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0 | 5480 | .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0 |
5005 | }; | 5481 | }; |
5006 | // printf("%u prerendering\n", SDL_GetTicks()); | 5482 | // printf("%u prerendering\n", SDL_GetTicks()); |
5007 | if (d->visBuf->buffers[0].texture) { | 5483 | if (d->view.visBuf->buffers[0].texture) { |
5008 | if (render_DocumentWidget_(d, &ctx, iTrue /* just fill up progressively */)) { | 5484 | makePaletteGlobal_GmDocument(d->view.doc); |
5009 | /* Something was drawn, should check if there is still more to do. */ | 5485 | if (render_DocumentView_(&d->view, &ctx, iTrue /* just fill up progressively */)) { |
5486 | /* Something was drawn, should check later if there is still more to do. */ | ||
5010 | addTicker_App(prerender_DocumentWidget_, context); | 5487 | addTicker_App(prerender_DocumentWidget_, context); |
5011 | } | 5488 | } |
5012 | } | 5489 | } |
@@ -5020,108 +5497,54 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
5020 | if (width_Rect(bounds) <= 0) { | 5497 | if (width_Rect(bounds) <= 0) { |
5021 | return; | 5498 | return; |
5022 | } | 5499 | } |
5023 | /* TODO: Come up with a better palette caching system. | 5500 | checkPendingInvalidation_DocumentWidget_(d); |
5024 | It should be able to recompute cached colors in `History` when the theme has changed. | 5501 | draw_DocumentView_(&d->view); |
5025 | Cache the theme seed in `GmDocument`? */ | 5502 | iPaint p; |
5026 | // makePaletteGlobal_GmDocument(d->doc); | 5503 | init_Paint(&p); |
5027 | if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { | ||
5028 | updateTimestampBuf_DocumentWidget_(d); | ||
5029 | } | ||
5030 | if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) { | ||
5031 | updateSideIconBuf_DocumentWidget_(d); | ||
5032 | } | ||
5033 | const iRect docBounds = documentBounds_DocumentWidget_(d); | ||
5034 | const iRangei vis = visibleRange_DocumentWidget_(d); | ||
5035 | iDrawContext ctx = { | ||
5036 | .widget = d, | ||
5037 | .docBounds = docBounds, | ||
5038 | .vis = vis, | ||
5039 | .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0, | ||
5040 | }; | ||
5041 | init_Paint(&ctx.paint); | ||
5042 | render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); | ||
5043 | int yTop = docBounds.pos.y + viewPos_DocumentWidget_(d); | ||
5044 | const iBool isDocEmpty = size_GmDocument(d->doc).y == 0; | ||
5045 | const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; | ||
5046 | if (!isDocEmpty || !isEmpty_Banner(d->banner)) { | ||
5047 | const int docBgColor = isDocEmpty ? tmBannerBackground_ColorId : tmBackground_ColorId; | ||
5048 | setClip_Paint(&ctx.paint, clipBounds); | ||
5049 | if (!isDocEmpty) { | ||
5050 | draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds)); | ||
5051 | } | ||
5052 | /* Text markers. */ | ||
5053 | if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { | ||
5054 | SDL_Renderer *render = renderer_Window(get_Window()); | ||
5055 | ctx.firstMarkRect = zero_Rect(); | ||
5056 | ctx.lastMarkRect = zero_Rect(); | ||
5057 | SDL_SetRenderDrawBlendMode(render, | ||
5058 | isDark_ColorTheme(colorTheme_App()) ? SDL_BLENDMODE_ADD | ||
5059 | : SDL_BLENDMODE_BLEND); | ||
5060 | ctx.viewPos = topLeft_Rect(docBounds); | ||
5061 | /* Marker starting outside the visible range? */ | ||
5062 | if (d->visibleRuns.start) { | ||
5063 | if (!isEmpty_Range(&d->selectMark) && | ||
5064 | d->selectMark.start < d->visibleRuns.start->text.start && | ||
5065 | d->selectMark.end > d->visibleRuns.start->text.start) { | ||
5066 | ctx.inSelectMark = iTrue; | ||
5067 | } | ||
5068 | if (isEmpty_Range(&d->foundMark) && | ||
5069 | d->foundMark.start < d->visibleRuns.start->text.start && | ||
5070 | d->foundMark.end > d->visibleRuns.start->text.start) { | ||
5071 | ctx.inFoundMark = iTrue; | ||
5072 | } | ||
5073 | } | ||
5074 | render_GmDocument(d->doc, vis, drawMark_DrawContext_, &ctx); | ||
5075 | SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); | ||
5076 | /* Selection range pins. */ | ||
5077 | if (isTouchSelecting) { | ||
5078 | drawPin_Paint(&ctx.paint, ctx.firstMarkRect, 0, tmQuote_ColorId); | ||
5079 | drawPin_Paint(&ctx.paint, ctx.lastMarkRect, 1, tmQuote_ColorId); | ||
5080 | } | ||
5081 | } | ||
5082 | drawMedia_DocumentWidget_(d, &ctx.paint); | ||
5083 | /* Fill the top and bottom, in case the document is short. */ | ||
5084 | if (yTop > top_Rect(bounds)) { | ||
5085 | fillRect_Paint(&ctx.paint, | ||
5086 | (iRect){ bounds.pos, init_I2(bounds.size.x, yTop - top_Rect(bounds)) }, | ||
5087 | !isEmpty_Banner(d->banner) ? tmBannerBackground_ColorId | ||
5088 | : docBgColor); | ||
5089 | } | ||
5090 | /* Banner. */ | ||
5091 | if (!isDocEmpty || numItems_Banner(d->banner) > 0) { | ||
5092 | /* Fill the part between the banner and the top of the document. */ | ||
5093 | fillRect_Paint(&ctx.paint, | ||
5094 | (iRect){ init_I2(left_Rect(bounds), | ||
5095 | top_Rect(docBounds) + viewPos_DocumentWidget_(d) - | ||
5096 | documentTopPad_DocumentWidget_(d)), | ||
5097 | init_I2(bounds.size.x, documentTopPad_DocumentWidget_(d)) }, | ||
5098 | docBgColor); | ||
5099 | setPos_Banner(d->banner, addY_I2(topLeft_Rect(docBounds), | ||
5100 | -pos_SmoothScroll(&d->scrollY))); | ||
5101 | draw_Banner(d->banner); | ||
5102 | } | ||
5103 | const int yBottom = yTop + size_GmDocument(d->doc).y; | ||
5104 | if (yBottom < bottom_Rect(bounds)) { | ||
5105 | fillRect_Paint(&ctx.paint, | ||
5106 | init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom), | ||
5107 | !isDocEmpty ? docBgColor : tmBannerBackground_ColorId); | ||
5108 | } | ||
5109 | unsetClip_Paint(&ctx.paint); | ||
5110 | drawSideElements_DocumentWidget_(d); | ||
5111 | if (prefs_App()->hoverLink && d->hoverLink) { | ||
5112 | const int font = uiLabel_FontId; | ||
5113 | const iRangecc linkUrl = range_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId)); | ||
5114 | const iInt2 size = measureRange_Text(font, linkUrl).bounds.size; | ||
5115 | const iRect linkRect = { addY_I2(bottomLeft_Rect(bounds), -size.y), | ||
5116 | addX_I2(size, 2 * gap_UI) }; | ||
5117 | fillRect_Paint(&ctx.paint, linkRect, tmBackground_ColorId); | ||
5118 | drawRange_Text(font, addX_I2(topLeft_Rect(linkRect), gap_UI), tmParagraph_ColorId, linkUrl); | ||
5119 | } | ||
5120 | } | ||
5121 | if (colorTheme_App() == pureWhite_ColorTheme) { | 5504 | if (colorTheme_App() == pureWhite_ColorTheme) { |
5122 | drawHLine_Paint(&ctx.paint, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId); | 5505 | drawHLine_Paint(&p, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId); |
5123 | } | 5506 | } |
5507 | /* Pull action indicator. */ | ||
5508 | if (deviceType_App() != desktop_AppDeviceType) { | ||
5509 | float pullPos = pullActionPos_SmoothScroll(&d->view.scrollY); | ||
5510 | /* Account for the part where the indicator isn't yet visible. */ | ||
5511 | pullPos = (pullPos - 0.2f) / 0.8f; | ||
5512 | iRect indRect = initCentered_Rect(init_I2(mid_Rect(bounds).x, | ||
5513 | top_Rect(bounds) - 5 * gap_UI - | ||
5514 | pos_SmoothScroll(&d->view.scrollY)), | ||
5515 | init_I2(20 * gap_UI, 2 * gap_UI)); | ||
5516 | setClip_Paint(&p, clipBounds); | ||
5517 | int color = pullPos < 1.0f ? tmBannerItemFrame_ColorId : tmBannerItemText_ColorId; | ||
5518 | drawRect_Paint(&p, indRect, color); | ||
5519 | if (pullPos > 0) { | ||
5520 | shrink_Rect(&indRect, divi_I2(gap2_UI, 2)); | ||
5521 | indRect.size.x *= pullPos; | ||
5522 | fillRect_Paint(&p, indRect, color); | ||
5523 | } | ||
5524 | unsetClip_Paint(&p); | ||
5525 | } | ||
5526 | /* Scroll bar. */ | ||
5124 | drawChildren_Widget(w); | 5527 | drawChildren_Widget(w); |
5528 | /* Information about the hovered link. */ | ||
5529 | if (deviceType_App() == desktop_AppDeviceType && prefs_App()->hoverLink && d->linkInfo) { | ||
5530 | const int pad = 0; /*gap_UI;*/ | ||
5531 | update_LinkInfo(d->linkInfo, | ||
5532 | d->view.doc, | ||
5533 | d->view.hoverLink ? d->view.hoverLink->linkId : 0, | ||
5534 | width_Rect(bounds) - 2 * pad); | ||
5535 | const iInt2 infoSize = size_LinkInfo(d->linkInfo); | ||
5536 | iInt2 infoPos = add_I2(bottomLeft_Rect(bounds), init_I2(pad, -infoSize.y - pad)); | ||
5537 | if (d->view.hoverLink) { | ||
5538 | const iRect runRect = runRect_DocumentView_(&d->view, d->view.hoverLink); | ||
5539 | d->linkInfo->isAltPos = | ||
5540 | (bottom_Rect(runRect) >= infoPos.y - lineHeight_Text(paragraph_FontId)); | ||
5541 | } | ||
5542 | if (d->linkInfo->isAltPos) { | ||
5543 | infoPos.y = top_Rect(bounds) + pad; | ||
5544 | } | ||
5545 | draw_LinkInfo(d->linkInfo, infoPos); | ||
5546 | } | ||
5547 | /* Full-sized download indicator. */ | ||
5125 | if (d->flags & drawDownloadCounter_DocumentWidgetFlag && isRequestOngoing_DocumentWidget(d)) { | 5548 | if (d->flags & drawDownloadCounter_DocumentWidgetFlag && isRequestOngoing_DocumentWidget(d)) { |
5126 | const int font = uiLabelLarge_FontId; | 5549 | const int font = uiLabelLarge_FontId; |
5127 | const iInt2 sevenSegWidth = measureRange_Text(font, range_CStr("\U0001fbf0")).bounds.size; | 5550 | const iInt2 sevenSegWidth = measureRange_Text(font, range_CStr("\U0001fbf0")).bounds.size; |
@@ -5131,62 +5554,26 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
5131 | tmQuote_ColorId, tmQuoteIcon_ColorId, | 5554 | tmQuote_ColorId, tmQuoteIcon_ColorId, |
5132 | bodySize_GmRequest(d->request)); | 5555 | bodySize_GmRequest(d->request)); |
5133 | } | 5556 | } |
5134 | /* Alt text. */ | ||
5135 | const float altTextOpacity = value_Anim(&d->altTextOpacity) * 6 - 5; | ||
5136 | if (d->hoverAltPre && altTextOpacity > 0) { | ||
5137 | const iGmPreMeta *meta = preMeta_GmDocument(d->doc, preId_GmRun(d->hoverAltPre)); | ||
5138 | if (meta->flags & topLeft_GmPreMetaFlag && ~meta->flags & decoration_GmRunFlag && | ||
5139 | !isEmpty_Range(&meta->altText)) { | ||
5140 | const int margin = 3 * gap_UI / 2; | ||
5141 | const int altFont = uiLabel_FontId; | ||
5142 | const int wrap = docBounds.size.x - 2 * margin; | ||
5143 | iInt2 pos = addY_I2(add_I2(docBounds.pos, meta->pixelRect.pos), | ||
5144 | viewPos_DocumentWidget_(d)); | ||
5145 | const iInt2 textSize = measureWrapRange_Text(altFont, wrap, meta->altText).bounds.size; | ||
5146 | pos.y -= textSize.y + gap_UI; | ||
5147 | pos.y = iMax(pos.y, top_Rect(bounds)); | ||
5148 | const iRect altRect = { pos, init_I2(docBounds.size.x, textSize.y) }; | ||
5149 | ctx.paint.alpha = altTextOpacity * 255; | ||
5150 | if (altTextOpacity < 1) { | ||
5151 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | ||
5152 | } | ||
5153 | fillRect_Paint(&ctx.paint, altRect, tmBackgroundAltText_ColorId); | ||
5154 | drawRect_Paint(&ctx.paint, altRect, tmFrameAltText_ColorId); | ||
5155 | setOpacity_Text(altTextOpacity); | ||
5156 | drawWrapRange_Text(altFont, addX_I2(pos, margin), wrap, | ||
5157 | tmQuote_ColorId, meta->altText); | ||
5158 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | ||
5159 | setOpacity_Text(1.0f); | ||
5160 | } | ||
5161 | } | ||
5162 | /* Pinch zoom indicator. */ | 5557 | /* Pinch zoom indicator. */ |
5163 | if (d->flags & pinchZoom_DocumentWidgetFlag) { | 5558 | if (d->flags & pinchZoom_DocumentWidgetFlag) { |
5164 | const int font = uiLabelLargeBold_FontId; | 5559 | const int font = uiLabelLargeBold_FontId; |
5165 | const int height = lineHeight_Text(font) * 2; | 5560 | const int height = lineHeight_Text(font) * 2; |
5166 | const iInt2 size = init_I2(height * 2, height); | 5561 | const iInt2 size = init_I2(height * 2, height); |
5167 | const iRect rect = { sub_I2(mid_Rect(bounds), divi_I2(size, 2)), size }; | 5562 | const iRect rect = { sub_I2(mid_Rect(bounds), divi_I2(size, 2)), size }; |
5168 | fillRect_Paint(&ctx.paint, rect, d->pinchZoomPosted == 100 ? uiTextCaution_ColorId : uiTextAction_ColorId); | 5563 | fillRect_Paint(&p, rect, d->pinchZoomPosted == 100 ? uiTextCaution_ColorId : uiTextAction_ColorId); |
5169 | drawCentered_Text(font, bounds, iFalse, uiBackground_ColorId, "%d %%", | 5564 | drawCentered_Text(font, bounds, iFalse, uiBackground_ColorId, "%d %%", |
5170 | d->pinchZoomPosted); | 5565 | d->pinchZoomPosted); |
5171 | } | 5566 | } |
5172 | /* Touch selection indicator. */ | 5567 | /* Dimming during swipe animation. */ |
5173 | if (isTouchSelecting) { | ||
5174 | iRect rect = { topLeft_Rect(bounds), | ||
5175 | init_I2(width_Rect(bounds), lineHeight_Text(uiLabelBold_FontId)) }; | ||
5176 | fillRect_Paint(&ctx.paint, rect, uiTextAction_ColorId); | ||
5177 | const iRangecc mark = selectMark_DocumentWidget_(d); | ||
5178 | drawCentered_Text(uiLabelBold_FontId, rect, iFalse, uiBackground_ColorId, "%zu bytes selected", | ||
5179 | size_Range(&mark)); | ||
5180 | } | ||
5181 | if (w->offsetRef) { | 5568 | if (w->offsetRef) { |
5182 | const int offX = visualOffsetByReference_Widget(w); | 5569 | const int offX = visualOffsetByReference_Widget(w); |
5183 | if (offX) { | 5570 | if (offX) { |
5184 | setClip_Paint(&ctx.paint, clipBounds); | 5571 | setClip_Paint(&p, clipBounds); |
5185 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | 5572 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); |
5186 | ctx.paint.alpha = iAbs(offX) / (float) get_Window()->size.x * 300; | 5573 | p.alpha = iAbs(offX) / (float) get_Window()->size.x * 300; |
5187 | fillRect_Paint(&ctx.paint, bounds, backgroundFadeColor_Widget()); | 5574 | fillRect_Paint(&p, bounds, backgroundFadeColor_Widget()); |
5188 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | 5575 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); |
5189 | unsetClip_Paint(&ctx.paint); | 5576 | unsetClip_Paint(&p); |
5190 | } | 5577 | } |
5191 | else { | 5578 | else { |
5192 | /* TODO: Should have a better place to do this; drawing is supposed to be immutable. */ | 5579 | /* TODO: Should have a better place to do this; drawing is supposed to be immutable. */ |
@@ -5195,11 +5582,139 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
5195 | mut->flags &= ~refChildrenOffset_WidgetFlag; | 5582 | mut->flags &= ~refChildrenOffset_WidgetFlag; |
5196 | } | 5583 | } |
5197 | } | 5584 | } |
5198 | // drawRect_Paint(&ctx.paint, docBounds, red_ColorId); | 5585 | // drawRect_Paint(&p, docBounds, red_ColorId); |
5586 | if (deviceType_App() == phone_AppDeviceType) { | ||
5587 | /* The phone toolbar uses the palette of the active tab, but there may be other | ||
5588 | documents drawn before the toolbar, causing the colors to be incorrect. */ | ||
5589 | makePaletteGlobal_GmDocument(document_App()->view.doc); | ||
5590 | } | ||
5199 | } | 5591 | } |
5200 | 5592 | ||
5201 | /*----------------------------------------------------------------------------------------------*/ | 5593 | /*----------------------------------------------------------------------------------------------*/ |
5202 | 5594 | ||
5595 | void init_DocumentWidget(iDocumentWidget *d) { | ||
5596 | iWidget *w = as_Widget(d); | ||
5597 | init_Widget(w); | ||
5598 | setId_Widget(w, format_CStr("document%03d", ++docEnum_)); | ||
5599 | setFlags_Widget(w, hover_WidgetFlag | noBackground_WidgetFlag, iTrue); | ||
5600 | #if defined (iPlatformAppleDesktop) | ||
5601 | iBool enableSwipeNavigation = iTrue; /* swipes on the trackpad */ | ||
5602 | #else | ||
5603 | iBool enableSwipeNavigation = (deviceType_App() != desktop_AppDeviceType); | ||
5604 | #endif | ||
5605 | if (enableSwipeNavigation) { | ||
5606 | setFlags_Widget(w, leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag | | ||
5607 | horizontalOffset_WidgetFlag, iTrue); | ||
5608 | } | ||
5609 | init_PersistentDocumentState(&d->mod); | ||
5610 | d->flags = 0; | ||
5611 | d->phoneToolbar = findWidget_App("toolbar"); | ||
5612 | d->footerButtons = NULL; | ||
5613 | iZap(d->certExpiry); | ||
5614 | d->certFingerprint = new_Block(0); | ||
5615 | d->certFlags = 0; | ||
5616 | d->certSubject = new_String(); | ||
5617 | d->state = blank_RequestState; | ||
5618 | d->titleUser = new_String(); | ||
5619 | d->request = NULL; | ||
5620 | d->requestLinkId = 0; | ||
5621 | d->isRequestUpdated = iFalse; | ||
5622 | d->media = new_ObjectList(); | ||
5623 | d->banner = new_Banner(); | ||
5624 | setOwner_Banner(d->banner, d); | ||
5625 | d->redirectCount = 0; | ||
5626 | d->ordinalBase = 0; | ||
5627 | d->wheelSwipeState = none_WheelSwipeState; | ||
5628 | d->selectMark = iNullRange; | ||
5629 | d->foundMark = iNullRange; | ||
5630 | d->contextLink = NULL; | ||
5631 | d->sourceStatus = none_GmStatusCode; | ||
5632 | init_String(&d->sourceHeader); | ||
5633 | init_String(&d->sourceMime); | ||
5634 | init_Block(&d->sourceContent, 0); | ||
5635 | iZap(d->sourceTime); | ||
5636 | d->sourceGempub = NULL; | ||
5637 | d->initNormScrollY = 0; | ||
5638 | d->grabbedPlayer = NULL; | ||
5639 | d->mediaTimer = 0; | ||
5640 | init_String(&d->pendingGotoHeading); | ||
5641 | init_String(&d->linePrecedingLink); | ||
5642 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | ||
5643 | d->linkInfo = (deviceType_App() == desktop_AppDeviceType ? new_LinkInfo() : NULL); | ||
5644 | init_DocumentView(&d->view); | ||
5645 | setOwner_DocumentView_(&d->view, d); | ||
5646 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); | ||
5647 | d->menu = NULL; /* created when clicking */ | ||
5648 | d->playerMenu = NULL; | ||
5649 | d->copyMenu = NULL; | ||
5650 | d->translation = NULL; | ||
5651 | addChildFlags_Widget(w, | ||
5652 | iClob(new_IndicatorWidget()), | ||
5653 | resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag); | ||
5654 | #if !defined (iPlatformAppleDesktop) /* in system menu */ | ||
5655 | addAction_Widget(w, reload_KeyShortcut, "navigate.reload"); | ||
5656 | addAction_Widget(w, closeTab_KeyShortcut, "tabs.close"); | ||
5657 | addAction_Widget(w, SDLK_d, KMOD_PRIMARY, "bookmark.add"); | ||
5658 | addAction_Widget(w, subscribeToPage_KeyModifier, "feeds.subscribe"); | ||
5659 | #endif | ||
5660 | addAction_Widget(w, navigateBack_KeyShortcut, "navigate.back"); | ||
5661 | addAction_Widget(w, navigateForward_KeyShortcut, "navigate.forward"); | ||
5662 | addAction_Widget(w, navigateParent_KeyShortcut, "navigate.parent"); | ||
5663 | addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root"); | ||
5664 | } | ||
5665 | |||
5666 | void cancelAllRequests_DocumentWidget(iDocumentWidget *d) { | ||
5667 | iForEach(ObjectList, i, d->media) { | ||
5668 | iMediaRequest *mr = i.object; | ||
5669 | cancel_GmRequest(mr->req); | ||
5670 | } | ||
5671 | if (d->request) { | ||
5672 | cancel_GmRequest(d->request); | ||
5673 | } | ||
5674 | } | ||
5675 | |||
5676 | void deinit_DocumentWidget(iDocumentWidget *d) { | ||
5677 | // printf("\n* * * * * * * *\nDEINIT DOCUMENT: %s\n* * * * * * * *\n\n", | ||
5678 | // cstr_String(&d->widget.id)); fflush(stdout); | ||
5679 | cancelAllRequests_DocumentWidget(d); | ||
5680 | pauseAllPlayers_Media(media_GmDocument(d->view.doc), iTrue); | ||
5681 | removeTicker_App(animate_DocumentWidget_, d); | ||
5682 | removeTicker_App(prerender_DocumentWidget_, d); | ||
5683 | remove_Periodic(periodic_App(), d); | ||
5684 | delete_Translation(d->translation); | ||
5685 | deinit_DocumentView(&d->view); | ||
5686 | delete_LinkInfo(d->linkInfo); | ||
5687 | iRelease(d->media); | ||
5688 | iRelease(d->request); | ||
5689 | delete_Gempub(d->sourceGempub); | ||
5690 | deinit_String(&d->linePrecedingLink); | ||
5691 | deinit_String(&d->pendingGotoHeading); | ||
5692 | deinit_Block(&d->sourceContent); | ||
5693 | deinit_String(&d->sourceMime); | ||
5694 | deinit_String(&d->sourceHeader); | ||
5695 | delete_Banner(d->banner); | ||
5696 | if (d->mediaTimer) { | ||
5697 | SDL_RemoveTimer(d->mediaTimer); | ||
5698 | } | ||
5699 | delete_Block(d->certFingerprint); | ||
5700 | delete_String(d->certSubject); | ||
5701 | delete_String(d->titleUser); | ||
5702 | deinit_PersistentDocumentState(&d->mod); | ||
5703 | } | ||
5704 | |||
5705 | void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { | ||
5706 | setUrl_GmDocument(d->view.doc, d->mod.url); | ||
5707 | const int docWidth = documentWidth_DocumentView_(&d->view); | ||
5708 | setSource_GmDocument(d->view.doc, | ||
5709 | source, | ||
5710 | docWidth, | ||
5711 | width_Widget(d), | ||
5712 | isFinished_GmRequest(d->request) ? final_GmDocumentUpdate | ||
5713 | : partial_GmDocumentUpdate); | ||
5714 | setWidth_Banner(d->banner, docWidth); | ||
5715 | documentWasChanged_DocumentWidget_(d); | ||
5716 | } | ||
5717 | |||
5203 | iHistory *history_DocumentWidget(iDocumentWidget *d) { | 5718 | iHistory *history_DocumentWidget(iDocumentWidget *d) { |
5204 | return d->mod.history; | 5719 | return d->mod.history; |
5205 | } | 5720 | } |
@@ -5209,7 +5724,7 @@ const iString *url_DocumentWidget(const iDocumentWidget *d) { | |||
5209 | } | 5724 | } |
5210 | 5725 | ||
5211 | const iGmDocument *document_DocumentWidget(const iDocumentWidget *d) { | 5726 | const iGmDocument *document_DocumentWidget(const iDocumentWidget *d) { |
5212 | return d->doc; | 5727 | return d->view.doc; |
5213 | } | 5728 | } |
5214 | 5729 | ||
5215 | const iBlock *sourceContent_DocumentWidget(const iDocumentWidget *d) { | 5730 | const iBlock *sourceContent_DocumentWidget(const iDocumentWidget *d) { |
@@ -5217,20 +5732,20 @@ const iBlock *sourceContent_DocumentWidget(const iDocumentWidget *d) { | |||
5217 | } | 5732 | } |
5218 | 5733 | ||
5219 | int documentWidth_DocumentWidget(const iDocumentWidget *d) { | 5734 | int documentWidth_DocumentWidget(const iDocumentWidget *d) { |
5220 | return documentWidth_DocumentWidget_(d); | 5735 | return documentWidth_DocumentView_(&d->view); |
5221 | } | 5736 | } |
5222 | 5737 | ||
5223 | const iString *feedTitle_DocumentWidget(const iDocumentWidget *d) { | 5738 | const iString *feedTitle_DocumentWidget(const iDocumentWidget *d) { |
5224 | if (!isEmpty_String(title_GmDocument(d->doc))) { | 5739 | if (!isEmpty_String(title_GmDocument(d->view.doc))) { |
5225 | return title_GmDocument(d->doc); | 5740 | return title_GmDocument(d->view.doc); |
5226 | } | 5741 | } |
5227 | return bookmarkTitle_DocumentWidget(d); | 5742 | return bookmarkTitle_DocumentWidget(d); |
5228 | } | 5743 | } |
5229 | 5744 | ||
5230 | const iString *bookmarkTitle_DocumentWidget(const iDocumentWidget *d) { | 5745 | const iString *bookmarkTitle_DocumentWidget(const iDocumentWidget *d) { |
5231 | iStringArray *title = iClob(new_StringArray()); | 5746 | iStringArray *title = iClob(new_StringArray()); |
5232 | if (!isEmpty_String(title_GmDocument(d->doc))) { | 5747 | if (!isEmpty_String(title_GmDocument(d->view.doc))) { |
5233 | pushBack_StringArray(title, title_GmDocument(d->doc)); | 5748 | pushBack_StringArray(title, title_GmDocument(d->view.doc)); |
5234 | } | 5749 | } |
5235 | if (!isEmpty_String(d->titleUser)) { | 5750 | if (!isEmpty_String(d->titleUser)) { |
5236 | pushBack_StringArray(title, d->titleUser); | 5751 | pushBack_StringArray(title, d->titleUser); |
@@ -5258,30 +5773,19 @@ void deserializeState_DocumentWidget(iDocumentWidget *d, iStream *ins) { | |||
5258 | updateFromHistory_DocumentWidget_(d); | 5773 | updateFromHistory_DocumentWidget_(d); |
5259 | } | 5774 | } |
5260 | 5775 | ||
5261 | static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) { | ||
5262 | url = canonicalUrl_String(url); | ||
5263 | if (!equal_String(d->mod.url, url)) { | ||
5264 | d->flags |= urlChanged_DocumentWidgetFlag; | ||
5265 | set_String(d->mod.url, url); | ||
5266 | } | ||
5267 | } | ||
5268 | |||
5269 | void setUrlFlags_DocumentWidget(iDocumentWidget *d, const iString *url, int setUrlFlags) { | 5776 | void setUrlFlags_DocumentWidget(iDocumentWidget *d, const iString *url, int setUrlFlags) { |
5270 | iChangeFlags(d->flags, openedFromSidebar_DocumentWidgetFlag, | 5777 | const iBool allowCache = (setUrlFlags & useCachedContentIfAvailable_DocumentWidgetSetUrlFlag) != 0; |
5271 | (setUrlFlags & openedFromSidebar_DocumentWidgetSetUrlFlag) != 0); | ||
5272 | const iBool isFromCache = (setUrlFlags & useCachedContentIfAvailable_DocumentWidgetSetUrlFlag) != 0; | ||
5273 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 5778 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
5274 | setUrl_DocumentWidget_(d, urlFragmentStripped_String(url)); | 5779 | setUrl_DocumentWidget_(d, urlFragmentStripped_String(url)); |
5275 | /* See if there a username in the URL. */ | 5780 | /* See if there a username in the URL. */ |
5276 | parseUser_DocumentWidget_(d); | 5781 | parseUser_DocumentWidget_(d); |
5277 | if (!isFromCache || !updateFromHistory_DocumentWidget_(d)) { | 5782 | if (!allowCache || !updateFromHistory_DocumentWidget_(d)) { |
5278 | fetch_DocumentWidget_(d); | 5783 | fetch_DocumentWidget_(d); |
5279 | } | 5784 | } |
5280 | } | 5785 | } |
5281 | 5786 | ||
5282 | void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, const iString *mime, | 5787 | void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, const iString *mime, |
5283 | const iBlock *source) { | 5788 | const iBlock *source) { |
5284 | d->flags &= ~openedFromSidebar_DocumentWidgetFlag; | ||
5285 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 5789 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
5286 | setUrl_DocumentWidget_(d, url); | 5790 | setUrl_DocumentWidget_(d, url); |
5287 | parseUser_DocumentWidget_(d); | 5791 | parseUser_DocumentWidget_(d); |
@@ -5291,18 +5795,26 @@ void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, cons | |||
5291 | set_String(&resp->meta, mime); | 5795 | set_String(&resp->meta, mime); |
5292 | set_Block(&resp->body, source); | 5796 | set_Block(&resp->body, source); |
5293 | updateFromCachedResponse_DocumentWidget_(d, 0, resp, NULL); | 5797 | updateFromCachedResponse_DocumentWidget_(d, 0, resp, NULL); |
5798 | updateBanner_DocumentWidget_(d); | ||
5294 | delete_GmResponse(resp); | 5799 | delete_GmResponse(resp); |
5295 | } | 5800 | } |
5296 | 5801 | ||
5297 | iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) { | 5802 | iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) { |
5298 | iDocumentWidget *d = new_DocumentWidget(); | 5803 | iDocumentWidget *d = new_DocumentWidget(); |
5299 | delete_History(d->mod.history); | 5804 | delete_History(d->mod.history); |
5300 | d->initNormScrollY = normScrollPos_DocumentWidget_(d); | 5805 | d->initNormScrollY = normScrollPos_DocumentView_(&d->view); |
5301 | d->mod.history = copy_History(orig->mod.history); | 5806 | d->mod.history = copy_History(orig->mod.history); |
5302 | setUrlFlags_DocumentWidget(d, orig->mod.url, useCachedContentIfAvailable_DocumentWidgetSetUrlFlag); | 5807 | setUrlFlags_DocumentWidget(d, orig->mod.url, useCachedContentIfAvailable_DocumentWidgetSetUrlFlag); |
5303 | return d; | 5808 | return d; |
5304 | } | 5809 | } |
5305 | 5810 | ||
5811 | void setOrigin_DocumentWidget(iDocumentWidget *d, const iDocumentWidget *other) { | ||
5812 | if (d != other) { | ||
5813 | /* TODO: Could remember the other's ID? */ | ||
5814 | set_String(&d->linePrecedingLink, &other->linePrecedingLink); | ||
5815 | } | ||
5816 | } | ||
5817 | |||
5306 | void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { | 5818 | void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { |
5307 | setUrlFlags_DocumentWidget(d, url, 0); | 5819 | setUrlFlags_DocumentWidget(d, url, 0); |
5308 | } | 5820 | } |
@@ -5315,11 +5827,6 @@ void setRedirectCount_DocumentWidget(iDocumentWidget *d, int count) { | |||
5315 | d->redirectCount = count; | 5827 | d->redirectCount = count; |
5316 | } | 5828 | } |
5317 | 5829 | ||
5318 | void setOpenedFromSidebar_DocumentWidget(iDocumentWidget *d, iBool fromSidebar) { | ||
5319 | iChangeFlags(d->flags, openedFromSidebar_DocumentWidgetFlag, fromSidebar); | ||
5320 | // setCachedDocument_History(d->mod.history, d->doc, fromSidebar); | ||
5321 | } | ||
5322 | |||
5323 | iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { | 5830 | iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { |
5324 | return d->request != NULL; | 5831 | return d->request != NULL; |
5325 | } | 5832 | } |
@@ -5327,7 +5834,7 @@ iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { | |||
5327 | void takeRequest_DocumentWidget(iDocumentWidget *d, iGmRequest *finishedRequest) { | 5834 | void takeRequest_DocumentWidget(iDocumentWidget *d, iGmRequest *finishedRequest) { |
5328 | cancelRequest_DocumentWidget_(d, iFalse /* don't post anything */); | 5835 | cancelRequest_DocumentWidget_(d, iFalse /* don't post anything */); |
5329 | const iString *url = url_GmRequest(finishedRequest); | 5836 | const iString *url = url_GmRequest(finishedRequest); |
5330 | 5837 | ||
5331 | add_History(d->mod.history, url); | 5838 | add_History(d->mod.history, url); |
5332 | setUrl_DocumentWidget_(d, url); | 5839 | setUrl_DocumentWidget_(d, url); |
5333 | d->state = fetching_RequestState; | 5840 | d->state = fetching_RequestState; |
@@ -5341,27 +5848,17 @@ void takeRequest_DocumentWidget(iDocumentWidget *d, iGmRequest *finishedRequest) | |||
5341 | } | 5848 | } |
5342 | 5849 | ||
5343 | void updateSize_DocumentWidget(iDocumentWidget *d) { | 5850 | void updateSize_DocumentWidget(iDocumentWidget *d) { |
5344 | updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, iFalse); | 5851 | iDocumentView *view = &d->view; |
5345 | resetWideRuns_DocumentWidget_(d); | 5852 | updateDocumentWidthRetainingScrollPosition_DocumentView_(view, iFalse); |
5346 | d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; | 5853 | resetWideRuns_DocumentView_(view); |
5347 | updateVisible_DocumentWidget_(d); | 5854 | view->drawBufs->flags |= updateSideBuf_DrawBufsFlag; |
5348 | setWidth_Banner(d->banner, documentWidth_DocumentWidget(d)); | 5855 | updateVisible_DocumentView_(view); |
5856 | setWidth_Banner(d->banner, documentWidth_DocumentView_(view)); | ||
5349 | invalidate_DocumentWidget_(d); | 5857 | invalidate_DocumentWidget_(d); |
5350 | arrange_Widget(d->footerButtons); | 5858 | arrange_Widget(d->footerButtons); |
5351 | } | 5859 | } |
5352 | 5860 | ||
5353 | #if 0 | ||
5354 | static void sizeChanged_DocumentWidget_(iDocumentWidget *d) { | ||
5355 | if (current_Root()) { | ||
5356 | /* TODO: This gets called more than once during a single arrange. | ||
5357 | It could be done via some sort of callback instead. */ | ||
5358 | updateVisible_DocumentWidget_(d); | ||
5359 | } | ||
5360 | } | ||
5361 | #endif | ||
5362 | |||
5363 | iBeginDefineSubclass(DocumentWidget, Widget) | 5861 | iBeginDefineSubclass(DocumentWidget, Widget) |
5364 | .processEvent = (iAny *) processEvent_DocumentWidget_, | 5862 | .processEvent = (iAny *) processEvent_DocumentWidget_, |
5365 | .draw = (iAny *) draw_DocumentWidget_, | 5863 | .draw = (iAny *) draw_DocumentWidget_, |
5366 | // .sizeChanged = (iAny *) sizeChanged_DocumentWidget_, | ||
5367 | iEndDefineSubclass(DocumentWidget) | 5864 | iEndDefineSubclass(DocumentWidget) |
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h index 2df3392b..1bee8351 100644 --- a/src/ui/documentwidget.h +++ b/src/ui/documentwidget.h | |||
@@ -48,21 +48,17 @@ const iString * bookmarkTitle_DocumentWidget (const iDocumentWidget *); | |||
48 | const iString * feedTitle_DocumentWidget (const iDocumentWidget *); | 48 | const iString * feedTitle_DocumentWidget (const iDocumentWidget *); |
49 | int documentWidth_DocumentWidget (const iDocumentWidget *); | 49 | int documentWidth_DocumentWidget (const iDocumentWidget *); |
50 | 50 | ||
51 | //iBool findCachedContent_DocumentWidget(const iDocumentWidget *, const iString *url, | ||
52 | // iString *mime_out, iBlock *data_out); | ||
53 | |||
54 | enum iDocumentWidgetSetUrlFlags { | 51 | enum iDocumentWidgetSetUrlFlags { |
55 | useCachedContentIfAvailable_DocumentWidgetSetUrlFlag = iBit(1), | 52 | useCachedContentIfAvailable_DocumentWidgetSetUrlFlag = iBit(1), |
56 | openedFromSidebar_DocumentWidgetSetUrlFlag = iBit(2), | ||
57 | }; | 53 | }; |
58 | 54 | ||
55 | void setOrigin_DocumentWidget (iDocumentWidget *, const iDocumentWidget *other); | ||
59 | void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); | 56 | void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); |
60 | void setUrlFlags_DocumentWidget (iDocumentWidget *, const iString *url, int setUrlFlags); | 57 | void setUrlFlags_DocumentWidget (iDocumentWidget *, const iString *url, int setUrlFlags); |
61 | void setUrlAndSource_DocumentWidget (iDocumentWidget *, const iString *url, const iString *mime, const iBlock *source); | 58 | void setUrlAndSource_DocumentWidget (iDocumentWidget *, const iString *url, const iString *mime, const iBlock *source); |
62 | void setInitialScroll_DocumentWidget (iDocumentWidget *, float normScrollY); /* set after content received */ | 59 | void setInitialScroll_DocumentWidget (iDocumentWidget *, float normScrollY); /* set after content received */ |
63 | void setRedirectCount_DocumentWidget (iDocumentWidget *, int count); | 60 | void setRedirectCount_DocumentWidget (iDocumentWidget *, int count); |
64 | void setSource_DocumentWidget (iDocumentWidget *, const iString *sourceText); | 61 | void setSource_DocumentWidget (iDocumentWidget *, const iString *sourceText); |
65 | void setOpenedFromSidebar_DocumentWidget(iDocumentWidget *, iBool fromSidebar); | ||
66 | 62 | ||
67 | void takeRequest_DocumentWidget (iDocumentWidget *, iGmRequest *finishedRequest); /* ownership given */ | 63 | void takeRequest_DocumentWidget (iDocumentWidget *, iGmRequest *finishedRequest); /* ownership given */ |
68 | 64 | ||
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index a1fb8cb5..9261da0c 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -20,6 +20,10 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
22 | 22 | ||
23 | /* InputWidget supports both fully custom and system-provided text editing. | ||
24 | The primary source of complexity is the handling of wrapped text content | ||
25 | in the custom text editor. */ | ||
26 | |||
23 | #include "inputwidget.h" | 27 | #include "inputwidget.h" |
24 | #include "command.h" | 28 | #include "command.h" |
25 | #include "paint.h" | 29 | #include "paint.h" |
@@ -40,10 +44,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
40 | # include "macos.h" | 44 | # include "macos.h" |
41 | #endif | 45 | #endif |
42 | 46 | ||
47 | #if defined (iPlatformAppleMobile) | ||
48 | # include "ios.h" | ||
49 | # define LAGRANGE_USE_SYSTEM_TEXT_INPUT 1 /* System-provided UI control almost handles everything. */ | ||
50 | #else | ||
51 | # define LAGRANGE_USE_SYSTEM_TEXT_INPUT 0 | ||
52 | iDeclareType(SystemTextInput) | ||
53 | #endif | ||
54 | |||
43 | static const int refreshInterval_InputWidget_ = 512; | 55 | static const int refreshInterval_InputWidget_ = 512; |
44 | static const size_t maxUndo_InputWidget_ = 64; | 56 | static const size_t maxUndo_InputWidget_ = 64; |
45 | static const int unlimitedWidth_InputWidget_ = 1000000; /* TODO: WrapText disables some functionality if maxWidth==0 */ | 57 | static const int unlimitedWidth_InputWidget_ = 1000000; /* TODO: WrapText disables some functionality if maxWidth==0 */ |
46 | 58 | ||
59 | static const iChar sensitiveChar_ = 0x25cf; /* black circle */ | ||
60 | static const char * sensitive_ = "\u25cf"; | ||
61 | |||
62 | #define minWidth_InputWidget_ (3 * gap_UI) | ||
63 | |||
47 | static void enableEditorKeysInMenus_(iBool enable) { | 64 | static void enableEditorKeysInMenus_(iBool enable) { |
48 | #if defined (iPlatformAppleDesktop) | 65 | #if defined (iPlatformAppleDesktop) |
49 | enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable); | 66 | enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable); |
@@ -57,7 +74,10 @@ static void enableEditorKeysInMenus_(iBool enable) { | |||
57 | #endif | 74 | #endif |
58 | } | 75 | } |
59 | 76 | ||
77 | static void updateMetrics_InputWidget_(iInputWidget *); | ||
78 | |||
60 | /*----------------------------------------------------------------------------------------------*/ | 79 | /*----------------------------------------------------------------------------------------------*/ |
80 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
61 | 81 | ||
62 | iDeclareType(InputLine) | 82 | iDeclareType(InputLine) |
63 | 83 | ||
@@ -178,6 +198,8 @@ static void deinit_InputUndo_(iInputUndo *d) { | |||
178 | deinit_String(&d->text); | 198 | deinit_String(&d->text); |
179 | } | 199 | } |
180 | 200 | ||
201 | #endif /* USE_SYSTEM_TEXT_INPUT */ | ||
202 | |||
181 | enum iInputWidgetFlag { | 203 | enum iInputWidgetFlag { |
182 | isSensitive_InputWidgetFlag = iBit(1), | 204 | isSensitive_InputWidgetFlag = iBit(1), |
183 | isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ | 205 | isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ |
@@ -203,41 +225,54 @@ enum iInputWidgetFlag { | |||
203 | struct Impl_InputWidget { | 225 | struct Impl_InputWidget { |
204 | iWidget widget; | 226 | iWidget widget; |
205 | enum iInputMode mode; | 227 | enum iInputMode mode; |
228 | int font; | ||
206 | int inFlags; | 229 | int inFlags; |
207 | size_t maxLen; /* characters */ | 230 | size_t maxLen; /* characters */ |
208 | iArray lines; /* iInputLine[] */ | ||
209 | iString oldText; /* for restoring if edits cancelled */ | ||
210 | int lastUpdateWidth; | ||
211 | iString srcHint; | 231 | iString srcHint; |
212 | iString hint; | 232 | iString hint; |
213 | int leftPadding; | 233 | int leftPadding; /* additional padding between frame and content */ |
214 | int rightPadding; | 234 | int rightPadding; |
235 | int minWrapLines, maxWrapLines; /* min/max number of visible lines allowed */ | ||
236 | iRangei visWrapLines; /* which wrap lines are current visible */ | ||
237 | iClick click; | ||
238 | int wheelAccum; | ||
239 | iTextBuf * buffered; /* pre-rendered static text */ | ||
240 | iInputWidgetValidatorFunc validator; | ||
241 | void * validatorContext; | ||
242 | iString * backupPath; | ||
243 | int backupTimer; | ||
244 | iString oldText; /* for restoring if edits cancelled */ | ||
245 | int lastUpdateWidth; | ||
246 | uint32_t lastOverflowScrollTime; /* scrolling to show focused widget */ | ||
247 | iSystemTextInput *sysCtrl; | ||
248 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
249 | iString text; | ||
250 | #else | ||
251 | iArray lines; /* iInputLine[] */ | ||
215 | iInt2 cursor; /* cursor position: x = byte offset, y = line index */ | 252 | iInt2 cursor; /* cursor position: x = byte offset, y = line index */ |
216 | iInt2 prevCursor; /* previous cursor position */ | 253 | iInt2 prevCursor; /* previous cursor position */ |
217 | iRangei visWrapLines; /* which wrap lines are current visible */ | ||
218 | int minWrapLines, maxWrapLines; /* min/max number of visible lines allowed */ | ||
219 | iRanges mark; /* TODO: would likely simplify things to use two Int2's for marking; no conversions needed */ | 254 | iRanges mark; /* TODO: would likely simplify things to use two Int2's for marking; no conversions needed */ |
220 | iRanges initialMark; | 255 | iRanges initialMark; |
221 | iArray undoStack; | 256 | iArray undoStack; |
222 | int font; | ||
223 | iClick click; | ||
224 | uint32_t tapStartTime; | 257 | uint32_t tapStartTime; |
225 | uint32_t lastTapTime; | 258 | uint32_t lastTapTime; |
226 | iInt2 lastTapPos; | 259 | iInt2 lastTapPos; |
227 | int tapCount; | 260 | int tapCount; |
228 | int wheelAccum; | ||
229 | int cursorVis; | 261 | int cursorVis; |
230 | uint32_t timer; | 262 | uint32_t timer; |
231 | iTextBuf * buffered; /* pre-rendered static text */ | 263 | #endif |
232 | iInputWidgetValidatorFunc validator; | ||
233 | void * validatorContext; | ||
234 | iString * backupPath; | ||
235 | int backupTimer; | ||
236 | }; | 264 | }; |
237 | 265 | ||
238 | iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) | 266 | iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) |
239 | 267 | ||
240 | static void updateMetrics_InputWidget_(iInputWidget *); | 268 | static int extraPaddingHeight_InputWidget_(const iInputWidget *d) { |
269 | if ((isPortraitPhone_App() || deviceType_App() == tablet_AppDeviceType) && | ||
270 | !cmp_String(id_Widget(&d->widget), "url")) { | ||
271 | /* Make the tap target more generous. */ | ||
272 | return 2.5f * gap_UI; | ||
273 | } | ||
274 | return 1.25f * gap_UI; | ||
275 | } | ||
241 | 276 | ||
242 | static void restoreBackup_InputWidget_(iInputWidget *d) { | 277 | static void restoreBackup_InputWidget_(iInputWidget *d) { |
243 | if (!d->backupPath) return; | 278 | if (!d->backupPath) return; |
@@ -252,17 +287,21 @@ static void saveBackup_InputWidget_(iInputWidget *d) { | |||
252 | if (!d->backupPath) return; | 287 | if (!d->backupPath) return; |
253 | iFile *f = new_File(d->backupPath); | 288 | iFile *f = new_File(d->backupPath); |
254 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { | 289 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { |
290 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
291 | write_File(f, utf8_String(&d->text)); | ||
292 | #else | ||
255 | iConstForEach(Array, i, &d->lines) { | 293 | iConstForEach(Array, i, &d->lines) { |
256 | const iInputLine *line = i.value; | 294 | const iInputLine *line = i.value; |
257 | write_File(f, utf8_String(&line->text)); | 295 | write_File(f, utf8_String(&line->text)); |
258 | } | 296 | } |
259 | d->inFlags &= ~needBackup_InputWidgetFlag; | 297 | # if !defined (NDEBUG) |
260 | #if !defined (NDEBUG) | ||
261 | iConstForEach(Array, j, &d->lines) { | 298 | iConstForEach(Array, j, &d->lines) { |
262 | iAssert(endsWith_String(&((const iInputLine *) j.value)->text, "\n") || | 299 | iAssert(endsWith_String(&((const iInputLine *) j.value)->text, "\n") || |
263 | index_ArrayConstIterator(&j) == size_Array(&d->lines) - 1); | 300 | index_ArrayConstIterator(&j) == size_Array(&d->lines) - 1); |
264 | } | 301 | } |
302 | # endif | ||
265 | #endif | 303 | #endif |
304 | d->inFlags &= ~needBackup_InputWidgetFlag; | ||
266 | } | 305 | } |
267 | iRelease(f); | 306 | iRelease(f); |
268 | } | 307 | } |
@@ -311,6 +350,12 @@ void setBackupFileName_InputWidget(iInputWidget *d, const char *fileName) { | |||
311 | restoreBackup_InputWidget_(d); | 350 | restoreBackup_InputWidget_(d); |
312 | } | 351 | } |
313 | 352 | ||
353 | iLocalDef iInt2 padding_(void) { | ||
354 | return init_I2(gap_UI / 2, gap_UI / 2); | ||
355 | } | ||
356 | |||
357 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
358 | |||
314 | static void clearUndo_InputWidget_(iInputWidget *d) { | 359 | static void clearUndo_InputWidget_(iInputWidget *d) { |
315 | iForEach(Array, i, &d->undoStack) { | 360 | iForEach(Array, i, &d->undoStack) { |
316 | deinit_InputUndo_(i.value); | 361 | deinit_InputUndo_(i.value); |
@@ -318,11 +363,12 @@ static void clearUndo_InputWidget_(iInputWidget *d) { | |||
318 | clear_Array(&d->undoStack); | 363 | clear_Array(&d->undoStack); |
319 | } | 364 | } |
320 | 365 | ||
321 | iLocalDef iInt2 padding_(void) { | 366 | static const iInputLine *line_InputWidget_(const iInputWidget *d, size_t index) { |
322 | return init_I2(gap_UI / 2, gap_UI / 2); | 367 | iAssert(!isEmpty_Array(&d->lines)); |
368 | return constAt_Array(&d->lines, index); | ||
323 | } | 369 | } |
324 | 370 | ||
325 | #define extraPaddingHeight_ (1.25f * gap_UI) | 371 | #endif /* !LAGRANGE_USE_SYSTEM_TEXT_INPUT */ |
326 | 372 | ||
327 | static iRect contentBounds_InputWidget_(const iInputWidget *d) { | 373 | static iRect contentBounds_InputWidget_(const iInputWidget *d) { |
328 | const iWidget *w = constAs_Widget(d); | 374 | const iWidget *w = constAs_Widget(d); |
@@ -332,11 +378,39 @@ static iRect contentBounds_InputWidget_(const iInputWidget *d) { | |||
332 | shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); | 378 | shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); |
333 | bounds.pos.y += padding_().y / 2; | 379 | bounds.pos.y += padding_().y / 2; |
334 | if (flags_Widget(w) & extraPadding_WidgetFlag) { | 380 | if (flags_Widget(w) & extraPadding_WidgetFlag) { |
335 | bounds.pos.y += extraPaddingHeight_ / 2; | 381 | if (d->sysCtrl && !cmp_String(id_Widget(w), "url")) { |
382 | /* TODO: This is super hacky: the native UI control would be offset incorrectly. | ||
383 | These paddings/offsets are getting a bit ridiculous, should rethink the whole thing. | ||
384 | Use the Widget paddings! */ | ||
385 | bounds.pos.y += 1.25f * gap_UI / 2; | ||
386 | } | ||
387 | else { | ||
388 | bounds.pos.y += extraPaddingHeight_InputWidget_(d) / 2; | ||
389 | } | ||
336 | } | 390 | } |
337 | return bounds; | 391 | return bounds; |
338 | } | 392 | } |
339 | 393 | ||
394 | static iWrapText wrap_InputWidget_(const iInputWidget *d, int y) { | ||
395 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
396 | iUnused(y); /* full text is wrapped always */ | ||
397 | iRangecc text = range_String(&d->text); | ||
398 | #else | ||
399 | iRangecc text = range_String(&line_InputWidget_(d, y)->text); | ||
400 | #endif | ||
401 | return (iWrapText){ | ||
402 | .text = text, | ||
403 | .maxWidth = d->maxLen == 0 ? iMaxi(minWidth_InputWidget_, | ||
404 | width_Rect(contentBounds_InputWidget_(d))) | ||
405 | : unlimitedWidth_InputWidget_, | ||
406 | .mode = | ||
407 | (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode), | ||
408 | .overrideChar = (d->inFlags & isSensitive_InputWidgetFlag ? sensitiveChar_ : 0), | ||
409 | }; | ||
410 | } | ||
411 | |||
412 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
413 | |||
340 | iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) { | 414 | iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) { |
341 | return (const void *) line == constBack_Array(&d->lines); | 415 | return (const void *) line == constBack_Array(&d->lines); |
342 | } | 416 | } |
@@ -350,11 +424,6 @@ static int numWrapLines_InputWidget_(const iInputWidget *d) { | |||
350 | return lastLine_InputWidget_(d)->wrapLines.end; | 424 | return lastLine_InputWidget_(d)->wrapLines.end; |
351 | } | 425 | } |
352 | 426 | ||
353 | static const iInputLine *line_InputWidget_(const iInputWidget *d, size_t index) { | ||
354 | iAssert(!isEmpty_Array(&d->lines)); | ||
355 | return constAt_Array(&d->lines, index); | ||
356 | } | ||
357 | |||
358 | static const iString *lineString_InputWidget_(const iInputWidget *d, int y) { | 427 | static const iString *lineString_InputWidget_(const iInputWidget *d, int y) { |
359 | return &line_InputWidget_(d, y)->text; | 428 | return &line_InputWidget_(d, y)->text; |
360 | } | 429 | } |
@@ -455,20 +524,6 @@ static int visLineOffsetY_InputWidget_(const iInputWidget *d) { | |||
455 | d->wheelAccum; | 524 | d->wheelAccum; |
456 | } | 525 | } |
457 | 526 | ||
458 | static const iChar sensitiveChar_ = 0x25cf; /* black circle */ | ||
459 | static const char *sensitive_ = "\u25cf"; | ||
460 | |||
461 | static iWrapText wrap_InputWidget_(const iInputWidget *d, int y) { | ||
462 | return (iWrapText){ | ||
463 | .text = range_String(&line_InputWidget_(d, y)->text), | ||
464 | .maxWidth = d->maxLen == 0 ? width_Rect(contentBounds_InputWidget_(d)) | ||
465 | : unlimitedWidth_InputWidget_, | ||
466 | .mode = | ||
467 | (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode), | ||
468 | .overrideChar = (d->inFlags & isSensitive_InputWidgetFlag ? sensitiveChar_ : 0), | ||
469 | }; | ||
470 | } | ||
471 | |||
472 | static iRangei visibleLineRange_InputWidget_(const iInputWidget *d) { | 527 | static iRangei visibleLineRange_InputWidget_(const iInputWidget *d) { |
473 | iRangei vis = { -1, -1 }; | 528 | iRangei vis = { -1, -1 }; |
474 | /* Determine which lines are in the potentially visible range. */ | 529 | /* Determine which lines are in the potentially visible range. */ |
@@ -518,6 +573,9 @@ static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { | |||
518 | } | 573 | } |
519 | 574 | ||
520 | static void updateVisible_InputWidget_(iInputWidget *d) { | 575 | static void updateVisible_InputWidget_(iInputWidget *d) { |
576 | if (width_Widget(d) == 0) { | ||
577 | return; /* Nothing to do yet. */ | ||
578 | } | ||
521 | const int totalWraps = numWrapLines_InputWidget_(d); | 579 | const int totalWraps = numWrapLines_InputWidget_(d); |
522 | const int visWraps = iClamp(totalWraps, d->minWrapLines, d->maxWrapLines); | 580 | const int visWraps = iClamp(totalWraps, d->minWrapLines, d->maxWrapLines); |
523 | /* Resize the height of the editor. */ | 581 | /* Resize the height of the editor. */ |
@@ -535,15 +593,24 @@ static void updateVisible_InputWidget_(iInputWidget *d) { | |||
535 | else if (cursorY < d->visWrapLines.start) { | 593 | else if (cursorY < d->visWrapLines.start) { |
536 | delta = cursorY - d->visWrapLines.start; | 594 | delta = cursorY - d->visWrapLines.start; |
537 | } | 595 | } |
596 | if (d->visWrapLines.end + delta > totalWraps) { | ||
597 | /* Don't scroll past the bottom. */ | ||
598 | delta = totalWraps - d->visWrapLines.end; | ||
599 | } | ||
600 | if (d->visWrapLines.start + delta < 0) { | ||
601 | /* Don't ever scroll above the top. */ | ||
602 | delta = -d->visWrapLines.start; | ||
603 | } | ||
538 | d->visWrapLines.start += delta; | 604 | d->visWrapLines.start += delta; |
539 | d->visWrapLines.end += delta; | 605 | d->visWrapLines.end += delta; |
540 | iAssert(contains_Range(&d->visWrapLines, cursorY)); | 606 | // iAssert(contains_Range(&d->visWrapLines, cursorY)); |
541 | if (!isFocused_Widget(d) && d->maxWrapLines == 1) { | 607 | if (!isFocused_Widget(d) && d->maxWrapLines == 1) { |
542 | d->visWrapLines.start = 0; | 608 | d->visWrapLines.start = 0; |
543 | d->visWrapLines.end = 1; | 609 | d->visWrapLines.end = 1; |
544 | } | 610 | } |
545 | // printf("[InputWidget %p] total:%d viswrp:%d cur:%d vis:%d..%d\n", | 611 | // printf("[InputWidget %p] total:%d viswrp:%d cur:%d vis:%d..%d\n", |
546 | // d, totalWraps, visWraps, d->cursor.y, d->visWrapLines.start, d->visWrapLines.end); | 612 | // d, totalWraps, visWraps, d->cursor.y, d->visWrapLines.start, d->visWrapLines.end); |
613 | // fflush(stdout); | ||
547 | } | 614 | } |
548 | 615 | ||
549 | static void showCursor_InputWidget_(iInputWidget *d) { | 616 | static void showCursor_InputWidget_(iInputWidget *d) { |
@@ -551,6 +618,19 @@ static void showCursor_InputWidget_(iInputWidget *d) { | |||
551 | updateVisible_InputWidget_(d); | 618 | updateVisible_InputWidget_(d); |
552 | } | 619 | } |
553 | 620 | ||
621 | #else /* if LAGRANGE_USE_SYSTEM_TEXT_INPUT */ | ||
622 | |||
623 | static int visLineOffsetY_InputWidget_(const iInputWidget *d) { | ||
624 | return 0; /* offset for the buffered text */ | ||
625 | } | ||
626 | |||
627 | static void updateVisible_InputWidget_(iInputWidget *d) { | ||
628 | iUnused(d); | ||
629 | /* TODO: Anything to do? */ | ||
630 | } | ||
631 | |||
632 | #endif | ||
633 | |||
554 | static void invalidateBuffered_InputWidget_(iInputWidget *d) { | 634 | static void invalidateBuffered_InputWidget_(iInputWidget *d) { |
555 | if (d->buffered) { | 635 | if (d->buffered) { |
556 | delete_TextBuf(d->buffered); | 636 | delete_TextBuf(d->buffered); |
@@ -563,7 +643,7 @@ static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { | |||
563 | /* Set a fixed size based on maximum possible width of the text. */ | 643 | /* Set a fixed size based on maximum possible width of the text. */ |
564 | iBlock *content = new_Block(d->maxLen); | 644 | iBlock *content = new_Block(d->maxLen); |
565 | fill_Block(content, 'M'); | 645 | fill_Block(content, 'M'); |
566 | int extraHeight = (flags_Widget(as_Widget(d)) & extraPadding_WidgetFlag ? extraPaddingHeight_ : 0); | 646 | int extraHeight = (flags_Widget(as_Widget(d)) & extraPadding_WidgetFlag ? extraPaddingHeight_InputWidget_(d) : 0); |
567 | setFixedSize_Widget( | 647 | setFixedSize_Widget( |
568 | as_Widget(d), | 648 | as_Widget(d), |
569 | add_I2(measure_Text(d->font, cstr_Block(content)).bounds.size, | 649 | add_I2(measure_Text(d->font, cstr_Block(content)).bounds.size, |
@@ -574,11 +654,16 @@ static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { | |||
574 | } | 654 | } |
575 | 655 | ||
576 | static iString *text_InputWidget_(const iInputWidget *d) { | 656 | static iString *text_InputWidget_(const iInputWidget *d) { |
657 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
658 | return copy_String(&d->text); | ||
659 | #else | ||
577 | iString *text = new_String(); | 660 | iString *text = new_String(); |
578 | mergeLines_(&d->lines, text); | 661 | mergeLines_(&d->lines, text); |
579 | return text; | 662 | return text; |
663 | #endif | ||
580 | } | 664 | } |
581 | 665 | ||
666 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
582 | static size_t length_InputWidget_(const iInputWidget *d) { | 667 | static size_t length_InputWidget_(const iInputWidget *d) { |
583 | /* Note: `d->length` is kept up to date, so don't call this normally. */ | 668 | /* Note: `d->length` is kept up to date, so don't call this normally. */ |
584 | size_t len = 0; | 669 | size_t len = 0; |
@@ -589,37 +674,10 @@ static size_t length_InputWidget_(const iInputWidget *d) { | |||
589 | return len; | 674 | return len; |
590 | } | 675 | } |
591 | 676 | ||
592 | static int contentHeight_InputWidget_(const iInputWidget *d) { | ||
593 | return size_Range(&d->visWrapLines) * lineHeight_Text(d->font); | ||
594 | } | ||
595 | |||
596 | static void updateTextInputRect_InputWidget_(const iInputWidget *d) { | ||
597 | #if !defined (iPlatformAppleMobile) | ||
598 | const iRect bounds = bounds_Widget(constAs_Widget(d)); | ||
599 | SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y }); | ||
600 | #endif | ||
601 | } | ||
602 | |||
603 | static void updateMetrics_InputWidget_(iInputWidget *d) { | ||
604 | iWidget *w = as_Widget(d); | ||
605 | updateSizeForFixedLength_InputWidget_(d); | ||
606 | /* Caller must arrange the width, but the height is set here. */ | ||
607 | const int oldHeight = height_Rect(w->rect); | ||
608 | w->rect.size.y = contentHeight_InputWidget_(d) + 3.0f * padding_().y; /* TODO: Why 3x? */ | ||
609 | if (flags_Widget(w) & extraPadding_WidgetFlag) { | ||
610 | w->rect.size.y += extraPaddingHeight_; | ||
611 | } | ||
612 | invalidateBuffered_InputWidget_(d); | ||
613 | if (height_Rect(w->rect) != oldHeight) { | ||
614 | postCommand_Widget(d, "input.resized"); | ||
615 | updateTextInputRect_InputWidget_(d); | ||
616 | } | ||
617 | } | ||
618 | |||
619 | static void updateLine_InputWidget_(iInputWidget *d, iInputLine *line) { | 677 | static void updateLine_InputWidget_(iInputWidget *d, iInputLine *line) { |
620 | iAssert(endsWith_String(&line->text, "\n") || isLastLine_InputWidget_(d, line)); | 678 | iAssert(endsWith_String(&line->text, "\n") || isLastLine_InputWidget_(d, line)); |
621 | iWrapText wrapText = wrap_InputWidget_(d, indexOf_Array(&d->lines, line)); | 679 | iWrapText wrapText = wrap_InputWidget_(d, indexOf_Array(&d->lines, line)); |
622 | if (wrapText.maxWidth <= 0) { | 680 | if (wrapText.maxWidth <= minWidth_InputWidget_) { |
623 | line->wrapLines.end = line->wrapLines.start + 1; | 681 | line->wrapLines.end = line->wrapLines.start + 1; |
624 | return; | 682 | return; |
625 | } | 683 | } |
@@ -668,7 +726,10 @@ static uint32_t cursorTimer_(uint32_t interval, void *w) { | |||
668 | return interval; | 726 | return interval; |
669 | } | 727 | } |
670 | 728 | ||
671 | static void startOrStopCursorTimer_InputWidget_(iInputWidget *d, iBool doStart) { | 729 | static void startOrStopCursorTimer_InputWidget_(iInputWidget *d, int doStart) { |
730 | if (!prefs_App()->blinkingCursor && doStart == 1) { | ||
731 | doStart = iFalse; | ||
732 | } | ||
672 | if (doStart && !d->timer) { | 733 | if (doStart && !d->timer) { |
673 | d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); | 734 | d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); |
674 | } | 735 | } |
@@ -678,6 +739,66 @@ static void startOrStopCursorTimer_InputWidget_(iInputWidget *d, iBool doStart) | |||
678 | } | 739 | } |
679 | } | 740 | } |
680 | 741 | ||
742 | #else /* using a system-provided text control */ | ||
743 | |||
744 | static void updateAllLinesAndResizeHeight_InputWidget_(iInputWidget *d) { | ||
745 | /* Rewrap the buffered text and resize accordingly. */ | ||
746 | iWrapText wt = wrap_InputWidget_(d, 0); | ||
747 | /* TODO: Set max lines limit for WrapText. */ | ||
748 | const int height = measure_WrapText(&wt, d->font).bounds.size.y; | ||
749 | /* We use this to store the number wrapped lines for determining widget height. */ | ||
750 | d->visWrapLines.start = 0; | ||
751 | d->visWrapLines.end = iMax(d->minWrapLines, | ||
752 | iMin(d->maxWrapLines, height / lineHeight_Text(d->font))); | ||
753 | updateMetrics_InputWidget_(d); | ||
754 | } | ||
755 | |||
756 | #endif | ||
757 | |||
758 | static int contentHeight_InputWidget_(const iInputWidget *d) { | ||
759 | const int lineHeight = lineHeight_Text(d->font); | ||
760 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
761 | const int minHeight = d->minWrapLines * lineHeight; | ||
762 | const int maxHeight = d->maxWrapLines * lineHeight; | ||
763 | if (d->sysCtrl) { | ||
764 | const int preferred = (preferredHeight_SystemTextInput(d->sysCtrl) + gap_UI) / lineHeight; | ||
765 | return iClamp(preferred * lineHeight, minHeight, maxHeight); | ||
766 | } | ||
767 | if (d->buffered && ~d->inFlags & needUpdateBuffer_InputWidgetFlag) { | ||
768 | return iClamp(d->buffered->size.y, minHeight, maxHeight); | ||
769 | } | ||
770 | #endif | ||
771 | return (int) size_Range(&d->visWrapLines) * lineHeight; | ||
772 | } | ||
773 | |||
774 | static void updateTextInputRect_InputWidget_(const iInputWidget *d) { | ||
775 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
776 | if (d->sysCtrl) { | ||
777 | setRect_SystemTextInput(d->sysCtrl, contentBounds_InputWidget_(d)); | ||
778 | } | ||
779 | #endif | ||
780 | #if !defined (iPlatformAppleMobile) | ||
781 | const iRect bounds = bounds_Widget(constAs_Widget(d)); | ||
782 | SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y }); | ||
783 | #endif | ||
784 | } | ||
785 | |||
786 | static void updateMetrics_InputWidget_(iInputWidget *d) { | ||
787 | iWidget *w = as_Widget(d); | ||
788 | updateSizeForFixedLength_InputWidget_(d); | ||
789 | /* Caller must arrange the width, but the height is set here. */ | ||
790 | const int oldHeight = height_Rect(w->rect); | ||
791 | w->rect.size.y = contentHeight_InputWidget_(d) + 3.0f * padding_().y; /* TODO: Why 3x? */ | ||
792 | if (flags_Widget(w) & extraPadding_WidgetFlag) { | ||
793 | w->rect.size.y += extraPaddingHeight_InputWidget_(d); | ||
794 | } | ||
795 | invalidateBuffered_InputWidget_(d); | ||
796 | if (height_Rect(w->rect) != oldHeight) { | ||
797 | postCommand_Widget(d, "input.resized"); | ||
798 | updateTextInputRect_InputWidget_(d); | ||
799 | } | ||
800 | } | ||
801 | |||
681 | void init_InputWidget(iInputWidget *d, size_t maxLen) { | 802 | void init_InputWidget(iInputWidget *d, size_t maxLen) { |
682 | iWidget *w = &d->widget; | 803 | iWidget *w = &d->widget; |
683 | init_Widget(w); | 804 | init_Widget(w); |
@@ -687,40 +808,44 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
687 | #if defined (iPlatformMobile) | 808 | #if defined (iPlatformMobile) |
688 | setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); | 809 | setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); |
689 | #endif | 810 | #endif |
811 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
812 | init_String(&d->text); | ||
813 | #else | ||
690 | init_Array(&d->lines, sizeof(iInputLine)); | 814 | init_Array(&d->lines, sizeof(iInputLine)); |
815 | init_Array(&d->undoStack, sizeof(iInputUndo)); | ||
816 | d->cursor = zero_I2(); | ||
817 | d->prevCursor = zero_I2(); | ||
818 | d->lastTapTime = 0; | ||
819 | d->tapCount = 0; | ||
820 | d->timer = 0; | ||
821 | d->cursorVis = 0; | ||
822 | iZap(d->mark); | ||
823 | splitToLines_(&iStringLiteral(""), &d->lines); | ||
824 | #endif | ||
691 | init_String(&d->oldText); | 825 | init_String(&d->oldText); |
692 | init_Array(&d->lines, sizeof(iInputLine)); | ||
693 | init_String(&d->srcHint); | 826 | init_String(&d->srcHint); |
694 | init_String(&d->hint); | 827 | init_String(&d->hint); |
695 | init_Array(&d->undoStack, sizeof(iInputUndo)); | ||
696 | d->font = uiInput_FontId | alwaysVariableFlag_FontId; | 828 | d->font = uiInput_FontId | alwaysVariableFlag_FontId; |
697 | d->leftPadding = 0; | 829 | d->leftPadding = 0; |
698 | d->rightPadding = 0; | 830 | d->rightPadding = 0; |
699 | d->cursor = zero_I2(); | ||
700 | d->prevCursor = zero_I2(); | ||
701 | d->lastUpdateWidth = 0; | 831 | d->lastUpdateWidth = 0; |
702 | d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag | | 832 | d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag | |
703 | lineBreaksEnabled_InputWidgetFlag | useReturnKeyBehavior_InputWidgetFlag; | 833 | lineBreaksEnabled_InputWidgetFlag | useReturnKeyBehavior_InputWidgetFlag; |
704 | // if (deviceType_App() != desktop_AppDeviceType) { | 834 | // if (deviceType_App() != desktop_AppDeviceType) { |
705 | // d->inFlags |= enterKeyInsertsLineFeed_InputWidgetFlag; | 835 | // d->inFlags |= enterKeyInsertsLineFeed_InputWidgetFlag; |
706 | // } | 836 | // } |
707 | iZap(d->mark); | ||
708 | setMaxLen_InputWidget(d, maxLen); | 837 | setMaxLen_InputWidget(d, maxLen); |
709 | d->visWrapLines.start = 0; | 838 | d->visWrapLines.start = 0; |
710 | d->visWrapLines.end = 1; | 839 | d->visWrapLines.end = 1; |
711 | d->maxWrapLines = maxLen > 0 ? 1 : 20; /* TODO: Choose maximum dynamically? */ | 840 | d->maxWrapLines = maxLen > 0 ? 1 : 20; /* TODO: Choose maximum dynamically? */ |
712 | d->minWrapLines = 1; | 841 | d->minWrapLines = 1; |
713 | splitToLines_(&iStringLiteral(""), &d->lines); | ||
714 | setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */ | 842 | setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */ |
715 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 843 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
716 | d->lastTapTime = 0; | ||
717 | d->tapCount = 0; | ||
718 | d->wheelAccum = 0; | 844 | d->wheelAccum = 0; |
719 | d->timer = 0; | ||
720 | d->cursorVis = 0; | ||
721 | d->buffered = NULL; | 845 | d->buffered = NULL; |
722 | d->backupPath = NULL; | 846 | d->backupPath = NULL; |
723 | d->backupTimer = 0; | 847 | d->backupTimer = 0; |
848 | d->sysCtrl = NULL; | ||
724 | updateMetrics_InputWidget_(d); | 849 | updateMetrics_InputWidget_(d); |
725 | } | 850 | } |
726 | 851 | ||
@@ -733,26 +858,48 @@ void deinit_InputWidget(iInputWidget *d) { | |||
733 | } | 858 | } |
734 | delete_String(d->backupPath); | 859 | delete_String(d->backupPath); |
735 | d->backupPath = NULL; | 860 | d->backupPath = NULL; |
861 | delete_TextBuf(d->buffered); | ||
862 | deinit_String(&d->srcHint); | ||
863 | deinit_String(&d->hint); | ||
864 | deinit_String(&d->oldText); | ||
865 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
866 | delete_SystemTextInput(d->sysCtrl); | ||
867 | deinit_String(&d->text); | ||
868 | #else | ||
869 | startOrStopCursorTimer_InputWidget_(d, iFalse); | ||
736 | clearInputLines_(&d->lines); | 870 | clearInputLines_(&d->lines); |
737 | if (isSelected_Widget(d)) { | 871 | if (isSelected_Widget(d)) { |
738 | SDL_StopTextInput(); | 872 | SDL_StopTextInput(); |
739 | enableEditorKeysInMenus_(iTrue); | 873 | enableEditorKeysInMenus_(iTrue); |
740 | } | 874 | } |
741 | delete_TextBuf(d->buffered); | ||
742 | clearUndo_InputWidget_(d); | 875 | clearUndo_InputWidget_(d); |
743 | deinit_Array(&d->undoStack); | 876 | deinit_Array(&d->undoStack); |
744 | startOrStopCursorTimer_InputWidget_(d, iFalse); | ||
745 | deinit_String(&d->srcHint); | ||
746 | deinit_String(&d->hint); | ||
747 | deinit_String(&d->oldText); | ||
748 | deinit_Array(&d->lines); | 877 | deinit_Array(&d->lines); |
878 | #endif | ||
879 | } | ||
880 | |||
881 | static iBool isAllowedToInsertNewline_InputWidget_(const iInputWidget *d) { | ||
882 | return ~d->inFlags & isSensitive_InputWidgetFlag && | ||
883 | ~d->inFlags & isUrl_InputWidgetFlag && | ||
884 | d->inFlags & lineBreaksEnabled_InputWidgetFlag && d->maxLen == 0; | ||
749 | } | 885 | } |
750 | 886 | ||
887 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
888 | static void updateAfterVisualOffsetChange_InputWidget_(iInputWidget *d, iRoot *root) { | ||
889 | iAssert(as_Widget(d)->root == root); | ||
890 | iUnused(root); | ||
891 | if (d->sysCtrl) { | ||
892 | setRect_SystemTextInput(d->sysCtrl, contentBounds_InputWidget_(d)); | ||
893 | } | ||
894 | } | ||
895 | #endif | ||
896 | |||
751 | void setFont_InputWidget(iInputWidget *d, int fontId) { | 897 | void setFont_InputWidget(iInputWidget *d, int fontId) { |
752 | d->font = fontId; | 898 | d->font = fontId; |
753 | updateMetrics_InputWidget_(d); | 899 | updateMetrics_InputWidget_(d); |
754 | } | 900 | } |
755 | 901 | ||
902 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
756 | static void pushUndo_InputWidget_(iInputWidget *d) { | 903 | static void pushUndo_InputWidget_(iInputWidget *d) { |
757 | iInputUndo undo; | 904 | iInputUndo undo; |
758 | init_InputUndo_(&undo, &d->lines, d->cursor); | 905 | init_InputUndo_(&undo, &d->lines, d->cursor); |
@@ -766,7 +913,6 @@ static void pushUndo_InputWidget_(iInputWidget *d) { | |||
766 | static iBool popUndo_InputWidget_(iInputWidget *d) { | 913 | static iBool popUndo_InputWidget_(iInputWidget *d) { |
767 | if (!isEmpty_Array(&d->undoStack)) { | 914 | if (!isEmpty_Array(&d->undoStack)) { |
768 | iInputUndo *undo = back_Array(&d->undoStack); | 915 | iInputUndo *undo = back_Array(&d->undoStack); |
769 | //setCopy_Array(&d->text, &undo->text); | ||
770 | splitToLines_(&undo->text, &d->lines); | 916 | splitToLines_(&undo->text, &d->lines); |
771 | d->cursor = undo->cursor; | 917 | d->cursor = undo->cursor; |
772 | deinit_InputUndo_(undo); | 918 | deinit_InputUndo_(undo); |
@@ -778,6 +924,43 @@ static iBool popUndo_InputWidget_(iInputWidget *d) { | |||
778 | return iFalse; | 924 | return iFalse; |
779 | } | 925 | } |
780 | 926 | ||
927 | iLocalDef iInputLine *cursorLine_InputWidget_(iInputWidget *d) { | ||
928 | return at_Array(&d->lines, d->cursor.y); | ||
929 | } | ||
930 | |||
931 | iLocalDef const iInputLine *constCursorLine_InputWidget_(const iInputWidget *d) { | ||
932 | return constAt_Array(&d->lines, d->cursor.y); | ||
933 | } | ||
934 | |||
935 | iLocalDef iInt2 cursorMax_InputWidget_(const iInputWidget *d) { | ||
936 | const int yLast = size_Array(&d->lines) - 1; | ||
937 | return init_I2(endX_InputWidget_(d, yLast), yLast); | ||
938 | } | ||
939 | |||
940 | static size_t cursorToIndex_InputWidget_(const iInputWidget *d, iInt2 pos) { | ||
941 | if (pos.y < 0) { | ||
942 | return 0; | ||
943 | } | ||
944 | if (pos.y >= size_Array(&d->lines)) { | ||
945 | return lastLine_InputWidget_(d)->range.end; | ||
946 | } | ||
947 | const iInputLine *line = line_InputWidget_(d, pos.y); | ||
948 | pos.x = iClamp(pos.x, 0, endX_InputWidget_(d, pos.y)); | ||
949 | return line->range.start + pos.x; | ||
950 | } | ||
951 | |||
952 | static iInt2 indexToCursor_InputWidget_(const iInputWidget *d, size_t index) { | ||
953 | /* TODO: The lines are sorted; this could use a binary search. */ | ||
954 | iConstForEach(Array, i, &d->lines) { | ||
955 | const iInputLine *line = i.value; | ||
956 | if (contains_Range(&line->range, index)) { | ||
957 | return init_I2(index - line->range.start, index_ArrayConstIterator(&i)); | ||
958 | } | ||
959 | } | ||
960 | return cursorMax_InputWidget_(d); | ||
961 | } | ||
962 | #endif | ||
963 | |||
781 | void setMode_InputWidget(iInputWidget *d, enum iInputMode mode) { | 964 | void setMode_InputWidget(iInputWidget *d, enum iInputMode mode) { |
782 | d->mode = mode; | 965 | d->mode = mode; |
783 | } | 966 | } |
@@ -876,7 +1059,11 @@ void setContentPadding_InputWidget(iInputWidget *d, int left, int right) { | |||
876 | } | 1059 | } |
877 | 1060 | ||
878 | iLocalDef iBool isEmpty_InputWidget_(const iInputWidget *d) { | 1061 | iLocalDef iBool isEmpty_InputWidget_(const iInputWidget *d) { |
1062 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
1063 | return isEmpty_String(&d->text); | ||
1064 | #else | ||
879 | return size_Array(&d->lines) == 1 && isEmpty_String(&line_InputWidget_(d, 0)->text); | 1065 | return size_Array(&d->lines) == 1 && isEmpty_String(&line_InputWidget_(d, 0)->text); |
1066 | #endif | ||
880 | } | 1067 | } |
881 | 1068 | ||
882 | static iBool isHintVisible_InputWidget_(const iInputWidget *d) { | 1069 | static iBool isHintVisible_InputWidget_(const iInputWidget *d) { |
@@ -890,11 +1077,15 @@ static void updateBuffered_InputWidget_(iInputWidget *d) { | |||
890 | } | 1077 | } |
891 | else { | 1078 | else { |
892 | /* Draw all the potentially visible lines to a buffer. */ | 1079 | /* Draw all the potentially visible lines to a buffer. */ |
1080 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
1081 | iString *visText = copy_String(&d->text); | ||
1082 | #else | ||
893 | iString *visText = new_String(); | 1083 | iString *visText = new_String(); |
894 | const iRangei visRange = visibleLineRange_InputWidget_(d); | 1084 | const iRangei visRange = visibleLineRange_InputWidget_(d); |
895 | for (int i = visRange.start; i < visRange.end; i++) { | 1085 | for (int i = visRange.start; i < visRange.end; i++) { |
896 | append_String(visText, &line_InputWidget_(d, i)->text); | 1086 | append_String(visText, &line_InputWidget_(d, i)->text); |
897 | } | 1087 | } |
1088 | #endif | ||
898 | if (d->inFlags & isUrl_InputWidgetFlag) { | 1089 | if (d->inFlags & isUrl_InputWidgetFlag) { |
899 | /* Highlight the host name. */ | 1090 | /* Highlight the host name. */ |
900 | iUrl parts; | 1091 | iUrl parts; |
@@ -912,6 +1103,7 @@ static void updateBuffered_InputWidget_(iInputWidget *d) { | |||
912 | } | 1103 | } |
913 | } | 1104 | } |
914 | iWrapText wt = wrap_InputWidget_(d, 0); | 1105 | iWrapText wt = wrap_InputWidget_(d, 0); |
1106 | wt.maxLines = d->maxWrapLines; | ||
915 | wt.text = range_String(visText); | 1107 | wt.text = range_String(visText); |
916 | const int fg = uiInputText_ColorId; | 1108 | const int fg = uiInputText_ColorId; |
917 | d->buffered = new_TextBuf(&wt, d->font, fg); | 1109 | d->buffered = new_TextBuf(&wt, d->font, fg); |
@@ -920,25 +1112,18 @@ static void updateBuffered_InputWidget_(iInputWidget *d) { | |||
920 | d->inFlags &= ~needUpdateBuffer_InputWidgetFlag; | 1112 | d->inFlags &= ~needUpdateBuffer_InputWidgetFlag; |
921 | } | 1113 | } |
922 | 1114 | ||
923 | iLocalDef iInputLine *cursorLine_InputWidget_(iInputWidget *d) { | ||
924 | return at_Array(&d->lines, d->cursor.y); | ||
925 | } | ||
926 | |||
927 | iLocalDef const iInputLine *constCursorLine_InputWidget_(const iInputWidget *d) { | ||
928 | return constAt_Array(&d->lines, d->cursor.y); | ||
929 | } | ||
930 | |||
931 | iLocalDef iInt2 cursorMax_InputWidget_(const iInputWidget *d) { | ||
932 | const int yLast = size_Array(&d->lines) - 1; | ||
933 | return init_I2(endX_InputWidget_(d, yLast), yLast); | ||
934 | } | ||
935 | |||
936 | void setText_InputWidget(iInputWidget *d, const iString *text) { | 1115 | void setText_InputWidget(iInputWidget *d, const iString *text) { |
937 | if (!d) return; | 1116 | if (!d) return; |
938 | if (d->inFlags & isUrl_InputWidgetFlag) { | 1117 | if (d->inFlags & isUrl_InputWidgetFlag) { |
939 | /* If user wants URLs encoded, also Punycode the domain. */ | 1118 | if (prefs_App()->decodeUserVisibleURLs) { |
940 | if (!prefs_App()->decodeUserVisibleURLs) { | 1119 | iString *enc = collect_String(copy_String(text)); |
1120 | urlDecodePath_String(enc); | ||
1121 | text = enc; | ||
1122 | } | ||
1123 | else { | ||
1124 | /* The user wants URLs encoded, also Punycode the domain. */ | ||
941 | iString *enc = collect_String(copy_String(text)); | 1125 | iString *enc = collect_String(copy_String(text)); |
1126 | urlEncodePath_String(enc); | ||
942 | /* Prevent address bar spoofing (mentioned as IDN homograph attack in | 1127 | /* Prevent address bar spoofing (mentioned as IDN homograph attack in |
943 | https://github.com/skyjake/lagrange/issues/73) */ | 1128 | https://github.com/skyjake/lagrange/issues/73) */ |
944 | punyEncodeUrlHost_String(enc); | 1129 | punyEncodeUrlHost_String(enc); |
@@ -949,9 +1134,10 @@ void setText_InputWidget(iInputWidget *d, const iString *text) { | |||
949 | text = omitDefaultScheme_(collect_String(copy_String(text))); | 1134 | text = omitDefaultScheme_(collect_String(copy_String(text))); |
950 | } | 1135 | } |
951 | } | 1136 | } |
952 | clearUndo_InputWidget_(d); | ||
953 | iString *nfcText = collect_String(copy_String(text)); | 1137 | iString *nfcText = collect_String(copy_String(text)); |
954 | normalize_String(nfcText); | 1138 | normalize_String(nfcText); |
1139 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
1140 | clearUndo_InputWidget_(d); | ||
955 | splitToLines_(nfcText, &d->lines); | 1141 | splitToLines_(nfcText, &d->lines); |
956 | iAssert(!isEmpty_Array(&d->lines)); | 1142 | iAssert(!isEmpty_Array(&d->lines)); |
957 | iForEach(Array, i, &d->lines) { | 1143 | iForEach(Array, i, &d->lines) { |
@@ -962,12 +1148,23 @@ void setText_InputWidget(iInputWidget *d, const iString *text) { | |||
962 | if (!isFocused_Widget(d)) { | 1148 | if (!isFocused_Widget(d)) { |
963 | iZap(d->mark); | 1149 | iZap(d->mark); |
964 | } | 1150 | } |
1151 | #else | ||
1152 | set_String(&d->text, nfcText); | ||
1153 | if (d->sysCtrl) { | ||
1154 | setText_SystemTextInput(d->sysCtrl, nfcText, iTrue); | ||
1155 | } | ||
1156 | else { | ||
1157 | updateAllLinesAndResizeHeight_InputWidget_(d); /* need to know the new height */ | ||
1158 | } | ||
1159 | #endif | ||
965 | if (!isFocused_Widget(d)) { | 1160 | if (!isFocused_Widget(d)) { |
966 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | 1161 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; |
967 | } | 1162 | } |
968 | updateVisible_InputWidget_(d); | 1163 | updateVisible_InputWidget_(d); |
969 | updateMetrics_InputWidget_(d); | 1164 | updateMetrics_InputWidget_(d); |
970 | refresh_Widget(as_Widget(d)); | 1165 | if (!d->sysCtrl) { |
1166 | refresh_Widget(as_Widget(d)); | ||
1167 | } | ||
971 | } | 1168 | } |
972 | 1169 | ||
973 | void setTextCStr_InputWidget(iInputWidget *d, const char *cstr) { | 1170 | void setTextCStr_InputWidget(iInputWidget *d, const char *cstr) { |
@@ -976,38 +1173,39 @@ void setTextCStr_InputWidget(iInputWidget *d, const char *cstr) { | |||
976 | delete_String(str); | 1173 | delete_String(str); |
977 | } | 1174 | } |
978 | 1175 | ||
979 | static size_t cursorToIndex_InputWidget_(const iInputWidget *d, iInt2 pos) { | ||
980 | if (pos.y < 0) { | ||
981 | return 0; | ||
982 | } | ||
983 | if (pos.y >= size_Array(&d->lines)) { | ||
984 | return lastLine_InputWidget_(d)->range.end; | ||
985 | } | ||
986 | const iInputLine *line = line_InputWidget_(d, pos.y); | ||
987 | pos.x = iClamp(pos.x, 0, endX_InputWidget_(d, pos.y)); | ||
988 | return line->range.start + pos.x; | ||
989 | } | ||
990 | |||
991 | static iInt2 indexToCursor_InputWidget_(const iInputWidget *d, size_t index) { | ||
992 | /* TODO: The lines are sorted; this could use a binary search. */ | ||
993 | iConstForEach(Array, i, &d->lines) { | ||
994 | const iInputLine *line = i.value; | ||
995 | if (contains_Range(&line->range, index)) { | ||
996 | return init_I2(index - line->range.start, index_ArrayConstIterator(&i)); | ||
997 | } | ||
998 | } | ||
999 | return cursorMax_InputWidget_(d); | ||
1000 | } | ||
1001 | |||
1002 | void selectAll_InputWidget(iInputWidget *d) { | 1176 | void selectAll_InputWidget(iInputWidget *d) { |
1177 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
1178 | if (d->sysCtrl) { | ||
1179 | selectAll_SystemTextInput(d->sysCtrl); | ||
1180 | } | ||
1181 | #else | ||
1003 | d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; | 1182 | d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; |
1004 | refresh_Widget(as_Widget(d)); | 1183 | refresh_Widget(as_Widget(d)); |
1184 | #endif | ||
1185 | } | ||
1186 | |||
1187 | void validate_InputWidget(iInputWidget *d) { | ||
1188 | if (d->validator) { | ||
1189 | d->validator(d, d->validatorContext); /* this may change the contents */ | ||
1190 | } | ||
1005 | } | 1191 | } |
1006 | 1192 | ||
1007 | iLocalDef iBool isEditing_InputWidget_(const iInputWidget *d) { | 1193 | iLocalDef iBool isEditing_InputWidget_(const iInputWidget *d) { |
1008 | return (flags_Widget(constAs_Widget(d)) & selected_WidgetFlag) != 0; | 1194 | return (flags_Widget(constAs_Widget(d)) & selected_WidgetFlag) != 0; |
1009 | } | 1195 | } |
1010 | 1196 | ||
1197 | static void contentsWereChanged_InputWidget_(iInputWidget *); | ||
1198 | |||
1199 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
1200 | void systemInputChanged_InputWidget_(iSystemTextInput *sysCtrl, void *widget) { | ||
1201 | iInputWidget *d = widget; | ||
1202 | set_String(&d->text, text_SystemTextInput(sysCtrl)); | ||
1203 | restartBackupTimer_InputWidget_(d); | ||
1204 | contentsWereChanged_InputWidget_(d); | ||
1205 | updateMetrics_InputWidget_(d); | ||
1206 | } | ||
1207 | #endif | ||
1208 | |||
1011 | void begin_InputWidget(iInputWidget *d) { | 1209 | void begin_InputWidget(iInputWidget *d) { |
1012 | iWidget *w = as_Widget(d); | 1210 | iWidget *w = as_Widget(d); |
1013 | if (isEditing_InputWidget_(d)) { | 1211 | if (isEditing_InputWidget_(d)) { |
@@ -1016,6 +1214,29 @@ void begin_InputWidget(iInputWidget *d) { | |||
1016 | } | 1214 | } |
1017 | invalidateBuffered_InputWidget_(d); | 1215 | invalidateBuffered_InputWidget_(d); |
1018 | setFlags_Widget(w, hidden_WidgetFlag | disabled_WidgetFlag, iFalse); | 1216 | setFlags_Widget(w, hidden_WidgetFlag | disabled_WidgetFlag, iFalse); |
1217 | setFlags_Widget(w, selected_WidgetFlag, iTrue); | ||
1218 | d->inFlags &= ~enterPressed_InputWidgetFlag; | ||
1219 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
1220 | set_String(&d->oldText, &d->text); | ||
1221 | d->sysCtrl = new_SystemTextInput( | ||
1222 | contentBounds_InputWidget_(d), | ||
1223 | (d->maxWrapLines > 1 ? multiLine_SystemTextInputFlags : 0) | | ||
1224 | (d->inFlags & isUrl_InputWidgetFlag ? (disableAutocorrect_SystemTextInputFlag | | ||
1225 | disableAutocapitalize_SystemTextInputFlag) | ||
1226 | : 0) | | ||
1227 | /* widget-specific tweaks (hacks) */ | ||
1228 | (!cmp_String(id_Widget(w), "url") ? returnGo_SystemTextInputFlags : 0) | | ||
1229 | (!cmp_String(id_Widget(w), "upload.text") ? extraPadding_SystemTextInputFlag : 0) | | ||
1230 | (flags_Widget(w) & alignRight_WidgetFlag ? alignRight_SystemTextInputFlag : 0) | | ||
1231 | (isAllowedToInsertNewline_InputWidget_(d) ? insertNewlines_SystemTextInputFlag : 0) | | ||
1232 | (d->inFlags & selectAllOnFocus_InputWidgetFlag ? selectAll_SystemTextInputFlags : 0)); | ||
1233 | setFont_SystemTextInput(d->sysCtrl, d->font); | ||
1234 | setText_SystemTextInput(d->sysCtrl, &d->oldText, iFalse); | ||
1235 | setTextChangedFunc_SystemTextInput(d->sysCtrl, systemInputChanged_InputWidget_, d); | ||
1236 | iConnect(Root, w->root, visualOffsetsChanged, d, updateAfterVisualOffsetChange_InputWidget_); | ||
1237 | updateTextInputRect_InputWidget_(d); | ||
1238 | updateMetrics_InputWidget_(d); | ||
1239 | #else | ||
1019 | mergeLines_(&d->lines, &d->oldText); | 1240 | mergeLines_(&d->lines, &d->oldText); |
1020 | if (d->mode == overwrite_InputMode) { | 1241 | if (d->mode == overwrite_InputMode) { |
1021 | d->cursor = zero_I2(); | 1242 | d->cursor = zero_I2(); |
@@ -1025,11 +1246,9 @@ void begin_InputWidget(iInputWidget *d) { | |||
1025 | d->cursor.x = iMin(d->cursor.x, cursorLine_InputWidget_(d)->range.end); | 1246 | d->cursor.x = iMin(d->cursor.x, cursorLine_InputWidget_(d)->range.end); |
1026 | } | 1247 | } |
1027 | SDL_StartTextInput(); | 1248 | SDL_StartTextInput(); |
1028 | setFlags_Widget(w, selected_WidgetFlag, iTrue); | ||
1029 | showCursor_InputWidget_(d); | 1249 | showCursor_InputWidget_(d); |
1030 | refresh_Widget(w); | 1250 | refresh_Widget(w); |
1031 | startOrStopCursorTimer_InputWidget_(d, iTrue); | 1251 | startOrStopCursorTimer_InputWidget_(d, iTrue); |
1032 | d->inFlags &= ~enterPressed_InputWidgetFlag; | ||
1033 | if (d->inFlags & selectAllOnFocus_InputWidgetFlag) { | 1252 | if (d->inFlags & selectAllOnFocus_InputWidgetFlag) { |
1034 | d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; | 1253 | d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; |
1035 | d->cursor = cursorMax_InputWidget_(d); | 1254 | d->cursor = cursorMax_InputWidget_(d); |
@@ -1040,6 +1259,7 @@ void begin_InputWidget(iInputWidget *d) { | |||
1040 | enableEditorKeysInMenus_(iFalse); | 1259 | enableEditorKeysInMenus_(iFalse); |
1041 | updateTextInputRect_InputWidget_(d); | 1260 | updateTextInputRect_InputWidget_(d); |
1042 | updateVisible_InputWidget_(d); | 1261 | updateVisible_InputWidget_(d); |
1262 | #endif | ||
1043 | } | 1263 | } |
1044 | 1264 | ||
1045 | void end_InputWidget(iInputWidget *d, iBool accept) { | 1265 | void end_InputWidget(iInputWidget *d, iBool accept) { |
@@ -1048,15 +1268,29 @@ void end_InputWidget(iInputWidget *d, iBool accept) { | |||
1048 | /* Was not active. */ | 1268 | /* Was not active. */ |
1049 | return; | 1269 | return; |
1050 | } | 1270 | } |
1051 | enableEditorKeysInMenus_(iTrue); | 1271 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT |
1272 | if (d->sysCtrl) { | ||
1273 | iDisconnect(Root, w->root, visualOffsetsChanged, d, updateAfterVisualOffsetChange_InputWidget_); | ||
1274 | if (accept) { | ||
1275 | set_String(&d->text, text_SystemTextInput(d->sysCtrl)); | ||
1276 | } | ||
1277 | else { | ||
1278 | set_String(&d->text, &d->oldText); | ||
1279 | } | ||
1280 | delete_SystemTextInput(d->sysCtrl); | ||
1281 | d->sysCtrl = NULL; | ||
1282 | } | ||
1283 | #else | ||
1052 | if (!accept) { | 1284 | if (!accept) { |
1053 | /* Overwrite the edited lines. */ | 1285 | /* Overwrite the edited lines. */ |
1054 | splitToLines_(&d->oldText, &d->lines); | 1286 | splitToLines_(&d->oldText, &d->lines); |
1055 | } | 1287 | } |
1056 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | 1288 | SDL_StopTextInput(); |
1289 | enableEditorKeysInMenus_(iTrue); | ||
1057 | d->inFlags &= ~isMarking_InputWidgetFlag; | 1290 | d->inFlags &= ~isMarking_InputWidgetFlag; |
1058 | startOrStopCursorTimer_InputWidget_(d, iFalse); | 1291 | startOrStopCursorTimer_InputWidget_(d, iFalse); |
1059 | SDL_StopTextInput(); | 1292 | #endif |
1293 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | ||
1060 | setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag | touchDrag_WidgetFlag, iFalse); | 1294 | setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag | touchDrag_WidgetFlag, iFalse); |
1061 | const char *id = cstr_String(id_Widget(as_Widget(d))); | 1295 | const char *id = cstr_String(id_Widget(as_Widget(d))); |
1062 | if (!*id) id = "_"; | 1296 | if (!*id) id = "_"; |
@@ -1068,6 +1302,7 @@ void end_InputWidget(iInputWidget *d, iBool accept) { | |||
1068 | accept ? 1 : 0); | 1302 | accept ? 1 : 0); |
1069 | } | 1303 | } |
1070 | 1304 | ||
1305 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
1071 | static void textOfLinesWasChanged_InputWidget_(iInputWidget *d, iRangei lineRange) { | 1306 | static void textOfLinesWasChanged_InputWidget_(iInputWidget *d, iRangei lineRange) { |
1072 | for (int i = lineRange.start; i < lineRange.end; i++) { | 1307 | for (int i = lineRange.start; i < lineRange.end; i++) { |
1073 | updateLine_InputWidget_(d, at_Array(&d->lines, i)); | 1308 | updateLine_InputWidget_(d, at_Array(&d->lines, i)); |
@@ -1201,27 +1436,6 @@ static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir, int horiz) | |||
1201 | return iTrue; | 1436 | return iTrue; |
1202 | } | 1437 | } |
1203 | 1438 | ||
1204 | void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) { | ||
1205 | iChangeFlags(d->inFlags, isSensitive_InputWidgetFlag, isSensitive); | ||
1206 | } | ||
1207 | |||
1208 | void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) { | ||
1209 | iChangeFlags(d->inFlags, isUrl_InputWidgetFlag, isUrl); | ||
1210 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | ||
1211 | } | ||
1212 | |||
1213 | void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) { | ||
1214 | iChangeFlags(d->inFlags, selectAllOnFocus_InputWidgetFlag, selectAllOnFocus); | ||
1215 | } | ||
1216 | |||
1217 | void setNotifyEdits_InputWidget(iInputWidget *d, iBool notifyEdits) { | ||
1218 | iChangeFlags(d->inFlags, notifyEdits_InputWidgetFlag, notifyEdits); | ||
1219 | } | ||
1220 | |||
1221 | void setEatEscape_InputWidget(iInputWidget *d, iBool eatEscape) { | ||
1222 | iChangeFlags(d->inFlags, eatEscape_InputWidgetFlag, eatEscape); | ||
1223 | } | ||
1224 | |||
1225 | static iRanges mark_InputWidget_(const iInputWidget *d) { | 1439 | static iRanges mark_InputWidget_(const iInputWidget *d) { |
1226 | iRanges m = { iMin(d->mark.start, d->mark.end), iMax(d->mark.start, d->mark.end) }; | 1440 | iRanges m = { iMin(d->mark.start, d->mark.end), iMax(d->mark.start, d->mark.end) }; |
1227 | const iInputLine *last = lastLine_InputWidget_(d); | 1441 | const iInputLine *last = lastLine_InputWidget_(d); |
@@ -1230,15 +1444,6 @@ static iRanges mark_InputWidget_(const iInputWidget *d) { | |||
1230 | return m; | 1444 | return m; |
1231 | } | 1445 | } |
1232 | 1446 | ||
1233 | static void contentsWereChanged_InputWidget_(iInputWidget *d) { | ||
1234 | if (d->validator) { | ||
1235 | d->validator(d, d->validatorContext); /* this may change the contents */ | ||
1236 | } | ||
1237 | if (d->inFlags & notifyEdits_InputWidgetFlag) { | ||
1238 | postCommand_Widget(d, "input.edited id:%s", cstr_String(id_Widget(constAs_Widget(d)))); | ||
1239 | } | ||
1240 | } | ||
1241 | |||
1242 | static void deleteIndexRange_InputWidget_(iInputWidget *d, iRanges deleted) { | 1447 | static void deleteIndexRange_InputWidget_(iInputWidget *d, iRanges deleted) { |
1243 | size_t firstModified = iInvalidPos; | 1448 | size_t firstModified = iInvalidPos; |
1244 | restartBackupTimer_InputWidget_(d); | 1449 | restartBackupTimer_InputWidget_(d); |
@@ -1364,7 +1569,7 @@ static iInt2 coordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) { | |||
1364 | // return cursorMax_InputWidget_(d); | 1569 | // return cursorMax_InputWidget_(d); |
1365 | // } | 1570 | // } |
1366 | iWrapText wrapText = { | 1571 | iWrapText wrapText = { |
1367 | .maxWidth = d->maxLen == 0 ? width_Rect(bounds) : unlimitedWidth_InputWidget_, | 1572 | .maxWidth = d->maxLen == 0 ? iMaxi(minWidth_InputWidget_, width_Rect(bounds)) : unlimitedWidth_InputWidget_, |
1368 | .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode), | 1573 | .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode), |
1369 | .hitPoint = relCoord, | 1574 | .hitPoint = relCoord, |
1370 | .overrideChar = (d->inFlags & isSensitive_InputWidgetFlag ? sensitiveChar_ : 0), | 1575 | .overrideChar = (d->inFlags & isSensitive_InputWidgetFlag ? sensitiveChar_ : 0), |
@@ -1447,6 +1652,40 @@ static void extendRange_InputWidget_(iInputWidget *d, size_t *index, int dir) { | |||
1447 | *index = cursorToIndex_InputWidget_(d, pos); | 1652 | *index = cursorToIndex_InputWidget_(d, pos); |
1448 | } | 1653 | } |
1449 | 1654 | ||
1655 | static void lineTextWasChanged_InputWidget_(iInputWidget *d, iInputLine *line) { | ||
1656 | const int y = indexOf_Array(&d->lines, line); | ||
1657 | textOfLinesWasChanged_InputWidget_(d, (iRangei){ y, y + 1 }); | ||
1658 | } | ||
1659 | #endif | ||
1660 | |||
1661 | void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) { | ||
1662 | iChangeFlags(d->inFlags, isSensitive_InputWidgetFlag, isSensitive); | ||
1663 | } | ||
1664 | |||
1665 | void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) { | ||
1666 | iChangeFlags(d->inFlags, isUrl_InputWidgetFlag, isUrl); | ||
1667 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | ||
1668 | } | ||
1669 | |||
1670 | void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) { | ||
1671 | iChangeFlags(d->inFlags, selectAllOnFocus_InputWidgetFlag, selectAllOnFocus); | ||
1672 | } | ||
1673 | |||
1674 | void setNotifyEdits_InputWidget(iInputWidget *d, iBool notifyEdits) { | ||
1675 | iChangeFlags(d->inFlags, notifyEdits_InputWidgetFlag, notifyEdits); | ||
1676 | } | ||
1677 | |||
1678 | void setEatEscape_InputWidget(iInputWidget *d, iBool eatEscape) { | ||
1679 | iChangeFlags(d->inFlags, eatEscape_InputWidgetFlag, eatEscape); | ||
1680 | } | ||
1681 | |||
1682 | static void contentsWereChanged_InputWidget_(iInputWidget *d) { | ||
1683 | validate_InputWidget(d); | ||
1684 | if (d->inFlags & notifyEdits_InputWidgetFlag) { | ||
1685 | postCommand_Widget(d, "input.edited id:%s", cstr_String(id_Widget(constAs_Widget(d)))); | ||
1686 | } | ||
1687 | } | ||
1688 | |||
1450 | static iRect bounds_InputWidget_(const iInputWidget *d) { | 1689 | static iRect bounds_InputWidget_(const iInputWidget *d) { |
1451 | const iWidget *w = constAs_Widget(d); | 1690 | const iWidget *w = constAs_Widget(d); |
1452 | iRect bounds = bounds_Widget(w); | 1691 | iRect bounds = bounds_Widget(w); |
@@ -1456,7 +1695,7 @@ static iRect bounds_InputWidget_(const iInputWidget *d) { | |||
1456 | /* There may be more visible lines than fits in the widget bounds. */ | 1695 | /* There may be more visible lines than fits in the widget bounds. */ |
1457 | bounds.size.y = contentHeight_InputWidget_(d) + 3 * padding_().y; | 1696 | bounds.size.y = contentHeight_InputWidget_(d) + 3 * padding_().y; |
1458 | if (w->flags & extraPadding_WidgetFlag) { | 1697 | if (w->flags & extraPadding_WidgetFlag) { |
1459 | bounds.size.y += extraPaddingHeight_; | 1698 | bounds.size.y += extraPaddingHeight_InputWidget_(d); |
1460 | } | 1699 | } |
1461 | return bounds; | 1700 | return bounds; |
1462 | } | 1701 | } |
@@ -1465,11 +1704,6 @@ static iBool contains_InputWidget_(const iInputWidget *d, iInt2 coord) { | |||
1465 | return contains_Rect(bounds_InputWidget_(d), coord); | 1704 | return contains_Rect(bounds_InputWidget_(d), coord); |
1466 | } | 1705 | } |
1467 | 1706 | ||
1468 | static void lineTextWasChanged_InputWidget_(iInputWidget *d, iInputLine *line) { | ||
1469 | const int y = indexOf_Array(&d->lines, line); | ||
1470 | textOfLinesWasChanged_InputWidget_(d, (iRangei){ y, y + 1 }); | ||
1471 | } | ||
1472 | |||
1473 | static iBool isArrowUpDownConsumed_InputWidget_(const iInputWidget *d) { | 1707 | static iBool isArrowUpDownConsumed_InputWidget_(const iInputWidget *d) { |
1474 | return d->maxWrapLines > 1; | 1708 | return d->maxWrapLines > 1; |
1475 | } | 1709 | } |
@@ -1494,6 +1728,7 @@ enum iEventResult { | |||
1494 | true_EventResult = 2, /* event was processed and should not be passed on */ | 1728 | true_EventResult = 2, /* event was processed and should not be passed on */ |
1495 | }; | 1729 | }; |
1496 | 1730 | ||
1731 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
1497 | static void markWordAtCursor_InputWidget_(iInputWidget *d) { | 1732 | static void markWordAtCursor_InputWidget_(iInputWidget *d) { |
1498 | d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); | 1733 | d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); |
1499 | extendRange_InputWidget_(d, &d->mark.start, -1); | 1734 | extendRange_InputWidget_(d, &d->mark.start, -1); |
@@ -1510,8 +1745,10 @@ static void showClipMenu_(iInt2 coord) { | |||
1510 | openMenuFlags_Widget(clipMenu, coord, iFalse); | 1745 | openMenuFlags_Widget(clipMenu, coord, iFalse); |
1511 | } | 1746 | } |
1512 | } | 1747 | } |
1748 | #endif | ||
1513 | 1749 | ||
1514 | static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 1750 | static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { |
1751 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
1515 | iWidget *w = as_Widget(d); | 1752 | iWidget *w = as_Widget(d); |
1516 | if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { | 1753 | if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { |
1517 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); | 1754 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); |
@@ -1585,9 +1822,11 @@ static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, cons | |||
1585 | return true_EventResult; | 1822 | return true_EventResult; |
1586 | } | 1823 | } |
1587 | } | 1824 | } |
1825 | #endif | ||
1588 | return ignored_EventResult; | 1826 | return ignored_EventResult; |
1589 | } | 1827 | } |
1590 | 1828 | ||
1829 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
1591 | static iInt2 touchCoordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) { | 1830 | static iInt2 touchCoordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) { |
1592 | /* Clamp to the bounds so the cursor doesn't wrap at the ends. */ | 1831 | /* Clamp to the bounds so the cursor doesn't wrap at the ends. */ |
1593 | iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); | 1832 | iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); |
@@ -1609,9 +1848,11 @@ static int distanceToPos_InputWidget_(const iInputWidget *d, iInt2 uiCoord, iInt | |||
1609 | } | 1848 | } |
1610 | return dist_I2(addY_I2(winCoord, lineHeight_Text(d->font) / 2), uiCoord); | 1849 | return dist_I2(addY_I2(winCoord, lineHeight_Text(d->font) / 2), uiCoord); |
1611 | } | 1850 | } |
1851 | #endif | ||
1612 | 1852 | ||
1613 | static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 1853 | static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { |
1614 | iWidget *w = as_Widget(d); | 1854 | iWidget *w = as_Widget(d); |
1855 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
1615 | /* | 1856 | /* |
1616 | + first tap to focus & select all/place cursor | 1857 | + first tap to focus & select all/place cursor |
1617 | + focused tap to place cursor | 1858 | + focused tap to place cursor |
@@ -1853,9 +2094,22 @@ static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const | |||
1853 | // /* Eat all mouse clicks on the widget. */ | 2094 | // /* Eat all mouse clicks on the widget. */ |
1854 | // return true_EventResult; | 2095 | // return true_EventResult; |
1855 | // } | 2096 | // } |
2097 | #else | ||
2098 | /* Just a tap to activate the system-provided text input control. */ | ||
2099 | switch (processEvent_Click(&d->click, ev)) { | ||
2100 | case none_ClickResult: | ||
2101 | break; | ||
2102 | case started_ClickResult: | ||
2103 | setFocus_Widget(w); | ||
2104 | return true_EventResult; | ||
2105 | default: | ||
2106 | return true_EventResult; | ||
2107 | } | ||
2108 | #endif | ||
1856 | return ignored_EventResult; | 2109 | return ignored_EventResult; |
1857 | } | 2110 | } |
1858 | 2111 | ||
2112 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
1859 | static void clampWheelAccum_InputWidget_(iInputWidget *d, int wheel) { | 2113 | static void clampWheelAccum_InputWidget_(iInputWidget *d, int wheel) { |
1860 | if (wheel > 0 && d->visWrapLines.start == 0) { | 2114 | if (wheel > 0 && d->visWrapLines.start == 0) { |
1861 | d->wheelAccum = 0; | 2115 | d->wheelAccum = 0; |
@@ -1866,12 +2120,41 @@ static void clampWheelAccum_InputWidget_(iInputWidget *d, int wheel) { | |||
1866 | refresh_Widget(d); | 2120 | refresh_Widget(d); |
1867 | } | 2121 | } |
1868 | } | 2122 | } |
2123 | #endif | ||
2124 | |||
2125 | static void overflowScrollToKeepVisible_InputWidget_(iAny *widget) { | ||
2126 | iInputWidget *d = widget; | ||
2127 | iWidget *w = as_Widget(d); | ||
2128 | if (!isFocused_Widget(w) || isAffectedByVisualOffset_Widget(w)) { | ||
2129 | return; | ||
2130 | } | ||
2131 | iRect rect = boundsWithoutVisualOffset_Widget(w); | ||
2132 | iRect visible = visibleRect_Root(w->root); | ||
2133 | const uint32_t nowTime = SDL_GetTicks(); | ||
2134 | const double elapsed = (nowTime - d->lastOverflowScrollTime) / 1000.0; | ||
2135 | int dist = bottom_Rect(rect) + gap_UI - bottom_Rect(visible); | ||
2136 | const int step = iRound(10 * dist * elapsed); | ||
2137 | if (step > 0) { | ||
2138 | iWidget *scrollable = findOverflowScrollable_Widget(w); | ||
2139 | if (scrollable) { | ||
2140 | scrollOverflow_Widget(scrollable, -iClamp(step, 1, dist)); | ||
2141 | d->lastOverflowScrollTime = nowTime; | ||
2142 | } | ||
2143 | } | ||
2144 | if (dist > 0) { | ||
2145 | addTicker_App(overflowScrollToKeepVisible_InputWidget_, widget); | ||
2146 | } | ||
2147 | } | ||
1869 | 2148 | ||
1870 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 2149 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { |
1871 | iWidget *w = as_Widget(d); | 2150 | iWidget *w = as_Widget(d); |
1872 | /* Resize according to width immediately. */ | 2151 | /* Resize according to width immediately. */ |
1873 | if (d->lastUpdateWidth != w->rect.size.x) { | 2152 | if (d->lastUpdateWidth != w->rect.size.x) { |
1874 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | 2153 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; |
2154 | if (contentBounds_InputWidget_(d).size.x < minWidth_InputWidget_) { | ||
2155 | setFocus_Widget(NULL); | ||
2156 | return iFalse; | ||
2157 | } | ||
1875 | if (d->inFlags & isUrl_InputWidgetFlag) { | 2158 | if (d->inFlags & isUrl_InputWidgetFlag) { |
1876 | /* Restore/omit the default scheme if necessary. */ | 2159 | /* Restore/omit the default scheme if necessary. */ |
1877 | setText_InputWidget(d, text_InputWidget(d)); | 2160 | setText_InputWidget(d, text_InputWidget(d)); |
@@ -1879,15 +2162,24 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1879 | updateAllLinesAndResizeHeight_InputWidget_(d); | 2162 | updateAllLinesAndResizeHeight_InputWidget_(d); |
1880 | d->lastUpdateWidth = w->rect.size.x; | 2163 | d->lastUpdateWidth = w->rect.size.x; |
1881 | } | 2164 | } |
1882 | if (isCommand_Widget(w, ev, "focus.gained")) { | 2165 | #if LAGRANGE_USE_SYSTEM_TEXT_INPUT |
1883 | begin_InputWidget(d); | 2166 | if (isResize_UserEvent(ev)) { |
2167 | if (d->sysCtrl) { | ||
2168 | updateAfterVisualOffsetChange_InputWidget_(d, w->root); | ||
2169 | } | ||
2170 | } | ||
2171 | #endif | ||
2172 | if (deviceType_App() != desktop_AppDeviceType && isCommand_UserEvent(ev, "menu.opened")) { | ||
2173 | setFocus_Widget(NULL); | ||
1884 | return iFalse; | 2174 | return iFalse; |
1885 | } | 2175 | } |
1886 | else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") || | 2176 | if (isCommand_Widget(w, ev, "focus.gained")) { |
1887 | isCommand_UserEvent(ev, "window.focus.gained"))) { | 2177 | if (contentBounds_InputWidget_(d).size.x < minWidth_InputWidget_) { |
1888 | startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained")); | 2178 | setFocus_Widget(NULL); |
1889 | d->cursorVis = 1; | 2179 | } |
1890 | refresh_Widget(d); | 2180 | else { |
2181 | begin_InputWidget(d); | ||
2182 | } | ||
1891 | return iFalse; | 2183 | return iFalse; |
1892 | } | 2184 | } |
1893 | else if (isCommand_UserEvent(ev, "keyroot.changed")) { | 2185 | else if (isCommand_UserEvent(ev, "keyroot.changed")) { |
@@ -1902,11 +2194,29 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1902 | end_InputWidget(d, iTrue); | 2194 | end_InputWidget(d, iTrue); |
1903 | return iFalse; | 2195 | return iFalse; |
1904 | } | 2196 | } |
2197 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
2198 | else if (isCommand_UserEvent(ev, "prefs.blink.changed")) { | ||
2199 | if (isEditing_InputWidget_(d) && arg_Command(command_UserEvent(ev))) { | ||
2200 | startOrStopCursorTimer_InputWidget_(d, 2); | ||
2201 | } | ||
2202 | return iFalse; | ||
2203 | } | ||
2204 | else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") || | ||
2205 | isCommand_UserEvent(ev, "window.focus.gained"))) { | ||
2206 | startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained")); | ||
2207 | d->cursorVis = 1; | ||
2208 | refresh_Widget(d); | ||
2209 | return iFalse; | ||
2210 | } | ||
1905 | else if ((isCommand_UserEvent(ev, "copy") || isCommand_UserEvent(ev, "input.copy")) && | 2211 | else if ((isCommand_UserEvent(ev, "copy") || isCommand_UserEvent(ev, "input.copy")) && |
1906 | isEditing_InputWidget_(d)) { | 2212 | isEditing_InputWidget_(d)) { |
1907 | copy_InputWidget_(d, argLabel_Command(command_UserEvent(ev), "cut")); | 2213 | copy_InputWidget_(d, argLabel_Command(command_UserEvent(ev), "cut")); |
1908 | return iTrue; | 2214 | return iTrue; |
1909 | } | 2215 | } |
2216 | // else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { | ||
2217 | // copy_InputWidget_(d, iFalse); | ||
2218 | // return iTrue; | ||
2219 | // } | ||
1910 | else if (isCommand_UserEvent(ev, "input.paste") && isEditing_InputWidget_(d)) { | 2220 | else if (isCommand_UserEvent(ev, "input.paste") && isEditing_InputWidget_(d)) { |
1911 | paste_InputWidget_(d); | 2221 | paste_InputWidget_(d); |
1912 | return iTrue; | 2222 | return iTrue; |
@@ -1918,6 +2228,14 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1918 | } | 2228 | } |
1919 | return iTrue; | 2229 | return iTrue; |
1920 | } | 2230 | } |
2231 | else if (isCommand_UserEvent(ev, "text.insert")) { | ||
2232 | pushUndo_InputWidget_(d); | ||
2233 | deleteMarked_InputWidget_(d); | ||
2234 | insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev))); | ||
2235 | contentsWereChanged_InputWidget_(d); | ||
2236 | return iTrue; | ||
2237 | } | ||
2238 | #endif | ||
1921 | else if (isCommand_UserEvent(ev, "input.selectall") && isEditing_InputWidget_(d)) { | 2239 | else if (isCommand_UserEvent(ev, "input.selectall") && isEditing_InputWidget_(d)) { |
1922 | selectAll_InputWidget(d); | 2240 | selectAll_InputWidget(d); |
1923 | return iTrue; | 2241 | return iTrue; |
@@ -1928,24 +2246,19 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1928 | } | 2246 | } |
1929 | return iFalse; | 2247 | return iFalse; |
1930 | } | 2248 | } |
1931 | /* TODO: Scroll to keep widget visible when keyboard appears. */ | 2249 | else if (isCommand_UserEvent(ev, "keyboard.changed")) { |
1932 | // else if (isCommand_UserEvent(ev, "keyboard.changed")) { | 2250 | const iBool isKeyboardVisible = (arg_Command(command_UserEvent(ev)) != 0); |
1933 | // if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) { | 2251 | /* Scroll to keep widget visible when keyboard appears. */ |
1934 | // iRect rect = bounds_Widget(w); | 2252 | if (isFocused_Widget(d)) { |
1935 | // rect.pos.y -= value_Anim(&get_Window()->rootOffset); | 2253 | if (isKeyboardVisible) { |
1936 | // const iInt2 visRoot = visibleSize_Root(w->root); | 2254 | d->lastOverflowScrollTime = SDL_GetTicks(); |
1937 | // if (bottom_Rect(rect) > visRoot.y) { | 2255 | overflowScrollToKeepVisible_InputWidget_(d); |
1938 | // setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250); | 2256 | } |
1939 | // } | 2257 | else { |
1940 | // } | 2258 | setFocus_Widget(NULL); /* stop editing */ |
1941 | // return iFalse; | 2259 | } |
1942 | // } | 2260 | } |
1943 | else if (isCommand_UserEvent(ev, "text.insert")) { | 2261 | return iFalse; |
1944 | pushUndo_InputWidget_(d); | ||
1945 | deleteMarked_InputWidget_(d); | ||
1946 | insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev))); | ||
1947 | contentsWereChanged_InputWidget_(d); | ||
1948 | return iTrue; | ||
1949 | } | 2262 | } |
1950 | else if (isCommand_Widget(w, ev, "input.backup")) { | 2263 | else if (isCommand_Widget(w, ev, "input.backup")) { |
1951 | if (d->inFlags & needBackup_InputWidgetFlag) { | 2264 | if (d->inFlags & needBackup_InputWidgetFlag) { |
@@ -1957,10 +2270,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1957 | updateMetrics_InputWidget_(d); | 2270 | updateMetrics_InputWidget_(d); |
1958 | // updateLinesAndResize_InputWidget_(d); | 2271 | // updateLinesAndResize_InputWidget_(d); |
1959 | } | 2272 | } |
1960 | else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { | 2273 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT |
1961 | copy_InputWidget_(d, iFalse); | ||
1962 | return iTrue; | ||
1963 | } | ||
1964 | if (ev->type == SDL_MOUSEWHEEL && contains_Widget(w, coord_MouseWheelEvent(&ev->wheel))) { | 2274 | if (ev->type == SDL_MOUSEWHEEL && contains_Widget(w, coord_MouseWheelEvent(&ev->wheel))) { |
1965 | if (numWrapLines_InputWidget_(d) <= size_Range(&d->visWrapLines)) { | 2275 | if (numWrapLines_InputWidget_(d) <= size_Range(&d->visWrapLines)) { |
1966 | return ignored_EventResult; | 2276 | return ignored_EventResult; |
@@ -1994,6 +2304,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1994 | } | 2304 | } |
1995 | return false_EventResult; | 2305 | return false_EventResult; |
1996 | } | 2306 | } |
2307 | if (ev->type == SDL_TEXTINPUT && isFocused_Widget(w)) { | ||
2308 | pushUndo_InputWidget_(d); | ||
2309 | deleteMarked_InputWidget_(d); | ||
2310 | insertRange_InputWidget_(d, range_CStr(ev->text.text)); | ||
2311 | contentsWereChanged_InputWidget_(d); | ||
2312 | return iTrue; | ||
2313 | } | ||
2314 | const iInt2 curMax = cursorMax_InputWidget_(d); | ||
2315 | const iInt2 lineFirst = init_I2(0, d->cursor.y); | ||
2316 | const iInt2 lineLast = init_I2(endX_InputWidget_(d, d->cursor.y), d->cursor.y); | ||
2317 | #endif | ||
1997 | /* Click behavior depends on device type. */ { | 2318 | /* Click behavior depends on device type. */ { |
1998 | const int mbResult = (deviceType_App() == desktop_AppDeviceType | 2319 | const int mbResult = (deviceType_App() == desktop_AppDeviceType |
1999 | ? processPointerEvents_InputWidget_(d, ev) | 2320 | ? processPointerEvents_InputWidget_(d, ev) |
@@ -2005,12 +2326,10 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
2005 | if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { | 2326 | if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { |
2006 | return iTrue; | 2327 | return iTrue; |
2007 | } | 2328 | } |
2008 | const iInt2 curMax = cursorMax_InputWidget_(d); | ||
2009 | const iInt2 lineFirst = init_I2(0, d->cursor.y); | ||
2010 | const iInt2 lineLast = init_I2(endX_InputWidget_(d, d->cursor.y), d->cursor.y); | ||
2011 | if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) { | 2329 | if (ev->type == SDL_KEYDOWN && isFocused_Widget(w)) { |
2012 | const int key = ev->key.keysym.sym; | 2330 | const int key = ev->key.keysym.sym; |
2013 | const int mods = keyMods_Sym(ev->key.keysym.mod); | 2331 | const int mods = keyMods_Sym(ev->key.keysym.mod); |
2332 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
2014 | if (mods == KMOD_PRIMARY) { | 2333 | if (mods == KMOD_PRIMARY) { |
2015 | switch (key) { | 2334 | switch (key) { |
2016 | case 'c': | 2335 | case 'c': |
@@ -2028,7 +2347,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
2028 | return iTrue; | 2347 | return iTrue; |
2029 | } | 2348 | } |
2030 | } | 2349 | } |
2031 | #if defined (iPlatformApple) | 2350 | # if defined (iPlatformApple) |
2032 | if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) { | 2351 | if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) { |
2033 | switch (key) { | 2352 | switch (key) { |
2034 | case SDLK_UP: | 2353 | case SDLK_UP: |
@@ -2038,19 +2357,14 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
2038 | return iTrue; | 2357 | return iTrue; |
2039 | } | 2358 | } |
2040 | } | 2359 | } |
2041 | #endif | 2360 | # endif |
2042 | d->prevCursor = d->cursor; | 2361 | d->prevCursor = d->cursor; |
2362 | #endif | ||
2043 | switch (key) { | 2363 | switch (key) { |
2044 | case SDLK_INSERT: | ||
2045 | if (mods == KMOD_SHIFT) { | ||
2046 | paste_InputWidget_(d); | ||
2047 | } | ||
2048 | return iTrue; | ||
2049 | case SDLK_RETURN: | 2364 | case SDLK_RETURN: |
2050 | case SDLK_KP_ENTER: | 2365 | case SDLK_KP_ENTER: |
2051 | if (~d->inFlags & isSensitive_InputWidgetFlag && | 2366 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT |
2052 | ~d->inFlags & isUrl_InputWidgetFlag && | 2367 | if (isAllowedToInsertNewline_InputWidget_(d)) { |
2053 | d->inFlags & lineBreaksEnabled_InputWidgetFlag && d->maxLen == 0) { | ||
2054 | if (checkLineBreakMods_InputWidget_(d, mods)) { | 2368 | if (checkLineBreakMods_InputWidget_(d, mods)) { |
2055 | pushUndo_InputWidget_(d); | 2369 | pushUndo_InputWidget_(d); |
2056 | deleteMarked_InputWidget_(d); | 2370 | deleteMarked_InputWidget_(d); |
@@ -2059,6 +2373,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
2059 | return iTrue; | 2373 | return iTrue; |
2060 | } | 2374 | } |
2061 | } | 2375 | } |
2376 | #endif | ||
2062 | if (d->inFlags & enterKeyEnabled_InputWidgetFlag && | 2377 | if (d->inFlags & enterKeyEnabled_InputWidgetFlag && |
2063 | (checkAcceptMods_InputWidget_(d, mods) || | 2378 | (checkAcceptMods_InputWidget_(d, mods) || |
2064 | (~d->inFlags & lineBreaksEnabled_InputWidgetFlag))) { | 2379 | (~d->inFlags & lineBreaksEnabled_InputWidgetFlag))) { |
@@ -2071,6 +2386,12 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
2071 | end_InputWidget(d, iTrue); | 2386 | end_InputWidget(d, iTrue); |
2072 | setFocus_Widget(NULL); | 2387 | setFocus_Widget(NULL); |
2073 | return (d->inFlags & eatEscape_InputWidgetFlag) != 0; | 2388 | return (d->inFlags & eatEscape_InputWidgetFlag) != 0; |
2389 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
2390 | case SDLK_INSERT: | ||
2391 | if (mods == KMOD_SHIFT) { | ||
2392 | paste_InputWidget_(d); | ||
2393 | } | ||
2394 | return iTrue; | ||
2074 | case SDLK_BACKSPACE: | 2395 | case SDLK_BACKSPACE: |
2075 | if (!isEmpty_Range(&d->mark)) { | 2396 | if (!isEmpty_Range(&d->mark)) { |
2076 | pushUndo_InputWidget_(d); | 2397 | pushUndo_InputWidget_(d); |
@@ -2170,7 +2491,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
2170 | refresh_Widget(w); | 2491 | refresh_Widget(w); |
2171 | return iTrue; | 2492 | return iTrue; |
2172 | } | 2493 | } |
2173 | #if defined (iPlatformApple) | 2494 | # if defined (iPlatformApple) |
2174 | /* fall through for Emacs-style Home/End */ | 2495 | /* fall through for Emacs-style Home/End */ |
2175 | case SDLK_e: | 2496 | case SDLK_e: |
2176 | if (mods == KMOD_CTRL || mods == (KMOD_CTRL | KMOD_SHIFT)) { | 2497 | if (mods == KMOD_CTRL || mods == (KMOD_CTRL | KMOD_SHIFT)) { |
@@ -2178,7 +2499,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
2178 | refresh_Widget(w); | 2499 | refresh_Widget(w); |
2179 | return iTrue; | 2500 | return iTrue; |
2180 | } | 2501 | } |
2181 | #endif | 2502 | # endif |
2182 | break; | 2503 | break; |
2183 | case SDLK_LEFT: | 2504 | case SDLK_LEFT: |
2184 | case SDLK_RIGHT: { | 2505 | case SDLK_RIGHT: { |
@@ -2228,22 +2549,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
2228 | } | 2549 | } |
2229 | refresh_Widget(d); | 2550 | refresh_Widget(d); |
2230 | return iTrue; | 2551 | return iTrue; |
2552 | #endif | ||
2231 | } | 2553 | } |
2232 | if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { | 2554 | if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { |
2233 | return iFalse; | 2555 | return iFalse; |
2234 | } | 2556 | } |
2235 | return iTrue; | 2557 | return iTrue; |
2236 | } | 2558 | } |
2237 | else if (ev->type == SDL_TEXTINPUT && isFocused_Widget(w)) { | ||
2238 | pushUndo_InputWidget_(d); | ||
2239 | deleteMarked_InputWidget_(d); | ||
2240 | insertRange_InputWidget_(d, range_CStr(ev->text.text)); | ||
2241 | contentsWereChanged_InputWidget_(d); | ||
2242 | return iTrue; | ||
2243 | } | ||
2244 | return processEvent_Widget(w, ev); | 2559 | return processEvent_Widget(w, ev); |
2245 | } | 2560 | } |
2246 | 2561 | ||
2562 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
2247 | iDeclareType(MarkPainter) | 2563 | iDeclareType(MarkPainter) |
2248 | 2564 | ||
2249 | struct Impl_MarkPainter { | 2565 | struct Impl_MarkPainter { |
@@ -2302,6 +2618,7 @@ static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, iTextA | |||
2302 | } | 2618 | } |
2303 | return iTrue; | 2619 | return iTrue; |
2304 | } | 2620 | } |
2621 | #endif | ||
2305 | 2622 | ||
2306 | static void draw_InputWidget_(const iInputWidget *d) { | 2623 | static void draw_InputWidget_(const iInputWidget *d) { |
2307 | const iWidget *w = constAs_Widget(d); | 2624 | const iWidget *w = constAs_Widget(d); |
@@ -2324,13 +2641,22 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
2324 | isFocused ? gap_UI / 4 : 1, | 2641 | isFocused ? gap_UI / 4 : 1, |
2325 | isFocused ? uiInputFrameFocused_ColorId | 2642 | isFocused ? uiInputFrameFocused_ColorId |
2326 | : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); | 2643 | : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); |
2327 | setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), | 2644 | if (d->sysCtrl) { |
2328 | init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0))); | 2645 | /* The system-provided control is drawing the text. */ |
2646 | drawChildren_Widget(w); | ||
2647 | return; | ||
2648 | } | ||
2329 | const iRect contentBounds = contentBounds_InputWidget_(d); | 2649 | const iRect contentBounds = contentBounds_InputWidget_(d); |
2330 | iInt2 drawPos = topLeft_Rect(contentBounds); | 2650 | iInt2 drawPos = topLeft_Rect(contentBounds); |
2331 | const int fg = isHint ? uiAnnotation_ColorId | 2651 | const int fg = isHint ? uiAnnotation_ColorId |
2332 | : isFocused /*&& !isEmpty_Array(&d->lines)*/ ? uiInputTextFocused_ColorId | 2652 | : isFocused ? uiInputTextFocused_ColorId |
2333 | : uiInputText_ColorId; | 2653 | : uiInputText_ColorId; |
2654 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
2655 | setClip_Paint(&p, | ||
2656 | adjusted_Rect(bounds, | ||
2657 | init_I2(d->leftPadding, 0), | ||
2658 | init_I2(-d->rightPadding, | ||
2659 | w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0))); | ||
2334 | iWrapText wrapText = { | 2660 | iWrapText wrapText = { |
2335 | .maxWidth = d->maxLen == 0 ? width_Rect(contentBounds) : unlimitedWidth_InputWidget_, | 2661 | .maxWidth = d->maxLen == 0 ? width_Rect(contentBounds) : unlimitedWidth_InputWidget_, |
2336 | .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode | 2662 | .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode |
@@ -2338,16 +2664,37 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
2338 | .overrideChar = (d->inFlags & isSensitive_InputWidgetFlag ? sensitiveChar_ : 0), | 2664 | .overrideChar = (d->inFlags & isSensitive_InputWidgetFlag ? sensitiveChar_ : 0), |
2339 | }; | 2665 | }; |
2340 | const iRangei visLines = visibleLineRange_InputWidget_(d); | 2666 | const iRangei visLines = visibleLineRange_InputWidget_(d); |
2341 | const int visLineOffsetY = visLineOffsetY_InputWidget_(d); | ||
2342 | iRect markerRects[2] = { zero_Rect(), zero_Rect() }; | 2667 | iRect markerRects[2] = { zero_Rect(), zero_Rect() }; |
2668 | #endif | ||
2669 | const int visLineOffsetY = visLineOffsetY_InputWidget_(d); | ||
2343 | /* If buffered, just draw the buffered copy. */ | 2670 | /* If buffered, just draw the buffered copy. */ |
2344 | if (d->buffered && !isFocused) { | 2671 | if (d->buffered && !isFocused) { |
2345 | /* Most input widgets will use this, since only one is focused at a time. */ | 2672 | /* Most input widgets will use this, since only one is focused at a time. */ |
2346 | draw_TextBuf(d->buffered, addY_I2(drawPos, visLineOffsetY), white_ColorId); | 2673 | if (flags_Widget(w) & alignRight_WidgetFlag) { |
2674 | draw_TextBuf( | ||
2675 | d->buffered, | ||
2676 | addY_I2(init_I2(right_Rect(contentBounds) - d->buffered->size.x, drawPos.y), | ||
2677 | visLineOffsetY), | ||
2678 | white_ColorId); | ||
2679 | } | ||
2680 | else { | ||
2681 | draw_TextBuf(d->buffered, addY_I2(drawPos, visLineOffsetY), white_ColorId); | ||
2682 | } | ||
2347 | } | 2683 | } |
2348 | else if (isHint) { | 2684 | else if (isHint) { |
2349 | drawRange_Text(d->font, drawPos, uiAnnotation_ColorId, range_String(&d->hint)); | 2685 | if (flags_Widget(w) & alignRight_WidgetFlag) { |
2686 | drawAlign_Text(d->font, | ||
2687 | init_I2(right_Rect(contentBounds), drawPos.y), | ||
2688 | uiAnnotation_ColorId, | ||
2689 | right_Alignment, | ||
2690 | "%s", | ||
2691 | cstr_String(&d->hint)); | ||
2692 | } | ||
2693 | else { | ||
2694 | drawRange_Text(d->font, drawPos, uiAnnotation_ColorId, range_String(&d->hint)); | ||
2695 | } | ||
2350 | } | 2696 | } |
2697 | #if !LAGRANGE_USE_SYSTEM_TEXT_INPUT | ||
2351 | else { | 2698 | else { |
2352 | iAssert(~d->inFlags & isSensitive_InputWidgetFlag || size_Range(&visLines) == 1); | 2699 | iAssert(~d->inFlags & isSensitive_InputWidgetFlag || size_Range(&visLines) == 1); |
2353 | drawPos.y += visLineOffsetY; | 2700 | drawPos.y += visLineOffsetY; |
@@ -2372,7 +2719,8 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
2372 | wrapText.context = NULL; | 2719 | wrapText.context = NULL; |
2373 | } | 2720 | } |
2374 | /* Draw the insertion point. */ | 2721 | /* Draw the insertion point. */ |
2375 | if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) && | 2722 | if (isFocused && (d->cursorVis || !prefs_App()->blinkingCursor) && |
2723 | contains_Range(&visLines, d->cursor.y) && | ||
2376 | (deviceType_App() == desktop_AppDeviceType || isEmpty_Range(&d->mark))) { | 2724 | (deviceType_App() == desktop_AppDeviceType || isEmpty_Range(&d->mark))) { |
2377 | iInt2 curSize; | 2725 | iInt2 curSize; |
2378 | iRangecc cursorChar = iNullRange; | 2726 | iRangecc cursorChar = iNullRange; |
@@ -2426,6 +2774,7 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
2426 | drawPin_Paint(&p, markerRects[i], i, uiTextCaution_ColorId); | 2774 | drawPin_Paint(&p, markerRects[i], i, uiTextCaution_ColorId); |
2427 | } | 2775 | } |
2428 | } | 2776 | } |
2777 | #endif | ||
2429 | drawChildren_Widget(w); | 2778 | drawChildren_Widget(w); |
2430 | } | 2779 | } |
2431 | 2780 | ||
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h index f70c81af..5a61ec22 100644 --- a/src/ui/inputwidget.h +++ b/src/ui/inputwidget.h | |||
@@ -57,6 +57,7 @@ void setBackupFileName_InputWidget (iInputWidget *, const char *fileName); | |||
57 | void begin_InputWidget (iInputWidget *); | 57 | void begin_InputWidget (iInputWidget *); |
58 | void end_InputWidget (iInputWidget *, iBool accept); | 58 | void end_InputWidget (iInputWidget *, iBool accept); |
59 | void selectAll_InputWidget (iInputWidget *); | 59 | void selectAll_InputWidget (iInputWidget *); |
60 | void validate_InputWidget (iInputWidget *); | ||
60 | 61 | ||
61 | void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); | 62 | void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); |
62 | void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive); | 63 | void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive); |
diff --git a/src/ui/keys.c b/src/ui/keys.c index 30072572..d4d9320e 100644 --- a/src/ui/keys.c +++ b/src/ui/keys.c | |||
@@ -240,6 +240,7 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] = | |||
240 | { 100,{ "${keys.hoverurl}", '/', KMOD_PRIMARY, "prefs.hoverlink.toggle" }, 0 }, | 240 | { 100,{ "${keys.hoverurl}", '/', KMOD_PRIMARY, "prefs.hoverlink.toggle" }, 0 }, |
241 | { 110,{ "${menu.save.downloads}", SDLK_s, KMOD_PRIMARY, "document.save" }, 0 }, | 241 | { 110,{ "${menu.save.downloads}", SDLK_s, KMOD_PRIMARY, "document.save" }, 0 }, |
242 | { 120,{ "${keys.upload}", SDLK_u, KMOD_PRIMARY, "document.upload" }, 0 }, | 242 | { 120,{ "${keys.upload}", SDLK_u, KMOD_PRIMARY, "document.upload" }, 0 }, |
243 | { 121,{ "${keys.upload.edit}", SDLK_e, KMOD_PRIMARY, "document.upload copy:1" }, 0 }, | ||
243 | /* The following cannot currently be changed (built-in duplicates). */ | 244 | /* The following cannot currently be changed (built-in duplicates). */ |
244 | #if defined (iPlatformApple) | 245 | #if defined (iPlatformApple) |
245 | { 1002, { NULL, SDLK_LEFTBRACKET, KMOD_PRIMARY, "navigate.back" }, 0 }, | 246 | { 1002, { NULL, SDLK_LEFTBRACKET, KMOD_PRIMARY, "navigate.back" }, 0 }, |
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index 4dd66a28..3454014a 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c | |||
@@ -36,6 +36,7 @@ struct Impl_LabelWidget { | |||
36 | iWidget widget; | 36 | iWidget widget; |
37 | iString srcLabel; | 37 | iString srcLabel; |
38 | iString label; | 38 | iString label; |
39 | iInt2 labelOffset; | ||
39 | int font; | 40 | int font; |
40 | int key; | 41 | int key; |
41 | int kmods; | 42 | int kmods; |
@@ -44,14 +45,15 @@ struct Impl_LabelWidget { | |||
44 | iString command; | 45 | iString command; |
45 | iClick click; | 46 | iClick click; |
46 | struct { | 47 | struct { |
47 | uint8_t alignVisual : 1; /* align according to visible bounds, not font metrics */ | 48 | uint16_t alignVisual : 1; /* align according to visible bounds, not font metrics */ |
48 | uint8_t noAutoMinHeight : 1; /* minimum height is not set automatically */ | 49 | uint16_t noAutoMinHeight : 1; /* minimum height is not set automatically */ |
49 | uint8_t drawAsOutline : 1; /* draw as outline, filled with background color */ | 50 | uint16_t drawAsOutline : 1; /* draw as outline, filled with background color */ |
50 | uint8_t noTopFrame : 1; | 51 | uint16_t noTopFrame : 1; |
51 | uint8_t wrap : 1; | 52 | uint16_t wrap : 1; |
52 | uint8_t allCaps : 1; | 53 | uint16_t allCaps : 1; |
53 | uint8_t removeTrailingColon : 1; | 54 | uint16_t removeTrailingColon : 1; |
54 | uint8_t chevron : 1; | 55 | uint16_t chevron : 1; |
56 | uint16_t checkMark : 1; | ||
55 | } flags; | 57 | } flags; |
56 | }; | 58 | }; |
57 | 59 | ||
@@ -132,6 +134,10 @@ static iBool processEvent_LabelWidget_(iLabelWidget *d, const SDL_Event *ev) { | |||
132 | refresh_Widget(d); | 134 | refresh_Widget(d); |
133 | return iFalse; | 135 | return iFalse; |
134 | } | 136 | } |
137 | else if (isCommand_Widget(w, ev, "trigger")) { | ||
138 | trigger_LabelWidget_(d); | ||
139 | return iTrue; | ||
140 | } | ||
135 | if (!isEmpty_String(&d->command)) { | 141 | if (!isEmpty_String(&d->command)) { |
136 | #if 0 && defined (iPlatformAppleMobile) | 142 | #if 0 && defined (iPlatformAppleMobile) |
137 | /* Touch allows activating any button on release. */ | 143 | /* Touch allows activating any button on release. */ |
@@ -193,6 +199,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int | |||
193 | int *icon, int *meta) { | 199 | int *icon, int *meta) { |
194 | const iWidget *w = constAs_Widget(d); | 200 | const iWidget *w = constAs_Widget(d); |
195 | const int64_t flags = flags_Widget(w); | 201 | const int64_t flags = flags_Widget(w); |
202 | const iBool isHover = isHover_LabelWidget_(d); | ||
196 | const iBool isFocus = (flags & focusable_WidgetFlag && isFocused_Widget(d)); | 203 | const iBool isFocus = (flags & focusable_WidgetFlag && isFocused_Widget(d)); |
197 | const iBool isPress = (flags & pressed_WidgetFlag) != 0; | 204 | const iBool isPress = (flags & pressed_WidgetFlag) != 0; |
198 | const iBool isSel = (flags & selected_WidgetFlag) != 0; | 205 | const iBool isSel = (flags & selected_WidgetFlag) != 0; |
@@ -205,6 +212,9 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int | |||
205 | *bg = isButton && ~flags & noBackground_WidgetFlag ? (d->widget.bgColor != none_ColorId ? | 212 | *bg = isButton && ~flags & noBackground_WidgetFlag ? (d->widget.bgColor != none_ColorId ? |
206 | d->widget.bgColor : uiBackground_ColorId) | 213 | d->widget.bgColor : uiBackground_ColorId) |
207 | : none_ColorId; | 214 | : none_ColorId; |
215 | if (d->flags.checkMark) { | ||
216 | *bg = none_ColorId; | ||
217 | } | ||
208 | *fg = uiText_ColorId; | 218 | *fg = uiText_ColorId; |
209 | *frame1 = isButton ? uiEmboss1_ColorId : d->widget.frameColor; | 219 | *frame1 = isButton ? uiEmboss1_ColorId : d->widget.frameColor; |
210 | *frame2 = isButton ? uiEmboss2_ColorId : *frame1; | 220 | *frame2 = isButton ? uiEmboss2_ColorId : *frame1; |
@@ -216,18 +226,17 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int | |||
216 | *meta = uiTextDisabled_ColorId; | 226 | *meta = uiTextDisabled_ColorId; |
217 | } | 227 | } |
218 | if (isSel) { | 228 | if (isSel) { |
219 | if (isMenuItem) { | 229 | if (!d->flags.checkMark) { |
220 | *bg = uiBackgroundUnfocusedSelection_ColorId; | 230 | if (isMenuItem) { |
221 | } | 231 | *bg = uiBackgroundUnfocusedSelection_ColorId; |
222 | else { | 232 | } |
223 | *bg = uiBackgroundSelected_ColorId; | 233 | else { |
224 | } | 234 | *bg = uiBackgroundSelected_ColorId; |
225 | // if (!isKeyRoot) { | 235 | } |
226 | // *bg = uiEmbossSelected1_ColorId; //uiBackgroundUnfocusedSelection_ColorId; | 236 | if (!isKeyRoot) { |
227 | // } | 237 | *bg = isDark_ColorTheme(colorTheme_App()) ? uiBackgroundUnfocusedSelection_ColorId |
228 | if (!isKeyRoot) { | 238 | : uiMarked_ColorId; |
229 | *bg = isDark_ColorTheme(colorTheme_App()) ? uiBackgroundUnfocusedSelection_ColorId | 239 | } |
230 | : uiMarked_ColorId ; | ||
231 | } | 240 | } |
232 | *fg = uiTextSelected_ColorId; | 241 | *fg = uiTextSelected_ColorId; |
233 | if (isButton) { | 242 | if (isButton) { |
@@ -248,7 +257,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int | |||
248 | if (colorEscape == uiTextCaution_ColorId) { | 257 | if (colorEscape == uiTextCaution_ColorId) { |
249 | *icon = *meta = colorEscape; | 258 | *icon = *meta = colorEscape; |
250 | } | 259 | } |
251 | if (isHover_LabelWidget_(d)) { | 260 | if (isHover) { |
252 | if (isFrameless) { | 261 | if (isFrameless) { |
253 | *bg = uiBackgroundFramelessHover_ColorId; | 262 | *bg = uiBackgroundFramelessHover_ColorId; |
254 | *fg = uiTextFramelessHover_ColorId; | 263 | *fg = uiTextFramelessHover_ColorId; |
@@ -274,7 +283,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int | |||
274 | } | 283 | } |
275 | } | 284 | } |
276 | if (d->forceFg >= 0) { | 285 | if (d->forceFg >= 0) { |
277 | *fg = /* *icon = */ *meta = d->forceFg; | 286 | *fg = *meta = d->forceFg; |
278 | } | 287 | } |
279 | if (isPress) { | 288 | if (isPress) { |
280 | if (colorEscape == uiTextAction_ColorId || colorEscape == uiTextCaution_ColorId) { | 289 | if (colorEscape == uiTextAction_ColorId || colorEscape == uiTextCaution_ColorId) { |
@@ -289,13 +298,12 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int | |||
289 | *frame1 = uiEmbossPressed1_ColorId; | 298 | *frame1 = uiEmbossPressed1_ColorId; |
290 | *frame2 = colorEscape != none_ColorId ? colorEscape : uiEmbossPressed2_ColorId; | 299 | *frame2 = colorEscape != none_ColorId ? colorEscape : uiEmbossPressed2_ColorId; |
291 | } | 300 | } |
292 | //if (colorEscape == none_ColorId || colorEscape == uiTextAction_ColorId) { | ||
293 | *fg = *icon = *meta = uiTextPressed_ColorId | permanent_ColorId; | 301 | *fg = *icon = *meta = uiTextPressed_ColorId | permanent_ColorId; |
294 | // } | ||
295 | // else { | ||
296 | // *fg = (isDark_ColorTheme(colorTheme_App()) ? white_ColorId : black_ColorId) | permanent_ColorId; | ||
297 | // } | ||
298 | } | 302 | } |
303 | } | ||
304 | if (((isSel || isHover) && isFrameless) || isPress) { | ||
305 | /* Ensure that the full label text remains readable. */ | ||
306 | *fg |= permanent_ColorId; | ||
299 | } | 307 | } |
300 | } | 308 | } |
301 | 309 | ||
@@ -328,6 +336,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
328 | init_Paint(&p); | 336 | init_Paint(&p); |
329 | int bg, fg, frame, frame2, iconColor, metaColor; | 337 | int bg, fg, frame, frame2, iconColor, metaColor; |
330 | getColors_LabelWidget_(d, &bg, &fg, &frame, &frame2, &iconColor, &metaColor); | 338 | getColors_LabelWidget_(d, &bg, &fg, &frame, &frame2, &iconColor, &metaColor); |
339 | setBaseAttributes_Text(d->font, fg); | ||
331 | const enum iColorId colorEscape = parseEscape_Color(cstr_String(&d->label), NULL); | 340 | const enum iColorId colorEscape = parseEscape_Color(cstr_String(&d->label), NULL); |
332 | const iBool isCaution = (colorEscape == uiTextCaution_ColorId); | 341 | const iBool isCaution = (colorEscape == uiTextCaution_ColorId); |
333 | if (bg >= 0) { | 342 | if (bg >= 0) { |
@@ -347,7 +356,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
347 | bottomRight_Rect(frameRect), | 356 | bottomRight_Rect(frameRect), |
348 | bottomLeft_Rect(frameRect) | 357 | bottomLeft_Rect(frameRect) |
349 | }; | 358 | }; |
350 | #if SDL_VERSION_ATLEAST(2, 0, 16) | 359 | #if SDL_COMPILEDVERSION == SDL_VERSIONNUM(2, 0, 16) |
351 | if (isOpenGLRenderer_Window()) { | 360 | if (isOpenGLRenderer_Window()) { |
352 | /* A very curious regression in SDL 2.0.16. */ | 361 | /* A very curious regression in SDL 2.0.16. */ |
353 | points[3].x--; | 362 | points[3].x--; |
@@ -362,10 +371,6 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
362 | } | 371 | } |
363 | setClip_Paint(&p, rect); | 372 | setClip_Paint(&p, rect); |
364 | const int iconPad = iconPadding_LabelWidget_(d); | 373 | const int iconPad = iconPadding_LabelWidget_(d); |
365 | // const int iconColor = isCaution ? uiTextCaution_ColorId | ||
366 | // : flags & (disabled_WidgetFlag | pressed_WidgetFlag) ? fg | ||
367 | // : isHover ? uiIconHover_ColorId | ||
368 | // : uiIcon_ColorId; | ||
369 | if (d->icon && d->icon != 0x20) { /* no need to draw an empty icon */ | 374 | if (d->icon && d->icon != 0x20) { /* no need to draw an empty icon */ |
370 | iString str; | 375 | iString str; |
371 | initUnicodeN_String(&str, &d->icon, 1); | 376 | initUnicodeN_String(&str, &d->icon, 1); |
@@ -427,22 +432,35 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
427 | else { | 432 | else { |
428 | drawCenteredOutline_Text( | 433 | drawCenteredOutline_Text( |
429 | d->font, | 434 | d->font, |
430 | adjusted_Rect(bounds, init_I2(iconPad * (flags & tight_WidgetFlag ? 1.0f : 1.5f), 0), | 435 | moved_Rect( |
436 | adjusted_Rect(bounds, | ||
437 | init_I2(iconPad * (flags & tight_WidgetFlag ? 1.0f : 1.5f), 0), | ||
431 | init_I2(-iconPad * (flags & tight_WidgetFlag ? 0.5f : 1.0f), 0)), | 438 | init_I2(-iconPad * (flags & tight_WidgetFlag ? 0.5f : 1.0f), 0)), |
439 | d->labelOffset), | ||
432 | d->flags.alignVisual, | 440 | d->flags.alignVisual, |
433 | d->flags.drawAsOutline ? fg : none_ColorId, | 441 | d->flags.drawAsOutline ? fg : none_ColorId, |
434 | d->flags.drawAsOutline ? d->widget.bgColor : fg, | 442 | d->flags.drawAsOutline ? d->widget.bgColor : fg, |
435 | "%s", | 443 | "%s", |
436 | cstr_String(&d->label)); | 444 | cstr_String(&d->label)); |
437 | } | 445 | } |
438 | if (d->flags.chevron) { | 446 | if (d->flags.chevron || (flags & selected_WidgetFlag && d->flags.checkMark)) { |
439 | const iRect chRect = rect; | 447 | const iRect chRect = rect; |
440 | const int chSize = lineHeight_Text(d->font); | 448 | const int chSize = lineHeight_Text(d->font); |
449 | int offset = 0; | ||
450 | if (d->flags.chevron) { | ||
451 | offset = -iconPad; | ||
452 | } | ||
453 | else { | ||
454 | offset = -10 * gap_UI; | ||
455 | } | ||
441 | drawCentered_Text(d->font, | 456 | drawCentered_Text(d->font, |
442 | (iRect){ addX_I2(topRight_Rect(chRect), -iconPad), | 457 | (iRect){ addX_I2(topRight_Rect(chRect), offset), |
443 | init_I2(chSize, height_Rect(chRect)) }, | 458 | init_I2(chSize, height_Rect(chRect)) }, |
444 | iTrue, iconColor, rightAngle_Icon); | 459 | iTrue, |
460 | iconColor, | ||
461 | d->flags.chevron ? rightAngle_Icon : check_Icon); | ||
445 | } | 462 | } |
463 | setBaseAttributes_Text(-1, -1); | ||
446 | unsetClip_Paint(&p); | 464 | unsetClip_Paint(&p); |
447 | drawChildren_Widget(w); | 465 | drawChildren_Widget(w); |
448 | } | 466 | } |
@@ -482,9 +500,10 @@ int font_LabelWidget(const iLabelWidget *d) { | |||
482 | } | 500 | } |
483 | 501 | ||
484 | void updateSize_LabelWidget(iLabelWidget *d) { | 502 | void updateSize_LabelWidget(iLabelWidget *d) { |
485 | iWidget *w = as_Widget(d); | 503 | if (!d) return; |
504 | iWidget *w = as_Widget(d); | ||
486 | const int64_t flags = flags_Widget(w); | 505 | const int64_t flags = flags_Widget(w); |
487 | const iInt2 size = defaultSize_LabelWidget(d); | 506 | const iInt2 size = defaultSize_LabelWidget(d); |
488 | if (!d->flags.noAutoMinHeight) { | 507 | if (!d->flags.noAutoMinHeight) { |
489 | w->minSize.y = size.y; /* vertically text must remain visible */ | 508 | w->minSize.y = size.y; /* vertically text must remain visible */ |
490 | } | 509 | } |
@@ -514,6 +533,7 @@ void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) { | |||
514 | d->font = uiLabel_FontId; | 533 | d->font = uiLabel_FontId; |
515 | d->forceFg = none_ColorId; | 534 | d->forceFg = none_ColorId; |
516 | d->icon = 0; | 535 | d->icon = 0; |
536 | d->labelOffset = zero_I2(); | ||
517 | initCStr_String(&d->srcLabel, label); | 537 | initCStr_String(&d->srcLabel, label); |
518 | initCopy_String(&d->label, &d->srcLabel); | 538 | initCopy_String(&d->label, &d->srcLabel); |
519 | replaceVariables_LabelWidget_(d); | 539 | replaceVariables_LabelWidget_(d); |
@@ -551,18 +571,22 @@ void setTextColor_LabelWidget(iLabelWidget *d, int color) { | |||
551 | } | 571 | } |
552 | 572 | ||
553 | void setText_LabelWidget(iLabelWidget *d, const iString *text) { | 573 | void setText_LabelWidget(iLabelWidget *d, const iString *text) { |
574 | if (d) { | ||
554 | updateText_LabelWidget(d, text); | 575 | updateText_LabelWidget(d, text); |
555 | updateSize_LabelWidget(d); | 576 | updateSize_LabelWidget(d); |
556 | if (isWrapped_LabelWidget(d)) { | 577 | if (isWrapped_LabelWidget(d)) { |
557 | sizeChanged_LabelWidget_(d); | 578 | sizeChanged_LabelWidget_(d); |
579 | } | ||
558 | } | 580 | } |
559 | } | 581 | } |
560 | 582 | ||
561 | void setTextCStr_LabelWidget(iLabelWidget *d, const char *text) { | 583 | void setTextCStr_LabelWidget(iLabelWidget *d, const char *text) { |
584 | if (d) { | ||
562 | updateTextCStr_LabelWidget(d, text); | 585 | updateTextCStr_LabelWidget(d, text); |
563 | updateSize_LabelWidget(d); | 586 | updateSize_LabelWidget(d); |
564 | if (isWrapped_LabelWidget(d)) { | 587 | if (isWrapped_LabelWidget(d)) { |
565 | sizeChanged_LabelWidget_(d); | 588 | sizeChanged_LabelWidget_(d); |
589 | } | ||
566 | } | 590 | } |
567 | } | 591 | } |
568 | 592 | ||
@@ -586,6 +610,10 @@ void setChevron_LabelWidget(iLabelWidget *d, iBool chevron) { | |||
586 | d->flags.chevron = chevron; | 610 | d->flags.chevron = chevron; |
587 | } | 611 | } |
588 | 612 | ||
613 | void setCheckMark_LabelWidget(iLabelWidget *d, iBool checkMark) { | ||
614 | d->flags.checkMark = checkMark; | ||
615 | } | ||
616 | |||
589 | void setWrap_LabelWidget(iLabelWidget *d, iBool wrap) { | 617 | void setWrap_LabelWidget(iLabelWidget *d, iBool wrap) { |
590 | d->flags.wrap = wrap; | 618 | d->flags.wrap = wrap; |
591 | } | 619 | } |
@@ -610,6 +638,10 @@ void setRemoveTrailingColon_LabelWidget(iLabelWidget *d, iBool removeTrailingCol | |||
610 | } | 638 | } |
611 | } | 639 | } |
612 | 640 | ||
641 | void setTextOffset_LabelWidget(iLabelWidget *d, iInt2 offset) { | ||
642 | d->labelOffset = offset; | ||
643 | } | ||
644 | |||
613 | void updateText_LabelWidget(iLabelWidget *d, const iString *text) { | 645 | void updateText_LabelWidget(iLabelWidget *d, const iString *text) { |
614 | set_String(&d->label, text); | 646 | set_String(&d->label, text); |
615 | set_String(&d->srcLabel, text); | 647 | set_String(&d->srcLabel, text); |
diff --git a/src/ui/labelwidget.h b/src/ui/labelwidget.h index 6542ae12..4f605d6b 100644 --- a/src/ui/labelwidget.h +++ b/src/ui/labelwidget.h | |||
@@ -33,10 +33,12 @@ void setAlignVisually_LabelWidget(iLabelWidget *, iBool alignVisual); | |||
33 | void setNoAutoMinHeight_LabelWidget (iLabelWidget *, iBool noAutoMinHeight); | 33 | void setNoAutoMinHeight_LabelWidget (iLabelWidget *, iBool noAutoMinHeight); |
34 | void setNoTopFrame_LabelWidget (iLabelWidget *, iBool noTopFrame); | 34 | void setNoTopFrame_LabelWidget (iLabelWidget *, iBool noTopFrame); |
35 | void setChevron_LabelWidget (iLabelWidget *, iBool chevron); | 35 | void setChevron_LabelWidget (iLabelWidget *, iBool chevron); |
36 | void setCheckMark_LabelWidget (iLabelWidget *, iBool checkMark); | ||
36 | void setWrap_LabelWidget (iLabelWidget *, iBool wrap); | 37 | void setWrap_LabelWidget (iLabelWidget *, iBool wrap); |
37 | void setOutline_LabelWidget (iLabelWidget *, iBool drawAsOutline); | 38 | void setOutline_LabelWidget (iLabelWidget *, iBool drawAsOutline); |
38 | void setAllCaps_LabelWidget (iLabelWidget *, iBool allCaps); | 39 | void setAllCaps_LabelWidget (iLabelWidget *, iBool allCaps); |
39 | void setRemoveTrailingColon_LabelWidget (iLabelWidget *, iBool removeTrailingColon); | 40 | void setRemoveTrailingColon_LabelWidget (iLabelWidget *, iBool removeTrailingColon); |
41 | void setTextOffset_LabelWidget (iLabelWidget *, iInt2 offset); | ||
40 | void setFont_LabelWidget (iLabelWidget *, int fontId); | 42 | void setFont_LabelWidget (iLabelWidget *, int fontId); |
41 | void setTextColor_LabelWidget (iLabelWidget *, int color); | 43 | void setTextColor_LabelWidget (iLabelWidget *, int color); |
42 | void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */ | 44 | void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */ |
diff --git a/src/ui/linkinfo.c b/src/ui/linkinfo.c new file mode 100644 index 00000000..5102f9b3 --- /dev/null +++ b/src/ui/linkinfo.c | |||
@@ -0,0 +1,176 @@ | |||
1 | /* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #include "linkinfo.h" | ||
24 | #include "metrics.h" | ||
25 | #include "paint.h" | ||
26 | #include "../gmcerts.h" | ||
27 | #include "../app.h" | ||
28 | |||
29 | #include <SDL_render.h> | ||
30 | |||
31 | iDefineTypeConstruction(LinkInfo) | ||
32 | |||
33 | #define minWidth_LinkInfo_ (40 * gap_UI) | ||
34 | #define hPad_LinkInfo_ (2 * gap_UI) | ||
35 | #define vPad_LinkInfo_ (1 * gap_UI) | ||
36 | |||
37 | void init_LinkInfo(iLinkInfo *d) { | ||
38 | d->buf = NULL; | ||
39 | init_Anim(&d->opacity, 0.0f); | ||
40 | d->isAltPos = iFalse; | ||
41 | } | ||
42 | |||
43 | void deinit_LinkInfo(iLinkInfo *d) { | ||
44 | delete_TextBuf(d->buf); | ||
45 | } | ||
46 | |||
47 | iInt2 size_LinkInfo(const iLinkInfo *d) { | ||
48 | if (!d->buf) { | ||
49 | return zero_I2(); | ||
50 | } | ||
51 | return add_I2(d->buf->size, init_I2(2 * hPad_LinkInfo_, 2 * vPad_LinkInfo_)); | ||
52 | } | ||
53 | |||
54 | void infoText_LinkInfo(const iGmDocument *doc, iGmLinkId linkId, iString *text_out) { | ||
55 | const iString *url = linkUrl_GmDocument(doc, linkId); | ||
56 | iUrl parts; | ||
57 | init_Url(&parts, url); | ||
58 | const int flags = linkFlags_GmDocument(doc, linkId); | ||
59 | const enum iGmLinkScheme scheme = scheme_GmLinkFlag(flags); | ||
60 | const iBool isImage = (flags & imageFileExtension_GmLinkFlag) != 0; | ||
61 | const iBool isAudio = (flags & audioFileExtension_GmLinkFlag) != 0; | ||
62 | /* Most important info first: the identity that will be used. */ | ||
63 | const iGmIdentity *ident = identityForUrl_GmCerts(certs_App(), url); | ||
64 | if (ident) { | ||
65 | appendFormat_String(text_out, person_Icon " %s", | ||
66 | //escape_Color(tmBannerItemTitle_ColorId), | ||
67 | cstr_String(name_GmIdentity(ident))); | ||
68 | } | ||
69 | /* Possibly inlined content. */ | ||
70 | if (isImage || isAudio) { | ||
71 | if (!isEmpty_String(text_out)) { | ||
72 | appendCStr_String(text_out, "\n"); | ||
73 | } | ||
74 | appendCStr_String( | ||
75 | text_out, | ||
76 | format_CStr(isImage ? photo_Icon " %s " : "\U0001f3b5 %s", | ||
77 | cstr_Lang(isImage ? "link.hint.image" : "link.hint.audio"))); | ||
78 | } | ||
79 | if (!isEmpty_String(text_out)) { | ||
80 | appendCStr_String(text_out, " \u2014 "); | ||
81 | } | ||
82 | /* Indicate non-Gemini schemes. */ | ||
83 | if (scheme == mailto_GmLinkScheme) { | ||
84 | appendCStr_String(text_out, envelope_Icon " "); | ||
85 | append_String(text_out, url); | ||
86 | } | ||
87 | else if (scheme != gemini_GmLinkScheme && !isEmpty_Range(&parts.host)) { | ||
88 | appendCStr_String(text_out, globe_Icon " \x1b[1m"); | ||
89 | appendRange_String(text_out, (iRangecc){ constBegin_String(url), | ||
90 | parts.host.end }); | ||
91 | appendCStr_String(text_out, "\x1b[0m"); | ||
92 | appendRange_String(text_out, (iRangecc){ parts.path.start, constEnd_String(url) }); | ||
93 | } | ||
94 | else if (scheme != gemini_GmLinkScheme) { | ||
95 | appendCStr_String(text_out, scheme == file_GmLinkScheme ? "" : globe_Icon " "); | ||
96 | append_String(text_out, url); | ||
97 | } | ||
98 | else { | ||
99 | appendCStr_String(text_out, "\x1b[1m"); | ||
100 | appendRange_String(text_out, parts.host); | ||
101 | if (!isEmpty_Range(&parts.port)) { | ||
102 | appendCStr_String(text_out, ":"); | ||
103 | appendRange_String(text_out, parts.port); | ||
104 | } | ||
105 | appendCStr_String(text_out, "\x1b[0m"); | ||
106 | appendRange_String(text_out, (iRangecc){ parts.path.start, constEnd_String(url) }); | ||
107 | } | ||
108 | /* Date of last visit. */ | ||
109 | if (flags & visited_GmLinkFlag) { | ||
110 | iDate date; | ||
111 | init_Date(&date, linkTime_GmDocument(doc, linkId)); | ||
112 | if (!isEmpty_String(text_out)) { | ||
113 | appendCStr_String(text_out, " \u2014 "); | ||
114 | } | ||
115 | iString *dateStr = format_Date(&date, "%b %d"); | ||
116 | append_String(text_out, dateStr); | ||
117 | delete_String(dateStr); | ||
118 | } | ||
119 | } | ||
120 | |||
121 | iBool update_LinkInfo(iLinkInfo *d, const iGmDocument *doc, iGmLinkId linkId, int maxWidth) { | ||
122 | if (!d) { | ||
123 | return iFalse; | ||
124 | } | ||
125 | const iBool isAnimated = prefs_App()->uiAnimations; | ||
126 | if (d->linkId != linkId || d->maxWidth != maxWidth) { | ||
127 | d->linkId = linkId; | ||
128 | d->maxWidth = maxWidth; | ||
129 | invalidate_LinkInfo(d); | ||
130 | if (linkId) { | ||
131 | iString str; | ||
132 | init_String(&str); | ||
133 | infoText_LinkInfo(doc, linkId, &str); | ||
134 | if (targetValue_Anim(&d->opacity) < 1) { | ||
135 | setValue_Anim(&d->opacity, 1, isAnimated ? 75 : 0); | ||
136 | } | ||
137 | /* Draw to a buffer, wrapped. */ | ||
138 | const int avail = iMax(minWidth_LinkInfo_, maxWidth) - 2 * hPad_LinkInfo_; | ||
139 | iWrapText wt = { .text = range_String(&str), .maxWidth = avail, .mode = word_WrapTextMode }; | ||
140 | d->buf = new_TextBuf(&wt, uiLabel_FontId, tmQuote_ColorId); | ||
141 | deinit_String(&str); | ||
142 | } | ||
143 | else { | ||
144 | if (targetValue_Anim(&d->opacity) > 0) { | ||
145 | setValue_Anim(&d->opacity, 0, isAnimated ? 150 : 0); | ||
146 | } | ||
147 | } | ||
148 | return iTrue; | ||
149 | } | ||
150 | return iFalse; | ||
151 | } | ||
152 | |||
153 | void invalidate_LinkInfo(iLinkInfo *d) { | ||
154 | if (targetValue_Anim(&d->opacity) > 0) { | ||
155 | setValue_Anim(&d->opacity, 0, prefs_App()->uiAnimations ? 150 : 0); | ||
156 | } | ||
157 | } | ||
158 | |||
159 | void draw_LinkInfo(const iLinkInfo *d, iInt2 topLeft) { | ||
160 | const float opacity = value_Anim(&d->opacity); | ||
161 | if (!d->buf || opacity <= 0.01f) { | ||
162 | return; | ||
163 | } | ||
164 | iPaint p; | ||
165 | init_Paint(&p); | ||
166 | iInt2 size = size_LinkInfo(d); | ||
167 | iRect rect = { topLeft, size }; | ||
168 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | ||
169 | p.alpha = 255 * opacity; | ||
170 | fillRect_Paint(&p, rect, tmBackgroundAltText_ColorId); | ||
171 | drawRect_Paint(&p, rect, tmFrameAltText_ColorId); | ||
172 | SDL_SetTextureAlphaMod(d->buf->texture, p.alpha); | ||
173 | draw_TextBuf(d->buf, add_I2(topLeft, init_I2(hPad_LinkInfo_, vPad_LinkInfo_)), | ||
174 | white_ColorId); | ||
175 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | ||
176 | } | ||
diff --git a/src/ui/linkinfo.h b/src/ui/linkinfo.h new file mode 100644 index 00000000..38b90b87 --- /dev/null +++ b/src/ui/linkinfo.h | |||
@@ -0,0 +1,47 @@ | |||
1 | /* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #pragma once | ||
24 | |||
25 | #include "text.h" | ||
26 | #include "util.h" | ||
27 | #include "../gmdocument.h" | ||
28 | |||
29 | iDeclareType(LinkInfo) | ||
30 | iDeclareTypeConstruction(LinkInfo) | ||
31 | |||
32 | struct Impl_LinkInfo { | ||
33 | iGmLinkId linkId; | ||
34 | int maxWidth; | ||
35 | iTextBuf *buf; | ||
36 | iAnim opacity; | ||
37 | iBool isAltPos; | ||
38 | }; | ||
39 | |||
40 | iBool update_LinkInfo (iLinkInfo *, const iGmDocument *doc, iGmLinkId linkId, | ||
41 | int maxWidth); /* returns true if changed */ | ||
42 | void invalidate_LinkInfo (iLinkInfo *); | ||
43 | |||
44 | void infoText_LinkInfo (const iGmDocument *doc, iGmLinkId linkId, iString *text_out); | ||
45 | |||
46 | iInt2 size_LinkInfo (const iLinkInfo *); | ||
47 | void draw_LinkInfo (const iLinkInfo *, iInt2 topLeft); | ||
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c index c2ba5581..1d0f1729 100644 --- a/src/ui/listwidget.c +++ b/src/ui/listwidget.c | |||
@@ -49,21 +49,6 @@ iDefineClass(ListItem) | |||
49 | 49 | ||
50 | iDefineObjectConstruction(ListWidget) | 50 | iDefineObjectConstruction(ListWidget) |
51 | 51 | ||
52 | struct Impl_ListWidget { | ||
53 | iWidget widget; | ||
54 | iScrollWidget *scroll; | ||
55 | iSmoothScroll scrollY; | ||
56 | int itemHeight; | ||
57 | iPtrArray items; | ||
58 | size_t hoverItem; | ||
59 | size_t dragItem; | ||
60 | iInt2 dragOrigin; /* offset from mouse to drag item's top-left corner */ | ||
61 | iClick click; | ||
62 | iIntSet invalidItems; | ||
63 | iVisBuf *visBuf; | ||
64 | iBool noHoverWhileScrolling; | ||
65 | }; | ||
66 | |||
67 | static void refreshWhileScrolling_ListWidget_(iAnyObject *any) { | 52 | static void refreshWhileScrolling_ListWidget_(iAnyObject *any) { |
68 | iListWidget *d = any; | 53 | iListWidget *d = any; |
69 | updateVisible_ListWidget(d); | 54 | updateVisible_ListWidget(d); |
@@ -96,11 +81,13 @@ void init_ListWidget(iListWidget *d) { | |||
96 | setThumb_ScrollWidget(d->scroll, 0, 0); | 81 | setThumb_ScrollWidget(d->scroll, 0, 0); |
97 | init_SmoothScroll(&d->scrollY, w, scrollBegan_ListWidget_); | 82 | init_SmoothScroll(&d->scrollY, w, scrollBegan_ListWidget_); |
98 | d->itemHeight = 0; | 83 | d->itemHeight = 0; |
84 | d->scrollMode = normal_ScrollMode; | ||
99 | d->noHoverWhileScrolling = iFalse; | 85 | d->noHoverWhileScrolling = iFalse; |
100 | init_PtrArray(&d->items); | 86 | init_PtrArray(&d->items); |
101 | d->hoverItem = iInvalidPos; | 87 | d->hoverItem = iInvalidPos; |
102 | d->dragItem = iInvalidPos; | 88 | d->dragItem = iInvalidPos; |
103 | d->dragOrigin = zero_I2(); | 89 | d->dragOrigin = zero_I2(); |
90 | d->dragHandleWidth = 0; | ||
104 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 91 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
105 | init_IntSet(&d->invalidItems); | 92 | init_IntSet(&d->invalidItems); |
106 | d->visBuf = new_VisBuf(); | 93 | d->visBuf = new_VisBuf(); |
@@ -202,6 +189,17 @@ void setScrollPos_ListWidget(iListWidget *d, int pos) { | |||
202 | refresh_Widget(as_Widget(d)); | 189 | refresh_Widget(as_Widget(d)); |
203 | } | 190 | } |
204 | 191 | ||
192 | void setScrollMode_ListWidget(iListWidget *d, enum iScrollMode mode) { | ||
193 | d->scrollMode = mode; | ||
194 | } | ||
195 | |||
196 | void setDragHandleWidth_ListWidget(iListWidget *d, int dragHandleWidth) { | ||
197 | d->dragHandleWidth = dragHandleWidth; | ||
198 | if (dragHandleWidth == 0) { | ||
199 | setFlags_Widget(as_Widget(d), touchDrag_WidgetFlag, iFalse); /* mobile drag handles */ | ||
200 | } | ||
201 | } | ||
202 | |||
205 | void scrollOffset_ListWidget(iListWidget *d, int offset) { | 203 | void scrollOffset_ListWidget(iListWidget *d, int offset) { |
206 | moveSpan_SmoothScroll(&d->scrollY, offset, 0); | 204 | moveSpan_SmoothScroll(&d->scrollY, offset, 0); |
207 | } | 205 | } |
@@ -363,6 +361,7 @@ static iBool endDrag_ListWidget_(iListWidget *d, iInt2 endPos) { | |||
363 | if (d->dragItem == iInvalidPos) { | 361 | if (d->dragItem == iInvalidPos) { |
364 | return iFalse; | 362 | return iFalse; |
365 | } | 363 | } |
364 | setFlags_Widget(as_Widget(d), touchDrag_WidgetFlag, iFalse); /* mobile drag handles */ | ||
366 | stop_Anim(&d->scrollY.pos); | 365 | stop_Anim(&d->scrollY.pos); |
367 | enum iDragDestination dstKind; | 366 | enum iDragDestination dstKind; |
368 | const size_t index = resolveDragDestination_ListWidget_(d, endPos, &dstKind); | 367 | const size_t index = resolveDragDestination_ListWidget_(d, endPos, &dstKind); |
@@ -381,12 +380,31 @@ static iBool endDrag_ListWidget_(iListWidget *d, iInt2 endPos) { | |||
381 | return iTrue; | 380 | return iTrue; |
382 | } | 381 | } |
383 | 382 | ||
383 | static iBool isScrollDisabled_ListWidget_(const iListWidget *d, const SDL_Event *ev) { | ||
384 | int dir = 0; | ||
385 | if (ev->type == SDL_MOUSEWHEEL) { | ||
386 | dir = iSign(ev->wheel.y); | ||
387 | } | ||
388 | switch (d->scrollMode) { | ||
389 | case disabled_ScrollMode: | ||
390 | return iTrue; | ||
391 | case disabledAtTopBothDirections_ScrollMode: | ||
392 | return scrollPos_ListWidget(d) <= 0; | ||
393 | case disabledAtTopUpwards_ScrollMode: | ||
394 | return scrollPos_ListWidget(d) <= 0 && dir > 0; | ||
395 | default: | ||
396 | break; | ||
397 | } | ||
398 | return iFalse; | ||
399 | } | ||
400 | |||
384 | static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | 401 | static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { |
385 | iWidget *w = as_Widget(d); | 402 | iWidget *w = as_Widget(d); |
386 | if (isMetricsChange_UserEvent(ev)) { | 403 | if (isMetricsChange_UserEvent(ev)) { |
387 | invalidate_ListWidget(d); | 404 | invalidate_ListWidget(d); |
388 | } | 405 | } |
389 | else if (processEvent_SmoothScroll(&d->scrollY, ev)) { | 406 | else if (!isScrollDisabled_ListWidget_(d, ev) && |
407 | processEvent_SmoothScroll(&d->scrollY, ev)) { | ||
390 | return iTrue; | 408 | return iTrue; |
391 | } | 409 | } |
392 | else if (isCommand_SDLEvent(ev)) { | 410 | else if (isCommand_SDLEvent(ev)) { |
@@ -435,6 +453,29 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | |||
435 | } | 453 | } |
436 | } | 454 | } |
437 | if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { | 455 | if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { |
456 | if (d->dragHandleWidth) { | ||
457 | if (d->dragItem == iInvalidPos) { | ||
458 | const iInt2 wpos = coord_MouseWheelEvent(&ev->wheel); | ||
459 | if (contains_Widget(w, wpos) && | ||
460 | wpos.x >= right_Rect(boundsWithoutVisualOffset_Widget(w)) - d->dragHandleWidth) { | ||
461 | setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); | ||
462 | printf("[%p] touch drag started\n", d); | ||
463 | return iTrue; | ||
464 | } | ||
465 | } | ||
466 | } | ||
467 | if (isScrollDisabled_ListWidget_(d, ev)) { | ||
468 | if (ev->wheel.which == SDL_TOUCH_MOUSEID) { | ||
469 | /* TODO: Could generalize this selection of the scrollable parent. */ | ||
470 | extern iWidgetClass Class_SidebarWidget; | ||
471 | iWidget *sidebar = findParentClass_Widget(w, &Class_SidebarWidget); | ||
472 | if (sidebar) { | ||
473 | transferAffinity_Touch(w, sidebar); | ||
474 | d->noHoverWhileScrolling = iTrue; | ||
475 | } | ||
476 | } | ||
477 | return iFalse; | ||
478 | } | ||
438 | int amount = -ev->wheel.y; | 479 | int amount = -ev->wheel.y; |
439 | if (isPerPixel_MouseWheelEvent(&ev->wheel)) { | 480 | if (isPerPixel_MouseWheelEvent(&ev->wheel)) { |
440 | stop_Anim(&d->scrollY.pos); | 481 | stop_Anim(&d->scrollY.pos); |
@@ -480,7 +521,9 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | |||
480 | return iTrue; | 521 | return iTrue; |
481 | } | 522 | } |
482 | redrawHoverItem_ListWidget_(d); | 523 | redrawHoverItem_ListWidget_(d); |
483 | if (contains_Rect(itemRect_ListWidget(d, d->hoverItem), pos_Click(&d->click)) && | 524 | if (contains_Rect(adjusted_Rect(itemRect_ListWidget(d, d->hoverItem), |
525 | zero_I2(), init_I2(-d->dragHandleWidth, 0)), | ||
526 | pos_Click(&d->click)) && | ||
484 | d->hoverItem != iInvalidPos) { | 527 | d->hoverItem != iInvalidPos) { |
485 | postCommand_Widget(w, "list.clicked arg:%zu item:%p", | 528 | postCommand_Widget(w, "list.clicked arg:%zu item:%p", |
486 | d->hoverItem, constHoverItem_ListWidget(d)); | 529 | d->hoverItem, constHoverItem_ListWidget(d)); |
@@ -580,8 +623,9 @@ static void draw_ListWidget_(const iListWidget *d) { | |||
580 | } | 623 | } |
581 | setClip_Paint(&p, bounds_Widget(w)); | 624 | setClip_Paint(&p, bounds_Widget(w)); |
582 | draw_VisBuf(d->visBuf, addY_I2(topLeft_Rect(bounds), -scrollY), ySpan_Rect(bounds)); | 625 | draw_VisBuf(d->visBuf, addY_I2(topLeft_Rect(bounds), -scrollY), ySpan_Rect(bounds)); |
583 | const iInt2 mousePos = mouseCoord_Window(get_Window(), 0); | 626 | const iBool isMobile = (deviceType_App() != desktop_AppDeviceType); |
584 | if (d->dragItem != iInvalidPos && contains_Rect(bounds, mousePos)) { | 627 | const iInt2 mousePos = mouseCoord_Window(get_Window(), isMobile ? SDL_TOUCH_MOUSEID : 0); |
628 | if (d->dragItem != iInvalidPos && (isMobile || contains_Rect(bounds, mousePos))) { | ||
585 | iInt2 pos = add_I2(mousePos, d->dragOrigin); | 629 | iInt2 pos = add_I2(mousePos, d->dragOrigin); |
586 | const iListItem *item = constAt_PtrArray(&d->items, d->dragItem); | 630 | const iListItem *item = constAt_PtrArray(&d->items, d->dragItem); |
587 | const iRect itemRect = { init_I2(left_Rect(bounds), pos.y), | 631 | const iRect itemRect = { init_I2(left_Rect(bounds), pos.y), |
diff --git a/src/ui/listwidget.h b/src/ui/listwidget.h index 8adf6ac3..da215b19 100644 --- a/src/ui/listwidget.h +++ b/src/ui/listwidget.h | |||
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
25 | #include "scrollwidget.h" | 25 | #include "scrollwidget.h" |
26 | #include "paint.h" | 26 | #include "paint.h" |
27 | 27 | ||
28 | #include <the_Foundation/intset.h> | ||
28 | #include <the_Foundation/ptrarray.h> | 29 | #include <the_Foundation/ptrarray.h> |
29 | 30 | ||
30 | iDeclareType(ListWidget) | 31 | iDeclareType(ListWidget) |
@@ -48,6 +49,34 @@ iDeclareObjectConstruction(ListItem) | |||
48 | iDeclareWidgetClass(ListWidget) | 49 | iDeclareWidgetClass(ListWidget) |
49 | iDeclareObjectConstruction(ListWidget) | 50 | iDeclareObjectConstruction(ListWidget) |
50 | 51 | ||
52 | iDeclareType(VisBuf) | ||
53 | |||
54 | enum iScrollMode { | ||
55 | normal_ScrollMode, | ||
56 | disabledAtTopBothDirections_ScrollMode, | ||
57 | disabledAtTopUpwards_ScrollMode, | ||
58 | disabled_ScrollMode, | ||
59 | }; | ||
60 | |||
61 | struct Impl_ListWidget { | ||
62 | iWidget widget; | ||
63 | iScrollWidget *scroll; | ||
64 | iSmoothScroll scrollY; | ||
65 | int itemHeight; | ||
66 | iPtrArray items; | ||
67 | size_t hoverItem; | ||
68 | size_t dragItem; | ||
69 | iInt2 dragOrigin; /* offset from mouse to drag item's top-left corner */ | ||
70 | int dragHandleWidth; | ||
71 | iClick click; | ||
72 | iIntSet invalidItems; | ||
73 | iVisBuf *visBuf; | ||
74 | enum iScrollMode scrollMode; | ||
75 | iBool noHoverWhileScrolling; | ||
76 | }; | ||
77 | |||
78 | void init_ListWidget (iListWidget *); | ||
79 | |||
51 | void setItemHeight_ListWidget (iListWidget *, int itemHeight); | 80 | void setItemHeight_ListWidget (iListWidget *, int itemHeight); |
52 | 81 | ||
53 | void invalidate_ListWidget (iListWidget *); | 82 | void invalidate_ListWidget (iListWidget *); |
@@ -62,6 +91,8 @@ int itemHeight_ListWidget (const iListWidget *); | |||
62 | int scrollPos_ListWidget (const iListWidget *); | 91 | int scrollPos_ListWidget (const iListWidget *); |
63 | 92 | ||
64 | void setScrollPos_ListWidget (iListWidget *, int pos); | 93 | void setScrollPos_ListWidget (iListWidget *, int pos); |
94 | void setScrollMode_ListWidget (iListWidget *, enum iScrollMode mode); | ||
95 | void setDragHandleWidth_ListWidget(iListWidget *, int dragHandleWidth); | ||
65 | void scrollToItem_ListWidget (iListWidget *, size_t index, uint32_t span); | 96 | void scrollToItem_ListWidget (iListWidget *, size_t index, uint32_t span); |
66 | void scrollOffset_ListWidget (iListWidget *, int offset); | 97 | void scrollOffset_ListWidget (iListWidget *, int offset); |
67 | void scrollOffsetSpan_ListWidget (iListWidget *, int offset, uint32_t span); | 98 | void scrollOffsetSpan_ListWidget (iListWidget *, int offset, uint32_t span); |
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c index da0113ce..f14170ad 100644 --- a/src/ui/lookupwidget.c +++ b/src/ui/lookupwidget.c | |||
@@ -658,23 +658,37 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) { | |||
658 | (equal_Command(cmd, "layout.changed") && | 658 | (equal_Command(cmd, "layout.changed") && |
659 | equal_Rangecc(range_Command(cmd, "id"), "navbar"))) { | 659 | equal_Rangecc(range_Command(cmd, "id"), "navbar"))) { |
660 | /* Position the lookup popup under the URL bar. */ { | 660 | /* Position the lookup popup under the URL bar. */ { |
661 | iRoot *root = w->root; | 661 | iRoot *root = w->root; |
662 | iWidget *url = findChild_Widget(root->widget, "url"); | ||
663 | const int minWidth = iMin(120 * gap_UI, width_Rect(safeRect_Root(root))); | ||
664 | const int urlWidth = width_Widget(url); | ||
665 | int extraWidth = 0; | ||
666 | if (urlWidth < minWidth) { | ||
667 | extraWidth = minWidth - urlWidth; | ||
668 | } | ||
662 | const iRect navBarBounds = bounds_Widget(findChild_Widget(root->widget, "navbar")); | 669 | const iRect navBarBounds = bounds_Widget(findChild_Widget(root->widget, "navbar")); |
663 | iWidget *url = findChild_Widget(root->widget, "url"); | 670 | setFixedSize_Widget( |
664 | setFixedSize_Widget(w, init_I2(width_Widget(url), | 671 | w, |
665 | (bottom_Rect(rect_Root(root)) - bottom_Rect(navBarBounds)) / 2)); | 672 | init_I2(width_Widget(url) + extraWidth, |
666 | setPos_Widget(w, windowToLocal_Widget(w, bottomLeft_Rect(bounds_Widget(url)))); | 673 | (bottom_Rect(rect_Root(root)) - bottom_Rect(navBarBounds)) / 2)); |
667 | #if defined (iPlatformAppleMobile) | 674 | setPos_Widget(w, |
675 | windowToLocal_Widget(w, | ||
676 | max_I2(zero_I2(), | ||
677 | addX_I2(bottomLeft_Rect(bounds_Widget(url)), | ||
678 | -extraWidth / 2)))); | ||
679 | #if defined(iPlatformMobile) | ||
668 | /* TODO: Check this again. */ | 680 | /* TODO: Check this again. */ |
669 | /* Adjust height based on keyboard size. */ { | 681 | /* Adjust height based on keyboard size. */ { |
670 | w->rect.size.y = bottom_Rect(visibleRect_Root(root)) - top_Rect(bounds_Widget(w)); | 682 | w->rect.size.y = bottom_Rect(visibleRect_Root(root)) - top_Rect(bounds_Widget(w)); |
683 | # if defined (iPlatformAppleMobile) | ||
671 | if (deviceType_App() == phone_AppDeviceType) { | 684 | if (deviceType_App() == phone_AppDeviceType) { |
672 | float l, r; | 685 | float l = 0.0f, r = 0.0f; |
673 | safeAreaInsets_iOS(&l, NULL, &r, NULL); | 686 | safeAreaInsets_iOS(&l, NULL, &r, NULL); |
674 | w->rect.size.x = size_Root(root).x - l - r; | 687 | w->rect.size.x = size_Root(root).x - l - r; |
675 | w->rect.pos.x = l; | 688 | w->rect.pos.x = l; |
676 | /* TODO: Need to use windowToLocal_Widget? */ | 689 | /* TODO: Need to use windowToLocal_Widget? */ |
677 | } | 690 | } |
691 | # endif | ||
678 | } | 692 | } |
679 | #endif | 693 | #endif |
680 | arrange_Widget(w); | 694 | arrange_Widget(w); |
diff --git a/src/ui/mediaui.c b/src/ui/mediaui.c index 4f2499b0..f0070688 100644 --- a/src/ui/mediaui.c +++ b/src/ui/mediaui.c | |||
@@ -104,7 +104,7 @@ static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) { | |||
104 | if (align == right_Alignment) { | 104 | if (align == right_Alignment) { |
105 | pos.x -= size.x; | 105 | pos.x -= size.x; |
106 | } | 106 | } |
107 | drawRange_Text(font, addY_I2(pos, -gap_UI / 8), color, range_String(&num)); | 107 | drawRange_Text(font, addY_I2(pos, gap_UI / 2), color, range_String(&num)); |
108 | deinit_String(&num); | 108 | deinit_String(&num); |
109 | return size.x; | 109 | return size.x; |
110 | } | 110 | } |
@@ -238,6 +238,56 @@ void init_DownloadUI(iDownloadUI *d, const iMedia *media, uint16_t mediaId, iRec | |||
238 | /*----------------------------------------------------------------------------------------------*/ | 238 | /*----------------------------------------------------------------------------------------------*/ |
239 | 239 | ||
240 | iBool processEvent_DownloadUI(iDownloadUI *d, const SDL_Event *ev) { | 240 | iBool processEvent_DownloadUI(iDownloadUI *d, const SDL_Event *ev) { |
241 | if (ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) { | ||
242 | const iInt2 mouse = init_I2(ev->button.x, ev->button.y); | ||
243 | if (!contains_Rect(d->bounds, mouse)) { | ||
244 | return iFalse; | ||
245 | } | ||
246 | float bytesPerSecond; | ||
247 | const iString *path; | ||
248 | iBool isFinished; | ||
249 | downloadStats_Media(d->media, (iMediaId){ download_MediaType, d->mediaId }, | ||
250 | &path, &bytesPerSecond, &isFinished); | ||
251 | if (isFinished) { | ||
252 | if (ev->button.button == SDL_BUTTON_RIGHT && ev->type == SDL_MOUSEBUTTONDOWN) { | ||
253 | const iMenuItem items[] = { | ||
254 | /* Items related to the file */ | ||
255 | { openTab_Icon " ${menu.opentab}", | ||
256 | 0, | ||
257 | 0, | ||
258 | format_CStr("!open newtab:1 url:%s", | ||
259 | cstrCollect_String(makeFileUrl_String(path))) }, | ||
260 | #if defined (iPlatformAppleDesktop) | ||
261 | { "${menu.reveal.macos}", | ||
262 | 0, | ||
263 | 0, | ||
264 | format_CStr("!reveal path:%s", cstr_String(path)) }, | ||
265 | #endif | ||
266 | #if defined (iPlatformAppleMobile) | ||
267 | { export_Icon " ${menu.share}", | ||
268 | 0, | ||
269 | 0, | ||
270 | format_CStr("!reveal path:%s", cstr_String(path)) }, | ||
271 | #endif | ||
272 | #if defined (iPlatformLinux) | ||
273 | { "${menu.reveal.filemgr}", | ||
274 | 0, | ||
275 | 0, | ||
276 | format_CStr("!reveal path:%s", cstr_String(path)) }, | ||
277 | #endif | ||
278 | { "---" }, | ||
279 | /* Generic items */ | ||
280 | { "${menu.downloads}", 0, 0, "downloads.open newtab:1" }, | ||
281 | }; | ||
282 | openMenu_Widget(makeMenu_Widget(get_Root()->widget, items, iElemCount(items)), | ||
283 | mouse); | ||
284 | return iTrue; | ||
285 | } | ||
286 | else if (ev->button.button == SDL_BUTTON_LEFT && ev->type == SDL_MOUSEBUTTONUP) { | ||
287 | postCommandf_App("open default:1 url:%s", cstrCollect_String(makeFileUrl_String(path))); | ||
288 | } | ||
289 | } | ||
290 | } | ||
241 | return iFalse; | 291 | return iFalse; |
242 | } | 292 | } |
243 | 293 | ||
diff --git a/src/ui/mobile.c b/src/ui/mobile.c index abc91218..cf955423 100644 --- a/src/ui/mobile.c +++ b/src/ui/mobile.c | |||
@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
23 | #include "mobile.h" | 23 | #include "mobile.h" |
24 | 24 | ||
25 | #include "app.h" | 25 | #include "app.h" |
26 | #include "certlistwidget.h" | ||
26 | #include "command.h" | 27 | #include "command.h" |
27 | #include "defs.h" | 28 | #include "defs.h" |
28 | #include "inputwidget.h" | 29 | #include "inputwidget.h" |
@@ -36,14 +37,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
36 | # include "ios.h" | 37 | # include "ios.h" |
37 | #endif | 38 | #endif |
38 | 39 | ||
40 | const iToolbarActionSpec toolbarActions_Mobile[max_ToolbarAction] = { | ||
41 | { backArrow_Icon, "${menu.back}", "navigate.back" }, | ||
42 | { forwardArrow_Icon, "${menu.forward}", "navigate.forward" }, | ||
43 | { home_Icon, "${menu.home}", "navigate.home" }, | ||
44 | { upArrow_Icon, "${menu.parent}", "navigate.parent" }, | ||
45 | { reload_Icon, "${menu.reload}", "navigate.reload" }, | ||
46 | { openTab_Icon, "${menu.newtab}", "tabs.new" }, | ||
47 | { close_Icon, "${menu.closetab}", "tabs.close" }, | ||
48 | { bookmark_Icon, "${menu.page.bookmark}", "bookmark.add" }, | ||
49 | { globe_Icon, "${menu.page.translate}", "document.translate" }, | ||
50 | { upload_Icon, "${menu.page.upload}", "document.upload" }, | ||
51 | { edit_Icon, "${menu.page.upload.edit}", "document.upload copy:1" }, | ||
52 | { magnifyingGlass_Icon, "${menu.find}", "focus.set id:find.input" }, | ||
53 | { gear_Icon, "${menu.settings}", "preferences" }, | ||
54 | { leftHalf_Icon, "${menu.sidebar.left}", "sidebar.toggle" }, | ||
55 | }; | ||
56 | |||
39 | iBool isUsingPanelLayout_Mobile(void) { | 57 | iBool isUsingPanelLayout_Mobile(void) { |
40 | return deviceType_App() != desktop_AppDeviceType; | 58 | return deviceType_App() != desktop_AppDeviceType; |
41 | } | 59 | } |
42 | 60 | ||
61 | #define topPanelMinWidth_Mobile (80 * gap_UI) | ||
62 | |||
43 | static iBool isSideBySideLayout_(void) { | 63 | static iBool isSideBySideLayout_(void) { |
64 | /* Minimum is an even split. */ | ||
65 | const int safeWidth = safeRect_Root(get_Root()).size.x; | ||
66 | if (safeWidth / 2 < topPanelMinWidth_Mobile) { | ||
67 | return iFalse; | ||
68 | } | ||
44 | if (deviceType_App() == phone_AppDeviceType) { | 69 | if (deviceType_App() == phone_AppDeviceType) { |
45 | return isLandscape_App(); | 70 | return isLandscape_App(); |
46 | } | 71 | } |
72 | /* Tablet may still be too narrow. */ | ||
47 | return numRoots_Window(get_Window()) == 1; | 73 | return numRoots_Window(get_Window()) == 1; |
48 | } | 74 | } |
49 | 75 | ||
@@ -99,6 +125,16 @@ static iWidget *findTitleLabel_(iWidget *panel) { | |||
99 | return NULL; | 125 | return NULL; |
100 | } | 126 | } |
101 | 127 | ||
128 | static void updateCertListHeight_(iWidget *detailStack) { | ||
129 | iWidget *certList = findChild_Widget(detailStack, "certlist"); | ||
130 | if (certList) { | ||
131 | setFixedSize_Widget(certList, | ||
132 | init_I2(-1, | ||
133 | -1 * gap_UI + bottom_Rect(safeRect_Root(certList->root)) - | ||
134 | top_Rect(boundsWithoutVisualOffset_Widget(certList)))); | ||
135 | } | ||
136 | } | ||
137 | |||
102 | static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) { | 138 | static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) { |
103 | if (equal_Command(cmd, "window.resized")) { | 139 | if (equal_Command(cmd, "window.resized")) { |
104 | const iBool isPortrait = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); | 140 | const iBool isPortrait = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); |
@@ -117,11 +153,13 @@ static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) | |||
117 | const int pad = isPortrait ? 0 : 3 * gap_UI; | 153 | const int pad = isPortrait ? 0 : 3 * gap_UI; |
118 | if (isSideBySide) { | 154 | if (isSideBySide) { |
119 | iAssert(topPanel); | 155 | iAssert(topPanel); |
120 | topPanel->rect.size.x = (deviceType_App() == phone_AppDeviceType ? | 156 | topPanel->rect.size.x = iMax(topPanelMinWidth_Mobile, |
121 | safeRoot.size.x * 2 / 5 : (safeRoot.size.x / 3)); | 157 | (deviceType_App() == phone_AppDeviceType ? |
122 | } | 158 | safeRoot.size.x * 2 / 5 : safeRoot.size.x / 3)); |
159 | } | ||
123 | if (deviceType_App() == tablet_AppDeviceType) { | 160 | if (deviceType_App() == tablet_AppDeviceType) { |
124 | setPadding_Widget(topPanel, pad, 0, pad, pad); | 161 | setPadding_Widget(topPanel, pad, 0, pad, pad); |
162 | #if 0 | ||
125 | if (numPanels == 0) { | 163 | if (numPanels == 0) { |
126 | setFlags_Widget(sheet, centerHorizontal_WidgetFlag, iTrue); | 164 | setFlags_Widget(sheet, centerHorizontal_WidgetFlag, iTrue); |
127 | const int sheetWidth = iMin(safeRoot.size.x, safeRoot.size.y); | 165 | const int sheetWidth = iMin(safeRoot.size.x, safeRoot.size.y); |
@@ -129,6 +167,7 @@ static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) | |||
129 | setFixedSize_Widget(sheet, init_I2(sheetWidth, -1)); | 167 | setFixedSize_Widget(sheet, init_I2(sheetWidth, -1)); |
130 | setFixedSize_Widget(navi, init_I2(sheetWidth, -1)); | 168 | setFixedSize_Widget(navi, init_I2(sheetWidth, -1)); |
131 | } | 169 | } |
170 | #endif | ||
132 | } | 171 | } |
133 | iWidget *detailTitle = findChild_Widget(navi, "detailtitle"); { | 172 | iWidget *detailTitle = findChild_Widget(navi, "detailtitle"); { |
134 | setPos_Widget(detailTitle, init_I2(width_Widget(topPanel), 0)); | 173 | setPos_Widget(detailTitle, init_I2(width_Widget(topPanel), 0)); |
@@ -143,9 +182,10 @@ static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) | |||
143 | if (isSideBySide) { | 182 | if (isSideBySide) { |
144 | setVisualOffset_Widget(panel, 0, 0, 0); | 183 | setVisualOffset_Widget(panel, 0, 0, 0); |
145 | } | 184 | } |
146 | setPadding_Widget(panel, pad, 0, pad, pad); | 185 | setPadding_Widget(panel, pad, 0, pad, pad + bottomSafeInset_Mobile()); |
147 | } | 186 | } |
148 | arrange_Widget(mainDetailSplit); | 187 | arrange_Widget(mainDetailSplit); |
188 | updateCertListHeight_(detailStack); | ||
149 | } | 189 | } |
150 | else if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd)) { | 190 | else if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd)) { |
151 | if (focus_Widget() && class_Widget(focus_Widget()) == &Class_InputWidget) { | 191 | if (focus_Widget() && class_Widget(focus_Widget()) == &Class_InputWidget) { |
@@ -168,13 +208,16 @@ size_t currentPanelIndex_Mobile(const iWidget *panels) { | |||
168 | return iInvalidPos; | 208 | return iInvalidPos; |
169 | } | 209 | } |
170 | 210 | ||
211 | iWidget *panel_Mobile(const iWidget *panels, size_t index) { | ||
212 | return child_Widget(findChild_Widget(panels, "detailstack"), index); | ||
213 | } | ||
214 | |||
171 | static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | 215 | static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { |
172 | const iBool isPortrait = !isSideBySideLayout_(); | 216 | const iBool isPortrait = !isSideBySideLayout_(); |
173 | if (equal_Command(cmd, "panel.open")) { | 217 | if (equal_Command(cmd, "panel.open")) { |
174 | iWidget *button = pointer_Command(cmd); | 218 | /* This command is sent by the button that opens the panel. */ |
219 | iWidget *button = pointer_Command(cmd); | ||
175 | iWidget *panel = userData_Object(button); | 220 | iWidget *panel = userData_Object(button); |
176 | // openMenu_Widget(panel, innerToWindow_Widget(panel, zero_I2())); | ||
177 | // setFlags_Widget(panel, hidden_WidgetFlag, iFalse); | ||
178 | unselectAllPanelButtons_(topPanel); | 221 | unselectAllPanelButtons_(topPanel); |
179 | int panelIndex = -1; | 222 | int panelIndex = -1; |
180 | size_t childIndex = 0; | 223 | size_t childIndex = 0; |
@@ -184,7 +227,7 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | |||
184 | /* Animate the current panel in. */ | 227 | /* Animate the current panel in. */ |
185 | if (child == panel && isPortrait) { | 228 | if (child == panel && isPortrait) { |
186 | setupSheetTransition_Mobile(panel, iTrue); | 229 | setupSheetTransition_Mobile(panel, iTrue); |
187 | panelIndex = childIndex; | 230 | panelIndex = (int) childIndex; |
188 | } | 231 | } |
189 | childIndex++; | 232 | childIndex++; |
190 | } | 233 | } |
@@ -196,6 +239,7 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | |||
196 | setText_LabelWidget(detailTitle, text_LabelWidget((iLabelWidget *) findTitleLabel_(panel))); | 239 | setText_LabelWidget(detailTitle, text_LabelWidget((iLabelWidget *) findTitleLabel_(panel))); |
197 | setFlags_Widget(button, selected_WidgetFlag, iTrue); | 240 | setFlags_Widget(button, selected_WidgetFlag, iTrue); |
198 | postCommand_Widget(topPanel, "panel.changed arg:%d", panelIndex); | 241 | postCommand_Widget(topPanel, "panel.changed arg:%d", panelIndex); |
242 | updateCertListHeight_(findDetailStack_(topPanel)); | ||
199 | return iTrue; | 243 | return iTrue; |
200 | } | 244 | } |
201 | if (equal_Command(cmd, "swipe.back")) { | 245 | if (equal_Command(cmd, "swipe.back")) { |
@@ -227,6 +271,10 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | |||
227 | else if (findWidget_App("upload")) { | 271 | else if (findWidget_App("upload")) { |
228 | postCommand_App("upload.cancel"); | 272 | postCommand_App("upload.cancel"); |
229 | } | 273 | } |
274 | else if (findWidget_App("bmed.sidebar") || findWidget_App("bmed.create") || | ||
275 | findWidget_App("bmed")) { | ||
276 | postCommand_App("bmed.cancel"); | ||
277 | } | ||
230 | else if (findWidget_App("ident")) { | 278 | else if (findWidget_App("ident")) { |
231 | postCommand_Widget(topPanel, "ident.cancel"); | 279 | postCommand_Widget(topPanel, "ident.cancel"); |
232 | } | 280 | } |
@@ -468,7 +516,7 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { | |||
468 | iLabelWidget *heading = NULL; | 516 | iLabelWidget *heading = NULL; |
469 | iWidget * value = NULL; | 517 | iWidget * value = NULL; |
470 | const char * spec = item->label; | 518 | const char * spec = item->label; |
471 | const char * id = cstr_Rangecc(range_Command(spec, "id")); | 519 | const char * id = cstr_Command(spec, "id"); |
472 | const char * label = hasLabel_Command(spec, "text") | 520 | const char * label = hasLabel_Command(spec, "text") |
473 | ? suffixPtr_Command(spec, "text") | 521 | ? suffixPtr_Command(spec, "text") |
474 | : format_CStr("${%s}", id); | 522 | : format_CStr("${%s}", id); |
@@ -482,7 +530,7 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { | |||
482 | collapse_WidgetFlag); | 530 | collapse_WidgetFlag); |
483 | setFont_LabelWidget(title, uiLabelLargeBold_FontId); | 531 | setFont_LabelWidget(title, uiLabelLargeBold_FontId); |
484 | setTextColor_LabelWidget(title, uiHeading_ColorId); | 532 | setTextColor_LabelWidget(title, uiHeading_ColorId); |
485 | setAllCaps_LabelWidget(title, iTrue); | 533 | // setAllCaps_LabelWidget(title, iTrue); |
486 | setId_Widget(as_Widget(title), id); | 534 | setId_Widget(as_Widget(title), id); |
487 | } | 535 | } |
488 | else if (equal_Command(spec, "heading")) { | 536 | else if (equal_Command(spec, "heading")) { |
@@ -511,10 +559,12 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { | |||
511 | setId_Widget(as_Widget(drop), id); | 559 | setId_Widget(as_Widget(drop), id); |
512 | widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label), as_Widget(drop)); | 560 | widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label), as_Widget(drop)); |
513 | setCommandHandler_Widget(widget, dropdownHeadingHandler_); | 561 | setCommandHandler_Widget(widget, dropdownHeadingHandler_); |
562 | widget->padding[2] = gap_UI; | ||
514 | setUserData_Object(widget, drop); | 563 | setUserData_Object(widget, drop); |
515 | } | 564 | } |
516 | else if (equal_Command(spec, "radio") || equal_Command(spec, "buttons")) { | 565 | else if (equal_Command(spec, "radio") || equal_Command(spec, "buttons")) { |
517 | const iBool isRadio = equal_Command(spec, "radio"); | 566 | const iBool isRadio = equal_Command(spec, "radio"); |
567 | const iBool isHorizontal = argLabel_Command(spec, "horizontal"); | ||
518 | addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | 568 | addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); |
519 | iLabelWidget *head = makeHeading_Widget(label); | 569 | iLabelWidget *head = makeHeading_Widget(label); |
520 | setAllCaps_LabelWidget(head, iTrue); | 570 | setAllCaps_LabelWidget(head, iTrue); |
@@ -522,25 +572,42 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { | |||
522 | addChild_Widget(panel, iClob(head)); | 572 | addChild_Widget(panel, iClob(head)); |
523 | widget = new_Widget(); | 573 | widget = new_Widget(); |
524 | setBackgroundColor_Widget(widget, uiBackgroundSidebar_ColorId); | 574 | setBackgroundColor_Widget(widget, uiBackgroundSidebar_ColorId); |
525 | setPadding_Widget(widget, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI); | 575 | const int hPad = (isHorizontal ? 0 : 1); |
576 | setPadding_Widget(widget, hPad * gap_UI, 2 * gap_UI, hPad * gap_UI, 2 * gap_UI); | ||
526 | setFlags_Widget(widget, | 577 | setFlags_Widget(widget, |
527 | borderTop_WidgetFlag | | 578 | borderTop_WidgetFlag | |
528 | borderBottom_WidgetFlag | | 579 | borderBottom_WidgetFlag | |
529 | arrangeHorizontal_WidgetFlag | | 580 | (isHorizontal ? arrangeHorizontal_WidgetFlag : arrangeVertical_WidgetFlag) | |
530 | arrangeHeight_WidgetFlag | | 581 | arrangeHeight_WidgetFlag | |
531 | resizeToParentWidth_WidgetFlag | | 582 | resizeToParentWidth_WidgetFlag | |
532 | resizeWidthOfChildren_WidgetFlag, | 583 | resizeWidthOfChildren_WidgetFlag, |
533 | iTrue); | 584 | iTrue); |
534 | setId_Widget(widget, id); | 585 | setId_Widget(widget, id); |
586 | iBool isFirst = iTrue; | ||
535 | for (const iMenuItem *radioItem = item->data; radioItem->label; radioItem++) { | 587 | for (const iMenuItem *radioItem = item->data; radioItem->label; radioItem++) { |
536 | const char * radId = cstr_Rangecc(range_Command(radioItem->label, "id")); | 588 | if (!isHorizontal && !isFirst) { |
537 | int64_t flags = noBackground_WidgetFlag; | 589 | /* The separator is padded from the left so we need two. */ |
590 | iWidget *sep = new_Widget(); | ||
591 | iWidget *sep2 = new_Widget(); | ||
592 | addChildFlags_Widget(sep, iClob(sep2), 0); | ||
593 | setFlags_Widget(sep, arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag, iTrue); | ||
594 | setBackgroundColor_Widget(sep2, uiSeparator_ColorId); | ||
595 | setFixedSize_Widget(sep2, init_I2(-1, gap_UI / 4)); | ||
596 | setPadding_Widget(sep, 5 * gap_UI, 0, 0, 0); | ||
597 | addChildFlags_Widget(widget, iClob(sep), 0); | ||
598 | } | ||
599 | isFirst = iFalse; | ||
600 | const char * radId = cstr_Command(radioItem->label, "id"); | ||
601 | int64_t flags = noBackground_WidgetFlag | frameless_WidgetFlag; | ||
602 | if (!isHorizontal) { | ||
603 | flags |= alignLeft_WidgetFlag; | ||
604 | } | ||
538 | iLabelWidget *button; | 605 | iLabelWidget *button; |
539 | if (isRadio) { | 606 | if (isRadio) { |
540 | const char *radLabel = | 607 | const char *radLabel = |
541 | hasLabel_Command(radioItem->label, "label") | 608 | hasLabel_Command(radioItem->label, "label") |
542 | ? format_CStr("${%s}", | 609 | ? format_CStr("${%s}", |
543 | cstr_Rangecc(range_Command(radioItem->label, "label"))) | 610 | cstr_Command(radioItem->label, "label")) |
544 | : suffixPtr_Command(radioItem->label, "text"); | 611 | : suffixPtr_Command(radioItem->label, "text"); |
545 | button = new_LabelWidget(radLabel, radioItem->command); | 612 | button = new_LabelWidget(radLabel, radioItem->command); |
546 | flags |= radio_WidgetFlag; | 613 | flags |= radio_WidgetFlag; |
@@ -549,17 +616,21 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { | |||
549 | button = (iLabelWidget *) makeToggle_Widget(radId); | 616 | button = (iLabelWidget *) makeToggle_Widget(radId); |
550 | setTextCStr_LabelWidget(button, format_CStr("${%s}", radId)); | 617 | setTextCStr_LabelWidget(button, format_CStr("${%s}", radId)); |
551 | setFlags_Widget(as_Widget(button), fixedWidth_WidgetFlag, iFalse); | 618 | setFlags_Widget(as_Widget(button), fixedWidth_WidgetFlag, iFalse); |
552 | updateSize_LabelWidget(button); | ||
553 | } | 619 | } |
554 | setId_Widget(as_Widget(button), radId); | 620 | setId_Widget(as_Widget(button), radId); |
555 | setFont_LabelWidget(button, uiLabelMedium_FontId); | 621 | setFont_LabelWidget(button, deviceType_App() == phone_AppDeviceType ? |
622 | (isHorizontal ? uiLabelMedium_FontId : uiLabelBig_FontId) : labelFont_()); | ||
623 | setCheckMark_LabelWidget(button, !isHorizontal); | ||
624 | setPadding_Widget(as_Widget(button), gap_UI, 1 * gap_UI, 0, 1 * gap_UI); | ||
625 | updateSize_LabelWidget(button); | ||
626 | setPadding_Widget(widget, 0, 0, 0, 0); | ||
556 | addChildFlags_Widget(widget, iClob(button), flags); | 627 | addChildFlags_Widget(widget, iClob(button), flags); |
557 | } | 628 | } |
558 | } | 629 | } |
559 | else if (equal_Command(spec, "input")) { | 630 | else if (equal_Command(spec, "input")) { |
560 | iInputWidget *input = new_InputWidget(argU32Label_Command(spec, "maxlen")); | 631 | iInputWidget *input = new_InputWidget(argU32Label_Command(spec, "maxlen")); |
561 | if (hasLabel_Command(spec, "hint")) { | 632 | if (hasLabel_Command(spec, "hint")) { |
562 | setHint_InputWidget(input, cstr_Lang(cstr_Rangecc(range_Command(spec, "hint")))); | 633 | setHint_InputWidget(input, cstr_Lang(cstr_Command(spec, "hint"))); |
563 | } | 634 | } |
564 | setId_Widget(as_Widget(input), id); | 635 | setId_Widget(as_Widget(input), id); |
565 | setUrlContent_InputWidget(input, argLabel_Command(spec, "url")); | 636 | setUrlContent_InputWidget(input, argLabel_Command(spec, "url")); |
@@ -570,12 +641,13 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { | |||
570 | setFlags_Widget(widget, expand_WidgetFlag, iTrue); | 641 | setFlags_Widget(widget, expand_WidgetFlag, iTrue); |
571 | } | 642 | } |
572 | else { | 643 | else { |
644 | setFlags_Widget(as_Widget(input), alignRight_WidgetFlag, iTrue); | ||
573 | setContentPadding_InputWidget(input, 3 * gap_UI, 0); | 645 | setContentPadding_InputWidget(input, 3 * gap_UI, 0); |
574 | if (hasLabel_Command(spec, "unit")) { | 646 | if (hasLabel_Command(spec, "unit")) { |
575 | iWidget *unit = addChildFlags_Widget( | 647 | iWidget *unit = addChildFlags_Widget( |
576 | as_Widget(input), | 648 | as_Widget(input), |
577 | iClob(new_LabelWidget( | 649 | iClob(new_LabelWidget( |
578 | format_CStr("${%s}", cstr_Rangecc(range_Command(spec, "unit"))), NULL)), | 650 | format_CStr("${%s}", cstr_Command(spec, "unit")), NULL)), |
579 | frameless_WidgetFlag | moveToParentRightEdge_WidgetFlag | | 651 | frameless_WidgetFlag | moveToParentRightEdge_WidgetFlag | |
580 | resizeToParentHeight_WidgetFlag); | 652 | resizeToParentHeight_WidgetFlag); |
581 | setContentPadding_InputWidget(input, -1, width_Widget(unit) - 4 * gap_UI); | 653 | setContentPadding_InputWidget(input, -1, width_Widget(unit) - 4 * gap_UI); |
@@ -586,6 +658,14 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { | |||
586 | setUserData_Object(widget, input); | 658 | setUserData_Object(widget, input); |
587 | } | 659 | } |
588 | } | 660 | } |
661 | else if (equal_Command(spec, "certlist")) { | ||
662 | iCertListWidget *certList = new_CertListWidget(); | ||
663 | iListWidget *list = (iListWidget *) certList; | ||
664 | setBackgroundColor_Widget(as_Widget(list), uiBackgroundSidebar_ColorId); | ||
665 | widget = as_Widget(certList); | ||
666 | updateItems_CertListWidget(certList); | ||
667 | invalidate_ListWidget(list); | ||
668 | } | ||
589 | else if (equal_Command(spec, "button")) { | 669 | else if (equal_Command(spec, "button")) { |
590 | widget = as_Widget(heading = makePanelButton_(label, item->command)); | 670 | widget = as_Widget(heading = makePanelButton_(label, item->command)); |
591 | setFlags_Widget(widget, selected_WidgetFlag, argLabel_Command(spec, "selected") != 0); | 671 | setFlags_Widget(widget, selected_WidgetFlag, argLabel_Command(spec, "selected") != 0); |
@@ -599,6 +679,9 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { | |||
599 | fixedHeight_WidgetFlag | | 679 | fixedHeight_WidgetFlag | |
600 | (!argLabel_Command(spec, "frame") ? frameless_WidgetFlag : 0), | 680 | (!argLabel_Command(spec, "frame") ? frameless_WidgetFlag : 0), |
601 | iTrue); | 681 | iTrue); |
682 | if (argLabel_Command(spec, "font")) { | ||
683 | setFont_LabelWidget(lab, argLabel_Command(spec, "font")); | ||
684 | } | ||
602 | } | 685 | } |
603 | else if (equal_Command(spec, "padding")) { | 686 | else if (equal_Command(spec, "padding")) { |
604 | float height = 1.5f; | 687 | float height = 1.5f; |
@@ -670,8 +753,10 @@ void initPanels_Mobile(iWidget *panels, iWidget *parentWidget, | |||
670 | setFlags_Widget(panels, | 753 | setFlags_Widget(panels, |
671 | resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag | | 754 | resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag | |
672 | frameless_WidgetFlag | focusRoot_WidgetFlag | commandOnClick_WidgetFlag | | 755 | frameless_WidgetFlag | focusRoot_WidgetFlag | commandOnClick_WidgetFlag | |
756 | horizontalOffset_WidgetFlag | | ||
673 | /*overflowScrollable_WidgetFlag |*/ leftEdgeDraggable_WidgetFlag, | 757 | /*overflowScrollable_WidgetFlag |*/ leftEdgeDraggable_WidgetFlag, |
674 | iTrue); | 758 | iTrue); |
759 | panels->flags2 |= fadeBackground_WidgetFlag2; | ||
675 | setFlags_Widget(panels, overflowScrollable_WidgetFlag, iFalse); | 760 | setFlags_Widget(panels, overflowScrollable_WidgetFlag, iFalse); |
676 | /* The top-level split between main and detail panels. */ | 761 | /* The top-level split between main and detail panels. */ |
677 | iWidget *mainDetailSplit = makeHDiv_Widget(); { | 762 | iWidget *mainDetailSplit = makeHDiv_Widget(); { |
@@ -733,7 +818,7 @@ void initPanels_Mobile(iWidget *panels, iWidget *parentWidget, | |||
733 | const iMenuItem *item = &itemsNullTerminated[i]; | 818 | const iMenuItem *item = &itemsNullTerminated[i]; |
734 | if (equal_Command(item->label, "panel")) { | 819 | if (equal_Command(item->label, "panel")) { |
735 | haveDetailPanels = iTrue; | 820 | haveDetailPanels = iTrue; |
736 | const char *id = cstr_Rangecc(range_Command(item->label, "id")); | 821 | const char *id = cstr_Command(item->label, "id"); |
737 | const iString *label = hasLabel_Command(item->label, "text") | 822 | const iString *label = hasLabel_Command(item->label, "text") |
738 | ? collect_String(suffix_Command(item->label, "text")) | 823 | ? collect_String(suffix_Command(item->label, "text")) |
739 | : collectNewFormat_String("${%s}", id); | 824 | : collectNewFormat_String("${%s}", id); |
@@ -849,18 +934,21 @@ void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { | |||
849 | if (!isUsingPanelLayout_Mobile()) { | 934 | if (!isUsingPanelLayout_Mobile()) { |
850 | return; | 935 | return; |
851 | } | 936 | } |
852 | const iBool isSlidePanel = (flags_Widget(sheet) & horizontalOffset_WidgetFlag) != 0; | 937 | const iBool isHorizPanel = (flags_Widget(sheet) & horizontalOffset_WidgetFlag) != 0; |
853 | if (isSlidePanel && isLandscape_App()) { | 938 | if (isHorizPanel && isLandscape_App()) { |
854 | return; | 939 | return; |
855 | } | 940 | } |
941 | const int maxOffset = isHorizPanel ? width_Widget(sheet) | ||
942 | : isPortraitPhone_App() ? height_Widget(sheet) | ||
943 | : (12 * gap_UI); | ||
856 | if (isIncoming) { | 944 | if (isIncoming) { |
857 | setVisualOffset_Widget(sheet, isSlidePanel ? width_Widget(sheet) : height_Widget(sheet), 0, 0); | 945 | setVisualOffset_Widget(sheet, maxOffset, 0, 0); |
858 | setVisualOffset_Widget(sheet, 0, 330, easeOut_AnimFlag | softer_AnimFlag); | 946 | setVisualOffset_Widget(sheet, 0, 330, easeOut_AnimFlag | softer_AnimFlag); |
859 | } | 947 | } |
860 | else { | 948 | else { |
861 | const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset) - 0) > 1; | 949 | const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset) - 0) > 1; |
862 | setVisualOffset_Widget(sheet, | 950 | setVisualOffset_Widget(sheet, |
863 | isSlidePanel ? width_Widget(sheet) : height_Widget(sheet), | 951 | maxOffset, |
864 | wasDragged ? 100 : 200, | 952 | wasDragged ? 100 : 200, |
865 | wasDragged ? 0 : easeIn_AnimFlag | softer_AnimFlag); | 953 | wasDragged ? 0 : easeIn_AnimFlag | softer_AnimFlag); |
866 | } | 954 | } |
@@ -930,3 +1018,13 @@ void setupSheetTransition_Mobile(iWidget *sheet, int flags) { | |||
930 | } | 1018 | } |
931 | } | 1019 | } |
932 | } | 1020 | } |
1021 | |||
1022 | int bottomSafeInset_Mobile(void) { | ||
1023 | #if defined (iPlatformAppleMobile) | ||
1024 | float bot; | ||
1025 | safeAreaInsets_iOS(NULL, NULL, NULL, &bot); | ||
1026 | return iRound(bot); | ||
1027 | #else | ||
1028 | return 0; | ||
1029 | #endif | ||
1030 | } | ||
diff --git a/src/ui/mobile.h b/src/ui/mobile.h index 9d7ac8e4..a719f20b 100644 --- a/src/ui/mobile.h +++ b/src/ui/mobile.h | |||
@@ -22,8 +22,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #pragma once | 23 | #pragma once |
24 | 24 | ||
25 | #include "defs.h" | ||
25 | #include <the_Foundation/rect.h> | 26 | #include <the_Foundation/rect.h> |
26 | 27 | ||
28 | iDeclareType(ToolbarActionSpec) | ||
29 | |||
30 | struct Impl_ToolbarActionSpec { | ||
31 | const char *icon; | ||
32 | const char *label; | ||
33 | const char *command; | ||
34 | }; | ||
35 | |||
36 | extern const iToolbarActionSpec toolbarActions_Mobile[max_ToolbarAction]; | ||
37 | |||
27 | iDeclareType(Widget) | 38 | iDeclareType(Widget) |
28 | iDeclareType(MenuItem) | 39 | iDeclareType(MenuItem) |
29 | 40 | ||
@@ -39,6 +50,7 @@ void initPanels_Mobile (iWidget *panels, iWidget *parentWidget, | |||
39 | const iMenuItem *itemsNullTerminated, | 50 | const iMenuItem *itemsNullTerminated, |
40 | const iMenuItem *actions, size_t numActions); | 51 | const iMenuItem *actions, size_t numActions); |
41 | 52 | ||
53 | iWidget * panel_Mobile (const iWidget *panels, size_t index); | ||
42 | size_t currentPanelIndex_Mobile (const iWidget *panels); | 54 | size_t currentPanelIndex_Mobile (const iWidget *panels); |
43 | 55 | ||
44 | enum iTransitionFlags { | 56 | enum iTransitionFlags { |
@@ -55,3 +67,5 @@ enum iTransitionDir { | |||
55 | 67 | ||
56 | void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); | 68 | void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); |
57 | void setupSheetTransition_Mobile (iWidget *sheet, int flags); | 69 | void setupSheetTransition_Mobile (iWidget *sheet, int flags); |
70 | |||
71 | int bottomSafeInset_Mobile (void); | ||
diff --git a/src/ui/paint.c b/src/ui/paint.c index b92be27e..5e66f521 100644 --- a/src/ui/paint.c +++ b/src/ui/paint.c | |||
@@ -41,6 +41,7 @@ void init_Paint(iPaint *d) { | |||
41 | d->dst = get_Window(); | 41 | d->dst = get_Window(); |
42 | d->setTarget = NULL; | 42 | d->setTarget = NULL; |
43 | d->oldTarget = NULL; | 43 | d->oldTarget = NULL; |
44 | d->oldOrigin = zero_I2(); | ||
44 | d->alpha = 255; | 45 | d->alpha = 255; |
45 | } | 46 | } |
46 | 47 | ||
@@ -48,6 +49,8 @@ void beginTarget_Paint(iPaint *d, SDL_Texture *target) { | |||
48 | SDL_Renderer *rend = renderer_Paint_(d); | 49 | SDL_Renderer *rend = renderer_Paint_(d); |
49 | if (!d->setTarget) { | 50 | if (!d->setTarget) { |
50 | d->oldTarget = SDL_GetRenderTarget(rend); | 51 | d->oldTarget = SDL_GetRenderTarget(rend); |
52 | d->oldOrigin = origin_Paint; | ||
53 | origin_Paint = zero_I2(); | ||
51 | SDL_SetRenderTarget(rend, target); | 54 | SDL_SetRenderTarget(rend, target); |
52 | d->setTarget = target; | 55 | d->setTarget = target; |
53 | } | 56 | } |
@@ -59,8 +62,10 @@ void beginTarget_Paint(iPaint *d, SDL_Texture *target) { | |||
59 | void endTarget_Paint(iPaint *d) { | 62 | void endTarget_Paint(iPaint *d) { |
60 | if (d->setTarget) { | 63 | if (d->setTarget) { |
61 | SDL_SetRenderTarget(renderer_Paint_(d), d->oldTarget); | 64 | SDL_SetRenderTarget(renderer_Paint_(d), d->oldTarget); |
65 | origin_Paint = d->oldOrigin; | ||
66 | d->oldOrigin = zero_I2(); | ||
62 | d->oldTarget = NULL; | 67 | d->oldTarget = NULL; |
63 | d->setTarget = NULL; | 68 | d->setTarget = NULL; |
64 | } | 69 | } |
65 | } | 70 | } |
66 | 71 | ||
@@ -108,7 +113,7 @@ void drawRect_Paint(const iPaint *d, iRect rect, int color) { | |||
108 | { left_Rect(rect), br.y }, | 113 | { left_Rect(rect), br.y }, |
109 | { left_Rect(rect), top_Rect(rect) } | 114 | { left_Rect(rect), top_Rect(rect) } |
110 | }; | 115 | }; |
111 | #if SDL_VERSION_ATLEAST(2, 0, 16) | 116 | #if SDL_COMPILEDVERSION == SDL_VERSIONNUM(2, 0, 16) |
112 | if (isOpenGLRenderer_Window()) { | 117 | if (isOpenGLRenderer_Window()) { |
113 | /* A very curious regression in SDL 2.0.16. */ | 118 | /* A very curious regression in SDL 2.0.16. */ |
114 | edges[3].y--; | 119 | edges[3].y--; |
@@ -170,7 +175,7 @@ void drawLines_Paint(const iPaint *d, const iInt2 *points, size_t n, int color) | |||
170 | for (size_t i = 0; i < n; i++) { | 175 | for (size_t i = 0; i < n; i++) { |
171 | offsetPoints[i] = add_I2(points[i], origin_Paint); | 176 | offsetPoints[i] = add_I2(points[i], origin_Paint); |
172 | } | 177 | } |
173 | SDL_RenderDrawLines(renderer_Paint_(d), (const SDL_Point *) offsetPoints, n); | 178 | SDL_RenderDrawLines(renderer_Paint_(d), (const SDL_Point *) offsetPoints, (int) n); |
174 | free(offsetPoints); | 179 | free(offsetPoints); |
175 | } | 180 | } |
176 | 181 | ||
diff --git a/src/ui/paint.h b/src/ui/paint.h index e894b62f..dfc9260d 100644 --- a/src/ui/paint.h +++ b/src/ui/paint.h | |||
@@ -33,6 +33,7 @@ struct Impl_Paint { | |||
33 | iWindow * dst; | 33 | iWindow * dst; |
34 | SDL_Texture *setTarget; | 34 | SDL_Texture *setTarget; |
35 | SDL_Texture *oldTarget; | 35 | SDL_Texture *oldTarget; |
36 | iInt2 oldOrigin; | ||
36 | uint8_t alpha; | 37 | uint8_t alpha; |
37 | }; | 38 | }; |
38 | 39 | ||
diff --git a/src/ui/root.c b/src/ui/root.c index cf13169d..5c4296cf 100644 --- a/src/ui/root.c +++ b/src/ui/root.c | |||
@@ -38,6 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
38 | #include "../history.h" | 38 | #include "../history.h" |
39 | #include "../gmcerts.h" | 39 | #include "../gmcerts.h" |
40 | #include "../gmutil.h" | 40 | #include "../gmutil.h" |
41 | #include "../sitespec.h" | ||
41 | #include "../visited.h" | 42 | #include "../visited.h" |
42 | 43 | ||
43 | #if defined (iPlatformMsys) | 44 | #if defined (iPlatformMsys) |
@@ -94,16 +95,16 @@ static const iMenuItem tabletNavMenuItems_[] = { | |||
94 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, | 95 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, |
95 | { "---" }, | 96 | { "---" }, |
96 | { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, | 97 | { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, |
97 | { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, | 98 | // { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, |
98 | { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" }, | 99 | { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" }, |
99 | { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" }, | 100 | { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" }, |
100 | { "---" }, | 101 | { "---" }, |
101 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, | 102 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, |
102 | { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" }, | 103 | { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" }, |
103 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, | 104 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, |
104 | { "${menu.downloads}", 0, 0, "downloads.open" }, | 105 | //{ "${menu.downloads}", 0, 0, "downloads.open" }, |
105 | { "---" }, | 106 | { "---" }, |
106 | { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | 107 | { gear_Icon " ${menu.settings}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, |
107 | { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, | 108 | { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, |
108 | { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, | 109 | { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, |
109 | }; | 110 | }; |
@@ -115,10 +116,10 @@ static const iMenuItem phoneNavMenuItems_[] = { | |||
115 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, | 116 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, |
116 | { "---" }, | 117 | { "---" }, |
117 | { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, | 118 | { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, |
118 | { leftHalf_Icon " ${menu.sidebar}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, | 119 | // { leftHalf_Icon " ${menu.sidebar}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, |
119 | { "---" }, | 120 | { "---" }, |
120 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, | 121 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, |
121 | { "${menu.downloads}", 0, 0, "downloads.open" }, | 122 | //{ "${menu.downloads}", 0, 0, "downloads.open" }, |
122 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, | 123 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, |
123 | { "---" }, | 124 | { "---" }, |
124 | { gear_Icon " ${menu.settings}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | 125 | { gear_Icon " ${menu.settings}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, |
@@ -240,6 +241,7 @@ static int loadAnimIndex_ = 0; | |||
240 | static iRoot * activeRoot_ = NULL; | 241 | static iRoot * activeRoot_ = NULL; |
241 | 242 | ||
242 | iDefineTypeConstruction(Root) | 243 | iDefineTypeConstruction(Root) |
244 | iDefineAudienceGetter(Root, visualOffsetsChanged) | ||
243 | 245 | ||
244 | void init_Root(iRoot *d) { | 246 | void init_Root(iRoot *d) { |
245 | iZap(*d); | 247 | iZap(*d); |
@@ -249,6 +251,7 @@ void deinit_Root(iRoot *d) { | |||
249 | iReleasePtr(&d->widget); | 251 | iReleasePtr(&d->widget); |
250 | delete_PtrArray(d->onTop); | 252 | delete_PtrArray(d->onTop); |
251 | delete_PtrSet(d->pendingDestruction); | 253 | delete_PtrSet(d->pendingDestruction); |
254 | delete_Audience(d->visualOffsetsChanged); | ||
252 | } | 255 | } |
253 | 256 | ||
254 | void setCurrent_Root(iRoot *root) { | 257 | void setCurrent_Root(iRoot *root) { |
@@ -315,9 +318,12 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
315 | if (equal_Command(cmd, "menu.open")) { | 318 | if (equal_Command(cmd, "menu.open")) { |
316 | iWidget *button = pointer_Command(cmd); | 319 | iWidget *button = pointer_Command(cmd); |
317 | iWidget *menu = findChild_Widget(button, "menu"); | 320 | iWidget *menu = findChild_Widget(button, "menu"); |
321 | const iBool isPlacedUnder = argLabel_Command(cmd, "under"); | ||
318 | iAssert(menu); | 322 | iAssert(menu); |
319 | if (!isVisible_Widget(menu)) { | 323 | if (!isVisible_Widget(menu)) { |
320 | openMenu_Widget(menu, topLeft_Rect(bounds_Widget(button))); | 324 | openMenu_Widget(menu, |
325 | isPlacedUnder ? bottomLeft_Rect(bounds_Widget(button)) | ||
326 | : topLeft_Rect(bounds_Widget(button))); | ||
321 | } | 327 | } |
322 | else { | 328 | else { |
323 | closeMenu_Widget(menu); | 329 | closeMenu_Widget(menu); |
@@ -330,6 +336,93 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
330 | openMenuFlags_Widget(menu, zero_I2(), postCommands_MenuOpenFlags | center_MenuOpenFlags); | 336 | openMenuFlags_Widget(menu, zero_I2(), postCommands_MenuOpenFlags | center_MenuOpenFlags); |
331 | return iTrue; | 337 | return iTrue; |
332 | } | 338 | } |
339 | else if (deviceType_App() == tablet_AppDeviceType && equal_Command(cmd, "toolbar.showident")) { | ||
340 | /* No toolbar on tablet, so we handle this command here. */ | ||
341 | postCommand_App("preferences idents:1"); | ||
342 | return iTrue; | ||
343 | } | ||
344 | else if (equal_Command(cmd, "identmenu.open")) { | ||
345 | iWidget *toolBar = findWidget_Root("toolbar"); | ||
346 | iWidget *button = findWidget_Root(toolBar && isPortraitPhone_App() ? "toolbar.ident" : "navbar.ident"); | ||
347 | iArray items; | ||
348 | init_Array(&items, sizeof(iMenuItem)); | ||
349 | /* Current identity. */ | ||
350 | const iString *docUrl = url_DocumentWidget(document_App()); | ||
351 | const iGmIdentity *ident = identityForUrl_GmCerts(certs_App(), docUrl); | ||
352 | const iString *fp = ident ? collect_String(hexEncode_Block(&ident->fingerprint)) : NULL; | ||
353 | iString *str = NULL; | ||
354 | if (ident) { | ||
355 | str = copy_String(name_GmIdentity(ident)); | ||
356 | if (!isEmpty_String(&ident->notes)) { | ||
357 | appendFormat_String(str, "\n\x1b[0m" uiHeading_ColorEscape "%s", cstr_String(&ident->notes)); | ||
358 | } | ||
359 | } | ||
360 | pushBack_Array( | ||
361 | &items, | ||
362 | &(iMenuItem){ format_CStr("```" uiHeading_ColorEscape "\x1b[1m%s", | ||
363 | str ? cstr_String(str) : "${menu.identity.notactive}") }); | ||
364 | if (ident && isUsedOn_GmIdentity(ident, docUrl)) { | ||
365 | pushBack_Array(&items, | ||
366 | &(iMenuItem){ close_Icon " ${ident.stopuse}", | ||
367 | 0, | ||
368 | 0, | ||
369 | format_CStr("ident.signout ident:%s url:%s", | ||
370 | cstr_String(fp), | ||
371 | cstr_String(docUrl)) }); | ||
372 | } | ||
373 | pushBack_Array(&items, &(iMenuItem){ "---" }); | ||
374 | delete_String(str); | ||
375 | /* Alternate identities. */ | ||
376 | const iString *site = collectNewRange_String(urlRoot_String(docUrl)); | ||
377 | iBool haveAlts = iFalse; | ||
378 | iConstForEach(StringArray, i, strings_SiteSpec(site, usedIdentities_SiteSpecKey)) { | ||
379 | if (!fp || !equal_String(i.value, fp)) { | ||
380 | const iBlock *otherFp = collect_Block(hexDecode_Rangecc(range_String(i.value))); | ||
381 | const iGmIdentity *other = findIdentity_GmCerts(certs_App(), otherFp); | ||
382 | if (other && other != ident) { | ||
383 | pushBack_Array( | ||
384 | &items, | ||
385 | &(iMenuItem){ | ||
386 | format_CStr(translateCStr_Lang("\U0001f816 ${ident.switch}"), | ||
387 | format_CStr("\x1b[1m%s", | ||
388 | cstr_String(name_GmIdentity(other)))), | ||
389 | 0, | ||
390 | 0, | ||
391 | format_CStr("ident.switch fp:%s", cstr_String(i.value)) }); | ||
392 | haveAlts = iTrue; | ||
393 | } | ||
394 | } | ||
395 | } | ||
396 | if (haveAlts) { | ||
397 | pushBack_Array(&items, &(iMenuItem){ "---" }); | ||
398 | } | ||
399 | iSidebarWidget *sidebar = findWidget_App("sidebar"); | ||
400 | pushBackN_Array( | ||
401 | &items, | ||
402 | (iMenuItem[]){ | ||
403 | { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" }, | ||
404 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, | ||
405 | { "---" } }, 3); | ||
406 | if (deviceType_App() == desktop_AppDeviceType) { | ||
407 | pushBack_Array(&items, | ||
408 | &(iMenuItem){ isVisible_Widget(sidebar) && mode_SidebarWidget(sidebar) == | ||
409 | identities_SidebarMode | ||
410 | ? leftHalf_Icon " ${menu.hide.identities}" | ||
411 | : leftHalf_Icon " ${menu.show.identities}", | ||
412 | 0, | ||
413 | 0, | ||
414 | "sidebar.mode arg:3 toggle:1" }); | ||
415 | } | ||
416 | else { | ||
417 | pushBack_Array(&items, &(iMenuItem){ gear_Icon " ${menu.identities}", 0, 0, | ||
418 | "toolbar.showident"}); | ||
419 | } | ||
420 | iWidget *menu = | ||
421 | makeMenu_Widget(button, constData_Array(&items), size_Array(&items)); | ||
422 | openMenu_Widget(menu, bottomLeft_Rect(bounds_Widget(button))); | ||
423 | deinit_Array(&items); | ||
424 | return iTrue; | ||
425 | } | ||
333 | else if (equal_Command(cmd, "contextclick")) { | 426 | else if (equal_Command(cmd, "contextclick")) { |
334 | iBool showBarMenu = iFalse; | 427 | iBool showBarMenu = iFalse; |
335 | if (equal_Rangecc(range_Command(cmd, "id"), "buttons")) { | 428 | if (equal_Rangecc(range_Command(cmd, "id"), "buttons")) { |
@@ -351,7 +444,7 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
351 | return iFalse; | 444 | return iFalse; |
352 | } | 445 | } |
353 | else if (equal_Command(cmd, "focus.set")) { | 446 | else if (equal_Command(cmd, "focus.set")) { |
354 | setFocus_Widget(findWidget_App(cstr_Rangecc(range_Command(cmd, "id")))); | 447 | setFocus_Widget(findWidget_App(cstr_Command(cmd, "id"))); |
355 | return iTrue; | 448 | return iTrue; |
356 | } | 449 | } |
357 | else if (equal_Command(cmd, "input.resized")) { | 450 | else if (equal_Command(cmd, "input.resized")) { |
@@ -401,14 +494,38 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
401 | SDL_PushEvent(&(SDL_Event){ .type = SDL_QUIT }); | 494 | SDL_PushEvent(&(SDL_Event){ .type = SDL_QUIT }); |
402 | return iTrue; | 495 | return iTrue; |
403 | } | 496 | } |
497 | else if (deviceType_App() == tablet_AppDeviceType && equal_Command(cmd, "window.resized")) { | ||
498 | iSidebarWidget *sidebar = findChild_Widget(root, "sidebar"); | ||
499 | iSidebarWidget *sidebar2 = findChild_Widget(root, "sidebar2"); | ||
500 | setWidth_SidebarWidget(sidebar, 73.0f); | ||
501 | setWidth_SidebarWidget(sidebar2, 73.0f); | ||
502 | return iFalse; | ||
503 | } | ||
404 | else if (deviceType_App() == phone_AppDeviceType && equal_Command(cmd, "window.resized")) { | 504 | else if (deviceType_App() == phone_AppDeviceType && equal_Command(cmd, "window.resized")) { |
405 | /* Place the sidebar next to or under doctabs depending on orientation. */ | 505 | /* Place the sidebar next to or under doctabs depending on orientation. */ |
406 | iSidebarWidget *sidebar = findChild_Widget(root, "sidebar"); | 506 | iSidebarWidget *sidebar = findChild_Widget(root, "sidebar"); |
407 | iSidebarWidget *sidebar2 = findChild_Widget(root, "sidebar2"); | ||
408 | removeChild_Widget(parent_Widget(sidebar), sidebar); | 507 | removeChild_Widget(parent_Widget(sidebar), sidebar); |
409 | // setBackgroundColor_Widget(findChild_Widget(as_Widget(sidebar), "buttons"), | 508 | iChangeFlags(as_Widget(sidebar)->flags2, fadeBackground_WidgetFlag2, isPortrait_App()); |
410 | // isPortrait_App() ? uiBackgroundUnfocusedSelection_ColorId | 509 | if (isLandscape_App()) { |
411 | // : uiBackgroundSidebar_ColorId); | 510 | setVisualOffset_Widget(as_Widget(sidebar), 0, 0, 0); |
511 | addChildPos_Widget(findChild_Widget(root, "tabs.content"), iClob(sidebar), front_WidgetAddPos); | ||
512 | setWidth_SidebarWidget(sidebar, 73.0f); | ||
513 | setFlags_Widget(as_Widget(sidebar), fixedHeight_WidgetFlag | fixedPosition_WidgetFlag, iFalse); | ||
514 | } | ||
515 | else { | ||
516 | addChild_Widget(root, iClob(sidebar)); | ||
517 | setWidth_SidebarWidget(sidebar, (float) width_Widget(root) / (float) gap_UI); | ||
518 | int midHeight = height_Widget(root) / 2;// + lineHeight_Text(uiLabelLarge_FontId); | ||
519 | #if defined (iPlatformAndroidMobile) | ||
520 | midHeight += 2 * lineHeight_Text(uiLabelLarge_FontId); | ||
521 | #endif | ||
522 | setMidHeight_SidebarWidget(sidebar, midHeight); | ||
523 | setFixedSize_Widget(as_Widget(sidebar), init_I2(-1, midHeight)); | ||
524 | setPos_Widget(as_Widget(sidebar), init_I2(0, height_Widget(root) - midHeight)); | ||
525 | } | ||
526 | #if 0 | ||
527 | iSidebarWidget *sidebar = findChild_Widget(root, "sidebar"); | ||
528 | iSidebarWidget *sidebar2 = findChild_Widget(root, "sidebar2"); | ||
412 | setFlags_Widget(findChild_Widget(as_Widget(sidebar), "buttons"), | 529 | setFlags_Widget(findChild_Widget(as_Widget(sidebar), "buttons"), |
413 | borderTop_WidgetFlag, | 530 | borderTop_WidgetFlag, |
414 | isPortrait_App()); | 531 | isPortrait_App()); |
@@ -424,6 +541,20 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
424 | setWidth_SidebarWidget(sidebar, (float) width_Widget(root) / (float) gap_UI); | 541 | setWidth_SidebarWidget(sidebar, (float) width_Widget(root) / (float) gap_UI); |
425 | setWidth_SidebarWidget(sidebar2, (float) width_Widget(root) / (float) gap_UI); | 542 | setWidth_SidebarWidget(sidebar2, (float) width_Widget(root) / (float) gap_UI); |
426 | } | 543 | } |
544 | #endif | ||
545 | return iFalse; | ||
546 | } | ||
547 | else if (equal_Command(cmd, "root.arrange")) { | ||
548 | iWidget *prefs = findWidget_Root("prefs"); | ||
549 | if (prefs) { | ||
550 | updatePreferencesLayout_Widget(prefs); | ||
551 | } | ||
552 | root->root->pendingArrange = iFalse; | ||
553 | return iTrue; | ||
554 | } | ||
555 | else if (equal_Command(cmd, "theme.changed")) { | ||
556 | /* The phone toolbar is draw-buffered so it needs refreshing. */ | ||
557 | refresh_Widget(findWidget_App("toolbar")); | ||
427 | return iFalse; | 558 | return iFalse; |
428 | } | 559 | } |
429 | else if (handleCommand_App(cmd)) { | 560 | else if (handleCommand_App(cmd)) { |
@@ -449,22 +580,54 @@ static void updateNavBarIdentity_(iWidget *navBar) { | |||
449 | iLabelWidget *toolName = findWidget_App("toolbar.name"); | 580 | iLabelWidget *toolName = findWidget_App("toolbar.name"); |
450 | if (toolName) { | 581 | if (toolName) { |
451 | setOutline_LabelWidget(toolButton, ident == NULL); | 582 | setOutline_LabelWidget(toolButton, ident == NULL); |
452 | updateTextCStr_LabelWidget(toolName, subjectName ? cstr_String(subjectName) : ""); | 583 | /* Fit the name in the widget. */ |
584 | if (subjectName) { | ||
585 | const char *endPos; | ||
586 | tryAdvanceNoWrap_Text(uiLabelTiny_FontId, range_String(subjectName), width_Widget(toolName), | ||
587 | &endPos); | ||
588 | updateText_LabelWidget( | ||
589 | toolName, | ||
590 | collectNewRange_String((iRangecc){ constBegin_String(subjectName), endPos })); | ||
591 | } | ||
592 | else { | ||
593 | updateTextCStr_LabelWidget(toolName, ""); | ||
594 | } | ||
453 | setFont_LabelWidget(toolButton, subjectName ? uiLabelMedium_FontId : uiLabelLarge_FontId); | 595 | setFont_LabelWidget(toolButton, subjectName ? uiLabelMedium_FontId : uiLabelLarge_FontId); |
454 | arrange_Widget(parent_Widget(toolButton)); | 596 | setTextOffset_LabelWidget(toolButton, init_I2(0, subjectName ? -1.5f * gap_UI : 0)); |
597 | arrange_Widget(parent_Widget(toolButton)); | ||
455 | } | 598 | } |
456 | } | 599 | } |
457 | 600 | ||
458 | static void updateNavDirButtons_(iWidget *navBar) { | 601 | static void updateNavDirButtons_(iWidget *navBar) { |
459 | const iHistory *history = history_DocumentWidget(document_App()); | 602 | iBeginCollect(); |
460 | setFlags_Widget(findChild_Widget(navBar, "navbar.back"), disabled_WidgetFlag, | 603 | const iHistory *history = history_DocumentWidget(document_App()); |
461 | atOldest_History(history)); | 604 | const iBool atOldest = atOldest_History(history); |
462 | setFlags_Widget(findChild_Widget(navBar, "navbar.forward"), disabled_WidgetFlag, | 605 | const iBool atNewest = atNewest_History(history); |
463 | atLatest_History(history)); | 606 | /* Reset button state. */ |
464 | setFlags_Widget(findWidget_App("toolbar.back"), disabled_WidgetFlag, | 607 | for (size_t i = 0; i < maxNavbarActions_Prefs; i++) { |
465 | atOldest_History(history)); | 608 | const char *id = format_CStr("navbar.action%d", i + 1); |
466 | setFlags_Widget(findWidget_App("toolbar.forward"), disabled_WidgetFlag, | 609 | setFlags_Widget(findChild_Widget(navBar, id), disabled_WidgetFlag, iFalse); |
467 | atLatest_History(history)); | 610 | } |
611 | setFlags_Widget(as_Widget(findMenuItem_Widget(navBar, "navigate.back")), disabled_WidgetFlag, atOldest); | ||
612 | setFlags_Widget(as_Widget(findMenuItem_Widget(navBar, "navigate.forward")), disabled_WidgetFlag, atNewest); | ||
613 | iWidget *toolBar = findWidget_App("toolbar"); | ||
614 | if (toolBar) { | ||
615 | /* Reset the state. */ | ||
616 | for (int i = 0; i < 2; i++) { | ||
617 | const char *id = (i == 0 ? "toolbar.action1" : "toolbar.action2"); | ||
618 | setFlags_Widget(findChild_Widget(toolBar, id), disabled_WidgetFlag, iFalse); | ||
619 | setOutline_LabelWidget(findChild_Widget(toolBar, id), iFalse); | ||
620 | } | ||
621 | /* Disable certain actions. */ | ||
622 | iLabelWidget *back = findMenuItem_Widget(toolBar, "navigate.back"); | ||
623 | iLabelWidget *fwd = findMenuItem_Widget(toolBar, "navigate.forward"); | ||
624 | setFlags_Widget(as_Widget(back), disabled_WidgetFlag, atOldest); | ||
625 | setOutline_LabelWidget(back, atOldest); | ||
626 | setFlags_Widget(as_Widget(fwd), disabled_WidgetFlag, atNewest); | ||
627 | setOutline_LabelWidget(fwd, atNewest); | ||
628 | refresh_Widget(toolBar); | ||
629 | } | ||
630 | iEndCollect(); | ||
468 | } | 631 | } |
469 | 632 | ||
470 | static const char *loadAnimationCStr_(void) { | 633 | static const char *loadAnimationCStr_(void) { |
@@ -502,10 +665,10 @@ static void checkLoadAnimation_Root_(iRoot *d) { | |||
502 | 665 | ||
503 | void updatePadding_Root(iRoot *d) { | 666 | void updatePadding_Root(iRoot *d) { |
504 | if (d == NULL) return; | 667 | if (d == NULL) return; |
505 | iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); | ||
506 | float bottom = 0.0f; | ||
507 | #if defined (iPlatformAppleMobile) | 668 | #if defined (iPlatformAppleMobile) |
669 | iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); | ||
508 | float left, top, right; | 670 | float left, top, right; |
671 | float bottom = 0.0f; | ||
509 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | 672 | safeAreaInsets_iOS(&left, &top, &right, &bottom); |
510 | /* Respect the safe area insets. */ { | 673 | /* Respect the safe area insets. */ { |
511 | setPadding_Widget(findChild_Widget(d->widget, "navdiv"), left, top, right, 0); | 674 | setPadding_Widget(findChild_Widget(d->widget, "navdiv"), left, top, right, 0); |
@@ -514,15 +677,6 @@ void updatePadding_Root(iRoot *d) { | |||
514 | } | 677 | } |
515 | } | 678 | } |
516 | #endif | 679 | #endif |
517 | if (toolBar) { | ||
518 | /* TODO: get this from toolBar height, but it's buggy for some reason */ | ||
519 | const int sidebarBottomPad = isPortrait_App() ? 11 * gap_UI + bottom : 0; | ||
520 | setPadding_Widget(findChild_Widget(d->widget, "sidebar"), 0, 0, 0, sidebarBottomPad); | ||
521 | setPadding_Widget(findChild_Widget(d->widget, "sidebar2"), 0, 0, 0, sidebarBottomPad); | ||
522 | /* TODO: There seems to be unrelated layout glitch in the sidebar where its children | ||
523 | are not arranged correctly until it's hidden and reshown. */ | ||
524 | } | ||
525 | /* Note that `handleNavBarCommands_` also adjusts padding and spacing. */ | ||
526 | } | 680 | } |
527 | 681 | ||
528 | void updateToolbarColors_Root(iRoot *d) { | 682 | void updateToolbarColors_Root(iRoot *d) { |
@@ -536,17 +690,25 @@ void updateToolbarColors_Root(iRoot *d) { | |||
536 | tmBannerBackground_ColorId; | 690 | tmBannerBackground_ColorId; |
537 | setBackgroundColor_Widget(toolBar, bg); | 691 | setBackgroundColor_Widget(toolBar, bg); |
538 | iForEach(ObjectList, i, children_Widget(toolBar)) { | 692 | iForEach(ObjectList, i, children_Widget(toolBar)) { |
539 | iLabelWidget *btn = i.object; | 693 | // iLabelWidget *btn = i.object; |
540 | setTextColor_LabelWidget(i.object, isSidebarVisible ? uiTextDim_ColorId : | 694 | setTextColor_LabelWidget(i.object, isSidebarVisible ? uiTextDim_ColorId : |
541 | tmBannerIcon_ColorId); | 695 | tmBannerIcon_ColorId); |
542 | setBackgroundColor_Widget(i.object, bg); /* using noBackground, but ident has outline */ | 696 | setBackgroundColor_Widget(i.object, bg); /* using noBackground, but ident has outline */ |
543 | } | 697 | } |
698 | setTextColor_LabelWidget(findChild_Widget(toolBar, "toolbar.name"), | ||
699 | isSidebarVisible ? uiTextDim_ColorId : tmBannerIcon_ColorId); | ||
544 | } | 700 | } |
545 | #else | 701 | #else |
546 | iUnused(d); | 702 | iUnused(d); |
547 | #endif | 703 | #endif |
548 | } | 704 | } |
549 | 705 | ||
706 | void notifyVisualOffsetChange_Root(iRoot *d) { | ||
707 | if (d && (d->didAnimateVisualOffsets || d->didChangeArrangement)) { | ||
708 | iNotifyAudience(d, visualOffsetsChanged, RootVisualOffsetsChanged); | ||
709 | } | ||
710 | } | ||
711 | |||
550 | void dismissPortraitPhoneSidebars_Root(iRoot *d) { | 712 | void dismissPortraitPhoneSidebars_Root(iRoot *d) { |
551 | if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) { | 713 | if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) { |
552 | iWidget *sidebar = findChild_Widget(d->widget, "sidebar"); | 714 | iWidget *sidebar = findChild_Widget(d->widget, "sidebar"); |
@@ -617,16 +779,15 @@ static void updateNavBarSize_(iWidget *navBar) { | |||
617 | const iBool isPhone = deviceType_App() == phone_AppDeviceType; | 779 | const iBool isPhone = deviceType_App() == phone_AppDeviceType; |
618 | const iBool isNarrow = !isPhone && isNarrow_Root(navBar->root); | 780 | const iBool isNarrow = !isPhone && isNarrow_Root(navBar->root); |
619 | /* Adjust navbar padding. */ { | 781 | /* Adjust navbar padding. */ { |
620 | int hPad = isPhone && isPortrait_App() ? 0 : (isPhone || isNarrow) ? gap_UI / 2 | 782 | int hPad = isPortraitPhone_App() ? 0 : isPhone || isNarrow ? gap_UI / 2 : (gap_UI * 3 / 2); |
621 | : gap_UI * 3 / 2; | 783 | int vPad = gap_UI * 3 / 2; |
622 | int vPad = gap_UI * 3 / 2; | ||
623 | int topPad = !findWidget_Root("winbar") ? gap_UI / 2 : 0; | 784 | int topPad = !findWidget_Root("winbar") ? gap_UI / 2 : 0; |
624 | setPadding_Widget(navBar, hPad, vPad / 3 + topPad, hPad, vPad / 2); | 785 | setPadding_Widget(navBar, hPad, vPad / 3 + topPad, hPad, vPad / 2); |
625 | } | 786 | } |
626 | /* Button sizing. */ | 787 | /* Button sizing. */ |
627 | if (isNarrow ^ ((flags_Widget(navBar) & tight_WidgetFlag) != 0)) { | 788 | if (isNarrow ^ ((flags_Widget(navBar) & tight_WidgetFlag) != 0)) { |
628 | setFlags_Widget(navBar, tight_WidgetFlag, isNarrow); | 789 | setFlags_Widget(navBar, tight_WidgetFlag, isNarrow); |
629 | showCollapsed_Widget(findChild_Widget(navBar, "navbar.sidebar"), !isNarrow); | 790 | showCollapsed_Widget(findChild_Widget(navBar, "navbar.action3"), !isNarrow); |
630 | iObjectList *lists[] = { | 791 | iObjectList *lists[] = { |
631 | children_Widget(navBar), | 792 | children_Widget(navBar), |
632 | children_Widget(findChild_Widget(navBar, "url")), | 793 | children_Widget(findChild_Widget(navBar, "url")), |
@@ -647,8 +808,8 @@ static void updateNavBarSize_(iWidget *navBar) { | |||
647 | updateUrlInputContentPadding_(navBar); | 808 | updateUrlInputContentPadding_(navBar); |
648 | } | 809 | } |
649 | if (isPhone) { | 810 | if (isPhone) { |
650 | static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar", | 811 | static const char *buttons[] = { "navbar.action1", "navbar.action2", "navbar.action3", |
651 | "navbar.ident", "navbar.home", "navbar.menu" }; | 812 | "navbar.action4", "navbar.ident", "navbar.menu" }; |
652 | iWidget *toolBar = findWidget_Root("toolbar"); | 813 | iWidget *toolBar = findWidget_Root("toolbar"); |
653 | setVisualOffset_Widget(toolBar, 0, 0, 0); | 814 | setVisualOffset_Widget(toolBar, 0, 0, 0); |
654 | setFlags_Widget(toolBar, hidden_WidgetFlag, isLandscape_App()); | 815 | setFlags_Widget(toolBar, hidden_WidgetFlag, isLandscape_App()); |
@@ -673,6 +834,22 @@ static void updateNavBarSize_(iWidget *navBar) { | |||
673 | postCommand_Widget(navBar, "layout.changed id:navbar"); | 834 | postCommand_Widget(navBar, "layout.changed id:navbar"); |
674 | } | 835 | } |
675 | 836 | ||
837 | static void updateNavBarActions_(iWidget *navBar) { | ||
838 | const iPrefs *prefs = prefs_App(); | ||
839 | for (size_t i = 0; i < iElemCount(prefs->navbarActions); i++) { | ||
840 | iBeginCollect(); | ||
841 | const int action = prefs->navbarActions[i]; | ||
842 | iLabelWidget *button = | ||
843 | findChild_Widget(navBar, format_CStr("navbar.action%d", i + 1)); | ||
844 | if (button) { | ||
845 | setFlags_Widget(as_Widget(button), disabled_WidgetFlag, iFalse); | ||
846 | updateTextCStr_LabelWidget(button, toolbarActions_Mobile[action].icon); | ||
847 | setCommand_LabelWidget(button, collectNewCStr_String(toolbarActions_Mobile[action].command)); | ||
848 | } | ||
849 | iEndCollect(); | ||
850 | } | ||
851 | } | ||
852 | |||
676 | static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { | 853 | static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { |
677 | if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "metrics.changed")) { | 854 | if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "metrics.changed")) { |
678 | updateNavBarSize_(navBar); | 855 | updateNavBarSize_(navBar); |
@@ -685,7 +862,41 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { | |||
685 | } | 862 | } |
686 | return iFalse; | 863 | return iFalse; |
687 | } | 864 | } |
865 | else if (equal_Command(cmd, "navbar.actions.changed")) { | ||
866 | updateNavBarActions_(navBar); | ||
867 | return iTrue; | ||
868 | } | ||
869 | else if (equal_Command(cmd, "contextclick")) { | ||
870 | const iRangecc id = range_Command(cmd, "id"); | ||
871 | if (id.start && startsWith_CStr(id.start, "navbar.action")) { | ||
872 | const int buttonIndex = id.end[-1] - '1'; | ||
873 | iArray items; | ||
874 | init_Array(&items, sizeof(iMenuItem)); | ||
875 | pushBack_Array(&items, &(iMenuItem){ "```${menu.toolbar.setaction}" }); | ||
876 | for (size_t i = 0; i < max_ToolbarAction; i++) { | ||
877 | pushBack_Array( | ||
878 | &items, | ||
879 | &(iMenuItem){ | ||
880 | format_CStr( | ||
881 | "%s %s", toolbarActions_Mobile[i].icon, toolbarActions_Mobile[i].label), | ||
882 | 0, | ||
883 | 0, | ||
884 | format_CStr("navbar.action.set arg:%d button:%d", i, buttonIndex) }); | ||
885 | } | ||
886 | openMenu_Widget( | ||
887 | makeMenu_Widget(get_Root()->widget, constData_Array(&items), size_Array(&items)), | ||
888 | coord_Command(cmd)); | ||
889 | deinit_Array(&items); | ||
890 | return iTrue; | ||
891 | } | ||
892 | return iFalse; | ||
893 | } | ||
688 | else if (equal_Command(cmd, "navigate.focus")) { | 894 | else if (equal_Command(cmd, "navigate.focus")) { |
895 | /* The upload dialog has its own path field. */ | ||
896 | if (findWidget_App("upload")) { | ||
897 | postCommand_App("focus.set id:upload.path"); | ||
898 | return iTrue; | ||
899 | } | ||
689 | iWidget *url = findChild_Widget(navBar, "url"); | 900 | iWidget *url = findChild_Widget(navBar, "url"); |
690 | if (focus_Widget() != url) { | 901 | if (focus_Widget() != url) { |
691 | setFocus_Widget(findChild_Widget(navBar, "url")); | 902 | setFocus_Widget(findChild_Widget(navBar, "url")); |
@@ -710,6 +921,8 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { | |||
710 | } | 921 | } |
711 | else if (equal_Command(cmd, "navbar.clear")) { | 922 | else if (equal_Command(cmd, "navbar.clear")) { |
712 | iInputWidget *url = findChild_Widget(navBar, "url"); | 923 | iInputWidget *url = findChild_Widget(navBar, "url"); |
924 | setText_InputWidget(url, collectNew_String()); | ||
925 | #if 0 | ||
713 | selectAll_InputWidget(url); | 926 | selectAll_InputWidget(url); |
714 | /* Emulate a Backspace keypress. */ | 927 | /* Emulate a Backspace keypress. */ |
715 | class_InputWidget(url)->processEvent( | 928 | class_InputWidget(url)->processEvent( |
@@ -718,6 +931,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { | |||
718 | .timestamp = SDL_GetTicks(), | 931 | .timestamp = SDL_GetTicks(), |
719 | .state = SDL_PRESSED, | 932 | .state = SDL_PRESSED, |
720 | .keysym = { .sym = SDLK_BACKSPACE } }); | 933 | .keysym = { .sym = SDLK_BACKSPACE } }); |
934 | #endif | ||
721 | return iTrue; | 935 | return iTrue; |
722 | } | 936 | } |
723 | else if (equal_Command(cmd, "navbar.cancel")) { | 937 | else if (equal_Command(cmd, "navbar.cancel")) { |
@@ -780,6 +994,26 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { | |||
780 | dismissPortraitPhoneSidebars_Root(get_Root()); | 994 | dismissPortraitPhoneSidebars_Root(get_Root()); |
781 | updateNavBarIdentity_(navBar); | 995 | updateNavBarIdentity_(navBar); |
782 | updateNavDirButtons_(navBar); | 996 | updateNavDirButtons_(navBar); |
997 | /* Update site-specific used identities. */ { | ||
998 | const iGmIdentity *ident = | ||
999 | identityForUrl_GmCerts(certs_App(), url_DocumentWidget(document_App())); | ||
1000 | if (ident) { | ||
1001 | const iString *site = | ||
1002 | collectNewRange_String(urlRoot_String(canonicalUrl_String(urlStr))); | ||
1003 | const iStringArray *usedIdents = | ||
1004 | strings_SiteSpec(site, usedIdentities_SiteSpecKey); | ||
1005 | const iString *fingerprint = collect_String(hexEncode_Block(&ident->fingerprint)); | ||
1006 | /* Keep this identity at the end of the list. */ | ||
1007 | removeString_SiteSpec(site, usedIdentities_SiteSpecKey, fingerprint); | ||
1008 | insertString_SiteSpec(site, usedIdentities_SiteSpecKey, fingerprint); | ||
1009 | /* Keep the list short. */ | ||
1010 | while (size_StringArray(usedIdents) > 5) { | ||
1011 | removeString_SiteSpec(site, | ||
1012 | usedIdentities_SiteSpecKey, | ||
1013 | constAt_StringArray(usedIdents, 0)); | ||
1014 | } | ||
1015 | } | ||
1016 | } | ||
783 | /* Icon updates should be limited to automatically chosen icons if the user | 1017 | /* Icon updates should be limited to automatically chosen icons if the user |
784 | is allowed to pick their own in the future. */ | 1018 | is allowed to pick their own in the future. */ |
785 | if (updateBookmarkIcon_Bookmarks(bookmarks_App(), urlStr, | 1019 | if (updateBookmarkIcon_Bookmarks(bookmarks_App(), urlStr, |
@@ -860,7 +1094,14 @@ static iBool handleSearchBarCommands_(iWidget *searchBar, const char *cmd) { | |||
860 | else if (equal_Command(cmd, "focus.gained")) { | 1094 | else if (equal_Command(cmd, "focus.gained")) { |
861 | if (pointer_Command(cmd) == findChild_Widget(searchBar, "find.input")) { | 1095 | if (pointer_Command(cmd) == findChild_Widget(searchBar, "find.input")) { |
862 | if (!isVisible_Widget(searchBar)) { | 1096 | if (!isVisible_Widget(searchBar)) { |
1097 | /* InputWidget will unfocus itself if there isn't enough space for editing | ||
1098 | text. A collapsed widget will not have been arranged yet, so on the first | ||
1099 | time the widget will just be unfocused immediately. */ | ||
1100 | const iBool wasArranged = area_Rect(bounds_Widget(searchBar)) > 0; | ||
863 | showCollapsed_Widget(searchBar, iTrue); | 1101 | showCollapsed_Widget(searchBar, iTrue); |
1102 | if (!wasArranged) { | ||
1103 | postCommand_App("focus.set id:find.input"); | ||
1104 | } | ||
864 | } | 1105 | } |
865 | } | 1106 | } |
866 | } | 1107 | } |
@@ -878,14 +1119,21 @@ static iBool handleSearchBarCommands_(iWidget *searchBar, const char *cmd) { | |||
878 | } | 1119 | } |
879 | 1120 | ||
880 | #if defined (iPlatformMobile) | 1121 | #if defined (iPlatformMobile) |
881 | static void dismissSidebar_(iWidget *sidebar, const char *toolButtonId) { | 1122 | |
882 | if (isVisible_Widget(sidebar)) { | 1123 | static void updateToolBarActions_(iWidget *toolBar) { |
883 | postCommandf_App("%s.toggle", cstr_String(id_Widget(sidebar))); | 1124 | const iPrefs *prefs = prefs_App(); |
884 | // if (toolButtonId) { | 1125 | for (int i = 0; i < 2; i++) { |
885 | // setFlags_Widget(findWidget_App(toolButtonId), noBackground_WidgetFlag, iTrue); | 1126 | const int action = prefs->toolbarActions[i]; |
886 | // } | 1127 | iLabelWidget *button = |
887 | setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); | 1128 | findChild_Widget(toolBar, i == 0 ? "toolbar.action1" : "toolbar.action2"); |
1129 | if (button) { | ||
1130 | setFlags_Widget(as_Widget(button), disabled_WidgetFlag, iFalse); | ||
1131 | setOutline_LabelWidget(button, iFalse); | ||
1132 | updateTextCStr_LabelWidget(button, toolbarActions_Mobile[action].icon); | ||
1133 | setCommand_LabelWidget(button, collectNewCStr_String(toolbarActions_Mobile[action].command)); | ||
1134 | } | ||
888 | } | 1135 | } |
1136 | refresh_Widget(toolBar); | ||
889 | } | 1137 | } |
890 | 1138 | ||
891 | static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) { | 1139 | static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) { |
@@ -897,57 +1145,20 @@ static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) { | |||
897 | return iTrue; | 1145 | return iTrue; |
898 | } | 1146 | } |
899 | else if (equal_Command(cmd, "toolbar.showview")) { | 1147 | else if (equal_Command(cmd, "toolbar.showview")) { |
900 | /* TODO: Clean this up. */ | ||
901 | iWidget *sidebar = findWidget_App("sidebar"); | ||
902 | iWidget *sidebar2 = findWidget_App("sidebar2"); | ||
903 | dismissSidebar_(sidebar2, "toolbar.ident"); | ||
904 | const iBool isVisible = isVisible_Widget(sidebar); | ||
905 | // setFlags_Widget(findChild_Widget(toolBar, "toolbar.view"), noBackground_WidgetFlag, | ||
906 | // isVisible); | ||
907 | /* If a sidebar hasn't been shown yet, it's height is zero. */ | ||
908 | const int viewHeight = size_Root(get_Root()).y; | ||
909 | if (arg_Command(cmd) >= 0) { | 1148 | if (arg_Command(cmd) >= 0) { |
910 | postCommandf_App("sidebar.mode arg:%d show:1", arg_Command(cmd)); | 1149 | postCommandf_App("sidebar.mode arg:%d show:1", arg_Command(cmd)); |
911 | // if (!isVisible) { | ||
912 | // setVisualOffset_Widget(sidebar, viewHeight, 0, 0); | ||
913 | // setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag); | ||
914 | // } | ||
915 | } | 1150 | } |
916 | else { | 1151 | else { |
917 | postCommandf_App("sidebar.toggle"); | 1152 | postCommandf_App("sidebar.toggle"); |
918 | // if (isVisible) { | ||
919 | // setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); | ||
920 | // } | ||
921 | // else { | ||
922 | // setVisualOffset_Widget(sidebar, viewHeight, 0, 0); | ||
923 | // setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag); | ||
924 | // } | ||
925 | } | 1153 | } |
926 | return iTrue; | 1154 | return iTrue; |
927 | } | 1155 | } |
928 | else if (equal_Command(cmd, "toolbar.showident")) { | 1156 | else if (equal_Command(cmd, "toolbar.showident")) { |
929 | /* TODO: Clean this up. */ | 1157 | iWidget *sidebar = findWidget_App("sidebar"); |
930 | iWidget *sidebar = findWidget_App("sidebar"); | ||
931 | iWidget *sidebar2 = findWidget_App("sidebar2"); | ||
932 | //dismissSidebar_(sidebar, "toolbar.view"); | ||
933 | if (isVisible_Widget(sidebar)) { | 1158 | if (isVisible_Widget(sidebar)) { |
934 | postCommandf_App("sidebar.toggle"); | 1159 | postCommandf_App("sidebar.toggle"); |
935 | } | 1160 | } |
936 | const iBool isVisible = isVisible_Widget(sidebar2); | 1161 | postCommand_App("preferences idents:1"); |
937 | // setFlags_Widget(findChild_Widget(toolBar, "toolbar.ident"), noBackground_WidgetFlag, | ||
938 | // isVisible); | ||
939 | /* If a sidebar hasn't been shown yet, it's height is zero. */ | ||
940 | const int viewHeight = size_Root(get_Root()).y; | ||
941 | if (isVisible) { | ||
942 | dismissSidebar_(sidebar2, NULL); | ||
943 | } | ||
944 | else { | ||
945 | postCommand_App("sidebar2.mode arg:3 show:1"); | ||
946 | int offset = height_Widget(sidebar2); | ||
947 | if (offset == 0) offset = size_Root(get_Root()).y; | ||
948 | setVisualOffset_Widget(sidebar2, offset, 0, 0); | ||
949 | setVisualOffset_Widget(sidebar2, 0, 400, easeOut_AnimFlag | softer_AnimFlag); | ||
950 | } | ||
951 | return iTrue; | 1162 | return iTrue; |
952 | } | 1163 | } |
953 | else if (equal_Command(cmd, "sidebar.mode.changed")) { | 1164 | else if (equal_Command(cmd, "sidebar.mode.changed")) { |
@@ -955,8 +1166,13 @@ static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) { | |||
955 | updateTextCStr_LabelWidget(viewTool, icon_SidebarMode(arg_Command(cmd))); | 1166 | updateTextCStr_LabelWidget(viewTool, icon_SidebarMode(arg_Command(cmd))); |
956 | return iFalse; | 1167 | return iFalse; |
957 | } | 1168 | } |
1169 | else if (equal_Command(cmd, "toolbar.actions.changed")) { | ||
1170 | updateToolBarActions_(toolBar); | ||
1171 | return iFalse; | ||
1172 | } | ||
958 | return iFalse; | 1173 | return iFalse; |
959 | } | 1174 | } |
1175 | |||
960 | #endif /* defined (iPlatformMobile) */ | 1176 | #endif /* defined (iPlatformMobile) */ |
961 | 1177 | ||
962 | static iLabelWidget *newLargeIcon_LabelWidget(const char *text, const char *cmd) { | 1178 | static iLabelWidget *newLargeIcon_LabelWidget(const char *text, const char *cmd) { |
@@ -987,40 +1203,22 @@ void updateMetrics_Root(iRoot *d) { | |||
987 | setFixedSize_Widget(appClose, appMin->rect.size); | 1203 | setFixedSize_Widget(appClose, appMin->rect.size); |
988 | setFixedSize_Widget(appIcon, init_I2(appIconSize_Root(), appMin->rect.size.y)); | 1204 | setFixedSize_Widget(appIcon, init_I2(appIconSize_Root(), appMin->rect.size.y)); |
989 | } | 1205 | } |
990 | iWidget *navBar = findChild_Widget(d->widget, "navbar"); | 1206 | iWidget *navBar = findChild_Widget(d->widget, "navbar"); |
991 | // iWidget *lock = findChild_Widget(navBar, "navbar.lock"); | 1207 | iWidget *url = findChild_Widget(d->widget, "url"); |
992 | iWidget *url = findChild_Widget(d->widget, "url"); | 1208 | iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); |
993 | iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); | 1209 | iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); |
994 | iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); | 1210 | iWidget *urlButtons = findChild_Widget(navBar, "url.buttons"); |
995 | iWidget *urlButtons = findChild_Widget(navBar, "url.buttons"); | 1211 | iLabelWidget *idName = findChild_Widget(d->widget, "toolbar.name"); |
996 | setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI); | 1212 | setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI); |
997 | navBar->rect.size.y = 0; /* recalculate height based on children (FIXME: shouldn't be needed) */ | 1213 | // navBar->rect.size.y = 0; /* recalculate height based on children (FIXME: shouldn't be needed) */ |
998 | // updateSize_LabelWidget((iLabelWidget *) lock); | ||
999 | // updateSize_LabelWidget((iLabelWidget *) findChild_Widget(navBar, "reload")); | ||
1000 | // arrange_Widget(urlButtons); | ||
1001 | setFixedSize_Widget(embedPad, init_I2(width_Widget(urlButtons) + gap_UI / 2, 1)); | 1214 | setFixedSize_Widget(embedPad, init_I2(width_Widget(urlButtons) + gap_UI / 2, 1)); |
1002 | // setContentPadding_InputWidget((iInputWidget *) url, width_Widget(lock) * 0.75, | ||
1003 | // width_Widget(lock) * 0.75); | ||
1004 | rightEmbed->rect.pos.y = gap_UI; | 1215 | rightEmbed->rect.pos.y = gap_UI; |
1005 | updatePadding_Root(d); | 1216 | updatePadding_Root(d); |
1006 | arrange_Widget(d->widget); | 1217 | arrange_Widget(d->widget); |
1007 | updateUrlInputContentPadding_(navBar); | 1218 | updateUrlInputContentPadding_(navBar); |
1008 | /* Position the toolbar identity name label manually. */ { | 1219 | if (idName) { |
1009 | iLabelWidget *idName = findChild_Widget(d->widget, "toolbar.name"); | 1220 | setFixedSize_Widget(as_Widget(idName), |
1010 | if (idName) { | 1221 | init_I2(-1, 2 * gap_UI + lineHeight_Text(uiLabelTiny_FontId))); |
1011 | const iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); | ||
1012 | const iWidget *viewButton = findChild_Widget(d->widget, "toolbar.view"); | ||
1013 | const iWidget *idButton = findChild_Widget(toolBar, "toolbar.ident"); | ||
1014 | const int font = uiLabelTiny_FontId; | ||
1015 | setFont_LabelWidget(idName, font); | ||
1016 | setPos_Widget(as_Widget(idName), | ||
1017 | windowToLocal_Widget(as_Widget(idName), | ||
1018 | init_I2(left_Rect(bounds_Widget(idButton)), | ||
1019 | bottom_Rect(bounds_Widget(viewButton)) - | ||
1020 | lineHeight_Text(font) - gap_UI / 2))); | ||
1021 | setFixedSize_Widget(as_Widget(idName), init_I2(width_Widget(idButton), | ||
1022 | lineHeight_Text(font))); | ||
1023 | } | ||
1024 | } | 1222 | } |
1025 | postRefresh_App(); | 1223 | postRefresh_App(); |
1026 | } | 1224 | } |
@@ -1044,11 +1242,9 @@ void createUserInterface_Root(iRoot *d) { | |||
1044 | setFlags_Widget( | 1242 | setFlags_Widget( |
1045 | root, resizeChildren_WidgetFlag | fixedSize_WidgetFlag | focusRoot_WidgetFlag, iTrue); | 1243 | root, resizeChildren_WidgetFlag | fixedSize_WidgetFlag | focusRoot_WidgetFlag, iTrue); |
1046 | setCommandHandler_Widget(root, handleRootCommands_); | 1244 | setCommandHandler_Widget(root, handleRootCommands_); |
1047 | |||
1048 | iWidget *div = makeVDiv_Widget(); | 1245 | iWidget *div = makeVDiv_Widget(); |
1049 | setId_Widget(div, "navdiv"); | 1246 | setId_Widget(div, "navdiv"); |
1050 | addChild_Widget(root, iClob(div)); | 1247 | addChild_Widget(root, iClob(div)); |
1051 | |||
1052 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 1248 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
1053 | /* Window title bar. */ | 1249 | /* Window title bar. */ |
1054 | if (prefs_App()->customFrame) { | 1250 | if (prefs_App()->customFrame) { |
@@ -1117,14 +1313,14 @@ void createUserInterface_Root(iRoot *d) { | |||
1117 | addUnsplitButton_(navBar); | 1313 | addUnsplitButton_(navBar); |
1118 | #endif | 1314 | #endif |
1119 | iWidget *navBack; | 1315 | iWidget *navBack; |
1120 | setId_Widget(navBack = addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(backArrow_Icon, 0, 0, "navigate.back")), collapse_WidgetFlag), "navbar.back"); | 1316 | setId_Widget(navBack = addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(backArrow_Icon, 0, 0, "navigate.back")), collapse_WidgetFlag), "navbar.action1"); |
1121 | setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(forwardArrow_Icon, 0, 0, "navigate.forward")), collapse_WidgetFlag), "navbar.forward"); | 1317 | setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(forwardArrow_Icon, 0, 0, "navigate.forward")), collapse_WidgetFlag), "navbar.action2"); |
1122 | /* Button for toggling the left sidebar. */ | 1318 | /* Button for toggling the left sidebar. */ |
1123 | setId_Widget(addChildFlags_Widget( | 1319 | setId_Widget(addChildFlags_Widget( |
1124 | navBar, | 1320 | navBar, |
1125 | iClob(newIcon_LabelWidget(leftHalf_Icon, 0, 0, "sidebar.toggle")), | 1321 | iClob(newIcon_LabelWidget(leftHalf_Icon, 0, 0, "sidebar.toggle")), |
1126 | collapse_WidgetFlag), | 1322 | collapse_WidgetFlag), |
1127 | "navbar.sidebar"); | 1323 | "navbar.action3"); |
1128 | addChildFlags_Widget(navBar, iClob(new_Widget()), expand_WidgetFlag); | 1324 | addChildFlags_Widget(navBar, iClob(new_Widget()), expand_WidgetFlag); |
1129 | iInputWidget *url; | 1325 | iInputWidget *url; |
1130 | /* URL input field. */ { | 1326 | /* URL input field. */ { |
@@ -1245,22 +1441,25 @@ void createUserInterface_Root(iRoot *d) { | |||
1245 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | 1441 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, |
1246 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, | 1442 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, |
1247 | { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, | 1443 | { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, |
1444 | { "${menu.page.upload.edit}", 0, 0, "document.upload copy:1" }, | ||
1248 | { "---" }, | 1445 | { "---" }, |
1249 | { "${menu.page.copyurl}", 0, 0, "document.copylink" }, | 1446 | { "${menu.page.copyurl}", 0, 0, "document.copylink" }, |
1250 | { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, | 1447 | { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, |
1251 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, | 1448 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, |
1252 | 12); | 1449 | 14); |
1253 | setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); | 1450 | setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); |
1254 | setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); | 1451 | setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); |
1255 | setAlignVisually_LabelWidget(pageMenuButton, iTrue); | 1452 | setAlignVisually_LabelWidget(pageMenuButton, iTrue); |
1256 | addChildFlags_Widget(urlButtons, iClob(pageMenuButton), | 1453 | addChildFlags_Widget(urlButtons, iClob(pageMenuButton), |
1257 | embedFlags | tight_WidgetFlag | collapse_WidgetFlag); | 1454 | embedFlags | tight_WidgetFlag | collapse_WidgetFlag | |
1455 | resizeToParentHeight_WidgetFlag); | ||
1258 | updateSize_LabelWidget(pageMenuButton); | 1456 | updateSize_LabelWidget(pageMenuButton); |
1259 | } | 1457 | } |
1260 | /* Reload button. */ { | 1458 | /* Reload button. */ { |
1261 | iLabelWidget *reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); | 1459 | iLabelWidget *reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); |
1262 | setId_Widget(as_Widget(reload), "reload"); | 1460 | setId_Widget(as_Widget(reload), "reload"); |
1263 | addChildFlags_Widget(urlButtons, iClob(reload), embedFlags | collapse_WidgetFlag); | 1461 | addChildFlags_Widget(urlButtons, iClob(reload), embedFlags | collapse_WidgetFlag | |
1462 | resizeToParentHeight_WidgetFlag); | ||
1264 | updateSize_LabelWidget(reload); | 1463 | updateSize_LabelWidget(reload); |
1265 | } | 1464 | } |
1266 | addChildFlags_Widget(as_Widget(url), iClob(urlButtons), moveToParentRightEdge_WidgetFlag); | 1465 | addChildFlags_Widget(as_Widget(url), iClob(urlButtons), moveToParentRightEdge_WidgetFlag); |
@@ -1268,17 +1467,16 @@ void createUserInterface_Root(iRoot *d) { | |||
1268 | setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); | 1467 | setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); |
1269 | } | 1468 | } |
1270 | /* The active identity menu. */ { | 1469 | /* The active identity menu. */ { |
1271 | iLabelWidget *idMenu = makeMenuButton_LabelWidget( | 1470 | iLabelWidget *idButton = new_LabelWidget(person_Icon, "identmenu.open"); |
1272 | "\U0001f464", identityButtonMenuItems_, iElemCount(identityButtonMenuItems_)); | 1471 | setAlignVisually_LabelWidget(idButton, iTrue); |
1273 | setAlignVisually_LabelWidget(idMenu, iTrue); | 1472 | setId_Widget(addChildFlags_Widget(navBar, iClob(idButton), collapse_WidgetFlag), "navbar.ident"); |
1274 | setId_Widget(addChildFlags_Widget(navBar, iClob(idMenu), collapse_WidgetFlag), "navbar.ident"); | ||
1275 | } | 1473 | } |
1276 | addChildFlags_Widget(navBar, iClob(new_Widget()), expand_WidgetFlag); | 1474 | addChildFlags_Widget(navBar, iClob(new_Widget()), expand_WidgetFlag); |
1277 | setId_Widget(addChildFlags_Widget(navBar, | 1475 | setId_Widget(addChildFlags_Widget(navBar, |
1278 | iClob(newIcon_LabelWidget( | 1476 | iClob(newIcon_LabelWidget( |
1279 | home_Icon, SDLK_h, KMOD_PRIMARY | KMOD_SHIFT, "navigate.home")), | 1477 | home_Icon, 0, 0, "navigate.home")), |
1280 | collapse_WidgetFlag), | 1478 | collapse_WidgetFlag), |
1281 | "navbar.home"); | 1479 | "navbar.action4"); |
1282 | #if defined (iPlatformMobile) | 1480 | #if defined (iPlatformMobile) |
1283 | const iBool isPhone = (deviceType_App() == phone_AppDeviceType); | 1481 | const iBool isPhone = (deviceType_App() == phone_AppDeviceType); |
1284 | #endif | 1482 | #endif |
@@ -1291,6 +1489,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1291 | iLabelWidget *navMenu = | 1489 | iLabelWidget *navMenu = |
1292 | makeMenuButton_LabelWidget(menu_Icon, navMenuItems_, iElemCount(navMenuItems_)); | 1490 | makeMenuButton_LabelWidget(menu_Icon, navMenuItems_, iElemCount(navMenuItems_)); |
1293 | # endif | 1491 | # endif |
1492 | setCommand_LabelWidget(navMenu, collectNewCStr_String("menu.open under:1")); | ||
1294 | setAlignVisually_LabelWidget(navMenu, iTrue); | 1493 | setAlignVisually_LabelWidget(navMenu, iTrue); |
1295 | setId_Widget(addChildFlags_Widget(navBar, iClob(navMenu), collapse_WidgetFlag), "navbar.menu"); | 1494 | setId_Widget(addChildFlags_Widget(navBar, iClob(navMenu), collapse_WidgetFlag), "navbar.menu"); |
1296 | #endif | 1495 | #endif |
@@ -1322,17 +1521,18 @@ void createUserInterface_Root(iRoot *d) { | |||
1322 | "newtab"); | 1521 | "newtab"); |
1323 | } | 1522 | } |
1324 | /* Sidebars. */ { | 1523 | /* Sidebars. */ { |
1325 | iWidget *content = findChild_Widget(root, "tabs.content"); | ||
1326 | iSidebarWidget *sidebar1 = new_SidebarWidget(left_SidebarSide); | 1524 | iSidebarWidget *sidebar1 = new_SidebarWidget(left_SidebarSide); |
1327 | addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos); | ||
1328 | iSidebarWidget *sidebar2 = new_SidebarWidget(right_SidebarSide); | ||
1329 | if (deviceType_App() != phone_AppDeviceType) { | 1525 | if (deviceType_App() != phone_AppDeviceType) { |
1526 | /* Sidebars are next to the tab content. */ | ||
1527 | iWidget *content = findChild_Widget(root, "tabs.content"); | ||
1528 | addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos); | ||
1529 | iSidebarWidget *sidebar2 = new_SidebarWidget(right_SidebarSide); | ||
1330 | addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos); | 1530 | addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos); |
1331 | } | 1531 | } |
1332 | else { | 1532 | else { |
1333 | /* The identities sidebar is always in the main area. */ | 1533 | /* Sidebar is a slide-over sheet. */ |
1334 | addChild_Widget(findChild_Widget(root, "stack"), iClob(sidebar2)); | 1534 | addChild_Widget(root, iClob(sidebar1)); |
1335 | setFlags_Widget(as_Widget(sidebar2), hidden_WidgetFlag, iTrue); | 1535 | setFlags_Widget(as_Widget(sidebar1), hidden_WidgetFlag, iTrue); |
1336 | } | 1536 | } |
1337 | } | 1537 | } |
1338 | /* Lookup results. */ { | 1538 | /* Lookup results. */ { |
@@ -1386,40 +1586,46 @@ void createUserInterface_Root(iRoot *d) { | |||
1386 | commandOnClick_WidgetFlag | | 1586 | commandOnClick_WidgetFlag | |
1387 | drawBackgroundToBottom_WidgetFlag, iTrue); | 1587 | drawBackgroundToBottom_WidgetFlag, iTrue); |
1388 | setId_Widget(addChildFlags_Widget(toolBar, | 1588 | setId_Widget(addChildFlags_Widget(toolBar, |
1389 | iClob(newLargeIcon_LabelWidget(backArrow_Icon, "navigate.back")), | 1589 | iClob(newLargeIcon_LabelWidget("", "...")), |
1390 | frameless_WidgetFlag), | 1590 | frameless_WidgetFlag), |
1391 | "toolbar.back"); | 1591 | "toolbar.action1"); |
1392 | setId_Widget(addChildFlags_Widget(toolBar, | 1592 | setId_Widget(addChildFlags_Widget(toolBar, |
1393 | iClob(newLargeIcon_LabelWidget(forwardArrow_Icon, "navigate.forward")), | 1593 | iClob(newLargeIcon_LabelWidget("", "...")), |
1394 | frameless_WidgetFlag), | ||
1395 | "toolbar.forward"); | ||
1396 | setId_Widget(addChildFlags_Widget(toolBar, | ||
1397 | iClob(newLargeIcon_LabelWidget("\U0001f464", "toolbar.showident")), | ||
1398 | frameless_WidgetFlag), | 1594 | frameless_WidgetFlag), |
1595 | "toolbar.action2"); | ||
1596 | iWidget *identButton; | ||
1597 | setId_Widget(identButton = addChildFlags_Widget( | ||
1598 | toolBar, | ||
1599 | iClob(newLargeIcon_LabelWidget("\U0001f464", "identmenu.open")), | ||
1600 | frameless_WidgetFlag | fixedHeight_WidgetFlag), | ||
1399 | "toolbar.ident"); | 1601 | "toolbar.ident"); |
1400 | setId_Widget(addChildFlags_Widget(toolBar, | 1602 | setId_Widget(addChildFlags_Widget(toolBar, |
1401 | iClob(newLargeIcon_LabelWidget(book_Icon, "toolbar.showview arg:-1")), | 1603 | iClob(newLargeIcon_LabelWidget(book_Icon, "toolbar.showview arg:-1")), |
1402 | frameless_WidgetFlag | commandOnClick_WidgetFlag), | 1604 | frameless_WidgetFlag | commandOnClick_WidgetFlag), |
1403 | "toolbar.view"); | 1605 | "toolbar.view"); |
1404 | setId_Widget(addChildFlags_Widget(toolBar, | 1606 | iLabelWidget *idName; |
1405 | iClob(new_LabelWidget("", "toolbar.showident")), | 1607 | setId_Widget(addChildFlags_Widget(identButton, |
1608 | iClob(idName = new_LabelWidget("", NULL)), | ||
1406 | frameless_WidgetFlag | | 1609 | frameless_WidgetFlag | |
1407 | noBackground_WidgetFlag | | 1610 | noBackground_WidgetFlag | |
1408 | fixedPosition_WidgetFlag | | 1611 | moveToParentBottomEdge_WidgetFlag | |
1612 | resizeToParentWidth_WidgetFlag | ||
1613 | /*fixedPosition_WidgetFlag | | ||
1409 | fixedSize_WidgetFlag | | 1614 | fixedSize_WidgetFlag | |
1410 | ignoreForParentWidth_WidgetFlag | | 1615 | ignoreForParentWidth_WidgetFlag | |
1411 | ignoreForParentHeight_WidgetFlag), | 1616 | ignoreForParentHeight_WidgetFlag*/), |
1412 | "toolbar.name"); | 1617 | "toolbar.name"); |
1618 | setFont_LabelWidget(idName, uiLabelTiny_FontId); | ||
1413 | iLabelWidget *menuButton = makeMenuButton_LabelWidget(menu_Icon, phoneNavMenuItems_, | 1619 | iLabelWidget *menuButton = makeMenuButton_LabelWidget(menu_Icon, phoneNavMenuItems_, |
1414 | iElemCount(phoneNavMenuItems_)); | 1620 | iElemCount(phoneNavMenuItems_)); |
1415 | setFont_LabelWidget(menuButton, uiLabelLarge_FontId); | 1621 | setFont_LabelWidget(menuButton, uiLabelLarge_FontId); |
1416 | setId_Widget(as_Widget(menuButton), "toolbar.navmenu"); | 1622 | setId_Widget(as_Widget(menuButton), "toolbar.navmenu"); |
1417 | addChildFlags_Widget(toolBar, iClob(menuButton), frameless_WidgetFlag); | 1623 | addChildFlags_Widget(toolBar, iClob(menuButton), frameless_WidgetFlag); |
1418 | iForEach(ObjectList, i, children_Widget(toolBar)) { | 1624 | iForEach(ObjectList, i, children_Widget(toolBar)) { |
1419 | iLabelWidget *btn = i.object; | ||
1420 | setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue); | 1625 | setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue); |
1421 | } | 1626 | } |
1422 | updateToolbarColors_Root(d); | 1627 | updateToolbarColors_Root(d); |
1628 | updateToolBarActions_(toolBar); | ||
1423 | const iMenuItem items[] = { | 1629 | const iMenuItem items[] = { |
1424 | { book_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" }, | 1630 | { book_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" }, |
1425 | { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" }, | 1631 | { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" }, |
@@ -1431,6 +1637,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1431 | setId_Widget(menu, "toolbar.menu"); /* view menu */ | 1637 | setId_Widget(menu, "toolbar.menu"); /* view menu */ |
1432 | } | 1638 | } |
1433 | #endif | 1639 | #endif |
1640 | updateNavBarActions_(navBar); | ||
1434 | updatePadding_Root(d); | 1641 | updatePadding_Root(d); |
1435 | /* Global context menus. */ { | 1642 | /* Global context menus. */ { |
1436 | iWidget *tabsMenu = makeMenu_Widget( | 1643 | iWidget *tabsMenu = makeMenu_Widget( |
@@ -1474,6 +1681,12 @@ void createUserInterface_Root(iRoot *d) { | |||
1474 | { select_Icon " ${menu.selectall}", 0, 0, "input.selectall" }, | 1681 | { select_Icon " ${menu.selectall}", 0, 0, "input.selectall" }, |
1475 | }, 8); | 1682 | }, 8); |
1476 | #endif | 1683 | #endif |
1684 | if (deviceType_App() == phone_AppDeviceType) { | ||
1685 | /* Small screen; conserve space by removing the Cancel item. */ | ||
1686 | iRelease(removeChild_Widget(clipMenu, lastChild_Widget(clipMenu))); | ||
1687 | iRelease(removeChild_Widget(clipMenu, lastChild_Widget(clipMenu))); | ||
1688 | iRelease(removeChild_Widget(clipMenu, lastChild_Widget(clipMenu))); | ||
1689 | } | ||
1477 | iWidget *splitMenu = makeMenu_Widget(root, (iMenuItem[]){ | 1690 | iWidget *splitMenu = makeMenu_Widget(root, (iMenuItem[]){ |
1478 | { "${menu.split.merge}", '1', 0, "ui.split arg:0" }, | 1691 | { "${menu.split.merge}", '1', 0, "ui.split arg:0" }, |
1479 | { "${menu.split.swap}", SDLK_x, 0, "ui.split swap:1" }, | 1692 | { "${menu.split.swap}", SDLK_x, 0, "ui.split swap:1" }, |
@@ -1493,6 +1706,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1493 | setId_Widget(splitMenu, "splitmenu"); | 1706 | setId_Widget(splitMenu, "splitmenu"); |
1494 | } | 1707 | } |
1495 | /* Global keyboard shortcuts. */ { | 1708 | /* Global keyboard shortcuts. */ { |
1709 | addAction_Widget(root, SDLK_h, KMOD_PRIMARY | KMOD_SHIFT, "navigate.home"); | ||
1496 | addAction_Widget(root, 'l', KMOD_PRIMARY, "navigate.focus"); | 1710 | addAction_Widget(root, 'l', KMOD_PRIMARY, "navigate.focus"); |
1497 | addAction_Widget(root, 'f', KMOD_PRIMARY, "focus.set id:find.input"); | 1711 | addAction_Widget(root, 'f', KMOD_PRIMARY, "focus.set id:find.input"); |
1498 | addAction_Widget(root, '1', KMOD_PRIMARY, "sidebar.mode arg:0 toggle:1"); | 1712 | addAction_Widget(root, '1', KMOD_PRIMARY, "sidebar.mode arg:0 toggle:1"); |
@@ -1558,9 +1772,10 @@ iRect safeRect_Root(const iRoot *d) { | |||
1558 | 1772 | ||
1559 | iRect visibleRect_Root(const iRoot *d) { | 1773 | iRect visibleRect_Root(const iRoot *d) { |
1560 | iRect visRect = rect_Root(d); | 1774 | iRect visRect = rect_Root(d); |
1775 | float bottom = 0.0f; | ||
1561 | #if defined (iPlatformAppleMobile) | 1776 | #if defined (iPlatformAppleMobile) |
1562 | /* TODO: Check this on device... Maybe DisplayUsableBounds would be good here, too? */ | 1777 | /* TODO: Check this on device... Maybe DisplayUsableBounds would be good here, too? */ |
1563 | float left, top, right, bottom; | 1778 | float left, top, right; |
1564 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | 1779 | safeAreaInsets_iOS(&left, &top, &right, &bottom); |
1565 | visRect.pos.x = (int) left; | 1780 | visRect.pos.x = (int) left; |
1566 | visRect.size.x -= (int) (left + right); | 1781 | visRect.size.x -= (int) (left + right); |
@@ -1585,6 +1800,6 @@ iRect visibleRect_Root(const iRoot *d) { | |||
1585 | visRect = intersect_Rect(visRect, init_Rect(usable.x, usable.y, usable.w, usable.h)); | 1800 | visRect = intersect_Rect(visRect, init_Rect(usable.x, usable.y, usable.w, usable.h)); |
1586 | } | 1801 | } |
1587 | #endif | 1802 | #endif |
1588 | adjustEdges_Rect(&visRect, 0, 0, -get_MainWindow()->keyboardHeight, 0); | 1803 | adjustEdges_Rect(&visRect, 0, 0, -get_MainWindow()->keyboardHeight + bottom, 0); |
1589 | return visRect; | 1804 | return visRect; |
1590 | } | 1805 | } |
diff --git a/src/ui/root.h b/src/ui/root.h index 851d927d..7e831be3 100644 --- a/src/ui/root.h +++ b/src/ui/root.h | |||
@@ -2,11 +2,15 @@ | |||
2 | 2 | ||
3 | #include "widget.h" | 3 | #include "widget.h" |
4 | #include "color.h" | 4 | #include "color.h" |
5 | #include <the_Foundation/audience.h> | ||
5 | #include <the_Foundation/ptrset.h> | 6 | #include <the_Foundation/ptrset.h> |
6 | #include <the_Foundation/vec2.h> | 7 | #include <the_Foundation/vec2.h> |
7 | 8 | ||
8 | iDeclareType(Root) | 9 | iDeclareType(Root) |
9 | 10 | ||
11 | iDeclareNotifyFunc(Root, VisualOffsetsChanged) | ||
12 | iDeclareAudienceGetter(Root, visualOffsetsChanged) | ||
13 | |||
10 | struct Impl_Root { | 14 | struct Impl_Root { |
11 | iWidget * widget; | 15 | iWidget * widget; |
12 | iWindow * window; | 16 | iWindow * window; |
@@ -14,6 +18,9 @@ struct Impl_Root { | |||
14 | iPtrSet * pendingDestruction; | 18 | iPtrSet * pendingDestruction; |
15 | iBool pendingArrange; | 19 | iBool pendingArrange; |
16 | int loadAnimTimer; | 20 | int loadAnimTimer; |
21 | iBool didAnimateVisualOffsets; | ||
22 | iBool didChangeArrangement; | ||
23 | iAudience *visualOffsetsChanged; /* called after running tickers */ | ||
17 | iColor tmPalette[tmMax_ColorId]; /* theme-specific palette */ | 24 | iColor tmPalette[tmMax_ColorId]; /* theme-specific palette */ |
18 | }; | 25 | }; |
19 | 26 | ||
@@ -36,6 +43,7 @@ void updatePadding_Root (iRoot *); /* TODO: is part of m | |||
36 | void dismissPortraitPhoneSidebars_Root (iRoot *); | 43 | void dismissPortraitPhoneSidebars_Root (iRoot *); |
37 | void showToolbar_Root (iRoot *, iBool show); | 44 | void showToolbar_Root (iRoot *, iBool show); |
38 | void updateToolbarColors_Root (iRoot *); | 45 | void updateToolbarColors_Root (iRoot *); |
46 | void notifyVisualOffsetChange_Root (iRoot *); | ||
39 | 47 | ||
40 | iInt2 size_Root (const iRoot *); | 48 | iInt2 size_Root (const iRoot *); |
41 | iRect rect_Root (const iRoot *); | 49 | iRect rect_Root (const iRoot *); |
diff --git a/src/ui/scrollwidget.c b/src/ui/scrollwidget.c index b6f73b6c..651669c6 100644 --- a/src/ui/scrollwidget.c +++ b/src/ui/scrollwidget.c | |||
@@ -107,7 +107,7 @@ static iRect bounds_ScrollWidget_(const iScrollWidget *d) { | |||
107 | static iRect thumbRect_ScrollWidget_(const iScrollWidget *d) { | 107 | static iRect thumbRect_ScrollWidget_(const iScrollWidget *d) { |
108 | const iRect bounds = bounds_ScrollWidget_(d); | 108 | const iRect bounds = bounds_ScrollWidget_(d); |
109 | iRect rect = init_Rect(bounds.pos.x, bounds.pos.y, bounds.size.x, 0); | 109 | iRect rect = init_Rect(bounds.pos.x, bounds.pos.y, bounds.size.x, 0); |
110 | const int total = size_Range(&d->range); | 110 | const int total = (int) size_Range(&d->range); |
111 | if (total > 0) { | 111 | if (total > 0) { |
112 | const int tsize = thumbSize_ScrollWidget_(d); | 112 | const int tsize = thumbSize_ScrollWidget_(d); |
113 | // iAssert(tsize <= height_Rect(bounds)); | 113 | // iAssert(tsize <= height_Rect(bounds)); |
@@ -197,7 +197,7 @@ static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) { | |||
197 | case drag_ClickResult: { | 197 | case drag_ClickResult: { |
198 | const iRect bounds = bounds_ScrollWidget_(d); | 198 | const iRect bounds = bounds_ScrollWidget_(d); |
199 | const int offset = delta_Click(&d->click).y; | 199 | const int offset = delta_Click(&d->click).y; |
200 | const int total = size_Range(&d->range); | 200 | const int total = (int) size_Range(&d->range); |
201 | int dpos = (float) offset / (float) (height_Rect(bounds) - thumbSize_ScrollWidget_(d)) * total; | 201 | int dpos = (float) offset / (float) (height_Rect(bounds) - thumbSize_ScrollWidget_(d)) * total; |
202 | d->thumb = iClamp(d->startThumb + dpos, d->range.start, d->range.end); | 202 | d->thumb = iClamp(d->startThumb + dpos, d->range.start, d->range.end); |
203 | postCommand_Widget(w, "scroll.moved arg:%d", d->thumb); | 203 | postCommand_Widget(w, "scroll.moved arg:%d", d->thumb); |
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 2219eba9..16677f9e 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
25 | #include "app.h" | 25 | #include "app.h" |
26 | #include "defs.h" | 26 | #include "defs.h" |
27 | #include "bookmarks.h" | 27 | #include "bookmarks.h" |
28 | #include "certlistwidget.h" | ||
28 | #include "command.h" | 29 | #include "command.h" |
29 | #include "documentwidget.h" | 30 | #include "documentwidget.h" |
30 | #include "feeds.h" | 31 | #include "feeds.h" |
@@ -34,10 +35,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
34 | #include "inputwidget.h" | 35 | #include "inputwidget.h" |
35 | #include "labelwidget.h" | 36 | #include "labelwidget.h" |
36 | #include "listwidget.h" | 37 | #include "listwidget.h" |
38 | #include "mobile.h" | ||
37 | #include "keys.h" | 39 | #include "keys.h" |
38 | #include "paint.h" | 40 | #include "paint.h" |
39 | #include "root.h" | 41 | #include "root.h" |
40 | #include "scrollwidget.h" | 42 | #include "scrollwidget.h" |
43 | #include "touch.h" | ||
41 | #include "util.h" | 44 | #include "util.h" |
42 | #include "visited.h" | 45 | #include "visited.h" |
43 | 46 | ||
@@ -88,6 +91,22 @@ iDefineObjectConstruction(SidebarItem) | |||
88 | 91 | ||
89 | /*----------------------------------------------------------------------------------------------*/ | 92 | /*----------------------------------------------------------------------------------------------*/ |
90 | 93 | ||
94 | static const char *normalModeLabels_[max_SidebarMode] = { | ||
95 | book_Icon " ${sidebar.bookmarks}", | ||
96 | star_Icon " ${sidebar.feeds}", | ||
97 | clock_Icon " ${sidebar.history}", | ||
98 | person_Icon " ${sidebar.identities}", | ||
99 | page_Icon " ${sidebar.outline}", | ||
100 | }; | ||
101 | |||
102 | static const char *tightModeLabels_[max_SidebarMode] = { | ||
103 | book_Icon, | ||
104 | star_Icon, | ||
105 | clock_Icon, | ||
106 | person_Icon, | ||
107 | page_Icon, | ||
108 | }; | ||
109 | |||
91 | struct Impl_SidebarWidget { | 110 | struct Impl_SidebarWidget { |
92 | iWidget widget; | 111 | iWidget widget; |
93 | enum iSidebarSide side; | 112 | enum iSidebarSide side; |
@@ -96,7 +115,10 @@ struct Impl_SidebarWidget { | |||
96 | iString cmdPrefix; | 115 | iString cmdPrefix; |
97 | iWidget * blank; | 116 | iWidget * blank; |
98 | iListWidget * list; | 117 | iListWidget * list; |
118 | iCertListWidget * certList; | ||
99 | iWidget * actions; /* below the list, area for buttons */ | 119 | iWidget * actions; /* below the list, area for buttons */ |
120 | int midHeight; /* on portrait phone, the height for the middle state */ | ||
121 | iBool isEditing; /* mobile edit mode */ | ||
100 | int modeScroll[max_SidebarMode]; | 122 | int modeScroll[max_SidebarMode]; |
101 | iLabelWidget * modeButtons[max_SidebarMode]; | 123 | iLabelWidget * modeButtons[max_SidebarMode]; |
102 | int maxButtonLabelWidth; | 124 | int maxButtonLabelWidth; |
@@ -114,6 +136,10 @@ struct Impl_SidebarWidget { | |||
114 | 136 | ||
115 | iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side) | 137 | iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side) |
116 | 138 | ||
139 | iLocalDef iListWidget *list_SidebarWidget_(iSidebarWidget *d) { | ||
140 | return d->mode == identities_SidebarMode ? (iListWidget *) d->certList : d->list; | ||
141 | } | ||
142 | |||
117 | static iBool isResizing_SidebarWidget_(const iSidebarWidget *d) { | 143 | static iBool isResizing_SidebarWidget_(const iSidebarWidget *d) { |
118 | return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0; | 144 | return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0; |
119 | } | 145 | } |
@@ -181,77 +207,31 @@ int cmpTree_Bookmark(const iBookmark **a, const iBookmark **b) { | |||
181 | return cmpStringCase_String(&bm1->title, &bm2->title); | 207 | return cmpStringCase_String(&bm1->title, &bm2->title); |
182 | } | 208 | } |
183 | 209 | ||
210 | static enum iFontId actionButtonFont_SidebarWidget_(const iSidebarWidget *d) { | ||
211 | switch (deviceType_App()) { | ||
212 | default: | ||
213 | break; | ||
214 | case phone_AppDeviceType: | ||
215 | return isPortrait_App() ? uiLabelBig_FontId : uiLabelMedium_FontId; | ||
216 | case tablet_AppDeviceType: | ||
217 | return uiLabelMedium_FontId; | ||
218 | } | ||
219 | return d->buttonFont; | ||
220 | } | ||
221 | |||
184 | static iLabelWidget *addActionButton_SidebarWidget_(iSidebarWidget *d, const char *label, | 222 | static iLabelWidget *addActionButton_SidebarWidget_(iSidebarWidget *d, const char *label, |
185 | const char *command, int64_t flags) { | 223 | const char *command, int64_t flags) { |
186 | iLabelWidget *btn = addChildFlags_Widget(d->actions, | 224 | iLabelWidget *btn = addChildFlags_Widget(d->actions, |
187 | iClob(new_LabelWidget(label, command)), | 225 | iClob(new_LabelWidget(label, command)), |
188 | //(deviceType_App() != desktop_AppDeviceType ? | ||
189 | // extraPadding_WidgetFlag : 0) | | ||
190 | flags); | 226 | flags); |
191 | setFont_LabelWidget(btn, deviceType_App() == phone_AppDeviceType && d->side == right_SidebarSide | 227 | setFont_LabelWidget(btn, actionButtonFont_SidebarWidget_(d)); |
192 | ? uiLabelBig_FontId | ||
193 | : d->buttonFont); | ||
194 | checkIcon_LabelWidget(btn); | 228 | checkIcon_LabelWidget(btn); |
195 | return btn; | 229 | if (deviceType_App() != desktop_AppDeviceType) { |
196 | } | 230 | setFlags_Widget(as_Widget(btn), frameless_WidgetFlag, iTrue); |
197 | 231 | setTextColor_LabelWidget(btn, uiTextAction_ColorId); | |
198 | static iGmIdentity *menuIdentity_SidebarWidget_(const iSidebarWidget *d) { | 232 | setBackgroundColor_Widget(as_Widget(btn), uiBackground_ColorId); |
199 | if (d->mode == identities_SidebarMode) { | ||
200 | if (d->contextItem) { | ||
201 | return identity_GmCerts(certs_App(), d->contextItem->id); | ||
202 | } | ||
203 | } | ||
204 | return NULL; | ||
205 | } | ||
206 | |||
207 | static void updateContextMenu_SidebarWidget_(iSidebarWidget *d) { | ||
208 | if (d->mode != identities_SidebarMode) { | ||
209 | return; | ||
210 | } | 233 | } |
211 | iArray *items = collectNew_Array(sizeof(iMenuItem)); | 234 | return btn; |
212 | pushBackN_Array(items, (iMenuItem[]){ | ||
213 | { person_Icon " ${ident.use}", 0, 0, "ident.use arg:1" }, | ||
214 | { close_Icon " ${ident.stopuse}", 0, 0, "ident.use arg:0" }, | ||
215 | { close_Icon " ${ident.stopuse.all}", 0, 0, "ident.use arg:0 clear:1" }, | ||
216 | { "---", 0, 0, NULL }, | ||
217 | { edit_Icon " ${menu.edit.notes}", 0, 0, "ident.edit" }, | ||
218 | { "${ident.fingerprint}", 0, 0, "ident.fingerprint" }, | ||
219 | { export_Icon " ${ident.export}", 0, 0, "ident.export" }, | ||
220 | { "---", 0, 0, NULL }, | ||
221 | { delete_Icon " " uiTextCaution_ColorEscape "${ident.delete}", 0, 0, "ident.delete confirm:1" }, | ||
222 | }, 9); | ||
223 | /* Used URLs. */ | ||
224 | const iGmIdentity *ident = menuIdentity_SidebarWidget_(d); | ||
225 | if (ident) { | ||
226 | size_t insertPos = 3; | ||
227 | if (!isEmpty_StringSet(ident->useUrls)) { | ||
228 | insert_Array(items, insertPos++, &(iMenuItem){ "---", 0, 0, NULL }); | ||
229 | } | ||
230 | const iString *docUrl = url_DocumentWidget(document_App()); | ||
231 | iBool usedOnCurrentPage = iFalse; | ||
232 | iConstForEach(StringSet, i, ident->useUrls) { | ||
233 | const iString *url = i.value; | ||
234 | usedOnCurrentPage |= equalCase_String(docUrl, url); | ||
235 | iRangecc urlStr = range_String(url); | ||
236 | if (startsWith_Rangecc(urlStr, "gemini://")) { | ||
237 | urlStr.start += 9; /* omit the default scheme */ | ||
238 | } | ||
239 | if (endsWith_Rangecc(urlStr, "/")) { | ||
240 | urlStr.end--; /* looks cleaner */ | ||
241 | } | ||
242 | insert_Array(items, | ||
243 | insertPos++, | ||
244 | &(iMenuItem){ format_CStr(globe_Icon " %s", cstr_Rangecc(urlStr)), | ||
245 | 0, | ||
246 | 0, | ||
247 | format_CStr("!open url:%s", cstr_String(url)) }); | ||
248 | } | ||
249 | if (!usedOnCurrentPage) { | ||
250 | remove_Array(items, 1); | ||
251 | } | ||
252 | } | ||
253 | destroy_Widget(d->menu); | ||
254 | d->menu = makeMenu_Widget(as_Widget(d), data_Array(items), size_Array(items)); | ||
255 | } | 235 | } |
256 | 236 | ||
257 | static iBool isBookmarkFolded_SidebarWidget_(const iSidebarWidget *d, const iBookmark *bm) { | 237 | static iBool isBookmarkFolded_SidebarWidget_(const iSidebarWidget *d, const iBookmark *bm) { |
@@ -264,11 +244,29 @@ static iBool isBookmarkFolded_SidebarWidget_(const iSidebarWidget *d, const iBoo | |||
264 | return iFalse; | 244 | return iFalse; |
265 | } | 245 | } |
266 | 246 | ||
247 | static iBool isSlidingSheet_SidebarWidget_(const iSidebarWidget *d) { | ||
248 | return isPortraitPhone_App(); | ||
249 | } | ||
250 | |||
251 | static void setMobileEditMode_SidebarWidget_(iSidebarWidget *d, iBool editing) { | ||
252 | iWidget *w = as_Widget(d); | ||
253 | d->isEditing = editing; | ||
254 | if (d->actions) { | ||
255 | setFlags_Widget(findChild_Widget(w, "sidebar.close"), hidden_WidgetFlag, editing); | ||
256 | setFlags_Widget(child_Widget(d->actions, 0), hidden_WidgetFlag, !editing); | ||
257 | setTextCStr_LabelWidget(child_Widget(as_Widget(d->actions), 2), | ||
258 | editing ? "${sidebar.close}" : "${sidebar.action.bookmarks.edit}"); | ||
259 | setDragHandleWidth_ListWidget(d->list, editing ? itemHeight_ListWidget(d->list) * 3 / 2 : 0); | ||
260 | arrange_Widget(d->actions); | ||
261 | } | ||
262 | } | ||
263 | |||
267 | static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepActions) { | 264 | static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepActions) { |
265 | const iBool isMobile = (deviceType_App() != desktop_AppDeviceType); | ||
268 | clear_ListWidget(d->list); | 266 | clear_ListWidget(d->list); |
269 | releaseChildren_Widget(d->blank); | 267 | releaseChildren_Widget(d->blank); |
270 | if (!keepActions) { | 268 | if (!keepActions) { |
271 | releaseChildren_Widget(d->actions); | 269 | releaseChildren_Widget(d->actions); |
272 | } | 270 | } |
273 | d->actions->rect.size.y = 0; | 271 | d->actions->rect.size.y = 0; |
274 | destroy_Widget(d->menu); | 272 | destroy_Widget(d->menu); |
@@ -288,7 +286,8 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct | |||
288 | iZap(on); | 286 | iZap(on); |
289 | size_t numItems = 0; | 287 | size_t numItems = 0; |
290 | isEmpty = iTrue; | 288 | isEmpty = iTrue; |
291 | iConstForEach(PtrArray, i, listEntries_Feeds()) { | 289 | const iPtrArray *feedEntries = listEntries_Feeds(); |
290 | iConstForEach(PtrArray, i, feedEntries) { | ||
292 | const iFeedEntry *entry = i.ptr; | 291 | const iFeedEntry *entry = i.ptr; |
293 | if (isHidden_FeedEntry(entry)) { | 292 | if (isHidden_FeedEntry(entry)) { |
294 | continue; /* A hidden entry. */ | 293 | continue; /* A hidden entry. */ |
@@ -301,12 +300,12 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct | |||
301 | if (secondsSince_Time(&now, &entry->discovered) > maxAge_Visited) { | 300 | if (secondsSince_Time(&now, &entry->discovered) > maxAge_Visited) { |
302 | break; /* the rest are even older */ | 301 | break; /* the rest are even older */ |
303 | } | 302 | } |
304 | isEmpty = iFalse; | ||
305 | const iBool isOpen = equal_String(docUrl, &entry->url); | 303 | const iBool isOpen = equal_String(docUrl, &entry->url); |
306 | const iBool isUnread = isUnread_FeedEntry(entry); | 304 | const iBool isUnread = isUnread_FeedEntry(entry); |
307 | if (d->feedsMode == unread_FeedsMode && !isUnread && !isOpen) { | 305 | if (d->feedsMode == unread_FeedsMode && !isUnread && !isOpen) { |
308 | continue; | 306 | continue; |
309 | } | 307 | } |
308 | isEmpty = iFalse; | ||
310 | /* Insert date separators. */ { | 309 | /* Insert date separators. */ { |
311 | iDate entryDate; | 310 | iDate entryDate; |
312 | init_Date(&entryDate, &entry->posted); | 311 | init_Date(&entryDate, &entry->posted); |
@@ -351,34 +350,67 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct | |||
351 | } | 350 | } |
352 | } | 351 | } |
353 | /* Actions. */ | 352 | /* Actions. */ |
354 | if (!keepActions) { | 353 | if (!isMobile) { |
355 | addActionButton_SidebarWidget_( | 354 | if (!keepActions && !isEmpty_PtrArray(feedEntries)) { |
356 | d, check_Icon " ${sidebar.action.feeds.markallread}", "feeds.markallread", expand_WidgetFlag | | 355 | addActionButton_SidebarWidget_(d, |
357 | tight_WidgetFlag); | 356 | check_Icon |
358 | updateSize_LabelWidget(addChildFlags_Widget(d->actions, | 357 | " ${sidebar.action.feeds.markallread}", |
359 | iClob(new_LabelWidget("${sidebar.action.show}", NULL)), | 358 | "feeds.markallread", |
360 | frameless_WidgetFlag | tight_WidgetFlag)); | 359 | expand_WidgetFlag | tight_WidgetFlag); |
361 | const iMenuItem items[] = { | 360 | updateSize_LabelWidget( |
362 | { "${sidebar.action.feeds.showall}", SDLK_u, KMOD_SHIFT, "feeds.mode arg:0" }, | 361 | addChildFlags_Widget(d->actions, |
363 | { "${sidebar.action.feeds.showunread}", SDLK_u, 0, "feeds.mode arg:1" }, | 362 | iClob(new_LabelWidget("${sidebar.action.show}", NULL)), |
364 | }; | 363 | frameless_WidgetFlag | tight_WidgetFlag)); |
365 | iWidget *dropButton = addChild_Widget( | 364 | const iMenuItem items[] = { |
366 | d->actions, | 365 | { page_Icon " ${sidebar.action.feeds.showall}", |
367 | iClob(makeMenuButton_LabelWidget(items[d->feedsMode].label, items, 2))); | 366 | SDLK_u, |
368 | setId_Widget(dropButton, "feeds.modebutton"); | 367 | KMOD_SHIFT, |
369 | checkIcon_LabelWidget((iLabelWidget *) dropButton); | 368 | "feeds.mode arg:0" }, |
370 | setFixedSize_Widget( | 369 | { circle_Icon " ${sidebar.action.feeds.showunread}", |
371 | dropButton, | 370 | SDLK_u, |
372 | init_I2(iMaxi(20 * gap_UI, measure_Text( | 371 | 0, |
373 | default_FontId, | 372 | "feeds.mode arg:1" }, |
374 | translateCStr_Lang(items[findWidestLabel_MenuItem(items, 2)].label)) | 373 | }; |
375 | .advance.x + | 374 | iWidget *dropButton = addChild_Widget( |
376 | 6 * gap_UI), | 375 | d->actions, |
376 | iClob(makeMenuButton_LabelWidget(items[d->feedsMode].label, items, 2))); | ||
377 | setId_Widget(dropButton, "feeds.modebutton"); | ||
378 | checkIcon_LabelWidget((iLabelWidget *) dropButton); | ||
379 | setFixedSize_Widget( | ||
380 | dropButton, | ||
381 | init_I2( | ||
382 | iMaxi(20 * gap_UI, | ||
383 | measure_Text(default_FontId, | ||
384 | translateCStr_Lang( | ||
385 | items[findWidestLabel_MenuItem(items, 2)].label)) | ||
386 | .advance.x + | ||
387 | 13 * gap_UI), | ||
377 | -1)); | 388 | -1)); |
389 | } | ||
390 | else { | ||
391 | updateDropdownSelection_LabelWidget( | ||
392 | findChild_Widget(d->actions, "feeds.modebutton"), | ||
393 | format_CStr(" arg:%d", d->feedsMode)); | ||
394 | } | ||
378 | } | 395 | } |
379 | else { | 396 | else { |
380 | updateDropdownSelection_LabelWidget(findChild_Widget(d->actions, "feeds.modebutton"), | 397 | if (!keepActions) { |
381 | format_CStr(" arg:%d", d->feedsMode)); | 398 | iLabelWidget *readAll = addActionButton_SidebarWidget_(d, |
399 | check_Icon, | ||
400 | "feeds.markallread confirm:1", | ||
401 | 0); | ||
402 | setTextColor_LabelWidget(readAll, uiTextCaution_ColorId); | ||
403 | addActionButton_SidebarWidget_(d, | ||
404 | page_Icon, | ||
405 | "feeds.mode arg:0", | ||
406 | 0); | ||
407 | addActionButton_SidebarWidget_(d, | ||
408 | circle_Icon, | ||
409 | "feeds.mode arg:1", | ||
410 | 0); | ||
411 | } | ||
412 | setOutline_LabelWidget(child_Widget(d->actions, 1), d->feedsMode != all_FeedsMode); | ||
413 | setOutline_LabelWidget(child_Widget(d->actions, 2), d->feedsMode != unread_FeedsMode); | ||
382 | } | 414 | } |
383 | d->menu = makeMenu_Widget( | 415 | d->menu = makeMenu_Widget( |
384 | as_Widget(d), | 416 | as_Widget(d), |
@@ -416,11 +448,6 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct | |||
416 | break; | 448 | break; |
417 | } | 449 | } |
418 | case bookmarks_SidebarMode: { | 450 | case bookmarks_SidebarMode: { |
419 | iRegExp *homeTag = iClob(new_RegExp("\\b" homepage_BookmarkTag "\\b", caseSensitive_RegExpOption)); | ||
420 | iRegExp *subTag = iClob(new_RegExp("\\b" subscribed_BookmarkTag "\\b", caseSensitive_RegExpOption)); | ||
421 | iRegExp *remoteSourceTag = iClob(new_RegExp("\\b" remoteSource_BookmarkTag "\\b", caseSensitive_RegExpOption)); | ||
422 | iRegExp *remoteTag = iClob(new_RegExp("\\b" remote_BookmarkTag "\\b", caseSensitive_RegExpOption)); | ||
423 | iRegExp *linkSplitTag = iClob(new_RegExp("\\b" linkSplit_BookmarkTag "\\b", caseSensitive_RegExpOption)); | ||
424 | iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTree_Bookmark, NULL, NULL)) { | 451 | iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTree_Bookmark, NULL, NULL)) { |
425 | const iBookmark *bm = i.ptr; | 452 | const iBookmark *bm = i.ptr; |
426 | if (isBookmarkFolded_SidebarWidget_(d, bm)) { | 453 | if (isBookmarkFolded_SidebarWidget_(d, bm)) { |
@@ -439,27 +466,21 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct | |||
439 | } | 466 | } |
440 | set_String(&item->url, &bm->url); | 467 | set_String(&item->url, &bm->url); |
441 | set_String(&item->label, &bm->title); | 468 | set_String(&item->label, &bm->title); |
442 | /* Icons for special tags. */ { | 469 | /* Icons for special behaviors. */ { |
443 | iRegExpMatch m; | 470 | if (bm->flags & subscribed_BookmarkFlag) { |
444 | init_RegExpMatch(&m); | ||
445 | if (matchString_RegExp(subTag, &bm->tags, &m)) { | ||
446 | appendChar_String(&item->meta, 0x2605); | 471 | appendChar_String(&item->meta, 0x2605); |
447 | } | 472 | } |
448 | init_RegExpMatch(&m); | 473 | if (bm->flags & homepage_BookmarkFlag) { |
449 | if (matchString_RegExp(homeTag, &bm->tags, &m)) { | ||
450 | appendChar_String(&item->meta, 0x1f3e0); | 474 | appendChar_String(&item->meta, 0x1f3e0); |
451 | } | 475 | } |
452 | init_RegExpMatch(&m); | 476 | if (bm->flags & remote_BookmarkFlag) { |
453 | if (matchString_RegExp(remoteTag, &bm->tags, &m)) { | ||
454 | item->listItem.isDraggable = iFalse; | 477 | item->listItem.isDraggable = iFalse; |
455 | } | 478 | } |
456 | init_RegExpMatch(&m); | 479 | if (bm->flags & remoteSource_BookmarkFlag) { |
457 | if (matchString_RegExp(remoteSourceTag, &bm->tags, &m)) { | ||
458 | appendChar_String(&item->meta, 0x2913); | 480 | appendChar_String(&item->meta, 0x2913); |
459 | item->isBold = iTrue; | 481 | item->isBold = iTrue; |
460 | } | 482 | } |
461 | init_RegExpMatch(&m); | 483 | if (bm->flags & linkSplit_BookmarkFlag) { |
462 | if (matchString_RegExp(linkSplitTag, &bm->tags, &m)) { | ||
463 | appendChar_String(&item->meta, 0x25e7); | 484 | appendChar_String(&item->meta, 0x25e7); |
464 | } | 485 | } |
465 | } | 486 | } |
@@ -495,6 +516,14 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct | |||
495 | { "---", 0, 0, NULL }, | 516 | { "---", 0, 0, NULL }, |
496 | { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" } }, | 517 | { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" } }, |
497 | 6); | 518 | 6); |
519 | if (isMobile) { | ||
520 | addActionButton_SidebarWidget_(d, "${sidebar.action.bookmarks.newfolder}", | ||
521 | "bookmarks.addfolder", !d->isEditing ? hidden_WidgetFlag : 0); | ||
522 | addChildFlags_Widget(d->actions, iClob(new_Widget()), expand_WidgetFlag); | ||
523 | iLabelWidget *btn = addActionButton_SidebarWidget_(d, | ||
524 | d->isEditing ? "${sidebar.close}" : "${sidebar.action.bookmarks.edit}", | ||
525 | "sidebar.bookmarks.edit", 0); | ||
526 | } | ||
498 | break; | 527 | break; |
499 | } | 528 | } |
500 | case history_SidebarMode: { | 529 | case history_SidebarMode: { |
@@ -554,49 +583,15 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct | |||
554 | (iMenuItem[]){ | 583 | (iMenuItem[]){ |
555 | { delete_Icon " " uiTextCaution_ColorEscape "${history.clear}", 0, 0, "history.clear confirm:1" }, | 584 | { delete_Icon " " uiTextCaution_ColorEscape "${history.clear}", 0, 0, "history.clear confirm:1" }, |
556 | }, 1); | 585 | }, 1); |
586 | if (isMobile) { | ||
587 | addChildFlags_Widget(d->actions, iClob(new_Widget()), expand_WidgetFlag); | ||
588 | iLabelWidget *btn = addActionButton_SidebarWidget_(d, "${sidebar.action.history.clear}", | ||
589 | "history.clear confirm:1", 0); | ||
590 | } | ||
557 | break; | 591 | break; |
558 | } | 592 | } |
559 | case identities_SidebarMode: { | 593 | case identities_SidebarMode: { |
560 | const iString *tabUrl = url_DocumentWidget(document_App()); | 594 | isEmpty = !updateItems_CertListWidget(d->certList); |
561 | const iRangecc tabHost = urlHost_String(tabUrl); | ||
562 | isEmpty = iTrue; | ||
563 | iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) { | ||
564 | const iGmIdentity *ident = i.ptr; | ||
565 | iSidebarItem *item = new_SidebarItem(); | ||
566 | item->id = (uint32_t) index_PtrArrayConstIterator(&i); | ||
567 | item->icon = 0x1f464; /* person */ | ||
568 | set_String(&item->label, name_GmIdentity(ident)); | ||
569 | iDate until; | ||
570 | validUntil_TlsCertificate(ident->cert, &until); | ||
571 | const iBool isActive = isUsedOn_GmIdentity(ident, tabUrl); | ||
572 | format_String(&item->meta, | ||
573 | "%s", | ||
574 | isActive ? cstr_Lang("ident.using") | ||
575 | : isUsed_GmIdentity(ident) | ||
576 | ? formatCStrs_Lang("ident.usedonurls.n", size_StringSet(ident->useUrls)) | ||
577 | : cstr_Lang("ident.notused")); | ||
578 | const char *expiry = | ||
579 | ident->flags & temporary_GmIdentityFlag | ||
580 | ? cstr_Lang("ident.temporary") | ||
581 | : cstrCollect_String(format_Date(&until, cstr_Lang("ident.expiry"))); | ||
582 | if (isEmpty_String(&ident->notes)) { | ||
583 | appendFormat_String(&item->meta, "\n%s", expiry); | ||
584 | } | ||
585 | else { | ||
586 | appendFormat_String(&item->meta, | ||
587 | " \u2014 %s\n%s%s", | ||
588 | expiry, | ||
589 | escape_Color(uiHeading_ColorId), | ||
590 | cstr_String(&ident->notes)); | ||
591 | } | ||
592 | item->listItem.isSelected = isActive; | ||
593 | if (isUsedOnDomain_GmIdentity(ident, tabHost)) { | ||
594 | item->indent = 1; /* will be highlighted */ | ||
595 | } | ||
596 | addItem_ListWidget(d->list, item); | ||
597 | iRelease(item); | ||
598 | isEmpty = iFalse; | ||
599 | } | ||
600 | /* Actions. */ | 595 | /* Actions. */ |
601 | if (!isEmpty) { | 596 | if (!isEmpty) { |
602 | addActionButton_SidebarWidget_(d, add_Icon " ${sidebar.action.ident.new}", "ident.new", 0); | 597 | addActionButton_SidebarWidget_(d, add_Icon " ${sidebar.action.ident.new}", "ident.new", 0); |
@@ -607,16 +602,27 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct | |||
607 | default: | 602 | default: |
608 | break; | 603 | break; |
609 | } | 604 | } |
610 | scrollOffset_ListWidget(d->list, 0); | 605 | setFlags_Widget(as_Widget(d->list), hidden_WidgetFlag, d->mode == identities_SidebarMode); |
611 | updateVisible_ListWidget(d->list); | 606 | setFlags_Widget(as_Widget(d->certList), hidden_WidgetFlag, d->mode != identities_SidebarMode); |
612 | invalidate_ListWidget(d->list); | 607 | scrollOffset_ListWidget(list_SidebarWidget_(d), 0); |
608 | updateVisible_ListWidget(list_SidebarWidget_(d)); | ||
609 | invalidate_ListWidget(list_SidebarWidget_(d)); | ||
613 | /* Content for a blank tab. */ | 610 | /* Content for a blank tab. */ |
614 | if (isEmpty) { | 611 | if (isEmpty) { |
615 | if (d->mode == feeds_SidebarMode) { | 612 | if (d->mode == feeds_SidebarMode) { |
616 | iWidget *div = makeVDiv_Widget(); | 613 | iWidget *div = makeVDiv_Widget(); |
617 | setPadding_Widget(div, 3 * gap_UI, 0, 3 * gap_UI, 2 * gap_UI); | 614 | setPadding_Widget(div, 3 * gap_UI, 0, 3 * gap_UI, 2 * gap_UI); |
615 | arrange_Widget(d->actions); | ||
616 | // setPadding_Widget(div, 0, 0, 0, height_Widget(d->actions)); | ||
618 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); /* pad */ | 617 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); /* pad */ |
619 | addChild_Widget(div, iClob(new_LabelWidget("${menu.feeds.refresh}", "feeds.refresh"))); | 618 | if (d->feedsMode == all_FeedsMode) { |
619 | addChild_Widget(div, iClob(new_LabelWidget("${menu.feeds.refresh}", "feeds.refresh"))); | ||
620 | } | ||
621 | else { | ||
622 | iLabelWidget *msg = addChildFlags_Widget(div, iClob(new_LabelWidget("${sidebar.empty.unread}", NULL)), | ||
623 | frameless_WidgetFlag); | ||
624 | setFont_LabelWidget(msg, uiLabelLarge_FontId); | ||
625 | } | ||
620 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); /* pad */ | 626 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); /* pad */ |
621 | addChild_Widget(d->blank, iClob(div)); | 627 | addChild_Widget(d->blank, iClob(div)); |
622 | } | 628 | } |
@@ -645,7 +651,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct | |||
645 | setWrap_LabelWidget(linkLabel, iTrue); | 651 | setWrap_LabelWidget(linkLabel, iTrue); |
646 | addChild_Widget(d->blank, iClob(div)); | 652 | addChild_Widget(d->blank, iClob(div)); |
647 | } | 653 | } |
648 | // arrange_Widget(d->blank); | 654 | arrange_Widget(d->blank); |
649 | } | 655 | } |
650 | #if 0 | 656 | #if 0 |
651 | if (deviceType_App() != desktop_AppDeviceType) { | 657 | if (deviceType_App() != desktop_AppDeviceType) { |
@@ -659,7 +665,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct | |||
659 | #endif | 665 | #endif |
660 | arrange_Widget(d->actions); | 666 | arrange_Widget(d->actions); |
661 | arrange_Widget(as_Widget(d)); | 667 | arrange_Widget(as_Widget(d)); |
662 | updateMouseHover_ListWidget(d->list); | 668 | updateMouseHover_ListWidget(list_SidebarWidget_(d)); |
663 | } | 669 | } |
664 | 670 | ||
665 | static void updateItems_SidebarWidget_(iSidebarWidget *d) { | 671 | static void updateItems_SidebarWidget_(iSidebarWidget *d) { |
@@ -678,35 +684,59 @@ static size_t findItem_SidebarWidget_(const iSidebarWidget *d, int id) { | |||
678 | } | 684 | } |
679 | 685 | ||
680 | static void updateItemHeight_SidebarWidget_(iSidebarWidget *d) { | 686 | static void updateItemHeight_SidebarWidget_(iSidebarWidget *d) { |
687 | const float heights[max_SidebarMode] = { 1.333f, 2.333f, 1.333f, 3.5f, 1.2f }; | ||
681 | if (d->list) { | 688 | if (d->list) { |
682 | const float heights[max_SidebarMode] = { 1.333f, 2.333f, 1.333f, 3.5f, 1.2f }; | ||
683 | setItemHeight_ListWidget(d->list, heights[d->mode] * lineHeight_Text(d->itemFonts[0])); | 689 | setItemHeight_ListWidget(d->list, heights[d->mode] * lineHeight_Text(d->itemFonts[0])); |
684 | } | 690 | } |
691 | if (d->certList) { | ||
692 | updateItemHeight_CertListWidget(d->certList); | ||
693 | } | ||
685 | } | 694 | } |
686 | 695 | ||
687 | iBool setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) { | 696 | iBool setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) { |
688 | if (d->mode == mode) { | 697 | if (d->mode == mode) { |
689 | return iFalse; | 698 | return iFalse; |
690 | } | 699 | } |
700 | if (mode == identities_SidebarMode && deviceType_App() != desktop_AppDeviceType) { | ||
701 | return iFalse; /* Identities are in Settings. */ | ||
702 | } | ||
691 | if (d->mode >= 0 && d->mode < max_SidebarMode) { | 703 | if (d->mode >= 0 && d->mode < max_SidebarMode) { |
692 | d->modeScroll[d->mode] = scrollPos_ListWidget(d->list); /* saved for later */ | 704 | d->modeScroll[d->mode] = scrollPos_ListWidget(list_SidebarWidget_(d)); /* saved for later */ |
693 | } | 705 | } |
694 | d->mode = mode; | 706 | d->mode = mode; |
695 | for (enum iSidebarMode i = 0; i < max_SidebarMode; i++) { | 707 | for (enum iSidebarMode i = 0; i < max_SidebarMode; i++) { |
696 | setFlags_Widget(as_Widget(d->modeButtons[i]), selected_WidgetFlag, i == d->mode); | 708 | setFlags_Widget(as_Widget(d->modeButtons[i]), selected_WidgetFlag, i == d->mode); |
697 | } | 709 | } |
698 | setBackgroundColor_Widget(as_Widget(d->list), | 710 | setBackgroundColor_Widget(as_Widget(list_SidebarWidget_(d)), |
699 | d->mode == documentOutline_SidebarMode ? tmBannerBackground_ColorId | 711 | d->mode == documentOutline_SidebarMode ? tmBannerBackground_ColorId |
700 | : uiBackgroundSidebar_ColorId); | 712 | : uiBackgroundSidebar_ColorId); |
701 | updateItemHeight_SidebarWidget_(d); | 713 | updateItemHeight_SidebarWidget_(d); |
714 | if (deviceType_App() != desktop_AppDeviceType && mode != bookmarks_SidebarMode) { | ||
715 | setMobileEditMode_SidebarWidget_(d, iFalse); | ||
716 | } | ||
702 | /* Restore previous scroll position. */ | 717 | /* Restore previous scroll position. */ |
703 | setScrollPos_ListWidget(d->list, d->modeScroll[mode]); | 718 | setScrollPos_ListWidget(list_SidebarWidget_(d), d->modeScroll[mode]); |
719 | /* Title of the mobile sliding sheet. */ | ||
720 | iLabelWidget *sheetTitle = findChild_Widget(&d->widget, "sidebar.title"); | ||
721 | if (sheetTitle) { | ||
722 | iString title; | ||
723 | initCStr_String(&title, normalModeLabels_[d->mode]); | ||
724 | removeIconPrefix_String(&title); | ||
725 | setText_LabelWidget(sheetTitle, &title); | ||
726 | deinit_String(&title); | ||
727 | } | ||
704 | return iTrue; | 728 | return iTrue; |
705 | } | 729 | } |
706 | 730 | ||
707 | void setClosedFolders_SidebarWidget(iSidebarWidget *d, const iIntSet *closedFolders) { | 731 | void setClosedFolders_SidebarWidget(iSidebarWidget *d, const iIntSet *closedFolders) { |
732 | if (d) { | ||
708 | delete_IntSet(d->closedFolders); | 733 | delete_IntSet(d->closedFolders); |
709 | d->closedFolders = copy_IntSet(closedFolders); | 734 | d->closedFolders = copy_IntSet(closedFolders); |
735 | } | ||
736 | } | ||
737 | |||
738 | void setMidHeight_SidebarWidget(iSidebarWidget *d, int midHeight) { | ||
739 | d->midHeight = midHeight; | ||
710 | } | 740 | } |
711 | 741 | ||
712 | enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) { | 742 | enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) { |
@@ -722,25 +752,9 @@ float width_SidebarWidget(const iSidebarWidget *d) { | |||
722 | } | 752 | } |
723 | 753 | ||
724 | const iIntSet *closedFolders_SidebarWidget(const iSidebarWidget *d) { | 754 | const iIntSet *closedFolders_SidebarWidget(const iSidebarWidget *d) { |
725 | return d->closedFolders; | 755 | return d ? d->closedFolders : collect_IntSet(new_IntSet()); |
726 | } | 756 | } |
727 | 757 | ||
728 | static const char *normalModeLabels_[max_SidebarMode] = { | ||
729 | book_Icon " ${sidebar.bookmarks}", | ||
730 | star_Icon " ${sidebar.feeds}", | ||
731 | clock_Icon " ${sidebar.history}", | ||
732 | person_Icon " ${sidebar.identities}", | ||
733 | page_Icon " ${sidebar.outline}", | ||
734 | }; | ||
735 | |||
736 | static const char *tightModeLabels_[max_SidebarMode] = { | ||
737 | book_Icon, | ||
738 | star_Icon, | ||
739 | clock_Icon, | ||
740 | person_Icon, | ||
741 | page_Icon, | ||
742 | }; | ||
743 | |||
744 | const char *icon_SidebarMode(enum iSidebarMode mode) { | 758 | const char *icon_SidebarMode(enum iSidebarMode mode) { |
745 | return tightModeLabels_[mode]; | 759 | return tightModeLabels_[mode]; |
746 | } | 760 | } |
@@ -762,6 +776,20 @@ static void updateMetrics_SidebarWidget_(iSidebarWidget *d) { | |||
762 | updateItemHeight_SidebarWidget_(d); | 776 | updateItemHeight_SidebarWidget_(d); |
763 | } | 777 | } |
764 | 778 | ||
779 | static void updateSlidingSheetHeight_SidebarWidget_(iSidebarWidget *sidebar, iRoot *root) { | ||
780 | if (!isPortraitPhone_App() || !isVisible_Widget(sidebar)) return; | ||
781 | iWidget *d = as_Widget(sidebar); | ||
782 | const int oldSize = d->rect.size.y; | ||
783 | const int newSize = bottom_Rect(safeRect_Root(d->root)) - top_Rect(bounds_Widget(d)); | ||
784 | if (oldSize != newSize) { | ||
785 | d->rect.size.y = newSize; | ||
786 | arrange_Widget(d); | ||
787 | } | ||
788 | // printf("[%p] %u: %d animating %d\n", d, window_Widget(d)->frameTime, | ||
789 | // (flags_Widget(d) & visualOffset_WidgetFlag) != 0, | ||
790 | // newSize); | ||
791 | } | ||
792 | |||
765 | void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | 793 | void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { |
766 | iWidget *w = as_Widget(d); | 794 | iWidget *w = as_Widget(d); |
767 | init_Widget(w); | 795 | init_Widget(w); |
@@ -778,6 +806,8 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
778 | d->side = side; | 806 | d->side = side; |
779 | d->mode = -1; | 807 | d->mode = -1; |
780 | d->feedsMode = all_FeedsMode; | 808 | d->feedsMode = all_FeedsMode; |
809 | d->midHeight = 0; | ||
810 | d->isEditing = iFalse; | ||
781 | d->numUnreadEntries = 0; | 811 | d->numUnreadEntries = 0; |
782 | d->buttonFont = uiLabel_FontId; /* wiil be changed later */ | 812 | d->buttonFont = uiLabel_FontId; /* wiil be changed later */ |
783 | d->itemFonts[0] = uiContent_FontId; | 813 | d->itemFonts[0] = uiContent_FontId; |
@@ -795,18 +825,38 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
795 | iWidget *vdiv = makeVDiv_Widget(); | 825 | iWidget *vdiv = makeVDiv_Widget(); |
796 | addChildFlags_Widget(w, vdiv, resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag); | 826 | addChildFlags_Widget(w, vdiv, resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag); |
797 | iZap(d->modeButtons); | 827 | iZap(d->modeButtons); |
798 | d->resizer = NULL; | 828 | d->resizer = NULL; |
799 | d->list = NULL; | 829 | d->list = NULL; |
800 | d->actions = NULL; | 830 | d->certList = NULL; |
831 | d->actions = NULL; | ||
801 | d->closedFolders = new_IntSet(); | 832 | d->closedFolders = new_IntSet(); |
802 | /* On a phone, the right sidebar is used exclusively for Identities. */ | 833 | /* On a phone, the right sidebar is not used. */ |
803 | const iBool isPhone = deviceType_App() == phone_AppDeviceType; | 834 | const iBool isPhone = (deviceType_App() == phone_AppDeviceType); |
804 | if (!isPhone || d->side == left_SidebarSide) { | 835 | if (isPhone) { |
836 | iLabelWidget *sheetTitle = | ||
837 | addChildFlags_Widget(vdiv, | ||
838 | iClob(new_LabelWidget("", NULL)), | ||
839 | collapse_WidgetFlag | | ||
840 | extraPadding_WidgetFlag | frameless_WidgetFlag); | ||
841 | setBackgroundColor_Widget(as_Widget(sheetTitle), uiBackground_ColorId); | ||
842 | iLabelWidget *closeButton = addChildFlags_Widget(as_Widget(sheetTitle), | ||
843 | iClob(new_LabelWidget(uiTextAction_ColorEscape "${sidebar.close}", "sidebar.toggle")), | ||
844 | extraPadding_WidgetFlag | frameless_WidgetFlag | | ||
845 | alignRight_WidgetFlag | moveToParentRightEdge_WidgetFlag); | ||
846 | as_Widget(sheetTitle)->flags2 |= slidingSheetDraggable_WidgetFlag2; /* phone */ | ||
847 | as_Widget(closeButton)->flags2 |= slidingSheetDraggable_WidgetFlag2; /* phone */ | ||
848 | setId_Widget(as_Widget(sheetTitle), "sidebar.title"); | ||
849 | setId_Widget(as_Widget(closeButton), "sidebar.close"); | ||
850 | setFont_LabelWidget(sheetTitle, uiLabelBig_FontId); | ||
851 | setFont_LabelWidget(closeButton, uiLabelBigBold_FontId); | ||
852 | iConnect(Root, get_Root(), visualOffsetsChanged, d, updateSlidingSheetHeight_SidebarWidget_); | ||
853 | } | ||
805 | iWidget *buttons = new_Widget(); | 854 | iWidget *buttons = new_Widget(); |
806 | setId_Widget(buttons, "buttons"); | 855 | setId_Widget(buttons, "buttons"); |
807 | setDrawBufferEnabled_Widget(buttons, iTrue); | 856 | setDrawBufferEnabled_Widget(buttons, iTrue); |
808 | for (int i = 0; i < max_SidebarMode; i++) { | 857 | for (int i = 0; i < max_SidebarMode; i++) { |
809 | if (deviceType_App() == phone_AppDeviceType && i == identities_SidebarMode) { | 858 | if (i == identities_SidebarMode && deviceType_App() != desktop_AppDeviceType) { |
859 | /* On mobile, identities are managed via Settings. */ | ||
810 | continue; | 860 | continue; |
811 | } | 861 | } |
812 | d->modeButtons[i] = addChildFlags_Widget( | 862 | d->modeButtons[i] = addChildFlags_Widget( |
@@ -815,51 +865,54 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
815 | tightModeLabels_[i], | 865 | tightModeLabels_[i], |
816 | format_CStr("%s.mode arg:%d", cstr_String(id_Widget(w)), i))), | 866 | format_CStr("%s.mode arg:%d", cstr_String(id_Widget(w)), i))), |
817 | frameless_WidgetFlag | noBackground_WidgetFlag); | 867 | frameless_WidgetFlag | noBackground_WidgetFlag); |
868 | as_Widget(d->modeButtons[i])->flags2 |= slidingSheetDraggable_WidgetFlag2; /* phone */ | ||
818 | } | 869 | } |
819 | setButtonFont_SidebarWidget(d, isPhone ? uiLabelBig_FontId : uiLabel_FontId); | 870 | setButtonFont_SidebarWidget(d, isPhone ? uiLabelBig_FontId : uiLabel_FontId); |
820 | addChildFlags_Widget(vdiv, | 871 | addChildFlags_Widget(vdiv, |
821 | iClob(buttons), | 872 | iClob(buttons), |
822 | arrangeHorizontal_WidgetFlag | | 873 | arrangeHorizontal_WidgetFlag | resizeWidthOfChildren_WidgetFlag | |
823 | resizeWidthOfChildren_WidgetFlag | | 874 | arrangeHeight_WidgetFlag | resizeToParentWidth_WidgetFlag); |
824 | arrangeHeight_WidgetFlag | resizeToParentWidth_WidgetFlag); // | | ||
825 | // drawBackgroundToHorizontalSafeArea_WidgetFlag); | ||
826 | setBackgroundColor_Widget(buttons, uiBackgroundSidebar_ColorId); | 875 | setBackgroundColor_Widget(buttons, uiBackgroundSidebar_ColorId); |
827 | } | ||
828 | else { | ||
829 | iLabelWidget *heading = new_LabelWidget(person_Icon " ${sidebar.identities}", NULL); | ||
830 | checkIcon_LabelWidget(heading); | ||
831 | setBackgroundColor_Widget(as_Widget(heading), uiBackgroundSidebar_ColorId); | ||
832 | setTextColor_LabelWidget(heading, uiTextSelected_ColorId); | ||
833 | setFont_LabelWidget(addChildFlags_Widget(vdiv, iClob(heading), borderTop_WidgetFlag | | ||
834 | alignLeft_WidgetFlag | frameless_WidgetFlag | | ||
835 | drawBackgroundToHorizontalSafeArea_WidgetFlag), | ||
836 | uiLabelLargeBold_FontId); | ||
837 | } | ||
838 | iWidget *content = new_Widget(); | 876 | iWidget *content = new_Widget(); |
839 | setFlags_Widget(content, resizeChildren_WidgetFlag, iTrue); | 877 | setFlags_Widget(content, resizeChildren_WidgetFlag, iTrue); |
840 | iWidget *listAndActions = makeVDiv_Widget(); | 878 | iWidget *listAndActions = makeVDiv_Widget(); |
841 | addChild_Widget(content, iClob(listAndActions)); | 879 | addChild_Widget(content, iClob(listAndActions)); |
880 | iWidget *listArea = new_Widget(); | ||
881 | setFlags_Widget(listArea, resizeChildren_WidgetFlag, iTrue); | ||
842 | d->list = new_ListWidget(); | 882 | d->list = new_ListWidget(); |
843 | setPadding_Widget(as_Widget(d->list), 0, gap_UI, 0, gap_UI); | 883 | setPadding_Widget(as_Widget(d->list), 0, gap_UI, 0, gap_UI); |
884 | addChild_Widget(listArea, iClob(d->list)); | ||
885 | if (!isPhone) { | ||
886 | d->certList = new_CertListWidget(); | ||
887 | setPadding_Widget(as_Widget(d->certList), 0, gap_UI, 0, gap_UI); | ||
888 | addChild_Widget(listArea, iClob(d->certList)); | ||
889 | } | ||
844 | addChildFlags_Widget(listAndActions, | 890 | addChildFlags_Widget(listAndActions, |
845 | iClob(d->list), | 891 | iClob(listArea), |
846 | expand_WidgetFlag); // | drawBackgroundToHorizontalSafeArea_WidgetFlag); | 892 | expand_WidgetFlag); // | drawBackgroundToHorizontalSafeArea_WidgetFlag); |
847 | setId_Widget(addChildPosFlags_Widget(listAndActions, | 893 | setId_Widget(addChildPosFlags_Widget(listAndActions, |
848 | iClob(d->actions = new_Widget()), | 894 | iClob(d->actions = new_Widget()), |
849 | isPhone ? front_WidgetAddPos : back_WidgetAddPos, | 895 | /*isPhone ? front_WidgetAddPos :*/ back_WidgetAddPos, |
850 | arrangeHorizontal_WidgetFlag | arrangeHeight_WidgetFlag | | 896 | arrangeHorizontal_WidgetFlag | arrangeHeight_WidgetFlag | |
851 | resizeWidthOfChildren_WidgetFlag), // | | 897 | resizeWidthOfChildren_WidgetFlag), // | |
852 | // drawBackgroundToHorizontalSafeArea_WidgetFlag), | 898 | // drawBackgroundToHorizontalSafeArea_WidgetFlag), |
853 | "actions"); | 899 | "actions"); |
900 | if (deviceType_App() != desktop_AppDeviceType) { | ||
901 | setFlags_Widget(findChild_Widget(w, "sidebar.title"), borderTop_WidgetFlag, iTrue); | ||
902 | setFlags_Widget(d->actions, drawBackgroundToBottom_WidgetFlag, iTrue); | ||
903 | setBackgroundColor_Widget(d->actions, uiBackground_ColorId); | ||
904 | } | ||
905 | else { | ||
854 | setBackgroundColor_Widget(d->actions, uiBackgroundSidebar_ColorId); | 906 | setBackgroundColor_Widget(d->actions, uiBackgroundSidebar_ColorId); |
907 | } | ||
855 | d->contextItem = NULL; | 908 | d->contextItem = NULL; |
856 | d->contextIndex = iInvalidPos; | 909 | d->contextIndex = iInvalidPos; |
857 | d->blank = new_Widget(); | 910 | d->blank = new_Widget(); |
858 | addChildFlags_Widget(content, iClob(d->blank), resizeChildren_WidgetFlag); | 911 | addChildFlags_Widget(content, iClob(d->blank), resizeChildren_WidgetFlag); |
859 | addChildFlags_Widget(vdiv, iClob(content), expand_WidgetFlag); | 912 | addChildFlags_Widget(vdiv, iClob(content), expand_WidgetFlag); |
860 | setMode_SidebarWidget(d, | 913 | setMode_SidebarWidget(d, |
861 | deviceType_App() == phone_AppDeviceType && d->side == right_SidebarSide ? | 914 | /*deviceType_App() == phone_AppDeviceType && d->side == right_SidebarSide ? |
862 | identities_SidebarMode : bookmarks_SidebarMode); | 915 | identities_SidebarMode :*/ bookmarks_SidebarMode); |
863 | d->resizer = | 916 | d->resizer = |
864 | addChildFlags_Widget(w, | 917 | addChildFlags_Widget(w, |
865 | iClob(new_Widget()), | 918 | iClob(new_Widget()), |
@@ -867,7 +920,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
867 | resizeToParentHeight_WidgetFlag | | 920 | resizeToParentHeight_WidgetFlag | |
868 | (side == left_SidebarSide ? moveToParentRightEdge_WidgetFlag | 921 | (side == left_SidebarSide ? moveToParentRightEdge_WidgetFlag |
869 | : moveToParentLeftEdge_WidgetFlag)); | 922 | : moveToParentLeftEdge_WidgetFlag)); |
870 | if (deviceType_App() == phone_AppDeviceType) { | 923 | if (deviceType_App() != desktop_AppDeviceType) { |
871 | setFlags_Widget(d->resizer, hidden_WidgetFlag | disabled_WidgetFlag, iTrue); | 924 | setFlags_Widget(d->resizer, hidden_WidgetFlag | disabled_WidgetFlag, iTrue); |
872 | } | 925 | } |
873 | setId_Widget(d->resizer, side == left_SidebarSide ? "sidebar.grab" : "sidebar2.grab"); | 926 | setId_Widget(d->resizer, side == left_SidebarSide ? "sidebar.grab" : "sidebar2.grab"); |
@@ -902,16 +955,16 @@ iBool setButtonFont_SidebarWidget(iSidebarWidget *d, int font) { | |||
902 | 955 | ||
903 | static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) { | 956 | static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) { |
904 | if (d->mode == identities_SidebarMode) { | 957 | if (d->mode == identities_SidebarMode) { |
905 | const iSidebarItem *hoverItem = constHoverItem_ListWidget(d->list); | 958 | return constHoverIdentity_CertListWidget(d->certList); |
906 | if (hoverItem) { | ||
907 | return identity_GmCerts(certs_App(), hoverItem->id); | ||
908 | } | 959 | } |
909 | } | ||
910 | return NULL; | 960 | return NULL; |
911 | } | 961 | } |
912 | 962 | ||
913 | static iGmIdentity *hoverIdentity_SidebarWidget_(const iSidebarWidget *d) { | 963 | static iGmIdentity *hoverIdentity_SidebarWidget_(const iSidebarWidget *d) { |
914 | return iConstCast(iGmIdentity *, constHoverIdentity_SidebarWidget_(d)); | 964 | if (d->mode == identities_SidebarMode) { |
965 | return hoverIdentity_CertListWidget(d->certList); | ||
966 | } | ||
967 | return NULL; | ||
915 | } | 968 | } |
916 | 969 | ||
917 | static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, size_t itemIndex) { | 970 | static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, size_t itemIndex) { |
@@ -923,7 +976,6 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si | |||
923 | const iGmHeading *head = constAt_Array(headings_GmDocument(doc), item->id); | 976 | const iGmHeading *head = constAt_Array(headings_GmDocument(doc), item->id); |
924 | postCommandf_App("document.goto loc:%p", head->text.start); | 977 | postCommandf_App("document.goto loc:%p", head->text.start); |
925 | dismissPortraitPhoneSidebars_Root(as_Widget(d)->root); | 978 | dismissPortraitPhoneSidebars_Root(as_Widget(d)->root); |
926 | setOpenedFromSidebar_DocumentWidget(document_App(), iTrue); | ||
927 | } | 979 | } |
928 | break; | 980 | break; |
929 | } | 981 | } |
@@ -945,6 +997,12 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si | |||
945 | updateItems_SidebarWidget_(d); | 997 | updateItems_SidebarWidget_(d); |
946 | break; | 998 | break; |
947 | } | 999 | } |
1000 | if (d->isEditing) { | ||
1001 | d->contextItem = item; | ||
1002 | d->contextIndex = itemIndex; | ||
1003 | postCommand_Widget(d, "bookmark.edit"); | ||
1004 | break; | ||
1005 | } | ||
948 | /* fall through */ | 1006 | /* fall through */ |
949 | case history_SidebarMode: { | 1007 | case history_SidebarMode: { |
950 | if (!isEmpty_String(&item->url)) { | 1008 | if (!isEmpty_String(&item->url)) { |
@@ -954,23 +1012,6 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si | |||
954 | } | 1012 | } |
955 | break; | 1013 | break; |
956 | } | 1014 | } |
957 | case identities_SidebarMode: { | ||
958 | d->contextItem = item; | ||
959 | if (d->contextIndex != iInvalidPos) { | ||
960 | invalidateItem_ListWidget(d->list, d->contextIndex); | ||
961 | } | ||
962 | d->contextIndex = itemIndex; | ||
963 | if (itemIndex < numItems_ListWidget(d->list)) { | ||
964 | updateContextMenu_SidebarWidget_(d); | ||
965 | arrange_Widget(d->menu); | ||
966 | openMenu_Widget(d->menu, | ||
967 | d->side == left_SidebarSide | ||
968 | ? topRight_Rect(itemRect_ListWidget(d->list, itemIndex)) | ||
969 | : addX_I2(topLeft_Rect(itemRect_ListWidget(d->list, itemIndex)), | ||
970 | -width_Widget(d->menu))); | ||
971 | } | ||
972 | break; | ||
973 | } | ||
974 | default: | 1015 | default: |
975 | break; | 1016 | break; |
976 | } | 1017 | } |
@@ -990,7 +1031,7 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) { | |||
990 | // updateMetrics_SidebarWidget_(d); | 1031 | // updateMetrics_SidebarWidget_(d); |
991 | updateItemHeight_SidebarWidget_(d); | 1032 | updateItemHeight_SidebarWidget_(d); |
992 | } | 1033 | } |
993 | setButtonFont_SidebarWidget(d, isPortrait_App() ? uiLabelBig_FontId : uiLabel_FontId); | 1034 | setButtonFont_SidebarWidget(d, isPortrait_App() ? uiLabelMedium_FontId : uiLabel_FontId); |
994 | } | 1035 | } |
995 | const iBool isTight = | 1036 | const iBool isTight = |
996 | (width_Rect(bounds_Widget(as_Widget(d->modeButtons[0]))) < d->maxButtonLabelWidth); | 1037 | (width_Rect(bounds_Widget(as_Widget(d->modeButtons[0]))) < d->maxButtonLabelWidth); |
@@ -1018,6 +1059,7 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) { | |||
1018 | } | 1059 | } |
1019 | 1060 | ||
1020 | void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) { | 1061 | void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) { |
1062 | if (!d) return; | ||
1021 | iWidget *w = as_Widget(d); | 1063 | iWidget *w = as_Widget(d); |
1022 | const iBool isFixedWidth = deviceType_App() == phone_AppDeviceType; | 1064 | const iBool isFixedWidth = deviceType_App() == phone_AppDeviceType; |
1023 | int width = widthAsGaps * gap_UI; /* in pixels */ | 1065 | int width = widthAsGaps * gap_UI; /* in pixels */ |
@@ -1056,19 +1098,16 @@ iBool handleBookmarkEditorCommands_SidebarWidget_(iWidget *editor, const char *c | |||
1056 | set_String(&bm->url, url); | 1098 | set_String(&bm->url, url); |
1057 | set_String(&bm->tags, tags); | 1099 | set_String(&bm->tags, tags); |
1058 | if (isEmpty_String(icon)) { | 1100 | if (isEmpty_String(icon)) { |
1059 | removeTag_Bookmark(bm, userIcon_BookmarkTag); | 1101 | bm->flags &= ~userIcon_BookmarkFlag; |
1060 | bm->icon = 0; | 1102 | bm->icon = 0; |
1061 | } | 1103 | } |
1062 | else { | 1104 | else { |
1063 | addTagIfMissing_Bookmark(bm, userIcon_BookmarkTag); | 1105 | bm->flags |= userIcon_BookmarkFlag; |
1064 | bm->icon = first_String(icon); | 1106 | bm->icon = first_String(icon); |
1065 | } | 1107 | } |
1066 | addOrRemoveTag_Bookmark(bm, homepage_BookmarkTag, | 1108 | iChangeFlags(bm->flags, homepage_BookmarkFlag, isSelected_Widget(findChild_Widget(editor, "bmed.tag.home"))); |
1067 | isSelected_Widget(findChild_Widget(editor, "bmed.tag.home"))); | 1109 | iChangeFlags(bm->flags, remoteSource_BookmarkFlag, isSelected_Widget(findChild_Widget(editor, "bmed.tag.remote"))); |
1068 | addOrRemoveTag_Bookmark(bm, remoteSource_BookmarkTag, | 1110 | iChangeFlags(bm->flags, linkSplit_BookmarkFlag, isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))); |
1069 | isSelected_Widget(findChild_Widget(editor, "bmed.tag.remote"))); | ||
1070 | addOrRemoveTag_Bookmark(bm, linkSplit_BookmarkTag, | ||
1071 | isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))); | ||
1072 | } | 1111 | } |
1073 | const iBookmark *folder = userData_Object(findChild_Widget(editor, "bmed.folder")); | 1112 | const iBookmark *folder = userData_Object(findChild_Widget(editor, "bmed.folder")); |
1074 | if (!folder || !hasParent_Bookmark(folder, id_Bookmark(bm))) { | 1113 | if (!folder || !hasParent_Bookmark(folder, id_Bookmark(bm))) { |
@@ -1083,6 +1122,36 @@ iBool handleBookmarkEditorCommands_SidebarWidget_(iWidget *editor, const char *c | |||
1083 | return iFalse; | 1122 | return iFalse; |
1084 | } | 1123 | } |
1085 | 1124 | ||
1125 | enum iSlidingSheetPos { | ||
1126 | top_SlidingSheetPos, | ||
1127 | middle_SlidingSheetPos, | ||
1128 | bottom_SlidingSheetPos, | ||
1129 | }; | ||
1130 | |||
1131 | static void setSlidingSheetPos_SidebarWidget_(iSidebarWidget *d, enum iSlidingSheetPos slide) { | ||
1132 | iWidget *w = as_Widget(d); | ||
1133 | const int pos = w->rect.pos.y; | ||
1134 | const iRect safeRect = safeRect_Root(w->root); | ||
1135 | if (slide == top_SlidingSheetPos) { | ||
1136 | w->rect.pos.y = top_Rect(safeRect); | ||
1137 | w->rect.size.y = height_Rect(safeRect); | ||
1138 | setVisualOffset_Widget(w, pos - w->rect.pos.y, 0, 0); | ||
1139 | setVisualOffset_Widget(w, 0, 200, easeOut_AnimFlag | softer_AnimFlag); | ||
1140 | setScrollMode_ListWidget(d->list, disabledAtTopUpwards_ScrollMode); | ||
1141 | } | ||
1142 | else if (slide == bottom_SlidingSheetPos) { | ||
1143 | postCommand_Widget(w, "sidebar.toggle"); | ||
1144 | } | ||
1145 | else { | ||
1146 | w->rect.size.y = d->midHeight; | ||
1147 | w->rect.pos.y = height_Rect(safeRect) - w->rect.size.y; | ||
1148 | setVisualOffset_Widget(w, pos - w->rect.pos.y, 0, 0); | ||
1149 | setVisualOffset_Widget(w, 0, 200, easeOut_AnimFlag | softer_AnimFlag); | ||
1150 | setScrollMode_ListWidget(d->list, disabledAtTopBothDirections_ScrollMode); | ||
1151 | } | ||
1152 | // animateSlidingSheetHeight_SidebarWidget_(d); | ||
1153 | } | ||
1154 | |||
1086 | static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *cmd) { | 1155 | static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *cmd) { |
1087 | iWidget *w = as_Widget(d); | 1156 | iWidget *w = as_Widget(d); |
1088 | if (equal_Command(cmd, "width")) { | 1157 | if (equal_Command(cmd, "width")) { |
@@ -1112,13 +1181,18 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
1112 | argLabel_Command(cmd, "noanim") == 0 && | 1181 | argLabel_Command(cmd, "noanim") == 0 && |
1113 | (d->side == left_SidebarSide || deviceType_App() != phone_AppDeviceType); | 1182 | (d->side == left_SidebarSide || deviceType_App() != phone_AppDeviceType); |
1114 | int visX = 0; | 1183 | int visX = 0; |
1184 | int visY = 0; | ||
1115 | if (isVisible_Widget(w)) { | 1185 | if (isVisible_Widget(w)) { |
1116 | visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect); | 1186 | visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect); |
1187 | visY = top_Rect(bounds_Widget(w)) - top_Rect(w->root->widget->rect); | ||
1117 | } | 1188 | } |
1118 | setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w)); | 1189 | const iBool isHiding = isVisible_Widget(w); |
1190 | setFlags_Widget(w, hidden_WidgetFlag, isHiding); | ||
1119 | /* Safe area inset for mobile. */ | 1191 | /* Safe area inset for mobile. */ |
1120 | const int safePad = (d->side == left_SidebarSide ? left_Rect(safeRect_Root(w->root)) : 0); | 1192 | const int safePad = (d->side == left_SidebarSide ? left_Rect(safeRect_Root(w->root)) : 0); |
1121 | if (isVisible_Widget(w)) { | 1193 | const int animFlags = easeOut_AnimFlag | softer_AnimFlag; |
1194 | if (!isPortraitPhone_App()) { | ||
1195 | if (!isHiding) { | ||
1122 | setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse); | 1196 | setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse); |
1123 | w->rect.size.x = d->widthAsGaps * gap_UI; | 1197 | w->rect.size.x = d->widthAsGaps * gap_UI; |
1124 | invalidate_ListWidget(d->list); | 1198 | invalidate_ListWidget(d->list); |
@@ -1126,7 +1200,7 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
1126 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); | 1200 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); |
1127 | setVisualOffset_Widget( | 1201 | setVisualOffset_Widget( |
1128 | w, (d->side == left_SidebarSide ? -1 : 1) * (w->rect.size.x + safePad), 0, 0); | 1202 | w, (d->side == left_SidebarSide ? -1 : 1) * (w->rect.size.x + safePad), 0, 0); |
1129 | setVisualOffset_Widget(w, 0, 300, easeOut_AnimFlag | softer_AnimFlag); | 1203 | setVisualOffset_Widget(w, 0, 300, animFlags); |
1130 | } | 1204 | } |
1131 | } | 1205 | } |
1132 | else if (isAnimated) { | 1206 | else if (isAnimated) { |
@@ -1134,19 +1208,42 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
1134 | if (d->side == right_SidebarSide) { | 1208 | if (d->side == right_SidebarSide) { |
1135 | setVisualOffset_Widget(w, visX, 0, 0); | 1209 | setVisualOffset_Widget(w, visX, 0, 0); |
1136 | setVisualOffset_Widget( | 1210 | setVisualOffset_Widget( |
1137 | w, visX + w->rect.size.x + safePad, 300, easeOut_AnimFlag | softer_AnimFlag); | 1211 | w, visX + w->rect.size.x + safePad, 300, animFlags); |
1138 | } | 1212 | } |
1139 | else { | 1213 | else { |
1140 | setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); | 1214 | setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); |
1141 | setVisualOffset_Widget( | 1215 | setVisualOffset_Widget( |
1142 | w, -w->rect.size.x - safePad, 300, easeOut_AnimFlag | softer_AnimFlag); | 1216 | w, -w->rect.size.x - safePad, 300, animFlags); |
1217 | } | ||
1143 | } | 1218 | } |
1219 | setScrollMode_ListWidget(d->list, normal_ScrollMode); | ||
1220 | } | ||
1221 | else { | ||
1222 | /* Portrait phone sidebar works differently: it slides up from the bottom. */ | ||
1223 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iFalse); | ||
1224 | if (!isHiding) { | ||
1225 | invalidate_ListWidget(d->list); | ||
1226 | w->rect.pos.y = height_Rect(safeRect_Root(w->root)) - d->midHeight; | ||
1227 | setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 0, 0); | ||
1228 | setVisualOffset_Widget(w, 0, 300, animFlags); | ||
1229 | //animateSlidingSheetHeight_SidebarWidget_(d); | ||
1230 | setScrollMode_ListWidget(d->list, disabledAtTopBothDirections_ScrollMode); | ||
1231 | } | ||
1232 | else { | ||
1233 | setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 300, animFlags); | ||
1234 | if (d->isEditing) { | ||
1235 | setMobileEditMode_SidebarWidget_(d, iFalse); | ||
1236 | } | ||
1237 | } | ||
1238 | showToolbar_Root(w->root, isHiding); | ||
1144 | } | 1239 | } |
1145 | updateToolbarColors_Root(w->root); | 1240 | updateToolbarColors_Root(w->root); |
1146 | arrange_Widget(w->parent); | 1241 | arrange_Widget(w->parent); |
1147 | /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ | 1242 | /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ |
1148 | arrange_Widget(w); | 1243 | arrange_Widget(w); |
1244 | if (!isPortraitPhone_App()) { | ||
1149 | updateSize_DocumentWidget(document_App()); | 1245 | updateSize_DocumentWidget(document_App()); |
1246 | } | ||
1150 | if (isVisible_Widget(w)) { | 1247 | if (isVisible_Widget(w)) { |
1151 | updateItems_SidebarWidget_(d); | 1248 | updateItems_SidebarWidget_(d); |
1152 | scrollOffset_ListWidget(d->list, 0); | 1249 | scrollOffset_ListWidget(d->list, 0); |
@@ -1154,6 +1251,10 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
1154 | refresh_Widget(w->parent); | 1251 | refresh_Widget(w->parent); |
1155 | return iTrue; | 1252 | return iTrue; |
1156 | } | 1253 | } |
1254 | else if (equal_Command(cmd, "bookmarks.edit")) { | ||
1255 | setMobileEditMode_SidebarWidget_(d, !d->isEditing); | ||
1256 | invalidate_ListWidget(d->list); | ||
1257 | } | ||
1157 | return iFalse; | 1258 | return iFalse; |
1158 | } | 1259 | } |
1159 | 1260 | ||
@@ -1166,7 +1267,7 @@ static void bookmarkMoved_SidebarWidget_(iSidebarWidget *d, size_t index, size_t | |||
1166 | : dstIndex); | 1267 | : dstIndex); |
1167 | if (isLast && isBefore) isBefore = iFalse; | 1268 | if (isLast && isBefore) isBefore = iFalse; |
1168 | const iBookmark *dst = get_Bookmarks(bookmarks_App(), dstItem->id); | 1269 | const iBookmark *dst = get_Bookmarks(bookmarks_App(), dstItem->id); |
1169 | if (hasParent_Bookmark(dst, movingItem->id) || hasTag_Bookmark(dst, remote_BookmarkTag)) { | 1270 | if (hasParent_Bookmark(dst, movingItem->id) || dst->flags & remote_BookmarkFlag) { |
1170 | /* Can't move a folder inside itself, and remote bookmarks cannot be reordered. */ | 1271 | /* Can't move a folder inside itself, and remote bookmarks cannot be reordered. */ |
1171 | return; | 1272 | return; |
1172 | } | 1273 | } |
@@ -1190,20 +1291,41 @@ static void bookmarkMovedOntoFolder_SidebarWidget_(iSidebarWidget *d, size_t ind | |||
1190 | static size_t numBookmarks_(const iPtrArray *bmList) { | 1291 | static size_t numBookmarks_(const iPtrArray *bmList) { |
1191 | size_t num = 0; | 1292 | size_t num = 0; |
1192 | iConstForEach(PtrArray, i, bmList) { | 1293 | iConstForEach(PtrArray, i, bmList) { |
1193 | if (!isFolder_Bookmark(i.ptr) && !hasTag_Bookmark(i.ptr, remote_BookmarkTag)) { | 1294 | const iBookmark *bm = i.ptr; |
1295 | if (!isFolder_Bookmark(bm) && ~bm->flags & remote_BookmarkFlag) { | ||
1194 | num++; | 1296 | num++; |
1195 | } | 1297 | } |
1196 | } | 1298 | } |
1197 | return num; | 1299 | return num; |
1198 | } | 1300 | } |
1199 | 1301 | ||
1302 | static iRangei SlidingSheetMiddleRegion_SidebarWidget_(const iSidebarWidget *d) { | ||
1303 | const iWidget *w = constAs_Widget(d); | ||
1304 | const iRect safeRect = safeRect_Root(w->root); | ||
1305 | const int midY = bottom_Rect(safeRect) - d->midHeight; | ||
1306 | const int topHalf = (top_Rect(safeRect) + midY) / 2; | ||
1307 | const int bottomHalf = (bottom_Rect(safeRect) + midY * 2) / 3; | ||
1308 | return (iRangei){ topHalf, bottomHalf }; | ||
1309 | } | ||
1310 | |||
1311 | static void gotoNearestSlidingSheetPos_SidebarWidget_(iSidebarWidget *d) { | ||
1312 | const iRangei midRegion = SlidingSheetMiddleRegion_SidebarWidget_(d); | ||
1313 | const int pos = top_Rect(d->widget.rect); | ||
1314 | setSlidingSheetPos_SidebarWidget_(d, pos < midRegion.start | ||
1315 | ? top_SlidingSheetPos | ||
1316 | : pos > midRegion.end ? bottom_SlidingSheetPos | ||
1317 | : middle_SlidingSheetPos); | ||
1318 | } | ||
1319 | |||
1200 | static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { | 1320 | static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { |
1201 | iWidget *w = as_Widget(d); | 1321 | iWidget *w = as_Widget(d); |
1202 | /* Handle commands. */ | 1322 | /* Handle commands. */ |
1203 | if (isResize_UserEvent(ev)) { | 1323 | if (isResize_UserEvent(ev)) { |
1204 | checkModeButtonLayout_SidebarWidget_(d); | 1324 | checkModeButtonLayout_SidebarWidget_(d); |
1205 | if (deviceType_App() == phone_AppDeviceType && d->side == left_SidebarSide) { | 1325 | if (deviceType_App() == phone_AppDeviceType) { |
1206 | setFlags_Widget(w, rightEdgeDraggable_WidgetFlag, isPortrait_App()); | 1326 | setPadding_Widget(d->actions, 0, 0, 0, 0); |
1327 | setFlags_Widget(findChild_Widget(w, "sidebar.title"), hidden_WidgetFlag, isLandscape_App()); | ||
1328 | setFlags_Widget(findChild_Widget(w, "sidebar.close"), hidden_WidgetFlag, isLandscape_App()); | ||
1207 | /* In landscape, visibility of the toolbar is controlled separately. */ | 1329 | /* In landscape, visibility of the toolbar is controlled separately. */ |
1208 | if (isVisible_Widget(w)) { | 1330 | if (isVisible_Widget(w)) { |
1209 | postCommand_Widget(w, "sidebar.toggle"); | 1331 | postCommand_Widget(w, "sidebar.toggle"); |
@@ -1217,8 +1339,16 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1217 | setFlags_Widget(as_Widget(d->list), | 1339 | setFlags_Widget(as_Widget(d->list), |
1218 | drawBackgroundToHorizontalSafeArea_WidgetFlag, | 1340 | drawBackgroundToHorizontalSafeArea_WidgetFlag, |
1219 | isLandscape_App()); | 1341 | isLandscape_App()); |
1220 | return iFalse; | 1342 | setFlags_Widget(w, |
1343 | drawBackgroundToBottom_WidgetFlag, | ||
1344 | isPortrait_App()); | ||
1345 | setBackgroundColor_Widget(w, isPortrait_App() ? uiBackgroundSidebar_ColorId : none_ColorId); | ||
1346 | } | ||
1347 | if (!isPortraitPhone_App()) { | ||
1348 | /* In sliding sheet mode, sidebar is resized to fit in the safe area. */ | ||
1349 | setPadding_Widget(d->actions, 0, 0, 0, bottomSafeInset_Mobile()); | ||
1221 | } | 1350 | } |
1351 | return iFalse; | ||
1222 | } | 1352 | } |
1223 | else if (isMetricsChange_UserEvent(ev)) { | 1353 | else if (isMetricsChange_UserEvent(ev)) { |
1224 | if (isVisible_Widget(w)) { | 1354 | if (isVisible_Widget(w)) { |
@@ -1230,7 +1360,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1230 | } | 1360 | } |
1231 | else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { | 1361 | else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { |
1232 | const char *cmd = command_UserEvent(ev); | 1362 | const char *cmd = command_UserEvent(ev); |
1233 | if (equal_Command(cmd, "tabs.changed") || equal_Command(cmd, "document.changed")) { | 1363 | if (startsWith_CStr(cmd, "tabs.changed id:doc") || equal_Command(cmd, "document.changed")) { |
1234 | updateItems_SidebarWidget_(d); | 1364 | updateItems_SidebarWidget_(d); |
1235 | scrollOffset_ListWidget(d->list, 0); | 1365 | scrollOffset_ListWidget(d->list, 0); |
1236 | } | 1366 | } |
@@ -1257,13 +1387,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1257 | } | 1387 | } |
1258 | } | 1388 | } |
1259 | } | 1389 | } |
1260 | else if (equal_Command(cmd, "idents.changed") && d->mode == identities_SidebarMode) { | ||
1261 | updateItems_SidebarWidget_(d); | ||
1262 | } | ||
1263 | else if (deviceType_App() == tablet_AppDeviceType && equal_Command(cmd, "toolbar.showident")) { | ||
1264 | postCommandf_App("sidebar.mode arg:%d toggle:1", identities_SidebarMode); | ||
1265 | return iTrue; | ||
1266 | } | ||
1267 | else if (isPortraitPhone_App() && isVisible_Widget(w) && d->side == left_SidebarSide && | 1390 | else if (isPortraitPhone_App() && isVisible_Widget(w) && d->side == left_SidebarSide && |
1268 | equal_Command(cmd, "swipe.forward")) { | 1391 | equal_Command(cmd, "swipe.forward")) { |
1269 | postCommand_App("sidebar.toggle"); | 1392 | postCommand_App("sidebar.toggle"); |
@@ -1328,9 +1451,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1328 | } | 1451 | } |
1329 | return iTrue; | 1452 | return iTrue; |
1330 | } | 1453 | } |
1331 | // else if (isCommand_Widget(w, ev, "menu.closed")) { | ||
1332 | // invalidateItem_ListWidget(d->list, d->contextIndex); | ||
1333 | // } | ||
1334 | else if (isCommand_Widget(w, ev, "bookmark.open")) { | 1454 | else if (isCommand_Widget(w, ev, "bookmark.open")) { |
1335 | const iSidebarItem *item = d->contextItem; | 1455 | const iSidebarItem *item = d->contextItem; |
1336 | if (d->mode == bookmarks_SidebarMode && item) { | 1456 | if (d->mode == bookmarks_SidebarMode && item) { |
@@ -1363,13 +1483,13 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1363 | if (!isFolder_Bookmark(bm)) { | 1483 | if (!isFolder_Bookmark(bm)) { |
1364 | setText_InputWidget(urlInput, &bm->url); | 1484 | setText_InputWidget(urlInput, &bm->url); |
1365 | setText_InputWidget(tagsInput, &bm->tags); | 1485 | setText_InputWidget(tagsInput, &bm->tags); |
1366 | if (hasTag_Bookmark(bm, userIcon_BookmarkTag)) { | 1486 | if (bm->flags & userIcon_BookmarkFlag) { |
1367 | setText_InputWidget(iconInput, | 1487 | setText_InputWidget(iconInput, |
1368 | collect_String(newUnicodeN_String(&bm->icon, 1))); | 1488 | collect_String(newUnicodeN_String(&bm->icon, 1))); |
1369 | } | 1489 | } |
1370 | setToggle_Widget(homeTag, hasTag_Bookmark(bm, homepage_BookmarkTag)); | 1490 | setToggle_Widget(homeTag, bm->flags & homepage_BookmarkFlag); |
1371 | setToggle_Widget(remoteSourceTag, hasTag_Bookmark(bm, remoteSource_BookmarkTag)); | 1491 | setToggle_Widget(remoteSourceTag, bm->flags & remoteSource_BookmarkFlag); |
1372 | setToggle_Widget(linkSplitTag, hasTag_Bookmark(bm, linkSplit_BookmarkTag)); | 1492 | setToggle_Widget(linkSplitTag, bm->flags & linkSplit_BookmarkFlag); |
1373 | } | 1493 | } |
1374 | else { | 1494 | else { |
1375 | setFlags_Widget(findChild_Widget(dlg, "bmed.special"), | 1495 | setFlags_Widget(findChild_Widget(dlg, "bmed.special"), |
@@ -1390,7 +1510,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1390 | const iSidebarItem *item = d->contextItem; | 1510 | const iSidebarItem *item = d->contextItem; |
1391 | if (d->mode == bookmarks_SidebarMode && item) { | 1511 | if (d->mode == bookmarks_SidebarMode && item) { |
1392 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); | 1512 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); |
1393 | const iBool isRemote = hasTag_Bookmark(bm, remote_BookmarkTag); | 1513 | const iBool isRemote = (bm->flags & remote_BookmarkFlag) != 0; |
1394 | iChar icon = isRemote ? 0x1f588 : bm->icon; | 1514 | iChar icon = isRemote ? 0x1f588 : bm->icon; |
1395 | iWidget *dlg = makeBookmarkCreation_Widget(&bm->url, &bm->title, icon); | 1515 | iWidget *dlg = makeBookmarkCreation_Widget(&bm->url, &bm->title, icon); |
1396 | setId_Widget(dlg, format_CStr("bmed.%s", cstr_String(id_Widget(w)))); | 1516 | setId_Widget(dlg, format_CStr("bmed.%s", cstr_String(id_Widget(w)))); |
@@ -1404,17 +1524,16 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1404 | else if (isCommand_Widget(w, ev, "bookmark.tag")) { | 1524 | else if (isCommand_Widget(w, ev, "bookmark.tag")) { |
1405 | const iSidebarItem *item = d->contextItem; | 1525 | const iSidebarItem *item = d->contextItem; |
1406 | if (d->mode == bookmarks_SidebarMode && item) { | 1526 | if (d->mode == bookmarks_SidebarMode && item) { |
1407 | const char *tag = cstr_String(string_Command(cmd, "tag")); | 1527 | const iRangecc tag = range_Command(cmd, "tag"); |
1528 | const int flag = | ||
1529 | (equal_Rangecc(tag, "homepage") ? homepage_BookmarkFlag : 0) | | ||
1530 | (equal_Rangecc(tag, "subscribed") ? subscribed_BookmarkFlag : 0) | | ||
1531 | (equal_Rangecc(tag, "remotesource") ? remoteSource_BookmarkFlag : 0); | ||
1408 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); | 1532 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); |
1409 | if (hasTag_Bookmark(bm, tag)) { | 1533 | if (flag == subscribed_BookmarkFlag && (bm->flags & flag)) { |
1410 | removeTag_Bookmark(bm, tag); | 1534 | removeEntries_Feeds(item->id); /* get rid of unsubscribed entries */ |
1411 | if (!iCmpStr(tag, subscribed_BookmarkTag)) { | ||
1412 | removeEntries_Feeds(item->id); | ||
1413 | } | ||
1414 | } | ||
1415 | else { | ||
1416 | addTag_Bookmark(bm, tag); | ||
1417 | } | 1535 | } |
1536 | bm->flags ^= flag; | ||
1418 | postCommand_App("bookmarks.changed"); | 1537 | postCommand_App("bookmarks.changed"); |
1419 | } | 1538 | } |
1420 | return iTrue; | 1539 | return iTrue; |
@@ -1491,6 +1610,15 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1491 | return iTrue; | 1610 | return iTrue; |
1492 | } | 1611 | } |
1493 | else if (equal_Command(cmd, "feeds.markallread") && d->mode == feeds_SidebarMode) { | 1612 | else if (equal_Command(cmd, "feeds.markallread") && d->mode == feeds_SidebarMode) { |
1613 | if (argLabel_Command(cmd, "confirm")) { | ||
1614 | /* This is used on mobile. */ | ||
1615 | iWidget *menu = makeMenu_Widget(w->root->widget, (iMenuItem[]){ | ||
1616 | check_Icon " " uiTextCaution_ColorEscape "${feeds.markallread}", 0, 0, | ||
1617 | "feeds.markallread" | ||
1618 | }, 1); | ||
1619 | openMenu_Widget(menu, topLeft_Rect(bounds_Widget(d->actions))); | ||
1620 | return iTrue; | ||
1621 | } | ||
1494 | iConstForEach(PtrArray, i, listEntries_Feeds()) { | 1622 | iConstForEach(PtrArray, i, listEntries_Feeds()) { |
1495 | const iFeedEntry *entry = i.ptr; | 1623 | const iFeedEntry *entry = i.ptr; |
1496 | const iString *url = url_FeedEntry(entry); | 1624 | const iString *url = url_FeedEntry(entry); |
@@ -1539,7 +1667,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1539 | } | 1667 | } |
1540 | if (isCommand_Widget(w, ev, "feed.entry.unsubscribe")) { | 1668 | if (isCommand_Widget(w, ev, "feed.entry.unsubscribe")) { |
1541 | if (arg_Command(cmd)) { | 1669 | if (arg_Command(cmd)) { |
1542 | removeTag_Bookmark(feedBookmark, subscribed_BookmarkTag); | 1670 | feedBookmark->flags &= ~subscribed_BookmarkFlag; |
1543 | removeEntries_Feeds(id_Bookmark(feedBookmark)); | 1671 | removeEntries_Feeds(id_Bookmark(feedBookmark)); |
1544 | updateItems_SidebarWidget_(d); | 1672 | updateItems_SidebarWidget_(d); |
1545 | } | 1673 | } |
@@ -1555,108 +1683,11 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1555 | 0, | 1683 | 0, |
1556 | format_CStr("!feed.entry.unsubscribe arg:1 ptr:%p", d) } }, | 1684 | format_CStr("!feed.entry.unsubscribe arg:1 ptr:%p", d) } }, |
1557 | 2); | 1685 | 2); |
1558 | } | ||
1559 | return iTrue; | ||
1560 | } | ||
1561 | } | ||
1562 | } | ||
1563 | } | ||
1564 | else if (isCommand_Widget(w, ev, "ident.use")) { | ||
1565 | iGmIdentity * ident = menuIdentity_SidebarWidget_(d); | ||
1566 | const iString *tabUrl = url_DocumentWidget(document_App()); | ||
1567 | if (ident) { | ||
1568 | if (argLabel_Command(cmd, "clear")) { | ||
1569 | clearUse_GmIdentity(ident); | ||
1570 | } | ||
1571 | else if (arg_Command(cmd)) { | ||
1572 | signIn_GmCerts(certs_App(), ident, tabUrl); | ||
1573 | postCommand_App("navigate.reload"); | ||
1574 | } | ||
1575 | else { | ||
1576 | signOut_GmCerts(certs_App(), tabUrl); | ||
1577 | postCommand_App("navigate.reload"); | ||
1578 | } | ||
1579 | saveIdentities_GmCerts(certs_App()); | ||
1580 | updateItems_SidebarWidget_(d); | ||
1581 | } | ||
1582 | return iTrue; | ||
1583 | } | ||
1584 | else if (isCommand_Widget(w, ev, "ident.edit")) { | ||
1585 | const iGmIdentity *ident = menuIdentity_SidebarWidget_(d); | ||
1586 | if (ident) { | ||
1587 | makeValueInput_Widget(get_Root()->widget, | ||
1588 | &ident->notes, | ||
1589 | uiHeading_ColorEscape "${heading.ident.notes}", | ||
1590 | format_CStr(cstr_Lang("dlg.ident.notes"), cstr_String(name_GmIdentity(ident))), | ||
1591 | uiTextAction_ColorEscape "${dlg.default}", | ||
1592 | format_CStr("!ident.setnotes ident:%p ptr:%p", ident, d)); | ||
1593 | } | ||
1594 | return iTrue; | ||
1595 | } | ||
1596 | else if (isCommand_Widget(w, ev, "ident.fingerprint")) { | ||
1597 | const iGmIdentity *ident = menuIdentity_SidebarWidget_(d); | ||
1598 | if (ident) { | ||
1599 | const iString *fps = collect_String( | ||
1600 | hexEncode_Block(collect_Block(fingerprint_TlsCertificate(ident->cert)))); | ||
1601 | SDL_SetClipboardText(cstr_String(fps)); | ||
1602 | } | 1686 | } |
1603 | return iTrue; | 1687 | return iTrue; |
1604 | } | 1688 | } |
1605 | else if (isCommand_Widget(w, ev, "ident.export")) { | ||
1606 | const iGmIdentity *ident = menuIdentity_SidebarWidget_(d); | ||
1607 | if (ident) { | ||
1608 | iString *pem = collect_String(pem_TlsCertificate(ident->cert)); | ||
1609 | append_String(pem, collect_String(privateKeyPem_TlsCertificate(ident->cert))); | ||
1610 | iDocumentWidget *expTab = newTab_App(NULL, iTrue); | ||
1611 | setUrlAndSource_DocumentWidget( | ||
1612 | expTab, | ||
1613 | collectNewFormat_String("file:%s.pem", cstr_String(name_GmIdentity(ident))), | ||
1614 | collectNewCStr_String("text/plain"), | ||
1615 | utf8_String(pem)); | ||
1616 | } | 1689 | } |
1617 | return iTrue; | ||
1618 | } | ||
1619 | else if (isCommand_Widget(w, ev, "ident.setnotes")) { | ||
1620 | iGmIdentity *ident = pointerLabel_Command(cmd, "ident"); | ||
1621 | if (ident) { | ||
1622 | setCStr_String(&ident->notes, suffixPtr_Command(cmd, "value")); | ||
1623 | updateItems_SidebarWidget_(d); | ||
1624 | } | ||
1625 | return iTrue; | ||
1626 | } | ||
1627 | else if (isCommand_Widget(w, ev, "ident.pickicon")) { | ||
1628 | return iTrue; | ||
1629 | } | ||
1630 | else if (isCommand_Widget(w, ev, "ident.reveal")) { | ||
1631 | const iGmIdentity *ident = menuIdentity_SidebarWidget_(d); | ||
1632 | if (ident) { | ||
1633 | const iString *crtPath = certificatePath_GmCerts(certs_App(), ident); | ||
1634 | if (crtPath) { | ||
1635 | revealPath_App(crtPath); | ||
1636 | } | ||
1637 | } | 1690 | } |
1638 | return iTrue; | ||
1639 | } | ||
1640 | else if (isCommand_Widget(w, ev, "ident.delete")) { | ||
1641 | iSidebarItem *item = d->contextItem; | ||
1642 | if (argLabel_Command(cmd, "confirm")) { | ||
1643 | makeQuestion_Widget( | ||
1644 | uiTextCaution_ColorEscape "${heading.ident.delete}", | ||
1645 | format_CStr(cstr_Lang("dlg.confirm.ident.delete"), | ||
1646 | uiTextAction_ColorEscape, | ||
1647 | cstr_String(&item->label), | ||
1648 | uiText_ColorEscape), | ||
1649 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, | ||
1650 | { uiTextCaution_ColorEscape "${dlg.ident.delete}", | ||
1651 | 0, | ||
1652 | 0, | ||
1653 | format_CStr("!ident.delete confirm:0 ptr:%p", d) } }, | ||
1654 | 2); | ||
1655 | return iTrue; | ||
1656 | } | ||
1657 | deleteIdentity_GmCerts(certs_App(), menuIdentity_SidebarWidget_(d)); | ||
1658 | postCommand_App("idents.changed"); | ||
1659 | return iTrue; | ||
1660 | } | 1691 | } |
1661 | else if (isCommand_Widget(w, ev, "history.delete")) { | 1692 | else if (isCommand_Widget(w, ev, "history.delete")) { |
1662 | if (d->contextItem && !isEmpty_String(&d->contextItem->url)) { | 1693 | if (d->contextItem && !isEmpty_String(&d->contextItem->url)) { |
@@ -1711,14 +1742,10 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1711 | /* Update cursor. */ | 1742 | /* Update cursor. */ |
1712 | else if (contains_Widget(w, mouse)) { | 1743 | else if (contains_Widget(w, mouse)) { |
1713 | const iSidebarItem *item = constHoverItem_ListWidget(d->list); | 1744 | const iSidebarItem *item = constHoverItem_ListWidget(d->list); |
1714 | if (item && d->mode != identities_SidebarMode) { | ||
1715 | setCursor_Window(get_Window(), | 1745 | setCursor_Window(get_Window(), |
1716 | item->listItem.isSeparator ? SDL_SYSTEM_CURSOR_ARROW | 1746 | item ? (item->listItem.isSeparator ? SDL_SYSTEM_CURSOR_ARROW |
1717 | : SDL_SYSTEM_CURSOR_HAND); | 1747 | : SDL_SYSTEM_CURSOR_HAND) |
1718 | } | 1748 | : SDL_SYSTEM_CURSOR_ARROW); |
1719 | else { | ||
1720 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); | ||
1721 | } | ||
1722 | } | 1749 | } |
1723 | if (d->contextIndex != iInvalidPos) { | 1750 | if (d->contextIndex != iInvalidPos) { |
1724 | invalidateItem_ListWidget(d->list, d->contextIndex); | 1751 | invalidateItem_ListWidget(d->list, d->contextIndex); |
@@ -1726,7 +1753,14 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1726 | } | 1753 | } |
1727 | } | 1754 | } |
1728 | /* Update context menu items. */ | 1755 | /* Update context menu items. */ |
1729 | if ((d->menu || d->mode == identities_SidebarMode) && ev->type == SDL_MOUSEBUTTONDOWN) { | 1756 | if (d->menu && ev->type == SDL_MOUSEBUTTONDOWN) { |
1757 | if (isSlidingSheet_SidebarWidget_(d) && | ||
1758 | ev->button.button == SDL_BUTTON_LEFT && | ||
1759 | isVisible_Widget(d) && | ||
1760 | !contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { | ||
1761 | setSlidingSheetPos_SidebarWidget_(d, bottom_SlidingSheetPos); | ||
1762 | return iTrue; | ||
1763 | } | ||
1730 | if (ev->button.button == SDL_BUTTON_RIGHT) { | 1764 | if (ev->button.button == SDL_BUTTON_RIGHT) { |
1731 | d->contextItem = NULL; | 1765 | d->contextItem = NULL; |
1732 | if (!isVisible_Widget(d->menu)) { | 1766 | if (!isVisible_Widget(d->menu)) { |
@@ -1739,7 +1773,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1739 | invalidateItem_ListWidget(d->list, d->contextIndex); | 1773 | invalidateItem_ListWidget(d->list, d->contextIndex); |
1740 | } | 1774 | } |
1741 | d->contextIndex = hoverItemIndex_ListWidget(d->list); | 1775 | d->contextIndex = hoverItemIndex_ListWidget(d->list); |
1742 | updateContextMenu_SidebarWidget_(d); | ||
1743 | /* TODO: Some callback-based mechanism would be nice for updating menus right | 1776 | /* TODO: Some callback-based mechanism would be nice for updating menus right |
1744 | before they open? At least move these to `updateContextMenu_ */ | 1777 | before they open? At least move these to `updateContextMenu_ */ |
1745 | if (d->mode == bookmarks_SidebarMode && d->contextItem) { | 1778 | if (d->mode == bookmarks_SidebarMode && d->contextItem) { |
@@ -1747,17 +1780,17 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1747 | if (bm) { | 1780 | if (bm) { |
1748 | setMenuItemLabel_Widget(d->menu, | 1781 | setMenuItemLabel_Widget(d->menu, |
1749 | "bookmark.tag tag:homepage", | 1782 | "bookmark.tag tag:homepage", |
1750 | hasTag_Bookmark(bm, homepage_BookmarkTag) | 1783 | bm->flags & homepage_BookmarkFlag |
1751 | ? home_Icon " ${bookmark.untag.home}" | 1784 | ? home_Icon " ${bookmark.untag.home}" |
1752 | : home_Icon " ${bookmark.tag.home}"); | 1785 | : home_Icon " ${bookmark.tag.home}"); |
1753 | setMenuItemLabel_Widget(d->menu, | 1786 | setMenuItemLabel_Widget(d->menu, |
1754 | "bookmark.tag tag:subscribed", | 1787 | "bookmark.tag tag:subscribed", |
1755 | hasTag_Bookmark(bm, subscribed_BookmarkTag) | 1788 | bm->flags & subscribed_BookmarkFlag |
1756 | ? star_Icon " ${bookmark.untag.sub}" | 1789 | ? star_Icon " ${bookmark.untag.sub}" |
1757 | : star_Icon " ${bookmark.tag.sub}"); | 1790 | : star_Icon " ${bookmark.tag.sub}"); |
1758 | setMenuItemLabel_Widget(d->menu, | 1791 | setMenuItemLabel_Widget(d->menu, |
1759 | "bookmark.tag tag:remotesource", | 1792 | "bookmark.tag tag:remotesource", |
1760 | hasTag_Bookmark(bm, remoteSource_BookmarkTag) | 1793 | bm->flags & remoteSource_BookmarkFlag |
1761 | ? downArrowBar_Icon " ${bookmark.untag.remote}" | 1794 | ? downArrowBar_Icon " ${bookmark.untag.remote}" |
1762 | : downArrowBar_Icon " ${bookmark.tag.remote}"); | 1795 | : downArrowBar_Icon " ${bookmark.tag.remote}"); |
1763 | } | 1796 | } |
@@ -1769,29 +1802,9 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1769 | isRead ? circle_Icon " ${feeds.entry.markunread}" | 1802 | isRead ? circle_Icon " ${feeds.entry.markunread}" |
1770 | : circleWhite_Icon " ${feeds.entry.markread}"); | 1803 | : circleWhite_Icon " ${feeds.entry.markread}"); |
1771 | } | 1804 | } |
1772 | else if (d->mode == identities_SidebarMode) { | ||
1773 | const iGmIdentity *ident = constHoverIdentity_SidebarWidget_(d); | ||
1774 | const iString * docUrl = url_DocumentWidget(document_App()); | ||
1775 | iForEach(ObjectList, i, children_Widget(d->menu)) { | ||
1776 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
1777 | iLabelWidget *menuItem = i.object; | ||
1778 | const char * cmdItem = cstr_String(command_LabelWidget(menuItem)); | ||
1779 | if (equal_Command(cmdItem, "ident.use")) { | ||
1780 | const iBool cmdUse = arg_Command(cmdItem) != 0; | ||
1781 | const iBool cmdClear = argLabel_Command(cmdItem, "clear") != 0; | ||
1782 | setFlags_Widget( | ||
1783 | as_Widget(menuItem), | ||
1784 | disabled_WidgetFlag, | ||
1785 | (cmdClear && !isUsed_GmIdentity(ident)) || | ||
1786 | (!cmdClear && cmdUse && isUsedOn_GmIdentity(ident, docUrl)) || | ||
1787 | (!cmdClear && !cmdUse && !isUsedOn_GmIdentity(ident, docUrl))); | ||
1788 | } | 1805 | } |
1789 | } | 1806 | } |
1790 | } | 1807 | } |
1791 | } | ||
1792 | } | ||
1793 | } | ||
1794 | } | ||
1795 | if (ev->type == SDL_KEYDOWN) { | 1808 | if (ev->type == SDL_KEYDOWN) { |
1796 | const int key = ev->key.keysym.sym; | 1809 | const int key = ev->key.keysym.sym; |
1797 | const int kmods = keyMods_Sym(ev->key.keysym.mod); | 1810 | const int kmods = keyMods_Sym(ev->key.keysym.mod); |
@@ -1801,6 +1814,61 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1801 | return iTrue; | 1814 | return iTrue; |
1802 | } | 1815 | } |
1803 | } | 1816 | } |
1817 | if (isSlidingSheet_SidebarWidget_(d)) { | ||
1818 | if (ev->type == SDL_MOUSEWHEEL) { | ||
1819 | enum iWidgetTouchMode touchMode = widgetMode_Touch(w); | ||
1820 | if (touchMode == momentum_WidgetTouchMode) { | ||
1821 | /* We don't do momentum. */ | ||
1822 | float swipe = stopWidgetMomentum_Touch(w) / gap_UI; | ||
1823 | // printf("swipe: %f\n", swipe); | ||
1824 | const iRangei midRegion = SlidingSheetMiddleRegion_SidebarWidget_(d); | ||
1825 | const int pos = top_Rect(w->rect); | ||
1826 | if (swipe < 170) { | ||
1827 | gotoNearestSlidingSheetPos_SidebarWidget_(d); | ||
1828 | } | ||
1829 | else if (swipe > 500 && ev->wheel.y > 0) { | ||
1830 | /* Fast swipe down will dismiss. */ | ||
1831 | setSlidingSheetPos_SidebarWidget_(d, bottom_SlidingSheetPos); | ||
1832 | } | ||
1833 | else if (ev->wheel.y < 0) { | ||
1834 | setSlidingSheetPos_SidebarWidget_(d, top_SlidingSheetPos); | ||
1835 | } | ||
1836 | else if (pos < (midRegion.start + midRegion.end) / 2) { | ||
1837 | setSlidingSheetPos_SidebarWidget_(d, middle_SlidingSheetPos); | ||
1838 | } | ||
1839 | else { | ||
1840 | setSlidingSheetPos_SidebarWidget_(d, bottom_SlidingSheetPos); | ||
1841 | } | ||
1842 | } | ||
1843 | else if (touchMode == touch_WidgetTouchMode) { | ||
1844 | /* Move with the finger. */ | ||
1845 | adjustEdges_Rect(&w->rect, ev->wheel.y, 0, 0, 0); | ||
1846 | /* Upon reaching the top, scrolling is switched back to the list. */ | ||
1847 | const iRect rootRect = safeRect_Root(w->root); | ||
1848 | const int top = top_Rect(rootRect); | ||
1849 | if (w->rect.pos.y < top) { | ||
1850 | setScrollMode_ListWidget(d->list, disabledAtTopUpwards_ScrollMode); | ||
1851 | setScrollPos_ListWidget(d->list, top - w->rect.pos.y); | ||
1852 | transferAffinity_Touch(w, as_Widget(d->list)); | ||
1853 | w->rect.pos.y = top; | ||
1854 | w->rect.size.y = height_Rect(rootRect); | ||
1855 | } | ||
1856 | else { | ||
1857 | setScrollMode_ListWidget(d->list, disabled_ScrollMode); | ||
1858 | } | ||
1859 | arrange_Widget(w); | ||
1860 | refresh_Widget(w); | ||
1861 | } | ||
1862 | else { | ||
1863 | return iFalse; | ||
1864 | } | ||
1865 | return iTrue; | ||
1866 | } | ||
1867 | if (ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode) { | ||
1868 | gotoNearestSlidingSheetPos_SidebarWidget_(d); | ||
1869 | return iTrue; | ||
1870 | } | ||
1871 | } | ||
1804 | if (ev->type == SDL_MOUSEBUTTONDOWN && | 1872 | if (ev->type == SDL_MOUSEBUTTONDOWN && |
1805 | contains_Widget(as_Widget(d->list), init_I2(ev->button.x, ev->button.y))) { | 1873 | contains_Widget(as_Widget(d->list), init_I2(ev->button.x, ev->button.y))) { |
1806 | if (hoverItem_ListWidget(d->list) || isVisible_Widget(d->menu)) { | 1874 | if (hoverItem_ListWidget(d->list) || isVisible_Widget(d->menu)) { |
@@ -1811,13 +1879,12 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1811 | const iSidebarItem *hoverItem = hoverItem_ListWidget(d->list); | 1879 | const iSidebarItem *hoverItem = hoverItem_ListWidget(d->list); |
1812 | iAssert(hoverItem); | 1880 | iAssert(hoverItem); |
1813 | const iBookmark * bm = get_Bookmarks(bookmarks_App(), hoverItem->id); | 1881 | const iBookmark * bm = get_Bookmarks(bookmarks_App(), hoverItem->id); |
1814 | const iBool isRemote = hasTag_Bookmark(bm, remote_BookmarkTag); | 1882 | const iBool isRemote = (bm->flags & remote_BookmarkFlag) != 0; |
1815 | static const char *localOnlyCmds[] = { "bookmark.edit", | 1883 | static const char *localOnlyCmds[] = { "bookmark.edit", |
1816 | "bookmark.delete", | 1884 | "bookmark.delete", |
1817 | "bookmark.tag tag:" subscribed_BookmarkTag, | 1885 | "bookmark.tag tag:subscribed", |
1818 | "bookmark.tag tag:" homepage_BookmarkTag, | 1886 | "bookmark.tag tag:homepage", |
1819 | "bookmark.tag tag:" remoteSource_BookmarkTag, | 1887 | "bookmark.tag tag:remotesource" }; |
1820 | "bookmark.tag tag:" subscribed_BookmarkTag }; | ||
1821 | iForIndices(i, localOnlyCmds) { | 1888 | iForIndices(i, localOnlyCmds) { |
1822 | setFlags_Widget(as_Widget(findMenuItem_Widget(d->menu, localOnlyCmds[i])), | 1889 | setFlags_Widget(as_Widget(findMenuItem_Widget(d->menu, localOnlyCmds[i])), |
1823 | disabled_WidgetFlag, | 1890 | disabled_WidgetFlag, |
@@ -1838,6 +1905,9 @@ static void draw_SidebarWidget_(const iSidebarWidget *d) { | |||
1838 | const iRect bounds = bounds_Widget(w); | 1905 | const iRect bounds = bounds_Widget(w); |
1839 | iPaint p; | 1906 | iPaint p; |
1840 | init_Paint(&p); | 1907 | init_Paint(&p); |
1908 | if (d->mode == documentOutline_SidebarMode) { | ||
1909 | makePaletteGlobal_GmDocument(document_DocumentWidget(document_App())); | ||
1910 | } | ||
1841 | if (!isPortraitPhone_App()) { /* this would erase page contents during transition on the phone */ | 1911 | if (!isPortraitPhone_App()) { /* this would erase page contents during transition on the phone */ |
1842 | if (flags_Widget(w) & visualOffset_WidgetFlag && | 1912 | if (flags_Widget(w) & visualOffset_WidgetFlag && |
1843 | flags_Widget(w) & horizontalOffset_WidgetFlag && isVisible_Widget(w)) { | 1913 | flags_Widget(w) & horizontalOffset_WidgetFlag && isVisible_Widget(w)) { |
@@ -1860,6 +1930,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1860 | &Class_SidebarWidget); | 1930 | &Class_SidebarWidget); |
1861 | const iBool isMenuVisible = isVisible_Widget(sidebar->menu); | 1931 | const iBool isMenuVisible = isVisible_Widget(sidebar->menu); |
1862 | const iBool isDragging = constDragItem_ListWidget(list) == d; | 1932 | const iBool isDragging = constDragItem_ListWidget(list) == d; |
1933 | const iBool isEditing = sidebar->isEditing; /* only on mobile */ | ||
1863 | const iBool isPressing = isMouseDown_ListWidget(list) && !isDragging; | 1934 | const iBool isPressing = isMouseDown_ListWidget(list) && !isDragging; |
1864 | const iBool isHover = | 1935 | const iBool isHover = |
1865 | (!isMenuVisible && | 1936 | (!isMenuVisible && |
@@ -2013,6 +2084,16 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
2013 | drawRange_Text(font, textPos, fg, range_String(&d->label)); | 2084 | drawRange_Text(font, textPos, fg, range_String(&d->label)); |
2014 | const int metaFont = uiLabel_FontId; | 2085 | const int metaFont = uiLabel_FontId; |
2015 | const int metaIconWidth = 4.5f * gap_UI; | 2086 | const int metaIconWidth = 4.5f * gap_UI; |
2087 | if (isEditing) { | ||
2088 | iRect dragRect = { | ||
2089 | addX_I2(topRight_Rect(itemRect), -itemHeight * 3 / 2), | ||
2090 | init_I2(itemHeight * 3 / 2, itemHeight) | ||
2091 | }; | ||
2092 | fillRect_Paint(p, dragRect, bg); | ||
2093 | drawVLine_Paint(p, topLeft_Rect(dragRect), height_Rect(dragRect), uiSeparator_ColorId); | ||
2094 | drawCentered_Text(uiContent_FontId, dragRect, iTrue, uiAnnotation_ColorId, menu_Icon); | ||
2095 | adjustEdges_Rect(&itemRect, 0, -width_Rect(dragRect), 0, 0); | ||
2096 | } | ||
2016 | const iInt2 metaPos = | 2097 | const iInt2 metaPos = |
2017 | init_I2(right_Rect(itemRect) - | 2098 | init_I2(right_Rect(itemRect) - |
2018 | length_String(&d->meta) * | 2099 | length_String(&d->meta) * |
@@ -2091,40 +2172,6 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
2091 | } | 2172 | } |
2092 | iEndCollect(); | 2173 | iEndCollect(); |
2093 | } | 2174 | } |
2094 | else if (sidebar->mode == identities_SidebarMode) { | ||
2095 | const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) | ||
2096 | : uiTextStrong_ColorId; | ||
2097 | const iBool isUsedOnDomain = (d->indent != 0); | ||
2098 | iString icon; | ||
2099 | initUnicodeN_String(&icon, &d->icon, 1); | ||
2100 | iInt2 cPos = topLeft_Rect(itemRect); | ||
2101 | const int indent = 1.4f * lineHeight_Text(font); | ||
2102 | addv_I2(&cPos, | ||
2103 | init_I2(3 * gap_UI, | ||
2104 | (itemHeight - lineHeight_Text(uiLabel_FontId) * 2 - lineHeight_Text(font)) / | ||
2105 | 2)); | ||
2106 | const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId | ||
2107 | : uiTextFramelessHover_ColorId) | ||
2108 | : uiTextDim_ColorId; | ||
2109 | if (!d->listItem.isSelected && !isUsedOnDomain) { | ||
2110 | drawOutline_Text(font, cPos, metaFg, none_ColorId, range_String(&icon)); | ||
2111 | } | ||
2112 | drawRange_Text(font, | ||
2113 | cPos, | ||
2114 | d->listItem.isSelected ? iconColor | ||
2115 | : isUsedOnDomain ? altIconColor | ||
2116 | : uiBackgroundSidebar_ColorId, | ||
2117 | range_String(&icon)); | ||
2118 | deinit_String(&icon); | ||
2119 | drawRange_Text(d->listItem.isSelected ? sidebar->itemFonts[1] : font, | ||
2120 | add_I2(cPos, init_I2(indent, 0)), | ||
2121 | fg, | ||
2122 | range_String(&d->label)); | ||
2123 | drawRange_Text(uiLabel_FontId, | ||
2124 | add_I2(cPos, init_I2(indent, lineHeight_Text(font))), | ||
2125 | metaFg, | ||
2126 | range_String(&d->meta)); | ||
2127 | } | ||
2128 | } | 2175 | } |
2129 | 2176 | ||
2130 | iBeginDefineSubclass(SidebarWidget, Widget) | 2177 | iBeginDefineSubclass(SidebarWidget, Widget) |
diff --git a/src/ui/sidebarwidget.h b/src/ui/sidebarwidget.h index 81c6681f..2a930a60 100644 --- a/src/ui/sidebarwidget.h +++ b/src/ui/sidebarwidget.h | |||
@@ -54,6 +54,7 @@ iBool setMode_SidebarWidget (iSidebarWidget *, enum iSidebar | |||
54 | void setWidth_SidebarWidget (iSidebarWidget *, float widthAsGaps); | 54 | void setWidth_SidebarWidget (iSidebarWidget *, float widthAsGaps); |
55 | iBool setButtonFont_SidebarWidget (iSidebarWidget *, int font); | 55 | iBool setButtonFont_SidebarWidget (iSidebarWidget *, int font); |
56 | void setClosedFolders_SidebarWidget (iSidebarWidget *, const iIntSet *closedFolders); | 56 | void setClosedFolders_SidebarWidget (iSidebarWidget *, const iIntSet *closedFolders); |
57 | void setMidHeight_SidebarWidget (iSidebarWidget *, int midHeight); /* phone layout */ | ||
57 | 58 | ||
58 | enum iSidebarMode mode_SidebarWidget (const iSidebarWidget *); | 59 | enum iSidebarMode mode_SidebarWidget (const iSidebarWidget *); |
59 | enum iFeedsMode feedsMode_SidebarWidget (const iSidebarWidget *); | 60 | enum iFeedsMode feedsMode_SidebarWidget (const iSidebarWidget *); |
diff --git a/src/ui/text.c b/src/ui/text.c index 4a4b3776..7bb418eb 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -390,14 +390,19 @@ static void deinitCache_Text_(iText *d) { | |||
390 | SDL_DestroyTexture(d->cache); | 390 | SDL_DestroyTexture(d->cache); |
391 | } | 391 | } |
392 | 392 | ||
393 | iRegExp *makeAnsiEscapePattern_Text(void) { | ||
394 | return new_RegExp("[[()][?]?([0-9;AB]*?)([ABCDEFGHJKSTfhilmn])", 0); | ||
395 | } | ||
396 | |||
393 | void init_Text(iText *d, SDL_Renderer *render) { | 397 | void init_Text(iText *d, SDL_Renderer *render) { |
394 | iText *oldActive = activeText_; | 398 | iText *oldActive = activeText_; |
395 | activeText_ = d; | 399 | activeText_ = d; |
396 | init_Array(&d->fonts, sizeof(iFont)); | 400 | init_Array(&d->fonts, sizeof(iFont)); |
397 | d->contentFontSize = contentScale_Text_; | 401 | d->contentFontSize = contentScale_Text_; |
398 | d->ansiEscape = new_RegExp("[[()][?]?([0-9;AB]*?)([ABCDEFGHJKSTfhilmn])", 0); | 402 | d->ansiEscape = makeAnsiEscapePattern_Text(); |
399 | d->baseFontId = -1; | 403 | d->baseFontId = -1; |
400 | d->baseFgColorId = -1; | 404 | d->baseFgColorId = -1; |
405 | d->missingGlyphs = iFalse; | ||
401 | d->render = render; | 406 | d->render = render; |
402 | /* A grayscale palette for rasterized glyphs. */ { | 407 | /* A grayscale palette for rasterized glyphs. */ { |
403 | SDL_Color colors[256]; | 408 | SDL_Color colors[256]; |
@@ -448,6 +453,10 @@ void setAnsiFlags_Text(int ansiFlags) { | |||
448 | activeText_->ansiFlags = ansiFlags; | 453 | activeText_->ansiFlags = ansiFlags; |
449 | } | 454 | } |
450 | 455 | ||
456 | int ansiFlags_Text(void) { | ||
457 | return activeText_->ansiFlags; | ||
458 | } | ||
459 | |||
451 | void setDocumentFontSize_Text(iText *d, float fontSizeFactor) { | 460 | void setDocumentFontSize_Text(iText *d, float fontSizeFactor) { |
452 | fontSizeFactor *= contentScale_Text_; | 461 | fontSizeFactor *= contentScale_Text_; |
453 | iAssert(fontSizeFactor > 0); | 462 | iAssert(fontSizeFactor > 0); |
@@ -642,6 +651,37 @@ static iBool isControl_Char_(iChar c) { | |||
642 | /*----------------------------------------------------------------------------------------------*/ | 651 | /*----------------------------------------------------------------------------------------------*/ |
643 | 652 | ||
644 | iDeclareType(AttributedRun) | 653 | iDeclareType(AttributedRun) |
654 | |||
655 | enum iScript { | ||
656 | unspecified_Script, | ||
657 | arabic_Script, | ||
658 | bengali_Script, | ||
659 | devanagari_Script, | ||
660 | han_Script, | ||
661 | hiragana_Script, | ||
662 | katakana_Script, | ||
663 | oriya_Script, | ||
664 | tamil_Script, | ||
665 | max_Script | ||
666 | }; | ||
667 | |||
668 | iLocalDef iBool isCJK_Script_(enum iScript d) { | ||
669 | return d == han_Script || d == hiragana_Script || d == katakana_Script; | ||
670 | } | ||
671 | |||
672 | #if defined (LAGRANGE_ENABLE_HARFBUZZ) | ||
673 | static const hb_script_t hbScripts_[max_Script] = { | ||
674 | 0, | ||
675 | HB_SCRIPT_ARABIC, | ||
676 | HB_SCRIPT_BENGALI, | ||
677 | HB_SCRIPT_DEVANAGARI, | ||
678 | HB_SCRIPT_HAN, | ||
679 | HB_SCRIPT_HIRAGANA, | ||
680 | HB_SCRIPT_KATAKANA, | ||
681 | HB_SCRIPT_ORIYA, | ||
682 | HB_SCRIPT_TAMIL, | ||
683 | }; | ||
684 | #endif | ||
645 | 685 | ||
646 | struct Impl_AttributedRun { | 686 | struct Impl_AttributedRun { |
647 | iRangei logical; /* UTF-32 codepoint indices in the logical-order text */ | 687 | iRangei logical; /* UTF-32 codepoint indices in the logical-order text */ |
@@ -651,8 +691,7 @@ struct Impl_AttributedRun { | |||
651 | iColor bgColor_; /* any RGB color; A > 0 */ | 691 | iColor bgColor_; /* any RGB color; A > 0 */ |
652 | struct { | 692 | struct { |
653 | uint8_t isLineBreak : 1; | 693 | uint8_t isLineBreak : 1; |
654 | // uint8_t isRTL : 1; | 694 | uint8_t script : 7; /* if script detected */ |
655 | uint8_t isArabic : 1; /* Arabic script detected */ | ||
656 | } flags; | 695 | } flags; |
657 | }; | 696 | }; |
658 | 697 | ||
@@ -744,7 +783,7 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i | |||
744 | #endif | 783 | #endif |
745 | pushBack_Array(&d->runs, &finishedRun); | 784 | pushBack_Array(&d->runs, &finishedRun); |
746 | run->flags.isLineBreak = iFalse; | 785 | run->flags.isLineBreak = iFalse; |
747 | run->flags.isArabic = iFalse; | 786 | run->flags.script = unspecified_Script; |
748 | } | 787 | } |
749 | run->logical.start = endAt; | 788 | run->logical.start = endAt; |
750 | } | 789 | } |
@@ -789,6 +828,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh | |||
789 | pushBack_Array(&d->logicalToSourceOffset, &(int){ ch - d->source.start }); | 828 | pushBack_Array(&d->logicalToSourceOffset, &(int){ ch - d->source.start }); |
790 | ch += len; | 829 | ch += len; |
791 | } | 830 | } |
831 | iBool bidiOk = iFalse; | ||
792 | #if defined (LAGRANGE_ENABLE_FRIBIDI) | 832 | #if defined (LAGRANGE_ENABLE_FRIBIDI) |
793 | /* Use FriBidi to reorder the codepoints. */ | 833 | /* Use FriBidi to reorder the codepoints. */ |
794 | resize_Array(&d->visual, length); | 834 | resize_Array(&d->visual, length); |
@@ -796,25 +836,25 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh | |||
796 | resize_Array(&d->visualToLogical, length); | 836 | resize_Array(&d->visualToLogical, length); |
797 | d->bidiLevels = length ? malloc(length) : NULL; | 837 | d->bidiLevels = length ? malloc(length) : NULL; |
798 | FriBidiParType baseDir = (FriBidiParType) FRIBIDI_TYPE_ON; | 838 | FriBidiParType baseDir = (FriBidiParType) FRIBIDI_TYPE_ON; |
799 | /* TODO: If this returns zero (error occurred), act like everything is LTR. */ | 839 | bidiOk = fribidi_log2vis(constData_Array(&d->logical), |
800 | fribidi_log2vis(constData_Array(&d->logical), | 840 | (FriBidiStrIndex) length, |
801 | length, | 841 | &baseDir, |
802 | &baseDir, | 842 | data_Array(&d->visual), |
803 | data_Array(&d->visual), | 843 | data_Array(&d->logicalToVisual), |
804 | data_Array(&d->logicalToVisual), | 844 | data_Array(&d->visualToLogical), |
805 | data_Array(&d->visualToLogical), | 845 | (FriBidiLevel *) d->bidiLevels) > 0; |
806 | (FriBidiLevel *) d->bidiLevels); | ||
807 | d->isBaseRTL = (overrideBaseDir == 0 ? FRIBIDI_IS_RTL(baseDir) : (overrideBaseDir < 0)); | 846 | d->isBaseRTL = (overrideBaseDir == 0 ? FRIBIDI_IS_RTL(baseDir) : (overrideBaseDir < 0)); |
808 | #else | ||
809 | /* 1:1 mapping. */ | ||
810 | setCopy_Array(&d->visual, &d->logical); | ||
811 | resize_Array(&d->logicalToVisual, length); | ||
812 | for (size_t i = 0; i < length; i++) { | ||
813 | set_Array(&d->logicalToVisual, i, &(int){ i }); | ||
814 | } | ||
815 | setCopy_Array(&d->visualToLogical, &d->logicalToVisual); | ||
816 | d->isBaseRTL = iFalse; | ||
817 | #endif | 847 | #endif |
848 | if (!bidiOk) { | ||
849 | /* 1:1 mapping. */ | ||
850 | setCopy_Array(&d->visual, &d->logical); | ||
851 | resize_Array(&d->logicalToVisual, length); | ||
852 | for (size_t i = 0; i < length; i++) { | ||
853 | set_Array(&d->logicalToVisual, i, &(int){ i }); | ||
854 | } | ||
855 | setCopy_Array(&d->visualToLogical, &d->logicalToVisual); | ||
856 | d->isBaseRTL = iFalse; | ||
857 | } | ||
818 | } | 858 | } |
819 | /* The mapping needs to include the terminating NULL position. */ { | 859 | /* The mapping needs to include the terminating NULL position. */ { |
820 | pushBack_Array(&d->logicalToSourceOffset, &(int){ d->source.end - d->source.start }); | 860 | pushBack_Array(&d->logicalToSourceOffset, &(int){ d->source.end - d->source.start }); |
@@ -828,7 +868,6 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh | |||
828 | .font = d->font, | 868 | .font = d->font, |
829 | }; | 869 | }; |
830 | const int *logToSource = constData_Array(&d->logicalToSourceOffset); | 870 | const int *logToSource = constData_Array(&d->logicalToSourceOffset); |
831 | const int * logToVis = constData_Array(&d->logicalToVisual); | ||
832 | const iChar * logicalText = constData_Array(&d->logical); | 871 | const iChar * logicalText = constData_Array(&d->logical); |
833 | iBool isRTL = d->isBaseRTL; | 872 | iBool isRTL = d->isBaseRTL; |
834 | int numNonSpace = 0; | 873 | int numNonSpace = 0; |
@@ -868,17 +907,34 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh | |||
868 | /* Note: This styling is hardcoded to match `typesetOneLine_RunTypesetter_()`. */ | 907 | /* Note: This styling is hardcoded to match `typesetOneLine_RunTypesetter_()`. */ |
869 | if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "1")) { | 908 | if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "1")) { |
870 | run.attrib.bold = iTrue; | 909 | run.attrib.bold = iTrue; |
910 | run.attrib.regular = iFalse; | ||
911 | run.attrib.light = iFalse; | ||
871 | if (d->baseFgColorId == tmParagraph_ColorId) { | 912 | if (d->baseFgColorId == tmParagraph_ColorId) { |
872 | setFgColor_AttributedRun_(&run, tmFirstParagraph_ColorId); | 913 | setFgColor_AttributedRun_(&run, tmFirstParagraph_ColorId); |
873 | } | 914 | } |
874 | attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), | 915 | attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), |
875 | bold_FontStyle)); | 916 | bold_FontStyle)); |
876 | } | 917 | } |
918 | else if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "2")) { | ||
919 | run.attrib.light = iTrue; | ||
920 | run.attrib.regular = iFalse; | ||
921 | run.attrib.bold = iFalse; | ||
922 | attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), | ||
923 | light_FontStyle)); | ||
924 | } | ||
877 | else if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "3")) { | 925 | else if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "3")) { |
878 | run.attrib.italic = iTrue; | 926 | run.attrib.italic = iTrue; |
879 | attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), | 927 | attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), |
880 | italic_FontStyle)); | 928 | italic_FontStyle)); |
881 | } | 929 | } |
930 | else if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "10")) { | ||
931 | run.attrib.regular = iTrue; | ||
932 | run.attrib.bold = iFalse; | ||
933 | run.attrib.light = iFalse; | ||
934 | run.attrib.italic = iFalse; | ||
935 | attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), | ||
936 | regular_FontStyle)); | ||
937 | } | ||
882 | else if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "11")) { | 938 | else if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "11")) { |
883 | run.attrib.monospace = iTrue; | 939 | run.attrib.monospace = iTrue; |
884 | setFgColor_AttributedRun_(&run, tmPreformatted_ColorId); | 940 | setFgColor_AttributedRun_(&run, tmPreformatted_ColorId); |
@@ -886,7 +942,9 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh | |||
886 | monospace_FontId)); | 942 | monospace_FontId)); |
887 | } | 943 | } |
888 | else if (equal_Rangecc(sequence, "0")) { | 944 | else if (equal_Rangecc(sequence, "0")) { |
945 | run.attrib.regular = iFalse; | ||
889 | run.attrib.bold = iFalse; | 946 | run.attrib.bold = iFalse; |
947 | run.attrib.light = iFalse; | ||
890 | run.attrib.italic = iFalse; | 948 | run.attrib.italic = iFalse; |
891 | run.attrib.monospace = iFalse; | 949 | run.attrib.monospace = iFalse; |
892 | attribFont = run.font = d->baseFont; | 950 | attribFont = run.font = d->baseFont; |
@@ -957,16 +1015,44 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh | |||
957 | (int)logicalText[pos]); | 1015 | (int)logicalText[pos]); |
958 | #endif | 1016 | #endif |
959 | } | 1017 | } |
1018 | /* Detect the script. */ | ||
960 | #if defined (LAGRANGE_ENABLE_FRIBIDI) | 1019 | #if defined (LAGRANGE_ENABLE_FRIBIDI) |
961 | if (fribidi_get_bidi_type(ch) == FRIBIDI_TYPE_AL) { | 1020 | if (fribidi_get_bidi_type(ch) == FRIBIDI_TYPE_AL) { |
962 | run.flags.isArabic = iTrue; /* Arabic letter */ | 1021 | run.flags.script = arabic_Script; |
963 | } | 1022 | } |
1023 | else | ||
964 | #endif | 1024 | #endif |
1025 | { | ||
1026 | const char *scr = script_Char(ch); | ||
1027 | // printf("Char %08x %lc => %s\n", ch, (int) ch, scr); | ||
1028 | if (!iCmpStr(scr, "Bengali")) { | ||
1029 | run.flags.script = bengali_Script; | ||
1030 | } | ||
1031 | else if (!iCmpStr(scr, "Devanagari")) { | ||
1032 | run.flags.script = devanagari_Script; | ||
1033 | } | ||
1034 | else if (!iCmpStr(scr, "Han")) { | ||
1035 | run.flags.script = han_Script; | ||
1036 | } | ||
1037 | else if (!iCmpStr(scr, "Hiragana")) { | ||
1038 | run.flags.script = hiragana_Script; | ||
1039 | } | ||
1040 | else if (!iCmpStr(scr, "Katakana")) { | ||
1041 | run.flags.script = katakana_Script; | ||
1042 | } | ||
1043 | else if (!iCmpStr(scr, "Oriya")) { | ||
1044 | run.flags.script = oriya_Script; | ||
1045 | } | ||
1046 | else if (!iCmpStr(scr, "Tamil")) { | ||
1047 | run.flags.script = tamil_Script; | ||
1048 | } | ||
1049 | } | ||
965 | } | 1050 | } |
966 | if (!isEmpty_Range(&run.logical)) { | 1051 | if (!isEmpty_Range(&run.logical)) { |
967 | pushBack_Array(&d->runs, &run); | 1052 | pushBack_Array(&d->runs, &run); |
968 | } | 1053 | } |
969 | #if 0 | 1054 | #if 0 |
1055 | const int *logToVis = constData_Array(&d->logicalToVisual); | ||
970 | printf("[AttributedText] %zu runs:\n", size_Array(&d->runs)); | 1056 | printf("[AttributedText] %zu runs:\n", size_Array(&d->runs)); |
971 | iConstForEach(Array, i, &d->runs) { | 1057 | iConstForEach(Array, i, &d->runs) { |
972 | const iAttributedRun *run = i.value; | 1058 | const iAttributedRun *run = i.value; |
@@ -1021,10 +1107,10 @@ struct Impl_RasterGlyph { | |||
1021 | iRect rect; | 1107 | iRect rect; |
1022 | }; | 1108 | }; |
1023 | 1109 | ||
1024 | static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { | 1110 | static void cacheGlyphs_Font_(iFont *d, const uint32_t *glyphIndices, size_t numGlyphIndices) { |
1025 | /* TODO: Make this an object so it can be used sequentially without reallocating buffers. */ | 1111 | /* TODO: Make this an object so it can be used sequentially without reallocating buffers. */ |
1026 | SDL_Surface *buf = NULL; | 1112 | SDL_Surface *buf = NULL; |
1027 | const iInt2 bufSize = init_I2(iMin(512, d->height * iMin(2 * size_Array(glyphIndices), 20)), | 1113 | const iInt2 bufSize = init_I2(iMin(512, d->height * iMin(2 * numGlyphIndices, 20)), |
1028 | d->height * 4 / 3); | 1114 | d->height * 4 / 3); |
1029 | int bufX = 0; | 1115 | int bufX = 0; |
1030 | iArray * rasters = NULL; | 1116 | iArray * rasters = NULL; |
@@ -1033,9 +1119,9 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { | |||
1033 | iAssert(isExposed_Window(get_Window())); | 1119 | iAssert(isExposed_Window(get_Window())); |
1034 | /* We'll flush the buffered rasters periodically until everything is cached. */ | 1120 | /* We'll flush the buffered rasters periodically until everything is cached. */ |
1035 | size_t index = 0; | 1121 | size_t index = 0; |
1036 | while (index < size_Array(glyphIndices)) { | 1122 | while (index < numGlyphIndices) { |
1037 | for (; index < size_Array(glyphIndices); index++) { | 1123 | for (; index < numGlyphIndices; index++) { |
1038 | const uint32_t glyphIndex = constValue_Array(glyphIndices, index, uint32_t); | 1124 | const uint32_t glyphIndex = glyphIndices[index]; |
1039 | const int lastCacheBottom = activeText_->cacheBottom; | 1125 | const int lastCacheBottom = activeText_->cacheBottom; |
1040 | iGlyph *glyph = glyphByIndex_Font_(d, glyphIndex); | 1126 | iGlyph *glyph = glyphByIndex_Font_(d, glyphIndex); |
1041 | if (activeText_->cacheBottom < lastCacheBottom) { | 1127 | if (activeText_->cacheBottom < lastCacheBottom) { |
@@ -1137,12 +1223,8 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { | |||
1137 | } | 1223 | } |
1138 | } | 1224 | } |
1139 | 1225 | ||
1140 | static void cacheSingleGlyph_Font_(iFont *d, uint32_t glyphIndex) { | 1226 | iLocalDef void cacheSingleGlyph_Font_(iFont *d, uint32_t glyphIndex) { |
1141 | iArray indices; | 1227 | cacheGlyphs_Font_(d, &glyphIndex, 1); |
1142 | init_Array(&indices, sizeof(uint32_t)); | ||
1143 | pushBack_Array(&indices, &glyphIndex); | ||
1144 | cacheGlyphs_Font_(d, &indices); | ||
1145 | deinit_Array(&indices); | ||
1146 | } | 1228 | } |
1147 | 1229 | ||
1148 | static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { | 1230 | static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { |
@@ -1171,7 +1253,7 @@ static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { | |||
1171 | } | 1253 | } |
1172 | deinit_AttributedText(&attrText); | 1254 | deinit_AttributedText(&attrText); |
1173 | /* TODO: Cache glyphs from ALL the fonts we encountered above. */ | 1255 | /* TODO: Cache glyphs from ALL the fonts we encountered above. */ |
1174 | cacheGlyphs_Font_(d, &glyphIndices); | 1256 | cacheGlyphs_Font_(d, constData_Array(&glyphIndices), size_Array(&glyphIndices)); |
1175 | deinit_Array(&glyphIndices); | 1257 | deinit_Array(&glyphIndices); |
1176 | } | 1258 | } |
1177 | 1259 | ||
@@ -1380,8 +1462,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1380 | } | 1462 | } |
1381 | hb_buffer_set_content_type(buf->hb, HB_BUFFER_CONTENT_TYPE_UNICODE); | 1463 | hb_buffer_set_content_type(buf->hb, HB_BUFFER_CONTENT_TYPE_UNICODE); |
1382 | hb_buffer_set_direction(buf->hb, HB_DIRECTION_LTR); /* visual */ | 1464 | hb_buffer_set_direction(buf->hb, HB_DIRECTION_LTR); /* visual */ |
1383 | if (run->flags.isArabic) { | 1465 | const hb_script_t script = hbScripts_[run->flags.script]; |
1384 | hb_buffer_set_script(buf->hb, HB_SCRIPT_ARABIC); | 1466 | if (script) { |
1467 | hb_buffer_set_script(buf->hb, script); | ||
1385 | } | 1468 | } |
1386 | } | 1469 | } |
1387 | if (isMonospaced) { | 1470 | if (isMonospaced) { |
@@ -1405,6 +1488,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1405 | iBool isFirst = iTrue; | 1488 | iBool isFirst = iTrue; |
1406 | const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2()); | 1489 | const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2()); |
1407 | const iBool checkHitChar = wrap && wrap->hitChar; | 1490 | const iBool checkHitChar = wrap && wrap->hitChar; |
1491 | size_t numWrapLines = 0; | ||
1408 | while (!isEmpty_Range(&wrapRuns)) { | 1492 | while (!isEmpty_Range(&wrapRuns)) { |
1409 | if (isFirst) { | 1493 | if (isFirst) { |
1410 | isFirst = iFalse; | 1494 | isFirst = iFalse; |
@@ -1469,8 +1553,11 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1469 | const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset; | 1553 | const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset; |
1470 | const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; | 1554 | const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; |
1471 | const iChar ch = logicalText[logPos]; | 1555 | const iChar ch = logicalText[logPos]; |
1556 | const enum iWrapTextMode wrapMode = isCJK_Script_(run->flags.script) | ||
1557 | ? anyCharacter_WrapTextMode | ||
1558 | : args->wrap->mode; | ||
1472 | iAssert(xAdvance >= 0); | 1559 | iAssert(xAdvance >= 0); |
1473 | if (args->wrap->mode == word_WrapTextMode) { | 1560 | if (wrapMode == word_WrapTextMode) { |
1474 | /* When word wrapping, only consider certain places breakable. */ | 1561 | /* When word wrapping, only consider certain places breakable. */ |
1475 | if ((prevCh == '-' || prevCh == '/') && !isPunct_Char(ch)) { | 1562 | if ((prevCh == '-' || prevCh == '/') && !isPunct_Char(ch)) { |
1476 | safeBreakPos = logPos; | 1563 | safeBreakPos = logPos; |
@@ -1515,7 +1602,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1515 | wrapPosRange.end = safeBreakPos; | 1602 | wrapPosRange.end = safeBreakPos; |
1516 | } | 1603 | } |
1517 | else { | 1604 | else { |
1518 | if (args->wrap->mode == word_WrapTextMode && run->logical.start > wrapPosRange.start) { | 1605 | if (wrapMode == word_WrapTextMode && run->logical.start > wrapPosRange.start) { |
1519 | /* Don't have a word break position, so the whole run needs | 1606 | /* Don't have a word break position, so the whole run needs |
1520 | to be cut. */ | 1607 | to be cut. */ |
1521 | wrapPosRange.end = run->logical.start; | 1608 | wrapPosRange.end = run->logical.start; |
@@ -1529,7 +1616,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1529 | breakRunIndex = runIndex; | 1616 | breakRunIndex = runIndex; |
1530 | } | 1617 | } |
1531 | wrapResumePos = wrapPosRange.end; | 1618 | wrapResumePos = wrapPosRange.end; |
1532 | if (args->wrap->mode != anyCharacter_WrapTextMode) { | 1619 | if (wrapMode != anyCharacter_WrapTextMode) { |
1533 | while (wrapResumePos < textLen && isSpace_Char(logicalText[wrapResumePos])) { | 1620 | while (wrapResumePos < textLen && isSpace_Char(logicalText[wrapResumePos])) { |
1534 | wrapResumePos++; /* skip space */ | 1621 | wrapResumePos++; /* skip space */ |
1535 | } | 1622 | } |
@@ -1634,6 +1721,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1634 | iRound(wrapAdvance))) { | 1721 | iRound(wrapAdvance))) { |
1635 | willAbortDueToWrap = iTrue; | 1722 | willAbortDueToWrap = iTrue; |
1636 | } | 1723 | } |
1724 | numWrapLines++; | ||
1725 | if (wrap && wrap->maxLines && numWrapLines == wrap->maxLines) { | ||
1726 | willAbortDueToWrap = iTrue; | ||
1727 | } | ||
1637 | wrapAttrib = lastAttrib; | 1728 | wrapAttrib = lastAttrib; |
1638 | xCursor = origin; | 1729 | xCursor = origin; |
1639 | /* We have determined a possible wrap position and alignment for the work runs, | 1730 | /* We have determined a possible wrap position and alignment for the work runs, |
@@ -1664,12 +1755,13 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1664 | /* Already handled this part of the run. */ | 1755 | /* Already handled this part of the run. */ |
1665 | continue; | 1756 | continue; |
1666 | } | 1757 | } |
1667 | const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset; | 1758 | const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset; |
1668 | const float yOffset = run->font->yScale * buf->glyphPos[i].y_offset; | 1759 | float yOffset = run->font->yScale * buf->glyphPos[i].y_offset; |
1669 | const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; | 1760 | const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; |
1670 | const float yAdvance = run->font->yScale * buf->glyphPos[i].y_advance; | 1761 | const float yAdvance = run->font->yScale * buf->glyphPos[i].y_advance; |
1671 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); | 1762 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); |
1672 | if (logicalText[logPos] == '\t') { | 1763 | const iChar ch = logicalText[logPos]; |
1764 | if (ch == '\t') { | ||
1673 | #if 0 | 1765 | #if 0 |
1674 | if (mode & draw_RunMode) { | 1766 | if (mode & draw_RunMode) { |
1675 | /* Tab indicator. */ | 1767 | /* Tab indicator. */ |
@@ -1688,6 +1780,13 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1688 | } | 1780 | } |
1689 | const float xf = xCursor + xOffset; | 1781 | const float xf = xCursor + xOffset; |
1690 | const int hoff = enableHalfPixelGlyphs_Text ? (xf - ((int) xf) > 0.5f ? 1 : 0) : 0; | 1782 | const int hoff = enableHalfPixelGlyphs_Text ? (xf - ((int) xf) > 0.5f ? 1 : 0) : 0; |
1783 | if (ch == 0x3001 || ch == 0x3002) { | ||
1784 | /* Vertical misalignment?? */ | ||
1785 | if (yOffset == 0.0f) { | ||
1786 | /* Move down to baseline. Why doesn't HarfBuzz do this? */ | ||
1787 | yOffset = glyph->d[hoff].y + glyph->rect[hoff].size.y + glyph->d[hoff].y / 4; | ||
1788 | } | ||
1789 | } | ||
1691 | /* Output position for the glyph. */ | 1790 | /* Output position for the glyph. */ |
1692 | SDL_Rect dst = { orig.x + xCursor + xOffset + glyph->d[hoff].x, | 1791 | SDL_Rect dst = { orig.x + xCursor + xOffset + glyph->d[hoff].x, |
1693 | orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y, | 1792 | orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y, |
@@ -2231,6 +2330,7 @@ void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) { | |||
2231 | SDL_Texture *oldTarget = SDL_GetRenderTarget(render); | 2330 | SDL_Texture *oldTarget = SDL_GetRenderTarget(render); |
2232 | const iInt2 oldOrigin = origin_Paint; | 2331 | const iInt2 oldOrigin = origin_Paint; |
2233 | origin_Paint = zero_I2(); | 2332 | origin_Paint = zero_I2(); |
2333 | setBaseAttributes_Text(font, color); | ||
2234 | SDL_SetRenderTarget(render, d->texture); | 2334 | SDL_SetRenderTarget(render, d->texture); |
2235 | SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); | 2335 | SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); |
2236 | SDL_SetRenderDrawColor(render, 255, 255, 255, 0); | 2336 | SDL_SetRenderDrawColor(render, 255, 255, 255, 0); |
@@ -2241,6 +2341,7 @@ void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) { | |||
2241 | SDL_SetRenderTarget(render, oldTarget); | 2341 | SDL_SetRenderTarget(render, oldTarget); |
2242 | origin_Paint = oldOrigin; | 2342 | origin_Paint = oldOrigin; |
2243 | SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); | 2343 | SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); |
2344 | setBaseAttributes_Text(-1, -1); | ||
2244 | } | 2345 | } |
2245 | } | 2346 | } |
2246 | 2347 | ||
diff --git a/src/ui/text.h b/src/ui/text.h index 63499484..c8bb6f85 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -29,6 +29,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
29 | 29 | ||
30 | #include "fontpack.h" | 30 | #include "fontpack.h" |
31 | 31 | ||
32 | iDeclareType(RegExp) | ||
33 | |||
32 | /* Content sizes: regular (1x) -> medium (1.2x) -> big (1.33x) -> large (1.67x) -> huge (2x) */ | 34 | /* Content sizes: regular (1x) -> medium (1.2x) -> big (1.33x) -> large (1.67x) -> huge (2x) */ |
33 | 35 | ||
34 | #define FONT_ID(name, style, size) ((name) + ((style) * max_FontSize) + (size)) | 36 | #define FONT_ID(name, style, size) ((name) + ((style) * max_FontSize) + (size)) |
@@ -159,6 +161,7 @@ enum iAnsiFlag { | |||
159 | void setOpacity_Text (float opacity); | 161 | void setOpacity_Text (float opacity); |
160 | void setBaseAttributes_Text (int fontId, int fgColorId); /* current "normal" text attributes */ | 162 | void setBaseAttributes_Text (int fontId, int fgColorId); /* current "normal" text attributes */ |
161 | void setAnsiFlags_Text (int ansiFlags); | 163 | void setAnsiFlags_Text (int ansiFlags); |
164 | int ansiFlags_Text (void); | ||
162 | 165 | ||
163 | void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ | 166 | void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ |
164 | 167 | ||
@@ -190,7 +193,9 @@ struct Impl_TextAttrib { | |||
190 | int16_t fgColorId; | 193 | int16_t fgColorId; |
191 | int16_t bgColorId; | 194 | int16_t bgColorId; |
192 | struct { | 195 | struct { |
196 | uint16_t regular : 1; | ||
193 | uint16_t bold : 1; | 197 | uint16_t bold : 1; |
198 | uint16_t light : 1; | ||
194 | uint16_t italic : 1; | 199 | uint16_t italic : 1; |
195 | uint16_t monospace : 1; | 200 | uint16_t monospace : 1; |
196 | uint16_t isBaseRTL : 1; | 201 | uint16_t isBaseRTL : 1; |
@@ -202,6 +207,7 @@ struct Impl_WrapText { | |||
202 | /* arguments */ | 207 | /* arguments */ |
203 | iRangecc text; | 208 | iRangecc text; |
204 | int maxWidth; | 209 | int maxWidth; |
210 | size_t maxLines; /* 0: unlimited */ | ||
205 | enum iWrapTextMode mode; | 211 | enum iWrapTextMode mode; |
206 | iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, iTextAttrib attrib, int origin, | 212 | iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, iTextAttrib attrib, int origin, |
207 | int advance); | 213 | int advance); |
@@ -229,6 +235,8 @@ enum iTextBlockMode { quadrants_TextBlockMode, shading_TextBlockMode }; | |||
229 | iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iTextBlockMode, | 235 | iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iTextBlockMode, |
230 | const iString *text); | 236 | const iString *text); |
231 | 237 | ||
238 | iRegExp * makeAnsiEscapePattern_Text (void); | ||
239 | |||
232 | /*-----------------------------------------------------------------------------------------------*/ | 240 | /*-----------------------------------------------------------------------------------------------*/ |
233 | 241 | ||
234 | iDeclareType(TextBuf) | 242 | iDeclareType(TextBuf) |
@@ -239,6 +247,5 @@ struct Impl_TextBuf { | |||
239 | iInt2 size; | 247 | iInt2 size; |
240 | }; | 248 | }; |
241 | 249 | ||
242 | iTextBuf * newRange_TextBuf (int font, int color, iRangecc text); | 250 | iTextBuf * newRange_TextBuf(int font, int color, iRangecc text); |
243 | |||
244 | void draw_TextBuf (const iTextBuf *, iInt2 pos, int color); | 251 | void draw_TextBuf (const iTextBuf *, iInt2 pos, int color); |
diff --git a/src/ui/text_simple.c b/src/ui/text_simple.c index 8560c138..71942cf1 100644 --- a/src/ui/text_simple.c +++ b/src/ui/text_simple.c | |||
@@ -118,8 +118,9 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
118 | if (match_RegExp(activeText_->ansiEscape, chPos, args->text.end - chPos, &m)) { | 118 | if (match_RegExp(activeText_->ansiEscape, chPos, args->text.end - chPos, &m)) { |
119 | if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { | 119 | if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { |
120 | /* Change the color. */ | 120 | /* Change the color. */ |
121 | iColor clr; | 121 | iColor clr = get_Color(args->color); |
122 | ansiColors_Color(capturedRange_RegExpMatch(&m, 1), tmParagraph_ColorId, | 122 | ansiColors_Color(capturedRange_RegExpMatch(&m, 1), |
123 | activeText_->baseFgColorId, | ||
123 | none_ColorId, &clr, NULL); | 124 | none_ColorId, &clr, NULL); |
124 | SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b); | 125 | SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b); |
125 | if (args->mode & fillBackground_RunMode) { | 126 | if (args->mode & fillBackground_RunMode) { |
diff --git a/src/ui/touch.c b/src/ui/touch.c index d6846572..20ccf7b8 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c | |||
@@ -111,6 +111,7 @@ struct Impl_TouchState { | |||
111 | double momFrictionPerStep; | 111 | double momFrictionPerStep; |
112 | double lastMomTime; | 112 | double lastMomTime; |
113 | iInt2 currentTouchPos; /* for emulating SDL_GetMouseState() */ | 113 | iInt2 currentTouchPos; /* for emulating SDL_GetMouseState() */ |
114 | iInt2 latestLongPressStartPos; | ||
114 | }; | 115 | }; |
115 | 116 | ||
116 | static iTouchState *touchState_(void) { | 117 | static iTouchState *touchState_(void) { |
@@ -260,6 +261,11 @@ static iFloat3 gestureVector_Touch_(const iTouch *d) { | |||
260 | return sub_F3(d->pos[0], d->pos[lastIndex]); | 261 | return sub_F3(d->pos[0], d->pos[lastIndex]); |
261 | } | 262 | } |
262 | 263 | ||
264 | static uint32_t gestureSpan_Touch_(const iTouch *d) { | ||
265 | const size_t lastIndex = iMin(d->posCount - 1, lastIndex_Touch_); | ||
266 | return d->posTime[0] - d->posTime[lastIndex]; | ||
267 | } | ||
268 | |||
263 | static void update_TouchState_(void *ptr) { | 269 | static void update_TouchState_(void *ptr) { |
264 | iWindow *win = get_Window(); | 270 | iWindow *win = get_Window(); |
265 | const iWidget *oldHover = win->hover; | 271 | const iWidget *oldHover = win->hover; |
@@ -308,6 +314,7 @@ static void update_TouchState_(void *ptr) { | |||
308 | } | 314 | } |
309 | if (!touch->isTapAndHold && nowTime - touch->startTime >= longPressSpanMs_ && | 315 | if (!touch->isTapAndHold && nowTime - touch->startTime >= longPressSpanMs_ && |
310 | touch->affinity) { | 316 | touch->affinity) { |
317 | touchState_()->latestLongPressStartPos = initF3_I2(touch->pos[0]); | ||
311 | dispatchClick_Touch_(touch, SDL_BUTTON_RIGHT); | 318 | dispatchClick_Touch_(touch, SDL_BUTTON_RIGHT); |
312 | touch->isTapAndHold = iTrue; | 319 | touch->isTapAndHold = iTrue; |
313 | touch->hasMoved = iFalse; | 320 | touch->hasMoved = iFalse; |
@@ -593,9 +600,18 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
593 | divvf_F3(&touch->accum, 6); | 600 | divvf_F3(&touch->accum, 6); |
594 | divfv_I2(&pixels, 6); | 601 | divfv_I2(&pixels, 6); |
595 | /* Allow scrolling a scrollable widget. */ | 602 | /* Allow scrolling a scrollable widget. */ |
596 | iWidget *flow = findOverflowScrollable_Widget(touch->affinity); | 603 | if (touch->affinity && touch->affinity->flags2 & slidingSheetDraggable_WidgetFlag2) { |
597 | if (flow) { | 604 | extern iWidgetClass Class_SidebarWidget; /* The only type of sliding sheet for now. */ |
598 | touch->affinity = flow; | 605 | iWidget *slider = findParentClass_Widget(touch->affinity, &Class_SidebarWidget); |
606 | if (slider) { | ||
607 | touch->affinity = slider; | ||
608 | } | ||
609 | } | ||
610 | else { | ||
611 | iWidget *flow = findOverflowScrollable_Widget(touch->affinity); | ||
612 | if (flow) { | ||
613 | touch->affinity = flow; | ||
614 | } | ||
599 | } | 615 | } |
600 | } | 616 | } |
601 | else { | 617 | else { |
@@ -621,11 +637,13 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
621 | if (touch->axis == y_TouchAxis) { | 637 | if (touch->axis == y_TouchAxis) { |
622 | pixels.x = 0; | 638 | pixels.x = 0; |
623 | } | 639 | } |
624 | // printf("%p (%s) py: %i wy: %f acc: %f edge: %d\n", | 640 | #if 0 |
625 | // touch->affinity, | 641 | printf("%p (%s) py: %i wy: %f acc: %f edge: %d\n", |
626 | // class_Widget(touch->affinity)->name, | 642 | touch->affinity, |
627 | // pixels.y, y_F3(amount), y_F3(touch->accum), | 643 | class_Widget(touch->affinity)->name, |
628 | // touch->edge); | 644 | pixels.y, y_F3(amount), y_F3(touch->accum), |
645 | touch->edge); | ||
646 | #endif | ||
629 | if (pixels.x || pixels.y) { | 647 | if (pixels.x || pixels.y) { |
630 | //setFocus_Widget(NULL); | 648 | //setFocus_Widget(NULL); |
631 | dispatchMotion_Touch_(touch->startPos /*pos[0]*/, 0); | 649 | dispatchMotion_Touch_(touch->startPos /*pos[0]*/, 0); |
@@ -661,11 +679,14 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
661 | #endif | 679 | #endif |
662 | if (touch->edge && !isStationary_Touch_(touch)) { | 680 | if (touch->edge && !isStationary_Touch_(touch)) { |
663 | const iFloat3 gesture = gestureVector_Touch_(touch); | 681 | const iFloat3 gesture = gestureVector_Touch_(touch); |
682 | const uint32_t duration = gestureSpan_Touch_(touch); | ||
664 | const float pixel = window->pixelRatio; | 683 | const float pixel = window->pixelRatio; |
665 | const int moveDir = x_F3(gesture) < -pixel ? -1 : x_F3(gesture) > pixel ? +1 : 0; | 684 | const int moveDir = x_F3(gesture) < -pixel ? -1 : x_F3(gesture) > pixel ? +1 : 0; |
666 | const int didAbort = (touch->edge == left_TouchEdge && moveDir < 0) || | 685 | const int didAbort = (touch->edge == left_TouchEdge && moveDir < 0) || |
667 | (touch->edge == right_TouchEdge && moveDir > 0); | 686 | (touch->edge == right_TouchEdge && moveDir > 0); |
668 | postCommandf_App("edgeswipe.ended abort:%d side:%d id:%llu", didAbort, touch->edge, touch->id); | 687 | postCommandf_App("edgeswipe.ended abort:%d side:%d id:%llu speed:%d", didAbort, |
688 | touch->edge, touch->id, | ||
689 | (int) (duration > 0 ? length_F3(gesture) / (duration / 1000.0f) : 0)); | ||
669 | remove_ArrayIterator(&i); | 690 | remove_ArrayIterator(&i); |
670 | continue; | 691 | continue; |
671 | } | 692 | } |
@@ -689,8 +710,8 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
689 | } | 710 | } |
690 | /* Edge swipes do not generate momentum. */ | 711 | /* Edge swipes do not generate momentum. */ |
691 | const size_t lastIndex = iMin(touch->posCount - 1, lastIndex_Touch_); | 712 | const size_t lastIndex = iMin(touch->posCount - 1, lastIndex_Touch_); |
692 | const uint32_t duration = nowTime - touch->startTime; | ||
693 | const iFloat3 gestureVector = sub_F3(pos, touch->pos[lastIndex]); | 713 | const iFloat3 gestureVector = sub_F3(pos, touch->pos[lastIndex]); |
714 | const uint32_t duration = nowTime - touch->startTime; | ||
694 | iFloat3 velocity = zero_F3(); | 715 | iFloat3 velocity = zero_F3(); |
695 | #if 0 | 716 | #if 0 |
696 | if (touch->edge && fabsf(2 * x_F3(gestureVector)) > fabsf(y_F3(gestureVector)) && | 717 | if (touch->edge && fabsf(2 * x_F3(gestureVector)) > fabsf(y_F3(gestureVector)) && |
@@ -805,10 +826,24 @@ void widgetDestroyed_Touch(iWidget *widget) { | |||
805 | } | 826 | } |
806 | } | 827 | } |
807 | 828 | ||
829 | void transferAffinity_Touch(iWidget *src, iWidget *dst) { | ||
830 | iTouchState *d = touchState_(); | ||
831 | iForEach(Array, i, d->touches) { | ||
832 | iTouch *touch = i.value; | ||
833 | if (touch->affinity == src) { | ||
834 | touch->affinity = dst; | ||
835 | } | ||
836 | } | ||
837 | } | ||
838 | |||
808 | iInt2 latestPosition_Touch(void) { | 839 | iInt2 latestPosition_Touch(void) { |
809 | return touchState_()->currentTouchPos; | 840 | return touchState_()->currentTouchPos; |
810 | } | 841 | } |
811 | 842 | ||
843 | iInt2 latestTapPosition_Touch(void) { | ||
844 | return touchState_()->latestLongPressStartPos; | ||
845 | } | ||
846 | |||
812 | iBool isHovering_Touch(void) { | 847 | iBool isHovering_Touch(void) { |
813 | iTouchState *d = touchState_(); | 848 | iTouchState *d = touchState_(); |
814 | if (numFingers_Touch() == 1) { | 849 | if (numFingers_Touch() == 1) { |
diff --git a/src/ui/touch.h b/src/ui/touch.h index e048224a..15c6da1f 100644 --- a/src/ui/touch.h +++ b/src/ui/touch.h | |||
@@ -36,10 +36,12 @@ enum iWidgetTouchMode { | |||
36 | iBool processEvent_Touch (const SDL_Event *); | 36 | iBool processEvent_Touch (const SDL_Event *); |
37 | void update_Touch (void); | 37 | void update_Touch (void); |
38 | 38 | ||
39 | float stopWidgetMomentum_Touch (const iWidget *widget); | 39 | float stopWidgetMomentum_Touch (const iWidget *widget); /* pixels per second */ |
40 | enum iWidgetTouchMode widgetMode_Touch (const iWidget *widget); | 40 | enum iWidgetTouchMode widgetMode_Touch (const iWidget *widget); |
41 | void widgetDestroyed_Touch (iWidget *widget); | 41 | void widgetDestroyed_Touch (iWidget *widget); |
42 | void transferAffinity_Touch (iWidget *src, iWidget *dst); | ||
42 | 43 | ||
43 | iInt2 latestPosition_Touch (void); /* valid during processing of current event */ | 44 | iInt2 latestPosition_Touch (void); /* valid during processing of current event */ |
45 | iInt2 latestTapPosition_Touch (void); | ||
44 | iBool isHovering_Touch (void); /* stationary touch or a long-press drag ongoing */ | 46 | iBool isHovering_Touch (void); /* stationary touch or a long-press drag ongoing */ |
45 | size_t numFingers_Touch (void); | 47 | size_t numFingers_Touch (void); |
diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c index bad00071..ed448768 100644 --- a/src/ui/uploadwidget.c +++ b/src/ui/uploadwidget.c | |||
@@ -56,8 +56,10 @@ struct Impl_UploadWidget { | |||
56 | iDocumentWidget *viewer; | 56 | iDocumentWidget *viewer; |
57 | iGmRequest * request; | 57 | iGmRequest * request; |
58 | iLabelWidget * info; | 58 | iLabelWidget * info; |
59 | iInputWidget * path; | ||
59 | iInputWidget * mime; | 60 | iInputWidget * mime; |
60 | iInputWidget * token; | 61 | iInputWidget * token; |
62 | iLabelWidget * ident; | ||
61 | iInputWidget * input; | 63 | iInputWidget * input; |
62 | iLabelWidget * filePathLabel; | 64 | iLabelWidget * filePathLabel; |
63 | iLabelWidget * fileSizeLabel; | 65 | iLabelWidget * fileSizeLabel; |
@@ -91,17 +93,19 @@ static void updateProgress_UploadWidget_(iGmRequest *request, size_t current, si | |||
91 | static void updateInputMaxHeight_UploadWidget_(iUploadWidget *d) { | 93 | static void updateInputMaxHeight_UploadWidget_(iUploadWidget *d) { |
92 | iWidget *w = as_Widget(d); | 94 | iWidget *w = as_Widget(d); |
93 | /* Calculate how many lines fits vertically in the view. */ | 95 | /* Calculate how many lines fits vertically in the view. */ |
94 | const iInt2 inputPos = topLeft_Rect(bounds_Widget(as_Widget(d->input))); | 96 | const iInt2 inputPos = topLeft_Rect(bounds_Widget(as_Widget(d->input))); |
95 | const int footerHeight = isUsingPanelLayout_Mobile() ? 0 : | 97 | int footerHeight = 0; |
96 | (height_Widget(d->token) + | 98 | if (!isUsingPanelLayout_Mobile()) { |
97 | height_Widget(findChild_Widget(w, "dialogbuttons")) + | 99 | footerHeight = (height_Widget(d->token) + |
98 | 12 * gap_UI); | 100 | height_Widget(findChild_Widget(w, "dialogbuttons")) + |
99 | const int avail = bottom_Rect(safeRect_Root(w->root)) - footerHeight - | 101 | 12 * gap_UI); |
100 | get_MainWindow()->keyboardHeight; | 102 | } |
101 | setLineLimits_InputWidget(d->input, | 103 | const int avail = bottom_Rect(visibleRect_Root(w->root)) - footerHeight - inputPos.y; |
102 | minLines_InputWidget(d->input), | 104 | /* On desktop, retain the previously set minLines value. */ |
103 | iMaxi(minLines_InputWidget(d->input), | 105 | int minLines = isUsingPanelLayout_Mobile() ? 1 : minLines_InputWidget(d->input); |
104 | (avail - inputPos.y) / lineHeight_Text(font_InputWidget(d->input)))); | 106 | int maxLines = iMaxi(minLines, avail / lineHeight_Text(font_InputWidget(d->input))); |
107 | /* On mobile, the height is fixed to the available space. */ | ||
108 | setLineLimits_InputWidget(d->input, isUsingPanelLayout_Mobile() ? maxLines : minLines, maxLines); | ||
105 | } | 109 | } |
106 | 110 | ||
107 | static const iGmIdentity *titanIdentityForUrl_(const iString *url) { | 111 | static const iGmIdentity *titanIdentityForUrl_(const iString *url) { |
@@ -123,9 +127,15 @@ static const iArray *makeIdentityItems_UploadWidget_(const iUploadWidget *d) { | |||
123 | pushBack_Array(items, &(iMenuItem){ "---" }); | 127 | pushBack_Array(items, &(iMenuItem){ "---" }); |
124 | iConstForEach(PtrArray, i, listIdentities_GmCerts(certs_App(), NULL, NULL)) { | 128 | iConstForEach(PtrArray, i, listIdentities_GmCerts(certs_App(), NULL, NULL)) { |
125 | const iGmIdentity *id = i.ptr; | 129 | const iGmIdentity *id = i.ptr; |
130 | iString *str = collect_String(copy_String(name_GmIdentity(id))); | ||
131 | prependCStr_String(str, "\x1b[1m"); | ||
132 | if (!isEmpty_String(&id->notes)) { | ||
133 | appendFormat_String( | ||
134 | str, "\x1b[0m\n%s%s", escape_Color(uiTextDim_ColorId), cstr_String(&id->notes)); | ||
135 | } | ||
126 | pushBack_Array( | 136 | pushBack_Array( |
127 | items, | 137 | items, |
128 | &(iMenuItem){ cstr_String(name_GmIdentity(id)), 0, 0, | 138 | &(iMenuItem){ cstr_String(str), 0, 0, |
129 | format_CStr("upload.setid fp:%s", | 139 | format_CStr("upload.setid fp:%s", |
130 | cstrCollect_String(hexEncode_Block(&id->fingerprint))) }); | 140 | cstrCollect_String(hexEncode_Block(&id->fingerprint))) }); |
131 | } | 141 | } |
@@ -182,15 +192,20 @@ void init_UploadWidget(iUploadWidget *d) { | |||
182 | }; | 192 | }; |
183 | initPanels_Mobile(w, NULL, (iMenuItem[]){ | 193 | initPanels_Mobile(w, NULL, (iMenuItem[]){ |
184 | { "title id:heading.upload" }, | 194 | { "title id:heading.upload" }, |
185 | { "label id:upload.info" }, | 195 | { "heading id:upload.url" }, |
196 | { format_CStr("label id:upload.info font:%d", | ||
197 | deviceType_App() == phone_AppDeviceType ? uiLabelBig_FontId : uiLabelMedium_FontId) }, | ||
198 | { "input id:upload.path hint:hint.upload.path noheading:1 url:1 text:" }, | ||
199 | { "heading text:${heading.upload.id}" }, | ||
200 | { "dropdown id:upload.id icon:0x1f464 text:", 0, 0, constData_Array(makeIdentityItems_UploadWidget_(d)) }, | ||
201 | { "input id:upload.token hint:hint.upload.token.long icon:0x1f516 text:" }, | ||
202 | { "heading id:upload.content" }, | ||
186 | { "panel id:dlg.upload.text icon:0x1f5b9 noscroll:1", 0, 0, (const void *) textItems }, | 203 | { "panel id:dlg.upload.text icon:0x1f5b9 noscroll:1", 0, 0, (const void *) textItems }, |
187 | { "panel id:dlg.upload.file icon:0x1f4c1", 0, 0, (const void *) fileItems }, | 204 | { "panel id:dlg.upload.file icon:0x1f4c1", 0, 0, (const void *) fileItems }, |
188 | { "padding" }, | ||
189 | { "dropdown id:upload.id icon:0x1f464", 0, 0, constData_Array(makeIdentityItems_UploadWidget_(d)) }, | ||
190 | { "input id:upload.token hint:hint.upload.token icon:0x1f511" }, | ||
191 | { NULL } | 205 | { NULL } |
192 | }, actions, iElemCount(actions)); | 206 | }, actions, iElemCount(actions)); |
193 | d->info = findChild_Widget(w, "upload.info"); | 207 | d->info = findChild_Widget(w, "upload.info"); |
208 | d->path = findChild_Widget(w, "upload.path"); | ||
194 | d->input = findChild_Widget(w, "upload.text"); | 209 | d->input = findChild_Widget(w, "upload.text"); |
195 | d->filePathLabel = findChild_Widget(w, "upload.filepathlabel"); | 210 | d->filePathLabel = findChild_Widget(w, "upload.filepathlabel"); |
196 | d->fileSizeLabel = findChild_Widget(w, "upload.filesizelabel"); | 211 | d->fileSizeLabel = findChild_Widget(w, "upload.filesizelabel"); |
@@ -200,17 +215,28 @@ void init_UploadWidget(iUploadWidget *d) { | |||
200 | if (isPortraitPhone_App()) { | 215 | if (isPortraitPhone_App()) { |
201 | enableUploadButton_UploadWidget_(d, iFalse); | 216 | enableUploadButton_UploadWidget_(d, iFalse); |
202 | } | 217 | } |
218 | iWidget *title = findChild_Widget(w, "heading.upload.text"); | ||
219 | iLabelWidget *menu = new_LabelWidget(midEllipsis_Icon, "upload.editmenu.open"); | ||
220 | setTextColor_LabelWidget(menu, uiTextAction_ColorId); | ||
221 | setFont_LabelWidget(menu, uiLabelBigBold_FontId); | ||
222 | addChildFlags_Widget(title, iClob(menu), frameless_WidgetFlag | moveToParentRightEdge_WidgetFlag); | ||
203 | } | 223 | } |
204 | else { | 224 | else { |
205 | useSheetStyle_Widget(w); | 225 | useSheetStyle_Widget(w); |
206 | setFlags_Widget(w, overflowScrollable_WidgetFlag, iFalse); | 226 | setFlags_Widget(w, overflowScrollable_WidgetFlag, iFalse); |
207 | addChildFlags_Widget(w, | 227 | addDialogTitle_Widget(w, "${heading.upload}", NULL); |
208 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.upload}", NULL)), | 228 | iWidget *headings, *values; |
209 | frameless_WidgetFlag); | 229 | /* URL path. */ { |
210 | d->info = addChildFlags_Widget(w, iClob(new_LabelWidget("", NULL)), | 230 | iWidget *page = makeTwoColumns_Widget(&headings, &values); |
211 | frameless_WidgetFlag | resizeToParentWidth_WidgetFlag | | 231 | d->path = new_InputWidget(0); |
212 | fixedHeight_WidgetFlag); | 232 | addTwoColumnDialogInputField_Widget( |
213 | setWrap_LabelWidget(d->info, iTrue); | 233 | headings, values, "", "upload.path", iClob(d->path)); |
234 | d->info = (iLabelWidget *) lastChild_Widget(headings); | ||
235 | setFont_LabelWidget(d->info, uiContent_FontId); | ||
236 | setTextColor_LabelWidget(d->info, uiInputTextFocused_ColorId); | ||
237 | addChild_Widget(w, iClob(page)); | ||
238 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | ||
239 | } | ||
214 | /* Tabs for input data. */ | 240 | /* Tabs for input data. */ |
215 | iWidget *tabs = makeTabs_Widget(w); | 241 | iWidget *tabs = makeTabs_Widget(w); |
216 | /* Make the tabs support vertical expansion based on content. */ { | 242 | /* Make the tabs support vertical expansion based on content. */ { |
@@ -220,7 +246,6 @@ void init_UploadWidget(iUploadWidget *d) { | |||
220 | setFlags_Widget(tabPages, resizeHeightOfChildren_WidgetFlag, iFalse); | 246 | setFlags_Widget(tabPages, resizeHeightOfChildren_WidgetFlag, iFalse); |
221 | setFlags_Widget(tabPages, arrangeHeight_WidgetFlag, iTrue); | 247 | setFlags_Widget(tabPages, arrangeHeight_WidgetFlag, iTrue); |
222 | } | 248 | } |
223 | iWidget *headings, *values; | ||
224 | setBackgroundColor_Widget(findChild_Widget(tabs, "tabs.buttons"), uiBackgroundSidebar_ColorId); | 249 | setBackgroundColor_Widget(findChild_Widget(tabs, "tabs.buttons"), uiBackgroundSidebar_ColorId); |
225 | setId_Widget(tabs, "upload.tabs"); | 250 | setId_Widget(tabs, "upload.tabs"); |
226 | /* Text input. */ { | 251 | /* Text input. */ { |
@@ -233,7 +258,8 @@ void init_UploadWidget(iUploadWidget *d) { | |||
233 | appendFramelessTabPage_Widget(tabs, iClob(page), "${heading.upload.text}", '1', 0); | 258 | appendFramelessTabPage_Widget(tabs, iClob(page), "${heading.upload.text}", '1', 0); |
234 | } | 259 | } |
235 | /* File content. */ { | 260 | /* File content. */ { |
236 | appendTwoColumnTabPage_Widget(tabs, "${heading.upload.file}", '2', &headings, &values); | 261 | iWidget *page = appendTwoColumnTabPage_Widget(tabs, "${heading.upload.file}", '2', &headings, &values); |
262 | setBackgroundColor_Widget(page, uiBackgroundSidebar_ColorId); | ||
237 | addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.name}", NULL)), frameless_WidgetFlag); | 263 | addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.name}", NULL)), frameless_WidgetFlag); |
238 | d->filePathLabel = addChildFlags_Widget(values, iClob(new_LabelWidget(uiTextAction_ColorEscape "${upload.file.drophere}", NULL)), frameless_WidgetFlag); | 264 | d->filePathLabel = addChildFlags_Widget(values, iClob(new_LabelWidget(uiTextAction_ColorEscape "${upload.file.drophere}", NULL)), frameless_WidgetFlag); |
239 | addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.size}", NULL)), frameless_WidgetFlag); | 265 | addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.size}", NULL)), frameless_WidgetFlag); |
@@ -245,19 +271,20 @@ void init_UploadWidget(iUploadWidget *d) { | |||
245 | /* Identity and Token. */ { | 271 | /* Identity and Token. */ { |
246 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | 272 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); |
247 | iWidget *page = makeTwoColumns_Widget(&headings, &values); | 273 | iWidget *page = makeTwoColumns_Widget(&headings, &values); |
248 | /* Token. */ | ||
249 | d->token = addTwoColumnDialogInputField_Widget( | ||
250 | headings, values, "${upload.token}", "upload.token", iClob(new_InputWidget(0))); | ||
251 | setHint_InputWidget(d->token, "${hint.upload.token}"); | ||
252 | setFixedSize_Widget(as_Widget(d->token), init_I2(50 * gap_UI, -1)); | ||
253 | /* Identity. */ | 274 | /* Identity. */ |
254 | const iArray * identItems = makeIdentityItems_UploadWidget_(d); | 275 | const iArray * identItems = makeIdentityItems_UploadWidget_(d); |
255 | const iMenuItem *items = constData_Array(identItems); | 276 | const iMenuItem *items = constData_Array(identItems); |
256 | const size_t numItems = size_Array(identItems); | 277 | const size_t numItems = size_Array(identItems); |
257 | iLabelWidget * ident = makeMenuButton_LabelWidget("${upload.id}", items, numItems); | 278 | d->ident = makeMenuButton_LabelWidget("${upload.id}", items, numItems); |
258 | setTextCStr_LabelWidget(ident, items[findWidestLabel_MenuItem(items, numItems)].label); | 279 | setTextCStr_LabelWidget(d->ident, items[findWidestLabel_MenuItem(items, numItems)].label); |
280 | //setFixedSize_Widget(as_Widget(d->ident), init_I2(50 * gap_UI, )); | ||
259 | addChild_Widget(headings, iClob(makeHeading_Widget("${upload.id}"))); | 281 | addChild_Widget(headings, iClob(makeHeading_Widget("${upload.id}"))); |
260 | setId_Widget(addChildFlags_Widget(values, iClob(ident), alignLeft_WidgetFlag), "upload.id"); | 282 | setId_Widget(addChildFlags_Widget(values, iClob(d->ident), alignLeft_WidgetFlag), "upload.id"); |
283 | /* Token. */ | ||
284 | d->token = addTwoColumnDialogInputField_Widget( | ||
285 | headings, values, "${upload.token}", "upload.token", iClob(new_InputWidget(0))); | ||
286 | setHint_InputWidget(d->token, "${hint.upload.token}"); | ||
287 | setFixedSize_Widget(as_Widget(d->token), init_I2(50 * gap_UI, -1)); | ||
261 | addChild_Widget(w, iClob(page)); | 288 | addChild_Widget(w, iClob(page)); |
262 | } | 289 | } |
263 | /* Buttons. */ { | 290 | /* Buttons. */ { |
@@ -272,7 +299,12 @@ void init_UploadWidget(iUploadWidget *d) { | |||
272 | } | 299 | } |
273 | resizeToLargestPage_Widget(tabs); | 300 | resizeToLargestPage_Widget(tabs); |
274 | arrange_Widget(w); | 301 | arrange_Widget(w); |
302 | setFixedSize_Widget(as_Widget(d->path), init_I2(width_Widget(tabs) - width_Widget(d->info), -1)); | ||
303 | setFixedSize_Widget(as_Widget(d->mime), init_I2(width_Widget(tabs) - 3 * gap_UI - | ||
304 | left_Rect(parent_Widget(d->mime)->rect), -1)); | ||
275 | setFixedSize_Widget(as_Widget(d->token), init_I2(width_Widget(tabs) - left_Rect(parent_Widget(d->token)->rect), -1)); | 305 | setFixedSize_Widget(as_Widget(d->token), init_I2(width_Widget(tabs) - left_Rect(parent_Widget(d->token)->rect), -1)); |
306 | setFixedSize_Widget(as_Widget(d->ident), init_I2(width_Widget(d->token), | ||
307 | lineHeight_Text(uiLabel_FontId) + 2 * gap_UI)); | ||
276 | setFlags_Widget(as_Widget(d->token), expand_WidgetFlag, iTrue); | 308 | setFlags_Widget(as_Widget(d->token), expand_WidgetFlag, iTrue); |
277 | setFocus_Widget(as_Widget(d->input)); | 309 | setFocus_Widget(as_Widget(d->input)); |
278 | } | 310 | } |
@@ -339,8 +371,24 @@ static void setUrlPort_UploadWidget_(iUploadWidget *d, const iString *url, uint1 | |||
339 | appendRange_String(&d->url, (iRangecc){ parts.scheme.end, parts.host.end }); | 371 | appendRange_String(&d->url, (iRangecc){ parts.scheme.end, parts.host.end }); |
340 | appendFormat_String(&d->url, ":%u", overridePort ? overridePort : titanPortForUrl_(url)); | 372 | appendFormat_String(&d->url, ":%u", overridePort ? overridePort : titanPortForUrl_(url)); |
341 | appendRange_String(&d->url, (iRangecc){ parts.path.start, constEnd_String(url) }); | 373 | appendRange_String(&d->url, (iRangecc){ parts.path.start, constEnd_String(url) }); |
342 | setText_LabelWidget(d->info, &d->url); | 374 | const iRangecc siteRoot = urlRoot_String(&d->url); |
343 | arrange_Widget(as_Widget(d)); | 375 | setTextCStr_LabelWidget(d->info, cstr_Rangecc((iRangecc){ urlHost_String(&d->url).start, |
376 | siteRoot.end })); | ||
377 | /* From root onwards, the URL is editable. */ | ||
378 | setTextCStr_InputWidget(d->path, | ||
379 | cstr_Rangecc((iRangecc){ siteRoot.end, constEnd_String(&d->url) })); | ||
380 | if (!cmp_String(text_InputWidget(d->path), "/")) { | ||
381 | setTextCStr_InputWidget(d->path, ""); /* might as well show the hint */ | ||
382 | } | ||
383 | if (isUsingPanelLayout_Mobile()) { | ||
384 | arrange_Widget(as_Widget(d)); /* a wrapped label */ | ||
385 | } | ||
386 | else { | ||
387 | setFixedSize_Widget(as_Widget(d->path), | ||
388 | init_I2(width_Widget(findChild_Widget(as_Widget(d), "upload.tabs")) - | ||
389 | width_Widget(d->info), | ||
390 | -1)); | ||
391 | } | ||
344 | } | 392 | } |
345 | 393 | ||
346 | void setUrl_UploadWidget(iUploadWidget *d, const iString *url) { | 394 | void setUrl_UploadWidget(iUploadWidget *d, const iString *url) { |
@@ -353,6 +401,10 @@ void setResponseViewer_UploadWidget(iUploadWidget *d, iDocumentWidget *doc) { | |||
353 | d->viewer = doc; | 401 | d->viewer = doc; |
354 | } | 402 | } |
355 | 403 | ||
404 | void setText_UploadWidget(iUploadWidget *d, const iString *text) { | ||
405 | setText_InputWidget(findChild_Widget(as_Widget(d), "upload.text"), text); | ||
406 | } | ||
407 | |||
356 | static iWidget *acceptButton_UploadWidget_(iUploadWidget *d) { | 408 | static iWidget *acceptButton_UploadWidget_(iUploadWidget *d) { |
357 | return lastChild_Widget(findChild_Widget(as_Widget(d), "dialogbuttons")); | 409 | return lastChild_Widget(findChild_Widget(as_Widget(d), "dialogbuttons")); |
358 | } | 410 | } |
@@ -396,6 +448,18 @@ static void showOrHideUploadButton_UploadWidget_(iUploadWidget *d) { | |||
396 | } | 448 | } |
397 | } | 449 | } |
398 | 450 | ||
451 | static const iString *requestUrl_UploadWidget_(const iUploadWidget *d) { | ||
452 | const iRangecc siteRoot = urlRoot_String(&d->url); | ||
453 | iString *reqUrl = collectNew_String(); | ||
454 | setRange_String(reqUrl, (iRangecc){ constBegin_String(&d->url), siteRoot.end }); | ||
455 | const iString *path = text_InputWidget(d->path); | ||
456 | if (!startsWith_String(path, "/")) { | ||
457 | appendCStr_String(reqUrl, "/"); | ||
458 | } | ||
459 | append_String(reqUrl, path); | ||
460 | return reqUrl; | ||
461 | } | ||
462 | |||
399 | static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { | 463 | static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { |
400 | iWidget *w = as_Widget(d); | 464 | iWidget *w = as_Widget(d); |
401 | const char *cmd = command_UserEvent(ev); | 465 | const char *cmd = command_UserEvent(ev); |
@@ -405,8 +469,22 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { | |||
405 | } | 469 | } |
406 | else if (equal_Command(cmd, "panel.changed")) { | 470 | else if (equal_Command(cmd, "panel.changed")) { |
407 | showOrHideUploadButton_UploadWidget_(d); | 471 | showOrHideUploadButton_UploadWidget_(d); |
472 | if (currentPanelIndex_Mobile(w) == 0) { | ||
473 | setFocus_Widget(as_Widget(d->input)); | ||
474 | } | ||
475 | else { | ||
476 | setFocus_Widget(NULL); | ||
477 | } | ||
478 | refresh_Widget(d->input); | ||
479 | return iFalse; | ||
480 | } | ||
481 | #if defined (iPlatformAppleMobile) | ||
482 | else if (deviceType_App() != desktop_AppDeviceType && equal_Command(cmd, "menu.opened")) { | ||
483 | setFocus_Widget(NULL); /* overlaid text fields! */ | ||
484 | refresh_Widget(d->input); | ||
408 | return iFalse; | 485 | return iFalse; |
409 | } | 486 | } |
487 | #endif | ||
410 | else if (equal_Command(cmd, "upload.cancel")) { | 488 | else if (equal_Command(cmd, "upload.cancel")) { |
411 | setupSheetTransition_Mobile(w, iFalse); | 489 | setupSheetTransition_Mobile(w, iFalse); |
412 | destroy_Widget(w); | 490 | destroy_Widget(w); |
@@ -444,6 +522,43 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { | |||
444 | updateIdentityDropdown_UploadWidget_(d); | 522 | updateIdentityDropdown_UploadWidget_(d); |
445 | return iTrue; | 523 | return iTrue; |
446 | } | 524 | } |
525 | if (isCommand_Widget(w, ev, "upload.editmenu.open")) { | ||
526 | setFocus_Widget(NULL); | ||
527 | refresh_Widget(as_Widget(d->input)); | ||
528 | iWidget *editMenu = makeMenu_Widget(root_Widget(w), (iMenuItem[]){ | ||
529 | { select_Icon " ${menu.selectall}", 0, 0, "upload.text.selectall" }, | ||
530 | { export_Icon " ${menu.upload.export}", 0, 0, "upload.text.export" }, | ||
531 | { "---" }, | ||
532 | { delete_Icon " " uiTextCaution_ColorEscape "${menu.upload.delete}", 0, 0, "upload.text.delete" } | ||
533 | }, 4); | ||
534 | openMenu_Widget(editMenu, topLeft_Rect(bounds_Widget(as_Widget(d->input)))); | ||
535 | return iTrue; | ||
536 | } | ||
537 | if (isCommand_UserEvent(ev, "upload.text.export")) { | ||
538 | #if defined (iPlatformAppleMobile) | ||
539 | openTextActivityView_iOS(text_InputWidget(d->input)); | ||
540 | #endif | ||
541 | return iTrue; | ||
542 | } | ||
543 | if (isCommand_UserEvent(ev, "upload.text.delete")) { | ||
544 | if (argLabel_Command(command_UserEvent(ev), "confirmed")) { | ||
545 | setTextCStr_InputWidget(d->input, ""); | ||
546 | setFocus_Widget(as_Widget(d->input)); | ||
547 | } | ||
548 | else { | ||
549 | openMenu_Widget(makeMenu_Widget(root_Widget(w), (iMenuItem[]){ | ||
550 | { delete_Icon " " uiTextCaution_ColorEscape "${menu.upload.delete.confirm}", 0, 0, | ||
551 | "upload.text.delete confirmed:1" } | ||
552 | }, 1), zero_I2()); | ||
553 | } | ||
554 | return iTrue; | ||
555 | } | ||
556 | if (isCommand_UserEvent(ev, "upload.text.selectall")) { | ||
557 | setFocus_Widget(as_Widget(d->input)); | ||
558 | refresh_Widget(as_Widget(d->input)); | ||
559 | postCommand_Widget(d->input, "input.selectall"); | ||
560 | return iTrue; | ||
561 | } | ||
447 | if (isCommand_Widget(w, ev, "upload.accept")) { | 562 | if (isCommand_Widget(w, ev, "upload.accept")) { |
448 | iBool isText; | 563 | iBool isText; |
449 | iWidget *tabs = findChild_Widget(w, "upload.tabs"); | 564 | iWidget *tabs = findChild_Widget(w, "upload.tabs"); |
@@ -464,7 +579,7 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { | |||
464 | d->request = new_GmRequest(certs_App()); | 579 | d->request = new_GmRequest(certs_App()); |
465 | setSendProgressFunc_GmRequest(d->request, updateProgress_UploadWidget_); | 580 | setSendProgressFunc_GmRequest(d->request, updateProgress_UploadWidget_); |
466 | setUserData_Object(d->request, d); | 581 | setUserData_Object(d->request, d); |
467 | setUrl_GmRequest(d->request, &d->url); | 582 | setUrl_GmRequest(d->request, requestUrl_UploadWidget_(d)); |
468 | const iString *site = collectNewRange_String(urlRoot_String(&d->url)); | 583 | const iString *site = collectNewRange_String(urlRoot_String(&d->url)); |
469 | switch (d->idMode) { | 584 | switch (d->idMode) { |
470 | case none_UploadIdentity: | 585 | case none_UploadIdentity: |
@@ -540,10 +655,15 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { | |||
540 | return iTrue; | 655 | return iTrue; |
541 | } | 656 | } |
542 | else if (isCommand_Widget(w, ev, "input.resized")) { | 657 | else if (isCommand_Widget(w, ev, "input.resized")) { |
543 | resizeToLargestPage_Widget(findChild_Widget(w, "upload.tabs")); | 658 | if (!isUsingPanelLayout_Mobile()) { |
544 | arrange_Widget(w); | 659 | resizeToLargestPage_Widget(findChild_Widget(w, "upload.tabs")); |
545 | refresh_Widget(w); | 660 | arrange_Widget(w); |
546 | return iTrue; | 661 | refresh_Widget(w); |
662 | return iTrue; | ||
663 | } | ||
664 | else { | ||
665 | refresh_Widget(as_Widget(d->input)); | ||
666 | } | ||
547 | } | 667 | } |
548 | else if (isCommand_Widget(w, ev, "upload.pickfile")) { | 668 | else if (isCommand_Widget(w, ev, "upload.pickfile")) { |
549 | #if defined (iPlatformAppleMobile) | 669 | #if defined (iPlatformAppleMobile) |
diff --git a/src/ui/uploadwidget.h b/src/ui/uploadwidget.h index 5a7de45e..1cc1f193 100644 --- a/src/ui/uploadwidget.h +++ b/src/ui/uploadwidget.h | |||
@@ -31,3 +31,4 @@ iDeclareType(DocumentWidget) | |||
31 | 31 | ||
32 | void setUrl_UploadWidget (iUploadWidget *, const iString *url); | 32 | void setUrl_UploadWidget (iUploadWidget *, const iString *url); |
33 | void setResponseViewer_UploadWidget (iUploadWidget *, iDocumentWidget *doc); | 33 | void setResponseViewer_UploadWidget (iUploadWidget *, iDocumentWidget *doc); |
34 | void setText_UploadWidget (iUploadWidget *, const iString *text); | ||
diff --git a/src/ui/util.c b/src/ui/util.c index 912e1d37..31907721 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -479,12 +479,15 @@ void init_SmoothScroll(iSmoothScroll *d, iWidget *owner, iSmoothScrollNotifyFunc | |||
479 | reset_SmoothScroll(d); | 479 | reset_SmoothScroll(d); |
480 | d->widget = owner; | 480 | d->widget = owner; |
481 | d->notify = notify; | 481 | d->notify = notify; |
482 | d->pullActionTriggered = 0; | ||
483 | d->flags = 0; | ||
482 | } | 484 | } |
483 | 485 | ||
484 | void reset_SmoothScroll(iSmoothScroll *d) { | 486 | void reset_SmoothScroll(iSmoothScroll *d) { |
485 | init_Anim(&d->pos, 0); | 487 | init_Anim(&d->pos, 0); |
486 | d->max = 0; | 488 | d->max = 0; |
487 | d->overscroll = (deviceType_App() != desktop_AppDeviceType ? 100 * gap_UI : 0); | 489 | d->overscroll = (deviceType_App() != desktop_AppDeviceType ? 100 * gap_UI : 0); |
490 | d->pullActionTriggered = 0; | ||
488 | } | 491 | } |
489 | 492 | ||
490 | void setMax_SmoothScroll(iSmoothScroll *d, int max) { | 493 | void setMax_SmoothScroll(iSmoothScroll *d, int max) { |
@@ -518,6 +521,29 @@ iBool isFinished_SmoothScroll(const iSmoothScroll *d) { | |||
518 | return isFinished_Anim(&d->pos); | 521 | return isFinished_Anim(&d->pos); |
519 | } | 522 | } |
520 | 523 | ||
524 | iLocalDef int pullActionThreshold_SmoothScroll_(const iSmoothScroll *d) { | ||
525 | return d->overscroll * 6 / 10; | ||
526 | } | ||
527 | |||
528 | float pullActionPos_SmoothScroll(const iSmoothScroll *d) { | ||
529 | if (d->pullActionTriggered >= 1) { | ||
530 | return 1.0f; | ||
531 | } | ||
532 | float pos = overscroll_SmoothScroll_(d); | ||
533 | if (pos >= 0.0f) { | ||
534 | return 0.0f; | ||
535 | } | ||
536 | pos = -pos / (float) pullActionThreshold_SmoothScroll_(d); | ||
537 | return iMin(pos, 1.0f); | ||
538 | } | ||
539 | |||
540 | static void checkPullAction_SmoothScroll_(iSmoothScroll *d) { | ||
541 | if (d->pullActionTriggered == 1 && d->widget) { | ||
542 | postCommand_Widget(d->widget, "pullaction"); | ||
543 | d->pullActionTriggered = 2; /* pending handling */ | ||
544 | } | ||
545 | } | ||
546 | |||
521 | void moveSpan_SmoothScroll(iSmoothScroll *d, int offset, uint32_t span) { | 547 | void moveSpan_SmoothScroll(iSmoothScroll *d, int offset, uint32_t span) { |
522 | #if !defined (iPlatformMobile) | 548 | #if !defined (iPlatformMobile) |
523 | if (!prefs_App()->smoothScrolling) { | 549 | if (!prefs_App()->smoothScrolling) { |
@@ -525,16 +551,19 @@ void moveSpan_SmoothScroll(iSmoothScroll *d, int offset, uint32_t span) { | |||
525 | } | 551 | } |
526 | #endif | 552 | #endif |
527 | int destY = targetValue_Anim(&d->pos) + offset; | 553 | int destY = targetValue_Anim(&d->pos) + offset; |
554 | if (d->flags & pullDownAction_SmoothScrollFlag && destY < -pullActionThreshold_SmoothScroll_(d)) { | ||
555 | if (d->pullActionTriggered == 0) { | ||
556 | d->pullActionTriggered = iTrue; | ||
557 | #if defined (iPlatformAppleMobile) | ||
558 | playHapticEffect_iOS(tap_HapticEffect); | ||
559 | #endif | ||
560 | } | ||
561 | } | ||
528 | if (destY < -d->overscroll) { | 562 | if (destY < -d->overscroll) { |
529 | destY = -d->overscroll; | 563 | destY = -d->overscroll; |
530 | } | 564 | } |
531 | if (d->max > 0) { | 565 | if (destY >= d->max + d->overscroll) { |
532 | if (destY >= d->max + d->overscroll) { | 566 | destY = d->max + d->overscroll; |
533 | destY = d->max + d->overscroll; | ||
534 | } | ||
535 | } | ||
536 | else { | ||
537 | destY = 0; | ||
538 | } | 567 | } |
539 | if (span) { | 568 | if (span) { |
540 | setValueEased_Anim(&d->pos, destY, span); | 569 | setValueEased_Anim(&d->pos, destY, span); |
@@ -552,6 +581,7 @@ void moveSpan_SmoothScroll(iSmoothScroll *d, int offset, uint32_t span) { | |||
552 | // printf("remaining: %f dur: %d\n", remaining, duration); | 581 | // printf("remaining: %f dur: %d\n", remaining, duration); |
553 | d->pos.bounce = (osDelta < 0 ? -1 : 1) * | 582 | d->pos.bounce = (osDelta < 0 ? -1 : 1) * |
554 | iMini(5 * d->overscroll, remaining * remaining * 0.00005f); | 583 | iMini(5 * d->overscroll, remaining * remaining * 0.00005f); |
584 | checkPullAction_SmoothScroll_(d); | ||
555 | } | 585 | } |
556 | } | 586 | } |
557 | if (d->notify) { | 587 | if (d->notify) { |
@@ -570,6 +600,7 @@ iBool processEvent_SmoothScroll(iSmoothScroll *d, const SDL_Event *ev) { | |||
570 | moveSpan_SmoothScroll(d, -osDelta, 100 * sqrt(iAbs(osDelta) / gap_UI)); | 600 | moveSpan_SmoothScroll(d, -osDelta, 100 * sqrt(iAbs(osDelta) / gap_UI)); |
571 | d->pos.flags = easeOut_AnimFlag | muchSofter_AnimFlag; | 601 | d->pos.flags = easeOut_AnimFlag | muchSofter_AnimFlag; |
572 | } | 602 | } |
603 | checkPullAction_SmoothScroll_(d); | ||
573 | return iTrue; | 604 | return iTrue; |
574 | } | 605 | } |
575 | return iFalse; | 606 | return iFalse; |
@@ -620,7 +651,7 @@ static iBool isCommandIgnoredByMenus_(const char *cmd) { | |||
620 | if (equal_Command(cmd, "window.focus.lost") || | 651 | if (equal_Command(cmd, "window.focus.lost") || |
621 | equal_Command(cmd, "window.focus.gained")) return iTrue; | 652 | equal_Command(cmd, "window.focus.gained")) return iTrue; |
622 | /* TODO: Perhaps a common way of indicating which commands are notifications and should not | 653 | /* TODO: Perhaps a common way of indicating which commands are notifications and should not |
623 | be reacted to by menus? */ | 654 | be reacted to by menus?! */ |
624 | return equal_Command(cmd, "media.updated") || | 655 | return equal_Command(cmd, "media.updated") || |
625 | equal_Command(cmd, "media.player.update") || | 656 | equal_Command(cmd, "media.player.update") || |
626 | startsWith_CStr(cmd, "feeds.update.") || | 657 | startsWith_CStr(cmd, "feeds.update.") || |
@@ -640,13 +671,16 @@ static iBool isCommandIgnoredByMenus_(const char *cmd) { | |||
640 | equal_Command(cmd, "window.reload.update") || | 671 | equal_Command(cmd, "window.reload.update") || |
641 | equal_Command(cmd, "window.mouse.exited") || | 672 | equal_Command(cmd, "window.mouse.exited") || |
642 | equal_Command(cmd, "window.mouse.entered") || | 673 | equal_Command(cmd, "window.mouse.entered") || |
674 | equal_Command(cmd, "input.backup") || | ||
675 | equal_Command(cmd, "input.ended") || | ||
676 | equal_Command(cmd, "focus.lost") || | ||
643 | (equal_Command(cmd, "mouse.clicked") && !arg_Command(cmd)); /* button released */ | 677 | (equal_Command(cmd, "mouse.clicked") && !arg_Command(cmd)); /* button released */ |
644 | } | 678 | } |
645 | 679 | ||
646 | static iLabelWidget *parentMenuButton_(const iWidget *menu) { | 680 | static iLabelWidget *parentMenuButton_(const iWidget *menu) { |
647 | if (isInstance_Object(menu->parent, &Class_LabelWidget)) { | 681 | if (isInstance_Object(menu->parent, &Class_LabelWidget)) { |
648 | iLabelWidget *button = (iLabelWidget *) menu->parent; | 682 | iLabelWidget *button = (iLabelWidget *) menu->parent; |
649 | if (!cmp_String(command_LabelWidget(button), "menu.open")) { | 683 | if (equal_Command(cstr_String(command_LabelWidget(button)), "menu.open")) { |
650 | return button; | 684 | return button; |
651 | } | 685 | } |
652 | } | 686 | } |
@@ -671,6 +705,21 @@ static iBool menuHandler_(iWidget *menu, const char *cmd) { | |||
671 | closeMenu_Widget(menu); | 705 | closeMenu_Widget(menu); |
672 | return iTrue; | 706 | return iTrue; |
673 | } | 707 | } |
708 | if (equal_Command(cmd, "cancel") && pointerLabel_Command(cmd, "menu") == menu) { | ||
709 | return iFalse; | ||
710 | } | ||
711 | if (equal_Command(cmd, "contextclick") && pointer_Command(cmd) == menu) { | ||
712 | return iFalse; | ||
713 | } | ||
714 | if (deviceType_App() == phone_AppDeviceType && equal_Command(cmd, "keyboard.changed") && | ||
715 | arg_Command(cmd) == 0) { | ||
716 | /* May need to reposition the menu. */ | ||
717 | menu->rect.pos = windowToLocal_Widget( | ||
718 | menu, | ||
719 | init_I2(left_Rect(bounds_Widget(menu)), | ||
720 | bottom_Rect(safeRect_Root(menu->root)) - menu->rect.size.y)); | ||
721 | return iFalse; | ||
722 | } | ||
674 | if (!isCommandIgnoredByMenus_(cmd)) { | 723 | if (!isCommandIgnoredByMenus_(cmd)) { |
675 | closeMenu_Widget(menu); | 724 | closeMenu_Widget(menu); |
676 | } | 725 | } |
@@ -733,13 +782,16 @@ void makeMenuItems_Widget(iWidget *menu, const iMenuItem *items, size_t n) { | |||
733 | noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | | 782 | noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | |
734 | drawKey_WidgetFlag | itemFlags); | 783 | drawKey_WidgetFlag | itemFlags); |
735 | setWrap_LabelWidget(label, isInfo); | 784 | setWrap_LabelWidget(label, isInfo); |
785 | if (!isInfo) { | ||
736 | haveIcons |= checkIcon_LabelWidget(label); | 786 | haveIcons |= checkIcon_LabelWidget(label); |
737 | updateSize_LabelWidget(label); /* drawKey was set */ | 787 | } |
738 | setFlags_Widget(as_Widget(label), disabled_WidgetFlag, isDisabled); | 788 | setFlags_Widget(as_Widget(label), disabled_WidgetFlag, isDisabled); |
739 | if (isInfo) { | 789 | if (isInfo) { |
740 | setFlags_Widget(as_Widget(label), fixedHeight_WidgetFlag, iTrue); /* wrap changes height */ | 790 | setFlags_Widget(as_Widget(label), resizeToParentWidth_WidgetFlag | |
791 | fixedHeight_WidgetFlag, iTrue); /* wrap changes height */ | ||
741 | setTextColor_LabelWidget(label, uiTextAction_ColorId); | 792 | setTextColor_LabelWidget(label, uiTextAction_ColorId); |
742 | } | 793 | } |
794 | updateSize_LabelWidget(label); /* drawKey was set */ | ||
743 | } | 795 | } |
744 | } | 796 | } |
745 | if (deviceType_App() == phone_AppDeviceType) { | 797 | if (deviceType_App() == phone_AppDeviceType) { |
@@ -861,12 +913,8 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { | |||
861 | setFlags_Widget(menu, | 913 | setFlags_Widget(menu, |
862 | keepOnTop_WidgetFlag | collapse_WidgetFlag | hidden_WidgetFlag | | 914 | keepOnTop_WidgetFlag | collapse_WidgetFlag | hidden_WidgetFlag | |
863 | arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag | | 915 | arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag | |
864 | resizeChildrenToWidestChild_WidgetFlag | overflowScrollable_WidgetFlag | | 916 | resizeChildrenToWidestChild_WidgetFlag | overflowScrollable_WidgetFlag, |
865 | (isPortraitPhone_App() ? drawBackgroundToVerticalSafeArea_WidgetFlag : 0), | ||
866 | iTrue); | 917 | iTrue); |
867 | if (!isPortraitPhone_App()) { | ||
868 | setFrameColor_Widget(menu, uiBackgroundSelected_ColorId); | ||
869 | } | ||
870 | makeMenuItems_Widget(menu, items, n); | 918 | makeMenuItems_Widget(menu, items, n); |
871 | addChild_Widget(parent, menu); | 919 | addChild_Widget(parent, menu); |
872 | iRelease(menu); /* owned by parent now */ | 920 | iRelease(menu); /* owned by parent now */ |
@@ -884,6 +932,7 @@ void openMenu_Widget(iWidget *d, iInt2 windowCoord) { | |||
884 | 932 | ||
885 | static void updateMenuItemFonts_Widget_(iWidget *d) { | 933 | static void updateMenuItemFonts_Widget_(iWidget *d) { |
886 | const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); | 934 | const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); |
935 | const iBool isMobile = (deviceType_App() != desktop_AppDeviceType); | ||
887 | const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0; | 936 | const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0; |
888 | iForEach(ObjectList, i, children_Widget(d)) { | 937 | iForEach(ObjectList, i, children_Widget(d)) { |
889 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | 938 | if (isInstance_Object(i.object, &Class_LabelWidget)) { |
@@ -892,16 +941,16 @@ static void updateMenuItemFonts_Widget_(iWidget *d) { | |||
892 | if (isWrapped_LabelWidget(label)) { | 941 | if (isWrapped_LabelWidget(label)) { |
893 | continue; | 942 | continue; |
894 | } | 943 | } |
895 | if (deviceType_App() == desktop_AppDeviceType) { | 944 | switch (deviceType_App()) { |
945 | case desktop_AppDeviceType: | ||
896 | setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); | 946 | setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); |
897 | } | 947 | break; |
898 | else if (isPortraitPhone) { | 948 | case tablet_AppDeviceType: |
899 | if (!isSlidePanel) { | 949 | setFont_LabelWidget(label, isCaution ? uiLabelMediumBold_FontId : uiLabelMedium_FontId); |
950 | break; | ||
951 | case phone_AppDeviceType: | ||
900 | setFont_LabelWidget(label, isCaution ? uiLabelBigBold_FontId : uiLabelBig_FontId); | 952 | setFont_LabelWidget(label, isCaution ? uiLabelBigBold_FontId : uiLabelBig_FontId); |
901 | } | 953 | break; |
902 | } | ||
903 | else { | ||
904 | setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId); | ||
905 | } | 954 | } |
906 | } | 955 | } |
907 | else if (childCount_Widget(i.object)) { | 956 | else if (childCount_Widget(i.object)) { |
@@ -1024,20 +1073,28 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) { | |||
1024 | #else | 1073 | #else |
1025 | const iRect rootRect = rect_Root(d->root); | 1074 | const iRect rootRect = rect_Root(d->root); |
1026 | const iInt2 rootSize = rootRect.size; | 1075 | const iInt2 rootSize = rootRect.size; |
1027 | const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); | 1076 | const iBool isPhone = (deviceType_App() == phone_AppDeviceType); |
1077 | const iBool isPortraitPhone = (isPhone && isPortrait_App()); | ||
1028 | const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0; | 1078 | const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0; |
1029 | if (postCommands) { | 1079 | if (postCommands) { |
1030 | postCommand_App("cancel"); /* dismiss any other menus */ | 1080 | postCommandf_App("cancel menu:%p", d); /* dismiss any other menus */ |
1031 | } | 1081 | } |
1032 | /* Menu closes when commands are emitted, so handle any pending ones beforehand. */ | 1082 | /* Menu closes when commands are emitted, so handle any pending ones beforehand. */ |
1033 | processEvents_App(postedEventsOnly_AppEventMode); | 1083 | processEvents_App(postedEventsOnly_AppEventMode); |
1034 | setFlags_Widget(d, hidden_WidgetFlag, iFalse); | 1084 | setFlags_Widget(d, hidden_WidgetFlag, iFalse); |
1035 | setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue); | 1085 | setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue); |
1036 | setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); | 1086 | setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); |
1087 | if (!isPortraitPhone) { | ||
1088 | setFrameColor_Widget(d, uiBackgroundSelected_ColorId); | ||
1089 | } | ||
1090 | else { | ||
1091 | setFrameColor_Widget(d, none_ColorId); | ||
1092 | } | ||
1037 | arrange_Widget(d); /* need to know the height */ | 1093 | arrange_Widget(d); /* need to know the height */ |
1038 | iBool allowOverflow = iFalse; | 1094 | iBool allowOverflow = iFalse; |
1039 | /* A vertical offset determined by a possible selected label in the menu. */ | 1095 | /* A vertical offset determined by a possible selected label in the menu. */ |
1040 | if (windowCoord.y < rootSize.y - lineHeight_Text(uiNormal_FontSize) * 3) { | 1096 | if (deviceType_App() == desktop_AppDeviceType && |
1097 | windowCoord.y < rootSize.y - lineHeight_Text(uiNormal_FontSize) * 3) { | ||
1041 | iConstForEach(ObjectList, child, children_Widget(d)) { | 1098 | iConstForEach(ObjectList, child, children_Widget(d)) { |
1042 | const iWidget *item = constAs_Widget(child.object); | 1099 | const iWidget *item = constAs_Widget(child.object); |
1043 | if (flags_Widget(item) & selected_WidgetFlag) { | 1100 | if (flags_Widget(item) & selected_WidgetFlag) { |
@@ -1104,22 +1161,35 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) { | |||
1104 | } | 1161 | } |
1105 | #endif | 1162 | #endif |
1106 | raise_Widget(d); | 1163 | raise_Widget(d); |
1107 | if (isPortraitPhone) { | 1164 | if (deviceType_App() != desktop_AppDeviceType) { |
1108 | setFlags_Widget(d, arrangeWidth_WidgetFlag | resizeChildrenToWidestChild_WidgetFlag, iFalse); | 1165 | setFlags_Widget(d, arrangeWidth_WidgetFlag | resizeChildrenToWidestChild_WidgetFlag, |
1109 | setFlags_Widget(d, resizeWidthOfChildren_WidgetFlag | drawBackgroundToBottom_WidgetFlag, iTrue); | 1166 | !isPhone); |
1110 | if (!isSlidePanel) { | 1167 | setFlags_Widget(d, |
1111 | setFlags_Widget(d, borderTop_WidgetFlag, iTrue); | 1168 | resizeWidthOfChildren_WidgetFlag | drawBackgroundToBottom_WidgetFlag | |
1169 | drawBackgroundToVerticalSafeArea_WidgetFlag, | ||
1170 | isPhone); | ||
1171 | if (isPhone) { | ||
1172 | setFlags_Widget(d, borderTop_WidgetFlag, !isSlidePanel && isPortrait_App()); /* menu is otherwise frameless */ | ||
1173 | setFixedSize_Widget(d, init_I2(iMin(rootSize.x, rootSize.y), -1)); | ||
1174 | } | ||
1175 | else { | ||
1176 | d->rect.size.x = 0; | ||
1112 | } | 1177 | } |
1113 | d->rect.size.x = rootSize.x; | ||
1114 | } | 1178 | } |
1115 | updateMenuItemFonts_Widget_(d); | 1179 | updateMenuItemFonts_Widget_(d); |
1116 | arrange_Widget(d); | 1180 | arrange_Widget(d); |
1117 | if (isPortraitPhone) { | 1181 | if (!isSlidePanel) { |
1182 | /* LAYOUT BUG: Height of wrapped menu items is incorrect with a single arrange! */ | ||
1183 | arrange_Widget(d); | ||
1184 | } | ||
1185 | if (deviceType_App() == phone_AppDeviceType) { | ||
1118 | if (isSlidePanel) { | 1186 | if (isSlidePanel) { |
1119 | d->rect.pos = zero_I2(); | 1187 | d->rect.pos = zero_I2(); |
1120 | } | 1188 | } |
1121 | else { | 1189 | else { |
1122 | d->rect.pos = init_I2(0, rootSize.y); | 1190 | d->rect.pos = windowToLocal_Widget(d, |
1191 | init_I2(rootSize.x / 2 - d->rect.size.x / 2, | ||
1192 | rootSize.y)); | ||
1123 | } | 1193 | } |
1124 | } | 1194 | } |
1125 | else if (menuOpenFlags & center_MenuOpenFlags) { | 1195 | else if (menuOpenFlags & center_MenuOpenFlags) { |
@@ -1143,6 +1213,9 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) { | |||
1143 | leftExcess += l; | 1213 | leftExcess += l; |
1144 | rightExcess += r; | 1214 | rightExcess += r; |
1145 | } | 1215 | } |
1216 | #elif defined (iPlatformMobile) | ||
1217 | /* Reserve space for the keyboard. */ | ||
1218 | bottomExcess += get_MainWindow()->keyboardHeight; | ||
1146 | #endif | 1219 | #endif |
1147 | if (!allowOverflow) { | 1220 | if (!allowOverflow) { |
1148 | if (bottomExcess > 0 && (!isPortraitPhone || !isSlidePanel)) { | 1221 | if (bottomExcess > 0 && (!isPortraitPhone || !isSlidePanel)) { |
@@ -1203,6 +1276,15 @@ iLabelWidget *findMenuItem_Widget(iWidget *menu, const char *command) { | |||
1203 | return NULL; | 1276 | return NULL; |
1204 | } | 1277 | } |
1205 | 1278 | ||
1279 | iWidget *findUserData_Widget(iWidget *d, void *userData) { | ||
1280 | iForEach(ObjectList, i, children_Widget(d)) { | ||
1281 | if (userData_Object(i.object) == userData) { | ||
1282 | return i.object; | ||
1283 | } | ||
1284 | } | ||
1285 | return NULL; | ||
1286 | } | ||
1287 | |||
1206 | void setMenuItemDisabled_Widget(iWidget *menu, const char *command, iBool disable) { | 1288 | void setMenuItemDisabled_Widget(iWidget *menu, const char *command, iBool disable) { |
1207 | if (flags_Widget(menu) & nativeMenu_WidgetFlag) { | 1289 | if (flags_Widget(menu) & nativeMenu_WidgetFlag) { |
1208 | setDisabled_NativeMenuItem(findNativeMenuItem_Widget(menu, command), disable); | 1290 | setDisabled_NativeMenuItem(findNativeMenuItem_Widget(menu, command), disable); |
@@ -1269,6 +1351,12 @@ const iString *removeMenuItemLabelPrefixes_String(const iString *d) { | |||
1269 | return collect_String(str); | 1351 | return collect_String(str); |
1270 | } | 1352 | } |
1271 | 1353 | ||
1354 | static const iString *replaceNewlinesWithDash_(const iString *str) { | ||
1355 | iString *mod = copy_String(str); | ||
1356 | replace_String(mod, "\n", " "); | ||
1357 | return collect_String(mod); | ||
1358 | } | ||
1359 | |||
1272 | void updateDropdownSelection_LabelWidget(iLabelWidget *dropButton, const char *selectedCommand) { | 1360 | void updateDropdownSelection_LabelWidget(iLabelWidget *dropButton, const char *selectedCommand) { |
1273 | if (!dropButton) { | 1361 | if (!dropButton) { |
1274 | return; | 1362 | return; |
@@ -1279,8 +1367,9 @@ void updateDropdownSelection_LabelWidget(iLabelWidget *dropButton, const char *s | |||
1279 | iMenuItem *item = findNativeMenuItem_Widget(menu, selectedCommand); | 1367 | iMenuItem *item = findNativeMenuItem_Widget(menu, selectedCommand); |
1280 | if (item) { | 1368 | if (item) { |
1281 | setSelected_NativeMenuItem(item, iTrue); | 1369 | setSelected_NativeMenuItem(item, iTrue); |
1282 | updateText_LabelWidget( | 1370 | updateText_LabelWidget(dropButton, |
1283 | dropButton, removeMenuItemLabelPrefixes_String(collectNewCStr_String(item->label))); | 1371 | replaceNewlinesWithDash_(removeMenuItemLabelPrefixes_String( |
1372 | collectNewCStr_String(item->label)))); | ||
1284 | checkIcon_LabelWidget(dropButton); | 1373 | checkIcon_LabelWidget(dropButton); |
1285 | } | 1374 | } |
1286 | return; | 1375 | return; |
@@ -1291,7 +1380,8 @@ void updateDropdownSelection_LabelWidget(iLabelWidget *dropButton, const char *s | |||
1291 | const iBool isSelected = endsWith_String(command_LabelWidget(item), selectedCommand); | 1380 | const iBool isSelected = endsWith_String(command_LabelWidget(item), selectedCommand); |
1292 | setFlags_Widget(as_Widget(item), selected_WidgetFlag, isSelected); | 1381 | setFlags_Widget(as_Widget(item), selected_WidgetFlag, isSelected); |
1293 | if (isSelected) { | 1382 | if (isSelected) { |
1294 | updateText_LabelWidget(dropButton, sourceText_LabelWidget(item)); | 1383 | updateText_LabelWidget(dropButton, |
1384 | replaceNewlinesWithDash_(text_LabelWidget(item))); | ||
1295 | checkIcon_LabelWidget(dropButton); | 1385 | checkIcon_LabelWidget(dropButton); |
1296 | } | 1386 | } |
1297 | } | 1387 | } |
@@ -1342,7 +1432,7 @@ static iBool tabSwitcher_(iWidget *tabs, const char *cmd) { | |||
1342 | if (equal_Command(cmd, "tabs.switch")) { | 1432 | if (equal_Command(cmd, "tabs.switch")) { |
1343 | iWidget *target = pointerLabel_Command(cmd, "page"); | 1433 | iWidget *target = pointerLabel_Command(cmd, "page"); |
1344 | if (!target) { | 1434 | if (!target) { |
1345 | target = findChild_Widget(tabs, cstr_Rangecc(range_Command(cmd, "id"))); | 1435 | target = findChild_Widget(tabs, cstr_Command(cmd, "id")); |
1346 | } | 1436 | } |
1347 | if (!target) return iFalse; | 1437 | if (!target) return iFalse; |
1348 | unfocusFocusInsideTabPage_(currentTabPage_Widget(tabs)); | 1438 | unfocusFocusInsideTabPage_(currentTabPage_Widget(tabs)); |
@@ -1377,7 +1467,7 @@ static iBool tabSwitcher_(iWidget *tabs, const char *cmd) { | |||
1377 | iWidget *nextTabs = findChild_Widget(otherRoot_Window(get_Window(), tabs->root)->widget, | 1467 | iWidget *nextTabs = findChild_Widget(otherRoot_Window(get_Window(), tabs->root)->widget, |
1378 | "doctabs"); | 1468 | "doctabs"); |
1379 | iWidget *nextPages = findChild_Widget(nextTabs, "tabs.pages"); | 1469 | iWidget *nextPages = findChild_Widget(nextTabs, "tabs.pages"); |
1380 | tabIndex = (dir < 0 ? childCount_Widget(nextPages) - 1 : 0); | 1470 | tabIndex = (int) (dir < 0 ? childCount_Widget(nextPages) - 1 : 0); |
1381 | showTabPage_Widget(nextTabs, child_Widget(nextPages, tabIndex)); | 1471 | showTabPage_Widget(nextTabs, child_Widget(nextPages, tabIndex)); |
1382 | postCommand_App("keyroot.next"); | 1472 | postCommand_App("keyroot.next"); |
1383 | } | 1473 | } |
@@ -1602,6 +1692,22 @@ void useSheetStyle_Widget(iWidget *d) { | |||
1602 | iTrue); | 1692 | iTrue); |
1603 | } | 1693 | } |
1604 | 1694 | ||
1695 | static iLabelWidget *addDialogTitle_(iWidget *dlg, const char *text, const char *id) { | ||
1696 | iLabelWidget *label = new_LabelWidget(text, NULL); | ||
1697 | addChildFlags_Widget(dlg, iClob(label), alignLeft_WidgetFlag | frameless_WidgetFlag | | ||
1698 | resizeToParentWidth_WidgetFlag); | ||
1699 | setAllCaps_LabelWidget(label, iTrue); | ||
1700 | setTextColor_LabelWidget(label, uiHeading_ColorId); | ||
1701 | if (id) { | ||
1702 | setId_Widget(as_Widget(label), id); | ||
1703 | } | ||
1704 | return label; | ||
1705 | } | ||
1706 | |||
1707 | iLabelWidget *addDialogTitle_Widget(iWidget *dlg, const char *text, const char *idOrNull) { | ||
1708 | return addDialogTitle_(dlg, text, idOrNull); | ||
1709 | } | ||
1710 | |||
1605 | static void acceptValueInput_(iWidget *dlg) { | 1711 | static void acceptValueInput_(iWidget *dlg) { |
1606 | const iInputWidget *input = findChild_Widget(dlg, "input"); | 1712 | const iInputWidget *input = findChild_Widget(dlg, "input"); |
1607 | if (!isEmpty_String(id_Widget(dlg))) { | 1713 | if (!isEmpty_String(id_Widget(dlg))) { |
@@ -1613,7 +1719,7 @@ static void acceptValueInput_(iWidget *dlg) { | |||
1613 | } | 1719 | } |
1614 | } | 1720 | } |
1615 | 1721 | ||
1616 | static void updateValueInputWidth_(iWidget *dlg) { | 1722 | static void updateValueInputSizing_(iWidget *dlg) { |
1617 | const iRect safeRoot = safeRect_Root(dlg->root); | 1723 | const iRect safeRoot = safeRect_Root(dlg->root); |
1618 | const iInt2 rootSize = safeRoot.size; | 1724 | const iInt2 rootSize = safeRoot.size; |
1619 | iWidget * title = findChild_Widget(dlg, "valueinput.title"); | 1725 | iWidget * title = findChild_Widget(dlg, "valueinput.title"); |
@@ -1623,18 +1729,19 @@ static void updateValueInputWidth_(iWidget *dlg) { | |||
1623 | } | 1729 | } |
1624 | else { | 1730 | else { |
1625 | dlg->rect.size.x = | 1731 | dlg->rect.size.x = |
1626 | iMin(rootSize.x, iMaxi(iMaxi(100 * gap_UI, title->rect.size.x), prompt->rect.size.x)); | 1732 | iMin(rootSize.x, iMaxi(iMaxi(100 * gap_UI, title ? title->rect.size.x : 0), |
1733 | prompt->rect.size.x)); | ||
1627 | } | 1734 | } |
1628 | /* Adjust the maximum number of visible lines. */ | 1735 | /* Adjust the maximum number of visible lines. */ |
1629 | int footer = 6 * gap_UI + get_MainWindow()->keyboardHeight; | 1736 | int footer = 6 * gap_UI; |
1630 | iWidget *buttons = findChild_Widget(dlg, "dialogbuttons"); | 1737 | iWidget *buttons = findChild_Widget(dlg, "dialogbuttons"); |
1631 | if (buttons) { | 1738 | if (buttons && deviceType_App() == desktop_AppDeviceType) { |
1632 | footer += height_Widget(buttons); | 1739 | footer += height_Widget(buttons); |
1633 | } | 1740 | } |
1634 | iInputWidget *input = findChild_Widget(dlg, "input"); | 1741 | iInputWidget *input = findChild_Widget(dlg, "input"); |
1635 | setLineLimits_InputWidget(input, | 1742 | setLineLimits_InputWidget(input, |
1636 | 1, | 1743 | 1, |
1637 | (bottom_Rect(safeRect_Root(dlg->root)) - footer - | 1744 | (bottom_Rect(visibleRect_Root(dlg->root)) - footer - |
1638 | top_Rect(boundsWithoutVisualOffset_Widget(as_Widget(input)))) / | 1745 | top_Rect(boundsWithoutVisualOffset_Widget(as_Widget(input)))) / |
1639 | lineHeight_Text(font_InputWidget(input))); | 1746 | lineHeight_Text(font_InputWidget(input))); |
1640 | } | 1747 | } |
@@ -1643,11 +1750,17 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) { | |||
1643 | iWidget *ptr = as_Widget(pointer_Command(cmd)); | 1750 | iWidget *ptr = as_Widget(pointer_Command(cmd)); |
1644 | if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "keyboard.changed")) { | 1751 | if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "keyboard.changed")) { |
1645 | if (isVisible_Widget(dlg)) { | 1752 | if (isVisible_Widget(dlg)) { |
1646 | updateValueInputWidth_(dlg); | 1753 | updateValueInputSizing_(dlg); |
1647 | arrange_Widget(dlg); | 1754 | arrange_Widget(dlg); |
1648 | } | 1755 | } |
1649 | return iFalse; | 1756 | return iFalse; |
1650 | } | 1757 | } |
1758 | if (equal_Command(cmd, "input.resized")) { | ||
1759 | /* BUG: A single arrange here is not sufficient, leaving a big gap between prompt and input. Why? */ | ||
1760 | arrange_Widget(dlg); | ||
1761 | arrange_Widget(dlg); | ||
1762 | return iTrue; | ||
1763 | } | ||
1651 | if (equal_Command(cmd, "input.ended")) { | 1764 | if (equal_Command(cmd, "input.ended")) { |
1652 | if (argLabel_Command(cmd, "enter") && hasParent_Widget(ptr, dlg)) { | 1765 | if (argLabel_Command(cmd, "enter") && hasParent_Widget(ptr, dlg)) { |
1653 | if (arg_Command(cmd)) { | 1766 | if (arg_Command(cmd)) { |
@@ -1663,6 +1776,12 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) { | |||
1663 | } | 1776 | } |
1664 | return iFalse; | 1777 | return iFalse; |
1665 | } | 1778 | } |
1779 | else if (equal_Command(cmd, "valueinput.set")) { | ||
1780 | iInputWidget *input = findChild_Widget(dlg, "input"); | ||
1781 | setTextCStr_InputWidget(input, suffixPtr_Command(cmd, "text")); | ||
1782 | validate_InputWidget(input); | ||
1783 | return iTrue; | ||
1784 | } | ||
1666 | else if (equal_Command(cmd, "valueinput.cancel")) { | 1785 | else if (equal_Command(cmd, "valueinput.cancel")) { |
1667 | postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); | 1786 | postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); |
1668 | setId_Widget(dlg, ""); /* no further commands to emit */ | 1787 | setId_Widget(dlg, ""); /* no further commands to emit */ |
@@ -1699,9 +1818,9 @@ iWidget *makeDialogButtons_Widget(const iMenuItem *actions, size_t numActions) { | |||
1699 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); | 1818 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); |
1700 | } | 1819 | } |
1701 | int fonts[2] = { uiLabel_FontId, uiLabelBold_FontId }; | 1820 | int fonts[2] = { uiLabel_FontId, uiLabelBold_FontId }; |
1702 | if (deviceType_App() == phone_AppDeviceType) { | 1821 | if (deviceType_App() != desktop_AppDeviceType) { |
1703 | fonts[0] = uiLabelMedium_FontId; | 1822 | fonts[0] = uiLabelBig_FontId; |
1704 | fonts[1] = uiLabelMediumBold_FontId; | 1823 | fonts[1] = uiLabelBigBold_FontId; |
1705 | } | 1824 | } |
1706 | for (size_t i = 0; i < numActions; i++) { | 1825 | for (size_t i = 0; i < numActions; i++) { |
1707 | const char *label = actions[i].label; | 1826 | const char *label = actions[i].label; |
@@ -1743,6 +1862,10 @@ iWidget *makeDialogButtons_Widget(const iMenuItem *actions, size_t numActions) { | |||
1743 | setId_Widget(as_Widget(button), "default"); | 1862 | setId_Widget(as_Widget(button), "default"); |
1744 | } | 1863 | } |
1745 | setFlags_Widget(as_Widget(button), alignLeft_WidgetFlag | drawKey_WidgetFlag, isDefault); | 1864 | setFlags_Widget(as_Widget(button), alignLeft_WidgetFlag | drawKey_WidgetFlag, isDefault); |
1865 | if (deviceType_App() != desktop_AppDeviceType) { | ||
1866 | setFlags_Widget(as_Widget(button), frameless_WidgetFlag | noBackground_WidgetFlag, iTrue); | ||
1867 | setTextColor_LabelWidget(button, uiTextAction_ColorId); | ||
1868 | } | ||
1746 | setFont_LabelWidget(button, isDefault ? fonts[1] : fonts[0]); | 1869 | setFont_LabelWidget(button, isDefault ? fonts[1] : fonts[0]); |
1747 | } | 1870 | } |
1748 | return div; | 1871 | return div; |
@@ -1758,9 +1881,9 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con | |||
1758 | if (parent) { | 1881 | if (parent) { |
1759 | addChild_Widget(parent, iClob(dlg)); | 1882 | addChild_Widget(parent, iClob(dlg)); |
1760 | } | 1883 | } |
1761 | setId_Widget( | 1884 | if (deviceType_App() == desktop_AppDeviceType) { /* conserve space on mobile */ |
1762 | addChildFlags_Widget(dlg, iClob(new_LabelWidget(title, NULL)), frameless_WidgetFlag), | 1885 | addDialogTitle_(dlg, title, "valueinput.title"); |
1763 | "valueinput.title"); | 1886 | } |
1764 | iLabelWidget *promptLabel; | 1887 | iLabelWidget *promptLabel; |
1765 | setId_Widget(addChildFlags_Widget( | 1888 | setId_Widget(addChildFlags_Widget( |
1766 | dlg, iClob(promptLabel = new_LabelWidget(prompt, NULL)), frameless_WidgetFlag | 1889 | dlg, iClob(promptLabel = new_LabelWidget(prompt, NULL)), frameless_WidgetFlag |
@@ -1780,27 +1903,38 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con | |||
1780 | } | 1903 | } |
1781 | setId_Widget(as_Widget(input), "input"); | 1904 | setId_Widget(as_Widget(input), "input"); |
1782 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); | 1905 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); |
1783 | addChild_Widget(dlg, | 1906 | /* On mobile, the actions are laid out a bit differently: buttons on top, on opposite edges. */ |
1784 | iClob(makeDialogButtons_Widget( | 1907 | iArray actions; |
1785 | (iMenuItem[]){ { "${cancel}", SDLK_ESCAPE, 0, "valueinput.cancel" }, | 1908 | init_Array(&actions, sizeof(iMenuItem)); |
1786 | { acceptLabel, | 1909 | pushBack_Array(&actions, &(iMenuItem){ "${cancel}", SDLK_ESCAPE, 0, "valueinput.cancel" }); |
1910 | if (deviceType_App() != desktop_AppDeviceType) { | ||
1911 | pushBack_Array(&actions, &(iMenuItem){ "---" }); | ||
1912 | } | ||
1913 | pushBack_Array(&actions, &(iMenuItem){ | ||
1914 | acceptLabel, | ||
1787 | SDLK_RETURN, | 1915 | SDLK_RETURN, |
1788 | acceptKeyMod_ReturnKeyBehavior(prefs_App()->returnKey), | 1916 | acceptKeyMod_ReturnKeyBehavior(prefs_App()->returnKey), |
1789 | "valueinput.accept" } }, | 1917 | "valueinput.accept" |
1790 | 2))); | 1918 | }); |
1791 | // finalizeSheet_Mobile(dlg); | 1919 | addChildPos_Widget(dlg, |
1920 | iClob(makeDialogButtons_Widget(constData_Array(&actions), | ||
1921 | size_Array(&actions))), | ||
1922 | deviceType_App() != desktop_AppDeviceType ? | ||
1923 | front_WidgetAddPos : back_WidgetAddPos); | ||
1924 | deinit_Array(&actions); | ||
1792 | arrange_Widget(dlg); | 1925 | arrange_Widget(dlg); |
1793 | if (parent) { | 1926 | if (parent) { |
1794 | setFocus_Widget(as_Widget(input)); | 1927 | setFocus_Widget(as_Widget(input)); |
1795 | } | 1928 | } |
1796 | /* Check that the top is in the safe area. */ { | 1929 | /* Check that the top is in the safe area. */ |
1797 | int top = top_Rect(bounds_Widget(dlg)); | 1930 | if (deviceType_App() != desktop_AppDeviceType) { |
1931 | int top = top_Rect(boundsWithoutVisualOffset_Widget(dlg)); | ||
1798 | int delta = top - top_Rect(safeRect_Root(dlg->root)); | 1932 | int delta = top - top_Rect(safeRect_Root(dlg->root)); |
1799 | if (delta < 0) { | 1933 | if (delta < 0) { |
1800 | dlg->rect.pos.y -= delta; | 1934 | dlg->rect.pos.y -= delta; |
1801 | } | 1935 | } |
1802 | } | 1936 | } |
1803 | updateValueInputWidth_(dlg); | 1937 | updateValueInputSizing_(dlg); |
1804 | setupSheetTransition_Mobile(dlg, incoming_TransitionFlag | top_TransitionDir); | 1938 | setupSheetTransition_Mobile(dlg, incoming_TransitionFlag | top_TransitionDir); |
1805 | return dlg; | 1939 | return dlg; |
1806 | } | 1940 | } |
@@ -1808,7 +1942,7 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con | |||
1808 | void updateValueInput_Widget(iWidget *d, const char *title, const char *prompt) { | 1942 | void updateValueInput_Widget(iWidget *d, const char *title, const char *prompt) { |
1809 | setTextCStr_LabelWidget(findChild_Widget(d, "valueinput.title"), title); | 1943 | setTextCStr_LabelWidget(findChild_Widget(d, "valueinput.title"), title); |
1810 | setTextCStr_LabelWidget(findChild_Widget(d, "valueinput.prompt"), prompt); | 1944 | setTextCStr_LabelWidget(findChild_Widget(d, "valueinput.prompt"), prompt); |
1811 | updateValueInputWidth_(d); | 1945 | updateValueInputSizing_(d); |
1812 | } | 1946 | } |
1813 | 1947 | ||
1814 | static void updateQuestionWidth_(iWidget *dlg) { | 1948 | static void updateQuestionWidth_(iWidget *dlg) { |
@@ -1894,9 +2028,7 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg, | |||
1894 | } | 2028 | } |
1895 | iWidget *dlg = makeSheet_Widget(""); | 2029 | iWidget *dlg = makeSheet_Widget(""); |
1896 | setCommandHandler_Widget(dlg, messageHandler_); | 2030 | setCommandHandler_Widget(dlg, messageHandler_); |
1897 | setId_Widget( | 2031 | addDialogTitle_(dlg, title, "question.title"); |
1898 | addChildFlags_Widget(dlg, iClob(new_LabelWidget(title, NULL)), frameless_WidgetFlag), | ||
1899 | "question.title"); | ||
1900 | iLabelWidget *msgLabel; | 2032 | iLabelWidget *msgLabel; |
1901 | setId_Widget(addChildFlags_Widget(dlg, | 2033 | setId_Widget(addChildFlags_Widget(dlg, |
1902 | iClob(msgLabel = new_LabelWidget(msg, NULL)), | 2034 | iClob(msgLabel = new_LabelWidget(msg, NULL)), |
@@ -2021,9 +2153,11 @@ iWidget *appendTwoColumnTabPage_Widget(iWidget *tabs, const char *title, int sho | |||
2021 | } | 2153 | } |
2022 | 2154 | ||
2023 | static void makeTwoColumnHeading_(const char *title, iWidget *headings, iWidget *values) { | 2155 | static void makeTwoColumnHeading_(const char *title, iWidget *headings, iWidget *values) { |
2024 | addChildFlags_Widget(headings, | 2156 | setFont_LabelWidget(addChildFlags_Widget(headings, |
2025 | iClob(makeHeading_Widget(format_CStr(uiHeading_ColorEscape "%s", title))), | 2157 | iClob(makeHeading_Widget( |
2026 | ignoreForParentWidth_WidgetFlag); | 2158 | format_CStr(uiHeading_ColorEscape "%s", title))), |
2159 | ignoreForParentWidth_WidgetFlag), | ||
2160 | uiLabelBold_FontId); | ||
2027 | addChild_Widget(values, iClob(makeHeading_Widget(""))); | 2161 | addChild_Widget(values, iClob(makeHeading_Widget(""))); |
2028 | } | 2162 | } |
2029 | 2163 | ||
@@ -2071,28 +2205,6 @@ static const iArray *makeFontItems_(const char *id) { | |||
2071 | 0, | 2205 | 0, |
2072 | format_CStr("!font.set %s:%s", id, cstr_String(&spec->id)) }); | 2206 | format_CStr("!font.set %s:%s", id, cstr_String(&spec->id)) }); |
2073 | } | 2207 | } |
2074 | #if 0 | ||
2075 | const struct { | ||
2076 | const char * name; | ||
2077 | enum iTextFont cfgId; | ||
2078 | } fonts[] = { { "Nunito", nunito_TextFont }, | ||
2079 | { "Source Sans 3", sourceSans3_TextFont }, | ||
2080 | { "Fira Sans", firaSans_TextFont }, | ||
2081 | { "---", -1 }, | ||
2082 | { "Literata", literata_TextFont }, | ||
2083 | { "Tinos", tinos_TextFont }, | ||
2084 | { "---", -1 }, | ||
2085 | { "Iosevka", iosevka_TextFont } }; | ||
2086 | iForIndices(i, fonts) { | ||
2087 | pushBack_Array(items, | ||
2088 | &(iMenuItem){ fonts[i].name, | ||
2089 | 0, | ||
2090 | 0, | ||
2091 | fonts[i].cfgId >= 0 | ||
2092 | ? format_CStr("!%s.set arg:%d", id, fonts[i].cfgId) | ||
2093 | : NULL }); | ||
2094 | } | ||
2095 | #endif | ||
2096 | pushBack_Array(items, &(iMenuItem){ NULL }); /* terminator */ | 2208 | pushBack_Array(items, &(iMenuItem){ NULL }); /* terminator */ |
2097 | return items; | 2209 | return items; |
2098 | } | 2210 | } |
@@ -2108,13 +2220,6 @@ static void addFontButtons_(iWidget *parent, const char *id) { | |||
2108 | addChildFlags_Widget(parent, iClob(button), alignLeft_WidgetFlag); | 2220 | addChildFlags_Widget(parent, iClob(button), alignLeft_WidgetFlag); |
2109 | } | 2221 | } |
2110 | 2222 | ||
2111 | #if 0 | ||
2112 | static int cmp_MenuItem_(const void *e1, const void *e2) { | ||
2113 | const iMenuItem *a = e1, *b = e2; | ||
2114 | return iCmpStr(a->label, b->label); | ||
2115 | } | ||
2116 | #endif | ||
2117 | |||
2118 | void updatePreferencesLayout_Widget(iWidget *prefs) { | 2223 | void updatePreferencesLayout_Widget(iWidget *prefs) { |
2119 | if (!prefs || deviceType_App() != desktop_AppDeviceType) { | 2224 | if (!prefs || deviceType_App() != desktop_AppDeviceType) { |
2120 | return; | 2225 | return; |
@@ -2299,6 +2404,15 @@ iWidget *makePreferences_Widget(void) { | |||
2299 | format_CStr("returnkey.set arg:%d", acceptWithPrimaryMod_ReturnKeyBehavior) }, | 2404 | format_CStr("returnkey.set arg:%d", acceptWithPrimaryMod_ReturnKeyBehavior) }, |
2300 | { NULL } | 2405 | { NULL } |
2301 | }; | 2406 | }; |
2407 | iMenuItem toolbarActionItems[2][max_ToolbarAction]; | ||
2408 | iZap(toolbarActionItems); | ||
2409 | for (int j = 0; j < 2; j++) { | ||
2410 | for (int i = 0; i < sidebar_ToolbarAction; i++) { | ||
2411 | toolbarActionItems[j][i].label = toolbarActions_Mobile[i].label; | ||
2412 | toolbarActionItems[j][i].command = | ||
2413 | format_CStr("toolbar.action.set arg:%d button:%d", i, j); | ||
2414 | } | ||
2415 | } | ||
2302 | iMenuItem docThemes[2][max_GmDocumentTheme + 1]; | 2416 | iMenuItem docThemes[2][max_GmDocumentTheme + 1]; |
2303 | for (int i = 0; i < 2; ++i) { | 2417 | for (int i = 0; i < 2; ++i) { |
2304 | const iBool isDark = (i == 0); | 2418 | const iBool isDark = (i == 0); |
@@ -2390,24 +2504,27 @@ iWidget *makePreferences_Widget(void) { | |||
2390 | }; | 2504 | }; |
2391 | const iMenuItem uiPanelItems[] = { | 2505 | const iMenuItem uiPanelItems[] = { |
2392 | { "title id:heading.prefs.interface" }, | 2506 | { "title id:heading.prefs.interface" }, |
2393 | { "dropdown device:1 id:prefs.returnkey", 0, 0, (const void *) returnKeyBehaviors }, | 2507 | { "dropdown device:0 id:prefs.returnkey", 0, 0, (const void *) returnKeyBehaviors }, |
2394 | { "padding device:1" }, | 2508 | { "padding device:1" }, |
2395 | { "toggle id:prefs.hoverlink" }, | ||
2396 | { "toggle device:2 id:prefs.hidetoolbarscroll" }, | 2509 | { "toggle device:2 id:prefs.hidetoolbarscroll" }, |
2510 | { "heading device:2 id:heading.prefs.toolbaractions" }, | ||
2511 | { "dropdown device:2 id:prefs.toolbaraction1", 0, 0, (const void *) toolbarActionItems[0] }, | ||
2512 | { "dropdown device:2 id:prefs.toolbaraction2", 0, 0, (const void *) toolbarActionItems[1] }, | ||
2397 | { "heading id:heading.prefs.sizing" }, | 2513 | { "heading id:heading.prefs.sizing" }, |
2398 | { "input id:prefs.uiscale maxlen:8" }, | 2514 | { "input id:prefs.uiscale maxlen:8" }, |
2399 | { NULL } | 2515 | { NULL } |
2400 | }; | 2516 | }; |
2401 | const iMenuItem colorPanelItems[] = { | 2517 | const iMenuItem colorPanelItems[] = { |
2402 | { "title id:heading.prefs.colors" }, | 2518 | { "title id:heading.prefs.colors" }, |
2403 | { "heading id:heading.prefs.uitheme" }, | 2519 | #if !defined (iPlatformAndroidMobile) |
2404 | { "toggle id:prefs.ostheme" }, | 2520 | { "toggle id:prefs.ostheme" }, |
2521 | #endif | ||
2405 | { "radio id:prefs.theme", 0, 0, (const void *) themeItems }, | 2522 | { "radio id:prefs.theme", 0, 0, (const void *) themeItems }, |
2406 | { "radio id:prefs.accent", 0, 0, (const void *) accentItems }, | 2523 | { "radio id:prefs.accent", 0, 0, (const void *) accentItems }, |
2407 | { "heading id:heading.prefs.pagecontent" }, | 2524 | { "heading id:heading.prefs.pagecontent" }, |
2408 | { "dropdown id:prefs.doctheme.dark", 0, 0, (const void *) docThemes[0] }, | 2525 | { "dropdown id:prefs.doctheme.dark", 0, 0, (const void *) docThemes[0] }, |
2409 | { "dropdown id:prefs.doctheme.light", 0, 0, (const void *) docThemes[1] }, | 2526 | { "dropdown id:prefs.doctheme.light", 0, 0, (const void *) docThemes[1] }, |
2410 | { "radio id:prefs.saturation", 0, 0, (const void *) satItems }, | 2527 | { "radio horizontal:1 id:prefs.saturation", 0, 0, (const void *) satItems }, |
2411 | { "padding" }, | 2528 | { "padding" }, |
2412 | { "dropdown id:prefs.imagestyle", 0, 0, (const void *) imgStyles }, | 2529 | { "dropdown id:prefs.imagestyle", 0, 0, (const void *) imgStyles }, |
2413 | { NULL } | 2530 | { NULL } |
@@ -2418,18 +2535,23 @@ iWidget *makePreferences_Widget(void) { | |||
2418 | { "dropdown id:prefs.font.body", 0, 0, (const void *) constData_Array(makeFontItems_("body")) }, | 2535 | { "dropdown id:prefs.font.body", 0, 0, (const void *) constData_Array(makeFontItems_("body")) }, |
2419 | { "dropdown id:prefs.font.mono", 0, 0, (const void *) constData_Array(makeFontItems_("mono")) }, | 2536 | { "dropdown id:prefs.font.mono", 0, 0, (const void *) constData_Array(makeFontItems_("mono")) }, |
2420 | { "buttons id:prefs.mono", 0, 0, (const void *) monoFontItems }, | 2537 | { "buttons id:prefs.mono", 0, 0, (const void *) monoFontItems }, |
2421 | { "dropdown id:prefs.font.monodoc", 0, 0, (const void *) constData_Array(makeFontItems_("monodoc")) }, | ||
2422 | { "padding" }, | 2538 | { "padding" }, |
2423 | { "toggle id:prefs.font.smooth" }, | 2539 | { "dropdown id:prefs.font.monodoc", 0, 0, (const void *) constData_Array(makeFontItems_("monodoc")) }, |
2424 | { "padding" }, | 2540 | { "padding" }, |
2425 | { "dropdown id:prefs.font.ui", 0, 0, (const void *) constData_Array(makeFontItems_("ui")) }, | 2541 | { "toggle id:prefs.font.warnmissing" }, |
2542 | { "heading id:prefs.gemtext.ansi" }, | ||
2543 | { "toggle id:prefs.gemtext.ansi.fg" }, | ||
2544 | { "toggle id:prefs.gemtext.ansi.bg" }, | ||
2545 | { "toggle id:prefs.gemtext.ansi.fontstyle" }, | ||
2546 | // { "padding" }, | ||
2547 | // { "dropdown id:prefs.font.ui", 0, 0, (const void *) constData_Array(makeFontItems_("ui")) }, | ||
2426 | { "padding" }, | 2548 | { "padding" }, |
2427 | { "button text:" fontpack_Icon " ${menu.fonts}", 0, 0, "!open url:about:fonts" }, | 2549 | { "button text:" fontpack_Icon " " uiTextAction_ColorEscape "${menu.fonts}", 0, 0, "!open url:about:fonts" }, |
2428 | { NULL } | 2550 | { NULL } |
2429 | }; | 2551 | }; |
2430 | const iMenuItem stylePanelItems[] = { | 2552 | const iMenuItem stylePanelItems[] = { |
2431 | { "title id:heading.prefs.style" }, | 2553 | { "title id:heading.prefs.style" }, |
2432 | { "radio id:prefs.linewidth", 0, 0, (const void *) lineWidthItems }, | 2554 | { "radio horizontal:1 id:prefs.linewidth", 0, 0, (const void *) lineWidthItems }, |
2433 | { "padding" }, | 2555 | { "padding" }, |
2434 | { "input id:prefs.linespacing maxlen:5" }, | 2556 | { "input id:prefs.linespacing maxlen:5" }, |
2435 | { "radio id:prefs.quoteicon", 0, 0, (const void *) quoteItems }, | 2557 | { "radio id:prefs.quoteicon", 0, 0, (const void *) quoteItems }, |
@@ -2458,6 +2580,7 @@ iWidget *makePreferences_Widget(void) { | |||
2458 | }; | 2580 | }; |
2459 | const iMenuItem identityPanelItems[] = { | 2581 | const iMenuItem identityPanelItems[] = { |
2460 | { "title id:sidebar.identities" }, | 2582 | { "title id:sidebar.identities" }, |
2583 | { "certlist" }, | ||
2461 | { NULL } | 2584 | { NULL } |
2462 | }; | 2585 | }; |
2463 | iString *aboutText = collectNew_String(); { | 2586 | iString *aboutText = collectNew_String(); { |
@@ -2466,6 +2589,10 @@ iWidget *makePreferences_Widget(void) { | |||
2466 | appendFormat_String(aboutText, " (" LAGRANGE_IOS_VERSION ") %s" LAGRANGE_IOS_BUILD_DATE, | 2589 | appendFormat_String(aboutText, " (" LAGRANGE_IOS_VERSION ") %s" LAGRANGE_IOS_BUILD_DATE, |
2467 | escape_Color(uiTextDim_ColorId)); | 2590 | escape_Color(uiTextDim_ColorId)); |
2468 | #endif | 2591 | #endif |
2592 | #if defined (iPlatformAndroidMobile) | ||
2593 | appendFormat_String(aboutText, " (" LAGRANGE_ANDROID_VERSION ") %s" LAGRANGE_ANDROID_BUILD_DATE, | ||
2594 | escape_Color(uiTextDim_ColorId)); | ||
2595 | #endif | ||
2469 | } | 2596 | } |
2470 | const iMenuItem aboutPanelItems[] = { | 2597 | const iMenuItem aboutPanelItems[] = { |
2471 | { format_CStr("heading text:%s", cstr_String(aboutText)) }, | 2598 | { format_CStr("heading text:%s", cstr_String(aboutText)) }, |
@@ -2482,7 +2609,7 @@ iWidget *makePreferences_Widget(void) { | |||
2482 | { "title id:heading.settings" }, | 2609 | { "title id:heading.settings" }, |
2483 | { "panel text:" gear_Icon " ${heading.prefs.general}", 0, 0, (const void *) generalPanelItems }, | 2610 | { "panel text:" gear_Icon " ${heading.prefs.general}", 0, 0, (const void *) generalPanelItems }, |
2484 | { "panel icon:0x1f5a7 id:heading.prefs.network", 0, 0, (const void *) networkPanelItems }, | 2611 | { "panel icon:0x1f5a7 id:heading.prefs.network", 0, 0, (const void *) networkPanelItems }, |
2485 | { "panel text:" person_Icon " ${sidebar.identities}", 0, 0, (const void *) identityPanelItems }, | 2612 | { "panel noscroll:1 text:" person_Icon " ${sidebar.identities}", 0, 0, (const void *) identityPanelItems }, |
2486 | { "padding" }, | 2613 | { "padding" }, |
2487 | { "panel icon:0x1f4f1 id:heading.prefs.interface", 0, 0, (const void *) uiPanelItems }, | 2614 | { "panel icon:0x1f4f1 id:heading.prefs.interface", 0, 0, (const void *) uiPanelItems }, |
2488 | { "panel icon:0x1f3a8 id:heading.prefs.colors", 0, 0, (const void *) colorPanelItems }, | 2615 | { "panel icon:0x1f3a8 id:heading.prefs.colors", 0, 0, (const void *) colorPanelItems }, |
@@ -2498,9 +2625,7 @@ iWidget *makePreferences_Widget(void) { | |||
2498 | return dlg; | 2625 | return dlg; |
2499 | } | 2626 | } |
2500 | iWidget *dlg = makeSheet_Widget("prefs"); | 2627 | iWidget *dlg = makeSheet_Widget("prefs"); |
2501 | addChildFlags_Widget(dlg, | 2628 | addDialogTitle_(dlg, "${heading.prefs}", NULL); |
2502 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.prefs}", NULL)), | ||
2503 | frameless_WidgetFlag); | ||
2504 | iWidget *tabs = makeTabs_Widget(dlg); | 2629 | iWidget *tabs = makeTabs_Widget(dlg); |
2505 | setBackgroundColor_Widget(findChild_Widget(tabs, "tabs.buttons"), uiBackgroundSidebar_ColorId); | 2630 | setBackgroundColor_Widget(findChild_Widget(tabs, "tabs.buttons"), uiBackgroundSidebar_ColorId); |
2506 | setId_Widget(tabs, "prefs.tabs"); | 2631 | setId_Widget(tabs, "prefs.tabs"); |
@@ -2546,9 +2671,8 @@ iWidget *makePreferences_Widget(void) { | |||
2546 | } | 2671 | } |
2547 | /* User Interface. */ { | 2672 | /* User Interface. */ { |
2548 | appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.interface}", '2', &headings, &values); | 2673 | appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.interface}", '2', &headings, &values); |
2549 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 2674 | addDialogToggle_(headings, values, "${prefs.animate}", "prefs.animate"); |
2550 | addDialogToggle_(headings, values, "${prefs.customframe}", "prefs.customframe"); | 2675 | addDialogToggle_(headings, values, "${prefs.blink}", "prefs.blink"); |
2551 | #endif | ||
2552 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.returnkey}"))); | 2676 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.returnkey}"))); |
2553 | /* Return key behaviors. */ { | 2677 | /* Return key behaviors. */ { |
2554 | iLabelWidget *returnKey = makeMenuButton_LabelWidget( | 2678 | iLabelWidget *returnKey = makeMenuButton_LabelWidget( |
@@ -2562,7 +2686,9 @@ iWidget *makePreferences_Widget(void) { | |||
2562 | setId_Widget(addChildFlags_Widget(values, iClob(returnKey), alignLeft_WidgetFlag), | 2686 | setId_Widget(addChildFlags_Widget(values, iClob(returnKey), alignLeft_WidgetFlag), |
2563 | "prefs.returnkey"); | 2687 | "prefs.returnkey"); |
2564 | } | 2688 | } |
2565 | addDialogToggle_(headings, values, "${prefs.animate}", "prefs.animate"); | 2689 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
2690 | addDialogToggle_(headings, values, "${prefs.customframe}", "prefs.customframe"); | ||
2691 | #endif | ||
2566 | makeTwoColumnHeading_("${heading.prefs.scrolling}", headings, values); | 2692 | makeTwoColumnHeading_("${heading.prefs.scrolling}", headings, values); |
2567 | addDialogToggle_(headings, values, "${prefs.smoothscroll}", "prefs.smoothscroll"); | 2693 | addDialogToggle_(headings, values, "${prefs.smoothscroll}", "prefs.smoothscroll"); |
2568 | /* Scroll speeds. */ { | 2694 | /* Scroll speeds. */ { |
@@ -2827,7 +2953,7 @@ static iBool isBookmarkFolder_(void *context, const iBookmark *bm) { | |||
2827 | return isFolder_Bookmark(bm); | 2953 | return isFolder_Bookmark(bm); |
2828 | } | 2954 | } |
2829 | 2955 | ||
2830 | static const iArray *makeBookmarkFolderItems_(void) { | 2956 | static const iArray *makeBookmarkFolderItems_(iBool withNullTerminator) { |
2831 | iArray *folders = new_Array(sizeof(iMenuItem)); | 2957 | iArray *folders = new_Array(sizeof(iMenuItem)); |
2832 | pushBack_Array(folders, &(iMenuItem){ "\u2014", 0, 0, "dlg.bookmark.setfolder arg:0" }); | 2958 | pushBack_Array(folders, &(iMenuItem){ "\u2014", 0, 0, "dlg.bookmark.setfolder arg:0" }); |
2833 | iConstForEach( | 2959 | iConstForEach( |
@@ -2848,6 +2974,9 @@ static const iArray *makeBookmarkFolderItems_(void) { | |||
2848 | 0, | 2974 | 0, |
2849 | format_CStr("dlg.bookmark.setfolder arg:%u", id_Bookmark(bm)) }); | 2975 | format_CStr("dlg.bookmark.setfolder arg:%u", id_Bookmark(bm)) }); |
2850 | } | 2976 | } |
2977 | if (withNullTerminator) { | ||
2978 | pushBack_Array(folders, &(iMenuItem){ NULL }); | ||
2979 | } | ||
2851 | return collect_Array(folders); | 2980 | return collect_Array(folders); |
2852 | } | 2981 | } |
2853 | 2982 | ||
@@ -2856,40 +2985,37 @@ iWidget *makeBookmarkEditor_Widget(void) { | |||
2856 | { "${cancel}", 0, 0, "bmed.cancel" }, | 2985 | { "${cancel}", 0, 0, "bmed.cancel" }, |
2857 | { uiTextCaution_ColorEscape "${dlg.bookmark.save}", SDLK_RETURN, KMOD_PRIMARY, "bmed.accept" } | 2986 | { uiTextCaution_ColorEscape "${dlg.bookmark.save}", SDLK_RETURN, KMOD_PRIMARY, "bmed.accept" } |
2858 | }; | 2987 | }; |
2988 | iWidget *dlg = NULL; | ||
2859 | if (isUsingPanelLayout_Mobile()) { | 2989 | if (isUsingPanelLayout_Mobile()) { |
2990 | const iArray *folderItems = makeBookmarkFolderItems_(iTrue); | ||
2860 | const iMenuItem items[] = { | 2991 | const iMenuItem items[] = { |
2861 | { "title id:bmed.heading text:${heading.bookmark.edit}" }, | 2992 | { "title id:bmed.heading text:${heading.bookmark.edit}" }, |
2862 | { "heading id:dlg.bookmark.url" }, | 2993 | { "heading id:dlg.bookmark.url" }, |
2863 | { "input id:bmed.url url:1 noheading:1" }, | 2994 | { "input id:bmed.url url:1 noheading:1" }, |
2864 | { "padding" }, | 2995 | { "padding" }, |
2865 | { "input id:bmed.title text:${dlg.bookmark.title}" }, | 2996 | { "input id:bmed.title text:${dlg.bookmark.title}" }, |
2866 | { "input id:bmed.tags text:${dlg.bookmark.tags}" }, | 2997 | { "dropdown id:bmed.folder text:${dlg.bookmark.folder}", 0, 0, (const void *) constData_Array(folderItems) }, |
2998 | { "padding" }, | ||
2867 | { "input id:bmed.icon maxlen:1 text:${dlg.bookmark.icon}" }, | 2999 | { "input id:bmed.icon maxlen:1 text:${dlg.bookmark.icon}" }, |
3000 | { "input id:bmed.tags text:${dlg.bookmark.tags}" }, | ||
2868 | { "heading text:${heading.bookmark.tags}" }, | 3001 | { "heading text:${heading.bookmark.tags}" }, |
2869 | { "toggle id:bmed.tag.home text:${bookmark.tag.home}" }, | 3002 | { "toggle id:bmed.tag.home text:${bookmark.tag.home}" }, |
2870 | { "toggle id:bmed.tag.remote text:${bookmark.tag.remote}" }, | 3003 | { "toggle id:bmed.tag.remote text:${bookmark.tag.remote}" }, |
2871 | { "toggle id:bmed.tag.linksplit text:${bookmark.tag.linksplit}" }, | 3004 | { "toggle id:bmed.tag.linksplit text:${bookmark.tag.linksplit}" }, |
2872 | { NULL } | 3005 | { NULL } |
2873 | }; | 3006 | }; |
2874 | iWidget *dlg = makePanels_Mobile("bmed", items, actions, iElemCount(actions)); | 3007 | dlg = makePanels_Mobile("bmed", items, actions, iElemCount(actions)); |
2875 | setupSheetTransition_Mobile(dlg, iTrue); | 3008 | setupSheetTransition_Mobile(dlg, iTrue); |
2876 | return dlg; | ||
2877 | } | 3009 | } |
2878 | iWidget *dlg = makeSheet_Widget("bmed"); | 3010 | else { |
2879 | setId_Widget(addChildFlags_Widget( | 3011 | dlg = makeSheet_Widget("bmed"); |
2880 | dlg, | 3012 | addDialogTitle_(dlg, "${heading.bookmark.edit}", "bmed.heading"); |
2881 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.bookmark.edit}", NULL)), | ||
2882 | frameless_WidgetFlag), | ||
2883 | "bmed.heading"); | ||
2884 | iWidget *headings, *values; | 3013 | iWidget *headings, *values; |
2885 | addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); | 3014 | addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); |
2886 | iInputWidget *inputs[4]; | 3015 | iInputWidget *inputs[4]; |
2887 | addDialogInputWithHeading_(headings, values, "${dlg.bookmark.title}", "bmed.title", iClob(inputs[0] = new_InputWidget(0))); | ||
2888 | addDialogInputWithHeading_(headings, values, "${dlg.bookmark.url}", "bmed.url", iClob(inputs[1] = new_InputWidget(0))); | ||
2889 | setUrlContent_InputWidget(inputs[1], iTrue); | ||
2890 | /* Folder to add to. */ { | 3016 | /* Folder to add to. */ { |
2891 | addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.bookmark.folder}"))); | 3017 | addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.bookmark.folder}"))); |
2892 | const iArray *folderItems = makeBookmarkFolderItems_(); | 3018 | const iArray *folderItems = makeBookmarkFolderItems_(iFalse); |
2893 | iLabelWidget *folderButton; | 3019 | iLabelWidget *folderButton; |
2894 | setId_Widget(addChildFlags_Widget(values, | 3020 | setId_Widget(addChildFlags_Widget(values, |
2895 | iClob(folderButton = makeMenuButton_LabelWidget( | 3021 | iClob(folderButton = makeMenuButton_LabelWidget( |
@@ -2897,11 +3023,10 @@ iWidget *makeBookmarkEditor_Widget(void) { | |||
2897 | constData_Array(folderItems), | 3023 | constData_Array(folderItems), |
2898 | size_Array(folderItems))), alignLeft_WidgetFlag), | 3024 | size_Array(folderItems))), alignLeft_WidgetFlag), |
2899 | "bmed.folder"); | 3025 | "bmed.folder"); |
2900 | const uint32_t recentFolderId = recentFolder_Bookmarks(bookmarks_App()); | ||
2901 | updateDropdownSelection_LabelWidget( | ||
2902 | folderButton, format_CStr(" arg:%u", recentFolderId)); | ||
2903 | setUserData_Object(folderButton, get_Bookmarks(bookmarks_App(), recentFolderId)); | ||
2904 | } | 3026 | } |
3027 | addDialogInputWithHeading_(headings, values, "${dlg.bookmark.title}", "bmed.title", iClob(inputs[0] = new_InputWidget(0))); | ||
3028 | addDialogInputWithHeading_(headings, values, "${dlg.bookmark.url}", "bmed.url", iClob(inputs[1] = new_InputWidget(0))); | ||
3029 | setUrlContent_InputWidget(inputs[1], iTrue); | ||
2905 | addDialogInputWithHeading_(headings, values, "${dlg.bookmark.tags}", "bmed.tags", iClob(inputs[2] = new_InputWidget(0))); | 3030 | addDialogInputWithHeading_(headings, values, "${dlg.bookmark.tags}", "bmed.tags", iClob(inputs[2] = new_InputWidget(0))); |
2906 | addDialogInputWithHeading_(headings, values, "${dlg.bookmark.icon}", "bmed.icon", iClob(inputs[3] = new_InputWidget(1))); | 3031 | addDialogInputWithHeading_(headings, values, "${dlg.bookmark.icon}", "bmed.icon", iClob(inputs[3] = new_InputWidget(1))); |
2907 | /* Buttons for special tags. */ | 3032 | /* Buttons for special tags. */ |
@@ -2921,6 +3046,12 @@ iWidget *makeBookmarkEditor_Widget(void) { | |||
2921 | addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); | 3046 | addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); |
2922 | addChild_Widget(get_Root()->widget, iClob(dlg)); | 3047 | addChild_Widget(get_Root()->widget, iClob(dlg)); |
2923 | setupSheetTransition_Mobile(dlg, iTrue); | 3048 | setupSheetTransition_Mobile(dlg, iTrue); |
3049 | } | ||
3050 | /* Use a recently accessed folder as the default. */ | ||
3051 | const uint32_t recentFolderId = recentFolder_Bookmarks(bookmarks_App()); | ||
3052 | iLabelWidget *folderDrop = findChild_Widget(dlg, "bmed.folder"); | ||
3053 | updateDropdownSelection_LabelWidget(folderDrop, format_CStr(" arg:%u", recentFolderId)); | ||
3054 | setUserData_Object(folderDrop, get_Bookmarks(bookmarks_App(), recentFolderId)); | ||
2924 | return dlg; | 3055 | return dlg; |
2925 | } | 3056 | } |
2926 | 3057 | ||
@@ -2945,16 +3076,16 @@ static iBool handleBookmarkCreationCommands_SidebarWidget_(iWidget *editor, cons | |||
2945 | const uint32_t id = add_Bookmarks(bookmarks_App(), url, title, tags, first_String(icon)); | 3076 | const uint32_t id = add_Bookmarks(bookmarks_App(), url, title, tags, first_String(icon)); |
2946 | iBookmark * bm = get_Bookmarks(bookmarks_App(), id); | 3077 | iBookmark * bm = get_Bookmarks(bookmarks_App(), id); |
2947 | if (!isEmpty_String(icon)) { | 3078 | if (!isEmpty_String(icon)) { |
2948 | addTagIfMissing_Bookmark(bm, userIcon_BookmarkTag); | 3079 | bm->flags |= userIcon_BookmarkFlag; |
2949 | } | 3080 | } |
2950 | if (isSelected_Widget(findChild_Widget(editor, "bmed.tag.home"))) { | 3081 | if (isSelected_Widget(findChild_Widget(editor, "bmed.tag.home"))) { |
2951 | addTag_Bookmark(bm, homepage_BookmarkTag); | 3082 | bm->flags |= homepage_BookmarkFlag; |
2952 | } | 3083 | } |
2953 | if (isSelected_Widget(findChild_Widget(editor, "bmed.tag.remote"))) { | 3084 | if (isSelected_Widget(findChild_Widget(editor, "bmed.tag.remote"))) { |
2954 | addTag_Bookmark(bm, remoteSource_BookmarkTag); | 3085 | bm->flags |= remoteSource_BookmarkFlag; |
2955 | } | 3086 | } |
2956 | if (isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))) { | 3087 | if (isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))) { |
2957 | addTag_Bookmark(bm, linkSplit_BookmarkTag); | 3088 | bm->flags |= linkSplit_BookmarkFlag; |
2958 | } | 3089 | } |
2959 | bm->parentId = folder ? id_Bookmark(folder) : 0; | 3090 | bm->parentId = folder ? id_Bookmark(folder) : 0; |
2960 | setRecentFolder_Bookmarks(bookmarks_App(), bm->parentId); | 3091 | setRecentFolder_Bookmarks(bookmarks_App(), bm->parentId); |
@@ -3020,9 +3151,9 @@ static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { | |||
3020 | iBookmark *bm = get_Bookmarks(bookmarks_App(), id); | 3151 | iBookmark *bm = get_Bookmarks(bookmarks_App(), id); |
3021 | iAssert(bm); | 3152 | iAssert(bm); |
3022 | set_String(&bm->title, feedTitle); | 3153 | set_String(&bm->title, feedTitle); |
3023 | addOrRemoveTag_Bookmark(bm, subscribed_BookmarkTag, iTrue); | 3154 | bm->flags |= subscribed_BookmarkFlag; |
3024 | addOrRemoveTag_Bookmark(bm, headings_BookmarkTag, headings); | 3155 | iChangeFlags(bm->flags, headings_BookmarkFlag, headings); |
3025 | addOrRemoveTag_Bookmark(bm, ignoreWeb_BookmarkTag, ignoreWeb); | 3156 | iChangeFlags(bm->flags, ignoreWeb_BookmarkFlag, ignoreWeb); |
3026 | postCommand_App("bookmarks.changed"); | 3157 | postCommand_App("bookmarks.changed"); |
3027 | setupSheetTransition_Mobile(dlg, iFalse); | 3158 | setupSheetTransition_Mobile(dlg, iFalse); |
3028 | destroy_Widget(dlg); | 3159 | destroy_Widget(dlg); |
@@ -3051,15 +3182,14 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { | |||
3051 | { format_CStr("title id:feedcfg.heading text:%s", headingText) }, | 3182 | { format_CStr("title id:feedcfg.heading text:%s", headingText) }, |
3052 | { "input id:feedcfg.title text:${dlg.feed.title}" }, | 3183 | { "input id:feedcfg.title text:${dlg.feed.title}" }, |
3053 | { "radio id:dlg.feed.entrytype", 0, 0, (const void *) typeItems }, | 3184 | { "radio id:dlg.feed.entrytype", 0, 0, (const void *) typeItems }, |
3185 | { "padding" }, | ||
3054 | { "toggle id:feedcfg.ignoreweb text:${dlg.feed.ignoreweb}" }, | 3186 | { "toggle id:feedcfg.ignoreweb text:${dlg.feed.ignoreweb}" }, |
3055 | { NULL } | 3187 | { NULL } |
3056 | }, actions, iElemCount(actions)); | 3188 | }, actions, iElemCount(actions)); |
3057 | } | 3189 | } |
3058 | else { | 3190 | else { |
3059 | dlg = makeSheet_Widget("feedcfg"); | 3191 | dlg = makeSheet_Widget("feedcfg"); |
3060 | setId_Widget( | 3192 | addDialogTitle_(dlg, headingText, "feedcfg.heading"); |
3061 | addChildFlags_Widget(dlg, iClob(new_LabelWidget(headingText, NULL)), frameless_WidgetFlag), | ||
3062 | "feedcfg.heading"); | ||
3063 | iWidget *headings, *values; | 3193 | iWidget *headings, *values; |
3064 | addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); | 3194 | addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); |
3065 | iInputWidget *input = new_InputWidget(0); | 3195 | iInputWidget *input = new_InputWidget(0); |
@@ -3085,13 +3215,13 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { | |||
3085 | setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"), | 3215 | setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"), |
3086 | bm ? &bm->title : feedTitle_DocumentWidget(document_App())); | 3216 | bm ? &bm->title : feedTitle_DocumentWidget(document_App())); |
3087 | setFlags_Widget(findChild_Widget(dlg, | 3217 | setFlags_Widget(findChild_Widget(dlg, |
3088 | hasTag_Bookmark(bm, headings_BookmarkTag) | 3218 | bm && bm->flags & headings_BookmarkFlag |
3089 | ? "feedcfg.type.headings" | 3219 | ? "feedcfg.type.headings" |
3090 | : "feedcfg.type.gemini"), | 3220 | : "feedcfg.type.gemini"), |
3091 | selected_WidgetFlag, | 3221 | selected_WidgetFlag, |
3092 | iTrue); | 3222 | iTrue); |
3093 | setToggle_Widget(findChild_Widget(dlg, "feedcfg.ignoreweb"), | 3223 | setToggle_Widget(findChild_Widget(dlg, "feedcfg.ignoreweb"), |
3094 | hasTag_Bookmark(bm, ignoreWeb_BookmarkTag)); | 3224 | bm && bm->flags & ignoreWeb_BookmarkFlag); |
3095 | setCommandHandler_Widget(dlg, handleFeedSettingCommands_); | 3225 | setCommandHandler_Widget(dlg, handleFeedSettingCommands_); |
3096 | } | 3226 | } |
3097 | setupSheetTransition_Mobile(dlg, incoming_TransitionFlag); | 3227 | setupSheetTransition_Mobile(dlg, incoming_TransitionFlag); |
@@ -3138,11 +3268,7 @@ iWidget *makeIdentityCreation_Widget(void) { | |||
3138 | } | 3268 | } |
3139 | else { | 3269 | else { |
3140 | dlg = makeSheet_Widget("ident"); | 3270 | dlg = makeSheet_Widget("ident"); |
3141 | setId_Widget(addChildFlags_Widget( | 3271 | addDialogTitle_(dlg, "${heading.newident}", "ident.heading"); |
3142 | dlg, | ||
3143 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.newident}", NULL)), | ||
3144 | frameless_WidgetFlag), | ||
3145 | "ident.heading"); | ||
3146 | iWidget *page = new_Widget(); | 3272 | iWidget *page = new_Widget(); |
3147 | addChildFlags_Widget( | 3273 | addChildFlags_Widget( |
3148 | dlg, iClob(new_LabelWidget("${dlg.newident.rsa.selfsign}", NULL)), frameless_WidgetFlag); | 3274 | dlg, iClob(new_LabelWidget("${dlg.newident.rsa.selfsign}", NULL)), frameless_WidgetFlag); |
@@ -3226,7 +3352,7 @@ static const iMenuItem languages[] = { | |||
3226 | static iBool translationHandler_(iWidget *dlg, const char *cmd) { | 3352 | static iBool translationHandler_(iWidget *dlg, const char *cmd) { |
3227 | iUnused(dlg); | 3353 | iUnused(dlg); |
3228 | if (equal_Command(cmd, "xlt.lang")) { | 3354 | if (equal_Command(cmd, "xlt.lang")) { |
3229 | const iMenuItem *langItem = &languages[languageIndex_CStr(cstr_Rangecc(range_Command(cmd, "id")))]; | 3355 | const iMenuItem *langItem = &languages[languageIndex_CStr(cstr_Command(cmd, "id"))]; |
3230 | iWidget *widget = pointer_Command(cmd); | 3356 | iWidget *widget = pointer_Command(cmd); |
3231 | iLabelWidget *drop; | 3357 | iLabelWidget *drop; |
3232 | if (flags_Widget(widget) & nativeMenu_WidgetFlag) { | 3358 | if (flags_Widget(widget) & nativeMenu_WidgetFlag) { |
@@ -3246,7 +3372,7 @@ const char *languageId_String(const iString *menuItemLabel) { | |||
3246 | iForIndices(i, languages) { | 3372 | iForIndices(i, languages) { |
3247 | if (!languages[i].label) break; | 3373 | if (!languages[i].label) break; |
3248 | if (!cmp_String(menuItemLabel, translateCStr_Lang(languages[i].label))) { | 3374 | if (!cmp_String(menuItemLabel, translateCStr_Lang(languages[i].label))) { |
3249 | return cstr_Rangecc(range_Command(languages[i].command, "id")); | 3375 | return cstr_Command(languages[i].command, "id"); |
3250 | } | 3376 | } |
3251 | } | 3377 | } |
3252 | return ""; | 3378 | return ""; |
@@ -3280,10 +3406,7 @@ iWidget *makeTranslation_Widget(iWidget *parent) { | |||
3280 | dlg = makeSheet_Widget("xlt"); | 3406 | dlg = makeSheet_Widget("xlt"); |
3281 | setFlags_Widget(dlg, keepOnTop_WidgetFlag, iFalse); | 3407 | setFlags_Widget(dlg, keepOnTop_WidgetFlag, iFalse); |
3282 | dlg->minSize.x = 70 * gap_UI; | 3408 | dlg->minSize.x = 70 * gap_UI; |
3283 | addChildFlags_Widget( | 3409 | addDialogTitle_(dlg, "${heading.translate}", NULL); |
3284 | dlg, | ||
3285 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.translate}", NULL)), | ||
3286 | frameless_WidgetFlag); | ||
3287 | addChild_Widget(dlg, iClob(makePadding_Widget(lineHeight_Text(uiLabel_FontId)))); | 3410 | addChild_Widget(dlg, iClob(makePadding_Widget(lineHeight_Text(uiLabel_FontId)))); |
3288 | iWidget *headings, *values; | 3411 | iWidget *headings, *values; |
3289 | iWidget *page; | 3412 | iWidget *page; |
@@ -3316,20 +3439,13 @@ iWidget *makeTranslation_Widget(iWidget *parent) { | |||
3316 | addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); | 3439 | addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); |
3317 | addChild_Widget(parent, iClob(dlg)); | 3440 | addChild_Widget(parent, iClob(dlg)); |
3318 | arrange_Widget(dlg); | 3441 | arrange_Widget(dlg); |
3442 | arrange_Widget(dlg); /* TODO: Augh, another layout bug: two arranges required. */ | ||
3319 | } | 3443 | } |
3320 | /* Update choices. */ | 3444 | /* Update choices. */ |
3321 | updateDropdownSelection_LabelWidget(findChild_Widget(dlg, "xlt.from"), | 3445 | updateDropdownSelection_LabelWidget(findChild_Widget(dlg, "xlt.from"), |
3322 | languages[prefs_App()->langFrom].command); | 3446 | languages[prefs_App()->langFrom].command); |
3323 | updateDropdownSelection_LabelWidget(findChild_Widget(dlg, "xlt.to"), | 3447 | updateDropdownSelection_LabelWidget(findChild_Widget(dlg, "xlt.to"), |
3324 | languages[prefs_App()->langTo].command); | 3448 | languages[prefs_App()->langTo].command); |
3325 | // updateText_LabelWidget( | ||
3326 | // findChild_Widget(dlg, "xlt.from"), | ||
3327 | // text_LabelWidget(child_Widget(findChild_Widget(findChild_Widget(dlg, "xlt.from"), "menu"), | ||
3328 | // prefs_App()->langFrom))); | ||
3329 | // updateText_LabelWidget( | ||
3330 | // findChild_Widget(dlg, "xlt.to"), | ||
3331 | // text_LabelWidget(child_Widget(findChild_Widget(findChild_Widget(dlg, "xlt.to"), "menu"), | ||
3332 | // prefs_App()->langTo))); | ||
3333 | setCommandHandler_Widget(dlg, translationHandler_); | 3449 | setCommandHandler_Widget(dlg, translationHandler_); |
3334 | setupSheetTransition_Mobile(dlg, iTrue); | 3450 | setupSheetTransition_Mobile(dlg, iTrue); |
3335 | return dlg; | 3451 | return dlg; |
diff --git a/src/ui/util.h b/src/ui/util.h index d13d751b..98ce784c 100644 --- a/src/ui/util.h +++ b/src/ui/util.h | |||
@@ -47,16 +47,31 @@ iLocalDef iBool isMetricsChange_UserEvent(const SDL_Event *d) { | |||
47 | } | 47 | } |
48 | 48 | ||
49 | enum iMouseWheelFlag { | 49 | enum iMouseWheelFlag { |
50 | perPixel_MouseWheelFlag = iBit(9), /* e.g., trackpad or finger scroll; applied to `direction` */ | 50 | /* Note: A future version of SDL may support per-pixel scrolling, but 2.0.x doesn't. */ |
51 | perPixel_MouseWheelFlag = iBit(9), /* e.g., trackpad or finger scroll; applied to `direction` */ | ||
52 | inertia_MouseWheelFlag = iBit(10), | ||
53 | scrollFinished_MouseWheelFlag = iBit(11), | ||
51 | }; | 54 | }; |
52 | 55 | ||
53 | /* Note: A future version of SDL may support per-pixel scrolling, but 2.0.x doesn't. */ | ||
54 | iLocalDef void setPerPixel_MouseWheelEvent(SDL_MouseWheelEvent *ev, iBool set) { | 56 | iLocalDef void setPerPixel_MouseWheelEvent(SDL_MouseWheelEvent *ev, iBool set) { |
55 | iChangeFlags(ev->direction, perPixel_MouseWheelFlag, set); | 57 | iChangeFlags(ev->direction, perPixel_MouseWheelFlag, set); |
56 | } | 58 | } |
59 | iLocalDef void setInertia_MouseWheelEvent(SDL_MouseWheelEvent *ev, iBool set) { | ||
60 | iChangeFlags(ev->direction, inertia_MouseWheelFlag, set); | ||
61 | } | ||
62 | iLocalDef void setScrollFinished_MouseWheelEvent(SDL_MouseWheelEvent *ev, iBool set) { | ||
63 | iChangeFlags(ev->direction, scrollFinished_MouseWheelFlag, set); | ||
64 | } | ||
65 | |||
57 | iLocalDef iBool isPerPixel_MouseWheelEvent(const SDL_MouseWheelEvent *ev) { | 66 | iLocalDef iBool isPerPixel_MouseWheelEvent(const SDL_MouseWheelEvent *ev) { |
58 | return (ev->direction & perPixel_MouseWheelFlag) != 0; | 67 | return (ev->direction & perPixel_MouseWheelFlag) != 0; |
59 | } | 68 | } |
69 | iLocalDef iBool isInertia_MouseWheelEvent(const SDL_MouseWheelEvent *ev) { | ||
70 | return (ev->direction & inertia_MouseWheelFlag) != 0; | ||
71 | } | ||
72 | iLocalDef iBool isScrollFinished_MouseWheelEvent(const SDL_MouseWheelEvent *ev) { | ||
73 | return (ev->direction & scrollFinished_MouseWheelFlag) != 0; | ||
74 | } | ||
60 | 75 | ||
61 | iInt2 coord_MouseWheelEvent (const SDL_MouseWheelEvent *); | 76 | iInt2 coord_MouseWheelEvent (const SDL_MouseWheelEvent *); |
62 | 77 | ||
@@ -179,11 +194,18 @@ iDeclareType(SmoothScroll) | |||
179 | 194 | ||
180 | typedef void (*iSmoothScrollNotifyFunc)(iAnyObject *, int offset, uint32_t span); | 195 | typedef void (*iSmoothScrollNotifyFunc)(iAnyObject *, int offset, uint32_t span); |
181 | 196 | ||
197 | enum iSmoothScrollFlags { | ||
198 | pullDownAction_SmoothScrollFlag = iBit(1), | ||
199 | pullUpAction_SmoothScrollFlag = iBit(2), | ||
200 | }; | ||
201 | |||
182 | struct Impl_SmoothScroll { | 202 | struct Impl_SmoothScroll { |
183 | iAnim pos; | 203 | iAnim pos; |
184 | int max; | 204 | int max; |
185 | int overscroll; | 205 | int overscroll; |
186 | iWidget *widget; | 206 | iWidget *widget; |
207 | int flags; | ||
208 | int pullActionTriggered; | ||
187 | iSmoothScrollNotifyFunc notify; | 209 | iSmoothScrollNotifyFunc notify; |
188 | }; | 210 | }; |
189 | 211 | ||
@@ -197,6 +219,7 @@ iBool processEvent_SmoothScroll (iSmoothScroll *, const SDL_Event *ev); | |||
197 | 219 | ||
198 | float pos_SmoothScroll (const iSmoothScroll *); | 220 | float pos_SmoothScroll (const iSmoothScroll *); |
199 | iBool isFinished_SmoothScroll (const iSmoothScroll *); | 221 | iBool isFinished_SmoothScroll (const iSmoothScroll *); |
222 | float pullActionPos_SmoothScroll (const iSmoothScroll *); /* 0...1 */ | ||
200 | 223 | ||
201 | /*-----------------------------------------------------------------------------------------------*/ | 224 | /*-----------------------------------------------------------------------------------------------*/ |
202 | 225 | ||
@@ -249,7 +272,8 @@ void setMenuItemDisabled_Widget (iWidget *menu, const char *comm | |||
249 | void setMenuItemDisabledByIndex_Widget(iWidget *menu, size_t index, iBool disable); | 272 | void setMenuItemDisabledByIndex_Widget(iWidget *menu, size_t index, iBool disable); |
250 | void setMenuItemLabel_Widget (iWidget *menu, const char *command, const char *newLabel); | 273 | void setMenuItemLabel_Widget (iWidget *menu, const char *command, const char *newLabel); |
251 | void setMenuItemLabelByIndex_Widget (iWidget *menu, size_t index, const char *newLabel); | 274 | void setMenuItemLabelByIndex_Widget (iWidget *menu, size_t index, const char *newLabel); |
252 | void setNativeMenuItems_Widget (iWidget *, const iMenuItem *items, size_t n); | 275 | void setNativeMenuItems_Widget (iWidget *menu, const iMenuItem *items, size_t n); |
276 | iWidget * findUserData_Widget (iWidget *, void *userData); | ||
253 | 277 | ||
254 | int checkContextMenu_Widget (iWidget *, const SDL_Event *ev); /* see macro below */ | 278 | int checkContextMenu_Widget (iWidget *, const SDL_Event *ev); /* see macro below */ |
255 | 279 | ||
@@ -293,6 +317,7 @@ iWidget * makeTwoColumns_Widget (iWidget **headings, iWidget **values); | |||
293 | 317 | ||
294 | iLabelWidget *dialogAcceptButton_Widget (const iWidget *); | 318 | iLabelWidget *dialogAcceptButton_Widget (const iWidget *); |
295 | 319 | ||
320 | iLabelWidget *addDialogTitle_Widget (iWidget *, const char *text, const char *idOrNull); | ||
296 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, | 321 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, |
297 | const char *labelText, const char *inputId, | 322 | const char *labelText, const char *inputId, |
298 | iInputWidget *input); | 323 | iInputWidget *input); |
diff --git a/src/ui/visbuf.c b/src/ui/visbuf.c index 8f7a4c46..0097b12a 100644 --- a/src/ui/visbuf.c +++ b/src/ui/visbuf.c | |||
@@ -21,6 +21,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
22 | 22 | ||
23 | #include "visbuf.h" | 23 | #include "visbuf.h" |
24 | #include "paint.h" | ||
24 | #include "window.h" | 25 | #include "window.h" |
25 | #include "util.h" | 26 | #include "util.h" |
26 | 27 | ||
@@ -224,6 +225,8 @@ void draw_VisBuf(const iVisBuf *d, const iInt2 topLeft, const iRangei yClipBound | |||
224 | continue; /* Outside the clipping area. */ | 225 | continue; /* Outside the clipping area. */ |
225 | #endif | 226 | #endif |
226 | } | 227 | } |
228 | dst.x += origin_Paint.x; | ||
229 | dst.y += origin_Paint.y; | ||
227 | #if defined (DEBUG_SCALE) | 230 | #if defined (DEBUG_SCALE) |
228 | dst.w *= DEBUG_SCALE; | 231 | dst.w *= DEBUG_SCALE; |
229 | dst.h *= DEBUG_SCALE; | 232 | dst.h *= DEBUG_SCALE; |
diff --git a/src/ui/widget.c b/src/ui/widget.c index cedda461..9f67b1c7 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c | |||
@@ -31,6 +31,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
31 | #include "util.h" | 31 | #include "util.h" |
32 | #include "window.h" | 32 | #include "window.h" |
33 | 33 | ||
34 | #include "labelwidget.h" | ||
35 | |||
34 | #include <the_Foundation/ptrarray.h> | 36 | #include <the_Foundation/ptrarray.h> |
35 | #include <the_Foundation/ptrset.h> | 37 | #include <the_Foundation/ptrset.h> |
36 | #include <SDL_mouse.h> | 38 | #include <SDL_mouse.h> |
@@ -122,7 +124,9 @@ void init_Widget(iWidget *d) { | |||
122 | init_String(&d->id); | 124 | init_String(&d->id); |
123 | d->root = get_Root(); /* never changes after this */ | 125 | d->root = get_Root(); /* never changes after this */ |
124 | d->flags = 0; | 126 | d->flags = 0; |
127 | d->flags2 = 0; | ||
125 | d->rect = zero_Rect(); | 128 | d->rect = zero_Rect(); |
129 | d->oldSize = zero_I2(); | ||
126 | d->minSize = zero_I2(); | 130 | d->minSize = zero_I2(); |
127 | d->sizeRef = NULL; | 131 | d->sizeRef = NULL; |
128 | d->offsetRef = NULL; | 132 | d->offsetRef = NULL; |
@@ -139,8 +143,11 @@ void init_Widget(iWidget *d) { | |||
139 | static void visualOffsetAnimation_Widget_(void *ptr) { | 143 | static void visualOffsetAnimation_Widget_(void *ptr) { |
140 | iWidget *d = ptr; | 144 | iWidget *d = ptr; |
141 | postRefresh_App(); | 145 | postRefresh_App(); |
146 | d->root->didAnimateVisualOffsets = iTrue; | ||
147 | // printf("'%s' visoffanim: fin:%d val:%f\n", cstr_String(&d->id), | ||
148 | // isFinished_Anim(&d->visualOffset), value_Anim(&d->visualOffset)); fflush(stdout); | ||
142 | if (!isFinished_Anim(&d->visualOffset)) { | 149 | if (!isFinished_Anim(&d->visualOffset)) { |
143 | addTicker_App(visualOffsetAnimation_Widget_, ptr); | 150 | addTickerRoot_App(visualOffsetAnimation_Widget_, d->root, ptr); |
144 | } | 151 | } |
145 | else { | 152 | else { |
146 | d->flags &= ~visualOffset_WidgetFlag; | 153 | d->flags &= ~visualOffset_WidgetFlag; |
@@ -269,10 +276,12 @@ void setMinSize_Widget(iWidget *d, iInt2 minSize) { | |||
269 | } | 276 | } |
270 | 277 | ||
271 | void setPadding_Widget(iWidget *d, int left, int top, int right, int bottom) { | 278 | void setPadding_Widget(iWidget *d, int left, int top, int right, int bottom) { |
272 | d->padding[0] = left; | 279 | if (d) { |
273 | d->padding[1] = top; | 280 | d->padding[0] = left; |
274 | d->padding[2] = right; | 281 | d->padding[1] = top; |
275 | d->padding[3] = bottom; | 282 | d->padding[2] = right; |
283 | d->padding[3] = bottom; | ||
284 | } | ||
276 | } | 285 | } |
277 | 286 | ||
278 | iWidget *root_Widget(const iWidget *d) { | 287 | iWidget *root_Widget(const iWidget *d) { |
@@ -414,9 +423,10 @@ static iBool setWidth_Widget_(iWidget *d, int width) { | |||
414 | if (d->rect.size.x != width) { | 423 | if (d->rect.size.x != width) { |
415 | d->rect.size.x = width; | 424 | d->rect.size.x = width; |
416 | TRACE(d, "width has changed to %d", width); | 425 | TRACE(d, "width has changed to %d", width); |
417 | if (class_Widget(d)->sizeChanged) { | 426 | // if (~d->flags2 & undefinedWidth_WidgetFlag2 && class_Widget(d)->sizeChanged) { |
418 | class_Widget(d)->sizeChanged(d); | 427 | // class_Widget(d)->sizeChanged(d); |
419 | } | 428 | // } |
429 | // d->flags2 &= ~undefinedWidth_WidgetFlag2; | ||
420 | return iTrue; | 430 | return iTrue; |
421 | } | 431 | } |
422 | } | 432 | } |
@@ -437,9 +447,10 @@ static iBool setHeight_Widget_(iWidget *d, int height) { | |||
437 | if (d->rect.size.y != height) { | 447 | if (d->rect.size.y != height) { |
438 | d->rect.size.y = height; | 448 | d->rect.size.y = height; |
439 | TRACE(d, "height has changed to %d", height); | 449 | TRACE(d, "height has changed to %d", height); |
440 | if (class_Widget(d)->sizeChanged) { | 450 | // if (~d->flags2 & undefinedHeight_WidgetFlag2 && class_Widget(d)->sizeChanged) { |
441 | class_Widget(d)->sizeChanged(d); | 451 | // class_Widget(d)->sizeChanged(d); |
442 | } | 452 | // } |
453 | // d->flags2 &= ~undefinedHeight_WidgetFlag2; | ||
443 | return iTrue; | 454 | return iTrue; |
444 | } | 455 | } |
445 | } | 456 | } |
@@ -836,6 +847,13 @@ static void arrange_Widget_(iWidget *d) { | |||
836 | } | 847 | } |
837 | 848 | ||
838 | static void resetArrangement_Widget_(iWidget *d) { | 849 | static void resetArrangement_Widget_(iWidget *d) { |
850 | d->oldSize = d->rect.size; | ||
851 | if (d->flags & resizeToParentWidth_WidgetFlag) { | ||
852 | d->rect.size.x = 0; | ||
853 | } | ||
854 | if (d->flags & resizeToParentHeight_WidgetFlag) { | ||
855 | d->rect.size.y = 0; | ||
856 | } | ||
839 | iForEach(ObjectList, i, children_Widget(d)) { | 857 | iForEach(ObjectList, i, children_Widget(d)) { |
840 | iWidget *child = as_Widget(i.object); | 858 | iWidget *child = as_Widget(i.object); |
841 | resetArrangement_Widget_(child); | 859 | resetArrangement_Widget_(child); |
@@ -847,6 +865,14 @@ static void resetArrangement_Widget_(iWidget *d) { | |||
847 | ~child->flags & fixedWidth_WidgetFlag) { | 865 | ~child->flags & fixedWidth_WidgetFlag) { |
848 | child->rect.size.x = 0; | 866 | child->rect.size.x = 0; |
849 | } | 867 | } |
868 | if (d->flags & resizeChildrenToWidestChild_WidgetFlag) { | ||
869 | if (isInstance_Object(child, &Class_LabelWidget)) { | ||
870 | updateSize_LabelWidget((iLabelWidget *) child); | ||
871 | } | ||
872 | else { | ||
873 | child->rect.size.x = 0; | ||
874 | } | ||
875 | } | ||
850 | if (d->flags & arrangeVertical_WidgetFlag) { | 876 | if (d->flags & arrangeVertical_WidgetFlag) { |
851 | child->rect.pos.y = 0; | 877 | child->rect.pos.y = 0; |
852 | } | 878 | } |
@@ -858,6 +884,15 @@ static void resetArrangement_Widget_(iWidget *d) { | |||
858 | } | 884 | } |
859 | } | 885 | } |
860 | 886 | ||
887 | static void notifySizeChanged_Widget_(iWidget *d) { | ||
888 | if (class_Widget(d)->sizeChanged && !isEqual_I2(d->rect.size, d->oldSize)) { | ||
889 | class_Widget(d)->sizeChanged(d); | ||
890 | } | ||
891 | iForEach(ObjectList, child, d->children) { | ||
892 | notifySizeChanged_Widget_(child.object); | ||
893 | } | ||
894 | } | ||
895 | |||
861 | void arrange_Widget(iWidget *d) { | 896 | void arrange_Widget(iWidget *d) { |
862 | if (d) { | 897 | if (d) { |
863 | #if !defined (NDEBUG) | 898 | #if !defined (NDEBUG) |
@@ -867,6 +902,8 @@ void arrange_Widget(iWidget *d) { | |||
867 | #endif | 902 | #endif |
868 | resetArrangement_Widget_(d); /* back to initial default sizes */ | 903 | resetArrangement_Widget_(d); /* back to initial default sizes */ |
869 | arrange_Widget_(d); | 904 | arrange_Widget_(d); |
905 | notifySizeChanged_Widget_(d); | ||
906 | d->root->didChangeArrangement = iTrue; | ||
870 | } | 907 | } |
871 | } | 908 | } |
872 | 909 | ||
@@ -884,6 +921,14 @@ int visualOffsetByReference_Widget(const iWidget *d) { | |||
884 | // const float factor = width_Widget(d) / (float) size_Root(d->root).x; | 921 | // const float factor = width_Widget(d) / (float) size_Root(d->root).x; |
885 | const int invOff = width_Widget(d) - iRound(value_Anim(&child->visualOffset)); | 922 | const int invOff = width_Widget(d) - iRound(value_Anim(&child->visualOffset)); |
886 | offX -= invOff / 4; | 923 | offX -= invOff / 4; |
924 | #if 0 | ||
925 | if (invOff) { | ||
926 | printf(" [%p] %s (%p, fin:%d visoff:%d drag:%d): invOff %d\n", d, cstr_String(&child->id), child, | ||
927 | isFinished_Anim(&child->visualOffset), | ||
928 | (child->flags & visualOffset_WidgetFlag) != 0, | ||
929 | (child->flags & dragged_WidgetFlag) != 0, invOff); fflush(stdout); | ||
930 | } | ||
931 | #endif | ||
887 | } | 932 | } |
888 | } | 933 | } |
889 | return offX; | 934 | return offX; |
@@ -1183,6 +1228,9 @@ iBool scrollOverflow_Widget(iWidget *d, int delta) { | |||
1183 | bounds.pos.y = iMin(bounds.pos.y, validPosRange.end); | 1228 | bounds.pos.y = iMin(bounds.pos.y, validPosRange.end); |
1184 | } | 1229 | } |
1185 | // printf("range: %d ... %d\n", range.start, range.end); | 1230 | // printf("range: %d ... %d\n", range.start, range.end); |
1231 | if (delta) { | ||
1232 | d->root->didChangeArrangement = iTrue; /* ensure that widgets update if needed */ | ||
1233 | } | ||
1186 | } | 1234 | } |
1187 | else { | 1235 | else { |
1188 | bounds.pos.y = iClamp(bounds.pos.y, validPosRange.start, validPosRange.end); | 1236 | bounds.pos.y = iClamp(bounds.pos.y, validPosRange.start, validPosRange.end); |
@@ -1370,7 +1418,8 @@ void drawLayerEffects_Widget(const iWidget *d) { | |||
1370 | shadowBorder = iFalse; | 1418 | shadowBorder = iFalse; |
1371 | } | 1419 | } |
1372 | } | 1420 | } |
1373 | const iBool isFaded = fadeBackground && ~d->flags & noFadeBackground_WidgetFlag; | 1421 | const iBool isFaded = (fadeBackground && ~d->flags & noFadeBackground_WidgetFlag) || |
1422 | (d->flags2 & fadeBackground_WidgetFlag2); | ||
1374 | if (shadowBorder && ~d->flags & noShadowBorder_WidgetFlag) { | 1423 | if (shadowBorder && ~d->flags & noShadowBorder_WidgetFlag) { |
1375 | iPaint p; | 1424 | iPaint p; |
1376 | init_Paint(&p); | 1425 | init_Paint(&p); |
@@ -1381,9 +1430,19 @@ void drawLayerEffects_Widget(const iWidget *d) { | |||
1381 | init_Paint(&p); | 1430 | init_Paint(&p); |
1382 | p.alpha = 0x50; | 1431 | p.alpha = 0x50; |
1383 | if (flags_Widget(d) & (visualOffset_WidgetFlag | dragged_WidgetFlag)) { | 1432 | if (flags_Widget(d) & (visualOffset_WidgetFlag | dragged_WidgetFlag)) { |
1384 | const float area = d->rect.size.x * d->rect.size.y; | 1433 | const float area = d->rect.size.x * d->rect.size.y; |
1434 | const float rootArea = area_Rect(rect_Root(d->root)); | ||
1385 | const float visibleArea = area_Rect(intersect_Rect(bounds_Widget(d), rect_Root(d->root))); | 1435 | const float visibleArea = area_Rect(intersect_Rect(bounds_Widget(d), rect_Root(d->root))); |
1386 | p.alpha *= (area > 0 ? visibleArea / area : 0.0f); | 1436 | if (isPortraitPhone_App() && !cmp_String(&d->id, "sidebar")) { |
1437 | p.alpha *= iClamp(visibleArea / rootArea * 2, 0.0f, 1.0f); | ||
1438 | } | ||
1439 | else if (area > 0) { | ||
1440 | p.alpha *= visibleArea / area; | ||
1441 | } | ||
1442 | else { | ||
1443 | p.alpha = 0; | ||
1444 | } | ||
1445 | //printf("area:%f visarea:%f alpha:%d\n", rootArea, visibleArea, p.alpha); | ||
1387 | } | 1446 | } |
1388 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | 1447 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); |
1389 | fillRect_Paint(&p, rect_Root(d->root), backgroundFadeColor_Widget()); | 1448 | fillRect_Paint(&p, rect_Root(d->root), backgroundFadeColor_Widget()); |
@@ -1851,7 +1910,7 @@ iAny *findParentClass_Widget(const iWidget *d, const iAnyClass *class) { | |||
1851 | } | 1910 | } |
1852 | 1911 | ||
1853 | iAny *findOverflowScrollable_Widget(iWidget *d) { | 1912 | iAny *findOverflowScrollable_Widget(iWidget *d) { |
1854 | const iRect rootRect = rect_Root(d->root); | 1913 | const iRect rootRect = visibleRect_Root(d->root); |
1855 | for (iWidget *w = d; w; w = parent_Widget(w)) { | 1914 | for (iWidget *w = d; w; w = parent_Widget(w)) { |
1856 | if (flags_Widget(w) & overflowScrollable_WidgetFlag) { | 1915 | if (flags_Widget(w) & overflowScrollable_WidgetFlag) { |
1857 | const iRect bounds = boundsWithoutVisualOffset_Widget(w); | 1916 | const iRect bounds = boundsWithoutVisualOffset_Widget(w); |
@@ -1956,12 +2015,16 @@ iBool isAffectedByVisualOffset_Widget(const iWidget *d) { | |||
1956 | if (w->flags & visualOffset_WidgetFlag) { | 2015 | if (w->flags & visualOffset_WidgetFlag) { |
1957 | return iTrue; | 2016 | return iTrue; |
1958 | } | 2017 | } |
2018 | if (visualOffsetByReference_Widget(w) != 0) { | ||
2019 | return iTrue; | ||
2020 | } | ||
1959 | } | 2021 | } |
1960 | return iFalse; | 2022 | return iFalse; |
1961 | } | 2023 | } |
1962 | 2024 | ||
1963 | void setFocus_Widget(iWidget *d) { | 2025 | void setFocus_Widget(iWidget *d) { |
1964 | iWindow *win = get_Window(); | 2026 | iWindow *win = d ? window_Widget(d) : get_Window(); |
2027 | iAssert(win); | ||
1965 | if (win->focus != d) { | 2028 | if (win->focus != d) { |
1966 | if (win->focus) { | 2029 | if (win->focus) { |
1967 | iAssert(!contains_PtrSet(win->focus->root->pendingDestruction, win->focus)); | 2030 | iAssert(!contains_PtrSet(win->focus->root->pendingDestruction, win->focus)); |
@@ -1976,6 +2039,13 @@ void setFocus_Widget(iWidget *d) { | |||
1976 | } | 2039 | } |
1977 | } | 2040 | } |
1978 | 2041 | ||
2042 | void setKeyboardGrab_Widget(iWidget *d) { | ||
2043 | iWindow *win = d ? window_Widget(d) : get_Window(); | ||
2044 | iAssert(win); | ||
2045 | win->focus = d; | ||
2046 | /* no notifications sent */ | ||
2047 | } | ||
2048 | |||
1979 | iWidget *focus_Widget(void) { | 2049 | iWidget *focus_Widget(void) { |
1980 | return get_Window()->focus; | 2050 | return get_Window()->focus; |
1981 | } | 2051 | } |
diff --git a/src/ui/widget.h b/src/ui/widget.h index 4025f5c5..fb7eb5e2 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h | |||
@@ -123,6 +123,11 @@ enum iWidgetFlag { | |||
123 | #define refChildrenOffset_WidgetFlag iBit64(63) /* visual offset determined by the offset of referenced children */ | 123 | #define refChildrenOffset_WidgetFlag iBit64(63) /* visual offset determined by the offset of referenced children */ |
124 | #define nativeMenu_WidgetFlag iBit64(64) | 124 | #define nativeMenu_WidgetFlag iBit64(64) |
125 | 125 | ||
126 | enum iWidgetFlag2 { | ||
127 | slidingSheetDraggable_WidgetFlag2 = iBit(1), | ||
128 | fadeBackground_WidgetFlag2 = iBit(2), | ||
129 | }; | ||
130 | |||
126 | enum iWidgetAddPos { | 131 | enum iWidgetAddPos { |
127 | back_WidgetAddPos, | 132 | back_WidgetAddPos, |
128 | front_WidgetAddPos, | 133 | front_WidgetAddPos, |
@@ -139,7 +144,9 @@ struct Impl_Widget { | |||
139 | iObject object; | 144 | iObject object; |
140 | iString id; | 145 | iString id; |
141 | int64_t flags; | 146 | int64_t flags; |
147 | int flags2; | ||
142 | iRect rect; | 148 | iRect rect; |
149 | iInt2 oldSize; /* in previous arrangement; for notification */ | ||
143 | iInt2 minSize; | 150 | iInt2 minSize; |
144 | iWidget * sizeRef; | 151 | iWidget * sizeRef; |
145 | iWidget * offsetRef; | 152 | iWidget * offsetRef; |
@@ -230,6 +237,24 @@ iLocalDef int height_Widget(const iAnyObject *d) { | |||
230 | } | 237 | } |
231 | return 0; | 238 | return 0; |
232 | } | 239 | } |
240 | iLocalDef int leftPad_Widget(const iWidget *d) { | ||
241 | return d->padding[0]; | ||
242 | } | ||
243 | iLocalDef int topPad_Widget(const iWidget *d) { | ||
244 | return d->padding[1]; | ||
245 | } | ||
246 | iLocalDef int rightPad_Widget(const iWidget *d) { | ||
247 | return d->padding[2]; | ||
248 | } | ||
249 | iLocalDef int bottomPad_Widget(const iWidget *d) { | ||
250 | return d->padding[3]; | ||
251 | } | ||
252 | iLocalDef iInt2 tlPad_Widget(const iWidget *d) { | ||
253 | return init_I2(leftPad_Widget(d), topPad_Widget(d)); | ||
254 | } | ||
255 | iLocalDef iInt2 brPad_Widget(const iWidget *d) { | ||
256 | return init_I2(rightPad_Widget(d), bottomPad_Widget(d)); | ||
257 | } | ||
233 | iLocalDef iObjectList *children_Widget(iAnyObject *d) { | 258 | iLocalDef iObjectList *children_Widget(iAnyObject *d) { |
234 | if (d == NULL) return NULL; | 259 | if (d == NULL) return NULL; |
235 | iAssert(isInstance_Object(d, &Class_Widget)); | 260 | iAssert(isInstance_Object(d, &Class_Widget)); |
@@ -302,7 +327,8 @@ void scrollInfo_Widget (const iWidget *, iWidgetScrollInfo *inf | |||
302 | 327 | ||
303 | int backgroundFadeColor_Widget (void); | 328 | int backgroundFadeColor_Widget (void); |
304 | 329 | ||
305 | void setFocus_Widget (iWidget *); | 330 | void setFocus_Widget (iWidget *); /* widget must be flagged `focusable` */ |
331 | void setKeyboardGrab_Widget (iWidget *); /* sets focus on any widget */ | ||
306 | iWidget * focus_Widget (void); | 332 | iWidget * focus_Widget (void); |
307 | void setHover_Widget (iWidget *); | 333 | void setHover_Widget (iWidget *); |
308 | iWidget * hover_Widget (void); | 334 | iWidget * hover_Widget (void); |
diff --git a/src/ui/window.c b/src/ui/window.c index 9f12cabf..af36bb22 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -442,7 +442,7 @@ void create_Window_(iWindow *d, iRect rect, uint32_t flags) { | |||
442 | static SDL_Surface *loadImage_(const iBlock *data, int resized) { | 442 | static SDL_Surface *loadImage_(const iBlock *data, int resized) { |
443 | int w = 0, h = 0, num = 4; | 443 | int w = 0, h = 0, num = 4; |
444 | stbi_uc *pixels = stbi_load_from_memory( | 444 | stbi_uc *pixels = stbi_load_from_memory( |
445 | constData_Block(data), size_Block(data), &w, &h, &num, STBI_rgb_alpha); | 445 | constData_Block(data), (int) size_Block(data), &w, &h, &num, STBI_rgb_alpha); |
446 | if (resized) { | 446 | if (resized) { |
447 | stbi_uc *rsPixels = malloc(num * resized * resized); | 447 | stbi_uc *rsPixels = malloc(num * resized * resized); |
448 | stbir_resize_uint8(pixels, w, h, 0, rsPixels, resized, resized, 0, num); | 448 | stbir_resize_uint8(pixels, w, h, 0, rsPixels, resized, resized, 0, num); |
@@ -560,6 +560,7 @@ void init_MainWindow(iMainWindow *d, iRect rect) { | |||
560 | d->splitMode = 0; | 560 | d->splitMode = 0; |
561 | d->pendingSplitMode = 0; | 561 | d->pendingSplitMode = 0; |
562 | d->pendingSplitUrl = new_String(); | 562 | d->pendingSplitUrl = new_String(); |
563 | d->pendingSplitOrigin = new_String(); | ||
563 | d->place.initialPos = rect.pos; | 564 | d->place.initialPos = rect.pos; |
564 | d->place.normalRect = rect; | 565 | d->place.normalRect = rect; |
565 | d->place.lastNotifiedSize = zero_I2(); | 566 | d->place.lastNotifiedSize = zero_I2(); |
@@ -605,6 +606,7 @@ void init_MainWindow(iMainWindow *d, iRect rect) { | |||
605 | #endif | 606 | #endif |
606 | setCurrent_Text(d->base.text); | 607 | setCurrent_Text(d->base.text); |
607 | SDL_GetRendererOutputSize(d->base.render, &d->base.size.x, &d->base.size.y); | 608 | SDL_GetRendererOutputSize(d->base.render, &d->base.size.x, &d->base.size.y); |
609 | d->maxDrawableHeight = d->base.size.y; | ||
608 | setupUserInterface_MainWindow(d); | 610 | setupUserInterface_MainWindow(d); |
609 | postCommand_App("~bindings.changed"); /* update from bindings */ | 611 | postCommand_App("~bindings.changed"); /* update from bindings */ |
610 | /* Load the border shadow texture. */ { | 612 | /* Load the border shadow texture. */ { |
@@ -642,6 +644,7 @@ void deinit_MainWindow(iMainWindow *d) { | |||
642 | if (theMainWindow_ == d) { | 644 | if (theMainWindow_ == d) { |
643 | theMainWindow_ = NULL; | 645 | theMainWindow_ = NULL; |
644 | } | 646 | } |
647 | delete_String(d->pendingSplitOrigin); | ||
645 | delete_String(d->pendingSplitUrl); | 648 | delete_String(d->pendingSplitUrl); |
646 | deinit_Window(&d->base); | 649 | deinit_Window(&d->base); |
647 | } | 650 | } |
@@ -1006,6 +1009,28 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { | |||
1006 | postCommand_App("media.player.update"); /* in case a player needs updating */ | 1009 | postCommand_App("media.player.update"); /* in case a player needs updating */ |
1007 | return iTrue; | 1010 | return iTrue; |
1008 | } | 1011 | } |
1012 | if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.sysframe") && mw) { | ||
1013 | /* This command is sent on Android to update the keyboard height. */ | ||
1014 | const char *cmd = command_UserEvent(ev); | ||
1015 | /* | ||
1016 | 0 | ||
1017 | | | ||
1018 | top | ||
1019 | | | | ||
1020 | | bottom (top of keyboard) : | ||
1021 | | | : keyboardHeight | ||
1022 | maxDrawableHeight : | ||
1023 | | | ||
1024 | fullheight | ||
1025 | */ | ||
1026 | const int top = argLabel_Command(cmd, "top"); | ||
1027 | const int bottom = argLabel_Command(cmd, "bottom"); | ||
1028 | if (!SDL_IsScreenKeyboardShown(mw->base.win)) { | ||
1029 | mw->maxDrawableHeight = bottom - top; | ||
1030 | } | ||
1031 | setKeyboardHeight_MainWindow(mw, top + mw->maxDrawableHeight - bottom); | ||
1032 | return iTrue; | ||
1033 | } | ||
1009 | if (processEvent_Touch(&event)) { | 1034 | if (processEvent_Touch(&event)) { |
1010 | return iTrue; | 1035 | return iTrue; |
1011 | } | 1036 | } |
@@ -1236,11 +1261,15 @@ void draw_MainWindow(iMainWindow *d) { | |||
1236 | isDrawing_ = iTrue; | 1261 | isDrawing_ = iTrue; |
1237 | setCurrent_Text(d->base.text); | 1262 | setCurrent_Text(d->base.text); |
1238 | /* Check if root needs resizing. */ { | 1263 | /* Check if root needs resizing. */ { |
1264 | const iBool wasPortrait = isPortrait_App(); | ||
1239 | iInt2 renderSize; | 1265 | iInt2 renderSize; |
1240 | SDL_GetRendererOutputSize(w->render, &renderSize.x, &renderSize.y); | 1266 | SDL_GetRendererOutputSize(w->render, &renderSize.x, &renderSize.y); |
1241 | if (!isEqual_I2(renderSize, w->size)) { | 1267 | if (!isEqual_I2(renderSize, w->size)) { |
1242 | updateSize_MainWindow_(d, iTrue); | 1268 | updateSize_MainWindow_(d, iTrue); |
1243 | processEvents_App(postedEventsOnly_AppEventMode); | 1269 | processEvents_App(postedEventsOnly_AppEventMode); |
1270 | if (isPortrait_App() != wasPortrait) { | ||
1271 | d->maxDrawableHeight = renderSize.y; | ||
1272 | } | ||
1244 | } | 1273 | } |
1245 | } | 1274 | } |
1246 | const int winFlags = SDL_GetWindowFlags(d->base.win); | 1275 | const int winFlags = SDL_GetWindowFlags(d->base.win); |
@@ -1268,6 +1297,14 @@ void draw_MainWindow(iMainWindow *d) { | |||
1268 | } | 1297 | } |
1269 | /* Draw widgets. */ | 1298 | /* Draw widgets. */ |
1270 | w->frameTime = SDL_GetTicks(); | 1299 | w->frameTime = SDL_GetTicks(); |
1300 | iForIndices(i, d->base.roots) { | ||
1301 | iRoot *root = d->base.roots[i]; | ||
1302 | if (root) { | ||
1303 | /* Some widgets may need a just-in-time visual update. */ | ||
1304 | notifyVisualOffsetChange_Root(root); | ||
1305 | root->didChangeArrangement = iFalse; | ||
1306 | } | ||
1307 | } | ||
1271 | if (isExposed_Window(w)) { | 1308 | if (isExposed_Window(w)) { |
1272 | w->isInvalidated = iFalse; | 1309 | w->isInvalidated = iFalse; |
1273 | extern int drawCount_; | 1310 | extern int drawCount_; |
@@ -1442,6 +1479,7 @@ iBool isOpenGLRenderer_Window(void) { | |||
1442 | } | 1479 | } |
1443 | 1480 | ||
1444 | void setKeyboardHeight_MainWindow(iMainWindow *d, int height) { | 1481 | void setKeyboardHeight_MainWindow(iMainWindow *d, int height) { |
1482 | height = iMax(0, height); | ||
1445 | if (d->keyboardHeight != height) { | 1483 | if (d->keyboardHeight != height) { |
1446 | d->keyboardHeight = height; | 1484 | d->keyboardHeight = height; |
1447 | postCommandf_App("keyboard.changed arg:%d", height); | 1485 | postCommandf_App("keyboard.changed arg:%d", height); |
@@ -1528,18 +1566,20 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) { | |||
1528 | } | 1566 | } |
1529 | } | 1567 | } |
1530 | if (!isEmpty_String(d->pendingSplitUrl)) { | 1568 | if (!isEmpty_String(d->pendingSplitUrl)) { |
1531 | postCommandf_Root(w->roots[newRootIndex], "open url:%s", | 1569 | postCommandf_Root(w->roots[newRootIndex], "open origin:%s url:%s", |
1570 | cstr_String(d->pendingSplitOrigin), | ||
1532 | cstr_String(d->pendingSplitUrl)); | 1571 | cstr_String(d->pendingSplitUrl)); |
1533 | clear_String(d->pendingSplitUrl); | 1572 | clear_String(d->pendingSplitUrl); |
1573 | clear_String(d->pendingSplitOrigin); | ||
1534 | } | 1574 | } |
1535 | else if (~splitFlags & noEvents_WindowSplit) { | 1575 | else if (~splitFlags & noEvents_WindowSplit) { |
1536 | iWidget *docTabs0 = findChild_Widget(w->roots[newRootIndex ^ 1]->widget, "doctabs"); | 1576 | iWidget *docTabs0 = findChild_Widget(w->roots[newRootIndex ^ 1]->widget, "doctabs"); |
1537 | iWidget *docTabs1 = findChild_Widget(w->roots[newRootIndex]->widget, "doctabs"); | 1577 | iWidget *docTabs1 = findChild_Widget(w->roots[newRootIndex]->widget, "doctabs"); |
1538 | /* If the old root has multiple tabs, move the current one to the new split. */ | 1578 | /* If the old root has multiple tabs, move the current one to the new split. */ |
1539 | if (tabCount_Widget(docTabs0) >= 2) { | 1579 | if (tabCount_Widget(docTabs0) >= 2) { |
1540 | int movedIndex = tabPageIndex_Widget(docTabs0, moved); | 1580 | size_t movedIndex = tabPageIndex_Widget(docTabs0, moved); |
1541 | removeTabPage_Widget(docTabs0, movedIndex); | 1581 | removeTabPage_Widget(docTabs0, movedIndex); |
1542 | showTabPage_Widget(docTabs0, tabPage_Widget(docTabs0, iMax(movedIndex - 1, 0))); | 1582 | showTabPage_Widget(docTabs0, tabPage_Widget(docTabs0, iMax((int) movedIndex - 1, 0))); |
1543 | iRelease(removeTabPage_Widget(docTabs1, 0)); /* delete the default tab */ | 1583 | iRelease(removeTabPage_Widget(docTabs1, 0)); /* delete the default tab */ |
1544 | setRoot_Widget(as_Widget(moved), w->roots[newRootIndex]); | 1584 | setRoot_Widget(as_Widget(moved), w->roots[newRootIndex]); |
1545 | prependTabPage_Widget(docTabs1, iClob(moved), "", 0, 0); | 1585 | prependTabPage_Widget(docTabs1, iClob(moved), "", 0, 0); |
diff --git a/src/ui/window.h b/src/ui/window.h index 6c921f09..b4e348d2 100644 --- a/src/ui/window.h +++ b/src/ui/window.h | |||
@@ -114,8 +114,10 @@ struct Impl_MainWindow { | |||
114 | int splitMode; | 114 | int splitMode; |
115 | int pendingSplitMode; | 115 | int pendingSplitMode; |
116 | iString * pendingSplitUrl; /* URL to open in a newly opened split */ | 116 | iString * pendingSplitUrl; /* URL to open in a newly opened split */ |
117 | iString * pendingSplitOrigin; /* tab from where split was initiated, if any */ | ||
117 | SDL_Texture * appIcon; | 118 | SDL_Texture * appIcon; |
118 | int keyboardHeight; /* mobile software keyboards */ | 119 | int keyboardHeight; /* mobile software keyboards */ |
120 | int maxDrawableHeight; | ||
119 | }; | 121 | }; |
120 | 122 | ||
121 | iLocalDef enum iWindowType type_Window(const iAnyWindow *d) { | 123 | iLocalDef enum iWindowType type_Window(const iAnyWindow *d) { |
@@ -186,10 +188,10 @@ void setKeyboardHeight_MainWindow (iMainWindow *, int height); | |||
186 | void setSplitMode_MainWindow (iMainWindow *, int splitMode); | 188 | void setSplitMode_MainWindow (iMainWindow *, int splitMode); |
187 | void checkPendingSplit_MainWindow (iMainWindow *); | 189 | void checkPendingSplit_MainWindow (iMainWindow *); |
188 | void swapRoots_MainWindow (iMainWindow *); | 190 | void swapRoots_MainWindow (iMainWindow *); |
189 | void showToolbars_MainWindow (iMainWindow *, iBool show); | 191 | //void showToolbars_MainWindow (iMainWindow *, iBool show); |
190 | void resize_MainWindow (iMainWindow *, int w, int h); | 192 | void resize_MainWindow (iMainWindow *, int w, int h); |
191 | 193 | ||
192 | iBool processEvent_MainWindow (iMainWindow *, const SDL_Event *); | 194 | //iBool processEvent_MainWindow (iMainWindow *, const SDL_Event *); |
193 | void draw_MainWindow (iMainWindow *); | 195 | void draw_MainWindow (iMainWindow *); |
194 | void drawWhileResizing_MainWindow (iMainWindow *, int w, int h); /* workaround for SDL bug */ | 196 | void drawWhileResizing_MainWindow (iMainWindow *, int w, int h); /* workaround for SDL bug */ |
195 | 197 | ||
diff --git a/src/visited.c b/src/visited.c index 4552a053..83e09071 100644 --- a/src/visited.c +++ b/src/visited.c | |||
@@ -105,7 +105,7 @@ void load_Visited(iVisited *d, const char *dirPath) { | |||
105 | char *endp = NULL; | 105 | char *endp = NULL; |
106 | const unsigned long long ts = strtoull(line.start, &endp, 10); | 106 | const unsigned long long ts = strtoull(line.start, &endp, 10); |
107 | if (ts == 0) break; | 107 | if (ts == 0) break; |
108 | const uint32_t flags = strtoul(skipSpace_CStr(endp), &endp, 16); | 108 | const uint32_t flags = (uint32_t) strtoul(skipSpace_CStr(endp), &endp, 16); |
109 | const char *urlStart = skipSpace_CStr(endp); | 109 | const char *urlStart = skipSpace_CStr(endp); |
110 | iVisitedUrl item; | 110 | iVisitedUrl item; |
111 | item.when.ts = (struct timespec){ .tv_sec = ts }; | 111 | item.when.ts = (struct timespec){ .tv_sec = ts }; |