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 // yes, yes we know (see wscript for various available OSX compat modes)
25 #if defined (__clang__)
26 # pragma clang diagnostic push
27 # pragma clang diagnostic ignored "-Wdeprecated-declarations"
30 #include "CAAudioUnit.h"
31 #include "CAComponent.h"
33 #if defined (__clang__)
34 # pragma clang diagnostic pop
37 #import <AudioUnit/AUCocoaUIView.h>
38 #import <CoreAudioKit/AUGenericView.h>
39 #import <objc/runtime.h>
42 #include <dispatch/dispatch.h>
49 #include "public_editor.h"
52 #include "gtk2ardour-config.h"
55 #define ArdourCloseComponent CloseComponent
57 #define ArdourCloseComponent AudioComponentInstanceDispose
59 using namespace ARDOUR;
61 using namespace Gtkmm2ext;
65 vector<string> AUPluginUI::automation_mode_strings;
66 int64_t AUPluginUI::last_timer = 0;
67 bool AUPluginUI::timer_needed = true;
68 CFRunLoopTimerRef AUPluginUI::cf_timer;
69 sigc::connection AUPluginUI::timer_connection;
71 static const gchar* _automation_mode_strings[] = {
80 dump_view_tree (NSView* view, int depth, int maxdepth)
82 NSArray* subviews = [view subviews];
83 unsigned long cnt = [subviews count];
86 NSView* su = [view superview];
88 NSRect sf = [su frame];
89 cerr << " PARENT view " << su << " @ " << sf.origin.x << ", " << sf.origin.y
90 << ' ' << sf.size.width << " x " << sf.size.height
95 for (int d = 0; d < depth; d++) {
98 NSRect frame = [view frame];
99 cerr << " view " << view << " @ " << frame.origin.x << ", " << frame.origin.y
100 << ' ' << frame.size.width << " x " << frame.size.height
103 if (depth >= maxdepth) {
106 for (unsigned long i = 0; i < cnt; ++i) {
107 NSView* subview = [subviews objectAtIndex:i];
108 dump_view_tree (subview, depth+1, maxdepth);
112 /* This deeply hacky block of code exists for a rather convoluted reason.
114 * The proximal reason is that there are plugins (such as XLN's Addictive Drums
115 * 2) which redraw their GUI/editor windows using a timer, and use a drawing
116 * technique that on Retina displays ends up calling arg32_image_mark_RGB32, a
117 * function that for some reason (probably byte-swapping or pixel-doubling) is
118 * many times slower than the function used on non-Retina displays.
120 * We are not the first people to discover the problem with
121 * arg32_image_mark_RGB32.
123 * Justin Fraenkel, the lead author of Reaper, wrote a very detailed account of
124 * the performance issues with arg32_image_mark_RGB32 here:
125 * http://www.1014.org/?article=516
127 * The problem was also seen by Robert O'Callahan (lead developer of rr, the
128 * reverse debugger) as far back as 2010:
129 * http://robert.ocallahan.org/2010/05/cglayer-performance-trap-with-isflipped_03.html
131 * In fact, it is so slow that the drawing takes up close to 100% of a single
132 * core, and the event loop that the drawing occurs in never sleeps or "idles".
134 * In AU hosts built directly on top of Cocoa, or some other toolkits, this
135 * isn't inherently a major problem - it just makes the entire GUI of the
138 * However, there is an additional problem for Ardour because GTK+ is built on
139 * top of the GDK/Quartz event loop integration. This integration is rather
140 * baroque, mostly because it was written at a time when CFRunLoop did not
141 * offer a way to wait for "input" from file descriptors (which arrived in OS X
142 * 10.5). As a result, it uses a hair-raising design involving an additional
143 * thread. This design has a major problem, which is that it effectively
144 * creates two nested run loops.
146 * The GTK+/GDK/glib one runs until it has nothing to do, at which time it
147 * calls a function to wait until there is something to do. On Linux or Windows
148 * that would involve some variant or relative of poll(2), which puts the
149 * process to sleep until there is something to do.
151 * On OS X, glib ends up calling [CFRunLoop waitForNextEventMatchingMask] which
152 * will eventually put the process to sleep, but won't do so until the
153 * CFRunLoop also has nothing to do. This includes (at least) a complete redraw
154 * cycle. If redrawing takes too long, and there are timers expired for another
155 * redraw (e.g. Addictive Drums 2, again), then the CFRunLoop will just start
156 * another redraw cycle after processing any events and other stuff.
158 * If the CFRunLoop stays busy, then it will never return to the glib
159 * level at all, thus stopping any further GTK+ level activity (events,
160 * drawing) from taking place. In short, the current (spring 2016) design of
161 * the GDK/Quartz event loop integration relies on the idea that the internal
162 * CFRunLoop will go idle, and totally breaks if this does not happen.
164 * So take a fully functional Ardour, add in XLN's Addictive Drums 2, and a
165 * Retina display, and Apple's ridiculously slow blitting code, and the
166 * CFRunLoop never goes idle. As soon as Addictive Drums starts drawing (over
167 * and over again), the GTK+ event loop stops receiving events and stops
170 * One fix for this was to run a nested GTK+ event loop iteration (or two)
171 * whenever a plugin window was redrawn. This works in the sense that the
172 * immediate issue (no GTK+ events or drawing) is fixed. But the recursive GTK+
173 * event loop causes its own (very subtle) problems too.
175 * This code takes a rather radical approach. We use Objective C's ability to
176 * swizzle object methods. Specifically, we replace [NSView displayIfNeeded]
177 * with our own version which will skip redraws of plugin windows if we tell it
178 * too. If we haven't done that, or if the redraw is of a non-plugin window,
179 * then we invoke the original displayIfNeeded method.
181 * After every 10 redraws of a given plugin GUI/editor window, we queue up a
182 * GTK/glib idle callback to measure the interval between those idle
183 * callbacks. We do this globally across all plugin windows, so if the callback
184 * is already queued, we don't requeue it.
186 * If the interval is longer than 40msec (a 25fps redraw rate), we set
187 * block_plugin_redraws to some number. Each successive call to our interposed
188 * displayIfNeeded method will (a) check this value and if non-zero (b) check
189 * if the call is for a plugin-related NSView/NSWindow. If it is, then we will
190 * skip the redisplay entirely, hopefully avoiding any calls to
191 * argb32_image_mark_RGB32 or any other slow drawing code, and thus allowing
192 * the CFRunLoop to go idle. If the value is zero or the call is for a
193 * non-plugin window, then we just invoke the "original" displayIfNeeded
196 * This hack adds a tiny bit of overhead onto redrawing of the entire
197 * application. But in the common case this consists of 1 conditional (the
198 * check on block_plugin_redraws, which will find it to be zero) and the
199 * invocation of the original method. Given how much work is typically done
200 * during drawing, this seems acceptable.
202 * The correct fix for this is to redesign the relationship between
203 * GTK+/GDK/glib so that a glib run loop is actually a CFRunLoop, with all
204 * GSources represented as CFRunLoopSources, without any nesting and without
205 * any additional thread. This is not a task to be undertaken lightly, and is
206 * certainly substantially more work than this was. It may never be possible to
207 * do that work in a way that could be integrated back into glib, because of
208 * the rather specific semantics and types of GSources, but it would almost
209 * certainly be possible to make it work for Ardour.
212 static uint32_t block_plugin_redraws = 0;
213 static const uint32_t minimum_redraw_rate = 30; /* frames per second */
214 static const uint32_t block_plugin_redraw_count = 15; /* number of combined plugin redraws to block, if blocking */
218 /* PowerPC versions of OS X do not support libdispatch, which we use below when swizzling objective C. But they also don't have Retina
219 * which is the underlying reason for this code. So just skip it on those CPUs.
223 static void add_plugin_view (id view) {}
224 static void remove_plugin_view (id view) {}
228 static IMP original_nsview_drawIfNeeded;
229 static std::vector<id> plugin_views;
231 static void add_plugin_view (id view)
233 if (plugin_views.empty()) {
234 AUPluginUI::start_cf_timer ();
237 plugin_views.push_back (view);
241 static void remove_plugin_view (id view)
243 std::vector<id>::iterator x = find (plugin_views.begin(), plugin_views.end(), view);
244 if (x != plugin_views.end()) {
245 plugin_views.erase (x);
247 if (plugin_views.empty()) {
248 AUPluginUI::stop_cf_timer ();
252 static void interposed_drawIfNeeded (id receiver, SEL selector, NSRect rect)
254 if (block_plugin_redraws && (find (plugin_views.begin(), plugin_views.end(), receiver) != plugin_views.end())) {
255 block_plugin_redraws--;
256 #ifdef AU_DEBUG_PRINT
257 std::cerr << "Plugin redraw blocked\n";
259 /* YOU ... SHALL .... NOT ... DRAW!!!! */
262 (void) ((int (*)(id,SEL,NSRect)) original_nsview_drawIfNeeded) (receiver, selector, rect);
265 @implementation NSView (Tracking)
267 static dispatch_once_t once_token;
269 /* this swizzles NSView::displayIfNeeded and replaces it with
270 * interposed_drawIfNeeded(), which allows us to interpose and block
271 * the redrawing of plugin UIs when their redrawing behaviour
272 * is interfering with event loop behaviour.
275 dispatch_once (&once_token, ^{
276 Method target = class_getInstanceMethod ([NSView class], @selector(displayIfNeeded));
277 original_nsview_drawIfNeeded = method_setImplementation (target, (IMP) interposed_drawIfNeeded);
285 /* END OF THE PLUGIN REDRAW HACK */
287 @implementation NotificationObject
289 - (NotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui andCocoaParent: (NSWindow*) cp andTopLevelParent: (NSWindow*) tlp
291 self = [ super init ];
294 plugin_ui = apluginui;
295 top_level_parent = tlp;
300 [[NSNotificationCenter defaultCenter]
302 selector:@selector(cocoaParentActivationHandler:)
303 name:NSWindowDidBecomeMainNotification
306 [[NSNotificationCenter defaultCenter]
308 selector:@selector(cocoaParentBecameKeyHandler:)
309 name:NSWindowDidBecomeKeyNotification
317 - (void)cocoaParentActivationHandler:(NSNotification *)notification
319 NSWindow* notification_window = (NSWindow *)[notification object];
321 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
322 if ([notification_window isMainWindow]) {
323 plugin_ui->activate();
325 plugin_ui->deactivate();
330 - (void)cocoaParentBecameKeyHandler:(NSNotification *)notification
332 NSWindow* notification_window = (NSWindow *)[notification object];
334 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
335 if ([notification_window isKeyWindow]) {
336 plugin_ui->activate();
338 plugin_ui->deactivate();
343 - (void)auViewResized:(NSNotification *)notification
345 (void) notification; // stop complaints about unusued argument
346 plugin_ui->cocoa_view_resized();
351 @implementation LiveResizeNotificationObject
353 - (LiveResizeNotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui
355 self = [ super init ];
357 plugin_ui = apluginui;
363 - (void)windowWillStartLiveResizeHandler:(NSNotification*)notification
365 plugin_ui->start_live_resize ();
368 - (void)windowWillEndLiveResizeHandler:(NSNotification*)notification
370 plugin_ui->end_live_resize ();
374 AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
375 : PlugUIBase (insert)
376 , automation_mode_label (_("Automation"))
377 , preset_label (_("Presets"))
383 , in_live_resize (false)
384 , plugin_requested_resize (0)
389 if (automation_mode_strings.empty()) {
390 automation_mode_strings = I18N (_automation_mode_strings);
393 set_popdown_strings (automation_mode_selector, automation_mode_strings);
394 automation_mode_selector.set_active_text (automation_mode_strings.front());
396 if ((au = boost::dynamic_pointer_cast<AUPlugin> (insert->plugin())) == 0) {
397 error << _("unknown type of editor-supplying plugin (note: no AudioUnit support in this version of ardour)") << endmsg;
398 throw failed_constructor ();
401 /* stuff some stuff into the top of the window */
403 HBox* smaller_hbox = manage (new HBox);
405 smaller_hbox->set_spacing (6);
406 smaller_hbox->pack_start (preset_label, false, false, 4);
407 smaller_hbox->pack_start (_preset_modified, false, false);
408 smaller_hbox->pack_start (_preset_combo, false, false);
409 smaller_hbox->pack_start (add_button, false, false);
411 /* Ardour does not currently allow to overwrite existing presets
412 * see save_property_list() in audio_unit.cc
414 smaller_hbox->pack_start (save_button, false, false);
417 /* one day these might be useful with an AU plugin, but not yet */
418 smaller_hbox->pack_start (automation_mode_label, false, false);
419 smaller_hbox->pack_start (automation_mode_selector, false, false);
421 smaller_hbox->pack_start (reset_button, false, false);
422 smaller_hbox->pack_start (bypass_button, false, true);
424 VBox* v1_box = manage (new VBox);
425 VBox* v2_box = manage (new VBox);
427 v1_box->pack_start (*smaller_hbox, false, true);
428 v2_box->pack_start (focus_button, false, true);
430 top_box.set_homogeneous (false);
431 top_box.set_spacing (6);
432 top_box.set_border_width (6);
434 top_box.pack_end (*v2_box, false, false);
435 top_box.pack_end (*v1_box, false, false);
438 pack_start (top_box, false, false);
439 pack_start (low_box, true, true);
441 preset_label.show ();
442 _preset_combo.show ();
443 automation_mode_label.show ();
444 automation_mode_selector.show ();
445 bypass_button.show ();
453 _activating_from_app = false;
460 /* prefer cocoa, fall back to cocoa, but use carbon if its there */
462 if (test_cocoa_view_support()) {
463 create_cocoa_view ();
465 } else if (test_carbon_view_support()) {
466 create_carbon_view ();
469 create_cocoa_view ();
472 low_box.add_events (Gdk::VISIBILITY_NOTIFY_MASK | Gdk::EXPOSURE_MASK);
474 low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
475 low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
477 low_box.signal_size_request ().connect (mem_fun (this, &AUPluginUI::lower_box_size_request));
478 low_box.signal_size_allocate ().connect (mem_fun (this, &AUPluginUI::lower_box_size_allocate));
479 low_box.signal_map ().connect (mem_fun (this, &AUPluginUI::lower_box_map));
480 low_box.signal_unmap ().connect (mem_fun (this, &AUPluginUI::lower_box_unmap));
484 AUPluginUI::~AUPluginUI ()
487 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
490 if (_resize_notify) {
491 [[NSNotificationCenter defaultCenter] removeObserver:_resize_notify];
494 NSWindow* win = get_nswindow();
496 remove_plugin_view ([[win contentView] superview]);
501 [win removeChildWindow:cocoa_parent];
505 /* not parented, just overlaid on top of our window */
506 DisposeWindow (carbon_window);
511 ArdourCloseComponent (editView);
515 /* remove whatever we packed into low_box so that GTK doesn't
518 [au_view removeFromSuperview];
523 AUPluginUI::test_carbon_view_support ()
528 carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
529 carbon_descriptor.componentSubType = 'gnrc';
530 carbon_descriptor.componentManufacturer = 'appl';
531 carbon_descriptor.componentFlags = 0;
532 carbon_descriptor.componentFlagsMask = 0;
536 // ask the AU for its first editor component
538 err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
540 int nEditors = propertySize / sizeof(ComponentDescription);
541 ComponentDescription *editors = new ComponentDescription[nEditors];
542 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
544 // just pick the first one for now
545 carbon_descriptor = editors[0];
558 AUPluginUI::test_cocoa_view_support ()
561 Boolean isWritable = 0;
562 OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
563 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
564 0, &dataSize, &isWritable);
566 return dataSize > 0 && err == noErr;
570 AUPluginUI::plugin_class_valid (Class pluginClass)
572 if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
573 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
574 [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
582 AUPluginUI::create_cocoa_view ()
584 bool wasAbleToLoadCustomView = false;
585 AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
586 UInt32 numberOfClasses = 0;
589 NSString* factoryClassName = 0;
590 NSURL* CocoaViewBundlePath = NULL;
592 OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
593 kAudioUnitProperty_CocoaUI,
594 kAudioUnitScope_Global,
599 numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
601 // Does view have custom Cocoa UI?
603 if ((result == noErr) && (numberOfClasses > 0) ) {
605 DEBUG_TRACE(DEBUG::AudioUnits,
606 string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
608 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
610 if(AudioUnitGetProperty(*au->get_au(),
611 kAudioUnitProperty_CocoaUI,
612 kAudioUnitScope_Global,
615 &dataSize) == noErr) {
617 CocoaViewBundlePath = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
619 // we only take the first view in this example.
620 factoryClassName = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
622 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
623 [factoryClassName UTF8String], CocoaViewBundlePath));
627 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
629 if (cocoaViewInfo != NULL) {
630 free (cocoaViewInfo);
631 cocoaViewInfo = NULL;
636 // [A] Show custom UI if view has it
638 if (CocoaViewBundlePath && factoryClassName) {
639 NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
641 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
643 if (viewBundle == NULL) {
644 error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
647 Class factoryClass = [viewBundle classNamed:factoryClassName];
648 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
650 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
654 // make sure 'factoryClass' implements the AUCocoaUIBase protocol
655 if (!plugin_class_valid (factoryClass)) {
656 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
660 id factory = [[[factoryClass alloc] init] autorelease];
661 if (factory == NULL) {
662 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
666 DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
669 au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
671 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
674 [CocoaViewBundlePath release];
677 for (i = 0; i < numberOfClasses; i++)
678 CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
680 free (cocoaViewInfo);
682 wasAbleToLoadCustomView = true;
686 if (!wasAbleToLoadCustomView) {
687 // load generic Cocoa view
688 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
690 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
691 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
692 [(AUGenericView *)au_view setShowsExpertParameters:1];
695 // Get the initial size of the new AU View's frame
696 NSRect frame = [au_view frame];
697 req_width = frame.size.width;
698 req_height = frame.size.height;
700 resizable = [au_view autoresizingMask];
702 low_box.queue_resize ();
708 AUPluginUI::update_view_size ()
710 last_au_frame = [au_view frame];
714 AUPluginUI::timer_callback ()
716 block_plugin_redraws = 0;
717 #ifdef AU_DEBUG_PRINT
718 std::cerr << "Resume redraws after idle\n";
724 au_cf_timer_callback (CFRunLoopTimerRef timer, void* info)
726 reinterpret_cast<AUPluginUI*> (info)->cf_timer_callback ();
730 AUPluginUI::cf_timer_callback ()
732 int64_t now = ARDOUR::get_microseconds ();
734 if (!last_timer || block_plugin_redraws) {
739 const int64_t usecs_slop = (1400000 / minimum_redraw_rate); // 140%
741 #ifdef AU_DEBUG_PRINT
742 std::cerr << "Timer elapsed : " << now - last_timer << std::endl;
745 if ((now - last_timer) > (usecs_slop + (1000000/minimum_redraw_rate))) {
746 block_plugin_redraws = block_plugin_redraw_count;
747 timer_connection.disconnect ();
748 timer_connection = Glib::signal_timeout().connect (&AUPluginUI::timer_callback, 40);
749 #ifdef AU_DEBUG_PRINT
750 std::cerr << "Timer too slow, block plugin redraws\n";
758 AUPluginUI::start_cf_timer ()
764 CFTimeInterval interval = 1.0 / (float) minimum_redraw_rate;
766 cf_timer = CFRunLoopTimerCreate (kCFAllocatorDefault,
767 CFAbsoluteTimeGetCurrent() + interval,
769 au_cf_timer_callback,
772 CFRunLoopAddTimer (CFRunLoopGetCurrent(), cf_timer, kCFRunLoopCommonModes);
773 timer_needed = false;
777 AUPluginUI::stop_cf_timer ()
783 CFRunLoopRemoveTimer (CFRunLoopGetCurrent(), cf_timer, kCFRunLoopCommonModes);
789 AUPluginUI::cocoa_view_resized ()
791 /* we can get here for two reasons:
793 1) the plugin window was resized by the user, a new size was
794 allocated to the window, ::update_view_size() was called, and we
795 explicitly/manually resized the AU NSView.
797 2) the plugin decided to resize itself (probably in response to user
798 action, but not in response to an actual window resize)
800 We only want to proceed with a window resizing in the second case.
803 if (in_live_resize) {
804 /* ::update_view_size() will be called at the right times and
805 * will update the view size. We don't need to anything while a
806 * live resize in underway.
811 if (plugin_requested_resize) {
812 /* we tried to change the plugin frame from inside this method
813 * (to adjust the origin), which changes the frame of the AU
814 * NSView, resulting in a reentrant call to the FrameDidChange
815 * handler (this method). Ignore this reentrant call.
817 #ifdef AU_DEBUG_PRINT
818 std::cerr << plugin->name() << " re-entrant call to cocoa_view_resized, ignored\n";
823 plugin_requested_resize = 1;
825 ProcessorWindowProxy* wp = insert->window_proxy();
827 /* Once a plugin has requested a resize of its own window, do
828 * NOT save the window. The user may save state with the plugin
829 * editor expanded to show "extra detail" - the plugin will not
830 * refill this space when the editor is first
831 * instantiated. Leaving the window in the "too big" state
832 * cannot be recovered from.
834 * The window will be sized to fit the plugin's own request. Done.
836 wp->set_state_mask (WindowProxy::Position);
839 NSRect new_frame = [au_view frame];
841 /* from here on, we know that we've been called because the plugin
842 * decided to change the NSView frame itself.
845 /* step one: compute the change in the frame size.
848 float dy = new_frame.size.height - last_au_frame.size.height;
849 float dx = new_frame.size.width - last_au_frame.size.width;
851 NSWindow* window = get_nswindow ();
852 NSRect windowFrame= [window frame];
854 /* we want the top edge of the window to remain in the same place,
855 but the Cocoa/Quartz origin is at the lower left. So, when we make
856 the window larger, we will move it down, which means shifting the
857 origin toward (x,0). This will leave the top edge in the same place.
860 windowFrame.origin.y -= dy;
861 windowFrame.origin.x -= dx;
862 windowFrame.size.height += dy;
863 windowFrame.size.width += dx;
865 NSUInteger old_auto_resize = [au_view autoresizingMask];
867 /* Some stupid AU Views change the origin of the original AU View when
868 they are resized (I'm looking at you AUSampler). If the origin has
869 been moved, move it back.
872 if (last_au_frame.origin.x != new_frame.origin.x ||
873 last_au_frame.origin.y != new_frame.origin.y) {
874 new_frame.origin = last_au_frame.origin;
875 [au_view setFrame:new_frame];
876 /* also be sure to redraw the topbox because this can
879 top_box.queue_draw ();
882 /* We resize the window using Cocoa. We can't use GTK mechanisms
885 * http://www.lists.apple.com/archives/coreaudio-api/2005/Aug/msg00245.html
887 * "The host needs to be aware that changing the size of the window in
888 * response to the NSViewFrameDidChangeNotification can cause the view
889 * size to change depending on the autoresizing mask of the view. The
890 * host may need to cache the autoresizing mask of the view, set it to
891 * NSViewNotSizable, resize the window, and then reset the autoresizing
892 * mask of the view once the window has been sized."
896 [au_view setAutoresizingMask:NSViewNotSizable];
897 [window setFrame:windowFrame display:1];
898 [au_view setAutoresizingMask:old_auto_resize];
900 /* keep a copy of the size of the AU NSView. We didn't set it - the plugin did */
901 last_au_frame = new_frame;
902 req_width = new_frame.size.width;
903 req_height = new_frame.size.height;
905 plugin_requested_resize = 0;
909 AUPluginUI::create_carbon_view ()
913 ControlRef root_control;
915 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
917 OpenAComponent(editComponent, &editView);
919 error << _("AU Carbon view: cannot open AU Component") << endmsg;
923 Rect r = { 100, 100, 100, 100 };
924 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
925 kWindowCompositingAttribute|
926 kWindowNoShadowAttribute|
927 kWindowNoTitleBarAttribute);
929 if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
930 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
931 ArdourCloseComponent (editView);
935 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
936 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
937 DisposeWindow (carbon_window);
938 ArdourCloseComponent (editView);
943 Float32Point location = { 0.0, 0.0 };
944 Float32Point size = { 0.0, 0.0 } ;
946 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
947 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
948 DisposeWindow (carbon_window);
949 ArdourCloseComponent (editView);
956 GetControlBounds(viewPane, &bounds);
957 size.x = bounds.right-bounds.left;
958 size.y = bounds.bottom-bounds.top;
960 req_width = (int) (size.x + 0.5);
961 req_height = (int) (size.y + 0.5);
963 SizeWindow (carbon_window, req_width, req_height, true);
964 low_box.set_size_request (req_width, req_height);
968 error << _("AU Carbon GUI is not supported.") << endmsg;
974 AUPluginUI::get_nswindow ()
976 Gtk::Container* toplevel = get_toplevel();
978 if (!toplevel || !toplevel->is_toplevel()) {
979 error << _("AUPluginUI: no top level window!") << endmsg;
983 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
986 error << _("AUPluginUI: no top level window!") << endmsg;
994 AUPluginUI::activate ()
997 ActivateWindow (carbon_window, TRUE);
1002 AUPluginUI::deactivate ()
1005 ActivateWindow (carbon_window, FALSE);
1010 AUPluginUI::parent_carbon_window ()
1013 NSWindow* win = get_nswindow ();
1014 Rect windowStructureBoundsRect;
1020 /* figure out where the cocoa parent window is in carbon-coordinate space, which
1021 differs from both cocoa-coordinate space and GTK-coordinate space
1024 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
1026 /* compute how tall the title bar is, because we have to offset the position of the carbon window
1030 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
1031 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
1033 int titlebar_height = wm_frame.size.height - content_frame.size.height;
1035 int packing_extra = 6; // this is the total vertical packing in our top level window
1037 /* move into position, based on parent window position */
1038 MoveWindow (carbon_window,
1039 windowStructureBoundsRect.left,
1040 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
1042 ShowWindow (carbon_window);
1044 // create the cocoa window for the carbon one and make it visible
1045 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
1047 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
1049 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
1051 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
1052 [win setAutodisplay:1]; // turn of GTK stuff for this window
1061 AUPluginUI::parent_cocoa_window ()
1063 NSWindow* win = get_nswindow ();
1069 //[win setAutodisplay:1]; // turn off GTK stuff for this window
1071 NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
1072 [view addSubview:au_view];
1073 /* despite the fact that the documentation says that [NSWindow
1074 contentView] is the highest "accessible" NSView in an NSWindow, when
1075 the redraw cycle is executed, displayIfNeeded is actually executed
1076 on the parent of the contentView. To provide a marginal speedup when
1077 checking if a given redraw is for a plugin, use this "hidden" NSView
1078 to identify the plugin, so that we do not have to call [superview]
1079 every time in interposed_drawIfNeeded().
1081 add_plugin_view ([[win contentView] superview]);
1083 /* this moves the AU NSView down and over to provide a left-hand margin
1084 * and to clear the Ardour "task bar" (with plugin preset mgmt buttons,
1085 * keyboard focus control, bypass etc).
1089 gtk_widget_translate_coordinates(
1090 GTK_WIDGET(low_box.gobj()),
1091 GTK_WIDGET(low_box.get_parent()->gobj()),
1093 [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
1095 last_au_frame = [au_view frame];
1096 // watch for size changes of the view
1097 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
1099 [[NSNotificationCenter defaultCenter] addObserver:_notify
1100 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
1103 // catch notifications that live resizing is about to start
1105 #if HAVE_COCOA_LIVE_RESIZING
1106 _resize_notify = [ [ LiveResizeNotificationObject alloc] initWithPluginUI:this ];
1108 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1109 selector:@selector(windowWillStartLiveResizeHandler:) name:NSWindowWillStartLiveResizeNotification
1112 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1113 selector:@selector(windowWillEndLiveResizeHandler:) name:NSWindowDidEndLiveResizeNotification
1116 /* No way before 10.6 to identify the start of a live resize (drag
1117 * resize) without subclassing NSView and overriding two of its
1118 * methods. Instead of that, we make the window non-resizable, thus
1119 * ending confusion about whether or not resizes are plugin or user
1120 * driven (they are always plugin-driven).
1123 Gtk::Container* toplevel = get_toplevel();
1128 if (toplevel && toplevel->is_toplevel()) {
1129 toplevel->size_request (req);
1130 toplevel->set_size_request (req.width, req.height);
1131 dynamic_cast<Gtk::Window*>(toplevel)->set_resizable (false);
1139 AUPluginUI::grab_focus()
1142 [au_view becomeFirstResponder];
1146 AUPluginUI::forward_key_event (GdkEventKey* ev)
1148 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
1150 if (au_view && nsevent) {
1152 /* filter on nsevent type here because GDK massages FlagsChanged
1153 messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
1154 handle a FlagsChanged message as a keyDown or keyUp
1157 if ([nsevent type] == NSKeyDown) {
1158 [[[au_view window] firstResponder] keyDown:nsevent];
1159 } else if ([nsevent type] == NSKeyUp) {
1160 [[[au_view window] firstResponder] keyUp:nsevent];
1161 } else if ([nsevent type] == NSFlagsChanged) {
1162 [[[au_view window] firstResponder] flagsChanged:nsevent];
1168 AUPluginUI::on_realize ()
1170 VBox::on_realize ();
1172 /* our windows should not have that resize indicator */
1174 NSWindow* win = get_nswindow ();
1176 [win setShowsResizeIndicator:0];
1181 AUPluginUI::lower_box_realized ()
1184 parent_cocoa_window ();
1185 } else if (carbon_window) {
1186 parent_carbon_window ();
1191 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
1194 if (carbon_window && ev->state != GDK_VISIBILITY_UNOBSCURED) {
1195 ShowWindow (carbon_window);
1196 ActivateWindow (carbon_window, TRUE);
1204 AUPluginUI::lower_box_map ()
1206 [au_view setHidden:0];
1207 update_view_size ();
1211 AUPluginUI::lower_box_unmap ()
1213 [au_view setHidden:1];
1217 AUPluginUI::lower_box_size_request (GtkRequisition* requisition)
1219 requisition->width = req_width;
1220 requisition->height = req_height;
1224 AUPluginUI::lower_box_size_allocate (Gtk::Allocation& allocation)
1226 update_view_size ();
1230 AUPluginUI::on_window_hide ()
1233 if (carbon_window) {
1234 HideWindow (carbon_window);
1235 ActivateWindow (carbon_window, FALSE);
1241 NSArray* wins = [NSApp windows];
1242 for (uint32_t i = 0; i < [wins count]; i++) {
1243 id win = [wins objectAtIndex:i];
1249 AUPluginUI::on_window_show (const string& /*title*/)
1251 /* this is idempotent so just call it every time we show the window */
1253 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
1258 if (carbon_window) {
1259 ShowWindow (carbon_window);
1260 ActivateWindow (carbon_window, TRUE);
1268 AUPluginUI::start_updating (GdkEventAny*)
1274 AUPluginUI::stop_updating (GdkEventAny*)
1280 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
1282 AUPluginUI* aup = new AUPluginUI (plugin_insert);
1288 AUPluginUI::start_live_resize ()
1290 in_live_resize = true;
1294 AUPluginUI::end_live_resize ()
1296 in_live_resize = false;