Lagrange [work/v1.17]

Submenus; pasting snippets via a submenu

=> 01898c3a1a711c7e22888deeb7c4e64427e99b13

diff --git a/po/en.po b/po/en.po
index f9c6676b..57ab0203 100644
--- a/po/en.po
+++ b/po/en.po
@@ -281,7 +281,7 @@ msgid "menu.zoom.reset"
 msgstr "Reset Zoom"
 
 msgid "menu.view.split"
-msgstr "Split View…"
+msgstr "Split View"
 
 msgid "menu.newfolder"
 msgstr "New Folder…"
diff --git a/res/lang/en.bin b/res/lang/en.bin
index 8df481fa..1075b5ff 100644
Binary files a/res/lang/en.bin and b/res/lang/en.bin differ
diff --git a/res/lang/eo.bin b/res/lang/eo.bin
index fd40d10e..a6ff2dc5 100644
Binary files a/res/lang/eo.bin and b/res/lang/eo.bin differ
diff --git a/res/lang/isv.bin b/res/lang/isv.bin
index 7febe96e..d97d008e 100644
Binary files a/res/lang/isv.bin and b/res/lang/isv.bin differ
diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin
index 53e5bd1c..af4b0f21 100644
Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ
diff --git a/src/app.c b/src/app.c
index 5323a368..cedbdbd0 100644
--- a/src/app.c
+++ b/src/app.c
@@ -3382,7 +3382,7 @@ static iBool handleNonWindowRelatedCommand_App_(iApp *d, const char *cmd) {
     }
     else if (equal_Command(cmd, "snippets.changed")) {
         save_Snippets(dataDir_App_());
-        return iTrue;
+        return iFalse;
     }
     else if (equal_Command(cmd, "width.save")) {
         insert_StringHash(d->savedWidths,
@@ -4721,6 +4721,9 @@ iBool handleCommand_App(const char *cmd) {
             /* Detach into a window if it doesn't fit otherwise. */
             promoteDialogToWindow_Widget(dlg);
         }
+        if (argLabel_Command(cmd, "sniped")) {
+            postCommand_Widget(dlg, "tabs.switch id:sniped");
+        }
     }
     else if (equal_Command(cmd, "navigate.home") && isMainWin) {
         /* Look for bookmarks tagged "homepage". */
diff --git a/src/macos.m b/src/macos.m
index e04774a2..1261082e 100644
--- a/src/macos.m
+++ b/src/macos.m
@@ -843,7 +843,21 @@ static NSMenuItem *makeMenuItems_(NSMenu *menu, MenuCommands *commands, int atIn
             NSAttributedString *title = [[NSAttributedString alloc] initWithString:cleanString_(&itemTitle)];
             item.attributedTitle = title;
             [title release];
-            item.action = (hasCommand ? @selector(postMenuItemCommand:) : nil);
+            if (hasCommand && startsWith_CStr(items[i].command, "submenu id:")) {
+                NSMenu *sub = [[NSMenu alloc] init];
+                sub.autoenablesItems = YES;
+                iWidget *subwidget = findChild_Widget(get_MainWindow()->base.roots[0]->widget,
+                                                      cstr_String(string_Command(items[i].command, "id")));
+                iAssert(subwidget);
+                const iArray *items = userData_Object(subwidget);
+                iAssert(items);
+                makeMenuItems_(sub, commands, 0, constData_Array(items), size_Array(items));
+                [item setSubmenu:sub];
+                [sub release];
+            }
+            else {
+                item.action = (hasCommand ? @selector(postMenuItemCommand:) : nil);
+            }
             [menu insertItem:item atIndex:atIndex++];
             deinit_String(&itemTitle);
             [item setTarget:commands];
@@ -869,6 +883,7 @@ static NSMenuItem *makeMenuItems_(NSMenu *menu, MenuCommands *commands, int atIn
                 }
             }
             setShortcut_NSMenuItem_(item, key, kmods);
+            [item release];
         }
     }
     return selectedItem;
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 4842b1ee..d0d11d6d 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -54,7 +54,7 @@ struct Impl_LabelWidget {
         uint16_t wrap                : 1;
         uint16_t allCaps             : 1;
         uint16_t removeTrailingColon : 1;
-        uint16_t chevron             : 1;        
+        uint16_t chevron             : 1;
         uint16_t checkMark           : 1;
         uint16_t truncateToFit       : 1;
     } flags;
