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 (pin_management_button, false, false, 4);
407 smaller_hbox->pack_start (preset_label, false, false, 4);
408 smaller_hbox->pack_start (_preset_modified, false, false);
409 smaller_hbox->pack_start (_preset_combo, false, false);
410 smaller_hbox->pack_start (add_button, false, false);
412 /* Ardour does not currently allow to overwrite existing presets
413 * see save_property_list() in audio_unit.cc
415 smaller_hbox->pack_start (save_button, false, false);
418 /* one day these might be useful with an AU plugin, but not yet */
419 smaller_hbox->pack_start (automation_mode_label, false, false);
420 smaller_hbox->pack_start (automation_mode_selector, false, false);
422 smaller_hbox->pack_start (reset_button, false, false);
423 smaller_hbox->pack_start (bypass_button, false, true);
425 VBox* v1_box = manage (new VBox);
426 VBox* v2_box = manage (new VBox);
428 v1_box->pack_start (*smaller_hbox, false, true);
429 v2_box->pack_start (focus_button, false, true);
431 top_box.set_homogeneous (false);
432 top_box.set_spacing (6);
433 top_box.set_border_width (6);
435 top_box.pack_end (*v2_box, false, false);
436 top_box.pack_end (*v1_box, false, false);
439 pack_start (top_box, false, false);
440 pack_start (low_box, true, true);
442 preset_label.show ();
443 _preset_combo.show ();
444 automation_mode_label.show ();
445 automation_mode_selector.show ();
446 bypass_button.show ();
454 _activating_from_app = false;
461 /* prefer cocoa, fall back to cocoa, but use carbon if its there */
463 if (test_cocoa_view_support()) {
464 create_cocoa_view ();
466 } else if (test_carbon_view_support()) {
467 create_carbon_view ();
470 create_cocoa_view ();
473 low_box.add_events (Gdk::VISIBILITY_NOTIFY_MASK | Gdk::EXPOSURE_MASK);
475 low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
476 low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
478 low_box.signal_size_request ().connect (mem_fun (this, &AUPluginUI::lower_box_size_request));
479 low_box.signal_size_allocate ().connect (mem_fun (this, &AUPluginUI::lower_box_size_allocate));
480 low_box.signal_map ().connect (mem_fun (this, &AUPluginUI::lower_box_map));
481 low_box.signal_unmap ().connect (mem_fun (this, &AUPluginUI::lower_box_unmap));
485 AUPluginUI::~AUPluginUI ()
488 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
491 if (_resize_notify) {
492 [[NSNotificationCenter defaultCenter] removeObserver:_resize_notify];
495 NSWindow* win = get_nswindow();
497 remove_plugin_view ([[win contentView] superview]);
502 [win removeChildWindow:cocoa_parent];
506 /* not parented, just overlaid on top of our window */
507 DisposeWindow (carbon_window);
512 ArdourCloseComponent (editView);
516 /* remove whatever we packed into low_box so that GTK doesn't
519 [au_view removeFromSuperview];
524 AUPluginUI::test_carbon_view_support ()
529 carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
530 carbon_descriptor.componentSubType = 'gnrc';
531 carbon_descriptor.componentManufacturer = 'appl';
532 carbon_descriptor.componentFlags = 0;
533 carbon_descriptor.componentFlagsMask = 0;
537 // ask the AU for its first editor component
539 err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
541 int nEditors = propertySize / sizeof(ComponentDescription);
542 ComponentDescription *editors = new ComponentDescription[nEditors];
543 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
545 // just pick the first one for now
546 carbon_descriptor = editors[0];
559 AUPluginUI::test_cocoa_view_support ()
562 Boolean isWritable = 0;
563 OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
564 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
565 0, &dataSize, &isWritable);
567 return dataSize > 0 && err == noErr;
571 AUPluginUI::plugin_class_valid (Class pluginClass)
573 if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
574 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
575 [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
583 AUPluginUI::create_cocoa_view ()
585 bool wasAbleToLoadCustomView = false;
586 AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
587 UInt32 numberOfClasses = 0;
590 NSString* factoryClassName = 0;
591 NSURL* CocoaViewBundlePath = NULL;
593 OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
594 kAudioUnitProperty_CocoaUI,
595 kAudioUnitScope_Global,
600 numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
602 // Does view have custom Cocoa UI?
604 if ((result == noErr) && (numberOfClasses > 0) ) {
606 DEBUG_TRACE(DEBUG::AudioUnits,
607 string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
609 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
611 if(AudioUnitGetProperty(*au->get_au(),
612 kAudioUnitProperty_CocoaUI,
613 kAudioUnitScope_Global,
616 &dataSize) == noErr) {
618 CocoaViewBundlePath = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
620 // we only take the first view in this example.
621 factoryClassName = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
623 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
624 [factoryClassName UTF8String], CocoaViewBundlePath));
628 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
630 if (cocoaViewInfo != NULL) {
631 free (cocoaViewInfo);
632 cocoaViewInfo = NULL;
637 // [A] Show custom UI if view has it
639 if (CocoaViewBundlePath && factoryClassName) {
640 NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
642 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
644 if (viewBundle == NULL) {
645 error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
648 Class factoryClass = [viewBundle classNamed:factoryClassName];
649 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
651 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
655 // make sure 'factoryClass' implements the AUCocoaUIBase protocol
656 if (!plugin_class_valid (factoryClass)) {
657 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
661 id factory = [[[factoryClass alloc] init] autorelease];
662 if (factory == NULL) {
663 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
667 DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
670 au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
672 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
675 [CocoaViewBundlePath release];
678 for (i = 0; i < numberOfClasses; i++)
679 CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
681 free (cocoaViewInfo);
683 wasAbleToLoadCustomView = true;
687 if (!wasAbleToLoadCustomView) {
688 // load generic Cocoa view
689 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
691 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
692 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
693 [(AUGenericView *)au_view setShowsExpertParameters:1];
696 // Get the initial size of the new AU View's frame
697 NSRect frame = [au_view frame];
698 req_width = frame.size.width;
699 req_height = frame.size.height;
701 resizable = [au_view autoresizingMask];
703 low_box.queue_resize ();
709 AUPluginUI::update_view_size ()
711 last_au_frame = [au_view frame];
715 AUPluginUI::timer_callback ()
717 block_plugin_redraws = 0;
718 #ifdef AU_DEBUG_PRINT
719 std::cerr << "Resume redraws after idle\n";
725 au_cf_timer_callback (CFRunLoopTimerRef timer, void* info)
727 reinterpret_cast<AUPluginUI*> (info)->cf_timer_callback ();
731 AUPluginUI::cf_timer_callback ()
733 int64_t now = ARDOUR::get_microseconds ();
735 if (!last_timer || block_plugin_redraws) {
740 const int64_t usecs_slop = (1400000 / minimum_redraw_rate); // 140%
742 #ifdef AU_DEBUG_PRINT
743 std::cerr << "Timer elapsed : " << now - last_timer << std::endl;
746 if ((now - last_timer) > (usecs_slop + (1000000/minimum_redraw_rate))) {
747 block_plugin_redraws = block_plugin_redraw_count;
748 timer_connection.disconnect ();
749 timer_connection = Glib::signal_timeout().connect (&AUPluginUI::timer_callback, 40);
750 #ifdef AU_DEBUG_PRINT
751 std::cerr << "Timer too slow, block plugin redraws\n";
759 AUPluginUI::start_cf_timer ()
765 CFTimeInterval interval = 1.0 / (float) minimum_redraw_rate;
767 cf_timer = CFRunLoopTimerCreate (kCFAllocatorDefault,
768 CFAbsoluteTimeGetCurrent() + interval,
770 au_cf_timer_callback,
773 CFRunLoopAddTimer (CFRunLoopGetCurrent(), cf_timer, kCFRunLoopCommonModes);
774 timer_needed = false;
778 AUPluginUI::stop_cf_timer ()
784 CFRunLoopRemoveTimer (CFRunLoopGetCurrent(), cf_timer, kCFRunLoopCommonModes);
790 AUPluginUI::cocoa_view_resized ()
792 /* we can get here for two reasons:
794 1) the plugin window was resized by the user, a new size was
795 allocated to the window, ::update_view_size() was called, and we
796 explicitly/manually resized the AU NSView.
798 2) the plugin decided to resize itself (probably in response to user
799 action, but not in response to an actual window resize)
801 We only want to proceed with a window resizing in the second case.
804 if (in_live_resize) {
805 /* ::update_view_size() will be called at the right times and
806 * will update the view size. We don't need to anything while a
807 * live resize in underway.
812 if (plugin_requested_resize) {
813 /* we tried to change the plugin frame from inside this method
814 * (to adjust the origin), which changes the frame of the AU
815 * NSView, resulting in a reentrant call to the FrameDidChange
816 * handler (this method). Ignore this reentrant call.
818 #ifdef AU_DEBUG_PRINT
819 std::cerr << plugin->name() << " re-entrant call to cocoa_view_resized, ignored\n";
824 plugin_requested_resize = 1;
826 ProcessorWindowProxy* wp = insert->window_proxy();
828 /* Once a plugin has requested a resize of its own window, do
829 * NOT save the window. The user may save state with the plugin
830 * editor expanded to show "extra detail" - the plugin will not
831 * refill this space when the editor is first
832 * instantiated. Leaving the window in the "too big" state
833 * cannot be recovered from.
835 * The window will be sized to fit the plugin's own request. Done.
837 wp->set_state_mask (WindowProxy::Position);
840 NSRect new_frame = [au_view frame];
842 /* from here on, we know that we've been called because the plugin
843 * decided to change the NSView frame itself.
846 /* step one: compute the change in the frame size.
849 float dy = new_frame.size.height - last_au_frame.size.height;
850 float dx = new_frame.size.width - last_au_frame.size.width;
852 NSWindow* window = get_nswindow ();
853 NSRect windowFrame= [window frame];
855 /* we want the top edge of the window to remain in the same place,
856 but the Cocoa/Quartz origin is at the lower left. So, when we make
857 the window larger, we will move it down, which means shifting the
858 origin toward (x,0). This will leave the top edge in the same place.
861 windowFrame.origin.y -= dy;
862 windowFrame.origin.x -= dx;
863 windowFrame.size.height += dy;
864 windowFrame.size.width += dx;
866 NSUInteger old_auto_resize = [au_view autoresizingMask];
868 /* Some stupid AU Views change the origin of the original AU View when
869 they are resized (I'm looking at you AUSampler). If the origin has
870 been moved, move it back.
873 if (last_au_frame.origin.x != new_frame.origin.x ||
874 last_au_frame.origin.y != new_frame.origin.y) {
875 new_frame.origin = last_au_frame.origin;
876 [au_view setFrame:new_frame];
877 /* also be sure to redraw the topbox because this can
880 top_box.queue_draw ();
883 /* We resize the window using Cocoa. We can't use GTK mechanisms
886 * http://www.lists.apple.com/archives/coreaudio-api/2005/Aug/msg00245.html
888 * "The host needs to be aware that changing the size of the window in
889 * response to the NSViewFrameDidChangeNotification can cause the view
890 * size to change depending on the autoresizing mask of the view. The
891 * host may need to cache the autoresizing mask of the view, set it to
892 * NSViewNotSizable, resize the window, and then reset the autoresizing
893 * mask of the view once the window has been sized."
897 [au_view setAutoresizingMask:NSViewNotSizable];
898 [window setFrame:windowFrame display:1];
899 [au_view setAutoresizingMask:old_auto_resize];
901 /* keep a copy of the size of the AU NSView. We didn't set it - the plugin did */
902 last_au_frame = new_frame;
903 req_width = new_frame.size.width;
904 req_height = new_frame.size.height;
906 plugin_requested_resize = 0;
910 AUPluginUI::create_carbon_view ()
914 ControlRef root_control;
916 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
918 OpenAComponent(editComponent, &editView);
920 error << _("AU Carbon view: cannot open AU Component") << endmsg;
924 Rect r = { 100, 100, 100, 100 };
925 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
926 kWindowCompositingAttribute|
927 kWindowNoShadowAttribute|
928 kWindowNoTitleBarAttribute);
930 if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
931 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
932 ArdourCloseComponent (editView);
936 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
937 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
938 DisposeWindow (carbon_window);
939 ArdourCloseComponent (editView);
944 Float32Point location = { 0.0, 0.0 };
945 Float32Point size = { 0.0, 0.0 } ;
947 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
948 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
949 DisposeWindow (carbon_window);
950 ArdourCloseComponent (editView);
957 GetControlBounds(viewPane, &bounds);
958 size.x = bounds.right-bounds.left;
959 size.y = bounds.bottom-bounds.top;
961 req_width = (int) (size.x + 0.5);
962 req_height = (int) (size.y + 0.5);
964 SizeWindow (carbon_window, req_width, req_height, true);
965 low_box.set_size_request (req_width, req_height);
969 error << _("AU Carbon GUI is not supported.") << endmsg;
975 AUPluginUI::get_nswindow ()
977 Gtk::Container* toplevel = get_toplevel();
979 if (!toplevel || !toplevel->is_toplevel()) {
980 error << _("AUPluginUI: no top level window!") << endmsg;
984 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
987 error << _("AUPluginUI: no top level window!") << endmsg;
995 AUPluginUI::activate ()
998 ActivateWindow (carbon_window, TRUE);
1003 AUPluginUI::deactivate ()
1006 ActivateWindow (carbon_window, FALSE);
1011 AUPluginUI::parent_carbon_window ()
1014 NSWindow* win = get_nswindow ();
1015 Rect windowStructureBoundsRect;
1021 /* figure out where the cocoa parent window is in carbon-coordinate space, which
1022 differs from both cocoa-coordinate space and GTK-coordinate space
1025 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
1027 /* compute how tall the title bar is, because we have to offset the position of the carbon window
1031 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
1032 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
1034 int titlebar_height = wm_frame.size.height - content_frame.size.height;
1036 int packing_extra = 6; // this is the total vertical packing in our top level window
1038 /* move into position, based on parent window position */
1039 MoveWindow (carbon_window,
1040 windowStructureBoundsRect.left,
1041 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
1043 ShowWindow (carbon_window);
1045 // create the cocoa window for the carbon one and make it visible
1046 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
1048 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
1050 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
1052 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
1053 [win setAutodisplay:1]; // turn of GTK stuff for this window
1062 AUPluginUI::parent_cocoa_window ()
1064 NSWindow* win = get_nswindow ();
1070 //[win setAutodisplay:1]; // turn off GTK stuff for this window
1072 NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
1073 [view addSubview:au_view];
1074 /* despite the fact that the documentation says that [NSWindow
1075 contentView] is the highest "accessible" NSView in an NSWindow, when
1076 the redraw cycle is executed, displayIfNeeded is actually executed
1077 on the parent of the contentView. To provide a marginal speedup when
1078 checking if a given redraw is for a plugin, use this "hidden" NSView
1079 to identify the plugin, so that we do not have to call [superview]
1080 every time in interposed_drawIfNeeded().
1082 add_plugin_view ([[win contentView] superview]);
1084 /* this moves the AU NSView down and over to provide a left-hand margin
1085 * and to clear the Ardour "task bar" (with plugin preset mgmt buttons,
1086 * keyboard focus control, bypass etc).
1090 gtk_widget_translate_coordinates(
1091 GTK_WIDGET(low_box.gobj()),
1092 GTK_WIDGET(low_box.get_parent()->gobj()),
1094 [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
1096 last_au_frame = [au_view frame];
1097 // watch for size changes of the view
1098 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
1100 [[NSNotificationCenter defaultCenter] addObserver:_notify
1101 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
1104 // catch notifications that live resizing is about to start
1106 #if HAVE_COCOA_LIVE_RESIZING
1107 _resize_notify = [ [ LiveResizeNotificationObject alloc] initWithPluginUI:this ];
1109 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1110 selector:@selector(windowWillStartLiveResizeHandler:) name:NSWindowWillStartLiveResizeNotification
1113 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1114 selector:@selector(windowWillEndLiveResizeHandler:) name:NSWindowDidEndLiveResizeNotification
1117 /* No way before 10.6 to identify the start of a live resize (drag
1118 * resize) without subclassing NSView and overriding two of its
1119 * methods. Instead of that, we make the window non-resizable, thus
1120 * ending confusion about whether or not resizes are plugin or user
1121 * driven (they are always plugin-driven).
1124 Gtk::Container* toplevel = get_toplevel();
1129 if (toplevel && toplevel->is_toplevel()) {
1130 toplevel->size_request (req);
1131 toplevel->set_size_request (req.width, req.height);
1132 dynamic_cast<Gtk::Window*>(toplevel)->set_resizable (false);
1140 AUPluginUI::grab_focus()
1143 [au_view becomeFirstResponder];
1147 AUPluginUI::forward_key_event (GdkEventKey* ev)
1149 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
1151 if (au_view && nsevent) {
1153 /* filter on nsevent type here because GDK massages FlagsChanged
1154 messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
1155 handle a FlagsChanged message as a keyDown or keyUp
1158 if ([nsevent type] == NSKeyDown) {
1159 [[[au_view window] firstResponder] keyDown:nsevent];
1160 } else if ([nsevent type] == NSKeyUp) {
1161 [[[au_view window] firstResponder] keyUp:nsevent];
1162 } else if ([nsevent type] == NSFlagsChanged) {
1163 [[[au_view window] firstResponder] flagsChanged:nsevent];
1169 AUPluginUI::on_realize ()
1171 VBox::on_realize ();
1173 /* our windows should not have that resize indicator */
1175 NSWindow* win = get_nswindow ();
1177 [win setShowsResizeIndicator:0];
1182 AUPluginUI::lower_box_realized ()
1185 parent_cocoa_window ();
1186 } else if (carbon_window) {
1187 parent_carbon_window ();
1192 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
1195 if (carbon_window && ev->state != GDK_VISIBILITY_UNOBSCURED) {
1196 ShowWindow (carbon_window);
1197 ActivateWindow (carbon_window, TRUE);
1205 AUPluginUI::lower_box_map ()
1207 [au_view setHidden:0];
1208 update_view_size ();
1212 AUPluginUI::lower_box_unmap ()
1214 [au_view setHidden:1];
1218 AUPluginUI::lower_box_size_request (GtkRequisition* requisition)
1220 requisition->width = req_width;
1221 requisition->height = req_height;
1225 AUPluginUI::lower_box_size_allocate (Gtk::Allocation& allocation)
1227 update_view_size ();
1231 AUPluginUI::on_window_hide ()
1234 if (carbon_window) {
1235 HideWindow (carbon_window);
1236 ActivateWindow (carbon_window, FALSE);
1242 NSArray* wins = [NSApp windows];
1243 for (uint32_t i = 0; i < [wins count]; i++) {
1244 id win = [wins objectAtIndex:i];
1250 AUPluginUI::on_window_show (const string& /*title*/)
1252 /* this is idempotent so just call it every time we show the window */
1254 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
1259 if (carbon_window) {
1260 ShowWindow (carbon_window);
1261 ActivateWindow (carbon_window, TRUE);
1269 AUPluginUI::start_updating (GdkEventAny*)
1275 AUPluginUI::stop_updating (GdkEventAny*)
1281 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
1283 AUPluginUI* aup = new AUPluginUI (plugin_insert);
1289 AUPluginUI::start_live_resize ()
1291 in_live_resize = true;
1295 AUPluginUI::end_live_resize ()
1297 in_live_resize = false;