1/**********************************************************************
2
3  Audacity: A Digital Audio Editor
4
5  VSTControlOSX.mm
6
7  Leland Lucius
8
9  Several ideas and code snippets taken from HairerSoft's HSVSTView class:
10
11      http://www.hairersoft.com/Downloads/HSVSTView.zip
12
13      Created by Martin on 02/06/2007.
14      Copyright 2010 by HairerSoft.
15
16      You are most welcome to use this code in your own (open source or not)
17      project. Use at your own risk of course, etc. Please acknowledge at an
18      appropriate location (manual or about box for example).
19
20      Bug reports most welcome: Martin@HairerSoft.com
21
22**********************************************************************/
23
24
25#include "VSTControlOSX.h"
26
27#include <memory>
28
29@interface VSTView : NSView
30{
31}
32@end
33
34@implementation VSTView
35
36+ (void)initialize
37{
38   static BOOL initialized = NO;
39   if (!initialized)
40   {
41      initialized = YES;
42      wxOSXCocoaClassAddWXMethods(self);
43   }
44}
45@end
46
47VSTControlImpl::VSTControlImpl(wxWindowMac *peer, NSView *view)
48:  wxWidgetCocoaImpl(peer, view)
49{
50}
51
52VSTControlImpl::~VSTControlImpl()
53{
54}
55
56VSTControl::VSTControl()
57:  VSTControlBase()
58{
59   mVSTView = nil;
60   mView = nil;
61
62#if !defined(_LP64)
63   mHIView = NULL;
64   mWindowRef = NULL;
65#endif
66}
67
68VSTControl::~VSTControl()
69{
70   Close();
71}
72
73void VSTControl::Close()
74{
75#if !defined(_LP64)
76   if (mWindowRef)
77   {
78      mLink->callDispatcher(effEditClose, 0, 0, mWindowRef, 0.0);
79      mWindowRef = 0;
80   }
81#endif
82}
83
84bool VSTControl::Create(wxWindow *parent, VSTEffectLink *link)
85{
86   DontCreatePeer();
87
88   if (!VSTControlBase::Create(parent, link))
89   {
90      return false;
91   }
92
93   mVSTView = [VSTView alloc];
94   if (!mVSTView)
95   {
96      return false;
97   }
98   [mVSTView init];
99   [mVSTView retain];
100
101   // wxWidgets takes ownership so safenew
102   SetPeer(safenew VSTControlImpl(this, mVSTView));
103
104   CreateCocoa();
105
106#if !defined(_LP64)
107   if (!mView)
108   {
109      CreateCarbon();
110   }
111#endif
112
113   if (!mView
114#if !defined(_LP64)
115       && !mHIView
116#endif
117       )
118   {
119      return false;
120   }
121
122   // Must get the size again since SetPeer() could cause it to change
123   SetInitialSize(GetMinSize());
124
125   MacPostControlCreate(wxDefaultPosition, wxDefaultSize);
126
127   return true;
128}
129
130void VSTControl::CreateCocoa()
131{
132   if ((mLink->callDispatcher(effCanDo, 0, 0, (void *) "hasCockosViewAsConfig", 0.0) & 0xffff0000) != 0xbeef0000)
133   {
134      return;
135   }
136
137   VstRect *rect;
138
139   // Some effects like to have us get their rect before opening them.
140   mLink->callDispatcher(effEditGetRect, 0, 0, &rect, 0.0);
141
142   // Ask the effect to add its GUI
143   mLink->callDispatcher(effEditOpen, 0, 0, mVSTView, 0.0);
144
145   // Get the subview it created
146   mView = [[mVSTView subviews] objectAtIndex:0];
147   if (mView == NULL)
148   {
149      // Doesn't seem the effect created the subview.  This can
150      // happen when an effect uses the content view directly.
151      // As of this time, we will not try to support those and
152      // just fall back to the textual interface.
153      return;
154   }
155
156   // Get the final bounds of the effect GUI
157   mLink->callDispatcher(effEditGetRect, 0, 0, &rect, 0.0);
158
159   NSRect frame = {
160      { 0, 0 },
161      { (CGFloat) rect->right - rect->left, (CGFloat) rect->bottom - rect->top }
162   };
163
164   [mView setFrame:frame];
165
166   [mVSTView addSubview:mView];
167
168   SetMinSize(wxSize(frame.size.width, frame.size.height));
169
170   return;
171}
172
173#if !defined(_LP64)
174
175void VSTControl::CreateCarbon()
176{
177   OSStatus result;
178
179   Bind(wxEVT_SIZE, &VSTControl::OnSize, this);
180
181   VstRect *rect;
182
183   // Some effects like to have us get their rect before opening them.
184   mLink->callDispatcher(effEditGetRect, 0, 0, &rect, 0.0);
185
186   // Suggest a dummy size
187   Rect bounds = { 0, 0, 0, 0 };
188
189   // And create the window
190   result = CreateNewWindow(kOverlayWindowClass,
191                            kWindowStandardHandlerAttribute |
192                            kWindowCompositingAttribute |
193                            kWindowOpaqueForEventsAttribute,
194                            &bounds,
195                            &mWindowRef);
196   if (result != noErr)
197   {
198      return;
199   }
200
201   // Get the root control
202   ControlRef root = HIViewGetRoot(mWindowRef);
203
204   // Find the content view within our window
205   HIViewRef content;
206   result = HIViewFindByID(root, kHIViewWindowContentID, &content);
207   if (result != noErr)
208   {
209      DisposeWindow(mWindowRef);
210      mWindowRef = NULL;
211
212      return;
213   }
214
215   // Some effects (iZotope Vinyl) seem to need an existing subview
216   // of the content view.  So just use a "dummy" scrollview.
217   result = HIScrollViewCreate(kHIScrollViewOptionsVertScroll, &mHIView);
218
219   // Don't want to see the scroll bars
220   HIScrollViewSetScrollBarAutoHide(mHIView, true);
221
222   // Add it as a subview of the content view
223   HIViewAddSubview(content, mHIView);
224
225   // Ask the effect to add its GUI
226   mLink->callDispatcher(effEditOpen, 0, 0, mWindowRef, 0.0);
227
228   // Get the subview it created
229   HIViewRef subview = HIViewGetFirstSubview(content);
230   if (subview)
231   {
232      // The scrollview was used, so leave it.
233      if (subview == mHIView)
234      {
235         subview = HIViewGetFirstSubview(mHIView);
236      }
237      // The effect didn't use our scrollview, so dispose of it.
238      else
239      {
240         HIViewRemoveFromSuperview(mHIView);
241         CFRelease(mHIView);
242         mHIView = subview;
243      }
244   }
245
246   // Doesn't seem the effect created a subview.  This can
247   // happen when an effect uses the content view directly.
248   // As of this time, we will not try to support those and
249   // just fall back to the textual interface.
250   if (subview == NULL)
251   {
252      mLink->callDispatcher(effEditClose, 0, 0, mWindowRef, 0.0);
253      DisposeWindow(mWindowRef);
254      mWindowRef = NULL;
255      mHIView = NULL;
256
257      return;
258   }
259
260   // Get the final bounds of the effect GUI
261   mLink->callDispatcher(effEditGetRect, 0, 0, &rect, 0.0);
262
263   // Set the size of the scrollview to match
264   HIRect r = {
265      { 0, 0 },
266      { (CGFloat) rect->right - rect->left, (CGFloat) rect->bottom - rect->top }
267   };
268
269   // One effect, mutagene lipredemuco, doesn't return a valid rect so
270   // try to detect it and use the created view dimensions instead.
271   if (rect->left < 0 || rect->top < 0 || rect->right <= 0 || rect->bottom <= 0)
272   {
273      HIViewGetFrame(subview, &r);
274   }
275
276   // Make sure container is the same size as the effect GUI
277   HIViewSetFrame(mHIView, &r);
278   HIViewPlaceInSuperviewAt(mHIView, 0, 0);
279
280   // Establish the minimum size
281   SetMinSize(wxSize(r.size.width, r.size.height));
282
283   NSWindow *parent = [mVSTView window];
284   NSWindow *host = [[[NSWindow alloc] initWithWindowRef:mWindowRef] autorelease];
285   [parent addChildWindow:host ordered:NSWindowAbove];
286
287   ShowWindow(mWindowRef);
288}
289
290void VSTControl::OnSize(wxSizeEvent & evt)
291{
292   evt.Skip();
293
294   wxRect rect = GetScreenRect();
295
296   MoveWindow(mWindowRef, rect.x, rect.y, true);
297   SizeWindow(mWindowRef, rect.width, rect.height, true);
298}
299
300#endif
301