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_combo, false, false);
157 smaller_hbox->pack_start (save_button, false, false);
159 /* one day these might be useful with an AU plugin, but not yet */
160 smaller_hbox->pack_start (automation_mode_label, false, false);
161 smaller_hbox->pack_start (automation_mode_selector, false, false);
163 smaller_hbox->pack_start (bypass_button, false, true);
165 VBox* v1_box = manage (new VBox);
166 VBox* v2_box = manage (new VBox);
168 v1_box->pack_start (*smaller_hbox, false, true);
169 v2_box->pack_start (focus_button, false, true);
171 top_box.set_homogeneous (false);
172 top_box.set_spacing (6);
173 top_box.set_border_width (6);
175 top_box.pack_end (*v2_box, false, false);
176 top_box.pack_end (*v1_box, false, false);
179 pack_start (top_box, false, false);
180 pack_start (low_box, false, false);
182 preset_label.show ();
183 _preset_combo.show ();
184 automation_mode_label.show ();
185 automation_mode_selector.show ();
186 bypass_button.show ();
194 _activating_from_app = false;
201 /* prefer cocoa, fall back to cocoa, but use carbon if its there */
203 if (test_cocoa_view_support()) {
204 create_cocoa_view ();
206 } else if (test_carbon_view_support()) {
207 create_carbon_view ();
210 create_cocoa_view ();
213 low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
216 AUPluginUI::~AUPluginUI ()
219 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
223 NSWindow* win = get_nswindow();
224 [win removeChildWindow:cocoa_parent];
229 /* not parented, just overlaid on top of our window */
230 DisposeWindow (carbon_window);
235 CloseComponent (editView);
239 /* remove whatever we packed into low_box so that GTK doesn't
243 [au_view removeFromSuperview];
248 AUPluginUI::test_carbon_view_support ()
253 carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
254 carbon_descriptor.componentSubType = 'gnrc';
255 carbon_descriptor.componentManufacturer = 'appl';
256 carbon_descriptor.componentFlags = 0;
257 carbon_descriptor.componentFlagsMask = 0;
261 // ask the AU for its first editor component
263 err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
265 int nEditors = propertySize / sizeof(ComponentDescription);
266 ComponentDescription *editors = new ComponentDescription[nEditors];
267 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
269 // just pick the first one for now
270 carbon_descriptor = editors[0];
283 AUPluginUI::test_cocoa_view_support ()
286 Boolean isWritable = 0;
287 OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
288 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
289 0, &dataSize, &isWritable);
291 return dataSize > 0 && err == noErr;
295 AUPluginUI::plugin_class_valid (Class pluginClass)
297 if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
298 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
299 [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
307 AUPluginUI::create_cocoa_view ()
309 bool wasAbleToLoadCustomView = false;
310 AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
311 UInt32 numberOfClasses = 0;
314 NSString* factoryClassName = 0;
315 NSURL* CocoaViewBundlePath = NULL;
317 OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
318 kAudioUnitProperty_CocoaUI,
319 kAudioUnitScope_Global,
324 numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
326 // Does view have custom Cocoa UI?
328 if ((result == noErr) && (numberOfClasses > 0) ) {
330 DEBUG_TRACE(DEBUG::AudioUnits,
331 string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
333 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
335 if(AudioUnitGetProperty(*au->get_au(),
336 kAudioUnitProperty_CocoaUI,
337 kAudioUnitScope_Global,
340 &dataSize) == noErr) {
342 CocoaViewBundlePath = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
344 // we only take the first view in this example.
345 factoryClassName = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
347 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
348 [factoryClassName UTF8String], CocoaViewBundlePath));
352 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
354 if (cocoaViewInfo != NULL) {
355 free (cocoaViewInfo);
356 cocoaViewInfo = NULL;
361 // [A] Show custom UI if view has it
363 if (CocoaViewBundlePath && factoryClassName) {
364 NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
366 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
368 if (viewBundle == NULL) {
369 error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
372 Class factoryClass = [viewBundle classNamed:factoryClassName];
373 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
375 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
379 // make sure 'factoryClass' implements the AUCocoaUIBase protocol
380 if (!plugin_class_valid (factoryClass)) {
381 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
385 id factory = [[[factoryClass alloc] init] autorelease];
386 if (factory == NULL) {
387 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
391 DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
394 au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
396 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
399 [CocoaViewBundlePath release];
402 for (i = 0; i < numberOfClasses; i++)
403 CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
405 free (cocoaViewInfo);
407 wasAbleToLoadCustomView = true;
411 if (!wasAbleToLoadCustomView) {
412 // load generic Cocoa view
413 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
415 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
416 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
417 [(AUGenericView *)au_view setShowsExpertParameters:1];
420 // Get the initial size of the new AU View's frame
422 NSRect rect = [au_view frame];
423 prefheight = rect.size.height;
424 prefwidth = rect.size.width;
425 low_box.set_size_request (rect.size.width, rect.size.height);
431 AUPluginUI::cocoa_view_resized ()
433 GtkRequisition topsize = top_box.size_request ();
434 NSWindow* window = get_nswindow ();
435 NSRect windowFrame= [window frame];
436 NSRect new_frame = [au_view frame];
438 float dy = last_au_frame.size.height - new_frame.size.height;
439 float dx = last_au_frame.size.width - new_frame.size.width;
441 windowFrame.origin.y += dy;
442 windowFrame.origin.x += dx;
443 windowFrame.size.height -= dy;
444 windowFrame.size.width -= dx;
446 [[NSNotificationCenter defaultCenter] removeObserver:_notify
447 name:NSViewFrameDidChangeNotification
450 NSUInteger old_auto_resize = [au_view autoresizingMask];
452 [au_view setAutoresizingMask:NSViewNotSizable];
453 [window setFrame:windowFrame display:1];
455 /* Some stupid AU Views change the origin of the original AU View
456 when they are resized (I'm looking at you AUSampler). If the origin
457 has been moved, move it back.
460 if (last_au_frame.origin.x != new_frame.origin.x ||
461 last_au_frame.origin.y != new_frame.origin.y) {
462 new_frame.origin = last_au_frame.origin;
463 [au_view setFrame:new_frame];
464 /* also be sure to redraw the topbox because this can
467 top_box.queue_draw ();
470 [au_view setAutoresizingMask:old_auto_resize];
472 [[NSNotificationCenter defaultCenter] addObserver:_notify
473 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
476 last_au_frame = new_frame;
480 AUPluginUI::create_carbon_view ()
484 ControlRef root_control;
486 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
488 OpenAComponent(editComponent, &editView);
490 error << _("AU Carbon view: cannot open AU Component") << endmsg;
494 Rect r = { 100, 100, 100, 100 };
495 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
496 kWindowCompositingAttribute|
497 kWindowNoShadowAttribute|
498 kWindowNoTitleBarAttribute);
500 if ((err = CreateNewWindow(kDocumentWindowClass, attr, &r, &carbon_window)) != noErr) {
501 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
502 CloseComponent (editView);
506 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
507 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
508 DisposeWindow (carbon_window);
509 CloseComponent (editView);
514 Float32Point location = { 0.0, 0.0 };
515 Float32Point size = { 0.0, 0.0 } ;
517 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
518 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
519 DisposeWindow (carbon_window);
520 CloseComponent (editView);
527 GetControlBounds(viewPane, &bounds);
528 size.x = bounds.right-bounds.left;
529 size.y = bounds.bottom-bounds.top;
531 prefwidth = (int) (size.x + 0.5);
532 prefheight = (int) (size.y + 0.5);
534 SizeWindow (carbon_window, prefwidth, prefheight, true);
535 low_box.set_size_request (prefwidth, prefheight);
539 error << _("AU Carbon GUI is not supported.") << endmsg;
545 AUPluginUI::get_nswindow ()
547 Gtk::Container* toplevel = get_toplevel();
549 if (!toplevel || !toplevel->is_toplevel()) {
550 error << _("AUPluginUI: no top level window!") << endmsg;
554 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
557 error << _("AUPluginUI: no top level window!") << endmsg;
565 AUPluginUI::activate ()
568 ActivateWindow (carbon_window, TRUE);
573 AUPluginUI::deactivate ()
576 ActivateWindow (carbon_window, FALSE);
581 AUPluginUI::parent_carbon_window ()
584 NSWindow* win = get_nswindow ();
585 Rect windowStructureBoundsRect;
591 Gtk::Container* toplevel = get_toplevel();
593 if (!toplevel || !toplevel->is_toplevel()) {
594 error << _("AUPluginUI: no top level window!") << endmsg;
598 /* figure out where the cocoa parent window is in carbon-coordinate space, which
599 differs from both cocoa-coordinate space and GTK-coordinate space
602 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
604 /* compute how tall the title bar is, because we have to offset the position of the carbon window
608 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
609 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
611 int titlebar_height = wm_frame.size.height - content_frame.size.height;
613 int packing_extra = 6; // this is the total vertical packing in our top level window
615 /* move into position, based on parent window position */
616 MoveWindow (carbon_window,
617 windowStructureBoundsRect.left,
618 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
620 ShowWindow (carbon_window);
622 // create the cocoa window for the carbon one and make it visible
623 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
625 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
627 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
629 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
638 AUPluginUI::parent_cocoa_window ()
640 NSWindow* win = get_nswindow ();
646 [win setAutodisplay:1]; // turn of GTK stuff for this window
648 Gtk::Container* toplevel = get_toplevel();
650 if (!toplevel || !toplevel->is_toplevel()) {
651 error << _("AUPluginUI: no top level window!") << endmsg;
655 NSView* view = gdk_quartz_window_get_nsview (get_toplevel()->get_window()->gobj());
656 GtkRequisition a = top_box.size_request ();
658 /* move the au_view down so that it doesn't overlap the top_box contents */
660 NSPoint origin = { 0, static_cast<CGFloat> (a.height) };
662 [au_view setFrameOrigin:origin];
663 [view addSubview:au_view positioned:NSWindowBelow relativeTo:NULL];
665 last_au_frame = [au_view frame];
667 // watch for size changes of the view
669 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
671 [[NSNotificationCenter defaultCenter] addObserver:_notify
672 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
679 AUPluginUI::forward_key_event (GdkEventKey* ev)
681 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
683 if (au_view && nsevent) {
685 /* filter on nsevent type here because GDK massages FlagsChanged
686 messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
687 handle a FlagsChanged message as a keyDown or keyUp
690 if ([nsevent type] == NSKeyDown) {
691 [[[au_view window] firstResponder] keyDown:nsevent];
692 } else if ([nsevent type] == NSKeyUp) {
693 [[[au_view window] firstResponder] keyUp:nsevent];
694 } else if ([nsevent type] == NSFlagsChanged) {
695 [[[au_view window] firstResponder] flagsChanged:nsevent];
701 AUPluginUI::on_realize ()
705 /* our windows should not have that resize indicator */
707 NSWindow* win = get_nswindow ();
709 [win setShowsResizeIndicator:0];
714 AUPluginUI::lower_box_realized ()
717 parent_cocoa_window ();
718 } else if (carbon_window) {
719 parent_carbon_window ();
724 AUPluginUI::on_window_hide ()
728 HideWindow (carbon_window);
729 ActivateWindow (carbon_window, FALSE);
735 NSArray* wins = [NSApp windows];
736 for (uint32_t i = 0; i < [wins count]; i++) {
737 id win = [wins objectAtIndex:i];
743 AUPluginUI::on_window_show (const string& /*title*/)
745 /* this is idempotent so just call it every time we show the window */
747 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
753 ShowWindow (carbon_window);
754 ActivateWindow (carbon_window, TRUE);
762 AUPluginUI::start_updating (GdkEventAny*)
768 AUPluginUI::stop_updating (GdkEventAny*)
774 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
776 AUPluginUI* aup = new AUPluginUI (plugin_insert);