blob: 38c0f21eabca013958f88dd9507a18099ca3b108 [file] [log] [blame]
DRC2ff39b82011-07-28 08:38:59 +00001//
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.
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// 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.
45
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
50
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
55
56#define CONSOLIDATE_MOTION 0
57extern "C" {
58#include <pthread.h>
59}
60
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>
71
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*/
83
84// external functions
85extern Fl_Window* fl_find(Window);
86extern void fl_fix_focus();
87
88// forward definition of functions in this file
89static void handleUpdateEvent( WindowPtr xid );
90//+ int fl_handle(const EventRecord &event);
91static int FSSpec2UnixPath( FSSpec *fs, char *dst );
92// converting cr lf converter function
93static void convert_crlf(char * string, size_t len);
94
95// public variables
96int fl_screen;
97CGContextRef fl_gc = 0;
98Handle fl_system_menu;
99Fl_Sys_Menu_Bar *fl_sys_menu_bar = 0;
100CursHandle fl_default_cursor;
101WindowRef fl_capture = 0; // we need this to compensate for a missing(?) mouse capture
102ulong fl_event_time; // the last timestamp from an x event
103char fl_key_vector[32]; // used by Fl::get_key()
104bool fl_show_iconic; // true if called from iconize() - shows the next created window in collapsed state
105int fl_disable_transient_for; // secret method of removing TRANSIENT_FOR
106const Fl_Window* fl_modal_for; // parent of modal() window
107Fl_Region fl_window_region = 0;
108Window fl_window;
109Fl_Window *Fl_Window::current_;
110EventRef fl_os_event; // last (mouse) event
111
112// forward declarations of variables in this file
113static int got_events = 0;
114static Fl_Window* resize_from_system;
115static CursPtr default_cursor_ptr;
116static Cursor default_cursor;
117static WindowRef fl_os_capture = 0; // the dispatch handler will redirect mose move and drag events to these windows
118
119#if CONSOLIDATE_MOTION
120static Fl_Window* send_motion;
121extern Fl_Window* fl_xmousewin;
122#endif
123
124enum { kEventClassFLTK = 'fltk' };
125enum { kEventFLTKBreakLoop = 1, kEventFLTKDataReady };
126
127/* fltk-utf8 placekeepers */
128void fl_reset_spot()
129{
130}
131
132void fl_set_spot(int font, int size, int X, int Y, int W, int H, Fl_Window *win)
133{
134}
135
136void fl_set_status(int x, int y, int w, int h)
137{
138}
139
140/**
141* Mac keyboard lookup table
142 */
143static unsigned short macKeyLookUp[128] =
144{
145 'a', 's', 'd', 'f', 'h', 'g', 'z', 'x',
146 'c', 'v', '^', 'b', 'q', 'w', 'e', 'r',
147
148 'y', 't', '1', '2', '3', '4', '6', '5',
149 '=', '9', '7', '-', '8', '0', ']', 'o',
150
151 'u', '[', 'i', 'p', FL_Enter, 'l', 'j', '\'',
152 'k', ';', '\\', ',', '/', 'n', 'm', '.',
153
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,
158
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,
161
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,
164
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,
167
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*/,
170};
171
172/**
173 * convert the current mouse chord into the FLTK modifier state
174 */
175static unsigned int mods_to_e_state( UInt32 mods )
176{
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;
188}
189
190
191/**
192 * convert the current mouse chord into the FLTK keysym
193 */
194static void mods_to_e_keysym( UInt32 mods )
195{
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 );
207}
208// these pointers are set by the Fl::lock() function:
209static void nothing() {}
210void (*fl_lock_function)() = nothing;
211void (*fl_unlock_function)() = nothing;
212
213//
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
220//
221#define POLLIN 1
222#define POLLOUT 4
223#define POLLERR 8
224
225// Class to handle select() 'data ready'
226class DataReady
227{
228 struct FD
229 {
230 int fd;
231 short events;
232 void (*cb)(int, void*);
233 void* arg;
234 };
235 int nfds, fd_array_size;
236 FD *fds;
237 pthread_t tid; // select()'s thread id
238
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
245
246public:
247 DataReady()
248 {
249 nfds = 0;
250 fd_array_size = 0;
251 fds = 0;
252 tid = 0;
253
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;
259 }
260
261 ~DataReady()
262 {
263 CancelThread(DEBUGTEXT("DESTRUCTOR\n"));
264 if (fds) { free(fds); fds = 0; }
265 nfds = 0;
266 }
267
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
272 //
273 void DataLock() { pthread_mutex_lock(&_datalock); }
274 void DataUnlock() { pthread_mutex_unlock(&_datalock); }
275
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]); }
281
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);
290};
291
292static DataReady dataready;
293
294void DataReady::AddFD(int n, int events, void (*cb)(int, void*), void *v)
295{
296 RemoveFD(n, events);
297 int i = nfds++;
298 if (i >= fd_array_size)
299 {
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;
306 }
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();
317}
318
319// Remove an FD from the array
320void DataReady::RemoveFD(int n, int events)
321{
322 int i,j;
323 for (i=j=0; i<nfds; i++)
324 {
325 if (fds[i].fd == n)
326 {
327 int e = fds[i].events & ~events;
328 if (!e) continue; // if no events left, delete this fd
329 fds[i].events = e;
330 }
331 // move it down in the array if necessary:
332 if (j<i)
333 { fds[j] = fds[i]; }
334 j++;
335 }
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();
343}
344
345// CHECK IF USER DATA READY, RETURNS r/w/x INDICATING WHICH IF ANY
346int DataReady::CheckData(fd_set& r, fd_set& w, fd_set& x)
347{
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);
357}
358
359// HANDLE DATA READY CALLBACKS
360void DataReady::HandleData(fd_set& r, fd_set& w, fd_set& x)
361{
362 for (int i=0; i<nfds; i++)
363 {
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)
370 {
371 DEBUGMSG("DOING CALLBACK: ");
372 fds[i].cb(f, fds[i].arg);
373 DEBUGMSG("DONE\n");
374 }
375 }
376}
377
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.
381//
382void* DataReady::DataReadyThread(void *o)
383{
384 DataReady *self = (DataReady*)o;
385 while ( 1 ) // loop until thread cancel or error
386 {
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 )
404 {
405 case 0: // NO DATA
406 continue;
407 case -1: // ERROR
408 {
409 DEBUGPERRORMSG("CHILD THREAD: select() failed");
410 return(NULL); // error? exit thread
411 }
412 default: // DATA READY
413 {
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
424 }
425 }
426 }
427}
428
429// START 'DATA READY' THREAD RUNNING, CREATE INTER-THREAD PIPE
430void DataReady::StartThread(void *new_userdata)
431{
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);
439}
440
441// CANCEL 'DATA READY' THREAD, CLOSE PIPE
442void DataReady::CancelThread(const char *reason)
443{
444 if ( tid )
445 {
446 DEBUGMSG("*** CANCEL THREAD: ");
447 DEBUGMSG(reason);
448 if ( pthread_cancel(tid) == 0 ) // cancel first
449 {
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
454 }
455 tid = 0;
456 DEBUGMSG("(JOINED) OK\n");
457 }
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();
463}
464
465void Fl::add_fd( int n, int events, void (*cb)(int, void*), void *v )
466 { dataready.AddFD(n, events, cb, v); }
467
468void Fl::add_fd(int fd, void (*cb)(int, void*), void* v)
469 { dataready.AddFD(fd, POLLIN, cb, v); }
470
471void Fl::remove_fd(int n, int events)
472 { dataready.RemoveFD(n, events); }
473
474void Fl::remove_fd(int n)
475 { dataready.RemoveFD(n, -1); }
476
477/**
478 * Check if there is actually a message pending!
479 */
480int fl_ready()
481{
482 EventRef event;
483 return !ReceiveNextEvent(0, NULL, 0.0, false, &event);
484}
485
486/**
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
489 */
490OSStatus HandleMenu( HICommand *cmd )
491{
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 )
498 {
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
505 {
506 Fl_Menu_Item *j = m;
507 int i = cmd->menu.menuItemIndex;
508 for (;;)
509 {
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 );
516 }
517 j = m-1; i = cmd->menu.menuItemIndex-1;
518 for ( ; i>0; j--, i-- )
519 {
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 );
523 }
524 SetItemMark( cmd->menu.menuRef, cmd->menu.menuItemIndex, ( m->flags & FL_MENU_VALUE ) ? 0x13 : 0 );
525 }
526 ret = noErr; // done handling this event
527 }
528 HiliteMenu(0);
529 return ret;
530}
531
532
533/**
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
538 */
539static pascal OSStatus carbonDispatchHandler( EventHandlerCallRef nextHandler, EventRef event, void *userData )
540{
541 OSStatus ret = eventNotHandledErr;
542 HICommand cmd;
543
544 fl_lock_function();
545
546 got_events = 1;
547
548 switch ( GetEventClass( event ) )
549 {
550 case kEventClassMouse:
551 switch ( GetEventKind( event ) )
552 {
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;
561 }
562 break;
563 }
564 break;
565 case kEventClassCommand:
566 switch (GetEventKind( event ) )
567 {
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;
575 }
576 break;
577 case kEventClassFLTK:
578 switch ( GetEventKind( event ) )
579 {
580 case kEventFLTKBreakLoop:
581 ret = noErr;
582 break;
583 case kEventFLTKDataReady:
584 {
585 dataready.CancelThread(DEBUGTEXT("DATA READY EVENT\n"));
586
587 // CHILD THREAD TELLS US DATA READY
588 // Check to see what's ready, and invoke user's cb's
589 //
590 fd_set r,w,x;
591 switch(dataready.CheckData(r,w,x))
592 {
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;
600 }
601 }
602 ret = noErr;
603 break;
604 }
605 }
606 if ( ret == eventNotHandledErr )
607 ret = CallNextEventHandler( nextHandler, event ); // let the OS handle the activation, but continue to get a click-through effect
608
609 fl_unlock_function();
610
611 return ret;
612}
613
614
615/**
616 * break the current event loop
617 */
618static void breakMacEventLoop()
619{
620 EventRef breakEvent;
621
622 fl_lock_function();
623
624 CreateEvent( 0, kEventClassFLTK, kEventFLTKBreakLoop, 0, kEventAttributeUserEvent, &breakEvent );
625 PostEventToQueue( GetCurrentEventQueue(), breakEvent, kEventPriorityStandard );
626 ReleaseEvent( breakEvent );
627
628 fl_unlock_function();
629}
630
631//
632// MacOS X timers
633//
634
635struct MacTimeout {
636 Fl_Timeout_Handler callback;
637 void* data;
638 EventLoopTimerRef timer;
639 EventLoopTimerUPP upp;
640 char pending;
641};
642static MacTimeout* mac_timers;
643static int mac_timer_alloc;
644static int mac_timer_used;
645
646
647static void realloc_timers()
648{
649 if (mac_timer_alloc == 0) {
650 mac_timer_alloc = 8;
651 }
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;
659}
660
661static void delete_timer(MacTimeout& t)
662{
663 if (t.timer) {
664 RemoveEventLoopTimer(t.timer);
665 DisposeEventLoopTimerUPP(t.upp);
666 memset(&t, 0, sizeof(MacTimeout));
667 }
668}
669
670
671static pascal void do_timer(EventLoopTimerRef timer, void* data)
672{
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;
681 }
682 }
683 breakMacEventLoop();
684}
685
686/**
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.
690 */
691static double do_queued_events( double time = 0.0 )
692{
693 static bool been_here = false;
694 static RgnHandle rgn;
695
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;
704 }
705 OSStatus ret;
706 static EventTargetRef target = 0;
707 if ( !target )
708 {
709 target = GetEventDispatcherTarget();
710
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 );
734 }
735
736 got_events = 0;
737
738 // Check for re-entrant condition
739 if ( dataready.IsThreadRunning() )
740 { dataready.CancelThread(DEBUGTEXT("AVOID REENTRY\n")); }
741
742 // Start thread to watch for data ready
743 if ( dataready.GetNfds() )
744 { dataready.StartThread((void*)GetCurrentEventQueue()); }
745
746 fl_unlock_function();
747
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);
758 }
759 }
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);
768 }
769 }
770 ReleaseEvent( event );
771 }
772
773 fl_lock_function();
774
775#if CONSOLIDATE_MOTION
776 if (send_motion && send_motion == fl_xmousewin) {
777 send_motion = 0;
778 Fl::handle(FL_MOVE, fl_xmousewin);
779 }
780#endif
781
782 return time;
783}
784
785
786/**
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.
790 *
791 * \todo there is no socket handling in this code whatsoever
792 */
793int fl_wait( double time )
794{
795 do_queued_events( time );
796 return (got_events);
797}
798
799
800/**
801 * event handler for Apple-Q key combination
802 * this is also called from the Carbon Window handler after all windows were closed
803 */
804static OSErr QuitAppleEventHandler( const AppleEvent *appleEvt, AppleEvent* reply, UInt32 refcon )
805{
806 fl_lock_function();
807
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
814 }
815 }
816
817 fl_unlock_function();
818
819 return noErr;
820}
821
822
823/**
824 * Carbon Window handler
825 * This needs to be linked into all new window event handlers
826 */
827static pascal OSStatus carbonWindowHandler( EventHandlerCallRef nextHandler, EventRef event, void *userData )
828{
829 UInt32 kind = GetEventKind( event );
830 OSStatus ret = eventNotHandledErr;
831 Fl_Window *window = (Fl_Window*)userData;
832 Fl::first_window(window);
833
834 Rect currentBounds, originalBounds;
835 WindowClass winClass;
836 static Fl_Window *activeWindow = 0;
837
838 fl_lock_function();
839
840 switch ( kind )
841 {
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 ) )
859 {
860 if ( window->shown() )
861 handleUpdateEvent( fl_xid( window ) );
862 }
863 break; }
864 case kEventWindowShown:
865 if ( !window->parent() )
866 {
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;
871 }
872 Fl::handle( FL_SHOW, window);
873 mods_to_e_state(GetCurrentKeyModifiers());
874 }
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 )
881 {
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;
886 }
887 }
888 break;
889 case kEventWindowDeactivated:
890 if ( window==activeWindow )
891 {
892 Fl::handle( FL_UNFOCUS, window);
893 activeWindow = 0;
894 }
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;
908 }
909
910 fl_unlock_function();
911
912 return ret;
913}
914
915
916/**
917 * Carbon Mousewheel handler
918 * This needs to be linked into all new window event handlers
919 */
920static pascal OSStatus carbonMousewheelHandler( EventHandlerCallRef nextHandler, EventRef event, void *userData )
921{
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();
926
927 fl_os_event = event;
928 Fl_Window *window = (Fl_Window*)userData;
929 if ( !window->shown() )
930 {
931 fl_unlock_function();
932 return noErr;
933 }
934 Fl::first_window(window);
935
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();
951
952 return eventNotHandledErr;
953 }
954
955 fl_unlock_function();
956
957 return noErr;
958}
959
960
961/**
962 * convert the current mouse chord into the FLTK modifier state
963 */
964static void chord_to_e_state( UInt32 chord )
965{
966 static ulong state[] =
967 {
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
971 };
972 Fl::e_state = ( Fl::e_state & 0xff0000 ) | state[ chord & 0x07 ];
973}
974
975
976/**
977 * Carbon Mouse Button Handler
978 */
979static pascal OSStatus carbonMouseHandler( EventHandlerCallRef nextHandler, EventRef event, void *userData )
980{
981 static int keysym[] = { 0, FL_Button+1, FL_Button+3, FL_Button+2 };
982 static int px, py;
983 static char suppressed = 0;
984
985 fl_lock_function();
986
987 fl_os_event = event;
988 Fl_Window *window = (Fl_Window*)userData;
989 if ( !window->shown() )
990 {
991 fl_unlock_function();
992 return noErr;
993 }
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 ) )
1006 {
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
1015 }
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
1022 }
1023 }
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
1027 }
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;
1041 }
1042 if ( !window ) break;
1043 if ( !sendEvent ) {
1044 sendEvent = FL_RELEASE;
1045 }
1046 Fl::e_keysym = keysym[ btn ];
1047 // fall through
1048 case kEventMouseMoved:
1049 suppressed = 0;
1050 if ( !sendEvent ) {
1051 sendEvent = FL_MOVE; chord = 0;
1052 }
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;
1060 }
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 );
1079 }
1080 break;
1081 }
1082
1083 fl_unlock_function();
1084
1085 return noErr;
1086}
1087
1088
1089/**
1090 * convert the keyboard return code into the symbol on the keycaps
1091 */
1092static unsigned short keycode_to_sym( UInt32 keyCode, UInt32 mods, unsigned short deflt )
1093{
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);
1101 }
1102 }
1103 if (map)
1104 return KeyTranslate(map, keyCode|mods, &state );
1105 return deflt;
1106}
1107
1108/*
1109 * keycode_function for post-10.5 systems, allows more sophisticated decoding of keys
1110 */
1111static 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
1118{
1119 // first get the keyboard mapping in a post 10.2 way
1120
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;
1133
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
1146 }
1147 // FIXME No Layout property found. Now we have a problem.
1148 }
1149 }
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);
1156 }
1157 }
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
1165 }
1166
1167 // now apply that keyboard mapping to our keycode
1168
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];
1179
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;
1185 }
1186
1187 UInt32 deadKeyState = *deadKeyStatePtr;
1188 if ((action==kUCKeyActionUp)&&(*deadKeyStatePtr))
1189 deadKeyStatePtr = &deadKeyState;
1190
1191 status = UCKeyTranslate(
1192 (const UCKeyboardLayout *) uchr,
1193 keycode, action, modifiers, keyboardType,
1194 0, deadKeyStatePtr,
1195 10, &actuallength, utext);
1196
1197 if (noErr != status) {
1198 fprintf(stderr,"UCKeyTranslate failed: %d\n", (int) status);
1199 actuallength = 0;
1200 }
1201
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);
1207 }
1208 uniChars[len] = 0;
1209 return len;
1210}
1211
1212/*
1213 * keycode_function for pre-10.5 systems, this is the "historic" fltk Mac key handling
1214 */
1215static 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)
1221{
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;
1229 }
1230} /* keycode_wrap_old */
1231/*
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.
1236 */
1237static int (*keycode_function)(char*, int, EventKind, UInt32, UInt32, UInt32*, unsigned char, unsigned short) = keycode_wrap_old;
1238
1239
1240// EXPERIMENTAL!
1241pascal OSStatus carbonTextHandler(
1242 EventHandlerCallRef nextHandler, EventRef event, void *userData )
1243{
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;
1269}
1270
1271/**
1272 * handle carbon keyboard events
1273 */
1274pascal OSStatus carbonKeyboardHandler(
1275 EventHandlerCallRef nextHandler, EventRef event, void *userData )
1276{
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() );
1283
1284 fl_lock_function();
1285
1286 int kind = GetEventKind(event);
1287
1288 // get the modifiers for any of the events
1289 GetEventParameter( event, kEventParamKeyModifiers, typeUInt32,
1290 NULL, sizeof(UInt32), NULL, &mods );
1291
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 );
1301 }
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";
1315 }
1316 printf("%08x %08x %08x '%c' %s \n", mods, keyCode, key, key, ev);
1317 */
1318 switch (kind)
1319 {
1320 case kEventRawKeyDown:
1321 case kEventRawKeyRepeat:
1322/*
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
1337 }
1338*/
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
1345 }
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.
1363
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 )
1375 {
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;
1382 }
1383 mods_to_e_state( mods );
1384 break; }
1385 }
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;
1396 }
1397}
1398
1399
1400
1401/**
1402 * Open callback function to call...
1403 */
1404
1405static void (*open_cb)(const char *) = 0;
1406
1407
1408/**
1409 * Event handler for Apple-O key combination and also for file opens
1410 * via the finder...
1411 */
1412
1413static 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];
1424
1425 if (!open_cb) return noErr;
1426
1427 // Initialize the document list...
1428 AECreateDesc(typeNull, NULL, 0, &documents);
1429
1430 // Get the open parameter(s)...
1431 err = AEGetParamDesc(appleEvt, keyDirectObject, typeAEList, &documents);
1432 if (err != noErr) {
1433 AEDisposeDesc(&documents);
1434 return err;
1435 }
1436
1437 // Lock access to FLTK in this thread...
1438 fl_lock_function();
1439
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));
1447
1448 // Convert to a UNIX path...
1449 FSSpec2UnixPath(&fileSpec, filename);
1450
1451 // Call the callback with the filename...
1452 (*open_cb)(filename);
1453 }
1454 }
1455
1456 // Unlock access to FLTK for all threads...
1457 fl_unlock_function();
1458
1459 // Get rid of the document list...
1460 AEDisposeDesc(&documents);
1461
1462 return noErr;
1463}
1464
1465
1466/**
1467 * Install an open documents event handler...
1468 */
1469
1470void 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);
1480 }
1481}
1482
1483
1484/**
1485 * initialize the Mac toolboxes, dock status, and set the default menubar
1486 */
1487
1488extern "C" {
1489 extern OSErr CPSEnableForegroundOperation(ProcessSerialNumber *psn, UInt32 _arg2,
1490 UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
1491}
1492
1493void fl_open_display() {
1494 static char beenHereDoneThat = 0;
1495 if ( !beenHereDoneThat ) {
1496 beenHereDoneThat = 1;
1497
1498 FlushEvents(everyEvent,0);
1499
1500 MoreMasters(); // \todo Carbon suggests MoreMasterPointers()
1501 AEInstallEventHandler( kCoreEventClass, kAEQuitApplication, NewAEEventHandlerUPP((AEEventHandlerProcPtr)QuitAppleEventHandler), 0, false );
1502
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;
1507
1508 ClearMenuBar();
1509 AppendResMenu( GetMenuHandle( 1 ), 'DRVR' );
1510 DrawMenuBar();
1511
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 )
1517 {
1518 // only transform the application type for unbundled apps
1519 CFBundleRef bundle = CFBundleGetMainBundle();
1520 if( bundle )
1521 {
1522 FSRef execFs;
1523 CFURLRef execUrl = CFBundleCopyExecutableURL( bundle );
1524 CFURLGetFSRef( execUrl, &execFs );
1525
1526 FSRef bundleFs;
1527 GetProcessBundleLocation( &cur_psn, &bundleFs );
1528
1529 if( !FSCompareFSRefs( &execFs, &bundleFs ) )
1530 bundle = NULL;
1531
1532 CFRelease(execUrl);
1533 }
1534
1535 if( !bundle )
1536 {
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;
1542
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);
1549
1550 if (err == noErr) {
1551 SetFrontProcess( &cur_psn );
1552 }
1553 }
1554 }
1555
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)
1561 {
1562 if(MacVersion >= 0x1050) { // 10.5.0 or later
1563 keycode_function = keycodeToUnicode;
1564 }
1565 }
1566 }
1567}
1568
1569
1570/**
1571 * get rid of allocated resources
1572 */
1573void fl_close_display() {
1574}
1575
1576
1577/**
1578 * smallest x ccordinate in screen space
1579 */
1580int Fl::x() {
1581 BitMap r;
1582 GetQDGlobalsScreenBits(&r);
1583 return r.bounds.left;
1584}
1585
1586
1587/**
1588 * smallest y ccordinate in screen space
1589 */
1590int Fl::y() {
1591 BitMap r;
1592 GetQDGlobalsScreenBits(&r);
1593 return r.bounds.top + 20; // \todo 20 pixel menu bar?
1594}
1595
1596
1597/**
1598 * screen width (single monitor!?)
1599 */
1600int Fl::w() {
1601 BitMap r;
1602 GetQDGlobalsScreenBits(&r);
1603 return r.bounds.right - r.bounds.left;
1604}
1605
1606
1607/**
1608 * screen height (single monitor!?)
1609 */
1610int Fl::h() {
1611 BitMap r;
1612 GetQDGlobalsScreenBits(&r);
1613 return r.bounds.bottom - r.bounds.top - 20;
1614}
1615
1616
1617/**
1618 * get the current mouse pointer world coordinates
1619 */
1620void Fl::get_mouse(int &x, int &y)
1621{
1622 fl_open_display();
1623 Point loc;
1624 GetMouse( &loc );
1625 LocalToGlobal( &loc );
1626 x = loc.h;
1627 y = loc.v;
1628}
1629
1630
1631/**
1632 * convert Mac keystrokes to FLTK
1633 */
1634unsigned short mac2fltk(ulong macKey)
1635{
1636 unsigned short cc = macKeyLookUp[(macKey>>8)&0x7f];
1637 if (cc) return cc;
1638 return macKey&0xff;
1639}
1640
1641
1642/**
1643 * Initialize the given port for redraw and call the windw's flush() to actually draw the content
1644 */
1645void Fl_X::flush()
1646{
1647 w->flush();
1648 if (fl_gc)
1649 CGContextFlush(fl_gc);
1650 SetOrigin( 0, 0 );
1651}
1652
1653
1654/**
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
1659 */
1660void handleUpdateEvent( WindowPtr xid )
1661{
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 );
1672 }
1673 }
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;
1677 }
1678 for ( Fl_X *cx = i->xidChildren; cx; cx = cx->xidNext )
1679 {
1680 cx->w->clear_damage(window->damage()|FL_DAMAGE_EXPOSE);
1681 cx->flush();
1682 cx->w->clear_damage();
1683 }
1684 window->clear_damage(window->damage()|FL_DAMAGE_EXPOSE);
1685 i->flush();
1686 window->clear_damage();
1687 SetPort( oldPort );
1688}
1689
1690// Gets the border sizes and the titlebar size
1691static 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);
1705
1706 if (!status)
1707 {
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);
1715 }
1716 else
1717#endif // MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2
1718 {
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);
1723 }
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
1729}
1730
1731/**
1732 * \todo this is a leftover from OS9 times. Please check how much applies to Carbon!
1733 */
1734int 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);
1741 /*
1742 bx = 6; // \todo Mac : GetSystemMetrics(SM_CXSIZEFRAME);
1743 by = 6; // \todo Mac : get Mac window frame size GetSystemMetrics(SM_CYSIZEFRAME);
1744 */
1745 } else {
1746 ret = 1;
1747 get_window_frame_sizes(bx, by, bt);
1748 /*
1749 bx = 6; // \todo Mac : GetSystemMetrics(SM_CXFIXEDFRAME);
1750 by = 6; // \todo Mac : GetSystemMetrics(SM_CYFIXEDFRAME);
1751 */
1752 }
1753 }
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;
1763
1764 //Proceed to positioning the window fully inside the screen, if possible
1765
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;
1781 }
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;
1789 }
1790 }
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;
1798 }
1799 }
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;
1807 }
1808 }
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;
1816 }
1817 }
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;
1827 }
1828
1829 //Return the client area's top left corner in (X,Y)
1830 X+=xoff;
1831 Y+=yoff;
1832
1833 return ret;
1834}
1835
1836/**
1837 * convert a Mac FSSpec structure into a Unix filename
1838 */
1839static int FSSpec2UnixPath( FSSpec *fs, char *dst )
1840{
1841 FSRef fsRef;
1842 FSpMakeFSRef( fs, &fsRef );
1843 FSRefMakePath( &fsRef, (UInt8*)dst, 1024 );
1844 return strlen(dst);
1845}
1846static void convert_crlf(char * s, size_t len)
1847{
1848 // turn all \r characters into \n:
1849 for (size_t x = 0; x < len; x++) if (s[x] == '\r') s[x] = '\n';
1850}
1851
1852
1853static DragReference currDragRef = 0;
1854static char *currDragData = 0L;
1855static int currDragSize = 0;
1856static OSErr currDragErr = noErr;
1857Fl_Window *fl_dnd_target_window = 0;
1858#include <FL/fl_draw.H>
1859
1860/**
1861 * Fill the currDrag* variables with the current DnD ASCII text.
1862 */
1863static OSErr fillCurrentDragData(DragReference dragRef)
1864{
1865 OSErr ret = noErr;
1866 char *dst = 0L;
1867
1868 // shortcut through this whole procedure if this is still the same drag event
1869 if (dragRef==currDragRef)
1870 return currDragErr;
1871
1872 // clear currDrag* for a new drag event
1873 currDragRef = dragRef;
1874 if (currDragData) free(currDragData);
1875 currDragData = 0;
1876 currDragSize = 0;
1877
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 );
1884
1885 for ( i = 1; i <= nItem; i++ )
1886 {
1887 GetDragItemReferenceNumber( dragRef, i, &itemRef );
1888 ret = GetFlavorFlags( dragRef, itemRef, 'utf8', &flags );
1889 if ( ret == noErr )
1890 {
1891 GetFlavorDataSize( dragRef, itemRef, 'utf8', &itemSize );
1892 size += itemSize;
1893 continue;
1894 }
1895 ret = GetFlavorFlags( dragRef, itemRef, 'utxt', &flags );
1896 if ( ret == noErr )
1897 {
1898 GetFlavorDataSize( dragRef, itemRef, 'utxt', &itemSize );
1899 size += itemSize;
1900 continue;
1901 }
1902 ret = GetFlavorFlags( dragRef, itemRef, 'TEXT', &flags );
1903 if ( ret == noErr )
1904 {
1905 GetFlavorDataSize( dragRef, itemRef, 'TEXT', &itemSize );
1906 size += itemSize;
1907 continue;
1908 }
1909 ret = GetFlavorFlags( dragRef, itemRef, 'hfs ', &flags );
1910 if ( ret == noErr )
1911 {
1912 size += 1024; //++ ouch! We should create the full pathname and figure out its length
1913 continue;
1914 }
1915 }
1916
1917 if ( !size )
1918 {
1919 currDragErr = userCanceledErr;
1920 return currDragErr;
1921 }
1922
1923 currDragSize = size + nItem - 1;
1924 currDragData = dst = (char*)malloc( size+nItem );;
1925
1926 for ( i = 1; i <= nItem; i++ )
1927 {
1928 GetDragItemReferenceNumber( dragRef, i, &itemRef );
1929 ret = GetFlavorFlags( dragRef, itemRef, 'utf8', &flags );
1930 if ( ret == noErr )
1931 {
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;
1937 }
1938 GetDragItemReferenceNumber( dragRef, i, &itemRef );
1939 ret = GetFlavorFlags( dragRef, itemRef, 'utxt', &flags );
1940 if ( ret == noErr )
1941 {
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;
1947 }
1948 ret = GetFlavorFlags( dragRef, itemRef, 'TEXT', &flags );
1949 if ( ret == noErr )
1950 {
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;
1956 }
1957 ret = GetFlavorFlags( dragRef, itemRef, 'hfs ', &flags );
1958 if ( ret == noErr )
1959 {
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;
1968 }
1969 }
1970
1971 dst[-1] = 0;
1972 currDragSize = dst - currDragData - 1;
1973 currDragErr = ret;
1974 return ret;
1975}
1976
1977/**
1978 * Drag'n'drop tracking handler
1979 */
1980static pascal OSErr dndTrackingHandler( DragTrackingMessage msg, WindowPtr w, void *userData, DragReference dragRef )
1981{
1982 Fl_Window *target = (Fl_Window*)userData;
1983 Fl::first_window(target);
1984 Point mp;
1985 static int px, py;
1986
1987 fillCurrentDragData(dragRef);
1988 Fl::e_length = currDragSize;
1989 Fl::e_text = currDragData;
1990
1991 switch ( msg )
1992 {
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 )
2027 {
2028 Fl::handle( FL_DND_LEAVE, fl_dnd_target_window );
2029 fl_dnd_target_window = 0;
2030 }
2031 breakMacEventLoop();
2032 return noErr;
2033 }
2034 return noErr;
2035}
2036
2037
2038/**
2039 * Drag'n'drop receive handler
2040 */
2041static pascal OSErr dndReceiveHandler( WindowPtr w, void *userData, DragReference dragRef )
2042{
2043 Point mp;
2044 OSErr ret;
2045
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;
2055
2056 ret = fillCurrentDragData(dragRef);
2057 if (ret==userCanceledErr)
2058 return userCanceledErr;
2059
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;
2066
2067 if (currDragData) {
2068 free(currDragData);
2069 }
2070 currDragData = 0L;
2071 currDragRef = 0;
2072 Fl::e_text = 0L;
2073 Fl::e_length = 0;
2074 fl_dnd_target_window = 0L;
2075
2076 breakMacEventLoop();
2077 return noErr;
2078}
2079// fc:
2080static 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]);
2091 }
2092 else
2093 pTitle[0] = 0;
2094 SetWTitle(xid, pTitle);
2095#endif
2096}
2097
2098/**
2099 * go ahead, create that (sub)window
2100 * \todo we should make menu windows slightly transparent for the new Mac look
2101 */
2102void Fl_X::make(Fl_Window* w)
2103{
2104 static int xyPos = 100;
2105 if ( w->parent() ) // create a subwindow
2106 {
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
2135 }
2136 fl_show_iconic = 0;
2137 }
2138 else // create a desktop window
2139 {
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());
2160 }
2161 }
2162 int xwm = xp, ywm = yp, bt, bx, by;
2163
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;
2171 }
2172 } else if (w->modal()) {
2173 winclass = kMovableModalWindowClass;
2174 }
2175
2176 if (by+bt) {
2177 wp += 2*bx;
2178 hp += 2*by+bt;
2179 }
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);
2190 }
2191 xp -= bx;
2192 yp -= by+bt;
2193 }
2194
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??)
2199 }
2200
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;
2206
2207 const char *name = w->label();
2208
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;
2217
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);
2226 }
2227 if (!(w->flags() & Fl_Widget::FORCE_POSITION))
2228 {
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);
2236 }
2237 }
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 );
2257
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 );
2265
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 );
2270
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 );
2286 }
2287
2288 if ( ! Fl_X::first->next ) // if this is the first window, we need to bring the application to the front
2289 {
2290 ProcessSerialNumber psn;
2291 OSErr err = GetCurrentProcess( &psn );
2292 if ( err==noErr ) SetFrontProcess( &psn );
2293 }
2294
2295 if (w->size_range_set) w->size_range_();
2296
2297 if (winclass != kHelpWindowClass) {
2298 Fl_Tooltip::enter(0);
2299 }
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();
2307 }
2308
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);
2313
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
2318
2319 if (w->modal()) { Fl::modal_ = w; fl_fix_focus(); }
2320 }
2321}
2322
2323
2324/**
2325 * Tell the OS what window sizes we want to allow
2326 */
2327void 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);
2333}
2334
2335
2336/**
2337 * returns pointer to the filename, or null if name ends with ':'
2338 */
2339const char *fl_filename_name( const char *name )
2340{
2341 const char *p, *q;
2342 if (!name) return (0);
2343 for ( p = q = name ; *p ; )
2344 {
2345 if ( ( p[0] == ':' ) && ( p[1] == ':' ) )
2346 {
2347 q = p+2;
2348 p++;
2349 }
2350 else if (p[0] == '/')
2351 q = p + 1;
2352 p++;
2353 }
2354 return q;
2355}
2356
2357
2358/**
2359 * set the window title bar
2360 * \todo make the titlebar icon work!
2361 */
2362void Fl_Window::label(const char *name,const char */*iname*/) {
2363 Fl_Widget::label(name);
2364
2365 if (shown() || i) {
2366 q_set_window_title(fl_xid(this), name);
2367 }
2368}
2369
2370
2371/**
2372 * make a window visible
2373 */
2374void 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);
2381 }
2382 Fl_Tooltip::exit(this);
2383 if (!shown() || !i) {
2384 Fl_X::make(this);
2385 } else {
2386 if ( !parent() )
2387 {
2388 if ( IsWindowCollapsed( i->xid ) ) CollapseWindow( i->xid, false );
2389 if (!fl_capture) {
2390 BringToFront(i->xid);
2391 SelectWindow(i->xid);
2392 }
2393 }
2394 }
2395}
2396
2397
2398/**
2399 * resize a window
2400 */
2401void 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);
2419 }
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);
2426 }
2427 }
2428 resize_from_system = 0;
2429 if (is_a_resize) {
2430 Fl_Group::resize(X,Y,W,H);
2431 if (shown()) {
2432 redraw();
2433 }
2434 } else {
2435 x(X); y(Y);
2436 }
2437}
2438
2439
2440/**
2441 * make all drawing go into this window (called by subclass flush() impl.)
2442 */
2443void Fl_Window::make_current()
2444{
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;
2451
2452 SetPort( GetWindowPort(i->xid) ); // \todo check for the handling of doublebuffered windows
2453
2454 int xp = 0, yp = 0;
2455 Fl_Window *win = this;
2456 while ( win )
2457 {
2458 if ( !win->window() )
2459 break;
2460 xp += win->x();
2461 yp += win->y();
2462 win = (Fl_Window*)win->window();
2463 }
2464 SetOrigin( -xp, -yp );
2465
2466 SetRectRgn( fl_window_region, 0, 0, w(), h() );
2467
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 )
2470 {
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 );
2478 }
2479
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
2489
2490 fl_clip_region( 0 );
2491 SetPortClipRegion( GetWindowPort(i->xid), fl_window_region );
2492
2493#if defined(USE_CAIRO)
2494 // update the cairo_t context
2495 if (Fl::cairo_autolink_context()) Fl::cairo_make_current(this);
2496#endif
2497}
2498
2499// helper function to manage the current CGContext fl_gc
2500extern Fl_Color fl_color_;
2501extern class Fl_Font_Descriptor *fl_fontsize;
2502extern void fl_font(class Fl_Font_Descriptor*);
2503extern void fl_quartz_restore_line_style_();
2504
2505// FLTK has only one global graphics state. This function copies the FLTK state into the
2506// current Quartz context
2507void 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);
2516 }
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_();
2522}
2523
2524// The only way to reset clipping to its original state is to pop the current graphics
2525// state and restore the global state.
2526void Fl_X::q_clear_clipping() {
2527 if (!fl_gc) return;
2528 CGContextRestoreGState(fl_gc);
2529 CGContextSaveGState(fl_gc);
2530}
2531
2532// Give the Quartz context back to the system
2533void 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);
2541 }
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
2546}
2547
2548void 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;
2561}
2562
2563void Fl_X::q_end_image() {
2564 CGContextRestoreGState(fl_gc);
2565}
2566
2567////////////////////////////////////////////////////////////////
2568// Copy & Paste fltk implementation.
2569////////////////////////////////////////////////////////////////
2570
2571// fltk 1.3 clipboard support constant definitions:
2572const CFStringRef flavorNames[] = {
2573 CFSTR("public.utf16-plain-text"),
2574 CFSTR("public.utf8-plain-text"),
2575 CFSTR("com.apple.traditional-mac-plain-text") };
2576const CFStringEncoding encodings[] = {
2577 kCFStringEncodingUTF16,
2578 kCFStringEncodingUTF8,
2579 kCFStringEncodingMacRoman};
2580const size_t handledFlavorsCount = sizeof(encodings)/sizeof(CFStringEncoding);
2581
2582// clipboard variables definitions :
2583Fl_Widget *fl_selection_requestor = 0;
2584char *fl_selection_buffer[2];
2585int fl_selection_length[2];
2586static int fl_selection_buffer_length[2];
2587
2588#ifdef USE_PASTEBOARD
2589static PasteboardRef myPasteboard = 0;
2590static void allocatePasteboard() {
2591 if (!myPasteboard)
2592 PasteboardCreate(kPasteboardClipboard, &myPasteboard);
2593}
2594#else
2595#endif
2596
2597#ifndef USE_PASTEBOARD
2598static ScrapRef myScrap = 0;
2599#endif
2600
2601/**
2602 * create a selection
2603 * owner: widget that created the selection
2604 * stuff: pointer to selected data
2605 * size of selected data
2606 */
2607void 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;
2613 }
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;
2635 }
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
2642 }
2643}
2644
2645// Call this when a "paste" operation happens:
2646void 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;
2655
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;
2671 }
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;
2682 }
2683 }
2684 if(found) break;
2685 }
2686 if (flavorTypeArray) {CFRelease(flavorTypeArray); flavorTypeArray = NULL;}
2687 if (found) break;
2688 }
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];
2698 }
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:
2704 }
2705 }
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];
2714 }
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);
2720 }
2721#endif
2722 }
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);
2727}
2728
2729void Fl::add_timeout(double time, Fl_Timeout_Handler cb, void* data)
2730{
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;
2739 }
2740 }
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;
2748 }
2749 }
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();
2755 }
2756 timer_id = mac_timer_used++;
2757 }
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);
2775 }
2776}
2777
2778void Fl::repeat_timeout(double time, Fl_Timeout_Handler cb, void* data)
2779{
2780 // currently, repeat_timeout does not subtract the trigger time of the previous timer event as it should.
2781 add_timeout(time, cb, data);
2782}
2783
2784int Fl::has_timeout(Fl_Timeout_Handler cb, void* data)
2785{
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;
2790 }
2791 }
2792 return 0;
2793}
2794
2795void Fl::remove_timeout(Fl_Timeout_Handler cb, void* data)
2796{
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);
2801 }
2802 }
2803}
2804
2805int 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;
2813 }
2814 if (pc->xidChildren) {
2815 if (pc->xidChildren == ip) {
2816 pc->xidChildren = ip->xidNext;
2817 return 1;
2818 }
2819 if (MacUnlinkWindow(ip, pc->xidChildren))
2820 return 1;
2821 }
2822 pc = pc->xidNext;
2823 }
2824 } else {
2825 for ( Fl_X *pc = Fl_X::first; pc; pc = pc->next ) {
2826 if (MacUnlinkWindow(ip, pc))
2827 return 1;
2828 }
2829 }
2830 return 0;
2831}
2832
2833static 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;
2838 }
2839 // now add 'x' as the first child of 'p'
2840 x->xidNext = p->xidChildren;
2841 p->xidChildren = x;
2842}
2843
2844void MacDestroyWindow(Fl_Window *w, WindowPtr p) {
2845 MacUnmapWindow(w, p);
2846 if (w && !w->parent() && p)
2847 DisposeWindow(p);
2848}
2849
2850void 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();
2857 }
2858}
2859
2860void 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));
2865}
2866#endif // FL_DOXYGEN
2867
2868//
2869// End of "$Id: Fl_mac.cxx 7913 2010-11-29 18:18:27Z greg.ercolano $".
2870//