Lagrange [work/v1.7]

Mobile: Working on dialogs

=> b85471a5b84f0837611fb47be35ee713139f702f

diff --git a/src/app.c b/src/app.c
index fa8cc105..0d0f2ffd 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1653,34 +1653,20 @@ static void updateScrollSpeedButtons_(iWidget *d, enum iScrollType type, const i
     }
 }
 
-static void updateDropdownSelection_(iLabelWidget *dropButton, const char *selectedCommand) {
-    iWidget *menu = findChild_Widget(as_Widget(dropButton), "menu");
-    iForEach(ObjectList, i, children_Widget(menu)) {
-        if (isInstance_Object(i.object, &Class_LabelWidget)) {
-            iLabelWidget *item = i.object;
-            const iBool isSelected = endsWith_String(command_LabelWidget(item), selectedCommand);
-            setFlags_Widget(as_Widget(item), selected_WidgetFlag, isSelected);
-            if (isSelected) {
-                updateText_LabelWidget(dropButton, sourceText_LabelWidget(item));
-            }
-        }
-    }
-}
-
 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));
+    updateDropdownSelection_LabelWidget(button, format_CStr(".set arg:%d", theme));
 }
 
 static void updateFontButton_(iLabelWidget *button, int font) {
     if (!button) return;
-    updateDropdownSelection_(button, format_CStr(".set arg:%d", font));
+    updateDropdownSelection_LabelWidget(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));
+    updateDropdownSelection_LabelWidget(button, format_CStr(".set arg:%d", style));
 }
 
 static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
