summaryrefslogtreecommitdiff
path: root/src/Fl_MacOS_Sys_Menu_Bar.mm
diff options
context:
space:
mode:
authorManolo Gouy <Manolo>2017-12-02 16:20:30 +0000
committerManolo Gouy <Manolo>2017-12-02 16:20:30 +0000
commit34dd1b15fbbc44e70bfdb21498ce7898bf8658b3 (patch)
treefb65dd41847ff4205236066de124a1c9cb27e33c /src/Fl_MacOS_Sys_Menu_Bar.mm
parenta8fe10cf88e757f59057fdaf51637d26ab3019aa (diff)
Converted class Fl_Sys_Menu_Bar to the driver model.
git-svn-id: file:///fltk/svn/fltk/branches/branch-1.4@12575 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
Diffstat (limited to 'src/Fl_MacOS_Sys_Menu_Bar.mm')
-rw-r--r--src/Fl_MacOS_Sys_Menu_Bar.mm513
1 files changed, 513 insertions, 0 deletions
diff --git a/src/Fl_MacOS_Sys_Menu_Bar.mm b/src/Fl_MacOS_Sys_Menu_Bar.mm
new file mode 100644
index 000000000..bf9af8145
--- /dev/null
+++ b/src/Fl_MacOS_Sys_Menu_Bar.mm
@@ -0,0 +1,513 @@
+//
+// "$Id$"
+//
+// MacOS system menu bar widget for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-2017 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file. If this
+// file is missing or damaged, see the license at:
+//
+// http://www.fltk.org/COPYING.php
+//
+// Please report all bugs and problems on the following page:
+//
+// http://www.fltk.org/str.php
+//
+
+/*
+ * This code has been tested on the "menubar" sample program and provides
+ * basic functionality.
+ *
+ * To use the System Menu Bar, simply replace the main Fl_Menu_Bar
+ * in an application with Fl_Sys_Menu_Bar.
+ *
+ * FLTK features not supported by the Mac System menu
+ *
+ * - no symbolic labels
+ * - no embossed labels
+ * - no font sizes
+ *
+ * Many other calls of the parent class don't work.
+ */
+#include <FL/Fl_Sys_Menu_Bar_Driver.H>
+#include <FL/x.H>
+
+
+#if defined(__APPLE__)
+
+class Fl_MacOS_Sys_Menu_Bar_Driver : public Fl_Sys_Menu_Bar_Driver {
+public:
+ Fl_MacOS_Sys_Menu_Bar_Driver();
+ virtual ~Fl_MacOS_Sys_Menu_Bar_Driver();
+ virtual void update();
+ virtual void draw() {}
+ virtual void about(Fl_Callback *cb, void *data);
+ virtual int add(const char* label, int shortcut, Fl_Callback *cb, void *user_data, int flags);
+ virtual int add(const char* str);
+ virtual int insert(int index, const char* label, int shortcut, Fl_Callback *cb, void *user_data, int flags);
+ virtual void menu(const Fl_Menu_Item *m);
+ virtual void shortcut (int i, int s);
+ virtual void setonly (Fl_Menu_Item *item);
+ virtual void clear();
+ virtual int clear_submenu(int index);
+ virtual void remove(int index);
+ virtual void replace(int index, const char *name);
+ virtual void mode(int i, int fl);
+};
+
+// this runs once if this source file is linked in, and initializes the
+// static variable Fl_Sys_Menu_Bar_Driver::driver_ with an object of class Fl_MacOS_Sys_Menu_Bar_Driver
+static int unused = (Fl_Sys_Menu_Bar_Driver::driver_ = new Fl_MacOS_Sys_Menu_Bar_Driver(), 0);
+
+#import <Cocoa/Cocoa.h>
+
+#include "flstring.h"
+#include <stdio.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+typedef const Fl_Menu_Item *pFl_Menu_Item;
+
+static Fl_Menu_Bar *custom_menu;
+
+static char *remove_ampersand(const char *s);
+extern void (*fl_lock_function)();
+extern void (*fl_unlock_function)();
+
+/* Each MacOS system menu item contains a pointer to a record of type sys_menu_item defined below.
+ The purpose of these records is to associate each MacOS system menu item with a relevant Fl_Menu_Item.
+
+ If use_index is YES, the "index" field is used, and fl_sys_menu_bar->menu() + index is the address
+ of the relevant Fl_Menu_Item;
+ Otherwise, the "item" field points to the relevant Fl_Menu_Item.
+ This allows the MacOS system menu to use the same Fl_Menu_Item's as those used by FLTK menus,
+ the address of which can be relocated by the FLTK menu logic.
+ The "item" field is used for non-relocatable Fl_Menu_Item's associated to FL_SUBMENU_POINTER.
+ Sending the getFlItem message to a MacOS system menu item (of class FLMenuItem) returns the address
+ of the relevant Fl_Menu_Item.
+*/
+typedef struct {
+ union {
+ int index;
+ const Fl_Menu_Item *item;
+ };
+ BOOL use_index;
+} sys_menu_item;
+
+// Apple App Menu
+const char *Fl_Mac_App_Menu::about = "About %@";
+const char *Fl_Mac_App_Menu::print = "Print Front Window";
+const char *Fl_Mac_App_Menu::services = "Services";
+const char *Fl_Mac_App_Menu::hide = "Hide %@";
+const char *Fl_Mac_App_Menu::hide_others = "Hide Others";
+const char *Fl_Mac_App_Menu::show = "Show All";
+const char *Fl_Mac_App_Menu::quit = "Quit %@";
+
+
+@interface FLMenuItem : NSMenuItem {
+}
+- (const Fl_Menu_Item*) getFlItem;
+- (void) itemCallback:(Fl_Menu_*)menu;
+- (void) doCallback;
+- (void) customCallback;
+- (void) directCallback;
+- (void) setKeyEquivalentModifierMask:(int)value;
+- (void) setFltkShortcut:(int)key;
++ (int) addNewItem:(const Fl_Menu_Item*)mitem menu:(NSMenu*)menu action:(SEL)selector;
+@end
+
+@implementation FLMenuItem
+- (const Fl_Menu_Item*) getFlItem
+// returns the Fl_Menu_Item corresponding to this system menu item
+{
+ sys_menu_item *smi = (sys_menu_item*)[(NSData*)[self representedObject] bytes];
+ if (smi->use_index) return fl_sys_menu_bar->menu() + smi->index;
+ return smi->item;
+}
+- (void) itemCallback:(Fl_Menu_*)menu
+{
+ const Fl_Menu_Item *item = [self getFlItem];
+ menu->picked(item);
+ if ( item->flags & FL_MENU_TOGGLE ) { // update the menu toggle symbol
+ [self setState:(item->value() ? NSOnState : NSOffState)];
+ }
+ else if ( item->flags & FL_MENU_RADIO ) { // update the menu radio symbols
+ NSMenu* menu = [self menu];
+ NSInteger flRank = [menu indexOfItem:self];
+ NSInteger last = [menu numberOfItems] - 1;
+ int from = flRank;
+ while(from > 0) {
+ if ([[menu itemAtIndex:from-1] isSeparatorItem]) break;
+ item = [(FLMenuItem*)[menu itemAtIndex:from-1] getFlItem];
+ if ( !(item->flags & FL_MENU_RADIO) ) break;
+ from--;
+ }
+ int to = flRank;
+ while (to < last) {
+ if ([[menu itemAtIndex:to+1] isSeparatorItem]) break;
+ item = [(FLMenuItem*)[menu itemAtIndex:to+1] getFlItem];
+ if (!(item->flags & FL_MENU_RADIO)) break;
+ to++;
+ }
+ for(int i = from; i <= to; i++) {
+ NSMenuItem *nsitem = [menu itemAtIndex:i];
+ [nsitem setState:(nsitem != self ? NSOffState : NSOnState)];
+ }
+ }
+}
+- (void) doCallback
+{
+ fl_lock_function();
+ [self itemCallback:fl_sys_menu_bar];
+ fl_unlock_function();
+}
+- (void) customCallback
+{
+ fl_lock_function();
+ [self itemCallback:custom_menu];
+ fl_unlock_function();
+}
+- (void) directCallback
+{
+ fl_lock_function();
+ Fl_Menu_Item *item = (Fl_Menu_Item *)[(NSData*)[self representedObject] bytes];
+ if ( item && item->callback() ) item->do_callback(NULL);
+ fl_unlock_function();
+}
+- (void) setKeyEquivalentModifierMask:(int)value
+{
+ NSUInteger macMod = 0;
+ if ( value & FL_META ) macMod = NSCommandKeyMask;
+ if ( value & FL_SHIFT || isupper(value) ) macMod |= NSShiftKeyMask;
+ if ( value & FL_ALT ) macMod |= NSAlternateKeyMask;
+ if ( value & FL_CTRL ) macMod |= NSControlKeyMask;
+ [super setKeyEquivalentModifierMask:macMod];
+}
+- (void) setFltkShortcut:(int)key
+{
+ // Separate key and modifier
+ int mod = key;
+ mod &= ~FL_KEY_MASK; // modifier(s)
+ key &= FL_KEY_MASK; // key
+ unichar mac_key = (unichar)key;
+ if ( (key >= (FL_F+1)) && (key <= FL_F_Last) ) { // Handle function keys
+ int fkey_num = (key - FL_F); // 1,2..
+ mac_key = NSF1FunctionKey + fkey_num - 1;
+ }
+ [self setKeyEquivalent:[NSString stringWithCharacters:&mac_key length:1]];
+ [self setKeyEquivalentModifierMask:mod];
+}
++ (int) addNewItem:(const Fl_Menu_Item*)mitem menu:(NSMenu*)menu action:(SEL)selector
+{
+ char *name = remove_ampersand(mitem->label());
+ NSString *title = NSLocalizedString([NSString stringWithUTF8String:name], nil);
+ free(name);
+ FLMenuItem *item = [[FLMenuItem alloc] initWithTitle:title
+ action:selector
+ keyEquivalent:@""];
+ sys_menu_item smi;
+ // >= 0 if mitem is in the menu items of fl_sys_menu_bar, -1 if not
+ smi.index = (fl_sys_menu_bar ? fl_sys_menu_bar->find_index(mitem) : -1);
+ smi.use_index = (smi.index >= 0);
+ if (!smi.use_index) smi.item = mitem;
+ NSData *pointer = [NSData dataWithBytes:&smi length:sizeof(smi)];
+ [item setRepresentedObject:pointer];
+ [menu addItem:item];
+ [item setTarget:item];
+ int retval = [menu indexOfItem:item];
+ [item release];
+ return retval;
+}
+@end
+
+
+void Fl_MacOS_Sys_Menu_Bar_Driver::about( Fl_Callback *cb, void *user_data)
+{
+ fl_open_display();
+ Fl_Menu_Item aboutItem;
+ memset(&aboutItem, 0, sizeof(Fl_Menu_Item));
+ aboutItem.callback(cb);
+ aboutItem.user_data(user_data);
+ NSMenu *appleMenu = [[[NSApp mainMenu] itemAtIndex:0] submenu];
+ CFStringRef cfname = CFStringCreateCopy(NULL, (CFStringRef)[[appleMenu itemAtIndex:0] title]);
+ [appleMenu removeItemAtIndex:0];
+ FLMenuItem *item = [[[FLMenuItem alloc] initWithTitle:(NSString*)cfname
+ action:@selector(directCallback)
+ keyEquivalent:@""] autorelease];
+ NSData *pointer = [NSData dataWithBytes:&aboutItem length:sizeof(Fl_Menu_Item)];
+ [item setRepresentedObject:pointer];
+ [appleMenu insertItem:item atIndex:0];
+ CFRelease(cfname);
+ [item setTarget:item];
+}
+
+/*
+ * Set a shortcut for an Apple menu item using the FLTK shortcut descriptor.
+ */
+static void setMenuShortcut( NSMenu* mh, int miCnt, const Fl_Menu_Item *m )
+{
+ if ( !m->shortcut_ )
+ return;
+ if ( m->flags & FL_SUBMENU )
+ return;
+ if ( m->flags & FL_SUBMENU_POINTER )
+ return;
+ FLMenuItem* menuItem = (FLMenuItem*)[mh itemAtIndex:miCnt];
+ [menuItem setFltkShortcut:(m->shortcut_)];
+}
+
+
+/*
+ * Set the Toggle and Radio flag based on FLTK flags
+ */
+static void setMenuFlags( NSMenu* mh, int miCnt, const Fl_Menu_Item *m )
+{
+ if ( m->flags & FL_MENU_TOGGLE )
+ {
+ NSMenuItem *menuItem = [mh itemAtIndex:miCnt];
+ [menuItem setState:(m->flags & FL_MENU_VALUE ? NSOnState : NSOffState)];
+ }
+ else if ( m->flags & FL_MENU_RADIO ) {
+ NSMenuItem *menuItem = [mh itemAtIndex:miCnt];
+ [menuItem setState:(m->flags & FL_MENU_VALUE ? NSOnState : NSOffState)];
+ }
+}
+
+static char *remove_ampersand(const char *s)
+{
+ char *ret = strdup(s);
+ const char *p = s;
+ char *q = ret;
+ while(*p != 0) {
+ if (p[0]=='&') {
+ if (p[1]=='&') {
+ *q++ = '&'; p+=2;
+ } else {
+ p++;
+ }
+ } else {
+ *q++ = *p++;
+ }
+ }
+ *q = 0;
+ return ret;
+}
+
+
+/*
+ * create a sub menu for a specific menu handle
+ */
+static void createSubMenu( NSMenu *mh, pFl_Menu_Item &mm, const Fl_Menu_Item *mitem, SEL selector)
+{
+ NSMenu *submenu;
+ int miCnt, flags;
+
+ if (mitem) {
+ NSMenuItem *menuItem;
+ char *ts = remove_ampersand(mitem->text);
+ NSString *title = NSLocalizedString([NSString stringWithUTF8String:ts], nil);
+ free(ts);
+ submenu = [[NSMenu alloc] initWithTitle:(NSString*)title];
+ [submenu setAutoenablesItems:NO];
+
+ int cnt;
+ cnt = [mh numberOfItems];
+ cnt--;
+ menuItem = [mh itemAtIndex:cnt];
+ [menuItem setSubmenu:submenu];
+ [submenu release];
+ } else submenu = mh;
+
+ while ( mm->text ) {
+ if (!mm->visible() ) { // skip invisible items and submenus
+ mm = mm->next(0);
+ continue;
+ }
+ miCnt = [FLMenuItem addNewItem:mm menu:submenu action:selector];
+ setMenuFlags( submenu, miCnt, mm );
+ setMenuShortcut( submenu, miCnt, mm );
+ if (mitem && (mm->flags & FL_MENU_INACTIVE || mitem->flags & FL_MENU_INACTIVE)) {
+ NSMenuItem *item = [submenu itemAtIndex:miCnt];
+ [item setEnabled:NO];
+ }
+ flags = mm->flags;
+ if ( mm->flags & FL_SUBMENU )
+ {
+ mm++;
+ createSubMenu( submenu, mm, mm - 1, selector);
+ }
+ else if ( mm->flags & FL_SUBMENU_POINTER )
+ {
+ const Fl_Menu_Item *smm = (Fl_Menu_Item*)mm->user_data_;
+ createSubMenu( submenu, smm, mm, selector);
+ }
+ if ( flags & FL_MENU_DIVIDER ) {
+ [submenu addItem:[NSMenuItem separatorItem]];
+ }
+ mm++;
+ }
+}
+
+
+/*
+ * convert a complete Fl_Menu_Item array into a series of menus in the top menu bar
+ * ALL PREVIOUS SYSTEM MENUS, EXCEPT THE APPLICATION MENU, ARE REPLACED BY THE NEW DATA
+ */
+static void convertToMenuBar(const Fl_Menu_Item *mm)
+{
+ NSMenu *fl_system_menu = [NSApp mainMenu];
+ int count;//first, delete all existing system menus
+ count = [fl_system_menu numberOfItems];
+ for(int i = count - 1; i > 0; i--) {
+ [fl_system_menu removeItem:[fl_system_menu itemAtIndex:i]];
+ }
+ if (mm) createSubMenu(fl_system_menu, mm, NULL, @selector(doCallback));
+}
+
+void Fl_MacOS_Sys_Menu_Bar_Driver::update()
+{
+ convertToMenuBar(bar->Fl_Menu_::menu());
+}
+
+
+static int process_sys_menu_shortcuts(int event)
+{
+ if (event != FL_SHORTCUT || !fl_sys_menu_bar || Fl::modal()) return 0;
+ // is the last event the shortcut of an item of the fl_sys_menu_bar menu ?
+ const Fl_Menu_Item *item = fl_sys_menu_bar->menu()->test_shortcut();
+ if (!item) return 0;
+ if (item->visible()) // have the system menu process the shortcut, highlighting the corresponding menu
+ [[NSApp mainMenu] performKeyEquivalent:[NSApp currentEvent]];
+ else // have FLTK process the shortcut associated to an invisible Fl_Menu_Item
+ fl_sys_menu_bar->picked(item);
+ return 1;
+}
+
+Fl_MacOS_Sys_Menu_Bar_Driver::Fl_MacOS_Sys_Menu_Bar_Driver() : Fl_Sys_Menu_Bar_Driver()
+{
+ Fl::add_handler(process_sys_menu_shortcuts);
+}
+
+Fl_MacOS_Sys_Menu_Bar_Driver::~Fl_MacOS_Sys_Menu_Bar_Driver()
+{
+ Fl::remove_handler(process_sys_menu_shortcuts);
+}
+
+void Fl_MacOS_Sys_Menu_Bar_Driver::menu(const Fl_Menu_Item *m)
+{
+ fl_open_display();
+ bar->Fl_Menu_Bar::menu( m );
+ convertToMenuBar(m);
+}
+
+void Fl_MacOS_Sys_Menu_Bar_Driver::clear()
+{
+ bar->Fl_Menu_::clear();
+ convertToMenuBar(NULL);
+}
+
+int Fl_MacOS_Sys_Menu_Bar_Driver::clear_submenu(int index)
+{
+ int retval = bar->Fl_Menu_::clear_submenu(index);
+ if (retval != -1) update();
+ return retval;
+}
+
+void Fl_MacOS_Sys_Menu_Bar_Driver::remove(int index)
+{
+ bar->Fl_Menu_::remove(index);
+ update();
+}
+
+void Fl_MacOS_Sys_Menu_Bar_Driver::replace(int index, const char *name)
+{
+ bar->Fl_Menu_::replace(index, name);
+ update();
+}
+
+void Fl_MacOS_Sys_Menu_Bar_Driver::mode(int i, int fl) {
+ bar->Fl_Menu_::mode(i, fl);
+ update();
+}
+
+void Fl_MacOS_Sys_Menu_Bar_Driver::shortcut (int i, int s) {
+ bar->Fl_Menu_Bar::shortcut(i, s);
+ update();
+}
+
+void Fl_MacOS_Sys_Menu_Bar_Driver::setonly (Fl_Menu_Item *item) {
+ bar->Fl_Menu_::setonly(item);
+ update();
+}
+
+int Fl_MacOS_Sys_Menu_Bar_Driver::add(const char* label, int shortcut, Fl_Callback *cb, void *user_data, int flags)
+{
+ fl_open_display();
+ int index = bar->Fl_Menu_::add(label, shortcut, cb, user_data, flags);
+ update();
+ return index;
+}
+
+int Fl_MacOS_Sys_Menu_Bar_Driver::add(const char* str)
+{
+ fl_open_display();
+ int index = bar->Fl_Menu_::add(str);
+ update();
+ return index;
+}
+
+int Fl_MacOS_Sys_Menu_Bar_Driver::insert(int index, const char* label, int shortcut, Fl_Callback *cb, void *user_data, int flags)
+{
+ fl_open_display();
+ int menu_index = bar->Fl_Menu_::insert(index, label, shortcut, cb, user_data, flags);
+ update();
+ return menu_index;
+}
+
+/** \class Fl_Mac_App_Menu
+ Mac OS-specific class allowing to customize and localize the application menu.
+
+ The public class attributes are used to build the application menu. They can be localized
+ at run time to any UTF-8 text by placing instructions such as this before fl_open_display()
+ gets called:
+ \verbatim
+ Fl_Mac_App_Menu::print = "Imprimer la fenĂȘtre";
+ \endverbatim
+ \see \ref osissues_macos for another way to localization.
+ */
+
+
+/** Adds custom menu item(s) to the application menu of the system menu bar.
+ They are positioned after the "Print Front Window" item, or at its place
+ if it was removed with <tt>Fl_Mac_App_Menu::print = ""</tt>.
+ \param m zero-ending array of Fl_Menu_Item 's.
+ */
+void Fl_Mac_App_Menu::custom_application_menu_items(const Fl_Menu_Item *m)
+{
+ fl_open_display(); // create the system menu, if needed
+ custom_menu = new Fl_Menu_Bar(0,0,0,0);
+ custom_menu->menu(m);
+ NSMenu *menu = [[[NSApp mainMenu] itemAtIndex:0] submenu]; // the application menu
+ NSInteger to_index;
+ if ([[menu itemAtIndex:2] action] != @selector(printPanel)) { // the 'Print' item was removed
+ [menu insertItem:[NSMenuItem separatorItem] atIndex:1];
+ to_index = 2;
+ } else to_index = 3; // after the "Print Front Window" item
+ NSInteger count = [menu numberOfItems];
+ createSubMenu(menu, m, NULL, @selector(customCallback)); // add new items at end of application menu
+ NSInteger count2 = [menu numberOfItems];
+ for (NSInteger i = count; i < count2; i++) { // move new items to their desired position in application menu
+ NSMenuItem *item = [menu itemAtIndex:i];
+ [item retain];
+ [menu removeItemAtIndex:i];
+ [menu insertItem:item atIndex:to_index++];
+ [item release];
+ }
+}
+#endif /* __APPLE__ */
+
+//
+// End of "$Id$".
+//