@@ -70,15 +70,20 @@ static iBool isHover_LabelWidget_(const iLabelWidget *d) {
 static iInt2 padding_LabelWidget_(const iLabelWidget *d, int corner) {
     const iWidget *w = constAs_Widget(d);
     const int64_t flags = flags_Widget(w);
-    const iInt2 widgetPad = (corner   == 0 ? init_I2(w->padding[0], w->padding[1])
-                             : corner == 1 ? init_I2(w->padding[2], w->padding[1])
-                             : corner == 2 ? init_I2(w->padding[2], w->padding[3])
-                             : init_I2(w->padding[0], w->padding[3]));
+    iInt2          widgetPad = (corner == 0   ? init_I2(w->padding[0], w->padding[1])
+                                : corner == 1 ? init_I2(w->padding[2], w->padding[1])
+                                : corner == 2 ? init_I2(w->padding[2], w->padding[3])
+                                              : init_I2(w->padding[0], w->padding[3]));
     if (isMobile_Platform()) {
         return add_I2(widgetPad,
                       init_I2(flags & tight_WidgetFlag ? 2 * gap_UI : (4 * gap_UI),
                               (flags & extraPadding_WidgetFlag ? 1.5f : 1.0f) * 3 * gap_UI / 2));
     }
+    if (d->flags.chevron) {
+        if (corner == 1 || corner == 2) {
+            widgetPad.x += gap_UI * 5;
+        }
+    }
     return add_I2(widgetPad,
                   init_I2(flags & tight_WidgetFlag ? 3 * gap_UI / 2 : (3 * gap_UI),
                           gap_UI * aspect_UI));
@@ -329,7 +334,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int
                 *frame1 = *bg;
             }
         }
-    }    
+    }
     if (isFocus) {
         *frame1 = *frame2 = (isSel ? uiText_ColorId : uiInputFrameFocused_ColorId);
     }
