2 #define Marker FuckYouAppleAndYourLackOfNameSpaces
5 #include <gtkmm/button.h>
6 #include <gtkmm/comboboxtext.h>
7 #include <gdk/gdkquartz.h>
9 #include "pbd/convert.h"
10 #include "pbd/error.h"
12 #include "ardour/audio_unit.h"
13 #include "ardour/debug.h"
14 #include "ardour/plugin_insert.h"
16 #undef check // stupid gtk, stupid apple
18 #include <gtkmm2ext/utils.h>
19 #include <gtkmm2ext/window_proxy.h>
21 #include "au_pluginui.h"
22 #include "gui_thread.h"
23 #include "processor_box.h"
25 // yes, yes we know (see wscript for various available OSX compat modes)
26 #if defined (__clang__)
27 # pragma clang diagnostic push
28 # pragma clang diagnostic ignored "-Wdeprecated-declarations"
31 #include "CAAudioUnit.h"
32 #include "CAComponent.h"
34 #if defined (__clang__)
35 # pragma clang diagnostic pop
38 #import <AudioUnit/AUCocoaUIView.h>
39 #import <CoreAudioKit/AUGenericView.h>
40 #import <objc/runtime.h>
43 #include <dispatch/dispatch.h>
50 #include "public_editor.h"
53 #include "gtk2ardour-config.h"
56 #define ArdourCloseComponent CloseComponent
58 #define ArdourCloseComponent AudioComponentInstanceDispose
60 using namespace ARDOUR;
62 using namespace Gtkmm2ext;
66 vector<string> AUPluginUI::automation_mode_strings;
67 int64_t AUPluginUI::last_timer = 0;
68 bool AUPluginUI::timer_needed = true;
69 CFRunLoopTimerRef AUPluginUI::cf_timer;
70 sigc::connection AUPluginUI::timer_connection;
72 static const gchar* _automation_mode_strings[] = {
81 dump_view_tree (NSView* view, int depth, int maxdepth)
83 NSArray* subviews = [view subviews];
84 unsigned long cnt = [subviews count];
87 NSView* su = [view superview];
89 NSRect sf = [su frame];
90 cerr << " PARENT view " << su << " @ " << sf.origin.x << ", " << sf.origin.y
91 << ' ' << sf.size.width << " x " << sf.size.height
96 for (int d = 0; d < depth; d++) {
99 NSRect frame = [view frame];
100 cerr << " view " << view << " @ " << frame.origin.x << ", " << frame.origin.y
101 << ' ' << frame.size.width << " x " << frame.size.height
104 if (depth >= maxdepth) {
107 for (unsigned long i = 0; i < cnt; ++i) {
108 NSView* subview = [subviews objectAtIndex:i];
109 dump_view_tree (subview, depth+1, maxdepth);
113 /* This deeply hacky block of code exists for a rather convoluted reason.
115 * The proximal reason is that there are plugins (such as XLN's Addictive Drums
116 * 2) which redraw their GUI/editor windows using a timer, and use a drawing
117 * technique that on Retina displays ends up calling arg32_image_mark_RGB32, a
118 * function that for some reason (probably byte-swapping or pixel-doubling) is
119 * many times slower than the function used on non-Retina displays.
121 * We are not the first people to discover the problem with
122 * arg32_image_mark_RGB32.
124 * Justin Fraenkel, the lead author of Reaper, wrote a very detailed account of
125 * the performance issues with arg32_image_mark_RGB32 here:
126 * http://www.1014.org/?article=516
128 * The problem was also seen by Robert O'Callahan (lead developer of rr, the
129 * reverse debugger) as far back as 2010:
130 * http://robert.ocallahan.org/2010/05/cglayer-performance-trap-with-isflipped_03.html
132 * In fact, it is so slow that the drawing takes up close to 100% of a single
133 * core, and the event loop that the drawing occurs in never sleeps or "idles".
135 * In AU hosts built directly on top of Cocoa, or some other toolkits, this
136 * isn't inherently a major problem - it just makes the entire GUI of the
139 * However, there is an additional problem for Ardour because GTK+ is built on
140 * top of the GDK/Quartz event loop integration. This integration is rather
141 * baroque, mostly because it was written at a time when CFRunLoop did not
142 * offer a way to wait for "input" from file descriptors (which arrived in OS X
143 * 10.5). As a result, it uses a hair-raising design involving an additional
144 * thread. This design has a major problem, which is that it effectively
145 * creates two nested run loops.
147 * The GTK+/GDK/glib one runs until it has nothing to do, at which time it
148 * calls a function to wait until there is something to do. On Linux or Windows
149 * that would involve some variant or relative of poll(2), which puts the
150 * process to sleep until there is something to do.
152 * On OS X, glib ends up calling [CFRunLoop waitForNextEventMatchingMask] which
153 * will eventually put the process to sleep, but won't do so until the
154 * CFRunLoop also has nothing to do. This includes (at least) a complete redraw
155 * cycle. If redrawing takes too long, and there are timers expired for another
156 * redraw (e.g. Addictive Drums 2, again), then the CFRunLoop will just start
157 * another redraw cycle after processing any events and other stuff.
159 * If the CFRunLoop stays busy, then it will never return to the glib
160 * level at all, thus stopping any further GTK+ level activity (events,
161 * drawing) from taking place. In short, the current (spring 2016) design of
162 * the GDK/Quartz event loop integration relies on the idea that the internal
163 * CFRunLoop will go idle, and totally breaks if this does not happen.
165 * So take a fully functional Ardour, add in XLN's Addictive Drums 2, and a
166 * Retina display, and Apple's ridiculously slow blitting code, and the
167 * CFRunLoop never goes idle. As soon as Addictive Drums starts drawing (over
168 * and over again), the GTK+ event loop stops receiving events and stops
171 * One fix for this was to run a nested GTK+ event loop iteration (or two)
172 * whenever a plugin window was redrawn. This works in the sense that the
173 * immediate issue (no GTK+ events or drawing) is fixed. But the recursive GTK+
174 * event loop causes its own (very subtle) problems too.
176 * This code takes a rather radical approach. We use Objective C's ability to
177 * swizzle object methods. Specifically, we replace [NSView displayIfNeeded]
178 * with our own version which will skip redraws of plugin windows if we tell it
179 * too. If we haven't done that, or if the redraw is of a non-plugin window,
180 * then we invoke the original displayIfNeeded method.
182 * After every 10 redraws of a given plugin GUI/editor window, we queue up a
183 * GTK/glib idle callback to measure the interval between those idle
184 * callbacks. We do this globally across all plugin windows, so if the callback
185 * is already queued, we don't requeue it.
187 * If the interval is longer than 40msec (a 25fps redraw rate), we set
188 * block_plugin_redraws to some number. Each successive call to our interposed
189 * displayIfNeeded method will (a) check this value and if non-zero (b) check
190 * if the call is for a plugin-related NSView/NSWindow. If it is, then we will
191 * skip the redisplay entirely, hopefully avoiding any calls to
192 * argb32_image_mark_RGB32 or any other slow drawing code, and thus allowing
193 * the CFRunLoop to go idle. If the value is zero or the call is for a
194 * non-plugin window, then we just invoke the "original" displayIfNeeded
197 * This hack adds a tiny bit of overhead onto redrawing of the entire
198 * application. But in the common case this consists of 1 conditional (the
199 * check on block_plugin_redraws, which will find it to be zero) and the
200 * invocation of the original method. Given how much work is typically done
201 * during drawing, this seems acceptable.
203 * The correct fix for this is to redesign the relationship between
204 * GTK+/GDK/glib so that a glib run loop is actually a CFRunLoop, with all
205 * GSources represented as CFRunLoopSources, without any nesting and without
206 * any additional thread. This is not a task to be undertaken lightly, and is
207 * certainly substantially more work than this was. It may never be possible to
208 * do that work in a way that could be integrated back into glib, because of
209 * the rather specific semantics and types of GSources, but it would almost
210 * certainly be possible to make it work for Ardour.
213 static uint32_t block_plugin_redraws = 0;
214 static const uint32_t minimum_redraw_rate = 30; /* frames per second */
215 static const uint32_t block_plugin_redraw_count = 15; /* number of combined plugin redraws to block, if blocking */
219 /* PowerPC versions of OS X do not support libdispatch, which we use below when swizzling objective C. But they also don't have Retina
220 * which is the underlying reason for this code. So just skip it on those CPUs.
224 static void add_plugin_view (id view) {}
225 static void remove_plugin_view (id view) {}
229 static IMP original_nsview_drawIfNeeded;
230 static std::vector<id> plugin_views;
232 static void add_plugin_view (id view)
234 if (plugin_views.empty()) {
235 AUPluginUI::start_cf_timer ();
238 plugin_views.push_back (view);
242 static void remove_plugin_view (id view)
244 std::vector<id>::iterator x = find (plugin_views.begin(), plugin_views.end(), view);
245 if (x != plugin_views.end()) {
246 plugin_views.erase (x);
248 if (plugin_views.empty()) {
249 AUPluginUI::stop_cf_timer ();
253 static void interposed_drawIfNeeded (id receiver, SEL selector, NSRect rect)
255 if (block_plugin_redraws && (find (plugin_views.begin(), plugin_views.end(), receiver) != plugin_views.end())) {
256 block_plugin_redraws--;
257 #ifdef AU_DEBUG_PRINT
258 std::cerr << "Plugin redraw blocked\n";
260 /* YOU ... SHALL .... NOT ... DRAW!!!! */
263 (void) ((int (*)(id,SEL,NSRect)) original_nsview_drawIfNeeded) (receiver, selector, rect);
266 @implementation NSView (Tracking)
268 static dispatch_once_t once_token;
270 /* this swizzles NSView::displayIfNeeded and replaces it with
271 * interposed_drawIfNeeded(), which allows us to interpose and block
272 * the redrawing of plugin UIs when their redrawing behaviour
273 * is interfering with event loop behaviour.
276 dispatch_once (&once_token, ^{
277 Method target = class_getInstanceMethod ([NSView class], @selector(displayIfNeeded));
278 original_nsview_drawIfNeeded = method_setImplementation (target, (IMP) interposed_drawIfNeeded);
286 /* END OF THE PLUGIN REDRAW HACK */
288 @implementation NotificationObject
290 - (NotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui andCocoaParent: (NSWindow*) cp andTopLevelParent: (NSWindow*) tlp
292 self = [ super init ];
295 plugin_ui = apluginui;
296 top_level_parent = tlp;
301 [[NSNotificationCenter defaultCenter]
303 selector:@selector(cocoaParentActivationHandler:)
304 name:NSWindowDidBecomeMainNotification
307 [[NSNotificationCenter defaultCenter]
309 selector:@selector(cocoaParentBecameKeyHandler:)
310 name:NSWindowDidBecomeKeyNotification
318 - (void)cocoaParentActivationHandler:(NSNotification *)notification
320 NSWindow* notification_window = (NSWindow *)[notification object];
322 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
323 if ([notification_window isMainWindow]) {
324 plugin_ui->activate();
326 plugin_ui->deactivate();
331 - (void)cocoaParentBecameKeyHandler:(NSNotification *)notification
333 NSWindow* notification_window = (NSWindow *)[notification object];
335 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
336 if ([notification_window isKeyWindow]) {
337 plugin_ui->activate();
339 plugin_ui->deactivate();
344 - (void)auViewResized:(NSNotification *)notification
346 (void) notification; // stop complaints about unusued argument
347 plugin_ui->cocoa_view_resized();
352 @implementation LiveResizeNotificationObject
354 - (LiveResizeNotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui
356 self = [ super init ];
358 plugin_ui = apluginui;
364 - (void)windowWillStartLiveResizeHandler:(NSNotification*)notification
366 plugin_ui->start_live_resize ();
369 - (void)windowWillEndLiveResizeHandler:(NSNotification*)notification
371 plugin_ui->end_live_resize ();
375 AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
376 : PlugUIBase (insert)
377 , automation_mode_label (_("Automation"))
378 , preset_label (_("Presets"))
384 , in_live_resize (false)
385 , plugin_requested_resize (0)
390 if (automation_mode_strings.empty()) {
391 automation_mode_strings = I18N (_automation_mode_strings);
394 set_popdown_strings (automation_mode_selector, automation_mode_strings);
395 automation_mode_selector.set_active_text (automation_mode_strings.front());
397 if ((au = boost::dynamic_pointer_cast<AUPlugin> (insert->plugin())) == 0) {
398 error << _("unknown type of editor-supplying plugin (note: no AudioUnit support in this version of ardour)") << endmsg;
399 throw failed_constructor ();
402 /* stuff some stuff into the top of the window */
404 HBox* smaller_hbox = manage (new HBox);
406 smaller_hbox->set_spacing (6);
407 smaller_hbox->pack_start (pin_management_button, false, false, 4);
408 smaller_hbox->pack_start (preset_label, false, false, 4);
409 smaller_hbox->pack_start (_preset_modified, false, false);
410 smaller_hbox->pack_start (_preset_combo, false, false);
411 smaller_hbox->pack_start (add_button, false, false);
413 /* Ardour does not currently allow to overwrite existing presets
414 * see save_property_list() in audio_unit.cc
416 smaller_hbox->pack_start (save_button, false, false);
419 /* one day these might be useful with an AU plugin, but not yet */
420 smaller_hbox->pack_start (automation_mode_label, false, false);
421 smaller_hbox->pack_start (automation_mode_selector, false, false);
423 if (insert->controls().size() > 0) {
424 smaller_hbox->pack_start (reset_button, false, false);
426 smaller_hbox->pack_start (bypass_button, false, true);
428 VBox* v1_box = manage (new VBox);
429 VBox* v2_box = manage (new VBox);
431 v1_box->pack_start (*smaller_hbox, false, true);
432 v2_box->pack_start (focus_button, false, true);
434 top_box.set_homogeneous (false);
435 top_box.set_spacing (6);
436 top_box.set_border_width (6);
438 top_box.pack_end (*v2_box, false, false);
439 top_box.pack_end (*v1_box, false, false);
442 pack_start (top_box, false, false);
443 pack_start (low_box, true, true);
445 preset_label.show ();
446 _preset_combo.show ();
447 automation_mode_label.show ();
448 automation_mode_selector.show ();
449 bypass_button.show ();
457 _activating_from_app = false;
464 /* prefer cocoa, fall back to cocoa, but use carbon if its there */
466 if (test_cocoa_view_support()) {
467 create_cocoa_view ();
469 } else if (test_carbon_view_support()) {
470 create_carbon_view ();
473 create_cocoa_view ();
476 low_box.add_events (Gdk::VISIBILITY_NOTIFY_MASK | Gdk::EXPOSURE_MASK);
478 low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
479 low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
481 low_box.signal_size_request ().connect (mem_fun (this, &AUPluginUI::lower_box_size_request));
482 low_box.signal_size_allocate ().connect (mem_fun (this, &AUPluginUI::lower_box_size_allocate));
483 low_box.signal_map ().connect (mem_fun (this, &AUPluginUI::lower_box_map));
484 low_box.signal_unmap ().connect (mem_fun (this, &AUPluginUI::lower_box_unmap));
488 AUPluginUI::~AUPluginUI ()
491 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
494 if (_resize_notify) {
495 [[NSNotificationCenter defaultCenter] removeObserver:_resize_notify];
498 NSWindow* win = get_nswindow();
500 remove_plugin_view ([[win contentView] superview]);
505 [win removeChildWindow:cocoa_parent];
509 /* not parented, just overlaid on top of our window */
510 DisposeWindow (carbon_window);
515 ArdourCloseComponent (editView);
519 /* remove whatever we packed into low_box so that GTK doesn't
522 [au_view removeFromSuperview];
527 AUPluginUI::test_carbon_view_support ()
532 carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
533 carbon_descriptor.componentSubType = 'gnrc';
534 carbon_descriptor.componentManufacturer = 'appl';
535 carbon_descriptor.componentFlags = 0;
536 carbon_descriptor.componentFlagsMask = 0;
540 // ask the AU for its first editor component
542 err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
544 int nEditors = propertySize / sizeof(ComponentDescription);
545 ComponentDescription *editors = new ComponentDescription[nEditors];
546 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
548 // just pick the first one for now
549 carbon_descriptor = editors[0];
562 AUPluginUI::test_cocoa_view_support ()
565 Boolean isWritable = 0;
566 OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
567 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
568 0, &dataSize, &isWritable);
570 return dataSize > 0 && err == noErr;
574 AUPluginUI::plugin_class_valid (Class pluginClass)
576 if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
577 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
578 [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
586 AUPluginUI::create_cocoa_view ()
588 bool wasAbleToLoadCustomView = false;
589 AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
590 UInt32 numberOfClasses = 0;
593 NSString* factoryClassName = 0;
594 NSURL* CocoaViewBundlePath = NULL;
596 OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
597 kAudioUnitProperty_CocoaUI,
598 kAudioUnitScope_Global,
603 numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
605 // Does view have custom Cocoa UI?
607 if ((result == noErr) && (numberOfClasses > 0) ) {
609 DEBUG_TRACE(DEBUG::AudioUnits,
610 string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
612 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
614 if(AudioUnitGetProperty(*au->get_au(),
615 kAudioUnitProperty_CocoaUI,
616 kAudioUnitScope_Global,
619 &dataSize) == noErr) {
621 CocoaViewBundlePath = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
623 // we only take the first view in this example.
624 factoryClassName = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
626 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
627 [factoryClassName UTF8String], CocoaViewBundlePath));
631 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
633 if (cocoaViewInfo != NULL) {
634 free (cocoaViewInfo);
635 cocoaViewInfo = NULL;
640 // [A] Show custom UI if view has it
642 if (CocoaViewBundlePath && factoryClassName) {
643 NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
645 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
647 if (viewBundle == NULL) {
648 error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
651 Class factoryClass = [viewBundle classNamed:factoryClassName];
652 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
654 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
658 // make sure 'factoryClass' implements the AUCocoaUIBase protocol
659 if (!plugin_class_valid (factoryClass)) {
660 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
664 id factory = [[[factoryClass alloc] init] autorelease];
665 if (factory == NULL) {
666 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
670 DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
673 au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
675 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
678 [CocoaViewBundlePath release];
681 for (i = 0; i < numberOfClasses; i++)
682 CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
684 free (cocoaViewInfo);
686 wasAbleToLoadCustomView = true;
690 if (!wasAbleToLoadCustomView) {
691 // load generic Cocoa view
692 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
694 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
695 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
696 [(AUGenericView *)au_view setShowsExpertParameters:1];
699 // Get the initial size of the new AU View's frame
700 NSRect frame = [au_view frame];
701 req_width = frame.size.width;
702 req_height = frame.size.height;
704 resizable = [au_view autoresizingMask];
706 low_box.queue_resize ();
712 AUPluginUI::update_view_size ()
714 last_au_frame = [au_view frame];
718 AUPluginUI::timer_callback ()
720 block_plugin_redraws = 0;
721 #ifdef AU_DEBUG_PRINT
722 std::cerr << "Resume redraws after idle\n";
728 au_cf_timer_callback (CFRunLoopTimerRef timer, void* info)
730 reinterpret_cast<AUPluginUI*> (info)->cf_timer_callback ();
734 AUPluginUI::cf_timer_callback ()
736 int64_t now = ARDOUR::get_microseconds ();
738 if (!last_timer || block_plugin_redraws) {
743 const int64_t usecs_slop = (1400000 / minimum_redraw_rate); // 140%
745 #ifdef AU_DEBUG_PRINT
746 std::cerr << "Timer elapsed : " << now - last_timer << std::endl;
749 if ((now - last_timer) > (usecs_slop + (1000000/minimum_redraw_rate))) {
750 block_plugin_redraws = block_plugin_redraw_count;
751 timer_connection.disconnect ();
752 timer_connection = Glib::signal_timeout().connect (&AUPluginUI::timer_callback, 40);
753 #ifdef AU_DEBUG_PRINT
754 std::cerr << "Timer too slow, block plugin redraws\n";
762 AUPluginUI::start_cf_timer ()
768 CFTimeInterval interval = 1.0 / (float) minimum_redraw_rate;
770 cf_timer = CFRunLoopTimerCreate (kCFAllocatorDefault,
771 CFAbsoluteTimeGetCurrent() + interval,
773 au_cf_timer_callback,
776 CFRunLoopAddTimer (CFRunLoopGetCurrent(), cf_timer, kCFRunLoopCommonModes);
777 timer_needed = false;
781 AUPluginUI::stop_cf_timer ()
787 CFRunLoopRemoveTimer (CFRunLoopGetCurrent(), cf_timer, kCFRunLoopCommonModes);
793 AUPluginUI::cocoa_view_resized ()
795 /* we can get here for two reasons:
797 1) the plugin window was resized by the user, a new size was
798 allocated to the window, ::update_view_size() was called, and we
799 explicitly/manually resized the AU NSView.
801 2) the plugin decided to resize itself (probably in response to user
802 action, but not in response to an actual window resize)
804 We only want to proceed with a window resizing in the second case.
807 if (in_live_resize) {
808 /* ::update_view_size() will be called at the right times and
809 * will update the view size. We don't need to anything while a
810 * live resize in underway.
815 if (plugin_requested_resize) {
816 /* we tried to change the plugin frame from inside this method
817 * (to adjust the origin), which changes the frame of the AU
818 * NSView, resulting in a reentrant call to the FrameDidChange
819 * handler (this method). Ignore this reentrant call.
821 #ifdef AU_DEBUG_PRINT
822 std::cerr << plugin->name() << " re-entrant call to cocoa_view_resized, ignored\n";
827 plugin_requested_resize = 1;
829 ProcessorWindowProxy* wp = insert->window_proxy();
831 /* Once a plugin has requested a resize of its own window, do
832 * NOT save the window. The user may save state with the plugin
833 * editor expanded to show "extra detail" - the plugin will not
834 * refill this space when the editor is first
835 * instantiated. Leaving the window in the "too big" state
836 * cannot be recovered from.
838 * The window will be sized to fit the plugin's own request. Done.
840 wp->set_state_mask (WindowProxy::Position);
843 NSRect new_frame = [au_view frame];
845 /* from here on, we know that we've been called because the plugin
846 * decided to change the NSView frame itself.
849 /* step one: compute the change in the frame size.
852 float dy = new_frame.size.height - last_au_frame.size.height;
853 float dx = new_frame.size.width - last_au_frame.size.width;
855 NSWindow* window = get_nswindow ();
856 NSRect windowFrame= [window frame];
858 /* we want the top edge of the window to remain in the same place,
859 but the Cocoa/Quartz origin is at the lower left. So, when we make
860 the window larger, we will move it down, which means shifting the
861 origin toward (x,0). This will leave the top edge in the same place.
864 windowFrame.origin.y -= dy;
865 windowFrame.origin.x -= dx;
866 windowFrame.size.height += dy;
867 windowFrame.size.width += dx;
869 NSUInteger old_auto_resize = [au_view autoresizingMask];
871 /* Some stupid AU Views change the origin of the original AU View when
872 they are resized (I'm looking at you AUSampler). If the origin has
873 been moved, move it back.
876 if (last_au_frame.origin.x != new_frame.origin.x ||
877 last_au_frame.origin.y != new_frame.origin.y) {
878 new_frame.origin = last_au_frame.origin;
879 [au_view setFrame:new_frame];
880 /* also be sure to redraw the topbox because this can
883 top_box.queue_draw ();
886 /* We resize the window using Cocoa. We can't use GTK mechanisms
889 * http://www.lists.apple.com/archives/coreaudio-api/2005/Aug/msg00245.html
891 * "The host needs to be aware that changing the size of the window in
892 * response to the NSViewFrameDidChangeNotification can cause the view
893 * size to change depending on the autoresizing mask of the view. The
894 * host may need to cache the autoresizing mask of the view, set it to
895 * NSViewNotSizable, resize the window, and then reset the autoresizing
896 * mask of the view once the window has been sized."
900 [au_view setAutoresizingMask:NSViewNotSizable];
901 [window setFrame:windowFrame display:1];
902 [au_view setAutoresizingMask:old_auto_resize];
904 /* keep a copy of the size of the AU NSView. We didn't set it - the plugin did */
905 last_au_frame = new_frame;
906 req_width = new_frame.size.width;
907 req_height = new_frame.size.height;
909 plugin_requested_resize = 0;
913 AUPluginUI::create_carbon_view ()
917 ControlRef root_control;
919 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
921 OpenAComponent(editComponent, &editView);
923 error << _("AU Carbon view: cannot open AU Component") << endmsg;
927 Rect r = { 100, 100, 100, 100 };
928 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
929 kWindowCompositingAttribute|
930 kWindowNoShadowAttribute|
931 kWindowNoTitleBarAttribute);
933 if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
934 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
935 ArdourCloseComponent (editView);
939 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
940 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
941 DisposeWindow (carbon_window);
942 ArdourCloseComponent (editView);
947 Float32Point location = { 0.0, 0.0 };
948 Float32Point size = { 0.0, 0.0 } ;
950 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
951 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
952 DisposeWindow (carbon_window);
953 ArdourCloseComponent (editView);
960 GetControlBounds(viewPane, &bounds);
961 size.x = bounds.right-bounds.left;
962 size.y = bounds.bottom-bounds.top;
964 req_width = (int) (size.x + 0.5);
965 req_height = (int) (size.y + 0.5);
967 SizeWindow (carbon_window, req_width, req_height, true);
968 low_box.set_size_request (req_width, req_height);
972 error << _("AU Carbon GUI is not supported.") << endmsg;
978 AUPluginUI::get_nswindow ()
980 Gtk::Container* toplevel = get_toplevel();
982 if (!toplevel || !toplevel->is_toplevel()) {
983 error << _("AUPluginUI: no top level window!") << endmsg;
987 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
990 error << _("AUPluginUI: no top level window!") << endmsg;
998 AUPluginUI::activate ()
1001 ActivateWindow (carbon_window, TRUE);
1006 AUPluginUI::deactivate ()
1009 ActivateWindow (carbon_window, FALSE);
1014 AUPluginUI::parent_carbon_window ()
1017 NSWindow* win = get_nswindow ();
1018 Rect windowStructureBoundsRect;
1024 /* figure out where the cocoa parent window is in carbon-coordinate space, which
1025 differs from both cocoa-coordinate space and GTK-coordinate space
1028 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
1030 /* compute how tall the title bar is, because we have to offset the position of the carbon window
1034 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
1035 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
1037 int titlebar_height = wm_frame.size.height - content_frame.size.height;
1039 int packing_extra = 6; // this is the total vertical packing in our top level window
1041 /* move into position, based on parent window position */
1042 MoveWindow (carbon_window,
1043 windowStructureBoundsRect.left,
1044 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
1046 ShowWindow (carbon_window);
1048 // create the cocoa window for the carbon one and make it visible
1049 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
1051 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
1053 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
1055 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
1056 [win setAutodisplay:1]; // turn of GTK stuff for this window
1065 AUPluginUI::parent_cocoa_window ()
1067 NSWindow* win = get_nswindow ();
1073 //[win setAutodisplay:1]; // turn off GTK stuff for this window
1075 NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
1076 [view addSubview:au_view];
1077 /* despite the fact that the documentation says that [NSWindow
1078 contentView] is the highest "accessible" NSView in an NSWindow, when
1079 the redraw cycle is executed, displayIfNeeded is actually executed
1080 on the parent of the contentView. To provide a marginal speedup when
1081 checking if a given redraw is for a plugin, use this "hidden" NSView
1082 to identify the plugin, so that we do not have to call [superview]
1083 every time in interposed_drawIfNeeded().
1085 add_plugin_view ([[win contentView] superview]);
1087 /* this moves the AU NSView down and over to provide a left-hand margin
1088 * and to clear the Ardour "task bar" (with plugin preset mgmt buttons,
1089 * keyboard focus control, bypass etc).
1093 gtk_widget_translate_coordinates(
1094 GTK_WIDGET(low_box.gobj()),
1095 GTK_WIDGET(low_box.get_parent()->gobj()),
1097 [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
1099 last_au_frame = [au_view frame];
1100 // watch for size changes of the view
1101 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
1103 [[NSNotificationCenter defaultCenter] addObserver:_notify
1104 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
1107 // catch notifications that live resizing is about to start
1109 #if HAVE_COCOA_LIVE_RESIZING
1110 _resize_notify = [ [ LiveResizeNotificationObject alloc] initWithPluginUI:this ];
1112 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1113 selector:@selector(windowWillStartLiveResizeHandler:) name:NSWindowWillStartLiveResizeNotification
1116 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1117 selector:@selector(windowWillEndLiveResizeHandler:) name:NSWindowDidEndLiveResizeNotification
1120 /* No way before 10.6 to identify the start of a live resize (drag
1121 * resize) without subclassing NSView and overriding two of its
1122 * methods. Instead of that, we make the window non-resizable, thus
1123 * ending confusion about whether or not resizes are plugin or user
1124 * driven (they are always plugin-driven).
1127 Gtk::Container* toplevel = get_toplevel();
1132 if (toplevel && toplevel->is_toplevel()) {
1133 toplevel->size_request (req);
1134 toplevel->set_size_request (req.width, req.height);
1135 dynamic_cast<Gtk::Window*>(toplevel)->set_resizable (false);
1143 AUPluginUI::grab_focus()
1146 [au_view becomeFirstResponder];
1150 AUPluginUI::forward_key_event (GdkEventKey* ev)
1152 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
1154 if (au_view && nsevent) {
1156 /* filter on nsevent type here because GDK massages FlagsChanged
1157 messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
1158 handle a FlagsChanged message as a keyDown or keyUp
1161 if ([nsevent type] == NSKeyDown) {
1162 [[[au_view window] firstResponder] keyDown:nsevent];
1163 } else if ([nsevent type] == NSKeyUp) {
1164 [[[au_view window] firstResponder] keyUp:nsevent];
1165 } else if ([nsevent type] == NSFlagsChanged) {
1166 [[[au_view window] firstResponder] flagsChanged:nsevent];
1172 AUPluginUI::on_realize ()
1174 VBox::on_realize ();
1176 /* our windows should not have that resize indicator */
1178 NSWindow* win = get_nswindow ();
1180 [win setShowsResizeIndicator:0];
1185 AUPluginUI::lower_box_realized ()
1188 parent_cocoa_window ();
1189 } else if (carbon_window) {
1190 parent_carbon_window ();
1195 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
1198 if (carbon_window && ev->state != GDK_VISIBILITY_UNOBSCURED) {
1199 ShowWindow (carbon_window);
1200 ActivateWindow (carbon_window, TRUE);
1208 AUPluginUI::lower_box_map ()
1210 [au_view setHidden:0];
1211 update_view_size ();
1215 AUPluginUI::lower_box_unmap ()
1217 [au_view setHidden:1];
1221 AUPluginUI::lower_box_size_request (GtkRequisition* requisition)
1223 requisition->width = req_width;
1224 requisition->height = req_height;
1228 AUPluginUI::lower_box_size_allocate (Gtk::Allocation& allocation)
1230 update_view_size ();
1234 AUPluginUI::on_window_hide ()
1237 if (carbon_window) {
1238 HideWindow (carbon_window);
1239 ActivateWindow (carbon_window, FALSE);
1245 NSArray* wins = [NSApp windows];
1246 for (uint32_t i = 0; i < [wins count]; i++) {
1247 id win = [wins objectAtIndex:i];
1253 AUPluginUI::on_window_show (const string& /*title*/)
1255 /* this is idempotent so just call it every time we show the window */
1257 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
1262 if (carbon_window) {
1263 ShowWindow (carbon_window);
1264 ActivateWindow (carbon_window, TRUE);
1272 AUPluginUI::start_updating (GdkEventAny*)
1278 AUPluginUI::stop_updating (GdkEventAny*)
1284 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
1286 AUPluginUI* aup = new AUPluginUI (plugin_insert);
1292 AUPluginUI::start_live_resize ()
1294 in_live_resize = true;
1298 AUPluginUI::end_live_resize ()
1300 in_live_resize = false;