diff options
author | Aaron M. Ucko <ucko@debian.org> | 2015-07-06 18:51:56 -0400 |
---|---|---|
committer | Aaron M. Ucko <ucko@debian.org> | 2015-07-06 18:51:56 -0400 |
commit | a3d0ced57399c9fd8075377b7310d545f968e524 (patch) | |
tree | 63fa5d8b883c8c1235222810a0ebc99c74ac6610 /src/Fl_cocoa.mm | |
parent | 786f49e9d19180e6bf1c7d0fe0d9da89f953a330 (diff) |
Imported Upstream version 1.3.3
Diffstat (limited to 'src/Fl_cocoa.mm')
-rw-r--r-- | src/Fl_cocoa.mm | 2173 |
1 files changed, 1391 insertions, 782 deletions
diff --git a/src/Fl_cocoa.mm b/src/Fl_cocoa.mm index a361f29..6f5b8b1 100644 --- a/src/Fl_cocoa.mm +++ b/src/Fl_cocoa.mm @@ -1,9 +1,9 @@ // -// "$Id: Fl_cocoa.mm 9734 2012-11-30 18:20:36Z manolo $" +// "$Id: Fl_cocoa.mm 10427 2014-11-02 21:06:07Z manolo $" // // MacOS-Cocoa specific code for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2012 by Bill Spitzak and others. +// Copyright 1998-2014 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 @@ -41,7 +41,6 @@ extern "C" { #include <FL/x.H> #include <FL/Fl_Window.H> #include <FL/Fl_Tooltip.H> -#include <FL/Fl_Sys_Menu_Bar.H> #include <FL/Fl_Printer.H> #include <FL/Fl_Input_.H> #include <FL/Fl_Text_Display.H> @@ -51,6 +50,8 @@ extern "C" { #include <unistd.h> #include <stdarg.h> #include <math.h> +#include <limits.h> +#include <dlfcn.h> #import <Cocoa/Cocoa.h> @@ -80,6 +81,7 @@ typedef unsigned int NSUInteger; // external functions extern void fl_fix_focus(); extern unsigned short *fl_compute_macKeyLookUp(); +extern int fl_send_system_handlers(void *e); // forward definition of functions in this file // converting cr lf converter function @@ -88,27 +90,34 @@ static void createAppleMenu(void); static Fl_Region MacRegionMinusRect(Fl_Region r, int x,int y,int w,int h); static void cocoaMouseHandler(NSEvent *theEvent); static int calc_mac_os_version(); +static void clipboard_check(void); +static NSString *calc_utf8_format(void); +static void im_update(void); +static unsigned make_current_counts = 0; // if > 0, then Fl_Window::make_current() can be called only once +static Fl_X *fl_x_to_redraw = NULL; // set by Fl_X::flush() to the Fl_X object of the window to be redrawn -static Fl_Quartz_Graphics_Driver fl_quartz_driver; -static Fl_Display_Device fl_quartz_display(&fl_quartz_driver); -Fl_Display_Device *Fl_Display_Device::_display = &fl_quartz_display; // the platform display +Fl_Display_Device *Fl_Display_Device::_display = new Fl_Display_Device(new Fl_Quartz_Graphics_Driver); // the platform display // public variables CGContextRef fl_gc = 0; -void *fl_system_menu; // this is really a NSMenu* -Fl_Sys_Menu_Bar *fl_sys_menu_bar = 0; -void *fl_default_cursor; // this is really a NSCursor* void *fl_capture = 0; // (NSWindow*) we need this to compensate for a missing(?) mouse capture bool fl_show_iconic; // true if called from iconize() - shows the next created window in collapsed state //int fl_disable_transient_for; // secret method of removing TRANSIENT_FOR Window fl_window; Fl_Window *Fl_Window::current_; int fl_mac_os_version = calc_mac_os_version(); // the version number of the running Mac OS X (e.g., 100604 for 10.6.4) +static SEL inputContextSEL = (fl_mac_os_version >= 100600 ? @selector(inputContext) : @selector(FLinputContext)); +Fl_Fontdesc* fl_fonts = Fl_X::calc_fl_fonts(); +static NSString *utf8_format = calc_utf8_format(); // forward declarations of variables in this file static int got_events = 0; static Fl_Window* resize_from_system; static int main_screen_height; // height of menubar-containing screen used to convert between Cocoa and FLTK global screen coordinates +// through_drawRect = YES means the drawRect: message was sent to the view, +// thus the graphics context was prepared by the system +static BOOL through_drawRect = NO; +static int im_enabled = -1; #if CONSOLIDATE_MOTION static Fl_Window* send_motion; @@ -117,6 +126,29 @@ extern Fl_Window* fl_xmousewin; enum { FLTKTimerEvent = 1, FLTKDataReadyEvent }; +// Carbon functions and definitions + +typedef void *TSMDocumentID; + +extern "C" enum { + kTSMDocumentEnabledInputSourcesPropertyTag = 'enis' // from Carbon/TextServices.h +}; + +// Undocumented voodoo. Taken from Mozilla. +static const int smEnableRomanKybdsOnly = -23; + +typedef TSMDocumentID (*TSMGetActiveDocument_type)(void); +static TSMGetActiveDocument_type TSMGetActiveDocument; +typedef OSStatus (*TSMSetDocumentProperty_type)(TSMDocumentID, OSType, UInt32, void*); +static TSMSetDocumentProperty_type TSMSetDocumentProperty; +typedef OSStatus (*TSMRemoveDocumentProperty_type)(TSMDocumentID, OSType); +static TSMRemoveDocumentProperty_type TSMRemoveDocumentProperty; +typedef CFArrayRef (*TISCreateASCIICapableInputSourceList_type)(void); +static TISCreateASCIICapableInputSourceList_type TISCreateASCIICapableInputSourceList; + +typedef void (*KeyScript_type)(short); +static KeyScript_type KeyScript; + /* fltk-utf8 placekeepers */ void fl_reset_spot() @@ -462,9 +494,7 @@ static void processFLTKEvent(void) { * break the current event loop */ static void breakMacEventLoop() -{ - fl_lock_function(); - +{ NSPoint pt={0,0}; NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:pt modifierFlags:0 @@ -472,7 +502,6 @@ static void breakMacEventLoop() windowNumber:0 context:NULL subtype:FLTKTimerEvent data1:0 data2:0]; [NSApp postEvent:event atStart:NO]; - fl_unlock_function(); } // @@ -520,6 +549,7 @@ static void delete_timer(MacTimeout& t) static void do_timer(CFRunLoopTimerRef timer, void* data) { + fl_lock_function(); current_timer = (MacTimeout*)data; current_timer->pending = 0; (current_timer->callback)(current_timer->data); @@ -528,6 +558,7 @@ static void do_timer(CFRunLoopTimerRef timer, void* data) current_timer = NULL; breakMacEventLoop(); + fl_unlock_function(); } void Fl::add_timeout(double time, Fl_Timeout_Handler cb, void* data) @@ -628,11 +659,35 @@ void Fl::remove_timeout(Fl_Timeout_Handler cb, void* data) contentRect:(NSRect)rect styleMask:(NSUInteger)windowStyle; - (Fl_Window *)getFl_Window; +/* These two functions allow to check if a window contains OpenGL-subwindows. + This is useful only for Mac OS < 10.7 to repair a problem apparent with the "cube" test program: + if the cube window is moved around rapidly (with OS < 10.7), the GL pixels leak away from where they should be. + The repair is performed by [FLWindowDelegate windowDidMove:], only if OS < 10.7. + */ - (BOOL)containsGLsubwindow; -- (void)setContainsGLsubwindow:(BOOL)contains; +- (void)containsGLsubwindow:(BOOL)contains; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 +- (NSPoint)convertBaseToScreen:(NSPoint)aPoint; +#endif @end @implementation FLWindow +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 +- (NSPoint)convertBaseToScreen:(NSPoint)aPoint +{ + if (fl_mac_os_version >= 100700) { + NSRect r = [self convertRectToScreen:NSMakeRect(aPoint.x, aPoint.y, 0, 0)]; + return r.origin; + } + else { + // replaces return [super convertBaseToScreen:aPoint] that may trigger a compiler warning + typedef NSPoint (*convertIMP)(id, SEL, NSPoint); + convertIMP addr = (convertIMP)[NSWindow instanceMethodForSelector:@selector(convertBaseToScreen:)]; + return addr(self, @selector(convertBaseToScreen:), aPoint); + } +} +#endif + - (FLWindow*)initWithFl_W:(Fl_Window *)flw contentRect:(NSRect)rect styleMask:(NSUInteger)windowStyle @@ -641,6 +696,12 @@ void Fl::remove_timeout(Fl_Timeout_Handler cb, void* data) if (self) { w = flw; containsGLsubwindow = NO; + if (fl_mac_os_version >= 100700) { + // replaces [self setRestorable:NO] that may trigger a compiler warning + typedef void (*setIMP)(id, SEL, BOOL); + setIMP addr = (setIMP)[self methodForSelector:@selector(setRestorable:)]; + addr(self, @selector(setRestorable:), NO); + } } return self; } @@ -652,7 +713,7 @@ void Fl::remove_timeout(Fl_Timeout_Handler cb, void* data) { return containsGLsubwindow; } -- (void)setContainsGLsubwindow:(BOOL)contains +- (void)containsGLsubwindow:(BOOL)contains { containsGLsubwindow = contains; } @@ -666,9 +727,6 @@ void Fl::remove_timeout(Fl_Timeout_Handler cb, void* data) return !(w->tooltip_window() || w->menu_window()); } -// TODO see if we really need a canBecomeMainWindow ... -#if 0 - - (BOOL)canBecomeMainWindow { if (Fl::modal_ && (Fl::modal_ != w)) @@ -677,7 +735,6 @@ void Fl::remove_timeout(Fl_Timeout_Handler cb, void* data) return !(w->tooltip_window() || w->menu_window()); } -#endif @end @@ -726,6 +783,7 @@ static double do_queued_events( double time = 0.0 ) return time; } + /* * This public function handles all events. It wait a maximum of * 'time' seconds for an event. This version returns 1 if events @@ -739,17 +797,132 @@ int fl_wait( double time ) return (got_events); } -double fl_mac_flush_and_wait(double time_to_wait, char in_idle) { +double fl_mac_flush_and_wait(double time_to_wait) { + static int in_idle = 0; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + if (Fl::idle) { + if (!in_idle) { + in_idle = 1; + Fl::idle(); + in_idle = 0; + } + // the idle function may turn off idle, we can then wait: + if (Fl::idle) time_to_wait = 0.0; + } Fl::flush(); if (Fl::idle && !in_idle) // 'idle' may have been set within flush() time_to_wait = 0.0; double retval = fl_wait(time_to_wait); + if (fl_gc) { + CGContextFlush(fl_gc); + fl_gc = 0; + } [pool release]; return retval; } +static NSInteger max_normal_window_level(void) +{ + Fl_X *x; + NSInteger max_level; + + max_level = 0; + + for (x = Fl_X::first;x;x = x->next) { + NSInteger level; + FLWindow *cw = x->xid; + Fl_Window *win = x->w; + if (!win || !cw || ![cw isVisible]) + continue; + if (win->modal() || win->non_modal()) + continue; + level = [cw level]; + if (level >= max_level) + max_level = level; + } + + return max_level; +} + +// appropriate window level for modal windows +static NSInteger modal_window_level(void) +{ + NSInteger level; + + level = max_normal_window_level(); + if (level < NSModalPanelWindowLevel) + return NSModalPanelWindowLevel; + + // Need some room for non-modal windows + level += 2; + + // We cannot exceed this + if (level > CGShieldingWindowLevel()) + return CGShieldingWindowLevel(); + + return level; +} + +// appropriate window level for non-modal windows +static NSInteger non_modal_window_level(void) +{ + NSInteger level; + + level = max_normal_window_level(); + if (level < NSFloatingWindowLevel) + return NSFloatingWindowLevel; + + level += 1; + + if (level > CGShieldingWindowLevel()) + return CGShieldingWindowLevel(); + + return level; +} + +// makes sure modal and non-modal windows stay on top +static void fixup_window_levels(void) +{ + NSInteger modal_level, non_modal_level; + + Fl_X *x; + FLWindow *prev_modal, *prev_non_modal; + + modal_level = modal_window_level(); + non_modal_level = non_modal_window_level(); + + prev_modal = NULL; + prev_non_modal = NULL; + + for (x = Fl_X::first;x;x = x->next) { + FLWindow *cw = x->xid; + Fl_Window *win = x->w; + if (!win || !cw || ![cw isVisible]) + continue; + if (win->modal()) { + if ([cw level] != modal_level) { + [cw setLevel:modal_level]; + // changing level puts then in front, so make sure the + // stacking isn't messed up + if (prev_modal != NULL) + [cw orderWindow:NSWindowBelow + relativeTo:[prev_modal windowNumber]]; + } + prev_modal = cw; + } else if (win->non_modal()) { + if ([cw level] != non_modal_level) { + [cw setLevel:non_modal_level]; + if (prev_non_modal != NULL) + [cw orderWindow:NSWindowBelow + relativeTo:[prev_non_modal windowNumber]]; + } + prev_non_modal = cw; + } + } +} + + // updates Fl::e_x, Fl::e_y, Fl::e_x_root, and Fl::e_y_root static void update_e_xy_and_e_xy_root(NSWindow *nsw) { @@ -893,97 +1066,35 @@ static void cocoaMouseHandler(NSEvent *theEvent) return; } -@interface FLTextView : NSTextView -// this subclass is needed under OS X <= 10.5 but not under >= 10.6 where the base class is enough +@interface FLTextView : NSTextView // this subclass is only needed under OS X < 10.6 { + BOOL isActive; } +- (void)insertText:(id)aString; +- (void)doCommandBySelector:(SEL)aSelector; +- (void)setActive:(BOOL)a; @end @implementation FLTextView - (void)insertText:(id)aString { - [[[NSApp keyWindow] contentView] insertText:aString]; + if (isActive) [[[NSApp keyWindow] contentView] insertText:aString]; } - (void)doCommandBySelector:(SEL)aSelector { [[[NSApp keyWindow] contentView] doCommandBySelector:aSelector]; } -@end - -/* -Handle cocoa keyboard events -Events during a character composition sequence: - - keydown with deadkey -> [[theEvent characters] length] is 0 - - keyup -> [theEvent characters] contains the deadkey - - keydown with next key -> [theEvent characters] contains the composed character - - keyup -> [theEvent characters] contains the standard character - */ -static void cocoaKeyboardHandler(NSEvent *theEvent) +- (void)setActive:(BOOL)a { - NSUInteger mods; - - // get the modifiers - mods = [theEvent modifierFlags]; - // get the key code - UInt32 keyCode = 0, maskedKeyCode = 0; - unsigned short sym = 0; - keyCode = [theEvent keyCode]; - NSString *s = [theEvent characters]; - if ( (mods & NSShiftKeyMask) && (mods & NSCommandKeyMask) ) { - s = [s uppercaseString]; // US keyboards return lowercase letter in s if cmd-shift-key is hit - } - // extended keyboards can also send sequences on key-up to generate Kanji etc. codes. - // Some observed prefixes are 0x81 to 0x83, followed by an 8 bit keycode. - // In this mode, there seem to be no key-down codes - // printf("%08x %08x %08x\n", keyCode, mods, key); - maskedKeyCode = keyCode & 0x7f; - - if ([theEvent type] == NSKeyUp) { - Fl::e_state &= 0xbfffffff; // clear the deadkey flag - } - - mods_to_e_state( mods ); // process modifier keys - sym = macKeyLookUp[maskedKeyCode]; - if (sym < 0xff00) { // a "simple" key - // find the result of this key without modifier - NSString *sim = [theEvent charactersIgnoringModifiers]; - UniChar one; - CFStringGetCharacters((CFStringRef)sim, CFRangeMake(0, 1), &one); - // charactersIgnoringModifiers doesn't ignore shift, remove it when it's on - if(one >= 'A' && one <= 'Z') one += 32; - if (one > 0 && one <= 0x7f && (sym<'0' || sym>'9') ) sym = one; - } - Fl::e_keysym = Fl::e_original_keysym = sym; - - //NSLog(@"cocoaKeyboardHandler: keycode=%08x keysym=%08x mods=%08x symbol=%@ (%@)", - // keyCode, sym, mods, [theEvent characters], [theEvent charactersIgnoringModifiers]); - - // If there is text associated with this key, it will be filled in later. - Fl::e_length = 0; - Fl::e_text = (char*)""; -} - - -/* - * Open callback function to call... - */ -static void (*open_cb)(const char *) = 0; - -/* - * Install an open documents event handler... - */ -void fl_open_callback(void (*cb)(const char *)) { - fl_open_display(); - open_cb = cb; + isActive = a; } +@end -@interface FLDelegate : NSObject +@interface FLWindowDelegate : NSObject #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 -<NSWindowDelegate, NSApplicationDelegate> +<NSWindowDelegate> #endif -{ - BOOL seen_open_file; -} ++ (FLWindowDelegate*)createOnce; - (void)windowDidMove:(NSNotification *)notif; - (void)windowDidResize:(NSNotification *)notif; - (void)windowDidResignKey:(NSNotification *)notif; @@ -992,18 +1103,18 @@ void fl_open_callback(void (*cb)(const char *)) { - (void)windowDidDeminiaturize:(NSNotification *)notif; - (void)windowDidMiniaturize:(NSNotification *)notif; - (BOOL)windowShouldClose:(id)fl; -- (void)anyWindowWillClose:(NSNotification *)notif; -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender; -- (void)applicationDidBecomeActive:(NSNotification *)notify; -- (void)applicationDidChangeScreenParameters:(NSNotification *)aNotification; -- (void)applicationWillResignActive:(NSNotification *)notify; -- (void)applicationWillHide:(NSNotification *)notify; -- (void)applicationWillUnhide:(NSNotification *)notify; - (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client; -- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename; -- (void)applicationDidFinishLaunching:(NSNotification *)aNotification; +- (void)anyWindowWillClose:(NSNotification *)notif; @end -@implementation FLDelegate +@implementation FLWindowDelegate ++ (FLWindowDelegate*)createOnce +{ + static FLWindowDelegate* delegate = nil; + if (!delegate) { + delegate = [[FLWindowDelegate alloc] init]; + } + return delegate; +} - (void)windowDidMove:(NSNotification *)notif { fl_lock_function(); @@ -1016,8 +1127,8 @@ void fl_open_callback(void (*cb)(const char *)) { update_e_xy_and_e_xy_root(nsw); resize_from_system = window; window->position((int)pt2.x, (int)(main_screen_height - pt2.y)); - if ([nsw containsGLsubwindow] ) { - [nsw display];// redraw window after moving if it contains OpenGL subwindows + if ([nsw containsGLsubwindow] && fl_mac_os_version < 100700) { + [nsw display];// with OS < 10.7, redraw window after moving if it contains OpenGL subwindows } fl_unlock_function(); } @@ -1045,9 +1156,11 @@ void fl_open_callback(void (*cb)(const char *)) { FLWindow *nsw = (FLWindow*)[notif object]; Fl_Window *window = [nsw getFl_Window]; /* Fullscreen windows obscure all other windows so we need to return - to a "normal" level when the user switches to another window */ - if (window->fullscreen_active()) + to a "normal" level when the user switches to another window */ + if (window->fullscreen_active()) { [nsw setLevel:NSNormalWindowLevel]; + fixup_window_levels(); + } Fl::handle( FL_UNFOCUS, window); fl_unlock_function(); } @@ -1057,8 +1170,10 @@ void fl_open_callback(void (*cb)(const char *)) { FLWindow *nsw = (FLWindow*)[notif object]; Fl_Window *w = [nsw getFl_Window]; /* Restore previous fullscreen level */ - if (w->fullscreen_active()) + if (w->fullscreen_active()) { [nsw setLevel:NSStatusWindowLevel]; + fixup_window_levels(); + } if ( w->border() || (!w->modal() && !w->tooltip_window()) ) Fl::handle( FL_FOCUS, w); fl_unlock_function(); } @@ -1096,6 +1211,18 @@ void fl_open_callback(void (*cb)(const char *)) { // the system doesn't need to send [fl close] because FLTK does it when needed return NO; } +- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client +{ + if (fl_mac_os_version < 100600) { + static FLTextView *view = nil; + if (!view) { + NSRect rect={{0,0},{20,20}}; + view = [[FLTextView alloc] initWithFrame:rect]; + } + return view; + } + return nil; +} - (void)anyWindowWillClose:(NSNotification *)notif { fl_lock_function(); @@ -1105,13 +1232,34 @@ void fl_open_callback(void (*cb)(const char *)) { Fl_Window *w = Fl::first_window(); while (w && (w->parent() || !w->border() || !w->visible())) { w = Fl::next_window(w); - } + } if (w) { [Fl_X::i(w)->xid makeKeyWindow]; } } fl_unlock_function(); } +@end + +@interface FLAppDelegate : NSObject +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 +<NSApplicationDelegate> +#endif +{ + void (*open_cb)(const char*); + TSMDocumentID currentDoc; +} +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender; +- (void)applicationDidBecomeActive:(NSNotification *)notify; +- (void)applicationDidChangeScreenParameters:(NSNotification *)aNotification; +- (void)applicationDidUpdate:(NSNotification *)aNotification; +- (void)applicationWillResignActive:(NSNotification *)notify; +- (void)applicationWillHide:(NSNotification *)notify; +- (void)applicationWillUnhide:(NSNotification *)notify; +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename; +- (void)open_cb:(void (*)(const char*))cb; +@end +@implementation FLAppDelegate - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender { fl_lock_function(); @@ -1128,44 +1276,25 @@ void fl_open_callback(void (*cb)(const char *)) { fl_unlock_function(); return reply; } -/** - * Cocoa organizes the Z depth of windows on a global priority. FLTK however - * expects the window manager to organize Z level by application. The trickery - * below will change Z order during activation and deactivation. - */ - (void)applicationDidBecomeActive:(NSNotification *)notify { fl_lock_function(); - Fl_X *x; - FLWindow *top = 0, *topModal = 0, *topNonModal = 0; - for (x = Fl_X::first;x;x = x->next) { - FLWindow *cw = x->xid; - Fl_Window *win = x->w; - if (win && cw && [cw isVisible]) { - if (win->modal()) { - [cw setLevel:NSModalPanelWindowLevel]; - if (topModal) - [cw orderWindow:NSWindowBelow relativeTo:[topModal windowNumber]]; - else - topModal = cw; - } else if (win->non_modal()) { - [cw setLevel:NSFloatingWindowLevel]; - if (topNonModal) - [cw orderWindow:NSWindowBelow relativeTo:[topNonModal windowNumber]]; - else - topNonModal = cw; - } else { - if (top) - ; - else - top = cw; - } - } - } + + // update clipboard status + clipboard_check(); + + /** + * Cocoa organizes the Z depth of windows on a global priority. FLTK however + * expects the window manager to organize Z level by application. The trickery + * below will change Z order during activation and deactivation. + */ + fixup_window_levels(); + fl_unlock_function(); } - (void)applicationDidChangeScreenParameters:(NSNotification *)unused { // react to changes in screen numbers and positions + fl_lock_function(); main_screen_height = [[[NSScreen screens] objectAtIndex:0] frame].size.height; Fl::call_screen_init(); // FLTK windows have already been notified they were moved, @@ -1179,6 +1308,24 @@ void fl_open_callback(void (*cb)(const char *)) { } } Fl::handle(FL_SCREEN_CONFIGURATION_CHANGED, NULL); + fl_unlock_function(); +} +- (void)applicationDidUpdate:(NSNotification *)aNotification +{ + if ((fl_mac_os_version >= 100500) && (im_enabled != -1) && + (TSMGetActiveDocument != NULL)) { + TSMDocumentID newDoc; + // It is extremely unclear when Cocoa decides to create/update + // the input context, but debugging reveals that it is done + // by NSApplication:updateWindows. So check if the input context + // has shifted after each such run so that we can update our + // input methods status. + newDoc = TSMGetActiveDocument(); + if (newDoc != currentDoc) { + im_update(); + currentDoc = newDoc; + } + } } - (void)applicationWillResignActive:(NSNotification *)notify { @@ -1243,21 +1390,11 @@ void fl_open_callback(void (*cb)(const char *)) { } fl_unlock_function(); } -- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client -{ - if (fl_mac_os_version < 100600) { - static FLTextView *view = nil; - if (!view) { - NSRect rect={{0,0},{20,20}}; - view = [[FLTextView alloc] initWithFrame:rect]; - } - return view; - } - return nil; -} - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { - seen_open_file = YES; + // without the next statement, the opening of the 1st window is delayed by several seconds + // under Mac OS ≥ 10.8 when a file is dragged on the application icon + [[theApplication mainWindow] orderFront:self]; if (open_cb) { fl_lock_function(); (*open_cb)([filename UTF8String]); @@ -1266,17 +1403,26 @@ void fl_open_callback(void (*cb)(const char *)) { } return NO; } -- (void)applicationDidFinishLaunching:(NSNotification *)aNotification +- (void)open_cb:(void (*)(const char*))cb { - // without this, the opening of the 1st window is delayed by several seconds - // under Mac OS 10.8 when a file is dragged on the application icon - if (fl_mac_os_version >= 100800 && seen_open_file) [[NSApp mainWindow] orderFront:self]; + open_cb = cb; } @end +/* + * Install an open documents event handler... + */ +void fl_open_callback(void (*cb)(const char *)) { + fl_open_display(); + [(FLAppDelegate*)[NSApp delegate] open_cb:cb]; +} + @implementation FLApplication + (void)sendEvent:(NSEvent *)theEvent { + if (fl_send_system_handlers(theEvent)) + return; + NSEventType type = [theEvent type]; if (type == NSLeftMouseDown) { fl_lock_function(); @@ -1308,21 +1454,29 @@ void fl_open_callback(void (*cb)(const char *)) { } @end -extern "C" { - OSErr CPSEnableForegroundOperation(ProcessSerialNumber *psn, UInt32 _arg2, - UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); +/* Prototype of undocumented function needed to support Mac OS 10.2 or earlier + extern "C" { + OSErr CPSEnableForegroundOperation(ProcessSerialNumber*, UInt32, UInt32, UInt32, UInt32); } +*/ void fl_open_display() { static char beenHereDoneThat = 0; if ( !beenHereDoneThat ) { beenHereDoneThat = 1; + + TSMGetActiveDocument = (TSMGetActiveDocument_type)Fl_X::get_carbon_function("TSMGetActiveDocument"); + TSMSetDocumentProperty = (TSMSetDocumentProperty_type)Fl_X::get_carbon_function("TSMSetDocumentProperty"); + TSMRemoveDocumentProperty = (TSMRemoveDocumentProperty_type)Fl_X::get_carbon_function("TSMRemoveDocumentProperty"); + TISCreateASCIICapableInputSourceList = (TISCreateASCIICapableInputSourceList_type)Fl_X::get_carbon_function("TISCreateASCIICapableInputSourceList"); + + KeyScript = (KeyScript_type)Fl_X::get_carbon_function("KeyScript"); BOOL need_new_nsapp = (NSApp == nil); if (need_new_nsapp) [NSApplication sharedApplication]; NSAutoreleasePool *localPool; localPool = [[NSAutoreleasePool alloc] init]; // never released - [NSApp setDelegate:[[FLDelegate alloc] init]]; + [(NSApplication*)NSApp setDelegate:[[FLAppDelegate alloc] init]]; if (need_new_nsapp) [NSApp finishLaunching]; // empty the event queue but keep system events for drag&drop of files at launch @@ -1333,44 +1487,48 @@ void fl_open_display() { dequeue:YES]; while (ign_event); - fl_default_cursor = [NSCursor arrowCursor]; - // bring the application into foreground without a 'CARB' resource - Boolean same_psn; - ProcessSerialNumber cur_psn, front_psn; - if ( !GetCurrentProcess( &cur_psn ) && !GetFrontProcess( &front_psn ) && - !SameProcess( &front_psn, &cur_psn, &same_psn ) && !same_psn ) { + bool i_am_in_front; + ProcessSerialNumber cur_psn = { 0, kCurrentProcess }; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + if (fl_mac_os_version >= 100600) { + i_am_in_front = [[NSRunningApplication currentApplication] isActive]; + } + else +#endif + { + Boolean same_psn; + ProcessSerialNumber front_psn; + //avoid compilation warnings triggered by GetFrontProcess() and SameProcess() + void* h = dlopen(NULL, RTLD_LAZY); + typedef OSErr (*GetFrontProcess_type)(ProcessSerialNumber*); + GetFrontProcess_type GetFrontProcess_ = (GetFrontProcess_type)dlsym(h, "GetFrontProcess"); + typedef OSErr (*SameProcess_type)(ProcessSerialNumber*, ProcessSerialNumber*, Boolean*); + SameProcess_type SameProcess_ = (SameProcess_type)dlsym(h, "SameProcess"); + i_am_in_front = (!GetFrontProcess_( &front_psn ) && + !SameProcess_( &front_psn, &cur_psn, &same_psn ) && same_psn ); + } + if (!i_am_in_front) { // only transform the application type for unbundled apps NSBundle *bundle = [NSBundle mainBundle]; if (bundle) { - NSString *exe = [[bundle executablePath] stringByStandardizingPath]; - NSString *bpath = [bundle bundlePath]; - NSString *exe_dir = [exe stringByDeletingLastPathComponent]; - if ([bpath isEqualToString:exe] || [bpath isEqualToString:exe_dir]) bundle = nil; - } - - if ( !bundle ) - { - // Earlier versions of this code tried to use weak linking, however it - // appears that this does not work on 10.2. Since 10.3 and higher provide - // both TransformProcessType and CPSEnableForegroundOperation, the following - // conditional code compiled on 10.2 will still work on newer releases... - OSErr err; -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_3 - if (TransformProcessType != NULL) { - err = TransformProcessType(&cur_psn, kProcessTransformToForegroundApplication); - } else -#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_3 - err = CPSEnableForegroundOperation(&cur_psn, 0x03, 0x3C, 0x2C, 0x1103); - if (err == noErr) { - SetFrontProcess( &cur_psn ); - } + NSString *exe = [[bundle executablePath] stringByStandardizingPath]; + NSString *bpath = [bundle bundlePath]; + NSString *exe_dir = [exe stringByDeletingLastPathComponent]; + if ([bpath isEqualToString:exe] || [bpath isEqualToString:exe_dir]) bundle = nil; + } + + if ( !bundle ) { + TransformProcessType(&cur_psn, kProcessTransformToForegroundApplication); // needs Mac OS 10.3 + /* support of Mac OS 10.2 or earlier used this undocumented call instead + err = CPSEnableForegroundOperation(&cur_psn, 0x03, 0x3C, 0x2C, 0x1103); + */ + [NSApp activateIgnoringOtherApps:YES]; } } if (![NSApp servicesMenu]) createAppleMenu(); - fl_system_menu = [NSApp mainMenu]; main_screen_height = [[[NSScreen screens] objectAtIndex:0] frame].size.height; - [[NSNotificationCenter defaultCenter] addObserver:[NSApp delegate] + [[NSNotificationCenter defaultCenter] addObserver:[FLWindowDelegate createOnce] selector:@selector(anyWindowWillClose:) name:NSWindowWillCloseNotification object:nil]; @@ -1387,6 +1545,66 @@ void fl_open_display() { void fl_close_display() { } +// Force a "Roman" or "ASCII" keyboard, which both the Mozilla and +// Safari people seem to think implies turning off advanced IME stuff +// (see nsTSMManager::SyncKeyScript in Mozilla and enableSecureTextInput +// in Safari/Webcore). Should be good enough for us then... + +static void im_update(void) { + if (fl_mac_os_version >= 100500) { + TSMDocumentID doc; + + if ((TSMGetActiveDocument == NULL) || + (TSMSetDocumentProperty == NULL) || + (TSMRemoveDocumentProperty == NULL) || + (TISCreateASCIICapableInputSourceList == NULL)) + return; + + doc = TSMGetActiveDocument(); + + if (im_enabled) + TSMRemoveDocumentProperty(doc, kTSMDocumentEnabledInputSourcesPropertyTag); + else { + CFArrayRef inputSources; + + inputSources = TISCreateASCIICapableInputSourceList(); + TSMSetDocumentProperty(doc, kTSMDocumentEnabledInputSourcesPropertyTag, + sizeof(CFArrayRef), &inputSources); + CFRelease(inputSources); + } + } else { + if (KeyScript == NULL) + return; + + if (im_enabled) + KeyScript(smKeyEnableKybds); + else + KeyScript(smEnableRomanKybdsOnly); + } +} + +void Fl::enable_im() { + fl_open_display(); + + im_enabled = 1; + + if (fl_mac_os_version >= 100500) + [NSApp updateWindows]; + else + im_update(); +} + +void Fl::disable_im() { + fl_open_display(); + + im_enabled = 0; + + if (fl_mac_os_version >= 100500) + [NSApp updateWindows]; + else + im_update(); +} + // Gets the border sizes and the titlebar size static void get_window_frame_sizes(int &bx, int &by, int &bt) { @@ -1463,15 +1681,6 @@ void Fl::get_mouse(int &x, int &y) /* - * Initialize the given port for redraw and call the window's flush() to actually draw the content - */ -void Fl_X::flush() -{ - w->flush(); - if (fl_gc) CGContextFlush(fl_gc); -} - -/* * Gets called when a window is created, resized, or deminiaturized */ static void handleUpdateEvent( Fl_Window *window ) @@ -1491,7 +1700,10 @@ static void handleUpdateEvent( Fl_Window *window ) cx->region = 0; } cx->w->clear_damage(FL_DAMAGE_ALL); + CGContextRef gc = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + CGContextSaveGState(gc); // save original context cx->flush(); + CGContextRestoreGState(gc); // restore original context cx->w->clear_damage(); } window->clear_damage(FL_DAMAGE_ALL); @@ -1629,16 +1841,143 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi } } +/** How FLTK handles Mac OS text input + + Let myview be the instance of the FLView class that has the keyboard focus. FLView is an FLTK-defined NSView subclass + that implements the NSTextInputClient protocol to properly handle text input. It also implements the old NSTextInput + protocol to run with OS <= 10.4. The few NSTextInput protocol methods that differ in signature from the NSTextInputClient + protocol transmit the received message to the corresponding NSTextInputClient method. + + Keyboard input sends keyDown: and performKeyEquivalent: messages to myview. The latter occurs for keys such as + ForwardDelete, arrows and F1, and when the Ctrl or Cmd modifiers are used. Other key presses send keyDown: messages. + The keyDown: method calls [[myview inputContext] handleEvent:theEvent] that triggers system + processing of keyboard events. The performKeyEquivalent: method directly calls Fl::handle(FL_KEYBOARD, focus-window) + when the Ctrl or Cmd modifiers are used. If not, it also calls [[myview inputContext] handleEvent:theEvent]. + The performKeyEquivalent: method returns YES when the keystroke has been handled and NO otherwise, which allows + shortcuts of the system menu to be processed. Three sorts of messages are then sent back by the system to myview: + doCommandBySelector:, setMarkedText: and insertText:. All 3 messages eventually produce Fl::handle(FL_KEYBOARD, win) calls. + The doCommandBySelector: message allows to process events such as new-line, forward and backward delete, arrows, + escape, tab, F1. The message setMarkedText: is sent when marked text, that is, temporary text that gets replaced later + by some other text, is inserted. This happens when a dead key is pressed, and also + when entering complex scripts (e.g., Chinese). Fl_X::next_marked_length gives the byte + length of marked text before the FL_KEYBOARD event is processed. Fl::compose_state gives this length after this processing. + Message insertText: is sent to enter text in the focused widget. If there's marked text, Fl::compose_state is > 0, and this + marked text gets replaced by the inserted text. If there's no marked text, the new text is inserted at the insertion point. + When the character palette is used to enter text, the system sends an insertText: message to myview. + The in_key_event field of the FLView class allows to differentiate keyboard from palette inputs. + + During processing of the handleEvent message, inserted and marked strings are concatenated in a single string + inserted in a single FL_KEYBOARD event after return from handleEvent. The need_handle member variable of FLView allows + to determine when setMarkedText or insertText strings have been sent during handleEvent processing and must trigger + an FL_KEYBOARD event. Concatenating two insertText operations or an insertText followed by a setMarkedText is possible. + In contrast, setMarkedText followed by insertText or by another setMarkedText isn't correct if concatenated in a single + string. Thus, in such case, the setMarkedText and the next operation produce each an FL_KEYBOARD event. + + OS >= 10.7 contains a feature where pressing and holding certain keys opens a menu window that shows a list + of possible accented variants of this key. The selectedRange field of the FLView class and the selectedRange, insertText: + and setMarkedText: methods of the NSTextInputClient protocol are used to support this feature. + The notion of selected text (!= marked text) is monitored by the selectedRange field. + The -(NSRange)[FLView selectedRange] method is used to control whether an FLTK widget opens accented character windows + by returning .location = NSNotFound to disable that, or returning the value of the selectedRange field to enable the feature. + When selectedRange.location >= 0, the value of selectedRange.length is meaningful. 0 means no text is currently selected, + > 0 means this number of characters before the insertion point are selected. The insertText: method does + selectedRange = NSMakeRange(100, 0); to indicate no text is selected. The setMarkedText: method does + selectedRange = NSMakeRange(100, newSelection.length); to indicate that this length of text is selected. + + With OS <= 10.5, the crucial call [[myview inputContext] handleEvent:theEvent] is not possible because neither the + inputContext nor the handleEvent: methods are implemented. This call is re-written: + static SEL inputContextSEL = (fl_mac_os_version >= 100600 ? @selector(inputContext) : @selector(FLinputContext)); + [[myview performSelector:inputContextSEL] handleEvent:theEvent]; + that replaces the 10.6 inputContext message by the FLinputContext message. This message and two FLTK-defined classes, + FLTextInputContext and FLTextView, are used to emulate with OS <= 10.5 what's possible with OS >= 10.6. + Method -(FLTextInputContext*)[FLView FLinputContext] returns an instance of class FLTextInputContext that possesses + a handleEvent: method. FLView's FLinputContext method also calls [[self window] fieldEditor:YES forObject:nil] which + returns the so-called view's "field editor". This editor is an instance of the FLTextView class allocated by the + -(id)[FLWindowDelegate windowWillReturnFieldEditor: toObject:] method. + The -(BOOL)[FLTextInputContext handleEvent:] method emulates the missing 10.6 -(BOOL)[NSTextInputContext handleEvent:] + by sending the interpretKeyEvents: message to the FLTextView object. The system sends back doCommandBySelector: and + insertText: messages to the FLTextView object that are transmitted unchanged to myview to be processed as with OS >= 10.6. + The system also sends setMarkedText: messages directly to myview. + + There is furthermore an oddity of dead key processing with OS <= 10.5. It occurs when a dead key followed by a non-accented + key are pressed. Say, for example, that keys '^' followed by 'p' are pressed on a French or German keyboard. Resulting + messages are: [myview setMarkedText:@"^"], [myview insertText:@"^"], [myview insertText:@"p"], [FLTextView insertText:@"^p"]. + The 2nd '^' replaces the marked 1st one, followed by p^p. The resulting text in the widget is "^p^p" instead of the + desired "^p". To avoid that, the FLTextView object is deactivated by the insertText: message and reactivated after + the handleEvent: message has been processed. + + NSEvent's during a character composition sequence: + - keyDown with deadkey -> [[theEvent characters] length] is 0 + - keyUp -> [theEvent characters] contains the deadkey + - keyDown with next key -> [theEvent characters] contains the composed character + - keyUp -> [theEvent characters] contains the standard character + */ -@interface FLView : NSView <NSTextInput> { - int next_compose_length; - bool in_key_event; +static void cocoaKeyboardHandler(NSEvent *theEvent) +{ + NSUInteger mods; + // get the modifiers + mods = [theEvent modifierFlags]; + // get the key code + UInt32 keyCode = 0, maskedKeyCode = 0; + unsigned short sym = 0; + keyCode = [theEvent keyCode]; + // extended keyboards can also send sequences on key-up to generate Kanji etc. codes. + // Some observed prefixes are 0x81 to 0x83, followed by an 8 bit keycode. + // In this mode, there seem to be no key-down codes + // printf("%08x %08x %08x\n", keyCode, mods, key); + maskedKeyCode = keyCode & 0x7f; + mods_to_e_state( mods ); // process modifier keys + sym = macKeyLookUp[maskedKeyCode]; + if (sym < 0xff00) { // a "simple" key + // find the result of this key without modifier + NSString *sim = [theEvent charactersIgnoringModifiers]; + UniChar one; + CFStringGetCharacters((CFStringRef)sim, CFRangeMake(0, 1), &one); + // charactersIgnoringModifiers doesn't ignore shift, remove it when it's on + if(one >= 'A' && one <= 'Z') one += 32; + if (one > 0 && one <= 0x7f && (sym<'0' || sym>'9') ) sym = one; + } + Fl::e_keysym = Fl::e_original_keysym = sym; + /*NSLog(@"cocoaKeyboardHandler: keycode=%08x keysym=%08x mods=%08x symbol=%@ (%@)", + keyCode, sym, mods, [theEvent characters], [theEvent charactersIgnoringModifiers]);*/ + // If there is text associated with this key, it will be filled in later. + Fl::e_length = 0; + Fl::e_text = (char*)""; +} + +@interface FLTextInputContext : NSObject { // "emulates" NSTextInputContext before OS 10.6 +@public + FLTextView *edit; +} +-(BOOL)handleEvent:(NSEvent*)theEvent; +@end +@implementation FLTextInputContext +-(BOOL)handleEvent:(NSEvent*)theEvent { + [self->edit setActive:YES]; + [self->edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; + [self->edit setActive:YES]; + return YES; +} +@end + +@interface FLView : NSView <NSTextInput +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 +, NSTextInputClient +#endif +> { + BOOL in_key_event; // YES means keypress is being processed by handleEvent + BOOL need_handle; // YES means Fl::handle(FL_KEYBOARD,) is needed after handleEvent processing + NSInteger identifier; + NSRange selectedRange; } + (void)prepareEtext:(NSString*)aString; ++ (void)concatEtext:(NSString*)aString; - (id)init; - (void)drawRect:(NSRect)rect; - (BOOL)acceptsFirstResponder; - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent; +- (void)resetCursorRects; - (BOOL)performKeyEquivalent:(NSEvent*)theEvent; - (void)mouseUp:(NSEvent *)theEvent; - (void)rightMouseUp:(NSEvent *)theEvent; @@ -1651,7 +1990,6 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi - (void)rightMouseDragged:(NSEvent *)theEvent; - (void)otherMouseDragged:(NSEvent *)theEvent; - (void)scrollWheel:(NSEvent *)theEvent; -- (BOOL)handleKeyDown:(NSEvent *)theEvent; - (void)keyDown:(NSEvent *)theEvent; - (void)keyUp:(NSEvent *)theEvent; - (void)flagsChanged:(NSEvent *)theEvent; @@ -1660,24 +1998,36 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender; - (void)draggingExited:(id < NSDraggingInfo >)sender; - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal; +- (FLTextInputContext*)FLinputContext; +#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5 +- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange; +- (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection replacementRange:(NSRange)replacementRange; +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange; +- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange; +- (NSInteger)windowLevel; +#endif @end @implementation FLView - (id)init { + static NSInteger counter = 0; self = [super init]; if (self) { - next_compose_length = -1; - in_key_event = false; + in_key_event = NO; + identifier = ++counter; } return self; } - (void)drawRect:(NSRect)rect { fl_lock_function(); + through_drawRect = YES; FLWindow *cw = (FLWindow*)[self window]; Fl_Window *w = [cw getFl_Window]; - handleUpdateEvent(w); + if (fl_x_to_redraw) fl_x_to_redraw->flush(); + else handleUpdateEvent(w); + through_drawRect = NO; fl_unlock_function(); } @@ -1688,7 +2038,28 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi - (BOOL)performKeyEquivalent:(NSEvent*)theEvent { //NSLog(@"performKeyEquivalent:"); - return [self handleKeyDown:theEvent]; + fl_lock_function(); + cocoaKeyboardHandler(theEvent); + BOOL handled; + NSUInteger mods = [theEvent modifierFlags]; + if ( (mods & NSControlKeyMask) || (mods & NSCommandKeyMask) ) { + NSString *s = [theEvent characters]; + if ( (mods & NSShiftKeyMask) && (mods & NSCommandKeyMask) ) { + s = [s uppercaseString]; // US keyboards return lowercase letter in s if cmd-shift-key is hit + } + [FLView prepareEtext:s]; + Fl::compose_state = 0; + handled = Fl::handle(FL_KEYBOARD, [(FLWindow*)[theEvent window] getFl_Window]); + } + else { + in_key_event = YES; + need_handle = NO; + handled = [[self performSelector:inputContextSEL] handleEvent:theEvent]; + if (need_handle) handled = Fl::handle(FL_KEYBOARD, [(FLWindow*)[theEvent window] getFl_Window]); + in_key_event = NO; + } + fl_unlock_function(); + return handled; } - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent { @@ -1696,6 +2067,17 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi Fl_Window *first = Fl::first_window(); return (first == w || !first->modal()); } +- (void)resetCursorRects { + Fl_Window *w = [(FLWindow*)[self window] getFl_Window]; + Fl_X *i = Fl_X::i(w); + if (!i) return; // fix for STR #3128 + // We have to have at least one cursor rect for invalidateCursorRectsForView + // to work, hence the "else" clause. + if (i->cursor) + [self addCursorRect:[self visibleRect] cursor:(NSCursor*)i->cursor]; + else + [self addCursorRect:[self visibleRect] cursor:[NSCursor arrowCursor]]; +} - (void)mouseUp:(NSEvent *)theEvent { cocoaMouseHandler(theEvent); } @@ -1729,57 +2111,21 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi - (void)scrollWheel:(NSEvent *)theEvent { cocoaMouseWheelHandler(theEvent); } -- (BOOL)handleKeyDown:(NSEvent *)theEvent { - //NSLog(@"handleKeyDown"); +- (void)keyDown:(NSEvent *)theEvent { + //NSLog(@"keyDown:%@",[theEvent characters]); fl_lock_function(); - - Fl_Window *window = (Fl_Window*)[(FLWindow*)[theEvent window] getFl_Window]; + Fl_Window *window = [(FLWindow*)[theEvent window] getFl_Window]; Fl::first_window(window); - - next_compose_length = -1; - // First let's process the raw key press cocoaKeyboardHandler(theEvent); - - int no_text_key = false; - static const int notext[] = { // keys that don't emit text - FL_BackSpace, FL_Print, FL_Scroll_Lock, FL_Pause, - FL_Insert, FL_Home, FL_Page_Up, FL_Delete, FL_End, FL_Page_Down, - FL_Left, FL_Up, FL_Right, FL_Down, - FL_Menu, FL_Num_Lock, FL_Help - }; - static const int count = sizeof(notext)/sizeof(int); - if (Fl::e_keysym > FL_F && Fl::e_keysym <= FL_F_Last) no_text_key = true; - else for (int i=0; i < count; i++) { - if (notext[i] == Fl::e_keysym) { - no_text_key = true; - break; - } - } - if (!no_text_key && !(Fl::e_state & FL_META) ) { - // Don't send cmd-<key> to interpretKeyEvents because it beeps. - // Then we can let the OS have a stab at it and see if it thinks it - // should result in some text - NSText *edit = [[theEvent window] fieldEditor:YES forObject:nil]; - in_key_event = true; - [edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; - in_key_event = false; - } - //NSLog(@"to text=%@ l=%d", [NSString stringWithUTF8String:Fl::e_text], Fl::e_length); - int handled = Fl::handle(FL_KEYDOWN, window); - // We have to update this after Fl::handle as it says what to do on the - // _next_ input - if (next_compose_length != -1) - Fl::compose_state = next_compose_length; - + in_key_event = YES; + need_handle = NO; + [[self performSelector:inputContextSEL] handleEvent:theEvent]; + if (need_handle) Fl::handle(FL_KEYBOARD, window); + in_key_event = NO; fl_unlock_function(); - return (handled ? YES : NO); -} -- (void)keyDown:(NSEvent *)theEvent { - //NSLog(@"keyDown: "); - [self handleKeyDown:theEvent]; } - (void)keyUp:(NSEvent *)theEvent { - //NSLog(@"keyUp: "); + //NSLog(@"keyUp:%@",[theEvent characters]); fl_lock_function(); Fl_Window *window = (Fl_Window*)[(FLWindow*)[theEvent window] getFl_Window]; Fl::first_window(window); @@ -1863,8 +2209,8 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi CFStringGetCString(all, DragData, l + 1, kCFStringEncodingUTF8); CFRelease(all); } - else if ( [[pboard types] containsObject:NSStringPboardType] ) { - NSData *data = [pboard dataForType:NSStringPboardType]; + else if ( [[pboard types] containsObject:utf8_format] ) { + NSData *data = [pboard dataForType:utf8_format]; DragData = (char *)malloc([data length] + 1); [data getBytes:DragData]; DragData[[data length]] = 0; @@ -1902,6 +2248,15 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi return NSDragOperationGeneric; } +- (FLTextInputContext*)FLinputContext { // used only if OS < 10.6 to replace [NSView inputContext] + static FLTextInputContext *context = NULL; + if (!context) { + context = [[FLTextInputContext alloc] init]; + } + context->edit = (FLTextView*)[[self window] fieldEditor:YES forObject:nil]; + return context; +} + + (void)prepareEtext:(NSString*)aString { // fills Fl::e_text with UTF-8 encoded aString using an adequate memory allocation static char *received_utf8 = NULL; @@ -1923,69 +2278,124 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi Fl::e_length = l; } -// These functions implement text input. -// Only two-stroke character composition works at this point. -// Needs much elaboration to fully support CJK text input, -// but this is the way to go. ++ (void)concatEtext:(NSString*)aString { + // extends Fl::e_text with aString + NSString *newstring = [[NSString stringWithUTF8String:Fl::e_text] stringByAppendingString:aString]; + [FLView prepareEtext:newstring]; +} + - (void)doCommandBySelector:(SEL)aSelector { + NSString *s = [[NSApp currentEvent] characters]; + //NSLog(@"doCommandBySelector:%s text='%@'",sel_getName(aSelector), s); + s = [s substringFromIndex:[s length] - 1]; + [FLView prepareEtext:s]; // use the last character of the event; necessary for deadkey + Tab + Fl_Window *target = [(FLWindow*)[self window] getFl_Window]; + Fl::handle(FL_KEYBOARD, target); } - (void)insertText:(id)aString { + [self insertText:aString replacementRange:NSMakeRange(NSNotFound, 0)]; +} + +- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { NSString *received; if ([aString isKindOfClass:[NSAttributedString class]]) { received = [(NSAttributedString*)aString string]; } else { received = (NSString*)aString; } - //NSLog(@"insertText: received=%@",received); - - if (!in_key_event) fl_lock_function(); - [FLView prepareEtext:received]; - // We can get called outside of key events (e.g. from the character - // palette). Transform such actions to FL_PASTE events. - if (!in_key_event) { - Fl_Window *target = [(FLWindow*)[self window] getFl_Window]; - Fl::handle(FL_PASTE, target); - // for some reason, the window does not redraw until the next mouse move or button push - // sending a 'redraw()' or 'awake()' does not solve the issue! - Fl::flush(); - } - if (!in_key_event) fl_unlock_function(); + /*NSLog(@"insertText='%@' l=%d Fl::compose_state=%d range=%d,%d", + received,strlen([received UTF8String]),Fl::compose_state,replacementRange.location,replacementRange.length);*/ + fl_lock_function(); + Fl_Window *target = [(FLWindow*)[self window] getFl_Window]; + while (replacementRange.length--) { // delete replacementRange.length characters before insertion point + int saved_keysym = Fl::e_keysym; + Fl::e_keysym = FL_BackSpace; + Fl::handle(FL_KEYBOARD, target); + Fl::e_keysym = saved_keysym; + } + if (in_key_event && Fl_X::next_marked_length && Fl::e_length) { + // if setMarkedText + insertText is sent during handleEvent, text cannot be concatenated in single FL_KEYBOARD event + Fl::handle(FL_KEYBOARD, target); + Fl::e_length = 0; + } + if (in_key_event && Fl::e_length) [FLView concatEtext:received]; + else [FLView prepareEtext:received]; + Fl_X::next_marked_length = 0; + // We can get called outside of key events (e.g., from the character palette, from CJK text input). + BOOL palette = !(in_key_event || Fl::compose_state); + if (palette) Fl::e_keysym = 0; + // YES if key has text attached + BOOL has_text_key = Fl::e_keysym <= '~' || Fl::e_keysym == FL_Iso_Key || + (Fl::e_keysym >= FL_KP && Fl::e_keysym <= FL_KP_Last && Fl::e_keysym != FL_KP_Enter); + // insertText sent during handleEvent of a key without text cannot be processed in a single FL_KEYBOARD event. + // Occurs with deadkey followed by non-text key + if (!in_key_event || !has_text_key) { + Fl::handle(FL_KEYBOARD, target); + Fl::e_length = 0; + } + else need_handle = YES; + selectedRange = NSMakeRange(100, 0); // 100 is an arbitrary value + // for some reason, with the palette, the window does not redraw until the next mouse move or button push + // sending a 'redraw()' or 'awake()' does not solve the issue! + if (palette) Fl::flush(); + if (fl_mac_os_version < 100600) [(FLTextView*)[[self window] fieldEditor:YES forObject:nil] setActive:NO]; + fl_unlock_function(); } - (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection { + [self setMarkedText:aString selectedRange:newSelection replacementRange:NSMakeRange(NSNotFound, 0)]; +} + +- (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection replacementRange:(NSRange)replacementRange { NSString *received; - if (newSelection.location == 0) { - [self unmarkText]; - return; - } if ([aString isKindOfClass:[NSAttributedString class]]) { received = [(NSAttributedString*)aString string]; } else { received = (NSString*)aString; } - //NSLog(@"setMarkedText: %@ %d %d",received,newSelection.location,newSelection.length); - // This code creates the OS X behaviour of seeing dead keys as things - // are being composed. - next_compose_length = newSelection.location; - [FLView prepareEtext:received]; - //NSLog(@"Fl::e_text=%@ Fl::e_length=%d next_compose_length=%d", received, Fl::e_length, next_compose_length); + fl_lock_function(); + /*NSLog(@"setMarkedText:%@ l=%d newSelection=%d,%d Fl::compose_state=%d replacement=%d,%d", + received, strlen([received UTF8String]), newSelection.location, newSelection.length, Fl::compose_state, + replacementRange.location, replacementRange.length);*/ + Fl_Window *target = [(FLWindow*)[self window] getFl_Window]; + while (replacementRange.length--) { // delete replacementRange.length characters before insertion point + Fl::e_keysym = FL_BackSpace; + Fl::compose_state = 0; + Fl_X::next_marked_length = 0; + Fl::handle(FL_KEYBOARD, target); + Fl::e_keysym = 'a'; // pretend a letter key was hit + } + if (in_key_event && Fl_X::next_marked_length && Fl::e_length) { + // if setMarkedText + setMarkedText is sent during handleEvent, text cannot be concatenated in single FL_KEYBOARD event + Fl::handle(FL_KEYBOARD, target); + Fl::e_length = 0; + } + if (in_key_event && Fl::e_length) [FLView concatEtext:received]; + else [FLView prepareEtext:received]; + Fl_X::next_marked_length = strlen([received UTF8String]); + if (!in_key_event) Fl::handle( FL_KEYBOARD, target); + else need_handle = YES; + selectedRange = NSMakeRange(100, newSelection.length); + fl_unlock_function(); } - (void)unmarkText { fl_lock_function(); - Fl::compose_state = 0; + Fl::reset_marked_text(); fl_unlock_function(); //NSLog(@"unmarkText"); } - (NSRange)selectedRange { + Fl_Widget *w = Fl::focus(); + if (w && w->use_accents_menu()) return selectedRange; return NSMakeRange(NSNotFound, 0); } - (NSRange)markedRange { - //NSLog(@"markedRange ?"); - return NSMakeRange(NSNotFound, Fl::compose_state); + //NSLog(@"markedRange=%d %d", Fl::compose_state > 0?0:NSNotFound, Fl::compose_state); + return NSMakeRange(Fl::compose_state > 0?0:NSNotFound, Fl::compose_state); } - (BOOL)hasMarkedText { @@ -1994,6 +2404,9 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi } - (NSAttributedString *)attributedSubstringFromRange:(NSRange)aRange { + return [self attributedSubstringForProposedRange:aRange actualRange:NULL]; +} +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { //NSLog(@"attributedSubstringFromRange: %d %d",aRange.location,aRange.length); return nil; } @@ -2003,28 +2416,44 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi } - (NSRect)firstRectForCharacterRange:(NSRange)aRange { + return [self firstRectForCharacterRange:aRange actualRange:NULL]; +} +- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { + //NSLog(@"firstRectForCharacterRange %d %d actualRange=%p",aRange.location, aRange.length,actualRange); NSRect glyphRect; fl_lock_function(); Fl_Widget *focus = Fl::focus(); - Fl_Window *wfocus = focus->window(); - while (wfocus->window()) wfocus = wfocus->window(); + Fl_Window *wfocus = [(FLWindow*)[self window] getFl_Window]; + if (!focus) focus = wfocus; glyphRect.size.width = 0; - if (dynamic_cast<Fl_Text_Display*>(focus) != NULL) { - int x, y; - Fl_Text_Display *current = (Fl_Text_Display*)focus; - current->position_to_xy( current->insert_position(), &x, &y ); + int x, y, height; + if (Fl_X::insertion_point_location(&x, &y, &height)) { glyphRect.origin.x = (CGFloat)x; - glyphRect.origin.y = (CGFloat)y + current->textsize(); - glyphRect.size.height = current->textsize(); + glyphRect.origin.y = (CGFloat)y; } else { - glyphRect.origin.x = focus->x(); - glyphRect.origin.y = focus->y() + focus->h(); - glyphRect.size.height = 12; + if (focus->as_window()) { + glyphRect.origin.x = 0; + glyphRect.origin.y = focus->h(); + } + else { + glyphRect.origin.x = focus->x(); + glyphRect.origin.y = focus->y() + focus->h(); + } + height = 12; + } + glyphRect.size.height = height; + Fl_Window *win = focus->as_window(); + if (!win) win = focus->window(); + while (win != NULL && win != wfocus) { + glyphRect.origin.x += win->x(); + glyphRect.origin.y += win->y(); + win = win->window(); } // Convert the rect to screen coordinates glyphRect.origin.y = wfocus->h() - glyphRect.origin.y; - glyphRect.origin = [[self window] convertBaseToScreen:glyphRect.origin]; + glyphRect.origin = [(FLWindow*)[self window] convertBaseToScreen:glyphRect.origin]; + if (actualRange) *actualRange = aRange; fl_unlock_function(); return glyphRect; } @@ -2033,8 +2462,12 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi return 0; } +- (NSInteger)windowLevel { + return [[self window] level]; +} + - (NSInteger)conversationIdentifier { - return (NSInteger)self; + return identifier; } @end @@ -2057,6 +2490,29 @@ void Fl_Window::fullscreen_off_x(int X, int Y, int W, int H) { } /* + * Initialize the given port for redraw and call the window's flush() to actually draw the content + */ +void Fl_X::flush() +{ + if (through_drawRect || w->as_gl_window()) { + make_current_counts = 1; + w->flush(); + make_current_counts = 0; + Fl_X::q_release_context(); + return; + } + // have Cocoa immediately redraw the window's view + FLView *view = (FLView*)[fl_xid(w) contentView]; + fl_x_to_redraw = this; + [view setNeedsDisplay:YES]; + // will send the drawRect: message to the window's view after having prepared the adequate NSGraphicsContext + [view displayIfNeededIgnoringOpacity]; + fl_x_to_redraw = NULL; +} + +//bool Fl_X::make_shaped = false; + +/* * go ahead, create that (sub)window */ void Fl_X::make(Fl_Window* w) @@ -2069,8 +2525,9 @@ void Fl_X::make(Fl_Window* w) x->other_xid = 0; x->region = 0; x->subRegion = 0; - x->cursor = fl_default_cursor; + x->cursor = NULL; x->gc = 0; // stay 0 for Quickdraw; fill with CGContext for Quartz + w->set_visible(); Fl_Window *win = w->window(); Fl_X *xo = Fl_X::i(win); if (xo) { @@ -2092,7 +2549,7 @@ void Fl_X::make(Fl_Window* w) } if (w->as_gl_window()) { // if creating a sub-GL-window while (win->window()) win = win->window(); - [Fl_X::i(win)->xid setContainsGLsubwindow:YES]; + [Fl_X::i(win)->xid containsGLsubwindow:YES]; } fl_show_iconic = 0; } @@ -2109,7 +2566,7 @@ void Fl_X::make(Fl_Window* w) int hp = w->h(); if (w->size_range_set) { if ( w->minh != w->maxh || w->minw != w->maxw) { - winstyle |= NSResizableWindowMask; + if (w->border()) winstyle |= NSResizableWindowMask; } } else { if (w->resizable()) { @@ -2117,7 +2574,7 @@ void Fl_X::make(Fl_Window* w) int minw = o->w(); if (minw > 100) minw = 100; int minh = o->h(); if (minh > 100) minh = 100; w->size_range(w->w() - o->w() + minw, w->h() - o->h() + minh, 0, 0); - winstyle |= NSResizableWindowMask; + if (w->border()) winstyle |= NSResizableWindowMask; } else { w->size_range(w->w(), w->h(), w->w(), w->h()); } @@ -2127,18 +2584,17 @@ void Fl_X::make(Fl_Window* w) if (!fake_X_wm(w, xwm, ywm, bt, bx, by)) { // menu windows and tooltips if (w->modal()||w->tooltip_window()) { - winstyle = NSBorderlessWindowMask; - winlevel = NSModalPanelWindowLevel; - } else { - winstyle = NSBorderlessWindowMask; + winlevel = modal_window_level(); } - } else if (w->modal()) { + //winstyle = NSBorderlessWindowMask; + } + if (w->modal()) { winstyle &= ~NSMiniaturizableWindowMask; // winstyle &= ~(NSResizableWindowMask | NSMiniaturizableWindowMask); - winlevel = NSModalPanelWindowLevel; + winlevel = modal_window_level(); } else if (w->non_modal()) { - winlevel = NSFloatingWindowLevel; + winlevel = non_modal_window_level(); } if (by+bt) { @@ -2165,16 +2621,39 @@ void Fl_X::make(Fl_Window* w) x->other_xid = 0; // room for doublebuffering image map. On OS X this is only used by overlay windows x->region = 0; x->subRegion = 0; - x->cursor = fl_default_cursor; + x->cursor = NULL; x->xidChildren = 0; x->xidNext = 0; x->gc = 0; NSRect crect; if (w->fullscreen_active()) { - int sx, sy, sw, sh; - Fl::screen_xywh(sx, sy, sw, sh, w->x(), w->y(), w->w(), w->h()); - w->resize(sx, sy, sw, sh); + int top, bottom, left, right; + int sx, sy, sw, sh, X, Y, W, H; + + top = w->fullscreen_screen_top; + bottom = w->fullscreen_screen_bottom; + left = w->fullscreen_screen_left; + right = w->fullscreen_screen_right; + + if ((top < 0) || (bottom < 0) || (left < 0) || (right < 0)) { + top = Fl::screen_num(w->x(), w->y(), w->w(), w->h()); + bottom = top; + left = top; + right = top; + } + + Fl::screen_xywh(sx, sy, sw, sh, top); + Y = sy; + Fl::screen_xywh(sx, sy, sw, sh, bottom); + H = sy + sh - Y; + Fl::screen_xywh(sx, sy, sw, sh, left); + X = sx; + Fl::screen_xywh(sx, sy, sw, sh, right); + W = sx + sw - X; + + w->resize(X, Y, W, H); + winstyle = NSBorderlessWindowMask; winlevel = NSStatusWindowLevel; } @@ -2188,6 +2667,10 @@ void Fl_X::make(Fl_Window* w) [cw setFrameOrigin:crect.origin]; [cw setHasShadow:YES]; [cw setAcceptsMouseMovedEvents:YES]; + if (w->shape_data_) { + [cw setOpaque:NO]; // shaped windows must be non opaque + [cw setBackgroundColor:[NSColor clearColor]]; // and with transparent background color + } x->xid = cw; x->w = w; w->i = x; x->wait_for_expose = 1; @@ -2195,6 +2678,7 @@ void Fl_X::make(Fl_Window* w) Fl_X::first = x; FLView *myview = [[FLView alloc] init]; [cw setContentView:myview]; + [myview release]; [cw setLevel:winlevel]; q_set_window_title(cw, w->label(), w->iconlabel()); @@ -2212,12 +2696,10 @@ void Fl_X::make(Fl_Window* w) [cw setAlphaValue:0.97]; } // Install DnD handlers - [myview registerForDraggedTypes:[NSArray arrayWithObjects: - NSStringPboardType, NSFilenamesPboardType, nil]]; + [myview registerForDraggedTypes:[NSArray arrayWithObjects:utf8_format, NSFilenamesPboardType, nil]]; if ( ! Fl_X::first->next ) { // if this is the first window, we need to bring the application to the front - ProcessSerialNumber psn = { 0, kCurrentProcess }; - SetFrontProcess( &psn ); + [NSApp activateIgnoringOtherApps:YES]; } if (w->size_range_set) w->size_range_(); @@ -2231,7 +2713,7 @@ void Fl_X::make(Fl_Window* w) w->set_visible(); if ( w->border() || (!w->modal() && !w->tooltip_window()) ) Fl::handle(FL_FOCUS, w); Fl::first_window(w); - [cw setDelegate:[NSApp delegate]]; + [cw setDelegate:[FLWindowDelegate createOnce]]; if (fl_show_iconic) { fl_show_iconic = 0; [cw miniaturize:nil]; @@ -2316,7 +2798,7 @@ void Fl_Window::show() { labeltype(FL_NO_LABEL); } Fl_Tooltip::exit(this); - if (!shown() || !i) { + if (!shown()) { Fl_X::make(this); } else { if ( !parent() ) { @@ -2388,9 +2870,34 @@ void Fl_Window::resize(int X,int Y,int W,int H) { /* * make all drawing go into this window (called by subclass flush() impl.) + + This can be called in 3 different instances: + + 1) When a window is created, resized, or deminiaturized. + The system sends the drawRect: message to the window's view after having prepared the current graphics context + to draw to this view. Variable through_drawRect is YES, and fl_x_to_redraw is NULL. Processing of drawRect: calls + handleUpdateEvent() that calls Fl_X::flush() for the window and its subwindows. Fl_X::flush() calls + Fl_Window::flush() that calls Fl_Window::make_current() that only needs to identify the graphics port of the + current graphics context. The window's draw() function is then executed. + + 2) At each round of the FLTK event loop. + Fl::flush() is called, that calls Fl_X::flush() on each window that needs drawing. Fl_X::flush() sets + fl_x_to_redraw to this and sends the displayIfNeededIgnoringOpacity message to the window's view. + This message makes the system prepare the current graphics context adequately for drawing to this view, and + send it the drawRect: message which sets through_drawRect to YES. Processing of the drawRect: message calls + Fl_X::flush() for the window which proceeds as in 1) above. + + 3) An FLTK application can call Fl_Window::make_current() at any time before it draws to a window. + This occurs for instance in the idle callback function of the mandelbrot test program. Variable through_drawRect is NO, + so Fl_Window::make_current() creates a new graphics context adequate for the window. + Subsequent drawing requests go to this window. CAUTION: it's not possible to call Fl::wait(), Fl::check() + nor Fl::ready() while in the draw() function of a widget. Use an idle callback instead. + */ void Fl_Window::make_current() { + if (make_current_counts > 1) return; + if (make_current_counts) make_current_counts++; Fl_X::q_release_context(); fl_window = i->xid; current_ = this; @@ -2404,12 +2911,9 @@ void Fl_Window::make_current() yp += win->y(); win = (Fl_Window*)win->window(); } - - NSView *current_focus = [NSView focusView]; - // sometimes current_focus is set to a non-FLTK view: don't touch that - if ( [current_focus isKindOfClass:[FLView class]] ) [current_focus unlockFocus]; - [[i->xid contentView] lockFocus]; - i->gc = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + NSGraphicsContext *nsgc = through_drawRect ? [NSGraphicsContext currentContext] : + [NSGraphicsContext graphicsContextWithWindow:fl_window]; + i->gc = (CGContextRef)[nsgc graphicsPort]; fl_gc = i->gc; Fl_Region fl_window_region = XRectangleRegion(0,0,w(),h()); if ( ! this->window() ) { @@ -2477,7 +2981,8 @@ void Fl_X::q_clear_clipping() { void Fl_X::q_release_context(Fl_X *x) { if (x && x->gc!=fl_gc) return; if (!fl_gc) return; - CGContextRestoreGState(fl_gc); // matches the CGContextSaveGState of make_current + CGContextRestoreGState(fl_gc); // KEEP IT: matches the CGContextSaveGState of make_current + CGContextFlush(fl_gc); fl_gc = 0; #if defined(FLTK_USE_CAIRO) if (Fl::cairo_autolink_context()) Fl::cairo_make_current((Fl_Window*) 0); // capture gc changes automatically to update the cairo context adequately @@ -2514,35 +3019,55 @@ static void convert_crlf(char * s, size_t len) } // fltk 1.3 clipboard support constant definitions: -const CFStringRef flavorNames[] = { - CFSTR("public.utf16-plain-text"), - CFSTR("public.utf8-plain-text"), - CFSTR("com.apple.traditional-mac-plain-text") }; -const CFStringEncoding encodings[] = { - kCFStringEncodingUnicode, - kCFStringEncodingUTF8, - kCFStringEncodingMacRoman}; -const size_t handledFlavorsCount = sizeof(encodings)/sizeof(CFStringEncoding); +static NSString *calc_utf8_format(void) +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 +#define NSPasteboardTypeString @"public.utf8-plain-text" +#endif + if (fl_mac_os_version >= 100600) return NSPasteboardTypeString; + return NSStringPboardType; +} // clipboard variables definitions : -char *fl_selection_buffer[2]; -int fl_selection_length[2]; +char *fl_selection_buffer[2] = {NULL, NULL}; +int fl_selection_length[2] = {0, 0}; static int fl_selection_buffer_length[2]; -static PasteboardRef myPasteboard = 0; -static void allocatePasteboard() { - if (!myPasteboard) - PasteboardCreate(kPasteboardClipboard, &myPasteboard); +static PasteboardRef allocatePasteboard(void) +{ + PasteboardRef clip; + PasteboardCreate(kPasteboardClipboard, &clip); // requires Mac OS 10.3 + return clip; } +static PasteboardRef myPasteboard = allocatePasteboard(); +extern void fl_trigger_clipboard_notify(int source); + +void fl_clipboard_notify_change() { + // No need to do anything here... +} + +static void clipboard_check(void) +{ + PasteboardSyncFlags flags; + + flags = PasteboardSynchronize(myPasteboard); // requires Mac OS 10.3 + + if (!(flags & kPasteboardModified)) + return; + if (flags & kPasteboardClientIsOwner) + return; + + fl_trigger_clipboard_notify(1); +} /* * create a selection - * owner: widget that created the selection * stuff: pointer to selected data - * size of selected data + * len: size of selected data + * type: always "plain/text" for now */ -void Fl::copy(const char *stuff, int len, int clipboard) { +void Fl::copy(const char *stuff, int len, int clipboard, const char *type) { if (!stuff || len<0) return; if (len+1 > fl_selection_buffer_length[clipboard]) { delete[] fl_selection_buffer[clipboard]; @@ -2553,84 +3078,180 @@ void Fl::copy(const char *stuff, int len, int clipboard) { fl_selection_buffer[clipboard][len] = 0; // needed for direct paste fl_selection_length[clipboard] = len; if (clipboard) { - allocatePasteboard(); - OSStatus err = PasteboardClear(myPasteboard); - if (err!=noErr) return; // clear did not work, maybe not owner of clipboard. - PasteboardSynchronize(myPasteboard); CFDataRef text = CFDataCreate(kCFAllocatorDefault, (UInt8*)fl_selection_buffer[1], len); if (text==NULL) return; // there was a pb creating the object, abort. - err=PasteboardPutItemFlavor(myPasteboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), text, 0); + NSPasteboard *clip = [NSPasteboard generalPasteboard]; + [clip declareTypes:[NSArray arrayWithObject:utf8_format] owner:nil]; + [clip setData:(NSData*)text forType:utf8_format]; CFRelease(text); } } +static int get_plain_text_from_clipboard(char **buffer, int previous_length) +{ + NSInteger length = 0; + NSPasteboard *clip = [NSPasteboard generalPasteboard]; + NSString *found = [clip availableTypeFromArray:[NSArray arrayWithObjects:utf8_format, @"public.utf16-plain-text", @"com.apple.traditional-mac-plain-text", nil]]; + if (found) { + NSData *data = [clip dataForType:found]; + if (data) { + NSInteger len; + char *aux_c = NULL; + if (![found isEqualToString:utf8_format]) { + NSString *auxstring; + auxstring = (NSString *)CFStringCreateWithBytes(NULL, + (const UInt8*)[data bytes], + [data length], + [found isEqualToString:@"public.utf16-plain-text"] ? kCFStringEncodingUnicode : kCFStringEncodingMacRoman, + false); + aux_c = strdup([auxstring UTF8String]); + [auxstring release]; + len = strlen(aux_c) + 1; + } + else len = [data length] + 1; + if ( len >= previous_length ) { + length = len; + delete[] *buffer; + *buffer = new char[len]; + } + if (![found isEqualToString:utf8_format]) { + strcpy(*buffer, aux_c); + free(aux_c); + } + else { + [data getBytes:*buffer]; + } + (*buffer)[len - 1] = 0; + length = len - 1; + convert_crlf(*buffer, len - 1); // turn all \r characters into \n: + Fl::e_clipboard_type = Fl::clipboard_plain_text; + } + } + return length; +} + +static Fl_Image* get_image_from_clipboard() +{ + Fl_RGB_Image *image = NULL; + uchar *imagedata; + NSBitmapImageRep *bitmap; + NSPasteboard *clip = [NSPasteboard generalPasteboard]; + NSArray *present = [clip types]; // types in pasteboard in order of decreasing preference + NSArray *possible = [NSArray arrayWithObjects:@"com.adobe.pdf", @"public.tiff", @"com.apple.pict", nil]; + NSString *found = nil; + NSUInteger rank; + for (rank = 0; rank < [present count]; rank++) { // find first of possible types present in pasteboard + for (NSUInteger i = 0; i < [possible count]; i++) { + if ([[present objectAtIndex:rank] isEqualToString:[possible objectAtIndex:i]]) { + found = [present objectAtIndex:rank]; + goto after_loop; + } + } + } +after_loop: + if (found) { + NSData *data = [clip dataForType:found]; + if (data) { + if ([found isEqualToString:@"public.tiff"]) { + bitmap = [NSBitmapImageRep imageRepWithData:data]; + int bpp = [bitmap bytesPerPlane]; + int bpr = [bitmap bytesPerRow]; + int depth = [bitmap samplesPerPixel], w = bpr/depth, h = bpp/bpr; + imagedata = new uchar[w * h * depth]; + memcpy(imagedata, [bitmap bitmapData], w * h * depth); + image = new Fl_RGB_Image(imagedata, w, h, depth); + image->alloc_array = 1; + } + else if ([found isEqualToString:@"com.adobe.pdf"] || [found isEqualToString:@"com.apple.pict"]) { + NSRect rect; + NSImageRep *vectorial; + NSAffineTransform *dilate = [NSAffineTransform transform]; + if ([found isEqualToString:@"com.adobe.pdf"] ) { + vectorial = [NSPDFImageRep imageRepWithData:data]; + rect = [(NSPDFImageRep*)vectorial bounds]; // in points = 1/72 inch + Fl_Window *win = Fl::first_window(); + int screen_num = win ? Fl::screen_num(win->x(), win->y(), win->w(), win->h()) : 0; + float hr, vr; + Fl::screen_dpi(hr, vr, screen_num); // 1 inch = hr pixels = 72 points -> hr/72 pixel/point + CGFloat scale = hr/72; + [dilate scaleBy:scale]; + rect.size.width *= scale; + rect.size.height *= scale; + rect = NSIntegralRect(rect); + } + else { + vectorial = [NSPICTImageRep imageRepWithData:data]; + rect = [(NSPICTImageRep*)vectorial boundingBox]; // in pixel, no scaling required + } + imagedata = new uchar[(int)(rect.size.width * rect.size.height) * 4]; + memset(imagedata, -1, (int)(rect.size.width * rect.size.height) * 4); + bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&imagedata + pixelsWide:rect.size.width + pixelsHigh:rect.size.height + bitsPerSample:8 + samplesPerPixel:3 + hasAlpha:NO + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:rect.size.width*4 + bitsPerPixel:32]; + NSDictionary *dict = [NSDictionary dictionaryWithObject:bitmap + forKey:NSGraphicsContextDestinationAttributeName]; + NSGraphicsContext *oldgc = [NSGraphicsContext currentContext]; + [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithAttributes:dict]]; + [dilate concat]; + [vectorial draw]; + [NSGraphicsContext setCurrentContext:oldgc]; + [bitmap release]; + image = new Fl_RGB_Image(imagedata, rect.size.width, rect.size.height, 4); + image->alloc_array = 1; + } + Fl::e_clipboard_type = Fl::clipboard_image; + } + } + return image; +} + // Call this when a "paste" operation happens: -void Fl::paste(Fl_Widget &receiver, int clipboard) { +void Fl::paste(Fl_Widget &receiver, int clipboard, const char *type) { + if (type[0] == 0) type = Fl::clipboard_plain_text; if (clipboard) { - // see if we own the selection, if not go get it: - fl_selection_length[1] = 0; - OSStatus err = noErr; - Boolean found = false; - CFDataRef flavorData = NULL; - CFStringEncoding encoding = 0; - - allocatePasteboard(); - PasteboardSynchronize(myPasteboard); - ItemCount nFlavor = 0, i, j; - err = PasteboardGetItemCount(myPasteboard, &nFlavor); - if (err==noErr) { - for (i=1; i<=nFlavor; i++) { - PasteboardItemID itemID = 0; - CFArrayRef flavorTypeArray = NULL; - found = false; - err = PasteboardGetItemIdentifier(myPasteboard, i, &itemID); - if (err!=noErr) continue; - err = PasteboardCopyItemFlavors(myPasteboard, itemID, &flavorTypeArray); - if (err!=noErr) { - if (flavorTypeArray) {CFRelease(flavorTypeArray); flavorTypeArray = NULL;} - continue; - } - CFIndex flavorCount = CFArrayGetCount(flavorTypeArray); - for (j = 0; j < handledFlavorsCount; j++) { - for (CFIndex flavorIndex=0; flavorIndex<flavorCount; flavorIndex++) { - CFStringRef flavorType = (CFStringRef)CFArrayGetValueAtIndex(flavorTypeArray, flavorIndex); - if (UTTypeConformsTo(flavorType, flavorNames[j])) { - err = PasteboardCopyItemFlavorData( myPasteboard, itemID, flavorNames[j], &flavorData ); - if (err != noErr) continue; - encoding = encodings[j]; - found = true; - break; - } - } - if (found) break; - } - if (flavorTypeArray) {CFRelease(flavorTypeArray); flavorTypeArray = NULL;} - if (found) break; + Fl::e_clipboard_type = ""; + if (strcmp(type, Fl::clipboard_plain_text) == 0) { + fl_selection_length[1] = get_plain_text_from_clipboard( &fl_selection_buffer[1], fl_selection_length[1]); } - if (found) { - CFIndex len = CFDataGetLength(flavorData); - CFStringRef mycfs = CFStringCreateWithBytes(NULL, CFDataGetBytePtr(flavorData), len, encoding, false); - CFRelease(flavorData); - len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(mycfs), kCFStringEncodingUTF8) + 1; - if ( len >= fl_selection_buffer_length[1] ) { - fl_selection_buffer_length[1] = len; - delete[] fl_selection_buffer[1]; - fl_selection_buffer[1] = new char[len]; - } - CFStringGetCString(mycfs, fl_selection_buffer[1], len, kCFStringEncodingUTF8); - CFRelease(mycfs); - len = strlen(fl_selection_buffer[1]); - fl_selection_length[1] = len; - convert_crlf(fl_selection_buffer[1],len); // turn all \r characters into \n: + else if (strcmp(type, Fl::clipboard_image) == 0) { + Fl::e_clipboard_data = get_image_from_clipboard( ); + if (Fl::e_clipboard_data) { + int done = receiver.handle(FL_PASTE); + Fl::e_clipboard_type = ""; + if (done == 0) { + delete (Fl_Image*)Fl::e_clipboard_data; + Fl::e_clipboard_data = NULL; + } } - } + return; + } + else + fl_selection_length[1] = 0; } Fl::e_text = fl_selection_buffer[clipboard]; Fl::e_length = fl_selection_length[clipboard]; - if (!Fl::e_text) Fl::e_text = (char *)""; + if (!Fl::e_length) Fl::e_text = (char *)""; receiver.handle(FL_PASTE); } +int Fl::clipboard_contains(const char *type) { + NSString *found = nil; + if (strcmp(type, Fl::clipboard_plain_text) == 0) { + found = [[NSPasteboard generalPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:utf8_format, @"public.utf16-plain-text", @"com.apple.traditional-mac-plain-text", nil]]; + } + else if (strcmp(type, Fl::clipboard_image) == 0) { + found = [[NSPasteboard generalPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:@"public.tiff", @"com.adobe.pdf", @"com.apple.pict", nil]]; + } + return found != nil; +} + int Fl_X::unlink(Fl_X *start) { if (start) { Fl_X *pc = start; @@ -2674,11 +3295,6 @@ void Fl_X::relink(Fl_Window *w, Fl_Window *wp) { void Fl_X::destroy() { // subwindows share their xid with their parent window, so should not close it if (!subwindow && w && !w->parent() && xid) { - NSView *topview = [xid contentView]; - if ( [NSView focusView] == topview ) { - [topview unlockFocus]; - } - [topview release]; [xid close]; } } @@ -2692,6 +3308,10 @@ void Fl_X::map() { Fl_X::relink(w, w->window() ); w->redraw(); } + if (cursor) { + [(NSCursor*)cursor release]; + cursor = NULL; + } } void Fl_X::unmap() { @@ -2780,84 +3400,152 @@ void Fl_X::collapse() { static NSImage *CGBitmapContextToNSImage(CGContextRef c) // the returned NSImage is autoreleased { + NSImage* image; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + if (fl_mac_os_version >= 100600) { + CGImageRef cgimg = CGBitmapContextCreateImage(c); // requires 10.4 + image = [[NSImage alloc] initWithCGImage:cgimg size:NSZeroSize]; // requires 10.6 + CFRelease(cgimg); + } + else +#endif + { + unsigned char *pdata = (unsigned char *)CGBitmapContextGetData(c); + NSBitmapImageRep *imagerep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&pdata + pixelsWide:CGBitmapContextGetWidth(c) + pixelsHigh:CGBitmapContextGetHeight(c) + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:CGBitmapContextGetBytesPerRow(c) + bitsPerPixel:CGBitmapContextGetBitsPerPixel(c)]; + image = [[NSImage alloc] initWithData: [imagerep TIFFRepresentation]]; + [imagerep release]; + } + return [image autorelease]; +} + + +CFDataRef Fl_X::CGBitmapContextToTIFF(CGContextRef c) +{ // the returned value is autoreleased unsigned char *pdata = (unsigned char *)CGBitmapContextGetData(c); NSBitmapImageRep *imagerep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&pdata - pixelsWide:CGBitmapContextGetWidth(c) - pixelsHigh:CGBitmapContextGetHeight(c) - bitsPerSample:8 - samplesPerPixel:4 - hasAlpha:YES - isPlanar:NO - colorSpaceName:NSDeviceRGBColorSpace - bytesPerRow:CGBitmapContextGetBytesPerRow(c) - bitsPerPixel:CGBitmapContextGetBitsPerPixel(c)]; - NSImage* image = [[NSImage alloc] initWithData: [imagerep TIFFRepresentation]]; + pixelsWide:CGBitmapContextGetWidth(c) + pixelsHigh:CGBitmapContextGetHeight(c) + bitsPerSample:8 + samplesPerPixel:3 + hasAlpha:NO + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:CGBitmapContextGetBytesPerRow(c) + bitsPerPixel:CGBitmapContextGetBitsPerPixel(c)]; + NSData* tiff = [imagerep TIFFRepresentation]; [imagerep release]; - return [image autorelease]; + return (CFDataRef)tiff; } -static NSCursor *PrepareCursor(NSCursor *cursor, CGContextRef (*f)() ) +int Fl_X::set_cursor(Fl_Cursor c) { - if (cursor == nil) { - CGContextRef c = f(); - NSImage *image = CGBitmapContextToNSImage(c); - fl_delete_offscreen( (Fl_Offscreen)c ); - NSPoint pt = {[image size].width/2, [image size].height/2}; - cursor = [[NSCursor alloc] initWithImage:image hotSpot:pt]; + if (cursor) { + [(NSCursor*)cursor release]; + cursor = NULL; } - return cursor; -} -void Fl_X::set_cursor(Fl_Cursor c) -{ - NSCursor *icrsr; switch (c) { - case FL_CURSOR_CROSS: icrsr = [NSCursor crosshairCursor]; break; - case FL_CURSOR_WAIT: - static NSCursor *watch = nil; - watch = PrepareCursor(watch, &Fl_X::watch_cursor_image); - icrsr = watch; - break; - case FL_CURSOR_INSERT: icrsr = [NSCursor IBeamCursor]; break; - case FL_CURSOR_N: icrsr = [NSCursor resizeUpCursor]; break; - case FL_CURSOR_S: icrsr = [NSCursor resizeDownCursor]; break; - case FL_CURSOR_NS: icrsr = [NSCursor resizeUpDownCursor]; break; - case FL_CURSOR_HELP: - static NSCursor *help = nil; - help = PrepareCursor(help, &Fl_X::help_cursor_image); - icrsr = help; - break; - case FL_CURSOR_HAND: icrsr = [NSCursor pointingHandCursor]; break; - case FL_CURSOR_MOVE: icrsr = [NSCursor openHandCursor]; break; - case FL_CURSOR_NE: - case FL_CURSOR_SW: - case FL_CURSOR_NESW: - static NSCursor *nesw = nil; - nesw = PrepareCursor(nesw, &Fl_X::nesw_cursor_image); - icrsr = nesw; - break; - case FL_CURSOR_E: icrsr = [NSCursor resizeRightCursor]; break; - case FL_CURSOR_W: icrsr = [NSCursor resizeLeftCursor]; break; - case FL_CURSOR_WE: icrsr = [NSCursor resizeLeftRightCursor]; break; - case FL_CURSOR_SE: - case FL_CURSOR_NW: - case FL_CURSOR_NWSE: - static NSCursor *nwse = nil; - nwse = PrepareCursor(nwse, &Fl_X::nwse_cursor_image); - icrsr = nwse; - break; - case FL_CURSOR_NONE: - static NSCursor *none = nil; - none = PrepareCursor(none, &Fl_X::none_cursor_image); - icrsr = none; - break; - case FL_CURSOR_ARROW: - case FL_CURSOR_DEFAULT: - default: icrsr = [NSCursor arrowCursor]; - break; + case FL_CURSOR_ARROW: cursor = [NSCursor arrowCursor]; break; + case FL_CURSOR_CROSS: cursor = [NSCursor crosshairCursor]; break; + case FL_CURSOR_INSERT: cursor = [NSCursor IBeamCursor]; break; + case FL_CURSOR_HAND: cursor = [NSCursor pointingHandCursor]; break; + case FL_CURSOR_MOVE: cursor = [NSCursor openHandCursor]; break; + case FL_CURSOR_NS: cursor = [NSCursor resizeUpDownCursor]; break; + case FL_CURSOR_WE: cursor = [NSCursor resizeLeftRightCursor]; break; + case FL_CURSOR_N: cursor = [NSCursor resizeUpCursor]; break; + case FL_CURSOR_E: cursor = [NSCursor resizeRightCursor]; break; + case FL_CURSOR_W: cursor = [NSCursor resizeLeftCursor]; break; + case FL_CURSOR_S: cursor = [NSCursor resizeDownCursor]; break; + default: + return 0; } - [icrsr set]; - cursor = icrsr; + + [(NSCursor*)cursor retain]; + + [(NSWindow*)xid invalidateCursorRectsForView:[(NSWindow*)xid contentView]]; + + return 1; +} + +int Fl_X::set_cursor(const Fl_RGB_Image *image, int hotx, int hoty) { + if (cursor) { + [(NSCursor*)cursor release]; + cursor = NULL; + } + + if ((hotx < 0) || (hotx >= image->w())) + return 0; + if ((hoty < 0) || (hoty >= image->h())) + return 0; + + // OS X >= 10.6 can create a NSImage from a CGImage, but we need to + // support older versions, hence this pesky handling. + + NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:image->w() + pixelsHigh:image->h() + bitsPerSample:8 + samplesPerPixel:image->d() + hasAlpha:!(image->d() & 1) + isPlanar:NO + colorSpaceName:(image->d()<=2) ? NSDeviceWhiteColorSpace : NSDeviceRGBColorSpace + bytesPerRow:(image->w() * image->d()) + bitsPerPixel:(image->d()*8)]; + + // Alpha needs to be premultiplied for this format + + const uchar *i = (const uchar*)*image->data(); + unsigned char *o = [bitmap bitmapData]; + for (int y = 0;y < image->h();y++) { + if (!(image->d() & 1)) { + for (int x = 0;x < image->w();x++) { + unsigned int alpha; + if (image->d() == 4) { + alpha = i[3]; + *o++ = (unsigned char)((unsigned int)*i++ * alpha / 255); + *o++ = (unsigned char)((unsigned int)*i++ * alpha / 255); + } + + alpha = i[1]; + *o++ = (unsigned char)((unsigned int)*i++ * alpha / 255); + *o++ = alpha; + i++; + } + } else { + // No alpha, so we can just copy everything directly. + int len = image->w() * image->d(); + memcpy(o, i, len); + o += len; + i += len; + } + i += image->ld(); + } + + NSImage *nsimage = [[NSImage alloc] + initWithSize:NSMakeSize(image->w(), image->h())]; + + [nsimage addRepresentation:bitmap]; + + cursor = [[NSCursor alloc] + initWithImage:nsimage + hotSpot:NSMakePoint(hotx, hoty)]; + + [(NSWindow*)xid invalidateCursorRectsForView:[(NSWindow*)xid contentView]]; + + [bitmap release]; + [nsimage release]; + + return 1; } @interface FLaboutItemTarget : NSObject @@ -2879,7 +3567,7 @@ void Fl_X::set_cursor(Fl_Cursor c) } //#include <FL/Fl_PostScript.H> - (void)printPanel -{ +{ Fl_Printer printer; //Fl_PostScript_File_Device printer; int w, h, ww, wh; @@ -2920,24 +3608,26 @@ static void createAppleMenu(void) static BOOL donethat = NO; if (donethat) return; donethat = YES; - NSMenu *mainmenu, *services, *appleMenu; + NSMenu *mainmenu, *services = nil, *appleMenu; NSMenuItem *menuItem; NSString *title; - - NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; + + SEL infodictSEL = (fl_mac_os_version >= 100200 ? @selector(localizedInfoDictionary) : @selector(infoDictionary)); + NSString *nsappname = [[[NSBundle mainBundle] performSelector:infodictSEL] objectForKey:@"CFBundleName"]; if (nsappname == nil) nsappname = [[NSProcessInfo processInfo] processName]; appleMenu = [[NSMenu alloc] initWithTitle:@""]; /* Add menu items */ - title = [[NSString stringWithUTF8String:Fl_Mac_App_Menu::about] stringByAppendingString:nsappname]; + title = [NSString stringWithFormat:NSLocalizedString([NSString stringWithUTF8String:Fl_Mac_App_Menu::about],nil), nsappname]; menuItem = [appleMenu addItemWithTitle:title action:@selector(showPanel) keyEquivalent:@""]; FLaboutItemTarget *about = [[FLaboutItemTarget alloc] init]; [menuItem setTarget:about]; [appleMenu addItem:[NSMenuItem separatorItem]]; // Print front window - if (strlen(Fl_Mac_App_Menu::print) > 0) { + title = NSLocalizedString([NSString stringWithUTF8String:Fl_Mac_App_Menu::print], nil); + if ([title length] > 0) { menuItem = [appleMenu - addItemWithTitle:[NSString stringWithUTF8String:Fl_Mac_App_Menu::print] + addItemWithTitle:title action:@selector(printPanel) keyEquivalent:@""]; [menuItem setTarget:about]; @@ -2945,35 +3635,37 @@ static void createAppleMenu(void) [menuItem setEnabled:YES]; [appleMenu addItem:[NSMenuItem separatorItem]]; } - // Services Menu - services = [[NSMenu alloc] init]; - menuItem = [appleMenu - addItemWithTitle:[NSString stringWithUTF8String:Fl_Mac_App_Menu::services] - action:nil - keyEquivalent:@""]; - [appleMenu setSubmenu:services forItem:menuItem]; - [appleMenu addItem:[NSMenuItem separatorItem]]; - // Hide AppName - title = [[NSString stringWithUTF8String:Fl_Mac_App_Menu::hide] stringByAppendingString:nsappname]; - [appleMenu addItemWithTitle:title - action:@selector(hide:) + if (fl_mac_os_version >= 100400) { // services+hide+quit already in menu in OS 10.3 + // Services Menu + services = [[NSMenu alloc] init]; + menuItem = [appleMenu + addItemWithTitle:NSLocalizedString([NSString stringWithUTF8String:Fl_Mac_App_Menu::services], nil) + action:nil + keyEquivalent:@""]; + [appleMenu setSubmenu:services forItem:menuItem]; + [appleMenu addItem:[NSMenuItem separatorItem]]; + // Hide AppName + title = [NSString stringWithFormat:NSLocalizedString([NSString stringWithUTF8String:Fl_Mac_App_Menu::hide],nil), nsappname]; + [appleMenu addItemWithTitle:title + action:@selector(hide:) + keyEquivalent:@"h"]; + // Hide Others + menuItem = [appleMenu + addItemWithTitle:NSLocalizedString([NSString stringWithUTF8String:Fl_Mac_App_Menu::hide_others] , nil) + action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; - // Hide Others - menuItem = [appleMenu - addItemWithTitle:[NSString stringWithUTF8String:Fl_Mac_App_Menu::hide_others] - action:@selector(hideOtherApplications:) - keyEquivalent:@"h"]; - [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; - // Show All - [appleMenu addItemWithTitle:[NSString stringWithUTF8String:Fl_Mac_App_Menu::show] - action:@selector(unhideAllApplications:) keyEquivalent:@""]; - [appleMenu addItem:[NSMenuItem separatorItem]]; - // Quit AppName - title = [[NSString stringWithUTF8String:Fl_Mac_App_Menu::quit] - stringByAppendingString:nsappname]; - [appleMenu addItemWithTitle:title - action:@selector(terminate:) - keyEquivalent:@"q"]; + [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; + // Show All + [appleMenu addItemWithTitle:NSLocalizedString([NSString stringWithUTF8String:Fl_Mac_App_Menu::show] , nil) + action:@selector(unhideAllApplications:) keyEquivalent:@""]; + [appleMenu addItem:[NSMenuItem separatorItem]]; + // Quit AppName + title = [NSString stringWithFormat:NSLocalizedString([NSString stringWithUTF8String:Fl_Mac_App_Menu::quit] , nil), + nsappname]; + [appleMenu addItemWithTitle:title + action:@selector(terminate:) + keyEquivalent:@"q"]; + } /* Put menu into the menubar */ menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [menuItem setSubmenu:appleMenu]; @@ -2984,224 +3676,16 @@ static void createAppleMenu(void) // to avoid compiler warning raised by use of undocumented setAppleMenu : [NSApp performSelector:@selector(setAppleMenu:) withObject:appleMenu]; } - [NSApp setServicesMenu:services]; [NSApp setMainMenu:mainmenu]; - [services release]; + if (services) { + [NSApp setServicesMenu:services]; + [services release]; + } [mainmenu release]; [appleMenu release]; [menuItem release]; } -@interface FLMenuItem : NSMenuItem { -} -- (void) doCallback:(id)unused; -- (void) directCallback:(id)unused; -- (const Fl_Menu_Item*) getFlItem; -@end -@implementation FLMenuItem -- (const Fl_Menu_Item*) getFlItem -{ - return *(const Fl_Menu_Item **)[(NSData*)[self representedObject] bytes]; -} -- (void) doCallback:(id)unused -{ - fl_lock_function(); - const Fl_Menu_Item *item = [self getFlItem]; - fl_sys_menu_bar->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)]; - } - } - fl_unlock_function(); -} -- (void) directCallback:(id)unused -{ - 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(); -} -@end - -void fl_mac_set_about( Fl_Callback *cb, void *user_data, int shortcut) -{ - fl_open_display(); - Fl_Menu_Item aboutItem; - memset(&aboutItem, 0, sizeof(Fl_Menu_Item)); - aboutItem.callback(cb); - aboutItem.user_data(user_data); - aboutItem.shortcut(shortcut); - 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]; - if (aboutItem.shortcut()) { - Fl_Sys_Menu_Bar::doMenuOrItemOperation(Fl_Sys_Menu_Bar::setKeyEquivalent, item, aboutItem.shortcut() & 0xff); - Fl_Sys_Menu_Bar::doMenuOrItemOperation(Fl_Sys_Menu_Bar::setKeyEquivalentModifierMask, item, aboutItem.shortcut() ); - } - NSData *pointer = [NSData dataWithBytes:&aboutItem length:sizeof(Fl_Menu_Item)]; - [item setRepresentedObject:pointer]; - [appleMenu insertItem:item atIndex:0]; - CFRelease(cfname); - [item setTarget:item]; -} - -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; -} - -void *Fl_Sys_Menu_Bar::doMenuOrItemOperation(Fl_Sys_Menu_Bar::menuOrItemOperation operation, ...) -/* these operations apply to menus, submenus, or menu items - */ -{ - NSAutoreleasePool *localPool; - localPool = [[NSAutoreleasePool alloc] init]; - NSMenu *menu; - NSMenuItem *item; - int value; - void *pter; - void *retval = NULL; - va_list ap; - va_start(ap, operation); - - if (operation == Fl_Sys_Menu_Bar::itemAtIndex) { // arguments: NSMenu*, int. Returns the item - menu = va_arg(ap, NSMenu*); - value = va_arg(ap, int); - retval = (void *)[menu itemAtIndex:value]; - } - else if (operation == Fl_Sys_Menu_Bar::setKeyEquivalent) { // arguments: NSMenuItem*, int - item = va_arg(ap, NSMenuItem*); - value = va_arg(ap, int); - char key = value; - NSString *equiv = [[NSString alloc] initWithBytes:&key length:1 encoding:NSASCIIStringEncoding]; - [item setKeyEquivalent:equiv]; - [equiv release]; - } - else if (operation == Fl_Sys_Menu_Bar::setKeyEquivalentModifierMask) { // arguments: NSMenuItem*, int - item = va_arg(ap, NSMenuItem*); - value = va_arg(ap, int); - 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; - [item setKeyEquivalentModifierMask:macMod]; - } - else if (operation == Fl_Sys_Menu_Bar::setState) { // arguments: NSMenuItem*, int - item = va_arg(ap, NSMenuItem*); - value = va_arg(ap, int); - [item setState:(value ? NSOnState : NSOffState)]; - } - else if (operation == Fl_Sys_Menu_Bar::initWithTitle) { // arguments: const char*title. Returns the newly created menu - // creates a new (sub)menu - char *ts = remove_ampersand(va_arg(ap, char *)); - CFStringRef title = CFStringCreateWithCString(NULL, ts, kCFStringEncodingUTF8); - free(ts); - NSMenu *menu = [[NSMenu alloc] initWithTitle:(NSString*)title]; - CFRelease(title); - [menu setAutoenablesItems:NO]; - retval = (void *)menu; - } - else if (operation == Fl_Sys_Menu_Bar::numberOfItems) { // arguments: NSMenu *menu, int *pcount - // upon return, *pcount is set to menu's item count - menu = va_arg(ap, NSMenu*); - pter = va_arg(ap, void *); - *(int*)pter = [menu numberOfItems]; - } - else if (operation == Fl_Sys_Menu_Bar::setSubmenu) { // arguments: NSMenuItem *item, NSMenu *menu - // sets 'menu' as submenu attached to 'item' - item = va_arg(ap, NSMenuItem*); - menu = va_arg(ap, NSMenu*); - [item setSubmenu:menu]; - [menu release]; - } - else if (operation == Fl_Sys_Menu_Bar::setEnabled) { // arguments: NSMenuItem*, int - item = va_arg(ap, NSMenuItem*); - value = va_arg(ap, int); - [item setEnabled:(value ? YES : NO)]; - } - else if (operation == Fl_Sys_Menu_Bar::addSeparatorItem) { // arguments: NSMenu* - menu = va_arg(ap, NSMenu*); - [menu addItem:[NSMenuItem separatorItem]]; - } - else if (operation == Fl_Sys_Menu_Bar::setTitle) { // arguments: NSMenuItem*, const char * - item = va_arg(ap, NSMenuItem*); - char *ts = remove_ampersand(va_arg(ap, char *)); - CFStringRef title = CFStringCreateWithCString(NULL, ts, kCFStringEncodingUTF8); - free(ts); - [item setTitle:(NSString*)title]; - CFRelease(title); - } - else if (operation == Fl_Sys_Menu_Bar::removeItem) { // arguments: NSMenu*, int - menu = va_arg(ap, NSMenu*); - value = va_arg(ap, int); - [menu removeItem:[menu itemAtIndex:value]]; - } - else if (operation == Fl_Sys_Menu_Bar::addNewItem) { // arguments: NSMenu *menu, Fl_Menu_Item* mitem, int *prank - // creates a new menu item at the end of 'menu' - // attaches the item of fl_sys_menu_bar to it - // upon return, puts the rank (counted in NSMenu) of the new item in *prank unless prank is NULL - menu = va_arg(ap, NSMenu*); - Fl_Menu_Item *mitem = va_arg(ap, Fl_Menu_Item *); - int *prank = va_arg(ap, int*); - char *name = remove_ampersand(mitem->label()); - CFStringRef cfname = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8); - free(name); - FLMenuItem *item = [[FLMenuItem alloc] initWithTitle:(NSString*)cfname - action:@selector(doCallback:) - keyEquivalent:@""]; - NSData *pointer = [NSData dataWithBytes:&mitem length:sizeof(Fl_Menu_Item*)]; - [item setRepresentedObject:pointer]; - [menu addItem:item]; - CFRelease(cfname); - [item setTarget:item]; - if (prank != NULL) *prank = [menu indexOfItem:item]; - [item release]; - } - va_end(ap); - [localPool release]; - return retval; -} void Fl_X::set_key_window() { @@ -3215,7 +3699,7 @@ static NSImage *imageFromText(const char *text, int *pwidth, int *pheight) fl_font(FL_HELVETICA, 10); p = text; int nl = 0; - while((q=strchr(p, '\n')) != NULL) { + while(nl < 100 && (q=strchr(p, '\n')) != NULL) { nl++; w2 = int(fl_width(p, q - p)); if (w2 > width) width = w2; @@ -3256,14 +3740,30 @@ static NSImage *imageFromText(const char *text, int *pwidth, int *pheight) static NSImage *defaultDragImage(int *pwidth, int *pheight) { - const int width = 16, height = 16; + const int version_threshold = 100700; + int width, height; + if (fl_mac_os_version >= version_threshold) { + width = 50; height = 40; + } + else { + width = 16; height = 16; + } Fl_Offscreen off = Fl_Quartz_Graphics_Driver::create_offscreen_with_alpha(width, height); fl_begin_offscreen(off); - CGContextSetRGBFillColor( (CGContextRef)off, 0,0,0,0); - fl_rectf(0,0,width,height); - CGContextSetRGBStrokeColor( (CGContextRef)off, 0,0,0,0.6); - fl_rect(0,0,width,height); - fl_rect(2,2,width-4,height-4); + if (fl_mac_os_version >= version_threshold) { + fl_font(FL_HELVETICA, 20); + fl_color(FL_BLACK); + char str[4]; + int l = fl_utf8encode(0x1F69A, str); // the "Delivery truck" Unicode character from "Apple Color Emoji" font + fl_draw(str, l, 1, 16); + } + else { // draw two squares + CGContextSetRGBFillColor( (CGContextRef)off, 0,0,0,0); + fl_rectf(0,0,width,height); + CGContextSetRGBStrokeColor( (CGContextRef)off, 0,0,0,0.6); + fl_rect(0,0,width,height); + fl_rect(2,2,width-4,height-4); + } fl_end_offscreen(); NSImage* image = CGBitmapContextToNSImage( (CGContextRef)off ); fl_delete_offscreen( off ); @@ -3279,16 +3779,11 @@ int Fl::dnd(void) NSAutoreleasePool *localPool; localPool = [[NSAutoreleasePool alloc] init]; NSPasteboard *mypasteboard = [NSPasteboard pasteboardWithName:NSDragPboard]; - [mypasteboard declareTypes:[NSArray arrayWithObjects:@"public.utf8-plain-text", nil] owner:nil]; - [mypasteboard setData:(NSData*)text forType:@"public.utf8-plain-text"]; + [mypasteboard declareTypes:[NSArray arrayWithObject:utf8_format] owner:nil]; + [mypasteboard setData:(NSData*)text forType:utf8_format]; CFRelease(text); Fl_Widget *w = Fl::pushed(); - Fl_Window *win = w->window(); - if (win == NULL) { - win = (Fl_Window*)w; - } else { - while(win->window()) win = win->window(); - } + Fl_Window *win = w->top_window(); NSView *myview = [Fl_X::i(win)->xid contentView]; NSEvent *theEvent = [NSApp currentEvent]; @@ -3319,20 +3814,29 @@ int Fl::dnd(void) } static NSBitmapImageRep* rect_to_NSBitmapImageRep(Fl_Window *win, int x, int y, int w, int h) -// release the returned value after use +// the returned value is autoreleased { + NSRect rect; + NSView *winview = nil; while (win->window()) { x += win->x(); y += win->y(); win = win->window(); } - CGFloat epsilon = 0; - if (fl_mac_os_version >= 100600) epsilon = 0.5; // STR #2887 - // The epsilon offset is absolutely necessary under 10.6. Without it, the top pixel row and - // left pixel column are not read, and bitmap is read shifted by one pixel in both directions. - // Under 10.5, we want no offset. - NSRect rect = NSMakeRect(x - epsilon, y - epsilon, w, h); - return [[NSBitmapImageRep alloc] initWithFocusedViewRect:rect]; + if ( through_drawRect ) { + CGFloat epsilon = 0; + if (fl_mac_os_version >= 100600) epsilon = 0.5; // STR #2887 + rect = NSMakeRect(x - epsilon, y - epsilon, w, h); + } + else { + rect = NSMakeRect(x, win->h()-(y+h), w, h); + // lock focus to win's view + winview = [fl_xid(win) contentView]; + [winview lockFocus]; + } + NSBitmapImageRep *bitmap = [[[NSBitmapImageRep alloc] initWithFocusedViewRect:rect] autorelease]; + if ( !through_drawRect ) [winview unlockFocus]; + return bitmap; } unsigned char *Fl_X::bitmap_from_window_rect(Fl_Window *win, int x, int y, int w, int h, int *bytesPerPixel) @@ -3344,24 +3848,38 @@ unsigned char *Fl_X::bitmap_from_window_rect(Fl_Window *win, int x, int y, int w */ { NSBitmapImageRep *bitmap = rect_to_NSBitmapImageRep(win, x, y, w, h); + if (bitmap == nil) return NULL; *bytesPerPixel = [bitmap bitsPerPixel]/8; int bpp = (int)[bitmap bytesPerPlane]; int bpr = (int)[bitmap bytesPerRow]; - int hh = bpp/bpr; // sometimes hh = h-1 for unclear reason - int ww = bpr/(*bytesPerPixel); // sometimes ww = w-1 - unsigned char *data = new unsigned char[w * h * *bytesPerPixel]; - if (w == ww) { - memcpy(data, [bitmap bitmapData], w * hh * *bytesPerPixel); - } else { - unsigned char *p = [bitmap bitmapData]; - unsigned char *q = data; - for(int i = 0;i < hh; i++) { - memcpy(q, p, *bytesPerPixel * ww); - p += bpr; - q += w * *bytesPerPixel; + int hh = bpp/bpr; // sometimes hh = h-1 for unclear reason, and hh = 2*h with retina + int ww = bpr/(*bytesPerPixel); // sometimes ww = w-1, and ww = 2*w with retina + unsigned char *data; + if (ww > w) { // with a retina display + Fl_RGB_Image *rgb = new Fl_RGB_Image([bitmap bitmapData], ww, hh, 4); + Fl_RGB_Scaling save_scaling = Fl_Image::RGB_scaling(); + Fl_Image::RGB_scaling(FL_RGB_SCALING_BILINEAR); + Fl_RGB_Image *rgb2 = (Fl_RGB_Image*)rgb->copy(w, h); + Fl_Image::RGB_scaling(save_scaling); + delete rgb; + rgb2->alloc_array = 0; + data = (uchar*)rgb2->array; + delete rgb2; + } + else { + data = new unsigned char[w * h * *bytesPerPixel]; + if (w == ww) { + memcpy(data, [bitmap bitmapData], w * hh * *bytesPerPixel); + } else { + unsigned char *p = [bitmap bitmapData]; + unsigned char *q = data; + for(int i = 0;i < hh; i++) { + memcpy(q, p, *bytesPerPixel * ww); + p += bpr; + q += w * *bytesPerPixel; } + } } - [bitmap release]; return data; } @@ -3376,13 +3894,13 @@ CGImageRef Fl_X::CGImage_from_window_rect(Fl_Window *win, int x, int y, int w, i CGImageRef img; if (fl_mac_os_version >= 100500) { NSBitmapImageRep *bitmap = rect_to_NSBitmapImageRep(win, x, y, w, h); - img = [bitmap CGImage]; // requires Mac OS 10.5 + img = (CGImageRef)[bitmap performSelector:@selector(CGImage)]; // requires Mac OS 10.5 CGImageRetain(img); - [bitmap release]; } else { int bpp; unsigned char *bitmap = bitmap_from_window_rect(win, x, y, w, h, &bpp); + if (!bitmap) return NULL; CGColorSpaceRef lut = CGColorSpaceCreateDeviceRGB(); CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, bitmap, w*h*bpp, imgProviderReleaseData); img = CGImageCreate(w, h, 8, 8*bpp, w*bpp, lut, @@ -3401,7 +3919,7 @@ WindowRef Fl_X::window_ref() // so a CGRect matches exactly what is denoted x,y,w,h for clipping purposes CGRect fl_cgrectmake_cocoa(int x, int y, int w, int h) { - return CGRectMake(x, y, w > 0 ? w - 0.9 : 0, h > 0 ? h - 0.9 : 0); + return CGRectMake(x - 0.5, y - 0.5, w, h); } Window fl_xid(const Fl_Window* w) @@ -3428,30 +3946,110 @@ int Fl_Window::decorated_h() void Fl_Paged_Device::print_window(Fl_Window *win, int x_offset, int y_offset) { + NSButton *close = nil, *miniaturize = nil, *zoom = nil; if (!win->shown() || win->parent() || !win->border() || !win->visible()) { this->print_widget(win, x_offset, y_offset); return; } - int bx, by, bt; + int bx, by, bt, bpp; get_window_frame_sizes(bx, by, bt); Fl_Display_Device::display_device()->set_current(); // send win to front and make it current + const char *title = win->label(); + win->label(""); // temporarily set a void window title win->show(); + if (fl_mac_os_version >= 101000) { + // if linked for OS 10.10, capture of title bar does not capture the title bar buttons + // so we draw them in FLTK + NSWindow *xid = fl_xid(win); + close = [xid standardWindowButton:NSWindowCloseButton]; // 10.2 + miniaturize = [xid standardWindowButton:NSWindowMiniaturizeButton]; + zoom = [xid standardWindowButton:NSWindowZoomButton]; + [close setHidden:YES]; // 10.3 + [miniaturize setHidden:YES]; + [zoom setHidden:YES]; + } fl_gc = NULL; Fl::check(); - win->make_current(); + BOOL to_quartz = dynamic_cast<Fl_Printer*>(this) != NULL; + // capture the window title bar with no title + CGImageRef img = NULL; + unsigned char *bitmap = NULL; + if (to_quartz) + img = Fl_X::CGImage_from_window_rect(win, 0, -bt, win->w(), bt); + else + bitmap = Fl_X::bitmap_from_window_rect(win, 0, -bt, win->w(), bt, &bpp); + win->label(title); // put back the window title this->set_current(); // back to the Fl_Paged_Device - // capture the window title bar - CGImageRef img = Fl_X::CGImage_from_window_rect(win, 0, -bt, win->w(), bt); - // and print it - CGRect rect = { { x_offset, y_offset }, { win->w(), bt } }; - Fl_X::q_begin_image(rect, 0, 0, win->w(), bt); - CGContextDrawImage(fl_gc, rect, img); - Fl_X::q_end_image(); - CFRelease(img); + if (img && to_quartz) { // print the title bar + CGRect rect = { { x_offset, y_offset }, { win->w(), bt } }; + Fl_X::q_begin_image(rect, 0, 0, win->w(), bt); + CGContextDrawImage(fl_gc, rect, img); + Fl_X::q_end_image(); + CFRelease(img); + } + else if(!to_quartz) { + Fl_RGB_Image *rgb = new Fl_RGB_Image(bitmap, win->w(), bt, bpp); + rgb->draw(x_offset, y_offset); + delete rgb; + delete[] bitmap; + } + if (fl_mac_os_version >= 101000) { // print the title bar buttons + Fl_Color inactive = fl_rgb_color((uchar)0xCE, (uchar)0xCE, (uchar)0xCE); // inactive button color + Fl_Color redish, yellowish, greenish; + if ([[NSUserDefaults standardUserDefaults] integerForKey:@"AppleAquaColorVariant"] == 6) { // graphite appearance + redish = yellowish = greenish = fl_rgb_color((uchar)0x8C, (uchar)0x8C, (uchar)0x8C); + } + else { + redish = fl_rgb_color((uchar)0xFF, (uchar)0x63, (uchar)0x5A); + yellowish = fl_rgb_color((uchar)0xFF, (uchar)0xC6, (uchar)0x42); + greenish = fl_rgb_color((uchar)0x29, (uchar)0xD6, (uchar)0x52); + } + + if (![close isEnabled]) fl_color(inactive); else fl_color(redish); + fl_pie(x_offset+8, y_offset+5, 12, 12, 0, 360); + if (![miniaturize isEnabled]) fl_color(inactive); else fl_color(yellowish); + fl_pie(x_offset+28, y_offset+5, 12, 12, 0, 360); + if (![zoom isEnabled]) fl_color(inactive); else fl_color(greenish); + fl_pie(x_offset+48, y_offset+5, 12, 12, 0, 360); + + [close setHidden:NO]; // 10.3 + [miniaturize setHidden:NO]; + [zoom setHidden:NO]; + } + if (title) { // print the window title + const int skip = 65; // approx width of the zone of the 3 window control buttons +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 + if (fl_mac_os_version >= 100400 && to_quartz) { // use Cocoa string drawing with exact title bar font + // the exact font is LucidaGrande 13 pts (and HelveticaNeueDeskInterface-Regular with 10.10) + NSGraphicsContext *current = [NSGraphicsContext currentContext]; + [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:fl_gc flipped:YES]];//10.4 + NSDictionary *attr = [NSDictionary dictionaryWithObject:[NSFont titleBarFontOfSize:0] + forKey:NSFontAttributeName]; + NSString *title_s = [fl_xid(win) title]; + NSSize size = [title_s sizeWithAttributes:attr]; + int x = x_offset + win->w()/2 - size.width/2; + if (x < x_offset+skip) x = x_offset+skip; + NSRect r = {{x, y_offset+bt/2+4}, {win->w() - skip, bt}}; + [[NSGraphicsContext currentContext] setShouldAntialias:YES]; + [title_s drawWithRect:r options:(NSStringDrawingOptions)0 attributes:attr]; // 10.4 + [[NSGraphicsContext currentContext] setShouldAntialias:NO]; + [NSGraphicsContext setCurrentContext:current]; + } + else +#endif + { + fl_font(FL_HELVETICA, 14); + fl_color(FL_BLACK); + int x = x_offset + win->w()/2 - fl_width(title)/2; + if (x < x_offset+skip) x = x_offset+skip; + fl_push_clip(x_offset, y_offset, win->w(), bt); + fl_draw(title, x, y_offset+bt/2+4); + fl_pop_clip(); + } + } this->print_widget(win, x_offset, y_offset + bt); // print the window inner part } -#include <dlfcn.h> /* Returns the address of a Carbon function after dynamically loading the Carbon library if needed. Supports old Mac OS X versions that may use a couple of Carbon calls: @@ -3476,9 +4074,20 @@ void *Fl_X::get_carbon_function(const char *function_name) { static int calc_mac_os_version() { int M, m, b = 0; NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init]; - NSDictionary * sv = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]; - const char *s = [[sv objectForKey:@"ProductVersion"] UTF8String]; - sscanf(s, "%d.%d.%d", &M, &m, &b); +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 + if ([NSProcessInfo instancesRespondToSelector:@selector(operatingSystemVersion)]) { + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; + M = version.majorVersion; + m = version.minorVersion; + b = version.patchVersion; + } + else +#endif + { + NSDictionary * sv = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]; + const char *s = [[sv objectForKey:@"ProductVersion"] UTF8String]; + sscanf(s, "%d.%d.%d", &M, &m, &b); + } [localPool release]; return M*10000 + m*100 + b; } @@ -3486,5 +4095,5 @@ static int calc_mac_os_version() { #endif // __APPLE__ // -// End of "$Id: Fl_cocoa.mm 9734 2012-11-30 18:20:36Z manolo $". +// End of "$Id: Fl_cocoa.mm 10427 2014-11-02 21:06:07Z manolo $". // |