change ordering of origin-move and window-resize for AU plugins
[ardour.git] / gtk2_ardour / au_pluginui.mm
1 #undef  Marker
2 #define Marker FuckYouAppleAndYourLackOfNameSpaces
3
4 #include <gtkmm/button.h>
5 #include <gdk/gdkquartz.h>
6
7 #include "pbd/convert.h"
8 #include "pbd/error.h"
9
10 #include "ardour/audio_unit.h"
11 #include "ardour/debug.h"
12 #include "ardour/plugin_insert.h"
13
14 #undef check // stupid gtk, stupid apple
15
16 #include <gtkmm2ext/utils.h>
17 #include <gtkmm2ext/window_proxy.h>
18
19 #include "au_pluginui.h"
20 #include "gui_thread.h"
21 #include "processor_box.h"
22
23 #include "CAAudioUnit.h"
24 #include "CAComponent.h"
25
26 #import <AudioUnit/AUCocoaUIView.h>
27 #import <CoreAudioKit/AUGenericView.h>
28
29 #undef Marker
30
31 #include "keyboard.h"
32 #include "utils.h"
33 #include "public_editor.h"
34 #include "i18n.h"
35
36 #ifdef COREAUDIO105
37 #define ArdourCloseComponent CloseComponent
38 #else
39 #define ArdourCloseComponent AudioComponentInstanceDispose
40 #endif
41 using namespace ARDOUR;
42 using namespace Gtk;
43 using namespace Gtkmm2ext;
44 using namespace std;
45 using namespace PBD;
46
47 vector<string> AUPluginUI::automation_mode_strings;
48
49 static const gchar* _automation_mode_strings[] = {
50         X_("Manual"),
51         X_("Play"),
52         X_("Write"),
53         X_("Touch"),
54         0
55 };
56
57 static void
58 dump_view_tree (NSView* view, int depth, int maxdepth)
59 {
60         NSArray* subviews = [view subviews];
61         unsigned long cnt = [subviews count];
62
63         for (int d = 0; d < depth; d++) {
64                 cerr << '\t';
65         }
66         NSRect frame = [view frame];
67         cerr << " view @ " <<  frame.origin.x << ", " << frame.origin.y
68                 << ' ' << frame.size.width << " x " << frame.size.height
69                 << endl;
70
71         if (depth >= maxdepth) {
72                 return;
73         }
74         for (unsigned long i = 0; i < cnt; ++i) {
75                 NSView* subview = [subviews objectAtIndex:i];
76                 dump_view_tree (subview, depth+1, maxdepth);
77         }
78 }
79
80 @implementation NotificationObject
81
82 - (NotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui andCocoaParent: (NSWindow*) cp andTopLevelParent: (NSWindow*) tlp
83 {
84         self = [ super init ];
85
86         if (self) {
87                 plugin_ui = apluginui;
88                 top_level_parent = tlp;
89
90                 if (cp) {
91                         cocoa_parent = cp;
92
93                         [[NSNotificationCenter defaultCenter]
94                              addObserver:self
95                                 selector:@selector(cocoaParentActivationHandler:)
96                                     name:NSWindowDidBecomeMainNotification
97                                   object:NULL];
98
99                         [[NSNotificationCenter defaultCenter]
100                              addObserver:self
101                                 selector:@selector(cocoaParentBecameKeyHandler:)
102                                     name:NSWindowDidBecomeKeyNotification
103                                   object:NULL];
104                 }
105         }
106
107         return self;
108 }
109
110 - (void)cocoaParentActivationHandler:(NSNotification *)notification
111 {
112         NSWindow* notification_window = (NSWindow *)[notification object];
113
114         if (top_level_parent == notification_window || cocoa_parent == notification_window) {
115                 if ([notification_window isMainWindow]) {
116                         plugin_ui->activate();
117                 } else {
118                         plugin_ui->deactivate();
119                 }
120         }
121 }
122
123 - (void)cocoaParentBecameKeyHandler:(NSNotification *)notification
124 {
125         NSWindow* notification_window = (NSWindow *)[notification object];
126
127         if (top_level_parent == notification_window || cocoa_parent == notification_window) {
128                 if ([notification_window isKeyWindow]) {
129                         plugin_ui->activate();
130                 } else {
131                         plugin_ui->deactivate();
132                 }
133         }
134 }
135
136 - (void)auViewResized:(NSNotification *)notification
137 {
138         (void) notification; // stop complaints about unusued argument
139         plugin_ui->cocoa_view_resized();
140 }
141
142 @end
143
144 @implementation LiveResizeNotificationObject
145
146 - (LiveResizeNotificationObject*) initWithPluginUI: (AUPluginUI*) apluginui
147 {
148         self = [ super init ];
149         if (self) {
150                 plugin_ui = apluginui;
151         }
152
153         return self;
154 }
155
156 - (void)windowWillStartLiveResizeHandler:(NSNotification*)notification
157 {
158         plugin_ui->start_live_resize ();
159 }
160
161 - (void)windowWillEndLiveResizeHandler:(NSNotification*)notification
162 {
163         plugin_ui->end_live_resize ();
164 }
165 @end
166
167 AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
168         : PlugUIBase (insert)
169         , automation_mode_label (_("Automation"))
170         , preset_label (_("Presets"))
171         , mapped (false)
172         , resizable (false)
173         , min_width (0)
174         , min_height (0)
175         , req_width (0)
176         , req_height (0)
177         , alo_width (0)
178         , alo_height (0)
179         , cocoa_window (0)
180         , au_view (0)
181         , in_live_resize (false)
182         , plugin_requested_resize (0)
183         , cocoa_parent (0)
184         , _notify (0)
185         , _resize_notify (0)
186
187 {
188         if (automation_mode_strings.empty()) {
189                 automation_mode_strings = I18N (_automation_mode_strings);
190         }
191
192         set_popdown_strings (automation_mode_selector, automation_mode_strings);
193         automation_mode_selector.set_active_text (automation_mode_strings.front());
194
195         if ((au = boost::dynamic_pointer_cast<AUPlugin> (insert->plugin())) == 0) {
196                 error << _("unknown type of editor-supplying plugin (note: no AudioUnit support in this version of ardour)") << endmsg;
197                 throw failed_constructor ();
198         }
199
200         /* stuff some stuff into the top of the window */
201
202         HBox* smaller_hbox = manage (new HBox);
203
204         smaller_hbox->set_spacing (6);
205         smaller_hbox->pack_start (preset_label, false, false, 4);
206         smaller_hbox->pack_start (_preset_modified, false, false);
207         smaller_hbox->pack_start (_preset_combo, false, false);
208         smaller_hbox->pack_start (add_button, false, false);
209 #if 0
210         /* Ardour does not currently allow to overwrite existing presets
211          * see save_property_list() in audio_unit.cc
212          */
213         smaller_hbox->pack_start (save_button, false, false);
214 #endif
215 #if 0
216         /* one day these might be useful with an AU plugin, but not yet */
217         smaller_hbox->pack_start (automation_mode_label, false, false);
218         smaller_hbox->pack_start (automation_mode_selector, false, false);
219 #endif
220         smaller_hbox->pack_start (reset_button, false, false);
221         smaller_hbox->pack_start (bypass_button, false, true);
222
223         VBox* v1_box = manage (new VBox);
224         VBox* v2_box = manage (new VBox);
225
226         v1_box->pack_start (*smaller_hbox, false, true);
227         v2_box->pack_start (focus_button, false, true);
228
229         top_box.set_homogeneous (false);
230         top_box.set_spacing (6);
231         top_box.set_border_width (6);
232
233         top_box.pack_end (*v2_box, false, false);
234         top_box.pack_end (*v1_box, false, false);
235
236         set_spacing (6);
237         pack_start (top_box, false, false);
238         pack_start (low_box, true, true);
239
240         preset_label.show ();
241         _preset_combo.show ();
242         automation_mode_label.show ();
243         automation_mode_selector.show ();
244         bypass_button.show ();
245         top_box.show ();
246         low_box.show ();
247
248         cocoa_parent = 0;
249         cocoa_window = 0;
250
251 #ifdef WITH_CARBON
252         _activating_from_app = false;
253         _notify = 0;
254         au_view = 0;
255         editView = 0;
256         carbon_window = 0;
257 #endif
258
259         /* prefer cocoa, fall back to cocoa, but use carbon if its there */
260
261         if (test_cocoa_view_support()) {
262                 create_cocoa_view ();
263 #ifdef WITH_CARBON
264         } else if (test_carbon_view_support()) {
265                 create_carbon_view ();
266 #endif
267         } else {
268                 create_cocoa_view ();
269         }
270
271         low_box.add_events (Gdk::VISIBILITY_NOTIFY_MASK | Gdk::EXPOSURE_MASK);
272
273         low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
274         low_box.signal_visibility_notify_event ().connect (mem_fun (this, &AUPluginUI::lower_box_visibility_notify));
275         if (au_view) {
276                 low_box.signal_size_request ().connect (mem_fun (this, &AUPluginUI::lower_box_size_request));
277                 low_box.signal_size_allocate ().connect (mem_fun (this, &AUPluginUI::lower_box_size_allocate));
278                 low_box.signal_map ().connect (mem_fun (this, &AUPluginUI::lower_box_map));
279                 low_box.signal_unmap ().connect (mem_fun (this, &AUPluginUI::lower_box_unmap));
280                 low_box.signal_expose_event ().connect (mem_fun (this, &AUPluginUI::lower_box_expose));
281         }
282 }
283
284 AUPluginUI::~AUPluginUI ()
285 {
286         if (_notify) {
287                 [[NSNotificationCenter defaultCenter] removeObserver:_notify];
288         }
289
290         if (_resize_notify) {
291                 [[NSNotificationCenter defaultCenter] removeObserver:_resize_notify];
292         }
293
294         if (cocoa_parent) {
295                 NSWindow* win = get_nswindow();
296                 [win removeChildWindow:cocoa_parent];
297         }
298
299 #ifdef WITH_CARBON
300         if (carbon_window) {
301                 /* not parented, just overlaid on top of our window */
302                 DisposeWindow (carbon_window);
303         }
304 #endif
305
306         if (editView) {
307                 ArdourCloseComponent (editView);
308         }
309
310         if (au_view) {
311                 /* remove whatever we packed into low_box so that GTK doesn't
312                    mess with it.
313                  */
314
315                 [au_view removeFromSuperview];
316         }
317 }
318
319 bool
320 AUPluginUI::test_carbon_view_support ()
321 {
322 #ifdef WITH_CARBON
323         bool ret = false;
324
325         carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
326         carbon_descriptor.componentSubType = 'gnrc';
327         carbon_descriptor.componentManufacturer = 'appl';
328         carbon_descriptor.componentFlags = 0;
329         carbon_descriptor.componentFlagsMask = 0;
330
331         OSStatus err;
332
333         // ask the AU for its first editor component
334         UInt32 propertySize;
335         err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
336         if (!err) {
337                 int nEditors = propertySize / sizeof(ComponentDescription);
338                 ComponentDescription *editors = new ComponentDescription[nEditors];
339                 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
340                 if (!err) {
341                         // just pick the first one for now
342                         carbon_descriptor = editors[0];
343                         ret = true;
344                 }
345                 delete[] editors;
346         }
347
348         return ret;
349 #else
350         return false;
351 #endif
352 }
353
354 bool
355 AUPluginUI::test_cocoa_view_support ()
356 {
357         UInt32 dataSize   = 0;
358         Boolean isWritable = 0;
359         OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
360                                                 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
361                                                 0, &dataSize, &isWritable);
362
363         return dataSize > 0 && err == noErr;
364 }
365
366 bool
367 AUPluginUI::plugin_class_valid (Class pluginClass)
368 {
369         if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
370                 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
371                    [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
372                                 return true;
373                 }
374         }
375         return false;
376 }
377
378 int
379 AUPluginUI::create_cocoa_view ()
380 {
381         bool wasAbleToLoadCustomView = false;
382         AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
383         UInt32               numberOfClasses = 0;
384         UInt32     dataSize;
385         Boolean    isWritable;
386         NSString*           factoryClassName = 0;
387         NSURL*              CocoaViewBundlePath = NULL;
388
389         OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
390                                                     kAudioUnitProperty_CocoaUI,
391                                                     kAudioUnitScope_Global,
392                                                     0,
393                                                     &dataSize,
394                                                     &isWritable );
395
396         numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
397
398         // Does view have custom Cocoa UI?
399
400         if ((result == noErr) && (numberOfClasses > 0) ) {
401
402                 DEBUG_TRACE(DEBUG::AudioUnits,
403                             string_compose ( "based on %1, there are %2 cocoa UI classes\n", dataSize, numberOfClasses));
404
405                 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
406
407                 if(AudioUnitGetProperty(*au->get_au(),
408                                         kAudioUnitProperty_CocoaUI,
409                                         kAudioUnitScope_Global,
410                                         0,
411                                         cocoaViewInfo,
412                                         &dataSize) == noErr) {
413
414                         CocoaViewBundlePath     = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
415
416                         // we only take the first view in this example.
417                         factoryClassName        = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
418
419                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("the factory name is %1 bundle is %2\n",
420                                                                         [factoryClassName UTF8String], CocoaViewBundlePath));
421
422                 } else {
423
424                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("No cocoaUI property cocoaViewInfo = %1\n", cocoaViewInfo));
425
426                         if (cocoaViewInfo != NULL) {
427                                 free (cocoaViewInfo);
428                                 cocoaViewInfo = NULL;
429                         }
430                 }
431         }
432
433         // [A] Show custom UI if view has it
434
435         if (CocoaViewBundlePath && factoryClassName) {
436                 NSBundle *viewBundle    = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
437
438                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create bundle, result = %1\n", viewBundle));
439
440                 if (viewBundle == NULL) {
441                         error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
442                         return -1;
443                 } else {
444                         Class factoryClass = [viewBundle classNamed:factoryClassName];
445                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("tried to create factory class, result = %1\n", factoryClass));
446                         if (!factoryClass) {
447                                 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
448                                 return -1;
449                         }
450
451                         // make sure 'factoryClass' implements the AUCocoaUIBase protocol
452                         if (!plugin_class_valid (factoryClass)) {
453                                 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
454                                 return -1;
455                         }
456                         // make a factory
457                         id factory = [[[factoryClass alloc] init] autorelease];
458                         if (factory == NULL) {
459                                 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
460                                 return -1;
461                         }
462
463                         DEBUG_TRACE (DEBUG::AudioUnits, "got a factory instance\n");
464
465                         // make a view
466                         au_view = [factory uiViewForAudioUnit:*au->get_au() withSize:NSZeroSize];
467
468                         DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
469
470                         // cleanup
471                         [CocoaViewBundlePath release];
472                         if (cocoaViewInfo) {
473                                 UInt32 i;
474                                 for (i = 0; i < numberOfClasses; i++)
475                                         CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
476
477                                 free (cocoaViewInfo);
478                         }
479                         wasAbleToLoadCustomView = true;
480                 }
481         }
482
483         if (!wasAbleToLoadCustomView) {
484                 // load generic Cocoa view
485                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("Loading generic view using %1 -> %2\n", au,
486                                                                 au->get_au()));
487                 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
488                 DEBUG_TRACE (DEBUG::AudioUnits, string_compose ("view created @ %1\n", au_view));
489                 [(AUGenericView *)au_view setShowsExpertParameters:1];
490         }
491
492         // Get the initial size of the new AU View's frame
493         NSRect  frame = [au_view frame];
494         min_width  = req_width  = frame.size.width;
495         min_height = req_height = frame.size.height;
496
497         resizable  = [au_view autoresizingMask];
498         std::cerr << plugin->name() << " initial frame = " << [NSStringFromRect (frame) UTF8String] << " resizable ? " << resizable << std::endl;
499
500         low_box.queue_resize ();
501
502         return 0;
503 }
504
505 void
506 AUPluginUI::update_view_size ()
507 {
508         last_au_frame = [au_view frame];
509 }
510
511 void
512 AUPluginUI::cocoa_view_resized ()
513 {
514         /* we can get here for two reasons:
515
516            1) the plugin window was resized by the user, a new size was
517            allocated to the window, ::update_view_size() was called, and we
518            explicitly/manually resized the AU NSView.
519
520            2) the plugin decided to resize itself (probably in response to user
521            action, but not in response to an actual window resize)
522
523            We only want to proceed with a window resizing in the second case.
524         */
525
526         if (in_live_resize) {
527                 /* ::update_view_size() will be called at the right times and
528                  * will update the view size. We don't need to anything while a
529                  * live resize in underway.
530                  */
531                 return;
532         }
533
534         if (plugin_requested_resize) {
535                 /* we tried to change the plugin frame from inside this method
536                  * (to adjust the origin), and the plugin changed its size
537                  * again. Ignore this second call.
538                  */
539                 std::cerr << plugin->name() << " re-entrant call to cocoa_view_resized, ignored\n";
540                 return;
541         }
542
543         plugin_requested_resize = 1;
544
545         ProcessorWindowProxy* wp = insert->window_proxy();
546         if (wp) {
547                 /* Once a plugin has requested a resize of its own window, do
548                  * NOT save the window. The user may save state with the plugin
549                  * editor expanded to show "extra detail" - the plugin will not
550                  * refill this space when the editor is first
551                  * instantiated. Leaving the window in the "too big" state
552                  * cannot be recovered from.
553                  *
554                  * The window will be sized to fit the plugin's own request. Done.
555                  */
556                 wp->set_state_mask (WindowProxy::Position);
557         }
558
559         NSRect new_frame = [au_view frame];
560
561         std::cerr << "Plugin " << plugin->name() << " requested update (prs now = " << plugin_requested_resize << ")\n";
562         std::cerr << "\tAU NSView frame : " << [ NSStringFromRect (new_frame) UTF8String] << std::endl;
563         std::cerr << "\tlast au frame : " << [ NSStringFromRect (last_au_frame) UTF8String] << std::endl;
564
565         /* from here on, we know that we've been called because the plugin
566          * decided to change the NSView frame itself.
567          */
568
569         /* step one: compute the change in the frame size.
570          */
571
572         float dy = new_frame.size.height - last_au_frame.size.height;
573         float dx = new_frame.size.width - last_au_frame.size.width;
574
575         NSWindow* window = get_nswindow ();
576         NSRect windowFrame= [window frame];
577
578         /* we want the top edge of the window to remain in the same place,
579            but the Cocoa/Quartz origin is at the lower left. So, when we make
580            the window larger, we will move it down, which means shifting the
581            origin toward (x,0). This will leave the top edge in the same place.
582         */
583
584         windowFrame.origin.y    -= dy;
585         windowFrame.origin.x    -= dx;
586         windowFrame.size.height += dy;
587         windowFrame.size.width  += dx;
588
589         std::cerr << "\tChange size by " << dx << " x " << dy << std::endl;
590
591         NSUInteger old_auto_resize = [au_view autoresizingMask];
592
593         /* Stop the AU NSView from resizing itself *again* in response to
594            us changing the window size.
595         */
596
597
598         [au_view setAutoresizingMask:NSViewNotSizable];
599
600         /* Some stupid AU Views change the origin of the original AU View when
601            they are resized (I'm looking at you AUSampler). If the origin has
602            been moved, move it back.
603         */
604
605         if (last_au_frame.origin.x != new_frame.origin.x ||
606             last_au_frame.origin.y != new_frame.origin.y) {
607                 new_frame.origin = last_au_frame.origin;
608                 std::cerr << "Move AU NSView origin back to "
609                           << new_frame.origin.x << ", " << new_frame.origin.y
610                           << std::endl;
611                 [au_view setFrame:new_frame];
612                 /* also be sure to redraw the topbox because this can
613                    also go wrong.
614                  */
615                 top_box.queue_draw ();
616         } else {
617                 std::cerr << "No need to move origin, last au origin " << [NSStringFromPoint(last_au_frame.origin) UTF8String]
618                           << " == new au origin " << [NSStringFromPoint(new_frame.origin) UTF8String]
619                           << std::endl;
620         }
621
622         /* this resizes the window. it will eventually trigger a new
623          * size_allocate event/callback, and we'll end up in
624          * ::update_view_size(). We want to stop that from doing anything,
625          * because we've already resized the window to fit the new new view,
626          * so there's no need to actually update the view size again.
627          */
628
629         [window setFrame:windowFrame display:1];
630
631         [au_view setAutoresizingMask:old_auto_resize];
632
633         /* keep a copy of the size of the AU NSView. We didn't set - the plugin did */
634         last_au_frame = new_frame;
635         min_width  = req_width  = new_frame.size.width;
636         min_height = req_height = new_frame.size.height;
637
638         plugin_requested_resize = 0;
639 }
640
641 int
642 AUPluginUI::create_carbon_view ()
643 {
644 #ifdef WITH_CARBON
645         OSStatus err;
646         ControlRef root_control;
647
648         Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
649
650         OpenAComponent(editComponent, &editView);
651         if (!editView) {
652                 error << _("AU Carbon view: cannot open AU Component") << endmsg;
653                 return -1;
654         }
655
656         Rect r = { 100, 100, 100, 100 };
657         WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
658                                                   kWindowCompositingAttribute|
659                                                   kWindowNoShadowAttribute|
660                                                   kWindowNoTitleBarAttribute);
661
662         if ((err = CreateNewWindow(kUtilityWindowClass, attr, &r, &carbon_window)) != noErr) {
663                 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
664                 ArdourCloseComponent (editView);
665                 return -1;
666         }
667
668         if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
669                 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
670                 DisposeWindow (carbon_window);
671                 ArdourCloseComponent (editView);
672                 return -1;
673         }
674
675         ControlRef viewPane;
676         Float32Point location  = { 0.0, 0.0 };
677         Float32Point size = { 0.0, 0.0 } ;
678
679         if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
680                 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
681                 DisposeWindow (carbon_window);
682                 ArdourCloseComponent (editView);
683                 return -1;
684         }
685
686         // resize window
687
688         Rect bounds;
689         GetControlBounds(viewPane, &bounds);
690         size.x = bounds.right-bounds.left;
691         size.y = bounds.bottom-bounds.top;
692
693         req_width = (int) (size.x + 0.5);
694         req_height = (int) (size.y + 0.5);
695
696         SizeWindow (carbon_window, req_width, req_height,  true);
697         low_box.set_size_request (req_width, req_height);
698
699         return 0;
700 #else
701         error << _("AU Carbon GUI is not supported.") << endmsg;
702         return -1;
703 #endif
704 }
705
706 NSWindow*
707 AUPluginUI::get_nswindow ()
708 {
709         Gtk::Container* toplevel = get_toplevel();
710
711         if (!toplevel || !toplevel->is_toplevel()) {
712                 error << _("AUPluginUI: no top level window!") << endmsg;
713                 return 0;
714         }
715
716         NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
717
718         if (!true_parent) {
719                 error << _("AUPluginUI: no top level window!") << endmsg;
720                 return 0;
721         }
722
723         return true_parent;
724 }
725
726 void
727 AUPluginUI::activate ()
728 {
729 #ifdef WITH_CARBON
730         ActivateWindow (carbon_window, TRUE);
731 #endif
732 }
733
734 void
735 AUPluginUI::deactivate ()
736 {
737 #ifdef WITH_CARBON
738         ActivateWindow (carbon_window, FALSE);
739 #endif
740 }
741
742 int
743 AUPluginUI::parent_carbon_window ()
744 {
745 #ifdef WITH_CARBON
746         NSWindow* win = get_nswindow ();
747         Rect windowStructureBoundsRect;
748
749         if (!win) {
750                 return -1;
751         }
752
753         /* figure out where the cocoa parent window is in carbon-coordinate space, which
754            differs from both cocoa-coordinate space and GTK-coordinate space
755         */
756
757         GetWindowBounds((WindowRef) [win windowRef], kWindowStructureRgn, &windowStructureBoundsRect);
758
759         /* compute how tall the title bar is, because we have to offset the position of the carbon window
760            by that much.
761         */
762
763         NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
764         NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
765
766         int titlebar_height = wm_frame.size.height - content_frame.size.height;
767
768         int packing_extra = 6; // this is the total vertical packing in our top level window
769
770         /* move into position, based on parent window position */
771         MoveWindow (carbon_window,
772                     windowStructureBoundsRect.left,
773                     windowStructureBoundsRect.top + titlebar_height + top_box.get_height() + packing_extra,
774                     false);
775         ShowWindow (carbon_window);
776
777         // create the cocoa window for the carbon one and make it visible
778         cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
779
780         SetWindowActivationScope (carbon_window, kWindowActivationScopeNone);
781
782         _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:cocoa_parent andTopLevelParent:win ];
783
784         [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
785         [win setAutodisplay:1]; // turn of GTK stuff for this window
786
787         return 0;
788 #else
789         return -1;
790 #endif
791 }
792
793 int
794 AUPluginUI::parent_cocoa_window ()
795 {
796         NSWindow* win = get_nswindow ();
797
798         if (!win) {
799                 return -1;
800         }
801
802         //[win setAutodisplay:1]; // turn off GTK stuff for this window
803
804         NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
805         [view addSubview:au_view];
806
807         /* this moves the AU NSView down and over to provide a left-hand margin
808          * and to clear the Ardour "task bar" (with plugin preset mgmt buttons,
809          * keyboard focus control, bypass etc).
810          */
811
812         gint xx, yy;
813         gtk_widget_translate_coordinates(
814                         GTK_WIDGET(low_box.gobj()),
815                         GTK_WIDGET(low_box.get_parent()->gobj()),
816                         8, 6, &xx, &yy);
817         [au_view setFrame:NSMakeRect(xx, yy, req_width, req_height)];
818
819         last_au_frame = [au_view frame];
820         // watch for size changes of the view
821         _notify = [ [NotificationObject alloc] initWithPluginUI:this andCocoaParent:NULL andTopLevelParent:win ];
822
823         [[NSNotificationCenter defaultCenter] addObserver:_notify
824                 selector:@selector(auViewResized:) name:NSViewFrameDidChangeNotification
825                 object:au_view];
826
827         // catch notifications that live resizing is about to start
828
829         _resize_notify = [ [ LiveResizeNotificationObject alloc] initWithPluginUI:this ];
830
831         [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
832                 selector:@selector(windowWillStartLiveResizeHandler:) name:NSWindowWillStartLiveResizeNotification
833                 object:win];
834
835         [[NSNotificationCenter defaultCenter] addObserver:_resize_notify
836                 selector:@selector(windowWillEndLiveResizeHandler:) name:NSWindowDidEndLiveResizeNotification
837                 object:win];
838
839         return 0;
840 }
841
842 void
843 AUPluginUI::grab_focus()
844 {
845         if (au_view) {
846                 [au_view becomeFirstResponder];
847         }
848 }
849 void
850 AUPluginUI::forward_key_event (GdkEventKey* ev)
851 {
852         NSEvent* nsevent = gdk_quartz_event_get_nsevent ((GdkEvent*)ev);
853
854         if (au_view && nsevent) {
855
856                 /* filter on nsevent type here because GDK massages FlagsChanged
857                    messages into GDK_KEY_{PRESS,RELEASE} but Cocoa won't
858                    handle a FlagsChanged message as a keyDown or keyUp
859                 */
860
861                 if ([nsevent type] == NSKeyDown) {
862                         [[[au_view window] firstResponder] keyDown:nsevent];
863                 } else if ([nsevent type] == NSKeyUp) {
864                         [[[au_view window] firstResponder] keyUp:nsevent];
865                 } else if ([nsevent type] == NSFlagsChanged) {
866                         [[[au_view window] firstResponder] flagsChanged:nsevent];
867                 }
868         }
869 }
870
871 void
872 AUPluginUI::on_realize ()
873 {
874         VBox::on_realize ();
875
876         /* our windows should not have that resize indicator */
877
878         NSWindow* win = get_nswindow ();
879         if (win) {
880                 [win setShowsResizeIndicator:0];
881         }
882 }
883
884 void
885 AUPluginUI::lower_box_realized ()
886 {
887         if (au_view) {
888                 parent_cocoa_window ();
889         } else if (carbon_window) {
890                 parent_carbon_window ();
891         }
892 }
893
894 bool
895 AUPluginUI::lower_box_visibility_notify (GdkEventVisibility* ev)
896 {
897 #ifdef WITH_CARBON
898         if (carbon_window  && ev->state != GDK_VISIBILITY_UNOBSCURED) {
899                 ShowWindow (carbon_window);
900                 ActivateWindow (carbon_window, TRUE);
901                 return true;
902         }
903 #endif
904         return false;
905 }
906
907 void
908 AUPluginUI::lower_box_map ()
909 {
910         mapped = true;
911         [au_view setHidden:0];
912         update_view_size ();
913 }
914
915 void
916 AUPluginUI::lower_box_unmap ()
917 {
918         mapped = false;
919         [au_view setHidden:1];
920 }
921
922 void
923 AUPluginUI::lower_box_size_request (GtkRequisition* requisition)
924 {
925         requisition->width  = req_width;
926         requisition->height = req_height;
927 }
928
929 void
930 AUPluginUI::lower_box_size_allocate (Gtk::Allocation& allocation)
931 {
932         alo_width  = allocation.get_width ();
933         alo_height = allocation.get_height ();
934         std::cerr << "lower box size reallocated to " << alo_width << " x " << alo_height << std::endl;
935         update_view_size ();
936         std::cerr << "low box draw (0, 0, " << alo_width << " x " << alo_height << ")\n";
937         low_box.queue_draw_area (0, 0, alo_width, alo_height);
938 }
939
940 gboolean
941 AUPluginUI::lower_box_expose (GdkEventExpose* event)
942 {
943         std::cerr << "lower box expose: " << event->area.x << ", " << event->area.y
944                   << ' '
945                   << event->area.width << " x " << event->area.height
946                   << " ALLOC "
947                   << get_allocation().get_width() << " x " << get_allocation().get_height()
948                   << std::endl;
949
950         /* hack to keep ardour responsive
951          * some UIs (e.g Addictive Drums) completely hog the CPU
952          */
953         ARDOUR::GUIIdle();
954
955         return true;
956 }
957
958 void
959 AUPluginUI::on_window_hide ()
960 {
961 #ifdef WITH_CARBON
962         if (carbon_window) {
963                 HideWindow (carbon_window);
964                 ActivateWindow (carbon_window, FALSE);
965         }
966 #endif
967         hide_all ();
968
969 #if 0
970         NSArray* wins = [NSApp windows];
971         for (uint32_t i = 0; i < [wins count]; i++) {
972                 id win = [wins objectAtIndex:i];
973         }
974 #endif
975 }
976
977 bool
978 AUPluginUI::on_window_show (const string& /*title*/)
979 {
980         /* this is idempotent so just call it every time we show the window */
981
982         gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
983
984         show_all ();
985
986 #ifdef WITH_CARBON
987         if (carbon_window) {
988                 ShowWindow (carbon_window);
989                 ActivateWindow (carbon_window, TRUE);
990         }
991 #endif
992
993         return true;
994 }
995
996 bool
997 AUPluginUI::start_updating (GdkEventAny*)
998 {
999         return false;
1000 }
1001
1002 bool
1003 AUPluginUI::stop_updating (GdkEventAny*)
1004 {
1005         return false;
1006 }
1007
1008 PlugUIBase*
1009 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
1010 {
1011         AUPluginUI* aup = new AUPluginUI (plugin_insert);
1012         (*box) = aup;
1013         return aup;
1014 }
1015
1016 void
1017 AUPluginUI::start_live_resize ()
1018 {
1019   std::cerr << "\n\n\n++++ Entering Live Resize\n";
1020   in_live_resize = true;
1021 }
1022
1023 void
1024 AUPluginUI::end_live_resize ()
1025 {
1026   std::cerr << "\n\n\n ----Leaving Live Resize\n";
1027   in_live_resize = false;
1028 }