@@ -1733,8 +1719,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
         return iTrue;
     }
     else if (equal_Command(cmd, "uilang")) {
-        updateDropdownSelection_(findChild_Widget(d, "prefs.uilang"),
-                                 cstr_String(string_Command(cmd, "id")));
+        updateDropdownSelection_LabelWidget(findChild_Widget(d, "prefs.uilang"),
+                                            cstr_String(string_Command(cmd, "id")));
         return iFalse;
     }
     else if (equal_Command(cmd, "quoteicon.set")) {
@@ -1744,8 +1730,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
         return iFalse;
     }
     else if (equal_Command(cmd, "returnkey.set")) {
-        updateDropdownSelection_(findChild_Widget(d, "prefs.returnkey"),
-                                 format_CStr("returnkey.set arg:%d", arg_Command(cmd)));
+        updateDropdownSelection_LabelWidget(findChild_Widget(d, "prefs.returnkey"),
+                                            format_CStr("returnkey.set arg:%d", arg_Command(cmd)));
         return iFalse;
     }
     else if (equal_Command(cmd, "pinsplit.set")) {
@@ -1849,7 +1835,8 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe
 static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {
     iApp *d = &app_;
     if (equal_Command(cmd, "ident.showmore")) {
-        iForEach(ObjectList, i, children_Widget(findChild_Widget(dlg, "headings"))) {
+        iForEach(ObjectList, i,
+                 children_Widget(findChild_Widget(dlg,                                                                 isUsingPanelLayout_Mobile() ? "panel.top" : "headings"))) {
             if (flags_Widget(i.object) & collapse_WidgetFlag) {
                 setFlags_Widget(i.object, hidden_WidgetFlag, iFalse);
             }
@@ -1859,8 +1846,7 @@ static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {
                 setFlags_Widget(j.object, hidden_WidgetFlag, iFalse);
             }
         }
-        setFlags_Widget(child_Widget(findChild_Widget(dlg, "dialogbuttons"), 0), disabled_WidgetFlag,
-                        iTrue);
+        setFlags_Widget(pointer_Command(cmd), disabled_WidgetFlag, iTrue);
         arrange_Widget(dlg);
         refresh_Widget(dlg);        
         return iTrue;
@@ -1870,6 +1856,7 @@ static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {
         setText_LabelWidget(scope,
                             text_LabelWidget(child_Widget(
                                 findChild_Widget(as_Widget(scope), "menu"), arg_Command(cmd))));
+        arrange_Widget(findWidget_App("ident"));
         return iTrue;
     }
     if (equal_Command(cmd, "ident.temp.changed")) {
@@ -2596,9 +2583,10 @@ iBool handleCommand_App(const char *cmd) {
         updatePrefsPinSplitButtons_(dlg, d->prefs.pinSplit);
         updateScrollSpeedButtons_(dlg, mouse_ScrollType, d->prefs.smoothScrollSpeed[mouse_ScrollType]);
         updateScrollSpeedButtons_(dlg, keyboard_ScrollType, d->prefs.smoothScrollSpeed[keyboard_ScrollType]);
-        updateDropdownSelection_(findChild_Widget(dlg, "prefs.uilang"), cstr_String(&d->prefs.uiLanguage));
-        updateDropdownSelection_(findChild_Widget(dlg, "prefs.returnkey"),
-                                 format_CStr("returnkey.set arg:%d", d->prefs.returnKey));
+        updateDropdownSelection_LabelWidget(findChild_Widget(dlg, "prefs.uilang"), cstr_String(&d->prefs.uiLanguage));
+        updateDropdownSelection_LabelWidget(
+            findChild_Widget(dlg, "prefs.returnkey"),
+            format_CStr("returnkey.set arg:%d", d->prefs.returnKey));
         setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize);
         setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"),
                             collectNewFormat_String("%g", uiScale_Window(d->window)));
diff --git a/src/ui/certimportwidget.c b/src/ui/certimportwidget.c
index a8346e19..2e60c71f 100644
--- a/src/ui/certimportwidget.c
+++ b/src/ui/certimportwidget.c
@@ -152,7 +152,7 @@ void init_CertImportWidget(iCertImportWidget *d) {
     /* Buttons. */
     addChild_Widget(w, iClob(makePadding_Widget(gap_UI)));
     iWidget *buttons = makeDialogButtons_Widget(
-        (iMenuItem[]){ { "${cancel}", 0, 0, NULL },
+        (iMenuItem[]){ { "${cancel}" },
                        { uiTextAction_ColorEscape "${dlg.certimport.import}",
                          SDLK_RETURN,
                          KMOD_PRIMARY,
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 6ca4fd8d..83f2ea6a 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -3127,7 +3127,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
                 makeQuestion_Widget(
                     uiHeading_ColorEscape "${heading.import.bookmarks}",
                     formatCStrs_Lang("dlg.import.found.n", count),
-                    (iMenuItem[]){ { "${cancel}", 0, 0, NULL },
+                    (iMenuItem[]){ { "${cancel}" },
                                    { format_CStr(cstrCount_Lang("dlg.import.add.n", (int) count),
                                                  uiTextAction_ColorEscape,
                                                  count),
@@ -3299,7 +3299,7 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev
                 d->playerMenu = makeMenu_Widget(
                     as_Widget(d),
                     (iMenuItem[]){
-                        { cstrCollect_String(metadataLabel_Player(plr)), 0, 0, NULL },
+                        { cstrCollect_String(metadataLabel_Player(plr)) },
                     },
                     1);
                 openMenu_Widget(d->playerMenu, bottomLeft_Rect(ui.menuRect));
@@ -3604,7 +3604,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
                         pushBackN_Array(
                             &items,
                             (iMenuItem[]){
-                                { "---", 0, 0, NULL },
+                                { "---" },
                                 { isGemini ? "${link.noproxy}" : openExt_Icon " ${link.browser}",
                                   0,
                                   0,
@@ -3615,7 +3615,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
                         linkLabel_GmDocument(d->doc, d->contextLink->linkId));
                     urlEncodeSpaces_String(linkLabel);
                     pushBackN_Array(&items,
-                                    (iMenuItem[]){ { "---", 0, 0, NULL },
+                                    (iMenuItem[]){ { "---" },
                                                    { "${link.copy}", 0, 0, "document.copylink" },
                                                    { bookmark_Icon " ${link.bookmark}",
                                                      0,
@@ -3627,7 +3627,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
                                     3);
                     if (isNative && d->contextLink->mediaType != download_GmRunMediaType) {
                         pushBackN_Array(&items, (iMenuItem[]){
-                            { "---", 0, 0, NULL },
+                            { "---" },
                             { download_Icon " ${link.download}", 0, 0, "document.downloadlink" },
                         }, 2);
                     }
@@ -3670,22 +3670,22 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
                             { "${menu.forward}", navigateForward_KeyShortcut, "navigate.forward" },
                             { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" },
                             { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" },
-                            { "---", 0, 0, NULL },
+                            { "---" },
                             { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" },
                             { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" },
-                            { "---", 0, 0, NULL },
+                            { "---" },
                             { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" },
                             { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" },
-                            { "---", 0, 0, NULL },
+                            { "---" },
                             { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" },
                             { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" },
 #if defined (iPlatformMobile)
-                            { "---", 0, 0, NULL },
+                            { "---" },
                             { "${menu.page.copyurl}", 0, 0, "document.copylink" } },
                         14);
 #else
                             { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" },
-                            { "---", 0, 0, NULL },
+                            { "---" },
                             { "${menu.page.copyurl}", 0, 0, "document.copylink" } },
                         15);
 #endif
@@ -3862,7 +3862,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
                         }
                         d->copyMenu = makeMenu_Widget(w, (iMenuItem[]){
                             { clipCopy_Icon " ${menu.copy}", 0, 0, "copy" },
-                            { "---", 0, 0, NULL },
+                            { "---" },
                             { close_Icon " ${menu.select.clear}", 0, 0, "document.select arg:0" },
                         }, 3);
                         setFlags_Widget(d->copyMenu, noFadeBackground_WidgetFlag, iTrue);
@@ -3955,7 +3955,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
                                 uiTextAction_ColorEscape,
                                 cstr_String(url)),
                             (iMenuItem[]){
-                                { "${cancel}", 0, 0, NULL },
+                                { "${cancel}" },
                                 { uiTextCaution_ColorEscape "${dlg.openlink}",
                                   0, 0, format_CStr("!open default:1 url:%s", cstr_String(url)) } },
                             2);
diff --git a/src/ui/mobile.c b/src/ui/mobile.c
index 7e359a84..f3e23e06 100644
--- a/src/ui/mobile.c
+++ b/src/ui/mobile.c
@@ -36,7 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 #   include "ios.h"
 #endif
 
-static iBool useMobileSheetLayout_(void) {
+iBool isUsingPanelLayout_Mobile(void) {
     return deviceType_App() != desktop_AppDeviceType;
 }
 
@@ -381,6 +381,7 @@ static size_t countItems_(const iMenuItem *itemsNullTerminated) {
 void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) {
     iWidget *     widget  = NULL;
     iLabelWidget *heading = NULL;
+    iWidget *     value   = NULL;
     const char *  spec    = item->label;
     const char *  id      = cstr_Rangecc(range_Command(spec, "id"));
     const char *  label   = hasLabel_Command(spec, "text")
@@ -396,6 +397,7 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) {
         setFont_LabelWidget(title, uiLabelLargeBold_FontId);
         setTextColor_LabelWidget(title, uiHeading_ColorId);
         setAllCaps_LabelWidget(title, iTrue);
+        setId_Widget(as_Widget(title), id);
     }
     else if (equal_Command(spec, "heading")) {
         addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_()))));
@@ -403,6 +405,7 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) {
         setAllCaps_LabelWidget(heading, iTrue);
         setRemoveTrailingColon_LabelWidget(heading, iTrue);
         addChild_Widget(panel, iClob(heading));
+        setId_Widget(as_Widget(heading), id);
     }    
     else if (equal_Command(spec, "toggle")) {
         iLabelWidget *toggle = (iLabelWidget *) makeToggle_Widget(id);
@@ -412,7 +415,9 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) {
     }
     else if (equal_Command(spec, "dropdown")) {
         const iMenuItem *dropItems = item->data;
-        iLabelWidget *drop = makeMenuButton_LabelWidget("", dropItems, countItems_(dropItems));
+        iLabelWidget *drop = makeMenuButton_LabelWidget(dropItems[0].label,
+                                                        dropItems, countItems_(dropItems));
+        value = as_Widget(drop);
         setFont_LabelWidget(drop, labelFont_());
         setFlags_Widget(as_Widget(drop),
                         alignRight_WidgetFlag | noBackground_WidgetFlag |
@@ -465,6 +470,9 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) {
     }
     else if (equal_Command(spec, "input")) {
         iInputWidget *input = new_InputWidget(argU32Label_Command(spec, "maxlen"));
+        if (hasLabel_Command(spec, "hint")) {
+            setHint_InputWidget(input, cstr_Lang(cstr_Rangecc(range_Command(spec, "hint"))));
+        }
         setId_Widget(as_Widget(input), id);
         setUrlContent_InputWidget(input, argLabel_Command(spec, "url"));
         setSelectAllOnFocus_InputWidget(input, argLabel_Command(spec, "selectall"));        
@@ -491,6 +499,12 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) {
     else if (equal_Command(spec, "button")) {
         widget = as_Widget(heading = makePanelButton_(label, item->command));
     }
+    else if (equal_Command(spec, "label")) {
+        iLabelWidget *lab = new_LabelWidget(label, NULL);
+        widget = as_Widget(lab);
+        setWrap_LabelWidget(lab, iTrue);
+        setFlags_Widget(widget, frameless_WidgetFlag, iTrue);
+    }
     else if (equal_Command(spec, "padding")) {
         widget = makePadding_Widget(lineHeight_Text(labelFont_()) * 1.5f);
     }
@@ -500,8 +514,14 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) {
         if (icon) {
             setIcon_LabelWidget(heading, icon);
         }
+        if (value && as_Widget(heading) != value) {
+            as_Widget(heading)->sizeRef = value; /* heading height matches value widget */
+        }
     }
     if (widget) {
+        setFlags_Widget(widget,
+                        collapse_WidgetFlag | hidden_WidgetFlag,
+                        argLabel_Command(spec, "collapse") != 0);
         addChild_Widget(panel, iClob(widget));
     }
 }
@@ -512,11 +532,26 @@ void makePanelItems_Mobile(iWidget *panel, const iMenuItem *itemsNullTerminated)
     }
 }
 
-iWidget *makePanels_Mobile(const iMenuItem *itemsNullTerminated) {
+static const iMenuItem *findDialogCancelAction_(const iMenuItem *items, size_t n) {
+    if (n <= 1) {
+        return NULL;
+    }
+    for (size_t i = 0; i < n - 1; i++) {
+        if (!iCmpStr(items[i].label, "${cancel}")) {
+            return &items[i];
+        }
+    }
+    return NULL;
+}
+
+iWidget *makePanels_Mobile(const char *id,
+                           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,
                     resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag |
@@ -553,18 +588,21 @@ iWidget *makePanels_Mobile(const iMenuItem *itemsNullTerminated) {
         topPanel->offsetRef = detailStack;
     }
     /* Navigation bar at the top. */
+    iLabelWidget *naviBack;
     iWidget *navi = new_Widget(); {
         setId_Widget(navi, "panel.navi");
         setBackgroundColor_Widget(navi, uiBackground_ColorId);
         addChild_Widget(navi, iClob(makePadding_Widget(0)));
-        iLabelWidget *back = addChildFlags_Widget(
+        naviBack = addChildFlags_Widget(
             navi,
-            iClob(new_LabelWidget(leftAngle_Icon " ${panel.back}", "panel.close")),
+            iClob(newKeyMods_LabelWidget(leftAngle_Icon " ${panel.back}",
+                                         SDLK_ESCAPE, 0,
+                                         "panel.close")),
             noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag |
                 extraPadding_WidgetFlag);
-        checkIcon_LabelWidget(back);
-        setId_Widget(as_Widget(back), "panel.back");
-        setFont_LabelWidget(back, labelFont_());
+        checkIcon_LabelWidget(naviBack);
+        setId_Widget(as_Widget(naviBack), "panel.back");
+        setFont_LabelWidget(naviBack, labelFont_());
         addChildFlags_Widget(sheet, iClob(navi),
                              drawBackgroundToVerticalSafeArea_WidgetFlag |
                                  arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
@@ -593,6 +631,54 @@ iWidget *makePanels_Mobile(const iMenuItem *itemsNullTerminated) {
             makePanelItem_Mobile(topPanel, item);
         }
     }
+    /* Actions. */
+    if (numActions) {
+        /* Some actions go in the navigation bar and some go on the top panel. */
+        const iMenuItem *cancelItem = findDialogCancelAction_(actions, numActions);
+        const iMenuItem *defaultItem = &actions[numActions - 1];
+        iAssert(defaultItem);
+        if (!cancelItem) {
+            updateTextCStr_LabelWidget(naviBack, defaultItem->label);
+            setCommand_LabelWidget(naviBack, collectNewCStr_String(defaultItem->command));
+            setFlags_Widget(as_Widget(naviBack), alignLeft_WidgetFlag, iFalse);
+            setFlags_Widget(as_Widget(naviBack), alignRight_WidgetFlag, iTrue);
+            setIcon_LabelWidget(naviBack, 0);
+            setFont_LabelWidget(naviBack, labelBoldFont_());            
+        }
+        else {
+            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),
+                            frameless_WidgetFlag | extraPadding_WidgetFlag |
+                                noBackground_WidgetFlag,
+                            iTrue);
+            addChildFlags_Widget(as_Widget(naviBack), iClob(defaultButton),
+                                 moveToParentRightEdge_WidgetFlag);
+            updateSize_LabelWidget(defaultButton);
+        }
+        /* All other actions are added as buttons. */
+        iBool needPadding = iTrue;
+        for (size_t i = 0; i < numActions; i++) {
+            const iMenuItem *act = &actions[i];
+            if (act == cancelItem || act == defaultItem) {
+                continue;
+            }
+            if (!iCmpStr(act->label, "---")) {
+                continue;
+            }
+            if (needPadding) {
+                makePanelItem_Mobile(topPanel, &(iMenuItem){ "padding" });
+                needPadding = iFalse;
+            }
+            makePanelItem_Mobile(
+                topPanel,
+                &(iMenuItem){ format_CStr("button text:%s", act->label), 0, 0, act->command });
+        }
+    }
     /* Finalize the layout. */
     addChild_Widget(sheet->root->widget, iClob(sheet));
     mainDetailSplitHandler_(mainDetailSplit, "window.resized"); /* make it resize the split */
@@ -1011,7 +1097,7 @@ iWidget *makePanels_Mobile(const iMenuItem *itemsNullTerminated) {
 #endif
 
 void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) {
-    if (!useMobileSheetLayout_()) {
+    if (!isUsingPanelLayout_Mobile()) {
         return;    
     }
     const iBool isSlidePanel = (flags_Widget(sheet) & horizontalOffset_WidgetFlag) != 0;
@@ -1032,7 +1118,7 @@ void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) {
 }
 
 void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) {
-    if (!useMobileSheetLayout_()) {
+    if (!isUsingPanelLayout_Mobile()) {
         if (prefs_App()->uiAnimations) {
             setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iFalse);
             if (isIncoming) {
diff --git a/src/ui/mobile.h b/src/ui/mobile.h
index 5e2d8957..4d742a0a 100644
--- a/src/ui/mobile.h
+++ b/src/ui/mobile.h
@@ -27,7 +27,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 iDeclareType(Widget)
 iDeclareType(MenuItem)
     
-iWidget *   makePanels_Mobile           (const iMenuItem *itemsNullTerminated);
+iBool       isUsingPanelLayout_Mobile   (void);
+iWidget *   makePanels_Mobile           (const char *id,
+                                         const iMenuItem *itemsNullTerminated,
+                                         const iMenuItem *actions, size_t numActions);
     
 void        setupMenuTransition_Mobile  (iWidget *menu, iBool isIncoming);
 void        setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming);
diff --git a/src/ui/root.c b/src/ui/root.c
index 0b55d250..eae8e4bb 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -57,28 +57,28 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 static const iMenuItem navMenuItems_[] = {
     { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" },
     { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" },
     { "${menu.page.copysource}", SDLK_c, KMOD_PRIMARY, "copy" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" },
     { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" },
     { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" },
     { "${menu.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" },
     { "${menu.zoom.out}", SDLK_MINUS, KMOD_PRIMARY, "zoom.delta arg:-10" },
     { "${menu.zoom.reset}", SDLK_0, KMOD_PRIMARY, "zoom.set arg:100" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" },
     { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" },
     { "${menu.bookmarks.bytime}", 0, 0, "!open url:about:bookmarks?created" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { "${menu.downloads}", 0, 0, "downloads.open" },
     { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" },
     { "${menu.help}", SDLK_F1, 0, "!open url:about:help" },
     { "${menu.releasenotes}", 0, 0, "!open url:about:version" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { "${menu.quit}", 'q', KMOD_PRIMARY, "quit" }
 };
 #endif
@@ -89,17 +89,17 @@ static const iMenuItem tabletNavMenuItems_[] = {
     { folder_Icon " ${menu.openfile}", SDLK_o, KMOD_PRIMARY, "file.open" },
     { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" },
     { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" },
     { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" },
     { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" },
     { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" },
     { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" },
     { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" },
     { "${menu.downloads}", 0, 0, "downloads.open" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" },
     { "${menu.help}", SDLK_F1, 0, "!open url:about:help" },
     { "${menu.releasenotes}", 0, 0, "!open url:about:version" },
@@ -110,14 +110,14 @@ static const iMenuItem phoneNavMenuItems_[] = {
     { folder_Icon " ${menu.openfile}", SDLK_o, KMOD_PRIMARY, "file.open" },
     { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" },
     { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" },
     { leftHalf_Icon " ${menu.sidebar}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" },
     { "${menu.downloads}", 0, 0, "downloads.open" },
     { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { gear_Icon " Settings...", SDLK_COMMA, KMOD_PRIMARY, "preferences" },
 };
 #endif /* Mobile */
@@ -125,24 +125,24 @@ static const iMenuItem phoneNavMenuItems_[] = {
 #if defined (iPlatformMobile)
 static const iMenuItem identityButtonMenuItems_[] = {
     { "${menu.identity.notactive}", 0, 0, "ident.showactive" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" },
     { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { person_Icon " ${menu.show.identities}", 0, 0, "toolbar.showident" },
 };
 #else /* desktop */
 static const iMenuItem identityButtonMenuItems_[] = {
     { "${menu.identity.notactive}", 0, 0, "ident.showactive" },
-    { "---", 0, 0, NULL },
+    { "---" },
 # if !defined (iPlatformAppleDesktop)
     { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" },
     { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { person_Icon " ${menu.show.identities}", '4', KMOD_PRIMARY, "sidebar.mode arg:3 show:1" },
 # else
     { add_Icon " ${menu.identity.new}", 0, 0, "ident.new" },
-    { "---", 0, 0, NULL },
+    { "---" },
     { person_Icon " ${menu.show.identities}", 0, 0, "sidebar.mode arg:3 show:1" },
 # endif
 };
@@ -1158,20 +1158,20 @@ void createUserInterface_Root(iRoot *d) {
                         { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" },
                         { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" },
                         { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" },
-                        { "---", 0, 0, NULL },
+                        { "---" },
                         { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" },
                         { 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)
-                        { "---", 0, 0, NULL },
+                        { "---" },
                         { "${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" },
-                        { "---", 0, 0, NULL },
+                        { "---" },
                         { "${menu.page.copyurl}", 0, 0, "document.copylink" },
                         { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" },
                         { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } },
@@ -1355,7 +1355,7 @@ void createUserInterface_Root(iRoot *d) {
             (iMenuItem[]){
                 { close_Icon " ${menu.closetab}", 0, 0, "tabs.close" },
                 { copy_Icon " ${menu.duptab}", 0, 0, "tabs.new duplicate:1" },
-                { "---", 0, 0, NULL },
+                { "---" },
                 { "${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" },
@@ -1372,18 +1372,18 @@ void createUserInterface_Root(iRoot *d) {
                                             (iMenuItem[]){
                                                 { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" },
                                                 { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" },
-                                                { "---", 0, 0, NULL },
+                                                { "---" },
                                                 { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" },
                                                 },
                                             4);
         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" },
-            { "---", 0, 0, NULL },
+            { "---" },
             { "${menu.split.horizontal}", '3', 0, "ui.split arg:3 axis:0" },
             { "${menu.split.horizontal} 1:2", SDLK_d, 0, "ui.split arg:1 axis:0" },
             { "${menu.split.horizontal} 2:1", SDLK_e, 0, "ui.split arg:2 axis:0" },
-            { "---", 0, 0, NULL },
+            { "---" },
             { "${menu.split.vertical}", '2', 0, "ui.split arg:3 axis:1" },
             { "${menu.split.vertical} 1:2", SDLK_f, 0, "ui.split arg:1 axis:1" },
             { "${menu.split.vertical} 2:1", SDLK_r, 0, "ui.split arg:2 axis:1" },
diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c
index 5e1ee493..4c72c60a 100644
--- a/src/ui/uploadwidget.c
+++ b/src/ui/uploadwidget.c
@@ -148,7 +148,7 @@ void init_UploadWidget(iUploadWidget *d) {
         addChild_Widget(w, iClob(makePadding_Widget(gap_UI)));
         iWidget *buttons =
             makeDialogButtons_Widget((iMenuItem[]){ { "${upload.port}", 0, 0, "upload.setport" },
-                                                    { "---", 0, 0, NULL },
+                                                    { "---" },
                                                     { "${close}", SDLK_ESCAPE, 0, "upload.cancel" },
                                                     { uiTextAction_ColorEscape "${dlg.upload.send}",
                                                       SDLK_RETURN,
diff --git a/src/ui/util.c b/src/ui/util.c
index 22d8bce4..abe6f22e 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -907,6 +907,20 @@ iLabelWidget *makeMenuButton_LabelWidget(const char *label, const iMenuItem *ite
     return button;
 }
 
+void updateDropdownSelection_LabelWidget(iLabelWidget *dropButton, const char *selectedCommand) {
+    iWidget *menu = findChild_Widget(as_Widget(dropButton), "menu");
+    iForEach(ObjectList, i, children_Widget(menu)) {
+        if (isInstance_Object(i.object, &Class_LabelWidget)) {
+            iLabelWidget *item = i.object;
+            const iBool isSelected = endsWith_String(command_LabelWidget(item), selectedCommand);
+            setFlags_Widget(as_Widget(item), selected_WidgetFlag, isSelected);
+            if (isSelected) {
+                updateText_LabelWidget(dropButton, sourceText_LabelWidget(item));
+            }
+        }
+    }
+}
+
 /*-----------------------------------------------------------------------------------------------*/
 
 static iBool isTabPage_Widget_(const iWidget *tabs, const iWidget *page) {
@@ -1650,6 +1664,19 @@ static size_t findWidestItemLabel_(const iMenuItem *items, size_t num) {
     return widestPos;
 }
 
+iWidget *makeDialog_Widget(const char *id,
+                           const iMenuItem *itemsNullTerminated,
+                           const iMenuItem *actions, size_t numActions) {
+    iWidget *dlg = makeSheet_Widget(id);
+    /* TODO: Construct desktop dialogs using NULL-terminated item arrays, like mobile panels. */
+    addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
+    addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, numActions)));
+    addChild_Widget(dlg->root->widget, iClob(dlg));
+    arrange_Widget(dlg);
+    setupSheetTransition_Mobile(dlg, iTrue);
+    return dlg;
+}
+
 iWidget *makePreferences_Widget(void) {
     /* Common items. */
     const iMenuItem langItems[] = { { "${lang.de} - de", 0, 0, "uilang id:de" },
@@ -1715,7 +1742,7 @@ iWidget *makePreferences_Widget(void) {
         { NULL }
     };
     /* Create the Preferences UI. */
-    if (deviceType_App() != desktop_AppDeviceType) {
+    if (isUsingPanelLayout_Mobile()) {
         const iMenuItem pinSplitItems[] = {
             { "button id:prefs.pinsplit.0 label:prefs.pinsplit.none",  0, 0, "pinsplit.set arg:0" },
             { "button id:prefs.pinsplit.1 label:prefs.pinsplit.left",  0, 0, "pinsplit.set arg:1" },
@@ -1767,7 +1794,7 @@ iWidget *makePreferences_Widget(void) {
         };
         const iMenuItem generalPanelItems[] = {
             { "title id:heading.prefs.general" },
-            { "heading id:prefs.searchurl" },
+            { "heading text:${prefs.searchurl}" },
             { "input id:prefs.searchurl url:1 noheading:1" },
             { "padding" },
             { "toggle id:prefs.archive.openindex" },
@@ -1828,11 +1855,11 @@ iWidget *makePreferences_Widget(void) {
             { "padding" },
             { "input id:prefs.cachesize maxlen:4 selectall:1 unit:mb" },
             { "input id:prefs.memorysize maxlen:4 selectall:1 unit:mb" },
-            { "heading id:prefs.proxy.gemini" },
+            { "heading text:${prefs.proxy.gemini}" },
             { "input id:prefs.proxy.gemini noheading:1" },
-            { "heading id:prefs.proxy.gemini" },
+            { "heading text:${prefs.proxy.gopher}" },
             { "input id:prefs.proxy.gopher noheading:1" },
-            { "heading id:prefs.proxy.gemini" },
+            { "heading text:${prefs.proxy.http}" },
             { "input id:prefs.proxy.http noheading:1" },
             { NULL }
         };
@@ -1855,8 +1882,8 @@ iWidget *makePreferences_Widget(void) {
             { "button text:" bug_Icon " ${menu.debug}", 0, 0, "!open url:about:debug" },
             { NULL }
         };
-        iWidget *dlg = makePanels_Mobile((iMenuItem[]){
-            { "panel icon:0x2699 id:heading.prefs.general", 0, 0, (const void *) generalPanelItems },
+        iWidget *dlg = makePanels_Mobile("prefs", (iMenuItem[]){
+            { "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 },
             { "padding" },
@@ -1869,7 +1896,7 @@ iWidget *makePreferences_Widget(void) {
             { "padding" },
             { "panel text:" planet_Icon " ${menu.about}", 0, 0, (const void *) aboutPanelItems },
             { NULL }
-        });
+        }, NULL, 0);
         setupSheetTransition_Mobile(dlg, iTrue);
         return dlg;
     }
@@ -2153,6 +2180,29 @@ iWidget *makePreferences_Widget(void) {
 }
 
 iWidget *makeBookmarkEditor_Widget(void) {
+    const iMenuItem actions[] = {
+        { "${cancel}" },
+        { uiTextCaution_ColorEscape "${dlg.bookmark.save}", SDLK_RETURN, KMOD_PRIMARY, "bmed.accept" }
+    };
+    if (isUsingPanelLayout_Mobile()) {
+        const iMenuItem items[] = {
+            { "title id:bmed.heading text:${heading.bookmark.edit}" },
+            { "heading id:dlg.bookmark.url" },
+            { "input id:bmed.url url:1 noheading:1" },
+            { "padding" },
+            { "input id:bmed.title text:${dlg.bookmark.title}" },
+            { "input id:bmed.tags text:${dlg.bookmark.tags}" },
+            { "input id:bmed.icon maxlen:1 text:${dlg.bookmark.icon}" },
+            { "heading text:${heading.bookmark.tags}" },
+            { "toggle id:bmed.tag.home text:${bookmark.tag.home}" },
+            { "toggle id:bmed.tag.remote text:${bookmark.tag.remote}" },
+            { "toggle id:bmed.tag.linksplit text:${bookmark.tag.linksplit}" },
+            { NULL }
+        };
+        iWidget *dlg = makePanels_Mobile("bmed", items, actions, iElemCount(actions));
+        setupSheetTransition_Mobile(dlg, iTrue);
+        return dlg;
+    }
     iWidget *dlg = makeSheet_Widget("bmed");
     setId_Widget(addChildFlags_Widget(
                      dlg,
@@ -2179,14 +2229,7 @@ iWidget *makeBookmarkEditor_Widget(void) {
         as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x;
     }
     addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
-    addChild_Widget(
-        dlg,
-        iClob(makeDialogButtons_Widget((iMenuItem[]){ { "${cancel}" },
-                                                { uiTextCaution_ColorEscape "${dlg.bookmark.save}",
-                                                  SDLK_RETURN,
-                                                  KMOD_PRIMARY,
-                                                  "bmed.accept" } },
-                                 2)));
+    addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions))));
     addChild_Widget(get_Root()->widget, iClob(dlg));
     finalizeSheet_Mobile(dlg);
     return dlg;
@@ -2242,7 +2285,6 @@ iWidget *makeBookmarkCreation_Widget(const iString *url, const iString *title, i
     return dlg;
 }
 
-
 static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) {
     if (equal_Command(cmd, "cancel")) {
         setupSheetTransition_Mobile(dlg, iFalse);
@@ -2288,46 +2330,59 @@ static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) {
 }
 
 iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) {
-    iWidget *dlg = makeSheet_Widget("feedcfg");
-    setId_Widget(addChildFlags_Widget(
-                     dlg,
-                     iClob(new_LabelWidget(bookmarkId ? uiHeading_ColorEscape "${heading.feedcfg}"
-                                                      : uiHeading_ColorEscape "${heading.subscribe}",
-                                           NULL)),
-                     frameless_WidgetFlag),
-                 "feedcfg.heading");
-    iWidget *headings, *values;
-    addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values)));
-    iInputWidget *input = new_InputWidget(0);
-    addDialogInputWithHeading_(headings, values, "${dlg.feed.title}", "feedcfg.title", iClob(input));
-    addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.feed.entrytype}")));
-    iWidget *types = new_Widget(); {
-        addRadioButton_(types, "feedcfg.type.gemini", "${dlg.feed.type.gemini}", "feedcfg.type arg:0");
-        addRadioButton_(types, "feedcfg.type.headings", "${dlg.feed.type.headings}", "feedcfg.type arg:1");
-    }
-    addChildFlags_Widget(values, iClob(types), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
-    iWidget *buttons =
-        addChild_Widget(dlg,
-                        iClob(makeDialogButtons_Widget(
-                            (iMenuItem[]){ { "${cancel}" },
-                                           { bookmarkId ? uiTextCaution_ColorEscape "${dlg.feed.save}"
-                                                        : uiTextCaution_ColorEscape "${dlg.feed.sub}",
-                                             SDLK_RETURN,
-                                             KMOD_PRIMARY,
-                                             format_CStr("feedcfg.accept bmid:%d", bookmarkId) } },
-                            2)));
-    setId_Widget(child_Widget(buttons, childCount_Widget(buttons) - 1), "feedcfg.save");
-    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);
+    const char *headingText = bookmarkId ? uiHeading_ColorEscape "${heading.feedcfg}"
+                                         : uiHeading_ColorEscape "${heading.subscribe}";
+    const iMenuItem actions[] = { { "${cancel}" },
+                                  { bookmarkId ? uiTextCaution_ColorEscape "${dlg.feed.save}"
+                                               : uiTextCaution_ColorEscape "${dlg.feed.sub}",
+                                    SDLK_RETURN,
+                                    KMOD_PRIMARY,
+                                    format_CStr("feedcfg.accept bmid:%d", bookmarkId) } };
+    iWidget *dlg;
+    if (isUsingPanelLayout_Mobile()) {
+        const iMenuItem typeItems[] = {
+            { "button id:feedcfg.type.gemini label:dlg.feed.type.gemini", 0, 0, "feedcfg.type arg:0" },
+            { "button id:feedcfg.type.headings label:dlg.feed.type.headings", 0, 0, "feedcfg.type arg:1" },
+            { NULL }
+        };
+        dlg = makePanels_Mobile("feedcfg", (iMenuItem[]){
+            { format_CStr("title id:feedcfg.heading text:%s", headingText) },
+            { "input id:feedcfg.title text:${dlg.feed.title}" },
+            { "radio id:dlg.feed.entrytype", 0, 0, (const void *) typeItems },
+            { NULL }
+        }, actions, iElemCount(actions));
+    }
+    else {
+        dlg = makeSheet_Widget("feedcfg");
+        setId_Widget(
+            addChildFlags_Widget(dlg, iClob(new_LabelWidget(headingText, NULL)), frameless_WidgetFlag),
+            "feedcfg.heading");
+        iWidget *headings, *values;
+        addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values)));
+        iInputWidget *input = new_InputWidget(0);
+        addDialogInputWithHeading_(headings, values, "${dlg.feed.title}", "feedcfg.title", iClob(input));
+        addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.feed.entrytype}")));
+        iWidget *types = new_Widget(); {
+            addRadioButton_(types, "feedcfg.type.gemini", "${dlg.feed.type.gemini}", "feedcfg.type arg:0");
+            addRadioButton_(types, "feedcfg.type.headings", "${dlg.feed.type.headings}", "feedcfg.type arg:1");
+        }
+        addChildFlags_Widget(values, iClob(types), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
+        iWidget *buttons =
+            addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions))));
+        setId_Widget(child_Widget(buttons, childCount_Widget(buttons) - 1), "feedcfg.save");
+        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);
+    }
     /* Initialize. */ {
         const iBookmark *bm  = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL;
         setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"),
                             bm ? &bm->title : feedTitle_DocumentWidget(document_App()));
         setFlags_Widget(findChild_Widget(dlg,
-                                         hasTag_Bookmark(bm, headings_BookmarkTag) ? "feedcfg.type.headings"
-                                                                         : "feedcfg.type.gemini"),
+                                         hasTag_Bookmark(bm, headings_BookmarkTag)
+                                             ? "feedcfg.type.headings"
+                                             : "feedcfg.type.gemini"),
                         selected_WidgetFlag,
                         iTrue);
         setCommandHandler_Widget(dlg, handleFeedSettingCommands_);
@@ -2336,84 +2391,113 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) {
 }
 
 iWidget *makeIdentityCreation_Widget(void) {
-    iWidget *dlg = makeSheet_Widget("ident");
-    setId_Widget(addChildFlags_Widget(
-                     dlg,
-                     iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.newident}", NULL)),
-                     frameless_WidgetFlag),
-                 "ident.heading");
-    iWidget *page = new_Widget();
-    addChildFlags_Widget(
-        dlg, iClob(new_LabelWidget("${dlg.newident.rsa.selfsign}", NULL)), frameless_WidgetFlag);
-    /* TODO: Use makeTwoColumnWidget_? */
-    addChild_Widget(dlg, iClob(page));
-    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);
-    setId_Widget(headings, "headings");
-    setId_Widget(values, "values");
-    iInputWidget *inputs[6];
-    /* Where will the new identity be active on? */ {
-        addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.scope}")));
-        const iMenuItem items[] = {
-            { "${dlg.newident.scope.domain}", 0, 0, "ident.scope arg:0" },
-            { "${dlg.newident.scope.page}",   0, 0, "ident.scope arg:1" },
-            { "${dlg.newident.scope.none}",   0, 0, "ident.scope arg:2" },
-        };
-        setId_Widget(addChild_Widget(values,
-                                     iClob(makeMenuButton_LabelWidget(
-                                         items[0].label, items, iElemCount(items)))),
-                     "ident.scope");
-    }
-    addDialogInputWithHeading_(headings,
-                               values,
-                               "${dlg.newident.until}",
-                               "ident.until",
-                               iClob(newHint_InputWidget(19, "${hint.newident.date}")));
-    addDialogInputWithHeading_(headings,
-                               values,
-                               "${dlg.newident.commonname}",
-                               "ident.common",
-                               iClob(inputs[0] = new_InputWidget(0)));
-    /* Temporary? */ {
-        addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.temp}")));
-        iWidget *tmpGroup = new_Widget();
-        setFlags_Widget(tmpGroup, arrangeSize_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue);
-        addChild_Widget(tmpGroup, iClob(makeToggle_Widget("ident.temp")));
-        setId_Widget(
-            addChildFlags_Widget(tmpGroup,
-                                 iClob(new_LabelWidget(uiTextCaution_ColorEscape warning_Icon
-                                                       "  ${dlg.newident.notsaved}",
-                                                       NULL)),
-                                 hidden_WidgetFlag | frameless_WidgetFlag),
-            "ident.temp.note");
-        addChild_Widget(values, iClob(tmpGroup));
-    }
-    addChildFlags_Widget(headings, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag);
-    addChildFlags_Widget(values, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag);
-    addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.email}",   "ident.email",   iClob(inputs[1] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag);
-    addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.userid}",  "ident.userid",  iClob(inputs[2] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag);
-    addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.domain}",  "ident.domain",  iClob(inputs[3] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag);
-    addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.org}",     "ident.org",     iClob(inputs[4] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag);
-    addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.country}", "ident.country", iClob(inputs[5] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag);
-    arrange_Widget(dlg);
-    for (size_t i = 0; i < iElemCount(inputs); ++i) {
-        as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x;
+    const iMenuItem actions[] = { { "${dlg.newident.more}", 0, 0, "ident.showmore" },
+                                  { "---" },
+                                  { "${cancel}", SDLK_ESCAPE, 0, "ident.cancel" },
+                                  { uiTextAction_ColorEscape "${dlg.newident.create}",
+                                    SDLK_RETURN,
+                                    KMOD_PRIMARY,
+                                    "ident.accept" } };
+    iUrl url;
+    init_Url(&url, url_DocumentWidget(document_App()));
+    const iMenuItem scopeItems[] = {
+        { format_CStr("${dlg.newident.scope.domain}:\n%s", cstr_Rangecc(url.host)), 0, 0, "ident.scope arg:0" },
+        { format_CStr("${dlg.newident.scope.page}:\n%s", cstr_Rangecc(url.path)), 0, 0, "ident.scope arg:1" },
+        { "${dlg.newident.scope.none}", 0, 0, "ident.scope arg:2" },
+        { NULL }
+    };
+    iWidget *dlg;
+    if (isUsingPanelLayout_Mobile()) {
+        dlg = makePanels_Mobile("ident",
+                                (iMenuItem[]){ { "title id:ident.heading text:${heading.newident}" },
+                                               { "label text:${dlg.newident.rsa.selfsign}" },
+                                               { "dropdown id:ident.scope text:${dlg.newident.scope}", 0, 0,
+                                                 (const void *) scopeItems },
+                                               { "input id:ident.until hint:hint.newident.date maxlen:19 text:${dlg.newident.until}" },
+                                               //{ "padding" },
+                                               //{ "toggle id:ident.temp text:${dlg.newident.temp}" },
+                                               //{ "label text:${help.ident.temp}" },
+                                               { "heading id:dlg.newident.commonname" },
+                                               { "input id:ident.common noheading:1" },
+                                               { "padding collapse:1" },
+                                               { "input collapse:1 id:ident.email hint:hint.newident.optional text:${dlg.newident.email}" },
+                                               { "input collapse:1 id:ident.userid hint:hint.newident.optional text:${dlg.newident.userid}" },
+                                               { "input collapse:1 id:ident.domain hint:hint.newident.optional text:${dlg.newident.domain}" },
+                                               { "input collapse:1 id:ident.org hint:hint.newident.optional text:${dlg.newident.org}" },
+                                               { "input collapse:1 id:ident.country hint:hint.newident.optional text:${dlg.newident.country}" },
+                                               { NULL }
+                                }, actions, iElemCount(actions));
+        setupSheetTransition_Mobile(dlg, iTrue);
+    }
+    else {
+        dlg = makeSheet_Widget("ident");
+        setId_Widget(addChildFlags_Widget(
+                         dlg,
+                         iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.newident}", NULL)),
+                         frameless_WidgetFlag),
+                     "ident.heading");
+        iWidget *page = new_Widget();
+        addChildFlags_Widget(
+            dlg, iClob(new_LabelWidget("${dlg.newident.rsa.selfsign}", NULL)), frameless_WidgetFlag);
+        /* TODO: Use makeTwoColumnWidget_? */
+        addChild_Widget(dlg, iClob(page));
+        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);
+        setId_Widget(headings, "headings");
+        setId_Widget(values, "values");
+        iInputWidget *inputs[6];
+        /* Where will the new identity be active on? */ {
+            iWidget *head = addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.scope}")));
+            iWidget *val;
+            setId_Widget(
+                addChild_Widget(values,
+                                val = iClob(makeMenuButton_LabelWidget(
+                                    scopeItems[0].label, scopeItems, iElemCount(scopeItems)))),
+                "ident.scope");
+            head->sizeRef = val;
+        }
+        addDialogInputWithHeading_(headings,
+                                   values,
+                                   "${dlg.newident.until}",
+                                   "ident.until",
+                                   iClob(newHint_InputWidget(19, "${hint.newident.date}")));
+        addDialogInputWithHeading_(headings,
+                                   values,
+                                   "${dlg.newident.commonname}",
+                                   "ident.common",
+                                   iClob(inputs[0] = new_InputWidget(0)));
+        /* Temporary? */ {
+            addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.temp}")));
+            iWidget *tmpGroup = new_Widget();
+            setFlags_Widget(tmpGroup, arrangeSize_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue);
+            addChild_Widget(tmpGroup, iClob(makeToggle_Widget("ident.temp")));
+            setId_Widget(
+                addChildFlags_Widget(tmpGroup,
+                                     iClob(new_LabelWidget(uiTextCaution_ColorEscape warning_Icon
+                                                           "  ${dlg.newident.notsaved}",
+                                                           NULL)),
+                                     hidden_WidgetFlag | frameless_WidgetFlag),
+                "ident.temp.note");
+            addChild_Widget(values, iClob(tmpGroup));
+        }
+        addChildFlags_Widget(headings, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag);
+        addChildFlags_Widget(values, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag);
+        addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.email}",   "ident.email",   iClob(inputs[1] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag);
+        addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.userid}",  "ident.userid",  iClob(inputs[2] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag);
+        addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.domain}",  "ident.domain",  iClob(inputs[3] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag);
+        addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.org}",     "ident.org",     iClob(inputs[4] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag);
+        addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.country}", "ident.country", iClob(inputs[5] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag);
+        arrange_Widget(dlg);
+        for (size_t i = 0; i < iElemCount(inputs); ++i) {
+            as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x;
+        }
+        addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions))));
+        addChild_Widget(get_Root()->widget, iClob(dlg));
+        finalizeSheet_Mobile(dlg);
     }
-    addChild_Widget(dlg,
-                    iClob(makeDialogButtons_Widget(
-                        (iMenuItem[]){ { "${dlg.newident.more}", 0, 0, "ident.showmore" },
-                                       { "---" },
-                                       { "${cancel}", SDLK_ESCAPE, 0, "ident.cancel" },
-                                       { uiTextAction_ColorEscape "${dlg.newident.create}",
-                                         SDLK_RETURN,
-                                         KMOD_PRIMARY,
-                                         "ident.accept" } },
-                        4)));
-    addChild_Widget(get_Root()->widget, iClob(dlg));
-    finalizeSheet_Mobile(dlg);
     return dlg;
 }
 
diff --git a/src/ui/util.h b/src/ui/util.h
index 87b72394..0dff8978 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -242,7 +242,8 @@ int         checkContextMenu_Widget (iWidget *, const SDL_Event *ev); /* see mac
         break; \
     }
 
-iLabelWidget *  makeMenuButton_LabelWidget  (const char *label, const iMenuItem *items, size_t n);
+iLabelWidget *  makeMenuButton_LabelWidget          (const char *label, const iMenuItem *items, size_t n);
+void            updateDropdownSelection_LabelWidget (iLabelWidget *dropButton, const char *selectedCommand);
 
 /*-----------------------------------------------------------------------------------------------*/
 
Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.7/cdiff/b85471a5b84f0837611fb47be35ee713139f702f
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
85.032552 milliseconds
Gemini-to-HTML Time
2.367141 milliseconds

This content has been proxied by September (ba2dc).