2 #define Marker FuckYouAppleAndYourLackOfNameSpaces
4 #include "pbd/convert.h"
7 #include "ardour/audio_unit.h"
8 #include "ardour/debug.h"
9 #include "ardour/plugin_insert.h"
11 #undef check // stupid gtk, stupid apple
13 #include <gtkmm/button.h>
14 #include <gdk/gdkquartz.h>
16 #include <gtkmm2ext/utils.h>
18 #include "au_pluginui.h"
19 #include "gui_thread.h"
21 #include "appleutility/CAAudioUnit.h"
22 #include "appleutility/CAComponent.h"
24 #import <AudioUnit/AUCocoaUIView.h>
25 #import <CoreAudioKit/AUGenericView.h>
31 #include "public_editor.h"
34 using namespace ARDOUR;
36 using namespace Gtkmm2ext;
41 vector<string> AUPluginUI::automation_mode_strings;
43 static const gchar* _automation_mode_strings[] = {
52 dump_view_tree (NSView* view, int depth)
54 NSArray* subviews = [view subviews];
55 unsigned long cnt = [subviews count];
57 for (int d = 0; d < depth; d++) {
60 NSRect frame = [view frame];
61 cerr << " view @ " << frame.origin.x << ", " << frame.origin.y
62 << ' ' << frame.size.width << " x " << frame.size.height
65 for (unsigned long i = 0; i < cnt; ++i) {
66 NSView* subview = [subviews objectAtIndex:i];
67 dump_view_tree (subview, depth+1);
71 @implementation NotificationObject
73 - (NotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui andCocoaParent: (NSWindow*) cp andTopLevelParent: (NSWindow*) tlp
75 self = [ super init ];
78 plugin_ui = apluginui;
79 top_level_parent = tlp;
84 [[NSNotificationCenter defaultCenter] addObserver:self
85 selector:@selector(cocoaParentActivationHandler:)
86 name:NSWindowDidBecomeMainNotification
89 [[NSNotificationCenter defaultCenter] addObserver:self
90 selector:@selector(cocoaParentBecameKeyHandler:)
91 name:NSWindowDidBecomeKeyNotification
99 - (void)cocoaParentActivationHandler:(NSNotification *)notification
101 NSWindow* notification_window = (NSWindow *)[notification object];
103 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
104 if ([notification_window isMainWindow]) {
105 plugin_ui->activate();
107 plugin_ui->deactivate();
112 - (void)cocoaParentBecameKeyHandler:(NSNotification *)notification
114 NSWindow* notification_window = (NSWindow *)[notification object];
116 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
117 if ([notification_window isKeyWindow]) {
118 plugin_ui->activate();
120 plugin_ui->deactivate();
125 - (void)auViewResized:(NSNotification *)notification;
127 (void) notification; // stop complaints about unusued argument
128 plugin_ui->cocoa_view_resized();
133 AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
134 : PlugUIBase (insert)
135 , automation_mode_label (_("Automation"))
136 , preset_label (_("Presets"))
139 if (automation_mode_strings.empty()) {
140 automation_mode_strings = I18N (_automation_mode_strings);
143 set_popdown_strings (automation_mode_selector, automation_mode_strings);
144 automation_mode_selector.set_active_text (automation_mode_strings.front());
146 if ((au = boost::dynamic_pointer_cast<AUPlugin> (insert->plugin())) == 0) {
147 error << _("unknown type of editor-supplying plugin (note: no AudioUnit support in this version of ardour)") << endmsg;
148 throw failed_constructor ();
151 /* stuff some stuff into the top of the window */
153 HBox* smaller_hbox = manage (new HBox);
155 smaller_hbox->set_spacing (6);
156 smaller_hbox->pack_start (preset_label, false, false, 4);
157 smaller_hbox->pack_start (_preset_combo, false, false);
158 smaller_hbox->pack_start (save_button, false, false);
160 /* one day these might be useful with an AU plugin, but not yet */
161 smaller_hbox->pack_start (automation_mode_label, false, false);
162 smaller_hbox->pack_start (automation_mode_selector, false, false);
164 smaller_hbox->pack_start (bypass_button, false, true);
166 VBox* v1_box = manage (new VBox);
167 VBox* v2_box = manage (new VBox);
169 v1_box->pack_start (*smaller_hbox, false, true);
170 v2_box->pack_start (focus_button, false, true);
172 top_box.set_homogeneous (false);
173 top_box.set_spacing (6);
174 top_box.set_border_width (6);
176 top_box.pack_end (*v2_box, false, false);
177 top_box.pack_end (*v1_box, false, false);
180 pack_start (top_box, false, false);
181 pack_start (low_box, false, false);
183 preset_label.show ();
184 _preset_combo.show ();
185 automation_mode_label.show ();
186 automation_mode_selector.show ();
187 bypass_button.show ();
195 _activating_from_app = false;
202 /* prefer cocoa, fall back to cocoa, but use carbon if its there */
204 if (test_cocoa_view_support()) {
205 create_cocoa_view ();
207 } else if (test_carbon_view_support()) {
208 create_carbon_view ();
211 create_cocoa_view ();
214 low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
217 AUPluginUI::~AUPluginUI ()
220 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
224 NSWindow* win = get_nswindow();
225 [win removeChildWindow:cocoa_parent];
230 /* not parented, just overlaid on top of our window */
231 DisposeWindow (carbon_window);
236 CloseComponent (editView);
240 /* remove whatever we packed into low_box so that GTK doesn't
244 [au_view removeFromSuperview];
249 AUPluginUI::test_carbon_view_support ()
254 carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
255 carbon_descriptor.componentSubType = 'gnrc';
256 carbon_descriptor.componentManufacturer = 'appl';
257 carbon_descriptor.componentFlags = 0;
258 carbon_descriptor.componentFlagsMask = 0;
262 // ask the AU for its first editor component
264 err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
266 int nEditors = propertySize / sizeof(ComponentDescription);
267 ComponentDescription *editors = new ComponentDescription[nEditors];
268 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
270 // just pick the first one for now
271 carbon_descriptor = editors[0];
284 AUPluginUI::test_cocoa_view_support ()
287 Boolean isWritable = 0;
288 OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
289 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
290 0, &dataSize, &isWritable);
292 return dataSize > 0 && err == noErr;
296 AUPluginUI::plugin_class_valid (Class pluginClass)
298 if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
299 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
300 [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
308 AUPluginUI::create_cocoa_view ()
310 bool wasAbleToLoadCustomView = false;
311 AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
312 UInt32 numberOfClasses = 0;
315 NSString* factoryClassName = 0;
316 NSURL* CocoaViewBundlePath = NULL;
318 OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
319 kAudioUnitProperty_CocoaUI,
320 kAudioUnitScope_Global,
325 numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
327 // Does view have custom Cocoa UI?
329 if ((result == noErr) && (numberOfClasses > 0) ) {
331 DEBUG_TRACE(DEBUG::AudioUnits,
332 string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
334 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
336 if(AudioUnitGetProperty(*au->get_au(),
337 kAudioUnitProperty_CocoaUI,
338 kAudioUnitScope_Global,
341 &dataSize) == noErr) {
343 CocoaViewBundlePath = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
345 // we only take the first view in this example.
346 factoryClassName = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
348 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
349 [factoryClassName UTF8String], CocoaViewBundlePath));
353 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
355 if (cocoaViewInfo != NULL) {
356 free (cocoaViewInfo);
357 cocoaViewInfo = NULL;
362 // [A] Show custom UI if view has it
364 if (CocoaViewBundlePath && factoryClassName) {
365 NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
367 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
369 if (viewBundle == nil) {
370 error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
373 Class factoryClass = [viewBundle classNamed:factoryClassName];
374 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
376 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
380 // make sure 'factoryClass' implements the AUCocoaUIBase protocol
381 if (!plugin_class_valid (factoryClass)) {
382 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
386 id factory = [[[factoryClass alloc] init] autorelease];
387 if (factory == nil) {
388 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
392 DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
395 au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
397 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
400 [CocoaViewBundlePath release];
403 for (i = 0; i < numberOfClasses; i++)
404 CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
406 free (cocoaViewInfo);
408 wasAbleToLoadCustomView = true;
412 if (!wasAbleToLoadCustomView) {
413 // load generic Cocoa view
414 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
416 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
417 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
418 [(AUGenericView *)au_view setShowsExpertParameters:1];
421 // Get the initial size of the new AU View's frame
423 NSRect rect = [au_view frame];
424 prefheight = rect.size.height;
425 prefwidth = rect.size.width;
426 low_box.set_size_request (rect.size.width, rect.size.height);
432 AUPluginUI::cocoa_view_resized ()
434 GtkRequisition topsize = top_box.size_request ();
435 NSWindow* window = get_nswindow ();
436 NSRect windowFrame= [window frame];
437 NSRect new_frame = [au_view frame];
439 float dy = last_au_frame.size.height - new_frame.size.height;
440 float dx = last_au_frame.size.width - new_frame.size.width;
442 windowFrame.origin.y += dy;
443 windowFrame.origin.x += dx;
444 windowFrame.size.height -= dy;
445 windowFrame.size.width -= dx;
447 [[NSNotificationCenter defaultCenter] removeObserver:_notify
448 name:NSViewFrameDidChangeNotification
451 NSUInteger old_auto_resize = [au_view autoresizingMask];
453 [au_view setAutoresizingMask:NSViewNotSizable];
454 [window setFrame:windowFrame display:1];
456 /* Some stupid AU Views change the origin of the original AU View
457 when they are resized (I'm looking at you AUSampler). If the origin
458 has been moved, move it back.
461 if (last_au_frame.origin.x != new_frame.origin.x ||
462 last_au_frame.origin.y != new_frame.origin.y) {
463 new_frame.origin = last_au_frame.origin;
464 [au_view setFrame:new_frame];
465 /* also be sure to redraw the topbox because this can
468 top_box.queue_draw ();
471 [au_view setAutoresizingMask:old_auto_resize];
473 [[NSNotificationCenter defaultCenter] addObserver:_notify
474 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
477 last_au_frame = new_frame;
481 AUPluginUI::create_carbon_view ()
485 ControlRef root_control;
487 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
489 OpenAComponent(editComponent, &editView);
491 error << _("AU Carbon view: cannot open AU Component") << endmsg;
495 Rect r = { 100, 100, 100, 100 };
496 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
497 kWindowCompositingAttribute|
498 kWindowNoShadowAttribute|
499 kWindowNoTitleBarAttribute);
501 if ((err = CreateNewWindow(kDocumentWindowClass, attr, &r, &carbon_window)) != noErr) {
502 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
503 CloseComponent (editView);
507 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
508 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
509 DisposeWindow (carbon_window);
510 CloseComponent (editView);
515 Float32Point location = { 0.0, 0.0 };
516 Float32Point size = { 0.0, 0.0 } ;
518 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
519 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
520 DisposeWindow (carbon_window);
521 CloseComponent (editView);
528 GetControlBounds(viewPane, &bounds);
529 size.x = bounds.right-bounds.left;
530 size.y = bounds.bottom-bounds.top;
532 prefwidth = (int) (size.x + 0.5);
533 prefheight = (int) (size.y + 0.5);
535 SizeWindow (carbon_window, prefwidth, prefheight, true);
536 low_box.set_size_request (prefwidth, prefheight);
540 error << _("AU Carbon GUI is not supported.") << endmsg;
546 AUPluginUI::get_nswindow ()
548 Gtk::Container* toplevel = get_toplevel();
550 if (!toplevel || !toplevel->is_toplevel()) {
551 error << _("AUPluginUI: no top level window!") << endmsg;
555 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
558 error << _("AUPluginUI: no top level window!") << endmsg;
566 AUPluginUI::activate ()
569 ActivateWindow (carbon_window, TRUE);
574 AUPluginUI::deactivate ()
577 ActivateWindow (carbon_window, FALSE);
582 AUPluginUI::parent_carbon_window ()
585 NSWindow* win = get_nswindow ();
586 Rect windowStructureBoundsRect;
592 Gtk::Container* toplevel = get_toplevel();
594 if (!toplevel || !toplevel->is_toplevel()) {
595 error << _("AUPluginUI: no top level window!") << endmsg;
599 /* figure out where the cocoa parent window is in carbon-coordinate space, which
600 differs from both cocoa-coordinate space and GTK-coordinate space
603 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
605 /* compute how tall the title bar is, because we have to offset the position of the carbon window
609 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
610 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
612 int titlebar_height = wm_frame.size.height - content_frame.size.height;
614 int packing_extra = 6; // this is the total vertical packing in our top level window
616 /* move into position, based on parent window position */
617 MoveWindow (carbon_window,
618 windowStructureBoundsRect.left,
619 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
621 ShowWindow (carbon_window);
623 // create the cocoa window for the carbon one and make it visible
624 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
626 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
628 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
630 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
639 AUPluginUI::parent_cocoa_window ()
641 NSWindow* win = get_nswindow ();
647 [win setAutodisplay:1]; // turn of GTK stuff for this window
649 Gtk::Container* toplevel = get_toplevel();
651 if (!toplevel || !toplevel->is_toplevel()) {
652 error << _("AUPluginUI: no top level window!") << endmsg;
656 NSView* view = gdk_quartz_window_get_nsview (get_toplevel()->get_window()->gobj());
657 GtkRequisition a = top_box.size_request ();
659 /* move the au_view down so that it doesn't overlap the top_box contents */
661 NSPoint origin = { 0, static_cast<CGFloat> (a.height) };
663 [au_view setFrameOrigin:origin];
664 [view addSubview:au_view positioned:NSWindowBelow relativeTo:nil];
666 last_au_frame = [au_view frame];
668 // watch for size changes of the view
670 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:nil andTopLevelParent:win ];
672 [[NSNotificationCenter defaultCenter] addObserver:_notify
673 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
680 AUPluginUI::forward_key_event (GdkEventKey* ev)
682 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
684 if (au_view && nsevent) {
686 /* filter on nsevent type here because GDK massages FlagsChanged
687 messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
688 handle a FlagsChanged message as a keyDown or keyUp
691 if ([nsevent type] == NSKeyDown) {
692 [[[au_view window] firstResponder] keyDown:nsevent];
693 } else if ([nsevent type] == NSKeyUp) {
694 [[[au_view window] firstResponder] keyUp:nsevent];
695 } else if ([nsevent type] == NSFlagsChanged) {
696 [[[au_view window] firstResponder] flagsChanged:nsevent];
702 AUPluginUI::on_realize ()
706 /* our windows should not have that resize indicator */
708 NSWindow* win = get_nswindow ();
710 [win setShowsResizeIndicator:0];
715 AUPluginUI::lower_box_realized ()
718 parent_cocoa_window ();
719 } else if (carbon_window) {
720 parent_carbon_window ();
725 AUPluginUI::on_window_hide ()
729 HideWindow (carbon_window);
730 ActivateWindow (carbon_window, FALSE);
736 NSArray* wins = [NSApp windows];
737 for (uint32_t i = 0; i < [wins count]; i++) {
738 id win = [wins objectAtIndex:i];
744 AUPluginUI::on_window_show (const string& /*title*/)
746 /* this is idempotent so just call it every time we show the window */
748 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
754 ShowWindow (carbon_window);
755 ActivateWindow (carbon_window, TRUE);
763 AUPluginUI::start_updating (GdkEventAny*)
769 AUPluginUI::stop_updating (GdkEventAny*)
775 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
777 AUPluginUI* aup = new AUPluginUI (plugin_insert);