step 2 of keyboard focus work for plugin windows
[ardour.git] / gtk2_ardour / au_pluginui.mm
1 #undef  Marker
2 #define Marker FuckYouAppleAndYourLackOfNameSpaces
3
4 #include <pbd/error.h>
5 #include <ardour/audio_unit.h>
6 #include <ardour/insert.h>
7
8 #undef check // stupid gtk, stupid apple
9
10 #include <gtkmm/button.h>
11 #include <gdk/gdkquartz.h>
12
13 #include <gtkmm2ext/utils.h>
14
15 #include "au_pluginui.h"
16 #include "gui_thread.h"
17
18 #include <appleutility/CAAudioUnit.h>
19 #include <appleutility/CAComponent.h>
20
21 #import <AudioUnit/AUCocoaUIView.h>
22 #import <CoreAudioKit/AUGenericView.h>
23
24 #undef Marker
25
26 #include "keyboard.h"
27
28 #include "i18n.h"
29
30 using namespace ARDOUR;
31 using namespace Gtk;
32 using namespace Gtkmm2ext;
33 using namespace sigc;
34 using namespace std;
35 using namespace PBD;
36
37 vector<string> AUPluginUI::automation_mode_strings;
38
39 static const gchar* _automation_mode_strings[] = {
40         X_("Manual"),
41         X_("Play"),
42         X_("Write"),
43         X_("Touch"),
44         0
45 };
46
47 AUPluginUI::AUPluginUI (boost::shared_ptr<PluginInsert> insert)
48         : PlugUIBase (insert)
49         , automation_mode_label (_("Automation"))
50         , preset_label (_("Presets"))
51         
52 {
53         if (automation_mode_strings.empty()) {
54                 automation_mode_strings = I18N (_automation_mode_strings);
55         }
56         
57         set_popdown_strings (automation_mode_selector, automation_mode_strings);
58         automation_mode_selector.set_active_text (automation_mode_strings.front());
59
60         if ((au = boost::dynamic_pointer_cast<AUPlugin> (insert->plugin())) == 0) {
61                 error << _("unknown type of editor-supplying plugin (note: no AudioUnit support in this version of ardour)") << endmsg;
62                 throw failed_constructor ();
63         }
64
65         /* stuff some stuff into the top of the window */
66
67         top_box.set_spacing (6);
68         top_box.set_border_width (6);
69
70         top_box.pack_end (focus_button, false, true);
71         top_box.pack_end (bypass_button, false, true);
72         top_box.pack_end (automation_mode_selector, false, false);
73         top_box.pack_end (automation_mode_label, false, false);
74         top_box.pack_end (save_button, false, false);
75         top_box.pack_end (preset_combo, false, false);
76         top_box.pack_end (preset_label, false, false);
77
78         set_spacing (6);
79         pack_start (top_box, false, false);
80         pack_start (low_box, false, false);
81
82         preset_label.show ();
83         preset_combo.show ();
84         automation_mode_label.show ();
85         automation_mode_selector.show ();
86         bypass_button.show ();
87         top_box.show ();
88         low_box.show ();
89
90         _activating_from_app = false;
91         cocoa_parent = 0;
92         cocoa_window = 0;
93         au_view = 0;
94         packView = 0;
95
96         /* prefer cocoa, fall back to cocoa, but use carbon if its there */
97
98         if (test_cocoa_view_support()) {
99                 create_cocoa_view ();
100         } else if (test_carbon_view_support()) {
101                 create_carbon_view ();
102         } else {
103                 create_cocoa_view ();
104         }
105
106         low_box.signal_realize().connect (mem_fun (this, &AUPluginUI::lower_box_realized));
107 }
108
109 AUPluginUI::~AUPluginUI ()
110 {
111         if (cocoa_parent) {
112                 NSWindow* win = get_nswindow();
113                 RemoveEventHandler(carbon_event_handler);
114                 [win removeChildWindow:cocoa_parent];
115         } else if (carbon_window) {
116                 /* never parented */
117                 DisposeWindow (carbon_window);
118         }
119
120         if (packView && packView != au_view) {
121                 [packView release];
122         }
123 }
124
125 bool
126 AUPluginUI::test_carbon_view_support ()
127 {
128         bool ret = false;
129         
130         carbon_descriptor.componentType = kAudioUnitCarbonViewComponentType;
131         carbon_descriptor.componentSubType = 'gnrc';
132         carbon_descriptor.componentManufacturer = 'appl';
133         carbon_descriptor.componentFlags = 0;
134         carbon_descriptor.componentFlagsMask = 0;
135         
136         OSStatus err;
137
138         // ask the AU for its first editor component
139         UInt32 propertySize;
140         err = AudioUnitGetPropertyInfo(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propertySize, NULL);
141         if (!err) {
142                 int nEditors = propertySize / sizeof(ComponentDescription);
143                 ComponentDescription *editors = new ComponentDescription[nEditors];
144                 err = AudioUnitGetProperty(*au->get_au(), kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, editors, &propertySize);
145                 if (!err) {
146                         // just pick the first one for now
147                         carbon_descriptor = editors[0];
148                         ret = true;
149                 }
150                 delete[] editors;
151         }
152
153         return ret;
154 }
155         
156 bool
157 AUPluginUI::test_cocoa_view_support ()
158 {
159         UInt32 dataSize   = 0;
160         Boolean isWritable = 0;
161         OSStatus err = AudioUnitGetPropertyInfo(*au->get_au(),
162                                                 kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global,
163                                                 0, &dataSize, &isWritable);
164         
165         return dataSize > 0 && err == noErr;
166 }
167
168 bool
169 AUPluginUI::plugin_class_valid (Class pluginClass)
170 {
171         if([pluginClass conformsToProtocol: @protocol(AUCocoaUIBase)]) {
172                 if([pluginClass instancesRespondToSelector: @selector(interfaceVersion)] &&
173                    [pluginClass instancesRespondToSelector: @selector(uiViewForAudioUnit:withSize:)]) {
174                                 return true;
175                 }
176         }
177         return false;
178 }
179
180 int
181 AUPluginUI::create_cocoa_view ()
182 {
183         BOOL wasAbleToLoadCustomView = NO;
184         AudioUnitCocoaViewInfo* cocoaViewInfo = NULL;
185         UInt32               numberOfClasses = 0;
186         UInt32     dataSize;
187         Boolean    isWritable;
188         NSString*           factoryClassName = 0;
189         NSURL*              CocoaViewBundlePath;
190
191         OSStatus result = AudioUnitGetPropertyInfo (*au->get_au(),
192                                                     kAudioUnitProperty_CocoaUI,
193                                                     kAudioUnitScope_Global, 
194                                                     0,
195                                                     &dataSize,
196                                                     &isWritable );
197
198         numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
199         
200         // Does view have custom Cocoa UI?
201         
202         if ((result == noErr) && (numberOfClasses > 0) ) {
203                 cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize);
204                 if(AudioUnitGetProperty(*au->get_au(),
205                                         kAudioUnitProperty_CocoaUI,
206                                         kAudioUnitScope_Global,
207                                         0,
208                                         cocoaViewInfo,
209                                         &dataSize) == noErr) {
210
211                         CocoaViewBundlePath     = (NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation;
212                         
213                         // we only take the first view in this example.
214                         factoryClassName        = (NSString *)cocoaViewInfo->mCocoaAUViewClass[0];
215
216                 } else {
217
218                         if (cocoaViewInfo != NULL) {
219                                 free (cocoaViewInfo);
220                                 cocoaViewInfo = NULL;
221                         }
222                 }
223         }
224
225         NSRect crect = { { 0, 0 }, { 1, 1} };
226
227         // [A] Show custom UI if view has it
228
229         if (CocoaViewBundlePath && factoryClassName) {
230                 NSBundle *viewBundle    = [NSBundle bundleWithPath:[CocoaViewBundlePath path]];
231                 if (viewBundle == nil) {
232                         error << _("AUPluginUI: error loading AU view's bundle") << endmsg;
233                         return -1;
234                 } else {
235                         Class factoryClass = [viewBundle classNamed:factoryClassName];
236                         if (!factoryClass) {
237                                 error << _("AUPluginUI: error getting AU view's factory class from bundle") << endmsg;
238                                 return -1;
239                         }
240                         
241                         // make sure 'factoryClass' implements the AUCocoaUIBase protocol
242                         if (!plugin_class_valid (factoryClass)) {
243                                 error << _("AUPluginUI: U view's factory class does not properly implement the AUCocoaUIBase protocol") << endmsg;
244                                 return -1;
245                         }
246                         // make a factory
247                         id factoryInstance = [[[factoryClass alloc] init] autorelease];
248                         if (factoryInstance == nil) {
249                                 error << _("AUPluginUI: Could not create an instance of the AU view factory") << endmsg;
250                                 return -1;
251                         }
252
253                         // make a view
254                         au_view = [factoryInstance uiViewForAudioUnit:*au->get_au() withSize:crect.size];
255                         
256                         // cleanup
257                         [CocoaViewBundlePath release];
258                         if (cocoaViewInfo) {
259                                 UInt32 i;
260                                 for (i = 0; i < numberOfClasses; i++)
261                                         CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]);
262                                 
263                                 free (cocoaViewInfo);
264                         }
265                         wasAbleToLoadCustomView = YES;
266                 }
267         }
268
269         if (!wasAbleToLoadCustomView) {
270                 // load generic Cocoa view
271                 au_view = [[AUGenericView alloc] initWithAudioUnit:*au->get_au()];
272                 [(AUGenericView *)au_view setShowsExpertParameters:YES];
273         }
274
275         NSRect packFrame;
276
277         // Get the size of the new AU View's frame 
278         packFrame = [au_view frame];
279
280         packFrame.origin.x = 0;
281         packFrame.origin.y = 0;
282
283         if (packFrame.size.width > 500 || packFrame.size.height > 500) {
284                 
285                 /* its too big - use a scrollview */
286
287                 NSRect frameRect = [[cocoa_window contentView] frame];
288                 scroll_view = [[[NSScrollView alloc] initWithFrame:frameRect] autorelease];
289                 [scroll_view setDrawsBackground:NO];
290                 [scroll_view setHasHorizontalScroller:YES];
291                 [scroll_view setHasVerticalScroller:YES];
292
293                 packFrame.size = [NSScrollView  frameSizeForContentSize:packFrame.size
294                                     hasHorizontalScroller:[scroll_view hasHorizontalScroller]
295                                     hasVerticalScroller:[scroll_view hasVerticalScroller]
296                                     borderType:[scroll_view borderType]];
297                 
298                 // Create a new frame with same origin as current
299                 // frame but size equal to the size of the new view
300                 NSRect newFrame;
301                 newFrame.origin = [scroll_view frame].origin;
302                 newFrame.size = packFrame.size;
303                 
304                 // Set the new frame and document views on the scroll view
305                 [scroll_view setFrame:newFrame];
306                 [scroll_view setDocumentView:au_view];
307                 
308                 packView = scroll_view;
309
310         } else {
311
312                 packView = au_view;
313         }
314
315         prefwidth = packFrame.size.width;
316         prefheight = packFrame.size.height;
317
318         return 0;
319 }
320
321 int
322 AUPluginUI::create_carbon_view ()
323 {
324         OSStatus err;
325         ControlRef root_control;
326
327         Component editComponent = FindNextComponent(NULL, &carbon_descriptor);
328         
329         OpenAComponent(editComponent, &editView);
330         if (!editView) {
331                 error << _("AU Carbon view: cannot open AU Component") << endmsg;
332                 return -1;
333         }
334         
335         Rect r = { 100, 100, 100, 100 };
336         WindowAttributes attr = WindowAttributes (kWindowStandardHandlerAttribute |
337                                                   kWindowCompositingAttribute|
338                                                   kWindowNoShadowAttribute|
339                                                   kWindowNoTitleBarAttribute);
340
341         if ((err = CreateNewWindow(kDocumentWindowClass, attr, &r, &carbon_window)) != noErr) {
342                 error << string_compose (_("AUPluginUI: cannot create carbon window (err: %1)"), err) << endmsg;
343                 return -1;
344         }
345         
346         if ((err = GetRootControl(carbon_window, &root_control)) != noErr) {
347                 error << string_compose (_("AUPlugin: cannot get root control of carbon window (err: %1)"), err) << endmsg;
348                 return -1;
349         }
350
351         ControlRef viewPane;
352         Float32Point location  = { 0.0, 0.0 };
353         Float32Point size = { 0.0, 0.0 } ;
354
355         if ((err = AudioUnitCarbonViewCreate (editView, *au->get_au(), carbon_window, root_control, &location, &size, &viewPane)) != noErr) {
356                 error << string_compose (_("AUPluginUI: cannot create carbon plugin view (err: %1)"), err) << endmsg;
357                 return -1;
358         }
359
360         // resize window
361
362         Rect bounds;
363         GetControlBounds(viewPane, &bounds);
364         size.x = bounds.right-bounds.left;
365         size.y = bounds.bottom-bounds.top;
366
367         prefwidth = (int) (size.x + 0.5);
368         prefheight = (int) (size.y + 0.5);
369
370         SizeWindow (carbon_window, prefwidth, prefheight,  true);
371         low_box.set_size_request (prefwidth, prefheight);
372
373         return 0;
374 }
375
376 NSWindow*
377 AUPluginUI::get_nswindow ()
378 {
379         Gtk::Container* toplevel = get_toplevel();
380
381         if (!toplevel || !toplevel->is_toplevel()) {
382                 error << _("AUPluginUI: no top level window!") << endmsg;
383                 return 0;
384         }
385
386         NSWindow* true_parent = gdk_quartz_window_get_nswindow (toplevel->get_window()->gobj());
387
388         if (!true_parent) {
389                 error << _("AUPluginUI: no top level window!") << endmsg;
390                 return 0;
391         }
392
393         return true_parent;
394 }
395
396 void
397 AUPluginUI::activate ()
398 {
399         if (carbon_window && cocoa_parent) {
400                 cerr << "APP activated, activate carbon window " << insert->name() << endl;
401                 _activating_from_app = true;
402                 ActivateWindow (carbon_window, TRUE);
403                 _activating_from_app = false;
404                 [cocoa_parent makeKeyAndOrderFront:nil];
405         } 
406 }
407
408 void
409 AUPluginUI::deactivate ()
410 {
411         return;
412         cerr << "APP DEactivated, for " << insert->name() << endl;
413         _activating_from_app = true;
414         ActivateWindow (carbon_window, FALSE);
415         _activating_from_app = false;
416 }
417
418
419 OSStatus 
420 _carbon_event (EventHandlerCallRef nextHandlerRef, EventRef event, void *userData) 
421 {
422         return ((AUPluginUI*)userData)->carbon_event (nextHandlerRef, event);
423 }
424
425 OSStatus 
426 AUPluginUI::carbon_event (EventHandlerCallRef nextHandlerRef, EventRef event)
427 {
428         cerr << "CARBON EVENT\n";
429
430         UInt32 eventKind = GetEventKind(event);
431         ClickActivationResult howToHandleClick;
432         NSWindow* win = get_nswindow ();
433
434         cerr << "window " << win << " carbon event type " << eventKind << endl;
435
436         switch (eventKind) {
437         case kEventWindowHandleActivate:
438                 cerr << "carbon window for " << insert->name() << " activated\n";
439                 if (_activating_from_app) {
440                         cerr << "app activation, ignore window activation\n";
441                         return noErr;
442                 }
443                 [win makeMainWindow];
444                 return eventNotHandledErr;
445                 break;
446
447         case kEventWindowHandleDeactivate:
448                 cerr << "carbon window for " << insert->name() << " would have been deactivated\n";
449                 // never deactivate the carbon window
450                 return noErr;
451                 break;
452                 
453         case kEventWindowGetClickActivation:
454                 cerr << "carbon window CLICK activated\n";
455                 [win makeKeyAndOrderFront:nil];
456                 howToHandleClick = kActivateAndHandleClick;
457                 SetEventParameter(event, kEventParamClickActivation, typeClickActivationResult, 
458                                   sizeof(ClickActivationResult), &howToHandleClick);
459                 break;
460         }
461
462         return noErr;
463 }
464
465 int
466 AUPluginUI::parent_carbon_window ()
467 {
468         NSWindow* win = get_nswindow ();
469         int x, y;
470
471         if (!win) {
472                 return -1;
473         }
474
475         Gtk::Container* toplevel = get_toplevel();
476
477         if (!toplevel || !toplevel->is_toplevel()) {
478                 error << _("AUPluginUI: no top level window!") << endmsg;
479                 return -1;
480         }
481         
482         toplevel->get_window()->get_root_origin (x, y);
483
484         /* compute how tall the title bar is, because we have to offset the position of the carbon window
485            by that much.
486         */
487
488         NSRect content_frame = [NSWindow contentRectForFrameRect:[win frame] styleMask:[win styleMask]];
489         NSRect wm_frame = [NSWindow frameRectForContentRect:content_frame styleMask:[win styleMask]];
490
491         int titlebar_height = wm_frame.size.height - content_frame.size.height;
492
493         int packing_extra = 6; // this is the total vertical packing in our top level window
494
495         MoveWindow (carbon_window, x, y + titlebar_height + top_box.get_height() + packing_extra, false);
496         ShowWindow (carbon_window);
497
498         // create the cocoa window for the carbon one and make it visible
499         cocoa_parent = [[NSWindow alloc] initWithWindowRef: carbon_window];
500
501         EventTypeSpec   windowEventTypes[] = {
502                 {kEventClassWindow, kEventWindowGetClickActivation },
503                 {kEventClassWindow, kEventWindowHandleDeactivate }
504         };
505         
506         EventHandlerUPP   ehUPP = NewEventHandlerUPP(_carbon_event);
507         OSStatus result = InstallWindowEventHandler (carbon_window, ehUPP, 
508                                                      sizeof(windowEventTypes) / sizeof(EventTypeSpec), 
509                                                      windowEventTypes, this, &carbon_event_handler);
510         if (result != noErr) {
511                 return -1;
512         }
513
514         [win addChildWindow:cocoa_parent ordered:NSWindowAbove];
515
516         return 0;
517 }       
518
519 int
520 AUPluginUI::parent_cocoa_window ()
521 {
522         NSWindow* win = get_nswindow ();
523         NSRect packFrame;
524
525         if (!win) {
526                 return -1;
527         }
528
529         [win setAutodisplay:YES]; // turn of GTK stuff for this window
530
531         Gtk::Container* toplevel = get_toplevel();
532
533         if (!toplevel || !toplevel->is_toplevel()) {
534                 error << _("AUPluginUI: no top level window!") << endmsg;
535                 return -1;
536         }
537         
538         // Get the size of the new AU View's frame 
539         packFrame = [au_view frame];
540
541         NSView* view = gdk_quartz_window_get_nsview (low_box.get_window()->gobj());
542         
543
544         [view setFrame:packFrame];
545         [view addSubview:packView]; 
546
547         low_box.set_size_request (packFrame.size.width, packFrame.size.height);
548
549         return 0;
550 }
551
552 void
553 AUPluginUI::on_realize ()
554 {
555         VBox::on_realize ();
556
557         /* our windows should not have that resize indicator */
558
559         NSWindow* win = get_nswindow ();
560         if (win) {
561                 [win setShowsResizeIndicator:NO];
562         }
563 }
564
565 void
566 AUPluginUI::lower_box_realized ()
567 {
568         if (au_view) {
569                 parent_cocoa_window ();
570         } else if (carbon_window) {
571                 parent_carbon_window ();
572         }
573 }
574
575 void
576 AUPluginUI::on_hide ()
577 {
578         // VBox::on_hide ();
579         cerr << "AU plugin window hidden\n";
580 }
581
582 bool
583 AUPluginUI::on_map_event (GdkEventAny* ev)
584 {
585         return false;
586 }
587
588 void
589 AUPluginUI::on_show ()
590 {
591         cerr << "AU plugin window shown\n";
592
593         VBox::on_show ();
594
595         gtk_widget_realize (GTK_WIDGET(low_box.gobj()));
596
597         if (au_view) {
598                 show_all ();
599         } else if (carbon_window) {
600                 [cocoa_parent setIsVisible:YES];
601                 ShowWindow (carbon_window);
602         }
603 }
604
605 bool
606 AUPluginUI::start_updating (GdkEventAny* any)
607 {
608         return false;
609 }
610
611 bool
612 AUPluginUI::stop_updating (GdkEventAny* any)
613 {
614         return false;
615 }
616
617 PlugUIBase*
618 create_au_gui (boost::shared_ptr<PluginInsert> plugin_insert, VBox** box)
619 {
620         AUPluginUI* aup = new AUPluginUI (plugin_insert);
621         (*box) = aup;
622         return aup;
623 }
624
625 bool
626 AUPluginUI::on_focus_in_event (GdkEventFocus* ev)
627 {
628         //cerr << "au plugin focus in\n";
629         //Keyboard::magic_widget_grab_focus ();
630         return false;
631 }
632
633 bool
634 AUPluginUI::on_focus_out_event (GdkEventFocus* ev)
635 {
636         //cerr << "au plugin focus out\n";
637         //Keyboard::magic_widget_drop_focus ();
638         return false;
639 }
640