themes: Workaround for bug where a background color of RGB 0,0,0 in Black color schem...
[ntk.git] / src / Fl_mac.cxx
blob38c0f21eabca013958f88dd9507a18099ca3b108
1 //
2 // "$Id: Fl_mac.cxx 7913 2010-11-29 18:18:27Z greg.ercolano $"
3 //
4 // MacOS specific code for the Fast Light Tool Kit (FLTK).
5 //
6 // Copyright 1998-2010 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.
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.
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.
23 // Please report all bugs and problems on the following page:
25 // http://www.fltk.org/str.php
28 //// From the inner edge of a MetroWerks CodeWarrior CD:
29 // (without permission)
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."
41 // warning: the Apple Quartz version still uses some Quickdraw calls,
42 // mostly to get around the single active context in QD and
43 // to implement clipping. This should be changed into pure
44 // Quartz calls in the near future.
46 // FIXME moving away from Carbon, I am replacing the Scrap manager calls with Pasteboard
47 // calls that support utf8 encoding. As soon as these function haven proven working
48 // the Scrap manager calls should be removed
49 #define USE_PASTEBOARD 1
51 // we don't need the following definition because we deliver only
52 // true mouse moves. On very slow systems however, this flag may
53 // still be useful.
54 #ifndef FL_DOXYGEN
56 #define CONSOLIDATE_MOTION 0
57 extern "C" {
58 #include <pthread.h>
61 #include <config.h>
62 #include <FL/Fl.H>
63 #include <FL/x.H>
64 #include <FL/Fl_Window.H>
65 #include <FL/Fl_Tooltip.H>
66 #include <FL/Fl_Sys_Menu_Bar.H>
67 #include <stdio.h>
68 #include <stdlib.h>
69 #include "flstring.h"
70 #include <unistd.h>
72 // #define DEBUG_SELECT // UNCOMMENT FOR SELECT()/THREAD DEBUGGING
73 #ifdef DEBUG_SELECT
74 #include <stdio.h> // testing
75 #define DEBUGMSG(msg) if ( msg ) fprintf(stderr, msg);
76 #define DEBUGPERRORMSG(msg) if ( msg ) perror(msg)
77 #define DEBUGTEXT(txt) txt
78 #else
79 #define DEBUGMSG(msg)
80 #define DEBUGPERRORMSG(msg)
81 #define DEBUGTEXT(txt) NULL
82 #endif /*DEBUG_SELECT*/
84 // external functions
85 extern Fl_Window* fl_find(Window);
86 extern void fl_fix_focus();
88 // forward definition of functions in this file
89 static void handleUpdateEvent( WindowPtr xid );
90 //+ int fl_handle(const EventRecord &event);
91 static int FSSpec2UnixPath( FSSpec *fs, char *dst );
92 // converting cr lf converter function
93 static void convert_crlf(char * string, size_t len);
95 // public variables
96 int fl_screen;
97 CGContextRef fl_gc = 0;
98 Handle fl_system_menu;
99 Fl_Sys_Menu_Bar *fl_sys_menu_bar = 0;
100 CursHandle fl_default_cursor;
101 WindowRef fl_capture = 0; // we need this to compensate for a missing(?) mouse capture
102 ulong fl_event_time; // the last timestamp from an x event
103 char fl_key_vector[32]; // used by Fl::get_key()
104 bool fl_show_iconic; // true if called from iconize() - shows the next created window in collapsed state
105 int fl_disable_transient_for; // secret method of removing TRANSIENT_FOR
106 const Fl_Window* fl_modal_for; // parent of modal() window
107 Fl_Region fl_window_region = 0;
108 Window fl_window;
109 Fl_Window *Fl_Window::current_;
110 EventRef fl_os_event; // last (mouse) event
112 // forward declarations of variables in this file
113 static int got_events = 0;
114 static Fl_Window* resize_from_system;
115 static CursPtr default_cursor_ptr;
116 static Cursor default_cursor;
117 static WindowRef fl_os_capture = 0; // the dispatch handler will redirect mose move and drag events to these windows
119 #if CONSOLIDATE_MOTION
120 static Fl_Window* send_motion;
121 extern Fl_Window* fl_xmousewin;
122 #endif
124 enum { kEventClassFLTK = 'fltk' };
125 enum { kEventFLTKBreakLoop = 1, kEventFLTKDataReady };
127 /* fltk-utf8 placekeepers */
128 void fl_reset_spot()
132 void fl_set_spot(int font, int size, int X, int Y, int W, int H, Fl_Window *win)
136 void fl_set_status(int x, int y, int w, int h)
141 * Mac keyboard lookup table
143 static unsigned short macKeyLookUp[128] =
145 'a', 's', 'd', 'f', 'h', 'g', 'z', 'x',
146 'c', 'v', '^', 'b', 'q', 'w', 'e', 'r',
148 'y', 't', '1', '2', '3', '4', '6', '5',
149 '=', '9', '7', '-', '8', '0', ']', 'o',
151 'u', '[', 'i', 'p', FL_Enter, 'l', 'j', '\'',
152 'k', ';', '\\', ',', '/', 'n', 'm', '.',
154 FL_Tab, ' ', '`', FL_BackSpace,
155 FL_KP_Enter, FL_Escape, 0, 0/*FL_Meta_L*/,
156 0/*FL_Shift_L*/, 0/*FL_Caps_Lock*/, 0/*FL_Alt_L*/, 0/*FL_Control_L*/,
157 0/*FL_Shift_R*/, 0/*FL_Alt_R*/, 0/*FL_Control_R*/, 0,
159 0, FL_KP+'.', FL_Right, FL_KP+'*', 0, FL_KP+'+', FL_Left, FL_Delete,
160 FL_Down, 0, 0, FL_KP+'/', FL_KP_Enter, FL_Up, FL_KP+'-', 0,
162 0, FL_KP+'=', FL_KP+'0', FL_KP+'1', FL_KP+'2', FL_KP+'3', FL_KP+'4', FL_KP+'5',
163 FL_KP+'6', FL_KP+'7', 0, FL_KP+'8', FL_KP+'9', 0, 0, 0,
165 FL_F+5, FL_F+6, FL_F+7, FL_F+3, FL_F+8, FL_F+9, 0, FL_F+11,
166 0, 0/*FL_F+13*/, FL_Print, FL_Scroll_Lock, 0, FL_F+10, FL_Menu, FL_F+12,
168 0, FL_Pause, FL_Help, FL_Home, FL_Page_Up, FL_Delete, FL_F+4, FL_End,
169 FL_F+2, FL_Page_Down, FL_F+1, FL_Left, FL_Right, FL_Down, FL_Up, 0/*FL_Power*/,
173 * convert the current mouse chord into the FLTK modifier state
175 static unsigned int mods_to_e_state( UInt32 mods )
177 long state = 0;
178 if ( mods & kEventKeyModifierNumLockMask ) state |= FL_NUM_LOCK;
179 if ( mods & cmdKey ) state |= FL_META;
180 if ( mods & (optionKey|rightOptionKey) ) state |= FL_ALT;
181 if ( mods & (controlKey|rightControlKey) ) state |= FL_CTRL;
182 if ( mods & (shiftKey|rightShiftKey) ) state |= FL_SHIFT;
183 if ( mods & alphaLock ) state |= FL_CAPS_LOCK;
184 unsigned int ret = ( Fl::e_state & 0xff000000 ) | state;
185 Fl::e_state = ret;
186 //printf( "State 0x%08x (%04x)\n", Fl::e_state, mods );
187 return ret;
192 * convert the current mouse chord into the FLTK keysym
194 static void mods_to_e_keysym( UInt32 mods )
196 if ( mods & cmdKey ) Fl::e_keysym = FL_Meta_L;
197 else if ( mods & kEventKeyModifierNumLockMask ) Fl::e_keysym = FL_Num_Lock;
198 else if ( mods & optionKey ) Fl::e_keysym = FL_Alt_L;
199 else if ( mods & rightOptionKey ) Fl::e_keysym = FL_Alt_R;
200 else if ( mods & controlKey ) Fl::e_keysym = FL_Control_L;
201 else if ( mods & rightControlKey ) Fl::e_keysym = FL_Control_R;
202 else if ( mods & shiftKey ) Fl::e_keysym = FL_Shift_L;
203 else if ( mods & rightShiftKey ) Fl::e_keysym = FL_Shift_R;
204 else if ( mods & alphaLock ) Fl::e_keysym = FL_Caps_Lock;
205 else Fl::e_keysym = 0;
206 //printf( "to sym 0x%08x (%04x)\n", Fl::e_keysym, mods );
208 // these pointers are set by the Fl::lock() function:
209 static void nothing() {}
210 void (*fl_lock_function)() = nothing;
211 void (*fl_unlock_function)() = nothing;
214 // Select interface -- how it's implemented:
215 // When the user app configures one or more file descriptors to monitor
216 // with Fl::add_fd(), we start a separate thread to select() the data,
217 // sending a custom OSX 'FLTK data ready event' to the parent thread's
218 // RunApplicationLoop(), so that it triggers the data ready callbacks
219 // in the parent thread. -erco 04/04/04
221 #define POLLIN 1
222 #define POLLOUT 4
223 #define POLLERR 8
225 // Class to handle select() 'data ready'
226 class DataReady
228 struct FD
230 int fd;
231 short events;
232 void (*cb)(int, void*);
233 void* arg;
235 int nfds, fd_array_size;
236 FD *fds;
237 pthread_t tid; // select()'s thread id
239 // Data that needs to be locked (all start with '_')
240 pthread_mutex_t _datalock; // data lock
241 fd_set _fdsets[3]; // r/w/x sets user wants to monitor
242 int _maxfd; // max fd count to monitor
243 int _cancelpipe[2]; // pipe used to help cancel thread
244 void *_userdata; // thread's userdata
246 public:
247 DataReady()
249 nfds = 0;
250 fd_array_size = 0;
251 fds = 0;
252 tid = 0;
254 pthread_mutex_init(&_datalock, NULL);
255 FD_ZERO(&_fdsets[0]); FD_ZERO(&_fdsets[1]); FD_ZERO(&_fdsets[2]);
256 _cancelpipe[0] = _cancelpipe[1] = 0;
257 _userdata = 0;
258 _maxfd = 0;
261 ~DataReady()
263 CancelThread(DEBUGTEXT("DESTRUCTOR\n"));
264 if (fds) { free(fds); fds = 0; }
265 nfds = 0;
268 // Locks
269 // The convention for locks: volatile vars start with '_',
270 // and must be locked before use. Locked code is prefixed
271 // with /*LOCK*/ to make painfully obvious esp. in debuggers. -erco
273 void DataLock() { pthread_mutex_lock(&_datalock); }
274 void DataUnlock() { pthread_mutex_unlock(&_datalock); }
276 // Accessors
277 int IsThreadRunning() { return(tid ? 1 : 0); }
278 int GetNfds() { return(nfds); }
279 int GetCancelPipe(int ix) { return(_cancelpipe[ix]); }
280 fd_set GetFdset(int ix) { return(_fdsets[ix]); }
282 // Methods
283 void AddFD(int n, int events, void (*cb)(int, void*), void *v);
284 void RemoveFD(int n, int events);
285 int CheckData(fd_set& r, fd_set& w, fd_set& x);
286 void HandleData(fd_set& r, fd_set& w, fd_set& x);
287 static void* DataReadyThread(void *self);
288 void StartThread(void *userdata);
289 void CancelThread(const char *reason);
292 static DataReady dataready;
294 void DataReady::AddFD(int n, int events, void (*cb)(int, void*), void *v)
296 RemoveFD(n, events);
297 int i = nfds++;
298 if (i >= fd_array_size)
300 FD *temp;
301 fd_array_size = 2*fd_array_size+1;
302 if (!fds) { temp = (FD*)malloc(fd_array_size*sizeof(FD)); }
303 else { temp = (FD*)realloc(fds, fd_array_size*sizeof(FD)); }
304 if (!temp) return;
305 fds = temp;
307 fds[i].cb = cb;
308 fds[i].arg = v;
309 fds[i].fd = n;
310 fds[i].events = events;
311 DataLock();
312 /*LOCK*/ if (events & POLLIN) FD_SET(n, &_fdsets[0]);
313 /*LOCK*/ if (events & POLLOUT) FD_SET(n, &_fdsets[1]);
314 /*LOCK*/ if (events & POLLERR) FD_SET(n, &_fdsets[2]);
315 /*LOCK*/ if (n > _maxfd) _maxfd = n;
316 DataUnlock();
319 // Remove an FD from the array
320 void DataReady::RemoveFD(int n, int events)
322 int i,j;
323 for (i=j=0; i<nfds; i++)
325 if (fds[i].fd == n)
327 int e = fds[i].events & ~events;
328 if (!e) continue; // if no events left, delete this fd
329 fds[i].events = e;
331 // move it down in the array if necessary:
332 if (j<i)
333 { fds[j] = fds[i]; }
334 j++;
336 nfds = j;
337 DataLock();
338 /*LOCK*/ if (events & POLLIN) FD_CLR(n, &_fdsets[0]);
339 /*LOCK*/ if (events & POLLOUT) FD_CLR(n, &_fdsets[1]);
340 /*LOCK*/ if (events & POLLERR) FD_CLR(n, &_fdsets[2]);
341 /*LOCK*/ if (n == _maxfd) _maxfd--;
342 DataUnlock();
345 // CHECK IF USER DATA READY, RETURNS r/w/x INDICATING WHICH IF ANY
346 int DataReady::CheckData(fd_set& r, fd_set& w, fd_set& x)
348 int ret;
349 DataLock();
350 /*LOCK*/ timeval t = { 0, 1 }; // quick check
351 /*LOCK*/ r = _fdsets[0], w = _fdsets[1], x = _fdsets[2];
352 /*LOCK*/ ret = ::select(_maxfd+1, &r, &w, &x, &t);
353 DataUnlock();
354 if ( ret == -1 )
355 { DEBUGPERRORMSG("CheckData(): select()"); }
356 return(ret);
359 // HANDLE DATA READY CALLBACKS
360 void DataReady::HandleData(fd_set& r, fd_set& w, fd_set& x)
362 for (int i=0; i<nfds; i++)
364 int f = fds[i].fd;
365 short revents = 0;
366 if (FD_ISSET(f, &r)) revents |= POLLIN;
367 if (FD_ISSET(f, &w)) revents |= POLLOUT;
368 if (FD_ISSET(f, &x)) revents |= POLLERR;
369 if (fds[i].events & revents)
371 DEBUGMSG("DOING CALLBACK: ");
372 fds[i].cb(f, fds[i].arg);
373 DEBUGMSG("DONE\n");
378 // DATA READY THREAD
379 // This thread watches for changes in user's file descriptors.
380 // Sends a 'data ready event' to the main thread if any change.
382 void* DataReady::DataReadyThread(void *o)
384 DataReady *self = (DataReady*)o;
385 while ( 1 ) // loop until thread cancel or error
387 // Thread safe local copies of data before each select()
388 self->DataLock();
389 /*LOCK*/ int maxfd = self->_maxfd;
390 /*LOCK*/ fd_set r = self->GetFdset(0);
391 /*LOCK*/ fd_set w = self->GetFdset(1);
392 /*LOCK*/ fd_set x = self->GetFdset(2);
393 /*LOCK*/ void *userdata = self->_userdata;
394 /*LOCK*/ int cancelpipe = self->GetCancelPipe(0);
395 /*LOCK*/ if ( cancelpipe > maxfd ) maxfd = cancelpipe;
396 /*LOCK*/ FD_SET(cancelpipe, &r); // add cancelpipe to fd's to watch
397 /*LOCK*/ FD_SET(cancelpipe, &x);
398 self->DataUnlock();
399 // timeval t = { 1000, 0 }; // 1000 seconds;
400 timeval t = { 2, 0 }; // HACK: 2 secs prevents 'hanging' problem
401 int ret = ::select(maxfd+1, &r, &w, &x, &t);
402 pthread_testcancel(); // OSX 10.0.4 and older: needed for parent to cancel
403 switch ( ret )
405 case 0: // NO DATA
406 continue;
407 case -1: // ERROR
409 DEBUGPERRORMSG("CHILD THREAD: select() failed");
410 return(NULL); // error? exit thread
412 default: // DATA READY
414 if (FD_ISSET(cancelpipe, &r) || FD_ISSET(cancelpipe, &x)) // cancel?
415 { return(NULL); } // just exit
416 DEBUGMSG("CHILD THREAD: DATA IS READY\n");
417 EventRef drEvent;
418 CreateEvent( 0, kEventClassFLTK, kEventFLTKDataReady,
419 0, kEventAttributeUserEvent, &drEvent);
420 EventQueueRef eventqueue = (EventQueueRef)userdata;
421 PostEventToQueue(eventqueue, drEvent, kEventPriorityStandard );
422 ReleaseEvent( drEvent );
423 return(NULL); // done with thread
429 // START 'DATA READY' THREAD RUNNING, CREATE INTER-THREAD PIPE
430 void DataReady::StartThread(void *new_userdata)
432 CancelThread(DEBUGTEXT("STARTING NEW THREAD\n"));
433 DataLock();
434 /*LOCK*/ pipe(_cancelpipe); // pipe for sending cancel msg to thread
435 /*LOCK*/ _userdata = new_userdata;
436 DataUnlock();
437 DEBUGMSG("*** START THREAD\n");
438 pthread_create(&tid, NULL, DataReadyThread, (void*)this);
441 // CANCEL 'DATA READY' THREAD, CLOSE PIPE
442 void DataReady::CancelThread(const char *reason)
444 if ( tid )
446 DEBUGMSG("*** CANCEL THREAD: ");
447 DEBUGMSG(reason);
448 if ( pthread_cancel(tid) == 0 ) // cancel first
450 DataLock();
451 /*LOCK*/ write(_cancelpipe[1], "x", 1); // wake thread from select
452 DataUnlock();
453 pthread_join(tid, NULL); // wait for thread to finish
455 tid = 0;
456 DEBUGMSG("(JOINED) OK\n");
458 // Close pipe if open
459 DataLock();
460 /*LOCK*/ if ( _cancelpipe[0] ) { close(_cancelpipe[0]); _cancelpipe[0] = 0; }
461 /*LOCK*/ if ( _cancelpipe[1] ) { close(_cancelpipe[1]); _cancelpipe[1] = 0; }
462 DataUnlock();
465 void Fl::add_fd( int n, int events, void (*cb)(int, void*), void *v )
466 { dataready.AddFD(n, events, cb, v); }
468 void Fl::add_fd(int fd, void (*cb)(int, void*), void* v)
469 { dataready.AddFD(fd, POLLIN, cb, v); }
471 void Fl::remove_fd(int n, int events)
472 { dataready.RemoveFD(n, events); }
474 void Fl::remove_fd(int n)
475 { dataready.RemoveFD(n, -1); }
478 * Check if there is actually a message pending!
480 int fl_ready()
482 EventRef event;
483 return !ReceiveNextEvent(0, NULL, 0.0, false, &event);
487 * handle Apple Menu items (can be created using the Fl_Sys_Menu_Bar
488 * returns eventNotHandledErr if the menu item could not be handled
490 OSStatus HandleMenu( HICommand *cmd )
492 OSStatus ret = eventNotHandledErr;
493 // attributes, commandIDm menu.menuRef, menu.menuItemIndex
494 UInt32 ref;
495 OSErr rrc = GetMenuItemRefCon( cmd->menu.menuRef, cmd->menu.menuItemIndex, &ref );
496 //printf( "%d, %08x, %08x, %d, %d, %8x\n", rrc, cmd->attributes, cmd->commandID, cmd->menu.menuRef, cmd->menu.menuItemIndex, rrc );
497 if ( rrc==noErr && ref )
499 Fl_Menu_Item *m = (Fl_Menu_Item*)ref;
500 //printf( "Menu: %s\n", m->label() );
501 fl_sys_menu_bar->picked( m );
502 if ( m->flags & FL_MENU_TOGGLE ) // update the menu toggle symbol
503 SetItemMark( cmd->menu.menuRef, cmd->menu.menuItemIndex, (m->flags & FL_MENU_VALUE ) ? 0x12 : 0 );
504 if ( m->flags & FL_MENU_RADIO ) // update all radio buttons in this menu
506 Fl_Menu_Item *j = m;
507 int i = cmd->menu.menuItemIndex;
508 for (;;)
510 if ( j->flags & FL_MENU_DIVIDER )
511 break;
512 j++; i++;
513 if ( !j->text || !j->radio() )
514 break;
515 SetItemMark( cmd->menu.menuRef, i, ( j->flags & FL_MENU_VALUE ) ? 0x13 : 0 );
517 j = m-1; i = cmd->menu.menuItemIndex-1;
518 for ( ; i>0; j--, i-- )
520 if ( !j->text || j->flags&FL_MENU_DIVIDER || !j->radio() )
521 break;
522 SetItemMark( cmd->menu.menuRef, i, ( j->flags & FL_MENU_VALUE ) ? 0x13 : 0 );
524 SetItemMark( cmd->menu.menuRef, cmd->menu.menuItemIndex, ( m->flags & FL_MENU_VALUE ) ? 0x13 : 0 );
526 ret = noErr; // done handling this event
528 HiliteMenu(0);
529 return ret;
534 * We can make every event pass through this function
535 * - mouse events need to be manipulated to use a mouse focus window
536 * - keyboard, mouse and some window events need to quit the Apple Event Loop
537 * so FLTK can continue its own management
539 static pascal OSStatus carbonDispatchHandler( EventHandlerCallRef nextHandler, EventRef event, void *userData )
541 OSStatus ret = eventNotHandledErr;
542 HICommand cmd;
544 fl_lock_function();
546 got_events = 1;
548 switch ( GetEventClass( event ) )
550 case kEventClassMouse:
551 switch ( GetEventKind( event ) )
553 case kEventMouseUp:
554 case kEventMouseMoved:
555 case kEventMouseDragged:
556 if ( fl_capture )
557 ret = SendEventToEventTarget( event, GetWindowEventTarget( fl_capture ) );
558 else if ( fl_os_capture ){
559 ret = SendEventToEventTarget( event, GetWindowEventTarget( fl_os_capture ) );
560 fl_os_capture = 0;
562 break;
564 break;
565 case kEventClassCommand:
566 switch (GetEventKind( event ) )
568 case kEventCommandProcess:
569 ret = GetEventParameter( event, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &cmd );
570 if (ret == noErr && (cmd.attributes & kHICommandFromMenu) != 0)
571 ret = HandleMenu( &cmd );
572 else
573 ret = eventNotHandledErr;
574 break;
576 break;
577 case kEventClassFLTK:
578 switch ( GetEventKind( event ) )
580 case kEventFLTKBreakLoop:
581 ret = noErr;
582 break;
583 case kEventFLTKDataReady:
585 dataready.CancelThread(DEBUGTEXT("DATA READY EVENT\n"));
587 // CHILD THREAD TELLS US DATA READY
588 // Check to see what's ready, and invoke user's cb's
590 fd_set r,w,x;
591 switch(dataready.CheckData(r,w,x))
593 case 0: // NO DATA
594 break;
595 case -1: // ERROR
596 break;
597 default: // DATA READY
598 dataready.HandleData(r,w,x);
599 break;
602 ret = noErr;
603 break;
606 if ( ret == eventNotHandledErr )
607 ret = CallNextEventHandler( nextHandler, event ); // let the OS handle the activation, but continue to get a click-through effect
609 fl_unlock_function();
611 return ret;
616 * break the current event loop
618 static void breakMacEventLoop()
620 EventRef breakEvent;
622 fl_lock_function();
624 CreateEvent( 0, kEventClassFLTK, kEventFLTKBreakLoop, 0, kEventAttributeUserEvent, &breakEvent );
625 PostEventToQueue( GetCurrentEventQueue(), breakEvent, kEventPriorityStandard );
626 ReleaseEvent( breakEvent );
628 fl_unlock_function();
632 // MacOS X timers
635 struct MacTimeout {
636 Fl_Timeout_Handler callback;
637 void* data;
638 EventLoopTimerRef timer;
639 EventLoopTimerUPP upp;
640 char pending;
642 static MacTimeout* mac_timers;
643 static int mac_timer_alloc;
644 static int mac_timer_used;
647 static void realloc_timers()
649 if (mac_timer_alloc == 0) {
650 mac_timer_alloc = 8;
652 mac_timer_alloc *= 2;
653 MacTimeout* new_timers = new MacTimeout[mac_timer_alloc];
654 memset(new_timers, 0, sizeof(MacTimeout)*mac_timer_alloc);
655 memcpy(new_timers, mac_timers, sizeof(MacTimeout) * mac_timer_used);
656 MacTimeout* delete_me = mac_timers;
657 mac_timers = new_timers;
658 delete [] delete_me;
661 static void delete_timer(MacTimeout& t)
663 if (t.timer) {
664 RemoveEventLoopTimer(t.timer);
665 DisposeEventLoopTimerUPP(t.upp);
666 memset(&t, 0, sizeof(MacTimeout));
671 static pascal void do_timer(EventLoopTimerRef timer, void* data)
673 for (int i = 0; i < mac_timer_used; ++i) {
674 MacTimeout& t = mac_timers[i];
675 if (t.timer == timer && t.data == data) {
676 t.pending = 0;
677 (*t.callback)(data);
678 if (t.pending==0)
679 delete_timer(t);
680 break;
683 breakMacEventLoop();
687 * This function is the central event handler.
688 * It reads events from the event queue using the given maximum time
689 * Funny enough, it returns the same time that it got as the argument.
691 static double do_queued_events( double time = 0.0 )
693 static bool been_here = false;
694 static RgnHandle rgn;
696 // initialize events and a region that enables mouse move events
697 if (!been_here) {
698 rgn = NewRgn();
699 Point mp;
700 GetMouse(&mp);
701 SetRectRgn(rgn, mp.h, mp.v, mp.h, mp.v);
702 SetEventMask(everyEvent);
703 been_here = true;
705 OSStatus ret;
706 static EventTargetRef target = 0;
707 if ( !target )
709 target = GetEventDispatcherTarget();
711 EventHandlerUPP dispatchHandler = NewEventHandlerUPP( carbonDispatchHandler ); // will not be disposed by Carbon...
712 static EventTypeSpec dispatchEvents[] = {
713 { kEventClassWindow, kEventWindowShown },
714 { kEventClassWindow, kEventWindowHidden },
715 { kEventClassWindow, kEventWindowActivated },
716 { kEventClassWindow, kEventWindowDeactivated },
717 { kEventClassWindow, kEventWindowClose },
718 { kEventClassKeyboard, kEventRawKeyDown },
719 { kEventClassKeyboard, kEventRawKeyRepeat },
720 { kEventClassKeyboard, kEventRawKeyUp },
721 { kEventClassKeyboard, kEventRawKeyModifiersChanged },
722 { kEventClassMouse, kEventMouseDown },
723 { kEventClassMouse, kEventMouseUp },
724 { kEventClassMouse, kEventMouseMoved },
725 { kEventClassMouse, 11 }, // MightyMouse wheels
726 { kEventClassMouse, kEventMouseWheelMoved },
727 { kEventClassMouse, kEventMouseDragged },
728 { kEventClassFLTK, kEventFLTKBreakLoop },
729 { kEventClassFLTK, kEventFLTKDataReady } };
730 ret = InstallEventHandler( target, dispatchHandler, GetEventTypeCount(dispatchEvents), dispatchEvents, 0, 0L );
731 static EventTypeSpec appEvents[] = {
732 { kEventClassCommand, kEventCommandProcess } };
733 ret = InstallApplicationEventHandler( dispatchHandler, GetEventTypeCount(appEvents), appEvents, 0, 0L );
736 got_events = 0;
738 // Check for re-entrant condition
739 if ( dataready.IsThreadRunning() )
740 { dataready.CancelThread(DEBUGTEXT("AVOID REENTRY\n")); }
742 // Start thread to watch for data ready
743 if ( dataready.GetNfds() )
744 { dataready.StartThread((void*)GetCurrentEventQueue()); }
746 fl_unlock_function();
748 EventRef event;
749 EventTimeout timeout = time;
750 if (!ReceiveNextEvent(0, NULL, timeout, true, &event)) {
751 got_events = 1;
752 OSErr ret = SendEventToEventTarget( event, target );
753 if (ret!=noErr) {
754 EventRecord clevent;
755 ConvertEventRefToEventRecord(event, &clevent);
756 if (clevent.what==kHighLevelEvent) {
757 ret = AEProcessAppleEvent(&clevent);
760 if ( ret==eventNotHandledErr
761 && GetEventClass(event)==kEventClassMouse
762 && GetEventKind(event)==kEventMouseDown ) {
763 WindowRef win; Point pos;
764 GetEventParameter(event, kEventParamMouseLocation, typeQDPoint,
765 NULL, sizeof(pos), NULL, &pos);
766 if (MacFindWindow(pos, &win)==inMenuBar) {
767 MenuSelect(pos);
770 ReleaseEvent( event );
773 fl_lock_function();
775 #if CONSOLIDATE_MOTION
776 if (send_motion && send_motion == fl_xmousewin) {
777 send_motion = 0;
778 Fl::handle(FL_MOVE, fl_xmousewin);
780 #endif
782 return time;
787 * This public function handles all events. It wait a maximum of
788 * 'time' secods for an event. This version returns 1 if events
789 * other than the timeout timer were processed.
791 * \todo there is no socket handling in this code whatsoever
793 int fl_wait( double time )
795 do_queued_events( time );
796 return (got_events);
801 * event handler for Apple-Q key combination
802 * this is also called from the Carbon Window handler after all windows were closed
804 static OSErr QuitAppleEventHandler( const AppleEvent *appleEvt, AppleEvent* reply, UInt32 refcon )
806 fl_lock_function();
808 while ( Fl_X::first ) {
809 Fl_X *x = Fl_X::first;
810 Fl::handle( FL_CLOSE, x->w );
811 if ( Fl_X::first == x ) {
812 fl_unlock_function();
813 return noErr; // FLTK has not close all windows, so we return to the main program now
817 fl_unlock_function();
819 return noErr;
824 * Carbon Window handler
825 * This needs to be linked into all new window event handlers
827 static pascal OSStatus carbonWindowHandler( EventHandlerCallRef nextHandler, EventRef event, void *userData )
829 UInt32 kind = GetEventKind( event );
830 OSStatus ret = eventNotHandledErr;
831 Fl_Window *window = (Fl_Window*)userData;
832 Fl::first_window(window);
834 Rect currentBounds, originalBounds;
835 WindowClass winClass;
836 static Fl_Window *activeWindow = 0;
838 fl_lock_function();
840 switch ( kind )
842 case kEventWindowBoundsChanging:
843 GetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &currentBounds );
844 GetEventParameter( event, kEventParamOriginalBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &originalBounds );
845 break;
846 case kEventWindowDrawContent:
847 handleUpdateEvent( fl_xid( window ) );
848 ret = noErr;
849 break;
850 case kEventWindowBoundsChanged: {
851 GetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &currentBounds );
852 GetEventParameter( event, kEventParamOriginalBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &originalBounds );
853 int X = currentBounds.left, W = currentBounds.right-X;
854 int Y = currentBounds.top, H = currentBounds.bottom-Y;
855 resize_from_system = window;
856 window->resize( X, Y, W, H );
857 if ( ( originalBounds.right - originalBounds.left != W )
858 || ( originalBounds.bottom - originalBounds.top != H ) )
860 if ( window->shown() )
861 handleUpdateEvent( fl_xid( window ) );
863 break; }
864 case kEventWindowShown:
865 if ( !window->parent() )
867 GetWindowClass( fl_xid( window ), &winClass );
868 if ( winClass != kHelpWindowClass ) { // help windows can't get the focus!
869 Fl::handle( FL_FOCUS, window);
870 activeWindow = window;
872 Fl::handle( FL_SHOW, window);
873 mods_to_e_state(GetCurrentKeyModifiers());
875 break;
876 case kEventWindowHidden:
877 if ( !window->parent() ) Fl::handle( FL_HIDE, window);
878 break;
879 case kEventWindowActivated:
880 if ( window->shown() && window!=activeWindow )
882 GetWindowClass( fl_xid( window ), &winClass );
883 if ( winClass != kHelpWindowClass ) { // help windows can't get the focus!
884 Fl::handle( FL_FOCUS, window);
885 activeWindow = window;
888 break;
889 case kEventWindowDeactivated:
890 if ( window==activeWindow )
892 Fl::handle( FL_UNFOCUS, window);
893 activeWindow = 0;
895 break;
896 case kEventWindowClose:
897 Fl::handle( FL_CLOSE, window ); // this might or might not close the window
898 // if there are no more windows, send a high-level quit event
899 if (!Fl_X::first) QuitAppleEventHandler( 0, 0, 0 );
900 ret = noErr; // returning noErr tells Carbon to stop following up on this event
901 break;
902 case kEventWindowCollapsed:
903 window->clear_visible();
904 break;
905 case kEventWindowExpanded:
906 window->set_visible();
907 break;
910 fl_unlock_function();
912 return ret;
917 * Carbon Mousewheel handler
918 * This needs to be linked into all new window event handlers
920 static pascal OSStatus carbonMousewheelHandler( EventHandlerCallRef nextHandler, EventRef event, void *userData )
922 // Handle the new "MightyMouse" mouse wheel events. Please, someone explain
923 // to me why Apple changed the API on this even though the current API
924 // supports two wheels just fine. Matthias,
925 fl_lock_function();
927 fl_os_event = event;
928 Fl_Window *window = (Fl_Window*)userData;
929 if ( !window->shown() )
931 fl_unlock_function();
932 return noErr;
934 Fl::first_window(window);
936 EventMouseWheelAxis axis;
937 GetEventParameter( event, kEventParamMouseWheelAxis, typeMouseWheelAxis, NULL, sizeof(EventMouseWheelAxis), NULL, &axis );
938 long delta;
939 GetEventParameter( event, kEventParamMouseWheelDelta, typeLongInteger, NULL, sizeof(long), NULL, &delta );
940 // fprintf(stderr, "axis=%d, delta=%d\n", axis, delta);
941 if ( axis == kEventMouseWheelAxisX ) {
942 Fl::e_dx = -delta;
943 Fl::e_dy = 0;
944 if ( Fl::e_dx) Fl::handle( FL_MOUSEWHEEL, window );
945 } else if ( axis == kEventMouseWheelAxisY ) {
946 Fl::e_dx = 0;
947 Fl::e_dy = -delta;
948 if ( Fl::e_dy) Fl::handle( FL_MOUSEWHEEL, window );
949 } else {
950 fl_unlock_function();
952 return eventNotHandledErr;
955 fl_unlock_function();
957 return noErr;
962 * convert the current mouse chord into the FLTK modifier state
964 static void chord_to_e_state( UInt32 chord )
966 static ulong state[] =
968 0, FL_BUTTON1, FL_BUTTON3, FL_BUTTON1|FL_BUTTON3, FL_BUTTON2,
969 FL_BUTTON2|FL_BUTTON1, FL_BUTTON2|FL_BUTTON3,
970 FL_BUTTON2|FL_BUTTON1|FL_BUTTON3
972 Fl::e_state = ( Fl::e_state & 0xff0000 ) | state[ chord & 0x07 ];
977 * Carbon Mouse Button Handler
979 static pascal OSStatus carbonMouseHandler( EventHandlerCallRef nextHandler, EventRef event, void *userData )
981 static int keysym[] = { 0, FL_Button+1, FL_Button+3, FL_Button+2 };
982 static int px, py;
983 static char suppressed = 0;
985 fl_lock_function();
987 fl_os_event = event;
988 Fl_Window *window = (Fl_Window*)userData;
989 if ( !window->shown() )
991 fl_unlock_function();
992 return noErr;
994 Fl::first_window(window);
995 Point pos;
996 GetEventParameter( event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &pos );
997 EventMouseButton btn;
998 GetEventParameter( event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(EventMouseButton), NULL, &btn );
999 UInt32 clickCount;
1000 GetEventParameter( event, kEventParamClickCount, typeUInt32, NULL, sizeof(UInt32), NULL, &clickCount );
1001 UInt32 chord;
1002 GetEventParameter( event, kEventParamMouseChord, typeUInt32, NULL, sizeof(UInt32), NULL, &chord );
1003 WindowRef xid = fl_xid(window), tempXid;
1004 int sendEvent = 0, part = 0;
1005 switch ( GetEventKind( event ) )
1007 case kEventMouseDown:
1008 part = FindWindow( pos, &tempXid );
1009 if (!(Fl::grab() && window!=Fl::grab())) {
1010 if ( part == inGrow ) {
1011 fl_unlock_function();
1012 suppressed = 1;
1013 Fl_Tooltip::current(0L);
1014 return CallNextEventHandler( nextHandler, event ); // let the OS handle this for us
1016 if ( part != inContent ) {
1017 fl_unlock_function();
1018 suppressed = 1;
1019 Fl_Tooltip::current(0L);
1020 // anything else to here?
1021 return CallNextEventHandler( nextHandler, event ); // let the OS handle this for us
1024 suppressed = 0;
1025 if (part==inContent && !IsWindowActive( xid ) ) {
1026 CallNextEventHandler( nextHandler, event ); // let the OS handle the activation, but continue to get a click-through effect
1028 // normal handling of mouse-down follows
1029 fl_os_capture = xid;
1030 sendEvent = FL_PUSH;
1031 Fl::e_is_click = 1; px = pos.h; py = pos.v;
1032 if (clickCount>1)
1033 Fl::e_clicks++;
1034 else
1035 Fl::e_clicks = 0;
1036 // fall through
1037 case kEventMouseUp:
1038 if (suppressed) {
1039 suppressed = 0;
1040 break;
1042 if ( !window ) break;
1043 if ( !sendEvent ) {
1044 sendEvent = FL_RELEASE;
1046 Fl::e_keysym = keysym[ btn ];
1047 // fall through
1048 case kEventMouseMoved:
1049 suppressed = 0;
1050 if ( !sendEvent ) {
1051 sendEvent = FL_MOVE; chord = 0;
1053 // fall through
1054 case kEventMouseDragged:
1055 if (suppressed) break;
1056 if ( !sendEvent ) {
1057 sendEvent = FL_MOVE; // Fl::handle will convert into FL_DRAG
1058 if (abs(pos.h-px)>5 || abs(pos.v-py)>5)
1059 Fl::e_is_click = 0;
1061 chord_to_e_state( chord );
1062 GrafPtr oldPort;
1063 GetPort( &oldPort );
1064 SetPort( GetWindowPort(xid) ); // \todo replace this! There must be some GlobalToLocal call that has a port as an argument
1065 SetOrigin(0, 0);
1066 Fl::e_x_root = pos.h;
1067 Fl::e_y_root = pos.v;
1068 GlobalToLocal( &pos );
1069 Fl::e_x = pos.h;
1070 Fl::e_y = pos.v;
1071 SetPort( oldPort );
1072 if (GetEventKind(event)==kEventMouseDown && part!=inContent) {
1073 int used = Fl::handle( sendEvent, window );
1074 CallNextEventHandler( nextHandler, event ); // let the OS handle this for us
1075 if (!used)
1076 suppressed = 1;
1077 } else {
1078 Fl::handle( sendEvent, window );
1080 break;
1083 fl_unlock_function();
1085 return noErr;
1090 * convert the keyboard return code into the symbol on the keycaps
1092 static unsigned short keycode_to_sym( UInt32 keyCode, UInt32 mods, unsigned short deflt )
1094 static Ptr map = 0;
1095 UInt32 state = 0;
1096 if (!map) {
1097 map = (Ptr)GetScriptManagerVariable(smKCHRCache);
1098 if (!map) {
1099 long kbID = GetScriptManagerVariable(smKeyScript);
1100 map = *GetResource('KCHR', kbID);
1103 if (map)
1104 return KeyTranslate(map, keyCode|mods, &state );
1105 return deflt;
1109 * keycode_function for post-10.5 systems, allows more sophisticated decoding of keys
1111 static int keycodeToUnicode(
1112 char * uniChars, int maxChars,
1113 EventKind eKind,
1114 UInt32 keycode, UInt32 modifiers,
1115 UInt32 * deadKeyStatePtr,
1116 unsigned char, // not used in this function
1117 unsigned short) // not used in this function
1119 // first get the keyboard mapping in a post 10.2 way
1121 Ptr resource;
1122 TextEncoding encoding;
1123 static TextEncoding lastEncoding = kTextEncodingMacRoman;
1124 int len = 0;
1125 KeyboardLayoutRef currentLayout = NULL;
1126 static KeyboardLayoutRef lastLayout = NULL;
1127 SInt32 currentLayoutId = 0;
1128 static SInt32 lastLayoutId;
1129 int hasLayoutChanged = false;
1130 static Ptr uchr = NULL;
1131 static Ptr KCHR = NULL;
1132 // ScriptCode currentKeyScript;
1134 KLGetCurrentKeyboardLayout(&currentLayout);
1135 if (currentLayout) {
1136 KLGetKeyboardLayoutProperty(currentLayout, kKLIdentifier, (const void**)&currentLayoutId);
1137 if ( (lastLayout != currentLayout) || (lastLayoutId != currentLayoutId) ) {
1138 lastLayout = currentLayout;
1139 lastLayoutId = currentLayoutId;
1140 uchr = NULL;
1141 KCHR = NULL;
1142 if ((KLGetKeyboardLayoutProperty(currentLayout, kKLuchrData, (const void**)&uchr) == noErr) && (uchr != NULL)) {
1143 // done
1144 } else if ((KLGetKeyboardLayoutProperty(currentLayout, kKLKCHRData, (const void**)&KCHR) == noErr) && (KCHR != NULL)) {
1145 // done
1147 // FIXME No Layout property found. Now we have a problem.
1150 if (hasLayoutChanged) {
1151 //deadKeyStateUp = deadKeyStateDown = 0;
1152 if (KCHR != NULL) {
1153 // FIXME this must not happen
1154 } else if (uchr == NULL) {
1155 KCHR = (Ptr) GetScriptManagerVariable(smKCHRCache);
1158 if (uchr != NULL) {
1159 // this is what I expect
1160 resource = uchr;
1161 } else {
1162 resource = KCHR;
1163 encoding = lastEncoding;
1164 // this is actually not supported by the following code and will likely crash
1167 // now apply that keyboard mapping to our keycode
1169 int action;
1170 //OptionBits options = 0;
1171 // not used yet: OptionBits options = kUCKeyTranslateNoDeadKeysMask;
1172 unsigned long keyboardType;
1173 keycode &= 0xFF;
1174 modifiers = (modifiers >> 8) & 0xFF;
1175 keyboardType = LMGetKbdType();
1176 OSStatus status;
1177 UniCharCount actuallength;
1178 UniChar utext[10];
1180 switch(eKind) {
1181 case kEventRawKeyDown: action = kUCKeyActionDown; break;
1182 case kEventRawKeyUp: action = kUCKeyActionUp; break;
1183 case kEventRawKeyRepeat: action = kUCKeyActionAutoKey; break;
1184 default: return 0;
1187 UInt32 deadKeyState = *deadKeyStatePtr;
1188 if ((action==kUCKeyActionUp)&&(*deadKeyStatePtr))
1189 deadKeyStatePtr = &deadKeyState;
1191 status = UCKeyTranslate(
1192 (const UCKeyboardLayout *) uchr,
1193 keycode, action, modifiers, keyboardType,
1194 0, deadKeyStatePtr,
1195 10, &actuallength, utext);
1197 if (noErr != status) {
1198 fprintf(stderr,"UCKeyTranslate failed: %d\n", (int) status);
1199 actuallength = 0;
1202 // convert the list of unicode chars into utf8
1203 // FIXME no bounds check (see maxchars)
1204 unsigned i;
1205 for (i=0; i<actuallength; ++i) {
1206 len += fl_utf8encode(utext[i], uniChars+len);
1208 uniChars[len] = 0;
1209 return len;
1213 * keycode_function for pre-10.5 systems, this is the "historic" fltk Mac key handling
1215 static int keycode_wrap_old(
1216 char * buffer,
1217 int, EventKind, UInt32, // not used in this function
1218 UInt32, UInt32 *, // not used in this function
1219 unsigned char key,
1220 unsigned short sym)
1222 if ( (sym >= FL_KP && sym <= FL_KP_Last) || !(sym & 0xff00) ||
1223 sym == FL_Tab || sym == FL_Enter) {
1224 buffer[0] = key;
1225 return 1;
1226 } else {
1227 buffer[0] = 0;
1228 return 0;
1230 } /* keycode_wrap_old */
1232 * Stub pointer to select appropriate keycode_function per operating system version. This function pointer
1233 * is initialised in fl_open_display, based on the runtime identification of the host OS version. This is
1234 * intended to allow us to utilise 10.5 services dynamically to improve Unicode handling, whilst still
1235 * allowing code to run satisfactorily on older systems.
1237 static int (*keycode_function)(char*, int, EventKind, UInt32, UInt32, UInt32*, unsigned char, unsigned short) = keycode_wrap_old;
1240 // EXPERIMENTAL!
1241 pascal OSStatus carbonTextHandler(
1242 EventHandlerCallRef nextHandler, EventRef event, void *userData )
1244 Fl_Window *window = (Fl_Window*)userData;
1245 Fl::first_window(window);
1246 fl_lock_function();
1247 //int kind = GetEventKind(event);
1248 unsigned short buf[200];
1249 ByteCount size;
1250 GetEventParameter( event, kEventParamTextInputSendText, typeUnicodeText,
1251 NULL, 100, &size, &buf );
1252 // printf("TextEvent: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]);
1253 // FIXME: oversimplified!
1254 unsigned ucs = buf[0];
1255 char utf8buf[20];
1256 int len = fl_utf8encode(ucs, utf8buf);
1257 Fl::e_length = len;
1258 Fl::e_text = utf8buf;
1259 while (window->parent()) window = window->window();
1260 Fl::handle(FL_KEYBOARD, window);
1261 fl_unlock_function();
1262 fl_lock_function();
1263 Fl::handle(FL_KEYUP, window);
1264 fl_unlock_function();
1265 // for some reason, the window does not redraw until the next mouse move or button push
1266 // sending a 'redraw()' or 'awake()' does not solve the issue!
1267 Fl::flush();
1268 return noErr;
1272 * handle carbon keyboard events
1274 pascal OSStatus carbonKeyboardHandler(
1275 EventHandlerCallRef nextHandler, EventRef event, void *userData )
1277 static char buffer[32];
1278 int sendEvent = 0;
1279 Fl_Window *window = (Fl_Window*)userData;
1280 Fl::first_window(window);
1281 UInt32 mods;
1282 static UInt32 prevMods = mods_to_e_state( GetCurrentKeyModifiers() );
1284 fl_lock_function();
1286 int kind = GetEventKind(event);
1288 // get the modifiers for any of the events
1289 GetEventParameter( event, kEventParamKeyModifiers, typeUInt32,
1290 NULL, sizeof(UInt32), NULL, &mods );
1292 // get the key code only for key events
1293 UInt32 keyCode = 0, maskedKeyCode = 0;
1294 unsigned char key = 0;
1295 unsigned short sym = 0;
1296 if (kind!=kEventRawKeyModifiersChanged) {
1297 GetEventParameter( event, kEventParamKeyCode, typeUInt32,
1298 NULL, sizeof(UInt32), NULL, &keyCode );
1299 GetEventParameter( event, kEventParamKeyMacCharCodes, typeChar,
1300 NULL, sizeof(char), NULL, &key );
1302 // extended keyboards can also send sequences on key-up to generate Kanji etc. codes.
1303 // Some observed prefixes are 0x81 to 0x83, followed by an 8 bit keycode.
1304 // In this mode, there seem to be no key-down codes
1305 // printf("%08x %08x %08x\n", keyCode, mods, key);
1306 maskedKeyCode = keyCode & 0x7f;
1307 /* output a human readable event identifier for debugging
1308 const char *ev = "";
1309 switch (kind) {
1310 case kEventRawKeyDown: ev = "kEventRawKeyDown"; break;
1311 case kEventRawKeyRepeat: ev = "kEventRawKeyRepeat"; break;
1312 case kEventRawKeyUp: ev = "kEventRawKeyUp"; break;
1313 case kEventRawKeyModifiersChanged: ev = "kEventRawKeyModifiersChanged"; break;
1314 default: ev = "unknown";
1316 printf("%08x %08x %08x '%c' %s \n", mods, keyCode, key, key, ev);
1318 switch (kind)
1320 case kEventRawKeyDown:
1321 case kEventRawKeyRepeat:
1323 // FIXME Matt: For 10.5, the keycode_function will handle all this. This is untested for ealier versions of OS X.
1324 // When the user presses a "dead key", no information is send about
1325 // which dead key symbol was created. So we need to trick Carbon into
1326 // giving us the code by sending a "space" after the "dead key".
1327 if (key==0) {
1328 UInt32 ktState = 0;
1329 KeyboardLayoutRef klr;
1330 KLGetCurrentKeyboardLayout(&klr);
1331 const void *kchar = 0; KLGetKeyboardLayoutProperty(klr, kKLKCHRData, &kchar);
1332 KeyTranslate(kchar, (mods&0xff00) | keyCode, &ktState); // send the dead key
1333 key = KeyTranslate(kchar, 0x31, &ktState); // fake a space key press
1334 Fl::e_state |= 0x40000000; // mark this as a dead key
1335 } else {
1336 Fl::e_state &= 0xbfffffff; // clear the deadkey flag
1339 sendEvent = FL_KEYBOARD;
1340 // fall through
1341 case kEventRawKeyUp:
1342 if ( !sendEvent ) {
1343 sendEvent = FL_KEYUP;
1344 Fl::e_state &= 0xbfffffff; // clear the deadkey flag
1346 // if the user pressed alt/option, event_key should have the keycap,
1347 // but event_text should generate the international symbol
1348 sym = macKeyLookUp[maskedKeyCode];
1349 if ( isalpha(key) )
1350 sym = tolower(key);
1351 else if ( Fl::e_state&FL_CTRL && key<32 && sym<0xff00)
1352 sym = key+96;
1353 else if ( Fl::e_state&FL_ALT && sym<0xff00) // find the keycap of this key
1354 sym = keycode_to_sym( maskedKeyCode, 0, macKeyLookUp[ maskedKeyCode ] );
1355 Fl::e_keysym = Fl::e_original_keysym = sym;
1356 // Handle FL_KP_Enter on regular keyboards and on Powerbooks
1357 if ( maskedKeyCode==0x4c || maskedKeyCode==0x34) key=0x0d;
1358 // Handle the Delete key on the keypad
1359 // Matt: the Mac has no concept of a NumLock key, or at least not visible
1360 // Matt: to Carbon. The kEventKeyModifierNumLockMask is only set when
1361 // Matt: a numeric keypad key is pressed and does not correspond with
1362 // Matt: the NumLock light in PowerBook keyboards.
1364 // Matt: attempt to get the correct Unicode character(s) from our keycode
1365 // imm: keycode_function function pointer added to allow us to use different functions
1366 // imm: depending on which OS version we are running on (tested and set in fl_open_display)
1367 static UInt32 deadKeyState = 0; // must be cleared when losing focus
1368 Fl::e_length = (*keycode_function)(buffer, 31, kind, keyCode, mods, &deadKeyState, key, sym);
1369 Fl::e_text = buffer;
1370 buffer[Fl::e_length] = 0; // just in case...
1371 break;
1372 case kEventRawKeyModifiersChanged: {
1373 UInt32 tMods = prevMods ^ mods;
1374 if ( tMods )
1376 mods_to_e_keysym( tMods );
1377 if ( Fl::e_keysym )
1378 sendEvent = ( prevMods<mods ) ? FL_KEYBOARD : FL_KEYUP;
1379 Fl::e_length = 0;
1380 buffer[0] = 0;
1381 prevMods = mods;
1383 mods_to_e_state( mods );
1384 break; }
1386 while (window->parent()) window = window->window();
1387 if (sendEvent && Fl::handle(sendEvent,window)) {
1388 fl_unlock_function();
1389 return noErr; // return noErr if FLTK handled the event
1390 } else {
1391 fl_unlock_function();
1392 //return CallNextEventHandler( nextHandler, event );;
1393 // Matt: I had better results (no duplicate events) always returning
1394 // Matt: 'noErr'. System keyboard events still seem to work just fine.
1395 return noErr;
1402 * Open callback function to call...
1405 static void (*open_cb)(const char *) = 0;
1409 * Event handler for Apple-O key combination and also for file opens
1410 * via the finder...
1413 static OSErr OpenAppleEventHandler(const AppleEvent *appleEvt,
1414 AppleEvent *reply,
1415 UInt32 refcon) {
1416 OSErr err;
1417 AEDescList documents;
1418 long i, n;
1419 FSSpec fileSpec;
1420 AEKeyword keyWd;
1421 DescType typeCd;
1422 Size actSz;
1423 char filename[1024];
1425 if (!open_cb) return noErr;
1427 // Initialize the document list...
1428 AECreateDesc(typeNull, NULL, 0, &documents);
1430 // Get the open parameter(s)...
1431 err = AEGetParamDesc(appleEvt, keyDirectObject, typeAEList, &documents);
1432 if (err != noErr) {
1433 AEDisposeDesc(&documents);
1434 return err;
1437 // Lock access to FLTK in this thread...
1438 fl_lock_function();
1440 // Open the documents via the callback...
1441 if (AECountItems(&documents, &n) == noErr) {
1442 for (i = 1; i <= n; i ++) {
1443 // Get the next FSSpec record...
1444 AEGetNthPtr(&documents, i, typeFSS, &keyWd, &typeCd,
1445 (Ptr)&fileSpec, sizeof(fileSpec),
1446 (actSz = sizeof(fileSpec), &actSz));
1448 // Convert to a UNIX path...
1449 FSSpec2UnixPath(&fileSpec, filename);
1451 // Call the callback with the filename...
1452 (*open_cb)(filename);
1456 // Unlock access to FLTK for all threads...
1457 fl_unlock_function();
1459 // Get rid of the document list...
1460 AEDisposeDesc(&documents);
1462 return noErr;
1467 * Install an open documents event handler...
1470 void fl_open_callback(void (*cb)(const char *)) {
1471 open_cb = cb;
1472 if (cb) {
1473 AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
1474 NewAEEventHandlerUPP((AEEventHandlerProcPtr)
1475 OpenAppleEventHandler), 0, false);
1476 } else {
1477 AERemoveEventHandler(kCoreEventClass, kAEOpenDocuments,
1478 NewAEEventHandlerUPP((AEEventHandlerProcPtr)
1479 OpenAppleEventHandler), false);
1485 * initialize the Mac toolboxes, dock status, and set the default menubar
1488 extern "C" {
1489 extern OSErr CPSEnableForegroundOperation(ProcessSerialNumber *psn, UInt32 _arg2,
1490 UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
1493 void fl_open_display() {
1494 static char beenHereDoneThat = 0;
1495 if ( !beenHereDoneThat ) {
1496 beenHereDoneThat = 1;
1498 FlushEvents(everyEvent,0);
1500 MoreMasters(); // \todo Carbon suggests MoreMasterPointers()
1501 AEInstallEventHandler( kCoreEventClass, kAEQuitApplication, NewAEEventHandlerUPP((AEEventHandlerProcPtr)QuitAppleEventHandler), 0, false );
1503 // create the Mac Handle for the default cursor (a pointer to a pointer)
1504 GetQDGlobalsArrow(&default_cursor);
1505 default_cursor_ptr = &default_cursor;
1506 fl_default_cursor = &default_cursor_ptr;
1508 ClearMenuBar();
1509 AppendResMenu( GetMenuHandle( 1 ), 'DRVR' );
1510 DrawMenuBar();
1512 // bring the application into foreground without a 'CARB' resource
1513 Boolean same_psn;
1514 ProcessSerialNumber cur_psn, front_psn;
1515 if( !GetCurrentProcess( &cur_psn ) && !GetFrontProcess( &front_psn ) &&
1516 !SameProcess( &front_psn, &cur_psn, &same_psn ) && !same_psn )
1518 // only transform the application type for unbundled apps
1519 CFBundleRef bundle = CFBundleGetMainBundle();
1520 if( bundle )
1522 FSRef execFs;
1523 CFURLRef execUrl = CFBundleCopyExecutableURL( bundle );
1524 CFURLGetFSRef( execUrl, &execFs );
1526 FSRef bundleFs;
1527 GetProcessBundleLocation( &cur_psn, &bundleFs );
1529 if( !FSCompareFSRefs( &execFs, &bundleFs ) )
1530 bundle = NULL;
1532 CFRelease(execUrl);
1535 if( !bundle )
1537 // Earlier versions of this code tried to use weak linking, however it
1538 // appears that this does not work on 10.2. Since 10.3 and higher provide
1539 // both TransformProcessType and CPSEnableForegroundOperation, the following
1540 // conditional code compiled on 10.2 will still work on newer releases...
1541 OSErr err;
1543 #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2
1544 if (TransformProcessType != NULL) {
1545 err = TransformProcessType(&cur_psn, kProcessTransformToForegroundApplication);
1546 } else
1547 #endif // MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2
1548 err = CPSEnableForegroundOperation(&cur_psn, 0x03, 0x3C, 0x2C, 0x1103);
1550 if (err == noErr) {
1551 SetFrontProcess( &cur_psn );
1556 // imm: keycode handler stub setting - use Gestalt to determine the running system version,
1557 // then set the keycode_function pointer accordingly
1558 keycode_function = keycode_wrap_old; // default to pre-10.5 mechanism
1559 SInt32 MacVersion;
1560 if (Gestalt(gestaltSystemVersion, &MacVersion) == noErr)
1562 if(MacVersion >= 0x1050) { // 10.5.0 or later
1563 keycode_function = keycodeToUnicode;
1571 * get rid of allocated resources
1573 void fl_close_display() {
1578 * smallest x ccordinate in screen space
1580 int Fl::x() {
1581 BitMap r;
1582 GetQDGlobalsScreenBits(&r);
1583 return r.bounds.left;
1588 * smallest y ccordinate in screen space
1590 int Fl::y() {
1591 BitMap r;
1592 GetQDGlobalsScreenBits(&r);
1593 return r.bounds.top + 20; // \todo 20 pixel menu bar?
1598 * screen width (single monitor!?)
1600 int Fl::w() {
1601 BitMap r;
1602 GetQDGlobalsScreenBits(&r);
1603 return r.bounds.right - r.bounds.left;
1608 * screen height (single monitor!?)
1610 int Fl::h() {
1611 BitMap r;
1612 GetQDGlobalsScreenBits(&r);
1613 return r.bounds.bottom - r.bounds.top - 20;
1618 * get the current mouse pointer world coordinates
1620 void Fl::get_mouse(int &x, int &y)
1622 fl_open_display();
1623 Point loc;
1624 GetMouse( &loc );
1625 LocalToGlobal( &loc );
1626 x = loc.h;
1627 y = loc.v;
1632 * convert Mac keystrokes to FLTK
1634 unsigned short mac2fltk(ulong macKey)
1636 unsigned short cc = macKeyLookUp[(macKey>>8)&0x7f];
1637 if (cc) return cc;
1638 return macKey&0xff;
1643 * Initialize the given port for redraw and call the windw's flush() to actually draw the content
1645 void Fl_X::flush()
1647 w->flush();
1648 if (fl_gc)
1649 CGContextFlush(fl_gc);
1650 SetOrigin( 0, 0 );
1655 * Handle all clipping and redraw for the given port
1656 * There are two different callers for this event:
1657 * 1: the OS can request a redraw and provides all clipping itself
1658 * 2: Fl::flush() wants all redraws now
1660 void handleUpdateEvent( WindowPtr xid )
1662 Fl_Window *window = fl_find( xid );
1663 if ( !window ) return;
1664 GrafPtr oldPort;
1665 GetPort( &oldPort );
1666 SetPort( GetWindowPort(xid) );
1667 Fl_X *i = Fl_X::i( window );
1668 i->wait_for_expose = 0;
1669 if ( window->damage() ) {
1670 if ( i->region ) {
1671 InvalWindowRgn( xid, i->region );
1674 if ( i->region ) { // no region, so the sytem will take the update region from the OS
1675 DisposeRgn( i->region );
1676 i->region = 0;
1678 for ( Fl_X *cx = i->xidChildren; cx; cx = cx->xidNext )
1680 cx->w->clear_damage(window->damage()|FL_DAMAGE_EXPOSE);
1681 cx->flush();
1682 cx->w->clear_damage();
1684 window->clear_damage(window->damage()|FL_DAMAGE_EXPOSE);
1685 i->flush();
1686 window->clear_damage();
1687 SetPort( oldPort );
1690 // Gets the border sizes and the titlebar size
1691 static void get_window_frame_sizes(int &bx, int &by, int &bt) {
1692 #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2
1693 static HIRect contentRect = { {50,50}, {100,100} }; // a rect to stand in for the content rect of a real window
1694 static HIThemeWindowDrawInfo metrics= {0,
1695 kThemeStateActive, kThemeDocumentWindow,
1696 kThemeWindowHasFullZoom + kThemeWindowHasCloseBox +
1697 kThemeWindowHasCollapseBox + kThemeWindowHasTitleText,
1698 0, 0};
1699 HIShapeRef shape1=0, shape2=0, shape3=0;
1700 HIRect rect1, rect2, rect3;
1701 OSStatus status;
1702 status = HIThemeGetWindowShape(&contentRect, &metrics, kWindowStructureRgn, &shape1);
1703 status |= HIThemeGetWindowShape(&contentRect, &metrics, kWindowContentRgn, &shape2);
1704 status |= HIThemeGetWindowShape(&contentRect, &metrics, kWindowTitleBarRgn, &shape3);
1706 if (!status)
1708 HIShapeGetBounds(shape1, &rect1);
1709 HIShapeGetBounds(shape2, &rect2);
1710 HIShapeGetBounds(shape3, &rect3);
1711 bt = rect3.size.height;
1712 bx = rect2.origin.x - rect1.origin.x;
1713 by = rect2.origin.y - rect1.origin.y - bt;
1714 // fprintf(stderr, "HIThemeGetWindowShape succeeded bx=%d by=%d bt=%d\n", bx, by, bt);
1716 else
1717 #endif // MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2
1719 // sets default dimensions
1720 bx = by = 6;
1721 bt = 22;
1722 // fprintf(stderr, "HIThemeGetWindowShape failed, bx=%d by=%d bt=%d\n", bx, by, bt);
1724 #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2
1725 CFRelease(shape1); // we must free HIThemeGetWindowShape() (copied) handles
1726 CFRelease(shape2);
1727 CFRelease(shape3);
1728 #endif // MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2
1732 * \todo this is a leftover from OS9 times. Please check how much applies to Carbon!
1734 int Fl_X::fake_X_wm(const Fl_Window* w,int &X,int &Y, int &bt,int &bx, int &by) {
1735 int W, H, xoff, yoff, dx, dy;
1736 int ret = bx = by = bt = 0;
1737 if (w->border() && !w->parent()) {
1738 if (w->maxw != w->minw || w->maxh != w->minh) {
1739 ret = 2;
1740 get_window_frame_sizes(bx, by, bt);
1742 bx = 6; // \todo Mac : GetSystemMetrics(SM_CXSIZEFRAME);
1743 by = 6; // \todo Mac : get Mac window frame size GetSystemMetrics(SM_CYSIZEFRAME);
1745 } else {
1746 ret = 1;
1747 get_window_frame_sizes(bx, by, bt);
1749 bx = 6; // \todo Mac : GetSystemMetrics(SM_CXFIXEDFRAME);
1750 by = 6; // \todo Mac : GetSystemMetrics(SM_CYFIXEDFRAME);
1754 //The coordinates of the whole window, including non-client area
1755 xoff = bx;
1756 yoff = by + bt;
1757 dx = 2*bx;
1758 dy = 2*by + bt;
1759 X = w->x()-xoff;
1760 Y = w->y()-yoff;
1761 W = w->w()+dx;
1762 H = w->h()+dy;
1764 //Proceed to positioning the window fully inside the screen, if possible
1766 // let's get a little elaborate here. Mac OS X puts a lot of stuff on the desk
1767 // that we want to avoid when positioning our window, namely the Dock and the
1768 // top menu bar (and even more stuff in 10.4 Tiger). So we will go through the
1769 // list of all available screens and find the one that this window is most
1770 // likely to go to, and then reposition it to fit withing the 'good' area.
1771 Rect r;
1772 // find the screen, that the center of this window will fall into
1773 int R = X+W, B = Y+H; // right and bottom
1774 int cx = (X+R)/2, cy = (Y+B)/2; // center of window;
1775 GDHandle gd = 0L;
1776 for (gd = GetDeviceList(); gd; gd = GetNextDevice(gd)) {
1777 GDPtr gp = *gd;
1778 if ( cx >= gp->gdRect.left && cx <= gp->gdRect.right
1779 && cy >= gp->gdRect.top && cy <= gp->gdRect.bottom)
1780 break;
1782 // if the center doesn't fall on a screen, try the top left
1783 if (!gd) {
1784 for (gd = GetDeviceList(); gd; gd = GetNextDevice(gd)) {
1785 GDPtr gp = *gd;
1786 if ( X >= gp->gdRect.left && X <= gp->gdRect.right
1787 && Y >= gp->gdRect.top && Y <= gp->gdRect.bottom)
1788 break;
1791 // if that doesn't fall on a screen, try the top right
1792 if (!gd) {
1793 for (gd = GetDeviceList(); gd; gd = GetNextDevice(gd)) {
1794 GDPtr gp = *gd;
1795 if ( R >= gp->gdRect.left && R <= gp->gdRect.right
1796 && Y >= gp->gdRect.top && Y <= gp->gdRect.bottom)
1797 break;
1800 // if that doesn't fall on a screen, try the bottom left
1801 if (!gd) {
1802 for (gd = GetDeviceList(); gd; gd = GetNextDevice(gd)) {
1803 GDPtr gp = *gd;
1804 if ( X >= gp->gdRect.left && X <= gp->gdRect.right
1805 && B >= gp->gdRect.top && B <= gp->gdRect.bottom)
1806 break;
1809 // last resort, try the bottom right
1810 if (!gd) {
1811 for (gd = GetDeviceList(); gd; gd = GetNextDevice(gd)) {
1812 GDPtr gp = *gd;
1813 if ( R >= gp->gdRect.left && R <= gp->gdRect.right
1814 && B >= gp->gdRect.top && B <= gp->gdRect.bottom)
1815 break;
1818 // if we still have not found a screen, we will use the main
1819 // screen, the one that has the application menu bar.
1820 if (!gd) gd = GetMainDevice();
1821 if (gd) {
1822 GetAvailableWindowPositioningBounds(gd, &r);
1823 if ( R > r.right ) X -= R - r.right;
1824 if ( B > r.bottom ) Y -= B - r.bottom;
1825 if ( X < r.left ) X = r.left;
1826 if ( Y < r.top ) Y = r.top;
1829 //Return the client area's top left corner in (X,Y)
1830 X+=xoff;
1831 Y+=yoff;
1833 return ret;
1837 * convert a Mac FSSpec structure into a Unix filename
1839 static int FSSpec2UnixPath( FSSpec *fs, char *dst )
1841 FSRef fsRef;
1842 FSpMakeFSRef( fs, &fsRef );
1843 FSRefMakePath( &fsRef, (UInt8*)dst, 1024 );
1844 return strlen(dst);
1846 static void convert_crlf(char * s, size_t len)
1848 // turn all \r characters into \n:
1849 for (size_t x = 0; x < len; x++) if (s[x] == '\r') s[x] = '\n';
1853 static DragReference currDragRef = 0;
1854 static char *currDragData = 0L;
1855 static int currDragSize = 0;
1856 static OSErr currDragErr = noErr;
1857 Fl_Window *fl_dnd_target_window = 0;
1858 #include <FL/fl_draw.H>
1861 * Fill the currDrag* variables with the current DnD ASCII text.
1863 static OSErr fillCurrentDragData(DragReference dragRef)
1865 OSErr ret = noErr;
1866 char *dst = 0L;
1868 // shortcut through this whole procedure if this is still the same drag event
1869 if (dragRef==currDragRef)
1870 return currDragErr;
1872 // clear currDrag* for a new drag event
1873 currDragRef = dragRef;
1874 if (currDragData) free(currDragData);
1875 currDragData = 0;
1876 currDragSize = 0;
1878 // fill currDRag* with ASCII data, if available
1879 UInt16 i, nItem;
1880 ItemReference itemRef;
1881 FlavorFlags flags;
1882 Size itemSize, size = 0;
1883 CountDragItems( dragRef, &nItem );
1885 for ( i = 1; i <= nItem; i++ )
1887 GetDragItemReferenceNumber( dragRef, i, &itemRef );
1888 ret = GetFlavorFlags( dragRef, itemRef, 'utf8', &flags );
1889 if ( ret == noErr )
1891 GetFlavorDataSize( dragRef, itemRef, 'utf8', &itemSize );
1892 size += itemSize;
1893 continue;
1895 ret = GetFlavorFlags( dragRef, itemRef, 'utxt', &flags );
1896 if ( ret == noErr )
1898 GetFlavorDataSize( dragRef, itemRef, 'utxt', &itemSize );
1899 size += itemSize;
1900 continue;
1902 ret = GetFlavorFlags( dragRef, itemRef, 'TEXT', &flags );
1903 if ( ret == noErr )
1905 GetFlavorDataSize( dragRef, itemRef, 'TEXT', &itemSize );
1906 size += itemSize;
1907 continue;
1909 ret = GetFlavorFlags( dragRef, itemRef, 'hfs ', &flags );
1910 if ( ret == noErr )
1912 size += 1024; //++ ouch! We should create the full pathname and figure out its length
1913 continue;
1917 if ( !size )
1919 currDragErr = userCanceledErr;
1920 return currDragErr;
1923 currDragSize = size + nItem - 1;
1924 currDragData = dst = (char*)malloc( size+nItem );;
1926 for ( i = 1; i <= nItem; i++ )
1928 GetDragItemReferenceNumber( dragRef, i, &itemRef );
1929 ret = GetFlavorFlags( dragRef, itemRef, 'utf8', &flags );
1930 if ( ret == noErr )
1932 GetFlavorDataSize( dragRef, itemRef, 'utf8', &itemSize );
1933 GetFlavorData( dragRef, itemRef, 'utf8', dst, &itemSize, 0L );
1934 dst += itemSize;
1935 *dst++ = '\n'; // add our element separator
1936 continue;
1938 GetDragItemReferenceNumber( dragRef, i, &itemRef );
1939 ret = GetFlavorFlags( dragRef, itemRef, 'utxt', &flags );
1940 if ( ret == noErr )
1942 GetFlavorDataSize( dragRef, itemRef, 'utxt', &itemSize );
1943 GetFlavorData( dragRef, itemRef, 'utxt', dst, &itemSize, 0L );
1944 dst += itemSize;
1945 *dst++ = '\n'; // add our element separator
1946 continue;
1948 ret = GetFlavorFlags( dragRef, itemRef, 'TEXT', &flags );
1949 if ( ret == noErr )
1951 GetFlavorDataSize( dragRef, itemRef, 'TEXT', &itemSize );
1952 GetFlavorData( dragRef, itemRef, 'TEXT', dst, &itemSize, 0L );
1953 dst += itemSize;
1954 *dst++ = '\n'; // add our element separator
1955 continue;
1957 ret = GetFlavorFlags( dragRef, itemRef, 'hfs ', &flags );
1958 if ( ret == noErr )
1960 HFSFlavor hfs; itemSize = sizeof( hfs );
1961 GetFlavorData( dragRef, itemRef, 'hfs ', &hfs, &itemSize, 0L );
1962 itemSize = FSSpec2UnixPath( &hfs.fileSpec, dst ); // return the path name in UTF8
1963 dst += itemSize;
1964 if ( itemSize>1 && ( hfs.fileType=='fold' || hfs.fileType=='disk' ) )
1965 *dst++ = '/';
1966 *dst++ = '\n'; // add our element separator
1967 continue;
1971 dst[-1] = 0;
1972 currDragSize = dst - currDragData - 1;
1973 currDragErr = ret;
1974 return ret;
1978 * Drag'n'drop tracking handler
1980 static pascal OSErr dndTrackingHandler( DragTrackingMessage msg, WindowPtr w, void *userData, DragReference dragRef )
1982 Fl_Window *target = (Fl_Window*)userData;
1983 Fl::first_window(target);
1984 Point mp;
1985 static int px, py;
1987 fillCurrentDragData(dragRef);
1988 Fl::e_length = currDragSize;
1989 Fl::e_text = currDragData;
1991 switch ( msg )
1993 case kDragTrackingEnterWindow:
1994 // check if 'TEXT' is available
1995 GetDragMouse( dragRef, &mp, 0 );
1996 Fl::e_x_root = px = mp.h;
1997 Fl::e_y_root = py = mp.v;
1998 Fl::e_x = px - target->x();
1999 Fl::e_y = py - target->y();
2000 fl_dnd_target_window = target;
2001 if ( Fl::handle( FL_DND_ENTER, target ) )
2002 fl_cursor( FL_CURSOR_HAND ); //ShowDragHilite( ); // modify the mouse cursor?!
2003 else
2004 fl_cursor( FL_CURSOR_DEFAULT ); //HideDragHilite( dragRef );
2005 breakMacEventLoop();
2006 return noErr;
2007 case kDragTrackingInWindow:
2008 GetDragMouse( dragRef, &mp, 0 );
2009 if ( mp.h==px && mp.v==py )
2010 break; //+ return previous condition for dnd hiliting
2011 Fl::e_x_root = px = mp.h;
2012 Fl::e_y_root = py = mp.v;
2013 Fl::e_x = px - target->x();
2014 Fl::e_y = py - target->y();
2015 fl_dnd_target_window = target;
2016 if ( Fl::handle( FL_DND_DRAG, target ) )
2017 fl_cursor( FL_CURSOR_HAND ); //ShowDragHilite( ); // modify the mouse cursor?!
2018 else
2019 fl_cursor( FL_CURSOR_DEFAULT ); //HideDragHilite( dragRef );
2020 breakMacEventLoop();
2021 return noErr;
2022 break;
2023 case kDragTrackingLeaveWindow:
2024 // HideDragHilite()
2025 fl_cursor( FL_CURSOR_DEFAULT ); //HideDragHilite( dragRef );
2026 if ( fl_dnd_target_window )
2028 Fl::handle( FL_DND_LEAVE, fl_dnd_target_window );
2029 fl_dnd_target_window = 0;
2031 breakMacEventLoop();
2032 return noErr;
2034 return noErr;
2039 * Drag'n'drop receive handler
2041 static pascal OSErr dndReceiveHandler( WindowPtr w, void *userData, DragReference dragRef )
2043 Point mp;
2044 OSErr ret;
2046 Fl_Window *target = fl_dnd_target_window = (Fl_Window*)userData;
2047 Fl::first_window(target);
2048 GetDragMouse( dragRef, &mp, 0 );
2049 Fl::e_x_root = mp.h;
2050 Fl::e_y_root = mp.v;
2051 Fl::e_x = Fl::e_x_root - target->x();
2052 Fl::e_y = Fl::e_y_root - target->y();
2053 if ( !Fl::handle( FL_DND_RELEASE, target ) )
2054 return userCanceledErr;
2056 ret = fillCurrentDragData(dragRef);
2057 if (ret==userCanceledErr)
2058 return userCanceledErr;
2060 Fl::e_length = currDragSize;
2061 Fl::e_text = currDragData;
2062 // printf("Sending following text to widget %p:\n%s\n", Fl::belowmouse(), Fl::e_text);
2063 int old_event = Fl::e_number;
2064 Fl::belowmouse()->handle(Fl::e_number = FL_PASTE);
2065 Fl::e_number = old_event;
2067 if (currDragData) {
2068 free(currDragData);
2070 currDragData = 0L;
2071 currDragRef = 0;
2072 Fl::e_text = 0L;
2073 Fl::e_length = 0;
2074 fl_dnd_target_window = 0L;
2076 breakMacEventLoop();
2077 return noErr;
2079 // fc:
2080 static void q_set_window_title(Window xid, const char * name ) {
2081 #if 1
2082 CFStringRef utf8_title = CFStringCreateWithCString(NULL, (name ? name : ""), kCFStringEncodingUTF8);
2083 SetWindowTitleWithCFString(xid, utf8_title);
2084 CFRelease(utf8_title);
2085 #else // old non-utf8 code to remove after new utf8 code approval :
2086 Str255 pTitle;
2087 if (name) {
2088 if (strlen(name) > 255) pTitle[0] = 255;
2089 else pTitle[0] = strlen(name);
2090 memcpy(pTitle+1, name, pTitle[0]);
2092 else
2093 pTitle[0] = 0;
2094 SetWTitle(xid, pTitle);
2095 #endif
2099 * go ahead, create that (sub)window
2100 * \todo we should make menu windows slightly transparent for the new Mac look
2102 void Fl_X::make(Fl_Window* w)
2104 static int xyPos = 100;
2105 if ( w->parent() ) // create a subwindow
2107 Fl_Group::current(0);
2108 Rect wRect;
2109 wRect.top = w->y();
2110 wRect.left = w->x();
2111 wRect.bottom = w->y() + w->h(); if (wRect.bottom<=wRect.top) wRect.bottom = wRect.top+1;
2112 wRect.right = w->x() + w->w(); if (wRect.right<=wRect.left) wRect.right = wRect.left+1;
2113 // our subwindow needs this structure to know about its clipping.
2114 Fl_X* x = new Fl_X;
2115 x->other_xid = 0;
2116 x->region = 0;
2117 x->subRegion = 0;
2118 x->cursor = fl_default_cursor;
2119 x->gc = 0; // stay 0 for Quickdraw; fill with CGContext for Quartz
2120 Fl_Window *win = w->window();
2121 Fl_X *xo = Fl_X::i(win);
2122 if (xo) {
2123 x->xidNext = xo->xidChildren;
2124 x->xidChildren = 0L;
2125 xo->xidChildren = x;
2126 x->xid = fl_xid(win);
2127 x->w = w; w->i = x;
2128 x->wait_for_expose = 0;
2129 x->next = Fl_X::first; // must be in the list for ::flush()
2130 Fl_X::first = x;
2131 int old_event = Fl::e_number;
2132 w->handle(Fl::e_number = FL_SHOW);
2133 Fl::e_number = old_event;
2134 w->redraw(); // force draw to happen
2136 fl_show_iconic = 0;
2138 else // create a desktop window
2140 Fl_Group::current(0);
2141 fl_open_display();
2142 int winclass = kDocumentWindowClass;
2143 int winattr = kWindowStandardHandlerAttribute | kWindowCloseBoxAttribute | kWindowCollapseBoxAttribute;
2144 int xp = w->x();
2145 int yp = w->y();
2146 int wp = w->w();
2147 int hp = w->h();
2148 if (w->size_range_set) {
2149 if ( w->minh != w->maxh || w->minw != w->maxw)
2150 winattr |= kWindowFullZoomAttribute | kWindowResizableAttribute | kWindowLiveResizeAttribute;
2151 } else {
2152 if (w->resizable()) {
2153 Fl_Widget *o = w->resizable();
2154 int minw = o->w(); if (minw > 100) minw = 100;
2155 int minh = o->h(); if (minh > 100) minh = 100;
2156 w->size_range(w->w() - o->w() + minw, w->h() - o->h() + minh, 0, 0);
2157 winattr |= kWindowFullZoomAttribute | kWindowResizableAttribute | kWindowLiveResizeAttribute;
2158 } else {
2159 w->size_range(w->w(), w->h(), w->w(), w->h());
2162 int xwm = xp, ywm = yp, bt, bx, by;
2164 if (!fake_X_wm(w, xwm, ywm, bt, bx, by)) {
2165 // menu windows and tooltips
2166 if (w->modal()||w->override()) {
2167 winclass = kHelpWindowClass;
2168 winattr = 0;
2169 } else {
2170 winattr = 512; // kWindowNoTitleBarAttribute;
2172 } else if (w->modal()) {
2173 winclass = kMovableModalWindowClass;
2176 if (by+bt) {
2177 wp += 2*bx;
2178 hp += 2*by+bt;
2180 if (!(w->flags() & Fl_Widget::FORCE_POSITION)) {
2181 // use the Carbon functions below for default window positioning
2182 w->x(xyPos+Fl::x());
2183 w->y(xyPos+Fl::y());
2184 xyPos += 25;
2185 if (xyPos>200) xyPos = 100;
2186 } else {
2187 if (!Fl::grab()) {
2188 xp = xwm; yp = ywm;
2189 w->x(xp);w->y(yp);
2191 xp -= bx;
2192 yp -= by+bt;
2195 if (w->non_modal() && Fl_X::first && !fl_disable_transient_for) {
2196 // find some other window to be "transient for":
2197 Fl_Window* w = Fl_X::first->w;
2198 while (w->parent()) w = w->window(); // todo: this code does not make any sense! (w!=w??)
2201 Rect wRect;
2202 wRect.top = w->y();
2203 wRect.left = w->x();
2204 wRect.bottom = w->y() + w->h(); if (wRect.bottom<=wRect.top) wRect.bottom = wRect.top+1;
2205 wRect.right = w->x() + w->w(); if (wRect.right<=wRect.left) wRect.right = wRect.left+1;
2207 const char *name = w->label();
2209 Fl_X* x = new Fl_X;
2210 x->other_xid = 0; // room for doublebuffering image map. On OS X this is only used by overlay windows
2211 x->region = 0;
2212 x->subRegion = 0;
2213 x->cursor = fl_default_cursor;
2214 x->xidChildren = 0;
2215 x->xidNext = 0;
2216 x->gc = 0;
2218 winattr &= GetAvailableWindowAttributes( winclass ); // make sure that the window will open
2219 CreateNewWindow( winclass, winattr, &wRect, &(x->xid) );
2220 q_set_window_title(x->xid, name);
2221 MoveWindow(x->xid, wRect.left, wRect.top, 1); // avoid Carbon Bug on old OS
2222 if (w->non_modal() && !w->modal()) {
2223 // Major kludge: this is to have the regular look, but stay above the document windows
2224 SetWindowClass(x->xid, kFloatingWindowClass);
2225 SetWindowActivationScope(x->xid, kWindowActivationScopeAll);
2227 if (!(w->flags() & Fl_Widget::FORCE_POSITION))
2229 WindowRef pw = Fl_X::first ? Fl_X::first->xid : 0 ;
2230 if (w->modal()) {
2231 RepositionWindow(x->xid, pw, kWindowAlertPositionOnParentWindowScreen);
2232 } else if (w->non_modal()) {
2233 RepositionWindow(x->xid, pw, kWindowCenterOnParentWindowScreen);
2234 } else {
2235 RepositionWindow(x->xid, pw, kWindowCascadeOnParentWindowScreen);
2238 x->w = w; w->i = x;
2239 x->wait_for_expose = 1;
2240 x->next = Fl_X::first;
2241 Fl_X::first = x;
2242 { // Install Carbon Event handlers
2243 OSStatus ret;
2244 EventHandlerUPP mousewheelHandler = NewEventHandlerUPP( carbonMousewheelHandler ); // will not be disposed by Carbon...
2245 static EventTypeSpec mousewheelEvents[] = {
2246 { kEventClassMouse, kEventMouseWheelMoved } };
2247 ret = InstallWindowEventHandler( x->xid, mousewheelHandler,
2248 (int)(sizeof(mousewheelEvents)/sizeof(mousewheelEvents[0])),
2249 mousewheelEvents, w, 0L );
2250 EventHandlerUPP mouseHandler = NewEventHandlerUPP( carbonMouseHandler ); // will not be disposed by Carbon...
2251 static EventTypeSpec mouseEvents[] = {
2252 { kEventClassMouse, kEventMouseDown },
2253 { kEventClassMouse, kEventMouseUp },
2254 { kEventClassMouse, kEventMouseMoved },
2255 { kEventClassMouse, kEventMouseDragged } };
2256 ret = InstallWindowEventHandler( x->xid, mouseHandler, 4, mouseEvents, w, 0L );
2258 EventHandlerUPP keyboardHandler = NewEventHandlerUPP( carbonKeyboardHandler ); // will not be disposed by Carbon...
2259 static EventTypeSpec keyboardEvents[] = {
2260 { kEventClassKeyboard, kEventRawKeyDown },
2261 { kEventClassKeyboard, kEventRawKeyRepeat },
2262 { kEventClassKeyboard, kEventRawKeyUp },
2263 { kEventClassKeyboard, kEventRawKeyModifiersChanged } };
2264 ret = InstallWindowEventHandler( x->xid, keyboardHandler, 4, keyboardEvents, w, 0L );
2266 EventHandlerUPP textHandler = NewEventHandlerUPP( carbonTextHandler ); // will not be disposed by Carbon...
2267 static EventTypeSpec textEvents[] = {
2268 { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } };
2269 ret = InstallWindowEventHandler( x->xid, textHandler, 1, textEvents, w, 0L );
2271 EventHandlerUPP windowHandler = NewEventHandlerUPP( carbonWindowHandler ); // will not be disposed by Carbon...
2272 static EventTypeSpec windowEvents[] = {
2273 { kEventClassWindow, kEventWindowDrawContent },
2274 { kEventClassWindow, kEventWindowShown },
2275 { kEventClassWindow, kEventWindowHidden },
2276 { kEventClassWindow, kEventWindowActivated },
2277 { kEventClassWindow, kEventWindowDeactivated },
2278 { kEventClassWindow, kEventWindowClose },
2279 { kEventClassWindow, kEventWindowCollapsed },
2280 { kEventClassWindow, kEventWindowExpanded },
2281 { kEventClassWindow, kEventWindowBoundsChanging },
2282 { kEventClassWindow, kEventWindowBoundsChanged } };
2283 ret = InstallWindowEventHandler( x->xid, windowHandler, 10, windowEvents, w, 0L );
2284 ret = InstallTrackingHandler( dndTrackingHandler, x->xid, w );
2285 ret = InstallReceiveHandler( dndReceiveHandler, x->xid, w );
2288 if ( ! Fl_X::first->next ) // if this is the first window, we need to bring the application to the front
2290 ProcessSerialNumber psn;
2291 OSErr err = GetCurrentProcess( &psn );
2292 if ( err==noErr ) SetFrontProcess( &psn );
2295 if (w->size_range_set) w->size_range_();
2297 if (winclass != kHelpWindowClass) {
2298 Fl_Tooltip::enter(0);
2300 if (w->size_range_set) w->size_range_();
2301 ShowWindow(x->xid);
2302 if (fl_show_iconic) {
2303 fl_show_iconic = 0;
2304 CollapseWindow( x->xid, true ); // \todo Mac ; untested
2305 } else {
2306 w->set_visible();
2309 Rect rect;
2310 GetWindowBounds(x->xid, kWindowContentRgn, &rect);
2311 w->x(rect.left); w->y(rect.top);
2312 w->w(rect.right-rect.left); w->h(rect.bottom-rect.top);
2314 int old_event = Fl::e_number;
2315 w->handle(Fl::e_number = FL_SHOW);
2316 Fl::e_number = old_event;
2317 w->redraw(); // force draw to happen
2319 if (w->modal()) { Fl::modal_ = w; fl_fix_focus(); }
2325 * Tell the OS what window sizes we want to allow
2327 void Fl_Window::size_range_() {
2328 size_range_set = 1;
2329 HISize minSize = { minw, minh };
2330 HISize maxSize = { maxw?maxw:32000, maxh?maxh:32000 };
2331 if (i && i->xid)
2332 SetWindowResizeLimits(i->xid, &minSize, &maxSize);
2337 * returns pointer to the filename, or null if name ends with ':'
2339 const char *fl_filename_name( const char *name )
2341 const char *p, *q;
2342 if (!name) return (0);
2343 for ( p = q = name ; *p ; )
2345 if ( ( p[0] == ':' ) && ( p[1] == ':' ) )
2347 q = p+2;
2348 p++;
2350 else if (p[0] == '/')
2351 q = p + 1;
2352 p++;
2354 return q;
2359 * set the window title bar
2360 * \todo make the titlebar icon work!
2362 void Fl_Window::label(const char *name,const char */*iname*/) {
2363 Fl_Widget::label(name);
2365 if (shown() || i) {
2366 q_set_window_title(fl_xid(this), name);
2372 * make a window visible
2374 void Fl_Window::show() {
2375 image(Fl::scheme_bg_);
2376 if (Fl::scheme_bg_) {
2377 labeltype(FL_NORMAL_LABEL);
2378 align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE | FL_ALIGN_CLIP);
2379 } else {
2380 labeltype(FL_NO_LABEL);
2382 Fl_Tooltip::exit(this);
2383 if (!shown() || !i) {
2384 Fl_X::make(this);
2385 } else {
2386 if ( !parent() )
2388 if ( IsWindowCollapsed( i->xid ) ) CollapseWindow( i->xid, false );
2389 if (!fl_capture) {
2390 BringToFront(i->xid);
2391 SelectWindow(i->xid);
2399 * resize a window
2401 void Fl_Window::resize(int X,int Y,int W,int H) {
2402 if (W<=0) W = 1; // OS X does not like zero width windows
2403 if (H<=0) H = 1;
2404 int is_a_resize = (W != w() || H != h());
2405 // printf("Fl_Winodw::resize(X=%d, Y=%d, W=%d, H=%d), is_a_resize=%d, resize_from_system=%p, this=%p\n",
2406 // X, Y, W, H, is_a_resize, resize_from_system, this);
2407 if (X != x() || Y != y()) set_flag(FORCE_POSITION);
2408 else if (!is_a_resize) return;
2409 if ( (resize_from_system!=this) && (!parent()) && shown()) {
2410 if (is_a_resize) {
2411 if (resizable()) {
2412 if (W<minw) minw = W; // user request for resize takes priority
2413 if (W>maxw) maxw = W; // over a previously set size_range
2414 if (H<minh) minh = H;
2415 if (H>maxh) maxh = H;
2416 size_range(minw, minh, maxw, maxh);
2417 } else {
2418 size_range(W, H, W, H);
2420 Rect dim; dim.left=X; dim.top=Y; dim.right=X+W; dim.bottom=Y+H;
2421 SetWindowBounds(i->xid, kWindowContentRgn, &dim);
2422 Rect all; all.top=-32000; all.bottom=32000; all.left=-32000; all.right=32000;
2423 InvalWindowRect( i->xid, &all );
2424 } else {
2425 MoveWindow(i->xid, X, Y, 0);
2428 resize_from_system = 0;
2429 if (is_a_resize) {
2430 Fl_Group::resize(X,Y,W,H);
2431 if (shown()) {
2432 redraw();
2434 } else {
2435 x(X); y(Y);
2441 * make all drawing go into this window (called by subclass flush() impl.)
2443 void Fl_Window::make_current()
2445 OSStatus err;
2446 Fl_X::q_release_context();
2447 if ( !fl_window_region )
2448 fl_window_region = NewRgn();
2449 fl_window = i->xid;
2450 current_ = this;
2452 SetPort( GetWindowPort(i->xid) ); // \todo check for the handling of doublebuffered windows
2454 int xp = 0, yp = 0;
2455 Fl_Window *win = this;
2456 while ( win )
2458 if ( !win->window() )
2459 break;
2460 xp += win->x();
2461 yp += win->y();
2462 win = (Fl_Window*)win->window();
2464 SetOrigin( -xp, -yp );
2466 SetRectRgn( fl_window_region, 0, 0, w(), h() );
2468 // \todo for performance reasons: we don't have to create this unless the child windows moved
2469 for ( Fl_X *cx = i->xidChildren; cx; cx = cx->xidNext )
2471 Fl_Window *cw = cx->w;
2472 if (!cw->visible_r()) continue;
2473 Fl_Region r = NewRgn();
2474 SetRectRgn( r, cw->x() - xp, cw->y() - yp,
2475 cw->x() + cw->w() - xp, cw->y() + cw->h() - yp );
2476 DiffRgn( fl_window_region, r, fl_window_region );
2477 DisposeRgn( r );
2480 err = QDBeginCGContext(GetWindowPort(i->xid), &i->gc);
2481 if (err!=noErr)
2482 fprintf(stderr, "Error %d in QDBeginCGContext\n", (int)err);
2483 fl_gc = i->gc;
2484 CGContextSaveGState(fl_gc);
2485 Fl_X::q_fill_context();
2486 #if defined(USE_CAIRO)
2487 if (Fl::cairo_autolink_context()) Fl::cairo_make_current(this); // capture gc changes automatically to update the cairo context adequately
2488 #endif
2490 fl_clip_region( 0 );
2491 SetPortClipRegion( GetWindowPort(i->xid), fl_window_region );
2493 #if defined(USE_CAIRO)
2494 // update the cairo_t context
2495 if (Fl::cairo_autolink_context()) Fl::cairo_make_current(this);
2496 #endif
2499 // helper function to manage the current CGContext fl_gc
2500 extern Fl_Color fl_color_;
2501 extern class Fl_Font_Descriptor *fl_fontsize;
2502 extern void fl_font(class Fl_Font_Descriptor*);
2503 extern void fl_quartz_restore_line_style_();
2505 // FLTK has only one global graphics state. This function copies the FLTK state into the
2506 // current Quartz context
2507 void Fl_X::q_fill_context() {
2508 if (!fl_gc) return;
2509 int hgt = 0;
2510 if (fl_window) {
2511 Rect portRect;
2512 GetPortBounds(GetWindowPort( fl_window ), &portRect);
2513 hgt = portRect.bottom-portRect.top;
2514 } else {
2515 hgt = CGBitmapContextGetHeight(fl_gc);
2517 CGContextTranslateCTM(fl_gc, 0.5, hgt-0.5f);
2518 CGContextScaleCTM(fl_gc, 1.0f, -1.0f);
2519 fl_font(fl_fontsize);
2520 fl_color(fl_color_);
2521 fl_quartz_restore_line_style_();
2524 // The only way to reset clipping to its original state is to pop the current graphics
2525 // state and restore the global state.
2526 void Fl_X::q_clear_clipping() {
2527 if (!fl_gc) return;
2528 CGContextRestoreGState(fl_gc);
2529 CGContextSaveGState(fl_gc);
2532 // Give the Quartz context back to the system
2533 void Fl_X::q_release_context(Fl_X *x) {
2534 if (x && x->gc!=fl_gc) return;
2535 if (!fl_gc) return;
2536 CGContextRestoreGState(fl_gc);
2537 if (fl_window) {
2538 OSStatus err = QDEndCGContext(GetWindowPort(fl_window), &fl_gc);
2539 if (err!=noErr)
2540 fprintf(stderr, "Error %d in QDEndCGContext\n", (int)err);
2542 fl_gc = 0;
2543 #if defined(USE_CAIRO)
2544 if (Fl::cairo_autolink_context()) Fl::cairo_make_current((Fl_Window*) 0); // capture gc changes automatically to update the cairo context adequately
2545 #endif
2548 void Fl_X::q_begin_image(CGRect &rect, int cx, int cy, int w, int h) {
2549 CGContextSaveGState(fl_gc);
2550 CGAffineTransform mx = CGContextGetCTM(fl_gc);
2551 CGRect r2 = rect;
2552 r2.origin.x -= 0.5f;
2553 r2.origin.y -= 0.5f;
2554 CGContextClipToRect(fl_gc, r2);
2555 mx.d = -1.0; mx.tx = -mx.tx;
2556 CGContextConcatCTM(fl_gc, mx);
2557 rect.origin.x = -(mx.tx+0.5f) + rect.origin.x - cx;
2558 rect.origin.y = (mx.ty+0.5f) - rect.origin.y - h + cy;
2559 rect.size.width = w;
2560 rect.size.height = h;
2563 void Fl_X::q_end_image() {
2564 CGContextRestoreGState(fl_gc);
2567 ////////////////////////////////////////////////////////////////
2568 // Copy & Paste fltk implementation.
2569 ////////////////////////////////////////////////////////////////
2571 // fltk 1.3 clipboard support constant definitions:
2572 const CFStringRef flavorNames[] = {
2573 CFSTR("public.utf16-plain-text"),
2574 CFSTR("public.utf8-plain-text"),
2575 CFSTR("com.apple.traditional-mac-plain-text") };
2576 const CFStringEncoding encodings[] = {
2577 kCFStringEncodingUTF16,
2578 kCFStringEncodingUTF8,
2579 kCFStringEncodingMacRoman};
2580 const size_t handledFlavorsCount = sizeof(encodings)/sizeof(CFStringEncoding);
2582 // clipboard variables definitions :
2583 Fl_Widget *fl_selection_requestor = 0;
2584 char *fl_selection_buffer[2];
2585 int fl_selection_length[2];
2586 static int fl_selection_buffer_length[2];
2588 #ifdef USE_PASTEBOARD
2589 static PasteboardRef myPasteboard = 0;
2590 static void allocatePasteboard() {
2591 if (!myPasteboard)
2592 PasteboardCreate(kPasteboardClipboard, &myPasteboard);
2594 #else
2595 #endif
2597 #ifndef USE_PASTEBOARD
2598 static ScrapRef myScrap = 0;
2599 #endif
2602 * create a selection
2603 * owner: widget that created the selection
2604 * stuff: pointer to selected data
2605 * size of selected data
2607 void Fl::copy(const char *stuff, int len, int clipboard) {
2608 if (!stuff || len<0) return;
2609 if (len+1 > fl_selection_buffer_length[clipboard]) {
2610 delete[] fl_selection_buffer[clipboard];
2611 fl_selection_buffer[clipboard] = new char[len+100];
2612 fl_selection_buffer_length[clipboard] = len+100;
2614 memcpy(fl_selection_buffer[clipboard], stuff, len);
2615 fl_selection_buffer[clipboard][len] = 0; // needed for direct paste
2616 fl_selection_length[clipboard] = len;
2617 if (clipboard) {
2618 #ifdef USE_PASTEBOARD
2619 // FIXME no error checking done yet!
2620 allocatePasteboard();
2621 OSStatus err = PasteboardClear(myPasteboard);
2622 if (err!=noErr) return; // clear did not work, maybe not owner of clipboard.
2623 PasteboardSynchronize(myPasteboard);
2624 CFDataRef text = CFDataCreate(kCFAllocatorDefault, (UInt8*)fl_selection_buffer[1], len);
2625 if (text==NULL) return; // there was a pb creating the object, abort.
2626 err=PasteboardPutItemFlavor(myPasteboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), text, 0);
2627 CFRelease(text);
2628 #else
2629 OSStatus err = ClearCurrentScrap(); // whatever happens we should clear the current scrap.
2630 if(err!=noErr) {myScrap=0; return;} // don't get current scrap if a prev err occured.
2631 err = GetCurrentScrap( &myScrap );
2632 if ( err != noErr ) {
2633 myScrap = 0;
2634 return;
2636 // Previous version changed \n to \r before sending the text, but I would
2637 // prefer to leave the local buffer alone, so a copied buffer may be
2638 // needed. Check to see if this is necessary on OS/X.
2639 PutScrapFlavor( myScrap, kScrapFlavorTypeText, 0,
2640 len, fl_selection_buffer[1] );
2641 #endif
2645 // Call this when a "paste" operation happens:
2646 void Fl::paste(Fl_Widget &receiver, int clipboard) {
2647 if (clipboard) {
2648 // see if we own the selection, if not go get it:
2649 fl_selection_length[1] = 0;
2650 #ifdef USE_PASTEBOARD
2651 OSStatus err = noErr;
2652 Boolean found = false;
2653 CFDataRef flavorData = NULL;
2654 CFStringEncoding encoding = 0;
2656 allocatePasteboard();
2657 PasteboardSynchronize(myPasteboard);
2658 ItemCount nFlavor = 0, i, j;
2659 err = PasteboardGetItemCount(myPasteboard, &nFlavor);
2660 if (err==noErr) {
2661 for (i=1; i<=nFlavor; i++) {
2662 PasteboardItemID itemID = 0;
2663 CFArrayRef flavorTypeArray = NULL;
2664 found = false;
2665 err = PasteboardGetItemIdentifier(myPasteboard, i, &itemID);
2666 if (err!=noErr) continue;
2667 err = PasteboardCopyItemFlavors(myPasteboard, itemID, &flavorTypeArray);
2668 if (err!=noErr) {
2669 if (flavorTypeArray) {CFRelease(flavorTypeArray); flavorTypeArray = NULL;}
2670 continue;
2672 CFIndex flavorCount = CFArrayGetCount(flavorTypeArray);
2673 for (j = 0; j < handledFlavorsCount; j++) {
2674 for (CFIndex flavorIndex=0; flavorIndex<flavorCount; flavorIndex++) {
2675 CFStringRef flavorType = (CFStringRef)CFArrayGetValueAtIndex(flavorTypeArray, flavorIndex);
2676 if (UTTypeConformsTo(flavorType, flavorNames[j])) {
2677 err = PasteboardCopyItemFlavorData( myPasteboard, itemID, flavorNames[j], &flavorData );
2678 if(err != noErr) continue;
2679 encoding = encodings[j];
2680 found = true;
2681 break;
2684 if(found) break;
2686 if (flavorTypeArray) {CFRelease(flavorTypeArray); flavorTypeArray = NULL;}
2687 if (found) break;
2689 if(found) {
2690 CFIndex len = CFDataGetLength(flavorData);
2691 CFStringRef mycfs = CFStringCreateWithBytes(NULL, CFDataGetBytePtr(flavorData), len, encoding, false);
2692 CFRelease(flavorData);
2693 len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(mycfs), kCFStringEncodingUTF8) + 1;
2694 if ( len >= fl_selection_buffer_length[1] ) {
2695 fl_selection_buffer_length[1] = len;
2696 delete[] fl_selection_buffer[1];
2697 fl_selection_buffer[1] = new char[len];
2699 CFStringGetCString(mycfs, fl_selection_buffer[1], len, kCFStringEncodingUTF8);
2700 CFRelease(mycfs);
2701 len = strlen(fl_selection_buffer[1]);
2702 fl_selection_length[1] = len;
2703 convert_crlf(fl_selection_buffer[1],len); // turn all \r characters into \n:
2706 #else
2707 ScrapRef scrap = 0;
2708 if (GetCurrentScrap(&scrap) == noErr && scrap != myScrap &&
2709 GetScrapFlavorSize(scrap, kScrapFlavorTypeText, &len) == noErr) {
2710 if ( len >= fl_selection_buffer_length[1] ) {
2711 fl_selection_buffer_length[1] = len + 32;
2712 delete[] fl_selection_buffer[1];
2713 fl_selection_buffer[1] = new char[len + 32];
2715 fl_selection_length[1] = len; len++;
2716 GetScrapFlavorData( scrap, kScrapFlavorTypeText, &len,
2717 fl_selection_buffer[1] );
2718 fl_selection_buffer[1][fl_selection_length[1]] = 0;
2719 convert_crlf(fl_selection_buffer[1],len);
2721 #endif
2723 Fl::e_text = fl_selection_buffer[clipboard];
2724 Fl::e_length = fl_selection_length[clipboard];
2725 if (!Fl::e_text) Fl::e_text = (char *)"";
2726 receiver.handle(FL_PASTE);
2729 void Fl::add_timeout(double time, Fl_Timeout_Handler cb, void* data)
2731 // check, if this timer slot exists already
2732 for (int i = 0; i < mac_timer_used; ++i) {
2733 MacTimeout& t = mac_timers[i];
2734 // if so, simply change the fire interval
2735 if (t.callback == cb && t.data == data) {
2736 SetEventLoopTimerNextFireTime(t.timer, (EventTimerInterval)time);
2737 t.pending = 1;
2738 return;
2741 // no existing timer to use. Create a new one:
2742 int timer_id = -1;
2743 // find an empty slot in the timer array
2744 for (int i = 0; i < mac_timer_used; ++i) {
2745 if ( !mac_timers[i].timer ) {
2746 timer_id = i;
2747 break;
2750 // if there was no empty slot, append a new timer
2751 if (timer_id == -1) {
2752 // make space if needed
2753 if (mac_timer_used == mac_timer_alloc) {
2754 realloc_timers();
2756 timer_id = mac_timer_used++;
2758 // now install a brand new timer
2759 MacTimeout& t = mac_timers[timer_id];
2760 EventTimerInterval fireDelay = (EventTimerInterval)time;
2761 EventLoopTimerUPP timerUPP = NewEventLoopTimerUPP(do_timer);
2762 EventLoopTimerRef timerRef = 0;
2763 OSStatus err = InstallEventLoopTimer(GetMainEventLoop(), fireDelay, 0, timerUPP, data, &timerRef);
2764 if (err == noErr) {
2765 t.callback = cb;
2766 t.data = data;
2767 t.timer = timerRef;
2768 t.upp = timerUPP;
2769 t.pending = 1;
2770 } else {
2771 if (timerRef)
2772 RemoveEventLoopTimer(timerRef);
2773 if (timerUPP)
2774 DisposeEventLoopTimerUPP(timerUPP);
2778 void Fl::repeat_timeout(double time, Fl_Timeout_Handler cb, void* data)
2780 // currently, repeat_timeout does not subtract the trigger time of the previous timer event as it should.
2781 add_timeout(time, cb, data);
2784 int Fl::has_timeout(Fl_Timeout_Handler cb, void* data)
2786 for (int i = 0; i < mac_timer_used; ++i) {
2787 MacTimeout& t = mac_timers[i];
2788 if (t.callback == cb && t.data == data && t.pending) {
2789 return 1;
2792 return 0;
2795 void Fl::remove_timeout(Fl_Timeout_Handler cb, void* data)
2797 for (int i = 0; i < mac_timer_used; ++i) {
2798 MacTimeout& t = mac_timers[i];
2799 if (t.callback == cb && ( t.data == data || data == NULL)) {
2800 delete_timer(t);
2805 int MacUnlinkWindow(Fl_X *ip, Fl_X *start) {
2806 if (!ip) return 0;
2807 if (start) {
2808 Fl_X *pc = start;
2809 while (pc) {
2810 if (pc->xidNext == ip) {
2811 pc->xidNext = ip->xidNext;
2812 return 1;
2814 if (pc->xidChildren) {
2815 if (pc->xidChildren == ip) {
2816 pc->xidChildren = ip->xidNext;
2817 return 1;
2819 if (MacUnlinkWindow(ip, pc->xidChildren))
2820 return 1;
2822 pc = pc->xidNext;
2824 } else {
2825 for ( Fl_X *pc = Fl_X::first; pc; pc = pc->next ) {
2826 if (MacUnlinkWindow(ip, pc))
2827 return 1;
2830 return 0;
2833 static void MacRelinkWindow(Fl_X *x, Fl_X *p) {
2834 if (!x || !p) return;
2835 // first, check if 'x' is already registered as a child of 'p'
2836 for (Fl_X *i = p->xidChildren; i; i=i->xidNext) {
2837 if (i == x) return;
2839 // now add 'x' as the first child of 'p'
2840 x->xidNext = p->xidChildren;
2841 p->xidChildren = x;
2844 void MacDestroyWindow(Fl_Window *w, WindowPtr p) {
2845 MacUnmapWindow(w, p);
2846 if (w && !w->parent() && p)
2847 DisposeWindow(p);
2850 void MacMapWindow(Fl_Window *w, WindowPtr p) {
2851 if (w && p)
2852 ShowWindow(p);
2853 //+ link to window list
2854 if (w && w->parent()) {
2855 MacRelinkWindow(Fl_X::i(w), Fl_X::i(w->window()));
2856 w->redraw();
2860 void MacUnmapWindow(Fl_Window *w, WindowPtr p) {
2861 if (w && !w->parent() && p)
2862 HideWindow(p);
2863 if (w && Fl_X::i(w))
2864 MacUnlinkWindow(Fl_X::i(w));
2866 #endif // FL_DOXYGEN
2869 // End of "$Id: Fl_mac.cxx 7913 2010-11-29 18:18:27Z greg.ercolano $".