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