From aab32f03ba1ae3ee6745c7122f86b1cf3c8b99c6 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 19 Jan 2022 15:47:33 +0200 Subject: Updated the_Foundation: gzip decompress --- lib/the_Foundation | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/the_Foundation b/lib/the_Foundation index 453f05f6..f2f7fef5 160000 --- a/lib/the_Foundation +++ b/lib/the_Foundation @@ -1 +1 @@ -Subproject commit 453f05f6efb46824ff0dca0174036c7624473e43 +Subproject commit f2f7fef560fc8642e3e449239d7e00f14491e13c -- cgit v1.2.3 From ea1b72af1b43c846ada3380ba422fffd400ed5b4 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 19 Jan 2022 15:49:26 +0200 Subject: Bumped version number to 1.11 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 97d5d279..b185f646 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ cmake_minimum_required (VERSION 3.9) project (Lagrange - VERSION 1.10.1 + VERSION 1.11.0 DESCRIPTION "A Beautiful Gemini Client" LANGUAGES C ) -- cgit v1.2.3 From a6146ac91c90a4003efd489ee3ada175203995cc Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 19 Jan 2022 15:51:07 +0200 Subject: Fontpack lookup via missing glyphs IssueID #435 --- po/en.po | 27 +++++++++++++ res/lang/cs.bin | Bin 32069 -> 32702 bytes res/lang/de.bin | Bin 30896 -> 31529 bytes res/lang/en.bin | Bin 26965 -> 27598 bytes res/lang/eo.bin | Bin 26163 -> 26796 bytes res/lang/es.bin | Bin 30784 -> 31417 bytes res/lang/es_MX.bin | Bin 28035 -> 28668 bytes res/lang/fi.bin | Bin 30606 -> 31239 bytes res/lang/fr.bin | Bin 31693 -> 32326 bytes res/lang/gl.bin | Bin 29952 -> 30585 bytes res/lang/hu.bin | Bin 31718 -> 32351 bytes res/lang/ia.bin | Bin 29748 -> 30381 bytes res/lang/ie.bin | Bin 29655 -> 30288 bytes res/lang/isv.bin | Bin 25686 -> 26319 bytes res/lang/nl.bin | Bin 29075 -> 29708 bytes res/lang/pl.bin | Bin 30321 -> 30954 bytes res/lang/ru.bin | Bin 45641 -> 46274 bytes res/lang/sk.bin | Bin 26022 -> 26655 bytes res/lang/sr.bin | Bin 44947 -> 45580 bytes res/lang/tok.bin | Bin 27801 -> 28434 bytes res/lang/tr.bin | Bin 29902 -> 30535 bytes res/lang/uk.bin | Bin 45028 -> 45661 bytes res/lang/zh_Hans.bin | Bin 25930 -> 26563 bytes res/lang/zh_Hant.bin | Bin 26328 -> 26961 bytes src/app.c | 53 +++++++++++++++++++------- src/app.h | 1 + src/fontpack.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++- src/fontpack.h | 2 + src/ui/banner.c | 3 +- src/ui/documentwidget.c | 1 + src/ui/text.c | 40 ++++++++++++++++--- src/ui/text.h | 2 + src/ui/util.c | 48 +++++++++++++++++++++++ src/ui/util.h | 1 + 34 files changed, 256 insertions(+), 21 deletions(-) diff --git a/po/en.po b/po/en.po index e0e086e3..cc5f2091 100644 --- a/po/en.po +++ b/po/en.po @@ -1181,6 +1181,33 @@ msgstr "Russian" msgid "lang.es" msgstr "Spanish" +msgid "heading.glyphfinder" +msgstr "Missing Glyphs" + +msgid "dlg.glyphfinder.missing" +msgstr "The following characters could not be shown:" + +msgid "dlg.glyphfinder.help" +msgstr "You can try searching the skyjake.fi Font Library for fonts that provide glyphs for these characters, or manually install new TrueType fonts." + +msgid "dlg.glyphfinder.help.empty" +msgstr "Please reload the page to check again for missing glyphs." + +msgid "dlg.glyphfinder.disable" +msgstr "Disable Warnings" + +msgid "dlg.glyphfinder.search" +msgstr "Search Font Library" + +msgid "heading.glyphfinder.results" +msgstr "Search Results" + +msgid "glyphfinder.results" +msgstr "The following fontpacks provide one or more of the missing glyphs:" + +msgid "glyphfinder.results.empty" +msgstr "Sorry, no matching fontpacks were found." + msgid "heading.newident" msgstr "New Identity" diff --git a/res/lang/cs.bin b/res/lang/cs.bin index eb59f65d..6f1afa85 100644 Binary files a/res/lang/cs.bin and b/res/lang/cs.bin differ diff --git a/res/lang/de.bin b/res/lang/de.bin index 9c3d4541..ecb601aa 100644 Binary files a/res/lang/de.bin and b/res/lang/de.bin differ diff --git a/res/lang/en.bin b/res/lang/en.bin index 5f649846..758adbba 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/eo.bin b/res/lang/eo.bin index ace592df..1de8a7db 100644 Binary files a/res/lang/eo.bin and b/res/lang/eo.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 14c1b843..a11c6f78 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin index dfcd6306..40379ee0 100644 Binary files a/res/lang/es_MX.bin and b/res/lang/es_MX.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 44d2741d..18a4c11d 100644 Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ diff --git a/res/lang/fr.bin b/res/lang/fr.bin index ad16b2e7..ba098242 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/gl.bin b/res/lang/gl.bin index f1501b4a..7727f587 100644 Binary files a/res/lang/gl.bin and b/res/lang/gl.bin differ diff --git a/res/lang/hu.bin b/res/lang/hu.bin index c070dd49..94af8aa8 100644 Binary files a/res/lang/hu.bin and b/res/lang/hu.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 34633dd1..1aac22fe 100644 Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ diff --git a/res/lang/ie.bin b/res/lang/ie.bin index 722ee20d..4d5ac8d9 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/isv.bin b/res/lang/isv.bin index 3be01643..c3931a80 100644 Binary files a/res/lang/isv.bin and b/res/lang/isv.bin differ diff --git a/res/lang/nl.bin b/res/lang/nl.bin index dcc9fe97..579f0df8 100644 Binary files a/res/lang/nl.bin and b/res/lang/nl.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index a32e9d10..448be098 100644 Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index e434971b..04114010 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sk.bin b/res/lang/sk.bin index 833872c9..fc984d97 100644 Binary files a/res/lang/sk.bin and b/res/lang/sk.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index 43a6deda..ef949942 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tok.bin b/res/lang/tok.bin index d17f075e..cfbee57e 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/tr.bin b/res/lang/tr.bin index 06fc22e5..8552574c 100644 Binary files a/res/lang/tr.bin and b/res/lang/tr.bin differ diff --git a/res/lang/uk.bin b/res/lang/uk.bin index 23329907..8f39c7fd 100644 Binary files a/res/lang/uk.bin and b/res/lang/uk.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 246b7c42..2f0c03fa 100644 Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index 0e94f66a..f978013c 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/app.c b/src/app.c index 4045610e..31f506b5 100644 --- a/src/app.c +++ b/src/app.c @@ -2245,6 +2245,13 @@ void resetFonts_App(void) { } } +void availableFontsChanged_App(void) { + iApp *d = &app_; + iConstForEach(PtrArray, win, listWindows_App_(d, collectNew_PtrArray())) { + resetMissing_Text(text_Window(win.ptr)); + } +} + static void invalidateCachedDocuments_App_(void) { iForEach(ObjectList, i, iClob(listDocuments_App(NULL))) { invalidateCachedLayout_History(history_DocumentWidget(i.object)); @@ -2371,24 +2378,42 @@ iBool handleCommand_App(const char *cmd) { reload_Fonts(); /* also does font cache reset, window invalidation */ return iTrue; } -#if 0 - else if (equal_Command(cmd, "font.user")) { - const char *path = suffixPtr_Command(cmd, "path"); - if (cmp_String(&d->prefs.symbolFontPath, path)) { - if (!isFrozen) { - setFreezeDraw_MainWindow(get_MainWindow(), iTrue); - } - setCStr_String(&d->prefs.symbolFontPath, path); - loadUserFonts_Text(); - resetFonts_App(d); - if (!isFrozen) { - postCommand_App("font.changed"); - postCommand_App("window.unfreeze"); + else if (equal_Command(cmd, "font.find")) { + searchOnlineLibraryForCharacters_Fonts(string_Command(cmd, "chars")); + return iTrue; + } + else if (equal_Command(cmd, "font.found")) { + if (hasLabel_Command(cmd, "error")) { + makeSimpleMessage_Widget("${heading.glyphfinder}", + format_CStr("%d %s", + argLabel_Command(cmd, "error"), + suffixPtr_Command(cmd, "msg"))); + return iTrue; + } + iString *src = collectNew_String(); + setCStr_String(src, "# ${heading.glyphfinder.results}\n\n"); + iRangecc path = iNullRange; + iBool isFirst = iTrue; + while (nextSplit_Rangecc(range_Command(cmd, "packs"), ";", &path)) { + if (isFirst) { + appendCStr_String(src, "${glyphfinder.results}\n\n"); } + const char *fp = cstr_Rangecc(path); + appendFormat_String(src, "=> gemini://skyjake.fi/fonts/%s %s\n", fp, fp); + isFirst = iFalse; + } + if (isFirst) { + appendFormat_String(src, "${glyphfinder.results.empty}\n"); } + appendCStr_String(src, "\n=> about:fonts ${menu.fonts}"); + iDocumentWidget *page = newTab_App(NULL, iTrue); + translate_Lang(src); + setUrlAndSource_DocumentWidget(page, + collectNewCStr_String(""), + collectNewCStr_String("text/gemini"), + utf8_String(src)); return iTrue; } -#endif else if (equal_Command(cmd, "font.set")) { if (!isFrozen) { setFreezeDraw_MainWindow(get_MainWindow(), iTrue); diff --git a/src/app.h b/src/app.h index 5968de0d..1a4aa556 100644 --- a/src/app.h +++ b/src/app.h @@ -142,6 +142,7 @@ iDocumentWidget * document_Command (const char *cmd); void openInDefaultBrowser_App(const iString *url); void revealPath_App (const iString *path); void resetFonts_App (void); +void availableFontsChanged_App(void); iMainWindow * mainWindow_App (void); void closePopups_App (void); diff --git a/src/fontpack.c b/src/fontpack.c index a440234e..3faf0e92 100644 --- a/src/fontpack.c +++ b/src/fontpack.c @@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "fontpack.h" #include "resources.h" #include "ui/window.h" +#include "gmrequest.h" #include "app.h" #include @@ -1018,6 +1019,7 @@ void install_Fonts(const iString *packId, const iBlock *data) { iRelease(f); /* Newly installed fontpacks may have a higher priority that overrides other fonts. */ reload_Fonts(); + availableFontsChanged_App(); } void installFontFile_Fonts(const iString *fileName, const iBlock *data) { @@ -1028,6 +1030,7 @@ void installFontFile_Fonts(const iString *fileName, const iBlock *data) { } iRelease(f); reload_Fonts(); + availableFontsChanged_App(); } void enablePack_Fonts(const iString *packId, iBool enable) { @@ -1040,6 +1043,7 @@ void enablePack_Fonts(const iString *packId, iBool enable) { } updateActive_Fonts(); resetFonts_App(); + availableFontsChanged_App(); invalidate_Window(get_MainWindow()); } @@ -1047,5 +1051,98 @@ void updateActive_Fonts(void) { sortSpecs_Fonts_(&fonts_); } -iDefineClass(FontFile) +static void findCharactersInCMap_(iGmRequest *d, iGmRequest *req) { + /* Note: Called in background thread. */ + iUnused(req); + const iString *missingChars = userData_Object(d); + if (isSuccess_GmStatusCode(status_GmRequest(d))) { + iStringList *matchingPacks = new_StringList(); + iChar needed[20]; + iChar minChar = UINT32_MAX, maxChar = 0; + size_t numNeeded = 0; + iConstForEach(String, ch, missingChars) { + needed[numNeeded++] = ch.value; + minChar = iMin(minChar, ch.value); + maxChar = iMax(maxChar, ch.value); + if (numNeeded == iElemCount(needed)) { + /* Shouldn't be that many. */ + break; + } + } + iBlock *data = decompressGzip_Block(body_GmRequest(d)); + iRangecc line = iNullRange; + while (nextSplit_Rangecc(range_Block(data), "\n", &line)) { + iRangecc fontpackPath = iNullRange; + for (const char *pos = line.start; pos < line.end; pos++) { + if (*pos == ':') { + fontpackPath.start = line.start; + fontpackPath.end = pos; + line.start = pos + 1; + trimStart_Rangecc(&line); + break; + } + } + if (fontpackPath.start) { + /* Parse the character ranges and see if any match what we need. */ + const char *pos = line.start; + while (pos < line.end) { + char *endp; + uint32_t first = strtoul(pos, &endp, 10); + uint32_t last = first; + if (*endp == '-') { + last = strtoul(endp + 1, &endp, 10); + } + if (maxChar < first) { + break; /* The rest are even higher. */ + } + if (minChar <= last) { + for (size_t i = 0; i < numNeeded; i++) { + if (needed[i] >= first && needed[i] <= last) { + /* Got it. */ + pushBackRange_StringList(matchingPacks, fontpackPath); + break; + } + } + } + pos = endp + 1; + } + } + } + delete_Block(data); + iString result; + init_String(&result); + format_String(&result, "font.found chars:%s packs:", cstr_String(missingChars)); + iConstForEach(StringList, s, matchingPacks) { + if (s.pos != 0) { + appendCStr_String(&result, ";"); + } + append_String(&result, s.value); + } + postCommandString_Root(NULL, &result); + deinit_String(&result); + iRelease(matchingPacks); + } + else { + /* Report error. */ + postCommandf_Root(NULL, + "font.found chars:%s error:%d msg:\x1b[1m%s\x1b[0m\n%s", + cstr_String(missingChars), + status_GmRequest(d), + cstr_String(meta_GmRequest(d)), + cstr_String(url_GmRequest(d))); + } + fflush(stdout); + delete_String(userData_Object(d)); + iReleaseLater(d); +} +void searchOnlineLibraryForCharacters_Fonts(const iString *chars) { + /* Fetch the character map from skyjake.fi. */ + iGmRequest *req = new_GmRequest(certs_App()); + setUrl_GmRequest(req, collectNewCStr_String("gemini://skyjake.fi/fonts/cmap.txt.gz")); + setUserData_Object(req, copy_String(chars)); + iConnect(GmRequest, req, finished, req, findCharactersInCMap_); + submit_GmRequest(req); +} + +iDefineClass(FontFile) diff --git a/src/fontpack.h b/src/fontpack.h index aa6f2b9f..27ecd087 100644 --- a/src/fontpack.h +++ b/src/fontpack.h @@ -186,3 +186,5 @@ void reload_Fonts (void); iLocalDef iBool isInstalled_Fonts(const char *packId) { return pack_Fonts(packId) != NULL; } + +void searchOnlineLibraryForCharacters_Fonts (const iString *chars); diff --git a/src/ui/banner.c b/src/ui/banner.c index 11ae1574..79d70039 100644 --- a/src/ui/banner.c +++ b/src/ui/banner.c @@ -327,7 +327,8 @@ iBool processEvent_Banner(iBanner *d, const SDL_Event *ev) { else { switch (item->code) { case missingGlyphs_GmStatusCode: - postCommandf_App("open newtab:1 url:about:fonts"); + //postCommandf_App("open newtab:1 url:about:fonts"); + makeGlyphFinder_Widget(); break; case ansiEscapes_GmStatusCode: makeQuestion_Widget( diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 6a535882..fdb55232 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -2756,6 +2756,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, } d->flags |= drawDownloadCounter_DocumentWidgetFlag; clear_PtrSet(d->view.invalidRuns); + documentRunsInvalidated_DocumentWidget_(d); deinit_String(&str); return; } diff --git a/src/ui/text.c b/src/ui/text.c index 7bb418eb..86ac709b 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -247,8 +247,6 @@ struct Impl_CacheRow { }; struct Impl_Text { -// enum iTextFont contentFont; -// enum iTextFont headingFont; float contentFontSize; iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */ int overrideFontId; /* always checked for glyphs first, regardless of which font is used */ @@ -264,7 +262,8 @@ struct Impl_Text { int ansiFlags; int baseFontId; /* base attributes (for restoring via escapes) */ int baseFgColorId; - iBool missingGlyphs; /* true if a glyph couldn't be found */ + iBool missingGlyphs; /* true if a glyph couldn't be found */ + iChar missingChars[20]; /* rotating buffer of the latest missing characters */ }; iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) @@ -341,6 +340,8 @@ static void initFonts_Text_(iText *d) { printf("[Text] %zu font variants ready\n", size_Array(&d->fonts)); #endif gap_Text = iRound(gap_UI * d->contentFontSize); +// d->missingGlyphs = iFalse; +// iZap(d->missingChars); } static void deinitFonts_Text_(iText *d) { @@ -403,6 +404,7 @@ void init_Text(iText *d, SDL_Renderer *render) { d->baseFontId = -1; d->baseFgColorId = -1; d->missingGlyphs = iFalse; + iZap(d->missingChars); d->render = render; /* A grayscale palette for rasterized glyphs. */ { SDL_Color colors[256]; @@ -589,8 +591,23 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { } } if (!*glyphIndex) { - activeText_->missingGlyphs = iTrue; - fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr); + fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int) ch); fflush(stderr); + iText *tx = activeText_; + tx->missingGlyphs = iTrue; + /* Remember a few of the latest missing characters. */ + iBool gotIt = iFalse; + for (size_t i = 0; i < iElemCount(tx->missingChars); i++) { + if (tx->missingChars[i] == ch) { + gotIt = iTrue; + break; + } + } + if (!gotIt) { + memmove(tx->missingChars + 1, + tx->missingChars, + sizeof(tx->missingChars) - sizeof(tx->missingChars[0])); + tx->missingChars[0] = ch; + } } return d; } @@ -2199,6 +2216,19 @@ iBool checkMissing_Text(void) { return missing; } +iChar missing_Text(size_t index) { + const iText *d = activeText_; + if (index >= iElemCount(d->missingChars)) { + return 0; + } + return d->missingChars[index]; +} + +void resetMissing_Text(iText *d) { + d->missingGlyphs = iFalse; + iZap(d->missingChars); +} + SDL_Texture *glyphCache_Text(void) { return activeText_->cache; } diff --git a/src/ui/text.h b/src/ui/text.h index c8bb6f85..a34cc9bd 100644 --- a/src/ui/text.h +++ b/src/ui/text.h @@ -227,6 +227,8 @@ struct Impl_WrapText { iTextMetrics measure_WrapText (iWrapText *, int fontId); iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); +iChar missing_Text (size_t index); +void resetMissing_Text (iText *); iBool checkMissing_Text (void); /* returns the flag, and clears it */ SDL_Texture * glyphCache_Text (void); diff --git a/src/ui/util.c b/src/ui/util.c index 31907721..8e71dcec 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -3451,6 +3451,54 @@ iWidget *makeTranslation_Widget(iWidget *parent) { return dlg; } +iWidget *makeGlyphFinder_Widget(void) { + iString msg; + iString command; + init_String(&msg); + initCStr_String(&command, "!font.find chars:"); + for (size_t i = 0; ; i++) { + iChar ch = missing_Text(i); + if (!ch) break; + appendFormat_String(&msg, " U+%04X", ch); + appendChar_String(&command, ch); + } + iArray items; + init_Array(&items, sizeof(iMenuItem)); + if (!isEmpty_String(&msg)) { + prependCStr_String(&msg, "${dlg.glyphfinder.missing} "); + appendCStr_String(&msg, "\n\n${dlg.glyphfinder.help}"); + pushBackN_Array( + &items, + (iMenuItem[]){ + { "${menu.fonts}", 0, 0, "!open newtab:1 url:about:fonts" }, + { "${dlg.glyphfinder.disable}", 0, 0, "prefs.font.warnmissing.changed arg:0" }, + { "---" }, + { uiTextCaution_ColorEscape magnifyingGlass_Icon " ${dlg.glyphfinder.search}", + 0, + 0, + cstr_String(&command) }, + { "${close}", 0, 0, "cancel" } }, + 5); + } + else { + setCStr_String(&msg, "${dlg.glyphfinder.help.empty}"); + pushBackN_Array(&items, + (iMenuItem[]){ { "${menu.reload}", 0, 0, "navigate.reload" }, + { "${close}", 0, 0, "cancel" } }, + 2); + } + iWidget *dlg = makeQuestion_Widget("${heading.glyphfinder}", cstr_String(&msg), + constData_Array(&items), + size_Array(&items)); + arrange_Widget(dlg); + deinit_Array(&items); + deinit_String(&command); + deinit_String(&msg); + return dlg; +} + +/*----------------------------------------------------------------------------------------------*/ + void init_PerfTimer(iPerfTimer *d) { d->ticks = SDL_GetPerformanceCounter(); } diff --git a/src/ui/util.h b/src/ui/util.h index 98ce784c..0289d579 100644 --- a/src/ui/util.h +++ b/src/ui/util.h @@ -342,6 +342,7 @@ iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *titl iWidget * makeIdentityCreation_Widget (void); iWidget * makeFeedSettings_Widget (uint32_t bookmarkId); iWidget * makeTranslation_Widget (iWidget *parent); +iWidget * makeGlyphFinder_Widget (void); const char * languageId_String (const iString *menuItemLabel); int languageIndex_CStr (const char *langId); -- cgit v1.2.3 From 7c08d8c02cf03bcc3036d7d47547cd5bb273fa85 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Thu, 20 Jan 2022 06:01:49 +0200 Subject: App: Font Library cmap includes pack sizes --- src/app.c | 17 ++++++++++++++--- src/app.h | 1 + src/fontpack.c | 8 +++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/app.c b/src/app.c index 31f506b5..4d0e0f79 100644 --- a/src/app.c +++ b/src/app.c @@ -1362,6 +1362,10 @@ void processEvents_App(enum iAppEventMode eventMode) { dispatchCommands_Periodic(&d->periodic); continue; } + if (ev.type == SDL_USEREVENT && ev.user.code == releaseObject_UserEventCode) { + iRelease(ev.user.data1); + continue; + } #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) { if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_ && @@ -2394,12 +2398,19 @@ iBool handleCommand_App(const char *cmd) { setCStr_String(src, "# ${heading.glyphfinder.results}\n\n"); iRangecc path = iNullRange; iBool isFirst = iTrue; - while (nextSplit_Rangecc(range_Command(cmd, "packs"), ";", &path)) { + while (nextSplit_Rangecc(range_Command(cmd, "packs"), ",", &path)) { if (isFirst) { appendCStr_String(src, "${glyphfinder.results}\n\n"); } - const char *fp = cstr_Rangecc(path); - appendFormat_String(src, "=> gemini://skyjake.fi/fonts/%s %s\n", fp, fp); + iRangecc fpath = path; + iRangecc fsize = path; + fpath.end = strchr(fpath.start, ';'); + fsize.start = fpath.end + 1; + const uint32_t size = strtoul(fsize.start, NULL, 10); + appendFormat_String(src, "=> gemini://skyjake.fi/fonts/%s %s (%.1f MB)\n", + cstr_Rangecc(fpath), + cstr_Rangecc(fpath), + (double) size / 1.0e6); isFirst = iFalse; } if (isFirst) { diff --git a/src/app.h b/src/app.h index 1a4aa556..22fe5d46 100644 --- a/src/app.h +++ b/src/app.h @@ -67,6 +67,7 @@ enum iUserEventCode { take, it could turn into a tap-and-hold for example. */ widgetTapBegins_UserEventCode, widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */ + releaseObject_UserEventCode, /* object that needs releasing in the main thread */ }; const iString *execPath_App (void); diff --git a/src/fontpack.c b/src/fontpack.c index 3faf0e92..924d4781 100644 --- a/src/fontpack.c +++ b/src/fontpack.c @@ -1114,7 +1114,7 @@ static void findCharactersInCMap_(iGmRequest *d, iGmRequest *req) { format_String(&result, "font.found chars:%s packs:", cstr_String(missingChars)); iConstForEach(StringList, s, matchingPacks) { if (s.pos != 0) { - appendCStr_String(&result, ";"); + appendCStr_String(&result, ","); } append_String(&result, s.value); } @@ -1131,9 +1131,11 @@ static void findCharactersInCMap_(iGmRequest *d, iGmRequest *req) { cstr_String(meta_GmRequest(d)), cstr_String(url_GmRequest(d))); } - fflush(stdout); +// fflush(stdout); delete_String(userData_Object(d)); - iReleaseLater(d); + /* We can't delete ourselves; threads must be joined from another thread. */ + SDL_PushEvent((SDL_Event *) &(SDL_UserEvent){ + .type = SDL_USEREVENT, .code = releaseObject_UserEventCode, .data1 = d }); } void searchOnlineLibraryForCharacters_Fonts(const iString *chars) { -- cgit v1.2.3 From df37aaff1278b8f283af421a0c47cffcf0cbeb06 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Thu, 20 Jan 2022 06:07:15 +0200 Subject: Updated language strings --- res/lang/cs.bin | Bin 32702 -> 32719 bytes res/lang/es.bin | Bin 31417 -> 31441 bytes res/lang/ru.bin | Bin 46274 -> 46301 bytes res/lang/sr.bin | Bin 45580 -> 45597 bytes res/lang/tr.bin | Bin 30535 -> 30545 bytes res/lang/uk.bin | Bin 45661 -> 45678 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/res/lang/cs.bin b/res/lang/cs.bin index 6f1afa85..98cd5d32 100644 Binary files a/res/lang/cs.bin and b/res/lang/cs.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index a11c6f78..d50a2cae 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index 04114010..098c2b11 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index ef949942..49ec2b90 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tr.bin b/res/lang/tr.bin index 8552574c..8e9a09c4 100644 Binary files a/res/lang/tr.bin and b/res/lang/tr.bin differ diff --git a/res/lang/uk.bin b/res/lang/uk.bin index 8f39c7fd..ae80d456 100644 Binary files a/res/lang/uk.bin and b/res/lang/uk.bin differ -- cgit v1.2.3 From abb060ba17024759a7cfcc7b9256742e1c1e418b Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 21 Jan 2022 13:50:56 +0200 Subject: Fonts: Show abbreviated list of names on "about:fonts" A big set can be viewed separately to see all the individual fonts. --- src/fontpack.c | 19 ++++++++++++++----- src/fontpack.h | 2 +- src/ui/documentwidget.c | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/fontpack.c b/src/fontpack.c index 924d4781..96006226 100644 --- a/src/fontpack.c +++ b/src/fontpack.c @@ -749,7 +749,7 @@ const iPtrArray *listSpecsByPriority_Fonts(void) { return &fonts_.specOrder; } -iString *infoText_FontPack(const iFontPack *d) { +iString *infoText_FontPack(const iFontPack *d, iBool isFull) { const iFontPack *installed = pack_Fonts(cstr_String(&d->id)); const iBool isInstalled = (installed != NULL); const int installedVersion = installed ? installed->version : 0; @@ -758,9 +758,17 @@ iString *infoText_FontPack(const iFontPack *d) { size_t sizeInBytes = 0; iPtrSet *uniqueFiles = new_PtrSet(); iStringList *names = new_StringList(); + size_t numNames = 0; + iBool isAbbreviated = iFalse; iConstForEach(PtrArray, i, listSpecs_FontPack(d)) { const iFontSpec *spec = i.ptr; - pushBack_StringList(names, &spec->name); + numNames++; + if (isFull || size_StringList(names) < 20) { + pushBack_StringList(names, &spec->name); + } + else { + isAbbreviated = iTrue; + } iForIndices(j, spec->styles) { insert_PtrSet(uniqueFiles, spec->styles[j]->sourceData.i); } @@ -778,11 +786,12 @@ iString *infoText_FontPack(const iFontPack *d) { if (!endsWith_String(str, "(")) { appendCStr_String(str, ", "); } - appendCStr_String(str, formatCStrs_Lang("num.fonts.n", size_StringList(names))); + appendCStr_String(str, formatCStrs_Lang("num.fonts.n", numNames)); } appendFormat_String(str, ")"); } - appendFormat_String(str, " \u2014 %s\n", cstrCollect_String(joinCStr_StringList(names, ", "))); + appendFormat_String(str, " \u2014 %s%s\n", cstrCollect_String(joinCStr_StringList(names, ", ")), + isAbbreviated ? ", ..." : ""); if (isInstalled && installedVersion != d->version) { appendCStr_String(str, format_Lang("${fontpack.meta.version}\n", d->version)); } @@ -946,7 +955,7 @@ const iString *infoPage_Fonts(iRangecc query) { appendFormat_String(str, "### %s\n", isEmpty_String(packId) ? "fonts.ini" : cstr_String(packId)); - append_String(str, collect_String(infoText_FontPack(pack))); + append_String(str, collect_String(infoText_FontPack(pack, iFalse))); appendFormat_String(str, "=> %s ${fontpack.meta.viewfile}\n", cstrCollect_String(makeFileUrl_String(&spec->sourcePath))); if (pack->isStandalone) { diff --git a/src/fontpack.h b/src/fontpack.h index 27ecd087..f6d4d483 100644 --- a/src/fontpack.h +++ b/src/fontpack.h @@ -160,7 +160,7 @@ const iString * loadPath_FontPack (const iFontPack *); /* may return N iBool isDisabled_FontPack (const iFontPack *); iBool isReadOnly_FontPack (const iFontPack *); const iPtrArray * listSpecs_FontPack (const iFontPack *); -iString * infoText_FontPack (const iFontPack *); +iString * infoText_FontPack (const iFontPack *, iBool isFull); const iArray * actions_FontPack (const iFontPack *, iBool showInstalled); const iString * idFromUrl_FontPack (const iString *url); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index fdb55232..03119ca2 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -2659,7 +2659,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, if (loadArchive_FontPack(fp, zip)) { appendFormat_String(&str, "# " fontpack_Icon "%s\n%s", cstr_String(id_FontPack(fp).id), - cstrCollect_String(infoText_FontPack(fp))); + cstrCollect_String(infoText_FontPack(fp, iTrue))); } appendCStr_String(&str, "\n"); appendCStr_String(&str, cstr_Lang("fontpack.help")); -- cgit v1.2.3 From edb260848a42de2e1af44cab93dbf267d20e1f6f Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sat, 22 Jan 2022 13:01:46 +0200 Subject: Updated Smol Emoji --- res/fonts/SmolEmoji-Regular.ttf | Bin 58544 -> 67168 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/res/fonts/SmolEmoji-Regular.ttf b/res/fonts/SmolEmoji-Regular.ttf index 3ab9484b..a9694f3a 100644 Binary files a/res/fonts/SmolEmoji-Regular.ttf and b/res/fonts/SmolEmoji-Regular.ttf differ -- cgit v1.2.3 From e9b2b0fa360c1802ca05f744e85702e8a0a98cd0 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 7 Feb 2022 06:11:05 +0200 Subject: Cleanup --- src/ui/documentwidget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 97ecb4ba..31b96644 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -1804,7 +1804,7 @@ static void draw_DocumentView_(const iDocumentView *d) { } if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) { updateSideIconBuf_DocumentView_(d); - } + } const iRect docBounds = documentBounds_DocumentView_(d); const iRangei vis = visibleRange_DocumentView_(d); iDrawContext ctx = { -- cgit v1.2.3 From 5be7f76e30f6655f82a677b73c7fcea4feeda42d Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 7 Feb 2022 10:52:57 +0200 Subject: GmDocument: Handling images in "data:" URLs One can embed arbitrary data inside a "data:" URL. If an image is recognized, attempt to show it as part of the document. --- src/gmdocument.c | 3 +++ src/ui/documentwidget.c | 25 +++++++++++++++++++++---- src/ui/linkinfo.c | 8 ++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/gmdocument.c b/src/gmdocument.c index b5e71e21..4978d97d 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -369,6 +369,9 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li } else if (equalCase_Rangecc(parts.scheme, "data")) { setScheme_GmLink_(link, data_GmLinkScheme); + if (startsWith_Rangecc(parts.path, "image/")) { + link->flags |= imageFileExtension_GmLinkFlag; + } } else if (equalCase_Rangecc(parts.scheme, "about")) { setScheme_GmLink_(link, about_GmLinkScheme); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 31b96644..b19958b2 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -358,6 +358,7 @@ static void updateSideIconBuf_DocumentWidget_ (const iDocumentWidget *d); static void prerender_DocumentWidget_ (iAny *); static void scrollBegan_DocumentWidget_ (iAnyObject *, int, uint32_t); static void refreshWhileScrolling_DocumentWidget_ (iAny *); +static iBool requestMedia_DocumentWidget_ (iDocumentWidget *d, iGmLinkId linkId, iBool enableFilters); /* TODO: The following methods are called from DocumentView, which goes the wrong way. */ @@ -2390,6 +2391,19 @@ static const char *zipPageHeading_(const iRangecc mime) { static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) { iWidget *w = as_Widget(d); + /* Embedded images in data links should be shown immediately as they are already fetched + data that is part of the document. */ { + iGmDocument *doc = d->view.doc; + for (size_t linkId = 1; ; linkId++) { + const int linkFlags = linkFlags_GmDocument(doc, linkId); + const iString *linkUrl = linkUrl_GmDocument(doc, linkId); + if (!linkUrl) break; + if (scheme_GmLinkFlag(linkFlags) == data_GmLinkScheme && + (linkFlags & imageFileExtension_GmLinkFlag)) { + requestMedia_DocumentWidget_(d, linkId, 0); + } + } + } /* Gempub page behavior and footer actions. */ { /* TODO: move this to gempub.c */ delete_Gempub(d->sourceGempub); @@ -3136,6 +3150,9 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { setUrl_DocumentWidget_(d, url_GmDocument(d->view.doc)); updateFetchProgress_DocumentWidget_(d); postCommand_Widget(d, "media.updated link:%u request:%p", d->requestLinkId, mr); + if (isFinished_GmRequest(mr->req)) { + postCommand_Widget(d, "media.finished link:%u request:%p", d->requestLinkId, mr); + } return; } /* Get ready for the incoming new document. */ @@ -5515,10 +5532,10 @@ static void prerender_DocumentWidget_(iAny *context) { } const iDocumentWidget *d = context; iDrawContext ctx = { - .view = &d->view, - .docBounds = documentBounds_DocumentView_(&d->view), - .vis = visibleRange_DocumentView_(&d->view), - .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0 + .view = &d->view, + .docBounds = documentBounds_DocumentView_(&d->view), + .vis = visibleRange_DocumentView_(&d->view), + .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0 }; // printf("%u prerendering\n", SDL_GetTicks()); if (d->view.visBuf->buffers[0].texture) { diff --git a/src/ui/linkinfo.c b/src/ui/linkinfo.c index 5102f9b3..8974a486 100644 --- a/src/ui/linkinfo.c +++ b/src/ui/linkinfo.c @@ -91,6 +91,14 @@ void infoText_LinkInfo(const iGmDocument *doc, iGmLinkId linkId, iString *text_o appendCStr_String(text_out, "\x1b[0m"); appendRange_String(text_out, (iRangecc){ parts.path.start, constEnd_String(url) }); } + else if (scheme == data_GmLinkScheme) { + appendCStr_String(text_out, "\U0001f4e6 "); + const char *comma = strchr(cstr_String(url), ','); + if (!comma) { + comma = iMin(constEnd_String(url), constBegin_String(url) + 256); + } + appendRange_String(text_out, (iRangecc){ constBegin_String(url), comma }); + } else if (scheme != gemini_GmLinkScheme) { appendCStr_String(text_out, scheme == file_GmLinkScheme ? "" : globe_Icon " "); append_String(text_out, url); -- cgit v1.2.3 From 1f4271ea540b85243df593d64b5d2230ad023343 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 7 Feb 2022 19:14:01 +0200 Subject: GmDocument: Icon for "data" URLs --- src/defs.h | 1 + src/gmdocument.c | 1 + 2 files changed, 2 insertions(+) diff --git a/src/defs.h b/src/defs.h index e2edd100..550ae5bd 100644 --- a/src/defs.h +++ b/src/defs.h @@ -186,6 +186,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { #define downAngle_Icon "\ufe40" #define photo_Icon "\U0001f5bc" #define fontpack_Icon "\U0001f520" +#define package_Icon "\U0001f4e6" #if defined (iPlatformApple) # define shift_Icon "\u21e7" diff --git a/src/gmdocument.c b/src/gmdocument.c index 4978d97d..69fe77f8 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -906,6 +906,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { : scheme == titan_GmLinkScheme ? uploadArrow : scheme == finger_GmLinkScheme ? pointingFinger : scheme == mailto_GmLinkScheme ? envelope + : scheme == data_GmLinkScheme ? package_Icon : link->flags & remote_GmLinkFlag ? globe : link->flags & imageFileExtension_GmLinkFlag ? image : link->flags & fontpackFileExtension_GmLinkFlag ? fontpack_Icon -- cgit v1.2.3 From 59363d3e230a0e9702e31936309473fb576ecec5 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 9 Feb 2022 10:41:55 +0200 Subject: Style consistency: popup frames A colored popup frame indicates an active UI element, like a dropdown selection of values. A popup with a separator-colored frame is a passive element, like a context menu. --- src/ui/util.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ui/util.c b/src/ui/util.c index 8e71dcec..d0d24eb2 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -903,6 +903,7 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { #else /* Non-native custom popup menu. This may still be displayed inside a separate window. */ setDrawBufferEnabled_Widget(menu, iTrue); + setFrameColor_Widget(menu, uiSeparator_ColorId); setBackgroundColor_Widget(menu, uiBackgroundMenu_ColorId); if (deviceType_App() != desktop_AppDeviceType) { setPadding1_Widget(menu, 2 * gap_UI); @@ -1084,12 +1085,12 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) { setFlags_Widget(d, hidden_WidgetFlag, iFalse); setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue); setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); - if (!isPortraitPhone) { - setFrameColor_Widget(d, uiBackgroundSelected_ColorId); - } - else { - setFrameColor_Widget(d, none_ColorId); - } +// if (!isPortraitPhone) { +// setFrameColor_Widget(d, uiSeparator_ColorId); +// } +// else { +// setFrameColor_Widget(d, none_ColorId); +// } arrange_Widget(d); /* need to know the height */ iBool allowOverflow = iFalse; /* A vertical offset determined by a possible selected label in the menu. */ @@ -1327,6 +1328,7 @@ int checkContextMenu_Widget(iWidget *menu, const SDL_Event *ev) { iLabelWidget *makeMenuButton_LabelWidget(const char *label, const iMenuItem *items, size_t n) { iLabelWidget *button = new_LabelWidget(label, "menu.open"); iWidget *menu = makeMenu_Widget(as_Widget(button), items, n); + setFrameColor_Widget(menu, uiBackgroundSelected_ColorId); setId_Widget(menu, "menu"); return button; } -- cgit v1.2.3 From 46acccaa8128a18ed026c9c5b4e6a401a307c3b0 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 11 Feb 2022 19:15:26 +0200 Subject: Paperclip icon for data URLs --- src/defs.h | 1 + src/gmdocument.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/defs.h b/src/defs.h index 550ae5bd..cd502f70 100644 --- a/src/defs.h +++ b/src/defs.h @@ -187,6 +187,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { #define photo_Icon "\U0001f5bc" #define fontpack_Icon "\U0001f520" #define package_Icon "\U0001f4e6" +#define paperclip_Icon "\U0001f4ce" #if defined (iPlatformApple) # define shift_Icon "\u21e7" diff --git a/src/gmdocument.c b/src/gmdocument.c index 546dc6f5..63d100e2 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -907,7 +907,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { : scheme == titan_GmLinkScheme ? uploadArrow : scheme == finger_GmLinkScheme ? pointingFinger : scheme == mailto_GmLinkScheme ? envelope - : scheme == data_GmLinkScheme ? package_Icon + : scheme == data_GmLinkScheme ? paperclip_Icon : link->flags & remote_GmLinkFlag ? globe : link->flags & imageFileExtension_GmLinkFlag ? image : link->flags & fontpackFileExtension_GmLinkFlag ? fontpack_Icon -- cgit v1.2.3 From fb71407409b2debd8e03c42faecd811d168321bf Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 13 Feb 2022 20:27:11 +0200 Subject: Added UI helper for presenting data URLs Omit the actual data. --- src/gmutil.c | 25 +++++++++++++++++++++++ src/gmutil.h | 1 + src/ui/linkinfo.c | 8 ++------ src/ui/sidebarwidget.c | 54 ++++++++++++++++++++++++++++++-------------------- 4 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/gmutil.c b/src/gmutil.c index e59e6649..6c2271d7 100644 --- a/src/gmutil.c +++ b/src/gmutil.c @@ -22,6 +22,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "gmutil.h" #include "fontpack.h" +#include "lang.h" +#include "ui/color.h" #include #include @@ -739,6 +741,29 @@ const iString *canonicalUrl_String(const iString *d) { return canon ? collect_String(canon) : d; } +const iString *prettyDataUrl_String(const iString *d, int contentColor) { + iUrl url; + init_Url(&url, d); + if (!equalCase_Rangecc(url.scheme, "data")) { + return d; + } + iString *pretty = new_String(); + const char *comma = strchr(url.path.start, ','); + if (!comma) { + comma = iMin(constEnd_String(d), constBegin_String(d) + 256); + } + appendRange_String(pretty, (iRangecc){ constBegin_String(d), comma }); + if (size_Range(&url.path)) { + if (contentColor != none_ColorId) { + appendCStr_String(pretty, escape_Color(contentColor)); + } + appendCStr_String(pretty, " ("); + appendCStr_String(pretty, formatCStrs_Lang("num.bytes.n", size_Range(&url.path))); + appendCStr_String(pretty, ")"); + } + return collect_String(pretty); +} + iRangecc mediaTypeWithoutParameters_Rangecc(iRangecc mime) { iRangecc part = iNullRange; nextSplit_Rangecc(mime, ";", &part); diff --git a/src/gmutil.h b/src/gmutil.h index 1594afc4..9217b0e3 100644 --- a/src/gmutil.h +++ b/src/gmutil.h @@ -141,6 +141,7 @@ void urlEncodeSpaces_String (iString *); const iString * withSpacesEncoded_String(const iString *); const iString * withScheme_String (const iString *, const char *scheme); /* replace URI scheme */ const iString * canonicalUrl_String (const iString *); +const iString * prettyDataUrl_String (const iString *, int contentColor); const char * mediaType_Path (const iString *path); const char * mediaTypeFromFileExtension_String (const iString *); diff --git a/src/ui/linkinfo.c b/src/ui/linkinfo.c index 46aa6663..15aea16e 100644 --- a/src/ui/linkinfo.c +++ b/src/ui/linkinfo.c @@ -92,12 +92,8 @@ void infoText_LinkInfo(const iGmDocument *doc, iGmLinkId linkId, iString *text_o appendRange_String(text_out, (iRangecc){ parts.path.start, constEnd_String(url) }); } else if (scheme == data_GmLinkScheme) { - appendCStr_String(text_out, "\U0001f4e6 "); - const char *comma = strchr(cstr_String(url), ','); - if (!comma) { - comma = iMin(constEnd_String(url), constBegin_String(url) + 256); - } - appendRange_String(text_out, (iRangecc){ constBegin_String(url), comma }); + appendCStr_String(text_out, paperclip_Icon " "); + append_String(text_out, prettyDataUrl_String(url, none_ColorId)); } else if (scheme != gemini_GmLinkScheme) { const size_t maxDispLen = 300; diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index da377ac2..73023a4f 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c @@ -2156,28 +2156,38 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, : uiTextDim_ColorId; iUrl parts; init_Url(&parts, &d->label); - const iBool isAbout = equalCase_Rangecc(parts.scheme, "about"); - const iBool isGemini = equalCase_Rangecc(parts.scheme, "gemini"); - draw_Text(font, - add_I2(topLeft_Rect(itemRect), - init_I2(3 * gap_UI, (itemHeight - lineHeight_Text(font)) / 2)), - fg, - "%s%s%s%s%s%s%s%s", - isGemini ? "" : cstr_Rangecc(parts.scheme), - isGemini ? "" - : isAbout ? ":" - : "://", - escape_Color(isHover ? (isPressing ? uiTextPressed_ColorId - : uiTextFramelessHover_ColorId) - : uiTextStrong_ColorId), - cstr_Rangecc(parts.host), - escape_Color(fg), - cstr_Rangecc(parts.path), - !isEmpty_Range(&parts.query) ? escape_Color(isPressing ? uiTextPressed_ColorId - : isHover ? uiText_ColorId - : uiAnnotation_ColorId) - : "", - !isEmpty_Range(&parts.query) ? cstr_Rangecc(parts.query) : ""); + const iBool isAbout = equalCase_Rangecc(parts.scheme, "about"); + const iBool isGemini = equalCase_Rangecc(parts.scheme, "gemini"); + const iBool isData = equalCase_Rangecc(parts.scheme, "data"); + const int queryColor = isPressing ? uiTextPressed_ColorId + : isHover ? uiText_ColorId + : uiAnnotation_ColorId; + const iInt2 textPos = + add_I2(topLeft_Rect(itemRect), + init_I2(3 * gap_UI, (itemHeight - lineHeight_Text(font)) / 2)); + if (isData) { + drawRange_Text( + font, textPos, fg, range_String(prettyDataUrl_String(&d->label, queryColor))); + } + else { + draw_Text( + font, + textPos, + fg, + "%s%s%s%s%s%s%s%s", + isGemini ? "" : cstr_Rangecc(parts.scheme), + isGemini ? "" + : isAbout ? ":" + : "://", + escape_Color(isHover ? (isPressing ? uiTextPressed_ColorId + : uiTextFramelessHover_ColorId) + : uiTextStrong_ColorId), + cstr_Rangecc(parts.host), + escape_Color(fg), + cstr_Rangecc(parts.path), + !isEmpty_Range(&parts.query) ? escape_Color(queryColor) : "", + !isEmpty_Range(&parts.query) ? cstr_Rangecc(parts.query) : ""); + } } iEndCollect(); } -- cgit v1.2.3 From 672a534a044d811aa57e927288de50360882ac54 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 13 Feb 2022 20:57:09 +0200 Subject: Added a preference for maximum URL size The setting defaults to 8 KB. Link lines with longer URLs are not recognized as links. --- po/en.po | 3 +++ res/about/help.gmi | 7 ++++++- res/lang/cs.bin | Bin 32779 -> 32811 bytes res/lang/de.bin | Bin 31589 -> 31621 bytes res/lang/en.bin | Bin 27658 -> 27690 bytes res/lang/eo.bin | Bin 26848 -> 26880 bytes res/lang/es.bin | Bin 31501 -> 31533 bytes res/lang/es_MX.bin | Bin 28728 -> 28760 bytes res/lang/fi.bin | Bin 31299 -> 31331 bytes res/lang/fr.bin | Bin 32386 -> 32418 bytes res/lang/gl.bin | Bin 30662 -> 30694 bytes res/lang/hu.bin | Bin 32411 -> 32443 bytes res/lang/ia.bin | Bin 30441 -> 30473 bytes res/lang/ie.bin | Bin 30398 -> 30430 bytes res/lang/isv.bin | Bin 26379 -> 26411 bytes res/lang/nl.bin | Bin 29768 -> 29800 bytes res/lang/pl.bin | Bin 31014 -> 31046 bytes res/lang/ru.bin | Bin 46361 -> 46393 bytes res/lang/sk.bin | Bin 26715 -> 26747 bytes res/lang/sr.bin | Bin 45657 -> 45689 bytes res/lang/tok.bin | Bin 28504 -> 28536 bytes res/lang/tr.bin | Bin 30605 -> 30637 bytes res/lang/uk.bin | Bin 45738 -> 45770 bytes res/lang/zh_Hans.bin | Bin 26623 -> 26655 bytes res/lang/zh_Hant.bin | Bin 27021 -> 27053 bytes src/app.c | 12 ++++++++++++ src/gmdocument.c | 17 +++++++++-------- src/prefs.c | 1 + src/prefs.h | 1 + src/ui/util.c | 1 + 30 files changed, 33 insertions(+), 9 deletions(-) diff --git a/po/en.po b/po/en.po index 100cfb9f..aae67ff0 100644 --- a/po/en.po +++ b/po/en.po @@ -1670,6 +1670,9 @@ msgstr "Wrap plain text:" msgid "prefs.decodeurls" msgstr "Decode URLs:" +msgid "prefs.urlsize" +msgstr "Maximum URL size:" + msgid "prefs.cachesize" msgstr "Cache size:" diff --git a/res/about/help.gmi b/res/about/help.gmi index 93bc6a05..5bf77a76 100644 --- a/res/about/help.gmi +++ b/res/about/help.gmi @@ -106,10 +106,15 @@ The type and destination of a link are indicated by the link's icon and color: Link colors remain the same regardless of which color theme is being used for page content. (Color themes are discussed in the Customization section.) -When you move the mouse cursor over a link, additional information will appear: the destination domain, with the URL scheme shown for non-Gemini links, and the date of the last visit to the URL. +The "Show URL on hover" option can be enabled in Preferences to show additional information when you move the mouse cursor over a link: the destination domain, URL scheme for non-Gemini links, date of the last visit to the URL, and the identity that will be used when opening the link. If a link would normally use the default ➤ icon but there is an Emoji at the beginning of the link label, that Emoji is used as the link icon instead. In these cases, you can always assume that the link is a Gemini link whose destination is the same domain that you're currently on. +The "Network" tab of Preferences has a few settings that affect the presentation of links and URLs in general: + +* "Decode URLs" causes percent-coding to be decoded for the user interface, so one can see international characters in URLs. +* "Maximum URL size" sets a limit for how long URLs can be. While Gemini servers are required to enforce a limit of 1024 bytes for URLs, this setting affects all URLs regardless of scheme. Link lines with URLs longer than this will be presented as plain text. + ### 1.1.3 Page caching When navigating to a new page, the old page is cached in memory. If you navigate back, the cached copy of the page is restored. Think of it as rewinding time — you return to a past time as if nothing had happened. The same applies to forward navigation; cached pages are loaded if available. This allows back and forward navigation to happen instantly, without any network requests. diff --git a/res/lang/cs.bin b/res/lang/cs.bin index a45f34ca..a14c5cf4 100644 Binary files a/res/lang/cs.bin and b/res/lang/cs.bin differ diff --git a/res/lang/de.bin b/res/lang/de.bin index 9b99e7ed..8dd9d478 100644 Binary files a/res/lang/de.bin and b/res/lang/de.bin differ diff --git a/res/lang/en.bin b/res/lang/en.bin index 7b06807d..dd3389ef 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/eo.bin b/res/lang/eo.bin index 6c3bab91..c65c893c 100644 Binary files a/res/lang/eo.bin and b/res/lang/eo.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index f82aca48..a0f2a07f 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin index e1956da3..e83f92e2 100644 Binary files a/res/lang/es_MX.bin and b/res/lang/es_MX.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index fc7ccab8..f390cdca 100644 Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ diff --git a/res/lang/fr.bin b/res/lang/fr.bin index 44b391b4..c268b356 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/gl.bin b/res/lang/gl.bin index 583f7909..04311f53 100644 Binary files a/res/lang/gl.bin and b/res/lang/gl.bin differ diff --git a/res/lang/hu.bin b/res/lang/hu.bin index 6776ea40..28ba0e48 100644 Binary files a/res/lang/hu.bin and b/res/lang/hu.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 2659c595..27e3ba71 100644 Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ diff --git a/res/lang/ie.bin b/res/lang/ie.bin index 33bc6419..244ade42 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/isv.bin b/res/lang/isv.bin index 23726e20..8c177e99 100644 Binary files a/res/lang/isv.bin and b/res/lang/isv.bin differ diff --git a/res/lang/nl.bin b/res/lang/nl.bin index 32ec0205..2bdf4a71 100644 Binary files a/res/lang/nl.bin and b/res/lang/nl.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 52e02876..129d34da 100644 Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index 7445fe10..ac407168 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sk.bin b/res/lang/sk.bin index 76d4dd6f..099f0ccf 100644 Binary files a/res/lang/sk.bin and b/res/lang/sk.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index 11ab7e1d..e550a05a 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tok.bin b/res/lang/tok.bin index 7e9b02d9..2bf27e86 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/tr.bin b/res/lang/tr.bin index c13d575f..6bd0c26c 100644 Binary files a/res/lang/tr.bin and b/res/lang/tr.bin differ diff --git a/res/lang/uk.bin b/res/lang/uk.bin index 609b1c53..520f6c97 100644 Binary files a/res/lang/uk.bin and b/res/lang/uk.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 904fe90d..81ea7de6 100644 Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index 9161c061..029551c7 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/app.c b/src/app.c index 0f9249cc..a2ada36e 100644 --- a/src/app.c +++ b/src/app.c @@ -244,6 +244,7 @@ static iString *serializePrefs_App_(const iApp *d) { appendFormat_String(str, "imageloadscroll arg:%d\n", d->prefs.loadImageInsteadOfScrolling); appendFormat_String(str, "cachesize.set arg:%d\n", d->prefs.maxCacheSize); appendFormat_String(str, "memorysize.set arg:%d\n", d->prefs.maxMemorySize); + appendFormat_String(str, "urlsize.set arg:%d\n", d->prefs.maxUrlSize); appendFormat_String(str, "decodeurls arg:%d\n", d->prefs.decodeUserVisibleURLs); appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); appendFormat_String(str, "linespacing.set arg:%f\n", d->prefs.lineSpacing); @@ -1974,6 +1975,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize")))); postCommandf_App("memorysize.set arg:%d", toInt_String(text_InputWidget(findChild_Widget(d, "prefs.memorysize")))); + postCommandf_App("urlsize.set arg:%d", + toInt_String(text_InputWidget(findChild_Widget(d, "prefs.urlsize")))); postCommandf_App("ca.file path:%s", cstrText_InputWidget(findChild_Widget(d, "prefs.ca.file"))); postCommandf_App("ca.path path:%s", @@ -2771,6 +2774,13 @@ iBool handleCommand_App(const char *cmd) { } return iTrue; } + else if (equal_Command(cmd, "urlsize.set")) { + d->prefs.maxUrlSize = arg_Command(cmd); + if (d->prefs.maxUrlSize < 1024) { + d->prefs.maxUrlSize = 1024; /* Gemini protocol requirement */ + } + return iTrue; + } else if (equal_Command(cmd, "searchurl")) { iString *url = &d->prefs.strings[searchUrl_PrefsString]; setCStr_String(url, suffixPtr_Command(cmd, "address")); @@ -3158,6 +3168,8 @@ iBool handleCommand_App(const char *cmd) { collectNewFormat_String("%d", d->prefs.maxCacheSize)); setText_InputWidget(findChild_Widget(dlg, "prefs.memorysize"), collectNewFormat_String("%d", d->prefs.maxMemorySize)); + setText_InputWidget(findChild_Widget(dlg, "prefs.urlsize"), + collectNewFormat_String("%d", d->prefs.maxUrlSize)); setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs); setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.strings[searchUrl_PrefsString]); setText_InputWidget(findChild_Widget(dlg, "prefs.ca.file"), &d->prefs.strings[caFile_PrefsString]); diff --git a/src/gmdocument.c b/src/gmdocument.c index 63d100e2..5cae4138 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -333,13 +333,14 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li link->urlRange = capturedRange_RegExpMatch(&m, 1); setRange_String(&link->url, link->urlRange); set_String(&link->url, canonicalUrl_String(absoluteUrl_String(&d->url, &link->url))); - if (startsWithCase_String(&link->url, "about:command")) { - /* This is a special internal page that allows submitting UI events. */ - if (!d->enableCommandLinks) { - delete_GmLink(link); - *linkId = 0; - return line; - } + /* If invalid, disregard the link. */ + if (size_String(&link->url) > prefs_App()->maxUrlSize || + (startsWithCase_String(&link->url, "about:command") + /* this is a special internal page that allows submitting UI events */ + && !d->enableCommandLinks)) { + delete_GmLink(link); + *linkId = 0; + return line; } /* Check the URL. */ { iUrl parts; @@ -385,7 +386,7 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li iString *path = newRange_String(parts.path); if (endsWithCase_String(path, ".gif") || endsWithCase_String(path, ".jpg") || endsWithCase_String(path, ".jpeg") || endsWithCase_String(path, ".png") || - endsWithCase_String(path, ".tga") || endsWithCase_String(path, ".psd") || + endsWithCase_String(path, ".tga") || endsWithCase_String(path, ".psd") || #if defined (LAGRANGE_ENABLE_WEBP) endsWithCase_String(path, ".webp") || #endif diff --git a/src/prefs.c b/src/prefs.c index 13a1dab7..cd86bf60 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -73,6 +73,7 @@ void init_Prefs(iPrefs *d) { d->decodeUserVisibleURLs = iTrue; d->maxCacheSize = 10; d->maxMemorySize = 200; + d->maxUrlSize = 8192; setCStr_String(&d->strings[uiFont_PrefsString], "default"); setCStr_String(&d->strings[headingFont_PrefsString], "default"); setCStr_String(&d->strings[bodyFont_PrefsString], "default"); diff --git a/src/prefs.h b/src/prefs.h index ea864f51..25bf56c4 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -172,6 +172,7 @@ struct Impl_Prefs { /* Network */ int maxCacheSize; /* MB */ int maxMemorySize; /* MB */ + int maxUrlSize; /* bytes; longer ones will be disregarded */ /* Style */ iStringSet * disabledFontPacks; int gemtextAnsiEscapes; diff --git a/src/ui/util.c b/src/ui/util.c index 41f8eaa9..53ee8fda 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -2902,6 +2902,7 @@ iWidget *makePreferences_Widget(void) { appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.network}", '6', &headings, &values); addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.decodeurls}"))); addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls"))); + addPrefsInputWithHeading_(headings, values, "prefs.urlsize", iClob(new_InputWidget(10))); /* Cache size. */ { iInputWidget *cache = new_InputWidget(4); setSelectAllOnFocus_InputWidget(cache, iTrue); -- cgit v1.2.3 From cb7a3cf7a47bdcf2366efb06e7e4c056394fcfd3 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 14 Feb 2022 09:22:31 +0200 Subject: Added option for autoshowing images in data URLs Disabled by default to be consistent with behavior of other links. --- po/en.po | 4 ++++ res/lang/cs.bin | Bin 32811 -> 32862 bytes res/lang/de.bin | Bin 31621 -> 31672 bytes res/lang/en.bin | Bin 27690 -> 27741 bytes res/lang/eo.bin | Bin 26880 -> 26931 bytes res/lang/es.bin | Bin 31533 -> 31584 bytes res/lang/es_MX.bin | Bin 28760 -> 28811 bytes res/lang/fi.bin | Bin 31331 -> 31382 bytes res/lang/fr.bin | Bin 32418 -> 32469 bytes res/lang/gl.bin | Bin 30694 -> 30745 bytes res/lang/hu.bin | Bin 32443 -> 32494 bytes res/lang/ia.bin | Bin 30473 -> 30524 bytes res/lang/ie.bin | Bin 30430 -> 30481 bytes res/lang/isv.bin | Bin 26411 -> 26462 bytes res/lang/nl.bin | Bin 29800 -> 29851 bytes res/lang/pl.bin | Bin 31046 -> 31097 bytes res/lang/ru.bin | Bin 46393 -> 46444 bytes res/lang/sk.bin | Bin 26747 -> 26798 bytes res/lang/sr.bin | Bin 45689 -> 45740 bytes res/lang/tok.bin | Bin 28536 -> 28587 bytes res/lang/tr.bin | Bin 30637 -> 30688 bytes res/lang/uk.bin | Bin 45770 -> 45821 bytes res/lang/zh_Hans.bin | Bin 26655 -> 26706 bytes res/lang/zh_Hant.bin | Bin 27053 -> 27104 bytes src/app.c | 6 ++++++ src/gmdocument.c | 8 ++++++-- src/prefs.c | 11 ++++++----- src/prefs.h | 2 ++ src/ui/documentwidget.c | 5 +++-- src/ui/util.c | 3 +++ 30 files changed, 30 insertions(+), 9 deletions(-) diff --git a/po/en.po b/po/en.po index aae67ff0..aa557043 100644 --- a/po/en.po +++ b/po/en.po @@ -1429,6 +1429,10 @@ msgstr "Collapse preformatted:" msgid "prefs.bookmarks.addbottom" msgstr "Add bookmarks to bottom:" +# User preference that controls whether image data embedded in Data URLs gets automatically displayed when a page is loaded. +msgid "prefs.dataurl.openimages" +msgstr "Open images in Data URLs:" + # User preference that controls whether index.gmi pages get automatically opened when browsing the contents of a directory inside a compressed archive. msgid "prefs.archive.openindex" msgstr "Open archive indices:" diff --git a/res/lang/cs.bin b/res/lang/cs.bin index a14c5cf4..f3f2a060 100644 Binary files a/res/lang/cs.bin and b/res/lang/cs.bin differ diff --git a/res/lang/de.bin b/res/lang/de.bin index 8dd9d478..0f918bfb 100644 Binary files a/res/lang/de.bin and b/res/lang/de.bin differ diff --git a/res/lang/en.bin b/res/lang/en.bin index dd3389ef..727258d1 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/eo.bin b/res/lang/eo.bin index c65c893c..f68ac15d 100644 Binary files a/res/lang/eo.bin and b/res/lang/eo.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index a0f2a07f..1d09138c 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin index e83f92e2..d3efe8fe 100644 Binary files a/res/lang/es_MX.bin and b/res/lang/es_MX.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index f390cdca..24e2a905 100644 Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ diff --git a/res/lang/fr.bin b/res/lang/fr.bin index c268b356..c644fab8 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/gl.bin b/res/lang/gl.bin index 04311f53..e93d1eb7 100644 Binary files a/res/lang/gl.bin and b/res/lang/gl.bin differ diff --git a/res/lang/hu.bin b/res/lang/hu.bin index 28ba0e48..5bfe37eb 100644 Binary files a/res/lang/hu.bin and b/res/lang/hu.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 27e3ba71..4b0b9bc3 100644 Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ diff --git a/res/lang/ie.bin b/res/lang/ie.bin index 244ade42..b69fd2a4 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/isv.bin b/res/lang/isv.bin index 8c177e99..0cbd0f67 100644 Binary files a/res/lang/isv.bin and b/res/lang/isv.bin differ diff --git a/res/lang/nl.bin b/res/lang/nl.bin index 2bdf4a71..82d13774 100644 Binary files a/res/lang/nl.bin and b/res/lang/nl.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 129d34da..9d13a9e2 100644 Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index ac407168..6a09521d 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sk.bin b/res/lang/sk.bin index 099f0ccf..9532ca17 100644 Binary files a/res/lang/sk.bin and b/res/lang/sk.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index e550a05a..58828fea 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tok.bin b/res/lang/tok.bin index 2bf27e86..0c4f6123 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/tr.bin b/res/lang/tr.bin index 6bd0c26c..f62d0968 100644 Binary files a/res/lang/tr.bin and b/res/lang/tr.bin differ diff --git a/res/lang/uk.bin b/res/lang/uk.bin index 520f6c97..bbc10575 100644 Binary files a/res/lang/uk.bin and b/res/lang/uk.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 81ea7de6..3a34d15d 100644 Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index 029551c7..f0ac2727 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/app.c b/src/app.c index a2ada36e..d1e3870a 100644 --- a/src/app.c +++ b/src/app.c @@ -280,6 +280,7 @@ static iString *serializePrefs_App_(const iApp *d) { { "prefs.collapsepreonload", &d->prefs.collapsePreOnLoad }, { "prefs.hoverlink", &d->prefs.hoverLink }, { "prefs.bookmarks.addbottom", &d->prefs.addBookmarksToBottom }, + { "prefs.dataurl.openimages", &d->prefs.openDataUrlImagesOnLoad }, { "prefs.archive.openindex", &d->prefs.openArchiveIndexPages }, { "prefs.font.warnmissing", &d->prefs.warnAboutMissingGlyphs }, { "prefs.blink", &d->prefs.blinkingCursor }, @@ -2729,6 +2730,10 @@ iBool handleCommand_App(const char *cmd) { postRefresh_App(); return iTrue; } + else if (equal_Command(cmd, "prefs.dataurl.openimages.changed")) { + d->prefs.openDataUrlImagesOnLoad = arg_Command(cmd) != 0; + return iTrue; + } else if (equal_Command(cmd, "prefs.archive.openindex.changed")) { d->prefs.openArchiveIndexPages = arg_Command(cmd) != 0; return iTrue; @@ -3096,6 +3101,7 @@ iBool handleCommand_App(const char *cmd) { setToggle_Widget(findChild_Widget(dlg, "prefs.hidetoolbarscroll"), d->prefs.hideToolbarOnScroll); setToggle_Widget(findChild_Widget(dlg, "prefs.bookmarks.addbottom"), d->prefs.addBookmarksToBottom); setToggle_Widget(findChild_Widget(dlg, "prefs.font.warnmissing"), d->prefs.warnAboutMissingGlyphs); + setToggle_Widget(findChild_Widget(dlg, "prefs.dataurl.openimages"), d->prefs.openDataUrlImagesOnLoad); setToggle_Widget(findChild_Widget(dlg, "prefs.archive.openindex"), d->prefs.openArchiveIndexPages); setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme); setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame); diff --git a/src/gmdocument.c b/src/gmdocument.c index 5cae4138..a3e233f3 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -334,7 +334,7 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li setRange_String(&link->url, link->urlRange); set_String(&link->url, canonicalUrl_String(absoluteUrl_String(&d->url, &link->url))); /* If invalid, disregard the link. */ - if (size_String(&link->url) > prefs_App()->maxUrlSize || + if ((d->format == gemini_SourceFormat && size_String(&link->url) > prefs_App()->maxUrlSize) || (startsWithCase_String(&link->url, "about:command") /* this is a special internal page that allows submitting UI events */ && !d->enableCommandLinks)) { @@ -371,7 +371,11 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li } else if (equalCase_Rangecc(parts.scheme, "data")) { setScheme_GmLink_(link, data_GmLinkScheme); - if (startsWith_Rangecc(parts.path, "image/")) { + if (startsWith_Rangecc(parts.path, "image/png") || + startsWith_Rangecc(parts.path, "image/jpg") || + startsWith_Rangecc(parts.path, "image/jpeg") || + startsWith_Rangecc(parts.path, "image/webp") || + startsWith_Rangecc(parts.path, "image/gif")) { link->flags |= imageFileExtension_GmLinkFlag; } } diff --git a/src/prefs.c b/src/prefs.c index cd86bf60..08355a6a 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -66,11 +66,12 @@ void init_Prefs(iPrefs *d) { d->smoothScrollSpeed[keyboard_ScrollType] = 13; d->smoothScrollSpeed[mouse_ScrollType] = 13; d->loadImageInsteadOfScrolling = iFalse; - d->collapsePreOnLoad = iFalse; - d->openArchiveIndexPages = iTrue; - d->addBookmarksToBottom = iTrue; - d->warnAboutMissingGlyphs = iTrue; - d->decodeUserVisibleURLs = iTrue; + d->openDataUrlImagesOnLoad = iFalse; + d->collapsePreOnLoad = iFalse; + d->openArchiveIndexPages = iTrue; + d->addBookmarksToBottom = iTrue; + d->warnAboutMissingGlyphs = iTrue; + d->decodeUserVisibleURLs = iTrue; d->maxCacheSize = 10; d->maxMemorySize = 200; d->maxUrlSize = 8192; diff --git a/src/prefs.h b/src/prefs.h index 25bf56c4..59c36a16 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -75,6 +75,7 @@ enum iPrefsBool { hoverLink_PrefsBool, smoothScrolling_PrefsBool, loadImageInsteadOfScrolling_PrefsBool, + openDataUrlImagesOnLoad_PrefsBool, collapsePreOnLoad_PrefsBool, openArchiveIndexPages_PrefsBool, @@ -128,6 +129,7 @@ struct Impl_Prefs { iBool hoverLink; iBool smoothScrolling; iBool loadImageInsteadOfScrolling; + iBool openDataUrlImagesOnLoad; iBool collapsePreOnLoad; iBool openArchiveIndexPages; diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 6513fc33..293e4507 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -2411,8 +2411,9 @@ static const char *zipPageHeading_(const iRangecc mime) { static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) { iWidget *w = as_Widget(d); - /* Embedded images in data links should be shown immediately as they are already fetched - data that is part of the document. */ { + /* Embedded images in data links can be shown immediately as they are already fetched + data that is part of the document. */ + if (prefs_App()->openDataUrlImagesOnLoad) { iGmDocument *doc = d->view.doc; for (size_t linkId = 1; ; linkId++) { const int linkFlags = linkFlags_GmDocument(doc, linkId); diff --git a/src/ui/util.c b/src/ui/util.c index 53ee8fda..b9f37476 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -2497,6 +2497,7 @@ iWidget *makePreferences_Widget(void) { { "input id:prefs.searchurl url:1 noheading:1" }, { "padding" }, { "toggle id:prefs.bookmarks.addbottom" }, + { "toggle id:prefs.dataurl.openimages" }, { "toggle id:prefs.archive.openindex" }, { "radio device:1 id:prefs.pinsplit", 0, 0, (const void *) pinSplitItems }, { "padding" }, @@ -2569,6 +2570,7 @@ iWidget *makePreferences_Widget(void) { const iMenuItem networkPanelItems[] = { { "title id:heading.prefs.network" }, { "toggle id:prefs.decodeurls" }, + { "input id:prefs.urlsize maxlen:10 selectall:1" }, { "padding" }, { "input id:prefs.cachesize maxlen:4 selectall:1 unit:mb" }, { "input id:prefs.memorysize maxlen:4 selectall:1 unit:mb" }, @@ -2643,6 +2645,7 @@ iWidget *makePreferences_Widget(void) { addDialogPadding_(headings, values); addDialogToggle_(headings, values, "${prefs.hoverlink}", "prefs.hoverlink"); addDialogToggle_(headings, values, "${prefs.bookmarks.addbottom}", "prefs.bookmarks.addbottom"); + addDialogToggle_(headings, values, "${prefs.dataurl.openimages}", "prefs.dataurl.openimages"); addDialogToggle_(headings, values, "${prefs.archive.openindex}", "prefs.archive.openindex"); if (deviceType_App() != phone_AppDeviceType) { addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.pinsplit}"))); -- cgit v1.2.3 From 61f7e50378088834b793e9a4fd8d040c8c19bc21 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 14 Feb 2022 11:52:34 +0200 Subject: Cleanup --- src/ui/util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/util.c b/src/ui/util.c index b9f37476..befc2fd5 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -2644,9 +2644,9 @@ iWidget *makePreferences_Widget(void) { setUrlContent_InputWidget(searchUrl, iTrue); addDialogPadding_(headings, values); addDialogToggle_(headings, values, "${prefs.hoverlink}", "prefs.hoverlink"); - addDialogToggle_(headings, values, "${prefs.bookmarks.addbottom}", "prefs.bookmarks.addbottom"); addDialogToggle_(headings, values, "${prefs.dataurl.openimages}", "prefs.dataurl.openimages"); addDialogToggle_(headings, values, "${prefs.archive.openindex}", "prefs.archive.openindex"); + addDialogToggle_(headings, values, "${prefs.bookmarks.addbottom}", "prefs.bookmarks.addbottom"); if (deviceType_App() != phone_AppDeviceType) { addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.pinsplit}"))); iWidget *pinSplit = new_Widget(); -- cgit v1.2.3 From 2a5e0219e70dfd1f266c9697c2fc00f3b332e49f Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 14 Feb 2022 15:03:22 +0200 Subject: Updated the_Foundation to v1.2 --- Depends.cmake | 2 +- lib/the_Foundation | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Depends.cmake b/Depends.cmake index b7a6cd25..348039b3 100644 --- a/Depends.cmake +++ b/Depends.cmake @@ -18,7 +18,7 @@ set (_dependsToBuild) if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/CMakeLists.txt) set (INSTALL_THE_FOUNDATION YES) - find_package (the_Foundation 1.1.0 REQUIRED) + find_package (the_Foundation 1.2.0 REQUIRED) else () if (EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/.git) # the_Foundation is checked out as a submodule, make sure it's up to date. diff --git a/lib/the_Foundation b/lib/the_Foundation index f2f7fef5..11e7a3bd 160000 --- a/lib/the_Foundation +++ b/lib/the_Foundation @@ -1 +1 @@ -Subproject commit f2f7fef560fc8642e3e449239d7e00f14491e13c +Subproject commit 11e7a3bdbb4b40ea4417518dac9eba7ad55748d3 -- cgit v1.2.3 From d19691de189227ef1976cb8104cdc55830fdb347 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 14 Feb 2022 15:04:16 +0200 Subject: Moved a function to the_Foundation --- src/gmutil.c | 37 ------------------------------------- src/gmutil.h | 5 ----- 2 files changed, 42 deletions(-) diff --git a/src/gmutil.c b/src/gmutil.c index 6c2271d7..0573aac1 100644 --- a/src/gmutil.c +++ b/src/gmutil.c @@ -930,40 +930,3 @@ const iGmError *get_GmError(enum iGmStatusCode code) { return &errors_[0].err; /* unknown */ } -int replaceRegExp_String(iString *d, const iRegExp *regexp, const char *replacement, - void (*matchHandler)(void *, const iRegExpMatch *), - void *context) { - iRegExpMatch m; - iString result; - int numMatches = 0; - const char *pos = constBegin_String(d); - init_RegExpMatch(&m); - init_String(&result); - while (matchString_RegExp(regexp, d, &m)) { - appendRange_String(&result, (iRangecc){ pos, begin_RegExpMatch(&m) }); - /* Replace any capture group back-references. */ - for (const char *ch = replacement; *ch; ch++) { - if (*ch == '\\') { - ch++; - if (*ch == '\\') { - appendCStr_String(&result, "\\"); - } - else if (*ch >= '0' && *ch <= '9') { - appendRange_String(&result, capturedRange_RegExpMatch(&m, *ch - '0')); - } - } - else { - appendData_Block(&result.chars, ch, 1); - } - } - if (matchHandler) { - matchHandler(context, &m); - } - pos = end_RegExpMatch(&m); - numMatches++; - } - appendRange_String(&result, (iRangecc){ pos, constEnd_String(d) }); - set_String(d, &result); - deinit_String(&result); - return numMatches; -} diff --git a/src/gmutil.h b/src/gmutil.h index 9217b0e3..c9d7baae 100644 --- a/src/gmutil.h +++ b/src/gmutil.h @@ -151,8 +151,3 @@ const iString * findContainerArchive_Path (const iString *path); const iString * feedEntryOpenCommand_String (const iString *url, int newTab); /* checks fragment */ - -/* TODO: Consider adding this to the_Foundation. */ -int replaceRegExp_String (iString *, const iRegExp *regexp, const char *replacement, - void (*matchHandler)(void *, const iRegExpMatch *), - void *context); -- cgit v1.2.3 From 64eaaaac57fc909a9c9d47302f17f0801b7f2965 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 15 Feb 2022 17:49:17 +0200 Subject: Working on site-specific settings dialog --- src/gmdocument.c | 27 ++++++++++++++++++--------- src/gmdocument.h | 4 +++- src/gmutil.c | 14 ++++++++++++++ src/gmutil.h | 1 + src/sitespec.c | 13 ++++++++++++- src/sitespec.h | 1 + src/ui/documentwidget.c | 12 ++++++++---- src/ui/util.c | 43 ++++++++++++++++++++++++++++++++++--------- src/ui/util.h | 15 ++++++++------- 9 files changed, 99 insertions(+), 31 deletions(-) diff --git a/src/gmdocument.c b/src/gmdocument.c index a3e233f3..bc0cc073 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -1292,7 +1292,7 @@ static void updateIconBasedOnUrl_GmDocument_(iGmDocument *d) { } } -void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { +void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *paletteSeed, const iBlock *iconSeed) { const iPrefs * prefs = prefs_App(); enum iGmDocumentTheme theme = currentTheme_(); static const iChar siteIcons[] = { @@ -1303,6 +1303,16 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { 0x1f306, 0x1f308, 0x1f30a, 0x1f319, 0x1f31f, 0x1f320, 0x1f340, 0x1f4cd, 0x1f4e1, 0x1f531, 0x1f533, 0x1f657, 0x1f659, 0x1f665, 0x1f668, 0x1f66b, 0x1f78b, 0x1f796, 0x1f79c, }; + if (!iconSeed) { + iconSeed = paletteSeed; + } + if (iconSeed && !isEmpty_Block(iconSeed)) { + const uint32_t seedHash = themeHash_(iconSeed); + d->siteIcon = siteIcons[(seedHash >> 7) % iElemCount(siteIcons)]; + } + else { + d->siteIcon = 0; + } /* Default colors. These are used on "about:" pages and local files, for example. */ { /* Link colors are generally the same in all themes. */ set_Color(tmBadLink_ColorId, get_Color(red_ColorId)); @@ -1504,13 +1514,11 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { } } } - if (seed && !isEmpty_Block(seed)) { - d->themeSeed = themeHash_(seed); - d->siteIcon = siteIcons[(d->themeSeed >> 7) % iElemCount(siteIcons)]; + if (paletteSeed && !isEmpty_Block(paletteSeed)) { + d->themeSeed = themeHash_(paletteSeed); } else { d->themeSeed = 0; - d->siteIcon = 0; } /* Set up colors. */ if (d->themeSeed) { @@ -1739,8 +1747,8 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { /* Derived colors. */ setDerivedThemeColors_(theme); /* Special exceptions. */ - if (seed) { - if (equal_CStr(cstr_Block(seed), "gemini.circumlunar.space")) { + if (iconSeed) { + if (equal_CStr(cstr_Block(iconSeed), "gemini.circumlunar.space")) { d->siteIcon = 0x264a; /* gemini symbol */ } updateIconBasedOnUrl_GmDocument_(d); @@ -1761,7 +1769,8 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { void makePaletteGlobal_GmDocument(const iGmDocument *d) { if (!d->isPaletteValid) { /* Recompute the palette since it's needed now. */ - setThemeSeed_GmDocument((iGmDocument *) d, urlThemeSeed_String(&d->url)); + setThemeSeed_GmDocument( + (iGmDocument *) d, urlPaletteSeed_String(&d->url), urlThemeSeed_String(&d->url)); } iAssert(d->isPaletteValid); memcpy(get_Root()->tmPalette, d->palette, sizeof(d->palette)); @@ -1938,7 +1947,7 @@ static void normalize_GmDocument(iGmDocument *d) { void setUrl_GmDocument(iGmDocument *d, const iString *url) { url = canonicalUrl_String(url); set_String(&d->url, url); - setThemeSeed_GmDocument(d, urlThemeSeed_String(url)); + setThemeSeed_GmDocument(d, urlPaletteSeed_String(url), urlThemeSeed_String(url)); iUrl parts; init_Url(&parts, url); setRange_String(&d->localHost, parts.host); diff --git a/src/gmdocument.h b/src/gmdocument.h index 0969794c..6c25dd6f 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h @@ -181,7 +181,9 @@ enum iGmDocumentUpdate { final_GmDocumentUpdate, /* process all lines, including the last one if not terminated */ }; -void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); +void setThemeSeed_GmDocument (iGmDocument *, + const iBlock *paletteSeed, + const iBlock *iconSeed); /* seeds may be NULL; NULL iconSeed will use paletteSeed instead */ void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format); void setWidth_GmDocument (iGmDocument *, int width, int canvasWidth); iBool updateWidth_GmDocument (iGmDocument *, int width, int canvasWidth); diff --git a/src/gmutil.c b/src/gmutil.c index 0573aac1..9188091d 100644 --- a/src/gmutil.c +++ b/src/gmutil.c @@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "gmutil.h" #include "fontpack.h" #include "lang.h" +#include "sitespec.h" #include "ui/color.h" #include @@ -279,6 +280,19 @@ const iBlock *urlThemeSeed_String(const iString *url) { return collect_Block(newRange_Block(user)); } +const iBlock *urlPaletteSeed_String(const iString *url) { + if (equalCase_Rangecc(urlScheme_String(url), "file")) { + return urlThemeSeed_String(url); + } + /* Check for a site-specific setting. */ + const iString *seed = + valueString_SiteSpec(collectNewRange_String(urlRoot_String(url)), paletteSeed_SiteSpecKey); + if (!isEmpty_String(seed)) { + return utf8_String(seed); + } + return urlThemeSeed_String(url); +} + static iBool isAbsolutePath_(iRangecc path) { return isAbsolute_Path(collect_String(urlDecode_String(collect_String(newRange_String(path))))); } diff --git a/src/gmutil.h b/src/gmutil.h index c9d7baae..01eb8e52 100644 --- a/src/gmutil.h +++ b/src/gmutil.h @@ -120,6 +120,7 @@ uint16_t urlPort_String (const iString *); iRangecc urlUser_String (const iString *); iRangecc urlRoot_String (const iString *); const iBlock * urlThemeSeed_String (const iString *); +const iBlock * urlPaletteSeed_String (const iString *); const iString * absoluteUrl_String (const iString *, const iString *urlMaybeRelative); iBool isLikelyUrl_String (const iString *); diff --git a/src/sitespec.c b/src/sitespec.c index fe80ad13..31094981 100644 --- a/src/sitespec.c +++ b/src/sitespec.c @@ -37,7 +37,8 @@ struct Impl_SiteParams { iString titanIdentity; /* fingerprint */ int dismissWarnings; iStringArray usedIdentities; /* fingerprints; latest ones at the end */ - /* TODO: theme seed, style settings */ + iString paletteSeed; + /* TODO: style settings */ }; void init_SiteParams(iSiteParams *d) { @@ -45,9 +46,11 @@ void init_SiteParams(iSiteParams *d) { init_String(&d->titanIdentity); d->dismissWarnings = 0; init_StringArray(&d->usedIdentities); + init_String(&d->paletteSeed); } void deinit_SiteParams(iSiteParams *d) { + deinit_String(&d->paletteSeed); deinit_StringArray(&d->usedIdentities); deinit_String(&d->titanIdentity); } @@ -149,6 +152,9 @@ static void handleIniKeyValue_SiteSpec_(void *context, const iString *table, con pushBack_StringArray(&d->loadParams->usedIdentities, collectNewRange_String(seg)); } } + else if (!cmp_String(key, "paletteSeed") && value->type == string_TomlType) { + set_String(&d->loadParams->paletteSeed, value->value.string); + } } static iBool load_SiteSpec_(iSiteSpec *d) { @@ -190,6 +196,11 @@ static void save_SiteSpec_(iSiteSpec *d) { "usedIdentities = \"%s\"\n", cstrCollect_String(joinCStr_StringArray(¶ms->usedIdentities, " "))); } + if (!isEmpty_String(¶ms->paletteSeed)) { + appendCStr_String(buf, "paletteSeed = \""); + append_String(buf, collect_String(quote_String(¶ms->paletteSeed, iFalse))); + appendCStr_String(buf, "\"\n"); + } appendCStr_String(buf, "\n"); write_File(f, utf8_String(buf)); iEndCollect(); diff --git a/src/sitespec.h b/src/sitespec.h index 11c40e3c..372e021e 100644 --- a/src/sitespec.h +++ b/src/sitespec.h @@ -31,6 +31,7 @@ enum iSiteSpecKey { titanIdentity_SiteSpecKey, /* String */ dismissWarnings_SiteSpecKey, /* int */ usedIdentities_SiteSpecKey, /* StringArray */ + paletteSeed_SiteSpecKey, /* String */ }; void init_SiteSpec (const char *saveDir); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 293e4507..7d9ac154 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -3936,12 +3936,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) const char *unchecked = red_ColorEscape "\u2610"; const char *checked = green_ColorEscape "\u2611"; const iBool haveFingerprint = (d->certFlags & haveFingerprint_GmCertFlag) != 0; - const int requiredForTrust = (available_GmCertFlag | haveFingerprint_GmCertFlag | - timeVerified_GmCertFlag); + const int requiredForTrust = + (available_GmCertFlag | haveFingerprint_GmCertFlag | timeVerified_GmCertFlag); const iBool canTrust = ~d->certFlags & trusted_GmCertFlag && ((d->certFlags & requiredForTrust) == requiredForTrust); const iRecentUrl *recent = constMostRecentUrl_History(d->mod.history); - const iString *meta = &d->sourceMime; + const iString *meta = &d->sourceMime; if (recent && recent->cachedResponse) { meta = &recent->cachedResponse->meta; } @@ -4006,6 +4006,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) if (haveFingerprint) { pushBack_Array(items, &(iMenuItem){ "${dlg.cert.fingerprint}", 0, 0, "server.copycert" }); } + const iRangecc root = urlRoot_String(d->mod.url); + if (!isEmpty_Range(&root)) { + pushBack_Array(items, &(iMenuItem){ "${pageinfo.settings}", 0, 0, "document.sitespec" }); + } if (!isEmpty_Array(items)) { pushBack_Array(items, &(iMenuItem){ "---", 0, 0, 0 }); } @@ -4937,7 +4941,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e for (size_t i = 0; i < 64; ++i) { setByte_Block(seed, i, iRandom(0, 256)); } - setThemeSeed_GmDocument(view->doc, seed); + setThemeSeed_GmDocument(view->doc, seed, NULL); delete_Block(seed); invalidate_DocumentWidget_(d); refresh_Widget(w); diff --git a/src/ui/util.c b/src/ui/util.c index befc2fd5..54715121 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -3169,15 +3169,14 @@ static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { } iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { - const char *headingText = bookmarkId ? uiHeading_ColorEscape "${heading.feedcfg}" - : uiHeading_ColorEscape "${heading.subscribe}"; - const iMenuItem actions[] = { { "${cancel}" }, - { bookmarkId ? uiTextCaution_ColorEscape "${dlg.feed.save}" - : uiTextCaution_ColorEscape "${dlg.feed.sub}", - SDLK_RETURN, - KMOD_PRIMARY, - format_CStr("feedcfg.accept bmid:%d", bookmarkId) } }; - iWidget *dlg; + iWidget *dlg; + const char *headingText = bookmarkId ? "${heading.feedcfg}" : "${heading.subscribe}"; + const iMenuItem actions[] = { { "${cancel}" }, + { bookmarkId ? uiTextCaution_ColorEscape "${dlg.feed.save}" + : uiTextCaution_ColorEscape "${dlg.feed.sub}", + SDLK_RETURN, + KMOD_PRIMARY, + format_CStr("feedcfg.accept bmid:%d", bookmarkId) } }; if (isUsingPanelLayout_Mobile()) { const iMenuItem typeItems[] = { { "button id:feedcfg.type.gemini label:dlg.feed.type.gemini", 0, 0, "feedcfg.type arg:0" }, @@ -3234,6 +3233,32 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { return dlg; } +iWidget *makeSiteSpecificSettings_Widget(const iString *url) { + iWidget *dlg; + const iMenuItem actions[] = { + { "${cancel}" }, { "${sitespec.accept}", SDLK_RETURN, KMOD_PRIMARY, "sitespec.accept" } + }; + if (isUsingPanelLayout_Mobile()) { + iAssert(iFalse); + } + else { + iWidget *headings, *values; + dlg = makeSheet_Widget("sitespec"); + addDialogTitle_(dlg, "${heading.sitespec}", "heading.sitespec"); + addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); + addDialogToggle_(headings, values, "${sitespec.ansi}", "sitespec.ansi"); + iInputWidget *palInput = new_InputWidget(0); + addPrefsInputWithHeading_(headings, values, "sitespec.palette", iClob(palInput)); + as_Widget(palInput)->rect.size.x = 80 * gap_UI; + addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); + } + /* Initialize. */ { + const iRangecc root = urlRoot_String(url); + + } + return dlg; +} + iWidget *makeIdentityCreation_Widget(void) { const iMenuItem actions[] = { { "${dlg.newident.more}", 0, 0, "ident.showmore" }, { "---" }, diff --git a/src/ui/util.h b/src/ui/util.h index 0289d579..31c8cedc 100644 --- a/src/ui/util.h +++ b/src/ui/util.h @@ -336,13 +336,14 @@ iWidget * makeQuestion_Widget (const char *title, const char *msg, iWidget * makePreferences_Widget (void); void updatePreferencesLayout_Widget (iWidget *prefs); -iWidget * makeBookmarkEditor_Widget (void); -void setBookmarkEditorFolder_Widget(iWidget *editor, uint32_t folderId); -iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *title, iChar icon); -iWidget * makeIdentityCreation_Widget (void); -iWidget * makeFeedSettings_Widget (uint32_t bookmarkId); -iWidget * makeTranslation_Widget (iWidget *parent); -iWidget * makeGlyphFinder_Widget (void); +iWidget * makeBookmarkEditor_Widget (void); +void setBookmarkEditorFolder_Widget (iWidget *editor, uint32_t folderId); +iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *title, iChar icon); +iWidget * makeIdentityCreation_Widget (void); +iWidget * makeFeedSettings_Widget (uint32_t bookmarkId); +iWidget * makeSiteSpecificSettings_Widget (const iString *url); +iWidget * makeTranslation_Widget (iWidget *parent); +iWidget * makeGlyphFinder_Widget (void); const char * languageId_String (const iString *menuItemLabel); int languageIndex_CStr (const char *langId); -- cgit v1.2.3 From e455699fca461e7e73d920a9ebfe557c20a67f98 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 15 Feb 2022 23:18:06 +0200 Subject: Site-specific settings The theme palette seed phrase and ANSI warnings can be configured in the new site-specific settings dialog. Added bindings for Page Information and for opening the site-specific settings dialog. IssueID #381 --- po/en.po | 21 +++++++++++ res/lang/cs.bin | Bin 32862 -> 33216 bytes res/lang/de.bin | Bin 31672 -> 32026 bytes res/lang/en.bin | Bin 27741 -> 28095 bytes res/lang/eo.bin | Bin 26931 -> 27285 bytes res/lang/es.bin | Bin 31584 -> 31938 bytes res/lang/es_MX.bin | Bin 28811 -> 29165 bytes res/lang/fi.bin | Bin 31382 -> 31736 bytes res/lang/fr.bin | Bin 32469 -> 32823 bytes res/lang/gl.bin | Bin 30745 -> 31099 bytes res/lang/hu.bin | Bin 32494 -> 32848 bytes res/lang/ia.bin | Bin 30524 -> 30878 bytes res/lang/ie.bin | Bin 30481 -> 30835 bytes res/lang/isv.bin | Bin 26462 -> 26816 bytes res/lang/nl.bin | Bin 29851 -> 30205 bytes res/lang/pl.bin | Bin 31097 -> 31451 bytes res/lang/ru.bin | Bin 46444 -> 46798 bytes res/lang/sk.bin | Bin 26798 -> 27152 bytes res/lang/sr.bin | Bin 45740 -> 46094 bytes res/lang/tok.bin | Bin 28587 -> 28941 bytes res/lang/tr.bin | Bin 30688 -> 31042 bytes res/lang/uk.bin | Bin 45821 -> 46175 bytes res/lang/zh_Hans.bin | Bin 26706 -> 27060 bytes res/lang/zh_Hant.bin | Bin 27104 -> 27458 bytes src/sitespec.c | 8 ++++ src/ui/documentwidget.c | 6 +++ src/ui/keys.c | 2 + src/ui/util.c | 95 +++++++++++++++++++++++++++++++++++++++++++----- 28 files changed, 123 insertions(+), 9 deletions(-) diff --git a/po/en.po b/po/en.po index 6db26259..22c10e44 100644 --- a/po/en.po +++ b/po/en.po @@ -760,6 +760,27 @@ msgstr "Trust" msgid "dlg.cert.fingerprint" msgstr "Copy Fingerprint" +msgid "pageinfo.settings" +msgstr "Settings" + +msgid "heading.sitespec" +msgstr "Site-Specific Settings" + +msgid "sitespec.ansi" +msgstr "ANSI escape warnings:" + +msgid "sitespec.palette" +msgstr "Theme palette seed:" + +msgid "sitespec.accept" +msgstr "Save Settings" + +msgid "keys.pageinfo" +msgstr "Show page information" + +msgid "keys.sitespec" +msgstr "Show site-specific settings" + #, c-format msgid "dlg.input.prompt" msgstr "Please enter input for %s:" diff --git a/res/lang/cs.bin b/res/lang/cs.bin index f3f2a060..0cf36a2b 100644 Binary files a/res/lang/cs.bin and b/res/lang/cs.bin differ diff --git a/res/lang/de.bin b/res/lang/de.bin index 0f918bfb..ea6e1841 100644 Binary files a/res/lang/de.bin and b/res/lang/de.bin differ diff --git a/res/lang/en.bin b/res/lang/en.bin index 727258d1..372cef24 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/eo.bin b/res/lang/eo.bin index f68ac15d..8a9519b6 100644 Binary files a/res/lang/eo.bin and b/res/lang/eo.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 1d09138c..3435a8d8 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin index d3efe8fe..5c527322 100644 Binary files a/res/lang/es_MX.bin and b/res/lang/es_MX.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 24e2a905..cdbda504 100644 Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ diff --git a/res/lang/fr.bin b/res/lang/fr.bin index c644fab8..3ab07f12 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/gl.bin b/res/lang/gl.bin index e93d1eb7..5f5d467b 100644 Binary files a/res/lang/gl.bin and b/res/lang/gl.bin differ diff --git a/res/lang/hu.bin b/res/lang/hu.bin index 5bfe37eb..17486efa 100644 Binary files a/res/lang/hu.bin and b/res/lang/hu.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 4b0b9bc3..8cae5947 100644 Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ diff --git a/res/lang/ie.bin b/res/lang/ie.bin index b69fd2a4..bd647d76 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/isv.bin b/res/lang/isv.bin index 0cbd0f67..81d30963 100644 Binary files a/res/lang/isv.bin and b/res/lang/isv.bin differ diff --git a/res/lang/nl.bin b/res/lang/nl.bin index 82d13774..e860c109 100644 Binary files a/res/lang/nl.bin and b/res/lang/nl.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 9d13a9e2..b759b1b7 100644 Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index 6a09521d..8463fbfe 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sk.bin b/res/lang/sk.bin index 9532ca17..a0197c5b 100644 Binary files a/res/lang/sk.bin and b/res/lang/sk.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index 58828fea..dc10b8a0 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tok.bin b/res/lang/tok.bin index 0c4f6123..977454a8 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/tr.bin b/res/lang/tr.bin index f62d0968..c131d0ea 100644 Binary files a/res/lang/tr.bin and b/res/lang/tr.bin differ diff --git a/res/lang/uk.bin b/res/lang/uk.bin index bbc10575..8377c0ba 100644 Binary files a/res/lang/uk.bin and b/res/lang/uk.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 3a34d15d..acaaaa77 100644 Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index f0ac2727..cf674d55 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/sitespec.c b/src/sitespec.c index 31094981..21edc0a2 100644 --- a/src/sitespec.c +++ b/src/sitespec.c @@ -268,6 +268,12 @@ void setValueString_SiteSpec(const iString *site, enum iSiteSpecKey key, const i set_String(¶ms->titanIdentity, value); } break; + case paletteSeed_SiteSpecKey: + if (!equal_String(¶ms->paletteSeed, value)) { + needSave = iTrue; + set_String(¶ms->paletteSeed, value); + } + break; default: break; } @@ -339,6 +345,8 @@ const iString *valueString_SiteSpec(const iString *site, enum iSiteSpecKey key) switch (key) { case titanIdentity_SiteSpecKey: return ¶ms->titanIdentity; + case paletteSeed_SiteSpecKey: + return ¶ms->paletteSeed; default: return collectNew_String(); } diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 7d9ac154..1f3e0e37 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -4033,6 +4033,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) addAction_Widget(dlg, SDLK_SPACE, 0, "message.ok"); return iTrue; } + else if (equal_Command(cmd, "document.sitespec") && d == document_App()) { + if (!findWidget_App("sitespec.palette")) { + makeSiteSpecificSettings_Widget(d->mod.url); + } + return iTrue; + } else if (equal_Command(cmd, "server.unexpire") && document_App() == d) { const iRangecc host = urlHost_String(d->mod.url); const uint16_t port = urlPort_String(d->mod.url); diff --git a/src/ui/keys.c b/src/ui/keys.c index 26a286bc..88efa98b 100644 --- a/src/ui/keys.c +++ b/src/ui/keys.c @@ -243,6 +243,8 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] = { 110,{ "${menu.save.downloads}", SDLK_s, KMOD_PRIMARY, "document.save" }, 0 }, { 120,{ "${keys.upload}", SDLK_u, KMOD_PRIMARY, "document.upload" }, 0 }, { 121,{ "${keys.upload.edit}", SDLK_e, KMOD_PRIMARY, "document.upload copy:1" }, 0 }, + { 125,{ "${keys.pageinfo}", SDLK_i, KMOD_PRIMARY, "document.info" }, 0 }, + { 126,{ "${keys.sitespec}", ',', KMOD_PRIMARY | KMOD_SHIFT, "document.sitespec" }, 0 }, { 130,{ "${keys.input.precedingline}", SDLK_v, KMOD_PRIMARY | KMOD_SHIFT, "input.precedingline" }, 0 }, /* The following cannot currently be changed (built-in duplicates). */ #if defined (iPlatformApple) diff --git a/src/ui/util.c b/src/ui/util.c index 54715121..5cd8a582 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -35,6 +35,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "keys.h" #include "labelwidget.h" #include "root.h" +#include "sitespec.h" #include "text.h" #include "touch.h" #include "widget.h" @@ -3126,7 +3127,7 @@ iWidget *makeBookmarkCreation_Widget(const iString *url, const iString *title, i static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { if (equal_Command(cmd, "cancel")) { - setupSheetTransition_Mobile(dlg, iFalse); + setupSheetTransition_Mobile(dlg, 0); destroy_Widget(dlg); return iTrue; } @@ -3233,32 +3234,108 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { return dlg; } +/*----------------------------------------------------------------------------------------------*/ + +static void siteSpecificThemeChanged_(const iWidget *dlg) { + iDocumentWidget *doc = document_App(); + setThemeSeed_GmDocument((iGmDocument *) document_DocumentWidget(doc), + urlPaletteSeed_String(url_DocumentWidget(doc)), + urlThemeSeed_String(url_DocumentWidget(doc))); + postCommand_App("theme.changed"); +} + +static const iString *siteSpecificRoot_(const iWidget *dlg) { + return collect_String(suffix_Command(cstr_String(id_Widget(dlg)), "site")); +} + +static void updateSiteSpecificTheme_(iInputWidget *palSeed, void *context) { + iWidget *dlg = context; + const iString *siteRoot = siteSpecificRoot_(dlg); + setValueString_SiteSpec(siteRoot, paletteSeed_SiteSpecKey, text_InputWidget(palSeed)); + siteSpecificThemeChanged_(dlg); + /* Allow seeing the new theme. */ + setFlags_Widget(dlg, noFadeBackground_WidgetFlag, iTrue); +} + +static void closeSiteSpecific_(iWidget *dlg) { + setupSheetTransition_Mobile(dlg, 0); + delete_String(userData_Object(dlg)); /* saved original palette seed */ + destroy_Widget(dlg); +} + +static iBool siteSpecificSettingsHandler_(iWidget *dlg, const char *cmd) { + if (equal_Command(cmd, "cancel")) { + iInputWidget *palSeed = findChild_Widget(dlg, "sitespec.palette"); + setText_InputWidget(palSeed, userData_Object(dlg)); + updateSiteSpecificTheme_(palSeed, dlg); + closeSiteSpecific_(dlg); + return iTrue; + } + if (startsWith_CStr(cmd, "input.ended id:sitespec.palette")) { + setFlags_Widget(dlg, noFadeBackground_WidgetFlag, iFalse); + refresh_Widget(dlg); + siteSpecificThemeChanged_(dlg); + return iTrue; + } + if (equal_Command(cmd, "sitespec.accept")) { + const iInputWidget *palSeed = findChild_Widget(dlg, "sitespec.palette"); + const iBool warnAnsi = isSelected_Widget(findChild_Widget(dlg, "sitespec.ansi")); + const iString *siteRoot = siteSpecificRoot_(dlg); + int dismissed = value_SiteSpec(siteRoot, dismissWarnings_SiteSpecKey); + iChangeFlags(dismissed, ansiEscapes_GmDocumentWarning, !warnAnsi); + setValue_SiteSpec(siteRoot, dismissWarnings_SiteSpecKey, dismissed); + setValueString_SiteSpec(siteRoot, paletteSeed_SiteSpecKey, text_InputWidget(palSeed)); + siteSpecificThemeChanged_(dlg); + /* Note: The active DocumentWidget may actually be different than when opening the dialog. */ + closeSiteSpecific_(dlg); + return iTrue; + } + return iFalse; +} + iWidget *makeSiteSpecificSettings_Widget(const iString *url) { iWidget *dlg; const iMenuItem actions[] = { - { "${cancel}" }, { "${sitespec.accept}", SDLK_RETURN, KMOD_PRIMARY, "sitespec.accept" } + { "${cancel}" }, + { "${sitespec.accept}", SDLK_RETURN, KMOD_PRIMARY, "sitespec.accept" } }; if (isUsingPanelLayout_Mobile()) { iAssert(iFalse); } else { iWidget *headings, *values; - dlg = makeSheet_Widget("sitespec"); + dlg = makeSheet_Widget(format_CStr("sitespec site:%s", cstr_Rangecc(urlRoot_String(url)))); addDialogTitle_(dlg, "${heading.sitespec}", "heading.sitespec"); addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); + iInputWidget *palSeed = new_InputWidget(0); + setHint_InputWidget(palSeed, cstr_Block(urlThemeSeed_String(url))); + addPrefsInputWithHeading_(headings, values, "sitespec.palette", iClob(palSeed)); addDialogToggle_(headings, values, "${sitespec.ansi}", "sitespec.ansi"); - iInputWidget *palInput = new_InputWidget(0); - addPrefsInputWithHeading_(headings, values, "sitespec.palette", iClob(palInput)); - as_Widget(palInput)->rect.size.x = 80 * gap_UI; - addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); + addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); + addChild_Widget(get_Root()->widget, iClob(dlg)); + as_Widget(palSeed)->rect.size.x = 60 * gap_UI; + arrange_Widget(dlg); } /* Initialize. */ { - const iRangecc root = urlRoot_String(url); - + const iString *site = collectNewRange_String(urlRoot_String(url)); + setToggle_Widget(findChild_Widget(dlg, "sitespec.ansi"), + ~value_SiteSpec(site, dismissWarnings_SiteSpecKey) & ansiEscapes_GmDocumentWarning); + setText_InputWidget(findChild_Widget(dlg, "sitespec.palette"), + valueString_SiteSpec(site, paletteSeed_SiteSpecKey)); + /* Keep a copy of the original palette seed for restoring on cancel. */ + setUserData_Object(dlg, copy_String(valueString_SiteSpec(site, paletteSeed_SiteSpecKey))); + if (!isUsingPanelLayout_Mobile()) { + setValidator_InputWidget(findChild_Widget(dlg, "sitespec.palette"), + updateSiteSpecificTheme_, dlg); + } } + setCommandHandler_Widget(dlg, siteSpecificSettingsHandler_); + setupSheetTransition_Mobile(dlg, incoming_TransitionFlag); return dlg; } +/*----------------------------------------------------------------------------------------------*/ + iWidget *makeIdentityCreation_Widget(void) { const iMenuItem actions[] = { { "${dlg.newident.more}", 0, 0, "ident.showmore" }, { "---" }, -- cgit v1.2.3 From dd114c2e44f5e5a4b5058db8bb9e37232d28eade Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 16 Feb 2022 06:28:00 +0200 Subject: Cleanup: Omit empty sitespec.ini sections --- src/sitespec.c | 11 ++++++++--- src/ui/util.c | 2 ++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/sitespec.c b/src/sitespec.c index 21edc0a2..5133abe5 100644 --- a/src/sitespec.c +++ b/src/sitespec.c @@ -179,7 +179,7 @@ static void save_SiteSpec_(iSiteSpec *d) { iBeginCollect(); const iBlock * key = &i.value->keyBlock; const iSiteParams *params = i.value->object; - format_String(buf, "[%s]\n", cstr_Block(key)); + clear_String(buf); if (params->titanPort) { appendFormat_String(buf, "titanPort = %u\n", params->titanPort); } @@ -201,8 +201,13 @@ static void save_SiteSpec_(iSiteSpec *d) { append_String(buf, collect_String(quote_String(¶ms->paletteSeed, iFalse))); appendCStr_String(buf, "\"\n"); } - appendCStr_String(buf, "\n"); - write_File(f, utf8_String(buf)); + if (!isEmpty_String(buf)) { + writeData_File(f, "[", 1); + writeData_File(f, constData_Block(key), size_Block(key)); + writeData_File(f, "]\n", 2); + appendCStr_String(buf, "\n"); + write_File(f, utf8_String(buf)); + } iEndCollect(); } delete_String(buf); diff --git a/src/ui/util.c b/src/ui/util.c index 5cd8a582..94e4b00c 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -3265,9 +3265,11 @@ static void closeSiteSpecific_(iWidget *dlg) { static iBool siteSpecificSettingsHandler_(iWidget *dlg, const char *cmd) { if (equal_Command(cmd, "cancel")) { + const iBool wasNoFade = (flags_Widget(dlg) & noFadeBackground_WidgetFlag) != 0; iInputWidget *palSeed = findChild_Widget(dlg, "sitespec.palette"); setText_InputWidget(palSeed, userData_Object(dlg)); updateSiteSpecificTheme_(palSeed, dlg); + setFlags_Widget(dlg, noFadeBackground_WidgetFlag, wasNoFade); closeSiteSpecific_(dlg); return iTrue; } -- cgit v1.2.3 From d743f11dd80264c2ac871981fd79ef9288bde219 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 16 Feb 2022 10:14:57 +0200 Subject: Help: Site-specific theme; max URL size; data URL images --- res/about/help.gmi | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/res/about/help.gmi b/res/about/help.gmi index 5bf77a76..cd1a332e 100644 --- a/res/about/help.gmi +++ b/res/about/help.gmi @@ -360,6 +360,8 @@ You can find a number of settings in Preferences to customize the user interface One important characteristic of Gemini is that you remain in control of what gets loaded and when. The browser will not suddenly fetch a ton of images, autoplay videos, or make surreptitious connections to any tracking servers — each network request is purposeful and manually triggered. With this in mind, the "Load image on scroll" option is provided to assist keyboard-only browsing and to facilitate a focused reading experience. When enabled, if there is an image link visible on the page and you press Space or ↓, it will be loaded and shown inline _instead_ of the view scrolling down. This allows you to read and see all the content of the page while only tapping on a single key on the keyboard. +The "Show images in Data URLs" option determines if image data embedded into links using data URLs is automatically shown after the page has finished loading. This only applies to a handful of common image media types: JPEG, PNG, GIF, and WebP. Note that the maximum size of an URL has a limit, so these embedded images have to fit under the configured limit to be recognized as valid links. Image from data URLs are displayed as links' inline content, just like with any other URL scheme. + 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. "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. @@ -393,6 +395,17 @@ Page content color themes are selected on the "Colors" tab of Preferences. The " * Sepia: Light sepia background with black text. Does not change depending on domain; use this for readability if you prefer a sepia reading experience. * High Contrast: White background with black text. Does not change depending on domain; use this for readability if you prefer maximum contrast between text and the background. +### 2.3.1 Site-specific theme + +You can manually customize the color theme of a site. Open "Page Information" and click on "Settings" to open the Site-Specific Settings dialog. The default keyboard shortcut for this is ${SHIFT+}${CTRL+}Comma. + +The "Theme palette seed" is the input data given to the theme generator that determines a site's color palette. The theme generator is designed to algorithmically choose a palette whose colors go together. It is not possible to individually pick the colors of a theme. + +You can enter any text as the palette seed. By default, the seed is the site's hostname, or a user name found in the URL path (e.g., "gemini://example.com/~User/" → seed "User"). Examples of how to use this: + +* Enter a word or phrase, or just random characters, to find a theme that you like. +* To copy the theme of another site, enter the hostname of that site as the seed. + ## 2.4 Fonts 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). @@ -498,7 +511,19 @@ Gemini allows relaying requests via a proxy server. On the "Network" tab, you ca When an HTTP proxy server is configured, HTTP/HTTPS links will no longer open in the system's default web browser but will be loaded via the proxy, expecting it to serve a 'text/gemini' version of the link contents. -## 2.7 Keybindings +## 2.7 URL handling + +The "Network" tab of Preferences has a few options related to handling URLs. + +Enabling the "Decode URLs" option causes all percent-encoded URLs to be shown in decoded form in the UI. Enabling this option is useful when encountering URLs that contain characters outside the basic Latin (A-Z) alphabet. This does not affect what gets sent to a server when loading pages: Gemini requests are always required to be sent in encoded form. + +The "Maximum URL size" setting defines what is considered a valid link in Gemtext. Link lines with URLs longer than this are not considered to be links and are displayed like regular text lines. This only affects links that don't use the "gemini" scheme. The maximum size of a Gemini URL is 1024 bytes. + +Having a length limit is necessary due the special case of Data URLs: +=> https://datatracker.ietf.org/doc/html/rfc2397 RFC 2397: The "data" URL scheme +With Data URLs, one is able to embed arbitrary data into a Gemtext file using link lines. This makes it possible to circumvent the intentional limitations of Gemini and override the clients' ability to choose which linked resources should be loaded and when. Without a limit, a server could for example send an arbitrarily large image attachment as part of the page, with no regard to the user's or client's preferences or abilities to view images. Also note that like any non-Gemini scheme, "data" is not expected to be supported by Gemini clients. + +## 2.8 Keybindings The "Keys" tab lets you change which keys are bound to UI commands. Click on an item with the mouse and then press the new key combination that you want bound to it. Right-click on a binding to erase it or reset it to the default. -- cgit v1.2.3 From bbd7c82c7da273408ea965a9db368a35f8236943 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Thu, 17 Feb 2022 13:41:59 +0200 Subject: Save text entered in the input prompt Use the same mechanism as in the Upload dialog to keep the text entered in the input prompt safe, in case the dialog is accidentally closed or the app crashes. --- src/ui/documentwidget.c | 8 +++++--- src/ui/inputwidget.c | 5 +++++ src/ui/inputwidget.h | 1 + src/ui/util.c | 4 +++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 1f3e0e37..7c071f47 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -3273,9 +3273,11 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { setTextColor_LabelWidget(menu, uiTextAction_ColorId); } } - setValidator_InputWidget(findChild_Widget(dlg, "input"), inputQueryValidator_, d); - setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"), - statusCode == sensitiveInput_GmStatusCode); + iInputWidget *input = findChild_Widget(dlg, "input"); + setValidator_InputWidget(input, inputQueryValidator_, d); + setBackupFileName_InputWidget(input, "inputbackup.txt"); + setSelectAllOnFocus_InputWidget(input, iTrue); + setSensitiveContent_InputWidget(input, statusCode == sensitiveInput_GmStatusCode); if (document_App() != d) { postCommandf_App("tabs.switch page:%p", d); } diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 6a8d428a..1b68ff57 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c @@ -1201,6 +1201,11 @@ void selectAll_InputWidget(iInputWidget *d) { #endif } +void deselect_InputWidget(iInputWidget *d) { + iZap(d->mark); + refresh_Widget(as_Widget(d)); +} + void validate_InputWidget(iInputWidget *d) { if (d->validator) { d->validator(d, d->validatorContext); /* this may change the contents */ diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h index 000fa4b7..832f7853 100644 --- a/src/ui/inputwidget.h +++ b/src/ui/inputwidget.h @@ -59,6 +59,7 @@ void setBackupFileName_InputWidget (iInputWidget *, const char *fileName); void begin_InputWidget (iInputWidget *); void end_InputWidget (iInputWidget *, iBool accept); void selectAll_InputWidget (iInputWidget *); +void deselect_InputWidget (iInputWidget *); void validate_InputWidget (iInputWidget *); void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); diff --git a/src/ui/util.c b/src/ui/util.c index 94e4b00c..de755b3f 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -1712,13 +1712,14 @@ iLabelWidget *addDialogTitle_Widget(iWidget *dlg, const char *text, const char * } static void acceptValueInput_(iWidget *dlg) { - const iInputWidget *input = findChild_Widget(dlg, "input"); + iInputWidget *input = findChild_Widget(dlg, "input"); if (!isEmpty_String(id_Widget(dlg))) { const iString *val = text_InputWidget(input); postCommandf_App("%s arg:%d value:%s", cstr_String(id_Widget(dlg)), toInt_String(val), cstr_String(val)); + setBackupFileName_InputWidget(input, NULL); } } @@ -1782,6 +1783,7 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) { else if (equal_Command(cmd, "valueinput.set")) { iInputWidget *input = findChild_Widget(dlg, "input"); setTextUndoableCStr_InputWidget(input, suffixPtr_Command(cmd, "text"), iTrue); + deselect_InputWidget(input); validate_InputWidget(input); return iTrue; } -- cgit v1.2.3 From 9fff17787b195e53ff0d9ef97b8579b57fc10c85 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Thu, 17 Feb 2022 14:29:12 +0200 Subject: Fixed mismatched banner and background There was an issue with loss of color precision. Saturation dropped to zero when converting to 8-bit RGB. --- src/gmdocument.c | 6 +++--- src/ui/color.c | 4 ++-- src/ui/color.h | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/gmdocument.c b/src/gmdocument.c index bc0cc073..79deb861 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -1569,7 +1569,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *paletteSeed, const iB if (theme == colorfulDark_GmDocumentTheme) { iHSLColor base = { hues[primIndex], - 0.8f * (d->themeSeed >> 24) / 255.0f, + 0.8f * (d->themeSeed >> 24) / 255.0f + minSat_HSLColor, 0.06f + 0.09f * ((d->themeSeed >> 5) & 0x7) / 7.0f, 1.0f }; iHSLColor altBase = { altHue, base.sat, base.lum, 1 }; @@ -1579,13 +1579,13 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *paletteSeed, const iB setHsl_Color(tmBannerBackground_ColorId, addSatLum_HSLColor(base, 0.1f, 0.04f * (isBannerLighter ? 1 : -1))); setHsl_Color(tmBannerTitle_ColorId, setLum_HSLColor(addSatLum_HSLColor(base, 0.1f, 0), 0.55f)); setHsl_Color(tmBannerIcon_ColorId, setLum_HSLColor(addSatLum_HSLColor(base, 0.35f, 0), 0.65f)); - + // printf("primHue: %zu alts: %d %d isDarkBgSat: %d\n", // primIndex, // altHues[primIndex].index[altIndex[0]], // altHues[primIndex].index[altIndex[1]], // isDarkBgSat); - + const float titleLum = 0.2f * ((d->themeSeed >> 17) & 0x7) / 7.0f; setHsl_Color(tmHeading1_ColorId, setLum_HSLColor(altBase, titleLum + 0.80f)); setHsl_Color(tmHeading2_ColorId, setLum_HSLColor(altBase, titleLum + 0.70f)); diff --git a/src/ui/color.c b/src/ui/color.c index 824342ae..9cba322d 100644 --- a/src/ui/color.c +++ b/src/ui/color.c @@ -522,8 +522,8 @@ iHSLColor setLum_HSLColor(iHSLColor d, float lum) { } iHSLColor addSatLum_HSLColor(iHSLColor d, float sat, float lum) { - d.sat = iClamp(d.sat + sat, 0, 1); - d.lum = iClamp(d.lum + lum, 0, 1); + d.sat = iClamp(d.sat + sat, minSat_HSLColor, 1); + d.lum = iClamp(d.lum + lum, minSat_HSLColor, 1); return d; } diff --git a/src/ui/color.h b/src/ui/color.h index 24f9e713..f46976d7 100644 --- a/src/ui/color.h +++ b/src/ui/color.h @@ -231,6 +231,8 @@ struct Impl_HSLColor { float hue, sat, lum, a; }; +#define minSat_HSLColor 0.013f /* Conversion to 8-bit RGB may result in saturation dropping to zero. */ + iHSLColor hsl_Color (iColor); iColor rgb_HSLColor (iHSLColor); float luma_Color (iColor); -- cgit v1.2.3 From 95cf0209c6a2357788167e01cc8eca2215244f36 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Thu, 17 Feb 2022 14:30:36 +0200 Subject: Focus palette input in Site-Specific Settings --- src/ui/util.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/util.c b/src/ui/util.c index de755b3f..d6dc461a 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -3335,6 +3335,7 @@ iWidget *makeSiteSpecificSettings_Widget(const iString *url) { } setCommandHandler_Widget(dlg, siteSpecificSettingsHandler_); setupSheetTransition_Mobile(dlg, incoming_TransitionFlag); + setFocus_Widget(findChild_Widget(dlg, "sitespec.palette")); return dlg; } -- cgit v1.2.3 From 3b553dd49d8f9adeda125ee6cc13f923611d308b Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Thu, 17 Feb 2022 14:31:12 +0200 Subject: Tab button coloring with colored backgrounds It's best to match the active tab button color to the document's background in case the latter is colored. --- src/gmdocument.c | 3 +-- src/prefs.h | 4 ++++ src/ui/labelwidget.c | 12 +++++++++++- src/ui/root.c | 2 ++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/gmdocument.c b/src/gmdocument.c index 79deb861..a85e4b71 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -491,8 +491,7 @@ static iBool isNormalized_GmDocument_(const iGmDocument *d) { } static enum iGmDocumentTheme currentTheme_(void) { - return (isDark_ColorTheme(colorTheme_App()) ? prefs_App()->docThemeDark - : prefs_App()->docThemeLight); + return docTheme_Prefs(prefs_App()); } static void alignDecoration_GmRun_(iGmRun *run, iBool isCentered) { diff --git a/src/prefs.h b/src/prefs.h index 59c36a16..d988399e 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -193,3 +193,7 @@ iLocalDef float scrollSpeedFactor_Prefs(const iPrefs *d, enum iScrollType type) iAssert(type >= 0 && type < max_ScrollType); return 10.0f / iMax(1, d->smoothScrollSpeed[type]) * (type == mouse_ScrollType ? 0.5f : 1.0f); } + +iLocalDef enum iGmDocumentTheme docTheme_Prefs(const iPrefs *d) { + return isDark_ColorTheme(d->theme) ? d->docThemeDark : d->docThemeLight; +} diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index 3454014a..75cbbf3a 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c @@ -231,7 +231,17 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int *bg = uiBackgroundUnfocusedSelection_ColorId; } else { - *bg = uiBackgroundSelected_ColorId; + const enum iGmDocumentTheme docTheme = docTheme_Prefs(prefs_App()); + if ((docTheme == colorfulLight_GmDocumentTheme || docTheme == sepia_GmDocumentTheme) && + !cmp_String(&d->widget.parent->id, "tabs.buttons")) { + *bg = (docTheme == sepia_GmDocumentTheme && + colorTheme_App() == pureWhite_ColorTheme + ? tmBackground_ColorId + : tmBannerBackground_ColorId); + } + else { + *bg = uiBackgroundSelected_ColorId; + } } if (!isKeyRoot) { *bg = isDark_ColorTheme(colorTheme_App()) ? uiBackgroundUnfocusedSelection_ColorId diff --git a/src/ui/root.c b/src/ui/root.c index 6e187313..eeb5956f 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -1059,6 +1059,8 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { updateNavBarIdentity_(navBar); } setFocus_Widget(NULL); + makePaletteGlobal_GmDocument(document_DocumentWidget(doc)); + refresh_Widget(findWidget_Root("doctabs")); } else if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd)) { iWidget *widget = pointer_Command(cmd); -- cgit v1.2.3 From 5f1b30cafcf251d52550915e3880609c099095ed Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Thu, 17 Feb 2022 18:03:32 +0200 Subject: Don't suggest fonts any more Now you can just do searches in the font library. --- po/en.po | 9 --------- src/app.c | 4 ++++ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/po/en.po b/po/en.po index 22c10e44..bff4c495 100644 --- a/po/en.po +++ b/po/en.po @@ -2144,12 +2144,3 @@ msgstr "Permanently dismiss warning about terminal emulation on %s?" msgid "dlg.dismiss.warning" msgstr "Dismiss Warning" - -msgid "heading.fontpack.classic" -msgstr "Download Fontpack" - -msgid "dlg.fontpack.classic.msg" -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?" - -msgid "dlg.fontpack.classic" -msgstr "Download Fontpack (25 MB)" diff --git a/src/app.c b/src/app.c index d1e3870a..d47c6ef1 100644 --- a/src/app.c +++ b/src/app.c @@ -441,6 +441,7 @@ static void loadPrefs_App_(iApp *d) { } iRelease(f); /* Upgrade checks. */ +#if 0 /* disabled in v1.11 (font library search) */ if (cmp_Version(&upgradedFromAppVersion, &(iVersion){ 1, 8, 0 }) < 0) { #if !defined (iPlatformAppleMobile) && !defined (iPlatformAndroidMobile) /* When upgrading to v1.8.0, the old hardcoded font library is gone and that means @@ -450,6 +451,7 @@ static void loadPrefs_App_(iApp *d) { postCommand_App("~fontpack.suggest.classic"); #endif } +#endif #if !defined (LAGRANGE_ENABLE_CUSTOM_FRAME) d->prefs.customFrame = iFalse; #endif @@ -2289,6 +2291,7 @@ iBool handleCommand_App(const char *cmd) { suffixPtr_Command(cmd, "where"))); return iTrue; } +#if 0 /* disabled in v1.11 */ else if (equal_Command(cmd, "fontpack.suggest.classic")) { /* TODO: Don't use this when system fonts are accessible. */ if (!isInstalled_Fonts("classic-set") && !isInstalled_Fonts("cjk")) { @@ -2305,6 +2308,7 @@ iBool handleCommand_App(const char *cmd) { } return iTrue; } +#endif else if (equal_Command(cmd, "prefs.changed")) { savePrefs_App_(d); return iTrue; -- cgit v1.2.3 From bef61b34d8a23e2e8594207c2876ce982fe0e15f Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 18 Feb 2022 14:59:03 +0200 Subject: Basic opening and closing of new windows One can create a new window with `window.new` and close it with the window close button. --- src/app.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- src/app.h | 5 ++++ src/ui/window.c | 16 ++++++++++++- 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/app.c b/src/app.c index d47c6ef1..686d8b82 100644 --- a/src/app.c +++ b/src/app.c @@ -125,7 +125,8 @@ struct Impl_App { iGmCerts * certs; iVisited * visited; iBookmarks * bookmarks; - iMainWindow *window; + iMainWindow *window; /* currently active MainWindow */ + iPtrArray mainWindows; iPtrArray popupWindows; iSortedArray tickers; /* per-frame callbacks, used for animations */ uint32_t lastTickerTime; @@ -474,7 +475,7 @@ static const char *magicTabDocument_App_ = "tabd"; static const char *magicSidebar_App_ = "side"; enum iDocumentStateFlag { - current_DocumentStateFlag = iBit(1), + current_DocumentStateFlag = iBit(1), rootIndex1_DocumentStateFlag = iBit(2) }; @@ -946,8 +947,10 @@ static void init_App_(iApp *d, int argc, char **argv) { d->initialWindowRect.size.y = toInt_String(value_CommandLineArg(arg, 0)); } } + init_PtrArray(&d->mainWindows); init_PtrArray(&d->popupWindows); d->window = new_MainWindow(d->initialWindowRect); + addWindow_App(d->window); load_Visited(d->visited, dataDir_App_()); load_Bookmarks(d->bookmarks, dataDir_App_()); load_MimeHooks(d->mimehooks, dataDir_App_()); @@ -1012,7 +1015,11 @@ static void deinit_App(iApp *d) { SDL_RemoveTimer(d->autoReloadTimer); saveState_App_(d); savePrefs_App_(d); - delete_MainWindow(d->window); + iReverseForEach(PtrArray, j, &d->mainWindows) { + delete_MainWindow(j.ptr); + } + iAssert(isEmpty_PtrArray(&d->mainWindows)); + deinit_PtrArray(&d->mainWindows); d->window = NULL; deinit_Feeds(); save_Keys(dataDir_App_()); @@ -1027,7 +1034,6 @@ static void deinit_App(iApp *d) { delete_GmCerts(d->certs); save_MimeHooks(d->mimehooks); delete_MimeHooks(d->mimehooks); - d->window = NULL; deinit_CommandLine(&d->args); iRelease(d->launchCommands); delete_String(d->execPath); @@ -1286,7 +1292,14 @@ static iPtrArray *listWindows_App_(const iApp *d, iPtrArray *windows) { iReverseConstForEach(PtrArray, i, &d->popupWindows) { pushBack_PtrArray(windows, i.ptr); } - pushBack_PtrArray(windows, d->window); + if (d->window) { + pushBack_PtrArray(windows, d->window); + } + iConstForEach(PtrArray, j, &d->mainWindows) { + if (j.ptr != d->window) { + pushBack_PtrArray(windows, j.ptr); + } + } return windows; } @@ -1843,6 +1856,26 @@ void removeTicker_App(iTickerFunc ticker, iAny *context) { remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker }); } +void addWindow_App(iMainWindow *win) { + iApp *d = &app_; + pushBack_PtrArray(&d->mainWindows, win); +} + +void removeWindow_App(iMainWindow *win) { + iApp *d = &app_; + removeOne_PtrArray(&d->mainWindows, win); +} + +size_t numWindows_App(void) { + return size_PtrArray(&app_.mainWindows); +} + +void setActiveWindow_App(iMainWindow *win) { + iApp *d = &app_; + d->window = win; + printf("Active window: %p\n", win); fflush(stdout); +} + void addPopup_App(iWindow *popup) { iApp *d = &app_; pushBack_PtrArray(&d->popupWindows, popup); @@ -2121,6 +2154,22 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe return doc; } +void closeWindow_App(iMainWindow *win) { + iApp *d = &app_; + delete_MainWindow(win); + if (d->window == win) { + /* Activate another window. */ + iForEach(PtrArray, i, &d->mainWindows) { + if (i.ptr != d->window) { + SDL_RaiseWindow(i.ptr); + setActiveWindow_App(i.ptr); + setCurrent_Window(i.ptr); + break; + } + } + } +} + static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { iApp *d = &app_; if (equal_Command(cmd, "ident.showmore")) { @@ -3016,6 +3065,15 @@ iBool handleCommand_App(const char *cmd) { #endif return iFalse; } + else if (equal_Command(cmd, "window.new")) { + iMainWindow *newWin = new_MainWindow(moved_Rect(d->initialWindowRect, init_I2(20, 20))); + addWindow_App(newWin); + SDL_ShowWindow(newWin->base.win); + setCurrent_Window(newWin); + postCommand_Root(newWin->base.roots[0], "navigate.home"); + postCommand_Root(newWin->base.roots[0], "window.unfreeze"); + return iTrue; + } else if (equal_Command(cmd, "tabs.new")) { const iBool isDuplicate = argLabel_Command(cmd, "duplicate") != 0; newTab_App(isDuplicate ? document_App() : NULL, iTrue); @@ -3076,6 +3134,9 @@ iBool handleCommand_App(const char *cmd) { } } } + else if (numWindows_App() > 1) { + closeWindow_App(d->window); + } else { postCommand_App("quit"); } diff --git a/src/app.h b/src/app.h index 22fe5d46..74a5429b 100644 --- a/src/app.h +++ b/src/app.h @@ -122,6 +122,11 @@ iAny * findWidget_App (const char *id); void addTicker_App (iTickerFunc ticker, iAny *context); void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context); void removeTicker_App (iTickerFunc ticker, iAny *context); +void addWindow_App (iMainWindow *win); +void removeWindow_App (iMainWindow *win); +void setActiveWindow_App (iMainWindow *win); +void closeWindow_App (iMainWindow *win); +size_t numWindows_App (void); void addPopup_App (iWindow *popup); void removePopup_App (iWindow *popup); void postRefresh_App (void); diff --git a/src/ui/window.c b/src/ui/window.c index 47abf878..bc32e479 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -80,6 +80,7 @@ iDefineTypeConstructionArgs(MainWindow, (iRect rect), rect) #if defined (iHaveNativeMenus) /* Using native menus. */ static const iMenuItem fileMenuItems_[] = { + { "${menu.newwindow}", SDLK_n, KMOD_PRIMARY, "window.new" }, { "${menu.newtab}", SDLK_t, KMOD_PRIMARY, "tabs.new" }, { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, { "---", 0, 0, NULL }, @@ -210,7 +211,9 @@ static void windowSizeChanged_MainWindow_(iMainWindow *d) { static void setupUserInterface_MainWindow(iMainWindow *d) { #if defined (iHaveNativeMenus) - insertMacMenus_(); + if (numWindows_App() == 0) { + insertMacMenus_(); /* TODO: Shouldn't this be in the App? */ + } #endif /* One root is created by default. */ d->base.roots[0] = new_Root(); @@ -246,6 +249,7 @@ static void updateSize_MainWindow_(iMainWindow *d, iBool notifyAlways) { void drawWhileResizing_MainWindow(iMainWindow *d, int w, int h) { if (!isDrawing_) { + setCurrent_Window(d); draw_MainWindow(d); } } @@ -647,6 +651,7 @@ void init_MainWindow(iMainWindow *d, iRect rect) { } void deinit_MainWindow(iMainWindow *d) { + removeWindow_App(d); if (d->backBuf) { SDL_DestroyTexture(d->backBuf); } @@ -830,6 +835,9 @@ static void savePlace_MainWindow_(iAny *mainWindow) { } static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) { + if (ev->windowID != SDL_GetWindowID(d->base.win)) { + return iFalse; + } switch (ev->event) { #if defined(iPlatformDesktop) case SDL_WINDOWEVENT_EXPOSED: @@ -953,6 +961,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent setCapsLockDown_Keys(iFalse); postCommand_App("window.focus.gained"); d->base.isExposed = iTrue; + setActiveWindow_App(d); #if !defined (iPlatformDesktop) /* Returned to foreground, may have lost buffered content. */ invalidate_MainWindow_(d, iTrue); @@ -970,6 +979,11 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent SDL_SetWindowInputFocus(d->base.win); postRefresh_App(); return iTrue; + case SDL_WINDOWEVENT_CLOSE: + if (numWindows_App() > 1) { + closeWindow_App(d); + } + return iTrue; default: break; } -- cgit v1.2.3 From dd9add718c4a9a2b29cd38b05886f22877b89b35 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 18 Feb 2022 19:51:38 +0200 Subject: Serializing multiple windows Not quite fully functional yet. The window positioning still needs to be written to prefs.cfg. --- res/about/version.gmi | 3 + src/app.c | 202 ++++++++++++++++++++++++++++++++---------------- src/app.h | 3 +- src/defs.h | 5 +- src/ui/documentwidget.c | 5 ++ src/ui/text.c | 20 +++-- src/ui/window.c | 36 ++++++++- 7 files changed, 193 insertions(+), 81 deletions(-) diff --git a/res/about/version.gmi b/res/about/version.gmi index aee62e82..73b88a52 100644 --- a/res/about/version.gmi +++ b/res/about/version.gmi @@ -6,6 +6,9 @@ ``` # Release notes +## 1.11 +⚠️ Downgrading back to v1.10 causes all site-specific themes to be forgotten. Back up your sitespec.ini beforehand. + ## 1.10.6 * Added bindings for switching Feeds list to Unread/All mode. * Fixed normalization of empty Gemini URL paths to `/` as per the November 2021 spec update. diff --git a/src/app.c b/src/app.c index 686d8b82..850a9706 100644 --- a/src/app.c +++ b/src/app.c @@ -476,7 +476,11 @@ static const char *magicSidebar_App_ = "side"; enum iDocumentStateFlag { current_DocumentStateFlag = iBit(1), - rootIndex1_DocumentStateFlag = iBit(2) + rootIndex1_DocumentStateFlag = iBit(2), +}; + +enum iWindowStateFlag { + current_WindowStateFlag = iBit(9), }; static iBool loadState_App_(iApp *d) { @@ -499,19 +503,48 @@ static iBool loadState_App_(iApp *d) { } setVersion_Stream(stream_File(f), version); /* Window state. */ - iDocumentWidget *doc = NULL; - iDocumentWidget *current[2] = { NULL, NULL }; - iBool isFirstTab[2] = { iTrue, iTrue }; + iDeclareType(CurrentTabs); + struct Impl_CurrentTabs { + iDocumentWidget *currentTab[2]; /* for each root */ + }; + int numWins = 0; + iMainWindow * win = NULL; + iMainWindow * currentWin = d->window; + iArray * currentTabs; /* two per window (per root per window) */ + iBool isFirstTab[2]; + currentTabs = collectNew_Array(sizeof(iCurrentTabs)); while (!atEnd_File(f)) { readData_File(f, 4, magic); if (!memcmp(magic, magicWindow_App_, 4)) { - const int splitMode = read32_File(f); - const int keyRoot = read32_File(f); - d->window->pendingSplitMode = splitMode; - setSplitMode_MainWindow(d->window, splitMode | noEvents_WindowSplit); - d->window->base.keyRoot = d->window->base.roots[keyRoot]; + numWins++; + const int splitMode = read32_File(f); + const int winState = read32_File(f); + const int keyRoot = (winState & 1); + const iBool isCurrent = (winState & current_WindowStateFlag) != 0; + if (numWins == 1) { + win = d->window; + } + else { + win = new_MainWindow(d->initialWindowRect); + addWindow_App(win); + } + pushBack_Array(currentTabs, &(iCurrentTabs){ { NULL, NULL } }); + isFirstTab[0] = isFirstTab[1] = iTrue; + if (isCurrent) { + currentWin = win; + } + setCurrent_Window(win); + setCurrent_Root(NULL); + win->pendingSplitMode = splitMode; + setSplitMode_MainWindow(win, splitMode | noEvents_WindowSplit); + win->base.keyRoot = d->window->base.roots[keyRoot]; } else if (!memcmp(magic, magicSidebar_App_, 4)) { + if (!win) { + printf("%s: missing window\n", cstr_String(path_File(f))); + setCurrent_Root(NULL); + return iFalse; + } const uint16_t bits = readU16_File(f); const uint8_t modes = readU8_File(f); const float widths[2] = { @@ -528,7 +561,7 @@ static iBool loadState_App_(iApp *d) { } const uint8_t rootIndex = bits & 0xff; const uint8_t flags = bits >> 8; - iRoot *root = d->window->base.roots[rootIndex]; + iRoot *root = win->base.roots[rootIndex]; if (root) { iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); @@ -551,12 +584,18 @@ static iBool loadState_App_(iApp *d) { } } else if (!memcmp(magic, magicTabDocument_App_, 4)) { + if (!win) { + printf("%s: missing window\n", cstr_String(path_File(f))); + setCurrent_Root(NULL); + return iFalse; + } const int8_t flags = read8_File(f); int rootIndex = flags & rootIndex1_DocumentStateFlag ? 1 : 0; - if (rootIndex > numRoots_Window(as_Window(d->window)) - 1) { + if (rootIndex > numRoots_Window(as_Window(win)) - 1) { rootIndex = 0; } - setCurrent_Root(d->window->base.roots[rootIndex]); + setCurrent_Root(win->base.roots[rootIndex]); + iDocumentWidget *doc; if (isFirstTab[rootIndex]) { isFirstTab[rootIndex] = iFalse; /* There is one pre-created tab in each root. */ @@ -566,7 +605,7 @@ static iBool loadState_App_(iApp *d) { doc = newTab_App(NULL, iFalse /* no switching */); } if (flags & current_DocumentStateFlag) { - current[rootIndex] = doc; + value_Array(currentTabs, numWins - 1, iCurrentTabs).currentTab[rootIndex] = doc; } deserializeState_DocumentWidget(doc, stream_File(f)); doc = NULL; @@ -577,12 +616,23 @@ static iBool loadState_App_(iApp *d) { return iFalse; } } - if (d->window->splitMode) { - /* Update root placement. */ - resize_MainWindow(d->window, -1, -1); + iForEach(Array, i, currentTabs) { + const iCurrentTabs *cur = i.value; + win = at_PtrArray(&d->mainWindows, index_ArrayIterator(&i)); + for (size_t j = 0; j < 2; ++j) { + postCommandf_Root(win->base.roots[j], "tabs.switch page:%p", cur->currentTab[j]); + } + if (win->splitMode) { + /* Update root placement. */ + resize_MainWindow(win, -1, -1); + } +// postCommand_Root(win->base.roots[0], "window.unfreeze"); + win->isDrawFrozen = iFalse; + SDL_ShowWindow(win->base.win); } - iForIndices(i, current) { - postCommandf_Root(NULL, "tabs.switch page:%p", current[i]); + if (numWindows_App() > 1) { + SDL_RaiseWindow(currentWin->base.win); + setActiveWindow_App(currentWin); } setCurrent_Root(NULL); return iTrue; @@ -593,7 +643,6 @@ static iBool loadState_App_(iApp *d) { static void saveState_App_(const iApp *d) { iUnused(d); trimCache_App(); - iMainWindow *win = d->window; /* UI state is saved in binary because it is quite complex (e.g., navigation history, cached content) and depends closely on the widget tree. The data is largely not reorderable and should not be modified @@ -602,43 +651,48 @@ static void saveState_App_(const iApp *d) { if (open_File(f, writeOnly_FileMode)) { writeData_File(f, magicState_App_, 4); writeU32_File(f, latest_FileVersion); /* version */ - /* Begin with window state. */ { - writeData_File(f, magicWindow_App_, 4); - writeU32_File(f, win->splitMode); - writeU32_File(f, win->base.keyRoot == win->base.roots[0] ? 0 : 1); - } - /* State of UI elements. */ { - iForIndices(i, win->base.roots) { - const iRoot *root = win->base.roots[i]; - if (root) { - writeData_File(f, magicSidebar_App_, 4); - const iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); - const iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); - writeU16_File(f, i | - (isVisible_Widget(sidebar) ? 0x100 : 0) | - (isVisible_Widget(sidebar2) ? 0x200 : 0) | - (feedsMode_SidebarWidget(sidebar) == unread_FeedsMode ? 0x400 : 0) | - (feedsMode_SidebarWidget(sidebar2) == unread_FeedsMode ? 0x800 : 0)); - writeU8_File(f, - mode_SidebarWidget(sidebar) | - (mode_SidebarWidget(sidebar2) << 4)); - writef_Stream(stream_File(f), width_SidebarWidget(sidebar)); - writef_Stream(stream_File(f), width_SidebarWidget(sidebar2)); - serialize_IntSet(closedFolders_SidebarWidget(sidebar), stream_File(f)); - serialize_IntSet(closedFolders_SidebarWidget(sidebar2), stream_File(f)); + iConstForEach(PtrArray, winIter, &d->mainWindows) { + const iMainWindow *win = winIter.ptr; + setCurrent_Window(winIter.ptr); + /* Window state. */ { + writeData_File(f, magicWindow_App_, 4); + writeU32_File(f, win->splitMode); + writeU32_File(f, (win->base.keyRoot == win->base.roots[0] ? 0 : 1) | + (win == d->window ? current_WindowStateFlag : 0)); + } + /* State of UI elements. */ { + iForIndices(i, win->base.roots) { + const iRoot *root = win->base.roots[i]; + if (root) { + writeData_File(f, magicSidebar_App_, 4); + const iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); + const iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); + writeU16_File(f, i | + (isVisible_Widget(sidebar) ? 0x100 : 0) | + (isVisible_Widget(sidebar2) ? 0x200 : 0) | + (feedsMode_SidebarWidget(sidebar) == unread_FeedsMode ? 0x400 : 0) | + (feedsMode_SidebarWidget(sidebar2) == unread_FeedsMode ? 0x800 : 0)); + writeU8_File(f, + mode_SidebarWidget(sidebar) | + (mode_SidebarWidget(sidebar2) << 4)); + writef_Stream(stream_File(f), width_SidebarWidget(sidebar)); + writef_Stream(stream_File(f), width_SidebarWidget(sidebar2)); + serialize_IntSet(closedFolders_SidebarWidget(sidebar), stream_File(f)); + serialize_IntSet(closedFolders_SidebarWidget(sidebar2), stream_File(f)); + } } } - } - iConstForEach(ObjectList, i, iClob(listDocuments_App(NULL))) { - iAssert(isInstance_Object(i.object, &Class_DocumentWidget)); - const iWidget *widget = constAs_Widget(i.object); - writeData_File(f, magicTabDocument_App_, 4); - int8_t flags = (document_Root(widget->root) == i.object ? current_DocumentStateFlag : 0); - if (widget->root == win->base.roots[1]) { - flags |= rootIndex1_DocumentStateFlag; + iConstForEach(ObjectList, i, iClob(listDocuments_App(NULL))) { + iAssert(isInstance_Object(i.object, &Class_DocumentWidget)); + const iWidget *widget = constAs_Widget(i.object); + writeData_File(f, magicTabDocument_App_, 4); + int8_t flags = (document_Root(widget->root) == i.object ? current_DocumentStateFlag : 0); + if (widget->root == win->base.roots[1]) { + flags |= rootIndex1_DocumentStateFlag; + } + write8_File(f, flags); + serializeState_DocumentWidget(i.object, stream_File(f)); } - write8_File(f, flags); - serializeState_DocumentWidget(i.object, stream_File(f)); } iRelease(f); } @@ -1491,6 +1545,7 @@ void processEvents_App(enum iAppEventMode eventMode) { window->lastHover = window->hover; wasUsed = processEvent_Window(window, &ev); if (ev.type == SDL_MOUSEMOTION || ev.type == SDL_MOUSEBUTTONDOWN) { + /* Only offered to the frontmost window. */ break; } if (wasUsed) break; @@ -1776,7 +1831,7 @@ void postCommand_Root(iRoot *d, const char *command) { } SDL_Event ev = { .type = SDL_USEREVENT }; ev.user.code = command_UserEventCode; - /*ev.user.windowID = id_Window(get_Window());*/ +// ev.user.windowID = id_Window(get_Window()); ev.user.data1 = strdup(command); ev.user.data2 = d; /* all events are root-specific */ SDL_PushEvent(&ev); @@ -1788,10 +1843,17 @@ void postCommand_Root(iRoot *d, const char *command) { command); #else if (app_.commandEcho) { - printf("%s[command] {%d} %s\n", - app_.isLoadingPrefs ? "[Prefs] " : "", - (d == NULL || win == NULL ? 0 : d == win->roots[0] ? 1 : 2), - command); fflush(stdout); + const int windowIndex = + win && type_Window(win) == main_WindowType ? windowIndex_App(as_MainWindow(win)) + 1 : 0; + printf("%s%s[command] {%d:%d} %s\n", + !app_.isFinishedLaunching ? " " : "", + app_.isLoadingPrefs ? " " : "", + windowIndex, + (d == NULL || win == NULL ? 0 + : d == win->roots[0] ? 1 + : 2), + command); + fflush(stdout); } #endif } @@ -1870,6 +1932,10 @@ size_t numWindows_App(void) { return size_PtrArray(&app_.mainWindows); } +size_t windowIndex_App(const iMainWindow *win) { + return indexOf_PtrArray(&app_.mainWindows, win); +} + void setActiveWindow_App(iMainWindow *win) { iApp *d = &app_; d->window = win; @@ -3066,12 +3132,12 @@ iBool handleCommand_App(const char *cmd) { return iFalse; } else if (equal_Command(cmd, "window.new")) { - iMainWindow *newWin = new_MainWindow(moved_Rect(d->initialWindowRect, init_I2(20, 20))); - addWindow_App(newWin); + iMainWindow *newWin = new_MainWindow(moved_Rect(d->initialWindowRect, init_I2(30, 30))); + addWindow_App(newWin); /* takes ownership */ SDL_ShowWindow(newWin->base.win); setCurrent_Window(newWin); - postCommand_Root(newWin->base.roots[0], "navigate.home"); - postCommand_Root(newWin->base.roots[0], "window.unfreeze"); + postCommand_Root(newWin->base.roots[0], "~navigate.home"); + postCommand_Root(newWin->base.roots[0], "~window.unfreeze"); return iTrue; } else if (equal_Command(cmd, "tabs.new")) { @@ -3659,12 +3725,16 @@ iMainWindow *mainWindow_App(void) { return app_.window; } -void closePopups_App(void) { +void closePopups_App(iBool doForce) { iApp *d = &app_; const uint32_t now = SDL_GetTicks(); - iConstForEach(PtrArray, i, &d->popupWindows) { - const iWindow *win = i.ptr; - if (now - win->focusGainedAt > 200) { + iForEach(PtrArray, i, &d->popupWindows) { + iWindow *win = i.ptr; +// if (doForce) { +// collect_Garbage(win, (iDeleteFunc) delete_Window); +// } +// else + if (now - win->focusGainedAt > 200) { postCommand_Root(((const iWindow *) i.ptr)->roots[0], "cancel"); } } diff --git a/src/app.h b/src/app.h index 74a5429b..dd24ec8e 100644 --- a/src/app.h +++ b/src/app.h @@ -127,6 +127,7 @@ void removeWindow_App (iMainWindow *win); void setActiveWindow_App (iMainWindow *win); void closeWindow_App (iMainWindow *win); size_t numWindows_App (void); +size_t windowIndex_App (const iMainWindow *win); void addPopup_App (iWindow *popup); void removePopup_App (iWindow *popup); void postRefresh_App (void); @@ -151,4 +152,4 @@ void resetFonts_App (void); void availableFontsChanged_App(void); iMainWindow * mainWindow_App (void); -void closePopups_App (void); +void closePopups_App (iBool doForce); diff --git a/src/defs.h b/src/defs.h index cd502f70..be5280fa 100644 --- a/src/defs.h +++ b/src/defs.h @@ -38,9 +38,10 @@ enum iFileVersion { serializedSidebarState_FileVersion = 3, addedRecentUrlFlags_FileVersion = 4, bookmarkFolderState_FileVersion = 5, + multipleWindows_FileVersion = 6, /* meta */ - idents_FileVersion = 1, /* version used by GmCerts/idents.lgr */ - latest_FileVersion = 5, + latest_FileVersion = 6, /* used by state.lgr */ + idents_FileVersion = 1, /* used by GmCerts/idents.lgr */ }; enum iImageStyle { diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 7c071f47..f8cc10b9 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -5275,6 +5275,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e "document.upload", !equalCase_Rangecc(urlScheme_String(d->mod.url), "gemini") && !equalCase_Rangecc(urlScheme_String(d->mod.url), "titan")); + setMenuItemDisabled_Widget( + d->menu, + "document.upload copy:1", + !equalCase_Rangecc(urlScheme_String(d->mod.url), "gemini") && + !equalCase_Rangecc(urlScheme_String(d->mod.url), "titan")); } processContextMenuEvent_Widget(d->menu, ev, {}); } diff --git a/src/ui/text.c b/src/ui/text.c index 51531057..83e87d0c 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -295,6 +295,7 @@ static void setupFontVariants_Text_(iText *d, const iFontSpec *spec, int baseId) /* This is the highest priority override font. */ d->overrideFontId = baseId; } + iAssert(activeText_ == d); pushBack_Array(&d->fontPriorityOrder, &(iPrioMapItem){ spec->priority, baseId }); for (enum iFontStyle style = 0; style < max_FontStyle; style++) { for (enum iFontSize sizeId = 0; sizeId < max_FontSize; sizeId++) { @@ -499,10 +500,13 @@ static void resetCache_Text_(iText *d) { } void resetFonts_Text(iText *d) { + iText *oldActive = activeText_; + setCurrent_Text(d); /* some routines rely on the global `activeText_` pointer */ deinitFonts_Text_(d); deinitCache_Text_(d); initCache_Text_(d); initFonts_Text_(d); + setCurrent_Text(oldActive); } static SDL_Palette *glyphPalette_(void) { @@ -1476,14 +1480,14 @@ static void evenMonospaceAdvances_GlyphBuffer_(iGlyphBuffer *d, iFont *baseFont) } static iRect run_Font_(iFont *d, const iRunArgs *args) { - const int mode = args->mode; - const iInt2 orig = args->pos; - iRect bounds = { orig, init_I2(0, d->height) }; - float xCursor = 0.0f; - float yCursor = 0.0f; - float xCursorMax = 0.0f; - const iBool isMonospaced = isMonospaced_Font(d); - iWrapText *wrap = args->wrap; + const int mode = args->mode; + const iInt2 orig = args->pos; + iRect bounds = { orig, init_I2(0, d->height) }; + float xCursor = 0.0f; + float yCursor = 0.0f; + float xCursorMax = 0.0f; + const iBool isMonospaced = isMonospaced_Font(d); + iWrapText *wrap = args->wrap; iAssert(args->text.end >= args->text.start); /* Split the text into a number of attributed runs that specify exactly which font is used and other attributes such as color. (HarfBuzz shaping is done diff --git a/src/ui/window.c b/src/ui/window.c index bc32e479..0a97b97c 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -865,7 +865,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent if (d->base.isMinimized) { return iFalse; } - closePopups_App(); + closePopups_App(iFalse); checkPixelRatioChange_Window_(as_Window(d)); const iInt2 newPos = init_I2(ev->data1, ev->data2); if (isEqual_I2(newPos, init1_I2(-32000))) { /* magic! */ @@ -915,7 +915,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent // updateSize_Window_(d, iTrue); return iTrue; } - closePopups_App(); + closePopups_App(iFalse); if (unsnap_MainWindow_(d, NULL)) { return iTrue; } @@ -937,7 +937,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent return iTrue; case SDL_WINDOWEVENT_MINIMIZED: d->base.isMinimized = iTrue; - closePopups_App(); + closePopups_App(iTrue); return iTrue; #else /* if defined (!iPlatformDesktop) */ case SDL_WINDOWEVENT_RESIZED: @@ -973,7 +973,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent #if !defined (iPlatformDesktop) setFreezeDraw_MainWindow(d, iTrue); #endif - closePopups_App(); + closePopups_App(iTrue); return iFalse; case SDL_WINDOWEVENT_TAKE_FOCUS: SDL_SetWindowInputFocus(d->base.win); @@ -1178,7 +1178,35 @@ iLocalDef iBool isEscapeKeypress_(const SDL_Event *ev) { return (ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP) && ev->key.keysym.sym == SDLK_ESCAPE; } +static uint32_t windowId_SDLEvent_(const SDL_Event *ev) { + switch (ev->type) { + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + return ev->button.windowID; + case SDL_MOUSEMOTION: + return ev->motion.windowID; + case SDL_MOUSEWHEEL: + return ev->wheel.windowID; + case SDL_KEYDOWN: + case SDL_KEYUP: + return ev->key.windowID; + case SDL_TEXTINPUT: + return ev->text.windowID; + case SDL_USEREVENT: + return ev->user.windowID; + default: + return 0; + } +} + iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) { +#if 0 + /* For the right window? */ + const uint32_t evWin = windowId_SDLEvent_(ev); + if (evWin && evWin != id_Window(d)) { + return iFalse; /* Meant for a different window. */ + } +#endif if (ev->type == SDL_MOUSEMOTION) { /* Hover widget may change. */ setHover_Widget(NULL); -- cgit v1.2.3 From 1ef7170f2b1b8c48fababc112673afa17729033f Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sat, 19 Feb 2022 08:15:43 +0200 Subject: App: Save multiple window rectangles --- src/app.c | 127 ++++++++++++++++++++++++++++++++++++---------------------- src/ui/root.c | 11 +++++ src/ui/root.h | 1 + 3 files changed, 92 insertions(+), 47 deletions(-) diff --git a/src/app.c b/src/app.c index 850a9706..ec8b59b3 100644 --- a/src/app.c +++ b/src/app.c @@ -154,7 +154,8 @@ struct Impl_App { /* Preferences: */ iBool commandEcho; /* --echo */ iBool forceSoftwareRender; /* --sw */ - iRect initialWindowRect; + //iRect initialWindowRect; + iArray initialWindowRects; /* one per window */ iPrefs prefs; }; @@ -200,33 +201,43 @@ static iString *serializePrefs_App_(const iApp *d) { appendFormat_String(str, "window.retain arg:%d\n", d->prefs.retainWindowSize); if (d->prefs.retainWindowSize) { int w, h, x, y; - x = d->window->place.normalRect.pos.x; - y = d->window->place.normalRect.pos.y; - w = d->window->place.normalRect.size.x; - h = d->window->place.normalRect.size.y; - appendFormat_String(str, "window.setrect width:%d height:%d coord:%d %d\n", w, h, x, y); - /* On macOS, maximization should be applied at creation time or the window will take - a moment to animate to its maximized size. */ + iConstForEach(PtrArray, i, &d->mainWindows) { + const iMainWindow *win = i.ptr; + const size_t winIndex = index_PtrArrayConstIterator(&i); + x = win->place.normalRect.pos.x; + y = win->place.normalRect.pos.y; + w = win->place.normalRect.size.x; + h = win->place.normalRect.size.y; + appendFormat_String(str, + "window.setrect index:%zu width:%d height:%d coord:%d %d\n", + winIndex, + w, + h, + x, + y); + /* On macOS, maximization should be applied at creation time or the window will take + a moment to animate to its maximized size. */ #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) - if (snap_MainWindow(d->window)) { - if (snap_MainWindow(d->window) == maximized_WindowSnap) { - appendFormat_String(str, "~window.maximize\n"); - } - else if (~SDL_GetWindowFlags(d->window->base.win) & SDL_WINDOW_MINIMIZED) { - /* Save the actual visible window position, too, because snapped windows may - still be resized/moved without affecting normalRect. */ - SDL_GetWindowPosition(d->window->base.win, &x, &y); - SDL_GetWindowSize(d->window->base.win, &w, &h); - appendFormat_String( - str, "~window.setrect snap:%d width:%d height:%d coord:%d %d\n", - snap_MainWindow(d->window), w, h, x, y); + if (snap_MainWindow(win)) { + if (snap_MainWindow(win) == maximized_WindowSnap) { + appendFormat_String(str, "~window.maximize index:%zu\n", winIndex); + } + else if (~SDL_GetWindowFlags(win->base.win) & SDL_WINDOW_MINIMIZED) { + /* Save the actual visible window position, too, because snapped windows may + still be resized/moved without affecting normalRect. */ + SDL_GetWindowPosition(win->base.win, &x, &y); + SDL_GetWindowSize(win->base.win, &w, &h); + appendFormat_String( + str, "~window.setrect index:%zu snap:%d width:%d height:%d coord:%d %d\n", + winIndex, snap_MainWindow(d->window), w, h, x, y); + } } - } #elif !defined (iPlatformApple) - if (snap_MainWindow(d->window) == maximized_WindowSnap) { - appendFormat_String(str, "~window.maximize\n"); - } + if (snap_MainWindow(win) == maximized_WindowSnap) { + appendFormat_String(str, "~window.maximize index:%zu\n", winIndex); + } #endif + } } appendFormat_String(str, "uilang id:%s\n", cstr_String(&d->prefs.strings[uiLanguage_PrefsString])); appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(as_Window(d->window))); @@ -404,9 +415,14 @@ static void loadPrefs_App_(iApp *d) { d->prefs.customFrame = arg_Command(cmd); } else if (equal_Command(cmd, "window.setrect") && !argLabel_Command(cmd, "snap")) { - const iInt2 pos = coord_Command(cmd); - d->initialWindowRect = init_Rect( + const int index = argLabel_Command(cmd, "index"); + const iInt2 pos = coord_Command(cmd); + iRect winRect = init_Rect( pos.x, pos.y, argLabel_Command(cmd, "width"), argLabel_Command(cmd, "height")); + if (index >= 0 && index < 100) { + resize_Array(&d->initialWindowRects, index + 1); + set_Array(&d->initialWindowRects, index, &winRect); + } } else if (equal_Command(cmd, "fontpack.disable")) { insert_StringSet(d->prefs.disabledFontPacks, @@ -483,6 +499,28 @@ enum iWindowStateFlag { current_WindowStateFlag = iBit(9), }; +static iRect initialWindowRect_App_(const iApp *d, size_t windowIndex) { + if (windowIndex < size_Array(&d->initialWindowRects)) { + return constValue_Array(&d->initialWindowRects, windowIndex, iRect); + } + /* The default window rectangle. */ + iRect rect = init_Rect(-1, -1, 900, 560); +#if defined (iPlatformMsys) + /* Must scale by UI scaling factor. */ + mulfv_I2(&rect.size, desktopDPI_Win32()); +#endif +#if defined (iPlatformLinux) && !defined (iPlatformAndroid) + /* Scale by the primary (?) monitor DPI. */ + if (isRunningUnderWindowSystem_App()) { + float vdpi; + SDL_GetDisplayDPI(0, NULL, NULL, &vdpi); + const float factor = vdpi / 96.0f; + mulfv_I2(&rect.size, iMax(factor, 1.0f)); + } +#endif + return rect; +} + static iBool loadState_App_(iApp *d) { iUnused(d); const char *oldPath = concatPath_CStr(dataDir_App_(), oldStateFileName_App_); @@ -525,7 +563,7 @@ static iBool loadState_App_(iApp *d) { win = d->window; } else { - win = new_MainWindow(d->initialWindowRect); + win = new_MainWindow(initialWindowRect_App_(d, numWins - 1)); addWindow_App(win); } pushBack_Array(currentTabs, &(iCurrentTabs){ { NULL, NULL } }); @@ -819,6 +857,7 @@ static void init_App_(iApp *d, int argc, char **argv) { d->isDarkSystemTheme = iTrue; /* will be updated by system later on, if supported */ d->isSuspended = iFalse; d->tempFilesPendingDeletion = new_StringSet(); + init_Array(&d->initialWindowRects, sizeof(iRect)); init_CommandLine(&d->args, argc, argv); /* Where was the app started from? We ask SDL first because the command line alone cannot be relied on (behavior differs depending on OS). */ { @@ -953,20 +992,6 @@ static void init_App_(iApp *d, int argc, char **argv) { d->elapsedSinceLastTicker = 0; d->commandEcho = iClob(checkArgument_CommandLine(&d->args, "echo;E")) != NULL; d->forceSoftwareRender = iClob(checkArgument_CommandLine(&d->args, "sw")) != NULL; - d->initialWindowRect = init_Rect(-1, -1, 900, 560); -#if defined (iPlatformMsys) - /* Must scale by UI scaling factor. */ - mulfv_I2(&d->initialWindowRect.size, desktopDPI_Win32()); -#endif -#if defined (iPlatformLinux) && !defined (iPlatformAndroid) - /* Scale by the primary (?) monitor DPI. */ - if (isRunningUnderWindowSystem_App()) { - float vdpi; - SDL_GetDisplayDPI(0, NULL, NULL, &vdpi); - const float factor = vdpi / 96.0f; - mulfv_I2(&d->initialWindowRect.size, iMax(factor, 1.0f)); - } -#endif init_Prefs(&d->prefs); init_SiteSpec(dataDir_App_()); setCStr_String(&d->prefs.strings[downloadDir_PrefsString], downloadDir_App_()); @@ -988,22 +1013,29 @@ static void init_App_(iApp *d, int argc, char **argv) { init_Fonts(dataDir_App_()); loadPalette_Color(dataDir_App_()); setThemePalette_Color(d->prefs.theme); /* default UI colors */ - loadPrefs_App_(d); + /* Initial window rectangle of the first window. */ { + iAssert(isEmpty_Array(&d->initialWindowRects)); + const iRect winRect = initialWindowRect_App_(d, 0); /* calculated */ + resize_Array(&d->initialWindowRects, 1); + set_Array(&d->initialWindowRects, 0, &winRect); + } + loadPrefs_App_(d); updateActive_Fonts(); load_Keys(dataDir_App_()); + iRect *winRect0 = at_Array(&d->initialWindowRects, 0); /* See if the user wants to override the window size. */ { iCommandLineArg *arg = iClob(checkArgument_CommandLine(&d->args, windowWidth_CommandLineOption)); if (arg) { - d->initialWindowRect.size.x = toInt_String(value_CommandLineArg(arg, 0)); + winRect0->size.x = toInt_String(value_CommandLineArg(arg, 0)); } arg = iClob(checkArgument_CommandLine(&d->args, windowHeight_CommandLineOption)); if (arg) { - d->initialWindowRect.size.y = toInt_String(value_CommandLineArg(arg, 0)); + winRect0->size.y = toInt_String(value_CommandLineArg(arg, 0)); } } init_PtrArray(&d->mainWindows); init_PtrArray(&d->popupWindows); - d->window = new_MainWindow(d->initialWindowRect); + d->window = new_MainWindow(*winRect0); /* first window is always created */ addWindow_App(d->window); load_Visited(d->visited, dataDir_App_()); load_Bookmarks(d->bookmarks, dataDir_App_()); @@ -1101,7 +1133,8 @@ static void deinit_App(iApp *d) { /* Delete all temporary files created while running. */ iConstForEach(StringSet, tmp, d->tempFilesPendingDeletion) { remove(cstr_String(tmp.value)); -} + } + deinit_Array(&d->initialWindowRects); iRelease(d->tempFilesPendingDeletion); } @@ -3132,7 +3165,7 @@ iBool handleCommand_App(const char *cmd) { return iFalse; } else if (equal_Command(cmd, "window.new")) { - iMainWindow *newWin = new_MainWindow(moved_Rect(d->initialWindowRect, init_I2(30, 30))); + iMainWindow *newWin = new_MainWindow(initialWindowRect_App_(d, numWindows_App())); addWindow_App(newWin); /* takes ownership */ SDL_ShowWindow(newWin->base.win); setCurrent_Window(newWin); diff --git a/src/ui/root.c b/src/ui/root.c index eeb5956f..7e4b4863 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -468,6 +468,10 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { return iFalse; } else if (equal_Command(cmd, "window.setrect")) { + if (hasLabel_Command(cmd, "index") && + argU32Label_Command(cmd, "index") != windowIndex_Root(root->root)) { + return iFalse; + } const int snap = argLabel_Command(cmd, "snap"); if (snap) { iMainWindow *window = get_MainWindow(); @@ -1776,6 +1780,13 @@ void showToolbar_Root(iRoot *d, iBool show) { } } +size_t windowIndex_Root(const iRoot *d) { + if (type_Window(d->window) == main_WindowType) { + return windowIndex_App(as_MainWindow(d->window)); + } + return iInvalidPos; +} + iInt2 size_Root(const iRoot *d) { return d && d->widget ? d->widget->rect.size : zero_I2(); } diff --git a/src/ui/root.h b/src/ui/root.h index a81ebdf7..3b053c9e 100644 --- a/src/ui/root.h +++ b/src/ui/root.h @@ -47,6 +47,7 @@ void showOrHideNewTabButton_Root (iRoot *); void notifyVisualOffsetChange_Root (iRoot *); +size_t windowIndex_Root (const iRoot *); iInt2 size_Root (const iRoot *); iRect rect_Root (const iRoot *); iRect safeRect_Root (const iRoot *); -- cgit v1.2.3 From 44214359c0f95dcce3a1390506e9fd74202d5ae7 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sat, 19 Feb 2022 15:16:51 +0200 Subject: Fixed several event handling issues Events are expected to have a valid windowID now, and the current window also has to be correct when dispatching or processing any events. --- src/app.c | 38 ++++++++++++++++++++++++++---------- src/app.h | 2 ++ src/macos.m | 51 +++++++++++++++++-------------------------------- src/periodic.c | 19 ++++++++++-------- src/ui/documentwidget.c | 2 +- src/ui/touch.c | 3 ++- src/ui/widget.c | 3 ++- src/ui/window.c | 10 +++++----- src/ui/window.h | 2 +- 9 files changed, 69 insertions(+), 61 deletions(-) diff --git a/src/app.c b/src/app.c index ec8b59b3..010c6d74 100644 --- a/src/app.c +++ b/src/app.c @@ -559,6 +559,7 @@ static iBool loadState_App_(iApp *d) { const int winState = read32_File(f); const int keyRoot = (winState & 1); const iBool isCurrent = (winState & current_WindowStateFlag) != 0; +// printf("[State] '%.4s' split:%d state:%x\n", magic, splitMode, winState); if (numWins == 1) { win = d->window; } @@ -575,7 +576,7 @@ static iBool loadState_App_(iApp *d) { setCurrent_Root(NULL); win->pendingSplitMode = splitMode; setSplitMode_MainWindow(win, splitMode | noEvents_WindowSplit); - win->base.keyRoot = d->window->base.roots[keyRoot]; + win->base.keyRoot = win->base.roots[keyRoot]; } else if (!memcmp(magic, magicSidebar_App_, 4)) { if (!win) { @@ -1061,8 +1062,8 @@ static void init_App_(iApp *d, int argc, char **argv) { } postCommand_App("~navbar.actions.changed"); postCommand_App("~toolbar.actions.changed"); - postCommand_Root(NULL, "~window.unfreeze"); - postCommand_Root(NULL, "font.reset"); + postCommand_App("~window.unfreeze"); + postCommand_App("font.reset"); d->autoReloadTimer = SDL_AddTimer(60 * 1000, postAutoReloadCommand_App_, NULL); postCommand_Root(NULL, "document.autoreload"); #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) @@ -1390,6 +1391,12 @@ static iPtrArray *listWindows_App_(const iApp *d, iPtrArray *windows) { return windows; } +iPtrArray *listWindows_App(void) { + iPtrArray *wins = new_PtrArray(); + listWindows_App_(&app_, wins); + return wins; +} + void processEvents_App(enum iAppEventMode eventMode) { iApp *d = &app_; iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */ @@ -1672,6 +1679,9 @@ static void runTickers_App_(iApp *d) { iConstForEach(Array, i, &pending->values) { const iTicker *ticker = i.value; if (ticker->callback) { + if (ticker->root) { + setCurrent_Window(ticker->root->window); + } setCurrent_Root(ticker->root); /* root might be NULL */ ticker->callback(ticker->context); } @@ -1864,9 +1874,9 @@ void postCommand_Root(iRoot *d, const char *command) { } SDL_Event ev = { .type = SDL_USEREVENT }; ev.user.code = command_UserEventCode; -// ev.user.windowID = id_Window(get_Window()); ev.user.data1 = strdup(command); ev.user.data2 = d; /* all events are root-specific */ + ev.user.windowID = d ? id_Window(d->window) : 0; /* root-specific means window-specific */ SDL_PushEvent(&ev); iWindow *win = get_Window(); #if defined (iPlatformAndroid) @@ -1969,6 +1979,10 @@ size_t windowIndex_App(const iMainWindow *win) { return indexOf_PtrArray(&app_.mainWindows, win); } +const iPtrArray *mainWindows_App(void) { + return &app_.mainWindows; +} + void setActiveWindow_App(iMainWindow *win) { iApp *d = &app_; d->window = win; @@ -2527,12 +2541,16 @@ iBool handleCommand_App(const char *cmd) { return iTrue; } else if (equal_Command(cmd, "window.maximize")) { - if (!argLabel_Command(cmd, "toggle")) { - setSnap_MainWindow(d->window, maximized_WindowSnap); - } - else { - setSnap_MainWindow(d->window, snap_MainWindow(d->window) == maximized_WindowSnap ? 0 : - maximized_WindowSnap); + const size_t winIndex = argU32Label_Command(cmd, "index"); + if (winIndex < size_PtrArray(&d->mainWindows)) { + iMainWindow *win = at_PtrArray(&d->mainWindows, winIndex); + if (!argLabel_Command(cmd, "toggle")) { + setSnap_MainWindow(win, maximized_WindowSnap); + } + else { + setSnap_MainWindow( + win, snap_MainWindow(win) == maximized_WindowSnap ? 0 : maximized_WindowSnap); + } } return iTrue; } diff --git a/src/app.h b/src/app.h index dd24ec8e..63a477a5 100644 --- a/src/app.h +++ b/src/app.h @@ -98,6 +98,7 @@ iPeriodic * periodic_App (void); iDocumentWidget * document_App (void); iObjectList * listDocuments_App (const iRoot *rootOrNull); /* NULL for all roots */ iStringSet * listOpenURLs_App (void); /* all tabs */ +iPtrArray * listWindows_App (void); iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew); void trimCache_App (void); void trimMemory_App (void); @@ -128,6 +129,7 @@ void setActiveWindow_App (iMainWindow *win); void closeWindow_App (iMainWindow *win); size_t numWindows_App (void); size_t windowIndex_App (const iMainWindow *win); +const iPtrArray *mainWindows_App(void); void addPopup_App (iWindow *popup); void removePopup_App (iWindow *popup); void postRefresh_App (void); diff --git a/src/macos.m b/src/macos.m index 7b248c3b..5f376874 100644 --- a/src/macos.m +++ b/src/macos.m @@ -83,7 +83,9 @@ static void ignoreImmediateKeyDownEvents_(void) { However, we shouldn't double-activate menu items when a shortcut key is used in our widgets. Quite a kludge: take advantage of Window's focus-acquisition threshold to ignore the immediately following key down events. */ - get_Window()->focusGainedAt = SDL_GetTicks(); + iForEach(PtrArray, w, collect_PtrArray(listWindows_App())) { + as_Window(w.ptr)->focusGainedAt = SDL_GetTicks(); + } } /*----------------------------------------------------------------------------------------------*/ @@ -435,9 +437,17 @@ static iBool processScrollWheelEvent_(NSEvent *event) { const iBool isPerPixel = (event.hasPreciseScrollingDeltas != 0); const iBool isInertia = (event.momentumPhase & (NSEventPhaseBegan | NSEventPhaseChanged)) != 0; const iBool isEnded = event.scrollingDeltaX == 0.0f && event.scrollingDeltaY == 0.0f && !isInertia; - const iWindow *win = &get_MainWindow()->base; - if (event.window != nsWindow_(win->win)) { - /* Not the main window. */ + const iWindow *win = NULL; //&get_MainWindow()->base; + /* If this event belongs to one of the MainWindows, handle it and mark it for that window. + If it's for an auxiliary window, let the system handle it. */ + iConstForEach(PtrArray, i, mainWindows_App()) { + if (event.window == nsWindow_(as_Window(i.ptr)->win)) { + win = i.ptr; + break; + } + } + if (!win) { //event.window != nsWindow_(win->win)) { + /* Not a main window. */ return iFalse; } if (isPerPixel) { @@ -478,16 +488,18 @@ static iBool processScrollWheelEvent_(NSEvent *event) { else { SDL_MouseWheelEvent e = { .type = SDL_MOUSEWHEEL }; e.timestamp = SDL_GetTicks(); + e.windowID = id_Window(win); e.which = 1; /* Distinction between trackpad and regular mouse. */ /* Disregard any wheel acceleration. */ e.x = event.scrollingDeltaX > 0 ? 1 : event.scrollingDeltaX < 0 ? -1 : 0; - e.y = event.scrollingDeltaY > 0 ? 1 : event.scrollingDeltaY < 0 ? -1 : 0; + e.y = event.scrollingDeltaY > 0 ? 1 : event.scrollingDeltaY < 0 ? -1 : 0; SDL_PushEvent((SDL_Event *) &e); return iTrue; } /* Post corresponding MOUSEWHEEL events. */ SDL_MouseWheelEvent e = { .type = SDL_MOUSEWHEEL }; e.timestamp = SDL_GetTicks(); + e.windowID = id_Window(win); e.which = isPerPixel ? 0 : 1; /* Distinction between trackpad and regular mouse. */ setPerPixel_MouseWheelEvent(&e, isPerPixel); if (isPerPixel) { @@ -517,28 +529,6 @@ static iBool processScrollWheelEvent_(NSEvent *event) { // printf("#### [%d] dx:%d dy:%d phase:%ld inertia:%d end:%d\n", preventTapGlitch_, e.x, e.y, (long) event.momentumPhase, // isInertia, isEnded); fflush(stdout); SDL_PushEvent((SDL_Event *) &e); -#if 0 - /* On macOS, we handle both trackpad and mouse events. We expect SDL to identify - which device is sending the event. */ - if (ev.wheel.which == 0) { - /* Trackpad with precise scrolling w/inertia (points). */ - setPerPixel_MouseWheelEvent(&ev.wheel, iTrue); - ev.wheel.x *= -d->window->base.pixelRatio; - ev.wheel.y *= d->window->base.pixelRatio; - /* Only scroll on one axis at a time. */ - if (iAbs(ev.wheel.x) > iAbs(ev.wheel.y)) { - ev.wheel.y = 0; - } - else { - ev.wheel.x = 0; - } - } - else { - /* Disregard wheel acceleration applied by the OS. */ - ev.wheel.x = -ev.wheel.x; - ev.wheel.y = iSign(ev.wheel.y); - } -#endif return iTrue; } @@ -565,13 +555,6 @@ void setupApplication_MacOS(void) { windowCloseItem.action = @selector(closeTab); [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskScrollWheel handler:^NSEvent*(NSEvent *event){ -// printf("event type: %lu\n", (unsigned long) event.type); -// fflush(stdout); -// if (event.type == NSEventTypeGesture) { -// trackSwipe_(event); -// printf("GESTURE phase:%lu\n", (unsigned long) event.phase); -//fflush(stdout); -// } if (event.type == NSEventTypeScrollWheel && processScrollWheelEvent_(event)) { return nil; /* was eaten */ diff --git a/src/periodic.c b/src/periodic.c index b4f51ed3..0558ed50 100644 --- a/src/periodic.c +++ b/src/periodic.c @@ -107,14 +107,17 @@ iBool dispatchCommands_Periodic(iPeriodic *d) { iConstForEach(Array, i, &d->commands.values) { const iPeriodicCommand *pc = i.value; iAssert(isInstance_Object(pc->context, &Class_Widget)); - const SDL_UserEvent ev = { - .type = SDL_USEREVENT, - .code = command_UserEventCode, - .data1 = (void *) cstr_String(&pc->command), - .data2 = findRoot_Window(get_Window(), pc->context) - }; - if (ev.data2) { - setCurrent_Root(ev.data2); + iRoot *root = constAs_Widget(pc->context)->root; + if (root) { + const SDL_UserEvent ev = { + .type = SDL_USEREVENT, + .code = command_UserEventCode, + .data1 = (void *) cstr_String(&pc->command), + .data2 = root, + .windowID = id_Window(root->window), + }; + setCurrent_Window(root->window); + setCurrent_Root(root); dispatchEvent_Widget(pc->context, (const SDL_Event *) &ev); wasPosted = iTrue; } diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index f8cc10b9..76c26e27 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -5582,7 +5582,7 @@ static void prerender_DocumentWidget_(iAny *context) { .vis = visibleRange_DocumentView_(&d->view), .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0 }; - // printf("%u prerendering\n", SDL_GetTicks()); + // printf("%u prerendering\n", SDL_GetTicks()); if (d->view.visBuf->buffers[0].texture) { makePaletteGlobal_GmDocument(d->view.doc); if (render_DocumentView_(&d->view, &ctx, iTrue /* just fill up progressively */)) { diff --git a/src/ui/touch.c b/src/ui/touch.c index a178a913..21a92b80 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c @@ -244,7 +244,8 @@ static void dispatchNotification_Touch_(const iTouch *d, int code) { .timestamp = SDL_GetTicks(), .code = code, .data1 = d->affinity, - .data2 = d->affinity->root + .data2 = d->affinity->root, + .windowID = id_Window(window_Widget(d->affinity)), }); setCurrent_Root(oldRoot); } diff --git a/src/ui/widget.c b/src/ui/widget.c index fc754b7a..2e878878 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -168,7 +168,8 @@ void deinit_Widget(iWidget *d) { if (d->flags & visualOffset_WidgetFlag) { removeTicker_App(visualOffsetAnimation_Widget_, d); } - iWindow *win = get_Window(); + iWindow *win = d->root->window; + iAssert(win); if (win->lastHover == d) { win->lastHover = NULL; } diff --git a/src/ui/window.c b/src/ui/window.c index 0a97b97c..6f680cd4 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -682,6 +682,7 @@ iBool isFullscreen_MainWindow(const iMainWindow *d) { } iRoot *findRoot_Window(const iWindow *d, const iWidget *widget) { + while (widget->parent) { widget = widget->parent; } @@ -1023,7 +1024,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { } } case SDL_RENDER_TARGETS_RESET: - case SDL_RENDER_DEVICE_RESET: { + case SDL_RENDER_DEVICE_RESET: { if (mw) { invalidate_MainWindow_(mw, iTrue /* force full reset */); } @@ -1109,7 +1110,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) { if (mouseGrab_Widget()) { iWidget *grabbed = mouseGrab_Widget(); - setCurrent_Root(findRoot_Window(d, grabbed)); + setCurrent_Root(grabbed->root /* findRoot_Window(d, grabbed)*/); wasUsed = dispatchEvent_Widget(grabbed, &event); } } @@ -1200,13 +1201,11 @@ static uint32_t windowId_SDLEvent_(const SDL_Event *ev) { } iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) { -#if 0 /* For the right window? */ const uint32_t evWin = windowId_SDLEvent_(ev); if (evWin && evWin != id_Window(d)) { return iFalse; /* Meant for a different window. */ } -#endif if (ev->type == SDL_MOUSEMOTION) { /* Hover widget may change. */ setHover_Widget(NULL); @@ -1591,6 +1590,7 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) { } iWindow *w = as_Window(d); iAssert(current_Root() == NULL); + setCurrent_Window(w); if (d->splitMode != splitMode) { int oldCount = numRoots_Window(w); setFreezeDraw_MainWindow(d, iTrue); @@ -1619,8 +1619,8 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) { /* The last child is the [+] button for adding a tab. */ moveTabButtonToEnd_Widget(findChild_Widget(docTabs, "newtab")); setFlags_Widget(findWidget_Root("navbar.unsplit"), hidden_WidgetFlag, iTrue); - iRelease(tabs); postCommandf_App("tabs.switch id:%s", cstr_String(id_Widget(constAs_Widget(curPage)))); + iRelease(tabs); } else if (oldCount == 1 && splitMode) { /* Add a second root. */ diff --git a/src/ui/window.h b/src/ui/window.h index 5abf23eb..c7d59380 100644 --- a/src/ui/window.h +++ b/src/ui/window.h @@ -139,7 +139,7 @@ iAnyObject * hitChild_Window (const iWindow *, iInt2 coord); uint32_t frameTime_Window (const iWindow *); SDL_Renderer * renderer_Window (const iWindow *); int numRoots_Window (const iWindow *); -iRoot * findRoot_Window (const iWindow *, const iWidget *widget); +//iRoot * findRoot_Window (const iWindow *, const iWidget *widget); iRoot * otherRoot_Window (const iWindow *, iRoot *root); iBool processEvent_Window (iWindow *, const SDL_Event *); -- cgit v1.2.3 From ed14d3467d6046ddfa66c3143a340428aeef66ae Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 20 Feb 2022 09:33:20 +0200 Subject: Adding context items to open in new window --- po/en.po | 12 ++++++--- src/app.c | 45 +++++++++++++++++++------------- src/app.h | 1 + src/defs.h | 5 ++-- src/gmutil.c | 11 +++++--- src/gmutil.h | 2 +- src/ui/documentwidget.c | 68 +++++++++++++++++++++++++++---------------------- src/ui/lookupwidget.c | 2 +- src/ui/root.c | 3 ++- src/ui/sidebarwidget.c | 28 ++++++++++++++------ src/ui/window.c | 17 +++++++++++++ src/ui/window.h | 1 + 12 files changed, 128 insertions(+), 67 deletions(-) diff --git a/po/en.po b/po/en.po index bff4c495..6647324e 100644 --- a/po/en.po +++ b/po/en.po @@ -148,6 +148,9 @@ msgstr "Identity" msgid "menu.title.help" msgstr "Help" +msgid "menu.newwindow" +msgstr "New Window" + msgid "menu.newtab" msgstr "New Tab" @@ -519,9 +522,6 @@ msgstr "%b. %d, %Y" msgid "feeds.today" msgstr "Today" -msgid "feeds.entry.newtab" -msgstr "Open Entry in New Tab" - msgid "feeds.entry.markread" msgstr "Mark as Read" @@ -558,6 +558,9 @@ msgstr "Open in New Tab" msgid "menu.opentab.background" msgstr "Open in Background Tab" +msgid "menu.openwindow" +msgstr "Open in New Window" + msgid "menu.openfile" msgstr "Open File…" @@ -870,6 +873,9 @@ msgstr "Open Link to the Side" msgid "link.side.newtab" msgstr "Open Link in New Tab to the Side" +msgid "link.newwindow" +msgstr "Open Link in New Window" + msgid "link.browser" msgstr "Open Link in Default Browser" diff --git a/src/app.c b/src/app.c index 010c6d74..543467ef 100644 --- a/src/app.c +++ b/src/app.c @@ -1878,7 +1878,7 @@ void postCommand_Root(iRoot *d, const char *command) { ev.user.data2 = d; /* all events are root-specific */ ev.user.windowID = d ? id_Window(d->window) : 0; /* root-specific means window-specific */ SDL_PushEvent(&ev); - iWindow *win = get_Window(); + iWindow *win = d ? d->window : NULL; #if defined (iPlatformAndroid) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s[command] {%d} %s", app_.isLoadingPrefs ? "[Prefs] " : "", @@ -1979,6 +1979,13 @@ size_t windowIndex_App(const iMainWindow *win) { return indexOf_PtrArray(&app_.mainWindows, win); } +iMainWindow *newMainWindow_App(void) { + iApp *d = &app_; + iMainWindow *win = new_MainWindow(initialWindowRect_App_(d, size_PtrArray(&d->mainWindows))); + addWindow_App(win); + return win; +} + const iPtrArray *mainWindows_App(void) { return &app_.mainWindows; } @@ -3038,7 +3045,18 @@ iBool handleCommand_App(const char *cmd) { return iTrue; /* invalid command */ } if (findWidget_App("prefs")) { - postCommand_App("prefs.dismiss"); + postCommand_App("prefs.dismiss"); + } + if (argLabel_Command(cmd, "newwindow")) { + const iRangecc gotoheading = range_Command(cmd, "gotoheading"); + const iRangecc gotourlheading = range_Command(cmd, "gotourlheading"); + postCommandf_Root(get_Root(), "window.new%s%s%s%s url:%s", + isEmpty_Range(&gotoheading) ? "" : " gotoheading:", + isEmpty_Range(&gotoheading) ? "" : cstr_Rangecc(gotoheading), + isEmpty_Range(&gotourlheading) ? "" : " gotourlheading:", + isEmpty_Range(&gotourlheading) ? "" : cstr_Rangecc(gotourlheading), + urlArg); + return iTrue; } iString *url = collectNewCStr_String(urlArg); const iBool noProxy = argLabel_Command(cmd, "noproxy") != 0; @@ -3187,7 +3205,12 @@ iBool handleCommand_App(const char *cmd) { addWindow_App(newWin); /* takes ownership */ SDL_ShowWindow(newWin->base.win); setCurrent_Window(newWin); - postCommand_Root(newWin->base.roots[0], "~navigate.home"); + if (hasLabel_Command(cmd, "url")) { + postCommandf_Root(newWin->base.roots[0], "~open %s", cmd + 11 /* all arguments passed on */); + } + else { + postCommand_Root(newWin->base.roots[0], "~navigate.home"); + } postCommand_Root(newWin->base.roots[0], "~window.unfreeze"); return iTrue; } @@ -3745,21 +3768,7 @@ void revealPath_App(const iString *path) { } iObjectList *listDocuments_App(const iRoot *rootOrNull) { - iWindow *win = get_Window(); - iObjectList *docs = new_ObjectList(); - iForIndices(i, win->roots) { - iRoot *root = win->roots[i]; - if (!root) continue; - if (!rootOrNull || root == rootOrNull) { - const iWidget *tabs = findChild_Widget(root->widget, "doctabs"); - iForEach(ObjectList, i, children_Widget(findChild_Widget(tabs, "tabs.pages"))) { - if (isInstance_Object(i.object, &Class_DocumentWidget)) { - pushBack_ObjectList(docs, i.object); - } - } - } - } - return docs; + return listDocuments_MainWindow(get_MainWindow(), rootOrNull); } iStringSet *listOpenURLs_App(void) { diff --git a/src/app.h b/src/app.h index 63a477a5..5f7b506f 100644 --- a/src/app.h +++ b/src/app.h @@ -129,6 +129,7 @@ void setActiveWindow_App (iMainWindow *win); void closeWindow_App (iMainWindow *win); size_t numWindows_App (void); size_t windowIndex_App (const iMainWindow *win); +iMainWindow *newMainWindow_App (void); const iPtrArray *mainWindows_App(void); void addPopup_App (iWindow *popup); void removePopup_App (iWindow *popup); diff --git a/src/defs.h b/src/defs.h index be5280fa..c3480bc2 100644 --- a/src/defs.h +++ b/src/defs.h @@ -157,8 +157,9 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { #define bookmark_Icon "\U0001f516" #define folder_Icon "\U0001f4c1" #define file_Icon "\U0001f5ce" -#define openTab_Icon "\u2750" -#define openTabBg_Icon "\u2b1a" +#define openWindow_Icon "\u2b1a" //"\U0001F5d4" +#define openTab_Icon add_Icon +#define openTabBg_Icon "\u2750" //"\u2b1a" #define openExt_Icon "\u27a0" #define add_Icon "\u2795" #define page_Icon "\U00010117" diff --git a/src/gmutil.c b/src/gmutil.c index e862b18a..b32722ac 100644 --- a/src/gmutil.c +++ b/src/gmutil.c @@ -780,7 +780,7 @@ iRangecc mediaTypeWithoutParameters_Rangecc(iRangecc mime) { return part; } -const iString *feedEntryOpenCommand_String(const iString *url, int newTab) { +const iString *feedEntryOpenCommand_String(const iString *url, int newTab, int newWindow) { if (!isEmpty_String(url)) { iString *cmd = collectNew_String(); const size_t fragPos = indexOf_String(url, '#'); @@ -788,15 +788,20 @@ const iString *feedEntryOpenCommand_String(const iString *url, int newTab) { iString *head = newRange_String( (iRangecc){ constBegin_String(url) + fragPos + 1, constEnd_String(url) }); format_String(cmd, - "open fromsidebar:1 newtab:%d gotourlheading:%s url:%s", + "open fromsidebar:1 newtab:%d newwindow:%d gotourlheading:%s url:%s", newTab, + newWindow, cstr_String(head), cstr_Rangecc((iRangecc){ constBegin_String(url), constBegin_String(url) + fragPos })); delete_String(head); } else { - format_String(cmd, "open fromsidebar:1 newtab:%d url:%s", newTab, cstr_String(url)); + format_String(cmd, + "open fromsidebar:1 newtab:%d newwindow:%d url:%s", + newTab, + newWindow, + cstr_String(url)); } return cmd; } diff --git a/src/gmutil.h b/src/gmutil.h index 01eb8e52..e4284cfd 100644 --- a/src/gmutil.h +++ b/src/gmutil.h @@ -151,4 +151,4 @@ iRangecc mediaTypeWithoutParameters_Rangecc (iRangecc mime); const iString * findContainerArchive_Path (const iString *path); -const iString * feedEntryOpenCommand_String (const iString *url, int newTab); /* checks fragment */ +const iString * feedEntryOpenCommand_String (const iString *url, int newTab, int newWindow); /* checks fragment */ diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 76c26e27..99039ff5 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -5071,10 +5071,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e iArray items; init_Array(&items, sizeof(iMenuItem)); if (d->contextLink) { - /* Context menu for a link. */ + /* Construct the link context menu, depending on what kind of link was clicked. */ interactingWithLink_DocumentWidget_(d, d->contextLink->linkId); /* perhaps will be triggered */ const iString *linkUrl = linkUrl_GmDocument(view->doc, d->contextLink->linkId); -// const int linkFlags = linkFlags_GmDocument(d->doc, d->contextLink->linkId); const iRangecc scheme = urlScheme_String(linkUrl); const iBool isGemini = equalCase_Rangecc(scheme, "gemini"); iBool isNative = iFalse; @@ -5086,39 +5085,48 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e format_CStr("```%s", cstr_String(infoText)), 0, 0, NULL }); } - if (willUseProxy_App(scheme) || isGemini || + if (isGemini || + willUseProxy_App(scheme) || + equalCase_Rangecc(scheme, "data") || equalCase_Rangecc(scheme, "file") || equalCase_Rangecc(scheme, "finger") || equalCase_Rangecc(scheme, "gopher")) { isNative = iTrue; /* Regular links that we can open. */ - pushBackN_Array( - &items, - (iMenuItem[]){ { openTab_Icon " ${link.newtab}", - 0, - 0, - format_CStr("!open newtab:1 origin:%s url:%s", - cstr_String(id_Widget(w)), - cstr_String(linkUrl)) }, - { openTabBg_Icon " ${link.newtab.background}", - 0, - 0, - format_CStr("!open newtab:2 origin:%s url:%s", - cstr_String(id_Widget(w)), - cstr_String(linkUrl)) }, - { "${link.side}", - 0, - 0, - format_CStr("!open newtab:4 origin:%s url:%s", - cstr_String(id_Widget(w)), - cstr_String(linkUrl)) }, - { "${link.side.newtab}", - 0, - 0, - format_CStr("!open newtab:5 origin:%s url:%s", - cstr_String(id_Widget(w)), - cstr_String(linkUrl)) } }, - 4); + pushBackN_Array(&items, + (iMenuItem[]){ + { openTab_Icon " ${link.newtab}", + 0, + 0, + format_CStr("!open newtab:1 origin:%s url:%s", + cstr_String(id_Widget(w)), + cstr_String(linkUrl)) }, + { openTabBg_Icon " ${link.newtab.background}", + 0, + 0, + format_CStr("!open newtab:2 origin:%s url:%s", + cstr_String(id_Widget(w)), + cstr_String(linkUrl)) }, + { "${link.side}", + 0, + 0, + format_CStr("!open newtab:4 origin:%s url:%s", + cstr_String(id_Widget(w)), + cstr_String(linkUrl)) }, + { "${link.side.newtab}", + 0, + 0, + format_CStr("!open newtab:5 origin:%s url:%s", + cstr_String(id_Widget(w)), + cstr_String(linkUrl)) }, + { openWindow_Icon " ${link.newwindow}", + 0, + 0, + format_CStr("!open newwindow:1 origin:%s url:%s", + cstr_String(id_Widget(w)), + cstr_String(linkUrl)) }, + }, + 5); if (deviceType_App() == phone_AppDeviceType) { removeN_Array(&items, size_Array(&items) - 2, iInvalidSize); } diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c index f14170ad..dc3264a2 100644 --- a/src/ui/lookupwidget.c +++ b/src/ui/lookupwidget.c @@ -568,7 +568,7 @@ static void presentResults_LookupWidget_(iLookupWidget *d) { cstr_String(&res->label), uiText_ColorEscape, cstr_String(&res->meta)); - const iString *cmd = feedEntryOpenCommand_String(&res->url, 0); + const iString *cmd = feedEntryOpenCommand_String(&res->url, 0, 0); if (cmd) { set_String(&item->command, cmd); } diff --git a/src/ui/root.c b/src/ui/root.c index 7e4b4863..9dee50ae 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -56,7 +56,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #if defined (iPlatformPcDesktop) /* TODO: Submenus wouldn't hurt here. */ static const iMenuItem navMenuItems_[] = { - { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, + { openWindow_Icon " ${menu.newwindow}", SDLK_n, KMOD_PRIMARY, "window.new" }, + { add_Icon " ${menu.newtab}", SDLK_t, KMOD_PRIMARY, "tabs.new" }, { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, { "---" }, { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" }, diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 73023a4f..0322b2a9 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c @@ -414,9 +414,13 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct } d->menu = makeMenu_Widget( as_Widget(d), - (iMenuItem[]){ { openTab_Icon " ${feeds.entry.newtab}", 0, 0, "feed.entry.opentab" }, + (iMenuItem[]){ { openTab_Icon " ${menu.opentab}", 0, 0, "feed.entry.open newtab:1" }, + { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "feed.entry.open newtab:2" }, + { openWindow_Icon " ${menu.openwindow}", 0, 0, "feed.entry.open newwindow:1" }, + { "---", 0, 0, NULL }, { circle_Icon " ${feeds.entry.markread}", 0, 0, "feed.entry.toggleread" }, { bookmark_Icon " ${feeds.entry.bookmark}", 0, 0, "feed.entry.bookmark" }, + { "${menu.copyurl}", 0, 0, "feed.entry.copyurl" }, { "---", 0, 0, NULL }, { page_Icon " ${feeds.entry.openfeed}", 0, 0, "feed.entry.openfeed" }, { edit_Icon " ${feeds.edit}", 0, 0, "feed.entry.edit" }, @@ -424,7 +428,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct { "---", 0, 0, NULL }, { check_Icon " ${feeds.markallread}", SDLK_a, KMOD_SHIFT, "feeds.markallread" }, { reload_Icon " ${feeds.refresh}", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" } }, - 10); + 13); d->modeMenu = makeMenu_Widget( as_Widget(d), (iMenuItem[]){ @@ -491,6 +495,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct as_Widget(d), (iMenuItem[]){ { openTab_Icon " ${menu.opentab}", 0, 0, "bookmark.open newtab:1" }, { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "bookmark.open newtab:2" }, + { openWindow_Icon " ${menu.openwindow}", 0, 0, "bookmark.open newwindow:1" }, { "---", 0, 0, NULL }, { edit_Icon " ${menu.edit}", 0, 0, "bookmark.edit" }, { copy_Icon " ${menu.dup}", 0, 0, "bookmark.dup" }, @@ -502,11 +507,11 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct { "---", 0, 0, NULL }, { delete_Icon " " uiTextCaution_ColorEscape "${bookmark.delete}", 0, 0, "bookmark.delete" }, { "---", 0, 0, NULL }, - { add_Icon " ${menu.newfolder}", 0, 0, "bookmark.addfolder" }, + { folder_Icon " ${menu.newfolder}", 0, 0, "bookmark.addfolder" }, { upDownArrow_Icon " ${menu.sort.alpha}", 0, 0, "bookmark.sortfolder" }, { "---", 0, 0, NULL }, { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" } }, - 17); + 18); d->modeMenu = makeMenu_Widget( as_Widget(d), (iMenuItem[]){ { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, @@ -571,13 +576,17 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct d->menu = makeMenu_Widget( as_Widget(d), (iMenuItem[]){ + { openTab_Icon " ${menu.opentab}", 0, 0, "history.open newtab:1" }, + { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "history.open newtab:2" }, + { openWindow_Icon " ${menu.openwindow}", 0, 0, "history.open newwindow:1" }, + { "---" }, { "${menu.copyurl}", 0, 0, "history.copy" }, { bookmark_Icon " ${sidebar.entry.bookmark}", 0, 0, "history.addbookmark" }, { "---", 0, 0, NULL }, { close_Icon " ${menu.forgeturl}", 0, 0, "history.delete" }, { "---", 0, 0, NULL }, { delete_Icon " " uiTextCaution_ColorEscape "${history.clear}", 0, 0, "history.clear confirm:1" }, - }, 6); + }, 10); d->modeMenu = makeMenu_Widget( as_Widget(d), (iMenuItem[]){ @@ -981,7 +990,7 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si } case feeds_SidebarMode: { postCommandString_Root(get_Root(), - feedEntryOpenCommand_String(&item->url, openTabMode_Sym(modState_Keys()))); + feedEntryOpenCommand_String(&item->url, openTabMode_Sym(modState_Keys()), 0)); break; } case bookmarks_SidebarMode: @@ -1641,8 +1650,11 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) else if (startsWith_CStr(cmd, "feed.entry.") && d->mode == feeds_SidebarMode) { const iSidebarItem *item = d->contextItem; if (item) { - if (isCommand_Widget(w, ev, "feed.entry.opentab")) { - postCommandString_Root(get_Root(), feedEntryOpenCommand_String(&item->url, 1)); + if (isCommand_Widget(w, ev, "feed.entry.open")) { + const char *cmd = command_UserEvent(ev); + postCommandString_Root(get_Root(), feedEntryOpenCommand_String(&item->url, + argLabel_Command(cmd, "newtab"), + argLabel_Command(cmd, "newwindow"))); return iTrue; } if (isCommand_Widget(w, ev, "feed.entry.toggleread")) { diff --git a/src/ui/window.c b/src/ui/window.c index 6f680cd4..b0de0557 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -1567,6 +1567,23 @@ void setKeyboardHeight_MainWindow(iMainWindow *d, int height) { } } +iObjectList *listDocuments_MainWindow(iMainWindow *d, const iRoot *rootOrNull) { + iObjectList *docs = new_ObjectList(); + iForIndices(i, d->base.roots) { + iRoot *root = d->base.roots[i]; + if (!root) continue; + if (!rootOrNull || root == rootOrNull) { + const iWidget *tabs = findChild_Widget(root->widget, "doctabs"); + iForEach(ObjectList, i, children_Widget(findChild_Widget(tabs, "tabs.pages"))) { + if (isInstance_Object(i.object, &Class_DocumentWidget)) { + pushBack_ObjectList(docs, i.object); + } + } + } + } + return docs; +} + void checkPendingSplit_MainWindow(iMainWindow *d) { if (d->splitMode != d->pendingSplitMode) { setSplitMode_MainWindow(d, d->pendingSplitMode); diff --git a/src/ui/window.h b/src/ui/window.h index c7d59380..c3c34e1b 100644 --- a/src/ui/window.h +++ b/src/ui/window.h @@ -187,6 +187,7 @@ void setTitle_MainWindow (iMainWindow *, const iString *title void setSnap_MainWindow (iMainWindow *, int snapMode); void setFreezeDraw_MainWindow (iMainWindow *, iBool freezeDraw); void setKeyboardHeight_MainWindow (iMainWindow *, int height); +iObjectList *listDocuments_MainWindow (iMainWindow *, const iRoot *rootOrNull); void setSplitMode_MainWindow (iMainWindow *, int splitMode); void checkPendingSplit_MainWindow (iMainWindow *); void swapRoots_MainWindow (iMainWindow *); -- cgit v1.2.3 From a8fd69b995058252c025f2ca5c11031373e5d63b Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 20 Feb 2022 11:42:21 +0200 Subject: Fixed icon of feeds filter mode dropdown --- src/ui/util.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ui/util.c b/src/ui/util.c index d6dc461a..4f5de7f9 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -1386,6 +1386,9 @@ void updateDropdownSelection_LabelWidget(iLabelWidget *dropButton, const char *s updateText_LabelWidget(dropButton, replaceNewlinesWithDash_(text_LabelWidget(item))); checkIcon_LabelWidget(dropButton); + if (!icon_LabelWidget(dropButton)) { + setIcon_LabelWidget(dropButton, icon_LabelWidget(item)); + } } } } -- cgit v1.2.3 From 09c1f3d7c06b1852ab973e16e2c169a6e541082e Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 20 Feb 2022 11:42:36 +0200 Subject: Context menu items for opening items --- src/ui/documentwidget.c | 12 ++++++------ src/ui/sidebarwidget.c | 32 +++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 99039ff5..7492d9bd 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -5107,22 +5107,22 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e format_CStr("!open newtab:2 origin:%s url:%s", cstr_String(id_Widget(w)), cstr_String(linkUrl)) }, - { "${link.side}", + { openWindow_Icon " ${link.newwindow}", 0, 0, - format_CStr("!open newtab:4 origin:%s url:%s", + format_CStr("!open newwindow:1 origin:%s url:%s", cstr_String(id_Widget(w)), cstr_String(linkUrl)) }, - { "${link.side.newtab}", + { "${link.side}", 0, 0, - format_CStr("!open newtab:5 origin:%s url:%s", + format_CStr("!open newtab:4 origin:%s url:%s", cstr_String(id_Widget(w)), cstr_String(linkUrl)) }, - { openWindow_Icon " ${link.newwindow}", + { "${link.side.newtab}", 0, 0, - format_CStr("!open newwindow:1 origin:%s url:%s", + format_CStr("!open newtab:5 origin:%s url:%s", cstr_String(id_Widget(w)), cstr_String(linkUrl)) }, }, diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 0322b2a9..8f9e44c8 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c @@ -420,7 +420,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct { "---", 0, 0, NULL }, { circle_Icon " ${feeds.entry.markread}", 0, 0, "feed.entry.toggleread" }, { bookmark_Icon " ${feeds.entry.bookmark}", 0, 0, "feed.entry.bookmark" }, - { "${menu.copyurl}", 0, 0, "feed.entry.copyurl" }, + { "${menu.copyurl}", 0, 0, "feed.entry.copy" }, { "---", 0, 0, NULL }, { page_Icon " ${feeds.entry.openfeed}", 0, 0, "feed.entry.openfeed" }, { edit_Icon " ${feeds.edit}", 0, 0, "feed.entry.edit" }, @@ -580,8 +580,8 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "history.open newtab:2" }, { openWindow_Icon " ${menu.openwindow}", 0, 0, "history.open newwindow:1" }, { "---" }, - { "${menu.copyurl}", 0, 0, "history.copy" }, { bookmark_Icon " ${sidebar.entry.bookmark}", 0, 0, "history.addbookmark" }, + { "${menu.copyurl}", 0, 0, "history.copy" }, { "---", 0, 0, NULL }, { close_Icon " ${menu.forgeturl}", 0, 0, "history.delete" }, { "---", 0, 0, NULL }, @@ -1652,12 +1652,18 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) if (item) { if (isCommand_Widget(w, ev, "feed.entry.open")) { const char *cmd = command_UserEvent(ev); - postCommandString_Root(get_Root(), feedEntryOpenCommand_String(&item->url, - argLabel_Command(cmd, "newtab"), - argLabel_Command(cmd, "newwindow"))); + postCommandString_Root( + get_Root(), + feedEntryOpenCommand_String(&item->url, + argLabel_Command(cmd, "newtab"), + argLabel_Command(cmd, "newwindow"))); return iTrue; } - if (isCommand_Widget(w, ev, "feed.entry.toggleread")) { + else if (isCommand_Widget(w, ev, "feed.entry.copy")) { + SDL_SetClipboardText(cstr_String(&item->url)); + return iTrue; + } + else if (isCommand_Widget(w, ev, "feed.entry.toggleread")) { iVisited *vis = visited_App(); const iString *url = urlFragmentStripped_String(&item->url); if (containsUrl_Visited(vis, url)) { @@ -1669,7 +1675,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) postCommand_App("visited.changed"); return iTrue; } - if (isCommand_Widget(w, ev, "feed.entry.bookmark")) { + else if (isCommand_Widget(w, ev, "feed.entry.bookmark")) { makeBookmarkCreation_Widget(&item->url, &item->label, item->icon); if (deviceType_App() == desktop_AppDeviceType) { postCommand_App("focus.set id:bmed.title"); @@ -1718,6 +1724,18 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) } return iTrue; } + else if (isCommand_Widget(w, ev, "history.open")) { + const iSidebarItem *item = d->contextItem; + if (item && !isEmpty_String(&item->url)) { + const char *cmd = command_UserEvent(ev); + postCommand_Widget(d, + "!open newtab:%d newwindow:%d url:%s", + argLabel_Command(cmd, "newtab"), + argLabel_Command(cmd, "newwindow"), + cstr_String(&item->url)); + } + return iTrue; + } else if (isCommand_Widget(w, ev, "history.copy")) { const iSidebarItem *item = d->contextItem; if (item && !isEmpty_String(&item->url)) { -- cgit v1.2.3 From 41f378c4b46cb5dd3599d44c81fa51d3183eefee Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 20 Feb 2022 12:52:36 +0200 Subject: Mobile: Omit menu items for new windows --- src/ui/documentwidget.c | 7 ++- src/ui/sidebarwidget.c | 111 +++++++++++++++++++++++++----------------------- 2 files changed, 64 insertions(+), 54 deletions(-) diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 7492d9bd..fdc0dd75 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -5128,7 +5128,12 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e }, 5); if (deviceType_App() == phone_AppDeviceType) { - removeN_Array(&items, size_Array(&items) - 2, iInvalidSize); + /* Phones don't do windows or splits. */ + removeN_Array(&items, size_Array(&items) - 3, iInvalidSize); + } + else if (deviceType_App() == tablet_AppDeviceType) { + /* Tablets only do splits. */ + removeN_Array(&items, size_Array(&items) - 3, 1); } if (equalCase_Rangecc(scheme, "file")) { pushBack_Array(&items, &(iMenuItem){ "---" }); diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 8f9e44c8..8a96961a 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c @@ -412,23 +412,25 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct setOutline_LabelWidget(child_Widget(d->actions, 1), d->feedsMode != all_FeedsMode); setOutline_LabelWidget(child_Widget(d->actions, 2), d->feedsMode != unread_FeedsMode); } - d->menu = makeMenu_Widget( - as_Widget(d), - (iMenuItem[]){ { openTab_Icon " ${menu.opentab}", 0, 0, "feed.entry.open newtab:1" }, - { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "feed.entry.open newtab:2" }, - { openWindow_Icon " ${menu.openwindow}", 0, 0, "feed.entry.open newwindow:1" }, - { "---", 0, 0, NULL }, - { circle_Icon " ${feeds.entry.markread}", 0, 0, "feed.entry.toggleread" }, - { bookmark_Icon " ${feeds.entry.bookmark}", 0, 0, "feed.entry.bookmark" }, - { "${menu.copyurl}", 0, 0, "feed.entry.copy" }, - { "---", 0, 0, NULL }, - { page_Icon " ${feeds.entry.openfeed}", 0, 0, "feed.entry.openfeed" }, - { edit_Icon " ${feeds.edit}", 0, 0, "feed.entry.edit" }, - { whiteStar_Icon " " uiTextCaution_ColorEscape "${feeds.unsubscribe}", 0, 0, "feed.entry.unsubscribe" }, - { "---", 0, 0, NULL }, - { check_Icon " ${feeds.markallread}", SDLK_a, KMOD_SHIFT, "feeds.markallread" }, - { reload_Icon " ${feeds.refresh}", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" } }, - 13); + const iMenuItem menuItems[] = { + { openTab_Icon " ${menu.opentab}", 0, 0, "feed.entry.open newtab:1" }, + { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "feed.entry.open newtab:2" }, +#if defined (iPlatformDesktop) + { openWindow_Icon " ${menu.openwindow}", 0, 0, "feed.entry.open newwindow:1" }, +#endif + { "---", 0, 0, NULL }, + { circle_Icon " ${feeds.entry.markread}", 0, 0, "feed.entry.toggleread" }, + { bookmark_Icon " ${feeds.entry.bookmark}", 0, 0, "feed.entry.bookmark" }, + { "${menu.copyurl}", 0, 0, "feed.entry.copy" }, + { "---", 0, 0, NULL }, + { page_Icon " ${feeds.entry.openfeed}", 0, 0, "feed.entry.openfeed" }, + { edit_Icon " ${feeds.edit}", 0, 0, "feed.entry.edit" }, + { whiteStar_Icon " " uiTextCaution_ColorEscape "${feeds.unsubscribe}", 0, 0, "feed.entry.unsubscribe" }, + { "---", 0, 0, NULL }, + { check_Icon " ${feeds.markallread}", SDLK_a, KMOD_SHIFT, "feeds.markallread" }, + { reload_Icon " ${feeds.refresh}", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" } + }; + d->menu = makeMenu_Widget(as_Widget(d), menuItems, iElemCount(menuItems)); d->modeMenu = makeMenu_Widget( as_Widget(d), (iMenuItem[]){ @@ -491,27 +493,29 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct addItem_ListWidget(d->list, item); iRelease(item); } - d->menu = makeMenu_Widget( - as_Widget(d), - (iMenuItem[]){ { openTab_Icon " ${menu.opentab}", 0, 0, "bookmark.open newtab:1" }, - { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "bookmark.open newtab:2" }, - { openWindow_Icon " ${menu.openwindow}", 0, 0, "bookmark.open newwindow:1" }, - { "---", 0, 0, NULL }, - { edit_Icon " ${menu.edit}", 0, 0, "bookmark.edit" }, - { copy_Icon " ${menu.dup}", 0, 0, "bookmark.dup" }, - { "${menu.copyurl}", 0, 0, "bookmark.copy" }, - { "---", 0, 0, NULL }, - { "", 0, 0, "bookmark.tag tag:subscribed" }, - { "", 0, 0, "bookmark.tag tag:homepage" }, - { "", 0, 0, "bookmark.tag tag:remotesource" }, - { "---", 0, 0, NULL }, - { delete_Icon " " uiTextCaution_ColorEscape "${bookmark.delete}", 0, 0, "bookmark.delete" }, - { "---", 0, 0, NULL }, - { folder_Icon " ${menu.newfolder}", 0, 0, "bookmark.addfolder" }, - { upDownArrow_Icon " ${menu.sort.alpha}", 0, 0, "bookmark.sortfolder" }, - { "---", 0, 0, NULL }, - { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" } }, - 18); + const iMenuItem menuItems[] = { + { openTab_Icon " ${menu.opentab}", 0, 0, "bookmark.open newtab:1" }, + { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "bookmark.open newtab:2" }, +#if defined (iPlatformDesktop) + { openWindow_Icon " ${menu.openwindow}", 0, 0, "bookmark.open newwindow:1" }, +#endif + { "---", 0, 0, NULL }, + { edit_Icon " ${menu.edit}", 0, 0, "bookmark.edit" }, + { copy_Icon " ${menu.dup}", 0, 0, "bookmark.dup" }, + { "${menu.copyurl}", 0, 0, "bookmark.copy" }, + { "---", 0, 0, NULL }, + { "", 0, 0, "bookmark.tag tag:subscribed" }, + { "", 0, 0, "bookmark.tag tag:homepage" }, + { "", 0, 0, "bookmark.tag tag:remotesource" }, + { "---", 0, 0, NULL }, + { delete_Icon " " uiTextCaution_ColorEscape "${bookmark.delete}", 0, 0, "bookmark.delete" }, + { "---", 0, 0, NULL }, + { folder_Icon " ${menu.newfolder}", 0, 0, "bookmark.addfolder" }, + { upDownArrow_Icon " ${menu.sort.alpha}", 0, 0, "bookmark.sortfolder" }, + { "---", 0, 0, NULL }, + { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" } + }; + d->menu = makeMenu_Widget(as_Widget(d), menuItems, iElemCount(menuItems)); d->modeMenu = makeMenu_Widget( as_Widget(d), (iMenuItem[]){ { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, @@ -525,7 +529,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct addActionButton_SidebarWidget_(d, "${sidebar.action.bookmarks.newfolder}", "bookmarks.addfolder", !d->isEditing ? hidden_WidgetFlag : 0); addChildFlags_Widget(d->actions, iClob(new_Widget()), expand_WidgetFlag); - iLabelWidget *btn = addActionButton_SidebarWidget_(d, + addActionButton_SidebarWidget_(d, d->isEditing ? "${sidebar.close}" : "${sidebar.action.bookmarks.edit}", "sidebar.bookmarks.edit", 0); } @@ -573,20 +577,21 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct addItem_ListWidget(d->list, item); iRelease(item); } - d->menu = makeMenu_Widget( - as_Widget(d), - (iMenuItem[]){ - { openTab_Icon " ${menu.opentab}", 0, 0, "history.open newtab:1" }, - { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "history.open newtab:2" }, - { openWindow_Icon " ${menu.openwindow}", 0, 0, "history.open newwindow:1" }, - { "---" }, - { bookmark_Icon " ${sidebar.entry.bookmark}", 0, 0, "history.addbookmark" }, - { "${menu.copyurl}", 0, 0, "history.copy" }, - { "---", 0, 0, NULL }, - { close_Icon " ${menu.forgeturl}", 0, 0, "history.delete" }, - { "---", 0, 0, NULL }, - { delete_Icon " " uiTextCaution_ColorEscape "${history.clear}", 0, 0, "history.clear confirm:1" }, - }, 10); + const iMenuItem menuItems[] = { + { openTab_Icon " ${menu.opentab}", 0, 0, "history.open newtab:1" }, + { openTabBg_Icon " ${menu.opentab.background}", 0, 0, "history.open newtab:2" }, +#if defined (iPlatformDesktop) + { openWindow_Icon " ${menu.openwindow}", 0, 0, "history.open newwindow:1" }, +#endif + { "---" }, + { bookmark_Icon " ${sidebar.entry.bookmark}", 0, 0, "history.addbookmark" }, + { "${menu.copyurl}", 0, 0, "history.copy" }, + { "---", 0, 0, NULL }, + { close_Icon " ${menu.forgeturl}", 0, 0, "history.delete" }, + { "---", 0, 0, NULL }, + { delete_Icon " " uiTextCaution_ColorEscape "${history.clear}", 0, 0, "history.clear confirm:1" }, + }; + d->menu = makeMenu_Widget(as_Widget(d), menuItems, iElemCount(menuItems)); d->modeMenu = makeMenu_Widget( as_Widget(d), (iMenuItem[]){ -- cgit v1.2.3