The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

610 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. void LookAndFeel::playAlertSound()
  22. {
  23. NSBeep();
  24. }
  25. //==============================================================================
  26. class OSXMessageBox : private AsyncUpdater
  27. {
  28. public:
  29. OSXMessageBox (AlertWindow::AlertIconType type, const String& t, const String& m,
  30. const char* b1, const char* b2, const char* b3,
  31. ModalComponentManager::Callback* c, const bool runAsync)
  32. : iconType (type), title (t), message (m), callback (c),
  33. button1 (b1), button2 (b2), button3 (b3)
  34. {
  35. if (runAsync)
  36. triggerAsyncUpdate();
  37. }
  38. int getResult() const
  39. {
  40. switch (getRawResult())
  41. {
  42. case NSAlertFirstButtonReturn: return 1;
  43. case NSAlertThirdButtonReturn: return 2;
  44. default: return 0;
  45. }
  46. }
  47. static int show (AlertWindow::AlertIconType iconType, const String& title, const String& message,
  48. ModalComponentManager::Callback* callback, const char* b1, const char* b2, const char* b3,
  49. bool runAsync)
  50. {
  51. std::unique_ptr<OSXMessageBox> mb (new OSXMessageBox (iconType, title, message, b1, b2, b3,
  52. callback, runAsync));
  53. if (! runAsync)
  54. return mb->getResult();
  55. mb.release();
  56. return 0;
  57. }
  58. private:
  59. AlertWindow::AlertIconType iconType;
  60. String title, message;
  61. std::unique_ptr<ModalComponentManager::Callback> callback;
  62. const char* button1;
  63. const char* button2;
  64. const char* button3;
  65. void handleAsyncUpdate() override
  66. {
  67. auto result = getResult();
  68. if (callback != nullptr)
  69. callback->modalStateFinished (result);
  70. delete this;
  71. }
  72. NSInteger getRawResult() const
  73. {
  74. NSAlert* alert = [[[NSAlert alloc] init] autorelease];
  75. [alert setMessageText: juceStringToNS (title)];
  76. [alert setInformativeText: juceStringToNS (message)];
  77. [alert setAlertStyle: iconType == AlertWindow::WarningIcon ? NSAlertStyleCritical
  78. : NSAlertStyleInformational];
  79. addButton (alert, button1);
  80. addButton (alert, button2);
  81. addButton (alert, button3);
  82. return [alert runModal];
  83. }
  84. static void addButton (NSAlert* alert, const char* button)
  85. {
  86. if (button != nullptr)
  87. [alert addButtonWithTitle: juceStringToNS (TRANS (button))];
  88. }
  89. };
  90. #if JUCE_MODAL_LOOPS_PERMITTED
  91. void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType iconType,
  92. const String& title, const String& message,
  93. Component* /*associatedComponent*/)
  94. {
  95. OSXMessageBox::show (iconType, title, message, nullptr, "OK", nullptr, nullptr, false);
  96. }
  97. #endif
  98. void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIconType iconType,
  99. const String& title, const String& message,
  100. Component* /*associatedComponent*/,
  101. ModalComponentManager::Callback* callback)
  102. {
  103. OSXMessageBox::show (iconType, title, message, callback, "OK", nullptr, nullptr, true);
  104. }
  105. bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType iconType,
  106. const String& title, const String& message,
  107. Component* /*associatedComponent*/,
  108. ModalComponentManager::Callback* callback)
  109. {
  110. return OSXMessageBox::show (iconType, title, message, callback,
  111. "OK", "Cancel", nullptr, callback != nullptr) == 1;
  112. }
  113. int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconType iconType,
  114. const String& title, const String& message,
  115. Component* /*associatedComponent*/,
  116. ModalComponentManager::Callback* callback)
  117. {
  118. return OSXMessageBox::show (iconType, title, message, callback,
  119. "Yes", "Cancel", "No", callback != nullptr);
  120. }
  121. int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (AlertWindow::AlertIconType iconType,
  122. const String& title, const String& message,
  123. Component* /*associatedComponent*/,
  124. ModalComponentManager::Callback* callback)
  125. {
  126. return OSXMessageBox::show (iconType, title, message, callback,
  127. "Yes", "No", nullptr, callback != nullptr);
  128. }
  129. //==============================================================================
  130. static NSRect getDragRect (NSView* view, NSEvent* event)
  131. {
  132. auto eventPos = [event locationInWindow];
  133. return [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
  134. fromView: nil];
  135. }
  136. static NSView* getNSViewForDragEvent (Component* sourceComp)
  137. {
  138. if (sourceComp == nullptr)
  139. if (auto* draggingSource = Desktop::getInstance().getDraggingMouseSource(0))
  140. sourceComp = draggingSource->getComponentUnderMouse();
  141. if (sourceComp != nullptr)
  142. return (NSView*) sourceComp->getWindowHandle();
  143. jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event!
  144. return nil;
  145. }
  146. struct TextDragDataProviderClass : public ObjCClass<NSObject>
  147. {
  148. TextDragDataProviderClass() : ObjCClass<NSObject> ("JUCE_NSTextDragDataProvider_")
  149. {
  150. addIvar<String*> ("text");
  151. addMethod (@selector (dealloc), dealloc, "v@:");
  152. addMethod (@selector (pasteboard:item:provideDataForType:), provideDataForType, "v@:@@@");
  153. addProtocol (@protocol (NSPasteboardItemDataProvider));
  154. registerClass();
  155. }
  156. static void setText (id self, const String& text)
  157. {
  158. object_setInstanceVariable (self, "text", new String (text));
  159. }
  160. private:
  161. static void dealloc (id self, SEL)
  162. {
  163. delete getIvar<String*> (self, "text");
  164. sendSuperclassMessage (self, @selector (dealloc));
  165. }
  166. static void provideDataForType (id self, SEL, NSPasteboard* sender, NSPasteboardItem*, NSString* type)
  167. {
  168. if ([type compare: NSPasteboardTypeString] == NSOrderedSame)
  169. if (auto* text = getIvar<String*> (self, "text"))
  170. [sender setData: [juceStringToNS (*text) dataUsingEncoding: NSUTF8StringEncoding]
  171. forType: NSPasteboardTypeString];
  172. }
  173. };
  174. bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComponent)
  175. {
  176. if (text.isEmpty())
  177. return false;
  178. if (auto* view = getNSViewForDragEvent (sourceComponent))
  179. {
  180. JUCE_AUTORELEASEPOOL
  181. {
  182. if (auto* event = [[view window] currentEvent])
  183. {
  184. static TextDragDataProviderClass dataProviderClass;
  185. id delegate = [dataProviderClass.createInstance() init];
  186. TextDragDataProviderClass::setText (delegate, text);
  187. auto* pasteboardItem = [[NSPasteboardItem new] autorelease];
  188. [pasteboardItem setDataProvider: delegate
  189. forTypes: [NSArray arrayWithObjects: NSPasteboardTypeString, nil]];
  190. auto* dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter: pasteboardItem] autorelease];
  191. NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: nsEmptyString()];
  192. [dragItem setDraggingFrame: getDragRect (view, event) contents: image];
  193. auto* draggingSession = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem]
  194. event: event
  195. source: delegate];
  196. draggingSession.animatesToStartingPositionsOnCancelOrFail = YES;
  197. draggingSession.draggingFormation = NSDraggingFormationNone;
  198. return true;
  199. }
  200. }
  201. }
  202. return false;
  203. }
  204. struct NSDraggingSourceHelper : public ObjCClass<NSObject<NSDraggingSource>>
  205. {
  206. NSDraggingSourceHelper() : ObjCClass<NSObject<NSDraggingSource>> ("JUCENSDraggingSourceHelper_")
  207. {
  208. addMethod (@selector (draggingSession:sourceOperationMaskForDraggingContext:), sourceOperationMaskForDraggingContext, "c@:@@");
  209. registerClass();
  210. }
  211. static NSDragOperation sourceOperationMaskForDraggingContext (id, SEL, NSDraggingSession*, NSDraggingContext)
  212. {
  213. return NSDragOperationCopy;
  214. }
  215. };
  216. static NSDraggingSourceHelper draggingSourceHelper;
  217. bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool /*canMoveFiles*/,
  218. Component* sourceComponent)
  219. {
  220. if (files.isEmpty())
  221. return false;
  222. if (auto* view = getNSViewForDragEvent (sourceComponent))
  223. {
  224. JUCE_AUTORELEASEPOOL
  225. {
  226. if (auto* event = [[view window] currentEvent])
  227. {
  228. auto* dragItems = [[[NSMutableArray alloc] init] autorelease];
  229. for (auto& filename : files)
  230. {
  231. auto* nsFilename = juceStringToNS (filename);
  232. auto* fileURL = [NSURL fileURLWithPath: nsFilename];
  233. auto* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: fileURL];
  234. auto eventPos = [event locationInWindow];
  235. auto dragRect = [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
  236. fromView: nil];
  237. auto* dragImage = [[NSWorkspace sharedWorkspace] iconForFile: nsFilename];
  238. [dragItem setDraggingFrame: dragRect
  239. contents: dragImage];
  240. [dragItems addObject: dragItem];
  241. [dragItem release];
  242. }
  243. auto* helper = [draggingSourceHelper.createInstance() autorelease];
  244. return [view beginDraggingSessionWithItems: dragItems
  245. event: event
  246. source: helper];
  247. }
  248. }
  249. }
  250. return false;
  251. }
  252. //==============================================================================
  253. bool Desktop::canUseSemiTransparentWindows() noexcept
  254. {
  255. return true;
  256. }
  257. Point<float> MouseInputSource::getCurrentRawMousePosition()
  258. {
  259. JUCE_AUTORELEASEPOOL
  260. {
  261. auto p = [NSEvent mouseLocation];
  262. return { (float) p.x, (float) (getMainScreenHeight() - p.y) };
  263. }
  264. }
  265. void MouseInputSource::setRawMousePosition (Point<float> newPosition)
  266. {
  267. // this rubbish needs to be done around the warp call, to avoid causing a
  268. // bizarre glitch..
  269. CGAssociateMouseAndMouseCursorPosition (false);
  270. CGWarpMouseCursorPosition (convertToCGPoint (newPosition));
  271. CGAssociateMouseAndMouseCursorPosition (true);
  272. }
  273. double Desktop::getDefaultMasterScale()
  274. {
  275. return 1.0;
  276. }
  277. Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
  278. {
  279. return upright;
  280. }
  281. //==============================================================================
  282. #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)
  283. #define JUCE_USE_IOPM_SCREENSAVER_DEFEAT 1
  284. #endif
  285. #if ! (defined (JUCE_USE_IOPM_SCREENSAVER_DEFEAT) || defined (__POWER__))
  286. extern "C" { extern OSErr UpdateSystemActivity (UInt8); } // Some versions of the SDK omit this function..
  287. #endif
  288. class ScreenSaverDefeater : public Timer
  289. {
  290. public:
  291. #if JUCE_USE_IOPM_SCREENSAVER_DEFEAT
  292. ScreenSaverDefeater()
  293. {
  294. startTimer (5000);
  295. timerCallback();
  296. }
  297. void timerCallback() override
  298. {
  299. if (Process::isForegroundProcess())
  300. {
  301. if (assertion == nullptr)
  302. assertion.reset (new PMAssertion());
  303. }
  304. else
  305. {
  306. assertion.reset();
  307. }
  308. }
  309. struct PMAssertion
  310. {
  311. PMAssertion() : assertionID (kIOPMNullAssertionID)
  312. {
  313. IOReturn res = IOPMAssertionCreateWithName (kIOPMAssertionTypePreventUserIdleDisplaySleep,
  314. kIOPMAssertionLevelOn,
  315. CFSTR ("JUCE Playback"),
  316. &assertionID);
  317. jassert (res == kIOReturnSuccess); ignoreUnused (res);
  318. }
  319. ~PMAssertion()
  320. {
  321. if (assertionID != kIOPMNullAssertionID)
  322. IOPMAssertionRelease (assertionID);
  323. }
  324. IOPMAssertionID assertionID;
  325. };
  326. std::unique_ptr<PMAssertion> assertion;
  327. #else
  328. ScreenSaverDefeater()
  329. {
  330. startTimer (10000);
  331. timerCallback();
  332. }
  333. void timerCallback() override
  334. {
  335. if (Process::isForegroundProcess())
  336. UpdateSystemActivity (1 /*UsrActivity*/);
  337. }
  338. #endif
  339. };
  340. static std::unique_ptr<ScreenSaverDefeater> screenSaverDefeater;
  341. void Desktop::setScreenSaverEnabled (const bool isEnabled)
  342. {
  343. if (isEnabled)
  344. screenSaverDefeater.reset();
  345. else if (screenSaverDefeater == nullptr)
  346. screenSaverDefeater.reset (new ScreenSaverDefeater());
  347. }
  348. bool Desktop::isScreenSaverEnabled()
  349. {
  350. return screenSaverDefeater == nullptr;
  351. }
  352. //==============================================================================
  353. struct DisplaySettingsChangeCallback : private DeletedAtShutdown
  354. {
  355. DisplaySettingsChangeCallback()
  356. {
  357. CGDisplayRegisterReconfigurationCallback (displayReconfigurationCallBack, 0);
  358. }
  359. ~DisplaySettingsChangeCallback()
  360. {
  361. CGDisplayRemoveReconfigurationCallback (displayReconfigurationCallBack, 0);
  362. clearSingletonInstance();
  363. }
  364. static void displayReconfigurationCallBack (CGDirectDisplayID, CGDisplayChangeSummaryFlags, void*)
  365. {
  366. const_cast<Desktop::Displays&> (Desktop::getInstance().getDisplays()).refresh();
  367. }
  368. JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (DisplaySettingsChangeCallback)
  369. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DisplaySettingsChangeCallback)
  370. };
  371. JUCE_IMPLEMENT_SINGLETON (DisplaySettingsChangeCallback)
  372. static Rectangle<int> convertDisplayRect (NSRect r, CGFloat mainScreenBottom)
  373. {
  374. r.origin.y = mainScreenBottom - (r.origin.y + r.size.height);
  375. return convertToRectInt (r);
  376. }
  377. static Desktop::Displays::Display getDisplayFromScreen (NSScreen* s, CGFloat& mainScreenBottom, const float masterScale)
  378. {
  379. Desktop::Displays::Display d;
  380. d.isMain = (mainScreenBottom == 0);
  381. if (d.isMain)
  382. mainScreenBottom = [s frame].size.height;
  383. d.userArea = convertDisplayRect ([s visibleFrame], mainScreenBottom) / masterScale;
  384. d.totalArea = convertDisplayRect ([s frame], mainScreenBottom) / masterScale;
  385. d.scale = masterScale;
  386. #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
  387. if ([s respondsToSelector: @selector (backingScaleFactor)])
  388. d.scale *= s.backingScaleFactor;
  389. #endif
  390. NSSize dpi = [[[s deviceDescription] objectForKey: NSDeviceResolution] sizeValue];
  391. d.dpi = (dpi.width + dpi.height) / 2.0;
  392. return d;
  393. }
  394. void Desktop::Displays::findDisplays (const float masterScale)
  395. {
  396. JUCE_AUTORELEASEPOOL
  397. {
  398. DisplaySettingsChangeCallback::getInstance();
  399. CGFloat mainScreenBottom = 0;
  400. for (NSScreen* s in [NSScreen screens])
  401. displays.add (getDisplayFromScreen (s, mainScreenBottom, masterScale));
  402. }
  403. }
  404. //==============================================================================
  405. bool juce_areThereAnyAlwaysOnTopWindows()
  406. {
  407. for (NSWindow* window in [NSApp windows])
  408. if ([window level] > NSNormalWindowLevel)
  409. return true;
  410. return false;
  411. }
  412. //==============================================================================
  413. static void selectImageForDrawing (const Image& image)
  414. {
  415. [NSGraphicsContext saveGraphicsState];
  416. [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (image)
  417. flipped: false]];
  418. }
  419. static void releaseImageAfterDrawing()
  420. {
  421. [[NSGraphicsContext currentContext] flushGraphics];
  422. [NSGraphicsContext restoreGraphicsState];
  423. }
  424. Image juce_createIconForFile (const File& file)
  425. {
  426. JUCE_AUTORELEASEPOOL
  427. {
  428. NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: juceStringToNS (file.getFullPathName())];
  429. Image result (Image::ARGB, (int) [image size].width, (int) [image size].height, true);
  430. selectImageForDrawing (result);
  431. [image drawAtPoint: NSMakePoint (0, 0)
  432. fromRect: NSMakeRect (0, 0, [image size].width, [image size].height)
  433. operation: NSCompositingOperationSourceOver fraction: 1.0f];
  434. releaseImageAfterDrawing();
  435. return result;
  436. }
  437. }
  438. static Image createNSWindowSnapshot (NSWindow* nsWindow)
  439. {
  440. JUCE_AUTORELEASEPOOL
  441. {
  442. CGImageRef screenShot = CGWindowListCreateImage (CGRectNull,
  443. kCGWindowListOptionIncludingWindow,
  444. (CGWindowID) [nsWindow windowNumber],
  445. kCGWindowImageBoundsIgnoreFraming);
  446. NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: screenShot];
  447. Image result (Image::ARGB, (int) [bitmapRep size].width, (int) [bitmapRep size].height, true);
  448. selectImageForDrawing (result);
  449. [bitmapRep drawAtPoint: NSMakePoint (0, 0)];
  450. releaseImageAfterDrawing();
  451. [bitmapRep release];
  452. CGImageRelease (screenShot);
  453. return result;
  454. }
  455. }
  456. Image createSnapshotOfNativeWindow (void*);
  457. Image createSnapshotOfNativeWindow (void* nativeWindowHandle)
  458. {
  459. if (id windowOrView = (id) nativeWindowHandle)
  460. {
  461. if ([windowOrView isKindOfClass: [NSWindow class]])
  462. return createNSWindowSnapshot ((NSWindow*) windowOrView);
  463. if ([windowOrView isKindOfClass: [NSView class]])
  464. return createNSWindowSnapshot ([(NSView*) windowOrView window]);
  465. }
  466. return {};
  467. }
  468. //==============================================================================
  469. void SystemClipboard::copyTextToClipboard (const String& text)
  470. {
  471. NSPasteboard* pb = [NSPasteboard generalPasteboard];
  472. [pb declareTypes: [NSArray arrayWithObject: NSStringPboardType]
  473. owner: nil];
  474. [pb setString: juceStringToNS (text)
  475. forType: NSStringPboardType];
  476. }
  477. String SystemClipboard::getTextFromClipboard()
  478. {
  479. return nsStringToJuce ([[NSPasteboard generalPasteboard] stringForType: NSStringPboardType]);
  480. }
  481. void Process::setDockIconVisible (bool isVisible)
  482. {
  483. ProcessSerialNumber psn { 0, kCurrentProcess };
  484. OSStatus err = TransformProcessType (&psn, isVisible ? kProcessTransformToForegroundApplication
  485. : kProcessTransformToUIElementApplication);
  486. jassert (err == 0);
  487. ignoreUnused (err);
  488. }
  489. bool Desktop::isOSXDarkModeActive()
  490. {
  491. return [[[NSUserDefaults standardUserDefaults] stringForKey: nsStringLiteral ("AppleInterfaceStyle")]
  492. isEqualToString: nsStringLiteral ("Dark")];
  493. }
  494. } // namespace juce