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