test workaround for OSX IK-Multimedia Plugin GUIs.
[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
18 #include "au_pluginui.h"
19 #include "gui_thread.h"
20
21 #include "CAAudioUnit.h"
22 #include "CAComponent.h"
23
24 #import <AudioUnit/AUCocoaUIView.h>
25 #import <CoreAudioKit/AUGenericView.h>
26
27 #undef Marker
28
29 #include "keyboard.h"
30 #include "utils.h"
31 #include "public_editor.h"
32 #include "i18n.h"
33
34 #ifdef COREAUDIO105
35 #define ArdourCloseComponent CloseComponent
36 #else
37 #define ArdourCloseComponent AudioComponentInstanceDispose
38 #endif
39 using namespace ARDOUR;
40 using namespace Gtk;
41 using namespace Gtkmm2ext;
42 using namespace std;
43 using namespace PBD;
44
45 vector<string> AUPluginUI::automation_mode_strings;
46
47 static const gchar* _automation_mode_strings[] = {
48         X_("Manual"),
49         X_("Play"),
50         X_("Write"),
51         X_("Touch"),
52         0
53 };
54
55 static void
56 dump_view_tree (NSView* view, int depth, int maxdepth)
57 {
58         NSArray* subviews = [view subviews];
59         unsigned long cnt = [subviews count];
60
61         for (int d = 0; d < depth; d++) {
62                 cerr << '\t';
63         }
64         NSRect frame = [view frame];
65         cerr << " view @ " <<  frame.origin.x << ", " << frame.origin.y
66                 << ' ' << frame.size.width << " x " << frame.size.height
67                 << endl;
68
69         if (depth >= maxdepth) {
70                 return;
71         }
72         for (unsigned long i = 0; i < cnt; ++i) {
73                 NSView* subview = [subviews objectAtIndex:i];
74                 dump_view_tree (subview, depth+1, maxdepth);
75         }
76 }
77
78 @implementation NotificationObject
79
80 - (NotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui andCocoaParent: (NSWindow*) cp andTopLevelParent: (NSWindow*) tlp
81 {
82         self = [ super init ];
83
84         if (self) {
85                 plugin_ui = apluginui;
86                 top_level_parent = tlp;
87
88                 if (cp) {
89                         cocoa_parent = cp;
90
91                         [[NSNotificationCenter defaultCenter]
92                              addObserver:self
93                                 selector:@selector(cocoaParentActivationHandler:)
94                                     name:NSWindowDidBecomeMainNotification
95                                   object:NULL];
96
97                         [[NSNotificationCenter defaultCenter]
98                              addObserver:self
99                                 selector:@selector(cocoaParentBecameKeyHandler:)
100                                     name:NSWindowDidBecomeKeyNotification
101                                   object:NULL];
102                 }
103         }
104
105         return self;
106 }
107
108 - (void)cocoaParentActivationHandler:(NSNotification *)notification
109 {
110         NSWindow* notification_window = (NSWindow *)[notification object];
111
112         if (top_level_parent == notification_window || cocoa_parent == notification_window) {
113                 if ([notification_window isMainWindow]) {
114                         plugin_ui->activate();
115                 } else {
116                         plugin_ui->deactivate();
117                 }
118         }
119 }
120
121 - (void)cocoaParentBecameKeyHandler:(NSNotification *)notification
122 {
123         NSWindow* notification_window = (NSWindow *)[notification object];
124
125         if (top_level_parent == notification_window || cocoa_parent == notification_window) {
126                 if ([notification_window isKeyWindow]) {
127                         plugin_ui->activate();
128                 } else {
129                         plugin_ui->deactivate();
130                 }
131         }
132 }
133
134 - (void)auViewResized:(NSNotification *)notification
135 {
136         (void) notification; // stop complaints about unusued argument
137         plugin_ui->cocoa_view_resized();
138 }
139
140 @end
141
142 AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
143         : PlugUIBase (insert)
144         , automation_mode_label (_("Automation"))
145         , preset_label (_("Presets"))
146         , mapped (false)
147         , resizable (false)
148         , min_width (0)
149         , min_height (0)
150         , req_width (0)
151         , req_height (0)
152         , alo_width (0)
153         , alo_height (0)
154
155 {
156         if (automation_mode_strings.empty()) {
157                 automation_mode_strings = I18N (_automation_mode_strings);
158         }
159
160         set_popdown_strings (automation_mode_selector, automation_mode_strings);
161         automation_mode_selector.set_active_text (automation_mode_strings.front());
162
163         if ((au = boost::dynamic_pointer_cast<AUPlugin> (insert->plugin())) == 0) {
164                 error << _("unknown type of editor-supplying plugin (note: no AudioUnit support in this version of ardour)") << endmsg;
165                 throw failed_constructor ();
166         }
167
168         /* stuff some stuff into the top of the window */
169
170         HBox* smaller_hbox = manage (new HBox);
171
172         smaller_hbox->set_spacing (6);
173         smaller_hbox->pack_start (preset_label, false, false, 4);
174         smaller_hbox->pack_start (_preset_modified, false, false);
175         smaller_hbox->pack_start (_preset_combo, false, false);
176         smaller_hbox->pack_start (add_button, false, false);
177 #if 0
178         /* Ardour does not currently allow to overwrite existing presets
179          * see save_property_list() in audio_unit.cc
180          */
181         smaller_hbox->pack_start (save_button, false, false);
182 #endif
183 #if 0
184         /* one day these might be useful with an AU plugin, but not yet */
185         smaller_hbox->pack_start (automation_mode_label, false, false);
186         smaller_hbox->pack_start (automation_mode_selector, false, false);
187 #endif
188         smaller_hbox->pack_start (reset_button, false, false);
189         smaller_hbox->pack_start (bypass_button, false, true);
190
191         VBox* v1_box = manage (new VBox);
192         VBox* v2_box = manage (new VBox);
193
194         v1_box->pack_start (*smaller_hbox, false, true);
195         v2_box->pack_start (focus_button, false, true);
196
197         top_box.set_homogeneous (false);
198         top_box.set_spacing (6);
199         top_box.set_border_width (6);
200
201         top_box.pack_end (*v2_box, false, false);
202         top_box.pack_end (*v1_box, false, false);
203
204         set_spacing (6);
205         pack_start (top_box, false, false);
206         pack_start (low_box, true, true);
207
208         preset_label.show ();
209         _preset_combo.show ();
210         automation_mode_label.show ();
211         automation_mode_selector.show ();
212         bypass_button.show ();
213         top_box.show ();
214         low_box.show ();
215
216         cocoa_parent = 0;
217         cocoa_window = 0;
218
219 #ifdef WITH_CARBON
220         _activating_from_app = false;
221         _notify = 0;
222         au_view = 0;
223         editView = 0;
224         carbon_window = 0;
225 #endif
226
227         /* prefer cocoa, fall back to cocoa, but use carbon if its there */
228
229         if (test_cocoa_view_support()) {
230                 create_cocoa_view ();
231 #ifdef WITH_CARBON
232         } else if (test_carbon_view_support()) {
233                 create_carbon_view ();
234 #endif
235         } else {
236                 create_cocoa_view ();
237         }
238
239         low_box.add_events (Gdk::VISIBILITY_NOTIFY_MASK | Gdk::EXPOSURE_MASK);
240
241         low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
242         low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
243         if (au_view) {
244                 low_box.signal_size_request ().connect (mem_fun (this, &AUPluginUI::lower_box_size_request));
245                 low_box.signal_size_allocate ().connect (mem_fun (this, &AUPluginUI::lower_box_size_allocate));
246                 low_box.signal_map ().connect (mem_fun (this, &AUPluginUI::lower_box_map));
247                 low_box.signal_unmap ().connect (mem_fun (this, &AUPluginUI::lower_box_unmap));
248                 low_box.signal_expose_event ().connect (mem_fun (this, &AUPluginUI::lower_box_expose));
249         }
250 }
251
252 AUPluginUI::~AUPluginUI ()
253 {
254         if (_notify) {
255                 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
256         }
257
258         if (cocoa_parent) {
259                 NSWindow* win = get_nswindow();
260                 [win removeChildWindow:cocoa_parent];
261         }
262
263 #ifdef WITH_CARBON
264         if (carbon_window) {
265                 /* not parented, just overlaid on top of our window */
266                 DisposeWindow (carbon_window);
267         }
268 #endif
269
270         if (editView) {
271                 ArdourCloseComponent (editView);
272         }
273
274         if (au_view) {
275                 /* remove whatever we packed into low_box so that GTK doesn't
276                    mess with it.
277                  */
278
279                 [au_view removeFromSuperview];
280         }
281 }
282
283 bool
284 AUPluginUI::test_carbon_view_support ()
285 {
286 #ifdef WITH_CARBON
287         bool ret = false;
288
289         carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
290         carbon_descriptor.componentSubType = 'gnrc';
291         carbon_descriptor.componentManufacturer = 'appl';
292         carbon_descriptor.componentFlags = 0;
293         carbon_descriptor.componentFlagsMask = 0;
294
295         OSStatus err;
296
297         // ask the AU for its first editor component
298         UInt32 propertySize;
299         err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
300         if (!err) {
301                 int nEditors = propertySize / sizeof(ComponentDescription);
302                 ComponentDescription *editors = new ComponentDescription[nEditors];
303                 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
304                 if (!err) {
305                         // just pick the first one for now
306                         carbon_descriptor = editors[0];
307                         ret = true;
308                 }
309                 delete[] editors;
310         }
311
312         return ret;
313 #else
314         return false;
315 #endif
316 }
317
318 bool
319 AUPluginUI::test_cocoa_view_support ()
320 {
321         UInt32 dataSize   = 0;
322         Boolean isWritable = 0;
323         OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
324                                                 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
325                                                 0, &dataSize, &isWritable);
326
327         return dataSize > 0 && err == noErr;
328 }
329
330 bool
331 AUPluginUI::plugin_class_valid (Class pluginClass)
332 {
333         if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
334                 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
335                    [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
336                                 return true;
337                 }
338         }
339         return false;
340 }
341
342 int
343 AUPluginUI::create_cocoa_view ()
344 {
345         bool wasAbleToLoadCustomView = false;
346         AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
347         UInt32               numberOfClasses = 0;
348         UInt32     dataSize;
349         Boolean    isWritable;
350         NSString*           factoryClassName = 0;
351         NSURL*              CocoaViewBundlePath = NULL;
352
353         OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
354                                                     kAudioUnitProperty_CocoaUI,
355                                                     kAudioUnitScope_Global,
356                                                     0,
357                                                     &dataSize,
358                                                     &isWritable );
359
360         numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
361
362         // Does view have custom Cocoa UI?
363
364         if ((result == noErr) && (numberOfClasses > 0) ) {
365
366                 DEBUG_TRACE(DEBUG::AudioUnits,
367                             string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
368
369                 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
370
371                 if(AudioUnitGetProperty(*au->get_au(),
372                                         kAudioUnitProperty_CocoaUI,
373                                         kAudioUnitScope_Global,
374                                         0,
375                                         cocoaViewInfo,
376                                         &dataSize) == noErr) {
377
378                         CocoaViewBundlePath     = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
379
380                         // we only take the first view in this example.
381                         factoryClassName        = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
382
383                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
384                                                                         [factoryClassName UTF8String], CocoaViewBundlePath));
385
386                 } else {
387
388                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
389
390                         if (cocoaViewInfo != NULL) {
391                                 free (cocoaViewInfo);
392                                 cocoaViewInfo = NULL;
393                         }
394                 }
395         }
396
397         // [A] Show custom UI if view has it
398
399         if (CocoaViewBundlePath && factoryClassName) {
400                 NSBundle *viewBundle    = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
401
402                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
403
404                 if (viewBundle == NULL) {
405                         error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
406                         return -1;
407                 } else {
408                         Class factoryClass = [viewBundle classNamed:factoryClassName];
409                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
410                         if (!factoryClass) {
411                                 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
412                                 return -1;
413                         }
414
415                         // make sure 'factoryClass' implements the AUCocoaUIBase protocol
416                         if (!plugin_class_valid (factoryClass)) {
417                                 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
418                                 return -1;
419                         }
420                         // make a factory
421
422 #if 0 // TODO: release when destoying the UI  -- Test workaround for IK Multimedia crash
423
424                         /* 0   Philharmonik 2                      0x000000011df4ebd4 IK::Products::STShell::GUIData::SetListener(IK::Products::STShell::GUIDataListener*) + 4
425                                  1   Philharmonik 2                      0x000000011df4a4f8 IK::Products::STShell::GUI::CloseWindow() + 24
426                                  2   com.ikmultimedia.audiounit.Philharmonik2    0x00000001164cef3a -[Philharmonik2CocoaView dealloc] + 26
427                                  3   libobjc.A.dylib                     0x00007fff8f63a89c objc_object::sidetable_release(bool) + 236
428                                  4   libobjc.A.dylib                     0x00007fff8f620e8f (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 575
429                                  5   com.apple.CoreFoundation            0x00007fff8500f6f2 _CFAutoreleasePoolPop + 50
430                                  6   com.apple.Foundation                0x00007fff81729832 -[NSAutoreleasePool drain] + 153
431                                  7   libgdk-quartz-2.0.0.dylib           0x00000001068c5bac gdk_event_prepare + 140
432                                  8   libglib-2.0.0.dylib                 0x0000000106255085 g_main_context_prepare + 405
433                                  9   libglib-2.0.0.dylib                 0x0000000106255977 g_main_context_iterate + 119
434                                  10  libglib-2.0.0.dylib                 0x0000000106255c75 g_main_loop_run + 261
435                                  11  libgtk-quartz-2.0.0.dylib           0x0000000106472dd0 gtk_main + 176
436                                  12  libgtkmm2ext.dylib                  0x000000010374191d Gtkmm2ext::UI::run(Receiver&) + 385
437                                  13  Ardour.bin                          0x000000010054b424 main + 1914
438                                  14  Ardour.bin                          0x00000001000309bc start + 52
439                          */
440                         id factory = [[[factoryClass alloc] init] autorelease];
441 #else
442                         id factory = [[factoryClass alloc] init];
443 #endif
444                         if (factory == NULL) {
445                                 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
446                                 return -1;
447                         }
448
449                         DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
450
451                         // make a view
452                         au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
453
454                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
455
456                         // cleanup
457                         [CocoaViewBundlePath release];
458                         if (cocoaViewInfo) {
459                                 UInt32 i;
460                                 for (i = 0; i < numberOfClasses; i++)
461                                         CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
462
463                                 free (cocoaViewInfo);
464                         }
465                         wasAbleToLoadCustomView = true;
466                 }
467         }
468
469         if (!wasAbleToLoadCustomView) {
470                 // load generic Cocoa view
471                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
472                                                                 au->get_au()));
473                 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
474                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
475                 [(AUGenericView *)au_view setShowsExpertParameters:1];
476         }
477
478         // Get the initial size of the new AU View's frame
479         NSRect  frame = [au_view frame];
480         min_width  = req_width  = CGRectGetWidth(NSRectToCGRect(frame));
481         min_height = req_height = CGRectGetHeight(NSRectToCGRect(frame));
482         resizable  = [au_view autoresizingMask];
483
484         low_box.queue_resize ();
485
486         return 0;
487 }
488
489 void
490 AUPluginUI::cocoa_view_resized ()
491 {
492         if (!mapped || alo_width == 0 || alo_height == 0 || !resizable) {
493                 return;
494         }
495         /* check for self-resizing plugins (e.g expand settings in AUSampler)
496          * if the widget expands it moves its y-offset (cocoa y-axis points towards the top)
497          */
498         NSRect new_au_frame = [au_view frame];
499
500         //float dx = last_au_frame.origin.x - new_au_frame.origin.x;
501         float dy = last_au_frame.origin.y - new_au_frame.origin.y;
502         //req_width += dx;
503         req_height += dy;
504         if (req_width < min_width) req_width = min_width;
505         if (req_height < min_height) req_height = min_height;
506
507         last_au_frame = new_au_frame;
508         low_box.queue_resize ();
509 }
510
511 int
512 AUPluginUI::create_carbon_view ()
513 {
514 #ifdef WITH_CARBON
515         OSStatus err;
516         ControlRef root_control;
517
518         Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
519
520         OpenAComponent(editComponent, &editView);
521         if (!editView) {
522                 error << _("AU Carbon view: cannot open AU Component") << endmsg;
523                 return -1;
524         }
525
526         Rect r = { 100, 100, 100, 100 };
527         WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
528                                                   kWindowCompositingAttribute|
529                                                   kWindowNoShadowAttribute|
530                                                   kWindowNoTitleBarAttribute);
531
532         if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
533                 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
534                 ArdourCloseComponent (editView);
535                 return -1;
536         }
537
538         if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
539                 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
540                 DisposeWindow (carbon_window);
541                 ArdourCloseComponent (editView);
542                 return -1;
543         }
544
545         ControlRef viewPane;
546         Float32Point location  = { 0.0, 0.0 };
547         Float32Point size = { 0.0, 0.0 } ;
548
549         if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
550                 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
551                 DisposeWindow (carbon_window);
552                 ArdourCloseComponent (editView);
553                 return -1;
554         }
555
556         // resize window
557
558         Rect bounds;
559         GetControlBounds(viewPane, &bounds);
560         size.x = bounds.right-bounds.left;
561         size.y = bounds.bottom-bounds.top;
562
563         req_width = (int) (size.x + 0.5);
564         req_height = (int) (size.y + 0.5);
565
566         SizeWindow (carbon_window, req_width, req_height,  true);
567         low_box.set_size_request (req_width, req_height);
568
569         return 0;
570 #else
571         error << _("AU Carbon GUI is not supported.") << endmsg;
572         return -1;
573 #endif
574 }
575
576 NSWindow*
577 AUPluginUI::get_nswindow ()
578 {
579         Gtk::Container* toplevel = get_toplevel();
580
581         if (!toplevel || !toplevel->is_toplevel()) {
582                 error << _("AUPluginUI: no top level window!") << endmsg;
583                 return 0;
584         }
585
586         NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
587
588         if (!true_parent) {
589                 error << _("AUPluginUI: no top level window!") << endmsg;
590                 return 0;
591         }
592
593         return true_parent;
594 }
595
596 void
597 AUPluginUI::activate ()
598 {
599 #ifdef WITH_CARBON
600         ActivateWindow (carbon_window, TRUE);
601 #endif
602 }
603
604 void
605 AUPluginUI::deactivate ()
606 {
607 #ifdef WITH_CARBON
608         ActivateWindow (carbon_window, FALSE);
609 #endif
610 }
611
612 int
613 AUPluginUI::parent_carbon_window ()
614 {
615 #ifdef WITH_CARBON
616         NSWindow* win = get_nswindow ();
617         Rect windowStructureBoundsRect;
618
619         if (!win) {
620                 return -1;
621         }
622
623         /* figure out where the cocoa parent window is in carbon-coordinate space, which
624            differs from both cocoa-coordinate space and GTK-coordinate space
625         */
626
627         GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
628
629         /* compute how tall the title bar is, because we have to offset the position of the carbon window
630            by that much.
631         */
632
633         NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
634         NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
635
636         int titlebar_height = wm_frame.size.height - content_frame.size.height;
637
638         int packing_extra = 6; // this is the total vertical packing in our top level window
639
640         /* move into position, based on parent window position */
641         MoveWindow (carbon_window,
642                     windowStructureBoundsRect.left,
643                     windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
644                     false);
645         ShowWindow (carbon_window);
646
647         // create the cocoa window for the carbon one and make it visible
648         cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
649
650         SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
651
652         _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
653
654         [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
655         [win setAutodisplay:1]; // turn of GTK stuff for this window
656
657         return 0;
658 #else
659         return -1;
660 #endif
661 }
662
663 int
664 AUPluginUI::parent_cocoa_window ()
665 {
666         NSWindow* win = get_nswindow ();
667
668         if (!win) {
669                 return -1;
670         }
671
672         //[win setAutodisplay:1]; // turn off GTK stuff for this window
673
674         NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
675         [view addSubview:au_view];
676
677         gint xx, yy;
678         gtk_widget_translate_coordinates(
679                         GTK_WIDGET(low_box.gobj()),
680                         GTK_WIDGET(low_box.get_parent()->gobj()),
681                         8, 6, &xx, &yy);
682         [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
683
684         last_au_frame = [au_view frame];
685         // watch for size changes of the view
686         _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
687
688         [[NSNotificationCenter defaultCenter] addObserver:_notify
689                 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
690                 object:au_view];
691
692         return 0;
693 }
694
695 void
696 AUPluginUI::grab_focus()
697 {
698         if (au_view) {
699                 [au_view becomeFirstResponder];
700         }
701 }
702 void
703 AUPluginUI::forward_key_event (GdkEventKey* ev)
704 {
705         NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
706
707         if (au_view && nsevent) {
708
709                 /* filter on nsevent type here because GDK massages FlagsChanged
710                    messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
711                    handle a FlagsChanged message as a keyDown or keyUp
712                 */
713
714                 if ([nsevent type] == NSKeyDown) {
715                         [[[au_view window] firstResponder] keyDown:nsevent];
716                 } else if ([nsevent type] == NSKeyUp) {
717                         [[[au_view window] firstResponder] keyUp:nsevent];
718                 } else if ([nsevent type] == NSFlagsChanged) {
719                         [[[au_view window] firstResponder] flagsChanged:nsevent];
720                 }
721         }
722 }
723
724 void
725 AUPluginUI::on_realize ()
726 {
727         VBox::on_realize ();
728
729         /* our windows should not have that resize indicator */
730
731         NSWindow* win = get_nswindow ();
732         if (win) {
733                 [win setShowsResizeIndicator:0];
734         }
735 }
736
737 void
738 AUPluginUI::lower_box_realized ()
739 {
740         if (au_view) {
741                 parent_cocoa_window ();
742         } else if (carbon_window) {
743                 parent_carbon_window ();
744         }
745 }
746
747 bool
748 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
749 {
750 #ifdef WITH_CARBON
751         if (carbon_window  && ev->state != GDK_VISIBILITY_UNOBSCURED) {
752                 ShowWindow (carbon_window);
753                 ActivateWindow (carbon_window, TRUE);
754                 return true;
755         }
756 #endif
757         return false;
758 }
759
760 void
761 AUPluginUI::update_view_size ()
762 {
763         if (!mapped || alo_width == 0 || alo_height == 0) {
764                 return;
765         }
766         gint xx, yy;
767         gtk_widget_translate_coordinates(
768                         GTK_WIDGET(low_box.gobj()),
769                         GTK_WIDGET(low_box.get_parent()->gobj()),
770                         8, 6, &xx, &yy);
771
772         [[NSNotificationCenter defaultCenter] removeObserver:_notify
773                 name:NSViewFrameDidChangeNotification
774                 object:au_view];
775
776         if (!resizable) {
777                 xx += (alo_width - req_width) * .5;
778                 [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
779         } else {
780                 /* this mitigates issues with plugins that resize themselves
781                  * depending on visible options (e.g AUSampler)
782                  * since the OSX y-axis points upwards, the plugin adjusts its
783                  * own y-offset if the view expands to the bottom to accomodate
784                  * subviews inside the main view.
785                  */
786                 [au_view setAutoresizesSubviews:0];
787                 [au_view setFrame:NSMakeRect(xx, yy, alo_width, alo_height)];
788                 [au_view setAutoresizesSubviews:1];
789                 [au_view setNeedsDisplay:1];
790         }
791
792         last_au_frame = [au_view frame];
793
794         [[NSNotificationCenter defaultCenter]
795              addObserver:_notify
796                 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
797                   object:au_view];
798 }
799
800 void
801 AUPluginUI::lower_box_map ()
802 {
803         mapped = true;
804         [au_view setHidden:0];
805         update_view_size ();
806 }
807
808 void
809 AUPluginUI::lower_box_unmap ()
810 {
811         mapped = false;
812         [au_view setHidden:1];
813 }
814
815 void
816 AUPluginUI::lower_box_size_request (GtkRequisition* requisition)
817 {
818         requisition->width  = req_width;
819         requisition->height = req_height;
820 }
821
822 void
823 AUPluginUI::lower_box_size_allocate (Gtk::Allocation& allocation)
824 {
825         alo_width  = allocation.get_width ();
826         alo_height = allocation.get_height ();
827         update_view_size ();
828 }
829
830 gboolean
831 AUPluginUI::lower_box_expose (GdkEventExpose* event)
832 {
833 #if 0 // AU view magically redraws by itself
834         [au_view drawRect:NSMakeRect(event->area.x,
835                         event->area.y,
836                         event->area.width,
837                         event->area.height)];
838 #endif
839         /* hack to keep ardour responsive
840          * some UIs (e.g Addictive Drums) completely hog the CPU
841          */
842         ARDOUR::GUIIdle();
843
844         return true;
845 }
846
847 void
848 AUPluginUI::on_window_hide ()
849 {
850 #ifdef WITH_CARBON
851         if (carbon_window) {
852                 HideWindow (carbon_window);
853                 ActivateWindow (carbon_window, FALSE);
854         }
855 #endif
856         hide_all ();
857
858 #if 0
859         NSArray* wins = [NSApp windows];
860         for (uint32_t i = 0; i < [wins count]; i++) {
861                 id win = [wins objectAtIndex:i];
862         }
863 #endif
864 }
865
866 bool
867 AUPluginUI::on_window_show (const string& /*title*/)
868 {
869         /* this is idempotent so just call it every time we show the window */
870
871         gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
872
873         show_all ();
874
875 #ifdef WITH_CARBON
876         if (carbon_window) {
877                 ShowWindow (carbon_window);
878                 ActivateWindow (carbon_window, TRUE);
879         }
880 #endif
881
882         return true;
883 }
884
885 bool
886 AUPluginUI::start_updating (GdkEventAny*)
887 {
888         return false;
889 }
890
891 bool
892 AUPluginUI::stop_updating (GdkEventAny*)
893 {
894         return false;
895 }
896
897 PlugUIBase*
898 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
899 {
900         AUPluginUI* aup = new AUPluginUI (plugin_insert);
901         (*box) = aup;
902         return aup;
903 }
904
905