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