From e9e496ffb64d83a55162d38ffc6d87a364bb6a95 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 20 Aug 2021 09:07:09 +0300 Subject: Added WebP decoding using libwebp --- res/about/version.gmi | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'res') diff --git a/res/about/version.gmi b/res/about/version.gmi index 89e81047..5141a4b1 100644 --- a/res/about/version.gmi +++ b/res/about/version.gmi @@ -6,6 +6,10 @@ ``` # Release notes +## 1.7 +* Added support for viewing WebP images. The libwebp library is an optional dependency and will be included in the build if found via pkg-config. +* Added a footer action to view `application/octet-stream` content depending on recognized file extensions. + ## 1.6.4 * Local files containing UTF-8 text can be viewed regardless of their file extension. * Fixed input field cursor positioning and insertion problems around Emoji variation selectors. -- cgit v1.2.3 From ab893836d09a178460b9e64e76e42c89836e5721 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 7 Sep 2021 13:14:59 +0300 Subject: Added image colorization preference Option to colorize images to grayscale, text color, or preformatted color. --- po/en.po | 15 +++++++++++++++ res/lang/de.bin | Bin 23761 -> 23942 bytes res/lang/en.bin | Bin 22362 -> 22543 bytes res/lang/es.bin | Bin 24888 -> 25069 bytes res/lang/fi.bin | Bin 24893 -> 25074 bytes res/lang/fr.bin | Bin 25862 -> 26043 bytes res/lang/ia.bin | Bin 24489 -> 24670 bytes res/lang/ie.bin | Bin 24247 -> 24428 bytes res/lang/pl.bin | Bin 25422 -> 25603 bytes res/lang/ru.bin | Bin 37144 -> 37325 bytes res/lang/sr.bin | Bin 36801 -> 36982 bytes res/lang/tok.bin | Bin 22683 -> 22864 bytes res/lang/zh_Hans.bin | Bin 21441 -> 21622 bytes res/lang/zh_Hant.bin | Bin 21626 -> 21807 bytes src/app.c | 16 ++++++++++++++++ src/defs.h | 7 +++++++ src/gmdocument.c | 10 ++++++---- src/media.c | 35 +++++++++++++++++++++++++++++++++++ src/prefs.c | 1 + src/prefs.h | 1 + src/ui/util.c | 17 +++++++++++++++++ 21 files changed, 98 insertions(+), 4 deletions(-) (limited to 'res') diff --git a/po/en.po b/po/en.po index 609a4bca..46514e49 100644 --- a/po/en.po +++ b/po/en.po @@ -1333,6 +1333,21 @@ msgstr "High Contrast" msgid "prefs.saturation" msgstr "Saturation:" +msgid "prefs.imagestyle" +msgstr "Colorize images:" + +msgid "prefs.imagestyle.original" +msgstr "None" + +msgid "prefs.imagestyle.grayscale" +msgstr "Grayscale" + +msgid "prefs.imagestyle.text" +msgstr "Text Color" + +msgid "prefs.imagestyle.preformat" +msgstr "Preformatted Color" + msgid "prefs.headingfont" msgstr "Heading font:" diff --git a/res/lang/de.bin b/res/lang/de.bin index bac3d41b..5788ed84 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 d2f19cef..536ca6a2 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 8462461d..14203f8e 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 2f1bb18f..875413a5 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 86282bc2..6976a00f 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 4b82125e..a77a1d61 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 a8bce39d..33850dc5 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index f905414b..0b4c3e10 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 687adc01..fbafc283 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 df8e532b..b9ce2f5a 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 f2b59891..f1988dff 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 49decd2d..0ef1a915 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 10a8e86c..f12f0ef2 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 be96bf6c..9ff8ebc8 100644 --- a/src/app.c +++ b/src/app.c @@ -242,6 +242,7 @@ static iString *serializePrefs_App_(const iApp *d) { appendFormat_String(str, "doctheme.dark.set arg:%d\n", d->prefs.docThemeDark); appendFormat_String(str, "doctheme.light.set arg:%d\n", d->prefs.docThemeLight); appendFormat_String(str, "saturation.set arg:%d\n", (int) ((d->prefs.saturation * 100) + 0.5f)); + appendFormat_String(str, "imagestyle.set arg:%d\n", d->prefs.imageStyle); appendFormat_String(str, "ca.file noset:1 path:%s\n", cstr_String(&d->prefs.caFile)); appendFormat_String(str, "ca.path path:%s\n", cstr_String(&d->prefs.caPath)); appendFormat_String(str, "proxy.gemini address:%s\n", cstr_String(&d->prefs.geminiProxy)); @@ -1655,6 +1656,7 @@ static void updateDropdownSelection_(iLabelWidget *dropButton, const char *selec } static void updateColorThemeButton_(iLabelWidget *button, int theme) { + /* TODO: These three functions are all the same? Cleanup? */ if (!button) return; updateDropdownSelection_(button, format_CStr(".set arg:%d", theme)); } @@ -1664,6 +1666,11 @@ static void updateFontButton_(iLabelWidget *button, int font) { updateDropdownSelection_(button, format_CStr(".set arg:%d", font)); } +static void updateImageStyleButton_(iLabelWidget *button, int style) { + if (!button) return; + updateDropdownSelection_(button, format_CStr(".set arg:%d", style)); +} + static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { if (equal_Command(cmd, "prefs.dismiss") || equal_Command(cmd, "preferences")) { setupSheetTransition_Mobile(d, iFalse); @@ -1745,6 +1752,10 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { updateColorThemeButton_(findChild_Widget(d, "prefs.doctheme.light"), arg_Command(cmd)); return iFalse; } + else if (equal_Command(cmd, "imagestyle.set")) { + updateImageStyleButton_(findChild_Widget(d, "prefs.imagestyle"), arg_Command(cmd)); + return iFalse; + } else if (equal_Command(cmd, "font.set")) { updateFontButton_(findChild_Widget(d, "prefs.font"), arg_Command(cmd)); return iFalse; @@ -2177,6 +2188,10 @@ iBool handleCommand_App(const char *cmd) { } return iTrue; } + else if (equal_Command(cmd, "imagestyle.set")) { + d->prefs.imageStyle = arg_Command(cmd); + return iTrue; + } else if (equal_Command(cmd, "linewidth.set")) { d->prefs.lineWidth = iMax(20, arg_Command(cmd)); postCommand_App("document.layout.changed"); @@ -2611,6 +2626,7 @@ iBool handleCommand_App(const char *cmd) { setToggle_Widget(findChild_Widget(dlg, "prefs.collapsepreonload"), d->prefs.collapsePreOnLoad); updateColorThemeButton_(findChild_Widget(dlg, "prefs.doctheme.dark"), d->prefs.docThemeDark); updateColorThemeButton_(findChild_Widget(dlg, "prefs.doctheme.light"), d->prefs.docThemeLight); + updateImageStyleButton_(findChild_Widget(dlg, "prefs.imagestyle"), d->prefs.imageStyle); updateFontButton_(findChild_Widget(dlg, "prefs.font"), d->prefs.font); updateFontButton_(findChild_Widget(dlg, "prefs.headingfont"), d->prefs.headingFont); setFlags_Widget( diff --git a/src/defs.h b/src/defs.h index cf04514e..135cc053 100644 --- a/src/defs.h +++ b/src/defs.h @@ -41,6 +41,13 @@ enum iFileVersion { latest_FileVersion = 4, }; +enum iImageStyle { + original_ImageStyle = 0, + grayscale_ImageStyle = 1, + textColorized_ImageStyle = 2, + preformatColorized_ImageStyle = 3, +}; + enum iScrollType { keyboard_ScrollType, mouse_ScrollType, diff --git a/src/gmdocument.c b/src/gmdocument.c index 75f6f06b..4e92c89c 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -464,6 +464,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { const iBool isNarrow = d->size.x < 90 * gap_Text; const iBool isVeryNarrow = d->size.x <= 70 * gap_Text; const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text; + const iBool isFullWidthImages = (d->outsideMargin < 5 * gap_UI); const iBool isDarkBg = isDark_GmDocumentTheme( isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); /* TODO: Collect these parameters into a GmTheme. */ @@ -817,7 +818,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { rts.layoutWidth = d->size.x; rts.indent = indent * gap_Text; /* The right margin is used for balancing lines horizontally. */ - if (isVeryNarrow) { + if (isVeryNarrow || isFullWidthImages) { rts.rightMargin = 0; } else { @@ -900,14 +901,15 @@ static void doLayout_GmDocument_(iGmDocument *d) { pos.y += margin; run.bounds.pos = pos; run.bounds.size.x = d->size.x; + const float aspect = (float) imgSize.y / (float) imgSize.x; + run.bounds.size.y = d->size.x * aspect; /* Extend the image to full width, including outside margin, if the viewport is narrow enough. */ - if (d->outsideMargin < 5 * gap_UI) { + if (isFullWidthImages) { run.bounds.size.x += d->outsideMargin * 2; + run.bounds.size.y += d->outsideMargin * 2 * aspect; run.bounds.pos.x -= d->outsideMargin; } - const float aspect = (float) imgSize.y / (float) imgSize.x; - run.bounds.size.y = d->size.x * aspect; run.visBounds = run.bounds; const iInt2 maxSize = mulf_I2(imgSize, get_Window()->pixelRatio); if (width_Rect(run.visBounds) > maxSize.x) { diff --git a/src/media.c b/src/media.c index 5240ab5d..e47c5278 100644 --- a/src/media.c +++ b/src/media.c @@ -87,6 +87,40 @@ void deinit_GmImage(iGmImage *d) { deinit_GmMediaProps_(&d->props); } +static void applyImageStyle_(enum iImageStyle style, iInt2 size, uint8_t *imgData) { + if (style == original_ImageStyle) { + return; + } + iColor colorize = (iColor){ 255, 255, 255, 255}; + float brighten = 0.0f; + if (style != grayscale_ImageStyle) { + colorize = get_Color(style == textColorized_ImageStyle ? tmParagraph_ColorId + : tmPreformatted_ColorId); + /* Maximize contrast. */ + const int colMax = iMax(iMax(colorize.r, colorize.g), colorize.b); + if (colMax >= 8) { + colorize.r = colorize.r * 255 / colMax; + colorize.g = colorize.g * 255 / colMax; + colorize.b = colorize.b * 255 / colMax; + } + else { + colorize = (iColor){ 255, 255, 255, 255}; + } +// printf("colorize:%d %d %d\n", colorize.r, colorize.g, colorize.b); +// brighten = iClamp(1.0f - (colorize.r + colorize.g + colorize.b) / 600.0f, 0.0f, 0.5f); /* compensate loss of light */ +// printf("bright:%f\n", brighten); + } + uint8_t *pos = imgData; + size_t numPixels = size.x * size.y; + while (numPixels-- > 0) { + iHSLColor hsl = hsl_Color((iColor){ pos[0], pos[1], pos[2], 255 }); + pos[0] = powf((colorize.r * hsl.lum) / 255.0f, 1.0f - brighten) * 255; + pos[1] = powf((colorize.g * hsl.lum) / 255.0f, 1.0f - brighten) * 255; + pos[2] = powf((colorize.b * hsl.lum) / 255.0f, 1.0f - brighten) * 255; + pos += 4; + } +} + void makeTexture_GmImage(iGmImage *d) { iBlock *data = &d->partialData; d->numBytes = size_Block(data); @@ -105,6 +139,7 @@ void makeTexture_GmImage(iGmImage *d) { d->texture = NULL; } else { + applyImageStyle_(prefs_App()->imageStyle, d->size, imgData); /* TODO: Save some memory by checking if the alpha channel is actually in use. */ iWindow *window = get_Window(); iInt2 texSize = d->size; diff --git a/src/prefs.c b/src/prefs.c index 13a95a3d..749accf8 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -62,6 +62,7 @@ void init_Prefs(iPrefs *d) { d->quoteIcon = iTrue; d->centerShortDocs = iTrue; d->plainTextWrap = iTrue; + d->imageStyle = original_ImageStyle; d->docThemeDark = colorfulDark_GmDocumentTheme; d->docThemeLight = white_GmDocumentTheme; d->saturation = 1.0f; diff --git a/src/prefs.h b/src/prefs.h index 58c73bf7..21960646 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -86,6 +86,7 @@ struct Impl_Prefs { iBool quoteIcon; iBool centerShortDocs; iBool plainTextWrap; + enum iImageStyle imageStyle; /* Colors */ enum iGmDocumentTheme docThemeDark; enum iGmDocumentTheme docThemeLight; diff --git a/src/ui/util.c b/src/ui/util.c index b6ecc7d5..8a42f75b 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -1831,6 +1831,23 @@ iWidget *makePreferences_Widget(void) { addRadioButton_(sats, "prefs.saturation.0", "0 %", "saturation.set arg:0"); } addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); + /* Colorize images. */ { + addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.imagestyle}"))); + const iMenuItem imgStyles[] = { + { "${prefs.imagestyle.original}", 0, 0, format_CStr("imagestyle.set arg:%d", original_ImageStyle) }, + { "${prefs.imagestyle.grayscale}", 0, 0, format_CStr("imagestyle.set arg:%d", grayscale_ImageStyle) }, + { "${prefs.imagestyle.text}", 0, 0, format_CStr("imagestyle.set arg:%d", textColorized_ImageStyle) }, + { "${prefs.imagestyle.preformat}", 0, 0, format_CStr("imagestyle.set arg:%d", preformatColorized_ImageStyle) }, + }; + iLabelWidget *button = makeMenuButton_LabelWidget( + imgStyles[findWidestItemLabel_(imgStyles, iElemCount(imgStyles))].label, + imgStyles, + iElemCount(imgStyles)); + setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), + uiBackgroundMenu_ColorId); + setId_Widget(addChildFlags_Widget(values, iClob(button), alignLeft_WidgetFlag), + "prefs.imagestyle"); + } } /* Fonts. */ { setId_Widget(appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.fonts}", '4', &headings, &values), "prefs.page.fonts"); -- cgit v1.2.3 From 7ebf62baffed32be593fa1307afd3a303575f1c4 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 7 Sep 2021 14:43:02 +0300 Subject: Added BG-to-FG image colorization mode --- po/en.po | 4 ++++ res/lang/de.bin | Bin 23942 -> 23973 bytes res/lang/en.bin | Bin 22543 -> 22574 bytes res/lang/es.bin | Bin 25069 -> 25100 bytes res/lang/fi.bin | Bin 25074 -> 25105 bytes res/lang/fr.bin | Bin 26043 -> 26074 bytes res/lang/ia.bin | Bin 24670 -> 24701 bytes res/lang/ie.bin | Bin 24428 -> 24459 bytes res/lang/pl.bin | Bin 25603 -> 25634 bytes res/lang/ru.bin | Bin 37325 -> 37356 bytes res/lang/sr.bin | Bin 36982 -> 37013 bytes res/lang/tok.bin | Bin 22864 -> 22895 bytes res/lang/zh_Hans.bin | Bin 21622 -> 21653 bytes res/lang/zh_Hant.bin | Bin 21807 -> 21838 bytes src/defs.h | 5 +++-- src/media.c | 27 ++++++++++++++++++++++----- src/ui/util.c | 1 + 17 files changed, 30 insertions(+), 7 deletions(-) (limited to 'res') diff --git a/po/en.po b/po/en.po index 46514e49..97bab2c4 100644 --- a/po/en.po +++ b/po/en.po @@ -1342,6 +1342,10 @@ msgstr "None" msgid "prefs.imagestyle.grayscale" msgstr "Grayscale" +# Abbrevation: background-to-foreground +msgid "prefs.imagestyle.bgfg" +msgstr "BG-to-FG" + msgid "prefs.imagestyle.text" msgstr "Text Color" diff --git a/res/lang/de.bin b/res/lang/de.bin index 5788ed84..8e09c601 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 536ca6a2..859c6461 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 14203f8e..a1e31cb4 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 875413a5..19aa8c35 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 6976a00f..7ad2f654 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index a77a1d61..6d69af59 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 33850dc5..dc519da2 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 0b4c3e10..93877061 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 fbafc283..0b97d0ec 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 b9ce2f5a..182a2dca 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 f1988dff..8fee6738 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 0ef1a915..7c7d9487 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 f12f0ef2..5691aef9 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/defs.h b/src/defs.h index 135cc053..6b12c86c 100644 --- a/src/defs.h +++ b/src/defs.h @@ -44,8 +44,9 @@ enum iFileVersion { enum iImageStyle { original_ImageStyle = 0, grayscale_ImageStyle = 1, - textColorized_ImageStyle = 2, - preformatColorized_ImageStyle = 3, + bgFg_ImageStyle = 2, + textColorized_ImageStyle = 3, + preformatColorized_ImageStyle = 4, }; enum iScrollType { diff --git a/src/media.c b/src/media.c index 94de0bc6..999368c5 100644 --- a/src/media.c +++ b/src/media.c @@ -91,8 +91,27 @@ static void applyImageStyle_(enum iImageStyle style, iInt2 size, uint8_t *imgDat if (style == original_ImageStyle) { return; } - iColor colorize = (iColor){ 255, 255, 255, 255}; - float brighten = 0.0f; + uint8_t *pos = imgData; + size_t numPixels = size.x * size.y; + float brighten = 0.0f; + if (style == bgFg_ImageStyle) { + iColor dark = get_Color(tmBackground_ColorId); + iColor light = get_Color(tmParagraph_ColorId); + if (hsl_Color(dark).lum > hsl_Color(light).lum) { + iSwap(iColor, dark, light); + } + while (numPixels-- > 0) { + iHSLColor hsl = hsl_Color((iColor){ pos[0], pos[1], pos[2], 255 }); + const float s = 1.0f - hsl.lum; + const float t = hsl.lum; + pos[0] = dark.r * s + light.r * t; + pos[1] = dark.g * s + light.g * t; + pos[2] = dark.b * s + light.b * t; + pos += 4; + } + return; + } + iColor colorize = (iColor){ 255, 255, 255, 255 }; if (style != grayscale_ImageStyle) { colorize = get_Color(style == textColorized_ImageStyle ? tmParagraph_ColorId : tmPreformatted_ColorId); @@ -100,9 +119,7 @@ static void applyImageStyle_(enum iImageStyle style, iInt2 size, uint8_t *imgDat const int colMax = iMax(iMax(colorize.r, colorize.g), colorize.b); brighten = iClamp(1.0f - (colorize.r + colorize.g + colorize.b) / (colMax * 3), 0.0f, 0.5f); /* compensate loss of light */ } - uint8_t *pos = imgData; - size_t numPixels = size.x * size.y; - iHSLColor hslColorize = hsl_Color(colorize); + iHSLColor hslColorize = hsl_Color(colorize); while (numPixels-- > 0) { iHSLColor hsl = hsl_Color((iColor){ pos[0], pos[1], pos[2], 255 }); iHSLColor out = { hslColorize.hue, hslColorize.sat, hsl.lum, 1.0f }; diff --git a/src/ui/util.c b/src/ui/util.c index 8a42f75b..906d30ae 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -1836,6 +1836,7 @@ iWidget *makePreferences_Widget(void) { const iMenuItem imgStyles[] = { { "${prefs.imagestyle.original}", 0, 0, format_CStr("imagestyle.set arg:%d", original_ImageStyle) }, { "${prefs.imagestyle.grayscale}", 0, 0, format_CStr("imagestyle.set arg:%d", grayscale_ImageStyle) }, + { "${prefs.imagestyle.bgfg}", 0, 0, format_CStr("imagestyle.set arg:%d", bgFg_ImageStyle) }, { "${prefs.imagestyle.text}", 0, 0, format_CStr("imagestyle.set arg:%d", textColorized_ImageStyle) }, { "${prefs.imagestyle.preformat}", 0, 0, format_CStr("imagestyle.set arg:%d", preformatColorized_ImageStyle) }, }; -- cgit v1.2.3 From c10fc7a058c05edcedeb23bdcc9faae635f1780d Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 7 Sep 2021 22:08:22 +0300 Subject: App: Added options -w, -h for window sizing --- res/arg-help.txt | 2 ++ src/app.c | 12 ++++++++++++ src/app.h | 2 ++ 3 files changed, 16 insertions(+) (limited to 'res') diff --git a/res/arg-help.txt b/res/arg-help.txt index 9f6548f2..bf6e7e97 100644 --- a/res/arg-help.txt +++ b/res/arg-help.txt @@ -6,6 +6,7 @@ separate tabs. General options: -E, --echo Print all internal app events to stdout. + -h, --height N Set initial window height to N pixels. --help Print these instructions. --sw Disable hardware accelerated rendering. -u, --url-or-search URL | text @@ -13,6 +14,7 @@ General options: This only works if the search query URL has been configured. -V, --version Print the application version. + -w, --width N Set initial window width to N pixels. Options that control a running instance of Lagrange: diff --git a/src/app.c b/src/app.c index 9ff8ebc8..fa8cc105 100644 --- a/src/app.c +++ b/src/app.c @@ -668,6 +668,8 @@ static void init_App_(iApp *d, int argc, char **argv) { defineValues_CommandLine(&d->args, "help", 0); defineValues_CommandLine(&d->args, listTabUrls_CommandLineOption, 0); defineValues_CommandLine(&d->args, openUrlOrSearch_CommandLineOption, 1); + defineValues_CommandLine(&d->args, windowWidth_CommandLineOption, 1); + defineValues_CommandLine(&d->args, windowHeight_CommandLineOption, 1); defineValuesN_CommandLine(&d->args, "new-tab", 0, 1); defineValues_CommandLine(&d->args, "tab-url", 0); defineValues_CommandLine(&d->args, "sw", 0); @@ -787,6 +789,16 @@ static void init_App_(iApp *d, int argc, char **argv) { setThemePalette_Color(d->prefs.theme); /* default UI colors */ loadPrefs_App_(d); load_Keys(dataDir_App_()); + /* 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)); + } + arg = iClob(checkArgument_CommandLine(&d->args, windowHeight_CommandLineOption)); + if (arg) { + d->initialWindowRect.size.y = toInt_String(value_CommandLineArg(arg, 0)); + } + } d->window = new_Window(d->initialWindowRect); load_Visited(d->visited, dataDir_App_()); load_Bookmarks(d->bookmarks, dataDir_App_()); diff --git a/src/app.h b/src/app.h index 55bec5a6..08589000 100644 --- a/src/app.h +++ b/src/app.h @@ -44,6 +44,8 @@ iDeclareType(Window) /* Command line options strings. */ #define listTabUrls_CommandLineOption "list-tab-urls;L" #define openUrlOrSearch_CommandLineOption "url-or-search;u" +#define windowWidth_CommandLineOption "width;w" +#define windowHeight_CommandLineOption "height;h" enum iAppDeviceType { desktop_AppDeviceType, -- cgit v1.2.3 From 0d5049ef730983be35e4603e7b8c8cbc4c9e67b7 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 8 Sep 2021 17:29:41 +0300 Subject: Lang: Added "Special tags" string --- po/en.po | 3 +++ res/lang/de.bin | Bin 23973 -> 24008 bytes res/lang/en.bin | Bin 22574 -> 22609 bytes res/lang/es.bin | Bin 25100 -> 25135 bytes res/lang/fi.bin | Bin 25105 -> 25140 bytes res/lang/fr.bin | Bin 26074 -> 26109 bytes res/lang/ia.bin | Bin 24701 -> 24736 bytes res/lang/ie.bin | Bin 24459 -> 24494 bytes res/lang/pl.bin | Bin 25634 -> 25669 bytes res/lang/ru.bin | Bin 37356 -> 37391 bytes res/lang/sr.bin | Bin 37013 -> 37048 bytes res/lang/tok.bin | Bin 22895 -> 22930 bytes res/lang/zh_Hans.bin | Bin 21653 -> 21688 bytes res/lang/zh_Hant.bin | Bin 21838 -> 21873 bytes 14 files changed, 3 insertions(+) (limited to 'res') diff --git a/po/en.po b/po/en.po index 97bab2c4..dd3af388 100644 --- a/po/en.po +++ b/po/en.po @@ -1145,6 +1145,9 @@ msgstr "Tags:" msgid "dlg.bookmark.icon" msgstr "Icon:" +msgid "heading.bookmark.tags" +msgstr "SPECIAL TAGS" + msgid "heading.prefs" msgstr "PREFERENCES" diff --git a/res/lang/de.bin b/res/lang/de.bin index 8e09c601..9d87657f 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 859c6461..9fe4cfb3 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index a1e31cb4..1effaf3d 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 19aa8c35..fcaa8cc6 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 7ad2f654..32104f44 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 6d69af59..5b64a182 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 dc519da2..6d568972 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 93877061..69ad06f8 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 0b97d0ec..67babbcd 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 182a2dca..a7f7639b 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 8fee6738..b7476101 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 7c7d9487..03eb8b43 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 5691aef9..c0378db8 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ -- cgit v1.2.3 From 1410bbde7779efe3a20f603523547c8b8f55b6a1 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sat, 11 Sep 2021 07:43:57 +0300 Subject: Mobile: Many UI improvements; Upload UI --- po/en.po | 22 +++++- res/lang/de.bin | Bin 24008 -> 24162 bytes res/lang/en.bin | Bin 22609 -> 22763 bytes res/lang/es.bin | Bin 25135 -> 25286 bytes res/lang/fi.bin | Bin 25140 -> 25291 bytes res/lang/fr.bin | Bin 26109 -> 26260 bytes res/lang/ia.bin | Bin 24736 -> 24890 bytes res/lang/ie.bin | Bin 24494 -> 24645 bytes res/lang/pl.bin | Bin 25669 -> 25823 bytes res/lang/ru.bin | Bin 37391 -> 37542 bytes res/lang/sr.bin | Bin 37048 -> 37199 bytes res/lang/tok.bin | Bin 22930 -> 23081 bytes res/lang/zh_Hans.bin | Bin 21688 -> 21842 bytes res/lang/zh_Hant.bin | Bin 21873 -> 22027 bytes src/app.c | 7 +- src/ui/certimportwidget.c | 120 +++++++++++++++++------------ src/ui/documentwidget.c | 9 +-- src/ui/labelwidget.c | 17 +++-- src/ui/mobile.c | 134 +++++++++++++++++++++++++-------- src/ui/mobile.h | 19 ++++- src/ui/root.c | 10 +-- src/ui/uploadwidget.c | 188 +++++++++++++++++++++++++++------------------- src/ui/util.c | 15 ++-- src/ui/widget.c | 4 +- 24 files changed, 349 insertions(+), 196 deletions(-) (limited to 'res') diff --git a/po/en.po b/po/en.po index dd3af388..546a9489 100644 --- a/po/en.po +++ b/po/en.po @@ -251,6 +251,10 @@ msgstr "Show Feed Entries" msgid "menu.preferences" msgstr "Preferences…" +# used for Preferences on mobile +msgid "menu.settings" +msgstr "Settings…" + msgid "menu.help" msgstr "Help" @@ -906,7 +910,7 @@ msgid "heading.lookup.other" msgstr "OTHER" msgid "menu.page.upload" -msgstr "Upload Page with Titan" +msgstr "Upload Page with Titan…" msgid "heading.upload" msgstr "UPLOAD WITH TITAN" @@ -953,6 +957,18 @@ msgstr "Set the Titan server port to use for this URL.\nThe port is saved in the msgid "dlg.uploadport.set" msgstr "Set Port" +# used on mobile +msgid "dlg.upload.text" +msgstr "Upload Plain Text" + +# used on mobile +msgid "dlg.upload.file" +msgstr "Upload a File" + +# used on mobile +msgid "dlg.upload.pickfile" +msgstr "Select File…" + msgid "heading.translate" msgstr "TRANSLATE PAGE" @@ -1151,6 +1167,10 @@ msgstr "SPECIAL TAGS" msgid "heading.prefs" msgstr "PREFERENCES" +# used on mobile +msgid "heading.settings" +msgstr "SETTINGS" + msgid "heading.prefs.certs" msgstr "CERTIFICATES" diff --git a/res/lang/de.bin b/res/lang/de.bin index 9d87657f..ba87d002 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 9fe4cfb3..a7dfb866 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 1effaf3d..7e7398d6 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index fcaa8cc6..607e52fd 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 32104f44..955695ed 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 5b64a182..61a18efc 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 6d568972..06ea7979 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 69ad06f8..5fc5e24a 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 67babbcd..1718f647 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 a7f7639b..60b7b600 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 b7476101..3298f0e8 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 03eb8b43..8c32a0c5 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 c0378db8..68f7d3bc 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 a52552c0..e597edbe 100644 --- a/src/app.c +++ b/src/app.c @@ -2382,7 +2382,8 @@ iBool handleCommand_App(const char *cmd) { setUrl_UploadWidget(upload, url); setResponseViewer_UploadWidget(upload, document_App()); addChild_Widget(get_Root()->widget, iClob(upload)); - finalizeSheet_Mobile(as_Widget(upload)); +// finalizeSheet_Mobile(as_Widget(upload)); + setupSheetTransition_Mobile(as_Widget(upload), iTrue); postRefresh_App(); return iTrue; } @@ -2761,7 +2762,9 @@ iBool handleCommand_App(const char *cmd) { iCertImportWidget *imp = new_CertImportWidget(); setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App())); addChild_Widget(get_Root()->widget, iClob(imp)); - finalizeSheet_Mobile(as_Widget(imp)); +// finalizeSheet_Mobile(as_Widget(imp)); + arrange_Widget(as_Widget(imp)); + setupSheetTransition_Mobile(as_Widget(imp), iTrue); postRefresh_App(); return iTrue; } diff --git a/src/ui/certimportwidget.c b/src/ui/certimportwidget.c index 2e60c71f..65cb6654 100644 --- a/src/ui/certimportwidget.c +++ b/src/ui/certimportwidget.c @@ -104,61 +104,83 @@ static iBool tryImport_CertImportWidget_(iCertImportWidget *d, const iBlock *dat void init_CertImportWidget(iCertImportWidget *d) { iWidget *w = as_Widget(d); + const iMenuItem actions[] = { +#if defined (iPlatformAppleMobile) + { "${dlg.certimport.pickfile}", 0, 0, "certimport.pickfile" }, + { "---" }, +#endif + { "${cancel}" }, + { uiTextAction_ColorEscape "${dlg.certimport.import}", + SDLK_RETURN, KMOD_PRIMARY, + "certimport.accept" } + }; init_Widget(w); setId_Widget(w, "certimport"); d->cert = NULL; - /* This should behave similar to sheets. */ - useSheetStyle_Widget(w); - addChildFlags_Widget( - w, - iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.certimport}", NULL)), - frameless_WidgetFlag); - d->info = addChildFlags_Widget(w, iClob(new_LabelWidget(infoText_, NULL)), frameless_WidgetFlag); - addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); - d->crtLabel = new_LabelWidget("", NULL); { - setFont_LabelWidget(d->crtLabel, uiContent_FontId); - addChildFlags_Widget(w, iClob(d->crtLabel), 0); - setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId); + if (isUsingPanelLayout_Mobile()) { + initPanels_Mobile(w, NULL, (iMenuItem[]){ + { "title id:heading.certimport" }, + { format_CStr("label id:certimport.info text:%s", infoText_) }, + //{ "padding" }, + { "label id:certimport.crt nowrap:1 frame:1" }, + { "padding arg:0.25" }, + { "label id:certimport.key nowrap:1 frame:1" }, + { "heading text:${dlg.certimport.notes}" }, + { "input id:certimport.notes hint:hint.certimport.description noheading:1" }, + { NULL } + }, actions, iElemCount(actions)); + d->info = findChild_Widget(w, "certimport.info"); + d->crtLabel = findChild_Widget(w, "certimport.crt"); + d->keyLabel = findChild_Widget(w, "certimport.key"); + d->notes = findChild_Widget(w, "certimport.notes"); + setFixedSize_Widget(as_Widget(d->crtLabel), init_I2(-1, gap_UI * 12)); + setFixedSize_Widget(as_Widget(d->keyLabel), init_I2(-1, gap_UI * 12)); } - d->keyLabel = new_LabelWidget("", NULL); { - setFont_LabelWidget(d->keyLabel, uiContent_FontId); + else { + /* This should behave similar to sheets. */ + useSheetStyle_Widget(w); + addChildFlags_Widget( + w, + iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.certimport}", NULL)), + frameless_WidgetFlag); + d->info = addChildFlags_Widget(w, iClob(new_LabelWidget(infoText_, NULL)), frameless_WidgetFlag); addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); - addChildFlags_Widget(w, iClob(d->keyLabel), 0); - setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId); - } - addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); - /* TODO: Use makeTwoColumnWidget_() */ - iWidget *page = new_Widget(); { - setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); - iWidget *headings = addChildFlags_Widget( - page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); - iWidget *values = addChildFlags_Widget( - page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); -// addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.certimport.notes}"))); -// addChild_Widget(values, iClob(d->notes = new_InputWidget(0))); -// setHint_InputWidget(d->notes, "${hint.certimport.description}"); - addTwoColumnDialogInputField_Widget( - headings, - values, - "${dlg.certimport.notes}", - "", - iClob(d->notes = newHint_InputWidget(0, "${hint.certimport.description}"))); - as_Widget(d->notes)->rect.size.x = gap_UI * 70; + d->crtLabel = new_LabelWidget("", NULL); { + setFont_LabelWidget(d->crtLabel, uiContent_FontId); + addChildFlags_Widget(w, iClob(d->crtLabel), 0); + } + d->keyLabel = new_LabelWidget("", NULL); { + setFont_LabelWidget(d->keyLabel, uiContent_FontId); + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + addChildFlags_Widget(w, iClob(d->keyLabel), 0); + } + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + /* TODO: Use makeTwoColumnWidget_() */ + iWidget *page = new_Widget(); { + setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); + iWidget *headings = addChildFlags_Widget( + page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); + iWidget *values = addChildFlags_Widget( + page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); + addTwoColumnDialogInputField_Widget( + headings, + values, + "${dlg.certimport.notes}", + "", + iClob(d->notes = newHint_InputWidget(0, "${hint.certimport.description}"))); + as_Widget(d->notes)->rect.size.x = gap_UI * 70; + } + addChild_Widget(w, iClob(page)); + arrange_Widget(w); + setFixedSize_Widget(as_Widget(d->crtLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); + setFixedSize_Widget(as_Widget(d->keyLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); + /* Buttons. */ + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + iWidget *buttons = makeDialogButtons_Widget(actions, iElemCount(actions)); + addChild_Widget(w, iClob(buttons)); } - addChild_Widget(w, iClob(page)); - arrange_Widget(w); - setFixedSize_Widget(as_Widget(d->crtLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); - setFixedSize_Widget(as_Widget(d->keyLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); - /* Buttons. */ - addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); - iWidget *buttons = makeDialogButtons_Widget( - (iMenuItem[]){ { "${cancel}" }, - { uiTextAction_ColorEscape "${dlg.certimport.import}", - SDLK_RETURN, - KMOD_PRIMARY, - "certimport.accept" } }, - 2); - addChild_Widget(w, iClob(buttons)); + setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId); + setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId); if (deviceType_App() != desktop_AppDeviceType) { /* Try auto-pasting. */ postCommand_App("certimport.paste"); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 83f2ea6a..4b3c2db0 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -2852,7 +2852,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) setUrl_UploadWidget(upload, d->mod.url); setResponseViewer_UploadWidget(upload, d); addChild_Widget(get_Root()->widget, iClob(upload)); - finalizeSheet_Mobile(as_Widget(upload)); +// finalizeSheet_Mobile(as_Widget(upload)); + setupSheetTransition_Mobile(as_Widget(upload), iTrue); postRefresh_App(); } return iTrue; @@ -3679,16 +3680,10 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e { "---" }, { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, -#if defined (iPlatformMobile) - { "---" }, - { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, - 14); -#else { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, { "---" }, { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, 15); -#endif if (isEmpty_Range(&d->selectMark)) { pushBackN_Array( &items, diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index 03595d1a..ec324d02 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c @@ -244,6 +244,9 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int } } } + if (d->forceFg >= 0) { + *fg = d->forceFg; + } if (isPress) { *bg = uiBackgroundPressed_ColorId | permanent_ColorId; if (isButton) { @@ -257,9 +260,6 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int *fg = isDark_ColorTheme(colorTheme_App()) ? white_ColorId : black_ColorId; } } - if (d->forceFg >= 0) { - *fg = d->forceFg; - } } iLocalDef int iconPadding_LabelWidget_(const iLabelWidget *d) { @@ -318,6 +318,10 @@ static void draw_LabelWidget_(const iLabelWidget *d) { } setClip_Paint(&p, rect); const int iconPad = iconPadding_LabelWidget_(d); + const int iconColor = isCaution ? uiTextCaution_ColorId + : flags & (disabled_WidgetFlag | pressed_WidgetFlag) ? fg + : isHover ? uiIconHover_ColorId + : uiIcon_ColorId; if (d->icon && d->icon != 0x20) { /* no need to draw an empty icon */ iString str; initUnicodeN_String(&str, &d->icon, 1); @@ -331,10 +335,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { -gap_UI / 8)), init_I2(iconPad, lineHeight_Text(d->font)) }, iTrue, - isCaution ? uiTextCaution_ColorId - : flags & (disabled_WidgetFlag | pressed_WidgetFlag) ? fg - : isHover ? uiIconHover_ColorId - : uiIcon_ColorId, + iconColor, "%s", cstr_String(&str)); deinit_String(&str); @@ -387,7 +388,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { drawCentered_Text(d->font, (iRect){ addX_I2(topRight_Rect(chRect), -iconPad), init_I2(chSize, height_Rect(chRect)) }, - iTrue, uiSeparator_ColorId, rightAngle_Icon); + iTrue, iconColor /*uiSeparator_ColorId*/, rightAngle_Icon); } unsetClip_Paint(&p); } diff --git a/src/ui/mobile.c b/src/ui/mobile.c index daa1fa1a..6ea672e6 100644 --- a/src/ui/mobile.c +++ b/src/ui/mobile.c @@ -90,15 +90,15 @@ static void unselectAllPanelButtons_(iWidget *topPanel) { static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) { if (equal_Command(cmd, "window.resized")) { - const iBool isPortrait = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); - const iRect safeRoot = safeRect_Root(mainDetailSplit->root); - setPos_Widget(mainDetailSplit, topLeft_Rect(safeRoot)); - setFixedSize_Widget(mainDetailSplit, safeRoot.size); + const iBool isPortrait = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); + const iRect safeRoot = safeRect_Root(mainDetailSplit->root); iWidget * sheet = parent_Widget(mainDetailSplit); iWidget * navi = findChild_Widget(sheet, "panel.navi"); iWidget * detailStack = findChild_Widget(mainDetailSplit, "detailstack"); const size_t numPanels = childCount_Widget(detailStack); const iBool isSideBySide = isSideBySideLayout_() && numPanels > 0; + setPos_Widget(mainDetailSplit, topLeft_Rect(safeRoot)); + setFixedSize_Widget(mainDetailSplit, safeRoot.size); setFlags_Widget(mainDetailSplit, arrangeHorizontal_WidgetFlag, isSideBySide); setFlags_Widget(detailStack, expand_WidgetFlag, isSideBySide); setFlags_Widget(detailStack, hidden_WidgetFlag, numPanels == 0); @@ -172,7 +172,16 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { } unselectAllPanelButtons_(topPanel); if (!wasClosed) { - postCommand_App("prefs.dismiss"); + /* TODO: Should come up with a more general-purpose approach here. */ + if (findWidget_App("prefs")) { + postCommand_App("prefs.dismiss"); + } + else if (findWidget_App("upload")) { + postCommand_App("upload.cancel"); + } + else { + postCommand_App("cancel"); + } } return iTrue; } @@ -503,11 +512,19 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { else if (equal_Command(spec, "label")) { iLabelWidget *lab = new_LabelWidget(label, NULL); widget = as_Widget(lab); - setWrap_LabelWidget(lab, iTrue); - setFlags_Widget(widget, fixedHeight_WidgetFlag | frameless_WidgetFlag, iTrue); + setId_Widget(widget, id); + setWrap_LabelWidget(lab, !argLabel_Command(spec, "nowrap")); + setFlags_Widget(widget, + fixedHeight_WidgetFlag | + (!argLabel_Command(spec, "frame") ? frameless_WidgetFlag : 0), + iTrue); } else if (equal_Command(spec, "padding")) { - widget = makePadding_Widget(lineHeight_Text(labelFont_()) * 1.5f); + float height = 1.5f; + if (hasLabel_Command(spec, "arg")) { + height *= argfLabel_Command(spec, "arg"); + } + widget = makePadding_Widget(lineHeight_Text(labelFont_()) * height); } /* Apply common styling to the heading. */ if (heading) { @@ -539,7 +556,7 @@ static const iMenuItem *findDialogCancelAction_(const iMenuItem *items, size_t n return NULL; } for (size_t i = 0; i < n; i++) { - if (!iCmpStr(items[i].label, "${cancel}")) { + if (!iCmpStr(items[i].label, "${cancel}") || !iCmpStr(items[i].label, "${close}")) { return &items[i]; } } @@ -556,13 +573,20 @@ iWidget *makePanelsParent_Mobile(iWidget *parentWidget, const char *id, const iMenuItem *itemsNullTerminated, const iMenuItem *actions, size_t numActions) { + iWidget *panels = new_Widget(); + setId_Widget(panels, id); + initPanels_Mobile(panels, parentWidget, itemsNullTerminated, actions, numActions); + return panels; +} + +void initPanels_Mobile(iWidget *panels, iWidget *parentWidget, + const iMenuItem *itemsNullTerminated, + const iMenuItem *actions, size_t numActions) { /* A multipanel widget has a top panel and one or more detail panels. In a horizontal layout, the detail panels slide in from the right and cover the top panel. In a landscape layout, the detail panels are always visible on the side. */ - iWidget *sheet = new_Widget(); - setId_Widget(sheet, id); - setBackgroundColor_Widget(sheet, uiBackground_ColorId); - setFlags_Widget(sheet, + setBackgroundColor_Widget(panels, uiBackground_ColorId); + setFlags_Widget(panels, resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag | frameless_WidgetFlag | focusRoot_WidgetFlag | commandOnClick_WidgetFlag | overflowScrollable_WidgetFlag | leftEdgeDraggable_WidgetFlag, @@ -572,7 +596,7 @@ iWidget *makePanelsParent_Mobile(iWidget *parentWidget, setCommandHandler_Widget(mainDetailSplit, mainDetailSplitHandler_); setFlags_Widget(mainDetailSplit, resizeHeightOfChildren_WidgetFlag, iFalse); setId_Widget(mainDetailSplit, "mdsplit"); - addChild_Widget(sheet, iClob(mainDetailSplit)); + addChild_Widget(panels, iClob(mainDetailSplit)); } /* The panel roots. */ iWidget *topPanel = new_Widget(); { @@ -591,7 +615,6 @@ iWidget *makePanelsParent_Mobile(iWidget *parentWidget, setFlags_Widget(detailStack, collapse_WidgetFlag | resizeWidthOfChildren_WidgetFlag, iTrue); addChild_Widget(mainDetailSplit, iClob(detailStack)); } - addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); /* Slide top panel with detail panels. */ { setFlags_Widget(topPanel, refChildrenOffset_WidgetFlag, iTrue); topPanel->offsetRef = detailStack; @@ -612,15 +635,17 @@ iWidget *makePanelsParent_Mobile(iWidget *parentWidget, checkIcon_LabelWidget(naviBack); setId_Widget(as_Widget(naviBack), "panel.back"); setFont_LabelWidget(naviBack, labelFont_()); - addChildFlags_Widget(sheet, iClob(navi), + addChildFlags_Widget(panels, iClob(navi), drawBackgroundToVerticalSafeArea_WidgetFlag | arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag); } + iBool haveDetailPanels = iFalse; /* Create panel contents based on provided items. */ for (size_t i = 0; itemsNullTerminated[i].label; i++) { const iMenuItem *item = &itemsNullTerminated[i]; if (equal_Command(item->label, "panel")) { + haveDetailPanels = iTrue; const char *id = cstr_Rangecc(range_Command(item->label, "id")); const iString *label = hasLabel_Command(item->label, "text") ? collect_String(suffix_Command(item->label, "text")) @@ -655,10 +680,12 @@ iWidget *makePanelsParent_Mobile(iWidget *parentWidget, setFont_LabelWidget(naviBack, labelBoldFont_()); } else if (defaultItem && defaultItem != cancelItem) { - updateTextCStr_LabelWidget(naviBack, cancelItem->label); - setCommand_LabelWidget(naviBack, collectNewCStr_String(cancelItem->command - ? cancelItem->command - : "cancel")); + if (!haveDetailPanels) { + updateTextCStr_LabelWidget(naviBack, cancelItem->label); + setCommand_LabelWidget(naviBack, collectNewCStr_String(cancelItem->command + ? cancelItem->command + : "cancel")); + } iLabelWidget *defaultButton = new_LabelWidget(defaultItem->label, defaultItem->command); setFont_LabelWidget(defaultButton, labelBoldFont_()); setFlags_Widget(as_Widget(defaultButton), @@ -689,16 +716,21 @@ iWidget *makePanelsParent_Mobile(iWidget *parentWidget, } makePanelItem_Mobile( topPanel, - &(iMenuItem){ format_CStr("button text:%s", act->label), 0, 0, act->command }); + &(iMenuItem){ format_CStr("button text:" uiTextAction_ColorEscape "%s", act->label), + 0, + 0, + act->command }); } } /* Finalize the layout. */ - addChild_Widget(parentWidget, iClob(sheet)); + if (parentWidget) { + addChild_Widget(parentWidget, iClob(panels)); + } mainDetailSplitHandler_(mainDetailSplit, "window.resized"); /* make it resize the split */ - updatePanelSheetMetrics_(sheet); - arrange_Widget(sheet); + updatePanelSheetMetrics_(panels); + arrange_Widget(panels); postCommand_App("widget.overflow"); /* with the correct dimensions */ - return sheet; + printTree_Widget(panels); } #if 0 @@ -1130,7 +1162,9 @@ void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { } } -void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { +void setupSheetTransition_Mobile(iWidget *sheet, int flags) { + const iBool isIncoming = (flags & incoming_TransitionFlag) != 0; + const int dir = flags & dirMask_TransitionFlag; if (!isUsingPanelLayout_Mobile()) { if (prefs_App()->uiAnimations) { setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iFalse); @@ -1144,17 +1178,51 @@ void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { } return; } - if(isSideBySideLayout_()) { + if (isSideBySideLayout_()) { + /* TODO: Landscape transitions? */ return; } - setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); + setFlags_Widget(sheet, + horizontalOffset_WidgetFlag, + dir == right_TransitionDir || dir == left_TransitionDir); if (isIncoming) { - setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); + switch (dir) { + case right_TransitionDir: + setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); + break; + case left_TransitionDir: + setVisualOffset_Widget(sheet, -size_Root(sheet->root).x, 0, 0); + break; + case top_TransitionDir: + setVisualOffset_Widget( + sheet, -bottom_Rect(boundsWithoutVisualOffset_Widget(sheet)), 0, 0); + break; + case bottom_TransitionDir: + setVisualOffset_Widget(sheet, height_Widget(sheet), 0, 0); + break; + } setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag); - } + } else { - const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; - setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, - wasDragged ? 0 : easeIn_AnimFlag); + switch (dir) { + case right_TransitionDir: { + const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; + setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, + wasDragged ? 0 : easeIn_AnimFlag); + break; + } + case left_TransitionDir: + setVisualOffset_Widget(sheet, -size_Root(sheet->root).x, 200, easeIn_AnimFlag); + break; + case top_TransitionDir: + setVisualOffset_Widget(sheet, + -bottom_Rect(boundsWithoutVisualOffset_Widget(sheet)), + 200, + easeIn_AnimFlag); + break; + case bottom_TransitionDir: + setVisualOffset_Widget(sheet, height_Widget(sheet), 200, easeIn_AnimFlag); + break; + } } } diff --git a/src/ui/mobile.h b/src/ui/mobile.h index 30679c7c..e1131953 100644 --- a/src/ui/mobile.h +++ b/src/ui/mobile.h @@ -35,8 +35,23 @@ iWidget * makePanelsParent_Mobile (iWidget *parent, const char *id, const iMenuItem *itemsNullTerminated, const iMenuItem *actions, size_t numActions); +void initPanels_Mobile (iWidget *panels, iWidget *parentWidget, + const iMenuItem *itemsNullTerminated, + const iMenuItem *actions, size_t numActions); + +enum iTransitionFlags { + incoming_TransitionFlag = iBit(1), + dirMask_TransitionFlag = iBit(2) | iBit(3), +}; + +enum iTransitionDir { + right_TransitionDir = 0, + bottom_TransitionDir = 2, + left_TransitionDir = 4, + top_TransitionDir = 6, +}; -void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); -void setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming); +void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); +void setupSheetTransition_Mobile (iWidget *sheet, int flags); void finalizeSheet_Mobile (iWidget *sheet); diff --git a/src/ui/root.c b/src/ui/root.c index eae8e4bb..a792e93d 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -118,7 +118,7 @@ static const iMenuItem phoneNavMenuItems_[] = { { "${menu.downloads}", 0, 0, "downloads.open" }, { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, { "---" }, - { gear_Icon " Settings...", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, + { gear_Icon " ${menu.settings}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, }; #endif /* Mobile */ @@ -1163,20 +1163,12 @@ void createUserInterface_Root(iRoot *d) { { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, -#if defined (iPlatformMobile) - { "---" }, - { "${menu.page.copyurl}", 0, 0, "document.copylink" }, - { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, - { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, - 11); -#else { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, { "---" }, { "${menu.page.copyurl}", 0, 0, "document.copylink" }, { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, 12); -#endif setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); setAlignVisually_LabelWidget(pageMenuButton, iTrue); diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c index 4c72c60a..fb8aaf0a 100644 --- a/src/ui/uploadwidget.c +++ b/src/ui/uploadwidget.c @@ -81,91 +81,122 @@ void init_UploadWidget(iUploadWidget *d) { iWidget *w = as_Widget(d); init_Widget(w); setId_Widget(w, "upload"); - useSheetStyle_Widget(w); init_String(&d->originalUrl); init_String(&d->url); d->viewer = NULL; d->request = NULL; init_String(&d->filePath); d->fileSize = 0; - addChildFlags_Widget(w, - iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.upload}", NULL)), - frameless_WidgetFlag); - d->info = addChildFlags_Widget(w, iClob(new_LabelWidget("", NULL)), - frameless_WidgetFlag | resizeToParentWidth_WidgetFlag | - fixedHeight_WidgetFlag); - setWrap_LabelWidget(d->info, iTrue); - /* Tabs for input data. */ - iWidget *tabs = makeTabs_Widget(w); - /* Make the tabs support vertical expansion based on content. */ { - setFlags_Widget(tabs, resizeHeightOfChildren_WidgetFlag, iFalse); - setFlags_Widget(tabs, arrangeHeight_WidgetFlag, iTrue); - iWidget *tabPages = findChild_Widget(tabs, "tabs.pages"); - setFlags_Widget(tabPages, resizeHeightOfChildren_WidgetFlag, iFalse); - setFlags_Widget(tabPages, arrangeHeight_WidgetFlag, iTrue); + const iMenuItem actions[] = { + { "${upload.port}", 0, 0, "upload.setport" }, + { "---" }, + { "${close}", SDLK_ESCAPE, 0, "upload.cancel" }, + { uiTextAction_ColorEscape "${dlg.upload.send}", SDLK_RETURN, KMOD_PRIMARY, "upload.accept" } + }; + if (isUsingPanelLayout_Mobile()) { + const iMenuItem textItems[] = { + { "title id:heading.upload.text" }, + { "input id:upload.text noheading:1" }, + { NULL } + }; + const iMenuItem fileItems[] = { + { "title id:heading.upload.file" }, + { "button text:" uiTextAction_ColorEscape "${dlg.upload.pickfile}", 0, 0, "upload.pickfile" }, + { "heading id:upload.file.name" }, + { "label id:upload.filepathlabel" }, + { "heading id:upload.file.size" }, + { "label id:upload.filesizelabel" }, + { "padding" }, + { "input id:upload.mime" }, + { "label id:upload.counter text:" }, + { NULL } + }; + initPanels_Mobile(w, NULL, (iMenuItem[]){ + { "title id:heading.upload" }, + { "label id:upload.info" }, +// { "padding" }, + { "panel id:dlg.upload.text icon:0x1f5b9", 0, 0, (const void *) textItems }, + { "panel id:dlg.upload.file icon:0x1f4c1", 0, 0, (const void *) fileItems }, + { "padding" }, + { "input id:upload.token hint:hint.upload.token" }, + { NULL } + }, actions, iElemCount(actions)); + d->info = findChild_Widget(w, "upload.info"); + d->input = findChild_Widget(w, "upload.text"); + d->filePathLabel = findChild_Widget(w, "upload.file.name"); + d->fileSizeLabel = findChild_Widget(w, "upload.file.size"); + d->mime = findChild_Widget(w, "upload.mime"); + d->token = findChild_Widget(w, "upload.token"); + d->counter = findChild_Widget(w, "upload.counter"); } - iWidget *headings, *values; - setBackgroundColor_Widget(findChild_Widget(tabs, "tabs.buttons"), uiBackgroundSidebar_ColorId); - setId_Widget(tabs, "upload.tabs"); -// const int bigGap = lineHeight_Text(uiLabel_FontId) * 3 / 4; - /* Text input. */ { - //appendTwoColumnTabPage_Widget(tabs, "${heading.upload.text}", '1', &headings, &values); - iWidget *page = new_Widget(); - setFlags_Widget(page, arrangeSize_WidgetFlag, iTrue); - d->input = new_InputWidget(0); - setId_Widget(as_Widget(d->input), "upload.text"); - setFont_InputWidget(d->input, monospace_FontId); - setLineLimits_InputWidget(d->input, 7, 20); - setUseReturnKeyBehavior_InputWidget(d->input, iFalse); /* traditional text editor */ - setHint_InputWidget(d->input, "${hint.upload.text}"); - setFixedSize_Widget(as_Widget(d->input), init_I2(120 * gap_UI, -1)); - addChild_Widget(page, iClob(d->input)); - appendFramelessTabPage_Widget(tabs, iClob(page), "${heading.upload.text}", '1', 0); - } - /* File content. */ { - appendTwoColumnTabPage_Widget(tabs, "${heading.upload.file}", '2', &headings, &values); -// iWidget *pad = addChild_Widget(headings, iClob(makePadding_Widget(0))); -// iWidget *hint = addChild_Widget(values, iClob(new_LabelWidget("${upload.file.drophint}", NULL))); -// pad->sizeRef = hint; - addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.name}", NULL)), frameless_WidgetFlag); - d->filePathLabel = addChildFlags_Widget(values, iClob(new_LabelWidget(uiTextAction_ColorEscape "${upload.file.drophere}", NULL)), frameless_WidgetFlag); - addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.size}", NULL)), frameless_WidgetFlag); - d->fileSizeLabel = addChildFlags_Widget(values, iClob(new_LabelWidget("\u2014", NULL)), frameless_WidgetFlag); - d->mime = new_InputWidget(0); - setFixedSize_Widget(as_Widget(d->mime), init_I2(70 * gap_UI, -1)); - addTwoColumnDialogInputField_Widget(headings, values, "${upload.mime}", "upload.mime", iClob(d->mime)); - } - /* Token. */ { - addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); - iWidget *page = makeTwoColumns_Widget(&headings, &values); - d->token = addTwoColumnDialogInputField_Widget( - headings, values, "${upload.token}", "upload.token", iClob(new_InputWidget(0))); - setHint_InputWidget(d->token, "${hint.upload.token}"); - setFixedSize_Widget(as_Widget(d->token), init_I2(50 * gap_UI, -1)); - addChild_Widget(w, iClob(page)); - } - /* Buttons. */ { - addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); - iWidget *buttons = - makeDialogButtons_Widget((iMenuItem[]){ { "${upload.port}", 0, 0, "upload.setport" }, - { "---" }, - { "${close}", SDLK_ESCAPE, 0, "upload.cancel" }, - { uiTextAction_ColorEscape "${dlg.upload.send}", - SDLK_RETURN, - KMOD_PRIMARY, - "upload.accept" } }, - 4); - setId_Widget(insertChildAfterFlags_Widget(buttons, - iClob(d->counter = new_LabelWidget("", NULL)), - 0, frameless_WidgetFlag), - "upload.counter"); - addChild_Widget(w, iClob(buttons)); + else { + useSheetStyle_Widget(w); + addChildFlags_Widget(w, + iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.upload}", NULL)), + frameless_WidgetFlag); + d->info = addChildFlags_Widget(w, iClob(new_LabelWidget("", NULL)), + frameless_WidgetFlag | resizeToParentWidth_WidgetFlag | + fixedHeight_WidgetFlag); + setWrap_LabelWidget(d->info, iTrue); + /* Tabs for input data. */ + iWidget *tabs = makeTabs_Widget(w); + /* Make the tabs support vertical expansion based on content. */ { + setFlags_Widget(tabs, resizeHeightOfChildren_WidgetFlag, iFalse); + setFlags_Widget(tabs, arrangeHeight_WidgetFlag, iTrue); + iWidget *tabPages = findChild_Widget(tabs, "tabs.pages"); + setFlags_Widget(tabPages, resizeHeightOfChildren_WidgetFlag, iFalse); + setFlags_Widget(tabPages, arrangeHeight_WidgetFlag, iTrue); + } + iWidget *headings, *values; + setBackgroundColor_Widget(findChild_Widget(tabs, "tabs.buttons"), uiBackgroundSidebar_ColorId); + setId_Widget(tabs, "upload.tabs"); + /* Text input. */ { + iWidget *page = new_Widget(); + setFlags_Widget(page, arrangeSize_WidgetFlag, iTrue); + d->input = new_InputWidget(0); + setId_Widget(as_Widget(d->input), "upload.text"); + setFixedSize_Widget(as_Widget(d->input), init_I2(120 * gap_UI, -1)); + addChild_Widget(page, iClob(d->input)); + appendFramelessTabPage_Widget(tabs, iClob(page), "${heading.upload.text}", '1', 0); + } + /* File content. */ { + appendTwoColumnTabPage_Widget(tabs, "${heading.upload.file}", '2', &headings, &values); + addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.name}", NULL)), frameless_WidgetFlag); + d->filePathLabel = addChildFlags_Widget(values, iClob(new_LabelWidget(uiTextAction_ColorEscape "${upload.file.drophere}", NULL)), frameless_WidgetFlag); + addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.size}", NULL)), frameless_WidgetFlag); + d->fileSizeLabel = addChildFlags_Widget(values, iClob(new_LabelWidget("\u2014", NULL)), frameless_WidgetFlag); + d->mime = new_InputWidget(0); + setFixedSize_Widget(as_Widget(d->mime), init_I2(70 * gap_UI, -1)); + addTwoColumnDialogInputField_Widget(headings, values, "${upload.mime}", "upload.mime", iClob(d->mime)); + } + /* Token. */ { + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + iWidget *page = makeTwoColumns_Widget(&headings, &values); + d->token = addTwoColumnDialogInputField_Widget( + headings, values, "${upload.token}", "upload.token", iClob(new_InputWidget(0))); + setHint_InputWidget(d->token, "${hint.upload.token}"); + setFixedSize_Widget(as_Widget(d->token), init_I2(50 * gap_UI, -1)); + addChild_Widget(w, iClob(page)); + } + /* Buttons. */ { + addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); + iWidget *buttons = makeDialogButtons_Widget(actions, iElemCount(actions)); + setId_Widget(insertChildAfterFlags_Widget(buttons, + iClob(d->counter = new_LabelWidget("", NULL)), + 0, frameless_WidgetFlag), + "upload.counter"); + addChild_Widget(w, iClob(buttons)); + } + resizeToLargestPage_Widget(tabs); + arrange_Widget(w); + setFixedSize_Widget(as_Widget(d->token), init_I2(width_Widget(tabs) - left_Rect(parent_Widget(d->token)->rect), -1)); + setFlags_Widget(as_Widget(d->token), expand_WidgetFlag, iTrue); + setFocus_Widget(as_Widget(d->input)); } - resizeToLargestPage_Widget(tabs); - arrange_Widget(w); - setFixedSize_Widget(as_Widget(d->token), init_I2(width_Widget(tabs) - left_Rect(parent_Widget(d->token)->rect), -1)); - setFlags_Widget(as_Widget(d->token), expand_WidgetFlag, iTrue); - setFocus_Widget(as_Widget(d->input)); + setFont_InputWidget(d->input, monospace_FontId); + setUseReturnKeyBehavior_InputWidget(d->input, iFalse); /* traditional text editor */ + setLineLimits_InputWidget(d->input, 7, 20); + setHint_InputWidget(d->input, "${hint.upload.text}"); setBackupFileName_InputWidget(d->input, "uploadbackup.txt"); updateInputMaxHeight_UploadWidget_(d); } @@ -201,6 +232,7 @@ static void setUrlPort_UploadWidget_(iUploadWidget *d, const iString *url, uint1 appendFormat_String(&d->url, ":%u", overridePort ? overridePort : titanPortForUrl_(url)); appendRange_String(&d->url, (iRangecc){ parts.path.start, constEnd_String(url) }); setText_LabelWidget(d->info, &d->url); + arrange_Widget(as_Widget(d)); } void setUrl_UploadWidget(iUploadWidget *d, const iString *url) { @@ -233,7 +265,7 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { if (isResize_UserEvent(ev)) { updateInputMaxHeight_UploadWidget_(d); } - if (isCommand_Widget(w, ev, "upload.cancel")) { + if (equal_Command(cmd, "upload.cancel")) { setupSheetTransition_Mobile(w, iFalse); destroy_Widget(w); return iTrue; diff --git a/src/ui/util.c b/src/ui/util.c index b875e260..6069e800 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -1061,6 +1061,7 @@ iWidget *removeTabPage_Widget(iWidget *tabs, size_t index) { } void resizeToLargestPage_Widget(iWidget *tabs) { + if (!tabs) return; // puts("RESIZE TO LARGEST PAGE ..."); iWidget *pages = findChild_Widget(tabs, "tabs.pages"); iForEach(ObjectList, i, children_Widget(pages)) { @@ -1216,7 +1217,7 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) { postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); setId_Widget(dlg, ""); /* no further commands to emit */ } - setupSheetTransition_Mobile(dlg, iFalse); + setupSheetTransition_Mobile(dlg, top_TransitionDir); destroy_Widget(dlg); return iTrue; } @@ -1225,13 +1226,13 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) { else if (equal_Command(cmd, "valueinput.cancel")) { postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); setId_Widget(dlg, ""); /* no further commands to emit */ - setupSheetTransition_Mobile(dlg, iFalse); + setupSheetTransition_Mobile(dlg, top_TransitionDir); destroy_Widget(dlg); return iTrue; } else if (equal_Command(cmd, "valueinput.accept")) { acceptValueInput_(dlg); - setupSheetTransition_Mobile(dlg, iFalse); + setupSheetTransition_Mobile(dlg, top_TransitionDir); destroy_Widget(dlg); return iTrue; } @@ -1345,7 +1346,9 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con acceptKeyMod_ReturnKeyBehavior(prefs_App()->returnKey), "valueinput.accept" } }, 2))); - finalizeSheet_Mobile(dlg); +// finalizeSheet_Mobile(dlg); + arrange_Widget(dlg); + setupSheetTransition_Mobile(dlg, incoming_TransitionFlag | top_TransitionDir); if (parent) { setFocus_Widget(as_Widget(input)); } @@ -1915,6 +1918,7 @@ iWidget *makePreferences_Widget(void) { { NULL } }; iWidget *dlg = makePanels_Mobile("prefs", (iMenuItem[]){ + { "title id:heading.settings" }, { "panel text:" gear_Icon " ${heading.prefs.general}", 0, 0, (const void *) generalPanelItems }, { "panel icon:0x1f5a7 id:heading.prefs.network", 0, 0, (const void *) networkPanelItems }, { "panel text:" person_Icon " ${sidebar.identities}", 0, 0, (const void *) identityPanelItems }, @@ -2405,7 +2409,7 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { arrange_Widget(dlg); as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; addChild_Widget(get_Root()->widget, iClob(dlg)); - finalizeSheet_Mobile(dlg); +// finalizeSheet_Mobile(dlg); } /* Initialize. */ { const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; @@ -2419,6 +2423,7 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { iTrue); setCommandHandler_Widget(dlg, handleFeedSettingCommands_); } + setupSheetTransition_Mobile(dlg, incoming_TransitionFlag); return dlg; } diff --git a/src/ui/widget.c b/src/ui/widget.c index 4fd8f066..659a00cc 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -452,7 +452,7 @@ static void arrange_Widget_(iWidget *d) { else if (d->flags & centerHorizontal_WidgetFlag) { centerHorizontal_Widget_(d); } - if (d->flags & resizeToParentWidth_WidgetFlag) { + if (d->flags & resizeToParentWidth_WidgetFlag && d->parent) { iRect childBounds = zero_Rect(); if (flags_Widget(d->parent) & arrangeWidth_WidgetFlag) { /* Can't go narrower than what the children require, though. */ @@ -462,7 +462,7 @@ static void arrange_Widget_(iWidget *d) { setWidth_Widget_(d, iMaxi(width_Rect(innerRect_Widget_(d->parent)), width_Rect(childBounds))); } - if (d->flags & resizeToParentHeight_WidgetFlag) { + if (d->flags & resizeToParentHeight_WidgetFlag && d->parent) { TRACE(d, "resize to parent height"); setHeight_Widget_(d, height_Rect(innerRect_Widget_(d->parent))); } -- cgit v1.2.3 From 4cf52f29b926a924d838a3158d5c78b3337ee0ee Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 13 Sep 2021 19:52:21 +0300 Subject: Mobile: New selection logic for InputWidget Touch-based interaction requires a different kind of selection and copy/paste behavior. This isn't done yet; especially multi-line text still needs work. --- po/en.po | 12 ++ res/lang/de.bin | Bin 24162 -> 24222 bytes res/lang/en.bin | Bin 22763 -> 22823 bytes res/lang/es.bin | Bin 25286 -> 25346 bytes res/lang/fi.bin | Bin 25291 -> 25351 bytes res/lang/fr.bin | Bin 26260 -> 26320 bytes res/lang/ia.bin | Bin 24890 -> 24950 bytes res/lang/ie.bin | Bin 24645 -> 24705 bytes res/lang/pl.bin | Bin 25823 -> 25883 bytes res/lang/ru.bin | Bin 37542 -> 37602 bytes res/lang/sr.bin | Bin 37199 -> 37259 bytes res/lang/tok.bin | Bin 23081 -> 23141 bytes res/lang/zh_Hans.bin | Bin 21842 -> 21902 bytes res/lang/zh_Hant.bin | Bin 22027 -> 22087 bytes src/app.c | 16 +- src/defs.h | 2 + src/ui/color.h | 1 + src/ui/documentwidget.c | 21 +- src/ui/inputwidget.c | 563 +++++++++++++++++++++++++++++++++++++----------- src/ui/paint.c | 19 +- src/ui/paint.h | 2 + src/ui/root.c | 33 ++- src/ui/touch.c | 9 +- src/ui/util.c | 70 +++--- src/ui/widget.c | 40 ++-- src/ui/window.c | 10 +- src/ui/window.h | 2 +- 27 files changed, 591 insertions(+), 209 deletions(-) (limited to 'res') diff --git a/po/en.po b/po/en.po index 546a9489..96026528 100644 --- a/po/en.po +++ b/po/en.po @@ -273,6 +273,18 @@ msgstr "Copy" msgid "menu.paste" msgstr "Paste" +# keep this short (3x1 horiz layout) +msgid "menu.selectall" +msgstr "Select All" + +# keep this short (3x1 horiz layout) +msgid "menu.delete" +msgstr "Delete" + +# keep this short (3x1 horiz layout) +msgid "menu.undo" +msgstr "Undo" + msgid "menu.select.clear" msgstr "Clear Selection" diff --git a/res/lang/de.bin b/res/lang/de.bin index ba87d002..f5a6b07e 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 a7dfb866..df3c4025 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 7e7398d6..9f5a169f 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 607e52fd..ac3b99ef 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 955695ed..7d40e32c 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 61a18efc..d10ab85e 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 06ea7979..14349637 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 5fc5e24a..9ddd137f 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 1718f647..366c6ee5 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 60b7b600..870b0950 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 3298f0e8..dac5fb33 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 8c32a0c5..3407c485 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 68f7d3bc..b9001e2d 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 fa601ac3..37f9c804 100644 --- a/src/app.c +++ b/src/app.c @@ -1069,11 +1069,11 @@ iLocalDef iBool isWaitingAllowed_App_(iApp *d) { return iFalse; } #endif -#if defined (iPlatformMobile) - if (!isFinished_Anim(&d->window->rootOffset)) { - return iFalse; - } -#endif +//#if defined (iPlatformMobile) +// if (!isFinished_Anim(&d->window->rootOffset)) { +// return iFalse; +// } +//#endif return !value_Atomic(&d->pendingRefresh) && isEmpty_SortedArray(&d->tickers); } @@ -1318,7 +1318,7 @@ void processEvents_App(enum iAppEventMode eventMode) { } } #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) - if (d->isIdling && !gotEvents && isFinished_Anim(&d->window->rootOffset)) { + if (d->isIdling && !gotEvents /*&& isFinished_Anim(&d->window->rootOffset)*/) { /* This is where we spend most of our time when idle. 60 Hz still quite a lot but we can't wait too long after the user tries to interact again with the app. In any case, on macOS SDL_WaitEvent() seems to use 10x more CPU time than sleeping. */ @@ -1411,9 +1411,9 @@ void refresh_App(void) { #endif if (!exchange_Atomic(&d->pendingRefresh, iFalse)) { /* Refreshing wasn't pending. */ - if (isFinished_Anim(&d->window->rootOffset)) { +// if (isFinished_Anim(&d->window->rootOffset)) { return; - } +// } } // iTime draw; // initCurrent_Time(&draw); diff --git a/src/defs.h b/src/defs.h index 6b12c86c..c3a23596 100644 --- a/src/defs.h +++ b/src/defs.h @@ -151,6 +151,8 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { #define magnifyingGlass_Icon "\U0001f50d" #define midEllipsis_Icon "\u00b7\u00b7\u00b7" #define return_Icon "\u23ce" +#define undo_Icon "\u23ea" +#define select_Icon "\u2b1a" #if defined (iPlatformApple) # define shift_Icon "\u21e7" diff --git a/src/ui/color.h b/src/ui/color.h index 37ec49eb..a1d863dc 100644 --- a/src/ui/color.h +++ b/src/ui/color.h @@ -183,6 +183,7 @@ iLocalDef iBool isRegularText_ColorId(enum iColorId d) { #define mask_ColorId 0x7f #define permanent_ColorId 0x80 /* cannot be changed via escapes */ #define fillBackground_ColorId 0x100 /* fill background with same color, but alpha 0 */ +#define opaque_ColorId 0x200 #define asciiBase_ColorEscape 33 #define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape) diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 4b3c2db0..8ea695d5 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -4597,23 +4597,6 @@ static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { } } -static void drawPin_(iPaint *p, iRect rangeRect, int dir) { - const int pinColor = tmQuote_ColorId; - const int height = height_Rect(rangeRect); - iRect pin; - if (dir == 0) { - pin = (iRect){ add_I2(topLeft_Rect(rangeRect), init_I2(-gap_UI / 4, -gap_UI)), - init_I2(gap_UI / 2, height + gap_UI) }; - } - else { - pin = (iRect){ addX_I2(topRight_Rect(rangeRect), -gap_UI / 4), - init_I2(gap_UI / 2, height + gap_UI) }; - } - fillRect_Paint(p, pin, pinColor); - fillRect_Paint(p, initCentered_Rect(dir == 0 ? topMid_Rect(pin) : bottomMid_Rect(pin), - init1_I2(gap_UI * 2)), pinColor); -} - static void extend_GmRunRange_(iGmRunRange *runs) { if (runs->start) { runs->start--; @@ -4857,8 +4840,8 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); /* Selection range pins. */ if (isTouchSelecting) { - drawPin_(&ctx.paint, ctx.firstMarkRect, 0); - drawPin_(&ctx.paint, ctx.lastMarkRect, 1); + drawPin_Paint(&ctx.paint, ctx.firstMarkRect, 0, tmQuote_ColorId); + drawPin_Paint(&ctx.paint, ctx.lastMarkRect, 1, tmQuote_ColorId); } } drawMedia_DocumentWidget_(d, &ctx.paint); diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index ad630223..12eb490d 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c @@ -179,19 +179,23 @@ static void deinit_InputUndo_(iInputUndo *d) { } enum iInputWidgetFlag { - isSensitive_InputWidgetFlag = iBit(1), - isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ - enterPressed_InputWidgetFlag = iBit(3), - selectAllOnFocus_InputWidgetFlag = iBit(4), - notifyEdits_InputWidgetFlag = iBit(5), - eatEscape_InputWidgetFlag = iBit(6), - isMarking_InputWidgetFlag = iBit(7), - markWords_InputWidgetFlag = iBit(8), - needUpdateBuffer_InputWidgetFlag = iBit(9), - enterKeyEnabled_InputWidgetFlag = iBit(10), - lineBreaksEnabled_InputWidgetFlag= iBit(11), - needBackup_InputWidgetFlag = iBit(12), + isSensitive_InputWidgetFlag = iBit(1), + isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ + enterPressed_InputWidgetFlag = iBit(3), + selectAllOnFocus_InputWidgetFlag = iBit(4), + notifyEdits_InputWidgetFlag = iBit(5), + eatEscape_InputWidgetFlag = iBit(6), + isMarking_InputWidgetFlag = iBit(7), + markWords_InputWidgetFlag = iBit(8), + needUpdateBuffer_InputWidgetFlag = iBit(9), + enterKeyEnabled_InputWidgetFlag = iBit(10), + lineBreaksEnabled_InputWidgetFlag = iBit(11), + needBackup_InputWidgetFlag = iBit(12), useReturnKeyBehavior_InputWidgetFlag = iBit(13), + //touchBehavior_InputWidgetFlag = iBit(14), /* different behavior depending on interaction method */ + dragCursor_InputWidgetFlag = iBit(14), + dragMarkerStart_InputWidgetFlag = iBit(15), + dragMarkerEnd_InputWidgetFlag = iBit(16), }; /*----------------------------------------------------------------------------------------------*/ @@ -217,6 +221,10 @@ struct Impl_InputWidget { iArray undoStack; int font; iClick click; + uint32_t tapStartTime; + uint32_t lastTapTime; + iInt2 lastTapPos; + int tapCount; int wheelAccum; int cursorVis; uint32_t timer; @@ -460,14 +468,18 @@ static iWrapText wrap_InputWidget_(const iInputWidget *d, int y) { }; } -static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { +static iInt2 relativeCoord_InputWidget_(const iInputWidget *d, iInt2 pos) { /* Relative to the start of the line on which the cursor is. */ - iWrapText wt = wrap_InputWidget_(d, d->cursor.y); - wt.hitChar = wt.text.start + d->cursor.x; + iWrapText wt = wrap_InputWidget_(d, pos.y); + wt.hitChar = wt.text.start + pos.x; measure_WrapText(&wt, d->font); return wt.hitAdvance_out; } +static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { + return relativeCoord_InputWidget_(d, d->cursor); +} + static void updateVisible_InputWidget_(iInputWidget *d) { const int totalWraps = numWrapLines_InputWidget_(d); const int visWraps = iClamp(totalWraps, d->minWrapLines, d->maxWrapLines); @@ -632,7 +644,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { init_Widget(w); d->validator = NULL; d->validatorContext = NULL; - setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag | touchDrag_WidgetFlag, iTrue); + setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag, iTrue); #if defined (iPlatformMobile) setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); #endif @@ -662,6 +674,8 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { splitToLines_(&iStringLiteral(""), &d->lines); setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */ init_Click(&d->click, d, SDL_BUTTON_LEFT); + d->lastTapTime = 0; + d->tapCount = 0; d->wheelAccum = 0; d->timer = 0; d->cursorVis = 0; @@ -993,7 +1007,7 @@ void begin_InputWidget(iInputWidget *d) { d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; d->cursor = cursorMax_InputWidget_(d); } - else { + else if (~d->inFlags & isMarking_InputWidgetFlag) { iZap(d->mark); } enableEditorKeysInMenus_(iFalse); @@ -1013,9 +1027,10 @@ void end_InputWidget(iInputWidget *d, iBool accept) { splitToLines_(&d->oldText, &d->lines); } d->inFlags |= needUpdateBuffer_InputWidgetFlag; + d->inFlags &= ~isMarking_InputWidgetFlag; startOrStopCursorTimer_InputWidget_(d, iFalse); SDL_StopTextInput(); - setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iFalse); + setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag | touchDrag_WidgetFlag, iFalse); const char *id = cstr_String(id_Widget(as_Widget(d))); if (!*id) id = "_"; refresh_Widget(w); @@ -1445,88 +1460,31 @@ static iBool checkAcceptMods_InputWidget_(const iInputWidget *d, int mods) { return mods == 0; } -static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { - iWidget *w = as_Widget(d); - /* Resize according to width immediately. */ - if (d->lastUpdateWidth != w->rect.size.x) { - d->inFlags |= needUpdateBuffer_InputWidgetFlag; - if (d->inFlags & isUrl_InputWidgetFlag) { - /* Restore/omit the default scheme if necessary. */ - setText_InputWidget(d, text_InputWidget(d)); - } - updateAllLinesAndResizeHeight_InputWidget_(d); - d->lastUpdateWidth = w->rect.size.x; - } - if (isCommand_Widget(w, ev, "focus.gained")) { - begin_InputWidget(d); - return iFalse; - } - else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") || - isCommand_UserEvent(ev, "window.focus.gained"))) { - startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained")); - d->cursorVis = 1; - refresh_Widget(d); - return iFalse; - } - else if (isCommand_UserEvent(ev, "keyroot.changed")) { - d->inFlags |= needUpdateBuffer_InputWidgetFlag; - } - else if (isCommand_UserEvent(ev, "lang.changed")) { - set_String(&d->hint, &d->srcHint); - translate_Lang(&d->hint); - return iFalse; - } - else if (isCommand_Widget(w, ev, "focus.lost")) { - end_InputWidget(d, iTrue); - return iFalse; - } - else if ((isCommand_UserEvent(ev, "copy") || isCommand_UserEvent(ev, "input.copy")) && - isEditing_InputWidget_(d)) { - copy_InputWidget_(d, argLabel_Command(command_UserEvent(ev), "cut")); - return iTrue; - } - else if (isCommand_UserEvent(ev, "input.paste") && isEditing_InputWidget_(d)) { - paste_InputWidget_(d); - return iTrue; - } - else if (isCommand_UserEvent(ev, "theme.changed")) { - if (d->buffered) { - d->inFlags |= needUpdateBuffer_InputWidgetFlag; - } - return iFalse; - } - else if (isCommand_UserEvent(ev, "keyboard.changed")) { - if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) { - iRect rect = bounds_Widget(w); - rect.pos.y -= value_Anim(&get_Window()->rootOffset); - const iInt2 visRoot = visibleSize_Root(w->root); - if (bottom_Rect(rect) > visRoot.y) { - setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250); - } - } - return iFalse; - } - else if (isCommand_UserEvent(ev, "text.insert")) { - pushUndo_InputWidget_(d); - deleteMarked_InputWidget_(d); - insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev))); - contentsWereChanged_InputWidget_(d); - return iTrue; - } - else if (isCommand_Widget(w, ev, "input.backup")) { - if (d->inFlags & needBackup_InputWidgetFlag) { - saveBackup_InputWidget_(d); - } - return iTrue; - } - else if (isMetricsChange_UserEvent(ev)) { - updateMetrics_InputWidget_(d); - // updateLinesAndResize_InputWidget_(d); +enum iEventResult { + ignored_EventResult = 0, /* event was not processed */ + false_EventResult = 1, /* event was processed but other widgets can still process it, too*/ + true_EventResult = 2, /* event was processed and should not be passed on */ +}; + +static void markWordAtCursor_InputWidget_(iInputWidget *d) { + d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); + extendRange_InputWidget_(d, &d->mark.start, -1); + extendRange_InputWidget_(d, &d->mark.end, +1); + d->initialMark = d->mark; +} + +static void showClipMenu_(iInt2 coord) { + iWidget *clipMenu = findWidget_App("clipmenu"); + if (isVisible_Widget(clipMenu)) { + closeMenu_Widget(clipMenu); } - else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { - copy_InputWidget_(d, iFalse); - return iTrue; + else { + openMenuFlags_Widget(clipMenu, coord, iFalse); } +} + +static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { + iWidget *w = as_Widget(d); if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); const iInt2 inner = windowToInner_Widget(w, coord); @@ -1559,10 +1517,15 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { d->visWrapLines.start += lineDelta; d->visWrapLines.end += lineDelta; d->inFlags |= needUpdateBuffer_InputWidgetFlag; - refresh_Widget(d); - return iTrue; + refresh_Widget(d); + return true_EventResult; } - return iFalse; + return false_EventResult; + } + if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && + contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { + showClipMenu_(mouseCoord_Window(get_Window(), ev->button.which)); + return iTrue; } switch (processEvent_Click(&d->click, ev)) { case none_ClickResult: @@ -1584,10 +1547,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag); if (d->click.count == 2) { d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag; - d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); - extendRange_InputWidget_(d, &d->mark.start, -1); - extendRange_InputWidget_(d, &d->mark.end, +1); - d->initialMark = d->mark; + markWordAtCursor_InputWidget_(d); refresh_Widget(w); } if (d->click.count == 3) { @@ -1595,11 +1555,11 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { } } refresh_Widget(d); - return iTrue; + return true_EventResult; } case aborted_ClickResult: d->inFlags &= ~isMarking_InputWidgetFlag; - return iTrue; + return true_EventResult; case drag_ClickResult: d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); showCursor_InputWidget_(d); @@ -1614,30 +1574,374 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end; } refresh_Widget(w); - return iTrue; + return true_EventResult; case finished_ClickResult: d->inFlags &= ~isMarking_InputWidgetFlag; - return iTrue; + return true_EventResult; } if (ev->type == SDL_MOUSEMOTION && flags_Widget(w) & keepOnTop_WidgetFlag) { const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); if (contains_Click(&d->click, coord)) { - return iTrue; + return true_EventResult; } } - if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && - contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { - iWidget *clipMenu = findWidget_App("clipmenu"); - if (isVisible_Widget(clipMenu)) { - closeMenu_Widget(clipMenu); + return ignored_EventResult; +} + +static iInt2 touchCoordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) { + /* Clamp to the bounds so the cursor doesn't wrap at the ends. */ + iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); + bounds.size.y = iMini(numWrapLines_InputWidget_(d), d->maxWrapLines) * lineHeight_Text(d->font) - 2; + return coordCursor_InputWidget_(d, min_I2(bottomRight_Rect(bounds), + max_I2(coord, topLeft_Rect(bounds)))); +} + +static iBool isInsideMark_InputWidget_(const iInputWidget *d, size_t pos) { + const iRanges mark = mark_InputWidget_(d); + return contains_Range(&mark, pos); +} + +static int distanceToPos_InputWidget_(const iInputWidget *d, iInt2 uiCoord, iInt2 textPos) { + const iInt2 a = addY_I2(relativeCoord_InputWidget_(d, textPos), lineHeight_Text(d->font) / 2); + const iInt2 b = sub_I2(uiCoord, topLeft_Rect(contentBounds_InputWidget_(d))); + return dist_I2(a, b); +} + +static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { + iWidget *w = as_Widget(d); + /* + + first tap to focus & select all/place cursor + + focused tap to place cursor + - drag cursor to move it + - double-click to select a word + - drag to move selection handles + - long-press for context menu: copy, paste, delete, select all, deselect + - double-click and hold to select words + - triple-click to select all + - drag/wheel elsewhere to scroll (contents or overflow), no change in focus + */ +// if (ev->type != SDL_MOUSEBUTTONUP && ev->type != SDL_MOUSEBUTTONDOWN && +// ev->type != SDL_MOUSEWHEEL && ev->type != SDL_MOUSEMOTION && +// !(ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) && +// !(ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode)) { +// return ignored_EventResult; +// } + if (isFocused_Widget(w)) { + if (ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) { + d->lastTapTime = d->tapStartTime; + d->tapStartTime = SDL_GetTicks(); + const int tapDist = dist_I2(latestPosition_Touch(), d->lastTapPos); + d->lastTapPos = latestPosition_Touch(); + printf("[%p] tap start time: %u (%u) %d\n", w, d->tapStartTime, d->tapStartTime - d->lastTapTime, tapDist); + if (d->tapStartTime - d->lastTapTime < 400 && tapDist < gap_UI * 4) { + d->tapCount++; + printf("[%p] >> tap count: %d\n", w, d->tapCount); + } + else { + d->tapCount = 0; + } + if (!isEmpty_Range(&d->mark)) { + const int dist[2] = { + distanceToPos_InputWidget_(d, latestPosition_Touch(), + indexToCursor_InputWidget_(d, d->mark.start)), + distanceToPos_InputWidget_(d, latestPosition_Touch(), + indexToCursor_InputWidget_(d, d->mark.end)) + }; + if (dist[0] < dist[1]) { + printf("[%p] begin marker start drag\n", w); + d->inFlags |= dragMarkerStart_InputWidgetFlag; + } + else { + printf("[%p] begin marker end drag\n", w); + d->inFlags |= dragMarkerEnd_InputWidgetFlag; + } + d->inFlags |= isMarking_InputWidgetFlag; + setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); + } + else { + const int dist = distanceToPos_InputWidget_(d, latestPosition_Touch(), d->cursor); + printf("[%p] tap dist: %d\n", w, dist); + if (dist < gap_UI * 10) { + printf("[%p] begin cursor drag\n", w); + setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); + d->inFlags |= dragCursor_InputWidgetFlag; +// d->inFlags |= touchBehavior_InputWidgetFlag; +// setMouseGrab_Widget(w); +// return iTrue; + } + } +// if (~d->inFlags & selectAllOnFocus_InputWidgetFlag) { +// d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); +// showCursor_InputWidget_(d); +// } + return true_EventResult; + } + } +#if 0 + else if (isFocused_Widget(w)) { + if (ev->type == SDL_MOUSEMOTION) { + if (~d->inFlags & touchBehavior_InputWidgetFlag) { + const iInt2 curPos = relativeCursorCoord_InputWidget_(d); + const iInt2 relClick = sub_I2(pos_Click(&d->click), + topLeft_Rect(contentBounds_InputWidget_(d))); + if (dist_I2(curPos, relClick) < gap_UI * 8) { + printf("tap on cursor!\n"); + setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); + d->inFlags |= touchBehavior_InputWidgetFlag; + printf("[Input] begin cursor drag\n"); + setMouseGrab_Widget(w); + return iTrue; + } + } + else if (ev->motion.x > 0 && ev->motion.y > 0) { + printf("[Input] cursor being dragged\n"); + iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); + bounds.size.y = iMini(numWrapLines_InputWidget_(d), d->maxWrapLines) * lineHeight_Text(d->font) - 2; + iInt2 mpos = init_I2(ev->motion.x, ev->motion.y); + mpos = min_I2(bottomRight_Rect(bounds), max_I2(mpos, topLeft_Rect(bounds))); + d->cursor = coordCursor_InputWidget_(d, mpos); + showCursor_InputWidget_(d); + refresh_Widget(w); + return iTrue; + } } - else { - openMenuFlags_Widget(clipMenu, - mouseCoord_Window(get_Window(), ev->button.which), - iFalse); + if (d->inFlags & touchBehavior_InputWidgetFlag) { + if (ev->type == SDL_MOUSEBUTTONUP || + (ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode)) { + d->inFlags &= ~touchBehavior_InputWidgetFlag; + setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); + setMouseGrab_Widget(NULL); + printf("[Input] touch ends\n"); + return iFalse; + } + } + } +#endif +#if 1 + if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && + ev->button.button == SDL_BUTTON_RIGHT && contains_Widget(w, latestPosition_Touch())) { + if (ev->type == SDL_MOUSEBUTTONDOWN) { + /*if (isFocused_Widget(w)) { + d->inFlags |= isMarking_InputWidgetFlag; + d->cursor = touchCoordCursor_InputWidget_(d, latestPosition_Touch()); + markWordAtCursor_InputWidget_(d); + refresh_Widget(d); + return true_EventResult; + }*/ + setFocus_Widget(w); + d->inFlags |= isMarking_InputWidgetFlag; + d->cursor = touchCoordCursor_InputWidget_(d, latestPosition_Touch()); + markWordAtCursor_InputWidget_(d); + d->cursor = indexToCursor_InputWidget_(d, d->mark.end); + refresh_Widget(d); + } + return true_EventResult; + } + switch (processEvent_Click(&d->click, ev)) { + case none_ClickResult: + break; + case started_ClickResult: { + printf("[%p] started\n", w); + /* + const iInt2 curPos = relativeCursorCoord_InputWidget_(d); + const iInt2 relClick = sub_I2(pos_Click(&d->click), + topLeft_Rect(contentBounds_InputWidget_(d))); + if (dist_I2(curPos, relClick) < gap_UI * 8) { + printf("tap on cursor!\n"); + setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); + } + else { + printf("tap elsewhere\n"); + }*/ + return true_EventResult; + } + case drag_ClickResult: + printf("[%p] drag %d,%d\n", w, pos_Click(&d->click).x, pos_Click(&d->click).y); + if (d->inFlags & dragCursor_InputWidgetFlag) { + iZap(d->mark); + d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); + showCursor_InputWidget_(d); + refresh_Widget(w); + } + else if (d->inFlags & dragMarkerStart_InputWidgetFlag) { + d->mark.start = cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, pos_Click(&d->click))); + refresh_Widget(w); + } + else if (d->inFlags & dragMarkerEnd_InputWidgetFlag) { + d->mark.end = cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, pos_Click(&d->click))); + refresh_Widget(w); + } + return true_EventResult; + // printf("[%p] aborted\n", w); +// d->inFlags &= ~touchBehavior_InputWidgetFlag; +// setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); +// return true_EventResult; + case finished_ClickResult: + case aborted_ClickResult: + printf("[%p] ended\n", w); + uint32_t tapElapsed = SDL_GetTicks() - d->tapStartTime; + printf("tapElapsed: %u\n", tapElapsed); + if (!isFocused_Widget(w)) { + setFocus_Widget(w); + d->lastTapPos = latestPosition_Touch(); + d->tapStartTime = SDL_GetTicks(); + d->tapCount = 0; + d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); + showCursor_InputWidget_(d); + } + else if (!isEmpty_Range(&d->mark) && !isMoved_Click(&d->click)) { + if (isInsideMark_InputWidget_(d, cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, latestPosition_Touch())))) { + showClipMenu_(latestPosition_Touch()); + } + else { + iZap(d->mark); + d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); + } + } + else if (SDL_GetTicks() - d->lastTapTime > 1000 && + d->tapCount == 0 && isEmpty_Range(&d->mark) && !isMoved_Click(&d->click) && + distanceToPos_InputWidget_(d, latestPosition_Touch(), d->cursor) < gap_UI * 5) { + showClipMenu_(latestPosition_Touch()); + } + else { + if (~d->inFlags & isMarking_InputWidgetFlag) { + iZap(d->mark); + d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); + } + } + if (d->inFlags & (dragCursor_InputWidgetFlag | dragMarkerStart_InputWidgetFlag | + dragMarkerEnd_InputWidgetFlag)) { + printf("[%p] finished cursor/marker drag\n", w); + d->inFlags &= ~(dragCursor_InputWidgetFlag | + dragMarkerStart_InputWidgetFlag | + dragMarkerEnd_InputWidgetFlag); + setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); + } + d->inFlags &= ~isMarking_InputWidgetFlag; + showCursor_InputWidget_(d); + refresh_Widget(w); +#if 0 + d->inFlags &= ~touchBehavior_InputWidgetFlag; + if (flags_Widget(w) & touchDrag_WidgetFlag) { + setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); + return true_EventResult; + } + if (!isMoved_Click(&d->click)) { + if (!isFocused_Widget(w)) { + setFocus_Widget(w); + if (~d->inFlags & selectAllOnFocus_InputWidgetFlag) { + d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); + showCursor_InputWidget_(d); + } + } + else { + iZap(d->mark); + d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); + showCursor_InputWidget_(d); + } + } +#endif + return true_EventResult; + } +#endif +// if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && +// contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { +// /* Eat all mouse clicks on the widget. */ +// return true_EventResult; +// } + return ignored_EventResult; +} + +static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { + iWidget *w = as_Widget(d); + /* Resize according to width immediately. */ + if (d->lastUpdateWidth != w->rect.size.x) { + d->inFlags |= needUpdateBuffer_InputWidgetFlag; + if (d->inFlags & isUrl_InputWidgetFlag) { + /* Restore/omit the default scheme if necessary. */ + setText_InputWidget(d, text_InputWidget(d)); } + updateAllLinesAndResizeHeight_InputWidget_(d); + d->lastUpdateWidth = w->rect.size.x; + } + if (isCommand_Widget(w, ev, "focus.gained")) { + begin_InputWidget(d); + return iFalse; + } + else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") || + isCommand_UserEvent(ev, "window.focus.gained"))) { + startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained")); + d->cursorVis = 1; + refresh_Widget(d); + return iFalse; + } + else if (isCommand_UserEvent(ev, "keyroot.changed")) { + d->inFlags |= needUpdateBuffer_InputWidgetFlag; + } + else if (isCommand_UserEvent(ev, "lang.changed")) { + set_String(&d->hint, &d->srcHint); + translate_Lang(&d->hint); + return iFalse; + } + else if (isCommand_Widget(w, ev, "focus.lost")) { + end_InputWidget(d, iTrue); + return iFalse; + } + else if ((isCommand_UserEvent(ev, "copy") || isCommand_UserEvent(ev, "input.copy")) && + isEditing_InputWidget_(d)) { + copy_InputWidget_(d, argLabel_Command(command_UserEvent(ev), "cut")); + return iTrue; + } + else if (isCommand_UserEvent(ev, "input.paste") && isEditing_InputWidget_(d)) { + paste_InputWidget_(d); return iTrue; } + else if (isCommand_UserEvent(ev, "theme.changed")) { + if (d->buffered) { + d->inFlags |= needUpdateBuffer_InputWidgetFlag; + } + return iFalse; + } +// else if (isCommand_UserEvent(ev, "keyboard.changed")) { +// if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) { +// iRect rect = bounds_Widget(w); +// rect.pos.y -= value_Anim(&get_Window()->rootOffset); +// const iInt2 visRoot = visibleSize_Root(w->root); +// if (bottom_Rect(rect) > visRoot.y) { +// setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250); +// } +// } +// return iFalse; +// } + else if (isCommand_UserEvent(ev, "text.insert")) { + pushUndo_InputWidget_(d); + deleteMarked_InputWidget_(d); + insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev))); + contentsWereChanged_InputWidget_(d); + return iTrue; + } + else if (isCommand_Widget(w, ev, "input.backup")) { + if (d->inFlags & needBackup_InputWidgetFlag) { + saveBackup_InputWidget_(d); + } + return iTrue; + } + else if (isMetricsChange_UserEvent(ev)) { + updateMetrics_InputWidget_(d); + // updateLinesAndResize_InputWidget_(d); + } + else if (isFocused_Widget(d) && isCommand_UserEvent(ev, "copy")) { + copy_InputWidget_(d, iFalse); + return iTrue; + } + /* Click behavior depends on device type. */ { + const int mbResult = (deviceType_App() == desktop_AppDeviceType + ? processPointerEvents_InputWidget_(d, ev) + : processTouchEvents_InputWidget_(d, ev)); + if (mbResult) { + return mbResult >> 1; + } + } if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { return iTrue; } @@ -1884,6 +2188,8 @@ struct Impl_MarkPainter { const iInputLine * line; iInt2 pos; iRanges mark; + iRect firstMarkRect; + iRect lastMarkRect; }; static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance, @@ -1922,7 +2228,11 @@ static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int or } rect.size.x = iMax(gap_UI / 3, rect.size.x); mp->pos.y += lineHeight_Text(mp->d->font); - fillRect_Paint(mp->paint, rect, uiMarked_ColorId); + fillRect_Paint(mp->paint, rect, uiMarked_ColorId | opaque_ColorId); + if (deviceType_App() != desktop_AppDeviceType) { + if (isEmpty_Rect(mp->firstMarkRect)) mp->firstMarkRect = rect; + mp->lastMarkRect = rect; + } return iTrue; } @@ -1962,6 +2272,7 @@ static void draw_InputWidget_(const iInputWidget *d) { }; const iRangei visLines = visibleLineRange_InputWidget_(d); const int visLineOffsetY = visLineOffsetY_InputWidget_(d); + iRect markerRects[2]; /* If buffered, just draw the buffered copy. */ if (d->buffered && !isFocused) { /* Most input widgets will use this, since only one is focused at a time. */ @@ -1977,7 +2288,7 @@ static void draw_InputWidget_(const iInputWidget *d) { .paint = &p, .d = d, .contentBounds = contentBounds, - .mark = mark_InputWidget_(d) + .mark = mark_InputWidget_(d), }; wrapText.context = ▮ wrapText.wrapFunc = isFocused ? draw_MarkPainter_ : NULL; /* mark is drawn under each line of text */ @@ -1988,11 +2299,14 @@ static void draw_InputWidget_(const iInputWidget *d) { marker.pos = drawPos; addv_I2(&drawPos, draw_WrapText(&wrapText, d->font, drawPos, fg).advance); /* lines end with \n */ } + markerRects[0] = marker.firstMarkRect; + markerRects[1] = marker.lastMarkRect; wrapText.wrapFunc = NULL; wrapText.context = NULL; } /* Draw the insertion point. */ - if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y)) { + if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) && + isEmpty_Range(&d->mark)) { iInt2 curSize; iRangecc cursorChar = iNullRange; int visWrapsAbove = 0; @@ -2040,6 +2354,11 @@ static void draw_InputWidget_(const iInputWidget *d) { } } unsetClip_Paint(&p); + if (!isEmpty_Rect(markerRects[0])) { + for (int i = 0; i < 2; ++i) { + drawPin_Paint(&p, markerRects[i], i, uiTextCaution_ColorId); + } + } drawChildren_Widget(w); } diff --git a/src/ui/paint.c b/src/ui/paint.c index 71ebb81d..89de47d4 100644 --- a/src/ui/paint.c +++ b/src/ui/paint.c @@ -33,7 +33,8 @@ iLocalDef SDL_Renderer *renderer_Paint_(const iPaint *d) { static void setColor_Paint_(const iPaint *d, int color) { const iColor clr = get_Color(color & mask_ColorId); - SDL_SetRenderDrawColor(renderer_Paint_(d), clr.r, clr.g, clr.b, clr.a * d->alpha / 255); + SDL_SetRenderDrawColor(renderer_Paint_(d), clr.r, clr.g, clr.b, + (color & opaque_ColorId ? 255 : clr.a) * d->alpha / 255); } void init_Paint(iPaint *d) { @@ -186,6 +187,22 @@ void drawLines_Paint(const iPaint *d, const iInt2 *points, size_t n, int color) free(offsetPoints); } +void drawPin_Paint(iPaint *d, iRect rangeRect, int dir, int pinColor) { + const int height = height_Rect(rangeRect); + iRect pin; + if (dir == 0) { + pin = (iRect){ add_I2(topLeft_Rect(rangeRect), init_I2(-gap_UI / 4, -gap_UI)), + init_I2(gap_UI / 2, height + gap_UI) }; + } + else { + pin = (iRect){ addX_I2(topRight_Rect(rangeRect), -gap_UI / 4), + init_I2(gap_UI / 2, height + gap_UI) }; + } + fillRect_Paint(d, pin, pinColor); + fillRect_Paint(d, initCentered_Rect(dir == 0 ? topMid_Rect(pin) : bottomMid_Rect(pin), + init1_I2(gap_UI * 2)), pinColor); +} + iInt2 size_SDLTexture(SDL_Texture *d) { iInt2 size; SDL_QueryTexture(d, NULL, NULL, &size.x, &size.y); diff --git a/src/ui/paint.h b/src/ui/paint.h index e6701635..e894b62f 100644 --- a/src/ui/paint.h +++ b/src/ui/paint.h @@ -63,4 +63,6 @@ iLocalDef void drawVLine_Paint(const iPaint *d, iInt2 pos, int len, int color) { drawLine_Paint(d, pos, addY_I2(pos, len), color); } +void drawPin_Paint (iPaint *, iRect rangeRect, int dir, int pinColor); + iInt2 size_SDLTexture (SDL_Texture *); diff --git a/src/ui/root.c b/src/ui/root.c index 59f98aa4..91f9fbb3 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -1401,23 +1401,38 @@ void createUserInterface_Root(iRoot *d) { { "${menu.closetab.other}", 0, 0, "tabs.close toleft:1 toright:1" }, { barLeftArrow_Icon " ${menu.closetab.left}", 0, 0, "tabs.close toleft:1" }, { barRightArrow_Icon " ${menu.closetab.right}", 0, 0, "tabs.close toright:1" }, - }, + }, 6); iWidget *barMenu = makeMenu_Widget(root, (iMenuItem[]){ { leftHalf_Icon " ${menu.sidebar.left}", 0, 0, "sidebar.toggle" }, { rightHalf_Icon " ${menu.sidebar.right}", 0, 0, "sidebar2.toggle" }, - }, + }, deviceType_App() == phone_AppDeviceType ? 1 : 2); iWidget *clipMenu = makeMenu_Widget(root, - (iMenuItem[]){ - { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, - { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, - { "---" }, - { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, - }, - 4); +#if defined (iPlatformMobile) + (iMenuItem[]){ + { ">>>" scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, + { ">>>" clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, + { ">>>" clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, + { "---" }, + { ">>>" delete_Icon " " uiTextCaution_ColorEscape "${menu.delete}", 0, 0, "input.delete" }, + { ">>>" select_Icon " ${menu.selectall}", 0, 0, "input.selectall" }, + { ">>>" undo_Icon " ${menu.undo}", 0, 0, "input.undo" }, + }, 7); +#else + (iMenuItem[]){ + { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, + { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, + { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, + { "---" }, + { delete_Icon " " uiTextCaution_ColorEscape "${menu.delete}", 0, 0, "input.delete" }, + { undo_Icon " ${menu.undo}", 0, 0, "input.undo" }, + { "---" }, + { select_Icon " ${menu.selectall}", 0, 0, "input.selectall" }, + }, 8); +#endif iWidget *splitMenu = makeMenu_Widget(root, (iMenuItem[]){ { "${menu.split.merge}", '1', 0, "ui.split arg:0" }, { "${menu.split.swap}", SDLK_x, 0, "ui.split swap:1" }, diff --git a/src/ui/touch.c b/src/ui/touch.c index 5fc8f245..61882739 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c @@ -293,6 +293,7 @@ static void update_TouchState_(void *ptr) { } if (elapsed > 50 && !touch->isTapBegun) { /* Looks like a possible tap. */ + touchState_()->currentTouchPos = initF3_I2(touch->pos[0]); dispatchNotification_Touch_(touch, widgetTapBegins_UserEventCode); dispatchMotion_Touch_(touch->pos[0], 0); refresh_Widget(touch->affinity); @@ -471,13 +472,13 @@ iBool processEvent_Touch(const SDL_Event *ev) { } iTouchState *d = touchState_(); iWindow *window = get_Window(); - if (!isFinished_Anim(&window->rootOffset)) { - return iFalse; - } +// if (!isFinished_Anim(&window->rootOffset)) { +// return iFalse; +// } const iInt2 rootSize = size_Window(window); const SDL_TouchFingerEvent *fing = &ev->tfinger; const iFloat3 pos = add_F3(init_F3(fing->x * rootSize.x, fing->y * rootSize.y, 0), /* pixels */ - init_F3(0, -value_Anim(&window->rootOffset), 0)); + init_F3(0, 0 /*-value_Anim(&window->rootOffset)*/, 0)); const uint32_t nowTime = SDL_GetTicks(); if (ev->type == SDL_FINGERDOWN) { /* Register the new touch. */ diff --git a/src/ui/util.c b/src/ui/util.c index 48ed41a6..cfa8152c 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -706,23 +706,36 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { setFrameColor_Widget(menu, uiSeparator_ColorId); } iBool haveIcons = iFalse; + iWidget *horizGroup = NULL; for (size_t i = 0; i < n; ++i) { const iMenuItem *item = &items[i]; if (!item->label) { break; } - if (equal_CStr(item->label, "---")) { + const char *labelText = item->label; + if (!startsWith_CStr(labelText, ">>>")) { + horizGroup = NULL; + } + if (equal_CStr(labelText, "---")) { addChild_Widget(menu, iClob(makeMenuSeparator_())); } else { iBool isInfo = iFalse; - const char *labelText = item->label; + if (startsWith_CStr(labelText, ">>>")) { + labelText += 3; + if (!horizGroup) { + horizGroup = makeHDiv_Widget(); + setFlags_Widget(horizGroup, resizeHeightOfChildren_WidgetFlag, iFalse); + setFlags_Widget(horizGroup, arrangeHeight_WidgetFlag, iTrue); + addChild_Widget(menu, iClob(horizGroup)); + } + } if (startsWith_CStr(labelText, "```")) { labelText += 3; isInfo = iTrue; } iLabelWidget *label = addChildFlags_Widget( - menu, + horizGroup ? horizGroup : menu, iClob(newKeyMods_LabelWidget(labelText, item->key, item->kmods, item->command)), noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | drawKey_WidgetFlag | itemFlags); @@ -766,6 +779,34 @@ void openMenu_Widget(iWidget *d, iInt2 windowCoord) { openMenuFlags_Widget(d, windowCoord, iTrue); } +static void updateMenuItemFonts_Widget_(iWidget *d) { + const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); + const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0; + iForEach(ObjectList, i, children_Widget(d)) { + if (isInstance_Object(i.object, &Class_LabelWidget)) { + iLabelWidget *label = i.object; + const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); + if (isWrapped_LabelWidget(label)) { + continue; + } + if (deviceType_App() == desktop_AppDeviceType) { + setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); + } + else if (isPortraitPhone) { + if (!isSlidePanel) { + setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); + } + } + else { + setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId); + } + } + else if (childCount_Widget(i.object)) { + updateMenuItemFonts_Widget_(i.object); + } + } +} + void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { const iRect rootRect = rect_Root(d->root); const iInt2 rootSize = rootRect.size; @@ -788,28 +829,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { } d->rect.size.x = rootSize.x; } - /* Update item fonts. */ { - iForEach(ObjectList, i, children_Widget(d)) { - if (isInstance_Object(i.object, &Class_LabelWidget)) { - iLabelWidget *label = i.object; - const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); - if (isWrapped_LabelWidget(label)) { - continue; - } - if (deviceType_App() == desktop_AppDeviceType) { - setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); - } - else if (isPortraitPhone) { - if (!isSlidePanel) { - setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); - } - } - else { - setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId); - } - } - } - } + updateMenuItemFonts_Widget_(d); arrange_Widget(d); if (isPortraitPhone) { if (isSlidePanel) { diff --git a/src/ui/widget.c b/src/ui/widget.c index 0765bf9f..1c0fb271 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c @@ -880,9 +880,9 @@ iInt2 localToWindow_Widget(const iWidget *d, iInt2 localCoord) { applyVisualOffset_Widget_(w, &pos); addv_I2(&window, pos); } -#if defined (iPlatformMobile) - window.y += value_Anim(&get_Window()->rootOffset); -#endif +//#if defined (iPlatformMobile) +// window.y += value_Anim(&get_Window()->rootOffset); +//#endif return window; } @@ -1072,23 +1072,33 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { } iBool scrollOverflow_Widget(iWidget *d, int delta) { - iRect bounds = boundsWithoutVisualOffset_Widget(d); - const iInt2 rootSize = size_Root(d->root); - const iRect winRect = safeRect_Root(d->root); - const int yTop = top_Rect(winRect); - const int yBottom = bottom_Rect(winRect); + iRect bounds = boundsWithoutVisualOffset_Widget(d); +// const iInt2 rootSize = size_Root(d->root); + const iRect winRect = adjusted_Rect(safeRect_Root(d->root), + zero_I2(), + init_I2(0, -get_Window()->keyboardHeight)); + const int yTop = top_Rect(winRect); + const int yBottom = bottom_Rect(winRect); if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) { return iFalse; /* fits inside just fine */ } //const int safeBottom = rootSize.y - yBottom; - bounds.pos.y += delta; - const iRangei range = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; + iRangei validPosRange = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; + if (validPosRange.start > validPosRange.end) { + validPosRange.start = validPosRange.end; /* no room to scroll */ + } + if (delta) { + if (delta < 0 && bounds.pos.y < validPosRange.start) { + delta = 0; + } + if (delta > 0 && bounds.pos.y > validPosRange.end) { + delta = 0; + } + bounds.pos.y += delta; // printf("range: %d ... %d\n", range.start, range.end); - if (range.start >= range.end) { - bounds.pos.y = range.end; } else { - bounds.pos.y = iClamp(bounds.pos.y, range.start, range.end); + bounds.pos.y = iClamp(bounds.pos.y, validPosRange.start, validPosRange.end); } // if (delta >= 0) { // bounds.pos.y = iMin(bounds.pos.y, yTop); @@ -1454,7 +1464,7 @@ void setDrawBufferEnabled_Widget(iWidget *d, iBool enable) { static void beginBufferDraw_Widget_(const iWidget *d) { if (d->drawBuf) { - printf("[%p] drawbuffer update %d\n", d, d->drawBuf->isValid); +// printf("[%p] drawbuffer update %d\n", d, d->drawBuf->isValid); if (d->drawBuf->isValid) { iAssert(!isEqual_I2(d->drawBuf->size, boundsForDraw_Widget_(d).size)); // printf(" drawBuf:%dx%d boundsForDraw:%dx%d\n", @@ -1503,7 +1513,7 @@ void draw_Widget(const iWidget *d) { endBufferDraw_Widget_(d); } if (d->drawBuf) { - iAssert(d->drawBuf->isValid); + //iAssert(d->drawBuf->isValid); const iRect bounds = bounds_Widget(d); SDL_RenderCopy(renderer_Window(get_Window()), d->drawBuf->texture, NULL, &(SDL_Rect){ bounds.pos.x, bounds.pos.y, diff --git a/src/ui/window.c b/src/ui/window.c index 8034d858..ed2ec024 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -421,7 +421,7 @@ void init_Window(iWindow *d, iRect rect) { d->ignoreClick = iFalse; d->focusGainedAt = 0; d->keyboardHeight = 0; - init_Anim(&d->rootOffset, 0.0f); +// init_Anim(&d->rootOffset, 0.0f); uint32_t flags = 0; #if defined (iPlatformAppleDesktop) SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); @@ -1215,10 +1215,10 @@ iBool isOpenGLRenderer_Window(void) { void setKeyboardHeight_Window(iWindow *d, int height) { if (d->keyboardHeight != height) { d->keyboardHeight = height; - if (height == 0) { - setFlags_Anim(&d->rootOffset, easeBoth_AnimFlag, iTrue); - setValue_Anim(&d->rootOffset, 0, 250); - } +// if (height == 0) { +// setFlags_Anim(&d->rootOffset, easeBoth_AnimFlag, iTrue); +// setValue_Anim(&d->rootOffset, 0, 250); +// } postCommandf_App("keyboard.changed arg:%d", height); postRefresh_App(); } diff --git a/src/ui/window.h b/src/ui/window.h index 63f7e5f2..a5b8f137 100644 --- a/src/ui/window.h +++ b/src/ui/window.h @@ -98,7 +98,7 @@ struct Impl_Window { SDL_Cursor * cursors[SDL_NUM_SYSTEM_CURSORS]; SDL_Cursor * pendingCursor; int loadAnimTimer; - iAnim rootOffset; +// iAnim rootOffset; int keyboardHeight; /* mobile software keyboards */ }; -- cgit v1.2.3 From 5119a72481a2c752972470ea7b488a5c9e58b720 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 14 Sep 2021 19:55:20 +0300 Subject: UploadWidget: Identity selection (mobile) --- po/en.po | 11 +++++- res/lang/de.bin | Bin 24222 -> 24289 bytes res/lang/en.bin | Bin 22823 -> 22890 bytes res/lang/es.bin | Bin 25346 -> 25420 bytes res/lang/fi.bin | Bin 25351 -> 25425 bytes res/lang/fr.bin | Bin 26320 -> 26394 bytes res/lang/ia.bin | Bin 24950 -> 25017 bytes res/lang/ie.bin | Bin 24705 -> 24779 bytes res/lang/pl.bin | Bin 25883 -> 25950 bytes res/lang/ru.bin | Bin 37602 -> 37676 bytes res/lang/sr.bin | Bin 37259 -> 37333 bytes res/lang/tok.bin | Bin 23141 -> 23215 bytes res/lang/zh_Hans.bin | Bin 21902 -> 21969 bytes res/lang/zh_Hant.bin | Bin 22087 -> 22154 bytes src/defs.h | 1 + src/gmrequest.c | 12 +++++-- src/gmrequest.h | 2 ++ src/ui/mobile.c | 12 +++++++ src/ui/mobile.h | 2 ++ src/ui/uploadwidget.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++---- 20 files changed, 124 insertions(+), 10 deletions(-) (limited to 'res') diff --git a/po/en.po b/po/en.po index 96026528..76045819 100644 --- a/po/en.po +++ b/po/en.po @@ -927,6 +927,15 @@ msgstr "Upload Page with Titan…" msgid "heading.upload" msgstr "UPLOAD WITH TITAN" +msgid "upload.id" +msgstr "Identity:" + +msgid "dlg.upload.id.none" +msgstr "None" + +msgid "dlg.upload.id.default" +msgstr "Default" + msgid "heading.upload.text" msgstr "Text" @@ -949,7 +958,7 @@ msgid "upload.mime" msgstr "MIME type:" msgid "upload.token" -msgstr "Upload token:" +msgstr "Token:" msgid "hint.upload.token" msgstr "see server's instructions" diff --git a/res/lang/de.bin b/res/lang/de.bin index f5a6b07e..e03b96b5 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 df3c4025..8d6d5e2e 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 9f5a169f..2ed64493 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index ac3b99ef..0ec7cb1c 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 7d40e32c..9b96418a 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index d10ab85e..c916a78a 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 14349637..202c26a2 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 9ddd137f..e009d285 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 366c6ee5..b4393e88 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 870b0950..1972d923 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 dac5fb33..758b0442 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 3407c485..9d00b171 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 b9001e2d..5278c7b2 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/defs.h b/src/defs.h index c3a23596..edaaecde 100644 --- a/src/defs.h +++ b/src/defs.h @@ -112,6 +112,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { #define star_Icon "\u2605" #define whiteStar_Icon "\u2606" #define person_Icon "\U0001f464" +#define key_Icon "\U0001f511" #define download_Icon "\u2ba7" #define upload_Icon "\u2ba5" #define export_Icon "\U0001f4e4" diff --git a/src/gmrequest.c b/src/gmrequest.c index 00a02983..1a9e83a9 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c @@ -158,6 +158,7 @@ struct Impl_GmRequest { uint32_t id; iMutex * mtx; iGmCerts * certs; /* not owned */ + const iGmIdentity * identity; enum iGmRequestState state; iString url; iTitanData * titan; @@ -527,6 +528,7 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host, void init_GmRequest(iGmRequest *d, iGmCerts *certs) { d->mtx = new_Mutex(); d->id = add_Atomic(&idGen_, 1) + 1; + d->identity = NULL; d->resp = new_GmResponse(); d->isFilterEnabled = iTrue; d->isRespLocked = iFalse; @@ -582,6 +584,11 @@ void setUrl_GmRequest(iGmRequest *d, const iString *url) { the web. */ urlEncodePath_String(&d->url); urlEncodeSpaces_String(&d->url); + d->identity = identityForUrl_GmCerts(d->certs, &d->url); +} + +void setIdentity_GmRequest(iGmRequest *d, const iGmIdentity *id) { + d->identity = id; } static iBool isTitan_GmRequest_(const iGmRequest *d) { @@ -902,9 +909,8 @@ void submit_GmRequest(iGmRequest *d) { } d->state = receivingHeader_GmRequestState; d->req = new_TlsRequest(); - const iGmIdentity *identity = identityForUrl_GmCerts(d->certs, &d->url); - if (identity) { - setCertificate_TlsRequest(d->req, identity->cert); + if (d->identity) { + setCertificate_TlsRequest(d->req, d->identity->cert); } iConnect(TlsRequest, d->req, readyRead, d, readIncoming_GmRequest_); iConnect(TlsRequest, d->req, sent, d, bytesSent_GmRequest_); diff --git a/src/gmrequest.h b/src/gmrequest.h index 97b23f3c..a377ac91 100644 --- a/src/gmrequest.h +++ b/src/gmrequest.h @@ -28,6 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "gmutil.h" iDeclareType(GmCerts) +iDeclareType(GmIdentity) iDeclareType(GmResponse) enum iGmCertFlag { @@ -69,6 +70,7 @@ typedef void (*iGmRequestProgressFunc)(iGmRequest *, size_t current, size_t tota void enableFilters_GmRequest (iGmRequest *, iBool enable); void setUrl_GmRequest (iGmRequest *, const iString *url); +void setIdentity_GmRequest (iGmRequest *, const iGmIdentity *id); void setTitanData_GmRequest (iGmRequest *, const iString *mime, const iBlock *payload, const iString *token); void setSendProgressFunc_GmRequest(iGmRequest *, iGmRequestProgressFunc func); diff --git a/src/ui/mobile.c b/src/ui/mobile.c index 459e9ce7..05e88815 100644 --- a/src/ui/mobile.c +++ b/src/ui/mobile.c @@ -156,6 +156,18 @@ static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) return iFalse; } +size_t currentPanelIndex_Mobile(const iWidget *panels) { + size_t index = 0; + iConstForEach(ObjectList, i, children_Widget(findChild_Widget(panels, "detailstack"))) { + const iWidget *child = i.object; + if (isVisible_Widget(child)) { + return index; + } + index++; + } + return iInvalidPos; +} + static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { const iBool isPortrait = !isSideBySideLayout_(); if (equal_Command(cmd, "panel.open")) { diff --git a/src/ui/mobile.h b/src/ui/mobile.h index e1131953..957c0e42 100644 --- a/src/ui/mobile.h +++ b/src/ui/mobile.h @@ -39,6 +39,8 @@ void initPanels_Mobile (iWidget *panels, iWidget *parentWidget, const iMenuItem *itemsNullTerminated, const iMenuItem *actions, size_t numActions); +size_t currentPanelIndex_Mobile (const iWidget *panels); + enum iTransitionFlags { incoming_TransitionFlag = iBit(1), dirMask_TransitionFlag = iBit(2) | iBit(3), diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c index c3f71ab9..34cace08 100644 --- a/src/ui/uploadwidget.c +++ b/src/ui/uploadwidget.c @@ -30,12 +30,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "gmrequest.h" #include "sitespec.h" #include "window.h" +#include "gmcerts.h" #include "app.h" #include #include iDefineObjectConstruction(UploadWidget) + +enum iUploadIdentity { + none_UploadIdentity, + defaultForUrl_UploadIdentity, + dropdown_UploadIdentity, +}; struct Impl_UploadWidget { iWidget widget; @@ -52,6 +59,8 @@ struct Impl_UploadWidget { iLabelWidget * counter; iString filePath; size_t fileSize; + enum iUploadIdentity idMode; + iBlock idFingerprint; iAtomicInt isRequestUpdated; }; @@ -80,6 +89,26 @@ static void updateInputMaxHeight_UploadWidget_(iUploadWidget *d) { (avail - inputPos.y) / lineHeight_Text(font_InputWidget(d->input)))); } +static const iArray *makeIdentityItems_UploadWidget_(const iUploadWidget *d) { + iArray *items = collectNew_Array(sizeof(iMenuItem)); + const iGmIdentity *urlId = identityForUrl_GmCerts(certs_App(), &d->url); + pushBack_Array(items, &(iMenuItem){ format_CStr("${dlg.upload.id.default} (%s)", + urlId ? cstr_String(name_GmIdentity(urlId)) : "${dlg.upload.id.none}"), + 0, 0, "upload.setid arg:1" }); + pushBack_Array(items, &(iMenuItem){ "${dlg.upload.id.none}", 0, 0, "upload.setid arg:0" }); + pushBack_Array(items, &(iMenuItem){ "---" }); + iConstForEach(PtrArray, i, listIdentities_GmCerts(certs_App(), NULL, NULL)) { + const iGmIdentity *id = i.ptr; + pushBack_Array( + items, + &(iMenuItem){ cstr_String(name_GmIdentity(id)), 0, 0, + format_CStr("upload.setid fp:%s", + cstrCollect_String(hexEncode_Block(&id->fingerprint))) }); + } + pushBack_Array(items, &(iMenuItem){ NULL }); + return items; +} + void init_UploadWidget(iUploadWidget *d) { iWidget *w = as_Widget(d); init_Widget(w); @@ -90,6 +119,8 @@ void init_UploadWidget(iUploadWidget *d) { d->request = NULL; init_String(&d->filePath); d->fileSize = 0; + d->idMode = defaultForUrl_UploadIdentity; + init_Block(&d->idFingerprint, 0); const iMenuItem actions[] = { { "${upload.port}", 0, 0, "upload.setport" }, { "---" }, @@ -117,11 +148,11 @@ void init_UploadWidget(iUploadWidget *d) { initPanels_Mobile(w, NULL, (iMenuItem[]){ { "title id:heading.upload" }, { "label id:upload.info" }, -// { "padding" }, { "panel id:dlg.upload.text icon:0x1f5b9", 0, 0, (const void *) textItems }, { "panel id:dlg.upload.file icon:0x1f4c1", 0, 0, (const void *) fileItems }, { "padding" }, - { "input id:upload.token hint:hint.upload.token" }, + { "dropdown id:upload.id icon:0x1f464", 0, 0, constData_Array(makeIdentityItems_UploadWidget_(d)) }, + { "input id:upload.token hint:hint.upload.token icon:0x1f511" }, { NULL } }, actions, iElemCount(actions)); d->info = findChild_Widget(w, "upload.info"); @@ -205,7 +236,8 @@ void init_UploadWidget(iUploadWidget *d) { updateInputMaxHeight_UploadWidget_(d); } -void deinit_UploadWidget(iUploadWidget *d) { +void deinit_UploadWidget(iUploadWidget *d) { + deinit_Block(&d->idFingerprint); deinit_String(&d->filePath); deinit_String(&d->url); deinit_String(&d->originalUrl); @@ -263,6 +295,15 @@ static void requestFinished_UploadWidget_(iUploadWidget *d, iGmRequest *req) { postCommand_Widget(d, "upload.request.finished reqid:%u", id_GmRequest(req)); } +static void updateIdentityDropdown_UploadWidget_(iUploadWidget *d) { + updateDropdownSelection_LabelWidget( + findChild_Widget(as_Widget(d), "upload.id"), + d->idMode == none_UploadIdentity ? " arg:0" + : d->idMode == defaultForUrl_UploadIdentity + ? " arg:1" + : format_CStr(" fp:%s", cstrCollect_String(hexEncode_Block(&d->idFingerprint)))); +} + static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { iWidget *w = as_Widget(d); const char *cmd = command_UserEvent(ev); @@ -290,9 +331,36 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { } return iTrue; } + if (isCommand_Widget(w, ev, "upload.setid")) { + if (hasLabel_Command(cmd, "fp")) { + set_Block(&d->idFingerprint, collect_Block(hexDecode_Rangecc(range_Command(cmd, "fp")))); + d->idMode = dropdown_UploadIdentity; + } + else if (arg_Command(cmd)) { + clear_Block(&d->idFingerprint); + d->idMode = defaultForUrl_UploadIdentity; + } + else { + clear_Block(&d->idFingerprint); + d->idMode = none_UploadIdentity; + } + updateIdentityDropdown_UploadWidget_(d); + return iTrue; + } if (isCommand_Widget(w, ev, "upload.accept")) { - iWidget * tabs = findChild_Widget(w, "upload.tabs"); - const int tabIndex = tabPageIndex_Widget(tabs, currentTabPage_Widget(tabs)); + iBool isText; + iWidget *tabs = findChild_Widget(w, "upload.tabs"); + if (tabs) { + const size_t tabIndex = tabPageIndex_Widget(tabs, currentTabPage_Widget(tabs)); + isText = (tabIndex == 0); + } + else { + const size_t panelIndex = currentPanelIndex_Mobile(w); + if (panelIndex == iInvalidPos) { + return iTrue; + } + isText = (currentPanelIndex_Mobile(w) == 0); + } /* Make a GmRequest and send the data. */ iAssert(d->request == NULL); iAssert(!isEmpty_String(&d->url)); @@ -300,7 +368,21 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { setSendProgressFunc_GmRequest(d->request, updateProgress_UploadWidget_); setUserData_Object(d->request, d); setUrl_GmRequest(d->request, &d->url); - if (tabIndex == 0) { + switch (d->idMode) { + case defaultForUrl_UploadIdentity: + break; /* GmRequest handles it */ + case none_UploadIdentity: + setIdentity_GmRequest(d->request, NULL); + signOut_GmCerts(certs_App(), url_GmRequest(d->request)); + break; + case dropdown_UploadIdentity: { + iGmIdentity *ident = findIdentity_GmCerts(certs_App(), &d->idFingerprint); + setIdentity_GmRequest(d->request, ident); + signIn_GmCerts(certs_App(), ident, url_GmRequest(d->request)); + break; + } + } + if (isText) { /* Uploading text. */ setTitanData_GmRequest(d->request, collectNewCStr_String("text/plain"), -- cgit v1.2.3 From 4651a6bef1c89542d01f9e14096208628e6ed24b Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 17 Sep 2021 07:12:58 +0300 Subject: Mobile: Updated a few strings --- po/en.po | 8 ++++++-- res/lang/de.bin | Bin 24289 -> 24335 bytes res/lang/en.bin | Bin 22890 -> 22936 bytes res/lang/es.bin | Bin 25420 -> 25466 bytes res/lang/fi.bin | Bin 25425 -> 25471 bytes res/lang/fr.bin | Bin 26394 -> 26440 bytes res/lang/ia.bin | Bin 25017 -> 25063 bytes res/lang/ie.bin | Bin 24779 -> 24825 bytes res/lang/pl.bin | Bin 25950 -> 25996 bytes res/lang/ru.bin | Bin 37676 -> 37722 bytes res/lang/sr.bin | Bin 37333 -> 37379 bytes res/lang/tok.bin | Bin 23215 -> 23261 bytes res/lang/zh_Hans.bin | Bin 21969 -> 22015 bytes res/lang/zh_Hant.bin | Bin 22154 -> 22200 bytes 14 files changed, 6 insertions(+), 2 deletions(-) (limited to 'res') diff --git a/po/en.po b/po/en.po index 76045819..63aeb75f 100644 --- a/po/en.po +++ b/po/en.po @@ -253,7 +253,7 @@ msgstr "Preferences…" # used for Preferences on mobile msgid "menu.settings" -msgstr "Settings…" +msgstr "Settings" msgid "menu.help" msgstr "Help" @@ -838,6 +838,10 @@ msgstr "PASTED FROM CLIPBOARD" msgid "heading.certimport.dropped" msgstr "DROPPED FILE" +# button in the mobile New Identity dialog +msgid "dlg.certimport.pickfile" +msgstr "Import Certificate/Key File" + msgid "dlg.certimport.import" msgstr "Import" @@ -988,7 +992,7 @@ msgstr "Upload a File" # used on mobile msgid "dlg.upload.pickfile" -msgstr "Select File…" +msgstr "Select File" msgid "heading.translate" msgstr "TRANSLATE PAGE" diff --git a/res/lang/de.bin b/res/lang/de.bin index e03b96b5..060745b2 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 8d6d5e2e..d30b1cd3 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 2ed64493..c03b6960 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 0ec7cb1c..4898c850 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 9b96418a..65a17bfe 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index c916a78a..93904f71 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 202c26a2..769f38b1 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index e009d285..36c6e552 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 b4393e88..e1479727 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 1972d923..90e04616 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 758b0442..b0be44d4 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 9d00b171..d2685429 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 5278c7b2..5319e518 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ -- cgit v1.2.3 From 53f0596cfd6060cd63cc8e6f92f981672da97ee2 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 24 Sep 2021 16:00:22 +0300 Subject: Bookmark folders The user can now create bookmark folders, and drag bookmarks into them. Folders can also be nested. The bookmark sorting menu item sorts inside the chosen folder/root. Remote bookmark sources appear also as folders, although they cannot be sorted/edited. IssueID #339 --- po/compile.py | 4 +- po/en.po | 23 ++++++- res/lang/de.bin | Bin 24335 -> 24591 bytes res/lang/en.bin | Bin 22936 -> 23192 bytes res/lang/es.bin | Bin 25466 -> 25722 bytes res/lang/fi.bin | Bin 25471 -> 25727 bytes res/lang/fr.bin | Bin 26440 -> 26696 bytes res/lang/ia.bin | Bin 25063 -> 25319 bytes res/lang/ie.bin | Bin 24825 -> 25081 bytes res/lang/pl.bin | Bin 25996 -> 26252 bytes res/lang/ru.bin | Bin 37722 -> 37978 bytes res/lang/sr.bin | Bin 37379 -> 37635 bytes res/lang/tok.bin | Bin 23261 -> 23517 bytes res/lang/zh_Hans.bin | Bin 22015 -> 22271 bytes res/lang/zh_Hant.bin | Bin 22200 -> 22456 bytes src/app.c | 20 ++++++ src/bookmarks.c | 30 ++++++--- src/bookmarks.h | 13 ++-- src/defs.h | 2 + src/ui/keys.c | 1 + src/ui/listwidget.c | 30 +++++---- src/ui/lookupwidget.c | 3 + src/ui/sidebarwidget.c | 170 +++++++++++++++++++++++++++++++++++++++---------- src/ui/util.c | 8 +++ src/ui/window.c | 3 + 25 files changed, 247 insertions(+), 60 deletions(-) (limited to 'res') diff --git a/po/compile.py b/po/compile.py index 178342a6..2b0273b6 100755 --- a/po/compile.py +++ b/po/compile.py @@ -101,7 +101,9 @@ def parse_po(src): def compile_string(msg_id, msg_str): return msg_id.encode('utf-8') + bytes([0]) + \ msg_str.encode('utf-8') + bytes([0]) - + + +os.chdir(os.path.dirname(__file__)) if MODE == 'compile': BASE_STRINGS = {} diff --git a/po/en.po b/po/en.po index 63aeb75f..24525b03 100644 --- a/po/en.po +++ b/po/en.po @@ -225,7 +225,13 @@ msgid "menu.zoom.reset" msgstr "Reset Zoom" msgid "menu.view.split" -msgstr "Split View..." +msgstr "Split View…" + +msgid "menu.newfolder" +msgstr "New Folder…" + +msgid "menu.sort.alpha" +msgstr "Sort Alphabetically" msgid "menu.bookmarks.list" msgstr "List All Bookmarks" @@ -1189,6 +1195,18 @@ msgstr "Icon:" msgid "heading.bookmark.tags" msgstr "SPECIAL TAGS" +msgid "heading.addfolder" +msgstr "ADD FOLDER" + +msgid "dlg.addfolder.defaulttitle" +msgstr "New Folder" + +msgid "dlg.addfolder.prompt" +msgstr "Enter the name of the new folder:" + +msgid "dlg.addfolder" +msgstr "Add Folder" + msgid "heading.prefs" msgstr "PREFERENCES" @@ -1564,6 +1582,9 @@ msgstr "Next set of home row key links" msgid "keys.bookmark.add" msgstr "Add bookmark" +msgid "keys.bookmark.addfolder" +msgstr "Add bookmark folder" + msgid "keys.subscribe" msgstr "Subscribe to page" diff --git a/res/lang/de.bin b/res/lang/de.bin index 060745b2..4d48c61f 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 d30b1cd3..8782920f 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index c03b6960..fa86ea3a 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 4898c850..f23cbf09 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 65a17bfe..d4c5cf55 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 93904f71..ee6533ca 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 769f38b1..2bbc7ada 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 36c6e552..651a6231 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 e1479727..1a3f7213 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 90e04616..184699f1 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 b0be44d4..a77aa161 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index d2685429..1fe4392d 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 5319e518..244bb3a1 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 5ff93f2a..c6918eb5 100644 --- a/src/app.c +++ b/src/app.c @@ -2033,6 +2033,7 @@ static void resetFonts_App_(iApp *d) { iBool handleCommand_App(const char *cmd) { iApp *d = &app_; const iBool isFrozen = !d->window || d->window->isDrawFrozen; + /* TODO: Maybe break this up a little bit? There's a very long list of ifs here. */ if (equal_Command(cmd, "config.error")) { makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR", format_CStr("Error in config file: %s\n" @@ -2764,6 +2765,25 @@ iBool handleCommand_App(const char *cmd) { makeFeedSettings_Widget(findUrl_Bookmarks(d->bookmarks, url)); return iTrue; } + else if (equal_Command(cmd, "bookmarks.addfolder")) { + if (suffixPtr_Command(cmd, "value")) { + add_Bookmarks(d->bookmarks, NULL, collect_String(suffix_Command(cmd, "value")), NULL, 0); + postCommand_App("bookmarks.changed"); + } + else { + iWidget *dlg = makeValueInput_Widget(get_Root()->widget, + collectNewCStr_String(cstr_Lang("dlg.addfolder.defaulttitle")), + uiHeading_ColorEscape "${heading.addfolder}", "${dlg.addfolder.prompt}", + uiTextAction_ColorEscape "${dlg.addfolder}", "bookmarks.addfolder"); + setSelectAllOnFocus_InputWidget(findChild_Widget(dlg, "input"), iTrue); + } + return iTrue; + } + else if (equal_Command(cmd, "bookmarks.sort")) { + sort_Bookmarks(d->bookmarks, arg_Command(cmd), cmpTitleAscending_Bookmark); + postCommand_App("bookmarks.changed"); + return iTrue; + } else if (equal_Command(cmd, "bookmarks.reload.remote")) { fetchRemote_Bookmarks(bookmarks_App()); return iTrue; diff --git a/src/bookmarks.c b/src/bookmarks.c index 616e4632..f7691655 100644 --- a/src/bookmarks.c +++ b/src/bookmarks.c @@ -79,7 +79,7 @@ static int cmpTimeDescending_Bookmark_(const iBookmark **a, const iBookmark **b) return iCmp(seconds_Time(&(*b)->when), seconds_Time(&(*a)->when)); } -static int cmpTitleAscending_Bookmark_(const iBookmark **a, const iBookmark **b) { +int cmpTitleAscending_Bookmark(const iBookmark **a, const iBookmark **b) { return cmpStringCase_String(&(*a)->title, &(*b)->title); } @@ -250,7 +250,20 @@ static void load_BookmarkLoader(iBookmarkLoader *d, iFile *file) { iDefineTypeConstructionArgs(BookmarkLoader, (iBookmarks *b), b) /*----------------------------------------------------------------------------------------------*/ - + +static iBool isMatchingParent_Bookmark_(void *context, const iBookmark *bm) { + return bm->parentId == *(const uint32_t *) context; +} + +void sort_Bookmarks(iBookmarks *d, uint32_t parentId, iBookmarksCompareFunc cmp) { + lock_Mutex(d->mtx); + iConstForEach(PtrArray, i, list_Bookmarks(d, cmp, isMatchingParent_Bookmark_, &parentId)) { + iBookmark *bm = i.ptr; + bm->order = index_PtrArrayConstIterator(&i) + 1; + } + unlock_Mutex(d->mtx); +} + void load_Bookmarks(iBookmarks *d, const char *dirPath) { clear_Bookmarks(d); /* Load new .ini bookmarks, if present. */ @@ -258,11 +271,8 @@ void load_Bookmarks(iBookmarks *d, const char *dirPath) { if (!open_File(f, readOnly_FileMode | text_FileMode)) { /* As a fallback, try loading the v1.6 bookmarks file. */ loadOldFormat_Bookmarks(d, dirPath); - /* Set ordering based on titles. */ - iConstForEach(PtrArray, i, list_Bookmarks(d, cmpTitleAscending_Bookmark_, NULL, NULL)) { - iBookmark *bm = i.ptr; - bm->order = index_PtrArrayConstIterator(&i) + 1; - } + /* Old format has an implicit alphabetic sort order. */ + sort_Bookmarks(d, 0, cmpTitleAscending_Bookmark); return; } iBookmarkLoader loader; @@ -317,7 +327,9 @@ uint32_t add_Bookmarks(iBookmarks *d, const iString *url, const iString *title, iChar icon) { lock_Mutex(d->mtx); iBookmark *bm = new_Bookmark(); - set_String(&bm->url, canonicalUrl_String(url)); + if (url) { + set_String(&bm->url, canonicalUrl_String(url)); + } set_String(&bm->title, title); if (tags) { set_String(&bm->tags, tags); @@ -471,7 +483,7 @@ const iString *bookmarkListPage_Bookmarks(const iBookmarks *d, enum iBookmarkLis const iPtrArray *bmList = list_Bookmarks(d, listType == listByCreationTime_BookmarkListType ? cmpTimeDescending_Bookmark_ - : cmpTitleAscending_Bookmark_, + : cmpTitleAscending_Bookmark, NULL, NULL); iConstForEach(PtrArray, i, bmList) { diff --git a/src/bookmarks.h b/src/bookmarks.h index 0de930d7..40170062 100644 --- a/src/bookmarks.h +++ b/src/bookmarks.h @@ -53,7 +53,8 @@ struct Impl_Bookmark { int order; /* sort order */ }; -iLocalDef uint32_t id_Bookmark (const iBookmark *d) { return d->node.key; } +iLocalDef uint32_t id_Bookmark (const iBookmark *d) { return d->node.key; } +iLocalDef iBool isFolder_Bookmark (const iBookmark *d) { return isEmpty_String(&d->url); } iBool hasTag_Bookmark (const iBookmark *, const char *tag); void addTag_Bookmark (iBookmark *, const char *tag); @@ -73,11 +74,17 @@ iLocalDef void addOrRemoveTag_Bookmark(iBookmark *d, const char *tag, iBool add) } } +int cmpTitleAscending_Bookmark (const iBookmark **, const iBookmark **); +int cmpTree_Bookmark (const iBookmark **, const iBookmark **); + /*----------------------------------------------------------------------------------------------*/ iDeclareType(Bookmarks) iDeclareTypeConstruction(Bookmarks) +typedef iBool (*iBookmarksFilterFunc) (void *context, const iBookmark *); +typedef int (*iBookmarksCompareFunc) (const iBookmark **, const iBookmark **); + void clear_Bookmarks (iBookmarks *); void load_Bookmarks (iBookmarks *, const char *dirPath); void save_Bookmarks (const iBookmarks *, const char *dirPath); @@ -88,15 +95,13 @@ iBool remove_Bookmarks (iBookmarks *, uint32_t id); iBookmark * get_Bookmarks (iBookmarks *, uint32_t id); void reorder_Bookmarks (iBookmarks *, uint32_t id, int newOrder); iBool updateBookmarkIcon_Bookmarks(iBookmarks *, const iString *url, iChar icon); +void sort_Bookmarks (iBookmarks *, uint32_t parentId, iBookmarksCompareFunc cmp); void fetchRemote_Bookmarks (iBookmarks *); void requestFinished_Bookmarks (iBookmarks *, iGmRequest *req); iChar siteIcon_Bookmarks (const iBookmarks *, const iString *url); uint32_t findUrl_Bookmarks (const iBookmarks *, const iString *url); /* O(n) */ -typedef iBool (*iBookmarksFilterFunc) (void *context, const iBookmark *); -typedef int (*iBookmarksCompareFunc)(const iBookmark **, const iBookmark **); - iBool filterTagsRegExp_Bookmarks (void *regExp, const iBookmark *); /** diff --git a/src/defs.h b/src/defs.h index 01bf2b3d..f199fd2b 100644 --- a/src/defs.h +++ b/src/defs.h @@ -107,6 +107,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { #define rightArrow_Icon "\u279e" #define barLeftArrow_Icon "\u21a4" #define barRightArrow_Icon "\u21a6" +#define upDownArrow_Icon "\u21c5" #define clock_Icon "\U0001f553" #define pin_Icon "\U0001f588" #define star_Icon "\u2605" @@ -155,6 +156,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { #define return_Icon "\u23ce" #define undo_Icon "\u23ea" #define select_Icon "\u2b1a" +#define downAngle_Icon "\ufe40" #if defined (iPlatformApple) # define shift_Icon "\u21e7" diff --git a/src/ui/keys.c b/src/ui/keys.c index 6de30f57..30072572 100644 --- a/src/ui/keys.c +++ b/src/ui/keys.c @@ -213,6 +213,7 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] = { 46, { "${keys.link.homerow.hover}", 'h', 0, "document.linkkeys arg:1 hover:1" }, 0 }, { 47, { "${keys.link.homerow.next}", '.', 0, "document.linkkeys more:1" }, 0 }, { 50, { "${keys.bookmark.add}", 'd', KMOD_PRIMARY, "bookmark.add" }, 0 }, + { 51, { "${keys.bookmark.addfolder}", 'n', KMOD_SHIFT, "bookmarks.addfolder" }, 0 }, { 55, { "${keys.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, 0 }, { 60, { "${keys.findtext}", 'f', KMOD_PRIMARY, "focus.set id:find.input" }, 0 }, { 70, { "${keys.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, 0 }, diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c index a34f3d03..f896b493 100644 --- a/src/ui/listwidget.c +++ b/src/ui/listwidget.c @@ -331,8 +331,8 @@ static size_t resolveDragDestination_ListWidget_(const iListWidget *d, iInt2 dst const iRect rect = itemRect_ListWidget(d, index); const iRangei span = ySpan_Rect(rect); if (item->isDropTarget) { - const int pad = size_Range(&span) / 4; - if (dstPos.y >= span.start + pad && dstPos.y < span.end) { + const int pad = size_Range(&span) / 3; + if (dstPos.y >= span.start + pad && dstPos.y < span.end - pad) { *isOnto = iTrue; return index; } @@ -352,11 +352,13 @@ static iBool endDrag_ListWidget_(iListWidget *d, iInt2 endPos) { stop_Anim(&d->scrollY.pos); iBool isOnto; const size_t index = resolveDragDestination_ListWidget_(d, endPos, &isOnto); - if (isOnto) { - postCommand_Widget(d, "list.dragged arg:%zu onto:%zu", d->dragItem, index); - } - else if (index != d->dragItem) { - postCommand_Widget(d, "list.dragged arg:%zu before:%zu", d->dragItem, index); + if (index != d->dragItem) { + if (isOnto) { + postCommand_Widget(d, "list.dragged arg:%zu onto:%zu", d->dragItem, index); + } + else { + postCommand_Widget(d, "list.dragged arg:%zu before:%zu", d->dragItem, index); + } } invalidateItem_ListWidget(d, d->dragItem); d->dragItem = iInvalidPos; @@ -394,7 +396,7 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { } else if (d->dragItem != iInvalidPos) { /* Start scrolling if near the ends. */ - const int zone = d->itemHeight; + const int zone = 2 * d->itemHeight; const iRect bounds = bounds_Widget(w); float scrollSpeed = 0.0f; if (mousePos.y > bottom_Rect(bounds) - zone) { @@ -410,7 +412,7 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { } else { setValueSpeed_Anim(&d->scrollY.pos, scrollSpeed < 0 ? 0 : scrollMax_ListWidget_(d), - iAbs(scrollSpeed * gap_UI * 100)); + scrollSpeed * scrollSpeed * gap_UI * 400); refreshWhileScrolling_ListWidget_(d); } } @@ -451,7 +453,7 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { ((const iListItem *) item_ListWidget(d, over))->isDraggable) { d->dragItem = over; d->dragOrigin = sub_I2(topLeft_Rect(itemRect_ListWidget(d, over)), - pos_Click(&d->click)); + d->click.startPos); invalidateItem_ListWidget(d, d->dragItem); } } @@ -569,20 +571,22 @@ static void draw_ListWidget_(const iListWidget *d) { SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); iBool dstOnto; const size_t dstIndex = resolveDragDestination_ListWidget_(d, mousePos, &dstOnto); - if (dstIndex != d->dragItem && dstIndex != d->dragItem + 1) { + if (dstIndex != d->dragItem) { const iRect dstRect = itemRect_ListWidget(d, dstIndex); p.alpha = 0xff; if (dstOnto) { - fillRect_Paint(&p, dstRect, uiTextAction_ColorId); + drawRectThickness_Paint(&p, dstRect, gap_UI / 2, uiTextAction_ColorId); } - else { + else if (dstIndex != d->dragItem + 1) { fillRect_Paint(&p, (iRect){ addY_I2(dstRect.pos, -gap_UI / 4), init_I2(width_Rect(dstRect), gap_UI / 2) }, uiTextAction_ColorId); } } p.alpha = 0x80; + setOpacity_Text(0.5f); class_ListItem(item)->draw(item, &p, itemRect, d); + setOpacity_Text(1.0f); SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); } unsetClip_Paint(&p); diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c index 85217336..ab649eee 100644 --- a/src/ui/lookupwidget.c +++ b/src/ui/lookupwidget.c @@ -171,6 +171,9 @@ static float scoreMatch_(const iRegExp *pattern, iRangecc text) { } static float bookmarkRelevance_LookupJob_(const iLookupJob *d, const iBookmark *bm) { + if (isFolder_Bookmark(bm)) { + return 0.0f; + } iUrl parts; init_Url(&parts, &bm->url); const float t = scoreMatch_(d->term, range_String(&bm->title)); diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 20e43153..6c2934ec 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c @@ -108,6 +108,7 @@ struct Impl_SidebarWidget { iWidget * menu; iSidebarItem * contextItem; /* list item accessed in the context menu */ size_t contextIndex; /* index of list item accessed in the context menu */ + iIntSet closedFolders; /* otherwise open */ }; iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side) @@ -116,26 +117,67 @@ static iBool isResizing_SidebarWidget_(const iSidebarWidget *d) { return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0; } -static int cmpTree_Bookmark_(const iBookmark **a, const iBookmark **b) { +iBookmark *parent_Bookmark(const iBookmark *d) { + /* TODO: Parent pointers should be prefetched! */ + if (d->parentId) { + return get_Bookmarks(bookmarks_App(), d->parentId); + } + return NULL; +} + +iBool hasParent_Bookmark(const iBookmark *d, uint32_t parentId) { + /* TODO: Parent pointers should be prefetched! */ + while (d->parentId) { + if (d->parentId == parentId) { + return iTrue; + } + d = get_Bookmarks(bookmarks_App(), d->parentId); + } + return iFalse; +} + +int depth_Bookmark(const iBookmark *d) { + /* TODO: Precalculate this! */ + int depth = 0; + for (; d->parentId; depth++) { + d = get_Bookmarks(bookmarks_App(), d->parentId); + } + return depth; +} + +int cmpTree_Bookmark(const iBookmark **a, const iBookmark **b) { const iBookmark *bm1 = *a, *bm2 = *b; - if (bm2->parentId == id_Bookmark(bm1)) { + /* Contents of a parent come after it. */ + if (hasParent_Bookmark(bm2, id_Bookmark(bm1))) { return -1; } - if (bm1->parentId == id_Bookmark(bm2)) { + if (hasParent_Bookmark(bm1, id_Bookmark(bm2))) { return 1; } - if (bm1->parentId == bm2->parentId) { - //return cmpStringCase_String(&bm1->title, &bm2->title); - return iCmp(bm1->order, bm2->order); - } - if (bm1->parentId) { - bm1 = get_Bookmarks(bookmarks_App(), bm1->parentId); - } - if (bm2->parentId) { - bm2 = get_Bookmarks(bookmarks_App(), bm2->parentId); + /* Comparisons are only valid inside the same parent. */ + while (bm1->parentId != bm2->parentId) { + int depth1 = depth_Bookmark(bm1); + int depth2 = depth_Bookmark(bm2); + if (depth1 != depth2) { + /* Equalize the depth. */ + while (depth1 > depth2) { + bm1 = parent_Bookmark(bm1); + depth1--; + } + while (depth2 > depth1) { + bm2 = parent_Bookmark(bm2); + depth2--; + } + continue; + } + bm1 = parent_Bookmark(bm1); + depth1--; + bm2 = parent_Bookmark(bm2); + depth2--; } -// return cmpStringCase_String(&bm1->title, &bm2->title); - return iCmp(bm1->order, bm2->order); + const int cmp = iCmp(bm1->order, bm2->order); + if (cmp) return cmp; + return cmpStringCase_String(&bm1->title, &bm2->title); } static iLabelWidget *addActionButton_SidebarWidget_(iSidebarWidget *d, const char *label, @@ -211,6 +253,16 @@ static void updateContextMenu_SidebarWidget_(iSidebarWidget *d) { d->menu = makeMenu_Widget(as_Widget(d), data_Array(items), size_Array(items)); } +static iBool isBookmarkFolded_SidebarWidget_(const iSidebarWidget *d, const iBookmark *bm) { + while (bm->parentId) { + if (contains_IntSet(&d->closedFolders, bm->parentId)) { + return iTrue; + } + bm = get_Bookmarks(bookmarks_App(), bm->parentId); + } + return iFalse; +} + static void updateItems_SidebarWidget_(iSidebarWidget *d) { clear_ListWidget(d->list); releaseChildren_Widget(d->blank); @@ -334,12 +386,22 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { iRegExp *remoteSourceTag = iClob(new_RegExp("\\b" remoteSource_BookmarkTag "\\b", caseSensitive_RegExpOption)); iRegExp *remoteTag = iClob(new_RegExp("\\b" remote_BookmarkTag "\\b", caseSensitive_RegExpOption)); iRegExp *linkSplitTag = iClob(new_RegExp("\\b" linkSplit_BookmarkTag "\\b", caseSensitive_RegExpOption)); - iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTree_Bookmark_, NULL, NULL)) { + iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTree_Bookmark, NULL, NULL)) { const iBookmark *bm = i.ptr; + if (isBookmarkFolded_SidebarWidget_(d, bm)) { + continue; /* inside a closed folder */ + } iSidebarItem *item = new_SidebarItem(); item->listItem.isDraggable = iTrue; + item->isBold = item->listItem.isDropTarget = isFolder_Bookmark(bm); item->id = id_Bookmark(bm); - item->icon = bm->icon; + item->indent = depth_Bookmark(bm); + if (isFolder_Bookmark(bm)) { + item->icon = contains_IntSet(&d->closedFolders, item->id) ? 0x27e9 : 0xfe40; + } + else { + item->icon = bm->icon; + } set_String(&item->url, &bm->url); set_String(&item->label, &bm->title); /* Icons for special tags. */ { @@ -384,8 +446,11 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { { "---", 0, 0, NULL }, { delete_Icon " " uiTextCaution_ColorEscape "${bookmark.delete}", 0, 0, "bookmark.delete" }, { "---", 0, 0, NULL }, - { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" } }, - 14); + { add_Icon " ${menu.newfolder}", 0, 0, "bookmarks.addfolder" }, + { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" }, + { "---", 0, 0, NULL }, + { upDownArrow_Icon " ${menu.sort.alpha}", 0, 0, "bookmark.sortfolder" } }, + 17); break; } case history_SidebarMode: { @@ -671,6 +736,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { d->resizer = NULL; d->list = NULL; d->actions = NULL; + init_IntSet(&d->closedFolders); /* On a phone, the right sidebar is used exclusively for Identities. */ const iBool isPhone = deviceType_App() == phone_AppDeviceType; if (!isPhone || d->side == left_SidebarSide) { @@ -754,6 +820,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { void deinit_SidebarWidget(iSidebarWidget *d) { deinit_String(&d->cmdPrefix); + deinit_IntSet(&d->closedFolders); } iBool setButtonFont_SidebarWidget(iSidebarWidget *d, int font) { @@ -801,6 +868,17 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si break; } case bookmarks_SidebarMode: + if (isEmpty_String(&item->url)) /* a folder */ { + if (contains_IntSet(&d->closedFolders, item->id)) { + remove_IntSet(&d->closedFolders, item->id); + } + else { + insert_IntSet(&d->closedFolders, item->id); + } + updateItems_SidebarWidget_(d); + break; + } + /* fall through */ case history_SidebarMode: { if (!isEmpty_String(&item->url)) { postCommandf_Root(get_Root(), "open fromsidebar:1 newtab:%d url:%s", @@ -1013,10 +1091,24 @@ static void bookmarkMoved_SidebarWidget_(iSidebarWidget *d, size_t index, size_t isLast ? numItems_ListWidget(d->list) - 1 : beforeIndex); const iBookmark *dst = get_Bookmarks(bookmarks_App(), dstItem->id); + if (hasParent_Bookmark(dst, movingItem->id)) { + return; + } reorder_Bookmarks(bookmarks_App(), movingItem->id, dst->order + (isLast ? 1 : 0)); + get_Bookmarks(bookmarks_App(), movingItem->id)->parentId = dst->parentId; updateItems_SidebarWidget_(d); /* Don't confuse the user: keep the dragged item in hover state. */ setHoverItem_ListWidget(d->list, index < beforeIndex ? beforeIndex - 1 : beforeIndex); + postCommandf_App("bookmarks.changed nosidebar:%p", d); /* skip this sidebar since we updated already */ +} + +static void bookmarkMovedOntoFolder_SidebarWidget_(iSidebarWidget *d, size_t index, + size_t folderIndex) { + const iSidebarItem *movingItem = item_ListWidget(d->list, index); + const iSidebarItem *dstItem = item_ListWidget(d->list, folderIndex); + iBookmark *bm = get_Bookmarks(bookmarks_App(), movingItem->id); + bm->parentId = dstItem->id; + postCommand_App("bookmarks.changed"); } static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { @@ -1070,7 +1162,9 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) } else if (equal_Command(cmd, "bookmarks.changed") && (d->mode == bookmarks_SidebarMode || d->mode == feeds_SidebarMode)) { - updateItems_SidebarWidget_(d); + if (pointerLabel_Command(cmd, "nosidebar") != d) { + updateItems_SidebarWidget_(d); + } } else if (equal_Command(cmd, "idents.changed") && d->mode == identities_SidebarMode) { updateItems_SidebarWidget_(d); @@ -1135,6 +1229,9 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) } else { /* Dragged onto a folder. */ + bookmarkMovedOntoFolder_SidebarWidget_(d, + argU32Label_Command(cmd, "arg"), + argU32Label_Command(cmd, "onto")); } return iTrue; } @@ -1170,15 +1267,12 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) setText_InputWidget(findChild_Widget(dlg, "bmed.icon"), collect_String(newUnicodeN_String(&bm->icon, 1))); } - setFlags_Widget(findChild_Widget(dlg, "bmed.tag.home"), - selected_WidgetFlag, - hasTag_Bookmark(bm, homepage_BookmarkTag)); - setFlags_Widget(findChild_Widget(dlg, "bmed.tag.remote"), - selected_WidgetFlag, - hasTag_Bookmark(bm, remoteSource_BookmarkTag)); - setFlags_Widget(findChild_Widget(dlg, "bmed.tag.linksplit"), - selected_WidgetFlag, - hasTag_Bookmark(bm, linkSplit_BookmarkTag)); + setToggle_Widget(findChild_Widget(dlg, "bmed.tag.home"), + hasTag_Bookmark(bm, homepage_BookmarkTag)); + setToggle_Widget(findChild_Widget(dlg, "bmed.tag.remote"), + hasTag_Bookmark(bm, remoteSource_BookmarkTag)); + setToggle_Widget(findChild_Widget(dlg, "bmed.tag.linksplit"), + hasTag_Bookmark(bm, linkSplit_BookmarkTag)); setCommandHandler_Widget(dlg, handleBookmarkEditorCommands_SidebarWidget_); setFocus_Widget(findChild_Widget(dlg, "bmed.title")); } @@ -1225,6 +1319,16 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) } return iTrue; } + else if (isCommand_Widget(w, ev, "bookmark.sortfolder")) { + const iSidebarItem *item = d->contextItem; + if (d->mode == bookmarks_SidebarMode && item) { + postCommandf_App("bookmarks.sort arg:%zu", + item->listItem.isDropTarget + ? item->id + : get_Bookmarks(bookmarks_App(), item->id)->parentId); + } + return iTrue; + } else if (equal_Command(cmd, "feeds.update.finished")) { d->numUnreadEntries = argLabel_Command(cmd, "unread"); checkModeButtonLayout_SidebarWidget_(d); @@ -1638,7 +1742,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, fillRect_Paint(p, itemRect, bg); } else if (sidebar->mode == bookmarks_SidebarMode) { - if (d->icon == 0x2913) { /* TODO: Remote icon; meaning: is this in a folder? */ + if (d->indent) /* remote icon */ { bg = uiBackgroundFolder_ColorId; fillRect_Paint(p, itemRect, bg); } @@ -1740,11 +1844,13 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, } else if (sidebar->mode == bookmarks_SidebarMode) { const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) - : uiText_ColorId; + : d->listItem.isDropTarget ? uiHeading_ColorId : uiText_ColorId; + /* The icon. */ iString str; init_String(&str); - appendChar_String(&str, d->icon ? d->icon : 0x1f588); - const iRect iconArea = { addX_I2(pos, gap_UI), + appendChar_String(&str, d->icon ? d->icon : 0x1f588); + const int leftIndent = d->indent * gap_UI * 4; + const iRect iconArea = { addX_I2(pos, gap_UI + leftIndent), init_I2(1.75f * lineHeight_Text(font), itemHeight) }; drawCentered_Text(font, iconArea, diff --git a/src/ui/util.c b/src/ui/util.c index 7fa5d675..5b9f15a9 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -816,6 +816,14 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { setUserData_Object(menu, deepCopyMenuItems_(menu, items, n)); addChild_Widget(parent, menu); iRelease(menu); /* owned by parent now */ + /* Keyboard shortcuts still need to triggerable via the menu, although the items don't exist. */ { + for (size_t i = 0; i < n; i++) { + const iMenuItem *item = &items[i]; + if (item->key) { + addAction_Widget(menu, item->key, item->kmods, item->command); + } + } + } #else /* Non-native custom popup menu. This may still be displayed inside a separate window. */ setDrawBufferEnabled_Widget(menu, iTrue); diff --git a/src/ui/window.c b/src/ui/window.c index 0863aa47..5941ef5f 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -120,6 +120,7 @@ static const iMenuItem viewMenuItems_[] = { static iMenuItem bookmarksMenuItems_[] = { { "${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, { "${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, + { "${menu.newfolder}", 0, 0, "bookmarks.addfolder" }, { "---", 0, 0, NULL }, { "${menu.import.links}", 0, 0, "bookmark.links confirm:1" }, { "---", 0, 0, NULL }, @@ -128,6 +129,8 @@ static iMenuItem bookmarksMenuItems_[] = { { "${macos.menu.bookmarks.bytime}", 0, 0, "open url:about:bookmarks?created" }, { "${menu.feeds.entrylist}", 0, 0, "open url:about:feeds" }, { "---", 0, 0, NULL }, + { "${menu.sort.alpha}", 0, 0, "bookmarks.sort" }, + { "---", 0, 0, NULL }, { "${menu.bookmarks.refresh}", 0, 0, "bookmarks.reload.remote" }, { "${menu.feeds.refresh}", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" }, }; -- cgit v1.2.3 From de976d7d8bcfcb7bcc1436e215a8713aefbf091f Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 24 Sep 2021 22:52:13 +0300 Subject: Bookmarks: Deleting a tree of bookmarks Sidebar asks for confirmation when deleting a folder hierarchy of bookmarks. --- po/en.po | 15 +++++++++++++++ res/lang/de.bin | Bin 24591 -> 24641 bytes res/lang/en.bin | Bin 23192 -> 23514 bytes res/lang/es.bin | Bin 25722 -> 25772 bytes res/lang/fi.bin | Bin 25727 -> 25777 bytes res/lang/fr.bin | Bin 26696 -> 26746 bytes res/lang/ia.bin | Bin 25319 -> 25369 bytes res/lang/ie.bin | Bin 25081 -> 25131 bytes res/lang/pl.bin | Bin 26252 -> 26302 bytes res/lang/ru.bin | Bin 37978 -> 38028 bytes res/lang/sr.bin | Bin 37635 -> 37685 bytes res/lang/tok.bin | Bin 23517 -> 23567 bytes res/lang/zh_Hans.bin | Bin 22271 -> 22321 bytes res/lang/zh_Hant.bin | Bin 22456 -> 22506 bytes src/bookmarks.c | 17 +++++++---------- src/bookmarks.h | 3 +++ src/ui/sidebarwidget.c | 44 +++++++++++++++++++++++++++++++++++++++++--- 17 files changed, 66 insertions(+), 13 deletions(-) (limited to 'res') diff --git a/po/en.po b/po/en.po index 24525b03..24ca12a3 100644 --- a/po/en.po +++ b/po/en.po @@ -532,6 +532,21 @@ msgstr "Do you really want to erase the history of all visited pages?" msgid "dlg.history.clear" msgstr "Clear History" +msgid "heading.confirm.bookmarks.delete" +msgstr "DELETE BOOKMARKS" + +#, c-format +msgid "dlg.confirm.bookmarks.delete" +msgid_plural "dlg.confirm.bookmarks.delete.n" +msgstr[0] "Do you really want to delete the bookmark inside this folder?" +msgstr[1] "Do you really want to delete all %u bookmarks inside this folder?" + +#, c-format +msgid "dlg.bookmarks.delete" +msgid_plural "dlg.bookmarks.delete.n" +msgstr[0] "Delete Bookmark" +msgstr[1] "Delete %u Bookmarks" + msgid "bookmark.tag.home" msgstr "Use as Homepage" diff --git a/res/lang/de.bin b/res/lang/de.bin index 4d48c61f..0d63824a 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 8782920f..ef09f624 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index fa86ea3a..a45af687 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index f23cbf09..404c34cd 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 d4c5cf55..e7a5e71d 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index ee6533ca..ef2c8ca8 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 2bbc7ada..2f7400d6 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 651a6231..1325a496 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 1a3f7213..9d6d24de 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 184699f1..de8bc5b2 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 a77aa161..e233fa63 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 1fe4392d..754d3a3f 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 244bb3a1..a8d3690d 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/bookmarks.c b/src/bookmarks.c index 35e16d9a..fe2ca47a 100644 --- a/src/bookmarks.c +++ b/src/bookmarks.c @@ -83,6 +83,10 @@ int cmpTitleAscending_Bookmark(const iBookmark **a, const iBookmark **b) { return cmpStringCase_String(&(*a)->title, &(*b)->title); } +iBool filterInsideFolder_Bookmark(void *context, const iBookmark *bm) { + return hasParent_Bookmark(bm, id_Bookmark(context)); +} + /*----------------------------------------------------------------------------------------------*/ static const char *oldFileName_Bookmarks_ = "bookmarks.txt"; @@ -345,16 +349,9 @@ iBool remove_Bookmarks(iBookmarks *d, uint32_t id) { lock_Mutex(d->mtx); iBookmark *bm = (iBookmark *) remove_Hash(&d->bookmarks, id); if (bm) { - /* If this is a remote source, make sure all the remote bookmarks are - removed as well. */ - if (hasTag_Bookmark(bm, remoteSource_BookmarkTag)) { - iForEach(Hash, i, &d->bookmarks) { - iBookmark *j = (iBookmark *) i.value; - if (j->parentId == id_Bookmark(bm)) { - remove_HashIterator(&i); - delete_Bookmark(j); - } - } + /* Remove all the contained bookmarks as well. */ + iConstForEach(PtrArray, i, list_Bookmarks(d, NULL, filterInsideFolder_Bookmark, bm)) { + delete_Bookmark((iBookmark *) remove_Hash(&d->bookmarks, id_Bookmark(i.ptr))); } delete_Bookmark(bm); } diff --git a/src/bookmarks.h b/src/bookmarks.h index 61a5c102..13501ded 100644 --- a/src/bookmarks.h +++ b/src/bookmarks.h @@ -56,6 +56,7 @@ struct Impl_Bookmark { iLocalDef uint32_t id_Bookmark (const iBookmark *d) { return d->node.key; } iLocalDef iBool isFolder_Bookmark (const iBookmark *d) { return isEmpty_String(&d->url); } +iBool hasParent_Bookmark (const iBookmark *, uint32_t parentId); int depth_Bookmark (const iBookmark *); iBool hasTag_Bookmark (const iBookmark *, const char *tag); void addTag_Bookmark (iBookmark *, const char *tag); @@ -78,6 +79,8 @@ iLocalDef void addOrRemoveTag_Bookmark(iBookmark *d, const char *tag, iBool add) int cmpTitleAscending_Bookmark (const iBookmark **, const iBookmark **); int cmpTree_Bookmark (const iBookmark **, const iBookmark **); +iBool filterInsideFolder_Bookmark (void *parentFolder, const iBookmark *); + /*----------------------------------------------------------------------------------------------*/ iDeclareType(Bookmarks) diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index e8aa69c1..8e38dcb8 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c @@ -1111,6 +1111,16 @@ static void bookmarkMovedOntoFolder_SidebarWidget_(iSidebarWidget *d, size_t ind postCommand_App("bookmarks.changed"); } +static size_t numBookmarks_(const iPtrArray *bmList) { + size_t num = 0; + iConstForEach(PtrArray, i, bmList) { + if (!isFolder_Bookmark(i.ptr) && !hasTag_Bookmark(i.ptr, remote_BookmarkTag)) { + num++; + } + } + return num; +} + static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { iWidget *w = as_Widget(d); /* Handle commands. */ @@ -1313,9 +1323,37 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) } else if (isCommand_Widget(w, ev, "bookmark.delete")) { const iSidebarItem *item = d->contextItem; - if (d->mode == bookmarks_SidebarMode && item && remove_Bookmarks(bookmarks_App(), item->id)) { - removeEntries_Feeds(item->id); - postCommand_App("bookmarks.changed"); + if (d->mode == bookmarks_SidebarMode && item) { + iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); + if (isFolder_Bookmark(bm)) { + const iPtrArray *list = list_Bookmarks(bookmarks_App(), NULL, + filterInsideFolder_Bookmark, bm); + if (argLabel_Command(cmd, "confirmed") || isEmpty_PtrArray(list)) { + iConstForEach(PtrArray, i, list) { + removeEntries_Feeds(id_Bookmark(i.ptr)); + } + remove_Bookmarks(bookmarks_App(), item->id); + postCommand_App("bookmarks.changed"); + } + else { + const size_t numBookmarks = numBookmarks_(list); + makeQuestion_Widget(uiHeading_ColorEscape "${heading.confirm.bookmarks.delete}", + formatCStrs_Lang("dlg.confirm.bookmarks.delete.n", numBookmarks), + (iMenuItem[]){ + { "${cancel}" }, + { format_CStr(uiTextCaution_ColorEscape "%s", + formatCStrs_Lang("dlg.bookmarks.delete.n", numBookmarks)), + 0, 0, format_CStr("!bookmark.delete confirmed:1 ptr:%p", d) }, + }, 2); + } + } + else { + /* TODO: Move it to a Trash folder? */ + if (remove_Bookmarks(bookmarks_App(), item->id)) { + removeEntries_Feeds(item->id); + postCommand_App("bookmarks.changed"); + } + } } return iTrue; } -- cgit v1.2.3