GUI changes use new AppleUtility Library
[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 using namespace ARDOUR;
35 using namespace Gtk;
36 using namespace Gtkmm2ext;
37 using namespace std;
38 using namespace PBD;
39
40 vector<string> AUPluginUI::automation_mode_strings;
41
42 static const gchar* _automation_mode_strings[] = {
43         X_("Manual"),
44         X_("Play"),
45         X_("Write"),
46         X_("Touch"),
47         0
48 };
49
50 static void
51 dump_view_tree (NSView* view, int depth, int maxdepth)
52 {
53         NSArray* subviews = [view subviews];
54         unsigned long cnt = [subviews count];
55
56         for (int d = 0; d < depth; d++) {
57                 cerr << '\t';
58         }
59         NSRect frame = [view frame];
60         cerr << " view @ " <<  frame.origin.x << ", " << frame.origin.y
61                 << ' ' << frame.size.width << " x " << frame.size.height
62                 << endl;
63
64         if (depth >= maxdepth) {
65                 return;
66         }
67         for (unsigned long i = 0; i < cnt; ++i) {
68                 NSView* subview = [subviews objectAtIndex:i];
69                 dump_view_tree (subview, depth+1, maxdepth);
70         }
71 }
72
73 @implementation NotificationObject
74
75 - (NotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui andCocoaParent: (NSWindow*) cp andTopLevelParent: (NSWindow*) tlp
76 {
77         self = [ super init ];
78
79         if (self) {
80                 plugin_ui = apluginui;
81                 top_level_parent = tlp;
82
83                 if (cp) {
84                         cocoa_parent = cp;
85
86                         [[NSNotificationCenter defaultCenter]
87                              addObserver:self
88                                 selector:@selector(cocoaParentActivationHandler:)
89                                     name:NSWindowDidBecomeMainNotification
90                                   object:NULL];
91
92                         [[NSNotificationCenter defaultCenter]
93                              addObserver:self
94                                 selector:@selector(cocoaParentBecameKeyHandler:)
95                                     name:NSWindowDidBecomeKeyNotification
96                                   object:NULL];
97                 }
98         }
99
100         return self;
101 }
102
103 - (void)cocoaParentActivationHandler:(NSNotification *)notification
104 {
105         NSWindow* notification_window = (NSWindow *)[notification object];
106
107         if (top_level_parent == notification_window || cocoa_parent == notification_window) {
108                 if ([notification_window isMainWindow]) {
109                         plugin_ui->activate();
110                 } else {
111                         plugin_ui->deactivate();
112                 }
113         }
114 }
115
116 - (void)cocoaParentBecameKeyHandler:(NSNotification *)notification
117 {
118         NSWindow* notification_window = (NSWindow *)[notification object];
119
120         if (top_level_parent == notification_window || cocoa_parent == notification_window) {
121                 if ([notification_window isKeyWindow]) {
122                         plugin_ui->activate();
123                 } else {
124                         plugin_ui->deactivate();
125                 }
126         }
127 }
128
129 - (void)auViewResized:(NSNotification *)notification
130 {
131         (void) notification; // stop complaints about unusued argument
132         plugin_ui->cocoa_view_resized();
133 }
134
135 @end
136
137 AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
138         : PlugUIBase (insert)
139         , automation_mode_label (_("Automation"))
140         , preset_label (_("Presets"))
141         , mapped (false)
142         , resizable (false)
143         , min_width (0)
144         , min_height (0)
145         , req_width (0)
146         , req_height (0)
147         , alo_width (0)
148         , alo_height (0)
149
150 {
151         if (automation_mode_strings.empty()) {
152                 automation_mode_strings = I18N (_automation_mode_strings);
153         }
154
155         set_popdown_strings (automation_mode_selector, automation_mode_strings);
156         automation_mode_selector.set_active_text (automation_mode_strings.front());
157
158         if ((au = boost::dynamic_pointer_cast<AUPlugin> (insert->plugin())) == 0) {
159                 error << _("unknown type of editor-supplying plugin (note: no AudioUnit support in this version of ardour)") << endmsg;
160                 throw failed_constructor ();
161         }
162
163         /* stuff some stuff into the top of the window */
164
165         HBox* smaller_hbox = manage (new HBox);
166
167         smaller_hbox->set_spacing (6);
168         smaller_hbox->pack_start (preset_label, false, false, 4);
169         smaller_hbox->pack_start (_preset_modified, false, false);
170         smaller_hbox->pack_start (_preset_combo, false, false);
171         smaller_hbox->pack_start (add_button, false, false);
172 #if 0
173         /* Ardour does not currently allow to overwrite existing presets
174          * see save_property_list() in audio_unit.cc
175          */
176         smaller_hbox->pack_start (save_button, false, false);
177 #endif
178 #if 0
179         /* one day these might be useful with an AU plugin, but not yet */
180         smaller_hbox->pack_start (automation_mode_label, false, false);
181         smaller_hbox->pack_start (automation_mode_selector, false, false);
182 #endif
183         smaller_hbox->pack_start (reset_button, false, false);
184         smaller_hbox->pack_start (bypass_button, false, true);
185
186         VBox* v1_box = manage (new VBox);
187         VBox* v2_box = manage (new VBox);
188
189         v1_box->pack_start (*smaller_hbox, false, true);
190         v2_box->pack_start (focus_button, false, true);
191
192         top_box.set_homogeneous (false);
193         top_box.set_spacing (6);
194         top_box.set_border_width (6);
195
196         top_box.pack_end (*v2_box, false, false);
197         top_box.pack_end (*v1_box, false, false);
198
199         set_spacing (6);
200         pack_start (top_box, false, false);
201         pack_start (low_box, true, true);
202
203         preset_label.show ();
204         _preset_combo.show ();
205         automation_mode_label.show ();
206         automation_mode_selector.show ();
207         bypass_button.show ();
208         top_box.show ();
209         low_box.show ();
210
211         cocoa_parent = 0;
212         cocoa_window = 0;
213
214 #ifdef WITH_CARBON
215         _activating_from_app = false;
216         _notify = 0;
217         au_view = 0;
218         editView = 0;
219         carbon_window = 0;
220 #endif
221
222         /* prefer cocoa, fall back to cocoa, but use carbon if its there */
223
224         if (test_cocoa_view_support()) {
225                 create_cocoa_view ();
226 #ifdef WITH_CARBON
227         } else if (test_carbon_view_support()) {
228                 create_carbon_view ();
229 #endif
230         } else {
231                 create_cocoa_view ();
232         }
233
234         low_box.add_events (Gdk::VISIBILITY_NOTIFY_MASK | Gdk::EXPOSURE_MASK);
235
236         low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
237         low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
238         low_box.signal_size_request ().connect (mem_fun (this, &AUPluginUI::lower_box_size_request));
239         low_box.signal_size_allocate ().connect (mem_fun (this, &AUPluginUI::lower_box_size_allocate));
240         low_box.signal_map ().connect (mem_fun (this, &AUPluginUI::lower_box_map));
241         low_box.signal_unmap ().connect (mem_fun (this, &AUPluginUI::lower_box_unmap));
242         //low_box.signal_expose_event ().connect (mem_fun (this, &AUPluginUI::lower_box_expose));
243 }
244
245 AUPluginUI::~AUPluginUI ()
246 {
247         if (_notify) {
248                 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
249         }
250
251         if (cocoa_parent) {
252                 NSWindow* win = get_nswindow();
253                 [win removeChildWindow:cocoa_parent];
254         }
255
256 #ifdef WITH_CARBON
257         if (carbon_window) {
258                 /* not parented, just overlaid on top of our window */
259                 DisposeWindow (carbon_window);
260         }
261 #endif
262
263         if (editView) {
264                 CloseComponent (editView);
265         }
266
267         if (au_view) {
268                 /* remove whatever we packed into low_box so that GTK doesn't
269                    mess with it.
270                  */
271
272                 [au_view removeFromSuperview];
273         }
274 }
275
276 bool
277 AUPluginUI::test_carbon_view_support ()
278 {
279 #ifdef WITH_CARBON
280         bool ret = false;
281
282         carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
283         carbon_descriptor.componentSubType = 'gnrc';
284         carbon_descriptor.componentManufacturer = 'appl';
285         carbon_descriptor.componentFlags = 0;
286         carbon_descriptor.componentFlagsMask = 0;
287
288         OSStatus err;
289
290         // ask the AU for its first editor component
291         UInt32 propertySize;
292         err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
293         if (!err) {
294                 int nEditors = propertySize / sizeof(ComponentDescription);
295                 ComponentDescription *editors = new ComponentDescription[nEditors];
296                 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
297                 if (!err) {
298                         // just pick the first one for now
299                         carbon_descriptor = editors[0];
300                         ret = true;
301                 }
302                 delete[] editors;
303         }
304
305         return ret;
306 #else
307         return false;
308 #endif
309 }
310
311 bool
312 AUPluginUI::test_cocoa_view_support ()
313 {
314         UInt32 dataSize   = 0;
315         Boolean isWritable = 0;
316         OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
317                                                 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
318                                                 0, &dataSize, &isWritable);
319
320         return dataSize > 0 && err == noErr;
321 }
322
323 bool
324 AUPluginUI::plugin_class_valid (Class pluginClass)
325 {
326         if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
327                 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
328                    [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
329                                 return true;
330                 }
331         }
332         return false;
333 }
334
335 int
336 AUPluginUI::create_cocoa_view ()
337 {
338         bool wasAbleToLoadCustomView = false;
339         AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
340         UInt32               numberOfClasses = 0;
341         UInt32     dataSize;
342         Boolean    isWritable;
343         NSString*           factoryClassName = 0;
344         NSURL*              CocoaViewBundlePath = NULL;
345
346         OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
347                                                     kAudioUnitProperty_CocoaUI,
348                                                     kAudioUnitScope_Global,
349                                                     0,
350                                                     &dataSize,
351                                                     &isWritable );
352
353         numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
354
355         // Does view have custom Cocoa UI?
356
357         if ((result == noErr) && (numberOfClasses > 0) ) {
358
359                 DEBUG_TRACE(DEBUG::AudioUnits,
360                             string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
361
362                 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
363
364                 if(AudioUnitGetProperty(*au->get_au(),
365                                         kAudioUnitProperty_CocoaUI,
366                                         kAudioUnitScope_Global,
367                                         0,
368                                         cocoaViewInfo,
369                                         &dataSize) == noErr) {
370
371                         CocoaViewBundlePath     = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
372
373                         // we only take the first view in this example.
374                         factoryClassName        = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
375
376                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
377                                                                         [factoryClassName UTF8String], CocoaViewBundlePath));
378
379                 } else {
380
381                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
382
383                         if (cocoaViewInfo != NULL) {
384                                 free (cocoaViewInfo);
385                                 cocoaViewInfo = NULL;
386                         }
387                 }
388         }
389
390         // [A] Show custom UI if view has it
391
392         if (CocoaViewBundlePath && factoryClassName) {
393                 NSBundle *viewBundle    = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
394
395                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
396
397                 if (viewBundle == NULL) {
398                         error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
399                         return -1;
400                 } else {
401                         Class factoryClass = [viewBundle classNamed:factoryClassName];
402                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
403                         if (!factoryClass) {
404                                 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
405                                 return -1;
406                         }
407
408                         // make sure 'factoryClass' implements the AUCocoaUIBase protocol
409                         if (!plugin_class_valid (factoryClass)) {
410                                 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
411                                 return -1;
412                         }
413                         // make a factory
414                         id factory = [[[factoryClass alloc] init] autorelease];
415                         if (factory == NULL) {
416                                 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
417                                 return -1;
418                         }
419
420                         DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
421
422                         // make a view
423                         au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
424
425                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
426
427                         // cleanup
428                         [CocoaViewBundlePath release];
429                         if (cocoaViewInfo) {
430                                 UInt32 i;
431                                 for (i = 0; i < numberOfClasses; i++)
432                                         CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
433
434                                 free (cocoaViewInfo);
435                         }
436                         wasAbleToLoadCustomView = true;
437                 }
438         }
439
440         if (!wasAbleToLoadCustomView) {
441                 // load generic Cocoa view
442                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
443                                                                 au->get_au()));
444                 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
445                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
446                 [(AUGenericView *)au_view setShowsExpertParameters:1];
447         }
448
449         // Get the initial size of the new AU View's frame
450         NSRect  frame = [au_view frame];
451         min_width  = req_width  = CGRectGetWidth(NSRectToCGRect(frame));
452         min_height = req_height = CGRectGetHeight(NSRectToCGRect(frame));
453         resizable  = [au_view autoresizingMask];
454
455         low_box.queue_resize ();
456
457         return 0;
458 }
459
460 void
461 AUPluginUI::cocoa_view_resized ()
462 {
463         if (!mapped || alo_width == 0 || alo_height == 0 || !resizable) {
464                 return;
465         }
466         /* check for self-resizing plugins (e.g expand settings in AUSampler)
467          * if the widget expands it moves its y-offset (cocoa y-axis points towards the top)
468          */
469         NSRect new_au_frame = [au_view frame];
470
471         //float dx = last_au_frame.origin.x - new_au_frame.origin.x;
472         float dy = last_au_frame.origin.y - new_au_frame.origin.y;
473         //req_width += dx;
474         req_height += dy;
475         if (req_width < min_width) req_width = min_width;
476         if (req_height < min_height) req_height = min_height;
477
478         last_au_frame = new_au_frame;
479         low_box.queue_resize ();
480 }
481
482 int
483 AUPluginUI::create_carbon_view ()
484 {
485 #ifdef WITH_CARBON
486         OSStatus err;
487         ControlRef root_control;
488
489         Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
490
491         OpenAComponent(editComponent, &editView);
492         if (!editView) {
493                 error << _("AU Carbon view: cannot open AU Component") << endmsg;
494                 return -1;
495         }
496
497         Rect r = { 100, 100, 100, 100 };
498         WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
499                                                   kWindowCompositingAttribute|
500                                                   kWindowNoShadowAttribute|
501                                                   kWindowNoTitleBarAttribute);
502
503         if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
504                 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
505                 CloseComponent (editView);
506                 return -1;
507         }
508
509         if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
510                 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
511                 DisposeWindow (carbon_window);
512                 CloseComponent (editView);
513                 return -1;
514         }
515
516         ControlRef viewPane;
517         Float32Point location  = { 0.0, 0.0 };
518         Float32Point size = { 0.0, 0.0 } ;
519
520         if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
521                 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
522                 DisposeWindow (carbon_window);
523                 CloseComponent (editView);
524                 return -1;
525         }
526
527         // resize window
528
529         Rect bounds;
530         GetControlBounds(viewPane, &bounds);
531         size.x = bounds.right-bounds.left;
532         size.y = bounds.bottom-bounds.top;
533
534         req_width = (int) (size.x + 0.5);
535         req_height = (int) (size.y + 0.5);
536
537         SizeWindow (carbon_window, prefwidth, req_height,  true);
538         low_box.set_size_request (prefwidth, req_height); // ??
539
540         return 0;
541 #else
542         error << _("AU Carbon GUI is not supported.") << endmsg;
543         return -1;
544 #endif
545 }
546
547 NSWindow*
548 AUPluginUI::get_nswindow ()
549 {
550         Gtk::Container* toplevel = get_toplevel();
551
552         if (!toplevel || !toplevel->is_toplevel()) {
553                 error << _("AUPluginUI: no top level window!") << endmsg;
554                 return 0;
555         }
556
557         NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
558
559         if (!true_parent) {
560                 error << _("AUPluginUI: no top level window!") << endmsg;
561                 return 0;
562         }
563
564         return true_parent;
565 }
566
567 void
568 AUPluginUI::activate ()
569 {
570 #ifdef WITH_CARBON
571         ActivateWindow (carbon_window, TRUE);
572 #endif
573 }
574
575 void
576 AUPluginUI::deactivate ()
577 {
578 #ifdef WITH_CARBON
579         ActivateWindow (carbon_window, FALSE);
580 #endif
581 }
582
583 int
584 AUPluginUI::parent_carbon_window ()
585 {
586 #ifdef WITH_CARBON
587         NSWindow* win = get_nswindow ();
588         Rect windowStructureBoundsRect;
589
590         if (!win) {
591                 return -1;
592         }
593
594         Gtk::Container* toplevel = get_toplevel();
595
596         if (!toplevel || !toplevel->is_toplevel()) {
597                 error << _("AUPluginUI: no top level window!") << endmsg;
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         [au_view drawRect:NSMakeRect(event->area.x,
812                         event->area.y,
813                         event->area.width,
814                         event->area.height)];
815         return true;
816 }
817
818 void
819 AUPluginUI::on_window_hide ()
820 {
821 #ifdef WITH_CARBON
822         if (carbon_window) {
823                 HideWindow (carbon_window);
824                 ActivateWindow (carbon_window, FALSE);
825         }
826 #endif
827         hide_all ();
828
829 #if 0
830         NSArray* wins = [NSApp windows];
831         for (uint32_t i = 0; i < [wins count]; i++) {
832                 id win = [wins objectAtIndex:i];
833         }
834 #endif
835 }
836
837 bool
838 AUPluginUI::on_window_show (const string& /*title*/)
839 {
840         /* this is idempotent so just call it every time we show the window */
841
842         gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
843
844         show_all ();
845
846 #ifdef WITH_CARBON
847         if (carbon_window) {
848                 ShowWindow (carbon_window);
849                 ActivateWindow (carbon_window, TRUE);
850         }
851 #endif
852
853         return true;
854 }
855
856 bool
857 AUPluginUI::start_updating (GdkEventAny*)
858 {
859         return false;
860 }
861
862 bool
863 AUPluginUI::stop_updating (GdkEventAny*)
864 {
865         return false;
866 }
867
868 PlugUIBase*
869 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
870 {
871         AUPluginUI* aup = new AUPluginUI (plugin_insert);
872         (*box) = aup;
873         return aup;
874 }
875
876