2 #define Marker FuckYouAppleAndYourLackOfNameSpaces
4 #include <gtkmm/button.h>
5 #include <gdk/gdkquartz.h>
7 #include "pbd/convert.h"
10 #include "ardour/audio_unit.h"
11 #include "ardour/debug.h"
12 #include "ardour/plugin_insert.h"
14 #undef check // stupid gtk, stupid apple
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;
40 vector<string> AUPluginUI::automation_mode_strings;
42 static const gchar* _automation_mode_strings[] = {
51 dump_view_tree (NSView* view, int depth)
53 NSArray* subviews = [view subviews];
54 unsigned long cnt = [subviews count];
56 for (int d = 0; d < depth; d++) {
59 NSRect frame = [view frame];
60 cerr << " view @ " << frame.origin.x << ", " << frame.origin.y
61 << ' ' << frame.size.width << " x " << frame.size.height
64 for (unsigned long i = 0; i < cnt; ++i) {
65 NSView* subview = [subviews objectAtIndex:i];
66 dump_view_tree (subview, depth+1);
70 @implementation NotificationObject
72 - (NotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui andCocoaParent: (NSWindow*) cp andTopLevelParent: (NSWindow*) tlp
74 self = [ super init ];
77 plugin_ui = apluginui;
78 top_level_parent = tlp;
83 [[NSNotificationCenter defaultCenter] addObserver:self
84 selector:@selector(cocoaParentActivationHandler:)
85 name:NSWindowDidBecomeMainNotification
88 [[NSNotificationCenter defaultCenter] addObserver:self
89 selector:@selector(cocoaParentBecameKeyHandler:)
90 name:NSWindowDidBecomeKeyNotification
98 - (void)cocoaParentActivationHandler:(NSNotification *)notification
100 NSWindow* notification_window = (NSWindow *)[notification object];
102 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
103 if ([notification_window isMainWindow]) {
104 plugin_ui->activate();
106 plugin_ui->deactivate();
111 - (void)cocoaParentBecameKeyHandler:(NSNotification *)notification
113 NSWindow* notification_window = (NSWindow *)[notification object];
115 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
116 if ([notification_window isKeyWindow]) {
117 plugin_ui->activate();
119 plugin_ui->deactivate();
124 - (void)auViewResized:(NSNotification *)notification
126 (void) notification; // stop complaints about unusued argument
127 plugin_ui->cocoa_view_resized();
132 AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
133 : PlugUIBase (insert)
134 , automation_mode_label (_("Automation"))
135 , preset_label (_("Presets"))
138 if (automation_mode_strings.empty()) {
139 automation_mode_strings = I18N (_automation_mode_strings);
142 set_popdown_strings (automation_mode_selector, automation_mode_strings);
143 automation_mode_selector.set_active_text (automation_mode_strings.front());
145 if ((au = boost::dynamic_pointer_cast<AUPlugin> (insert->plugin())) == 0) {
146 error << _("unknown type of editor-supplying plugin (note: no AudioUnit support in this version of ardour)") << endmsg;
147 throw failed_constructor ();
150 /* stuff some stuff into the top of the window */
152 HBox* smaller_hbox = manage (new HBox);
154 smaller_hbox->set_spacing (6);
155 smaller_hbox->pack_start (preset_label, false, false, 4);
156 smaller_hbox->pack_start (_preset_modified, false, false);
157 smaller_hbox->pack_start (_preset_combo, false, false);
158 smaller_hbox->pack_start (add_button, false, false);
160 /* Ardour does not currently allow to overwrite existing presets
161 * see save_property_list() in audio_unit.cc
163 smaller_hbox->pack_start (save_button, false, false);
166 /* one day these might be useful with an AU plugin, but not yet */
167 smaller_hbox->pack_start (automation_mode_label, false, false);
168 smaller_hbox->pack_start (automation_mode_selector, false, false);
170 smaller_hbox->pack_start (bypass_button, false, true);
172 VBox* v1_box = manage (new VBox);
173 VBox* v2_box = manage (new VBox);
175 v1_box->pack_start (*smaller_hbox, false, true);
176 v2_box->pack_start (focus_button, false, true);
178 top_box.set_homogeneous (false);
179 top_box.set_spacing (6);
180 top_box.set_border_width (6);
182 top_box.pack_end (*v2_box, false, false);
183 top_box.pack_end (*v1_box, false, false);
186 pack_start (top_box, false, false);
187 pack_start (low_box, false, false);
189 preset_label.show ();
190 _preset_combo.show ();
191 automation_mode_label.show ();
192 automation_mode_selector.show ();
193 bypass_button.show ();
201 _activating_from_app = false;
208 /* prefer cocoa, fall back to cocoa, but use carbon if its there */
210 if (test_cocoa_view_support()) {
211 create_cocoa_view ();
213 } else if (test_carbon_view_support()) {
214 create_carbon_view ();
217 create_cocoa_view ();
220 low_box.add_events(Gdk::VISIBILITY_NOTIFY_MASK);
222 low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
223 low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
226 AUPluginUI::~AUPluginUI ()
229 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
233 NSWindow* win = get_nswindow();
234 [win removeChildWindow:cocoa_parent];
239 /* not parented, just overlaid on top of our window */
240 DisposeWindow (carbon_window);
245 CloseComponent (editView);
249 /* remove whatever we packed into low_box so that GTK doesn't
253 [au_view removeFromSuperview];
258 AUPluginUI::test_carbon_view_support ()
263 carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
264 carbon_descriptor.componentSubType = 'gnrc';
265 carbon_descriptor.componentManufacturer = 'appl';
266 carbon_descriptor.componentFlags = 0;
267 carbon_descriptor.componentFlagsMask = 0;
271 // ask the AU for its first editor component
273 err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
275 int nEditors = propertySize / sizeof(ComponentDescription);
276 ComponentDescription *editors = new ComponentDescription[nEditors];
277 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
279 // just pick the first one for now
280 carbon_descriptor = editors[0];
293 AUPluginUI::test_cocoa_view_support ()
296 Boolean isWritable = 0;
297 OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
298 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
299 0, &dataSize, &isWritable);
301 return dataSize > 0 && err == noErr;
305 AUPluginUI::plugin_class_valid (Class pluginClass)
307 if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
308 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
309 [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
317 AUPluginUI::create_cocoa_view ()
319 bool wasAbleToLoadCustomView = false;
320 AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
321 UInt32 numberOfClasses = 0;
324 NSString* factoryClassName = 0;
325 NSURL* CocoaViewBundlePath = NULL;
327 OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
328 kAudioUnitProperty_CocoaUI,
329 kAudioUnitScope_Global,
334 numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
336 // Does view have custom Cocoa UI?
338 if ((result == noErr) && (numberOfClasses > 0) ) {
340 DEBUG_TRACE(DEBUG::AudioUnits,
341 string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
343 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
345 if(AudioUnitGetProperty(*au->get_au(),
346 kAudioUnitProperty_CocoaUI,
347 kAudioUnitScope_Global,
350 &dataSize) == noErr) {
352 CocoaViewBundlePath = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
354 // we only take the first view in this example.
355 factoryClassName = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
357 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
358 [factoryClassName UTF8String], CocoaViewBundlePath));
362 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
364 if (cocoaViewInfo != NULL) {
365 free (cocoaViewInfo);
366 cocoaViewInfo = NULL;
371 // [A] Show custom UI if view has it
373 if (CocoaViewBundlePath && factoryClassName) {
374 NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
376 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
378 if (viewBundle == NULL) {
379 error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
382 Class factoryClass = [viewBundle classNamed:factoryClassName];
383 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
385 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
389 // make sure 'factoryClass' implements the AUCocoaUIBase protocol
390 if (!plugin_class_valid (factoryClass)) {
391 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
395 id factory = [[[factoryClass alloc] init] autorelease];
396 if (factory == NULL) {
397 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
401 DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
404 au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
406 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
409 [CocoaViewBundlePath release];
412 for (i = 0; i < numberOfClasses; i++)
413 CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
415 free (cocoaViewInfo);
417 wasAbleToLoadCustomView = true;
421 if (!wasAbleToLoadCustomView) {
422 // load generic Cocoa view
423 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
425 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
426 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
427 [(AUGenericView *)au_view setShowsExpertParameters:1];
430 // Get the initial size of the new AU View's frame
432 NSRect rect = [au_view frame];
433 prefheight = rect.size.height;
434 prefwidth = rect.size.width;
435 low_box.set_size_request (rect.size.width, rect.size.height);
441 AUPluginUI::cocoa_view_resized ()
443 NSWindow* window = get_nswindow ();
444 NSRect windowFrame= [window frame];
445 NSRect new_frame = [au_view frame];
447 float dy = last_au_frame.size.height - new_frame.size.height;
448 float dx = last_au_frame.size.width - new_frame.size.width;
450 windowFrame.origin.y += dy;
451 windowFrame.origin.x += dx;
452 windowFrame.size.height -= dy;
453 windowFrame.size.width -= dx;
455 [[NSNotificationCenter defaultCenter] removeObserver:_notify
456 name:NSViewFrameDidChangeNotification
459 NSUInteger old_auto_resize = [au_view autoresizingMask];
461 [au_view setAutoresizingMask:NSViewNotSizable];
462 [window setFrame:windowFrame display:1];
464 /* Some stupid AU Views change the origin of the original AU View
465 when they are resized (I'm looking at you AUSampler). If the origin
466 has been moved, move it back.
469 if (last_au_frame.origin.x != new_frame.origin.x ||
470 last_au_frame.origin.y != new_frame.origin.y) {
471 new_frame.origin = last_au_frame.origin;
472 [au_view setFrame:new_frame];
473 /* also be sure to redraw the topbox because this can
476 top_box.queue_draw ();
479 [au_view setAutoresizingMask:old_auto_resize];
481 [[NSNotificationCenter defaultCenter] addObserver:_notify
482 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
485 last_au_frame = new_frame;
489 AUPluginUI::create_carbon_view ()
493 ControlRef root_control;
495 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
497 OpenAComponent(editComponent, &editView);
499 error << _("AU Carbon view: cannot open AU Component") << endmsg;
503 Rect r = { 100, 100, 100, 100 };
504 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
505 kWindowCompositingAttribute|
506 kWindowNoShadowAttribute|
507 kWindowNoTitleBarAttribute);
509 if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
510 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
511 CloseComponent (editView);
515 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
516 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
517 DisposeWindow (carbon_window);
518 CloseComponent (editView);
523 Float32Point location = { 0.0, 0.0 };
524 Float32Point size = { 0.0, 0.0 } ;
526 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
527 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
528 DisposeWindow (carbon_window);
529 CloseComponent (editView);
536 GetControlBounds(viewPane, &bounds);
537 size.x = bounds.right-bounds.left;
538 size.y = bounds.bottom-bounds.top;
540 prefwidth = (int) (size.x + 0.5);
541 prefheight = (int) (size.y + 0.5);
543 SizeWindow (carbon_window, prefwidth, prefheight, true);
544 low_box.set_size_request (prefwidth, prefheight);
548 error << _("AU Carbon GUI is not supported.") << endmsg;
554 AUPluginUI::get_nswindow ()
556 Gtk::Container* toplevel = get_toplevel();
558 if (!toplevel || !toplevel->is_toplevel()) {
559 error << _("AUPluginUI: no top level window!") << endmsg;
563 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
566 error << _("AUPluginUI: no top level window!") << endmsg;
574 AUPluginUI::activate ()
577 ActivateWindow (carbon_window, TRUE);
582 AUPluginUI::deactivate ()
585 ActivateWindow (carbon_window, FALSE);
590 AUPluginUI::parent_carbon_window ()
593 NSWindow* win = get_nswindow ();
594 Rect windowStructureBoundsRect;
600 Gtk::Container* toplevel = get_toplevel();
602 if (!toplevel || !toplevel->is_toplevel()) {
603 error << _("AUPluginUI: no top level window!") << endmsg;
607 /* figure out where the cocoa parent window is in carbon-coordinate space, which
608 differs from both cocoa-coordinate space and GTK-coordinate space
611 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
613 /* compute how tall the title bar is, because we have to offset the position of the carbon window
617 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
618 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
620 int titlebar_height = wm_frame.size.height - content_frame.size.height;
622 int packing_extra = 6; // this is the total vertical packing in our top level window
624 /* move into position, based on parent window position */
625 MoveWindow (carbon_window,
626 windowStructureBoundsRect.left,
627 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
629 ShowWindow (carbon_window);
631 // create the cocoa window for the carbon one and make it visible
632 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
634 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
636 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
638 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
639 [win setAutodisplay:1]; // turn of GTK stuff for this window
648 AUPluginUI::parent_cocoa_window ()
650 NSWindow* win = get_nswindow ();
656 [win setAutodisplay:1]; // turn of GTK stuff for this window
658 Gtk::Container* toplevel = get_toplevel();
660 if (!toplevel || !toplevel->is_toplevel()) {
661 error << _("AUPluginUI: no top level window!") << endmsg;
665 NSView* view = gdk_quartz_window_get_nsview (get_toplevel()->get_window()->gobj());
666 GtkRequisition a = top_box.size_request ();
668 /* move the au_view down so that it doesn't overlap the top_box contents */
670 const int spacing = 6; // main vbox spacing
671 const int pad = 4; // box pad
673 NSPoint origin = { spacing + pad, static_cast<CGFloat> (a.height) + (2 * spacing) + pad };
675 [au_view setFrameOrigin:origin];
676 [view addSubview:au_view positioned:NSWindowBelow relativeTo:NULL];
678 last_au_frame = [au_view frame];
680 // watch for size changes of the view
682 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
684 [[NSNotificationCenter defaultCenter] addObserver:_notify
685 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
692 AUPluginUI::grab_focus()
695 [au_view becomeFirstResponder];
699 AUPluginUI::forward_key_event (GdkEventKey* ev)
701 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
703 if (au_view && nsevent) {
705 /* filter on nsevent type here because GDK massages FlagsChanged
706 messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
707 handle a FlagsChanged message as a keyDown or keyUp
710 if ([nsevent type] == NSKeyDown) {
711 [[[au_view window] firstResponder] keyDown:nsevent];
712 } else if ([nsevent type] == NSKeyUp) {
713 [[[au_view window] firstResponder] keyUp:nsevent];
714 } else if ([nsevent type] == NSFlagsChanged) {
715 [[[au_view window] firstResponder] flagsChanged:nsevent];
721 AUPluginUI::on_realize ()
725 /* our windows should not have that resize indicator */
727 NSWindow* win = get_nswindow ();
729 [win setShowsResizeIndicator:0];
734 AUPluginUI::lower_box_realized ()
737 parent_cocoa_window ();
738 } else if (carbon_window) {
739 parent_carbon_window ();
744 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
747 if (carbon_window && ev->state != GDK_VISIBILITY_UNOBSCURED) {
748 ShowWindow (carbon_window);
749 ActivateWindow (carbon_window, TRUE);
757 AUPluginUI::on_window_hide ()
761 HideWindow (carbon_window);
762 ActivateWindow (carbon_window, FALSE);
768 NSArray* wins = [NSApp windows];
769 for (uint32_t i = 0; i < [wins count]; i++) {
770 id win = [wins objectAtIndex:i];
776 AUPluginUI::on_window_show (const string& /*title*/)
778 /* this is idempotent so just call it every time we show the window */
780 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
786 ShowWindow (carbon_window);
787 ActivateWindow (carbon_window, TRUE);
795 AUPluginUI::start_updating (GdkEventAny*)
801 AUPluginUI::stop_updating (GdkEventAny*)
807 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
809 AUPluginUI* aup = new AUPluginUI (plugin_insert);