iPlug2 - C++ Audio Plug-in Framework
IGraphicsMac.mm
1 /*
2  ==============================================================================
3 
4  This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
5 
6  See LICENSE.txt for more info.
7 
8  ==============================================================================
9 */
10 
11 #include "IGraphicsMac.h"
12 #import "IGraphicsMac_view.h"
13 
14 #include "IControl.h"
15 #include "IPopupMenuControl.h"
16 
17 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
18 
19 using namespace iplug;
20 using namespace igraphics;
21 
22 static int GetSystemVersion()
23 {
24  static int32_t v;
25  if (!v)
26  {
27  if (NSAppKitVersionNumber >= 1266.0)
28  {
29  if (NSAppKitVersionNumber >= 1404.0)
30  v = 0x10b0;
31  else
32  v = 0x10a0; // 10.10+ Gestalt(gsv) return 0x109x, so we bump this to 0x10a0
33  }
34  else
35  {
36  SInt32 a = 0x1040;
37  Gestalt(gestaltSystemVersion,&a);
38  v=a;
39  }
40  }
41  return v;
42 }
43 
44 StaticStorage<CoreTextFontDescriptor> sFontDescriptorCache;
45 
46 #pragma mark -
47 
48 IGraphicsMac::IGraphicsMac(IGEditorDelegate& dlg, int w, int h, int fps, float scale)
49 : IGRAPHICS_DRAW_CLASS(dlg, w, h, fps, scale)
50 {
51  NSApplicationLoad();
52  StaticStorage<CoreTextFontDescriptor>::Accessor storage(sFontDescriptorCache);
53  storage.Retain();
54 }
55 
56 IGraphicsMac::~IGraphicsMac()
57 {
58  StaticStorage<CoreTextFontDescriptor>::Accessor storage(sFontDescriptorCache);
59  storage.Release();
60 
61  CloseWindow();
62 }
63 
64 PlatformFontPtr IGraphicsMac::LoadPlatformFont(const char* fontID, const char* fileNameOrResID)
65 {
66  return CoreTextHelpers::LoadPlatformFont(fontID, fileNameOrResID, GetBundleID(), GetSharedResourcesSubPath());
67 }
68 
69 PlatformFontPtr IGraphicsMac::LoadPlatformFont(const char* fontID, const char* fontName, ETextStyle style)
70 {
71  return CoreTextHelpers::LoadPlatformFont(fontID, fontName, style);
72 }
73 
74 PlatformFontPtr IGraphicsMac::LoadPlatformFont(const char* fontID, void* pData, int dataSize)
75 {
76  return CoreTextHelpers::LoadPlatformFont(fontID, pData, dataSize);
77 }
78 
79 void IGraphicsMac::CachePlatformFont(const char* fontID, const PlatformFontPtr& font)
80 {
81  CoreTextHelpers::CachePlatformFont(fontID, font, sFontDescriptorCache);
82 }
83 
84 float IGraphicsMac::MeasureText(const IText& text, const char* str, IRECT& bounds) const
85 {
86  return IGRAPHICS_DRAW_CLASS::MeasureText(text, str, bounds);
87 }
88 
89 void* IGraphicsMac::OpenWindow(void* pParent)
90 {
91  TRACE
92  CloseWindow();
93  IGRAPHICS_VIEW* pView = [[IGRAPHICS_VIEW alloc] initWithIGraphics: this];
94  mView = (void*) pView;
95 
96 #ifdef IGRAPHICS_GL
97  [[pView openGLContext] makeCurrentContext];
98 #endif
99 
100  OnViewInitialized([pView layer]);
101  SetScreenScale([[NSScreen mainScreen] backingScaleFactor]);
102  GetDelegate()->LayoutUI(this);
103  UpdateTooltips();
104  GetDelegate()->OnUIOpen();
105 
106  if (pParent)
107  {
108  [(NSView*) pParent addSubview: pView];
109  }
110 
111  return mView;
112 }
113 
114 void IGraphicsMac::AttachPlatformView(const IRECT& r, void* pView)
115 {
116  NSView* pNewSubView = (NSView*) pView;
117  [pNewSubView setFrame:ToNSRect(this, r)];
118 
119  [(IGRAPHICS_VIEW*) mView addSubview:(NSView*) pNewSubView];
120 }
121 
122 void IGraphicsMac::RemovePlatformView(void* pView)
123 {
124  [(NSView*) pView removeFromSuperview];
125 }
126 
127 void IGraphicsMac::CloseWindow()
128 {
129  if (mView)
130  {
131 #if defined IGRAPHICS_IMGUI
132  if(mImGuiView)
133  {
134  IGRAPHICS_IMGUIVIEW* pImGuiView = (IGRAPHICS_IMGUIVIEW*) mImGuiView;
135  [pImGuiView removeFromSuperview];
136  [pImGuiView release];
137  mImGuiView = nullptr;
138  }
139 #endif
140 
141  IGRAPHICS_VIEW* pView = (IGRAPHICS_VIEW*) mView;
142 
143 #ifdef IGRAPHICS_GL
144  [[pView openGLContext] makeCurrentContext];
145 #endif
146 
147  [pView removeAllToolTips];
148  [pView killTimer];
149  [pView removeFromSuperview];
150  [pView release];
151 
152  mView = nullptr;
153  OnViewDestroyed();
154  }
155 }
156 
157 bool IGraphicsMac::WindowIsOpen()
158 {
159  return mView;
160 }
161 
162 void IGraphicsMac::PlatformResize(bool parentHasResized)
163 {
164  if (mView)
165  {
166  NSSize size = { static_cast<CGFloat>(WindowWidth()), static_cast<CGFloat>(WindowHeight()) };
167 
168  [NSAnimationContext beginGrouping]; // Prevent animated resizing
169  [[NSAnimationContext currentContext] setDuration:0.0];
170  [(IGRAPHICS_VIEW*) mView setFrameSize: size ];
171 
172 #if defined IGRAPHICS_IMGUI && !defined IGRAPHICS_SKIA && !defined IGRAPHICS_GL
173  if(mImGuiView)
174  [(IGRAPHICS_IMGUIVIEW*) mImGuiView setFrameSize: size ];
175 #endif
176 
177  [NSAnimationContext endGrouping];
178  }
179 
180  UpdateTooltips();
181 }
182 
183 void IGraphicsMac::PointToScreen(float& x, float& y) const
184 {
185  if (mView)
186  {
187  x *= GetDrawScale();
188  y *= GetDrawScale();
189  NSWindow* pWindow = [(IGRAPHICS_VIEW*) mView window];
190  NSPoint wndpt = [(IGRAPHICS_VIEW*) mView convertPoint:NSMakePoint(x, y) toView:nil];
191  NSPoint pt = [pWindow convertRectToScreen: NSMakeRect(wndpt.x, wndpt.y, 0.0, 0.0)].origin;
192 
193  x = pt.x;
194  y = pt.y;
195  }
196 }
197 
198 void IGraphicsMac::ScreenToPoint(float& x, float& y) const
199 {
200  if (mView)
201  {
202  NSWindow* pWindow = [(IGRAPHICS_VIEW*) mView window];
203  NSPoint wndpt = [pWindow convertRectFromScreen: NSMakeRect(x, y, 0.0, 0.0)].origin;
204  NSPoint pt = [(IGRAPHICS_VIEW*) mView convertPoint:NSMakePoint(wndpt.x, wndpt.y) fromView:nil];
205 
206  x = pt.x / GetDrawScale();
207  y = pt.y / GetDrawScale();
208  }
209 }
210 
211 void IGraphicsMac::HideMouseCursor(bool hide, bool lock)
212 {
213  if (mCursorHidden == hide)
214  return;
215 
216  mCursorHidden = hide;
217 
218  if (hide)
219  {
220  StoreCursorPosition();
221  CGDisplayHideCursor(kCGDirectMainDisplay);
222  mCursorLock = lock;
223  }
224  else
225  {
226  DoCursorLock(mCursorX, mCursorY, mCursorX, mCursorY);
227  CGDisplayShowCursor(kCGDirectMainDisplay);
228  mCursorLock = false;
229  }
230 }
231 
232 void IGraphicsMac::MoveMouseCursor(float x, float y)
233 {
234  if (mTabletInput)
235  return;
236 
237  PointToScreen(x, y);
238  RepositionCursor(CGPoint{x, y});
239  StoreCursorPosition();
240 }
241 
242 void IGraphicsMac::DoCursorLock(float x, float y, float& prevX, float& prevY)
243 {
244  if (mCursorHidden && mCursorLock && !mTabletInput)
245  {
246  RepositionCursor(mCursorLockPosition);
247  prevX = mCursorX;
248  prevY = mCursorY;
249  }
250  else
251  {
252  mCursorX = prevX = x;
253  mCursorY = prevY = y;
254  }
255 }
256 
257 void IGraphicsMac::RepositionCursor(CGPoint point)
258 {
259  point = CGPoint{point.x, CGDisplayPixelsHigh(CGMainDisplayID()) - point.y};
260  CGAssociateMouseAndMouseCursorPosition(false);
261  CGDisplayMoveCursorToPoint(CGMainDisplayID(), point);
262  CGAssociateMouseAndMouseCursorPosition(true);
263 }
264 
265 void IGraphicsMac::StoreCursorPosition()
266 {
267  // Get position in screen coordinates
268  NSPoint mouse = [NSEvent mouseLocation];
269  mCursorX = mouse.x = std::round(mouse.x);
270  mCursorY = mouse.y = std::round(mouse.y);
271  mCursorLockPosition = CGPoint{mouse.x, mouse.y};
272 
273  // Convert to IGraphics coordinates
274  ScreenToPoint(mCursorX, mCursorY);
275 }
276 
277 void IGraphicsMac::GetMouseLocation(float& x, float&y) const
278 {
279  // Get position in screen coordinates
280  NSPoint mouse = [NSEvent mouseLocation];
281  x = mouse.x;
282  y = mouse.y;
283 
284  // Convert to IGraphics coordinates
285  ScreenToPoint(x, y);
286 }
287 
288 EMsgBoxResult IGraphicsMac::ShowMessageBox(const char* str, const char* caption, EMsgBoxType type, IMsgBoxCompletionHanderFunc completionHandler)
289 {
290  ReleaseMouseCapture();
291 
292  long result = (long) kCANCEL;
293 
294  if (!str) str= "";
295  if (!caption) caption= "";
296 
297  NSString *msg = (NSString *) CFStringCreateWithCString(NULL,str,kCFStringEncodingUTF8);
298  NSString *cap = (NSString *) CFStringCreateWithCString(NULL,caption,kCFStringEncodingUTF8);
299 
300  msg = msg ? msg : (NSString *) CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII);
301  cap = cap ? cap : (NSString *) CFStringCreateWithCString(NULL, caption, kCFStringEncodingASCII);
302 
303  switch (type)
304  {
305  case kMB_OK:
306  NSRunAlertPanel(msg, @"%@", @"OK", @"", @"", cap);
307  result = kOK;
308  break;
309  case kMB_OKCANCEL:
310  result = NSRunAlertPanel(msg, @"%@", @"OK", @"Cancel", @"", cap);
311  result = result ? kOK : kCANCEL;
312  break;
313  case kMB_YESNO:
314  result = NSRunAlertPanel(msg, @"%@", @"Yes", @"No", @"", cap);
315  result = result ? kYES : kNO;
316  break;
317  case kMB_RETRYCANCEL:
318  result = NSRunAlertPanel(msg, @"%@", @"Retry", @"Cancel", @"", cap);
319  result = result ? kRETRY : kCANCEL;
320  break;
321  case kMB_YESNOCANCEL:
322  result = NSRunAlertPanel(msg, @"%@", @"Yes", @"Cancel", @"No", cap);
323  result = (result == 1) ? kYES : (result == -1) ? kNO : kCANCEL;
324  break;
325  }
326 
327  [msg release];
328  [cap release];
329 
330  if(completionHandler)
331  completionHandler(static_cast<EMsgBoxResult>(result));
332 
333  return static_cast<EMsgBoxResult>(result);
334 }
335 
336 void IGraphicsMac::ForceEndUserEdit()
337 {
338  if (mView)
339  {
340  [(IGRAPHICS_VIEW*) mView endUserInput];
341  }
342 }
343 
344 void IGraphicsMac::UpdateTooltips()
345 {
346  if (!(mView && TooltipsEnabled()))
347  return;
348 
349  @autoreleasepool {
350 
351  [(IGRAPHICS_VIEW*) mView removeAllToolTips];
352 
353  if (GetPopupMenuControl() && GetPopupMenuControl()->GetState() > IPopupMenuControl::kCollapsed)
354  {
355  return;
356  }
357 
358  auto func = [this](IControl* pControl)
359  {
360  if (pControl->GetTooltip() && !pControl->IsHidden())
361  {
362  IRECT pR = pControl->GetTargetRECT();
363  if (!pR.Empty())
364  {
365  [(IGRAPHICS_VIEW*) mView registerToolTip: pR];
366  }
367  }
368  };
369 
370  ForStandardControlsFunc(func);
371 
372  }
373 }
374 
375 const char* IGraphicsMac::GetPlatformAPIStr()
376 {
377  return "Cocoa";
378 }
379 
380 bool IGraphicsMac::RevealPathInExplorerOrFinder(WDL_String& path, bool select)
381 {
382  BOOL success = FALSE;
383 
384  @autoreleasepool {
385 
386  if(path.GetLength())
387  {
388  NSString* pPath = [NSString stringWithCString:path.Get() encoding:NSUTF8StringEncoding];
389 
390  if([[NSFileManager defaultManager] fileExistsAtPath : pPath] == YES)
391  {
392  if (select)
393  {
394  NSString* pParentDirectoryPath = [pPath stringByDeletingLastPathComponent];
395 
396  if (pParentDirectoryPath)
397  {
398  success = [[NSWorkspace sharedWorkspace] openFile:pParentDirectoryPath];
399 
400  if (success)
401  success = [[NSWorkspace sharedWorkspace] selectFile: pPath inFileViewerRootedAtPath:pParentDirectoryPath];
402  }
403  }
404  else {
405  success = [[NSWorkspace sharedWorkspace] openFile:pPath];
406  }
407 
408  }
409  }
410 
411  }
412  return (bool) success;
413 }
414 
415 void IGraphicsMac::PromptForFile(WDL_String& fileName, WDL_String& path, EFileAction action, const char* ext)
416 {
417  if (!WindowIsOpen())
418  {
419  fileName.Set("");
420  return;
421  }
422 
423  NSString* pDefaultFileName;
424  NSString* pDefaultPath;
425  NSArray* pFileTypes = nil;
426 
427  if (fileName.GetLength())
428  pDefaultFileName = [NSString stringWithCString:fileName.Get() encoding:NSUTF8StringEncoding];
429  else
430  pDefaultFileName = [NSString stringWithCString:"" encoding:NSUTF8StringEncoding];
431 
432  if(!path.GetLength())
433  DesktopPath(path);
434 
435  pDefaultPath = [NSString stringWithCString:path.Get() encoding:NSUTF8StringEncoding];
436 
437  fileName.Set(""); // reset it
438 
439  if (CStringHasContents(ext))
440  pFileTypes = [[NSString stringWithUTF8String:ext] componentsSeparatedByString: @" "];
441 
442  if (action == EFileAction::Save)
443  {
444  NSSavePanel* pSavePanel = [NSSavePanel savePanel];
445 
446  //[panelOpen setTitle:title];
447  [pSavePanel setAllowedFileTypes: pFileTypes];
448  [pSavePanel setAllowsOtherFileTypes: NO];
449 
450  long result = [pSavePanel runModalForDirectory:pDefaultPath file:pDefaultFileName];
451 
452  if (result == NSOKButton)
453  {
454  NSString* pFullPath = [pSavePanel filename] ;
455  fileName.Set([pFullPath UTF8String]);
456 
457  NSString* pTruncatedPath = [pFullPath stringByDeletingLastPathComponent];
458 
459  if (pTruncatedPath)
460  {
461  path.Set([pTruncatedPath UTF8String]);
462  path.Append("/");
463  }
464  }
465  }
466  else
467  {
468  NSOpenPanel* pOpenPanel = [NSOpenPanel openPanel];
469 
470  //[pOpenPanel setTitle:title];
471  //[pOpenPanel setAllowsMultipleSelection:(allowmul?YES:NO)];
472  [pOpenPanel setCanChooseFiles:YES];
473  [pOpenPanel setCanChooseDirectories:NO];
474  [pOpenPanel setResolvesAliases:YES];
475 
476  long result = [pOpenPanel runModalForDirectory:pDefaultPath file:pDefaultFileName types:pFileTypes];
477 
478  if (result == NSOKButton)
479  {
480  NSString* pFullPath = [pOpenPanel filename] ;
481  fileName.Set([pFullPath UTF8String]);
482 
483  NSString* pTruncatedPath = [pFullPath stringByDeletingLastPathComponent];
484 
485  if (pTruncatedPath)
486  {
487  path.Set([pTruncatedPath UTF8String]);
488  path.Append("/");
489  }
490  }
491  }
492 }
493 
494 void IGraphicsMac::PromptForDirectory(WDL_String& dir)
495 {
496  NSString* defaultPath;
497 
498  if (dir.GetLength())
499  {
500  defaultPath = [NSString stringWithCString:dir.Get() encoding:NSUTF8StringEncoding];
501  }
502  else
503  {
504  defaultPath = [NSString stringWithCString:DEFAULT_PATH encoding:NSUTF8StringEncoding];
505  dir.Set(DEFAULT_PATH);
506  }
507 
508  NSOpenPanel* panelOpen = [NSOpenPanel openPanel];
509 
510  [panelOpen setTitle:@"Choose a Directory"];
511  [panelOpen setCanChooseFiles:NO];
512  [panelOpen setCanChooseDirectories:YES];
513  [panelOpen setResolvesAliases:YES];
514  [panelOpen setCanCreateDirectories:YES];
515 
516  [panelOpen setDirectoryURL: [NSURL fileURLWithPath: defaultPath]];
517 
518  if ([panelOpen runModal] == NSOKButton)
519  {
520  NSString* fullPath = [ panelOpen filename ] ;
521  dir.Set( [fullPath UTF8String] );
522  dir.Append("/");
523  }
524  else
525  {
526  dir.Set("");
527  }
528 }
529 
530 bool IGraphicsMac::PromptForColor(IColor& color, const char* str, IColorPickerHandlerFunc func)
531 {
532  if (mView)
533  return [(IGRAPHICS_VIEW*) mView promptForColor:color : func];
534 
535  return false;
536 }
537 
538 IPopupMenu* IGraphicsMac::CreatePlatformPopupMenu(IPopupMenu& menu, const IRECT& bounds, bool& isAsync)
539 {
540  IPopupMenu* pReturnMenu = nullptr;
541 
542  if (mView)
543  {
544  NSRect areaRect = ToNSRect(this, bounds);
545  pReturnMenu = [(IGRAPHICS_VIEW*) mView createPopupMenu: menu: areaRect];
546  }
547 
548  //synchronous
549  if(pReturnMenu && pReturnMenu->GetFunction())
550  pReturnMenu->ExecFunction();
551 
552  return pReturnMenu;
553 }
554 
555 void IGraphicsMac::CreatePlatformTextEntry(int paramIdx, const IText& text, const IRECT& bounds, int length, const char* str)
556 {
557  if (mView)
558  {
559  NSRect areaRect = ToNSRect(this, bounds);
560  [(IGRAPHICS_VIEW*) mView createTextEntry: paramIdx : text: str: length: areaRect];
561  }
562 }
563 
564 ECursor IGraphicsMac::SetMouseCursor(ECursor cursorType)
565 {
566  if (mView)
567  [(IGRAPHICS_VIEW*) mView setMouseCursor: cursorType];
568 
569  return IGraphics::SetMouseCursor(cursorType);
570 }
571 
572 bool IGraphicsMac::OpenURL(const char* url, const char* msgWindowTitle, const char* confirmMsg, const char* errMsgOnFailure)
573 {
574  #pragma REMINDER("Warning and error messages for OpenURL not implemented")
575  NSURL* pNSURL = nullptr;
576  if (strstr(url, "http"))
577  pNSURL = [NSURL URLWithString:[NSString stringWithCString:url encoding:NSUTF8StringEncoding]];
578  else
579  pNSURL = [NSURL fileURLWithPath:[NSString stringWithCString:url encoding:NSUTF8StringEncoding]];
580 
581  if (pNSURL)
582  {
583  bool ok = ([[NSWorkspace sharedWorkspace] openURL:pNSURL]);
584  return ok;
585  }
586  return true;
587 }
588 
589 void* IGraphicsMac::GetWindow()
590 {
591  if (mView) return mView;
592  else return 0;
593 }
594 
595 // static
596 int IGraphicsMac::GetUserOSVersion() // Returns a number like 0x1050 (10.5).
597 {
598  return (int) GetSystemVersion();
599 }
600 
601 bool IGraphicsMac::GetTextFromClipboard(WDL_String& str)
602 {
603  NSString* pTextOnClipboard = [[NSPasteboard generalPasteboard] stringForType: NSStringPboardType];
604 
605  if (pTextOnClipboard == nil)
606  {
607  str.Set("");
608  return false;
609  }
610  else
611  {
612  str.Set([pTextOnClipboard UTF8String]);
613  return true;
614  }
615 }
616 
617 bool IGraphicsMac::SetTextInClipboard(const char* str)
618 {
619  NSString* pTextForClipboard = [NSString stringWithUTF8String:str];
620  [[NSPasteboard generalPasteboard] clearContents];
621  return [[NSPasteboard generalPasteboard] setString:pTextForClipboard forType:NSStringPboardType];
622 }
623 
624 void IGraphicsMac::CreatePlatformImGui()
625 {
626 #if defined IGRAPHICS_IMGUI
627  #if defined IGRAPHICS_SKIA && IGRAPHICS_CPU
628  #define USE_IGRAPHICS_IMGUIVIEW 1
629  #elif defined IGRAPHICS_NANOVG && IGRAPHICS_METAL
630  #define USE_IGRAPHICS_IMGUIVIEW 1
631 #else
632  #define USE_IGRAPHICS_IMGUIVIEW 0
633 #endif
634 
635 #if USE_IGRAPHICS_IMGUIVIEW
636  if(mView)
637  {
638  IGRAPHICS_VIEW* pView = (IGRAPHICS_VIEW*) mView;
639 
640  IGRAPHICS_IMGUIVIEW* pImGuiView = [[IGRAPHICS_IMGUIVIEW alloc] initWithIGraphicsView:pView];
641  [pView addSubview: pImGuiView];
642  mImGuiView = pImGuiView;
643  }
644 #endif
645 #endif // IGRAPHICS_IMGUI
646 }
647 
648 #if defined IGRAPHICS_NANOVG
649  #include "IGraphicsNanoVG.cpp"
650 #elif defined IGRAPHICS_SKIA
651  #include "IGraphicsSkia.cpp"
652 #else
653  #error Either NO_IGRAPHICS or one and only one choice of graphics library must be defined!
654 #endif
The lowest level base class of an IGraphics control.
Definition: IControl.h:42
Used to manage a rectangular area, independent of draw class/platform.
virtual ECursor SetMouseCursor(ECursor cursorType=ECursor::ARROW)
Sets the mouse cursor to one of ECursor (implementations should return the result of the base impleme...
Definition: IGraphics.h:805
Used to manage color data, independent of draw class/platform.
This file contains the base IControl implementation, along with some base classes for specific types ...
An editor delegate base class for a SOMETHING that uses IGraphics for it&#39;s UI.
A class for setting the contents of a pop up menu.
IText is used to manage font and text/text entry style for a piece of text on the UI...
bool Empty() const
A base control for a pop-up menu/drop-down list that stays within the bounds of the IGraphics context...
void DesktopPath(WDL_String &path)