diff --git a/CHANGES.txt b/CHANGES.txt index c2ee9e889..282830c8d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -105,6 +105,8 @@ Changes in FLTK 1.4.0 Released: Feb ?? 2024 hardware support is present (a backup mechanism is available in absence of this support). Thus, all text drawable in Fl_Window's can be drawn in Fl_Gl_Window's (STR#3450). + - New member function Fl_Menu_Bar::play_menu(const char *title) to + programmatically open a menu of a menubar. - New member functions Fl::program_should_quit(void), and Fl::program_should_quit(int) to support detection by the library of a request to terminate cleanly the program. diff --git a/FL/Fl_Menu_Bar.H b/FL/Fl_Menu_Bar.H index 450708895..df7097996 100644 --- a/FL/Fl_Menu_Bar.H +++ b/FL/Fl_Menu_Bar.H @@ -61,18 +61,7 @@ Typing the shortcut() of any of the menu items will cause callbacks exactly the same as when you pick the item with the mouse. - - This code can be used to programmatically open a menu ("Edit" in the example) of a menubar : - \code - Fl_Menu_Bar *bar = ……; - const Fl_Menu_Item *v = bar->find_item("Edit"); - if (v) { - v = bar->menu()->pulldown(bar->x(), bar->y(), bar->w(), bar->h(), v, bar, 0, 1); - bar->picked(v); - } - \endcode - This code currently should be avoided with an Fl_Sys_Menu_Bar object on the macOS platform. -*/ + */ class FL_EXPORT Fl_Menu_Bar : public Fl_Menu_ { friend class Fl_Sys_Menu_Bar_Driver; protected: @@ -102,6 +91,10 @@ public: This is useful when the menu bar can be an Fl_Sys_Menu_Bar object. */ virtual void update() {} + /** + Opens the menu named \c title of the menubar. + */ + virtual void play_menu(const char *title); }; #endif diff --git a/FL/Fl_Sys_Menu_Bar.H b/FL/Fl_Sys_Menu_Bar.H index 7045757a6..27bf6a94b 100644 --- a/FL/Fl_Sys_Menu_Bar.H +++ b/FL/Fl_Sys_Menu_Bar.H @@ -111,6 +111,7 @@ public: const Fl_Menu_Item *menu() const {return Fl_Menu_::menu();} void menu(const Fl_Menu_Item *m); void update() FL_OVERRIDE; + void play_menu(const char *title) FL_OVERRIDE; int add(const char* label, int shortcut, Fl_Callback*, void *user_data=0, int flags=0); /** Adds a new menu item. \see Fl_Menu_::add(const char* label, int shortcut, Fl_Callback*, void *user_data=0, int flags=0) diff --git a/src/Fl_MacOS_Sys_Menu_Bar.mm b/src/Fl_MacOS_Sys_Menu_Bar.mm index d51734729..aa8560966 100644 --- a/src/Fl_MacOS_Sys_Menu_Bar.mm +++ b/src/Fl_MacOS_Sys_Menu_Bar.mm @@ -726,4 +726,36 @@ void fl_mac_set_about(Fl_Callback *cb, void *user_data, int shortcut) { Fl_Sys_Menu_Bar::about(cb, user_data); } + +void Fl_MacOS_Sys_Menu_Bar_Driver::play_menu(const char *menu_name) { + // Use the accessibility interface to programmatically open a menu of the system menubar + char *ts = remove_ampersand(menu_name); + NSString *mac_name = [NSString stringWithUTF8String:ts]; + free(ts); + AXUIElementRef appElement = AXUIElementCreateApplication(getpid()); + AXUIElementRef menuBar; + AXError error = AXUIElementCopyAttributeValue(appElement, kAXMenuBarAttribute, (CFTypeRef *)&menuBar); + if (error) return; + CFIndex count = -1; + error = AXUIElementGetAttributeValueCount(menuBar, kAXChildrenAttribute, &count); + if (error) { CFRelease(menuBar); return; } + NSArray *children = nil; + error = AXUIElementCopyAttributeValues(menuBar, kAXChildrenAttribute, 0, count, (CFArrayRef *)&children); + if (error) { CFRelease(menuBar); return; } + for (id child in children) { + AXUIElementRef element = (AXUIElementRef)child; + id title; + AXError error = AXUIElementCopyAttributeValue(element, kAXTitleAttribute, (CFTypeRef *)&title); + if (!error && [title isEqualToString:mac_name]) { + AXUIElementPerformAction(element, kAXPressAction); + CFRelease(title); + break; + } + CFRelease(title); + } + CFRelease(menuBar); + [children release]; + CFRelease(appElement); +} + #endif /* __APPLE__ */ diff --git a/src/Fl_Menu_Bar.cxx b/src/Fl_Menu_Bar.cxx index ebb97b7ca..d605efb1e 100644 --- a/src/Fl_Menu_Bar.cxx +++ b/src/Fl_Menu_Bar.cxx @@ -67,3 +67,12 @@ Fl_Menu_Bar::Fl_Menu_Bar(int X, int Y, int W, int H,const char *l) : Fl_Menu_(X,Y,W,H,l) { } + + +void Fl_Menu_Bar::play_menu(const char *title) { + const Fl_Menu_Item *v = find_item(title); + if (v) { + v = menu()->pulldown(x(), y(), w(), h(), v, this, 0, 1); + picked(v); + } +} diff --git a/src/Fl_Sys_Menu_Bar.cxx b/src/Fl_Sys_Menu_Bar.cxx index c0ae88b25..477470cd3 100644 --- a/src/Fl_Sys_Menu_Bar.cxx +++ b/src/Fl_Sys_Menu_Bar.cxx @@ -242,6 +242,11 @@ void Fl_Sys_Menu_Bar::create_window_menu() { } } +void Fl_Sys_Menu_Bar::play_menu(const char *title) { + if (driver()) fl_sys_menu_bar->driver()->play_menu(title); + else Fl_Menu_Bar::play_menu(title); +} + #if !defined(FL_DOXYGEN) Fl_Sys_Menu_Bar_Driver *Fl_Sys_Menu_Bar::driver() { return Fl::system_driver()->sys_menu_bar_driver(); diff --git a/src/Fl_Sys_Menu_Bar_Driver.H b/src/Fl_Sys_Menu_Bar_Driver.H index b8e4b4373..38862ce01 100644 --- a/src/Fl_Sys_Menu_Bar_Driver.H +++ b/src/Fl_Sys_Menu_Bar_Driver.H @@ -48,6 +48,7 @@ public: virtual void replace(int index, const char *name) { bar->Fl_Menu_Bar::replace(index, name); } virtual void mode(int i, int fl) { bar->Fl_Menu_Bar::mode(i, fl); } virtual void create_window_menu() {} + virtual void play_menu(const char *title) {} static Fl_Sys_Menu_Bar::window_menu_style_enum window_menu_style() { return window_menu_style_; } static void window_menu_style(Fl_Sys_Menu_Bar::window_menu_style_enum style) { window_menu_style_ = style; } }; diff --git a/src/drivers/Cocoa/Fl_MacOS_Sys_Menu_Bar_Driver.H b/src/drivers/Cocoa/Fl_MacOS_Sys_Menu_Bar_Driver.H index 684ae91f6..28f113a8e 100644 --- a/src/drivers/Cocoa/Fl_MacOS_Sys_Menu_Bar_Driver.H +++ b/src/drivers/Cocoa/Fl_MacOS_Sys_Menu_Bar_Driver.H @@ -44,6 +44,7 @@ public: void remove_window(Fl_Window *win); void rename_window(Fl_Window *win); static Fl_MacOS_Sys_Menu_Bar_Driver* driver(); + void play_menu(const char *menu_name) FL_OVERRIDE; };