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);
412 smaller_hbox->pack_start (save_button, false, false);
413 smaller_hbox->pack_start (delete_button, false, false);
416 /* one day these might be useful with an AU plugin, but not yet */
417 smaller_hbox->pack_start (automation_mode_label, false, false);
418 smaller_hbox->pack_start (automation_mode_selector, false, false);
420 if (insert->controls().size() > 0) {
421 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;