2 #define Marker FuckYouAppleAndYourLackOfNameSpaces
5 #include <gtkmm/button.h>
6 #include <gdk/gdkquartz.h>
8 #include "pbd/convert.h"
11 #include "ardour/audio_unit.h"
12 #include "ardour/debug.h"
13 #include "ardour/plugin_insert.h"
15 #undef check // stupid gtk, stupid apple
17 #include <gtkmm2ext/utils.h>
18 #include <gtkmm2ext/window_proxy.h>
20 #include "au_pluginui.h"
21 #include "gui_thread.h"
22 #include "processor_box.h"
24 // yes, yes we know (see wscript for various available OSX compat modes)
25 #if defined (__clang__)
26 # pragma clang diagnostic push
27 # pragma clang diagnostic ignored "-Wdeprecated-declarations"
30 #include "CAAudioUnit.h"
31 #include "CAComponent.h"
33 #if defined (__clang__)
34 # pragma clang diagnostic pop
37 #import <AudioUnit/AUCocoaUIView.h>
38 #import <CoreAudioKit/AUGenericView.h>
39 #import <objc/runtime.h>
40 #include <dispatch/dispatch.h>
46 #include "public_editor.h"
49 #include "gtk2ardour-config.h"
52 #define ArdourCloseComponent CloseComponent
54 #define ArdourCloseComponent AudioComponentInstanceDispose
56 using namespace ARDOUR;
58 using namespace Gtkmm2ext;
62 vector<string> AUPluginUI::automation_mode_strings;
63 int64_t AUPluginUI::last_timer = 0;
64 bool AUPluginUI::timer_needed = true;
65 CFRunLoopTimerRef AUPluginUI::cf_timer;
66 sigc::connection AUPluginUI::timer_connection;
68 static const gchar* _automation_mode_strings[] = {
77 dump_view_tree (NSView* view, int depth, int maxdepth)
79 NSArray* subviews = [view subviews];
80 unsigned long cnt = [subviews count];
83 NSView* su = [view superview];
85 NSRect sf = [su frame];
86 cerr << " PARENT view " << su << " @ " << sf.origin.x << ", " << sf.origin.y
87 << ' ' << sf.size.width << " x " << sf.size.height
92 for (int d = 0; d < depth; d++) {
95 NSRect frame = [view frame];
96 cerr << " view " << view << " @ " << frame.origin.x << ", " << frame.origin.y
97 << ' ' << frame.size.width << " x " << frame.size.height
100 if (depth >= maxdepth) {
103 for (unsigned long i = 0; i < cnt; ++i) {
104 NSView* subview = [subviews objectAtIndex:i];
105 dump_view_tree (subview, depth+1, maxdepth);
109 /* This deeply hacky block of code exists for a rather convoluted reason.
111 * The proximal reason is that there are plugins (such as XLN's Addictive Drums
112 * 2) which redraw their GUI/editor windows using a timer, and use a drawing
113 * technique that on Retina displays ends up calling arg32_image_mark_RGB32, a
114 * function that for some reason (probably byte-swapping or pixel-doubling) is
115 * many times slower than the function used on non-Retina displays.
117 * We are not the first people to discover the problem with
118 * arg32_image_mark_RGB32.
120 * Justin Fraenkel, the lead author of Reaper, wrote a very detailed account of
121 * the performance issues with arg32_image_mark_RGB32 here:
122 * http://www.1014.org/?article=516
124 * The problem was also seen by Robert O'Callahan (lead developer of rr, the
125 * reverse debugger) as far back as 2010:
126 * http://robert.ocallahan.org/2010/05/cglayer-performance-trap-with-isflipped_03.html
128 * In fact, it is so slow that the drawing takes up close to 100% of a single
129 * core, and the event loop that the drawing occurs in never sleeps or "idles".
131 * In AU hosts built directly on top of Cocoa, or some other toolkits, this
132 * isn't inherently a major problem - it just makes the entire GUI of the
135 * However, there is an additional problem for Ardour because GTK+ is built on
136 * top of the GDK/Quartz event loop integration. This integration is rather
137 * baroque, mostly because it was written at a time when CFRunLoop did not
138 * offer a way to wait for "input" from file descriptors (which arrived in OS X
139 * 10.5). As a result, it uses a hair-raising design involving an additional
140 * thread. This design has a major problem, which is that it effectively
141 * creates two nested run loops.
143 * The GTK+/GDK/glib one runs until it has nothing to do, at which time it
144 * calls a function to wait until there is something to do. On Linux or Windows
145 * that would involve some variant or relative of poll(2), which puts the
146 * process to sleep until there is something to do.
148 * On OS X, glib ends up calling [CFRunLoop waitForNextEventMatchingMask] which
149 * will eventually put the process to sleep, but won't do so until the
150 * CFRunLoop also has nothing to do. This includes (at least) a complete redraw
151 * cycle. If redrawing takes too long, and there are timers expired for another
152 * redraw (e.g. Addictive Drums 2, again), then the CFRunLoop will just start
153 * another redraw cycle after processing any events and other stuff.
155 * If the CFRunLoop stays busy, then it will never return to the glib
156 * level at all, thus stopping any further GTK+ level activity (events,
157 * drawing) from taking place. In short, the current (spring 2016) design of
158 * the GDK/Quartz event loop integration relies on the idea that the internal
159 * CFRunLoop will go idle, and totally breaks if this does not happen.
161 * So take a fully functional Ardour, add in XLN's Addictive Drums 2, and a
162 * Retina display, and Apple's ridiculously slow blitting code, and the
163 * CFRunLoop never goes idle. As soon as Addictive Drums starts drawing (over
164 * and over again), the GTK+ event loop stops receiving events and stops
167 * One fix for this was to run a nested GTK+ event loop iteration (or two)
168 * whenever a plugin window was redrawn. This works in the sense that the
169 * immediate issue (no GTK+ events or drawing) is fixed. But the recursive GTK+
170 * event loop causes its own (very subtle) problems too.
172 * This code takes a rather radical approach. We use Objective C's ability to
173 * swizzle object methods. Specifically, we replace [NSView displayIfNeeded]
174 * with our own version which will skip redraws of plugin windows if we tell it
175 * too. If we haven't done that, or if the redraw is of a non-plugin window,
176 * then we invoke the original displayIfNeeded method.
178 * After every 10 redraws of a given plugin GUI/editor window, we queue up a
179 * GTK/glib idle callback to measure the interval between those idle
180 * callbacks. We do this globally across all plugin windows, so if the callback
181 * is already queued, we don't requeue it.
183 * If the interval is longer than 40msec (a 25fps redraw rate), we set
184 * block_plugin_redraws to some number. Each successive call to our interposed
185 * displayIfNeeded method will (a) check this value and if non-zero (b) check
186 * if the call is for a plugin-related NSView/NSWindow. If it is, then we will
187 * skip the redisplay entirely, hopefully avoiding any calls to
188 * argb32_image_mark_RGB32 or any other slow drawing code, and thus allowing
189 * the CFRunLoop to go idle. If the value is zero or the call is for a
190 * non-plugin window, then we just invoke the "original" displayIfNeeded
193 * This hack adds a tiny bit of overhead onto redrawing of the entire
194 * application. But in the common case this consists of 1 conditional (the
195 * check on block_plugin_redraws, which will find it to be zero) and the
196 * invocation of the original method. Given how much work is typically done
197 * during drawing, this seems acceptable.
199 * The correct fix for this is to redesign the relationship between
200 * GTK+/GDK/glib so that a glib run loop is actually a CFRunLoop, with all
201 * GSources represented as CFRunLoopSources, without any nesting and without
202 * any additional thread. This is not a task to be undertaken lightly, and is
203 * certainly substantially more work than this was. It may never be possible to
204 * do that work in a way that could be integrated back into glib, because of
205 * the rather specific semantics and types of GSources, but it would almost
206 * certainly be possible to make it work for Ardour.
209 static IMP original_nsview_drawIfNeeded;
210 static std::vector<id> plugin_views;
211 static uint32_t block_plugin_redraws = 0;
212 static const uint32_t minimum_redraw_rate = 30; /* frames per second */
213 static const uint32_t block_plugin_redraw_count = 15; /* number of combined plugin redraws to block, if blocking */
215 static void add_plugin_view (id view)
217 if (plugin_views.empty()) {
218 AUPluginUI::start_cf_timer ();
221 plugin_views.push_back (view);
225 static void remove_plugin_view (id view)
227 std::vector<id>::iterator x = find (plugin_views.begin(), plugin_views.end(), view);
228 if (x != plugin_views.end()) {
229 plugin_views.erase (x);
231 if (plugin_views.empty()) {
232 AUPluginUI::stop_cf_timer ();
236 static void interposed_drawIfNeeded (id receiver, SEL selector, NSRect rect)
238 if (block_plugin_redraws && (find (plugin_views.begin(), plugin_views.end(), receiver) != plugin_views.end())) {
239 block_plugin_redraws--;
240 #ifdef AU_DEBUG_PRINT
241 std::cerr << "Plugin redraw blocked\n";
243 /* YOU ... SHALL .... NOT ... DRAW!!!! */
246 (void) ((int (*)(id,SEL,NSRect)) original_nsview_drawIfNeeded) (receiver, selector, rect);
249 @implementation NSView (Tracking)
251 static dispatch_once_t once_token;
253 /* this swizzles NSView::displayIfNeeded and replaces it with
254 * interposed_drawIfNeeded(), which allows us to interpose and block
255 * the redrawing of plugin UIs when their redrawing behaviour
256 * is interfering with event loop behaviour.
259 dispatch_once (&once_token, ^{
260 Method target = class_getInstanceMethod ([NSView class], @selector(displayIfNeeded));
261 original_nsview_drawIfNeeded = method_setImplementation (target, (IMP) interposed_drawIfNeeded);
267 /* END OF THE PLUGIN REDRAW HACK */
269 @implementation NotificationObject
271 - (NotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui andCocoaParent: (NSWindow*) cp andTopLevelParent: (NSWindow*) tlp
273 self = [ super init ];
276 plugin_ui = apluginui;
277 top_level_parent = tlp;
282 [[NSNotificationCenter defaultCenter]
284 selector:@selector(cocoaParentActivationHandler:)
285 name:NSWindowDidBecomeMainNotification
288 [[NSNotificationCenter defaultCenter]
290 selector:@selector(cocoaParentBecameKeyHandler:)
291 name:NSWindowDidBecomeKeyNotification
299 - (void)cocoaParentActivationHandler:(NSNotification *)notification
301 NSWindow* notification_window = (NSWindow *)[notification object];
303 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
304 if ([notification_window isMainWindow]) {
305 plugin_ui->activate();
307 plugin_ui->deactivate();
312 - (void)cocoaParentBecameKeyHandler:(NSNotification *)notification
314 NSWindow* notification_window = (NSWindow *)[notification object];
316 if (top_level_parent == notification_window || cocoa_parent == notification_window) {
317 if ([notification_window isKeyWindow]) {
318 plugin_ui->activate();
320 plugin_ui->deactivate();
325 - (void)auViewResized:(NSNotification *)notification
327 (void) notification; // stop complaints about unusued argument
328 plugin_ui->cocoa_view_resized();
333 @implementation LiveResizeNotificationObject
335 - (LiveResizeNotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui
337 self = [ super init ];
339 plugin_ui = apluginui;
345 - (void)windowWillStartLiveResizeHandler:(NSNotification*)notification
347 plugin_ui->start_live_resize ();
350 - (void)windowWillEndLiveResizeHandler:(NSNotification*)notification
352 plugin_ui->end_live_resize ();
356 AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
357 : PlugUIBase (insert)
358 , automation_mode_label (_("Automation"))
359 , preset_label (_("Presets"))
365 , in_live_resize (false)
366 , plugin_requested_resize (0)
371 if (automation_mode_strings.empty()) {
372 automation_mode_strings = I18N (_automation_mode_strings);
375 set_popdown_strings (automation_mode_selector, automation_mode_strings);
376 automation_mode_selector.set_active_text (automation_mode_strings.front());
378 if ((au = boost::dynamic_pointer_cast<AUPlugin> (insert->plugin())) == 0) {
379 error << _("unknown type of editor-supplying plugin (note: no AudioUnit support in this version of ardour)") << endmsg;
380 throw failed_constructor ();
383 /* stuff some stuff into the top of the window */
385 HBox* smaller_hbox = manage (new HBox);
387 smaller_hbox->set_spacing (6);
388 smaller_hbox->pack_start (preset_label, false, false, 4);
389 smaller_hbox->pack_start (_preset_modified, false, false);
390 smaller_hbox->pack_start (_preset_combo, false, false);
391 smaller_hbox->pack_start (add_button, false, false);
393 /* Ardour does not currently allow to overwrite existing presets
394 * see save_property_list() in audio_unit.cc
396 smaller_hbox->pack_start (save_button, false, false);
399 /* one day these might be useful with an AU plugin, but not yet */
400 smaller_hbox->pack_start (automation_mode_label, false, false);
401 smaller_hbox->pack_start (automation_mode_selector, false, false);
403 smaller_hbox->pack_start (reset_button, false, false);
404 smaller_hbox->pack_start (bypass_button, false, true);
406 VBox* v1_box = manage (new VBox);
407 VBox* v2_box = manage (new VBox);
409 v1_box->pack_start (*smaller_hbox, false, true);
410 v2_box->pack_start (focus_button, false, true);
412 top_box.set_homogeneous (false);
413 top_box.set_spacing (6);
414 top_box.set_border_width (6);
416 top_box.pack_end (*v2_box, false, false);
417 top_box.pack_end (*v1_box, false, false);
420 pack_start (top_box, false, false);
421 pack_start (low_box, true, true);
423 preset_label.show ();
424 _preset_combo.show ();
425 automation_mode_label.show ();
426 automation_mode_selector.show ();
427 bypass_button.show ();
435 _activating_from_app = false;
442 /* prefer cocoa, fall back to cocoa, but use carbon if its there */
444 if (test_cocoa_view_support()) {
445 create_cocoa_view ();
447 } else if (test_carbon_view_support()) {
448 create_carbon_view ();
451 create_cocoa_view ();
454 low_box.add_events (Gdk::VISIBILITY_NOTIFY_MASK | Gdk::EXPOSURE_MASK);
456 low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
457 low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
459 low_box.signal_size_request ().connect (mem_fun (this, &AUPluginUI::lower_box_size_request));
460 low_box.signal_size_allocate ().connect (mem_fun (this, &AUPluginUI::lower_box_size_allocate));
461 low_box.signal_map ().connect (mem_fun (this, &AUPluginUI::lower_box_map));
462 low_box.signal_unmap ().connect (mem_fun (this, &AUPluginUI::lower_box_unmap));
466 AUPluginUI::~AUPluginUI ()
469 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
472 if (_resize_notify) {
473 [[NSNotificationCenter defaultCenter] removeObserver:_resize_notify];
476 NSWindow* win = get_nswindow();
478 remove_plugin_view ([[win contentView] superview]);
483 [win removeChildWindow:cocoa_parent];
487 /* not parented, just overlaid on top of our window */
488 DisposeWindow (carbon_window);
493 ArdourCloseComponent (editView);
497 /* remove whatever we packed into low_box so that GTK doesn't
500 [au_view removeFromSuperview];
505 AUPluginUI::test_carbon_view_support ()
510 carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
511 carbon_descriptor.componentSubType = 'gnrc';
512 carbon_descriptor.componentManufacturer = 'appl';
513 carbon_descriptor.componentFlags = 0;
514 carbon_descriptor.componentFlagsMask = 0;
518 // ask the AU for its first editor component
520 err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
522 int nEditors = propertySize / sizeof(ComponentDescription);
523 ComponentDescription *editors = new ComponentDescription[nEditors];
524 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
526 // just pick the first one for now
527 carbon_descriptor = editors[0];
540 AUPluginUI::test_cocoa_view_support ()
543 Boolean isWritable = 0;
544 OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
545 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
546 0, &dataSize, &isWritable);
548 return dataSize > 0 && err == noErr;
552 AUPluginUI::plugin_class_valid (Class pluginClass)
554 if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
555 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
556 [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
564 AUPluginUI::create_cocoa_view ()
566 bool wasAbleToLoadCustomView = false;
567 AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
568 UInt32 numberOfClasses = 0;
571 NSString* factoryClassName = 0;
572 NSURL* CocoaViewBundlePath = NULL;
574 OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
575 kAudioUnitProperty_CocoaUI,
576 kAudioUnitScope_Global,
581 numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
583 // Does view have custom Cocoa UI?
585 if ((result == noErr) && (numberOfClasses > 0) ) {
587 DEBUG_TRACE(DEBUG::AudioUnits,
588 string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
590 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
592 if(AudioUnitGetProperty(*au->get_au(),
593 kAudioUnitProperty_CocoaUI,
594 kAudioUnitScope_Global,
597 &dataSize) == noErr) {
599 CocoaViewBundlePath = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
601 // we only take the first view in this example.
602 factoryClassName = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
604 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
605 [factoryClassName UTF8String], CocoaViewBundlePath));
609 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
611 if (cocoaViewInfo != NULL) {
612 free (cocoaViewInfo);
613 cocoaViewInfo = NULL;
618 // [A] Show custom UI if view has it
620 if (CocoaViewBundlePath && factoryClassName) {
621 NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
623 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
625 if (viewBundle == NULL) {
626 error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
629 Class factoryClass = [viewBundle classNamed:factoryClassName];
630 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
632 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
636 // make sure 'factoryClass' implements the AUCocoaUIBase protocol
637 if (!plugin_class_valid (factoryClass)) {
638 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
642 id factory = [[[factoryClass alloc] init] autorelease];
643 if (factory == NULL) {
644 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
648 DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
651 au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
653 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
656 [CocoaViewBundlePath release];
659 for (i = 0; i < numberOfClasses; i++)
660 CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
662 free (cocoaViewInfo);
664 wasAbleToLoadCustomView = true;
668 if (!wasAbleToLoadCustomView) {
669 // load generic Cocoa view
670 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
672 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
673 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
674 [(AUGenericView *)au_view setShowsExpertParameters:1];
677 // Get the initial size of the new AU View's frame
678 NSRect frame = [au_view frame];
679 req_width = frame.size.width;
680 req_height = frame.size.height;
682 resizable = [au_view autoresizingMask];
684 low_box.queue_resize ();
690 AUPluginUI::update_view_size ()
692 last_au_frame = [au_view frame];
696 AUPluginUI::timer_callback ()
698 block_plugin_redraws = 0;
699 #ifdef AU_DEBUG_PRINT
700 std::cerr << "Resume redraws after idle\n";
706 au_cf_timer_callback (CFRunLoopTimerRef timer, void* info)
708 reinterpret_cast<AUPluginUI*> (info)->cf_timer_callback ();
712 AUPluginUI::cf_timer_callback ()
714 int64_t now = ARDOUR::get_microseconds ();
716 if (!last_timer || block_plugin_redraws) {
721 const int64_t usecs_slop = (1400000 / minimum_redraw_rate); // 140%
723 #ifdef AU_DEBUG_PRINT
724 std::cerr << "Timer elapsed : " << now - last_timer << std::endl;
727 if ((now - last_timer) > (usecs_slop + (1000000/minimum_redraw_rate))) {
728 block_plugin_redraws = block_plugin_redraw_count;
729 timer_connection.disconnect ();
730 timer_connection = Glib::signal_timeout().connect (&AUPluginUI::timer_callback, 40);
731 #ifdef AU_DEBUG_PRINT
732 std::cerr << "Timer too slow, block plugin redraws\n";
740 AUPluginUI::start_cf_timer ()
746 CFTimeInterval interval = 1.0 / (float) minimum_redraw_rate;
748 cf_timer = CFRunLoopTimerCreate (kCFAllocatorDefault,
749 CFAbsoluteTimeGetCurrent() + interval,
751 au_cf_timer_callback,
754 CFRunLoopAddTimer (CFRunLoopGetCurrent(), cf_timer, kCFRunLoopCommonModes);
755 timer_needed = false;
759 AUPluginUI::stop_cf_timer ()
765 CFRunLoopRemoveTimer (CFRunLoopGetCurrent(), cf_timer, kCFRunLoopCommonModes);
771 AUPluginUI::cocoa_view_resized ()
773 /* we can get here for two reasons:
775 1) the plugin window was resized by the user, a new size was
776 allocated to the window, ::update_view_size() was called, and we
777 explicitly/manually resized the AU NSView.
779 2) the plugin decided to resize itself (probably in response to user
780 action, but not in response to an actual window resize)
782 We only want to proceed with a window resizing in the second case.
785 if (in_live_resize) {
786 /* ::update_view_size() will be called at the right times and
787 * will update the view size. We don't need to anything while a
788 * live resize in underway.
793 if (plugin_requested_resize) {
794 /* we tried to change the plugin frame from inside this method
795 * (to adjust the origin), which changes the frame of the AU
796 * NSView, resulting in a reentrant call to the FrameDidChange
797 * handler (this method). Ignore this reentrant call.
799 #ifdef AU_DEBUG_PRINT
800 std::cerr << plugin->name() << " re-entrant call to cocoa_view_resized, ignored\n";
805 plugin_requested_resize = 1;
807 ProcessorWindowProxy* wp = insert->window_proxy();
809 /* Once a plugin has requested a resize of its own window, do
810 * NOT save the window. The user may save state with the plugin
811 * editor expanded to show "extra detail" - the plugin will not
812 * refill this space when the editor is first
813 * instantiated. Leaving the window in the "too big" state
814 * cannot be recovered from.
816 * The window will be sized to fit the plugin's own request. Done.
818 wp->set_state_mask (WindowProxy::Position);
821 NSRect new_frame = [au_view frame];
823 /* from here on, we know that we've been called because the plugin
824 * decided to change the NSView frame itself.
827 /* step one: compute the change in the frame size.
830 float dy = new_frame.size.height - last_au_frame.size.height;
831 float dx = new_frame.size.width - last_au_frame.size.width;
833 NSWindow* window = get_nswindow ();
834 NSRect windowFrame= [window frame];
836 /* we want the top edge of the window to remain in the same place,
837 but the Cocoa/Quartz origin is at the lower left. So, when we make
838 the window larger, we will move it down, which means shifting the
839 origin toward (x,0). This will leave the top edge in the same place.
842 windowFrame.origin.y -= dy;
843 windowFrame.origin.x -= dx;
844 windowFrame.size.height += dy;
845 windowFrame.size.width += dx;
847 NSUInteger old_auto_resize = [au_view autoresizingMask];
849 /* Some stupid AU Views change the origin of the original AU View when
850 they are resized (I'm looking at you AUSampler). If the origin has
851 been moved, move it back.
854 if (last_au_frame.origin.x != new_frame.origin.x ||
855 last_au_frame.origin.y != new_frame.origin.y) {
856 new_frame.origin = last_au_frame.origin;
857 [au_view setFrame:new_frame];
858 /* also be sure to redraw the topbox because this can
861 top_box.queue_draw ();
864 /* We resize the window using Cocoa. We can't use GTK mechanisms
867 * http://www.lists.apple.com/archives/coreaudio-api/2005/Aug/msg00245.html
869 * "The host needs to be aware that changing the size of the window in
870 * response to the NSViewFrameDidChangeNotification can cause the view
871 * size to change depending on the autoresizing mask of the view. The
872 * host may need to cache the autoresizing mask of the view, set it to
873 * NSViewNotSizable, resize the window, and then reset the autoresizing
874 * mask of the view once the window has been sized."
878 [au_view setAutoresizingMask:NSViewNotSizable];
879 [window setFrame:windowFrame display:1];
880 [au_view setAutoresizingMask:old_auto_resize];
882 /* keep a copy of the size of the AU NSView. We didn't set it - the plugin did */
883 last_au_frame = new_frame;
884 req_width = new_frame.size.width;
885 req_height = new_frame.size.height;
887 plugin_requested_resize = 0;
891 AUPluginUI::create_carbon_view ()
895 ControlRef root_control;
897 Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
899 OpenAComponent(editComponent, &editView);
901 error << _("AU Carbon view: cannot open AU Component") << endmsg;
905 Rect r = { 100, 100, 100, 100 };
906 WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
907 kWindowCompositingAttribute|
908 kWindowNoShadowAttribute|
909 kWindowNoTitleBarAttribute);
911 if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
912 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
913 ArdourCloseComponent (editView);
917 if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
918 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
919 DisposeWindow (carbon_window);
920 ArdourCloseComponent (editView);
925 Float32Point location = { 0.0, 0.0 };
926 Float32Point size = { 0.0, 0.0 } ;
928 if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
929 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
930 DisposeWindow (carbon_window);
931 ArdourCloseComponent (editView);
938 GetControlBounds(viewPane, &bounds);
939 size.x = bounds.right-bounds.left;
940 size.y = bounds.bottom-bounds.top;
942 req_width = (int) (size.x + 0.5);
943 req_height = (int) (size.y + 0.5);
945 SizeWindow (carbon_window, req_width, req_height, true);
946 low_box.set_size_request (req_width, req_height);
950 error << _("AU Carbon GUI is not supported.") << endmsg;
956 AUPluginUI::get_nswindow ()
958 Gtk::Container* toplevel = get_toplevel();
960 if (!toplevel || !toplevel->is_toplevel()) {
961 error << _("AUPluginUI: no top level window!") << endmsg;
965 NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
968 error << _("AUPluginUI: no top level window!") << endmsg;
976 AUPluginUI::activate ()
979 ActivateWindow (carbon_window, TRUE);
984 AUPluginUI::deactivate ()
987 ActivateWindow (carbon_window, FALSE);
992 AUPluginUI::parent_carbon_window ()
995 NSWindow* win = get_nswindow ();
996 Rect windowStructureBoundsRect;
1002 /* figure out where the cocoa parent window is in carbon-coordinate space, which
1003 differs from both cocoa-coordinate space and GTK-coordinate space
1006 GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
1008 /* compute how tall the title bar is, because we have to offset the position of the carbon window
1012 NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
1013 NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
1015 int titlebar_height = wm_frame.size.height - content_frame.size.height;
1017 int packing_extra = 6; // this is the total vertical packing in our top level window
1019 /* move into position, based on parent window position */
1020 MoveWindow (carbon_window,
1021 windowStructureBoundsRect.left,
1022 windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
1024 ShowWindow (carbon_window);
1026 // create the cocoa window for the carbon one and make it visible
1027 cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
1029 SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
1031 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
1033 [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
1034 [win setAutodisplay:1]; // turn of GTK stuff for this window
1043 AUPluginUI::parent_cocoa_window ()
1045 NSWindow* win = get_nswindow ();
1051 //[win setAutodisplay:1]; // turn off GTK stuff for this window
1053 NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
1054 [view addSubview:au_view];
1055 /* despite the fact that the documentation says that [NSWindow
1056 contentView] is the highest "accessible" NSView in an NSWindow, when
1057 the redraw cycle is executed, displayIfNeeded is actually executed
1058 on the parent of the contentView. To provide a marginal speedup when
1059 checking if a given redraw is for a plugin, use this "hidden" NSView
1060 to identify the plugin, so that we do not have to call [superview]
1061 every time in interposed_drawIfNeeded().
1063 add_plugin_view ([[win contentView] superview]);
1065 /* this moves the AU NSView down and over to provide a left-hand margin
1066 * and to clear the Ardour "task bar" (with plugin preset mgmt buttons,
1067 * keyboard focus control, bypass etc).
1071 gtk_widget_translate_coordinates(
1072 GTK_WIDGET(low_box.gobj()),
1073 GTK_WIDGET(low_box.get_parent()->gobj()),
1075 [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
1077 last_au_frame = [au_view frame];
1078 // watch for size changes of the view
1079 _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
1081 [[NSNotificationCenter defaultCenter] addObserver:_notify
1082 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
1085 // catch notifications that live resizing is about to start
1087 #if HAVE_COCOA_LIVE_RESIZING
1088 _resize_notify = [ [ LiveResizeNotificationObject alloc] initWithPluginUI:this ];
1090 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1091 selector:@selector(windowWillStartLiveResizeHandler:) name:NSWindowWillStartLiveResizeNotification
1094 [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
1095 selector:@selector(windowWillEndLiveResizeHandler:) name:NSWindowDidEndLiveResizeNotification
1098 /* No way before 10.6 to identify the start of a live resize (drag
1099 * resize) without subclassing NSView and overriding two of its
1100 * methods. Instead of that, we make the window non-resizable, thus
1101 * ending confusion about whether or not resizes are plugin or user
1102 * driven (they are always plugin-driven).
1105 Gtk::Container* toplevel = get_toplevel();
1110 if (toplevel && toplevel->is_toplevel()) {
1111 toplevel->size_request (req);
1112 toplevel->set_size_request (req.width, req.height);
1113 dynamic_cast<Gtk::Window*>(toplevel)->set_resizable (false);
1121 AUPluginUI::grab_focus()
1124 [au_view becomeFirstResponder];
1128 AUPluginUI::forward_key_event (GdkEventKey* ev)
1130 NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
1132 if (au_view && nsevent) {
1134 /* filter on nsevent type here because GDK massages FlagsChanged
1135 messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
1136 handle a FlagsChanged message as a keyDown or keyUp
1139 if ([nsevent type] == NSKeyDown) {
1140 [[[au_view window] firstResponder] keyDown:nsevent];
1141 } else if ([nsevent type] == NSKeyUp) {
1142 [[[au_view window] firstResponder] keyUp:nsevent];
1143 } else if ([nsevent type] == NSFlagsChanged) {
1144 [[[au_view window] firstResponder] flagsChanged:nsevent];
1150 AUPluginUI::on_realize ()
1152 VBox::on_realize ();
1154 /* our windows should not have that resize indicator */
1156 NSWindow* win = get_nswindow ();
1158 [win setShowsResizeIndicator:0];
1163 AUPluginUI::lower_box_realized ()
1166 parent_cocoa_window ();
1167 } else if (carbon_window) {
1168 parent_carbon_window ();
1173 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
1176 if (carbon_window && ev->state != GDK_VISIBILITY_UNOBSCURED) {
1177 ShowWindow (carbon_window);
1178 ActivateWindow (carbon_window, TRUE);
1186 AUPluginUI::lower_box_map ()
1188 [au_view setHidden:0];
1189 update_view_size ();
1193 AUPluginUI::lower_box_unmap ()
1195 [au_view setHidden:1];
1199 AUPluginUI::lower_box_size_request (GtkRequisition* requisition)
1201 requisition->width = req_width;
1202 requisition->height = req_height;
1206 AUPluginUI::lower_box_size_allocate (Gtk::Allocation& allocation)
1208 update_view_size ();
1212 AUPluginUI::on_window_hide ()
1215 if (carbon_window) {
1216 HideWindow (carbon_window);
1217 ActivateWindow (carbon_window, FALSE);
1223 NSArray* wins = [NSApp windows];
1224 for (uint32_t i = 0; i < [wins count]; i++) {
1225 id win = [wins objectAtIndex:i];
1231 AUPluginUI::on_window_show (const string& /*title*/)
1233 /* this is idempotent so just call it every time we show the window */
1235 gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
1240 if (carbon_window) {
1241 ShowWindow (carbon_window);
1242 ActivateWindow (carbon_window, TRUE);
1250 AUPluginUI::start_updating (GdkEventAny*)
1256 AUPluginUI::stop_updating (GdkEventAny*)
1262 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
1264 AUPluginUI* aup = new AUPluginUI (plugin_insert);
1270 AUPluginUI::start_live_resize ()
1272 in_live_resize = true;
1276 AUPluginUI::end_live_resize ()
1278 in_live_resize = false;