Apply our FLTK extensions
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4605 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/common/fltk/src/Fl_cocoa.mm b/common/fltk/src/Fl_cocoa.mm
index c113564..5fd52d6 100644
--- a/common/fltk/src/Fl_cocoa.mm
+++ b/common/fltk/src/Fl_cocoa.mm
@@ -61,6 +61,7 @@
#include <stdarg.h>
#import <Cocoa/Cocoa.h>
+#import <Carbon/Carbon.h>
#ifndef NSINTEGER_DEFINED // appears with 10.5 in NSObjCRuntime.h
#if defined(__LP64__) && __LP64__
@@ -107,7 +108,6 @@
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
@@ -124,6 +124,8 @@
extern Fl_Window* fl_xmousewin;
#endif
+bool use_simple_keyboard = false;
+
enum { FLTKTimerEvent = 1, FLTKDataReadyEvent };
@@ -140,6 +142,39 @@
{
}
+// Undocumented voodoo. Taken from Mozilla.
+#define ENABLE_ROMAN_KYBDS_ONLY -23
+
+void fl_update_focus(void)
+{
+ Fl_Widget *focus;
+
+ focus = Fl::grab();
+ if (!focus)
+ focus = Fl::focus();
+ if (!focus)
+ return;
+
+ if (focus->simple_keyboard())
+ use_simple_keyboard = true;
+ else
+ use_simple_keyboard = false;
+
+ // 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...
+#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
+ CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
+ TSMSetDocumentProperty(TSMGetActiveDocument(),
+ kTSMDocumentEnabledInputSourcesPropertyTag,
+ sizeof(CFArrayRef), &inputSources);
+ CFRelease(inputSources);
+#else
+ KeyScript(use_simple_keyboard ? ENABLE_ROMAN_KYBDS_ONLY : smKeyEnableKybds);
+#endif
+}
+
/*
* Mac keyboard lookup table
* See also the inverse converter vktab in Fl_get_key_mac.cxx
@@ -614,6 +649,10 @@
{
containsGLsubwindow = contains;
}
+- (BOOL)canBecomeKeyWindow
+{
+ return YES;
+}
@end
@interface FLApplication : NSObject
@@ -843,6 +882,25 @@
}
@end
+static const char* cocoaDead2FLTK(const char *in)
+{
+ if (strcmp(in, "\140") == 0) // GRAVE ACCENT
+ return "\314\200"; // COMBINING GRAVE ACCENT
+ if (strcmp(in, "\302\264") == 0) // ACUTE ACCENT
+ return "\314\201"; // COMBINING ACUTE ACCENT
+ if (strcmp(in, "\136") == 0) // CIRCUMFLEX ACCENT
+ return "\314\202"; // COMBINING CIRCUMFLEX ACCENT
+ if (strcmp(in, "\176") == 0) // TILDE
+ return "\314\203"; // COMBINING TILDE
+ if (strcmp(in, "\302\250") == 0) // DIAERESIS
+ return "\314\210"; // COMBINING DIAERESIS
+ // FIXME: OS X dead key behaviour isn't documented and I don't have
+ // any more keyboards to test with...
+
+ // hope that OS X gave us something proper to begin with
+ return in;
+}
+
/*
Handle cocoa keyboard events
Events during a character composition sequence:
@@ -1033,6 +1091,10 @@
fl_lock_function();
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())
+ [nsw setLevel:NSNormalWindowLevel];
Fl::handle( FL_UNFOCUS, window);
fl_unlock_function();
}
@@ -1041,6 +1103,9 @@
fl_lock_function();
FLWindow *nsw = (FLWindow*)[notif object];
Fl_Window *w = [nsw getFl_Window];
+ /* Restore previous fullscreen level */
+ if (w->fullscreen_active())
+ [nsw setLevel:NSStatusWindowLevel];
if ( w->border() || (!w->modal() && !w->tooltip_window()) ) Fl::handle( FL_FOCUS, w);
fl_unlock_function();
}
@@ -1228,9 +1293,13 @@
}
@end
+static void clipboard_check(void);
+
@implementation FLApplication
+ (void)sendEvent:(NSEvent *)theEvent
{
+ // update clipboard status
+ clipboard_check();
NSEventType type = [theEvent type];
if (type == NSLeftMouseDown) {
fl_lock_function();
@@ -1285,8 +1354,6 @@
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;
@@ -1586,6 +1653,7 @@
- (void)drawRect:(NSRect)rect;
- (BOOL)acceptsFirstResponder;
- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent;
+- (void)resetCursorRects;
- (BOOL)performKeyEquivalent:(NSEvent*)theEvent;
- (void)mouseUp:(NSEvent *)theEvent;
- (void)rightMouseUp:(NSEvent *)theEvent;
@@ -1643,6 +1711,16 @@
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);
+ // 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);
}
@@ -1702,8 +1780,13 @@
break;
}
}
+ // Don't send cmd-<key> to interpretKeyEvents because it beeps.
if (!no_text_key && !(Fl::e_state & FL_META) ) {
- // Don't send cmd-<key> to interpretKeyEvents because it beeps.
+ // The simple keyboard model will ignore insertText, so we need to grab
+ // the symbol directly from the event. Note that we still use setMarkedText.
+ if (use_simple_keyboard)
+ [FLView prepareEtext:[theEvent charactersIgnoringModifiers]];
+
// 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];
@@ -1882,21 +1965,30 @@
//NSLog(@"insertText: received=%@",received);
if (!in_key_event) fl_lock_function();
+
+ // Simple keyboard widgets do not want these side channel inputs.
+ if (use_simple_keyboard)
+ goto end;
+
[FLView prepareEtext:received];
+
// We can get called outside of key events (e.g. from the character
- // palette). Transform such actions to FL_PASTE events.
+ // palette). We need to fake our own key event at that point.
if (!in_key_event) {
Fl_Window *target = [(FLWindow*)[self window] getFl_Window];
- Fl::handle(FL_PASTE, target);
+ Fl::e_keysym = Fl::e_original_keysym = 0;
+ Fl::handle(FL_KEYDOWN, 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();
}
+
+end:
if (!in_key_event) fl_unlock_function();
}
- (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection {
- NSString *received;
+ NSString *received, *current, *aggregate;
if (newSelection.location == 0) {
[self unmarkText];
return;
@@ -1907,11 +1999,47 @@
received = (NSString*)aString;
}
//NSLog(@"setMarkedText: %@ %d %d",received,newSelection.location,newSelection.length);
+
+ fl_lock_function();
+
+ // Simple keyboard widgets generally do not want these side channel
+ // inputs, but we have no other way of getting dead keys so we make
+ // an exception in that case.
+ if (use_simple_keyboard) {
+ if (in_key_event && (Fl::e_length == 0)) {
+ [FLView prepareEtext:received];
+
+ Fl::e_text = (char*)cocoaDead2FLTK(Fl::e_text);
+ Fl::e_length = strlen(Fl::e_text);
+ }
+ goto end;
+ }
+
// This code creates the OS X behaviour of seeing dead keys as things
// are being composed.
+ //
+ // Note: The concatenation thing is because of how OS X deals with
+ // invalid sequences. At that point it will spit out one call
+ // to insertText with the now aborted sequence, and one new
+ // call to setMarkedText with the new sequence. Since we want
+ // both to be visible, we need to concatenate.
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);
+ current = [NSString stringWithUTF8String:Fl::e_text];
+ aggregate = [current stringByAppendingString:received];
+
+ [FLView prepareEtext:aggregate];
+ //NSLog(@"Fl::e_text=%@ Fl::e_length=%d next_compose_length=%d", aggregate, Fl::e_length, next_compose_length);
+
+ // We can get called outside of key events (e.g. from the character
+ // palette). We need to fake our own key event at that point.
+ if (!in_key_event) {
+ Fl_Window *target = [(FLWindow*)[self window] getFl_Window];
+ Fl::e_keysym = Fl::e_original_keysym = 0;
+ Fl::handle(FL_KEYDOWN, target);
+ }
+
+end:
+ fl_unlock_function();
}
- (void)unmarkText {
@@ -1981,6 +2109,22 @@
@end
+void fullscreen_x(Fl_Window *w) {
+ w->_set_fullscreen();
+ /* On OS X < 10.6, it is necessary to recreate the window. This is done
+ with hide+show. */
+ w->hide();
+ w->show();
+ Fl::handle(FL_FULLSCREEN, w);
+}
+
+void fullscreen_off_x(Fl_Window *w, int X, int Y, int W, int H) {
+ w->_clear_fullscreen();
+ w->hide();
+ w->resize(X, Y, W, H);
+ w->show();
+ Fl::handle(FL_FULLSCREEN, w);
+}
/*
* go ahead, create that (sub)window
@@ -1996,7 +2140,7 @@
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
Fl_Window *win = w->window();
Fl_X *xo = Fl_X::i(win);
@@ -2098,12 +2242,19 @@
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 srect = [[NSScreen mainScreen] frame];
+ if (w->flags() & Fl_Widget::FULLSCREEN) {
+ 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);
+ winstyle = NSBorderlessWindowMask;
+ winlevel = NSStatusWindowLevel;
+ }
NSRect crect;
crect.origin.x = w->x();
crect.origin.y = srect.size.height - (w->y() + w->h());
@@ -2552,6 +2703,27 @@
receiver.handle(FL_PASTE);
}
+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;
+
+ allocatePasteboard();
+ flags = PasteboardSynchronize(myPasteboard);
+
+ if (!(flags & kPasteboardModified))
+ return;
+ if (flags & kPasteboardClientIsOwner)
+ return;
+
+ fl_trigger_clipboard_notify(1);
+}
+
void Fl::add_timeout(double time, Fl_Timeout_Handler cb, void* data)
{
// check, if this timer slot exists already
@@ -2676,6 +2848,10 @@
[[(NSWindow *)xid contentView] release];
[(NSWindow *)xid close];
}
+ if (cursor) {
+ [(NSCursor*)cursor release];
+ cursor = NULL;
+ }
}
void Fl_X::map() {
@@ -2791,68 +2967,106 @@
return [image autorelease];
}
-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;
+
+ switch (c) {
+ 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;
+ }
+
+ [(NSCursor*)cursor retain];
+
+ [(NSWindow*)xid invalidateCursorRectsForView:[(NSWindow*)xid contentView]];
+
+ return 1;
}
-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;
+int Fl_X::set_cursor(const Fl_RGB_Image *image, int hotx, int hoty) {
+ if (cursor) {
+ [(NSCursor*)cursor release];
+ cursor = NULL;
}
- [icrsr set];
- cursor = icrsr;
+
+ 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