update to 1.10.4
[profile/ivi/clutter.git] / clutter / osx / clutter-stage-osx.c
1 /* Clutter -  An OpenGL based 'interactive canvas' library.
2  * OSX backend - integration with NSWindow and NSView
3  *
4  * Copyright (C) 2007-2008  Tommi Komulainen <tommi.komulainen@iki.fi>
5  * Copyright (C) 2007  OpenedHand Ltd.
6  * Copyright (C) 2011  Crystalnix  <vgachkaylo@gmail.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser 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  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20  *
21  *
22  */
23 #include "config.h"
24
25 #import "clutter-osx.h"
26 #import "clutter-stage-osx.h"
27 #import "clutter-backend-osx.h"
28
29 #include "clutter-debug.h"
30 #include "clutter-private.h"
31 #include "clutter-stage-private.h"
32
33 enum
34 {
35   PROP_0,
36
37   PROP_BACKEND,
38   PROP_WRAPPER
39 };
40
41 static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface);
42
43 #define clutter_stage_osx_get_type      _clutter_stage_osx_get_type
44
45 G_DEFINE_TYPE_WITH_CODE (ClutterStageOSX,
46                          clutter_stage_osx,
47                          G_TYPE_OBJECT,
48                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_STAGE_WINDOW,
49                                                 clutter_stage_window_iface_init))
50
51 static ClutterActor *
52 clutter_stage_osx_get_wrapper (ClutterStageWindow *stage_window);
53
54 #define CLUTTER_OSX_FULLSCREEN_WINDOW_LEVEL (NSMainMenuWindowLevel + 1)
55
56 /*************************************************************************/
57 @implementation ClutterGLWindow
58 - (id)initWithView:(NSView *)aView UTF8Title:(const char *)aTitle stage:(ClutterStageOSX *)aStage
59 {
60   if ((self = [super initWithContentRect: [aView frame]
61                                styleMask: NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask
62                                  backing: NSBackingStoreBuffered
63                                    defer: NO]) != nil)
64     {
65       [self setDelegate: self];
66       [self useOptimizedDrawing: YES];
67       [self setAcceptsMouseMovedEvents:YES];
68       [self setContentView: aView];
69       [self setTitle:[NSString stringWithUTF8String: aTitle ? aTitle : ""]];
70       self->stage_osx = aStage;
71     }
72   return self;
73 }
74
75 - (BOOL) windowShouldClose: (id) sender
76 {
77   ClutterEvent event;
78
79   CLUTTER_NOTE (BACKEND, "[%p] windowShouldClose", self->stage_osx);
80
81   event.type = CLUTTER_DELETE;
82   event.any.stage = self->stage_osx->wrapper;
83   clutter_event_put (&event);
84
85   return NO;
86 }
87
88 - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)aScreen
89 {
90   /* in fullscreen mode we don't want to be constrained by menubar or dock
91    * FIXME: calculate proper constraints depending on fullscreen mode
92    */
93
94   return frameRect;
95 }
96
97 - (void) windowDidBecomeKey:(NSNotification*)aNotification
98 {
99   ClutterStage *stage;
100
101   CLUTTER_NOTE (BACKEND, "[%p] windowDidBecomeKey", self->stage_osx);
102
103   stage = self->stage_osx->wrapper;
104
105   if (_clutter_stage_is_fullscreen (stage))
106     [self setLevel: CLUTTER_OSX_FULLSCREEN_WINDOW_LEVEL];
107
108   _clutter_stage_update_state (stage, 0, CLUTTER_STAGE_STATE_ACTIVATED);
109 }
110
111 - (void) windowDidResignKey:(NSNotification*)aNotification
112 {
113   ClutterStage *stage;
114
115   CLUTTER_NOTE (BACKEND, "[%p] windowDidResignKey", self->stage_osx);
116
117   stage = self->stage_osx->wrapper;
118
119   if (_clutter_stage_is_fullscreen (stage))
120     {
121       [self setLevel: NSNormalWindowLevel];
122       if (!self->stage_osx->isHiding)
123         [self orderBack: nil];
124     }
125
126   _clutter_stage_update_state (stage, CLUTTER_STAGE_STATE_ACTIVATED, 0);
127 }
128
129 - (NSSize) windowWillResize:(NSWindow *) sender toSize:(NSSize) frameSize
130 {
131   if (clutter_stage_get_user_resizable (self->stage_osx->wrapper))
132     {
133       guint min_width, min_height;
134       clutter_stage_get_minimum_size (self->stage_osx->wrapper,
135                                       &min_width,
136                                       &min_height);
137       [self setContentMinSize:NSMakeSize(min_width, min_height)];
138       return frameSize;
139     }
140   else 
141     return [self frame].size;
142 }
143
144 - (void)windowDidChangeScreen:(NSNotification *)notification
145 {
146   clutter_stage_ensure_redraw (self->stage_osx->wrapper);
147 }
148 @end
149
150 /*************************************************************************/
151 @interface ClutterGLView : NSOpenGLView
152 {
153   ClutterStageOSX *stage_osx;
154
155   NSTrackingRectTag trackingRect;
156 }
157 - (void) drawRect: (NSRect) bounds;
158 @end
159
160 @implementation ClutterGLView
161 - (id) initWithFrame: (NSRect)aFrame pixelFormat:(NSOpenGLPixelFormat*)aFormat stage:(ClutterStageOSX*)aStage
162 {
163   if ((self = [super initWithFrame:aFrame pixelFormat:aFormat]) != nil)
164     {
165       self->stage_osx = aStage;
166       trackingRect = [self addTrackingRect:[self bounds]
167                                      owner:self
168                                   userData:NULL
169                               assumeInside:NO];
170     }
171
172   return self;
173 }
174
175 - (void) dealloc
176 {
177   if (trackingRect)
178     {
179       [self removeTrackingRect:trackingRect];
180       trackingRect = 0;
181     }
182
183   [super dealloc];
184 }
185
186 - (NSTrackingRectTag) trackingRect
187 {
188   return trackingRect;
189 }
190
191 - (ClutterActor *) clutterStage
192 {
193   return (ClutterActor *) stage_osx->wrapper;
194 }
195
196 - (void) drawRect: (NSRect) bounds
197 {
198   ClutterActor *stage = [self clutterStage];
199
200   _clutter_stage_do_paint (CLUTTER_STAGE (stage), NULL);
201
202   cogl_flush ();
203
204   [[self openGLContext] flushBuffer];
205 }
206
207 /* In order to receive key events */
208 - (BOOL) acceptsFirstResponder
209 {
210   return YES;
211 }
212
213 /* We want 0,0 top left */
214 - (BOOL) isFlipped
215 {
216   return YES;
217 }
218
219 - (BOOL) isOpaque
220 {
221   ClutterActor *stage = [self clutterStage];
222
223   if (CLUTTER_ACTOR_IN_DESTRUCTION (stage))
224    return YES;
225
226   if (clutter_stage_get_use_alpha (CLUTTER_STAGE (stage)))
227     return NO;
228
229   return YES;
230 }
231
232 - (void) reshape
233 {
234   ClutterActor *stage;
235
236   stage_osx->requisition_width = [self bounds].size.width;
237   stage_osx->requisition_height = [self bounds].size.height;
238
239   stage = [self clutterStage];
240   clutter_actor_set_size (stage,
241                           stage_osx->requisition_width,
242                           stage_osx->requisition_height);
243
244   [self removeTrackingRect:trackingRect];
245   trackingRect = [self addTrackingRect:[self bounds]
246                                  owner:self
247                               userData:NULL
248                           assumeInside:NO];
249 }
250
251 /* Simply forward all events that reach our view to clutter. */
252
253 #define EVENT_HANDLER(event) \
254 -(void)event:(NSEvent *) theEvent { \
255   _clutter_event_osx_put (theEvent, stage_osx->wrapper);  \
256 }
257
258 EVENT_HANDLER(mouseDown)
259 EVENT_HANDLER(mouseDragged)
260 EVENT_HANDLER(mouseUp)
261 EVENT_HANDLER(mouseMoved)
262 EVENT_HANDLER(mouseEntered)
263 EVENT_HANDLER(mouseExited)
264 EVENT_HANDLER(rightMouseDown)
265 EVENT_HANDLER(rightMouseDragged)
266 EVENT_HANDLER(rightMouseUp)
267 EVENT_HANDLER(otherMouseDown)
268 EVENT_HANDLER(otherMouseDragged)
269 EVENT_HANDLER(otherMouseUp)
270 EVENT_HANDLER(scrollWheel)
271 EVENT_HANDLER(keyDown)
272 EVENT_HANDLER(keyUp)
273 EVENT_HANDLER(flagsChanged)
274 EVENT_HANDLER(helpRequested)
275 EVENT_HANDLER(tabletPoint)
276 EVENT_HANDLER(tabletProximity)
277
278 #undef EVENT_HANDLER
279 @end
280
281 /*************************************************************************/
282
283 static void
284 clutter_stage_osx_save_frame (ClutterStageOSX *self)
285 {
286   g_assert (self->window != NULL);
287
288   self->normalFrame = [self->window frame];
289   self->haveNormalFrame = TRUE;
290 }
291
292 static void
293 clutter_stage_osx_set_frame (ClutterStageOSX *self)
294 {
295   g_assert (self->window != NULL);
296
297   if (_clutter_stage_is_fullscreen (self->wrapper))
298     {
299       /* Raise above the menubar (and dock) covering the whole screen.
300        *
301        * NOTE: This effectively breaks Option-Tabbing as our window covers
302        * all other applications completely. However we deal with the situation
303        * by lowering the window to the bottom of the normal level stack on
304        * windowDidResignKey notification.
305        */
306       [self->window setLevel: CLUTTER_OSX_FULLSCREEN_WINDOW_LEVEL];
307
308       [self->window setFrame: [self->window frameRectForContentRect: [[self->window screen] frame]]
309                                                             display: NO];
310     }
311   else
312     {
313       [self->window setLevel: NSNormalWindowLevel];
314
315       if (self->haveNormalFrame)
316         [self->window setFrame: self->normalFrame display: NO];
317       else
318         {
319           /* looks better than positioning to 0,0 (bottom right) */
320           [self->window center];
321         }
322     }
323 }
324
325 /*************************************************************************/
326 static gboolean
327 clutter_stage_osx_realize (ClutterStageWindow *stage_window)
328 {
329   ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
330   gfloat width, height;
331   NSRect rect;
332
333   CLUTTER_OSX_POOL_ALLOC();
334
335   CLUTTER_NOTE (BACKEND, "[%p] realize", self);
336
337   if (!self->haveRealized)
338     {
339       ClutterBackendOSX *backend_osx;
340
341       backend_osx = CLUTTER_BACKEND_OSX (self->backend);
342
343       /* Call get_size - this will either get the geometry size (which
344        * before we create the window is set to 640x480), or if a size
345        * is set, it will get that. This lets you set a size on the
346        * stage before it's realized.
347        */
348       clutter_actor_get_size (CLUTTER_ACTOR (self->wrapper), &width, &height);
349       self->requisition_width = width;
350       self->requisition_height= height;
351
352       rect = NSMakeRect (0, 0, self->requisition_width, self->requisition_height);
353
354       self->view = [[ClutterGLView alloc]
355                     initWithFrame: rect
356                       pixelFormat: backend_osx->pixel_format
357                             stage: self];
358       [self->view setOpenGLContext:backend_osx->context];
359
360       self->window = [[ClutterGLWindow alloc]
361                       initWithView: self->view
362                          UTF8Title: clutter_stage_get_title (CLUTTER_STAGE (self->wrapper))
363                              stage: self];
364       /* looks better than positioning to 0,0 (bottom right) */
365       [self->window center];
366       self->haveRealized = true;
367
368       CLUTTER_NOTE (BACKEND, "Stage successfully realized");
369     }
370
371   CLUTTER_OSX_POOL_RELEASE();
372
373   return TRUE;
374 }
375
376 static void
377 clutter_stage_osx_unrealize (ClutterStageWindow *stage_window)
378 {
379   ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
380
381   CLUTTER_OSX_POOL_ALLOC();
382
383   CLUTTER_NOTE (BACKEND, "[%p] unrealize", self);
384
385   /* ensure we get realize+unrealize properly paired */
386   g_return_if_fail (self->view != NULL && self->window != NULL);
387
388   [self->view release];
389   [self->window close];
390
391   self->view = NULL;
392   self->window = NULL;
393   self->haveRealized = false;
394
395   CLUTTER_OSX_POOL_RELEASE();
396 }
397
398 static void
399 clutter_stage_osx_show (ClutterStageWindow *stage_window,
400                         gboolean            do_raise)
401 {
402   ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
403   BOOL isViewHidden;
404   NSPoint nspoint;
405
406   CLUTTER_OSX_POOL_ALLOC();
407
408   CLUTTER_NOTE (BACKEND, "[%p] show", self);
409
410   clutter_stage_osx_realize (stage_window);
411   clutter_actor_map (CLUTTER_ACTOR (self->wrapper));
412
413   clutter_stage_osx_set_frame (self);
414
415   /* Draw view should be avoided and it is the reason why
416    * we should hide OpenGL view while we showing the stage.
417    */
418   isViewHidden = [self->view isHidden];
419   if (isViewHidden == NO)
420     [self->view setHidden:YES];
421
422   if (self->acceptFocus)
423     [self->window makeKeyAndOrderFront: nil];
424   else
425     [self->window orderFront: nil];
426
427   /* If the window is placed directly under the mouse pointer, Quartz will
428    * not send a NSMouseEntered event; we can easily synthesize one ourselves
429    * though.
430    */
431   nspoint = [self->window mouseLocationOutsideOfEventStream];
432   if ([self->view mouse:nspoint inRect:[self->view frame]])
433     {
434       NSEvent *event;
435
436       event = [NSEvent enterExitEventWithType: NSMouseEntered
437                                      location: NSMakePoint(0, 0)
438                                 modifierFlags: 0
439                                     timestamp: 0
440                                  windowNumber: [self->window windowNumber]
441                                       context: NULL
442                                   eventNumber: 0
443                                trackingNumber: [self->view trackingRect]
444                                      userData: nil];
445
446       [NSApp postEvent:event atStart:NO];
447     }
448
449   [self->view setHidden:isViewHidden];
450   [self->window setExcludedFromWindowsMenu:NO];
451
452   /*
453    * After hiding we cease to be first responder.
454    */
455   [self->window makeFirstResponder: self->view];
456
457   CLUTTER_OSX_POOL_RELEASE();
458 }
459
460 static void
461 clutter_stage_osx_hide (ClutterStageWindow *stage_window)
462 {
463   ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
464
465   CLUTTER_OSX_POOL_ALLOC();
466
467   CLUTTER_NOTE (BACKEND, "[%p] hide", self);
468
469   self->isHiding = true;
470   [self->window orderOut: nil];
471   [self->window setExcludedFromWindowsMenu:YES];
472
473   clutter_actor_unmap (CLUTTER_ACTOR (self->wrapper));
474
475   self->isHiding = false;
476
477   CLUTTER_OSX_POOL_RELEASE();
478 }
479
480 static void
481 clutter_stage_osx_get_geometry (ClutterStageWindow    *stage_window,
482                                 cairo_rectangle_int_t *geometry)
483 {
484   ClutterBackend *backend = clutter_get_default_backend ();
485   ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
486
487   g_return_if_fail (CLUTTER_IS_BACKEND_OSX (backend));
488
489   geometry->width = self->requisition_width;
490   geometry->height = self->requisition_height;
491 }
492
493 static void
494 clutter_stage_osx_resize (ClutterStageWindow *stage_window,
495                           gint                width,
496                           gint                height)
497 {
498   ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
499   ClutterActor *actor = clutter_stage_osx_get_wrapper (stage_window);
500   guint min_width, min_height;
501   NSSize size;
502
503   CLUTTER_OSX_POOL_ALLOC ();
504
505   clutter_stage_get_minimum_size (CLUTTER_STAGE (actor),
506                                   &min_width,
507                                   &min_height);
508
509   [self->window setContentMinSize: NSMakeSize (min_width, min_height)];
510   
511   width = width < min_width ? min_width : width;
512   height = height < min_height ? min_height : height;
513
514   self->requisition_width = width;
515   self->requisition_height = height;
516
517   size = NSMakeSize (self->requisition_width, self->requisition_height);
518   [self->window setContentSize: size];
519
520   CLUTTER_OSX_POOL_RELEASE ();
521 }
522
523 /*************************************************************************/
524 static ClutterActor *
525 clutter_stage_osx_get_wrapper (ClutterStageWindow *stage_window)
526 {
527   ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
528
529   return CLUTTER_ACTOR (self->wrapper);
530 }
531
532 static void
533 clutter_stage_osx_set_title (ClutterStageWindow *stage_window,
534                              const char         *title)
535 {
536   ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
537
538   CLUTTER_OSX_POOL_ALLOC();
539
540   CLUTTER_NOTE (BACKEND, "[%p] set_title: %s", self, title);
541
542   [self->window setTitle:[NSString stringWithUTF8String: title ? title : ""]];
543
544   CLUTTER_OSX_POOL_RELEASE();
545 }
546
547 static void
548 clutter_stage_osx_set_fullscreen (ClutterStageWindow *stage_window,
549                                   gboolean            fullscreen)
550 {
551   ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
552
553   CLUTTER_OSX_POOL_ALLOC();
554
555   CLUTTER_NOTE (BACKEND, "[%p] set_fullscreen: %u", self, fullscreen);
556
557   /* Make sure to update the state before clutter_stage_osx_set_frame.
558    *
559    * Toggling fullscreen isn't atomic, there's two "events" involved:
560    *  - stage state change (via state_update)
561    *  - stage size change (via set_frame -> setFrameSize / set_size)
562    *
563    * We do state change first. Not sure there's any difference.
564    */
565   if (fullscreen)
566     {
567       _clutter_stage_update_state (CLUTTER_STAGE (self->wrapper),
568                                    0,
569                                    CLUTTER_STAGE_STATE_FULLSCREEN);
570       clutter_stage_osx_save_frame (self);
571     }
572   else
573     {
574       _clutter_stage_update_state (CLUTTER_STAGE (self->wrapper),
575                                    CLUTTER_STAGE_STATE_FULLSCREEN,
576                                    0);
577     }
578
579   clutter_stage_osx_set_frame (self);
580
581   CLUTTER_OSX_POOL_RELEASE();
582 }
583 static void
584 clutter_stage_osx_set_cursor_visible (ClutterStageWindow *stage_window,
585                                       gboolean            cursor_visible)
586 {
587   CLUTTER_OSX_POOL_ALLOC();
588
589   if (cursor_visible)
590     [NSCursor unhide];
591   else
592     [NSCursor hide];
593
594   CLUTTER_OSX_POOL_RELEASE();
595 }
596
597 static void
598 clutter_stage_osx_set_user_resizable (ClutterStageWindow *stage_window,
599                                       gboolean            is_resizable)
600 {
601   ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
602
603   CLUTTER_OSX_POOL_ALLOC();
604
605   [self->window setShowsResizeIndicator:is_resizable];
606
607   CLUTTER_OSX_POOL_RELEASE();
608 }
609
610 static void
611 clutter_stage_osx_set_accept_focus (ClutterStageWindow *stage_window,
612                                     gboolean            accept_focus)
613 {
614   ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
615
616   CLUTTER_OSX_POOL_ALLOC();
617
618   self->acceptFocus = !!accept_focus;
619
620   CLUTTER_OSX_POOL_RELEASE();
621 }
622
623 static void
624 clutter_stage_osx_redraw (ClutterStageWindow *stage_window)
625 {
626   ClutterStageOSX *stage_osx = CLUTTER_STAGE_OSX (stage_window);
627
628   CLUTTER_OSX_POOL_ALLOC();
629
630   if (stage_osx->view != NULL)
631     [stage_osx->view setNeedsDisplay: YES];
632
633   CLUTTER_OSX_POOL_RELEASE();
634 }
635
636 static void
637 clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
638 {
639   iface->get_wrapper    = clutter_stage_osx_get_wrapper;
640   iface->set_title      = clutter_stage_osx_set_title;
641   iface->set_fullscreen = clutter_stage_osx_set_fullscreen;
642   iface->show           = clutter_stage_osx_show;
643   iface->hide           = clutter_stage_osx_hide;
644   iface->realize        = clutter_stage_osx_realize;
645   iface->unrealize      = clutter_stage_osx_unrealize;
646   iface->get_geometry   = clutter_stage_osx_get_geometry;
647   iface->resize         = clutter_stage_osx_resize;
648   iface->set_cursor_visible = clutter_stage_osx_set_cursor_visible;
649   iface->set_user_resizable = clutter_stage_osx_set_user_resizable;
650   iface->set_accept_focus   = clutter_stage_osx_set_accept_focus;
651   iface->redraw             = clutter_stage_osx_redraw;
652 }
653
654 /*************************************************************************/
655 static void
656 clutter_stage_osx_init (ClutterStageOSX *self)
657 {
658   self->requisition_width  = 640;
659   self->requisition_height = 480;
660   self->acceptFocus = TRUE;
661
662   self->isHiding = false;
663   self->haveRealized = false;
664   self->view = NULL;
665   self->window = NULL;
666 }
667
668 static void
669 clutter_stage_osx_set_property (GObject      *gobject,
670                                 guint         prop_id,
671                                 const GValue *value,
672                                 GParamSpec   *pspec)
673 {
674   ClutterStageOSX *self = CLUTTER_STAGE_OSX (gobject);
675
676   switch (prop_id)
677     {
678     case PROP_BACKEND:
679       self->backend = g_value_get_object (value);
680       break;
681
682     case PROP_WRAPPER:
683       self->wrapper = g_value_get_object (value);
684       break;
685
686     default:
687       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
688       break;
689     }
690 }
691
692 static void
693 clutter_stage_osx_finalize (GObject *gobject)
694 {
695   G_OBJECT_CLASS (clutter_stage_osx_parent_class)->finalize (gobject);
696 }
697
698 static void
699 clutter_stage_osx_dispose (GObject *gobject)
700 {
701   G_OBJECT_CLASS (clutter_stage_osx_parent_class)->dispose (gobject);
702 }
703
704 static void
705 clutter_stage_osx_class_init (ClutterStageOSXClass *klass)
706 {
707   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
708
709   gobject_class->set_property = clutter_stage_osx_set_property;
710   gobject_class->finalize = clutter_stage_osx_finalize;
711   gobject_class->dispose = clutter_stage_osx_dispose;
712
713   g_object_class_override_property (gobject_class, PROP_BACKEND, "backend");
714   g_object_class_override_property (gobject_class, PROP_WRAPPER, "wrapper");
715 }