significant reworking of AudioUnit window resizing.
[ardour.git] / gtk2_ardour / au_pluginui.mm
1 #undef  Marker
2 #define Marker FuckYouAppleAndYourLackOfNameSpaces
3
4 #include <gtkmm/button.h>
5 #include <gdk/gdkquartz.h>
6
7 #include "pbd/convert.h"
8 #include "pbd/error.h"
9
10 #include "ardour/audio_unit.h"
11 #include "ardour/debug.h"
12 #include "ardour/plugin_insert.h"
13
14 #undef check // stupid gtk, stupid apple
15
16 #include <gtkmm2ext/utils.h>
17 #include <gtkmm2ext/window_proxy.h>
18
19 #include "au_pluginui.h"
20 #include "gui_thread.h"
21 #include "processor_box.h"
22
23 #include "CAAudioUnit.h"
24 #include "CAComponent.h"
25
26 #import <AudioUnit/AUCocoaUIView.h>
27 #import <CoreAudioKit/AUGenericView.h>
28
29 #undef Marker
30
31 #include "keyboard.h"
32 #include "utils.h"
33 #include "public_editor.h"
34 #include "i18n.h"
35
36 #ifdef COREAUDIO105
37 #define ArdourCloseComponent CloseComponent
38 #else
39 #define ArdourCloseComponent AudioComponentInstanceDispose
40 #endif
41 using namespace ARDOUR;
42 using namespace Gtk;
43 using namespace Gtkmm2ext;
44 using namespace std;
45 using namespace PBD;
46
47 vector<string> AUPluginUI::automation_mode_strings;
48
49 static const gchar* _automation_mode_strings[] = {
50         X_("Manual"),
51         X_("Play"),
52         X_("Write"),
53         X_("Touch"),
54         0
55 };
56
57 static void
58 dump_view_tree (NSView* view, int depth, int maxdepth)
59 {
60         NSArray* subviews = [view subviews];
61         unsigned long cnt = [subviews count];
62
63         for (int d = 0; d < depth; d++) {
64                 cerr << '\t';
65         }
66         NSRect frame = [view frame];
67         cerr << " view @ " <<  frame.origin.x << ", " << frame.origin.y
68                 << ' ' << frame.size.width << " x " << frame.size.height
69                 << endl;
70
71         if (depth >= maxdepth) {
72                 return;
73         }
74         for (unsigned long i = 0; i < cnt; ++i) {
75                 NSView* subview = [subviews objectAtIndex:i];
76                 dump_view_tree (subview, depth+1, maxdepth);
77         }
78 }
79
80 @implementation NotificationObject
81
82 - (NotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui andCocoaParent: (NSWindow*) cp andTopLevelParent: (NSWindow*) tlp
83 {
84         self = [ super init ];
85
86         if (self) {
87                 plugin_ui = apluginui;
88                 top_level_parent = tlp;
89
90                 if (cp) {
91                         cocoa_parent = cp;
92
93                         [[NSNotificationCenter defaultCenter]
94                              addObserver:self
95                                 selector:@selector(cocoaParentActivationHandler:)
96                                     name:NSWindowDidBecomeMainNotification
97                                   object:NULL];
98
99                         [[NSNotificationCenter defaultCenter]
100                              addObserver:self
101                                 selector:@selector(cocoaParentBecameKeyHandler:)
102                                     name:NSWindowDidBecomeKeyNotification
103                                   object:NULL];
104                 }
105         }
106
107         return self;
108 }
109
110 - (void)cocoaParentActivationHandler:(NSNotification *)notification
111 {
112         NSWindow* notification_window = (NSWindow *)[notification object];
113
114         if (top_level_parent == notification_window || cocoa_parent == notification_window) {
115                 if ([notification_window isMainWindow]) {
116                         plugin_ui->activate();
117                 } else {
118                         plugin_ui->deactivate();
119                 }
120         }
121 }
122
123 - (void)cocoaParentBecameKeyHandler:(NSNotification *)notification
124 {
125         NSWindow* notification_window = (NSWindow *)[notification object];
126
127         if (top_level_parent == notification_window || cocoa_parent == notification_window) {
128                 if ([notification_window isKeyWindow]) {
129                         plugin_ui->activate();
130                 } else {
131                         plugin_ui->deactivate();
132                 }
133         }
134 }
135
136 - (void)auViewResized:(NSNotification *)notification
137 {
138         (void) notification; // stop complaints about unusued argument
139         plugin_ui->cocoa_view_resized();
140 }
141
142 @end
143
144 @implementation LiveResizeNotificationObject
145
146 - (LiveResizeNotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui
147 {
148         self = [ super init ];
149         if (self) {
150                 plugin_ui = apluginui;
151         }
152
153         return self;
154 }
155
156 - (void)windowWillStartLiveResizeHandler:(NSNotification*)notification
157 {
158         plugin_ui->start_live_resize ();
159 }
160
161 - (void)windowWillEndLiveResizeHandler:(NSNotification*)notification
162 {
163         plugin_ui->end_live_resize ();
164 }
165 @end
166
167 AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
168         : PlugUIBase (insert)
169         , automation_mode_label (_("Automation"))
170         , preset_label (_("Presets"))
171         , mapped (false)
172         , resizable (false)
173         , min_width (0)
174         , min_height (0)
175         , req_width (0)
176         , req_height (0)
177         , alo_width (0)
178         , alo_height (0)
179         , cocoa_window (0)
180         , au_view (0)
181         , in_live_resize (false)
182         , plugin_requested_resize (0)
183         , cocoa_parent (0)
184         , _notify (0)
185         , _resize_notify (0)
186
187 {
188         if (automation_mode_strings.empty()) {
189                 automation_mode_strings = I18N (_automation_mode_strings);
190         }
191
192         set_popdown_strings (automation_mode_selector, automation_mode_strings);
193         automation_mode_selector.set_active_text (automation_mode_strings.front());
194
195         if ((au = boost::dynamic_pointer_cast<AUPlugin> (insert->plugin())) == 0) {
196                 error << _("unknown type of editor-supplying plugin (note: no AudioUnit support in this version of ardour)") << endmsg;
197                 throw failed_constructor ();
198         }
199
200         /* stuff some stuff into the top of the window */
201
202         HBox* smaller_hbox = manage (new HBox);
203
204         smaller_hbox->set_spacing (6);
205         smaller_hbox->pack_start (preset_label, false, false, 4);
206         smaller_hbox->pack_start (_preset_modified, false, false);
207         smaller_hbox->pack_start (_preset_combo, false, false);
208         smaller_hbox->pack_start (add_button, false, false);
209 #if 0
210         /* Ardour does not currently allow to overwrite existing presets
211          * see save_property_list() in audio_unit.cc
212          */
213         smaller_hbox->pack_start (save_button, false, false);
214 #endif
215 #if 0
216         /* one day these might be useful with an AU plugin, but not yet */
217         smaller_hbox->pack_start (automation_mode_label, false, false);
218         smaller_hbox->pack_start (automation_mode_selector, false, false);
219 #endif
220         smaller_hbox->pack_start (reset_button, false, false);
221         smaller_hbox->pack_start (bypass_button, false, true);
222
223         VBox* v1_box = manage (new VBox);
224         VBox* v2_box = manage (new VBox);
225
226         v1_box->pack_start (*smaller_hbox, false, true);
227         v2_box->pack_start (focus_button, false, true);
228
229         top_box.set_homogeneous (false);
230         top_box.set_spacing (6);
231         top_box.set_border_width (6);
232
233         top_box.pack_end (*v2_box, false, false);
234         top_box.pack_end (*v1_box, false, false);
235
236         set_spacing (6);
237         pack_start (top_box, false, false);
238         pack_start (low_box, true, true);
239
240         preset_label.show ();
241         _preset_combo.show ();
242         automation_mode_label.show ();
243         automation_mode_selector.show ();
244         bypass_button.show ();
245         top_box.show ();
246         low_box.show ();
247
248         cocoa_parent = 0;
249         cocoa_window = 0;
250
251 #ifdef WITH_CARBON
252         _activating_from_app = false;
253         _notify = 0;
254         au_view = 0;
255         editView = 0;
256         carbon_window = 0;
257 #endif
258
259         /* prefer cocoa, fall back to cocoa, but use carbon if its there */
260
261         if (test_cocoa_view_support()) {
262                 create_cocoa_view ();
263 #ifdef WITH_CARBON
264         } else if (test_carbon_view_support()) {
265                 create_carbon_view ();
266 #endif
267         } else {
268                 create_cocoa_view ();
269         }
270
271         low_box.add_events (Gdk::VISIBILITY_NOTIFY_MASK | Gdk::EXPOSURE_MASK);
272
273         low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
274         low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
275         if (au_view) {
276                 low_box.signal_size_request ().connect (mem_fun (this, &AUPluginUI::lower_box_size_request));
277                 low_box.signal_size_allocate ().connect (mem_fun (this, &AUPluginUI::lower_box_size_allocate));
278                 low_box.signal_map ().connect (mem_fun (this, &AUPluginUI::lower_box_map));
279                 low_box.signal_unmap ().connect (mem_fun (this, &AUPluginUI::lower_box_unmap));
280                 low_box.signal_expose_event ().connect (mem_fun (this, &AUPluginUI::lower_box_expose));
281         }
282 }
283
284 AUPluginUI::~AUPluginUI ()
285 {
286         if (_notify) {
287                 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
288         }
289
290         if (_resize_notify) {
291                 [[NSNotificationCenter defaultCenter] removeObserver:_resize_notify];
292         }
293
294         if (cocoa_parent) {
295                 NSWindow* win = get_nswindow();
296                 [win removeChildWindow:cocoa_parent];
297         }
298
299 #ifdef WITH_CARBON
300         if (carbon_window) {
301                 /* not parented, just overlaid on top of our window */
302                 DisposeWindow (carbon_window);
303         }
304 #endif
305
306         if (editView) {
307                 ArdourCloseComponent (editView);
308         }
309
310         if (au_view) {
311                 /* remove whatever we packed into low_box so that GTK doesn't
312                    mess with it.
313                  */
314
315                 [au_view removeFromSuperview];
316         }
317 }
318
319 bool
320 AUPluginUI::test_carbon_view_support ()
321 {
322 #ifdef WITH_CARBON
323         bool ret = false;
324
325         carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
326         carbon_descriptor.componentSubType = 'gnrc';
327         carbon_descriptor.componentManufacturer = 'appl';
328         carbon_descriptor.componentFlags = 0;
329         carbon_descriptor.componentFlagsMask = 0;
330
331         OSStatus err;
332
333         // ask the AU for its first editor component
334         UInt32 propertySize;
335         err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
336         if (!err) {
337                 int nEditors = propertySize / sizeof(ComponentDescription);
338                 ComponentDescription *editors = new ComponentDescription[nEditors];
339                 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
340                 if (!err) {
341                         // just pick the first one for now
342                         carbon_descriptor = editors[0];
343                         ret = true;
344                 }
345                 delete[] editors;
346         }
347
348         return ret;
349 #else
350         return false;
351 #endif
352 }
353
354 bool
355 AUPluginUI::test_cocoa_view_support ()
356 {
357         UInt32 dataSize   = 0;
358         Boolean isWritable = 0;
359         OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
360                                                 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
361                                                 0, &dataSize, &isWritable);
362
363         return dataSize > 0 && err == noErr;
364 }
365
366 bool
367 AUPluginUI::plugin_class_valid (Class pluginClass)
368 {
369         if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
370                 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
371                    [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
372                                 return true;
373                 }
374         }
375         return false;
376 }
377
378 int
379 AUPluginUI::create_cocoa_view ()
380 {
381         bool wasAbleToLoadCustomView = false;
382         AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
383         UInt32               numberOfClasses = 0;
384         UInt32     dataSize;
385         Boolean    isWritable;
386         NSString*           factoryClassName = 0;
387         NSURL*              CocoaViewBundlePath = NULL;
388
389         OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
390                                                     kAudioUnitProperty_CocoaUI,
391                                                     kAudioUnitScope_Global,
392                                                     0,
393                                                     &dataSize,
394                                                     &isWritable );
395
396         numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
397
398         // Does view have custom Cocoa UI?
399
400         if ((result == noErr) && (numberOfClasses > 0) ) {
401
402                 DEBUG_TRACE(DEBUG::AudioUnits,
403                             string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
404
405                 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
406
407                 if(AudioUnitGetProperty(*au->get_au(),
408                                         kAudioUnitProperty_CocoaUI,
409                                         kAudioUnitScope_Global,
410                                         0,
411                                         cocoaViewInfo,
412                                         &dataSize) == noErr) {
413
414                         CocoaViewBundlePath     = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
415
416                         // we only take the first view in this example.
417                         factoryClassName        = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
418
419                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
420                                                                         [factoryClassName UTF8String], CocoaViewBundlePath));
421
422                 } else {
423
424                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
425
426                         if (cocoaViewInfo != NULL) {
427                                 free (cocoaViewInfo);
428                                 cocoaViewInfo = NULL;
429                         }
430                 }
431         }
432
433         // [A] Show custom UI if view has it
434
435         if (CocoaViewBundlePath && factoryClassName) {
436                 NSBundle *viewBundle    = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
437
438                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
439
440                 if (viewBundle == NULL) {
441                         error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
442                         return -1;
443                 } else {
444                         Class factoryClass = [viewBundle classNamed:factoryClassName];
445                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
446                         if (!factoryClass) {
447                                 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
448                                 return -1;
449                         }
450
451                         // make sure 'factoryClass' implements the AUCocoaUIBase protocol
452                         if (!plugin_class_valid (factoryClass)) {
453                                 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
454                                 return -1;
455                         }
456                         // make a factory
457                         id factory = [[[factoryClass alloc] init] autorelease];
458                         if (factory == NULL) {
459                                 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
460                                 return -1;
461                         }
462
463                         DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
464
465                         // make a view
466                         au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
467
468                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
469
470                         // cleanup
471                         [CocoaViewBundlePath release];
472                         if (cocoaViewInfo) {
473                                 UInt32 i;
474                                 for (i = 0; i < numberOfClasses; i++)
475                                         CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
476
477                                 free (cocoaViewInfo);
478                         }
479                         wasAbleToLoadCustomView = true;
480                 }
481         }
482
483         if (!wasAbleToLoadCustomView) {
484                 // load generic Cocoa view
485                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
486                                                                 au->get_au()));
487                 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
488                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
489                 [(AUGenericView *)au_view setShowsExpertParameters:1];
490         }
491
492         // Get the initial size of the new AU View's frame
493         NSRect  frame = [au_view frame];
494         min_width  = req_width  = frame.size.width;
495         min_height = req_height = frame.size.height;
496
497         resizable  = [au_view autoresizingMask];
498         std::cerr << plugin->name() << " initial frame = " << [NSStringFromRect (frame) UTF8String] << " resizable ? " << resizable << std::endl;
499
500         low_box.queue_resize ();
501
502         return 0;
503 }
504
505 void
506 AUPluginUI::update_view_size ()
507 {
508         last_au_frame = [au_view frame];
509 }
510
511 void
512 AUPluginUI::cocoa_view_resized ()
513 {
514         /* we can get here for two reasons:
515
516            1) the plugin window was resized by the user, a new size was
517            allocated to the window, ::update_view_size() was called, and we
518            explicitly/manually resized the AU NSView.
519
520            2) the plugin decided to resize itself (probably in response to user
521            action, but not in response to an actual window resize)
522
523            We only want to proceed with a window resizing in the second case.
524         */
525
526         if (in_live_resize) {
527                 /* ::update_view_size() will be called at the right times and
528                  * will update the view size. We don't need to anything while a
529                  * live resize in underway.
530                  */
531                 return;
532         }
533
534         if (plugin_requested_resize) {
535                 /* we tried to change the plugin frame from inside this method
536                  * (to adjust the origin), and the plugin changed its size
537                  * again. Ignore this second call.
538                  */
539                 std::cerr << plugin->name() << " re-entrant call to cocoa_view_resized, ignored\n";
540                 return;
541         }
542
543         plugin_requested_resize = 1;
544
545         ProcessorWindowProxy* wp = insert->window_proxy();
546         if (wp) {
547                 /* Once a plugin has requested a resize of its own window, do
548                  * NOT save the window. The user may save state with the plugin
549                  * editor expanded to show "extra detail" - the plugin will not
550                  * refill this space when the editor is first
551                  * instantiated. Leaving the window in the "too big" state
552                  * cannot be recovered from.
553                  *
554                  * The window will be sized to fit the plugin's own request. Done.
555                  */
556                 wp->set_state_mask (WindowProxy::Position);
557         }
558
559         NSRect new_frame = [au_view frame];
560
561         std::cerr << "Plugin " << plugin->name() << " requested update (prs now = " << plugin_requested_resize << ")\n";
562         std::cerr << "\tAU NSView frame : " << [ NSStringFromRect (new_frame) UTF8String] << std::endl;
563         std::cerr << "\tlast au frame : " << [ NSStringFromRect (last_au_frame) UTF8String] << std::endl;
564
565         /* from here on, we know that we've been called because the plugin
566          * decided to change the NSView frame itself.
567          */
568
569         /* step one: compute the change in the frame size.
570          */
571
572         float dy = new_frame.size.height - last_au_frame.size.height;
573         float dx = new_frame.size.width - last_au_frame.size.width;
574
575         NSWindow* window = get_nswindow ();
576         NSRect windowFrame= [window frame];
577
578         /* we want the top edge of the window to remain in the same place,
579            but the Cocoa/Quartz origin is at the lower left. So, when we make
580            the window larger, we will move it down, which means shifting the
581            origin toward (x,0). This will leave the top edge in the same place.
582         */
583
584         windowFrame.origin.y    -= dy;
585         windowFrame.origin.x    -= dx;
586         windowFrame.size.height += dy;
587         windowFrame.size.width  += dx;
588
589         std::cerr << "\tChange size by " << dx << " x " << dy << std::endl;
590
591         NSUInteger old_auto_resize = [au_view autoresizingMask];
592
593         /* Stop the AU NSView from resizing itself *again* in response to
594            us changing the window size.
595         */
596
597
598         [au_view setAutoresizingMask:NSViewNotSizable];
599
600         /* this resizes the window. it will eventually trigger a new
601          * size_allocate event/callback, and we'll end up in
602          * ::update_view_size(). We want to stop that from doing anything,
603          * because we've already resized the window to fit the new new view,
604          * so there's no need to actually update the view size again.
605          */
606
607         [window setFrame:windowFrame display:1];
608
609         /* Some stupid AU Views change the origin of the original AU View when
610            they are resized (I'm looking at you AUSampler). If the origin has
611            been moved, move it back.
612         */
613
614         if (last_au_frame.origin.x != new_frame.origin.x ||
615             last_au_frame.origin.y != new_frame.origin.y) {
616                 new_frame.origin = last_au_frame.origin;
617                 std::cerr << "Move AU NSView origin back to "
618                           << new_frame.origin.x << ", " << new_frame.origin.y
619                           << std::endl;
620                 [au_view setFrame:new_frame];
621                 /* also be sure to redraw the topbox because this can
622                    also go wrong.
623                  */
624                 top_box.queue_draw ();
625         }
626
627         [au_view setAutoresizingMask:old_auto_resize];
628
629         /* keep a copy of the size of the AU NSView. We didn't set - the plugin did */
630         last_au_frame = new_frame;
631         min_width  = req_width  = new_frame.size.width;
632         min_height = req_height = new_frame.size.height;
633
634         plugin_requested_resize = 0;
635 }
636
637 int
638 AUPluginUI::create_carbon_view ()
639 {
640 #ifdef WITH_CARBON
641         OSStatus err;
642         ControlRef root_control;
643
644         Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
645
646         OpenAComponent(editComponent, &editView);
647         if (!editView) {
648                 error << _("AU Carbon view: cannot open AU Component") << endmsg;
649                 return -1;
650         }
651
652         Rect r = { 100, 100, 100, 100 };
653         WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
654                                                   kWindowCompositingAttribute|
655                                                   kWindowNoShadowAttribute|
656                                                   kWindowNoTitleBarAttribute);
657
658         if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
659                 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
660                 ArdourCloseComponent (editView);
661                 return -1;
662         }
663
664         if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
665                 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
666                 DisposeWindow (carbon_window);
667                 ArdourCloseComponent (editView);
668                 return -1;
669         }
670
671         ControlRef viewPane;
672         Float32Point location  = { 0.0, 0.0 };
673         Float32Point size = { 0.0, 0.0 } ;
674
675         if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
676                 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
677                 DisposeWindow (carbon_window);
678                 ArdourCloseComponent (editView);
679                 return -1;
680         }
681
682         // resize window
683
684         Rect bounds;
685         GetControlBounds(viewPane, &bounds);
686         size.x = bounds.right-bounds.left;
687         size.y = bounds.bottom-bounds.top;
688
689         req_width = (int) (size.x + 0.5);
690         req_height = (int) (size.y + 0.5);
691
692         SizeWindow (carbon_window, req_width, req_height,  true);
693         low_box.set_size_request (req_width, req_height);
694
695         return 0;
696 #else
697         error << _("AU Carbon GUI is not supported.") << endmsg;
698         return -1;
699 #endif
700 }
701
702 NSWindow*
703 AUPluginUI::get_nswindow ()
704 {
705         Gtk::Container* toplevel = get_toplevel();
706
707         if (!toplevel || !toplevel->is_toplevel()) {
708                 error << _("AUPluginUI: no top level window!") << endmsg;
709                 return 0;
710         }
711
712         NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
713
714         if (!true_parent) {
715                 error << _("AUPluginUI: no top level window!") << endmsg;
716                 return 0;
717         }
718
719         return true_parent;
720 }
721
722 void
723 AUPluginUI::activate ()
724 {
725 #ifdef WITH_CARBON
726         ActivateWindow (carbon_window, TRUE);
727 #endif
728 }
729
730 void
731 AUPluginUI::deactivate ()
732 {
733 #ifdef WITH_CARBON
734         ActivateWindow (carbon_window, FALSE);
735 #endif
736 }
737
738 int
739 AUPluginUI::parent_carbon_window ()
740 {
741 #ifdef WITH_CARBON
742         NSWindow* win = get_nswindow ();
743         Rect windowStructureBoundsRect;
744
745         if (!win) {
746                 return -1;
747         }
748
749         /* figure out where the cocoa parent window is in carbon-coordinate space, which
750            differs from both cocoa-coordinate space and GTK-coordinate space
751         */
752
753         GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
754
755         /* compute how tall the title bar is, because we have to offset the position of the carbon window
756            by that much.
757         */
758
759         NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
760         NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
761
762         int titlebar_height = wm_frame.size.height - content_frame.size.height;
763
764         int packing_extra = 6; // this is the total vertical packing in our top level window
765
766         /* move into position, based on parent window position */
767         MoveWindow (carbon_window,
768                     windowStructureBoundsRect.left,
769                     windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
770                     false);
771         ShowWindow (carbon_window);
772
773         // create the cocoa window for the carbon one and make it visible
774         cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
775
776         SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
777
778         _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
779
780         [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
781         [win setAutodisplay:1]; // turn of GTK stuff for this window
782
783         return 0;
784 #else
785         return -1;
786 #endif
787 }
788
789 int
790 AUPluginUI::parent_cocoa_window ()
791 {
792         NSWindow* win = get_nswindow ();
793
794         if (!win) {
795                 return -1;
796         }
797
798         //[win setAutodisplay:1]; // turn off GTK stuff for this window
799
800         NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
801         [view addSubview:au_view];
802
803         /* this moves the AU NSView down and over to provide a left-hand margin
804          * and to clear the Ardour "task bar" (with plugin preset mgmt buttons,
805          * keyboard focus control, bypass etc).
806          */
807
808         gint xx, yy;
809         gtk_widget_translate_coordinates(
810                         GTK_WIDGET(low_box.gobj()),
811                         GTK_WIDGET(low_box.get_parent()->gobj()),
812                         8, 6, &xx, &yy);
813         [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
814
815         last_au_frame = [au_view frame];
816         // watch for size changes of the view
817         _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
818
819         [[NSNotificationCenter defaultCenter] addObserver:_notify
820                 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
821                 object:au_view];
822
823         // catch notifications that live resizing is about to start
824
825         _resize_notify = [ [ LiveResizeNotificationObject alloc] initWithPluginUI:this ];
826
827         [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
828                 selector:@selector(windowWillStartLiveResizeHandler:) name:NSWindowWillStartLiveResizeNotification
829                 object:win];
830
831         [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
832                 selector:@selector(windowWillEndLiveResizeHandler:) name:NSWindowDidEndLiveResizeNotification
833                 object:win];
834
835         return 0;
836 }
837
838 void
839 AUPluginUI::grab_focus()
840 {
841         if (au_view) {
842                 [au_view becomeFirstResponder];
843         }
844 }
845 void
846 AUPluginUI::forward_key_event (GdkEventKey* ev)
847 {
848         NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
849
850         if (au_view && nsevent) {
851
852                 /* filter on nsevent type here because GDK massages FlagsChanged
853                    messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
854                    handle a FlagsChanged message as a keyDown or keyUp
855                 */
856
857                 if ([nsevent type] == NSKeyDown) {
858                         [[[au_view window] firstResponder] keyDown:nsevent];
859                 } else if ([nsevent type] == NSKeyUp) {
860                         [[[au_view window] firstResponder] keyUp:nsevent];
861                 } else if ([nsevent type] == NSFlagsChanged) {
862                         [[[au_view window] firstResponder] flagsChanged:nsevent];
863                 }
864         }
865 }
866
867 void
868 AUPluginUI::on_realize ()
869 {
870         VBox::on_realize ();
871
872         /* our windows should not have that resize indicator */
873
874         NSWindow* win = get_nswindow ();
875         if (win) {
876                 [win setShowsResizeIndicator:0];
877         }
878 }
879
880 void
881 AUPluginUI::lower_box_realized ()
882 {
883         if (au_view) {
884                 parent_cocoa_window ();
885         } else if (carbon_window) {
886                 parent_carbon_window ();
887         }
888 }
889
890 bool
891 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
892 {
893 #ifdef WITH_CARBON
894         if (carbon_window  && ev->state != GDK_VISIBILITY_UNOBSCURED) {
895                 ShowWindow (carbon_window);
896                 ActivateWindow (carbon_window, TRUE);
897                 return true;
898         }
899 #endif
900         return false;
901 }
902
903 void
904 AUPluginUI::lower_box_map ()
905 {
906         mapped = true;
907         [au_view setHidden:0];
908         update_view_size ();
909 }
910
911 void
912 AUPluginUI::lower_box_unmap ()
913 {
914         mapped = false;
915         [au_view setHidden:1];
916 }
917
918 void
919 AUPluginUI::lower_box_size_request (GtkRequisition* requisition)
920 {
921         requisition->width  = req_width;
922         requisition->height = req_height;
923 }
924
925 void
926 AUPluginUI::lower_box_size_allocate (Gtk::Allocation& allocation)
927 {
928         alo_width  = allocation.get_width ();
929         alo_height = allocation.get_height ();
930         std::cerr << "lower box size reallocated to " << alo_width << " x " << alo_height << std::endl;
931         update_view_size ();
932         std::cerr << "low box draw (0, 0, " << alo_width << " x " << alo_height << ")\n";
933         low_box.queue_draw_area (0, 0, alo_width, alo_height);
934 }
935
936 gboolean
937 AUPluginUI::lower_box_expose (GdkEventExpose* event)
938 {
939         std::cerr << "lower box expose: " << event->area.x << ", " << event->area.y
940                   << ' '
941                   << event->area.width << " x " << event->area.height
942                   << " ALLOC "
943                   << get_allocation().get_width() << " x " << get_allocation().get_height()
944                   << std::endl;
945
946         /* hack to keep ardour responsive
947          * some UIs (e.g Addictive Drums) completely hog the CPU
948          */
949         ARDOUR::GUIIdle();
950
951         return true;
952 }
953
954 void
955 AUPluginUI::on_window_hide ()
956 {
957 #ifdef WITH_CARBON
958         if (carbon_window) {
959                 HideWindow (carbon_window);
960                 ActivateWindow (carbon_window, FALSE);
961         }
962 #endif
963         hide_all ();
964
965 #if 0
966         NSArray* wins = [NSApp windows];
967         for (uint32_t i = 0; i < [wins count]; i++) {
968                 id win = [wins objectAtIndex:i];
969         }
970 #endif
971 }
972
973 bool
974 AUPluginUI::on_window_show (const string& /*title*/)
975 {
976         /* this is idempotent so just call it every time we show the window */
977
978         gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
979
980         show_all ();
981
982 #ifdef WITH_CARBON
983         if (carbon_window) {
984                 ShowWindow (carbon_window);
985                 ActivateWindow (carbon_window, TRUE);
986         }
987 #endif
988
989         return true;
990 }
991
992 bool
993 AUPluginUI::start_updating (GdkEventAny*)
994 {
995         return false;
996 }
997
998 bool
999 AUPluginUI::stop_updating (GdkEventAny*)
1000 {
1001         return false;
1002 }
1003
1004 PlugUIBase*
1005 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
1006 {
1007         AUPluginUI* aup = new AUPluginUI (plugin_insert);
1008         (*box) = aup;
1009         return aup;
1010 }
1011
1012 void
1013 AUPluginUI::start_live_resize ()
1014 {
1015   std::cerr << "\n\n\n++++ Entering Live Resize\n";
1016   in_live_resize = true;
1017 }
1018
1019 void
1020 AUPluginUI::end_live_resize ()
1021 {
1022   std::cerr << "\n\n\n ----Leaving Live Resize\n";
1023   in_live_resize = false;
1024 }