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 NSWindow* window = get_nswindow ();
434 NSRect windowFrame= [window frame];
435 NSRect new_frame = [au_view frame];
437 float dy = last_au_frame.size.height - new_frame.size.height;
438 float dx = last_au_frame.size.width - new_frame.size.width;
440 windowFrame.origin.y += dy;
441 windowFrame.origin.x += dx;
442 windowFrame.size.height -= dy;
443 windowFrame.size.width -= dx;
445 [[NSNotificationCenter defaultCenter] removeObserver:_notify
446 name:NSViewFrameDidChangeNotification
449 NSUInteger old_auto_resize = [au_view autoresizingMask];
451 [au_view setAutoresizingMask:NSViewNotSizable];
452 [window setFrame:windowFrame display:1];
454 /* Some stupid AU Views change the origin of the original AU View
455 when they are resized (I'm looking at you AUSampler). If the origin
456 has been moved, move it back.
459 if (last_au_frame.origin.x != new_frame.origin.x ||
460 last_au_frame.origin.y != new_frame.origin.y) {
461 new_frame.origin = last_au_frame.origin;
462 [au_view setFrame:new_frame];
463 /* also be sure to redraw the topbox because this can
466 top_box.queue_draw ();
469 [au_view setAutoresizingMask:old_auto_resize];
471 [[NSNotificationCenter defaultCenter] addObserver:_notify
472 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
475 last_au_frame = new_frame;
479 AUPluginUI::create_carbon_view ()
483 ControlRef root_control;
485 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
487 OpenAComponent(editComponent, &editView);
489 error << _("AU Carbon view: cannot open AU Component") << endmsg;
493 Rect r = { 100, 100, 100, 100 };
494 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
495 kWindowCompositingAttribute|
496 kWindowNoShadowAttribute|
497 kWindowNoTitleBarAttribute);
499 if ((err = CreateNewWindow(kDocumentWindowClass, attr, &r, &carbon_window)) != noErr) {
500 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
501 CloseComponent (editView);
505 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
506 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
507 DisposeWindow (carbon_window);
508 CloseComponent (editView);
513 Float32Point location = { 0.0, 0.0 };
514 Float32Point size = { 0.0, 0.0 } ;
516 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
517 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
518 DisposeWindow (carbon_window);
519 CloseComponent (editView);
526 GetControlBounds(viewPane, &bounds);
527 size.x = bounds.right-bounds.left;
528 size.y = bounds.bottom-bounds.top;
530 prefwidth = (int) (size.x + 0.5);
531 prefheight = (int) (size.y + 0.5);
533 SizeWindow (carbon_window, prefwidth, prefheight, true);
534 low_box.set_size_request (prefwidth, prefheight);
538 error << _("AU Carbon GUI is not supported.") << endmsg;
544 AUPluginUI::get_nswindow ()
546 Gtk::Container* toplevel = get_toplevel();
548 if (!toplevel || !toplevel->is_toplevel()) {
549 error << _("AUPluginUI: no top level window!") << endmsg;
553 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
556 error << _("AUPluginUI: no top level window!") << endmsg;
564 AUPluginUI::activate ()
567 ActivateWindow (carbon_window, TRUE);
572 AUPluginUI::deactivate ()
575 ActivateWindow (carbon_window, FALSE);
580 AUPluginUI::parent_carbon_window ()
583 NSWindow* win = get_nswindow ();
584 Rect windowStructureBoundsRect;
590 Gtk::Container* toplevel = get_toplevel();
592 if (!toplevel || !toplevel->is_toplevel()) {
593 error << _("AUPluginUI: no top level window!") << endmsg;
597 /* figure out where the cocoa parent window is in carbon-coordinate space, which
598 differs from both cocoa-coordinate space and GTK-coordinate space
601 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
603 /* compute how tall the title bar is, because we have to offset the position of the carbon window
607 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
608 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
610 int titlebar_height = wm_frame.size.height - content_frame.size.height;
612 int packing_extra = 6; // this is the total vertical packing in our top level window
614 /* move into position, based on parent window position */
615 MoveWindow (carbon_window,
616 windowStructureBoundsRect.left,
617 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
619 ShowWindow (carbon_window);
621 // create the cocoa window for the carbon one and make it visible
622 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
624 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
626 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
628 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
637 AUPluginUI::parent_cocoa_window ()
639 NSWindow* win = get_nswindow ();
645 [win setAutodisplay:1]; // turn of GTK stuff for this window
647 Gtk::Container* toplevel = get_toplevel();
649 if (!toplevel || !toplevel->is_toplevel()) {
650 error << _("AUPluginUI: no top level window!") << endmsg;
654 NSView* view = gdk_quartz_window_get_nsview (get_toplevel()->get_window()->gobj());
655 GtkRequisition a = top_box.size_request ();
657 /* move the au_view down so that it doesn't overlap the top_box contents */
659 const int spacing = 6; // main vbox spacing
660 const int pad = 4; // box pad
662 NSPoint origin = { spacing + pad, static_cast<CGFloat> (a.height) + (2 * spacing) + pad };
664 [au_view setFrameOrigin:origin];
665 [view addSubview:au_view positioned:NSWindowBelow relativeTo:NULL];
667 last_au_frame = [au_view frame];
669 // watch for size changes of the view
671 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
673 [[NSNotificationCenter defaultCenter] addObserver:_notify
674 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
681 AUPluginUI::forward_key_event (GdkEventKey* ev)
683 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
685 if (au_view && nsevent) {
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
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];
703 AUPluginUI::on_realize ()
707 /* our windows should not have that resize indicator */
709 NSWindow* win = get_nswindow ();
711 [win setShowsResizeIndicator:0];
716 AUPluginUI::lower_box_realized ()
719 parent_cocoa_window ();
720 } else if (carbon_window) {
721 parent_carbon_window ();
726 AUPluginUI::on_window_hide ()
730 HideWindow (carbon_window);
731 ActivateWindow (carbon_window, FALSE);
737 NSArray* wins = [NSApp windows];
738 for (uint32_t i = 0; i < [wins count]; i++) {
739 id win = [wins objectAtIndex:i];
745 AUPluginUI::on_window_show (const string& /*title*/)
747 /* this is idempotent so just call it every time we show the window */
749 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
755 ShowWindow (carbon_window);
756 ActivateWindow (carbon_window, TRUE);
764 AUPluginUI::start_updating (GdkEventAny*)
770 AUPluginUI::stop_updating (GdkEventAny*)
776 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
778 AUPluginUI* aup = new AUPluginUI (plugin_insert);