blob: 4168f26dd665faf5326c0dd344d1094463ba44df [file] [log] [blame]
DRC2ff39b82011-07-28 08:38:59 +00001//
2// "$Id: Fl_cocoa.mm 8807 2011-06-16 12:35:32Z manolo $"
3//
4// MacOS-Cocoa specific code for the Fast Light Tool Kit (FLTK).
5//
6// Copyright 1998-2011 by Bill Spitzak and others.
7//
8// This library is free software; you can redistribute it and/or
9// modify it under the terms of the GNU Library General Public
10// License as published by the Free Software Foundation; either
11// version 2 of the License, or (at your option) any later version.
12//
13// This library is distributed in the hope that it will be useful,
14// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16// Library General Public License for more details.
17//
18// You should have received a copy of the GNU Library General Public
19// License along with this library; if not, write to the Free Software
20// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21// USA.
22//
23// Please report all bugs and problems on the following page:
24//
25// http://www.fltk.org/str.php
26//
27
28//// From the inner edge of a MetroWerks CodeWarrior CD:
29// (without permission)
30//
31// "Three Compiles for 68Ks under the sky,
32// Seven Compiles for PPCs in their fragments of code,
33// Nine Compiles for Mortal Carbon doomed to die,
34// One Compile for Mach-O Cocoa on its Mach-O throne,
35// in the Land of MacOS X where the Drop-Shadows lie.
36//
37// One Compile to link them all, One Compile to merge them,
38// One Compile to copy them all and in the bundle bind them,
39// in the Land of MacOS X where the Drop-Shadows lie."
40
41#ifdef __APPLE__
42
43#define CONSOLIDATE_MOTION 0
44extern "C" {
45#include <pthread.h>
46}
47
48
49#include <FL/Fl.H>
50#include <FL/x.H>
51#include <FL/Fl_Window.H>
52#include <FL/Fl_Tooltip.H>
53#include <FL/Fl_Sys_Menu_Bar.H>
54#include <FL/Fl_Printer.H>
55#include <FL/Fl_Input_.H>
56#include <FL/Fl_Text_Display.H>
57#include <stdio.h>
58#include <stdlib.h>
59#include "flstring.h"
60#include <unistd.h>
61#include <stdarg.h>
62
63#import <Cocoa/Cocoa.h>
DRC685f17e2011-07-28 09:23:00 +000064#import <Carbon/Carbon.h>
DRC2ff39b82011-07-28 08:38:59 +000065
66#ifndef NSINTEGER_DEFINED // appears with 10.5 in NSObjCRuntime.h
67#if defined(__LP64__) && __LP64__
68typedef long NSInteger;
69typedef unsigned long NSUInteger;
70#else
71typedef long NSInteger;
72typedef unsigned int NSUInteger;
73#endif
74#endif
75
76
77// #define DEBUG_SELECT // UNCOMMENT FOR SELECT()/THREAD DEBUGGING
78#ifdef DEBUG_SELECT
79#include <stdio.h> // testing
80#define DEBUGMSG(msg) if ( msg ) fprintf(stderr, msg);
81#define DEBUGPERRORMSG(msg) if ( msg ) perror(msg)
82#define DEBUGTEXT(txt) txt
83#else
84#define DEBUGMSG(msg)
85#define DEBUGPERRORMSG(msg)
86#define DEBUGTEXT(txt) NULL
87#endif /*DEBUG_SELECT*/
88
89// external functions
90extern void fl_fix_focus();
91extern Fl_Offscreen fl_create_offscreen_with_alpha(int w, int h);
92
93// forward definition of functions in this file
94// converting cr lf converter function
95static void convert_crlf(char * string, size_t len);
96static void createAppleMenu(void);
97static Fl_Region MacRegionMinusRect(Fl_Region r, int x,int y,int w,int h);
98static void cocoaMouseHandler(NSEvent *theEvent);
99
100static Fl_Quartz_Graphics_Driver fl_quartz_driver;
101static Fl_Display_Device fl_quartz_display(&fl_quartz_driver);
102FL_EXPORT Fl_Graphics_Driver *fl_graphics_driver = (Fl_Graphics_Driver*)&fl_quartz_driver; // the current target device of graphics operations
103Fl_Surface_Device* Fl_Surface_Device::_surface = (Fl_Surface_Device*)&fl_quartz_display; // the current target surface of graphics operations
104Fl_Display_Device *Fl_Display_Device::_display = &fl_quartz_display; // the platform display
105
106// public variables
107int fl_screen;
108CGContextRef fl_gc = 0;
109void *fl_system_menu; // this is really a NSMenu*
110Fl_Sys_Menu_Bar *fl_sys_menu_bar = 0;
DRC2ff39b82011-07-28 08:38:59 +0000111void *fl_capture = 0; // (NSWindow*) we need this to compensate for a missing(?) mouse capture
112bool fl_show_iconic; // true if called from iconize() - shows the next created window in collapsed state
113//int fl_disable_transient_for; // secret method of removing TRANSIENT_FOR
114Window fl_window;
115Fl_Window *Fl_Window::current_;
116int fl_mac_os_version = 0; // the version number of the running Mac OS X (e.g., 100604 for 10.6.4)
117
118// forward declarations of variables in this file
119static int got_events = 0;
120static Fl_Window* resize_from_system;
121
122#if CONSOLIDATE_MOTION
123static Fl_Window* send_motion;
124extern Fl_Window* fl_xmousewin;
125#endif
126
DRC685f17e2011-07-28 09:23:00 +0000127bool use_simple_keyboard = false;
128
DRC2ff39b82011-07-28 08:38:59 +0000129enum { FLTKTimerEvent = 1, FLTKDataReadyEvent };
130
131
132/* fltk-utf8 placekeepers */
133void fl_reset_spot()
134{
135}
136
137void fl_set_spot(int font, int size, int X, int Y, int W, int H, Fl_Window *win)
138{
139}
140
141void fl_set_status(int x, int y, int w, int h)
142{
143}
144
DRC685f17e2011-07-28 09:23:00 +0000145// Undocumented voodoo. Taken from Mozilla.
146#define ENABLE_ROMAN_KYBDS_ONLY -23
147
148void fl_update_focus(void)
149{
150 Fl_Widget *focus;
151
152 focus = Fl::grab();
153 if (!focus)
154 focus = Fl::focus();
155 if (!focus)
156 return;
157
158 if (focus->simple_keyboard())
159 use_simple_keyboard = true;
160 else
161 use_simple_keyboard = false;
162
163 // Force a "Roman" or "ASCII" keyboard, which both the Mozilla and
164 // Safari people seem to think implies turning off advanced IME stuff
165 // (see nsTSMManager::SyncKeyScript in Mozilla and enableSecureTextInput
166 // in Safari/Webcore). Should be good enough for us then...
167#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
168 CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
169 TSMSetDocumentProperty(TSMGetActiveDocument(),
170 kTSMDocumentEnabledInputSourcesPropertyTag,
171 sizeof(CFArrayRef), &inputSources);
172 CFRelease(inputSources);
173#else
174 KeyScript(use_simple_keyboard ? ENABLE_ROMAN_KYBDS_ONLY : smKeyEnableKybds);
175#endif
176}
177
DRC2ff39b82011-07-28 08:38:59 +0000178/*
179 * Mac keyboard lookup table
180 * See also the inverse converter vktab in Fl_get_key_mac.cxx
181 */
182static unsigned short macKeyLookUp[128] =
183{
184 'a', 's', 'd', 'f', 'h', 'g', 'z', 'x',
185 'c', 'v', '^', 'b', 'q', 'w', 'e', 'r',
186
187 'y', 't', '1', '2', '3', '4', '6', '5',
188 '=', '9', '7', '-', '8', '0', ']', 'o',
189
190 'u', '[', 'i', 'p', FL_Enter, 'l', 'j', '\'',
191 'k', ';', '\\', ',', '/', 'n', 'm', '.',
192
193 FL_Tab, ' ', '`', FL_BackSpace,
194 FL_KP_Enter, FL_Escape, FL_Meta_R, FL_Meta_L,
195 FL_Shift_L, FL_Caps_Lock, FL_Alt_L, FL_Control_L,
196 FL_Shift_R, FL_Alt_R, FL_Control_R, 0/*FL_F*/,
197
198 0, FL_KP+'.', FL_Right, FL_KP+'*', 0, FL_KP+'+', FL_Left, FL_Num_Lock,
199 FL_Down, 0, 0, FL_KP+'/', FL_KP_Enter, FL_Up, FL_KP+'-', 0,
200
201 0, FL_KP+'=', FL_KP+'0', FL_KP+'1', FL_KP+'2', FL_KP+'3', FL_KP+'4', FL_KP+'5',
202 FL_KP+'6', FL_KP+'7', 0, FL_KP+'8', FL_KP+'9', 0, 0, 0,
203
204 FL_F+5, FL_F+6, FL_F+7, FL_F+3, FL_F+8, FL_F+9, 0, FL_F+11,
205 0, FL_F+13, FL_F+16, FL_F+14, 0, FL_F+10, FL_Menu, FL_F+12,
206
207 0, FL_F+15, FL_Help, FL_Home, FL_Page_Up, FL_Delete, FL_F+4, FL_End,
208 FL_F+2, FL_Page_Down, FL_F+1, FL_Left, FL_Right, FL_Down, FL_Up, 0/*FL_Power*/,
209};
210
211/*
212 * convert the current mouse chord into the FLTK modifier state
213 */
214static unsigned int mods_to_e_state( NSUInteger mods )
215{
216 long state = 0;
217 if ( mods & NSCommandKeyMask ) state |= FL_META;
218 if ( mods & NSAlternateKeyMask ) state |= FL_ALT;
219 if ( mods & NSControlKeyMask ) state |= FL_CTRL;
220 if ( mods & NSShiftKeyMask ) state |= FL_SHIFT;
221 if ( mods & NSAlphaShiftKeyMask ) state |= FL_CAPS_LOCK;
222 unsigned int ret = ( Fl::e_state & 0xff000000 ) | state;
223 Fl::e_state = ret;
224 //printf( "State 0x%08x (%04x)\n", Fl::e_state, mods );
225 return ret;
226}
227
228// these pointers are set by the Fl::lock() function:
229static void nothing() {}
230void (*fl_lock_function)() = nothing;
231void (*fl_unlock_function)() = nothing;
232
233//
234// Select interface -- how it's implemented:
235// When the user app configures one or more file descriptors to monitor
236// with Fl::add_fd(), we start a separate thread to select() the data,
237// sending a custom OSX 'FLTK data ready event' to the parent thread's
238// RunApplicationLoop(), so that it triggers the data ready callbacks
239// in the parent thread. -erco 04/04/04
240//
241#define POLLIN 1
242#define POLLOUT 4
243#define POLLERR 8
244
245// Class to handle select() 'data ready'
246class DataReady
247{
248 struct FD
249 {
250 int fd;
251 short events;
252 void (*cb)(int, void*);
253 void* arg;
254 };
255 int nfds, fd_array_size;
256 FD *fds;
257 pthread_t tid; // select()'s thread id
258
259 // Data that needs to be locked (all start with '_')
260 pthread_mutex_t _datalock; // data lock
261 fd_set _fdsets[3]; // r/w/x sets user wants to monitor
262 int _maxfd; // max fd count to monitor
263 int _cancelpipe[2]; // pipe used to help cancel thread
264
265public:
266 DataReady()
267 {
268 nfds = 0;
269 fd_array_size = 0;
270 fds = 0;
271 tid = 0;
272
273 pthread_mutex_init(&_datalock, NULL);
274 FD_ZERO(&_fdsets[0]); FD_ZERO(&_fdsets[1]); FD_ZERO(&_fdsets[2]);
275 _cancelpipe[0] = _cancelpipe[1] = 0;
276 _maxfd = -1;
277 }
278
279 ~DataReady()
280 {
281 CancelThread(DEBUGTEXT("DESTRUCTOR\n"));
282 if (fds) { free(fds); fds = 0; }
283 nfds = 0;
284 }
285
286 // Locks
287 // The convention for locks: volatile vars start with '_',
288 // and must be locked before use. Locked code is prefixed
289 // with /*LOCK*/ to make painfully obvious esp. in debuggers. -erco
290 //
291 void DataLock() { pthread_mutex_lock(&_datalock); }
292 void DataUnlock() { pthread_mutex_unlock(&_datalock); }
293
294 // Accessors
295 int IsThreadRunning() { return(tid ? 1 : 0); }
296 int GetNfds() { return(nfds); }
297 int GetCancelPipe(int ix) { return(_cancelpipe[ix]); }
298 fd_set GetFdset(int ix) { return(_fdsets[ix]); }
299
300 // Methods
301 void AddFD(int n, int events, void (*cb)(int, void*), void *v);
302 void RemoveFD(int n, int events);
303 int CheckData(fd_set& r, fd_set& w, fd_set& x);
304 void HandleData(fd_set& r, fd_set& w, fd_set& x);
305 static void* DataReadyThread(void *self);
306 void StartThread(void);
307 void CancelThread(const char *reason);
308};
309
310static DataReady dataready;
311
312void DataReady::AddFD(int n, int events, void (*cb)(int, void*), void *v)
313{
314 RemoveFD(n, events);
315 int i = nfds++;
316 if (i >= fd_array_size)
317 {
318 fl_open_display(); // necessary for NSApp to be defined and the event loop to work
319 FD *temp;
320 fd_array_size = 2*fd_array_size+1;
321 if (!fds) { temp = (FD*)malloc(fd_array_size*sizeof(FD)); }
322 else { temp = (FD*)realloc(fds, fd_array_size*sizeof(FD)); }
323 if (!temp) return;
324 fds = temp;
325 }
326 fds[i].cb = cb;
327 fds[i].arg = v;
328 fds[i].fd = n;
329 fds[i].events = events;
330 DataLock();
331 /*LOCK*/ if (events & POLLIN) FD_SET(n, &_fdsets[0]);
332 /*LOCK*/ if (events & POLLOUT) FD_SET(n, &_fdsets[1]);
333 /*LOCK*/ if (events & POLLERR) FD_SET(n, &_fdsets[2]);
334 /*LOCK*/ if (n > _maxfd) _maxfd = n;
335 DataUnlock();
336}
337
338// Remove an FD from the array
339void DataReady::RemoveFD(int n, int events)
340{
341 int i,j;
342 _maxfd = -1; // recalculate maxfd on the fly
343 for (i=j=0; i<nfds; i++) {
344 if (fds[i].fd == n) {
345 int e = fds[i].events & ~events;
346 if (!e) continue; // if no events left, delete this fd
347 fds[i].events = e;
348 }
349 if (fds[i].fd > _maxfd) _maxfd = fds[i].fd;
350 // move it down in the array if necessary:
351 if (j<i) {
352 fds[j] = fds[i];
353 }
354 j++;
355 }
356 nfds = j;
357 DataLock();
358 /*LOCK*/ if (events & POLLIN) FD_CLR(n, &_fdsets[0]);
359 /*LOCK*/ if (events & POLLOUT) FD_CLR(n, &_fdsets[1]);
360 /*LOCK*/ if (events & POLLERR) FD_CLR(n, &_fdsets[2]);
361 DataUnlock();
362}
363
364// CHECK IF USER DATA READY, RETURNS r/w/x INDICATING WHICH IF ANY
365int DataReady::CheckData(fd_set& r, fd_set& w, fd_set& x)
366{
367 int ret;
368 DataLock();
369 /*LOCK*/ timeval t = { 0, 1 }; // quick check
370 /*LOCK*/ r = _fdsets[0], w = _fdsets[1], x = _fdsets[2];
371 /*LOCK*/ ret = ::select(_maxfd+1, &r, &w, &x, &t);
372 DataUnlock();
373 if ( ret == -1 ) {
374 DEBUGPERRORMSG("CheckData(): select()");
375 }
376 return(ret);
377}
378
379// HANDLE DATA READY CALLBACKS
380void DataReady::HandleData(fd_set& r, fd_set& w, fd_set& x)
381{
382 for (int i=0; i<nfds; i++) {
383 int f = fds[i].fd;
384 short revents = 0;
385 if (FD_ISSET(f, &r)) revents |= POLLIN;
386 if (FD_ISSET(f, &w)) revents |= POLLOUT;
387 if (FD_ISSET(f, &x)) revents |= POLLERR;
388 if (fds[i].events & revents) {
389 DEBUGMSG("DOING CALLBACK: ");
390 fds[i].cb(f, fds[i].arg);
391 DEBUGMSG("DONE\n");
392 }
393 }
394}
395
396// DATA READY THREAD
397// This thread watches for changes in user's file descriptors.
398// Sends a 'data ready event' to the main thread if any change.
399//
400void* DataReady::DataReadyThread(void *o)
401{
402 DataReady *self = (DataReady*)o;
403 NSAutoreleasePool *localPool;
404 localPool = [[NSAutoreleasePool alloc] init];
405 while ( 1 ) { // loop until thread cancel or error
406 // Thread safe local copies of data before each select()
407 self->DataLock();
408 /*LOCK*/ int maxfd = self->_maxfd;
409 /*LOCK*/ fd_set r = self->GetFdset(0);
410 /*LOCK*/ fd_set w = self->GetFdset(1);
411 /*LOCK*/ fd_set x = self->GetFdset(2);
412 /*LOCK*/ int cancelpipe = self->GetCancelPipe(0);
413 /*LOCK*/ if ( cancelpipe > maxfd ) maxfd = cancelpipe;
414 /*LOCK*/ FD_SET(cancelpipe, &r); // add cancelpipe to fd's to watch
415 /*LOCK*/ FD_SET(cancelpipe, &x);
416 self->DataUnlock();
417 // timeval t = { 1000, 0 }; // 1000 seconds;
418 timeval t = { 2, 0 }; // HACK: 2 secs prevents 'hanging' problem
419 int ret = ::select(maxfd+1, &r, &w, &x, &t);
420 pthread_testcancel(); // OSX 10.0.4 and older: needed for parent to cancel
421 switch ( ret ) {
422 case 0: // NO DATA
423 continue;
424 case -1: // ERROR
425 {
426 DEBUGPERRORMSG("CHILD THREAD: select() failed");
427 return(NULL); // error? exit thread
428 }
429 default: // DATA READY
430 {
431 if (FD_ISSET(cancelpipe, &r) || FD_ISSET(cancelpipe, &x)) // cancel?
432 { return(NULL); } // just exit
433 DEBUGMSG("CHILD THREAD: DATA IS READY\n");
434 NSPoint pt={0,0};
435 NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:pt
436 modifierFlags:0
437 timestamp:0
438 windowNumber:0 context:NULL
439 subtype:FLTKDataReadyEvent data1:0 data2:0];
440 [NSApp postEvent:event atStart:NO];
441 return(NULL); // done with thread
442 }
443 }
444 }
445}
446
447// START 'DATA READY' THREAD RUNNING, CREATE INTER-THREAD PIPE
448void DataReady::StartThread(void)
449{
450 CancelThread(DEBUGTEXT("STARTING NEW THREAD\n"));
451 DataLock();
452 /*LOCK*/ pipe(_cancelpipe); // pipe for sending cancel msg to thread
453 DataUnlock();
454 DEBUGMSG("*** START THREAD\n");
455 pthread_create(&tid, NULL, DataReadyThread, (void*)this);
456}
457
458// CANCEL 'DATA READY' THREAD, CLOSE PIPE
459void DataReady::CancelThread(const char *reason)
460{
461 if ( tid ) {
462 DEBUGMSG("*** CANCEL THREAD: ");
463 DEBUGMSG(reason);
464 if ( pthread_cancel(tid) == 0 ) { // cancel first
465 DataLock();
466 /*LOCK*/ write(_cancelpipe[1], "x", 1); // wake thread from select
467 DataUnlock();
468 pthread_join(tid, NULL); // wait for thread to finish
469 }
470 tid = 0;
471 DEBUGMSG("(JOINED) OK\n");
472 }
473 // Close pipe if open
474 DataLock();
475 /*LOCK*/ if ( _cancelpipe[0] ) { close(_cancelpipe[0]); _cancelpipe[0] = 0; }
476 /*LOCK*/ if ( _cancelpipe[1] ) { close(_cancelpipe[1]); _cancelpipe[1] = 0; }
477 DataUnlock();
478}
479
480void Fl::add_fd( int n, int events, void (*cb)(int, void*), void *v )
481{
482 dataready.AddFD(n, events, cb, v);
483}
484
485void Fl::add_fd(int fd, void (*cb)(int, void*), void* v)
486{
487 dataready.AddFD(fd, POLLIN, cb, v);
488}
489
490void Fl::remove_fd(int n, int events)
491{
492 dataready.RemoveFD(n, events);
493}
494
495void Fl::remove_fd(int n)
496{
497 dataready.RemoveFD(n, -1);
498}
499
500/*
501 * Check if there is actually a message pending!
502 */
503int fl_ready()
504{
505 NSEvent *retval = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate dateWithTimeIntervalSinceNow:0]
506 inMode:NSDefaultRunLoopMode dequeue:NO];
507 return retval != nil;
508}
509
510
511static void processFLTKEvent(void) {
512 fl_lock_function();
513 dataready.CancelThread(DEBUGTEXT("DATA READY EVENT\n"));
514
515 // CHILD THREAD TELLS US DATA READY
516 // Check to see what's ready, and invoke user's cb's
517 //
518 fd_set r,w,x;
519 switch(dataready.CheckData(r,w,x)) {
520 case 0: // NO DATA
521 break;
522 case -1: // ERROR
523 break;
524 default: // DATA READY
525 dataready.HandleData(r,w,x);
526 break;
527 }
528 fl_unlock_function();
529 return;
530}
531
532
533/*
534 * break the current event loop
535 */
536static void breakMacEventLoop()
537{
538 fl_lock_function();
539
540 NSPoint pt={0,0};
541 NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:pt
542 modifierFlags:0
543 timestamp:0
544 windowNumber:0 context:NULL
545 subtype:FLTKTimerEvent data1:0 data2:0];
546 [NSApp postEvent:event atStart:NO];
547 fl_unlock_function();
548}
549
550//
551// MacOS X timers
552//
553
554struct MacTimeout {
555 Fl_Timeout_Handler callback;
556 void* data;
557 CFRunLoopTimerRef timer;
558 char pending;
559};
560static MacTimeout* mac_timers;
561static int mac_timer_alloc;
562static int mac_timer_used;
563
564static void realloc_timers()
565{
566 if (mac_timer_alloc == 0) {
567 mac_timer_alloc = 8;
568 fl_open_display(); // needed because the timer creates an event
569 }
570 mac_timer_alloc *= 2;
571 MacTimeout* new_timers = new MacTimeout[mac_timer_alloc];
572 memset(new_timers, 0, sizeof(MacTimeout)*mac_timer_alloc);
573 memcpy(new_timers, mac_timers, sizeof(MacTimeout) * mac_timer_used);
574 MacTimeout* delete_me = mac_timers;
575 mac_timers = new_timers;
576 delete [] delete_me;
577}
578
579static void delete_timer(MacTimeout& t)
580{
581 if (t.timer) {
582 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(),
583 t.timer,
584 kCFRunLoopDefaultMode);
585 CFRelease(t.timer);
586 memset(&t, 0, sizeof(MacTimeout));
587 }
588}
589
590static void do_timer(CFRunLoopTimerRef timer, void* data)
591{
592 for (int i = 0; i < mac_timer_used; ++i) {
593 MacTimeout& t = mac_timers[i];
594 if (t.timer == timer && t.data == data) {
595 t.pending = 0;
596 (*t.callback)(data);
597 if (t.pending==0)
598 delete_timer(t);
599 break;
600 }
601 }
602 breakMacEventLoop();
603}
604
605@interface FLWindow : NSWindow {
606 Fl_Window *w;
607 BOOL containsGLsubwindow;
608}
609- (FLWindow*)initWithFl_W:(Fl_Window *)flw
610 contentRect:(NSRect)rect
611 styleMask:(NSUInteger)windowStyle;
612- (Fl_Window *)getFl_Window;
613- (BOOL)windowShouldClose:(FLWindow *)w;
614- (BOOL)containsGLsubwindow;
615- (void)setContainsGLsubwindow:(BOOL)contains;
616@end
617
618@implementation FLWindow
619- (FLWindow*)initWithFl_W:(Fl_Window *)flw
620 contentRect:(NSRect)rect
621 styleMask:(NSUInteger)windowStyle
622{
623 self = [super initWithContentRect:rect styleMask:windowStyle backing:NSBackingStoreBuffered defer:NO];
624 if (self) {
625 w = flw;
626 containsGLsubwindow = NO;
627 }
628 return self;
629}
630- (Fl_Window *)getFl_Window;
631{
632 return w;
633}
634- (BOOL)windowShouldClose:(FLWindow *)fl
635{
636 fl_lock_function();
637 Fl::handle( FL_CLOSE, [fl getFl_Window] ); // this might or might not close the window
638 if (!Fl_X::first) return YES;
639 Fl_Window *l = Fl::first_window();
640 while( l != NULL && l != [fl getFl_Window]) l = Fl::next_window(l);
641 fl_unlock_function();
642 return (l == NULL ? YES : NO);
643}
644- (BOOL)containsGLsubwindow
645{
646 return containsGLsubwindow;
647}
648- (void)setContainsGLsubwindow:(BOOL)contains
649{
650 containsGLsubwindow = contains;
651}
DRC685f17e2011-07-28 09:23:00 +0000652- (BOOL)canBecomeKeyWindow
653{
654 return YES;
655}
DRC2ff39b82011-07-28 08:38:59 +0000656@end
657
658@interface FLApplication : NSObject
659{
660}
661+ (void)sendEvent:(NSEvent *)theEvent;
662@end
663
664/*
665 * This function is the central event handler.
666 * It reads events from the event queue using the given maximum time
667 * Funny enough, it returns the same time that it got as the argument.
668 */
669static double do_queued_events( double time = 0.0 )
670{
671 got_events = 0;
672
673 // Check for re-entrant condition
674 if ( dataready.IsThreadRunning() ) {
675 dataready.CancelThread(DEBUGTEXT("AVOID REENTRY\n"));
676 }
677
678 // Start thread to watch for data ready
679 if ( dataready.GetNfds() ) {
680 dataready.StartThread();
681 }
682
683 fl_unlock_function();
684 NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask
685 untilDate:[NSDate dateWithTimeIntervalSinceNow:time]
686 inMode:NSDefaultRunLoopMode dequeue:YES];
687 if (event != nil) {
688 got_events = 1;
689 [FLApplication sendEvent:event]; // will then call [NSApplication sendevent:]
690 }
691 fl_lock_function();
692
693#if CONSOLIDATE_MOTION
694 if (send_motion && send_motion == fl_xmousewin) {
695 send_motion = 0;
696 Fl::handle(FL_MOVE, fl_xmousewin);
697 }
698#endif
699
700 return time;
701}
702
703/*
704 * This public function handles all events. It wait a maximum of
705 * 'time' seconds for an event. This version returns 1 if events
706 * other than the timeout timer were processed.
707 *
708 * \todo there is no socket handling in this code whatsoever
709 */
710int fl_wait( double time )
711{
712 do_queued_events( time );
713 return (got_events);
714}
715
716double fl_mac_flush_and_wait(double time_to_wait, char in_idle) {
717 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
718 Fl::flush();
719 if (Fl::idle && !in_idle) // 'idle' may have been set within flush()
720 time_to_wait = 0.0;
721 double retval = fl_wait(time_to_wait);
722 [pool release];
723 return retval;
724}
725
726// updates Fl::e_x, Fl::e_y, Fl::e_x_root, and Fl::e_y_root
727static void update_e_xy_and_e_xy_root(NSWindow *nsw)
728{
729 NSPoint pt;
730 pt = [nsw mouseLocationOutsideOfEventStream];
731 Fl::e_x = int(pt.x);
732 Fl::e_y = int([[nsw contentView] frame].size.height - pt.y);
733 pt = [NSEvent mouseLocation];
734 Fl::e_x_root = int(pt.x);
735 Fl::e_y_root = int([[nsw screen] frame].size.height - pt.y);
736}
737
738/*
739 * Cocoa Mousewheel handler
740 */
741static void cocoaMouseWheelHandler(NSEvent *theEvent)
742{
743 // Handle the new "MightyMouse" mouse wheel events. Please, someone explain
744 // to me why Apple changed the API on this even though the current API
745 // supports two wheels just fine. Matthias,
746 fl_lock_function();
747
748 Fl_Window *window = (Fl_Window*)[(FLWindow*)[theEvent window] getFl_Window];
749 if ( !window->shown() ) {
750 fl_unlock_function();
751 return;
752 }
753 Fl::first_window(window);
754
755 // Under OSX, single mousewheel increments are 0.1,
756 // so make sure they show up as at least 1..
757 //
758 float dx = [theEvent deltaX]; if ( fabs(dx) < 1.0 ) dx = (dx > 0) ? 1.0 : -1.0;
759 float dy = [theEvent deltaY]; if ( fabs(dy) < 1.0 ) dy = (dy > 0) ? 1.0 : -1.0;
760 if ([theEvent deltaX] != 0) {
761 Fl::e_dx = (int)-dx;
762 Fl::e_dy = 0;
763 if ( Fl::e_dx) Fl::handle( FL_MOUSEWHEEL, window );
764 } else if ([theEvent deltaY] != 0) {
765 Fl::e_dx = 0;
766 Fl::e_dy = (int)-dy;
767 if ( Fl::e_dy) Fl::handle( FL_MOUSEWHEEL, window );
768 } else {
769 fl_unlock_function();
770 return;
771 }
772
773 fl_unlock_function();
774
775 // return noErr;
776}
777
778/*
779 * Cocoa Mouse Button Handler
780 */
781static void cocoaMouseHandler(NSEvent *theEvent)
782{
783 static int keysym[] = { 0, FL_Button+1, FL_Button+3, FL_Button+2 };
784 static int px, py;
785 static char suppressed = 0;
786
787 fl_lock_function();
788
789 Fl_Window *window = (Fl_Window*)[(FLWindow*)[theEvent window] getFl_Window];
790 if ( !window->shown() ) {
791 fl_unlock_function();
792 return;
793 }
794 Fl_Window *first = Fl::first_window();
795 if (first != window && !(first->modal() || first->non_modal())) Fl::first_window(window);
796 NSPoint pos = [theEvent locationInWindow];
797 pos.y = window->h() - pos.y;
798 NSInteger btn = [theEvent buttonNumber] + 1;
799 NSUInteger mods = [theEvent modifierFlags];
800 int sendEvent = 0;
801
802 NSEventType etype = [theEvent type];
803 if (etype == NSLeftMouseDown || etype == NSRightMouseDown || etype == NSOtherMouseDown) {
804 if (btn == 1) Fl::e_state |= FL_BUTTON1;
805 else if (btn == 3) Fl::e_state |= FL_BUTTON2;
806 else if (btn == 2) Fl::e_state |= FL_BUTTON3;
807 }
808 else if (etype == NSLeftMouseUp || etype == NSRightMouseUp || etype == NSOtherMouseUp) {
809 if (btn == 1) Fl::e_state &= ~FL_BUTTON1;
810 else if (btn == 3) Fl::e_state &= ~FL_BUTTON2;
811 else if (btn == 2) Fl::e_state &= ~FL_BUTTON3;
812 }
813
814 switch ( etype ) {
815 case NSLeftMouseDown:
816 case NSRightMouseDown:
817 case NSOtherMouseDown:
818 suppressed = 0;
819 sendEvent = FL_PUSH;
820 Fl::e_is_click = 1;
821 px = (int)pos.x; py = (int)pos.y;
822 if ([theEvent clickCount] > 1)
823 Fl::e_clicks++;
824 else
825 Fl::e_clicks = 0;
826 // fall through
827 case NSLeftMouseUp:
828 case NSRightMouseUp:
829 case NSOtherMouseUp:
830 if (suppressed) {
831 suppressed = 0;
832 break;
833 }
834 if ( !window ) break;
835 if ( !sendEvent ) {
836 sendEvent = FL_RELEASE;
837 }
838 Fl::e_keysym = keysym[ btn ];
839 // fall through
840 case NSMouseMoved:
841 suppressed = 0;
842 if ( !sendEvent ) {
843 sendEvent = FL_MOVE;
844 }
845 // fall through
846 case NSLeftMouseDragged:
847 case NSRightMouseDragged:
848 case NSOtherMouseDragged: {
849 if (suppressed) break;
850 if ( !sendEvent ) {
851 sendEvent = FL_MOVE; // Fl::handle will convert into FL_DRAG
852 if (fabs(pos.x-px)>5 || fabs(pos.y-py)>5)
853 Fl::e_is_click = 0;
854 }
855 mods_to_e_state( mods );
856 update_e_xy_and_e_xy_root([theEvent window]);
857 Fl::handle( sendEvent, window );
858 }
859 break;
860 default:
861 break;
862 }
863
864 fl_unlock_function();
865
866 return;
867}
868
869@interface FLTextView : NSTextView
870// this subclass is needed under OS X <= 10.5 but not under >= 10.6 where the base class is enough
871{
872}
873@end
874@implementation FLTextView
875- (void)insertText:(id)aString
876{
877 [[[NSApp keyWindow] contentView] insertText:aString];
878}
879- (void)doCommandBySelector:(SEL)aSelector
880{
881 [[[NSApp keyWindow] contentView] doCommandBySelector:aSelector];
882}
883@end
884
DRC685f17e2011-07-28 09:23:00 +0000885static const char* cocoaDead2FLTK(const char *in)
886{
887 if (strcmp(in, "\140") == 0) // GRAVE ACCENT
888 return "\314\200"; // COMBINING GRAVE ACCENT
889 if (strcmp(in, "\302\264") == 0) // ACUTE ACCENT
890 return "\314\201"; // COMBINING ACUTE ACCENT
891 if (strcmp(in, "\136") == 0) // CIRCUMFLEX ACCENT
892 return "\314\202"; // COMBINING CIRCUMFLEX ACCENT
893 if (strcmp(in, "\176") == 0) // TILDE
894 return "\314\203"; // COMBINING TILDE
895 if (strcmp(in, "\302\250") == 0) // DIAERESIS
896 return "\314\210"; // COMBINING DIAERESIS
897 // FIXME: OS X dead key behaviour isn't documented and I don't have
898 // any more keyboards to test with...
899
900 // hope that OS X gave us something proper to begin with
901 return in;
902}
903
DRC2ff39b82011-07-28 08:38:59 +0000904/*
905Handle cocoa keyboard events
906Events during a character composition sequence:
907 - keydown with deadkey -> [[theEvent characters] length] is 0
908 - keyup -> [theEvent characters] contains the deadkey
909 - keydown with next key -> [theEvent characters] contains the composed character
910 - keyup -> [theEvent characters] contains the standard character
911 */
912static void cocoaKeyboardHandler(NSEvent *theEvent)
913{
914 NSUInteger mods;
915
916 // get the modifiers
917 mods = [theEvent modifierFlags];
918 // get the key code
919 UInt32 keyCode = 0, maskedKeyCode = 0;
920 unsigned short sym = 0;
921 keyCode = [theEvent keyCode];
922 NSString *s = [theEvent characters];
923 if ( (mods & NSShiftKeyMask) && (mods & NSCommandKeyMask) ) {
924 s = [s uppercaseString]; // US keyboards return lowercase letter in s if cmd-shift-key is hit
925 }
926 // extended keyboards can also send sequences on key-up to generate Kanji etc. codes.
927 // Some observed prefixes are 0x81 to 0x83, followed by an 8 bit keycode.
928 // In this mode, there seem to be no key-down codes
929 // printf("%08x %08x %08x\n", keyCode, mods, key);
930 maskedKeyCode = keyCode & 0x7f;
931
932 if ([theEvent type] == NSKeyUp) {
933 Fl::e_state &= 0xbfffffff; // clear the deadkey flag
934 }
935
936 mods_to_e_state( mods ); // process modifier keys
937 sym = macKeyLookUp[maskedKeyCode];
938 if (sym < 0xff00) { // a "simple" key
939 // find the result of this key without modifier
940 NSString *sim = [theEvent charactersIgnoringModifiers];
941 UniChar one;
942 CFStringGetCharacters((CFStringRef)sim, CFRangeMake(0, 1), &one);
943 // charactersIgnoringModifiers doesn't ignore shift, remove it when it's on
944 if(one >= 'A' && one <= 'Z') one += 32;
945 if (one > 0 && one <= 0x7f && (sym<'0' || sym>'9') ) sym = one;
946 }
947 Fl::e_keysym = Fl::e_original_keysym = sym;
948
949 //NSLog(@"cocoaKeyboardHandler: keycode=%08x keysym=%08x mods=%08x symbol=%@ (%@)",
950 // keyCode, sym, mods, [theEvent characters], [theEvent charactersIgnoringModifiers]);
951
952 // If there is text associated with this key, it will be filled in later.
953 Fl::e_length = 0;
954 Fl::e_text = (char*)"";
955}
956
957
958/*
959 * Open callback function to call...
960 */
961
962static void (*open_cb)(const char *) = 0;
963
964
965/*
966 * Install an open documents event handler...
967 */
968@interface FLAppleEventHandler : NSObject
969{
970}
971- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent;
972@end
973@implementation FLAppleEventHandler
974- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent
975{
976 NSAppleEventDescriptor *single = [event descriptorAtIndex:1];
977 const AEDesc *document = [single aeDesc];
978 long i, n;
979 FSRef fileRef;
980 AEKeyword keyWd;
981 DescType typeCd;
982 Size actSz;
983 char filename[1024];
984 // Lock access to FLTK in this thread...
985 fl_lock_function();
986
987 // Open the documents via the callback...
988 if (AECountItems(document, &n) == noErr) {
989 for (i = 1; i <= n; i ++) {
990 AEGetNthPtr(document, i, typeFSRef, &keyWd, &typeCd,
991 (Ptr)&fileRef, sizeof(fileRef),
992 (actSz = sizeof(fileRef), &actSz));
993 FSRefMakePath( &fileRef, (UInt8*)filename, sizeof(filename) );
994
995 (*open_cb)(filename);
996 }
997 }
998 // Unlock access to FLTK for all threads...
999 fl_unlock_function();
1000}
1001@end
1002
1003void fl_open_callback(void (*cb)(const char *)) {
1004 static NSAppleEventManager *aeventmgr = nil;
1005 static FLAppleEventHandler *handler;
1006 fl_open_display();
1007 if (!aeventmgr) {
1008 aeventmgr = [NSAppleEventManager sharedAppleEventManager];
1009 handler = [[FLAppleEventHandler alloc] init];
1010 }
1011
1012 open_cb = cb;
1013 if (cb) {
1014 [aeventmgr setEventHandler:handler andSelector: @selector(handleAppleEvent:withReplyEvent:)
1015 forEventClass:kCoreEventClass andEventID:kAEOpenDocuments];
1016 } else {
1017 [aeventmgr removeEventHandlerForEventClass:kCoreEventClass andEventID:kAEOpenDocuments];
1018 }
1019}
1020
1021
1022/*
1023 * initialize the Mac toolboxes, dock status, and set the default menubar
1024 */
1025
1026extern "C" {
1027 extern OSErr CPSEnableForegroundOperation(ProcessSerialNumber *psn, UInt32 _arg2,
1028 UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
1029}
1030
1031
1032@interface FLDelegate : NSObject
1033#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
1034<NSWindowDelegate, NSApplicationDelegate>
1035#endif
1036{
1037}
1038- (void)windowDidMove:(NSNotification *)notif;
1039- (void)windowDidResize:(NSNotification *)notif;
1040- (void)windowDidResignKey:(NSNotification *)notif;
1041- (void)windowDidBecomeKey:(NSNotification *)notif;
1042- (void)windowDidBecomeMain:(NSNotification *)notif;
1043- (void)windowDidDeminiaturize:(NSNotification *)notif;
1044- (void)windowDidMiniaturize:(NSNotification *)notif;
1045- (void)windowWillClose:(NSNotification *)notif;
1046- (void)anywindowwillclosenotif:(NSNotification *)notif;
1047- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender;
1048- (void)applicationDidBecomeActive:(NSNotification *)notify;
1049- (void)applicationWillResignActive:(NSNotification *)notify;
1050- (void)applicationWillHide:(NSNotification *)notify;
1051- (void)applicationWillUnhide:(NSNotification *)notify;
1052- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client;
1053@end
1054@implementation FLDelegate
1055- (void)windowDidMove:(NSNotification *)notif
1056{
1057 fl_lock_function();
1058 FLWindow *nsw = (FLWindow*)[notif object];
1059 Fl_Window *window = [nsw getFl_Window];
1060 NSPoint pt, pt2;
1061 pt.x = 0;
1062 pt.y = [[nsw contentView] frame].size.height;
1063 pt2 = [nsw convertBaseToScreen:pt];
1064 update_e_xy_and_e_xy_root(nsw);
Pierre Ossmanf5e59af2011-11-25 09:18:28 +00001065 resize_from_system = window;
DRC2ff39b82011-07-28 08:38:59 +00001066 window->position((int)pt2.x, (int)([[nsw screen] frame].size.height - pt2.y));
1067 if ([nsw containsGLsubwindow] ) {
1068 [nsw display];// redraw window after moving if it contains OpenGL subwindows
1069 }
1070 fl_unlock_function();
1071}
1072- (void)windowDidResize:(NSNotification *)notif
1073{
1074 fl_lock_function();
1075 FLWindow *nsw = (FLWindow*)[notif object];
1076 Fl_Window *window = [nsw getFl_Window];
1077 NSRect r = [[nsw contentView] frame];
1078 NSPoint pt, pt2;
1079 pt.x = 0;
1080 pt.y = [[nsw contentView] frame].size.height;
1081 pt2 = [nsw convertBaseToScreen:pt];
1082 resize_from_system = window;
1083 update_e_xy_and_e_xy_root(nsw);
1084 window->resize((int)pt2.x,
1085 (int)([[nsw screen] frame].size.height - pt2.y),
1086 (int)r.size.width,
1087 (int)r.size.height);
1088 fl_unlock_function();
1089}
1090- (void)windowDidResignKey:(NSNotification *)notif
1091{
1092 fl_lock_function();
1093 FLWindow *nsw = (FLWindow*)[notif object];
1094 Fl_Window *window = [nsw getFl_Window];
DRC685f17e2011-07-28 09:23:00 +00001095 /* Fullscreen windows obscure all other windows so we need to return
1096 to a "normal" level when the user switches to another window */
1097 if (window->fullscreen_active())
1098 [nsw setLevel:NSNormalWindowLevel];
DRC2ff39b82011-07-28 08:38:59 +00001099 Fl::handle( FL_UNFOCUS, window);
1100 fl_unlock_function();
1101}
1102- (void)windowDidBecomeKey:(NSNotification *)notif
1103{
1104 fl_lock_function();
1105 FLWindow *nsw = (FLWindow*)[notif object];
1106 Fl_Window *w = [nsw getFl_Window];
DRC685f17e2011-07-28 09:23:00 +00001107 /* Restore previous fullscreen level */
1108 if (w->fullscreen_active())
1109 [nsw setLevel:NSStatusWindowLevel];
DRC2ff39b82011-07-28 08:38:59 +00001110 if ( w->border() || (!w->modal() && !w->tooltip_window()) ) Fl::handle( FL_FOCUS, w);
1111 fl_unlock_function();
1112}
1113- (void)windowDidBecomeMain:(NSNotification *)notif
1114{
1115 fl_lock_function();
1116 FLWindow *nsw = (FLWindow*)[notif object];
1117 Fl_Window *window = [nsw getFl_Window];
1118 Fl::first_window(window);
1119 update_e_xy_and_e_xy_root(nsw);
1120 fl_unlock_function();
1121}
1122- (void)windowDidDeminiaturize:(NSNotification *)notif
1123{
1124 fl_lock_function();
1125 FLWindow *nsw = (FLWindow*)[notif object];
1126 Fl_Window *window = [nsw getFl_Window];
1127 Fl::handle(FL_SHOW, window);
1128 update_e_xy_and_e_xy_root(nsw);
1129 fl_unlock_function();
1130}
1131- (void)windowDidMiniaturize:(NSNotification *)notif
1132{
1133 fl_lock_function();
1134 FLWindow *nsw = (FLWindow*)[notif object];
1135 Fl_Window *window = [nsw getFl_Window];
1136 Fl::handle(FL_HIDE, window);
1137 fl_unlock_function();
1138}
1139- (void)windowWillClose:(NSNotification *)notif
1140{
1141 fl_lock_function();
1142 Fl_Window *w = Fl::first_window();
1143 if (w) {
1144 NSWindow *cw = (NSWindow*)Fl_X::i(w)->xid;
1145 if ( ![cw isMiniaturized] && ([cw styleMask] & NSTitledWindowMask) ) {
1146 if (![cw isKeyWindow]) { // always make Fl::first_window() the key widow
1147 [cw makeKeyAndOrderFront:nil];
1148 }
1149 if (![cw isMainWindow]) { // always make Fl::first_window() the main widow
1150 [cw makeMainWindow];
1151 }
1152 }
1153 }
1154 fl_unlock_function();
1155}
1156- (void)anywindowwillclosenotif:(NSNotification *)notif
1157{
1158 // necessary so that after closing a non-FLTK window (e.g., Fl_Native_File_Chooser)
1159 // the front window turns key again
1160 NSWindow *closing = (NSWindow*)[notif object];
1161 if ([closing isMemberOfClass:[FLWindow class]]) return;
1162 NSWindow *nsk = [NSApp keyWindow];
1163 NSWindow *nsm = [NSApp mainWindow];
1164 if ([nsm isMemberOfClass:[FLWindow class]] && nsk == nil) {
1165 [nsm makeKeyAndOrderFront:nil];
1166 }
1167}
1168- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender
1169{
1170 fl_lock_function();
1171 NSApplicationTerminateReply reply = NSTerminateNow;
1172 while ( Fl_X::first ) {
1173 Fl_X *x = Fl_X::first;
1174 Fl::handle( FL_CLOSE, x->w );
1175 if ( Fl_X::first == x ) {
1176 reply = NSTerminateCancel; // FLTK has not closed all windows, so we return to the main program now
1177 break;
1178 }
1179 }
1180 fl_unlock_function();
1181 return reply;
1182}
1183/**
1184 * Cocoa organizes the Z depth of windows on a global priority. FLTK however
1185 * expects the window manager to organize Z level by application. The trickery
1186 * below will change Z order during activation and deactivation.
1187 */
1188- (void)applicationDidBecomeActive:(NSNotification *)notify
1189{
1190 fl_lock_function();
1191 Fl_X *x;
1192 FLWindow *top = 0, *topModal = 0, *topNonModal = 0;
1193 for (x = Fl_X::first;x;x = x->next) {
1194 FLWindow *cw = (FLWindow*)x->xid;
1195 Fl_Window *win = x->w;
1196 if (win && cw) {
1197 if (win->modal()) {
1198 [cw setLevel:NSModalPanelWindowLevel];
1199 if (topModal)
1200 [cw orderWindow:NSWindowBelow relativeTo:[topModal windowNumber]];
1201 else
1202 topModal = cw;
1203 } else if (win->non_modal()) {
1204 [cw setLevel:NSFloatingWindowLevel];
1205 if (topNonModal)
1206 [cw orderWindow:NSWindowBelow relativeTo:[topNonModal windowNumber]];
1207 else
1208 topNonModal = cw;
1209 } else {
1210 if (top)
1211 ;
1212 else
1213 top = cw;
1214 }
1215 }
1216 }
1217 fl_unlock_function();
1218}
1219- (void)applicationWillResignActive:(NSNotification *)notify
1220{
1221 fl_lock_function();
1222 Fl_X *x;
1223 FLWindow *top = 0;
1224 // sort in all regular windows
1225 for (x = Fl_X::first;x;x = x->next) {
1226 FLWindow *cw = (FLWindow*)x->xid;
1227 Fl_Window *win = x->w;
1228 if (win && cw) {
1229 if (win->modal()) {
1230 } else if (win->non_modal()) {
1231 } else {
1232 if (!top) top = cw;
1233 }
1234 }
1235 }
1236 // now sort in all modals
1237 for (x = Fl_X::first;x;x = x->next) {
1238 FLWindow *cw = (FLWindow*)x->xid;
1239 Fl_Window *win = x->w;
1240 if (win && cw) {
1241 if (win->modal()) {
1242 [cw setLevel:NSNormalWindowLevel];
1243 if (top) [cw orderWindow:NSWindowAbove relativeTo:[top windowNumber]];
1244 }
1245 }
1246 }
1247 // finally all non-modals
1248 for (x = Fl_X::first;x;x = x->next) {
1249 FLWindow *cw = (FLWindow*)x->xid;
1250 Fl_Window *win = x->w;
1251 if (win && cw) {
1252 if (win->non_modal()) {
1253 [cw setLevel:NSNormalWindowLevel];
1254 if (top) [cw orderWindow:NSWindowAbove relativeTo:[top windowNumber]];
1255 }
1256 }
1257 }
1258 fl_unlock_function();
1259}
1260- (void)applicationWillHide:(NSNotification *)notify
1261{
1262 fl_lock_function();
1263 Fl_X *x;
1264 for (x = Fl_X::first;x;x = x->next) {
1265 Fl_Window *window = x->w;
1266 if ( !window->parent() ) Fl::handle( FL_HIDE, window);
1267 }
1268 fl_unlock_function();
1269}
1270- (void)applicationWillUnhide:(NSNotification *)notify
1271{
1272 fl_lock_function();
1273 Fl_X *x;
1274 for (x = Fl_X::first;x;x = x->next) {
1275 Fl_Window *w = x->w;
1276 if ( !w->parent() ) {
1277 if ( w->border() || (!w->modal() && !w->tooltip_window()) ) Fl::handle( FL_FOCUS, w);
1278 Fl::handle( FL_SHOW, w);
1279 }
1280 }
1281 fl_unlock_function();
1282}
1283- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client
1284{
1285 if (fl_mac_os_version < 100600) {
1286 static FLTextView *view = nil;
1287 if (!view) {
1288 NSRect rect={{0,0},{20,20}};
1289 view = [[FLTextView alloc] initWithFrame:rect];
1290 }
1291 return view;
1292 }
1293 return nil;
1294}
1295@end
1296
DRC685f17e2011-07-28 09:23:00 +00001297static void clipboard_check(void);
1298
DRC2ff39b82011-07-28 08:38:59 +00001299@implementation FLApplication
1300+ (void)sendEvent:(NSEvent *)theEvent
1301{
DRC685f17e2011-07-28 09:23:00 +00001302 // update clipboard status
1303 clipboard_check();
DRC2ff39b82011-07-28 08:38:59 +00001304 NSEventType type = [theEvent type];
1305 if (type == NSLeftMouseDown) {
1306 fl_lock_function();
1307 Fl_Window *grab = Fl::grab();
1308 if (grab) {
1309 FLWindow *win = (FLWindow *)[theEvent window];
1310 if ( [win isKindOfClass:[FLWindow class]] && grab != [win getFl_Window]) {
1311 // a click event out of a menu window, so we should close this menu
1312 // done here to catch also clicks on window title bar/resize box
1313 cocoaMouseHandler(theEvent);
1314 }
1315 }
1316 fl_unlock_function();
1317 } else if (type == NSApplicationDefined) {
1318 if ([theEvent subtype] == FLTKDataReadyEvent) {
1319 processFLTKEvent();
1320 }
1321 return;
1322 } else if (type == NSKeyUp) {
1323 // The default sendEvent turns key downs into performKeyEquivalent when
1324 // modifiers are down, but swallows the key up if the modifiers include
1325 // command. This one makes all modifiers consistent by always sending key ups.
1326 // FLView treats performKeyEquivalent to keyDown, but performKeyEquivalent is
1327 // still needed for the system menu.
1328 [[NSApp keyWindow] sendEvent:theEvent];
1329 return;
1330 }
1331 [NSApp sendEvent:theEvent];
1332}
1333@end
1334
1335static FLDelegate *mydelegate;
1336
1337void fl_open_display() {
1338 static char beenHereDoneThat = 0;
1339 if ( !beenHereDoneThat ) {
1340 beenHereDoneThat = 1;
1341
1342 BOOL need_new_nsapp = (NSApp == nil);
1343 if (need_new_nsapp) [NSApplication sharedApplication];
1344 NSAutoreleasePool *localPool;
1345 localPool = [[NSAutoreleasePool alloc] init]; // never released
1346 mydelegate = [[FLDelegate alloc] init];
1347 [NSApp setDelegate:mydelegate];
1348 if (need_new_nsapp) [NSApp finishLaunching];
1349
1350 // empty the event queue but keep system events for drag&drop of files at launch
1351 NSEvent *ign_event;
1352 do ign_event = [NSApp nextEventMatchingMask:(NSAnyEventMask & ~NSSystemDefinedMask)
1353 untilDate:[NSDate dateWithTimeIntervalSinceNow:0]
1354 inMode:NSDefaultRunLoopMode
1355 dequeue:YES];
1356 while (ign_event);
1357
DRC2ff39b82011-07-28 08:38:59 +00001358 // bring the application into foreground without a 'CARB' resource
1359 Boolean same_psn;
1360 ProcessSerialNumber cur_psn, front_psn;
1361 if ( !GetCurrentProcess( &cur_psn ) && !GetFrontProcess( &front_psn ) &&
1362 !SameProcess( &front_psn, &cur_psn, &same_psn ) && !same_psn ) {
1363 // only transform the application type for unbundled apps
1364 CFBundleRef bundle = CFBundleGetMainBundle();
1365 if ( bundle ) {
1366 FSRef execFs;
1367 CFURLRef execUrl = CFBundleCopyExecutableURL( bundle );
1368 CFURLGetFSRef( execUrl, &execFs );
1369
1370 FSRef bundleFs;
1371 GetProcessBundleLocation( &cur_psn, &bundleFs );
1372
1373 if ( !FSCompareFSRefs( &execFs, &bundleFs ) )
1374 bundle = NULL;
1375
1376 CFRelease(execUrl);
1377 }
1378
1379 if ( !bundle )
1380 {
1381 // Earlier versions of this code tried to use weak linking, however it
1382 // appears that this does not work on 10.2. Since 10.3 and higher provide
1383 // both TransformProcessType and CPSEnableForegroundOperation, the following
1384 // conditional code compiled on 10.2 will still work on newer releases...
1385 OSErr err;
1386#if __LP64__
1387 err = TransformProcessType(&cur_psn, kProcessTransformToForegroundApplication);
1388#else
1389
1390#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2
1391 if (TransformProcessType != NULL) {
1392 err = TransformProcessType(&cur_psn, kProcessTransformToForegroundApplication);
1393 } else
1394#endif // MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2
1395 err = CPSEnableForegroundOperation(&cur_psn, 0x03, 0x3C, 0x2C, 0x1103);
1396#endif // __LP64__
1397 if (err == noErr) {
1398 SetFrontProcess( &cur_psn );
1399 }
1400 }
1401 }
1402 if (![NSApp servicesMenu]) createAppleMenu();
1403 fl_system_menu = [NSApp mainMenu];
1404
1405 [[NSNotificationCenter defaultCenter] addObserver:mydelegate
1406 selector:@selector(anywindowwillclosenotif:)
1407 name:NSWindowWillCloseNotification
1408 object:nil];
1409 }
1410}
1411
1412
1413/*
1414 * get rid of allocated resources
1415 */
1416void fl_close_display() {
1417}
1418
1419
1420// Gets the border sizes and the titlebar size
1421static void get_window_frame_sizes(int &bx, int &by, int &bt) {
1422 static bool first = true;
1423 static int top, left, bottom;
1424 if (first) {
1425 first = false;
1426 if (NSApp == nil) fl_open_display();
1427 NSRect inside = { {20,20}, {100,100} };
1428 NSRect outside = [NSWindow frameRectForContentRect:inside styleMask:NSTitledWindowMask];
1429 left = int(outside.origin.x - inside.origin.x);
1430 bottom = int(outside.origin.y - inside.origin.y);
1431 top = int(outside.size.height - inside.size.height) - bottom;
1432 }
1433 bx = left;
1434 by = bottom;
1435 bt = top;
1436}
1437
1438/*
1439 * smallest x ccordinate in screen space
1440 */
1441int Fl::x() {
1442 return int([[NSScreen mainScreen] visibleFrame].origin.x);
1443}
1444
1445
1446/*
1447 * smallest y coordinate in screen space
1448 */
1449int Fl::y() {
1450 NSRect all = [[NSScreen mainScreen] frame];
1451 NSRect visible = [[NSScreen mainScreen] visibleFrame];
1452 return int(all.size.height - (visible.origin.y + visible.size.height));
1453}
1454
1455
1456/*
1457 * screen width
1458 */
1459int Fl::w() {
1460 return int([[NSScreen mainScreen] visibleFrame].size.width);
1461}
1462
1463
1464/*
1465 * screen height
1466 */
1467int Fl::h() {
1468 return int([[NSScreen mainScreen] visibleFrame].size.height);
1469}
1470
1471
1472/*
1473 * get the current mouse pointer world coordinates
1474 */
1475void Fl::get_mouse(int &x, int &y)
1476{
1477 fl_open_display();
1478 NSPoint pt = [NSEvent mouseLocation];
1479 x = int(pt.x);
1480 y = int([[NSScreen mainScreen] frame].size.height - pt.y);
1481}
1482
1483
1484/*
1485 * Initialize the given port for redraw and call the window's flush() to actually draw the content
1486 */
1487void Fl_X::flush()
1488{
1489 w->flush();
1490 if (fl_gc) CGContextFlush(fl_gc);
1491}
1492
1493/*
1494 * Gets called when a window is created, resized, or deminiaturized
1495 */
1496static void handleUpdateEvent( Fl_Window *window )
1497{
1498 if ( !window ) return;
1499 Fl_X *i = Fl_X::i( window );
1500 i->wait_for_expose = 0;
1501
1502 if ( i->region ) {
1503 XDestroyRegion(i->region);
1504 i->region = 0;
1505 }
1506
1507 for ( Fl_X *cx = i->xidChildren; cx; cx = cx->xidNext ) {
1508 if ( cx->region ) {
1509 XDestroyRegion(cx->region);
1510 cx->region = 0;
1511 }
1512 cx->w->clear_damage(FL_DAMAGE_ALL);
1513 cx->flush();
1514 cx->w->clear_damage();
1515 }
1516 window->clear_damage(FL_DAMAGE_ALL);
1517 i->flush();
1518 window->clear_damage();
1519}
1520
1521
1522int Fl_X::fake_X_wm(const Fl_Window* w,int &X,int &Y, int &bt,int &bx, int &by) {
1523 int W, H, xoff, yoff, dx, dy;
1524 int ret = bx = by = bt = 0;
1525 if (w->border() && !w->parent()) {
1526 if (w->maxw != w->minw || w->maxh != w->minh) {
1527 ret = 2;
1528 } else {
1529 ret = 1;
1530 }
1531 get_window_frame_sizes(bx, by, bt);
1532 }
1533 // The coordinates of the whole window, including non-client area
1534 xoff = bx;
1535 yoff = by + bt;
1536 dx = 2*bx;
1537 dy = 2*by + bt;
1538 X = w->x()-xoff;
1539 Y = w->y()-yoff;
1540 W = w->w()+dx;
1541 H = w->h()+dy;
1542
1543 // Proceed to positioning the window fully inside the screen, if possible
1544
1545 // let's get a little elaborate here. Mac OS X puts a lot of stuff on the desk
1546 // that we want to avoid when positioning our window, namely the Dock and the
1547 // top menu bar (and even more stuff in 10.4 Tiger). So we will go through the
1548 // list of all available screens and find the one that this window is most
1549 // likely to go to, and then reposition it to fit withing the 'good' area.
1550 // Rect r;
1551 // find the screen, that the center of this window will fall into
1552 int R = X+W, B = Y+H; // right and bottom
1553 int cx = (X+R)/2, cy = (Y+B)/2; // center of window;
1554 NSScreen *gd = NULL;
1555 NSArray *a = [NSScreen screens]; int count = (int)[a count]; NSRect r; int i;
1556 for( i = 0; i < count; i++) {
1557 r = [[a objectAtIndex:i] frame];
1558 cy = int(r.size.height - cy);
1559 if ( cx >= r.origin.x && cx <= r.origin.x + r.size.width
1560 && cy >= r.origin.y && cy <= r.origin.y + r.size.height)
1561 break;
1562 }
1563 if (i < count) gd = [a objectAtIndex:i];
1564
1565 // if the center doesn't fall on a screen, try the top left
1566 if (!gd) {
1567 for( i = 0; i < count; i++) {
1568 r = [[a objectAtIndex:i] frame];
1569 if ( X >= r.origin.x && X <= r.origin.x + r.size.width
1570 && r.size.height - Y >= r.origin.y && r.size.height - Y <= r.origin.y + r.size.height)
1571 break;
1572 }
1573 if (i < count) gd = [a objectAtIndex:i];
1574 }
1575 // if that doesn't fall on a screen, try the top right
1576 if (!gd) {
1577 for( i = 0; i < count; i++) {
1578 r = [[a objectAtIndex:i] frame];
1579 if ( R >= r.origin.x && R <= r.origin.x + r.size.width
1580 && r.size.height - Y >= r.origin.y && r.size.height - Y <= r.origin.y + r.size.height)
1581 break;
1582 }
1583 if (i < count) gd = [a objectAtIndex:i];
1584 }
1585 // if that doesn't fall on a screen, try the bottom left
1586 if (!gd) {
1587 for( i = 0; i < count; i++) {
1588 r = [[a objectAtIndex:i] frame];
1589 if ( X >= r.origin.x && X <= r.origin.x + r.size.width
1590 && Y-H >= r.origin.y && Y-H <= r.origin.y + r.size.height)
1591 break;
1592 }
1593 if (i < count) gd = [a objectAtIndex:i];
1594 }
1595 // last resort, try the bottom right
1596 if (!gd) {
1597 for( i = 0; i < count; i++) {
1598 r = [[a objectAtIndex:i] frame];
1599 if ( R >= r.origin.x && R <= r.origin.x + r.size.width
1600 && Y-H >= r.origin.y && Y-H <= r.origin.y + r.size.height)
1601 break;
1602 }
1603 if (i < count) gd = [a objectAtIndex:i];
1604 }
1605 // if we still have not found a screen, we will use the main
1606 // screen, the one that has the application menu bar.
1607 if (!gd) gd = [a objectAtIndex:0];
1608 if (gd) {
1609 r = [gd visibleFrame];
1610 int sh = int([gd frame].size.height);
1611 if ( R > r.origin.x + r.size.width ) X -= int(R - (r.origin.x + r.size.width));
1612 if ( B > sh - r.origin.y ) Y -= int(B - (sh - r.origin.y));
1613 if ( X < r.origin.x ) X = int(r.origin.x);
1614 if ( Y < sh - (r.origin.y + r.size.height) ) Y = int(sh - (r.origin.y + r.size.height));
1615 }
1616
1617 // Return the client area's top left corner in (X,Y)
1618 X+=xoff;
1619 Y+=yoff;
1620
1621 return ret;
1622}
1623
1624
1625Fl_Window *fl_dnd_target_window = 0;
1626
1627static void q_set_window_title(NSWindow *nsw, const char * name, const char *mininame) {
1628 CFStringRef title = CFStringCreateWithCString(NULL, (name ? name : ""), kCFStringEncodingUTF8);
1629 if(!title) { // fallback when name contains malformed UTF-8
1630 int l = strlen(name);
1631 unsigned short* utf16 = new unsigned short[l + 1];
1632 l = fl_utf8toUtf16(name, l, utf16, l + 1);
1633 title = CFStringCreateWithCharacters(NULL, utf16, l);
1634 delete[] utf16;
1635 }
1636 [nsw setTitle:(NSString*)title];
1637 CFRelease(title);
1638 if (mininame && strlen(mininame)) {
1639 CFStringRef minititle = CFStringCreateWithCString(NULL, mininame, kCFStringEncodingUTF8);
1640 if (minititle) {
1641 [nsw setMiniwindowTitle:(NSString*)minititle];
1642 CFRelease(minititle);
1643 }
1644 }
1645}
1646
1647
1648@interface FLView : NSView <NSTextInput> {
1649 int next_compose_length;
1650 bool in_key_event;
1651}
1652+ (void)prepareEtext:(NSString*)aString;
1653- (id)init;
1654- (void)drawRect:(NSRect)rect;
1655- (BOOL)acceptsFirstResponder;
1656- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent;
DRC685f17e2011-07-28 09:23:00 +00001657- (void)resetCursorRects;
DRC2ff39b82011-07-28 08:38:59 +00001658- (BOOL)performKeyEquivalent:(NSEvent*)theEvent;
1659- (void)mouseUp:(NSEvent *)theEvent;
1660- (void)rightMouseUp:(NSEvent *)theEvent;
1661- (void)otherMouseUp:(NSEvent *)theEvent;
1662- (void)mouseDown:(NSEvent *)theEvent;
1663- (void)rightMouseDown:(NSEvent *)theEvent;
1664- (void)otherMouseDown:(NSEvent *)theEvent;
1665- (void)mouseMoved:(NSEvent *)theEvent;
1666- (void)mouseDragged:(NSEvent *)theEvent;
1667- (void)rightMouseDragged:(NSEvent *)theEvent;
1668- (void)otherMouseDragged:(NSEvent *)theEvent;
1669- (void)scrollWheel:(NSEvent *)theEvent;
Henrik Andersson485f4192011-09-16 11:51:32 +00001670+ (NSString *)keyTranslate:(UInt16)keyCode withModifierFlags:(UInt32)modifierFlags;
DRC2ff39b82011-07-28 08:38:59 +00001671- (BOOL)handleKeyDown:(NSEvent *)theEvent;
1672- (void)keyDown:(NSEvent *)theEvent;
1673- (void)keyUp:(NSEvent *)theEvent;
1674- (void)flagsChanged:(NSEvent *)theEvent;
1675- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender;
1676- (NSDragOperation)draggingUpdated:(id < NSDraggingInfo >)sender;
1677- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
1678- (void)draggingExited:(id < NSDraggingInfo >)sender;
1679- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal;
1680@end
1681
1682@implementation FLView
1683- (id)init
1684{
1685 self = [super init];
1686 if (self) {
1687 next_compose_length = -1;
1688 in_key_event = false;
1689 }
1690 return self;
1691}
1692- (void)drawRect:(NSRect)rect
1693{
1694 fl_lock_function();
1695 FLWindow *cw = (FLWindow*)[self window];
1696 Fl_Window *w = [cw getFl_Window];
1697 handleUpdateEvent(w);
1698 fl_unlock_function();
1699}
1700
1701- (BOOL)acceptsFirstResponder
1702{
1703 return YES;
1704}
1705- (BOOL)performKeyEquivalent:(NSEvent*)theEvent
1706{
1707 //NSLog(@"performKeyEquivalent:");
1708 return [self handleKeyDown:theEvent];
1709}
1710- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent
1711{
1712 Fl_Window *w = [(FLWindow*)[theEvent window] getFl_Window];
1713 Fl_Window *first = Fl::first_window();
1714 return (first == w || !first->modal());
1715}
DRC685f17e2011-07-28 09:23:00 +00001716- (void)resetCursorRects {
1717 Fl_Window *w = [(FLWindow*)[self window] getFl_Window];
1718 Fl_X *i = Fl_X::i(w);
1719 // We have to have at least one cursor rect for invalidateCursorRectsForView
1720 // to work, hence the "else" clause.
1721 if (i->cursor)
1722 [self addCursorRect:[self visibleRect] cursor:(NSCursor*)i->cursor];
1723 else
1724 [self addCursorRect:[self visibleRect] cursor:[NSCursor arrowCursor]];
1725}
DRC2ff39b82011-07-28 08:38:59 +00001726- (void)mouseUp:(NSEvent *)theEvent {
1727 cocoaMouseHandler(theEvent);
1728}
1729- (void)rightMouseUp:(NSEvent *)theEvent {
1730 cocoaMouseHandler(theEvent);
1731}
1732- (void)otherMouseUp:(NSEvent *)theEvent {
1733 cocoaMouseHandler(theEvent);
1734}
1735- (void)mouseDown:(NSEvent *)theEvent {
1736 cocoaMouseHandler(theEvent);
1737}
1738- (void)rightMouseDown:(NSEvent *)theEvent {
1739 cocoaMouseHandler(theEvent);
1740}
1741- (void)otherMouseDown:(NSEvent *)theEvent {
1742 cocoaMouseHandler(theEvent);
1743}
1744- (void)mouseMoved:(NSEvent *)theEvent {
1745 cocoaMouseHandler(theEvent);
1746}
1747- (void)mouseDragged:(NSEvent *)theEvent {
1748 cocoaMouseHandler(theEvent);
1749}
1750- (void)rightMouseDragged:(NSEvent *)theEvent {
1751 cocoaMouseHandler(theEvent);
1752}
1753- (void)otherMouseDragged:(NSEvent *)theEvent {
1754 cocoaMouseHandler(theEvent);
1755}
1756- (void)scrollWheel:(NSEvent *)theEvent {
1757 cocoaMouseWheelHandler(theEvent);
1758}
Henrik Andersson485f4192011-09-16 11:51:32 +00001759+ (NSString *)keyTranslate:(UInt16)keyCode withModifierFlags:(UInt32)modifierFlags {
1760 const UCKeyboardLayout *layout;
1761 OSStatus err;
1762
1763 layout = NULL;
1764
1765#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
1766 TISInputSourceRef keyboard;
1767 CFDataRef uchr;
1768
1769 keyboard = TISCopyCurrentKeyboardInputSource();
1770 uchr = (CFDataRef)TISGetInputSourceProperty(keyboard,
1771 kTISPropertyUnicodeKeyLayoutData);
1772 if (uchr == NULL)
1773 return nil;
1774
1775 layout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr);
1776#else
1777 KeyboardLayoutRef old_layout;
1778 int kind;
1779
1780 err = KLGetCurrentKeyboardLayout(&old_layout);
1781 if (err != noErr)
1782 return nil;
1783
1784 err = KLGetKeyboardLayoutProperty(old_layout, kKLKind,
1785 (const void**)&kind);
1786 if (err != noErr)
1787 return nil;
1788
1789 // Old, crufty layout format?
1790 if (kind == kKLKCHRKind) {
1791 void *kchr_layout;
1792
1793 UInt32 chars, state;
1794 char buf[3];
1795
1796 unichar result[16];
1797 ByteCount in_len, out_len;
1798
1799 err = KLGetKeyboardLayoutProperty(old_layout, kKLKCHRData,
1800 (const void**)&kchr_layout);
1801 if (err != noErr)
1802 return nil;
1803
1804 state = 0;
1805
1806 keyCode &= 0x7f;
1807 modifierFlags &= 0xff00;
1808
1809 chars = KeyTranslate(kchr_layout, keyCode | modifierFlags, &state);
1810
1811 buf[0] = (chars >> 16) & 0xff;
1812 buf[1] = chars & 0xff;
1813 buf[2] = '\0';
1814
1815 if (buf[0] == '\0') {
1816 buf[0] = buf[1];
1817 buf[1] = '\0';
1818 }
1819
1820 // The data is now in some layout specific encoding. Need to convert
1821 // this to unicode.
1822
1823 ScriptCode script;
1824 TextEncoding encoding;
1825 TECObjectRef converter;
1826
1827 script = (ScriptCode)GetScriptManagerVariable(smKeyScript);
1828
1829 err = UpgradeScriptInfoToTextEncoding(script, kTextLanguageDontCare,
1830 kTextRegionDontCare, NULL,
1831 &encoding);
1832 if (err != noErr)
1833 return nil;
1834
1835 err = TECCreateConverter(&converter, encoding, kTextEncodingUnicodeV4_0);
1836 if (err != noErr)
1837 return nil;
1838
1839 in_len = strlen(buf);
1840 out_len = sizeof(result);
1841
1842 err = TECConvertText(converter, (ConstTextPtr)buf, in_len, &in_len,
1843 (TextPtr)result, out_len, &out_len);
1844
1845 TECDisposeConverter(converter);
1846
1847 if (err != noErr)
1848 return nil;
1849
1850 return [NSString stringWithCharacters:result
1851 length:(out_len / sizeof(unichar))];
1852 }
1853
1854 if ((kind != kKLKCHRuchrKind) && (kind != kKLuchrKind))
1855 return nil;
1856
1857 err = KLGetKeyboardLayoutProperty(old_layout, kKLuchrData,
1858 (const void**)&layout);
1859 if (err != noErr)
1860 return nil;
1861#endif
1862
1863 if (layout == NULL)
1864 return nil;
1865
1866 UInt32 dead_state;
1867 UniCharCount max_len, actual_len;
1868 UniChar string[255];
1869
1870 dead_state = 0;
1871 max_len = sizeof(string)/sizeof(*string);
1872
1873 modifierFlags = (modifierFlags >> 8) & 0xff;
1874
1875 err = UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierFlags,
1876 LMGetKbdType(), 0, &dead_state, max_len, &actual_len,
1877 string);
1878 if (err != noErr)
1879 return nil;
1880
1881 return [NSString stringWithCharacters:string length:actual_len];
1882}
DRC2ff39b82011-07-28 08:38:59 +00001883- (BOOL)handleKeyDown:(NSEvent *)theEvent {
1884 //NSLog(@"handleKeyDown");
1885 fl_lock_function();
1886
1887 Fl_Window *window = (Fl_Window*)[(FLWindow*)[theEvent window] getFl_Window];
1888 Fl::first_window(window);
1889
1890 next_compose_length = -1;
1891 // First let's process the raw key press
1892 cocoaKeyboardHandler(theEvent);
1893
1894 int no_text_key = false;
1895 static const int notext[] = { // keys that don't emit text
1896 FL_BackSpace, FL_Print, FL_Scroll_Lock, FL_Pause,
1897 FL_Insert, FL_Home, FL_Page_Up, FL_Delete, FL_End, FL_Page_Down,
1898 FL_Left, FL_Up, FL_Right, FL_Down,
1899 FL_Menu, FL_Num_Lock, FL_Help
1900 };
1901 static const int count = sizeof(notext)/sizeof(int);
1902 if (Fl::e_keysym > FL_F && Fl::e_keysym <= FL_F_Last) no_text_key = true;
1903 else for (int i=0; i < count; i++) {
1904 if (notext[i] == Fl::e_keysym) {
1905 no_text_key = true;
1906 break;
1907 }
1908 }
Henrik Andersson485f4192011-09-16 11:51:32 +00001909 if (!no_text_key) {
DRC685f17e2011-07-28 09:23:00 +00001910 // The simple keyboard model will ignore insertText, so we need to grab
1911 // the symbol directly from the event. Note that we still use setMarkedText.
Henrik Andersson485f4192011-09-16 11:51:32 +00001912 if (use_simple_keyboard) {
1913 NSString *simple_chars;
1914 UInt32 modifiers;
1915
1916 // We want a "normal" symbol out of the event, which basically means
1917 // we only respect the shift and alt/altgr modifiers. Cocoa can help
1918 // us if we only wanted shift, but as we also want alt/altgr, we'll
1919 // have to do some lookup ourselves. This matches our behaviour on
1920 // other platforms.
1921
1922 modifiers = 0;
1923 if ([theEvent modifierFlags] & NSAlphaShiftKeyMask)
1924 modifiers |= alphaLock;
1925 if ([theEvent modifierFlags] & NSShiftKeyMask)
1926 modifiers |= shiftKey;
1927 if ([theEvent modifierFlags] & NSAlternateKeyMask)
1928 modifiers |= optionKey;
1929
1930 simple_chars = [FLView keyTranslate:[theEvent keyCode]
1931 withModifierFlags:modifiers];
1932 if (simple_chars == nil) {
1933 // Something went wrong. Fall back to what Cocoa gave us...
1934 simple_chars = [theEvent charactersIgnoringModifiers];
1935 }
1936
1937 [FLView prepareEtext:simple_chars];
1938 }
DRC685f17e2011-07-28 09:23:00 +00001939
DRC2ff39b82011-07-28 08:38:59 +00001940 // Then we can let the OS have a stab at it and see if it thinks it
1941 // should result in some text
Henrik Andersson485f4192011-09-16 11:51:32 +00001942
1943 // Don't send cmd-<key> to interpretKeyEvents because it beeps.
1944 if (!(Fl::e_state & FL_META)) {
1945 NSText *edit = [[theEvent window] fieldEditor:YES forObject:nil];
1946 in_key_event = true;
1947 [edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
1948 in_key_event = false;
1949 }
DRC2ff39b82011-07-28 08:38:59 +00001950 }
1951 //NSLog(@"to text=%@ l=%d", [NSString stringWithUTF8String:Fl::e_text], Fl::e_length);
1952 int handled = Fl::handle(FL_KEYDOWN, window);
1953 // We have to update this after Fl::handle as it says what to do on the
1954 // _next_ input
1955 if (next_compose_length != -1)
1956 Fl::compose_state = next_compose_length;
1957
1958 fl_unlock_function();
1959 return (handled ? YES : NO);
1960}
1961- (void)keyDown:(NSEvent *)theEvent {
1962 //NSLog(@"keyDown: ");
1963 [self handleKeyDown:theEvent];
1964}
1965- (void)keyUp:(NSEvent *)theEvent {
1966 //NSLog(@"keyUp: ");
1967 fl_lock_function();
1968 Fl_Window *window = (Fl_Window*)[(FLWindow*)[theEvent window] getFl_Window];
1969 Fl::first_window(window);
1970 cocoaKeyboardHandler(theEvent);
1971 NSString *s = [theEvent characters];
1972 if ([s length] >= 1) [FLView prepareEtext:[s substringToIndex:1]];
1973 Fl::handle(FL_KEYUP,window);
1974 fl_unlock_function();
1975}
1976- (void)flagsChanged:(NSEvent *)theEvent {
1977 //NSLog(@"flagsChanged: ");
1978 fl_lock_function();
1979 static UInt32 prevMods = 0;
1980 NSUInteger mods = [theEvent modifierFlags];
1981 Fl_Window *window = (Fl_Window*)[(FLWindow*)[theEvent window] getFl_Window];
1982 UInt32 tMods = prevMods ^ mods;
1983 int sendEvent = 0;
1984 if ( tMods )
1985 {
1986 unsigned short keycode = [theEvent keyCode];
1987 Fl::e_keysym = Fl::e_original_keysym = macKeyLookUp[keycode & 0x7f];
1988 if ( Fl::e_keysym )
1989 sendEvent = ( prevMods<mods ) ? FL_KEYBOARD : FL_KEYUP;
1990 Fl::e_length = 0;
1991 Fl::e_text = (char*)"";
1992 prevMods = mods;
1993 }
1994 mods_to_e_state( mods );
1995 while (window->parent()) window = window->window();
1996 if (sendEvent) Fl::handle(sendEvent,window);
1997 fl_unlock_function();
1998}
1999- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender
2000{
2001 fl_lock_function();
2002 Fl_Window *target = [(FLWindow*)[self window] getFl_Window];
2003 update_e_xy_and_e_xy_root([self window]);
2004 fl_dnd_target_window = target;
2005 int ret = Fl::handle( FL_DND_ENTER, target );
2006 breakMacEventLoop();
2007 fl_unlock_function();
2008 return ret ? NSDragOperationCopy : NSDragOperationNone;
2009}
2010- (NSDragOperation)draggingUpdated:(id < NSDraggingInfo >)sender
2011{
2012 fl_lock_function();
2013 Fl_Window *target = [(FLWindow*)[self window] getFl_Window];
2014 update_e_xy_and_e_xy_root([self window]);
2015 fl_dnd_target_window = target;
2016 int ret = Fl::handle( FL_DND_DRAG, target );
2017 breakMacEventLoop();
2018 fl_unlock_function();
2019 return ret ? NSDragOperationCopy : NSDragOperationNone;
2020}
2021- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
2022{
2023 static char *DragData = NULL;
2024 fl_lock_function();
2025 Fl_Window *target = [(FLWindow*)[self window] getFl_Window];
2026 if ( !Fl::handle( FL_DND_RELEASE, target ) ) {
2027 breakMacEventLoop();
2028 fl_unlock_function();
2029 return NO;
2030 }
2031 NSPasteboard *pboard;
2032 // NSDragOperation sourceDragMask;
2033 // sourceDragMask = [sender draggingSourceOperationMask];
2034 pboard = [sender draggingPasteboard];
2035 update_e_xy_and_e_xy_root([self window]);
2036 if (DragData) { free(DragData); DragData = NULL; }
2037 if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
2038 CFArrayRef files = (CFArrayRef)[pboard propertyListForType:NSFilenamesPboardType];
2039 CFStringRef all = CFStringCreateByCombiningStrings(NULL, files, CFSTR("\n"));
2040 int l = CFStringGetMaximumSizeForEncoding(CFStringGetLength(all), kCFStringEncodingUTF8);
2041 DragData = (char *)malloc(l + 1);
2042 CFStringGetCString(all, DragData, l + 1, kCFStringEncodingUTF8);
2043 CFRelease(all);
2044 }
2045 else if ( [[pboard types] containsObject:NSStringPboardType] ) {
2046 NSData *data = [pboard dataForType:NSStringPboardType];
2047 DragData = (char *)malloc([data length] + 1);
2048 [data getBytes:DragData];
2049 DragData[[data length]] = 0;
2050 convert_crlf(DragData, strlen(DragData));
2051 }
2052 else {
2053 breakMacEventLoop();
2054 fl_unlock_function();
2055 return NO;
2056 }
2057 Fl::e_text = DragData;
2058 Fl::e_length = strlen(DragData);
2059 int old_event = Fl::e_number;
2060 Fl::belowmouse()->handle(Fl::e_number = FL_PASTE);
2061 Fl::e_number = old_event;
2062 if (DragData) { free(DragData); DragData = NULL; }
2063 Fl::e_text = NULL;
2064 Fl::e_length = 0;
2065 fl_dnd_target_window = NULL;
2066 breakMacEventLoop();
2067 fl_unlock_function();
2068 return YES;
2069}
2070- (void)draggingExited:(id < NSDraggingInfo >)sender
2071{
2072 fl_lock_function();
2073 if ( fl_dnd_target_window ) {
2074 Fl::handle( FL_DND_LEAVE, fl_dnd_target_window );
2075 fl_dnd_target_window = 0;
2076 }
2077 fl_unlock_function();
2078}
2079- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
2080{
2081 return NSDragOperationGeneric;
2082}
2083
2084+ (void)prepareEtext:(NSString*)aString {
2085 // fills Fl::e_text with UTF-8 encoded aString using an adequate memory allocation
2086 static char *received_utf8 = NULL;
2087 static int lreceived = 0;
2088 char *p = (char*)[aString UTF8String];
2089 int l = strlen(p);
2090 if (l > 0) {
2091 if (lreceived == 0) {
2092 received_utf8 = (char*)malloc(l + 1);
2093 lreceived = l;
2094 }
2095 else if (l > lreceived) {
2096 received_utf8 = (char*)realloc(received_utf8, l + 1);
2097 lreceived = l;
2098 }
2099 strcpy(received_utf8, p);
2100 Fl::e_text = received_utf8;
2101 }
2102 Fl::e_length = l;
2103}
2104
2105// These functions implement text input.
2106// Only two-stroke character composition works at this point.
2107// Needs much elaboration to fully support CJK text input,
2108// but this is the way to go.
2109- (void)doCommandBySelector:(SEL)aSelector {
2110}
2111
2112- (void)insertText:(id)aString {
2113 NSString *received;
2114 if ([aString isKindOfClass:[NSAttributedString class]]) {
2115 received = [(NSAttributedString*)aString string];
2116 } else {
2117 received = (NSString*)aString;
2118 }
2119 //NSLog(@"insertText: received=%@",received);
2120
2121 if (!in_key_event) fl_lock_function();
DRC685f17e2011-07-28 09:23:00 +00002122
2123 // Simple keyboard widgets do not want these side channel inputs.
2124 if (use_simple_keyboard)
2125 goto end;
2126
DRC2ff39b82011-07-28 08:38:59 +00002127 [FLView prepareEtext:received];
DRC685f17e2011-07-28 09:23:00 +00002128
DRC2ff39b82011-07-28 08:38:59 +00002129 // We can get called outside of key events (e.g. from the character
DRC685f17e2011-07-28 09:23:00 +00002130 // palette). We need to fake our own key event at that point.
DRC2ff39b82011-07-28 08:38:59 +00002131 if (!in_key_event) {
2132 Fl_Window *target = [(FLWindow*)[self window] getFl_Window];
DRC685f17e2011-07-28 09:23:00 +00002133 Fl::e_keysym = Fl::e_original_keysym = 0;
2134 Fl::handle(FL_KEYDOWN, target);
DRC2ff39b82011-07-28 08:38:59 +00002135 // for some reason, the window does not redraw until the next mouse move or button push
2136 // sending a 'redraw()' or 'awake()' does not solve the issue!
2137 Fl::flush();
2138 }
DRC685f17e2011-07-28 09:23:00 +00002139
2140end:
DRC2ff39b82011-07-28 08:38:59 +00002141 if (!in_key_event) fl_unlock_function();
2142}
2143
2144- (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection {
DRC685f17e2011-07-28 09:23:00 +00002145 NSString *received, *current, *aggregate;
DRC2ff39b82011-07-28 08:38:59 +00002146 if (newSelection.location == 0) {
2147 [self unmarkText];
2148 return;
2149 }
2150 if ([aString isKindOfClass:[NSAttributedString class]]) {
2151 received = [(NSAttributedString*)aString string];
2152 } else {
2153 received = (NSString*)aString;
2154 }
2155 //NSLog(@"setMarkedText: %@ %d %d",received,newSelection.location,newSelection.length);
DRC685f17e2011-07-28 09:23:00 +00002156
2157 fl_lock_function();
2158
2159 // Simple keyboard widgets generally do not want these side channel
2160 // inputs, but we have no other way of getting dead keys so we make
2161 // an exception in that case.
2162 if (use_simple_keyboard) {
2163 if (in_key_event && (Fl::e_length == 0)) {
2164 [FLView prepareEtext:received];
2165
2166 Fl::e_text = (char*)cocoaDead2FLTK(Fl::e_text);
2167 Fl::e_length = strlen(Fl::e_text);
2168 }
2169 goto end;
2170 }
2171
DRC2ff39b82011-07-28 08:38:59 +00002172 // This code creates the OS X behaviour of seeing dead keys as things
2173 // are being composed.
DRC685f17e2011-07-28 09:23:00 +00002174 //
2175 // Note: The concatenation thing is because of how OS X deals with
2176 // invalid sequences. At that point it will spit out one call
2177 // to insertText with the now aborted sequence, and one new
2178 // call to setMarkedText with the new sequence. Since we want
2179 // both to be visible, we need to concatenate.
DRC2ff39b82011-07-28 08:38:59 +00002180 next_compose_length = newSelection.location;
DRC685f17e2011-07-28 09:23:00 +00002181 current = [NSString stringWithUTF8String:Fl::e_text];
2182 aggregate = [current stringByAppendingString:received];
2183
2184 [FLView prepareEtext:aggregate];
2185 //NSLog(@"Fl::e_text=%@ Fl::e_length=%d next_compose_length=%d", aggregate, Fl::e_length, next_compose_length);
2186
2187 // We can get called outside of key events (e.g. from the character
2188 // palette). We need to fake our own key event at that point.
2189 if (!in_key_event) {
2190 Fl_Window *target = [(FLWindow*)[self window] getFl_Window];
2191 Fl::e_keysym = Fl::e_original_keysym = 0;
2192 Fl::handle(FL_KEYDOWN, target);
2193 }
2194
2195end:
2196 fl_unlock_function();
DRC2ff39b82011-07-28 08:38:59 +00002197}
2198
2199- (void)unmarkText {
2200 fl_lock_function();
2201 Fl::compose_state = 0;
2202 fl_unlock_function();
2203 //NSLog(@"unmarkText");
2204}
2205
2206- (NSRange)selectedRange {
2207 return NSMakeRange(NSNotFound, 0);
2208}
2209
2210- (NSRange)markedRange {
2211 //NSLog(@"markedRange ?");
2212 return NSMakeRange(NSNotFound, Fl::compose_state);
2213}
2214
2215- (BOOL)hasMarkedText {
2216 //NSLog(@"hasMarkedText %s", Fl::compose_state > 0?"YES":"NO");
2217 return (Fl::compose_state > 0);
2218}
2219
2220- (NSAttributedString *)attributedSubstringFromRange:(NSRange)aRange {
2221 //NSLog(@"attributedSubstringFromRange: %d %d",aRange.location,aRange.length);
2222 return nil;
2223}
2224
2225- (NSArray *)validAttributesForMarkedText {
2226 return nil;
2227}
2228
2229- (NSRect)firstRectForCharacterRange:(NSRange)aRange {
2230 NSRect glyphRect;
2231 fl_lock_function();
2232 Fl_Widget *focus = Fl::focus();
2233 Fl_Window *wfocus = focus->window();
2234 while (wfocus->window()) wfocus = wfocus->window();
2235 glyphRect.size.width = 0;
2236
2237 if (dynamic_cast<Fl_Text_Display*>(focus) != NULL) {
2238 int x, y;
2239 Fl_Text_Display *current = (Fl_Text_Display*)focus;
2240 current->position_to_xy( current->insert_position(), &x, &y );
2241 glyphRect.origin.x = (CGFloat)x;
2242 glyphRect.origin.y = (CGFloat)y + current->textsize();
2243 glyphRect.size.height = current->textsize();
2244 } else {
2245 glyphRect.origin.x = focus->x();
2246 glyphRect.origin.y = focus->y() + focus->h();
2247 glyphRect.size.height = 12;
2248 }
2249 // Convert the rect to screen coordinates
2250 glyphRect.origin.y = wfocus->h() - glyphRect.origin.y;
2251 glyphRect.origin = [[self window] convertBaseToScreen:glyphRect.origin];
2252 fl_unlock_function();
2253 return glyphRect;
2254}
2255
2256- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint {
2257 return 0;
2258}
2259
2260- (NSInteger)conversationIdentifier {
2261 return (NSInteger)self;
2262}
2263
2264@end
2265
DRC685f17e2011-07-28 09:23:00 +00002266void fullscreen_x(Fl_Window *w) {
2267 w->_set_fullscreen();
2268 /* On OS X < 10.6, it is necessary to recreate the window. This is done
2269 with hide+show. */
2270 w->hide();
2271 w->show();
2272 Fl::handle(FL_FULLSCREEN, w);
2273}
2274
2275void fullscreen_off_x(Fl_Window *w, int X, int Y, int W, int H) {
2276 w->_clear_fullscreen();
2277 w->hide();
2278 w->resize(X, Y, W, H);
2279 w->show();
2280 Fl::handle(FL_FULLSCREEN, w);
2281}
DRC2ff39b82011-07-28 08:38:59 +00002282
2283/*
2284 * go ahead, create that (sub)window
2285 */
2286void Fl_X::make(Fl_Window* w)
2287{
2288 static int xyPos = 100;
2289 if ( w->parent() ) { // create a subwindow
2290 Fl_Group::current(0);
2291 // our subwindow needs this structure to know about its clipping.
2292 Fl_X* x = new Fl_X;
2293 x->subwindow = true;
2294 x->other_xid = 0;
2295 x->region = 0;
2296 x->subRegion = 0;
DRC685f17e2011-07-28 09:23:00 +00002297 x->cursor = NULL;
DRC2ff39b82011-07-28 08:38:59 +00002298 x->gc = 0; // stay 0 for Quickdraw; fill with CGContext for Quartz
2299 Fl_Window *win = w->window();
2300 Fl_X *xo = Fl_X::i(win);
2301 if (xo) {
2302 x->xidNext = xo->xidChildren;
2303 x->xidChildren = 0L;
2304 xo->xidChildren = x;
2305 x->xid = win->i->xid;
2306 x->w = w; w->i = x;
2307 x->wait_for_expose = 0;
2308 {
2309 Fl_X *z = xo->next; // we don't want a subwindow in Fl_X::first
2310 xo->next = x;
2311 x->next = z;
2312 }
2313 int old_event = Fl::e_number;
2314 w->handle(Fl::e_number = FL_SHOW);
2315 Fl::e_number = old_event;
2316 w->redraw(); // force draw to happen
2317 }
2318 if (w->as_gl_window()) { // if creating a sub-GL-window
2319 while (win->window()) win = win->window();
2320 [(FLWindow*)Fl_X::i(win)->xid setContainsGLsubwindow:YES];
2321 }
2322 fl_show_iconic = 0;
2323 }
2324 else { // create a desktop window
2325 Fl_Group::current(0);
2326 fl_open_display();
2327 NSInteger winlevel = NSNormalWindowLevel;
2328 NSUInteger winstyle;
2329 if (w->border()) winstyle = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask;
2330 else winstyle = NSBorderlessWindowMask;
2331 int xp = w->x();
2332 int yp = w->y();
2333 int wp = w->w();
2334 int hp = w->h();
2335 if (w->size_range_set) {
2336 if ( w->minh != w->maxh || w->minw != w->maxw) {
2337 winstyle |= NSResizableWindowMask;
2338 }
2339 } else {
2340 if (w->resizable()) {
2341 Fl_Widget *o = w->resizable();
2342 int minw = o->w(); if (minw > 100) minw = 100;
2343 int minh = o->h(); if (minh > 100) minh = 100;
2344 w->size_range(w->w() - o->w() + minw, w->h() - o->h() + minh, 0, 0);
2345 winstyle |= NSResizableWindowMask;
2346 } else {
2347 w->size_range(w->w(), w->h(), w->w(), w->h());
2348 }
2349 }
2350 int xwm = xp, ywm = yp, bt, bx, by;
2351
2352 if (!fake_X_wm(w, xwm, ywm, bt, bx, by)) {
2353 // menu windows and tooltips
2354 if (w->modal()||w->tooltip_window()) {
2355 winstyle = NSBorderlessWindowMask;
2356 winlevel = NSModalPanelWindowLevel;
2357 } else {
2358 winstyle = NSBorderlessWindowMask;
2359 }
2360 } else if (w->modal()) {
2361 winstyle &= ~NSMiniaturizableWindowMask;
2362 // winstyle &= ~(NSResizableWindowMask | NSMiniaturizableWindowMask);
2363 winlevel = NSModalPanelWindowLevel;
2364 }
2365 else if (w->non_modal()) {
2366 winlevel = NSFloatingWindowLevel;
2367 }
2368
2369 if (by+bt) {
2370 wp += 2*bx;
2371 hp += 2*by+bt;
2372 }
2373 if (!(w->flags() & Fl_Window::FORCE_POSITION)) {
2374 // use the Carbon functions below for default window positioning
2375 w->x(xyPos+Fl::x());
2376 w->y(xyPos+Fl::y());
2377 xyPos += 25;
2378 if (xyPos>200) xyPos = 100;
2379 } else {
2380 if (!Fl::grab()) {
2381 xp = xwm; yp = ywm;
2382 w->x(xp);w->y(yp);
2383 }
2384 xp -= bx;
2385 yp -= by+bt;
2386 }
2387
2388 if (w->non_modal() && Fl_X::first /*&& !fl_disable_transient_for*/) {
2389 // find some other window to be "transient for":
2390 Fl_Window* w = Fl_X::first->w;
2391 while (w->parent()) w = w->window(); // todo: this code does not make any sense! (w!=w??)
2392 }
2393
2394 Fl_X* x = new Fl_X;
2395 x->subwindow = false;
2396 x->other_xid = 0; // room for doublebuffering image map. On OS X this is only used by overlay windows
2397 x->region = 0;
2398 x->subRegion = 0;
DRC685f17e2011-07-28 09:23:00 +00002399 x->cursor = NULL;
DRC2ff39b82011-07-28 08:38:59 +00002400 x->xidChildren = 0;
2401 x->xidNext = 0;
2402 x->gc = 0;
2403
2404 NSRect srect = [[NSScreen mainScreen] frame];
DRC685f17e2011-07-28 09:23:00 +00002405 if (w->flags() & Fl_Widget::FULLSCREEN) {
2406 int sx, sy, sw, sh;
2407 Fl::screen_xywh(sx, sy, sw, sh, w->x(), w->y(), w->w(), w->h());
2408 w->resize(sx, sy, sw, sh);
2409 winstyle = NSBorderlessWindowMask;
2410 winlevel = NSStatusWindowLevel;
2411 }
DRC2ff39b82011-07-28 08:38:59 +00002412 NSRect crect;
2413 crect.origin.x = w->x();
2414 crect.origin.y = srect.size.height - (w->y() + w->h());
2415 crect.size.width=w->w();
2416 crect.size.height=w->h();
2417 FLWindow *cw = [[FLWindow alloc] initWithFl_W:w
2418 contentRect:crect
2419 styleMask:winstyle];
2420 [cw setHasShadow:YES];
2421 [cw setAcceptsMouseMovedEvents:YES];
2422 x->xid = cw;
2423 FLView *myview = [[FLView alloc] init];
2424 [cw setContentView:myview];
2425 [cw setLevel:winlevel];
2426
2427 q_set_window_title(cw, w->label(), w->iconlabel());
2428 if (!(w->flags() & Fl_Window::FORCE_POSITION)) {
2429 if (w->modal()) {
2430 [cw center];
2431 } else if (w->non_modal()) {
2432 [cw center];
2433 } else {
2434 static NSPoint delta = NSZeroPoint;
2435 delta = [cw cascadeTopLeftFromPoint:delta];
2436 }
2437 }
2438 if(w->menu_window()) { // make menu windows slightly transparent
2439 [cw setAlphaValue:0.97];
2440 }
2441 x->w = w; w->i = x;
2442 x->wait_for_expose = 1;
2443 x->next = Fl_X::first;
2444 Fl_X::first = x;
2445 // Install DnD handlers
2446 [myview registerForDraggedTypes:[NSArray arrayWithObjects:
2447 NSStringPboardType, NSFilenamesPboardType, nil]];
2448 if ( ! Fl_X::first->next ) {
2449 // if this is the first window, we need to bring the application to the front
2450 ProcessSerialNumber psn = { 0, kCurrentProcess };
2451 SetFrontProcess( &psn );
2452 }
2453
2454 if (w->size_range_set) w->size_range_();
2455
2456 if ( w->border() || (!w->modal() && !w->tooltip_window()) ) {
2457 Fl_Tooltip::enter(0);
2458 }
2459 w->set_visible();
2460 if ( w->border() || (!w->modal() && !w->tooltip_window()) ) Fl::handle(FL_FOCUS, w);
2461 Fl::first_window(w);
2462 [cw setDelegate:mydelegate];
2463 if (fl_show_iconic) {
2464 fl_show_iconic = 0;
2465 [cw miniaturize:nil];
2466 } else {
2467 [cw makeKeyAndOrderFront:nil];
2468 }
2469
2470 crect = [[cw contentView] frame];
2471 w->w(int(crect.size.width));
2472 w->h(int(crect.size.height));
2473 crect = [cw frame];
2474 w->x(int(crect.origin.x));
2475 srect = [[cw screen] frame];
2476 w->y(int(srect.size.height - (crect.origin.y + w->h())));
2477
2478 int old_event = Fl::e_number;
2479 w->handle(Fl::e_number = FL_SHOW);
2480 Fl::e_number = old_event;
2481
2482 if (w->modal()) { Fl::modal_ = w; fl_fix_focus(); }
2483 }
2484}
2485
2486
2487/*
2488 * Tell the OS what window sizes we want to allow
2489 */
2490void Fl_Window::size_range_() {
2491 int bx, by, bt;
2492 get_window_frame_sizes(bx, by, bt);
2493 size_range_set = 1;
2494 NSSize minSize = { minw, minh + bt };
2495 NSSize maxSize = { maxw?maxw:32000, maxh?maxh + bt:32000 };
2496 if (i && i->xid) {
2497 [(NSWindow*)i->xid setMinSize:minSize];
2498 [(NSWindow*)i->xid setMaxSize:maxSize];
2499 }
2500}
2501
2502
2503/*
2504 * returns pointer to the filename, or null if name ends with ':'
2505 */
2506const char *fl_filename_name( const char *name )
2507{
2508 const char *p, *q;
2509 if (!name) return (0);
2510 for ( p = q = name ; *p ; ) {
2511 if ( ( p[0] == ':' ) && ( p[1] == ':' ) ) {
2512 q = p+2;
2513 p++;
2514 }
2515 else if (p[0] == '/') {
2516 q = p + 1;
2517 }
2518 p++;
2519 }
2520 return q;
2521}
2522
2523
2524/*
2525 * set the window title bar name
2526 */
2527void Fl_Window::label(const char *name, const char *mininame) {
2528 Fl_Widget::label(name);
2529 iconlabel_ = mininame;
2530 if (shown() || i) {
2531 NSWindow* nsw = (NSWindow*)i->xid;
2532 q_set_window_title(nsw, name, mininame);
2533 }
2534}
2535
2536
2537/*
2538 * make a window visible
2539 */
2540void Fl_Window::show() {
2541 image(Fl::scheme_bg_);
2542 if (Fl::scheme_bg_) {
2543 labeltype(FL_NORMAL_LABEL);
2544 align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE | FL_ALIGN_CLIP);
2545 } else {
2546 labeltype(FL_NO_LABEL);
2547 }
2548 Fl_Tooltip::exit(this);
2549 if (!shown() || !i) {
2550 Fl_X::make(this);
2551 } else {
2552 if ( !parent() ) {
2553 if ([(NSWindow*)i->xid isMiniaturized]) {
2554 i->w->redraw();
2555 [(NSWindow*)i->xid deminiaturize:nil];
2556 }
2557 if (!fl_capture) {
2558 [(NSWindow*)i->xid makeKeyAndOrderFront:nil];
2559 }
2560 }
2561 }
2562}
2563
2564
2565/*
2566 * resize a window
2567 */
2568void Fl_Window::resize(int X,int Y,int W,int H) {
2569 if (W<=0) W = 1; // OS X does not like zero width windows
2570 if (H<=0) H = 1;
2571 int is_a_resize = (W != w() || H != h());
2572 // printf("Fl_Window::resize(X=%d, Y=%d, W=%d, H=%d), is_a_resize=%d, resize_from_system=%p, this=%p\n",
2573 // X, Y, W, H, is_a_resize, resize_from_system, this);
2574 if (X != x() || Y != y()) set_flag(FORCE_POSITION);
2575 else if (!is_a_resize) return;
2576 if ( (resize_from_system!=this) && (!parent()) && shown()) {
2577 if (is_a_resize) {
2578 if (resizable()) {
2579 if (W<minw) minw = W; // user request for resize takes priority
2580 if (maxw && W>maxw) maxw = W; // over a previously set size_range
2581 if (H<minh) minh = H;
2582 if (maxh && H>maxh) maxh = H;
2583 size_range(minw, minh, maxw, maxh);
2584 } else {
2585 size_range(W, H, W, H);
2586 }
2587 int bx, by, bt;
2588 if ( ! this->border() ) bt = 0;
2589 else get_window_frame_sizes(bx, by, bt);
2590 NSRect dim;
2591 dim.origin.x = X;
2592 dim.origin.y = [[(NSWindow*)i->xid screen] frame].size.height - (Y + H);
2593 dim.size.width = W;
2594 dim.size.height = H + bt;
Pierre Ossmanf5e59af2011-11-25 09:18:28 +00002595 [(NSWindow*)i->xid setFrame:dim display:YES]; // calls windowDidResize
DRC2ff39b82011-07-28 08:38:59 +00002596 } else {
2597 NSPoint pt;
2598 pt.x = X;
2599 pt.y = [[(NSWindow*)i->xid screen] frame].size.height - (Y + h());
Pierre Ossmanf5e59af2011-11-25 09:18:28 +00002600 [(NSWindow*)i->xid setFrameOrigin:pt]; // calls windowDidMove
DRC2ff39b82011-07-28 08:38:59 +00002601 }
Pierre Ossmandd321ad2011-11-21 14:04:29 +00002602 return;
DRC2ff39b82011-07-28 08:38:59 +00002603 }
2604 resize_from_system = 0;
2605 if (is_a_resize) {
2606 Fl_Group::resize(X,Y,W,H);
2607 if (shown()) {
2608 redraw();
2609 }
2610 } else {
2611 x(X); y(Y);
2612 }
2613}
2614
2615
2616/*
2617 * make all drawing go into this window (called by subclass flush() impl.)
2618 */
2619void Fl_Window::make_current()
2620{
2621 Fl_X::q_release_context();
2622 fl_window = i->xid;
2623 current_ = this;
2624
2625 int xp = 0, yp = 0;
2626 Fl_Window *win = this;
2627 while ( win ) {
2628 if ( !win->window() )
2629 break;
2630 xp += win->x();
2631 yp += win->y();
2632 win = (Fl_Window*)win->window();
2633 }
2634
2635 NSView *current_focus = [NSView focusView];
2636 // sometimes current_focus is set to a non-FLTK view: don't touch that
2637 if ( [current_focus isKindOfClass:[FLView class]] ) [current_focus unlockFocus];
2638 [[(NSWindow*)i->xid contentView] lockFocus];
2639 i->gc = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
2640 fl_gc = i->gc;
2641 Fl_Region fl_window_region = XRectangleRegion(0,0,w(),h());
2642 if ( ! this->window() ) {
2643 for ( Fl_X *cx = i->xidChildren; cx; cx = cx->xidNext ) { // clip-out all sub-windows
2644 Fl_Window *cw = cx->w;
2645 Fl_Region from = fl_window_region;
2646 fl_window_region = MacRegionMinusRect(from, cw->x(), cw->y(), cw->w(), cw->h() );
2647 XDestroyRegion(from);
2648 }
2649 }
2650
2651 // antialiasing must be deactivated because it applies to rectangles too
2652 // and escapes even clipping!!!
2653 // it gets activated when needed (e.g., draw text)
2654 CGContextSetShouldAntialias(fl_gc, false);
2655 CGFloat hgt = [[(NSWindow*)fl_window contentView] frame].size.height;
2656 CGContextTranslateCTM(fl_gc, 0.5, hgt-0.5f);
2657 CGContextScaleCTM(fl_gc, 1.0f, -1.0f); // now 0,0 is top-left point of the window
2658 win = this;
2659 while(win && win->window()) { // translate to subwindow origin if this is a subwindow context
2660 CGContextTranslateCTM(fl_gc, win->x(), win->y());
2661 win = win->window();
2662 }
2663 //apply window's clip
2664 CGContextClipToRects(fl_gc, fl_window_region->rects, fl_window_region->count );
2665 XDestroyRegion(fl_window_region);
2666// this is the context with origin at top left of (sub)window clipped out of its subwindows if any
2667 CGContextSaveGState(fl_gc);
2668#if defined(FLTK_USE_CAIRO)
2669 if (Fl::cairo_autolink_context()) Fl::cairo_make_current(this); // capture gc changes automatically to update the cairo context adequately
2670#endif
2671 fl_clip_region( 0 );
2672
2673#if defined(FLTK_USE_CAIRO)
2674 // update the cairo_t context
2675 if (Fl::cairo_autolink_context()) Fl::cairo_make_current(this);
2676#endif
2677}
2678
2679// helper function to manage the current CGContext fl_gc
2680extern void fl_quartz_restore_line_style_();
2681
2682// FLTK has only one global graphics state. This function copies the FLTK state into the
2683// current Quartz context
2684void Fl_X::q_fill_context() {
2685 if (!fl_gc) return;
2686 if ( ! fl_window) { // a bitmap context
2687 size_t hgt = CGBitmapContextGetHeight(fl_gc);
2688 CGContextTranslateCTM(fl_gc, 0.5, hgt-0.5f);
2689 CGContextScaleCTM(fl_gc, 1.0f, -1.0f); // now 0,0 is top-left point of the context
2690 }
2691 fl_color(fl_graphics_driver->color());
2692 fl_quartz_restore_line_style_();
2693}
2694
2695// The only way to reset clipping to its original state is to pop the current graphics
2696// state and restore the global state.
2697void Fl_X::q_clear_clipping() {
2698 if (!fl_gc) return;
2699 CGContextRestoreGState(fl_gc);
2700 CGContextSaveGState(fl_gc);
2701}
2702
2703// Give the Quartz context back to the system
2704void Fl_X::q_release_context(Fl_X *x) {
2705 if (x && x->gc!=fl_gc) return;
2706 if (!fl_gc) return;
2707 CGContextRestoreGState(fl_gc); // matches the CGContextSaveGState of make_current
2708 fl_gc = 0;
2709#if defined(FLTK_USE_CAIRO)
2710 if (Fl::cairo_autolink_context()) Fl::cairo_make_current((Fl_Window*) 0); // capture gc changes automatically to update the cairo context adequately
2711#endif
2712}
2713
2714void Fl_X::q_begin_image(CGRect &rect, int cx, int cy, int w, int h) {
2715 CGContextSaveGState(fl_gc);
2716 CGRect r2 = rect;
2717 r2.origin.x -= 0.5f;
2718 r2.origin.y -= 0.5f;
2719 CGContextClipToRect(fl_gc, r2);
2720 // move graphics context to origin of vertically reversed image
2721 CGContextTranslateCTM(fl_gc, rect.origin.x - cx - 0.5, rect.origin.y - cy + h - 0.5);
2722 CGContextScaleCTM(fl_gc, 1, -1);
2723 rect.origin.x = rect.origin.y = 0;
2724 rect.size.width = w;
2725 rect.size.height = h;
2726}
2727
2728void Fl_X::q_end_image() {
2729 CGContextRestoreGState(fl_gc);
2730}
2731
2732
2733////////////////////////////////////////////////////////////////
2734// Copy & Paste fltk implementation.
2735////////////////////////////////////////////////////////////////
2736
2737static void convert_crlf(char * s, size_t len)
2738{
2739 // turn all \r characters into \n:
2740 for (size_t x = 0; x < len; x++) if (s[x] == '\r') s[x] = '\n';
2741}
2742
2743// fltk 1.3 clipboard support constant definitions:
2744const CFStringRef flavorNames[] = {
2745 CFSTR("public.utf16-plain-text"),
2746 CFSTR("public.utf8-plain-text"),
2747 CFSTR("com.apple.traditional-mac-plain-text") };
2748const CFStringEncoding encodings[] = {
2749 kCFStringEncodingUnicode,
2750 kCFStringEncodingUTF8,
2751 kCFStringEncodingMacRoman};
2752const size_t handledFlavorsCount = sizeof(encodings)/sizeof(CFStringEncoding);
2753
2754// clipboard variables definitions :
2755char *fl_selection_buffer[2];
2756int fl_selection_length[2];
2757static int fl_selection_buffer_length[2];
2758
2759static PasteboardRef myPasteboard = 0;
2760static void allocatePasteboard() {
2761 if (!myPasteboard)
2762 PasteboardCreate(kPasteboardClipboard, &myPasteboard);
2763}
2764
2765
2766/*
2767 * create a selection
2768 * owner: widget that created the selection
2769 * stuff: pointer to selected data
2770 * size of selected data
2771 */
2772void Fl::copy(const char *stuff, int len, int clipboard) {
2773 if (!stuff || len<0) return;
2774 if (len+1 > fl_selection_buffer_length[clipboard]) {
2775 delete[] fl_selection_buffer[clipboard];
2776 fl_selection_buffer[clipboard] = new char[len+100];
2777 fl_selection_buffer_length[clipboard] = len+100;
2778 }
2779 memcpy(fl_selection_buffer[clipboard], stuff, len);
2780 fl_selection_buffer[clipboard][len] = 0; // needed for direct paste
2781 fl_selection_length[clipboard] = len;
2782 if (clipboard) {
2783 allocatePasteboard();
2784 OSStatus err = PasteboardClear(myPasteboard);
2785 if (err!=noErr) return; // clear did not work, maybe not owner of clipboard.
2786 PasteboardSynchronize(myPasteboard);
2787 CFDataRef text = CFDataCreate(kCFAllocatorDefault, (UInt8*)fl_selection_buffer[1], len);
2788 if (text==NULL) return; // there was a pb creating the object, abort.
2789 err=PasteboardPutItemFlavor(myPasteboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), text, 0);
2790 CFRelease(text);
2791 }
2792}
2793
2794// Call this when a "paste" operation happens:
2795void Fl::paste(Fl_Widget &receiver, int clipboard) {
2796 if (clipboard) {
2797 // see if we own the selection, if not go get it:
2798 fl_selection_length[1] = 0;
2799 OSStatus err = noErr;
2800 Boolean found = false;
2801 CFDataRef flavorData = NULL;
2802 CFStringEncoding encoding = 0;
2803
2804 allocatePasteboard();
2805 PasteboardSynchronize(myPasteboard);
2806 ItemCount nFlavor = 0, i, j;
2807 err = PasteboardGetItemCount(myPasteboard, &nFlavor);
2808 if (err==noErr) {
2809 for (i=1; i<=nFlavor; i++) {
2810 PasteboardItemID itemID = 0;
2811 CFArrayRef flavorTypeArray = NULL;
2812 found = false;
2813 err = PasteboardGetItemIdentifier(myPasteboard, i, &itemID);
2814 if (err!=noErr) continue;
2815 err = PasteboardCopyItemFlavors(myPasteboard, itemID, &flavorTypeArray);
2816 if (err!=noErr) {
2817 if (flavorTypeArray) {CFRelease(flavorTypeArray); flavorTypeArray = NULL;}
2818 continue;
2819 }
2820 CFIndex flavorCount = CFArrayGetCount(flavorTypeArray);
2821 for (j = 0; j < handledFlavorsCount; j++) {
2822 for (CFIndex flavorIndex=0; flavorIndex<flavorCount; flavorIndex++) {
2823 CFStringRef flavorType = (CFStringRef)CFArrayGetValueAtIndex(flavorTypeArray, flavorIndex);
2824 if (UTTypeConformsTo(flavorType, flavorNames[j])) {
2825 err = PasteboardCopyItemFlavorData( myPasteboard, itemID, flavorNames[j], &flavorData );
2826 if (err != noErr) continue;
2827 encoding = encodings[j];
2828 found = true;
2829 break;
2830 }
2831 }
2832 if (found) break;
2833 }
2834 if (flavorTypeArray) {CFRelease(flavorTypeArray); flavorTypeArray = NULL;}
2835 if (found) break;
2836 }
2837 if (found) {
2838 CFIndex len = CFDataGetLength(flavorData);
2839 CFStringRef mycfs = CFStringCreateWithBytes(NULL, CFDataGetBytePtr(flavorData), len, encoding, false);
2840 CFRelease(flavorData);
2841 len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(mycfs), kCFStringEncodingUTF8) + 1;
2842 if ( len >= fl_selection_buffer_length[1] ) {
2843 fl_selection_buffer_length[1] = len;
2844 delete[] fl_selection_buffer[1];
2845 fl_selection_buffer[1] = new char[len];
2846 }
2847 CFStringGetCString(mycfs, fl_selection_buffer[1], len, kCFStringEncodingUTF8);
2848 CFRelease(mycfs);
2849 len = strlen(fl_selection_buffer[1]);
2850 fl_selection_length[1] = len;
2851 convert_crlf(fl_selection_buffer[1],len); // turn all \r characters into \n:
2852 }
2853 }
2854 }
2855 Fl::e_text = fl_selection_buffer[clipboard];
2856 Fl::e_length = fl_selection_length[clipboard];
2857 if (!Fl::e_text) Fl::e_text = (char *)"";
2858 receiver.handle(FL_PASTE);
2859}
2860
DRC685f17e2011-07-28 09:23:00 +00002861extern void fl_trigger_clipboard_notify(int source);
2862
2863void fl_clipboard_notify_change() {
2864 // No need to do anything here...
2865}
2866
2867static void clipboard_check(void)
2868{
2869 PasteboardSyncFlags flags;
2870
2871 allocatePasteboard();
2872 flags = PasteboardSynchronize(myPasteboard);
2873
2874 if (!(flags & kPasteboardModified))
2875 return;
2876 if (flags & kPasteboardClientIsOwner)
2877 return;
2878
2879 fl_trigger_clipboard_notify(1);
2880}
2881
DRC2ff39b82011-07-28 08:38:59 +00002882void Fl::add_timeout(double time, Fl_Timeout_Handler cb, void* data)
2883{
2884 // check, if this timer slot exists already
2885 for (int i = 0; i < mac_timer_used; ++i) {
2886 MacTimeout& t = mac_timers[i];
2887 // if so, simply change the fire interval
2888 if (t.callback == cb && t.data == data) {
2889 CFRunLoopTimerSetNextFireDate(t.timer, CFAbsoluteTimeGetCurrent() + time );
2890 t.pending = 1;
2891 return;
2892 }
2893 }
2894 // no existing timer to use. Create a new one:
2895 int timer_id = -1;
2896 // find an empty slot in the timer array
2897 for (int i = 0; i < mac_timer_used; ++i) {
2898 if ( !mac_timers[i].timer ) {
2899 timer_id = i;
2900 break;
2901 }
2902 }
2903 // if there was no empty slot, append a new timer
2904 if (timer_id == -1) {
2905 // make space if needed
2906 if (mac_timer_used == mac_timer_alloc) {
2907 realloc_timers();
2908 }
2909 timer_id = mac_timer_used++;
2910 }
2911 // now install a brand new timer
2912 MacTimeout& t = mac_timers[timer_id];
2913 CFRunLoopTimerContext context = {0, data, NULL,NULL,NULL};
2914 CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault,
2915 CFAbsoluteTimeGetCurrent() + time,
2916 1E30,
2917 0,
2918 0,
2919 do_timer,
2920 &context
2921 );
2922 if (timerRef) {
2923 CFRunLoopAddTimer(CFRunLoopGetCurrent(),
2924 timerRef,
2925 kCFRunLoopDefaultMode);
2926 t.callback = cb;
2927 t.data = data;
2928 t.timer = timerRef;
2929 t.pending = 1;
2930 }
2931}
2932
2933void Fl::repeat_timeout(double time, Fl_Timeout_Handler cb, void* data)
2934{
2935 // currently, repeat_timeout does not subtract the trigger time of the previous timer event as it should.
2936 add_timeout(time, cb, data);
2937}
2938
2939int Fl::has_timeout(Fl_Timeout_Handler cb, void* data)
2940{
2941 for (int i = 0; i < mac_timer_used; ++i) {
2942 MacTimeout& t = mac_timers[i];
2943 if (t.callback == cb && t.data == data && t.pending) {
2944 return 1;
2945 }
2946 }
2947 return 0;
2948}
2949
2950void Fl::remove_timeout(Fl_Timeout_Handler cb, void* data)
2951{
2952 for (int i = 0; i < mac_timer_used; ++i) {
2953 MacTimeout& t = mac_timers[i];
2954 if (t.callback == cb && ( t.data == data || data == NULL)) {
2955 delete_timer(t);
2956 }
2957 }
2958}
2959
2960int Fl_X::unlink(Fl_X *start) {
2961 if (start) {
2962 Fl_X *pc = start;
2963 while (pc) {
2964 if (pc->xidNext == this) {
2965 pc->xidNext = xidNext;
2966 return 1;
2967 }
2968 if (pc->xidChildren) {
2969 if (pc->xidChildren == this) {
2970 pc->xidChildren = xidNext;
2971 return 1;
2972 }
2973 if (unlink(pc->xidChildren))
2974 return 1;
2975 }
2976 pc = pc->xidNext;
2977 }
2978 } else {
2979 for ( Fl_X *pc = Fl_X::first; pc; pc = pc->next ) {
2980 if (unlink(pc))
2981 return 1;
2982 }
2983 }
2984 return 0;
2985}
2986
2987void Fl_X::relink(Fl_Window *w, Fl_Window *wp) {
2988 Fl_X *x = Fl_X::i(w);
2989 Fl_X *p = Fl_X::i(wp);
2990 if (!x || !p) return;
2991 // first, check if 'x' is already registered as a child of 'p'
2992 for (Fl_X *i = p->xidChildren; i; i=i->xidNext) {
2993 if (i == x) return;
2994 }
2995 // now add 'x' as the first child of 'p'
2996 x->xidNext = p->xidChildren;
2997 p->xidChildren = x;
2998}
2999
3000void Fl_X::destroy() {
3001 // subwindows share their xid with their parent window, so should not close it
3002 if (!subwindow && w && !w->parent() && xid) {
3003 [[(NSWindow *)xid contentView] release];
3004 [(NSWindow *)xid close];
3005 }
DRC685f17e2011-07-28 09:23:00 +00003006 if (cursor) {
3007 [(NSCursor*)cursor release];
3008 cursor = NULL;
3009 }
DRC2ff39b82011-07-28 08:38:59 +00003010}
3011
3012void Fl_X::map() {
3013 if (w && xid) {
3014 [(NSWindow *)xid orderFront:nil];
3015 }
3016 //+ link to window list
3017 if (w && w->parent()) {
3018 Fl_X::relink(w, w->window() );
3019 w->redraw();
3020 }
3021}
3022
3023void Fl_X::unmap() {
3024 if (w && !w->parent() && xid) {
3025 [(NSWindow *)xid orderOut:nil];
3026 }
3027 if (w && Fl_X::i(w))
3028 Fl_X::i(w)->unlink();
3029}
3030
3031
3032// removes x,y,w,h rectangle from region r and returns result as a new Fl_Region
3033static Fl_Region MacRegionMinusRect(Fl_Region r, int x,int y,int w,int h)
3034{
3035 Fl_Region outr = (Fl_Region)malloc(sizeof(*outr));
3036 outr->rects = (CGRect*)malloc(4 * r->count * sizeof(CGRect));
3037 outr->count = 0;
3038 CGRect rect = fl_cgrectmake_cocoa(x, y, w, h);
3039 for( int i = 0; i < r->count; i++) {
3040 CGRect A = r->rects[i];
3041 CGRect test = CGRectIntersection(A, rect);
3042 if (CGRectIsEmpty(test)) {
3043 outr->rects[(outr->count)++] = A;
3044 }
3045 else {
3046 const CGFloat verylarge = 100000.;
3047 CGRect side = CGRectMake(0,0,rect.origin.x,verylarge);// W side
3048 test = CGRectIntersection(A, side);
3049 if ( ! CGRectIsEmpty(test)) {
3050 outr->rects[(outr->count)++] = test;
3051 }
3052 side = CGRectMake(0,rect.origin.y + rect.size.height,verylarge,verylarge);// N side
3053 test = CGRectIntersection(A, side);
3054 if ( ! CGRectIsEmpty(test)) {
3055 outr->rects[(outr->count)++] = test;
3056 }
3057 side = CGRectMake(rect.origin.x + rect.size.width, 0, verylarge, verylarge);// E side
3058 test = CGRectIntersection(A, side);
3059 if ( ! CGRectIsEmpty(test)) {
3060 outr->rects[(outr->count)++] = test;
3061 }
3062 side = CGRectMake(0, 0, verylarge, rect.origin.y);// S side
3063 test = CGRectIntersection(A, side);
3064 if ( ! CGRectIsEmpty(test)) {
3065 outr->rects[(outr->count)++] = test;
3066 }
3067 }
3068 }
3069 if (outr->count == 0) {
3070 free(outr->rects);
3071 free(outr);
3072 outr = XRectangleRegion(0,0,0,0);
3073 }
3074 else outr->rects = (CGRect*)realloc(outr->rects, outr->count * sizeof(CGRect));
3075 return outr;
3076}
3077
3078// intersects current and x,y,w,h rectangle and returns result as a new Fl_Region
3079Fl_Region Fl_X::intersect_region_and_rect(Fl_Region current, int x,int y,int w, int h)
3080{
3081 if (current == NULL) return XRectangleRegion(x,y,w,h);
3082 CGRect r = fl_cgrectmake_cocoa(x, y, w, h);
3083 Fl_Region outr = (Fl_Region)malloc(sizeof(*outr));
3084 outr->count = current->count;
3085 outr->rects =(CGRect*)malloc(outr->count * sizeof(CGRect));
3086 int j = 0;
3087 for(int i = 0; i < current->count; i++) {
3088 CGRect test = CGRectIntersection(current->rects[i], r);
3089 if (!CGRectIsEmpty(test)) outr->rects[j++] = test;
3090 }
3091 if (j) {
3092 outr->count = j;
3093 outr->rects = (CGRect*)realloc(outr->rects, outr->count * sizeof(CGRect));
3094 }
3095 else {
3096 XDestroyRegion(outr);
3097 outr = XRectangleRegion(0,0,0,0);
3098 }
3099 return outr;
3100}
3101
3102void Fl_X::collapse() {
3103 [(NSWindow *)xid miniaturize:nil];
3104}
3105
3106static NSImage *CGBitmapContextToNSImage(CGContextRef c)
3107// the returned NSImage is autoreleased
3108{
3109 unsigned char *pdata = (unsigned char *)CGBitmapContextGetData(c);
3110 NSBitmapImageRep *imagerep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&pdata
3111 pixelsWide:CGBitmapContextGetWidth(c)
3112 pixelsHigh:CGBitmapContextGetHeight(c)
3113 bitsPerSample:8
3114 samplesPerPixel:4
3115 hasAlpha:YES
3116 isPlanar:NO
3117 colorSpaceName:NSDeviceRGBColorSpace
3118 bytesPerRow:CGBitmapContextGetBytesPerRow(c)
3119 bitsPerPixel:CGBitmapContextGetBitsPerPixel(c)];
3120 NSImage* image = [[NSImage alloc] initWithData: [imagerep TIFFRepresentation]];
3121 [imagerep release];
3122 return [image autorelease];
3123}
3124
DRC685f17e2011-07-28 09:23:00 +00003125int Fl_X::set_cursor(Fl_Cursor c)
DRC2ff39b82011-07-28 08:38:59 +00003126{
DRC685f17e2011-07-28 09:23:00 +00003127 if (cursor) {
3128 [(NSCursor*)cursor release];
3129 cursor = NULL;
DRC2ff39b82011-07-28 08:38:59 +00003130 }
DRC685f17e2011-07-28 09:23:00 +00003131
3132 switch (c) {
3133 case FL_CURSOR_ARROW: cursor = [NSCursor arrowCursor]; break;
3134 case FL_CURSOR_CROSS: cursor = [NSCursor crosshairCursor]; break;
3135 case FL_CURSOR_INSERT: cursor = [NSCursor IBeamCursor]; break;
3136 case FL_CURSOR_HAND: cursor = [NSCursor pointingHandCursor]; break;
3137 case FL_CURSOR_MOVE: cursor = [NSCursor openHandCursor]; break;
3138 case FL_CURSOR_NS: cursor = [NSCursor resizeUpDownCursor]; break;
3139 case FL_CURSOR_WE: cursor = [NSCursor resizeLeftRightCursor]; break;
3140 case FL_CURSOR_N: cursor = [NSCursor resizeUpCursor]; break;
3141 case FL_CURSOR_E: cursor = [NSCursor resizeRightCursor]; break;
3142 case FL_CURSOR_W: cursor = [NSCursor resizeLeftCursor]; break;
3143 case FL_CURSOR_S: cursor = [NSCursor resizeDownCursor]; break;
3144 default:
3145 return 0;
3146 }
3147
3148 [(NSCursor*)cursor retain];
3149
3150 [(NSWindow*)xid invalidateCursorRectsForView:[(NSWindow*)xid contentView]];
3151
3152 return 1;
DRC2ff39b82011-07-28 08:38:59 +00003153}
3154
DRC685f17e2011-07-28 09:23:00 +00003155int Fl_X::set_cursor(const Fl_RGB_Image *image, int hotx, int hoty) {
3156 if (cursor) {
3157 [(NSCursor*)cursor release];
3158 cursor = NULL;
DRC2ff39b82011-07-28 08:38:59 +00003159 }
DRC685f17e2011-07-28 09:23:00 +00003160
3161 if ((hotx < 0) || (hotx >= image->w()))
3162 return 0;
3163 if ((hoty < 0) || (hoty >= image->h()))
3164 return 0;
3165
3166 // OS X >= 10.6 can create a NSImage from a CGImage, but we need to
3167 // support older versions, hence this pesky handling.
3168
3169 NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc]
3170 initWithBitmapDataPlanes:NULL
3171 pixelsWide:image->w()
3172 pixelsHigh:image->h()
3173 bitsPerSample:8
3174 samplesPerPixel:image->d()
3175 hasAlpha:!(image->d() & 1)
3176 isPlanar:NO
3177 colorSpaceName:(image->d()<=2) ? NSDeviceWhiteColorSpace : NSDeviceRGBColorSpace
3178 bytesPerRow:(image->w() * image->d())
3179 bitsPerPixel:(image->d()*8)];
3180
3181 // Alpha needs to be premultiplied for this format
3182
3183 const uchar *i = (const uchar*)*image->data();
3184 unsigned char *o = [bitmap bitmapData];
3185 for (int y = 0;y < image->h();y++) {
3186 if (image->d() & 1) {
3187 for (int x = 0;x < image->w();x++) {
3188 unsigned int alpha;
3189 if (image->d() == 4) {
3190 alpha = i[3];
3191 *o++ = (unsigned char)((unsigned int)*i++ * alpha / 255);
3192 *o++ = (unsigned char)((unsigned int)*i++ * alpha / 255);
3193 }
3194
3195 alpha = i[1];
3196 *o++ = (unsigned char)((unsigned int)*i++ * alpha / 255);
3197 *o++ = alpha;
3198 i++;
3199 }
3200 } else {
3201 // No alpha, so we can just copy everything directly.
3202 int len = image->w() * image->d();
3203 memcpy(o, i, len);
3204 o += len;
3205 i += len;
3206 }
3207 i += image->ld();
3208 }
3209
3210 NSImage *nsimage = [[NSImage alloc]
3211 initWithSize:NSMakeSize(image->w(), image->h())];
3212
3213 [nsimage addRepresentation:bitmap];
3214
3215 cursor = [[NSCursor alloc]
3216 initWithImage:nsimage
3217 hotSpot:NSMakePoint(hotx, hoty)];
3218
3219 [(NSWindow*)xid invalidateCursorRectsForView:[(NSWindow*)xid contentView]];
3220
3221 [bitmap release];
3222 [nsimage release];
3223
3224 return 1;
DRC2ff39b82011-07-28 08:38:59 +00003225}
3226
3227@interface FLaboutItemTarget : NSObject
3228{
3229}
3230- (void)showPanel;
3231- (void)printPanel;
3232@end
3233@implementation FLaboutItemTarget
3234- (void)showPanel
3235{
Henrik Andersson485f4192011-09-16 11:51:32 +00003236 if ((Fl_Mac_App_Menu::copyright == NULL) ||
3237 (strlen(Fl_Mac_App_Menu::copyright) > 0)) {
3238 NSString *copyright;
3239
3240 if (Fl_Mac_App_Menu::copyright == NULL)
3241 copyright = [NSString stringWithFormat:@" GUI with FLTK %d.%d",
3242 FL_MAJOR_VERSION, FL_MINOR_VERSION ];
3243 else
3244 copyright = [NSString stringWithUTF8String:Fl_Mac_App_Menu::copyright];
3245
3246 NSDictionary *options;
3247 options = [NSDictionary dictionaryWithObjectsAndKeys:
3248 copyright, @"Copyright",
3249 nil];
3250
3251 [NSApp orderFrontStandardAboutPanelWithOptions:options];
3252 } else {
3253 [NSApp orderFrontStandardAboutPanelWithOptions:nil];
3254 }
DRC2ff39b82011-07-28 08:38:59 +00003255 }
3256//#include <FL/Fl_PostScript.H>
3257- (void)printPanel
3258{
3259 Fl_Printer printer;
3260 //Fl_PostScript_File_Device printer;
3261 int w, h, ww, wh;
3262 Fl_Window *win = Fl::first_window();
3263 if(!win) return;
3264 if( printer.start_job(1) ) return;
3265 if( printer.start_page() ) return;
3266 // scale the printer device so that the window fits on the page
3267 float scale = 1;
3268 printer.printable_rect(&w, &h);
3269 ww = win->decorated_w();
3270 wh = win->decorated_h();
3271 if (ww>w || wh>h) {
3272 scale = (float)w/win->w();
3273 if ((float)h/wh < scale) scale = (float)h/wh;
3274 printer.scale(scale);
3275 }
3276//#define ROTATE 1
3277#ifdef ROTATE
3278 printer.scale(scale * 0.8, scale * 0.8);
3279 printer.printable_rect(&w, &h);
3280 printer.origin(w/2, h/2 );
3281 printer.rotate(20.);
3282 printer.print_widget( win, - win->w()/2, - win->h()/2 );
3283#else
3284 printer.print_window(win);
3285#endif
3286 printer.end_page();
3287 printer.end_job();
3288}
3289@end
3290
3291static void createAppleMenu(void)
3292{
3293 static BOOL donethat = NO;
3294 if (donethat) return;
3295 donethat = YES;
3296 NSMenu *mainmenu, *services, *appleMenu;
3297 NSMenuItem *menuItem;
3298 NSString *title;
3299
3300 NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
3301 if (nsappname == nil)
3302 nsappname = [[NSProcessInfo processInfo] processName];
3303 appleMenu = [[NSMenu alloc] initWithTitle:@""];
3304 /* Add menu items */
3305 title = [[NSString stringWithUTF8String:Fl_Mac_App_Menu::about] stringByAppendingString:nsappname];
3306 menuItem = [appleMenu addItemWithTitle:title action:@selector(showPanel) keyEquivalent:@""];
3307 FLaboutItemTarget *about = [[FLaboutItemTarget alloc] init];
3308 [menuItem setTarget:about];
3309 [appleMenu addItem:[NSMenuItem separatorItem]];
3310 // Print front window
3311 if (strlen(Fl_Mac_App_Menu::print) > 0) {
3312 menuItem = [appleMenu
3313 addItemWithTitle:[NSString stringWithUTF8String:Fl_Mac_App_Menu::print]
3314 action:@selector(printPanel)
3315 keyEquivalent:@""];
3316 [menuItem setTarget:about];
3317 [appleMenu setAutoenablesItems:NO];
3318 [menuItem setEnabled:YES];
3319 [appleMenu addItem:[NSMenuItem separatorItem]];
3320 }
3321 // Services Menu
3322 services = [[NSMenu alloc] init];
3323 menuItem = [appleMenu
3324 addItemWithTitle:[NSString stringWithUTF8String:Fl_Mac_App_Menu::services]
3325 action:nil
3326 keyEquivalent:@""];
3327 [appleMenu setSubmenu:services forItem:menuItem];
3328 [appleMenu addItem:[NSMenuItem separatorItem]];
3329 // Hide AppName
3330 title = [[NSString stringWithUTF8String:Fl_Mac_App_Menu::hide] stringByAppendingString:nsappname];
3331 [appleMenu addItemWithTitle:title
3332 action:@selector(hide:)
3333 keyEquivalent:@"h"];
3334 // Hide Others
3335 menuItem = [appleMenu
3336 addItemWithTitle:[NSString stringWithUTF8String:Fl_Mac_App_Menu::hide_others]
3337 action:@selector(hideOtherApplications:)
3338 keyEquivalent:@"h"];
3339 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
3340 // Show All
3341 [appleMenu addItemWithTitle:[NSString stringWithUTF8String:Fl_Mac_App_Menu::show]
3342 action:@selector(unhideAllApplications:) keyEquivalent:@""];
3343 [appleMenu addItem:[NSMenuItem separatorItem]];
3344 // Quit AppName
3345 title = [[NSString stringWithUTF8String:Fl_Mac_App_Menu::quit]
3346 stringByAppendingString:nsappname];
3347 [appleMenu addItemWithTitle:title
3348 action:@selector(terminate:)
3349 keyEquivalent:@"q"];
3350 /* Put menu into the menubar */
3351 menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
3352 [menuItem setSubmenu:appleMenu];
3353 mainmenu = [[NSMenu alloc] initWithTitle:@""];
3354 [mainmenu addItem:menuItem];
3355 if (fl_mac_os_version < 100600) {
3356 // [NSApp setAppleMenu:appleMenu];
3357 // to avoid compiler warning raised by use of undocumented setAppleMenu :
3358 [NSApp performSelector:@selector(setAppleMenu:) withObject:appleMenu];
3359 }
3360 [NSApp setServicesMenu:services];
3361 [NSApp setMainMenu:mainmenu];
3362 [services release];
3363 [mainmenu release];
3364 [appleMenu release];
3365 [menuItem release];
3366}
3367
3368@interface FLMenuItem : NSMenuItem {
3369}
3370- (void) doCallback:(id)unused;
3371- (void) directCallback:(id)unused;
3372@end
3373@implementation FLMenuItem
3374- (void) doCallback:(id)unused
3375{
3376 int flRank = [self tag];
3377 const Fl_Menu_Item *items = fl_sys_menu_bar->Fl_Menu_::menu();
3378 const Fl_Menu_Item *item = items + flRank;
3379 if (item) {
3380 fl_sys_menu_bar->picked(item);
3381 if ( item->flags & FL_MENU_TOGGLE ) { // update the menu toggle symbol
3382 [self setState:(item->value() ? NSOnState : NSOffState)];
3383 }
3384 else if ( item->flags & FL_MENU_RADIO ) { // update the menu radio symbols
3385 int from = flRank;
3386 while( from > 0 && items[from - 1].label() && (items[from - 1].flags & FL_MENU_RADIO) &&
3387 !(items[from - 1].flags & FL_MENU_DIVIDER) ) {
3388 from--;
3389 }
3390 int to = flRank;
3391 while( !(items[to].flags & FL_MENU_DIVIDER) && items[to + 1].label() &&
3392 (items[to + 1].flags & FL_MENU_RADIO) ) {
3393 to++;
3394 }
3395 NSMenu *nsmenu = [self menu];
3396 int nsrank = (int)[nsmenu indexOfItem:self];
3397 for(int i = from - flRank + nsrank ; i <= to - flRank + nsrank; i++) {
3398 NSMenuItem *nsitem = [nsmenu itemAtIndex:i];
3399 if (nsitem != self) [nsitem setState:NSOffState];
3400 else [nsitem setState:(item->value() ? NSOnState : NSOffState) ];
3401 }
3402 }
3403 }
3404}
3405- (void) directCallback:(id)unused
3406{
3407 Fl_Menu_Item *item = (Fl_Menu_Item *)[(NSData*)[self representedObject] bytes];
3408 if ( item && item->callback() ) item->do_callback(NULL);
3409}
3410@end
3411
3412void fl_mac_set_about( Fl_Callback *cb, void *user_data, int shortcut)
3413{
3414 fl_open_display();
3415 Fl_Menu_Item aboutItem;
3416 memset(&aboutItem, 0, sizeof(Fl_Menu_Item));
3417 aboutItem.callback(cb);
3418 aboutItem.user_data(user_data);
3419 aboutItem.shortcut(shortcut);
3420 NSMenu *appleMenu = [[[NSApp mainMenu] itemAtIndex:0] submenu];
3421 CFStringRef cfname = CFStringCreateCopy(NULL, (CFStringRef)[[appleMenu itemAtIndex:0] title]);
3422 [appleMenu removeItemAtIndex:0];
3423 FLMenuItem *item = [[[FLMenuItem alloc] initWithTitle:(NSString*)cfname
3424 action:@selector(directCallback:)
3425 keyEquivalent:@""] autorelease];
3426 if (aboutItem.shortcut()) {
3427 Fl_Sys_Menu_Bar::doMenuOrItemOperation(Fl_Sys_Menu_Bar::setKeyEquivalent, item, aboutItem.shortcut() & 0xff);
3428 Fl_Sys_Menu_Bar::doMenuOrItemOperation(Fl_Sys_Menu_Bar::setKeyEquivalentModifierMask, item, aboutItem.shortcut() );
3429 }
3430 NSData *pointer = [NSData dataWithBytes:&aboutItem length:sizeof(Fl_Menu_Item)];
3431 [item setRepresentedObject:pointer];
3432 [appleMenu insertItem:item atIndex:0];
3433 CFRelease(cfname);
3434 [item setTarget:item];
3435}
3436
3437static char *remove_ampersand(const char *s)
3438{
3439 char *ret = strdup(s);
3440 const char *p = s;
3441 char *q = ret;
3442 while(*p != 0) {
3443 if (p[0]=='&') {
3444 if (p[1]=='&') {
3445 *q++ = '&'; p+=2;
3446 } else {
3447 p++;
3448 }
3449 } else {
3450 *q++ = *p++;
3451 }
3452 }
3453 *q = 0;
3454 return ret;
3455}
3456
3457void *Fl_Sys_Menu_Bar::doMenuOrItemOperation(Fl_Sys_Menu_Bar::menuOrItemOperation operation, ...)
3458/* these operations apply to menus, submenus, or menu items
3459 */
3460{
3461 NSAutoreleasePool *localPool;
3462 localPool = [[NSAutoreleasePool alloc] init];
3463 NSMenu *menu;
3464 NSMenuItem *item;
3465 int value;
3466 void *pter;
3467 void *retval = NULL;
3468 va_list ap;
3469 va_start(ap, operation);
3470
3471 if (operation == Fl_Sys_Menu_Bar::itemAtIndex) { // arguments: NSMenu*, int. Returns the item
3472 menu = va_arg(ap, NSMenu*);
3473 value = va_arg(ap, int);
3474 retval = (void *)[menu itemAtIndex:value];
3475 }
3476 else if (operation == Fl_Sys_Menu_Bar::setKeyEquivalent) { // arguments: NSMenuItem*, int
3477 item = va_arg(ap, NSMenuItem*);
3478 value = va_arg(ap, int);
3479 char key = value;
3480 NSString *equiv = [[NSString alloc] initWithBytes:&key length:1 encoding:NSASCIIStringEncoding];
3481 [item setKeyEquivalent:equiv];
3482 [equiv release];
3483 }
3484 else if (operation == Fl_Sys_Menu_Bar::setKeyEquivalentModifierMask) { // arguments: NSMenuItem*, int
3485 item = va_arg(ap, NSMenuItem*);
3486 value = va_arg(ap, int);
3487 NSUInteger macMod = 0;
3488 if ( value & FL_META ) macMod = NSCommandKeyMask;
3489 if ( value & FL_SHIFT || isupper(value) ) macMod |= NSShiftKeyMask;
3490 if ( value & FL_ALT ) macMod |= NSAlternateKeyMask;
3491 if ( value & FL_CTRL ) macMod |= NSControlKeyMask;
3492 [item setKeyEquivalentModifierMask:macMod];
3493 }
3494 else if (operation == Fl_Sys_Menu_Bar::setState) { // arguments: NSMenuItem*, int
3495 item = va_arg(ap, NSMenuItem*);
3496 value = va_arg(ap, int);
3497 [item setState:(value ? NSOnState : NSOffState)];
3498 }
3499 else if (operation == Fl_Sys_Menu_Bar::initWithTitle) { // arguments: const char*title. Returns the newly created menu
3500 // creates a new (sub)menu
3501 char *ts = remove_ampersand(va_arg(ap, char *));
3502 CFStringRef title = CFStringCreateWithCString(NULL, ts, kCFStringEncodingUTF8);
3503 free(ts);
3504 NSMenu *menu = [[NSMenu alloc] initWithTitle:(NSString*)title];
3505 CFRelease(title);
3506 [menu setAutoenablesItems:NO];
3507 retval = (void *)menu;
3508 }
3509 else if (operation == Fl_Sys_Menu_Bar::numberOfItems) { // arguments: NSMenu *menu, int *pcount
3510 // upon return, *pcount is set to menu's item count
3511 menu = va_arg(ap, NSMenu*);
3512 pter = va_arg(ap, void *);
3513 *(int*)pter = [menu numberOfItems];
3514 }
3515 else if (operation == Fl_Sys_Menu_Bar::setSubmenu) { // arguments: NSMenuItem *item, NSMenu *menu
3516 // sets 'menu' as submenu attached to 'item'
3517 item = va_arg(ap, NSMenuItem*);
3518 menu = va_arg(ap, NSMenu*);
3519 [item setSubmenu:menu];
3520 [menu release];
3521 }
3522 else if (operation == Fl_Sys_Menu_Bar::setEnabled) { // arguments: NSMenuItem*, int
3523 item = va_arg(ap, NSMenuItem*);
3524 value = va_arg(ap, int);
3525 [item setEnabled:(value ? YES : NO)];
3526 }
3527 else if (operation == Fl_Sys_Menu_Bar::addSeparatorItem) { // arguments: NSMenu*
3528 menu = va_arg(ap, NSMenu*);
3529 [menu addItem:[NSMenuItem separatorItem]];
3530 }
3531 else if (operation == Fl_Sys_Menu_Bar::setTitle) { // arguments: NSMenuItem*, const char *
3532 item = va_arg(ap, NSMenuItem*);
3533 char *ts = remove_ampersand(va_arg(ap, char *));
3534 CFStringRef title = CFStringCreateWithCString(NULL, ts, kCFStringEncodingUTF8);
3535 free(ts);
3536 [item setTitle:(NSString*)title];
3537 CFRelease(title);
3538 }
3539 else if (operation == Fl_Sys_Menu_Bar::removeItem) { // arguments: NSMenu*, int
3540 menu = va_arg(ap, NSMenu*);
3541 value = va_arg(ap, int);
3542 [menu removeItem:[menu itemAtIndex:value]];
3543 }
3544 else if (operation == Fl_Sys_Menu_Bar::addNewItem) { // arguments: NSMenu *menu, int flrank, int *prank
3545 // creates a new menu item at the end of 'menu'
3546 // attaches the item of rank flrank (counted in Fl_Menu_) of fl_sys_menu_bar to it
3547 // upon return, puts the rank (counted in NSMenu) of the new item in *prank unless prank is NULL
3548 menu = va_arg(ap, NSMenu*);
3549 int flRank = va_arg(ap, int);
3550 char *name = remove_ampersand( (fl_sys_menu_bar->Fl_Menu_::menu() + flRank)->label());
3551 int *prank = va_arg(ap, int*);
3552 CFStringRef cfname = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
3553 free(name);
3554 FLMenuItem *item = [[FLMenuItem alloc] initWithTitle:(NSString*)cfname
3555 action:@selector(doCallback:)
3556 keyEquivalent:@""];
3557 [item setTag:flRank];
3558 [menu addItem:item];
3559 CFRelease(cfname);
3560 [item setTarget:item];
3561 if (prank != NULL) *prank = [menu indexOfItem:item];
3562 [item release];
3563 }
3564 else if (operation == Fl_Sys_Menu_Bar::renameItem) { // arguments: int rank, const char *newname
3565 // renames the system menu item numbered rank in fl_sys_menu_bar->menu()
3566 int rank = va_arg(ap, int);
3567 char *newname = remove_ampersand( va_arg(ap, const char *) );
3568 int countmenus = [[NSApp mainMenu] numberOfItems];
3569 bool found = NO;
3570 NSMenuItem *macitem = 0;
3571 for(int i = 1; (!found) && i < countmenus; i++) {
3572 NSMenuItem *item = [[NSApp mainMenu] itemAtIndex:i];
3573 NSMenu *submenu = [item submenu];
3574 if (submenu == nil) continue;
3575 int countitems = [submenu numberOfItems];
3576 for(int j = 0; j < countitems; j++) {
3577 macitem = [submenu itemAtIndex:j];
3578 if ([macitem tag] == rank) { found = YES; break; }
3579 }
3580 }
3581 if (found) {
3582 [macitem setTitle:[[[NSString alloc] initWithUTF8String:newname] autorelease]];
3583 }
3584 free(newname);
3585 }
3586 va_end(ap);
3587 [localPool release];
3588 return retval;
3589}
3590
3591void Fl_X::set_key_window()
3592{
3593 [(NSWindow*)xid makeKeyWindow];
3594}
3595
3596static NSImage *imageFromText(const char *text, int *pwidth, int *pheight)
3597{
3598 const char *p, *q;
3599 int width = 0, height, w2, ltext = strlen(text);
3600 fl_font(FL_HELVETICA, 10);
3601 p = text;
3602 int nl = 0;
3603 while((q=strchr(p, '\n')) != NULL) {
3604 nl++;
3605 w2 = int(fl_width(p, q - p));
3606 if (w2 > width) width = w2;
3607 p = q + 1;
3608 }
3609 if (text[ ltext - 1] != '\n') {
3610 nl++;
3611 w2 = int(fl_width(p));
3612 if (w2 > width) width = w2;
3613 }
3614 height = nl * fl_height() + 3;
3615 width += 6;
3616 Fl_Offscreen off = fl_create_offscreen_with_alpha(width, height);
3617 fl_begin_offscreen(off);
3618 CGContextSetRGBFillColor( (CGContextRef)off, 0,0,0,0);
3619 fl_rectf(0,0,width,height);
3620 fl_color(FL_BLACK);
3621 p = text;
3622 int y = fl_height();
3623 while(TRUE) {
3624 q = strchr(p, '\n');
3625 if (q) {
3626 fl_draw(p, q - p, 3, y);
3627 } else {
3628 fl_draw(p, 3, y);
3629 break;
3630 }
3631 y += fl_height();
3632 p = q + 1;
3633 }
3634 fl_end_offscreen();
3635 NSImage* image = CGBitmapContextToNSImage( (CGContextRef)off );
3636 fl_delete_offscreen( off );
3637 *pwidth = width;
3638 *pheight = height;
3639 return image;
3640}
3641
3642static NSImage *defaultDragImage(int *pwidth, int *pheight)
3643{
3644 const int width = 16, height = 16;
3645 Fl_Offscreen off = fl_create_offscreen_with_alpha(width, height);
3646 fl_begin_offscreen(off);
3647 CGContextSetRGBFillColor( (CGContextRef)off, 0,0,0,0);
3648 fl_rectf(0,0,width,height);
3649 CGContextSetRGBStrokeColor( (CGContextRef)off, 0,0,0,0.6);
3650 fl_rect(0,0,width,height);
3651 fl_rect(2,2,width-4,height-4);
3652 fl_end_offscreen();
3653 NSImage* image = CGBitmapContextToNSImage( (CGContextRef)off );
3654 fl_delete_offscreen( off );
3655 *pwidth = width;
3656 *pheight = height;
3657 return image;
3658}
3659
3660int Fl::dnd(void)
3661{
3662 CFDataRef text = CFDataCreate(kCFAllocatorDefault, (UInt8*)fl_selection_buffer[0], fl_selection_length[0]);
3663 if (text==NULL) return false;
3664 NSAutoreleasePool *localPool;
3665 localPool = [[NSAutoreleasePool alloc] init];
3666 NSPasteboard *mypasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
3667 [mypasteboard declareTypes:[NSArray arrayWithObjects:@"public.utf8-plain-text", nil] owner:nil];
3668 [mypasteboard setData:(NSData*)text forType:@"public.utf8-plain-text"];
3669 CFRelease(text);
3670 Fl_Widget *w = Fl::pushed();
3671 Fl_Window *win = w->window();
3672 if (win == NULL) {
3673 win = (Fl_Window*)w;
3674 } else {
3675 while(win->window()) win = win->window();
3676 }
3677 NSView *myview = [(NSWindow*)Fl_X::i(win)->xid contentView];
3678 NSEvent *theEvent = [NSApp currentEvent];
3679
3680 int width, height;
3681 NSImage *image;
3682 if ( dynamic_cast<Fl_Input_*>(w) != NULL || dynamic_cast<Fl_Text_Display*>(w) != NULL) {
3683 fl_selection_buffer[0][ fl_selection_length[0] ] = 0;
3684 image = imageFromText(fl_selection_buffer[0], &width, &height);
3685 } else {
3686 image = defaultDragImage(&width, &height);
3687 }
3688
3689 static NSSize offset={0,0};
3690 NSPoint pt = [theEvent locationInWindow];
3691 pt.x -= width/2;
3692 pt.y -= height/2;
3693 [myview dragImage:image at:pt offset:offset
3694 event:theEvent pasteboard:mypasteboard
3695 source:myview slideBack:YES];
3696 if ( w ) {
3697 int old_event = Fl::e_number;
3698 w->handle(Fl::e_number = FL_RELEASE);
3699 Fl::e_number = old_event;
3700 Fl::pushed( 0 );
3701 }
3702 [localPool release];
3703 return true;
3704}
3705
3706unsigned char *Fl_X::bitmap_from_window_rect(Fl_Window *win, int x, int y, int w, int h, int *bytesPerPixel)
3707// delete the returned pointer after use
3708{
3709 while(win->window()) {
3710 x += win->x();
3711 y += win->y();
3712 win = win->window();
3713 }
3714 CGFloat epsilon = 0;
3715 if (fl_mac_os_version >= 100600) epsilon = 0.001;
3716 // The epsilon offset is absolutely necessary under 10.6. Without it, the top pixel row and
3717 // left pixel column are not read, and bitmap is read shifted by one pixel in both directions.
3718 // Under 10.5, we want no offset.
3719 NSRect rect = NSMakeRect(x - epsilon, y - epsilon, w, h);
3720 NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithFocusedViewRect:rect];
3721 *bytesPerPixel = [bitmap bitsPerPixel]/8;
3722 int bpp = (int)[bitmap bytesPerPlane];
3723 int bpr = (int)[bitmap bytesPerRow];
3724 int hh = bpp/bpr; // sometimes hh = h-1 for unclear reason
3725 int ww = bpr/(*bytesPerPixel); // sometimes ww = w-1
3726 unsigned char *data = new unsigned char[w * h * *bytesPerPixel];
3727 if (w == ww) {
3728 memcpy(data, [bitmap bitmapData], w * hh * *bytesPerPixel);
3729 } else {
3730 unsigned char *p = [bitmap bitmapData];
3731 unsigned char *q = data;
3732 for(int i = 0;i < hh; i++) {
3733 memcpy(q, p, *bytesPerPixel * ww);
3734 p += bpr;
3735 q += w * *bytesPerPixel;
3736 }
3737 }
3738 [bitmap release];
3739 return data;
3740}
3741
3742static void imgProviderReleaseData (void *info, const void *data, size_t size)
3743{
3744 delete[] (unsigned char *)data;
3745}
3746
3747CGImageRef Fl_X::CGImage_from_window_rect(Fl_Window *win, int x, int y, int w, int h)
3748// CFRelease the returned CGImageRef after use
3749{
3750 int bpp;
3751 unsigned char *bitmap = bitmap_from_window_rect(win, x, y, w, h, &bpp);
3752 CGImageRef img;
3753 CGColorSpaceRef lut = CGColorSpaceCreateDeviceRGB();
3754 CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, bitmap, w*h*bpp, imgProviderReleaseData);
3755 img = CGImageCreate(w, h, 8, 8*bpp, w*bpp, lut,
3756 bpp == 3 ? kCGImageAlphaNone : kCGImageAlphaLast,
3757 provider, NULL, false, kCGRenderingIntentDefault);
3758 CGColorSpaceRelease(lut);
3759 CGDataProviderRelease(provider);
3760 return img;
3761}
3762
3763WindowRef Fl_X::window_ref()
3764{
3765 return (WindowRef)[(FLWindow*)xid windowRef];
3766}
3767
3768// so a CGRect matches exactly what is denoted x,y,w,h for clipping purposes
3769CGRect fl_cgrectmake_cocoa(int x, int y, int w, int h) {
3770 if (Fl_Surface_Device::surface()->class_name() == Fl_Printer::class_id) return CGRectMake(x-0.5, y-0.5, w, h);
3771 return CGRectMake(x, y, w > 0 ? w - 0.9 : 0, h > 0 ? h - 0.9 : 0);
3772}
3773
3774Window fl_xid(const Fl_Window* w)
3775{
3776 Fl_X *temp = Fl_X::i(w);
3777 return temp ? temp->xid : 0;
3778}
3779
3780int Fl_Window::decorated_w()
3781{
3782 if (!shown() || parent() || !border() || !visible()) return w();
3783 int bx, by, bt;
3784 get_window_frame_sizes(bx, by, bt);
3785 return w() + 2 * bx;
3786}
3787
3788int Fl_Window::decorated_h()
3789{
3790 if (!shown() || parent() || !border() || !visible()) return h();
3791 int bx, by, bt;
3792 get_window_frame_sizes(bx, by, bt);
3793 return h() + bt + by;
3794}
3795
3796void Fl_Paged_Device::print_window(Fl_Window *win, int x_offset, int y_offset)
3797{
3798 if (!win->shown() || win->parent() || !win->border() || !win->visible()) {
3799 this->print_widget(win, x_offset, y_offset);
3800 return;
3801 }
3802 int bx, by, bt;
3803 get_window_frame_sizes(bx, by, bt);
3804 Fl_Display_Device::display_device()->set_current(); // send win to front and make it current
3805 win->show();
3806 fl_gc = NULL;
3807 Fl::check();
3808 win->make_current();
3809 // capture the window title bar from screen
3810 CGImageRef img = Fl_X::CGImage_from_window_rect(win, 0, -bt, win->w(), bt);
3811 this->set_current(); // back to the Fl_Paged_Device
3812 CGRect rect = { { 0, 0 }, { win->w(), bt } }; // print the title bar
3813 Fl_X::q_begin_image(rect, 0, 0, win->w(), bt);
3814 CGContextDrawImage(fl_gc, rect, img);
3815 Fl_X::q_end_image();
3816 CGImageRelease(img);
3817 this->print_widget(win, x_offset, y_offset + bt); // print the window inner part
3818}
3819
3820#include <dlfcn.h>
3821
3822/* Returns the address of a Carbon function after dynamically loading the Carbon library if needed.
3823 Supports old Mac OS X versions that may use a couple of Carbon calls:
3824 GetKeys used by OS X 10.3 or before (in Fl::get_key())
3825 PMSessionPageSetupDialog and PMSessionPrintDialog used by 10.4 or before (in Fl_Printer::start_job())
3826 GetWindowPort used by 10.4 or before (in Fl_Gl_Choice.cxx)
3827 */
3828void *Fl_X::get_carbon_function(const char *function_name) {
3829 static void *carbon = NULL;
3830 void *f = NULL;
3831 if (!carbon) {
3832 carbon = dlopen("/System/Library/Frameworks/Carbon.framework/Carbon", RTLD_LAZY);
3833 }
3834 if (carbon) {
3835 f = dlsym(carbon, function_name);
3836 }
3837 return f;
3838}
3839
3840#endif // __APPLE__
3841
3842//
3843// End of "$Id: Fl_cocoa.mm 8807 2011-06-16 12:35:32Z manolo $".
3844//