@@ -453,7 +458,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
     }
     if (isFocused_Widget(w)) {
         iRect frameRect = adjusted_Rect(rect, zero_I2(), init1_I2(-1));
-        drawRectThickness_Paint(&p, frameRect, gap_UI / 4, uiTextAction_ColorId /*frame*/);        
+        drawRectThickness_Paint(&p, frameRect, gap_UI / 4, uiTextAction_ColorId /*frame*/);
     }
     else if (~flags & frameless_WidgetFlag) {
         iRect frameRect = adjusted_Rect(rect, zero_I2(), init1_I2(-1));
@@ -468,7 +473,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
 #if SDL_COMPILEDVERSION == SDL_VERSIONNUM(2, 0, 16)
             if (isOpenGLRenderer_Window()) {
                 /* A very curious regression in SDL 2.0.16. */
-                points[3].x--;    
+                points[3].x--;
             }
 #endif
             if (d->flags.noBottomFrame && !isFocused_Widget(w) && !isHover) {
@@ -569,11 +574,17 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
         const iRect chRect = rect;
         const int chSize = lineHeight_Text(d->font);
         int offset = 0;
-        if (d->flags.chevron) {
-            offset = -iconPad;
+        if (isMobile_Platform()) {
+            /* These are used in the sub-panel buttons. */
+            if (d->flags.chevron) {
+                offset = -iconPad;
+            }
+            else {
+                offset = -10 * gap_UI;
+            }
         }
         else {
-            offset = -10 * gap_UI;
+            offset = -6 * gap_UI;
         }
         drawCentered_Text(d->font,
                           (iRect){ addX_I2(topRight_Rect(chRect), offset),
diff --git a/src/ui/root.c b/src/ui/root.c
index de5ce0e9..4eb81da9 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -33,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 #include "labelwidget.h"
 #include "lookupwidget.h"
 #include "sidebarwidget.h"
+#include "snippets.h"
 #include "window.h"
 #include "../visited.h"
 #include "../history.h"
@@ -64,7 +65,7 @@ static const iMenuItem desktopNavMenuItems_[] = {
     { "---" },
     { leftHalf_Icon " ${menu.sidebar.left}", leftSidebar_KeyShortcut, "sidebar.toggle" },
     { rightHalf_Icon " ${menu.sidebar.right}", rightSidebar_KeyShortcut, "sidebar2.toggle" },
-    { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" },
+    { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "submenu id:splitmenu" },
     { "${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" },
@@ -423,14 +424,15 @@ iBool handleRootCommands_Widget(iWidget *root, const char *cmd) {
     iUnused(root);
     if (equal_Command(cmd, "menu.open")) {
         iWidget *button = pointer_Command(cmd);
-        iWidget *menu = findChild_Widget(button, "menu");
+        iWidget *menu = argLabel_Command(cmd, "self") ? button : findChild_Widget(button, "menu");
         if (!menu) {
             /* Independent popup window. */
             postCommand_App("cancel");
             return iTrue;
         }
-        const iBool isPlacedUnder = argLabel_Command(cmd, "under");
-        const iBool isMenuBar = argLabel_Command(cmd, "bar");
+        const iBool isPlacedUnder  = argLabel_Command(cmd, "under");
+        const iBool isMenuBar      = argLabel_Command(cmd, "bar");
+        const iBool isSubmenu      = argLabel_Command(cmd, "self");
         iAssert(menu);
         if (!isVisible_Widget(menu)) {
             if (isMenuBar) {
@@ -440,9 +442,12 @@ iBool handleRootCommands_Widget(iWidget *root, const char *cmd) {
             if (menu->updateMenuItems) {
                 menu->updateMenuItems(menu);
             }
-            openMenu_Widget(menu,
-                            isPlacedUnder ? bottomLeft_Rect(bounds_Widget(button))
-                                          : topLeft_Rect(bounds_Widget(button)));
+            openMenuFlags_Widget(menu,
+                                 hasLabel_Command(cmd, "coord") ? coord_Command(cmd)
+                                 : isPlacedUnder ? bottomLeft_Rect(bounds_Widget(button))
+                                                 : topLeft_Rect(bounds_Widget(button)),
+                                 postCommands_MenuOpenFlags |
+                                     (isSubmenu ? submenu_MenuOpenFlags : 0));
         }
         else {
             /* Already open, do nothing. */
@@ -1565,6 +1570,30 @@ static iBool updateMobilePageMenuItems_(iWidget *menu, const char *cmd) {
     return handleMenuCommand_Widget(menu, cmd);
 }
 
+void recreateSnippetMenu_Root(iRoot *d) {
+    const iStringArray *snipNames = names_Snippets();
+    iArray *items = collectNew_Array(sizeof(iMenuItem));
+    iConstForEach(StringArray, s, snipNames) {
+        if (startsWith_String(s.value, "!")) {
+            continue;
+        }
+        pushBack_Array(items,
+                       &(iMenuItem){ format_CStr(clipboard_Icon " %s", cstr_String(s.value)),
+                                     0,
+                                     0,
+                                     format_CStr("!input.paste snippet:%s", cstr_String(s.value)) });
+    }
+    if (!isEmpty_Array(items)) {
+        pushBack_Array(items, &(iMenuItem){ "---" });
+    }
+    pushBack_Array(items,
+                   &(iMenuItem){ gear_Icon " ${menu.snip.prefs}", 0, 0, "preferences sniped:1" });
+    iWidget *menu = findChild_Widget(d->widget, "snippetmenu");
+    destroy_Widget(menu);
+    menu = makeMenu_Widget(d->widget, data_Array(items), size_Array(items));
+    setId_Widget(menu, "snippetmenu");
+}
+
 void createUserInterface_Root(iRoot *d) {
     iWidget *root = d->widget = new_Widget();
     root->rect.size = get_Window()->size;
@@ -1632,6 +1661,7 @@ void createUserInterface_Root(iRoot *d) {
             iClob(makeMenuBar_Widget(topLevelMenus_Window, iElemCount(topLevelMenus_Window))),
             collapse_WidgetFlag);
         /* The window menu needs to be dynamically updated with the list of open windows. */
+        /* TODO: Use Widget's `updateMenuItems` callback. */
         setCommandHandler_Widget(child_Widget(menuBar, 5), updateWindowMenu_);
         setId_Widget(menuBar, "menubar");
 #  if 0
@@ -2060,20 +2090,24 @@ void createUserInterface_Root(iRoot *d) {
                 { ">>>" delete_Icon " " uiTextCaution_ColorEscape "${menu.delete}", 0, 0, "input.delete" },
                 { ">>>" select_Icon " ${menu.selectall}", 0, 0, "input.selectall" },
                 { ">>>" undo_Icon " ${menu.undo}", 0, 0, "input.undo" },
-            }, 7);
+                { "---" },
+                { "${menu.paste.snippet}", 0, 0, "snippetmenu" },
+            }, 9);
 #else
             (iMenuItem[]){
                 { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" },
                 { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" },
                 { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" },
                 { return_Icon " ${menu.paste.go}", 0, 0, "input.paste enter:1" },
+                { "${menu.paste.snippet}", 0, 0, "submenu id:snippetmenu" },
                 { "---" },
                 { delete_Icon " " uiTextCaution_ColorEscape "${menu.delete}", 0, 0, "input.delete" },
                 { undo_Icon " ${menu.undo}", 0, 0, "input.undo" },
                 { "---" },
                 { select_Icon " ${menu.selectall}", 0, 0, "input.selectall" },
-            }, 9);
+            }, 10);
 #endif
+        recreateSnippetMenu_Root(d);
         if (deviceType_App() == phone_AppDeviceType) {
             /* Small screen; conserve space by removing the Cancel item. */
             iRelease(removeChild_Widget(clipMenu, lastChild_Widget(clipMenu)));
diff --git a/src/ui/root.h b/src/ui/root.h
index f46e0a53..9618d1b0 100644
--- a/src/ui/root.h
+++ b/src/ui/root.h
@@ -35,6 +35,7 @@ iDeclareTypeConstruction(Root)
 /*----------------------------------------------------------------------------------------------*/
 
 void        createUserInterface_Root            (iRoot *);
+void        recreateSnippetMenu_Root            (iRoot *);
 
 void        setCurrent_Root                     (iRoot *);
 iRoot *     current_Root                        (void);
diff --git a/src/ui/util.c b/src/ui/util.c
index 5ae3b0e6..73970ab1 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -816,11 +816,15 @@ iBool handleMenuCommand_Widget(iWidget *menu, const char *cmd) {
             arg_Command(cmd)) {
             /* Dismiss open menus when clicking outside them. */
             closeMenu_Widget(menu);
-            return iTrue;
+            return equal_Command(cmd, "mouse.clicked");
         }
         if (equal_Command(cmd, "cancel") && pointerLabel_Command(cmd, "menu") == menu) {
             return iFalse;
         }
+        if (equal_Command(cmd, "submenu.close") &&
+            (pointerLabel_Command(cmd, "menu") == menu || ~menu->flags & radio_WidgetFlag)) {
+            return iFalse;
+        }
         if (equal_Command(cmd, "contextclick") && pointer_Command(cmd) == menu) {
             return iFalse;
         }
@@ -834,7 +838,7 @@ iBool handleMenuCommand_Widget(iWidget *menu, const char *cmd) {
             return iFalse;
         }
         if (!isCommandIgnoredByMenus_(cmd)) {
-            //printf("closemenu being called on %p due to cmd: %s\n", menu, cmd);
+//            printf("closemenu being called on %p due to cmd: %s\n", menu, cmd);
             closeMenu_Widget(menu);
         }
     }
@@ -855,12 +859,56 @@ static iWidget *makeMenuSeparator_(void) {
     return sep;
 }
 
+static void closeSubmenus_(iWidget *menu, iRoot *root) {
+    iConstForEach(ObjectList, i, children_Widget(menu)) {
+        if (isInstance_Object(i.object, &Class_LabelWidget)) {
+            iLabelWidget *label = (iLabelWidget *) i.object;
+            const iString *subCmd = command_LabelWidget(label);
+            if (startsWith_String(subCmd, "submenu id:")) {
+                iWidget *submenu = findChild_Widget(root->widget,
+                                                    cstr_Command(cstr_String(subCmd), "id"));
+                iAssert(submenu);
+                if (isVisible_Widget(submenu)) {
+                    closeMenu_Widget(submenu);
+                }
+            }
+        }
+    }
+}
+
+static iBool submenuItemHandler_(iWidget *d, const char *cmd) {
+    if (equal_Command(cmd, "mouse.hovered") && isVisible_Widget(d)) {
+        iAssert(isInstance_Object(d, &Class_LabelWidget));
+        iLabelWidget *label = (iLabelWidget *) d;
+        const iString *subCmd = command_LabelWidget(label);
+        if (startsWith_String(subCmd, "submenu id:")) {
+            iWidget    *menu    = parent_Widget(d);
+            const iBool isPopup = type_Window(window_Widget(menu)) == popup_WindowType;
+            iRoot      *root    = isPopup ? constAs_Widget(userData_Object(menu))->root : d->root;
+            closeSubmenus_(menu, root);
+            iWidget *submenu = findChild_Widget(root->widget,
+                                                cstr_Command(cstr_String(subCmd), "id"));
+            iAssert(submenu);
+            if (!isVisible_Widget(submenu)) {
+//                const iInt2 coord = topRight_Rect(bounds_Widget(d));
+                openMenuAnchorFlags_Widget(submenu,
+                                           bounds_Widget(d),
+                                           submenu_MenuOpenFlags |
+                                               (isPopup ? forcePopup_MenuOpenFlags : 0));
+            }
+        }
+        return iTrue;
+    }
+    return iFalse;
+}
+
 void makeMenuItems_Widget(iWidget *menu, const iMenuItem *items, size_t n) {
     const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App());
     int64_t     itemFlags       = (deviceType_App() != desktop_AppDeviceType ? 0 : 0) |
                                   (isPortraitPhone ? extraPadding_WidgetFlag : 0);
-    iBool    haveIcons  = iFalse;
-    iWidget *horizGroup = NULL;
+    iBool    haveIcons   = iFalse;
+    iBool    haveSubmenu = iFalse;
+    iWidget *horizGroup  = NULL;
     for (size_t i = 0; items[i].label && i < n; ++i) {
         const iMenuItem *item = &items[i];
         if (!item->label) {
@@ -913,6 +961,12 @@ void makeMenuItems_Widget(iWidget *menu, const iMenuItem *items, size_t n) {
                 setTextColor_LabelWidget(label, uiIcon_ColorId);
                 setFont_LabelWidget(label, uiLabelMedium_FontId);
             }
+            if (item->command && startsWith_CStr(item->command, "submenu id:")) {
+                setChevron_LabelWidget(label, iTrue);
+                setFlags_Widget(as_Widget(label), drawKey_WidgetFlag, iFalse);
+                haveSubmenu = iTrue;
+            }
+            as_Widget(label)->flags2 |= commandOnHover_WidgetFlag2;
             setFlags_Widget(as_Widget(label), disabled_WidgetFlag, isDisabled);
             if (isInfo) {
                 setFlags_Widget(as_Widget(label), resizeToParentWidth_WidgetFlag |
@@ -929,13 +983,19 @@ void makeMenuItems_Widget(iWidget *menu, const iMenuItem *items, size_t n) {
                              itemFlags | noBackground_WidgetFlag | frameless_WidgetFlag |
                              alignLeft_WidgetFlag);
     }
-    if (haveIcons) {
-        /* All items must have icons if at least one of them has. */
+    if (haveIcons || haveSubmenu) {
         iForEach(ObjectList, i, children_Widget(menu)) {
             if (isInstance_Object(i.object, &Class_LabelWidget)) {
                 iLabelWidget *label = i.object;
-                if (!isWrapped_LabelWidget(label) && icon_LabelWidget(label) == 0) {
-                    setIcon_LabelWidget(label, ' ');
+                if (haveIcons) {
+                    /* All items must have icons if at least one of them has. */
+                    if (!isWrapped_LabelWidget(label) && icon_LabelWidget(label) == 0) {
+                        setIcon_LabelWidget(label, ' ');
+                    }
+                }
+                if (haveSubmenu) {
+                    /* Open and close submenus on hover. */
+                    setCommandHandler_Widget(i.object, submenuItemHandler_);
                 }
             }
         }
@@ -1253,10 +1313,17 @@ iLocalDef iBool isUsingMenuPopupWindows_(void) {
 }
 
 void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
+    openMenuAnchorFlags_Widget(d, initCorners_Rect(windowCoord, windowCoord), menuOpenFlags);
+}
+
+void openMenuAnchorFlags_Widget(iWidget *d, iRect windowAnchorRect, int menuOpenFlags) {
+    iInt2 windowCoord         = topRight_Rect(windowAnchorRect);
     const iBool postCommands  = (menuOpenFlags & postCommands_MenuOpenFlags) != 0;
     const iBool isMenuFocused = ((menuOpenFlags & setFocus_MenuOpenFlags) ||
                                  focus_Widget() == parent_Widget(d));
-    if (postCommands) {
+    const iBool isPopupForced = (menuOpenFlags & forcePopup_MenuOpenFlags) != 0;
+    const iBool isSubmenu     = (menuOpenFlags & submenu_MenuOpenFlags) != 0;
+    if (postCommands && !isSubmenu) {
         postCommandf_App("cancel menu:%p", d); /* dismiss any other menus */
     }
     /* Menu closes when commands are emitted, so handle any pending ones beforehand. */
@@ -1282,7 +1349,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
         setFrameColor_Widget(d, isPortraitPhone ? none_ColorId : uiSeparator_ColorId);
     }
     arrange_Widget(d); /* need to know the height */
-    iBool allowOverflow = iFalse;
+    iBool allowOverflow = (get_Window()->type == extra_WindowType);
     /* A vertical offset determined by a possible selected label in the menu. */
     if (deviceType_App() == desktop_AppDeviceType &&
         windowCoord.y < rootSize.y - lineHeight_Text(uiNormal_FontSize) * 3) {
@@ -1315,8 +1382,8 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
         winRect.size.y /= pixelRatio;
         addv_I2(&winRect.pos, winPos);
         iRect visibleWinRect = intersect_Rect(winRect, displayRect);
-        /* Only use a popup window if the menu can't fit inside the main window. */
-        if (height_Widget(d) / pixelRatio > visibleWinRect.size.y ||
+        /* Only use a popup window if the menu can't fit inside the window. */
+        if (isPopupForced || height_Widget(d) / pixelRatio > visibleWinRect.size.y ||
             (allowOverflow &&
              (windowCoord.y < 0 || windowCoord.y + height_Widget(d) > get_Window()->size.y))) {
             if (postCommands) {
@@ -1339,6 +1406,10 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
                     SDL_GetWindowSize(sdlWin, &winSize.x, &winSize.y);
                     menuPos = sub_I2(add_I2(winPos, divi_I2(winSize, 2)), divi_I2(menuSize, 2));
                 }
+                if (isSubmenu && menuPos.x + width_Widget(d) > right_Rect(displayRect)) {
+                    /* Flip it to the right side. */
+                    menuPos.x -= menuSize.x + width_Rect(windowAnchorRect) / pixelRatio;
+                }
                 menuPos.x = iMin(menuPos.x, right_Rect(displayRect) - menuSize.x);
                 menuPos.y = iMax(0, iMin(menuPos.y, bottom_Rect(displayRect) - menuSize.y));
             }
@@ -1354,6 +1425,10 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
             return;
         }
     }
+    if (isSubmenu && windowCoord.x + width_Widget(d) > rootSize.x) {
+        /* Flip it to the right side. */
+        windowCoord = addX_I2(topLeft_Rect(windowAnchorRect), -width_Widget(d));
+    }
     raise_Widget(d);
     if (deviceType_App() != desktop_AppDeviceType) {
         setFlags_Widget(d, arrangeWidth_WidgetFlag | resizeChildrenToWidestChild_WidgetFlag,
@@ -3620,7 +3695,7 @@ iWidget *makePreferences_Widget(void) {
         iSnippetWidget *sniped = new_SnippetWidget();
         appendFramelessTabPage_Widget(tabs,
                                       iClob(sniped),
-                                      scissor_Icon " ${heading.prefs.snip}",
+                                      clipboard_Icon " ${heading.prefs.snip}",
                                       cyan_ColorId,
                                       '7',
                                       KMOD_PRIMARY);
@@ -4181,7 +4256,7 @@ iWidget *makeSiteSpecificSettings_Widget(const iString *url) {
 
 /*----------------------------------------------------------------------------------------------*/
 
-static iBool snippetCreationHandler_(iWidget *dlg, const char *cmd) {
+static iBool handleSnippetCreationCommands_(iWidget *dlg, const char *cmd) {
     if (equal_Command(cmd, "widget.resized")) {
         iWidget  *headings   = findChild_Widget(dlg, "snip.columns.head");
         iWidget  *name       = findChild_Widget(dlg, "snip.name");
@@ -4202,7 +4277,7 @@ static iBool snippetCreationHandler_(iWidget *dlg, const char *cmd) {
         if (!set_Snippets(text_InputWidget(name), text_InputWidget(content))) {
             return iTrue;
         }
-        postCommand_App("snippets.changed");
+        postCommandf_App("snippets.changed added:%s", cstr_String(text_InputWidget(name)));
         setupSheetTransition_Mobile(dlg, dialogTransitionDir_Widget(dlg));
         destroy_Widget(dlg);
         return iTrue;
@@ -4219,7 +4294,7 @@ iWidget *makeSnippetCreation_Widget(void) {
     if (isUsingPanelLayout_Mobile()) {
         /* TODO */
 
-        setCommandHandler_Widget(dlg, snippetCreationHandler_);
+        setCommandHandler_Widget(dlg, handleSnippetCreationCommands_);
     }
     else {
         iWidget *headings, *values;
@@ -4232,12 +4307,13 @@ iWidget *makeSnippetCreation_Widget(void) {
         setLineBreaksEnabled_InputWidget(name, iFalse);
         addPrefsInputWithHeading_(headings, values, "snip.name", iClob(name));
         addPrefsInputWithHeading_(headings, values, "snip.content", iClob(content));
+        addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
         addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions))));
         addChild_Widget(get_Root()->widget, iClob(dlg));
         as_Widget(name)->rect.size.x = 60 * gap_UI;
         as_Widget(content)->rect.size.x = 60 * gap_UI;
         arrange_Widget(dlg);
-        setCommandHandler_Widget(dlg, snippetCreationHandler_);
+        setCommandHandler_Widget(dlg, handleSnippetCreationCommands_);
         enableResizing_Widget(dlg, width_Widget(dlg), "snip");
     }
     setupSheetTransition_Mobile(dlg, incoming_TransitionFlag | dialogTransitionDir_Widget(dlg));
diff --git a/src/ui/util.h b/src/ui/util.h
index f51f364b..76a92443 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -283,6 +283,8 @@ enum iMenuOpenFlags {
     postCommands_MenuOpenFlags = iBit(1),
     center_MenuOpenFlags       = iBit(2),
     setFocus_MenuOpenFlags     = iBit(3),
+    submenu_MenuOpenFlags      = iBit(4),
+    forcePopup_MenuOpenFlags   = iBit(5),
 };
 
 iWidget *       makeMenu_Widget                 (iWidget *parent, const iMenuItem *items, size_t n); /* returns no ref */
@@ -290,6 +292,7 @@ iWidget *       makeMenuFlags_Widget            (iWidget *parent, const iMenuIte
 void            makeMenuItems_Widget            (iWidget *menu, const iMenuItem *items, size_t n);
 void            openMenu_Widget                 (iWidget *, iInt2 windowCoord);
 void            openMenuFlags_Widget            (iWidget *, iInt2 windowCoord, int flags);
+void            openMenuAnchorFlags_Widget      (iWidget *, iRect windowAnchorRect, int menuOpenFlags);
 void            closeMenu_Widget                (iWidget *);
 iBool           handleMenuCommand_Widget        (iWidget *menu, const char *cmd); /* used as the command handler */
 void            releaseNativeMenu_Widget        (iWidget *);
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 98b86f30..cc7e0b1d 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -1291,14 +1291,14 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
     else if (ev->type == SDL_MOUSEMOTION &&
              ev->motion.windowID == id_Window(window_Widget(d)) &&
              (!window_Widget(d)->hover || hasParent_Widget(d, window_Widget(d)->hover)) &&
-             flags_Widget(d) & hover_WidgetFlag && !isHidden_Widget_(d) &&
+             flags_Widget(d) & hover_WidgetFlag &&
+             !isHidden_Widget_(d) /* hidden flag on self */ &&
              ~flags_Widget(d) & disabled_WidgetFlag) {
         if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) {
             setHover_Widget(d);
 #if 0
-            printf("set hover to [%p] %s:'%s'\n",
-                   d, class_Widget(d)->name,
-                   cstr_String(id_Widget(d)));
+            printf("<%u> set hover to ", window_Widget(d)->frameTime);
+            identify_Widget(d);
             fflush(stdout);
 #endif
         }
@@ -2607,7 +2607,7 @@ void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) {
         }
         deinit_String(&ptrStr);
     }
-    postCommandString_Root(((const iWidget *) d)->root, &str);
+    postCommandString_Root(isGlobal ? NULL : ((const iWidget *) d)->root, &str);
     deinit_String(&str);
 }
 
diff --git a/src/ui/window.c b/src/ui/window.c
index fc6dbb43..480ef4bb 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 #include "documentwidget.h"
 #include "sidebarwidget.h"
 #include "paint.h"
+#include "snippets.h"
 #include "root.h"
 #include "touch.h"
 #include "util.h"
@@ -129,7 +130,7 @@ static const iMenuItem viewMenuItems_[] = {
     { "${menu.zoom.out}", SDLK_MINUS, KMOD_PRIMARY, "zoom.delta arg:-10" },
     { "${menu.zoom.reset}", SDLK_0, KMOD_PRIMARY, "zoom.set arg:100" },
     { "---" },
-    { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" },
+    { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "submenu id:splitmenu" },
     { NULL }
 };
 
@@ -315,9 +316,6 @@ void resizeSplits_MainWindow(iMainWindow *d, iBool updateDocumentSize) {
 }
 
 static void setupUserInterface_MainWindow(iMainWindow *d) {
-#if defined (LAGRANGE_MAC_MENUBAR)
-    insertMacMenus_(); /* TODO: Shouldn't this be in the App? */
-#endif
     /* One root is created by default. */
     d->base.roots[0] = new_Root();
     d->base.roots[0]->window = as_Window(d);
@@ -326,6 +324,9 @@ static void setupUserInterface_MainWindow(iMainWindow *d) {
     setCurrent_Root(NULL);
     /* One of the roots always has keyboard input focus. */
     d->base.keyRoot = d->base.roots[0];
+#if defined (LAGRANGE_MAC_MENUBAR)
+    insertMacMenus_(); /* TODO: Shouldn't this be in the App? */
+#endif
 }
 
 static iBool updateSize_MainWindow_(iMainWindow *d, iBool notifyAlways) {
@@ -1012,7 +1013,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
         case SDL_WINDOWEVENT_FOCUS_LOST:
             if (d->type == popup_WindowType) {
                 /* Popup windows are currently only used for menus. */
-                closeMenu_Widget(d->roots[0]->widget);
+//                closeMenu_Widget(d->roots[0]->widget);
             }
             else {
                 postCommandf_App("window.focus.lost arg:%u", id_Window(d));
@@ -1386,6 +1387,14 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
                     updateMetrics_Root(d->roots[i]);
                 }
             }
+            if (isCommand_UserEvent(&event, "snippets.changed")) {
+                /* Recreate the snippet menus with the new list of snippets. */
+                iForIndices(i, d->roots) {
+                    if (d->roots[i]) {
+                        recreateSnippetMenu_Root(d->roots[i]);
+                    }
+                }
+            }
             if (isCommand_UserEvent(&event, "lang.changed") && (mw || extraw)) {
 #if defined (LAGRANGE_MAC_MENUBAR)
                 /* Retranslate the menus. */
Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.17/cdiff/01898c3a1a711c7e22888deeb7c4e64427e99b13
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
33.341148 milliseconds
Gemini-to-HTML Time
1.74626 milliseconds

This content has been proxied by September (3851b).