2 #define Marker FuckYouAppleAndYourLackOfNameSpaces
5 #include <gtkmm/button.h>
6 #include <gdk/gdkquartz.h>
8 #include "pbd/convert.h"
11 #include "ardour/audio_unit.h"
12 #include "ardour/debug.h"
13 #include "ardour/plugin_insert.h"
15 #undef check // stupid gtk, stupid apple
17 #include <gtkmm2ext/utils.h>
18 #include <gtkmm2ext/window_proxy.h>
20 #include "au_pluginui.h"
21 #include "gui_thread.h"
22 #include "processor_box.h"
24 #include "CAAudioUnit.h"
25 #include "CAComponent.h"
27 #import <AudioUnit/AUCocoaUIView.h>
28 #import <CoreAudioKit/AUGenericView.h>
29 #import <objc/runtime.h>
30 #include <dispatch/dispatch.h>
36 #include "public_editor.h"
39 #include "gtk2ardour-config.h"
42 #define ArdourCloseComponent CloseComponent
44 #define ArdourCloseComponent AudioComponentInstanceDispose
46 using namespace ARDOUR;
48 using namespace Gtkmm2ext;
52 vector<string> AUPluginUI::automation_mode_strings;
53 bool AUPluginUI::idle_meter_needed = true;
54 int64_t AUPluginUI::last_idle = 0;
56 static const gchar* _automation_mode_strings[] = {
65 dump_view_tree (NSView* view, int depth, int maxdepth)
67 NSArray* subviews = [view subviews];
68 unsigned long cnt = [subviews count];
71 NSView* su = [view superview];
73 NSRect sf = [su frame];
74 cerr << " PARENT view " << su << " @ " << sf.origin.x << ", " << sf.origin.y
75 << ' ' << sf.size.width << " x " << sf.size.height
80 for (int d = 0; d < depth; d++) {
83 NSRect frame = [view frame];
84 cerr << " view " << view << " @ " << frame.origin.x << ", " << frame.origin.y
85 << ' ' << frame.size.width << " x " << frame.size.height
88 if (depth >= maxdepth) {
91 for (unsigned long i = 0; i < cnt; ++i) {
92 NSView* subview = [subviews objectAtIndex:i];
93 dump_view_tree (subview, depth+1, maxdepth);
97 /* This deeply hacky block of code exists for a rather convoluted reason.
99 * The proximal reason is that there are plugins (such as XLN's Addictive Drums
100 * 2) which redraw their GUI/editor windows using a timer, and use a drawing
101 * technique that on Retina displays ends up calling arg32_image_mark_RGB32, a
102 * function that for some reason (probably byte-swapping or pixel-doubling) is
103 * many times slower than the function used on non-Retina displays.
105 * We are not the first people to discover the problem with
106 * arg32_image_mark_RGB32.
108 * Justin Fraenkel, the lead author of Reaper, wrote a very detailed account of
109 * the performance issues with arg32_image_mark_RGB32 here:
110 * http://www.1014.org/?article=516
112 * The problem was also seen by Robert O'Callahan (lead developer of rr, the
113 * reverse debugger) as far back as 2010:
114 * http://robert.ocallahan.org/2010/05/cglayer-performance-trap-with-isflipped_03.html
116 * In fact, it is so slow that the drawing takes up close to 100% of a single
117 * core, and the event loop that the drawing occurs in never sleeps or "idles".
119 * In AU hosts built directly on top of Cocoa, or some other toolkits, this
120 * isn't inherently a major problem - it just makes the entire GUI of the
123 * However, there is an additional problem for Ardour because GTK+ is built on
124 * top of the GDK/Quartz event loop integration. This integration is rather
125 * baroque, mostly because it was written at a time when CFRunLoop did not
126 * offer a way to wait for "input" from file descriptors (which arrived in OS X
127 * 10.5). As a result, it uses a hair-raising design involving an additional
128 * thread. This design has a major problem, which is that it effectively
129 * creates two nested run loops.
131 * The GTK+/GDK/glib one runs until it has nothing to do, at which time it
132 * calls a function to wait until there is something to do. On Linux or Windows
133 * that would involve some variant or relative of poll(2), which puts the
134 * process to sleep until there is something to do.
136 * On OS X, glib ends up calling [CFRunLoop waitForNextEventMatchingMask] which
137 * will eventually put the process to sleep, but won't do so until the
138 * CFRunLoop also has nothing to do. This includes (at least) a complete redraw
139 * cycle. If redrawing takes too long, and there are timers expired for another
140 * redraw (e.g. Addictive Drums 2, again), then the CFRunLoop will just start
141 * another redraw cycle after processing any events and other stuff.
143 * If the CFRunLoop stays busy, then it will never return to the glib
144 * level at all, thus stopping any further GTK+ level activity (events,
145 * drawing) from taking place. In short, the current (spring 2016) design of
146 * the GDK/Quartz event loop integration relies on the idea that the internal
147 * CFRunLoop will go idle, and totally breaks if this does not happen.
149 * So take a fully functional Ardour, add in XLN's Addictive Drums 2, and a
150 * Retina display, and Apple's ridiculously slow blitting code, and the
151 * CFRunLoop never goes idle. As soon as Addictive Drums starts drawing (over
152 * and over again), the GTK+ event loop stops receiving events and stops
155 * One fix for this was to run a nested GTK+ event loop iteration (or two)
156 * whenever a plugin window was redrawn. This works in the sense that the
157 * immediate issue (no GTK+ events or drawing) is fixed. But the recursive GTK+
158 * event loop causes its own (very subtle) problems too.
160 * This code takes a rather radical approach. We use Objective C's ability to
161 * swizzle object methods. Specifically, we replace [NSView displayIfNeeded]
162 * with our own version which will skip redraws of plugin windows if we tell it
163 * too. If we haven't done that, or if the redraw is of a non-plugin window,
164 * then we invoke the original displayIfNeeded method.
166 * After every 10 redraws of a given plugin GUI/editor window, we queue up a
167 * GTK/glib idle callback to measure the interval between those idle
168 * callbacks. We do this globally across all plugin windows, so if the callback
169 * is already queued, we don't requeue it.
171 * If the interval is longer than 40msec (a 25fps redraw rate), we set
172 * block_plugin_redraws to some number. Each successive call to our interposed
173 * displayIfNeeded method will (a) check this value and if non-zero (b) check
174 * if the call is for a plugin-related NSView/NSWindow. If it is, then we will
175 * skip the redisplay entirely, hopefully avoiding any calls to
176 * argb32_image_mark_RGB32 or any other slow drawing code, and thus allowing
177 * the CFRunLoop to go idle. If the value is zero or the call is for a
178 * non-plugin window, then we just invoke the "original" displayIfNeeded
181 * This hack adds a tiny bit of overhead onto redrawing of the entire
182 * application. But in the common case this consists of 1 conditional (the
183 * check on block_plugin_redraws, which will find it to be zero) and the
184 * invocation of the original method. Given how much work is typically done
185 * during drawing, this seems acceptable.
187 * The correct fix for this is to redesign the relationship between
188 * GTK+/GDK/glib so that a glib run loop is actually a CFRunLoop, with all
189 * GSources represented as CFRunLoopSources, without any nesting and without
190 * any additional thread. This is not a task to be undertaken lightly, and is
191 * certainly substantially more work than this was. It may never be possible to
192 * do that work in a way that could be integrated back into glib, because of
193 * the rather specific semantics and types of GSources, but it would almost
194 * certainly be possible to make it work for Ardour.
197 static IMP original_nsview_drawIfNeeded;
198 static std::vector<id> plugin_views;
199 static uint32_t block_plugin_redraws = 0;
200 static const uint32_t minimum_redraw_rate = 25; /* frames per second */
201 static const uint32_t block_plugin_redraw_count = 100; /* number of combined plugin redraws to block, if blocking */
203 static void add_plugin_view (id view)
205 plugin_views.push_back (view);
208 static void remove_plugin_view (id view)
210 std::vector<id>::iterator x = find (plugin_views.begin(), plugin_views.end(), view);
211 if (x != plugin_views.end()) {
212 plugin_views.erase (x);
216 static void interposed_drawIfNeeded (id receiver, SEL selector, NSRect rect)
218 if (block_plugin_redraws && (find (plugin_views.begin(), plugin_views.end(), receiver) != plugin_views.end())) {
219 block_plugin_redraws--;
220 std::cerr << "Plugin redraw blocked\n";
221 /* YOU ... SHALL .... NOT ... DRAW!!!! */
224 (void) ((int (*)(id,SEL,NSRect)) original_nsview_drawIfNeeded) (receiver, selector, rect);
227 @implementation NSView (Tracking)
229 static dispatch_once_t once_token;
231 /* this swizzles NSView::displayIfNeeded and replaces it with
232 * interposed_drawIfNeeded(), which allows us to interpose and block
233 * the redrawing of plugin UIs when their redrawing behaviour
234 * is interfering with event loop behaviour.
237 dispatch_once (&once_token, ^{
238 Method target = class_getInstanceMethod ([NSView class], @selector(displayIfNeeded));
239 original_nsview_drawIfNeeded = method_setImplementation (target, (IMP) interposed_drawIfNeeded);
245 /* END OF THE PLUGIN REDRAW HACK */
247 @implementation NotificationObject
249 - (NotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui andCocoaParent: (NSWindow*) cp andTopLevelParent: (NSWindow*) tlp
251 self = [ super init ];
254 plugin_ui = apluginui;
255 top_level_parent = tlp;
260 [[NSNotificationCenter defaultCenter]
262 selector:@selector(cocoaParentActivationHandler:)
263 name:NSWindowDidBecomeMainNotification
266 [[NSNotificationCenter defaultCenter]
268 selector:@selector(cocoaParentBecameKeyHandler:)
269 name:NSWindowDidBecomeKeyNotification
277 - (void)cocoaParentActivationHandler:(NSNotification *)notification
279 NSWindow* notification_window = (NSWindow *)[notification object];
281 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
282 if ([notification_window isMainWindow]) {
283 plugin_ui->activate();
285 plugin_ui->deactivate();
290 - (void)cocoaParentBecameKeyHandler:(NSNotification *)notification
292 NSWindow* notification_window = (NSWindow *)[notification object];
294 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
295 if ([notification_window isKeyWindow]) {
296 plugin_ui->activate();
298 plugin_ui->deactivate();
303 - (void)auViewResized:(NSNotification *)notification
305 (void) notification; // stop complaints about unusued argument
306 plugin_ui->cocoa_view_resized();
311 @implementation LiveResizeNotificationObject
313 - (LiveResizeNotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui
315 self = [ super init ];
317 plugin_ui = apluginui;
323 - (void)windowWillStartLiveResizeHandler:(NSNotification*)notification
325 plugin_ui->start_live_resize ();
328 - (void)windowWillEndLiveResizeHandler:(NSNotification*)notification
330 plugin_ui->end_live_resize ();
334 AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
335 : PlugUIBase (insert)
336 , automation_mode_label (_("Automation"))
337 , preset_label (_("Presets"))
343 , in_live_resize (false)
344 , plugin_requested_resize (0)
350 if (automation_mode_strings.empty()) {
351 automation_mode_strings = I18N (_automation_mode_strings);
354 set_popdown_strings (automation_mode_selector, automation_mode_strings);
355 automation_mode_selector.set_active_text (automation_mode_strings.front());
357 if ((au = boost::dynamic_pointer_cast<AUPlugin> (insert->plugin())) == 0) {
358 error << _("unknown type of editor-supplying plugin (note: no AudioUnit support in this version of ardour)") << endmsg;
359 throw failed_constructor ();
362 /* stuff some stuff into the top of the window */
364 HBox* smaller_hbox = manage (new HBox);
366 smaller_hbox->set_spacing (6);
367 smaller_hbox->pack_start (preset_label, false, false, 4);
368 smaller_hbox->pack_start (_preset_modified, false, false);
369 smaller_hbox->pack_start (_preset_combo, false, false);
370 smaller_hbox->pack_start (add_button, false, false);
372 /* Ardour does not currently allow to overwrite existing presets
373 * see save_property_list() in audio_unit.cc
375 smaller_hbox->pack_start (save_button, false, false);
378 /* one day these might be useful with an AU plugin, but not yet */
379 smaller_hbox->pack_start (automation_mode_label, false, false);
380 smaller_hbox->pack_start (automation_mode_selector, false, false);
382 smaller_hbox->pack_start (reset_button, false, false);
383 smaller_hbox->pack_start (bypass_button, false, true);
385 VBox* v1_box = manage (new VBox);
386 VBox* v2_box = manage (new VBox);
388 v1_box->pack_start (*smaller_hbox, false, true);
389 v2_box->pack_start (focus_button, false, true);
391 top_box.set_homogeneous (false);
392 top_box.set_spacing (6);
393 top_box.set_border_width (6);
395 top_box.pack_end (*v2_box, false, false);
396 top_box.pack_end (*v1_box, false, false);
399 pack_start (top_box, false, false);
400 pack_start (low_box, true, true);
402 preset_label.show ();
403 _preset_combo.show ();
404 automation_mode_label.show ();
405 automation_mode_selector.show ();
406 bypass_button.show ();
414 _activating_from_app = false;
421 /* prefer cocoa, fall back to cocoa, but use carbon if its there */
423 if (test_cocoa_view_support()) {
424 create_cocoa_view ();
426 } else if (test_carbon_view_support()) {
427 create_carbon_view ();
430 create_cocoa_view ();
433 low_box.add_events (Gdk::VISIBILITY_NOTIFY_MASK | Gdk::EXPOSURE_MASK);
435 low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
436 low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
438 low_box.signal_size_request ().connect (mem_fun (this, &AUPluginUI::lower_box_size_request));
439 low_box.signal_size_allocate ().connect (mem_fun (this, &AUPluginUI::lower_box_size_allocate));
440 low_box.signal_map ().connect (mem_fun (this, &AUPluginUI::lower_box_map));
441 low_box.signal_unmap ().connect (mem_fun (this, &AUPluginUI::lower_box_unmap));
442 low_box.signal_expose_event ().connect (mem_fun (this, &AUPluginUI::lower_box_expose));
446 AUPluginUI::~AUPluginUI ()
449 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
452 if (_resize_notify) {
453 [[NSNotificationCenter defaultCenter] removeObserver:_resize_notify];
456 NSWindow* win = get_nswindow();
458 remove_plugin_view ([[win contentView] superview]);
463 [win removeChildWindow:cocoa_parent];
467 /* not parented, just overlaid on top of our window */
468 DisposeWindow (carbon_window);
473 ArdourCloseComponent (editView);
477 /* remove whatever we packed into low_box so that GTK doesn't
480 [au_view removeFromSuperview];
485 AUPluginUI::test_carbon_view_support ()
490 carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
491 carbon_descriptor.componentSubType = 'gnrc';
492 carbon_descriptor.componentManufacturer = 'appl';
493 carbon_descriptor.componentFlags = 0;
494 carbon_descriptor.componentFlagsMask = 0;
498 // ask the AU for its first editor component
500 err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
502 int nEditors = propertySize / sizeof(ComponentDescription);
503 ComponentDescription *editors = new ComponentDescription[nEditors];
504 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
506 // just pick the first one for now
507 carbon_descriptor = editors[0];
520 AUPluginUI::test_cocoa_view_support ()
523 Boolean isWritable = 0;
524 OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
525 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
526 0, &dataSize, &isWritable);
528 return dataSize > 0 && err == noErr;
532 AUPluginUI::plugin_class_valid (Class pluginClass)
534 if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
535 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
536 [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
544 AUPluginUI::create_cocoa_view ()
546 bool wasAbleToLoadCustomView = false;
547 AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
548 UInt32 numberOfClasses = 0;
551 NSString* factoryClassName = 0;
552 NSURL* CocoaViewBundlePath = NULL;
554 OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
555 kAudioUnitProperty_CocoaUI,
556 kAudioUnitScope_Global,
561 numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
563 // Does view have custom Cocoa UI?
565 if ((result == noErr) && (numberOfClasses > 0) ) {
567 DEBUG_TRACE(DEBUG::AudioUnits,
568 string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
570 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
572 if(AudioUnitGetProperty(*au->get_au(),
573 kAudioUnitProperty_CocoaUI,
574 kAudioUnitScope_Global,
577 &dataSize) == noErr) {
579 CocoaViewBundlePath = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
581 // we only take the first view in this example.
582 factoryClassName = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
584 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
585 [factoryClassName UTF8String], CocoaViewBundlePath));
589 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
591 if (cocoaViewInfo != NULL) {
592 free (cocoaViewInfo);
593 cocoaViewInfo = NULL;
598 // [A] Show custom UI if view has it
600 if (CocoaViewBundlePath && factoryClassName) {
601 NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
603 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
605 if (viewBundle == NULL) {
606 error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
609 Class factoryClass = [viewBundle classNamed:factoryClassName];
610 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
612 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
616 // make sure 'factoryClass' implements the AUCocoaUIBase protocol
617 if (!plugin_class_valid (factoryClass)) {
618 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
622 id factory = [[[factoryClass alloc] init] autorelease];
623 if (factory == NULL) {
624 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
628 DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
631 au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
633 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
636 [CocoaViewBundlePath release];
639 for (i = 0; i < numberOfClasses; i++)
640 CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
642 free (cocoaViewInfo);
644 wasAbleToLoadCustomView = true;
648 if (!wasAbleToLoadCustomView) {
649 // load generic Cocoa view
650 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
652 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
653 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
654 [(AUGenericView *)au_view setShowsExpertParameters:1];
657 // Get the initial size of the new AU View's frame
658 NSRect frame = [au_view frame];
659 req_width = frame.size.width;
660 req_height = frame.size.height;
662 resizable = [au_view autoresizingMask];
664 low_box.queue_resize ();
670 AUPluginUI::update_view_size ()
672 last_au_frame = [au_view frame];
676 AUPluginUI::idle_meter ()
678 int64_t now = ARDOUR::get_microseconds ();
682 return true; /* call me again */
685 if ((now - last_idle) > (1000000/minimum_redraw_rate)) {
686 block_plugin_redraws = block_plugin_redraw_count;
687 std::cerr << "Idle too slow (" << (now - last_idle) << " usecs), block plugin redraws for the next 10 plugin exposes\n";
689 std::cerr << "idle all good: elapsed was " << (now - last_idle) << endl;
692 /* We've been called twice. Cancel everything for now. */
694 idle_meter_needed = true;
701 AUPluginUI::cocoa_view_resized ()
703 /* we can get here for two reasons:
705 1) the plugin window was resized by the user, a new size was
706 allocated to the window, ::update_view_size() was called, and we
707 explicitly/manually resized the AU NSView.
709 2) the plugin decided to resize itself (probably in response to user
710 action, but not in response to an actual window resize)
712 We only want to proceed with a window resizing in the second case.
715 if (in_live_resize) {
716 /* ::update_view_size() will be called at the right times and
717 * will update the view size. We don't need to anything while a
718 * live resize in underway.
723 if (plugin_requested_resize) {
724 /* we tried to change the plugin frame from inside this method
725 * (to adjust the origin), which changes the frame of the AU
726 * NSView, resulting in a reentrant call to the FrameDidChange
727 * handler (this method). Ignore this reentrant call.
729 std::cerr << plugin->name() << " re-entrant call to cocoa_view_resized, ignored\n";
733 plugin_requested_resize = 1;
735 ProcessorWindowProxy* wp = insert->window_proxy();
737 /* Once a plugin has requested a resize of its own window, do
738 * NOT save the window. The user may save state with the plugin
739 * editor expanded to show "extra detail" - the plugin will not
740 * refill this space when the editor is first
741 * instantiated. Leaving the window in the "too big" state
742 * cannot be recovered from.
744 * The window will be sized to fit the plugin's own request. Done.
746 wp->set_state_mask (WindowProxy::Position);
749 NSRect new_frame = [au_view frame];
751 /* from here on, we know that we've been called because the plugin
752 * decided to change the NSView frame itself.
755 /* step one: compute the change in the frame size.
758 float dy = new_frame.size.height - last_au_frame.size.height;
759 float dx = new_frame.size.width - last_au_frame.size.width;
761 NSWindow* window = get_nswindow ();
762 NSRect windowFrame= [window frame];
764 /* we want the top edge of the window to remain in the same place,
765 but the Cocoa/Quartz origin is at the lower left. So, when we make
766 the window larger, we will move it down, which means shifting the
767 origin toward (x,0). This will leave the top edge in the same place.
770 windowFrame.origin.y -= dy;
771 windowFrame.origin.x -= dx;
772 windowFrame.size.height += dy;
773 windowFrame.size.width += dx;
775 NSUInteger old_auto_resize = [au_view autoresizingMask];
777 /* Some stupid AU Views change the origin of the original AU View when
778 they are resized (I'm looking at you AUSampler). If the origin has
779 been moved, move it back.
782 if (last_au_frame.origin.x != new_frame.origin.x ||
783 last_au_frame.origin.y != new_frame.origin.y) {
784 new_frame.origin = last_au_frame.origin;
785 [au_view setFrame:new_frame];
786 /* also be sure to redraw the topbox because this can
789 top_box.queue_draw ();
792 /* We resize the window using Cocoa. We can't use GTK mechanisms
795 * http://www.lists.apple.com/archives/coreaudio-api/2005/Aug/msg00245.html
797 * "The host needs to be aware that changing the size of the window in
798 * response to the NSViewFrameDidChangeNotification can cause the view
799 * size to change depending on the autoresizing mask of the view. The
800 * host may need to cache the autoresizing mask of the view, set it to
801 * NSViewNotSizable, resize the window, and then reset the autoresizing
802 * mask of the view once the window has been sized."
806 [au_view setAutoresizingMask:NSViewNotSizable];
807 [window setFrame:windowFrame display:1];
808 [au_view setAutoresizingMask:old_auto_resize];
810 /* keep a copy of the size of the AU NSView. We didn't set it - the plugin did */
811 last_au_frame = new_frame;
812 req_width = new_frame.size.width;
813 req_height = new_frame.size.height;
815 plugin_requested_resize = 0;
819 AUPluginUI::create_carbon_view ()
823 ControlRef root_control;
825 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
827 OpenAComponent(editComponent, &editView);
829 error << _("AU Carbon view: cannot open AU Component") << endmsg;
833 Rect r = { 100, 100, 100, 100 };
834 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
835 kWindowCompositingAttribute|
836 kWindowNoShadowAttribute|
837 kWindowNoTitleBarAttribute);
839 if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
840 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
841 ArdourCloseComponent (editView);
845 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
846 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
847 DisposeWindow (carbon_window);
848 ArdourCloseComponent (editView);
853 Float32Point location = { 0.0, 0.0 };
854 Float32Point size = { 0.0, 0.0 } ;
856 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
857 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
858 DisposeWindow (carbon_window);
859 ArdourCloseComponent (editView);
866 GetControlBounds(viewPane, &bounds);
867 size.x = bounds.right-bounds.left;
868 size.y = bounds.bottom-bounds.top;
870 req_width = (int) (size.x + 0.5);
871 req_height = (int) (size.y + 0.5);
873 SizeWindow (carbon_window, req_width, req_height, true);
874 low_box.set_size_request (req_width, req_height);
878 error << _("AU Carbon GUI is not supported.") << endmsg;
884 AUPluginUI::get_nswindow ()
886 Gtk::Container* toplevel = get_toplevel();
888 if (!toplevel || !toplevel->is_toplevel()) {
889 error << _("AUPluginUI: no top level window!") << endmsg;
893 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
896 error << _("AUPluginUI: no top level window!") << endmsg;
904 AUPluginUI::activate ()
907 ActivateWindow (carbon_window, TRUE);
912 AUPluginUI::deactivate ()
915 ActivateWindow (carbon_window, FALSE);
920 AUPluginUI::parent_carbon_window ()
923 NSWindow* win = get_nswindow ();
924 Rect windowStructureBoundsRect;
930 /* figure out where the cocoa parent window is in carbon-coordinate space, which
931 differs from both cocoa-coordinate space and GTK-coordinate space
934 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
936 /* compute how tall the title bar is, because we have to offset the position of the carbon window
940 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
941 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
943 int titlebar_height = wm_frame.size.height - content_frame.size.height;
945 int packing_extra = 6; // this is the total vertical packing in our top level window
947 /* move into position, based on parent window position */
948 MoveWindow (carbon_window,
949 windowStructureBoundsRect.left,
950 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
952 ShowWindow (carbon_window);
954 // create the cocoa window for the carbon one and make it visible
955 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
957 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
959 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
961 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
962 [win setAutodisplay:1]; // turn of GTK stuff for this window
971 AUPluginUI::parent_cocoa_window ()
973 NSWindow* win = get_nswindow ();
979 //[win setAutodisplay:1]; // turn off GTK stuff for this window
981 NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
982 [view addSubview:au_view];
983 /* despite the fact that the documentation says that [NSWindow
984 contentView] is the highest "accessible" NSView in an NSWindow, when
985 the redraw cycle is executed, displayIfNeeded is actually executed
986 on the parent of the contentView. To provide a marginal speedup when
987 checking if a given redraw is for a plugin, use this "hidden" NSView
988 to identify the plugin, so that we do not have to call [superview]
989 every time in interposed_drawIfNeeded().
991 add_plugin_view ([[win contentView] superview]);
993 /* this moves the AU NSView down and over to provide a left-hand margin
994 * and to clear the Ardour "task bar" (with plugin preset mgmt buttons,
995 * keyboard focus control, bypass etc).
999 gtk_widget_translate_coordinates(
1000 GTK_WIDGET(low_box.gobj()),
1001 GTK_WIDGET(low_box.get_parent()->gobj()),
1003 [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
1005 last_au_frame = [au_view frame];
1006 // watch for size changes of the view
1007 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
1009 [[NSNotificationCenter defaultCenter] addObserver:_notify
1010 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
1013 // catch notifications that live resizing is about to start
1015 #if HAVE_COCOA_LIVE_RESIZING
1016 _resize_notify = [ [ LiveResizeNotificationObject alloc] initWithPluginUI:this ];
1018 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1019 selector:@selector(windowWillStartLiveResizeHandler:) name:NSWindowWillStartLiveResizeNotification
1022 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1023 selector:@selector(windowWillEndLiveResizeHandler:) name:NSWindowDidEndLiveResizeNotification
1026 /* No way before 10.6 to identify the start of a live resize (drag
1027 * resize) without subclassing NSView and overriding two of its
1028 * methods. Instead of that, we make the window non-resizable, thus
1029 * ending confusion about whether or not resizes are plugin or user
1030 * driven (they are always plugin-driven).
1033 Gtk::Container* toplevel = get_toplevel();
1038 if (toplevel && toplevel->is_toplevel()) {
1039 toplevel->size_request (req);
1040 toplevel->set_size_request (req.width, req.height);
1041 dynamic_cast<Gtk::Window*>(toplevel)->set_resizable (false);
1049 AUPluginUI::grab_focus()
1052 [au_view becomeFirstResponder];
1056 AUPluginUI::forward_key_event (GdkEventKey* ev)
1058 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
1060 if (au_view && nsevent) {
1062 /* filter on nsevent type here because GDK massages FlagsChanged
1063 messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
1064 handle a FlagsChanged message as a keyDown or keyUp
1067 if ([nsevent type] == NSKeyDown) {
1068 [[[au_view window] firstResponder] keyDown:nsevent];
1069 } else if ([nsevent type] == NSKeyUp) {
1070 [[[au_view window] firstResponder] keyUp:nsevent];
1071 } else if ([nsevent type] == NSFlagsChanged) {
1072 [[[au_view window] firstResponder] flagsChanged:nsevent];
1078 AUPluginUI::on_realize ()
1080 VBox::on_realize ();
1082 /* our windows should not have that resize indicator */
1084 NSWindow* win = get_nswindow ();
1086 [win setShowsResizeIndicator:0];
1091 AUPluginUI::lower_box_realized ()
1094 parent_cocoa_window ();
1095 } else if (carbon_window) {
1096 parent_carbon_window ();
1101 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
1104 if (carbon_window && ev->state != GDK_VISIBILITY_UNOBSCURED) {
1105 ShowWindow (carbon_window);
1106 ActivateWindow (carbon_window, TRUE);
1114 AUPluginUI::lower_box_map ()
1116 [au_view setHidden:0];
1117 update_view_size ();
1121 AUPluginUI::lower_box_unmap ()
1123 [au_view setHidden:1];
1127 AUPluginUI::lower_box_size_request (GtkRequisition* requisition)
1129 requisition->width = req_width;
1130 requisition->height = req_height;
1134 AUPluginUI::lower_box_size_allocate (Gtk::Allocation& allocation)
1136 update_view_size ();
1140 AUPluginUI::lower_box_expose (GdkEventExpose* event)
1144 if (!(expose_cnt % 10)) {
1145 /* every 10 exposes, check how frequently idle is being called */
1146 if (idle_meter_needed) {
1147 Glib::signal_idle().connect (sigc::ptr_fun (AUPluginUI::idle_meter));
1148 idle_meter_needed = false;
1156 AUPluginUI::on_window_hide ()
1159 if (carbon_window) {
1160 HideWindow (carbon_window);
1161 ActivateWindow (carbon_window, FALSE);
1167 NSArray* wins = [NSApp windows];
1168 for (uint32_t i = 0; i < [wins count]; i++) {
1169 id win = [wins objectAtIndex:i];
1175 AUPluginUI::on_window_show (const string& /*title*/)
1177 /* this is idempotent so just call it every time we show the window */
1179 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
1184 if (carbon_window) {
1185 ShowWindow (carbon_window);
1186 ActivateWindow (carbon_window, TRUE);
1194 AUPluginUI::start_updating (GdkEventAny*)
1200 AUPluginUI::stop_updating (GdkEventAny*)
1206 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
1208 AUPluginUI* aup = new AUPluginUI (plugin_insert);
1214 AUPluginUI::start_live_resize ()
1216 in_live_resize = true;
1220 AUPluginUI::end_live_resize ()
1222 in_live_resize = false;