summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/bindingswidget.c6
-rw-r--r--src/ui/documentwidget.c24
-rw-r--r--src/ui/labelwidget.c25
-rw-r--r--src/ui/sidebarwidget.c148
-rw-r--r--src/ui/util.c124
-rw-r--r--src/ui/util.h1
-rw-r--r--src/ui/widget.c10
-rw-r--r--src/ui/widget.h3
8 files changed, 195 insertions, 146 deletions
diff --git a/src/ui/bindingswidget.c b/src/ui/bindingswidget.c
index 4cf8df8e..13f9434e 100644
--- a/src/ui/bindingswidget.c
+++ b/src/ui/bindingswidget.c
@@ -143,12 +143,16 @@ static void setActiveItem_BindingsWidget_(iBindingsWidget *d, size_t pos) {
143 item->isWaitingForEvent = iTrue; 143 item->isWaitingForEvent = iTrue;
144 invalidateItem_ListWidget(d->list, d->activePos); 144 invalidateItem_ListWidget(d->list, d->activePos);
145 } 145 }
146#if defined (iPlatformAppleDesktop) 146#if defined (iPlatformAppleDesktop) && defined (iHaveNativeContextMenus)
147 /* Native menus must be disabled while grabbing keys so the shortcuts don't trigger. */ 147 /* Native menus must be disabled while grabbing keys so the shortcuts don't trigger. */
148 const iBool enableNativeMenus = (d->activePos == iInvalidPos); 148 const iBool enableNativeMenus = (d->activePos == iInvalidPos);
149 enableMenu_MacOS("${menu.title.file}", enableNativeMenus);
149 enableMenu_MacOS("${menu.title.edit}", enableNativeMenus); 150 enableMenu_MacOS("${menu.title.edit}", enableNativeMenus);
150 enableMenu_MacOS("${menu.title.view}", enableNativeMenus); 151 enableMenu_MacOS("${menu.title.view}", enableNativeMenus);
152 enableMenu_MacOS("${menu.title.bookmarks}", enableNativeMenus);
151 enableMenu_MacOS("${menu.title.identity}", enableNativeMenus); 153 enableMenu_MacOS("${menu.title.identity}", enableNativeMenus);
154 enableMenuIndex_MacOS(6, enableNativeMenus);
155 enableMenuIndex_MacOS(7, enableNativeMenus);
152#endif 156#endif
153} 157}
154 158
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 88e016f3..9b531957 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -56,6 +56,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
56#include "visbuf.h" 56#include "visbuf.h"
57#include "visited.h" 57#include "visited.h"
58 58
59#if defined (iPlatformAppleDesktop)
60# include "macos.h"
61#endif
59#if defined (iPlatformAppleMobile) 62#if defined (iPlatformAppleMobile)
60# include "ios.h" 63# include "ios.h"
61#endif 64#endif
@@ -1934,11 +1937,18 @@ static void enableActions_DocumentWidget_(iDocumentWidget *d, iBool enable) {
1934} 1937}
1935 1938
1936static void setLinkNumberMode_DocumentWidget_(iDocumentWidget *d, iBool set) { 1939static void setLinkNumberMode_DocumentWidget_(iDocumentWidget *d, iBool set) {
1937 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, set); 1940 if (((d->flags & showLinkNumbers_DocumentWidgetFlag) != 0) != set) {
1938 /* Children have priority when handling events. */ 1941 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, set);
1939 enableActions_DocumentWidget_(d, !set); 1942 /* Children have priority when handling events. */
1940 if (d->menu) { 1943 enableActions_DocumentWidget_(d, !set);
1941 setFlags_Widget(d->menu, disabled_WidgetFlag, set); 1944#if defined (iPlatformAppleDesktop)
1945 enableMenuItemsOnHomeRow_MacOS(!set);
1946#endif
1947 /* Ensure all keyboard events come here first. */
1948 setKeyboardGrab_Widget(set ? as_Widget(d) : NULL);
1949 if (d->menu) {
1950 setFlags_Widget(d->menu, disabled_WidgetFlag, set);
1951 }
1942 } 1952 }
1943} 1953}
1944 1954
@@ -2252,10 +2262,6 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
2252 case tooManyRedirects_GmStatusCode: 2262 case tooManyRedirects_GmStatusCode:
2253 appendFormat_String(src, "=> %s\n", cstr_String(meta)); 2263 appendFormat_String(src, "=> %s\n", cstr_String(meta));
2254 break; 2264 break;
2255 case tlsFailure_GmStatusCode:
2256// useBanner = iFalse; /* valid data wasn't received from host */
2257// appendFormat_String(src, ">%s\n", cstr_String(meta));
2258 break;
2259 case tlsServerCertificateExpired_GmStatusCode: 2265 case tlsServerCertificateExpired_GmStatusCode:
2260 makeFooterButtons_DocumentWidget_( 2266 makeFooterButtons_DocumentWidget_(
2261 d, 2267 d,
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 44ae3eec..89e95c85 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -205,6 +205,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int
205 const iBool isSel = (flags & selected_WidgetFlag) != 0; 205 const iBool isSel = (flags & selected_WidgetFlag) != 0;
206 const iBool isFrameless = (flags & frameless_WidgetFlag) != 0; 206 const iBool isFrameless = (flags & frameless_WidgetFlag) != 0;
207 const iBool isButton = d->click.button != 0; 207 const iBool isButton = d->click.button != 0;
208 const iBool isMenuItem = !cmp_String(id_Widget(parent_Widget(d)), "menu");
208 const iBool isKeyRoot = (w->root == get_Window()->keyRoot); 209 const iBool isKeyRoot = (w->root == get_Window()->keyRoot);
209 const iBool isDarkTheme = isDark_ColorTheme(colorTheme_App()); 210 const iBool isDarkTheme = isDark_ColorTheme(colorTheme_App());
210 /* Default color state. */ 211 /* Default color state. */
@@ -229,7 +230,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int
229 *bg = uiBackgroundSelected_ColorId; 230 *bg = uiBackgroundSelected_ColorId;
230 if (!isKeyRoot) { 231 if (!isKeyRoot) {
231 *bg = isDark_ColorTheme(colorTheme_App()) ? uiBackgroundUnfocusedSelection_ColorId 232 *bg = isDark_ColorTheme(colorTheme_App()) ? uiBackgroundUnfocusedSelection_ColorId
232 : uiMarked_ColorId; 233 : uiMarked_ColorId;
233 } 234 }
234 } 235 }
235 *fg = uiTextSelected_ColorId; 236 *fg = uiTextSelected_ColorId;
@@ -294,7 +295,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int
294 } 295 }
295 *fg = *icon = *meta = uiTextPressed_ColorId | permanent_ColorId; 296 *fg = *icon = *meta = uiTextPressed_ColorId | permanent_ColorId;
296 } 297 }
297 } 298 }
298 if (((isSel || isHover) && isFrameless) || isPress) { 299 if (((isSel || isHover) && isFrameless) || isPress) {
299 /* Ensure that the full label text remains readable. */ 300 /* Ensure that the full label text remains readable. */
300 *fg |= permanent_ColorId; 301 *fg |= permanent_ColorId;
@@ -429,7 +430,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
429 moved_Rect( 430 moved_Rect(
430 adjusted_Rect(bounds, 431 adjusted_Rect(bounds,
431 init_I2(iconPad * (flags & tight_WidgetFlag ? 1.0f : 1.5f), 0), 432 init_I2(iconPad * (flags & tight_WidgetFlag ? 1.0f : 1.5f), 0),
432 init_I2(-iconPad * (flags & tight_WidgetFlag ? 0.5f : 1.0f), 0)), 433 init_I2(-iconPad * (flags & tight_WidgetFlag ? 0.5f : 1.0f), 0)),
433 d->labelOffset), 434 d->labelOffset),
434 d->flags.alignVisual, 435 d->flags.alignVisual,
435 d->flags.drawAsOutline ? fg : none_ColorId, 436 d->flags.drawAsOutline ? fg : none_ColorId,
@@ -566,20 +567,20 @@ void setTextColor_LabelWidget(iLabelWidget *d, int color) {
566 567
567void setText_LabelWidget(iLabelWidget *d, const iString *text) { 568void setText_LabelWidget(iLabelWidget *d, const iString *text) {
568 if (d) { 569 if (d) {
569 updateText_LabelWidget(d, text); 570 updateText_LabelWidget(d, text);
570 updateSize_LabelWidget(d); 571 updateSize_LabelWidget(d);
571 if (isWrapped_LabelWidget(d)) { 572 if (isWrapped_LabelWidget(d)) {
572 sizeChanged_LabelWidget_(d); 573 sizeChanged_LabelWidget_(d);
573 } 574}
574 } 575 }
575} 576}
576 577
577void setTextCStr_LabelWidget(iLabelWidget *d, const char *text) { 578void setTextCStr_LabelWidget(iLabelWidget *d, const char *text) {
578 if (d) { 579 if (d) {
579 updateTextCStr_LabelWidget(d, text); 580 updateTextCStr_LabelWidget(d, text);
580 updateSize_LabelWidget(d); 581 updateSize_LabelWidget(d);
581 if (isWrapped_LabelWidget(d)) { 582 if (isWrapped_LabelWidget(d)) {
582 sizeChanged_LabelWidget_(d); 583 sizeChanged_LabelWidget_(d);
583 } 584 }
584 } 585 }
585} 586}
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index fe8ec939..409d749c 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -125,7 +125,7 @@ struct Impl_SidebarWidget {
125 float widthAsGaps; 125 float widthAsGaps;
126 int buttonFont; 126 int buttonFont;
127 int itemFonts[2]; 127 int itemFonts[2];
128 size_t numUnreadEntries; 128 size_t numUnreadEntries;
129 iWidget * resizer; 129 iWidget * resizer;
130 iWidget * menu; /* context menu for an item */ 130 iWidget * menu; /* context menu for an item */
131 iWidget * modeMenu; /* context menu for the sidebar mode (no item) */ 131 iWidget * modeMenu; /* context menu for the sidebar mode (no item) */
@@ -266,7 +266,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct
266 clear_ListWidget(d->list); 266 clear_ListWidget(d->list);
267 releaseChildren_Widget(d->blank); 267 releaseChildren_Widget(d->blank);
268 if (!keepActions) { 268 if (!keepActions) {
269 releaseChildren_Widget(d->actions); 269 releaseChildren_Widget(d->actions);
270 } 270 }
271 d->actions->rect.size.y = 0; 271 d->actions->rect.size.y = 0;
272 destroy_Widget(d->menu); 272 destroy_Widget(d->menu);
@@ -350,37 +350,37 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct
350 } 350 }
351 /* Actions. */ 351 /* Actions. */
352 if (!isMobile) { 352 if (!isMobile) {
353 if (!keepActions) { 353 if (!keepActions) {
354 addActionButton_SidebarWidget_(d, 354 addActionButton_SidebarWidget_(d,
355 check_Icon " ${sidebar.action.feeds.markallread}", 355 check_Icon " ${sidebar.action.feeds.markallread}",
356 "feeds.markallread", 356 "feeds.markallread",
357 expand_WidgetFlag | tight_WidgetFlag); 357 expand_WidgetFlag | tight_WidgetFlag);
358 updateSize_LabelWidget(addChildFlags_Widget(d->actions, 358 updateSize_LabelWidget(addChildFlags_Widget(d->actions,
359 iClob(new_LabelWidget("${sidebar.action.show}", NULL)), 359 iClob(new_LabelWidget("${sidebar.action.show}", NULL)),
360 frameless_WidgetFlag | tight_WidgetFlag)); 360 frameless_WidgetFlag | tight_WidgetFlag));
361 const iMenuItem items[] = { 361 const iMenuItem items[] = {
362 { page_Icon " ${sidebar.action.feeds.showall}", SDLK_u, KMOD_SHIFT, "feeds.mode arg:0" }, 362 { page_Icon " ${sidebar.action.feeds.showall}", SDLK_u, KMOD_SHIFT, "feeds.mode arg:0" },
363 { circle_Icon " ${sidebar.action.feeds.showunread}", SDLK_u, 0, "feeds.mode arg:1" }, 363 { circle_Icon " ${sidebar.action.feeds.showunread}", SDLK_u, 0, "feeds.mode arg:1" },
364 }; 364 };
365 iWidget *dropButton = addChild_Widget( 365 iWidget *dropButton = addChild_Widget(
366 d->actions, 366 d->actions,
367 iClob(makeMenuButton_LabelWidget(items[d->feedsMode].label, items, 2))); 367 iClob(makeMenuButton_LabelWidget(items[d->feedsMode].label, items, 2)));
368 setId_Widget(dropButton, "feeds.modebutton"); 368 setId_Widget(dropButton, "feeds.modebutton");
369 checkIcon_LabelWidget((iLabelWidget *) dropButton); 369 checkIcon_LabelWidget((iLabelWidget *) dropButton);
370 setFixedSize_Widget( 370 setFixedSize_Widget(
371 dropButton, 371 dropButton,
372 init_I2(iMaxi(20 * gap_UI, 372 init_I2(iMaxi(20 * gap_UI,
373 measure_Text(default_FontId, 373 measure_Text(default_FontId,
374 translateCStr_Lang( 374 translateCStr_Lang(
375 items[findWidestLabel_MenuItem(items, 2)].label)) 375 items[findWidestLabel_MenuItem(items, 2)].label))
376 .advance.x + 376 .advance.x +
377 13 * gap_UI), 377 13 * gap_UI),
378 -1)); 378 -1));
379 } 379 }
380 else { 380 else {
381 updateDropdownSelection_LabelWidget(findChild_Widget(d->actions, "feeds.modebutton"), 381 updateDropdownSelection_LabelWidget(findChild_Widget(d->actions, "feeds.modebutton"),
382 format_CStr(" arg:%d", d->feedsMode)); 382 format_CStr(" arg:%d", d->feedsMode));
383 } 383 }
384 } 384 }
385 else { 385 else {
386 if (!keepActions) { 386 if (!keepActions) {
@@ -605,7 +605,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct
605 setPadding_Widget(div, 0, 0, 0, height_Widget(d->actions)); 605 setPadding_Widget(div, 0, 0, 0, height_Widget(d->actions));
606 addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); /* pad */ 606 addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); /* pad */
607 if (d->feedsMode == all_FeedsMode) { 607 if (d->feedsMode == all_FeedsMode) {
608 addChild_Widget(div, iClob(new_LabelWidget("${menu.feeds.refresh}", "feeds.refresh"))); 608 addChild_Widget(div, iClob(new_LabelWidget("${menu.feeds.refresh}", "feeds.refresh")));
609 } 609 }
610 else { 610 else {
611 iLabelWidget *msg = addChildFlags_Widget(div, iClob(new_LabelWidget("${sidebar.empty.unread}", NULL)), 611 iLabelWidget *msg = addChildFlags_Widget(div, iClob(new_LabelWidget("${sidebar.empty.unread}", NULL)),
@@ -719,8 +719,8 @@ iBool setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) {
719 719
720void setClosedFolders_SidebarWidget(iSidebarWidget *d, const iIntSet *closedFolders) { 720void setClosedFolders_SidebarWidget(iSidebarWidget *d, const iIntSet *closedFolders) {
721 if (d) { 721 if (d) {
722 delete_IntSet(d->closedFolders); 722 delete_IntSet(d->closedFolders);
723 d->closedFolders = copy_IntSet(closedFolders); 723 d->closedFolders = copy_IntSet(closedFolders);
724 } 724 }
725} 725}
726 726
@@ -840,35 +840,35 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
840 setFont_LabelWidget(closeButton, uiLabelBigBold_FontId); 840 setFont_LabelWidget(closeButton, uiLabelBigBold_FontId);
841 iConnect(Root, get_Root(), visualOffsetsChanged, d, updateSlidingSheetHeight_SidebarWidget_); 841 iConnect(Root, get_Root(), visualOffsetsChanged, d, updateSlidingSheetHeight_SidebarWidget_);
842 } 842 }
843 iWidget *buttons = new_Widget(); 843 iWidget *buttons = new_Widget();
844 setId_Widget(buttons, "buttons"); 844 setId_Widget(buttons, "buttons");
845 setDrawBufferEnabled_Widget(buttons, iTrue); 845 setDrawBufferEnabled_Widget(buttons, iTrue);
846 for (int i = 0; i < max_SidebarMode; i++) { 846 for (int i = 0; i < max_SidebarMode; i++) {
847 if (i == identities_SidebarMode && deviceType_App() != desktop_AppDeviceType) { 847 if (i == identities_SidebarMode && deviceType_App() != desktop_AppDeviceType) {
848 /* On mobile, identities are managed via Settings. */ 848 /* On mobile, identities are managed via Settings. */
849 continue; 849 continue;
850 } 850 }
851 d->modeButtons[i] = addChildFlags_Widget( 851 d->modeButtons[i] = addChildFlags_Widget(
852 buttons, 852 buttons,
853 iClob(new_LabelWidget( 853 iClob(new_LabelWidget(
854 tightModeLabels_[i], 854 tightModeLabels_[i],
855 format_CStr("%s.mode arg:%d", cstr_String(id_Widget(w)), i))), 855 format_CStr("%s.mode arg:%d", cstr_String(id_Widget(w)), i))),
856 frameless_WidgetFlag | noBackground_WidgetFlag); 856 frameless_WidgetFlag | noBackground_WidgetFlag);
857 as_Widget(d->modeButtons[i])->flags2 |= slidingSheetDraggable_WidgetFlag2; /* phone */ 857 as_Widget(d->modeButtons[i])->flags2 |= slidingSheetDraggable_WidgetFlag2; /* phone */
858 } 858 }
859 setButtonFont_SidebarWidget(d, isPhone ? uiLabelBig_FontId : uiLabel_FontId); 859 setButtonFont_SidebarWidget(d, isPhone ? uiLabelBig_FontId : uiLabel_FontId);
860 addChildFlags_Widget(vdiv, 860 addChildFlags_Widget(vdiv,
861 iClob(buttons), 861 iClob(buttons),
862 arrangeHorizontal_WidgetFlag | resizeWidthOfChildren_WidgetFlag | 862 arrangeHorizontal_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
863 arrangeHeight_WidgetFlag | resizeToParentWidth_WidgetFlag); 863 arrangeHeight_WidgetFlag | resizeToParentWidth_WidgetFlag);
864 setBackgroundColor_Widget(buttons, uiBackgroundSidebar_ColorId); 864 setBackgroundColor_Widget(buttons, uiBackgroundSidebar_ColorId);
865 iWidget *content = new_Widget(); 865 iWidget *content = new_Widget();
866 setFlags_Widget(content, resizeChildren_WidgetFlag, iTrue); 866 setFlags_Widget(content, resizeChildren_WidgetFlag, iTrue);
867 iWidget *listAndActions = makeVDiv_Widget(); 867 iWidget *listAndActions = makeVDiv_Widget();
868 addChild_Widget(content, iClob(listAndActions)); 868 addChild_Widget(content, iClob(listAndActions));
869 iWidget *listArea = new_Widget(); 869 iWidget *listArea = new_Widget();
870 setFlags_Widget(listArea, resizeChildren_WidgetFlag, iTrue); 870 setFlags_Widget(listArea, resizeChildren_WidgetFlag, iTrue);
871 d->list = new_ListWidget(); 871 d->list = new_ListWidget();
872 setPadding_Widget(as_Widget(d->list), 0, gap_UI, 0, gap_UI); 872 setPadding_Widget(as_Widget(d->list), 0, gap_UI, 0, gap_UI);
873 addChild_Widget(listArea, iClob(d->list)); 873 addChild_Widget(listArea, iClob(d->list));
874 if (!isPhone) { 874 if (!isPhone) {
@@ -892,7 +892,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
892 setBackgroundColor_Widget(d->actions, uiBackground_ColorId); 892 setBackgroundColor_Widget(d->actions, uiBackground_ColorId);
893 } 893 }
894 else { 894 else {
895 setBackgroundColor_Widget(d->actions, uiBackgroundSidebar_ColorId); 895 setBackgroundColor_Widget(d->actions, uiBackgroundSidebar_ColorId);
896 } 896 }
897 d->contextItem = NULL; 897 d->contextItem = NULL;
898 d->contextIndex = iInvalidPos; 898 d->contextIndex = iInvalidPos;
@@ -945,7 +945,7 @@ iBool setButtonFont_SidebarWidget(iSidebarWidget *d, int font) {
945static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) { 945static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) {
946 if (d->mode == identities_SidebarMode) { 946 if (d->mode == identities_SidebarMode) {
947 return constHoverIdentity_CertListWidget(d->certList); 947 return constHoverIdentity_CertListWidget(d->certList);
948 } 948 }
949 return NULL; 949 return NULL;
950} 950}
951 951
@@ -1182,26 +1182,26 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
1182 const int animFlags = easeOut_AnimFlag | softer_AnimFlag; 1182 const int animFlags = easeOut_AnimFlag | softer_AnimFlag;
1183 if (!isPortraitPhone_App()) { 1183 if (!isPortraitPhone_App()) {
1184 if (!isHiding) { 1184 if (!isHiding) {
1185 setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse); 1185 setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse);
1186 w->rect.size.x = d->widthAsGaps * gap_UI; 1186 w->rect.size.x = d->widthAsGaps * gap_UI;
1187 invalidate_ListWidget(d->list); 1187 invalidate_ListWidget(d->list);
1188 if (isAnimated) { 1188 if (isAnimated) {
1189 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); 1189 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue);
1190 setVisualOffset_Widget( 1190 setVisualOffset_Widget(
1191 w, (d->side == left_SidebarSide ? -1 : 1) * (w->rect.size.x + safePad), 0, 0); 1191 w, (d->side == left_SidebarSide ? -1 : 1) * (w->rect.size.x + safePad), 0, 0);
1192 setVisualOffset_Widget(w, 0, 300, animFlags); 1192 setVisualOffset_Widget(w, 0, 300, animFlags);
1193 }
1194 } 1193 }
1195 else if (isAnimated) { 1194 }
1196 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); 1195 else if (isAnimated) {
1197 if (d->side == right_SidebarSide) { 1196 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue);
1198 setVisualOffset_Widget(w, visX, 0, 0); 1197 if (d->side == right_SidebarSide) {
1199 setVisualOffset_Widget( 1198 setVisualOffset_Widget(w, visX, 0, 0);
1199 setVisualOffset_Widget(
1200 w, visX + w->rect.size.x + safePad, 300, animFlags); 1200 w, visX + w->rect.size.x + safePad, 300, animFlags);
1201 } 1201 }
1202 else { 1202 else {
1203 setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); 1203 setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue);
1204 setVisualOffset_Widget( 1204 setVisualOffset_Widget(
1205 w, -w->rect.size.x - safePad, 300, animFlags); 1205 w, -w->rect.size.x - safePad, 300, animFlags);
1206 } 1206 }
1207 } 1207 }
@@ -1222,8 +1222,8 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
1222 setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 300, animFlags); 1222 setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 300, animFlags);
1223 if (d->isEditing) { 1223 if (d->isEditing) {
1224 setMobileEditMode_SidebarWidget_(d, iFalse); 1224 setMobileEditMode_SidebarWidget_(d, iFalse);
1225 }
1226 } 1225 }
1226 }
1227 showToolbar_Root(w->root, isHiding); 1227 showToolbar_Root(w->root, isHiding);
1228 } 1228 }
1229 updateToolbarColors_Root(w->root); 1229 updateToolbarColors_Root(w->root);
@@ -1231,7 +1231,7 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
1231 /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ 1231 /* BUG: Rearranging because the arrange above didn't fully resolve the height. */
1232 arrange_Widget(w); 1232 arrange_Widget(w);
1233 if (!isPortraitPhone_App()) { 1233 if (!isPortraitPhone_App()) {
1234 updateSize_DocumentWidget(document_App()); 1234 updateSize_DocumentWidget(document_App());
1235 } 1235 }
1236 if (isVisible_Widget(w)) { 1236 if (isVisible_Widget(w)) {
1237 updateItems_SidebarWidget_(d); 1237 updateItems_SidebarWidget_(d);
@@ -1337,7 +1337,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1337 /* In sliding sheet mode, sidebar is resized to fit in the safe area. */ 1337 /* In sliding sheet mode, sidebar is resized to fit in the safe area. */
1338 setPadding_Widget(d->actions, 0, 0, 0, bottomSafeInset_Mobile()); 1338 setPadding_Widget(d->actions, 0, 0, 0, bottomSafeInset_Mobile());
1339 } 1339 }
1340 return iFalse; 1340 return iFalse;
1341 } 1341 }
1342 else if (isMetricsChange_UserEvent(ev)) { 1342 else if (isMetricsChange_UserEvent(ev)) {
1343 if (isVisible_Widget(w)) { 1343 if (isVisible_Widget(w)) {
@@ -1521,8 +1521,8 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1521 iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); 1521 iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id);
1522 if (flag == subscribed_BookmarkFlag && (bm->flags & flag)) { 1522 if (flag == subscribed_BookmarkFlag && (bm->flags & flag)) {
1523 removeEntries_Feeds(item->id); /* get rid of unsubscribed entries */ 1523 removeEntries_Feeds(item->id); /* get rid of unsubscribed entries */
1524 }
1525 bm->flags ^= flag; 1524 bm->flags ^= flag;
1525 }
1526 postCommand_App("bookmarks.changed"); 1526 postCommand_App("bookmarks.changed");
1527 } 1527 }
1528 return iTrue; 1528 return iTrue;
@@ -1672,10 +1672,10 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1672 0, 1672 0,
1673 format_CStr("!feed.entry.unsubscribe arg:1 ptr:%p", d) } }, 1673 format_CStr("!feed.entry.unsubscribe arg:1 ptr:%p", d) } },
1674 2); 1674 2);
1675 } 1675 }
1676 return iTrue; 1676 return iTrue;
1677 } 1677 }
1678 } 1678 }
1679 } 1679 }
1680 } 1680 }
1681 else if (isCommand_Widget(w, ev, "history.delete")) { 1681 else if (isCommand_Widget(w, ev, "history.delete")) {
@@ -1731,7 +1731,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1731 /* Update cursor. */ 1731 /* Update cursor. */
1732 else if (contains_Widget(w, mouse)) { 1732 else if (contains_Widget(w, mouse)) {
1733 const iSidebarItem *item = constHoverItem_ListWidget(d->list); 1733 const iSidebarItem *item = constHoverItem_ListWidget(d->list);
1734 setCursor_Window(get_Window(), 1734 setCursor_Window(get_Window(),
1735 item ? (item->listItem.isSeparator ? SDL_SYSTEM_CURSOR_ARROW 1735 item ? (item->listItem.isSeparator ? SDL_SYSTEM_CURSOR_ARROW
1736 : SDL_SYSTEM_CURSOR_HAND) 1736 : SDL_SYSTEM_CURSOR_HAND)
1737 : SDL_SYSTEM_CURSOR_ARROW); 1737 : SDL_SYSTEM_CURSOR_ARROW);
@@ -1791,9 +1791,9 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1791 isRead ? circle_Icon " ${feeds.entry.markunread}" 1791 isRead ? circle_Icon " ${feeds.entry.markunread}"
1792 : circleWhite_Icon " ${feeds.entry.markread}"); 1792 : circleWhite_Icon " ${feeds.entry.markread}");
1793 } 1793 }
1794 } 1794 }
1795 } 1795 }
1796 } 1796 }
1797 if (ev->type == SDL_KEYDOWN) { 1797 if (ev->type == SDL_KEYDOWN) {
1798 const int key = ev->key.keysym.sym; 1798 const int key = ev->key.keysym.sym;
1799 const int kmods = keyMods_Sym(ev->key.keysym.mod); 1799 const int kmods = keyMods_Sym(ev->key.keysym.mod);
diff --git a/src/ui/util.c b/src/ui/util.c
index e631a092..13bfd92f 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -775,7 +775,7 @@ void makeMenuItems_Widget(iWidget *menu, const iMenuItem *items, size_t n) {
775 drawKey_WidgetFlag | itemFlags); 775 drawKey_WidgetFlag | itemFlags);
776 setWrap_LabelWidget(label, isInfo); 776 setWrap_LabelWidget(label, isInfo);
777 if (!isInfo) { 777 if (!isInfo) {
778 haveIcons |= checkIcon_LabelWidget(label); 778 haveIcons |= checkIcon_LabelWidget(label);
779 } 779 }
780 setFlags_Widget(as_Widget(label), disabled_WidgetFlag, isDisabled); 780 setFlags_Widget(as_Widget(label), disabled_WidgetFlag, isDisabled);
781 if (isInfo) { 781 if (isInfo) {
@@ -935,7 +935,7 @@ static void updateMenuItemFonts_Widget_(iWidget *d) {
935 } 935 }
936 switch (deviceType_App()) { 936 switch (deviceType_App()) {
937 case desktop_AppDeviceType: 937 case desktop_AppDeviceType:
938 setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); 938 setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId);
939 break; 939 break;
940 case tablet_AppDeviceType: 940 case tablet_AppDeviceType:
941 setFont_LabelWidget(label, isCaution ? uiLabelMediumBold_FontId : uiLabelMedium_FontId); 941 setFont_LabelWidget(label, isCaution ? uiLabelMediumBold_FontId : uiLabelMedium_FontId);
@@ -1084,8 +1084,9 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
1084 } 1084 }
1085 arrange_Widget(d); /* need to know the height */ 1085 arrange_Widget(d); /* need to know the height */
1086 iBool allowOverflow = iFalse; 1086 iBool allowOverflow = iFalse;
1087 /* A vertical offset determined by a possible selected label in the menu. */ 1087 /* A vertical offset determined by a possible selected label in the menu. */
1088 if (deviceType_App() == desktop_AppDeviceType) { 1088 if (deviceType_App() == desktop_AppDeviceType &&
1089 windowCoord.y < rootSize.y - lineHeight_Text(uiNormal_FontSize) * 3) {
1089 iConstForEach(ObjectList, child, children_Widget(d)) { 1090 iConstForEach(ObjectList, child, children_Widget(d)) {
1090 const iWidget *item = constAs_Widget(child.object); 1091 const iWidget *item = constAs_Widget(child.object);
1091 if (flags_Widget(item) & selected_WidgetFlag) { 1092 if (flags_Widget(item) & selected_WidgetFlag) {
@@ -1171,7 +1172,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
1171 arrange_Widget(d); 1172 arrange_Widget(d);
1172 if (!isSlidePanel) { 1173 if (!isSlidePanel) {
1173 /* LAYOUT BUG: Height of wrapped menu items is incorrect with a single arrange! */ 1174 /* LAYOUT BUG: Height of wrapped menu items is incorrect with a single arrange! */
1174 arrange_Widget(d); 1175 arrange_Widget(d);
1175 } 1176 }
1176 if (deviceType_App() == phone_AppDeviceType) { 1177 if (deviceType_App() == phone_AppDeviceType) {
1177 if (isSlidePanel) { 1178 if (isSlidePanel) {
@@ -1379,6 +1380,32 @@ void updateDropdownSelection_LabelWidget(iLabelWidget *dropButton, const char *s
1379 } 1380 }
1380} 1381}
1381 1382
1383const char *selectedDropdownCommand_LabelWidget(const iLabelWidget *dropButton) {
1384 if (!dropButton) {
1385 return "";
1386 }
1387 iWidget *menu = findChild_Widget(constAs_Widget(dropButton), "menu");
1388 if (flags_Widget(menu) & nativeMenu_WidgetFlag) {
1389 iConstForEach(Array, i, userData_Object(menu)) {
1390 const iMenuItem *item = i.value;
1391 if (item->label && startsWithCase_CStr(item->label, "###")) {
1392 return item->command ? item->command : "";
1393 }
1394 }
1395 }
1396 else {
1397 iForEach(ObjectList, i, children_Widget(menu)) {
1398 if (isInstance_Object(i.object, &Class_LabelWidget)) {
1399 iLabelWidget *item = i.object;
1400 if (flags_Widget(i.object) & selected_WidgetFlag) {
1401 return cstr_String(command_LabelWidget(item));
1402 }
1403 }
1404 }
1405 }
1406 return "";
1407}
1408
1382/*-----------------------------------------------------------------------------------------------*/ 1409/*-----------------------------------------------------------------------------------------------*/
1383 1410
1384static iBool isTabPage_Widget_(const iWidget *tabs, const iWidget *page) { 1411static iBool isTabPage_Widget_(const iWidget *tabs, const iWidget *page) {
@@ -1831,10 +1858,10 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con
1831 addChild_Widget(parent, iClob(dlg)); 1858 addChild_Widget(parent, iClob(dlg));
1832 } 1859 }
1833 if (deviceType_App() == desktop_AppDeviceType) { /* conserve space on mobile */ 1860 if (deviceType_App() == desktop_AppDeviceType) { /* conserve space on mobile */
1834 setId_Widget( 1861 setId_Widget(
1835 addChildFlags_Widget(dlg, iClob(new_LabelWidget(title, NULL)), 1862 addChildFlags_Widget(dlg, iClob(new_LabelWidget(title, NULL)),
1836 frameless_WidgetFlag), 1863 frameless_WidgetFlag),
1837 "valueinput.title"); 1864 "valueinput.title");
1838 } 1865 }
1839 iLabelWidget *promptLabel; 1866 iLabelWidget *promptLabel;
1840 setId_Widget(addChildFlags_Widget( 1867 setId_Widget(addChildFlags_Widget(
@@ -1864,8 +1891,8 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con
1864 } 1891 }
1865 pushBack_Array(&actions, &(iMenuItem){ 1892 pushBack_Array(&actions, &(iMenuItem){
1866 acceptLabel, 1893 acceptLabel,
1867 SDLK_RETURN, 1894 SDLK_RETURN,
1868 acceptKeyMod_ReturnKeyBehavior(prefs_App()->returnKey), 1895 acceptKeyMod_ReturnKeyBehavior(prefs_App()->returnKey),
1869 "valueinput.accept" 1896 "valueinput.accept"
1870 }); 1897 });
1871 addChildPos_Widget(dlg, 1898 addChildPos_Widget(dlg,
@@ -2352,6 +2379,7 @@ iWidget *makePreferences_Widget(void) {
2352 { u8"Interlingua - ia", 0, 0, "uilang id:ia" }, 2379 { u8"Interlingua - ia", 0, 0, "uilang id:ia" },
2353 { u8"Interlingue - ie", 0, 0, "uilang id:ie" }, 2380 { u8"Interlingue - ie", 0, 0, "uilang id:ie" },
2354 { u8"Interslavic - isv", 0, 0, "uilang id:isv" }, 2381 { u8"Interslavic - isv", 0, 0, "uilang id:isv" },
2382 { u8"Nederlands - nl", 0, 0, "uilang id:nl" },
2355 { u8"Polski - pl", 0, 0, "uilang id:pl" }, 2383 { u8"Polski - pl", 0, 0, "uilang id:pl" },
2356 { u8"Русский - ru", 0, 0, "uilang id:ru" }, 2384 { u8"Русский - ru", 0, 0, "uilang id:ru" },
2357 { u8"Slovak - sk", 0, 0, "uilang id:sk" }, 2385 { u8"Slovak - sk", 0, 0, "uilang id:sk" },
@@ -2519,8 +2547,8 @@ iWidget *makePreferences_Widget(void) {
2519 { "buttons id:prefs.mono", 0, 0, (const void *) monoFontItems }, 2547 { "buttons id:prefs.mono", 0, 0, (const void *) monoFontItems },
2520 { "padding" }, 2548 { "padding" },
2521 { "dropdown id:prefs.font.monodoc", 0, 0, (const void *) constData_Array(makeFontItems_("monodoc")) }, 2549 { "dropdown id:prefs.font.monodoc", 0, 0, (const void *) constData_Array(makeFontItems_("monodoc")) },
2522 { "padding" },
2523 { "toggle id:prefs.font.warnmissing" }, 2550 { "toggle id:prefs.font.warnmissing" },
2551 { "padding" },
2524 { "heading id:prefs.gemtext.ansi" }, 2552 { "heading id:prefs.gemtext.ansi" },
2525 { "toggle id:prefs.gemtext.ansi.fg" }, 2553 { "toggle id:prefs.gemtext.ansi.fg" },
2526 { "toggle id:prefs.gemtext.ansi.bg" }, 2554 { "toggle id:prefs.gemtext.ansi.bg" },
@@ -2528,8 +2556,8 @@ iWidget *makePreferences_Widget(void) {
2528// { "toggle id:prefs.font.smooth" }, 2556// { "toggle id:prefs.font.smooth" },
2529// { "padding" }, 2557// { "padding" },
2530// { "dropdown id:prefs.font.ui", 0, 0, (const void *) constData_Array(makeFontItems_("ui")) }, 2558// { "dropdown id:prefs.font.ui", 0, 0, (const void *) constData_Array(makeFontItems_("ui")) },
2531 { "padding" },
2532 { "button text:" fontpack_Icon " " uiTextAction_ColorEscape "${menu.fonts}", 0, 0, "!open url:about:fonts" }, 2559 { "button text:" fontpack_Icon " " uiTextAction_ColorEscape "${menu.fonts}", 0, 0, "!open url:about:fonts" },
2560 { "padding" },
2533 { NULL } 2561 { NULL }
2534 }; 2562 };
2535 const iMenuItem stylePanelItems[] = { 2563 const iMenuItem stylePanelItems[] = {
@@ -2994,47 +3022,47 @@ iWidget *makeBookmarkEditor_Widget(void) {
2994 } 3022 }
2995 else { 3023 else {
2996 dlg = makeSheet_Widget("bmed"); 3024 dlg = makeSheet_Widget("bmed");
2997 setId_Widget(addChildFlags_Widget( 3025 setId_Widget(addChildFlags_Widget(
2998 dlg, 3026 dlg,
2999 iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.bookmark.edit}", NULL)), 3027 iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.bookmark.edit}", NULL)),
3000 frameless_WidgetFlag), 3028 frameless_WidgetFlag),
3001 "bmed.heading"); 3029 "bmed.heading");
3002 iWidget *headings, *values; 3030 iWidget *headings, *values;
3003 addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); 3031 addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values)));
3004 iInputWidget *inputs[4]; 3032 iInputWidget *inputs[4];
3005 /* Folder to add to. */ { 3033 /* Folder to add to. */ {
3006 addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.bookmark.folder}"))); 3034 addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.bookmark.folder}")));
3007 const iArray *folderItems = makeBookmarkFolderItems_(iFalse); 3035 const iArray *folderItems = makeBookmarkFolderItems_(iFalse);
3008 iLabelWidget *folderButton; 3036 iLabelWidget *folderButton;
3009 setId_Widget(addChildFlags_Widget(values, 3037 setId_Widget(addChildFlags_Widget(values,
3010 iClob(folderButton = makeMenuButton_LabelWidget( 3038 iClob(folderButton = makeMenuButton_LabelWidget(
3011 widestLabel_MenuItemArray(folderItems), 3039 widestLabel_MenuItemArray(folderItems),
3012 constData_Array(folderItems), 3040 constData_Array(folderItems),
3013 size_Array(folderItems))), alignLeft_WidgetFlag), 3041 size_Array(folderItems))), alignLeft_WidgetFlag),
3014 "bmed.folder"); 3042 "bmed.folder");
3015 } 3043 }
3016 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.title}", "bmed.title", iClob(inputs[0] = new_InputWidget(0))); 3044 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.title}", "bmed.title", iClob(inputs[0] = new_InputWidget(0)));
3017 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.url}", "bmed.url", iClob(inputs[1] = new_InputWidget(0))); 3045 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.url}", "bmed.url", iClob(inputs[1] = new_InputWidget(0)));
3018 setUrlContent_InputWidget(inputs[1], iTrue); 3046 setUrlContent_InputWidget(inputs[1], iTrue);
3019 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.tags}", "bmed.tags", iClob(inputs[2] = new_InputWidget(0))); 3047 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.tags}", "bmed.tags", iClob(inputs[2] = new_InputWidget(0)));
3020 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.icon}", "bmed.icon", iClob(inputs[3] = new_InputWidget(1))); 3048 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.icon}", "bmed.icon", iClob(inputs[3] = new_InputWidget(1)));
3021 /* Buttons for special tags. */ 3049 /* Buttons for special tags. */
3022 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); 3050 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
3023 iWidget *special = addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); 3051 iWidget *special = addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values)));
3024 setFlags_Widget(special, collapse_WidgetFlag, iTrue); 3052 setFlags_Widget(special, collapse_WidgetFlag, iTrue);
3025 setId_Widget(special, "bmed.special"); 3053 setId_Widget(special, "bmed.special");
3026 makeTwoColumnHeading_("${heading.bookmark.tags}", headings, values); 3054 makeTwoColumnHeading_("${heading.bookmark.tags}", headings, values);
3027 addDialogToggle_(headings, values, "${bookmark.tag.home}", "bmed.tag.home"); 3055 addDialogToggle_(headings, values, "${bookmark.tag.home}", "bmed.tag.home");
3028 addDialogToggle_(headings, values, "${bookmark.tag.remote}", "bmed.tag.remote"); 3056 addDialogToggle_(headings, values, "${bookmark.tag.remote}", "bmed.tag.remote");
3029 addDialogToggle_(headings, values, "${bookmark.tag.linksplit}", "bmed.tag.linksplit"); 3057 addDialogToggle_(headings, values, "${bookmark.tag.linksplit}", "bmed.tag.linksplit");
3030 arrange_Widget(dlg); 3058 arrange_Widget(dlg);
3031 for (int i = 0; i < 3; ++i) { 3059 for (int i = 0; i < 3; ++i) {
3032 as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x; 3060 as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x;
3033 } 3061 }
3034 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); 3062 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
3035 addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); 3063 addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions))));
3036 addChild_Widget(get_Root()->widget, iClob(dlg)); 3064 addChild_Widget(get_Root()->widget, iClob(dlg));
3037 setupSheetTransition_Mobile(dlg, iTrue); 3065 setupSheetTransition_Mobile(dlg, iTrue);
3038 } 3066 }
3039 /* Use a recently accessed folder as the default. */ 3067 /* Use a recently accessed folder as the default. */
3040 const uint32_t recentFolderId = recentFolder_Bookmarks(bookmarks_App()); 3068 const uint32_t recentFolderId = recentFolder_Bookmarks(bookmarks_App());
diff --git a/src/ui/util.h b/src/ui/util.h
index 4fedd083..618022b9 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -285,6 +285,7 @@ int checkContextMenu_Widget (iWidget *, const SDL_Event *ev)
285 285
286iLabelWidget * makeMenuButton_LabelWidget (const char *label, const iMenuItem *items, size_t n); 286iLabelWidget * makeMenuButton_LabelWidget (const char *label, const iMenuItem *items, size_t n);
287void updateDropdownSelection_LabelWidget (iLabelWidget *dropButton, const char *selectedCommand); 287void updateDropdownSelection_LabelWidget (iLabelWidget *dropButton, const char *selectedCommand);
288const char * selectedDropdownCommand_LabelWidget (const iLabelWidget *dropButton);
288 289
289/*-----------------------------------------------------------------------------------------------*/ 290/*-----------------------------------------------------------------------------------------------*/
290 291
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 1ac4326a..9f67b1c7 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -2023,7 +2023,8 @@ iBool isAffectedByVisualOffset_Widget(const iWidget *d) {
2023} 2023}
2024 2024
2025void setFocus_Widget(iWidget *d) { 2025void setFocus_Widget(iWidget *d) {
2026 iWindow *win = get_Window(); 2026 iWindow *win = d ? window_Widget(d) : get_Window();
2027 iAssert(win);
2027 if (win->focus != d) { 2028 if (win->focus != d) {
2028 if (win->focus) { 2029 if (win->focus) {
2029 iAssert(!contains_PtrSet(win->focus->root->pendingDestruction, win->focus)); 2030 iAssert(!contains_PtrSet(win->focus->root->pendingDestruction, win->focus));
@@ -2038,6 +2039,13 @@ void setFocus_Widget(iWidget *d) {
2038 } 2039 }
2039} 2040}
2040 2041
2042void setKeyboardGrab_Widget(iWidget *d) {
2043 iWindow *win = d ? window_Widget(d) : get_Window();
2044 iAssert(win);
2045 win->focus = d;
2046 /* no notifications sent */
2047}
2048
2041iWidget *focus_Widget(void) { 2049iWidget *focus_Widget(void) {
2042 return get_Window()->focus; 2050 return get_Window()->focus;
2043} 2051}
diff --git a/src/ui/widget.h b/src/ui/widget.h
index eb3004dc..fb7eb5e2 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -327,7 +327,8 @@ void scrollInfo_Widget (const iWidget *, iWidgetScrollInfo *inf
327 327
328int backgroundFadeColor_Widget (void); 328int backgroundFadeColor_Widget (void);
329 329
330void setFocus_Widget (iWidget *); 330void setFocus_Widget (iWidget *); /* widget must be flagged `focusable` */
331void setKeyboardGrab_Widget (iWidget *); /* sets focus on any widget */
331iWidget * focus_Widget (void); 332iWidget * focus_Widget (void);
332void setHover_Widget (iWidget *); 333void setHover_Widget (iWidget *);
333iWidget * hover_Widget (void); 334iWidget * hover_Widget (void);