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 (reset_button, false, false);
171 smaller_hbox->pack_start (bypass_button, false, true);
173 VBox* v1_box = manage (new VBox);
174 VBox* v2_box = manage (new VBox);
176 v1_box->pack_start (*smaller_hbox, false, true);
177 v2_box->pack_start (focus_button, false, true);
179 top_box.set_homogeneous (false);
180 top_box.set_spacing (6);
181 top_box.set_border_width (6);
183 top_box.pack_end (*v2_box, false, false);
184 top_box.pack_end (*v1_box, false, false);
187 pack_start (top_box, false, false);
188 pack_start (low_box, false, false);
190 preset_label.show ();
191 _preset_combo.show ();
192 automation_mode_label.show ();
193 automation_mode_selector.show ();
194 bypass_button.show ();
202 _activating_from_app = false;
209 /* prefer cocoa, fall back to cocoa, but use carbon if its there */
211 if (test_cocoa_view_support()) {
212 create_cocoa_view ();
214 } else if (test_carbon_view_support()) {
215 create_carbon_view ();
218 create_cocoa_view ();
221 low_box.add_events(Gdk::VISIBILITY_NOTIFY_MASK);
223 low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
224 low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
227 AUPluginUI::~AUPluginUI ()
230 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
234 NSWindow* win = get_nswindow();
235 [win removeChildWindow:cocoa_parent];
240 /* not parented, just overlaid on top of our window */
241 DisposeWindow (carbon_window);
246 CloseComponent (editView);
250 /* remove whatever we packed into low_box so that GTK doesn't
254 [au_view removeFromSuperview];
259 AUPluginUI::test_carbon_view_support ()
264 carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
265 carbon_descriptor.componentSubType = 'gnrc';
266 carbon_descriptor.componentManufacturer = 'appl';
267 carbon_descriptor.componentFlags = 0;
268 carbon_descriptor.componentFlagsMask = 0;
272 // ask the AU for its first editor component
274 err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
276 int nEditors = propertySize / sizeof(ComponentDescription);
277 ComponentDescription *editors = new ComponentDescription[nEditors];
278 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
280 // just pick the first one for now
281 carbon_descriptor = editors[0];
294 AUPluginUI::test_cocoa_view_support ()
297 Boolean isWritable = 0;
298 OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
299 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
300 0, &dataSize, &isWritable);
302 return dataSize > 0 && err == noErr;
306 AUPluginUI::plugin_class_valid (Class pluginClass)
308 if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
309 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
310 [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
318 AUPluginUI::create_cocoa_view ()
320 bool wasAbleToLoadCustomView = false;
321 AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
322 UInt32 numberOfClasses = 0;
325 NSString* factoryClassName = 0;
326 NSURL* CocoaViewBundlePath = NULL;
328 OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
329 kAudioUnitProperty_CocoaUI,
330 kAudioUnitScope_Global,
335 numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
337 // Does view have custom Cocoa UI?
339 if ((result == noErr) && (numberOfClasses > 0) ) {
341 DEBUG_TRACE(DEBUG::AudioUnits,
342 string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
344 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
346 if(AudioUnitGetProperty(*au->get_au(),
347 kAudioUnitProperty_CocoaUI,
348 kAudioUnitScope_Global,
351 &dataSize) == noErr) {
353 CocoaViewBundlePath = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
355 // we only take the first view in this example.
356 factoryClassName = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
358 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
359 [factoryClassName UTF8String], CocoaViewBundlePath));
363 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
365 if (cocoaViewInfo != NULL) {
366 free (cocoaViewInfo);
367 cocoaViewInfo = NULL;
372 // [A] Show custom UI if view has it
374 if (CocoaViewBundlePath && factoryClassName) {
375 NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
377 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
379 if (viewBundle == NULL) {
380 error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
383 Class factoryClass = [viewBundle classNamed:factoryClassName];
384 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
386 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
390 // make sure 'factoryClass' implements the AUCocoaUIBase protocol
391 if (!plugin_class_valid (factoryClass)) {
392 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
396 id factory = [[[factoryClass alloc] init] autorelease];
397 if (factory == NULL) {
398 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
402 DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
405 au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
407 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
410 [CocoaViewBundlePath release];
413 for (i = 0; i < numberOfClasses; i++)
414 CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
416 free (cocoaViewInfo);
418 wasAbleToLoadCustomView = true;
422 if (!wasAbleToLoadCustomView) {
423 // load generic Cocoa view
424 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
426 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
427 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
428 [(AUGenericView *)au_view setShowsExpertParameters:1];
431 // Get the initial size of the new AU View's frame
433 NSRect rect = [au_view frame];
434 prefheight = rect.size.height;
435 prefwidth = rect.size.width;
436 low_box.set_size_request (rect.size.width, rect.size.height);
442 AUPluginUI::cocoa_view_resized ()
444 NSWindow* window = get_nswindow ();
445 NSRect windowFrame= [window frame];
446 NSRect new_frame = [au_view frame];
448 float dy = last_au_frame.size.height - new_frame.size.height;
449 float dx = last_au_frame.size.width - new_frame.size.width;
451 windowFrame.origin.y += dy;
452 windowFrame.origin.x += dx;
453 windowFrame.size.height -= dy;
454 windowFrame.size.width -= dx;
456 [[NSNotificationCenter defaultCenter] removeObserver:_notify
457 name:NSViewFrameDidChangeNotification
460 NSUInteger old_auto_resize = [au_view autoresizingMask];
462 [au_view setAutoresizingMask:NSViewNotSizable];
463 [window setFrame:windowFrame display:1];
465 /* Some stupid AU Views change the origin of the original AU View
466 when they are resized (I'm looking at you AUSampler). If the origin
467 has been moved, move it back.
470 if (last_au_frame.origin.x != new_frame.origin.x ||
471 last_au_frame.origin.y != new_frame.origin.y) {
472 new_frame.origin = last_au_frame.origin;
473 [au_view setFrame:new_frame];
474 /* also be sure to redraw the topbox because this can
477 top_box.queue_draw ();
480 [au_view setAutoresizingMask:old_auto_resize];
482 [[NSNotificationCenter defaultCenter] addObserver:_notify
483 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
486 last_au_frame = new_frame;
490 AUPluginUI::create_carbon_view ()
494 ControlRef root_control;
496 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
498 OpenAComponent(editComponent, &editView);
500 error << _("AU Carbon view: cannot open AU Component") << endmsg;
504 Rect r = { 100, 100, 100, 100 };
505 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
506 kWindowCompositingAttribute|
507 kWindowNoShadowAttribute|
508 kWindowNoTitleBarAttribute);
510 if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
511 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
512 CloseComponent (editView);
516 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
517 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
518 DisposeWindow (carbon_window);
519 CloseComponent (editView);
524 Float32Point location = { 0.0, 0.0 };
525 Float32Point size = { 0.0, 0.0 } ;
527 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
528 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
529 DisposeWindow (carbon_window);
530 CloseComponent (editView);
537 GetControlBounds(viewPane, &bounds);
538 size.x = bounds.right-bounds.left;
539 size.y = bounds.bottom-bounds.top;
541 prefwidth = (int) (size.x + 0.5);
542 prefheight = (int) (size.y + 0.5);
544 SizeWindow (carbon_window, prefwidth, prefheight, true);
545 low_box.set_size_request (prefwidth, prefheight);
549 error << _("AU Carbon GUI is not supported.") << endmsg;
555 AUPluginUI::get_nswindow ()
557 Gtk::Container* toplevel = get_toplevel();
559 if (!toplevel || !toplevel->is_toplevel()) {
560 error << _("AUPluginUI: no top level window!") << endmsg;
564 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
567 error << _("AUPluginUI: no top level window!") << endmsg;
575 AUPluginUI::activate ()
578 ActivateWindow (carbon_window, TRUE);
583 AUPluginUI::deactivate ()
586 ActivateWindow (carbon_window, FALSE);
591 AUPluginUI::parent_carbon_window ()
594 NSWindow* win = get_nswindow ();
595 Rect windowStructureBoundsRect;
601 Gtk::Container* toplevel = get_toplevel();
603 if (!toplevel || !toplevel->is_toplevel()) {
604 error << _("AUPluginUI: no top level window!") << endmsg;
608 /* figure out where the cocoa parent window is in carbon-coordinate space, which
609 differs from both cocoa-coordinate space and GTK-coordinate space
612 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
614 /* compute how tall the title bar is, because we have to offset the position of the carbon window
618 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
619 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
621 int titlebar_height = wm_frame.size.height - content_frame.size.height;
623 int packing_extra = 6; // this is the total vertical packing in our top level window
625 /* move into position, based on parent window position */
626 MoveWindow (carbon_window,
627 windowStructureBoundsRect.left,
628 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
630 ShowWindow (carbon_window);
632 // create the cocoa window for the carbon one and make it visible
633 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
635 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
637 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
639 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
640 [win setAutodisplay:1]; // turn of GTK stuff for this window
649 AUPluginUI::parent_cocoa_window ()
651 NSWindow* win = get_nswindow ();
657 [win setAutodisplay:1]; // turn of GTK stuff for this window
659 Gtk::Container* toplevel = get_toplevel();
661 if (!toplevel || !toplevel->is_toplevel()) {
662 error << _("AUPluginUI: no top level window!") << endmsg;
666 NSView* view = gdk_quartz_window_get_nsview (get_toplevel()->get_window()->gobj());
667 GtkRequisition a = top_box.size_request ();
669 /* move the au_view down so that it doesn't overlap the top_box contents */
671 const int spacing = 6; // main vbox spacing
672 const int pad = 4; // box pad
674 NSPoint origin = { spacing + pad, static_cast<CGFloat> (a.height) + (2 * spacing) + pad };
676 [au_view setFrameOrigin:origin];
677 [view addSubview:au_view positioned:NSWindowBelow relativeTo:NULL];
679 last_au_frame = [au_view frame];
681 // watch for size changes of the view
683 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
685 [[NSNotificationCenter defaultCenter] addObserver:_notify
686 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
693 AUPluginUI::grab_focus()
696 [au_view becomeFirstResponder];
700 AUPluginUI::forward_key_event (GdkEventKey* ev)
702 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
704 if (au_view && nsevent) {
706 /* filter on nsevent type here because GDK massages FlagsChanged
707 messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
708 handle a FlagsChanged message as a keyDown or keyUp
711 if ([nsevent type] == NSKeyDown) {
712 [[[au_view window] firstResponder] keyDown:nsevent];
713 } else if ([nsevent type] == NSKeyUp) {
714 [[[au_view window] firstResponder] keyUp:nsevent];
715 } else if ([nsevent type] == NSFlagsChanged) {
716 [[[au_view window] firstResponder] flagsChanged:nsevent];
722 AUPluginUI::on_realize ()
726 /* our windows should not have that resize indicator */
728 NSWindow* win = get_nswindow ();
730 [win setShowsResizeIndicator:0];
735 AUPluginUI::lower_box_realized ()
738 parent_cocoa_window ();
739 } else if (carbon_window) {
740 parent_carbon_window ();
745 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
748 if (carbon_window && ev->state != GDK_VISIBILITY_UNOBSCURED) {
749 ShowWindow (carbon_window);
750 ActivateWindow (carbon_window, TRUE);
758 AUPluginUI::on_window_hide ()
762 HideWindow (carbon_window);
763 ActivateWindow (carbon_window, FALSE);
769 NSArray* wins = [NSApp windows];
770 for (uint32_t i = 0; i < [wins count]; i++) {
771 id win = [wins objectAtIndex:i];
777 AUPluginUI::on_window_show (const string& /*title*/)
779 /* this is idempotent so just call it every time we show the window */
781 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
787 ShowWindow (carbon_window);
788 ActivateWindow (carbon_window, TRUE);
796 AUPluginUI::start_updating (GdkEventAny*)
802 AUPluginUI::stop_updating (GdkEventAny*)
808 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
810 AUPluginUI* aup = new AUPluginUI (plugin_insert);