diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Fl_MacOS_Sys_Menu_Bar.mm | 218 | ||||
| -rw-r--r-- | src/Fl_Sys_Menu_Bar.cxx | 49 | ||||
| -rw-r--r-- | src/Fl_cocoa.mm | 65 | ||||
| -rw-r--r-- | src/drivers/Cocoa/Fl_MacOS_Sys_Menu_Bar_Driver.H | 9 |
4 files changed, 313 insertions, 28 deletions
diff --git a/src/Fl_MacOS_Sys_Menu_Bar.mm b/src/Fl_MacOS_Sys_Menu_Bar.mm index 9dec537bb..dcedcd293 100644 --- a/src/Fl_MacOS_Sys_Menu_Bar.mm +++ b/src/Fl_MacOS_Sys_Menu_Bar.mm @@ -21,9 +21,30 @@ #include <FL/Fl_Sys_Menu_Bar_Driver.H> #include <FL/x.H> #include "drivers/Cocoa/Fl_MacOS_Sys_Menu_Bar_Driver.H" +#include "flstring.h" +#include <stdio.h> +#include <ctype.h> +#include <stdarg.h> + +#import <Cocoa/Cocoa.h> // keep this after include of Fl_Sys_Menu_Bar_Driver.H because of check() conflict +typedef const Fl_Menu_Item *pFl_Menu_Item; -Fl_Sys_Menu_Bar_Driver* Fl_MacOS_Sys_Menu_Bar_Driver::driver() { +static Fl_Menu_Bar *custom_menu; +static NSString *localized_Window = nil; + +static char *remove_ampersand(const char *s); +extern void (*fl_lock_function)(); +extern void (*fl_unlock_function)(); + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 +static void previous_tab_cb(Fl_Widget *, void *data); +static void next_tab_cb(Fl_Widget *, void *data); +static void move_tab_cb(Fl_Widget *, void *data); +static void merge_all_windows_cb(Fl_Widget *, void *data); +#endif + +Fl_MacOS_Sys_Menu_Bar_Driver* Fl_MacOS_Sys_Menu_Bar_Driver::driver() { static Fl_MacOS_Sys_Menu_Bar_Driver *once = new Fl_MacOS_Sys_Menu_Bar_Driver(); if (driver_ != once) { if (driver_) { @@ -36,22 +57,6 @@ Fl_Sys_Menu_Bar_Driver* Fl_MacOS_Sys_Menu_Bar_Driver::driver() { return once; } - -#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. @@ -92,6 +97,9 @@ const char *Fl_Mac_App_Menu::quit = "Quit %@"; - (void) setKeyEquivalentModifierMask:(int)value; - (void) setFltkShortcut:(int)key; + (int) addNewItem:(const Fl_Menu_Item*)mitem menu:(NSMenu*)menu action:(SEL)selector; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 +- (BOOL)validateMenuItem:(NSMenuItem *)item; +#endif @end @implementation FLMenuItem @@ -197,6 +205,37 @@ const char *Fl_Mac_App_Menu::quit = "Quit %@"; [item release]; return retval; } + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 +- (BOOL)validateMenuItem:(NSMenuItem *)item { + // return YES for all but items of the Window menu + if (fl_mac_os_version < 101200 || + Fl_Sys_Menu_Bar::window_menu_style() <= Fl_Sys_Menu_Bar::tabbing_mode_none || + [item hasSubmenu]) return YES; + NSString *title = [[item parentItem] title]; // 10.6 + if (!title || !localized_Window || [title compare:localized_Window] != NSOrderedSame) return YES; + const Fl_Menu_Item *flitem = [(FLMenuItem*)item getFlItem]; + Fl_Callback *item_cb = flitem->callback(); + if (item_cb == previous_tab_cb || item_cb == next_tab_cb || item_cb == move_tab_cb) { + // is the current window tabbed? + Fl_Window *win = Fl::first_window(); + NSWindow *main = win ? (NSWindow*)fl_xid(win) : nil; + return (main && [main tabbedWindows] != nil); + } else if (item_cb == merge_all_windows_cb) { + // is there any untabbed, tabbable window? + int total = 0, untabbed = 0; + while ((++flitem)->label()) { + total++; + NSWindow *nsw = (NSWindow*)fl_xid( (Fl_Window*)flitem->user_data() ); + if (![nsw tabbedWindows] && [nsw tabbingMode] != NSWindowTabbingModeDisallowed) { + untabbed++; + } + } + return (untabbed > 0 && total >= 2); + } + return YES; +} +#endif @end @@ -341,6 +380,12 @@ static void convertToMenuBar(const Fl_Menu_Item *mm) [fl_system_menu removeItem:[fl_system_menu itemAtIndex:i]]; } if (mm) createSubMenu(fl_system_menu, mm, NULL, @selector(doCallback)); +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 + if (localized_Window) { + NSMenuItem *item = [fl_system_menu itemWithTitle:localized_Window]; + if (item) [[item submenu] setAutoenablesItems:YES]; + } +#endif } void Fl_MacOS_Sys_Menu_Bar_Driver::update() @@ -483,6 +528,145 @@ void Fl_Mac_App_Menu::custom_application_menu_items(const Fl_Menu_Item *m) [item release]; } } + + +static void minimize_win_cb(Fl_Widget *, void *data) +{ + [[NSApp mainWindow] miniaturize:nil]; +} + +static void window_menu_cb(Fl_Widget *, void *data) +{ + if (data) ((Fl_Window*)data)->show(); +} + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 + +static void previous_tab_cb(Fl_Widget *, void *data) +{ + [[NSApp mainWindow] selectPreviousTab:nil]; +} + +static void next_tab_cb(Fl_Widget *, void *data) +{ + [[NSApp mainWindow] selectNextTab:nil]; +} + +static void move_tab_cb(Fl_Widget *, void *data) +{ + [[NSApp mainWindow] moveTabToNewWindow:nil]; +} + +static void merge_all_windows_cb(Fl_Widget *, void *) +{ + Fl_Window *first = Fl::first_window(); + if (first) { + [(NSWindow*)fl_xid(first) mergeAllWindows:nil]; + } +} + +#endif + + +static bool window_menu_installed = false; + +void Fl_MacOS_Sys_Menu_Bar_Driver::create_window_menu(void) +{ + if (window_menu_style() == Fl_Sys_Menu_Bar::no_window_menu) return; + if (window_menu_installed) return; + window_menu_installed = true; + int rank = 0; + if (fl_sys_menu_bar && fl_sys_menu_bar->menu()) { + if (fl_sys_menu_bar->find_index("Window") >= 0) { // there's already a "Window" menu -> don't create another + window_menu_style_ = Fl_Sys_Menu_Bar::no_window_menu; + return; + } + // put the Window menu last in menu bar or before Help if it's present + const Fl_Menu_Item *item = fl_sys_menu_bar->menu(); + while (item->label() && strcmp(item->label(), "Help") != 0) { + item = item->next(); + } + rank = fl_sys_menu_bar->find_index(item); + } else if (!fl_sys_menu_bar) { + fl_open_display(); + new Fl_Sys_Menu_Bar(0,0,0,0); + } + rank = fl_sys_menu_bar->Fl_Menu_::insert(rank, "Window", 0, NULL, 0, FL_SUBMENU); + localized_Window = NSLocalizedString(@"Window", nil); + + fl_sys_menu_bar->Fl_Menu_::insert(++rank, "Minimize", FL_COMMAND+'m', minimize_win_cb, 0, FL_MENU_DIVIDER); +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 + if (fl_mac_os_version >= 101200 && window_menu_style() != Fl_Sys_Menu_Bar::tabbing_mode_none) { + fl_sys_menu_bar->Fl_Menu_::insert(++rank, "Show Previous Tab", FL_SHIFT+FL_CTRL+0x9, previous_tab_cb, 0, 0); + fl_sys_menu_bar->Fl_Menu_::insert(++rank, "Show Next Tab", FL_CTRL+0x9, next_tab_cb, 0, 0); + fl_sys_menu_bar->Fl_Menu_::insert(++rank, "Move Tab To New Window", 0, move_tab_cb, 0, 0); + fl_sys_menu_bar->Fl_Menu_::insert(++rank, "Merge All Windows", 0, merge_all_windows_cb, 0, FL_MENU_DIVIDER); + } +#endif + ((Fl_Menu_Item*)fl_sys_menu_bar->menu()+rank)->user_data(&window_menu_style_); + fl_sys_menu_bar->update(); +} + +int Fl_MacOS_Sys_Menu_Bar_Driver::find_first_window() +{ + int count = bar->size(), i; + for (i = 0; i < count; i++) { + if (bar->menu()[i].user_data() == &window_menu_style_) break; + } + return i < count ? i : -1; +} + +void Fl_MacOS_Sys_Menu_Bar_Driver::new_window(Fl_Window *win) +{ + if (!window_menu_style() || !win->label()) return; + int index = find_first_window(); + if (index < 0) return; + while ((bar->menu()+index+1)->label()) index++; + const char *p = win->iconlabel() ? win->iconlabel() : win->label(); + int index2 = bar->Fl_Menu_::insert(index+1, p, 0, window_menu_cb, win, FL_MENU_RADIO); + setonly((Fl_Menu_Item*)bar->menu()+index2); +} + +void Fl_MacOS_Sys_Menu_Bar_Driver::remove_window(Fl_Window *win) +{ + if (!window_menu_style()) return; + int index = find_first_window() + 1; + if (index < 1) return; + while (true) { + Fl_Menu_Item *item = (Fl_Menu_Item*)bar->menu() + index; + if (!item->label()) return; + if (item->user_data() == win) { + bool doit = item->value(); + remove(index); + if (doit) { + item = (Fl_Menu_Item*)bar->menu() + find_first_window() + 1; + if (item->label()) { + ((Fl_Window*)item->user_data())->show(); + setonly(item); + } + } + break; + } + index++; + } +} + +void Fl_MacOS_Sys_Menu_Bar_Driver::rename_window(Fl_Window *win) +{ + if (!window_menu_style()) return; + int index = find_first_window() + 1; + if (index < 1) return; + while (true) { + Fl_Menu_Item *item = (Fl_Menu_Item*)bar->menu() + index; + if (!item->label()) return; + if (item->user_data() == win) { + replace(index, win->iconlabel() ? win->iconlabel() : win->label()); + return; + } + index++; + } +} + #endif /* __APPLE__ */ // diff --git a/src/Fl_Sys_Menu_Bar.cxx b/src/Fl_Sys_Menu_Bar.cxx index 6c4ee9a51..1e1dab81b 100644 --- a/src/Fl_Sys_Menu_Bar.cxx +++ b/src/Fl_Sys_Menu_Bar.cxx @@ -166,13 +166,58 @@ void Fl_Sys_Menu_Bar::replace(int index, const char *name) * \param data a pointer transmitted as 2nd argument to the callback. */ void Fl_Sys_Menu_Bar::about(Fl_Callback *cb, void *data) { - if (fl_sys_menu_bar) fl_sys_menu_bar->driver()->about(cb, data); + driver()->about(cb, data); } void Fl_Sys_Menu_Bar::draw() { driver()->draw(); } + + +/** Get the style of the Window menu in the system menu bar */ +Fl_Sys_Menu_Bar::window_menu_style_enum Fl_Sys_Menu_Bar::window_menu_style() { + return Fl_Sys_Menu_Bar_Driver::window_menu_style(); +} + +/** Set the desired style of the Window menu in the system menu bar. + This function, to be called before the first call to Fl_Window::show(), allows to + control whether the system menu bar should contain a Window menu, + and if yes, whether new windows should be displayed in tabbed form. These are + the effects of various values for \p style : + \li \c no_window_menu : don't add a Window menu to the system menu bar + \li \c tabbing_mode_none : add a simple Window menu to the system menu bar + \li \c tabbing_mode_automatic : the window menu also contains "Merge All Windows" to group + all windows in a single tabbed display mode. This is the \b default Window menu style + for FLTK apps. + \li \c tabbing_mode_preferred : new windows are displayed in tabbed mode when first created + + The Window menu, if present, is entirely created and controlled by the FLTK library. + Mac OS version 10.12 or later must be running for windows to be displayed in tabbed form. + Under non MacOS platforms, this function does nothing. + \version 1.4 + */ +void Fl_Sys_Menu_Bar::window_menu_style(Fl_Sys_Menu_Bar::window_menu_style_enum style) { + Fl_Sys_Menu_Bar_Driver::window_menu_style(style); +} + +/** Adds a Window menu, to the end of the system menu bar. + FLTK apps typically don't need to call this function which is automatically + called by the library the first time a window is shown. The default system menu bar + contains a Window menu with a "Merge All Windows" item. + Other Window menu styles can be obtained calling + Fl_Sys_Menu_Bar::window_menu_style(window_menu_style_enum) before the first Fl_Window::show(). + Alternatively, an app can call create_window_menu() after having populated the system menu bar, + for example with menu(const Fl_Menu_Item *), and before the first Fl_Window::show(). + + This function does nothing on non MacOS platforms. + \version 1.4 + */ +void Fl_Sys_Menu_Bar::create_window_menu() { + fl_open_display(); + driver()->create_window_menu(); +} + #if !defined(FL_DOXYGEN) Fl_Sys_Menu_Bar_Driver *Fl_Sys_Menu_Bar::driver() { if (!Fl_Sys_Menu_Bar_Driver::driver_) { // initialize this static variable if it was not initialized previously @@ -186,6 +231,8 @@ Fl_Sys_Menu_Bar_Driver *Fl_Sys_Menu_Bar_Driver::driver_ = 0; Fl_Sys_Menu_Bar_Driver::Fl_Sys_Menu_Bar_Driver() {bar = NULL;} Fl_Sys_Menu_Bar_Driver::~Fl_Sys_Menu_Bar_Driver() {} + +Fl_Sys_Menu_Bar::window_menu_style_enum Fl_Sys_Menu_Bar_Driver::window_menu_style_ = Fl_Sys_Menu_Bar::tabbing_mode_automatic; #endif // !defined(FL_DOXYGEN) // diff --git a/src/Fl_cocoa.mm b/src/Fl_cocoa.mm index 88b1adc51..1649f0b53 100644 --- a/src/Fl_cocoa.mm +++ b/src/Fl_cocoa.mm @@ -1309,9 +1309,14 @@ static FLWindowDelegate *flwindowdelegate_instance = nil; } - (void)windowDidMove:(NSNotification *)notif { - fl_lock_function(); FLWindow *nsw = (FLWindow*)[notif object]; Fl_Window *window = [nsw getFl_Window]; + if (abs([[nsw contentView] frame].size.height - window->h() * fl_graphics_driver->scale()) > 0.5) { + // the contentView, but not the window frame, is resized. This happens with tabbed windows. + [self windowDidResize:notif]; + return; + } + fl_lock_function(); resize_from_system = window; NSPoint pt2; pt2 = [nsw convertBaseToScreen:NSMakePoint(0, [[nsw contentView] frame].size.height)]; @@ -1402,6 +1407,19 @@ static FLWindowDelegate *flwindowdelegate_instance = nil; Fl_Window *window = [nsw getFl_Window]; Fl::first_window(window); update_e_xy_and_e_xy_root(nsw); + if (fl_sys_menu_bar && Fl_MacOS_Sys_Menu_Bar_Driver::window_menu_style()) { + // select the corresponding Window menu item + int index = Fl_MacOS_Sys_Menu_Bar_Driver::driver()->find_first_window() + 1; + while (index > 0) { + Fl_Menu_Item *item = (Fl_Menu_Item*)fl_sys_menu_bar->menu() + index; + if (!item->label()) break; + if (item->user_data() == window) { + fl_sys_menu_bar->setonly(item); + break; + } + index++; + } + } fl_unlock_function(); } - (void)windowDidDeminiaturize:(NSNotification *)notif @@ -1868,7 +1886,13 @@ void Fl_Cocoa_Screen_Driver::disable_im() { // Gets the border sizes and the titlebar size -static void get_window_frame_sizes(int &bx, int &by, int &bt) { +static void get_window_frame_sizes(int &bx, int &by, int &bt, Fl_Window *win) { + FLWindow *flw = fl_xid(win); + if (flw) { + bt = [flw frame].size.height - [[flw contentView] frame].size.height; + bx = by = 0; + return; + } static int top = 0, left, bottom; if (!top) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; @@ -1952,7 +1976,7 @@ static int fake_X_wm(Fl_Window* w,int &X,int &Y, int &bt,int &bx, int &by) { } else { ret = 1; } - get_window_frame_sizes(bx, by, bt); + get_window_frame_sizes(bx, by, bt, w); } // The coordinates of the whole window, including non-client area xoff = bx; @@ -2278,6 +2302,11 @@ static FLTextInputContext* fltextinputcontext_instance = nil; fl_lock_function(); FLWindow *cw = (FLWindow*)[self window]; Fl_Window *window = [cw getFl_Window]; + if ( !window->parent() && window->border() && abs(rect.size.height - window->h() * fl_graphics_driver->scale()) > 0.5 ) { // this happens with tabbed window + window->resize([cw frame].origin.x/fl_graphics_driver->scale(), + (main_screen_height - ([cw frame].origin.y + rect.size.height))/fl_graphics_driver->scale(), + rect.size.width/fl_graphics_driver->scale(), rect.size.height/fl_graphics_driver->scale()); + } through_drawRect = YES; Fl_Cocoa_Window_Driver *d = Fl_Cocoa_Window_Driver::driver(window); if (fl_mac_os_version >= 100700) { // determine whether window is mapped to a retina display @@ -2919,6 +2948,7 @@ Fl_X* Fl_Cocoa_Window_Driver::makeWindow() fl_open_display(); NSInteger winlevel = NSNormalWindowLevel; NSUInteger winstyle; + Fl_Sys_Menu_Bar::driver()->create_window_menu(); // effective once at most Fl_Window* w = pWindow; if (w->parent()) { w->border(0); @@ -3029,6 +3059,18 @@ Fl_X* Fl_Cocoa_Window_Driver::makeWindow() contentRect:crect styleMask:winstyle]; [cw setFrameOrigin:crect.origin]; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 + if (fl_mac_os_version >= 101200) { + if (!w->parent() && (winstyle & NSTitledWindowMask) && (winstyle & NSResizableWindowMask) + && !w->modal() && !w->non_modal() && Fl_MacOS_Sys_Menu_Bar_Driver::window_menu_style() > Fl_Sys_Menu_Bar::tabbing_mode_none) { + if (Fl_MacOS_Sys_Menu_Bar_Driver::window_menu_style() == Fl_Sys_Menu_Bar::tabbing_mode_preferred) + [cw setTabbingMode:NSWindowTabbingModePreferred]; + else [cw setTabbingMode:NSWindowTabbingModeAutomatic]; + } else { + [cw setTabbingMode:NSWindowTabbingModeDisallowed]; + } + } +#endif if (!w->parent()) { [cw setHasShadow:YES]; [cw setAcceptsMouseMovedEvents:YES]; @@ -3105,7 +3147,10 @@ Fl_X* Fl_Cocoa_Window_Driver::makeWindow() } else { // a top-level window [cw makeKeyAndOrderFront:nil]; } - + if (fl_sys_menu_bar && Fl_MacOS_Sys_Menu_Bar_Driver::window_menu_style() && !w->parent() && w->border() && + !w->modal() && !w->non_modal()) { + Fl_MacOS_Sys_Menu_Bar_Driver::driver()->new_window(w); + } int old_event = Fl::e_number; w->handle(Fl::e_number = FL_SHOW); Fl::e_number = old_event; @@ -3121,7 +3166,7 @@ Fl_X* Fl_Cocoa_Window_Driver::makeWindow() */ void Fl_Cocoa_Window_Driver::size_range() { int bx, by, bt; - get_window_frame_sizes(bx, by, bt); + get_window_frame_sizes(bx, by, bt, pWindow); Fl_Window_Driver::size_range(); NSSize minSize = NSMakeSize(minw(), minh() + bt); NSSize maxSize = NSMakeSize(maxw() ? maxw():32000, maxh() ? maxh() + bt:32000); @@ -3172,6 +3217,8 @@ const char *Fl_Darwin_System_Driver::filename_name( const char *name ) void Fl_Cocoa_Window_Driver::label(const char *name, const char *mininame) { if (shown() || Fl_X::i(pWindow)) { q_set_window_title(fl_xid(pWindow), name, mininame); + if (fl_sys_menu_bar && Fl_Sys_Menu_Bar_Driver::window_menu_style()) + Fl_MacOS_Sys_Menu_Bar_Driver::driver()->rename_window(pWindow); } } @@ -3230,7 +3277,7 @@ void Fl_Cocoa_Window_Driver::resize(int X,int Y,int W,int H) { } pWindow->Fl_Group::resize(X,Y,W,H); // transmit changes in FLTK coords to cocoa - get_window_frame_sizes(bx, by, bt); + get_window_frame_sizes(bx, by, bt, pWindow); bx = X; by = Y; parent = pWindow->window(); while (parent) { @@ -3552,6 +3599,8 @@ int Fl_Darwin_System_Driver::clipboard_contains(const char *type) { } void Fl_Cocoa_Window_Driver::destroy(FLWindow *xid) { + if (fl_sys_menu_bar && Fl_Sys_Menu_Bar_Driver::window_menu_style()) + Fl_MacOS_Sys_Menu_Bar_Driver::driver()->remove_window([xid getFl_Window]); [xid close]; } @@ -4291,7 +4340,7 @@ int Fl_Cocoa_Window_Driver::decorated_w() if (!shown() || parent() || !border() || !visible()) return w(); int bx, by, bt; - get_window_frame_sizes(bx, by, bt); + get_window_frame_sizes(bx, by, bt, pWindow); return w() + 2 * bx; } @@ -4300,7 +4349,7 @@ int Fl_Cocoa_Window_Driver::decorated_h() if (!shown() || parent() || !border() || !visible()) return h(); int bx, by, bt; - get_window_frame_sizes(bx, by, bt); + get_window_frame_sizes(bx, by, bt, pWindow); return h() + bt + by; } 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 b2a6f125b..8c332c6cb 100644 --- a/src/drivers/Cocoa/Fl_MacOS_Sys_Menu_Bar_Driver.H +++ b/src/drivers/Cocoa/Fl_MacOS_Sys_Menu_Bar_Driver.H @@ -1,7 +1,7 @@ // // "$Id$" // -// system menu bar widget for the Fast Light Tool Kit (FLTK). +// Definition of class Fl_MacOS_Sys_Menu_Bar_Driver for the Fast Light Tool Kit (FLTK). // // Copyright 2017 by Bill Spitzak and others. // @@ -39,7 +39,12 @@ public: virtual void remove(int index); virtual void replace(int index, const char *name); virtual void mode(int i, int fl); - static Fl_Sys_Menu_Bar_Driver* driver(); + virtual void create_window_menu(); + int find_first_window(); + void new_window(Fl_Window *win); + void remove_window(Fl_Window *win); + void rename_window(Fl_Window *win); + static Fl_MacOS_Sys_Menu_Bar_Driver* driver(); }; |
