af174f9963383fd56afe3e652956d9be30387380
[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                         id factory = [[[factoryClass alloc] init] autorelease];
422                         if (factory == NULL) {
423                                 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
424                                 return -1;
425                         }
426
427                         DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
428
429                         // make a view
430                         au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
431
432                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
433
434                         // cleanup
435                         [CocoaViewBundlePath release];
436                         if (cocoaViewInfo) {
437                                 UInt32 i;
438                                 for (i = 0; i < numberOfClasses; i++)
439                                         CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
440
441                                 free (cocoaViewInfo);
442                         }
443                         wasAbleToLoadCustomView = true;
444                 }
445         }
446
447         if (!wasAbleToLoadCustomView) {
448                 // load generic Cocoa view
449                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
450                                                                 au->get_au()));
451                 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
452                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
453                 [(AUGenericView *)au_view setShowsExpertParameters:1];
454         }
455
456         // Get the initial size of the new AU View's frame
457         NSRect  frame = [au_view frame];
458         min_width  = req_width  = CGRectGetWidth(NSRectToCGRect(frame));
459         min_height = req_height = CGRectGetHeight(NSRectToCGRect(frame));
460         resizable  = [au_view autoresizingMask];
461
462         low_box.queue_resize ();
463
464         return 0;
465 }
466
467 void
468 AUPluginUI::cocoa_view_resized ()
469 {
470         if (!mapped || alo_width == 0 || alo_height == 0 || !resizable) {
471                 return;
472         }
473         /* check for self-resizing plugins (e.g expand settings in AUSampler)
474          * if the widget expands it moves its y-offset (cocoa y-axis points towards the top)
475          */
476         NSRect new_au_frame = [au_view frame];
477
478         //float dx = last_au_frame.origin.x - new_au_frame.origin.x;
479         float dy = last_au_frame.origin.y - new_au_frame.origin.y;
480         //req_width += dx;
481         req_height += dy;
482         if (req_width < min_width) req_width = min_width;
483         if (req_height < min_height) req_height = min_height;
484
485         last_au_frame = new_au_frame;
486         low_box.queue_resize ();
487 }
488
489 int
490 AUPluginUI::create_carbon_view ()
491 {
492 #ifdef WITH_CARBON
493         OSStatus err;
494         ControlRef root_control;
495
496         Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
497
498         OpenAComponent(editComponent, &editView);
499         if (!editView) {
500                 error << _("AU Carbon view: cannot open AU Component") << endmsg;
501                 return -1;
502         }
503
504         Rect r = { 100, 100, 100, 100 };
505         WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
506                                                   kWindowCompositingAttribute|
507                                                   kWindowNoShadowAttribute|
508                                                   kWindowNoTitleBarAttribute);
509
510         if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
511                 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
512                 ArdourCloseComponent (editView);
513                 return -1;
514         }
515
516         if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
517                 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
518                 DisposeWindow (carbon_window);
519                 ArdourCloseComponent (editView);
520                 return -1;
521         }
522
523         ControlRef viewPane;
524         Float32Point location  = { 0.0, 0.0 };
525         Float32Point size = { 0.0, 0.0 } ;
526
527         if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
528                 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
529                 DisposeWindow (carbon_window);
530                 ArdourCloseComponent (editView);
531                 return -1;
532         }
533
534         // resize window
535
536         Rect bounds;
537         GetControlBounds(viewPane, &bounds);
538         size.x = bounds.right-bounds.left;
539         size.y = bounds.bottom-bounds.top;
540
541         req_width = (int) (size.x + 0.5);
542         req_height = (int) (size.y + 0.5);
543
544         SizeWindow (carbon_window, req_width, req_height,  true);
545         low_box.set_size_request (req_width, req_height);
546
547         return 0;
548 #else
549         error << _("AU Carbon GUI is not supported.") << endmsg;
550         return -1;
551 #endif
552 }
553
554 NSWindow*
555 AUPluginUI::get_nswindow ()
556 {
557         Gtk::Container* toplevel = get_toplevel();
558
559         if (!toplevel || !toplevel->is_toplevel()) {
560                 error << _("AUPluginUI: no top level window!") << endmsg;
561                 return 0;
562         }
563
564         NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
565
566         if (!true_parent) {
567                 error << _("AUPluginUI: no top level window!") << endmsg;
568                 return 0;
569         }
570
571         return true_parent;
572 }
573
574 void
575 AUPluginUI::activate ()
576 {
577 #ifdef WITH_CARBON
578         ActivateWindow (carbon_window, TRUE);
579 #endif
580 }
581
582 void
583 AUPluginUI::deactivate ()
584 {
585 #ifdef WITH_CARBON
586         ActivateWindow (carbon_window, FALSE);
587 #endif
588 }
589
590 int
591 AUPluginUI::parent_carbon_window ()
592 {
593 #ifdef WITH_CARBON
594         NSWindow* win = get_nswindow ();
595         Rect windowStructureBoundsRect;
596
597         if (!win) {
598                 return -1;
599         }
600
601         /* figure out where the cocoa parent window is in carbon-coordinate space, which
602            differs from both cocoa-coordinate space and GTK-coordinate space
603         */
604
605         GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
606
607         /* compute how tall the title bar is, because we have to offset the position of the carbon window
608            by that much.
609         */
610
611         NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
612         NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
613
614         int titlebar_height = wm_frame.size.height - content_frame.size.height;
615
616         int packing_extra = 6; // this is the total vertical packing in our top level window
617
618         /* move into position, based on parent window position */
619         MoveWindow (carbon_window,
620                     windowStructureBoundsRect.left,
621                     windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
622                     false);
623         ShowWindow (carbon_window);
624
625         // create the cocoa window for the carbon one and make it visible
626         cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
627
628         SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
629
630         _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
631
632         [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
633         [win setAutodisplay:1]; // turn of GTK stuff for this window
634
635         return 0;
636 #else
637         return -1;
638 #endif
639 }
640
641 int
642 AUPluginUI::parent_cocoa_window ()
643 {
644         NSWindow* win = get_nswindow ();
645
646         if (!win) {
647                 return -1;
648         }
649
650         //[win setAutodisplay:1]; // turn off GTK stuff for this window
651
652         NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
653         [view addSubview:au_view];
654
655         gint xx, yy;
656         gtk_widget_translate_coordinates(
657                         GTK_WIDGET(low_box.gobj()),
658                         GTK_WIDGET(low_box.get_parent()->gobj()),
659                         8, 6, &xx, &yy);
660         [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
661
662         last_au_frame = [au_view frame];
663         // watch for size changes of the view
664         _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
665
666         [[NSNotificationCenter defaultCenter] addObserver:_notify
667                 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
668                 object:au_view];
669
670         return 0;
671 }
672
673 void
674 AUPluginUI::grab_focus()
675 {
676         if (au_view) {
677                 [au_view becomeFirstResponder];
678         }
679 }
680 void
681 AUPluginUI::forward_key_event (GdkEventKey* ev)
682 {
683         NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
684
685         if (au_view && nsevent) {
686
687                 /* filter on nsevent type here because GDK massages FlagsChanged
688                    messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
689                    handle a FlagsChanged message as a keyDown or keyUp
690                 */
691
692                 if ([nsevent type] == NSKeyDown) {
693                         [[[au_view window] firstResponder] keyDown:nsevent];
694                 } else if ([nsevent type] == NSKeyUp) {
695                         [[[au_view window] firstResponder] keyUp:nsevent];
696                 } else if ([nsevent type] == NSFlagsChanged) {
697                         [[[au_view window] firstResponder] flagsChanged:nsevent];
698                 }
699         }
700 }
701
702 void
703 AUPluginUI::on_realize ()
704 {
705         VBox::on_realize ();
706
707         /* our windows should not have that resize indicator */
708
709         NSWindow* win = get_nswindow ();
710         if (win) {
711                 [win setShowsResizeIndicator:0];
712         }
713 }
714
715 void
716 AUPluginUI::lower_box_realized ()
717 {
718         if (au_view) {
719                 parent_cocoa_window ();
720         } else if (carbon_window) {
721                 parent_carbon_window ();
722         }
723 }
724
725 bool
726 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
727 {
728 #ifdef WITH_CARBON
729         if (carbon_window  && ev->state != GDK_VISIBILITY_UNOBSCURED) {
730                 ShowWindow (carbon_window);
731                 ActivateWindow (carbon_window, TRUE);
732                 return true;
733         }
734 #endif
735         return false;
736 }
737
738 void
739 AUPluginUI::update_view_size ()
740 {
741         if (!mapped || alo_width == 0 || alo_height == 0) {
742                 return;
743         }
744         gint xx, yy;
745         gtk_widget_translate_coordinates(
746                         GTK_WIDGET(low_box.gobj()),
747                         GTK_WIDGET(low_box.get_parent()->gobj()),
748                         8, 6, &xx, &yy);
749
750         [[NSNotificationCenter defaultCenter] removeObserver:_notify
751                 name:NSViewFrameDidChangeNotification
752                 object:au_view];
753
754         if (!resizable) {
755                 xx += (alo_width - req_width) * .5;
756                 [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
757         } else {
758                 /* this mitigates issues with plugins that resize themselves
759                  * depending on visible options (e.g AUSampler)
760                  * since the OSX y-axis points upwards, the plugin adjusts its
761                  * own y-offset if the view expands to the bottom to accomodate
762                  * subviews inside the main view.
763                  */
764                 [au_view setAutoresizesSubviews:0];
765                 [au_view setFrame:NSMakeRect(xx, yy, alo_width, alo_height)];
766                 [au_view setAutoresizesSubviews:1];
767                 [au_view setNeedsDisplay:1];
768         }
769
770         last_au_frame = [au_view frame];
771
772         [[NSNotificationCenter defaultCenter]
773              addObserver:_notify
774                 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
775                   object:au_view];
776 }
777
778 void
779 AUPluginUI::lower_box_map ()
780 {
781         mapped = true;
782         [au_view setHidden:0];
783         update_view_size ();
784 }
785
786 void
787 AUPluginUI::lower_box_unmap ()
788 {
789         mapped = false;
790         [au_view setHidden:1];
791 }
792
793 void
794 AUPluginUI::lower_box_size_request (GtkRequisition* requisition)
795 {
796         requisition->width  = req_width;
797         requisition->height = req_height;
798 }
799
800 void
801 AUPluginUI::lower_box_size_allocate (Gtk::Allocation& allocation)
802 {
803         alo_width  = allocation.get_width ();
804         alo_height = allocation.get_height ();
805         update_view_size ();
806 }
807
808 gboolean
809 AUPluginUI::lower_box_expose (GdkEventExpose* event)
810 {
811 #if 0 // AU view magically redraws by itself
812         [au_view drawRect:NSMakeRect(event->area.x,
813                         event->area.y,
814                         event->area.width,
815                         event->area.height)];
816 #endif
817         /* hack to keep ardour responsive
818          * some UIs (e.g Addictive Drums) completely hog the CPU
819          */
820         ARDOUR::GUIIdle();
821
822         return true;
823 }
824
825 void
826 AUPluginUI::on_window_hide ()
827 {
828 #ifdef WITH_CARBON
829         if (carbon_window) {
830                 HideWindow (carbon_window);
831                 ActivateWindow (carbon_window, FALSE);
832         }
833 #endif
834         hide_all ();
835
836 #if 0
837         NSArray* wins = [NSApp windows];
838         for (uint32_t i = 0; i < [wins count]; i++) {
839                 id win = [wins objectAtIndex:i];
840         }
841 #endif
842 }
843
844 bool
845 AUPluginUI::on_window_show (const string& /*title*/)
846 {
847         /* this is idempotent so just call it every time we show the window */
848
849         gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
850
851         show_all ();
852
853 #ifdef WITH_CARBON
854         if (carbon_window) {
855                 ShowWindow (carbon_window);
856                 ActivateWindow (carbon_window, TRUE);
857         }
858 #endif
859
860         return true;
861 }
862
863 bool
864 AUPluginUI::start_updating (GdkEventAny*)
865 {
866         return false;
867 }
868
869 bool
870 AUPluginUI::stop_updating (GdkEventAny*)
871 {
872         return false;
873 }
874
875 PlugUIBase*
876 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
877 {
878         AUPluginUI* aup = new AUPluginUI (plugin_insert);
879         (*box) = aup;
880         return aup;
881 }
882
883