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 smaller_hbox->pack_start (reset_button, false, false);
424 smaller_hbox->pack_start (bypass_button, false, true);
426 VBox* v1_box = manage (new VBox);
427 VBox* v2_box = manage (new VBox);
429 v1_box->pack_start (*smaller_hbox, false, true);
430 v2_box->pack_start (focus_button, false, true);
432 top_box.set_homogeneous (false);
433 top_box.set_spacing (6);
434 top_box.set_border_width (6);
436 top_box.pack_end (*v2_box, false, false);
437 top_box.pack_end (*v1_box, false, false);
440 pack_start (top_box, false, false);
441 pack_start (low_box, true, true);
443 preset_label.show ();
444 _preset_combo.show ();
445 automation_mode_label.show ();
446 automation_mode_selector.show ();
447 bypass_button.show ();
455 _activating_from_app = false;
462 /* prefer cocoa, fall back to cocoa, but use carbon if its there */
464 if (test_cocoa_view_support()) {
465 create_cocoa_view ();
467 } else if (test_carbon_view_support()) {
468 create_carbon_view ();
471 create_cocoa_view ();
474 low_box.add_events (Gdk::VISIBILITY_NOTIFY_MASK | Gdk::EXPOSURE_MASK);
476 low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
477 low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
479 low_box.signal_size_request ().connect (mem_fun (this, &AUPluginUI::lower_box_size_request));
480 low_box.signal_size_allocate ().connect (mem_fun (this, &AUPluginUI::lower_box_size_allocate));
481 low_box.signal_map ().connect (mem_fun (this, &AUPluginUI::lower_box_map));
482 low_box.signal_unmap ().connect (mem_fun (this, &AUPluginUI::lower_box_unmap));
486 AUPluginUI::~AUPluginUI ()
489 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
492 if (_resize_notify) {
493 [[NSNotificationCenter defaultCenter] removeObserver:_resize_notify];
496 NSWindow* win = get_nswindow();
498 remove_plugin_view ([[win contentView] superview]);
503 [win removeChildWindow:cocoa_parent];
507 /* not parented, just overlaid on top of our window */
508 DisposeWindow (carbon_window);
513 ArdourCloseComponent (editView);
517 /* remove whatever we packed into low_box so that GTK doesn't
520 [au_view removeFromSuperview];
525 AUPluginUI::test_carbon_view_support ()
530 carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
531 carbon_descriptor.componentSubType = 'gnrc';
532 carbon_descriptor.componentManufacturer = 'appl';
533 carbon_descriptor.componentFlags = 0;
534 carbon_descriptor.componentFlagsMask = 0;
538 // ask the AU for its first editor component
540 err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
542 int nEditors = propertySize / sizeof(ComponentDescription);
543 ComponentDescription *editors = new ComponentDescription[nEditors];
544 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
546 // just pick the first one for now
547 carbon_descriptor = editors[0];
560 AUPluginUI::test_cocoa_view_support ()
563 Boolean isWritable = 0;
564 OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
565 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
566 0, &dataSize, &isWritable);
568 return dataSize > 0 && err == noErr;
572 AUPluginUI::plugin_class_valid (Class pluginClass)
574 if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
575 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
576 [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
584 AUPluginUI::create_cocoa_view ()
586 bool wasAbleToLoadCustomView = false;
587 AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
588 UInt32 numberOfClasses = 0;
591 NSString* factoryClassName = 0;
592 NSURL* CocoaViewBundlePath = NULL;
594 OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
595 kAudioUnitProperty_CocoaUI,
596 kAudioUnitScope_Global,
601 numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
603 // Does view have custom Cocoa UI?
605 if ((result == noErr) && (numberOfClasses > 0) ) {
607 DEBUG_TRACE(DEBUG::AudioUnits,
608 string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
610 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
612 if(AudioUnitGetProperty(*au->get_au(),
613 kAudioUnitProperty_CocoaUI,
614 kAudioUnitScope_Global,
617 &dataSize) == noErr) {
619 CocoaViewBundlePath = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
621 // we only take the first view in this example.
622 factoryClassName = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
624 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
625 [factoryClassName UTF8String], CocoaViewBundlePath));
629 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
631 if (cocoaViewInfo != NULL) {
632 free (cocoaViewInfo);
633 cocoaViewInfo = NULL;
638 // [A] Show custom UI if view has it
640 if (CocoaViewBundlePath && factoryClassName) {
641 NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
643 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
645 if (viewBundle == NULL) {
646 error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
649 Class factoryClass = [viewBundle classNamed:factoryClassName];
650 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
652 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
656 // make sure 'factoryClass' implements the AUCocoaUIBase protocol
657 if (!plugin_class_valid (factoryClass)) {
658 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
662 id factory = [[[factoryClass alloc] init] autorelease];
663 if (factory == NULL) {
664 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
668 DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
671 au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
673 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
676 [CocoaViewBundlePath release];
679 for (i = 0; i < numberOfClasses; i++)
680 CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
682 free (cocoaViewInfo);
684 wasAbleToLoadCustomView = true;
688 if (!wasAbleToLoadCustomView) {
689 // load generic Cocoa view
690 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
692 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
693 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
694 [(AUGenericView *)au_view setShowsExpertParameters:1];
697 // Get the initial size of the new AU View's frame
698 NSRect frame = [au_view frame];
699 req_width = frame.size.width;
700 req_height = frame.size.height;
702 resizable = [au_view autoresizingMask];
704 low_box.queue_resize ();
710 AUPluginUI::update_view_size ()
712 last_au_frame = [au_view frame];
716 AUPluginUI::timer_callback ()
718 block_plugin_redraws = 0;
719 #ifdef AU_DEBUG_PRINT
720 std::cerr << "Resume redraws after idle\n";
726 au_cf_timer_callback (CFRunLoopTimerRef timer, void* info)
728 reinterpret_cast<AUPluginUI*> (info)->cf_timer_callback ();
732 AUPluginUI::cf_timer_callback ()
734 int64_t now = ARDOUR::get_microseconds ();
736 if (!last_timer || block_plugin_redraws) {
741 const int64_t usecs_slop = (1400000 / minimum_redraw_rate); // 140%
743 #ifdef AU_DEBUG_PRINT
744 std::cerr << "Timer elapsed : " << now - last_timer << std::endl;
747 if ((now - last_timer) > (usecs_slop + (1000000/minimum_redraw_rate))) {
748 block_plugin_redraws = block_plugin_redraw_count;
749 timer_connection.disconnect ();
750 timer_connection = Glib::signal_timeout().connect (&AUPluginUI::timer_callback, 40);
751 #ifdef AU_DEBUG_PRINT
752 std::cerr << "Timer too slow, block plugin redraws\n";
760 AUPluginUI::start_cf_timer ()
766 CFTimeInterval interval = 1.0 / (float) minimum_redraw_rate;
768 cf_timer = CFRunLoopTimerCreate (kCFAllocatorDefault,
769 CFAbsoluteTimeGetCurrent() + interval,
771 au_cf_timer_callback,
774 CFRunLoopAddTimer (CFRunLoopGetCurrent(), cf_timer, kCFRunLoopCommonModes);
775 timer_needed = false;
779 AUPluginUI::stop_cf_timer ()
785 CFRunLoopRemoveTimer (CFRunLoopGetCurrent(), cf_timer, kCFRunLoopCommonModes);
791 AUPluginUI::cocoa_view_resized ()
793 /* we can get here for two reasons:
795 1) the plugin window was resized by the user, a new size was
796 allocated to the window, ::update_view_size() was called, and we
797 explicitly/manually resized the AU NSView.
799 2) the plugin decided to resize itself (probably in response to user
800 action, but not in response to an actual window resize)
802 We only want to proceed with a window resizing in the second case.
805 if (in_live_resize) {
806 /* ::update_view_size() will be called at the right times and
807 * will update the view size. We don't need to anything while a
808 * live resize in underway.
813 if (plugin_requested_resize) {
814 /* we tried to change the plugin frame from inside this method
815 * (to adjust the origin), which changes the frame of the AU
816 * NSView, resulting in a reentrant call to the FrameDidChange
817 * handler (this method). Ignore this reentrant call.
819 #ifdef AU_DEBUG_PRINT
820 std::cerr << plugin->name() << " re-entrant call to cocoa_view_resized, ignored\n";
825 plugin_requested_resize = 1;
827 ProcessorWindowProxy* wp = insert->window_proxy();
829 /* Once a plugin has requested a resize of its own window, do
830 * NOT save the window. The user may save state with the plugin
831 * editor expanded to show "extra detail" - the plugin will not
832 * refill this space when the editor is first
833 * instantiated. Leaving the window in the "too big" state
834 * cannot be recovered from.
836 * The window will be sized to fit the plugin's own request. Done.
838 wp->set_state_mask (WindowProxy::Position);
841 NSRect new_frame = [au_view frame];
843 /* from here on, we know that we've been called because the plugin
844 * decided to change the NSView frame itself.
847 /* step one: compute the change in the frame size.
850 float dy = new_frame.size.height - last_au_frame.size.height;
851 float dx = new_frame.size.width - last_au_frame.size.width;
853 NSWindow* window = get_nswindow ();
854 NSRect windowFrame= [window frame];
856 /* we want the top edge of the window to remain in the same place,
857 but the Cocoa/Quartz origin is at the lower left. So, when we make
858 the window larger, we will move it down, which means shifting the
859 origin toward (x,0). This will leave the top edge in the same place.
862 windowFrame.origin.y -= dy;
863 windowFrame.origin.x -= dx;
864 windowFrame.size.height += dy;
865 windowFrame.size.width += dx;
867 NSUInteger old_auto_resize = [au_view autoresizingMask];
869 /* Some stupid AU Views change the origin of the original AU View when
870 they are resized (I'm looking at you AUSampler). If the origin has
871 been moved, move it back.
874 if (last_au_frame.origin.x != new_frame.origin.x ||
875 last_au_frame.origin.y != new_frame.origin.y) {
876 new_frame.origin = last_au_frame.origin;
877 [au_view setFrame:new_frame];
878 /* also be sure to redraw the topbox because this can
881 top_box.queue_draw ();
884 /* We resize the window using Cocoa. We can't use GTK mechanisms
887 * http://www.lists.apple.com/archives/coreaudio-api/2005/Aug/msg00245.html
889 * "The host needs to be aware that changing the size of the window in
890 * response to the NSViewFrameDidChangeNotification can cause the view
891 * size to change depending on the autoresizing mask of the view. The
892 * host may need to cache the autoresizing mask of the view, set it to
893 * NSViewNotSizable, resize the window, and then reset the autoresizing
894 * mask of the view once the window has been sized."
898 [au_view setAutoresizingMask:NSViewNotSizable];
899 [window setFrame:windowFrame display:1];
900 [au_view setAutoresizingMask:old_auto_resize];
902 /* keep a copy of the size of the AU NSView. We didn't set it - the plugin did */
903 last_au_frame = new_frame;
904 req_width = new_frame.size.width;
905 req_height = new_frame.size.height;
907 plugin_requested_resize = 0;
911 AUPluginUI::create_carbon_view ()
915 ControlRef root_control;
917 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
919 OpenAComponent(editComponent, &editView);
921 error << _("AU Carbon view: cannot open AU Component") << endmsg;
925 Rect r = { 100, 100, 100, 100 };
926 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
927 kWindowCompositingAttribute|
928 kWindowNoShadowAttribute|
929 kWindowNoTitleBarAttribute);
931 if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
932 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
933 ArdourCloseComponent (editView);
937 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
938 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
939 DisposeWindow (carbon_window);
940 ArdourCloseComponent (editView);
945 Float32Point location = { 0.0, 0.0 };
946 Float32Point size = { 0.0, 0.0 } ;
948 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
949 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
950 DisposeWindow (carbon_window);
951 ArdourCloseComponent (editView);
958 GetControlBounds(viewPane, &bounds);
959 size.x = bounds.right-bounds.left;
960 size.y = bounds.bottom-bounds.top;
962 req_width = (int) (size.x + 0.5);
963 req_height = (int) (size.y + 0.5);
965 SizeWindow (carbon_window, req_width, req_height, true);
966 low_box.set_size_request (req_width, req_height);
970 error << _("AU Carbon GUI is not supported.") << endmsg;
976 AUPluginUI::get_nswindow ()
978 Gtk::Container* toplevel = get_toplevel();
980 if (!toplevel || !toplevel->is_toplevel()) {
981 error << _("AUPluginUI: no top level window!") << endmsg;
985 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
988 error << _("AUPluginUI: no top level window!") << endmsg;
996 AUPluginUI::activate ()
999 ActivateWindow (carbon_window, TRUE);
1004 AUPluginUI::deactivate ()
1007 ActivateWindow (carbon_window, FALSE);
1012 AUPluginUI::parent_carbon_window ()
1015 NSWindow* win = get_nswindow ();
1016 Rect windowStructureBoundsRect;
1022 /* figure out where the cocoa parent window is in carbon-coordinate space, which
1023 differs from both cocoa-coordinate space and GTK-coordinate space
1026 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
1028 /* compute how tall the title bar is, because we have to offset the position of the carbon window
1032 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
1033 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
1035 int titlebar_height = wm_frame.size.height - content_frame.size.height;
1037 int packing_extra = 6; // this is the total vertical packing in our top level window
1039 /* move into position, based on parent window position */
1040 MoveWindow (carbon_window,
1041 windowStructureBoundsRect.left,
1042 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
1044 ShowWindow (carbon_window);
1046 // create the cocoa window for the carbon one and make it visible
1047 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
1049 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
1051 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
1053 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
1054 [win setAutodisplay:1]; // turn of GTK stuff for this window
1063 AUPluginUI::parent_cocoa_window ()
1065 NSWindow* win = get_nswindow ();
1071 //[win setAutodisplay:1]; // turn off GTK stuff for this window
1073 NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
1074 [view addSubview:au_view];
1075 /* despite the fact that the documentation says that [NSWindow
1076 contentView] is the highest "accessible" NSView in an NSWindow, when
1077 the redraw cycle is executed, displayIfNeeded is actually executed
1078 on the parent of the contentView. To provide a marginal speedup when
1079 checking if a given redraw is for a plugin, use this "hidden" NSView
1080 to identify the plugin, so that we do not have to call [superview]
1081 every time in interposed_drawIfNeeded().
1083 add_plugin_view ([[win contentView] superview]);
1085 /* this moves the AU NSView down and over to provide a left-hand margin
1086 * and to clear the Ardour "task bar" (with plugin preset mgmt buttons,
1087 * keyboard focus control, bypass etc).
1091 gtk_widget_translate_coordinates(
1092 GTK_WIDGET(low_box.gobj()),
1093 GTK_WIDGET(low_box.get_parent()->gobj()),
1095 [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
1097 last_au_frame = [au_view frame];
1098 // watch for size changes of the view
1099 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
1101 [[NSNotificationCenter defaultCenter] addObserver:_notify
1102 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
1105 // catch notifications that live resizing is about to start
1107 #if HAVE_COCOA_LIVE_RESIZING
1108 _resize_notify = [ [ LiveResizeNotificationObject alloc] initWithPluginUI:this ];
1110 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1111 selector:@selector(windowWillStartLiveResizeHandler:) name:NSWindowWillStartLiveResizeNotification
1114 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1115 selector:@selector(windowWillEndLiveResizeHandler:) name:NSWindowDidEndLiveResizeNotification
1118 /* No way before 10.6 to identify the start of a live resize (drag
1119 * resize) without subclassing NSView and overriding two of its
1120 * methods. Instead of that, we make the window non-resizable, thus
1121 * ending confusion about whether or not resizes are plugin or user
1122 * driven (they are always plugin-driven).
1125 Gtk::Container* toplevel = get_toplevel();
1130 if (toplevel && toplevel->is_toplevel()) {
1131 toplevel->size_request (req);
1132 toplevel->set_size_request (req.width, req.height);
1133 dynamic_cast<Gtk::Window*>(toplevel)->set_resizable (false);
1141 AUPluginUI::grab_focus()
1144 [au_view becomeFirstResponder];
1148 AUPluginUI::forward_key_event (GdkEventKey* ev)
1150 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
1152 if (au_view && nsevent) {
1154 /* filter on nsevent type here because GDK massages FlagsChanged
1155 messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
1156 handle a FlagsChanged message as a keyDown or keyUp
1159 if ([nsevent type] == NSKeyDown) {
1160 [[[au_view window] firstResponder] keyDown:nsevent];
1161 } else if ([nsevent type] == NSKeyUp) {
1162 [[[au_view window] firstResponder] keyUp:nsevent];
1163 } else if ([nsevent type] == NSFlagsChanged) {
1164 [[[au_view window] firstResponder] flagsChanged:nsevent];
1170 AUPluginUI::on_realize ()
1172 VBox::on_realize ();
1174 /* our windows should not have that resize indicator */
1176 NSWindow* win = get_nswindow ();
1178 [win setShowsResizeIndicator:0];
1183 AUPluginUI::lower_box_realized ()
1186 parent_cocoa_window ();
1187 } else if (carbon_window) {
1188 parent_carbon_window ();
1193 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
1196 if (carbon_window && ev->state != GDK_VISIBILITY_UNOBSCURED) {
1197 ShowWindow (carbon_window);
1198 ActivateWindow (carbon_window, TRUE);
1206 AUPluginUI::lower_box_map ()
1208 [au_view setHidden:0];
1209 update_view_size ();
1213 AUPluginUI::lower_box_unmap ()
1215 [au_view setHidden:1];
1219 AUPluginUI::lower_box_size_request (GtkRequisition* requisition)
1221 requisition->width = req_width;
1222 requisition->height = req_height;
1226 AUPluginUI::lower_box_size_allocate (Gtk::Allocation& allocation)
1228 update_view_size ();
1232 AUPluginUI::on_window_hide ()
1235 if (carbon_window) {
1236 HideWindow (carbon_window);
1237 ActivateWindow (carbon_window, FALSE);
1243 NSArray* wins = [NSApp windows];
1244 for (uint32_t i = 0; i < [wins count]; i++) {
1245 id win = [wins objectAtIndex:i];
1251 AUPluginUI::on_window_show (const string& /*title*/)
1253 /* this is idempotent so just call it every time we show the window */
1255 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
1260 if (carbon_window) {
1261 ShowWindow (carbon_window);
1262 ActivateWindow (carbon_window, TRUE);
1270 AUPluginUI::start_updating (GdkEventAny*)
1276 AUPluginUI::stop_updating (GdkEventAny*)
1282 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
1284 AUPluginUI* aup = new AUPluginUI (plugin_insert);
1290 AUPluginUI::start_live_resize ()
1292 in_live_resize = true;
1296 AUPluginUI::end_live_resize ()
1298 in_live_resize = false;