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.add_events(Gdk::VISIBILITY_NOTIFY_MASK);
215 low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
216 low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
219 AUPluginUI::~AUPluginUI ()
222 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
226 NSWindow* win = get_nswindow();
227 [win removeChildWindow:cocoa_parent];
232 /* not parented, just overlaid on top of our window */
233 DisposeWindow (carbon_window);
238 CloseComponent (editView);
242 /* remove whatever we packed into low_box so that GTK doesn't
246 [au_view removeFromSuperview];
251 AUPluginUI::test_carbon_view_support ()
256 carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
257 carbon_descriptor.componentSubType = 'gnrc';
258 carbon_descriptor.componentManufacturer = 'appl';
259 carbon_descriptor.componentFlags = 0;
260 carbon_descriptor.componentFlagsMask = 0;
264 // ask the AU for its first editor component
266 err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
268 int nEditors = propertySize / sizeof(ComponentDescription);
269 ComponentDescription *editors = new ComponentDescription[nEditors];
270 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
272 // just pick the first one for now
273 carbon_descriptor = editors[0];
286 AUPluginUI::test_cocoa_view_support ()
289 Boolean isWritable = 0;
290 OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
291 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
292 0, &dataSize, &isWritable);
294 return dataSize > 0 && err == noErr;
298 AUPluginUI::plugin_class_valid (Class pluginClass)
300 if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
301 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
302 [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
310 AUPluginUI::create_cocoa_view ()
312 bool wasAbleToLoadCustomView = false;
313 AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
314 UInt32 numberOfClasses = 0;
317 NSString* factoryClassName = 0;
318 NSURL* CocoaViewBundlePath = NULL;
320 OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
321 kAudioUnitProperty_CocoaUI,
322 kAudioUnitScope_Global,
327 numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
329 // Does view have custom Cocoa UI?
331 if ((result == noErr) && (numberOfClasses > 0) ) {
333 DEBUG_TRACE(DEBUG::AudioUnits,
334 string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
336 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
338 if(AudioUnitGetProperty(*au->get_au(),
339 kAudioUnitProperty_CocoaUI,
340 kAudioUnitScope_Global,
343 &dataSize) == noErr) {
345 CocoaViewBundlePath = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
347 // we only take the first view in this example.
348 factoryClassName = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
350 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
351 [factoryClassName UTF8String], CocoaViewBundlePath));
355 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
357 if (cocoaViewInfo != NULL) {
358 free (cocoaViewInfo);
359 cocoaViewInfo = NULL;
364 // [A] Show custom UI if view has it
366 if (CocoaViewBundlePath && factoryClassName) {
367 NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
369 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
371 if (viewBundle == NULL) {
372 error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
375 Class factoryClass = [viewBundle classNamed:factoryClassName];
376 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
378 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
382 // make sure 'factoryClass' implements the AUCocoaUIBase protocol
383 if (!plugin_class_valid (factoryClass)) {
384 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
388 id factory = [[[factoryClass alloc] init] autorelease];
389 if (factory == NULL) {
390 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
394 DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
397 au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
399 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
402 [CocoaViewBundlePath release];
405 for (i = 0; i < numberOfClasses; i++)
406 CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
408 free (cocoaViewInfo);
410 wasAbleToLoadCustomView = true;
414 if (!wasAbleToLoadCustomView) {
415 // load generic Cocoa view
416 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
418 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
419 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
420 [(AUGenericView *)au_view setShowsExpertParameters:1];
423 // Get the initial size of the new AU View's frame
425 NSRect rect = [au_view frame];
426 prefheight = rect.size.height;
427 prefwidth = rect.size.width;
428 low_box.set_size_request (rect.size.width, rect.size.height);
434 AUPluginUI::cocoa_view_resized ()
436 NSWindow* window = get_nswindow ();
437 NSRect windowFrame= [window frame];
438 NSRect new_frame = [au_view frame];
440 float dy = last_au_frame.size.height - new_frame.size.height;
441 float dx = last_au_frame.size.width - new_frame.size.width;
443 windowFrame.origin.y += dy;
444 windowFrame.origin.x += dx;
445 windowFrame.size.height -= dy;
446 windowFrame.size.width -= dx;
448 [[NSNotificationCenter defaultCenter] removeObserver:_notify
449 name:NSViewFrameDidChangeNotification
452 NSUInteger old_auto_resize = [au_view autoresizingMask];
454 [au_view setAutoresizingMask:NSViewNotSizable];
455 [window setFrame:windowFrame display:1];
457 /* Some stupid AU Views change the origin of the original AU View
458 when they are resized (I'm looking at you AUSampler). If the origin
459 has been moved, move it back.
462 if (last_au_frame.origin.x != new_frame.origin.x ||
463 last_au_frame.origin.y != new_frame.origin.y) {
464 new_frame.origin = last_au_frame.origin;
465 [au_view setFrame:new_frame];
466 /* also be sure to redraw the topbox because this can
469 top_box.queue_draw ();
472 [au_view setAutoresizingMask:old_auto_resize];
474 [[NSNotificationCenter defaultCenter] addObserver:_notify
475 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
478 last_au_frame = new_frame;
482 AUPluginUI::create_carbon_view ()
486 ControlRef root_control;
488 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
490 OpenAComponent(editComponent, &editView);
492 error << _("AU Carbon view: cannot open AU Component") << endmsg;
496 Rect r = { 100, 100, 100, 100 };
497 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
498 kWindowCompositingAttribute|
499 kWindowNoShadowAttribute|
500 kWindowNoTitleBarAttribute);
502 if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
503 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
504 CloseComponent (editView);
508 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
509 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
510 DisposeWindow (carbon_window);
511 CloseComponent (editView);
516 Float32Point location = { 0.0, 0.0 };
517 Float32Point size = { 0.0, 0.0 } ;
519 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
520 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
521 DisposeWindow (carbon_window);
522 CloseComponent (editView);
529 GetControlBounds(viewPane, &bounds);
530 size.x = bounds.right-bounds.left;
531 size.y = bounds.bottom-bounds.top;
533 prefwidth = (int) (size.x + 0.5);
534 prefheight = (int) (size.y + 0.5);
536 SizeWindow (carbon_window, prefwidth, prefheight, true);
537 low_box.set_size_request (prefwidth, prefheight);
541 error << _("AU Carbon GUI is not supported.") << endmsg;
547 AUPluginUI::get_nswindow ()
549 Gtk::Container* toplevel = get_toplevel();
551 if (!toplevel || !toplevel->is_toplevel()) {
552 error << _("AUPluginUI: no top level window!") << endmsg;
556 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
559 error << _("AUPluginUI: no top level window!") << endmsg;
567 AUPluginUI::activate ()
570 ActivateWindow (carbon_window, TRUE);
575 AUPluginUI::deactivate ()
578 ActivateWindow (carbon_window, FALSE);
583 AUPluginUI::parent_carbon_window ()
586 NSWindow* win = get_nswindow ();
587 Rect windowStructureBoundsRect;
593 Gtk::Container* toplevel = get_toplevel();
595 if (!toplevel || !toplevel->is_toplevel()) {
596 error << _("AUPluginUI: no top level window!") << endmsg;
600 /* figure out where the cocoa parent window is in carbon-coordinate space, which
601 differs from both cocoa-coordinate space and GTK-coordinate space
604 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
606 /* compute how tall the title bar is, because we have to offset the position of the carbon window
610 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
611 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
613 int titlebar_height = wm_frame.size.height - content_frame.size.height;
615 int packing_extra = 6; // this is the total vertical packing in our top level window
617 /* move into position, based on parent window position */
618 MoveWindow (carbon_window,
619 windowStructureBoundsRect.left,
620 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
622 ShowWindow (carbon_window);
624 // create the cocoa window for the carbon one and make it visible
625 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
627 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
629 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
631 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
632 [win setAutodisplay:1]; // turn of GTK stuff for this window
641 AUPluginUI::parent_cocoa_window ()
643 NSWindow* win = get_nswindow ();
649 [win setAutodisplay:1]; // turn of GTK stuff for this window
651 Gtk::Container* toplevel = get_toplevel();
653 if (!toplevel || !toplevel->is_toplevel()) {
654 error << _("AUPluginUI: no top level window!") << endmsg;
658 NSView* view = gdk_quartz_window_get_nsview (get_toplevel()->get_window()->gobj());
659 GtkRequisition a = top_box.size_request ();
661 /* move the au_view down so that it doesn't overlap the top_box contents */
663 const int spacing = 6; // main vbox spacing
664 const int pad = 4; // box pad
666 NSPoint origin = { spacing + pad, static_cast<CGFloat> (a.height) + (2 * spacing) + pad };
668 [au_view setFrameOrigin:origin];
669 [view addSubview:au_view positioned:NSWindowBelow relativeTo:NULL];
671 last_au_frame = [au_view frame];
673 // watch for size changes of the view
675 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
677 [[NSNotificationCenter defaultCenter] addObserver:_notify
678 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
685 AUPluginUI::grab_focus()
688 [au_view becomeFirstResponder];
692 AUPluginUI::forward_key_event (GdkEventKey* ev)
694 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
696 if (au_view && nsevent) {
698 /* filter on nsevent type here because GDK massages FlagsChanged
699 messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
700 handle a FlagsChanged message as a keyDown or keyUp
703 if ([nsevent type] == NSKeyDown) {
704 [[[au_view window] firstResponder] keyDown:nsevent];
705 } else if ([nsevent type] == NSKeyUp) {
706 [[[au_view window] firstResponder] keyUp:nsevent];
707 } else if ([nsevent type] == NSFlagsChanged) {
708 [[[au_view window] firstResponder] flagsChanged:nsevent];
714 AUPluginUI::on_realize ()
718 /* our windows should not have that resize indicator */
720 NSWindow* win = get_nswindow ();
722 [win setShowsResizeIndicator:0];
727 AUPluginUI::lower_box_realized ()
730 parent_cocoa_window ();
731 } else if (carbon_window) {
732 parent_carbon_window ();
737 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
740 if (carbon_window && ev->state != GDK_VISIBILITY_UNOBSCURED) {
741 ShowWindow (carbon_window);
742 ActivateWindow (carbon_window, TRUE);
750 AUPluginUI::on_window_hide ()
754 HideWindow (carbon_window);
755 ActivateWindow (carbon_window, FALSE);
761 NSArray* wins = [NSApp windows];
762 for (uint32_t i = 0; i < [wins count]; i++) {
763 id win = [wins objectAtIndex:i];
769 AUPluginUI::on_window_show (const string& /*title*/)
771 /* this is idempotent so just call it every time we show the window */
773 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
779 ShowWindow (carbon_window);
780 ActivateWindow (carbon_window, TRUE);
788 AUPluginUI::start_updating (GdkEventAny*)
794 AUPluginUI::stop_updating (GdkEventAny*)
800 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
802 AUPluginUI* aup = new AUPluginUI (plugin_insert);