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.

647 lines
23KB

  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 NSDraggingSourceHelper : public ObjCClass<NSObject<NSDraggingSource>>
  147. {
  148. NSDraggingSourceHelper() : ObjCClass<NSObject<NSDraggingSource>> ("JUCENSDraggingSourceHelper_")
  149. {
  150. addIvar<std::function<void()>*> ("callback");
  151. addIvar<String*> ("text");
  152. addIvar<NSDragOperation*> ("operation");
  153. addMethod (@selector (dealloc), dealloc, "v@:");
  154. addMethod (@selector (pasteboard:item:provideDataForType:), provideDataForType, "v@:@@@");
  155. addMethod (@selector (draggingSession:sourceOperationMaskForDraggingContext:), sourceOperationMaskForDraggingContext, "c@:@@");
  156. addMethod (@selector (draggingSession:endedAtPoint:operation:), draggingSessionEnded, "v@:@@@");
  157. addProtocol (@protocol (NSPasteboardItemDataProvider));
  158. registerClass();
  159. }
  160. static void setText (id self, const String& text)
  161. {
  162. object_setInstanceVariable (self, "text", new String (text));
  163. }
  164. static void setCompletionCallback (id self, std::function<void()> cb)
  165. {
  166. object_setInstanceVariable (self, "callback", new std::function<void()> (cb));
  167. }
  168. static void setDragOperation (id self, NSDragOperation op)
  169. {
  170. object_setInstanceVariable (self, "operation", new NSDragOperation (op));
  171. }
  172. private:
  173. static void dealloc (id self, SEL)
  174. {
  175. delete getIvar<String*> (self, "text");
  176. delete getIvar<std::function<void()>*> (self, "callback");
  177. delete getIvar<NSDragOperation*> (self, "operation");
  178. sendSuperclassMessage (self, @selector (dealloc));
  179. }
  180. static void provideDataForType (id self, SEL, NSPasteboard* sender, NSPasteboardItem*, NSString* type)
  181. {
  182. if ([type compare: NSPasteboardTypeString] == NSOrderedSame)
  183. if (auto* text = getIvar<String*> (self, "text"))
  184. [sender setData: [juceStringToNS (*text) dataUsingEncoding: NSUTF8StringEncoding]
  185. forType: NSPasteboardTypeString];
  186. }
  187. static NSDragOperation sourceOperationMaskForDraggingContext (id self, SEL, NSDraggingSession*, NSDraggingContext)
  188. {
  189. return *getIvar<NSDragOperation*> (self, "operation");
  190. }
  191. static void draggingSessionEnded (id self, SEL, NSDraggingSession*, NSPoint p, NSDragOperation)
  192. {
  193. // Our view doesn't receive a mouse up when the drag ends so we need to generate one here and send it...
  194. if (auto* view = getNSViewForDragEvent (nullptr))
  195. {
  196. auto* cgEvent = CGEventCreateMouseEvent (nullptr, kCGEventLeftMouseUp, CGPointMake (p.x, p.y), kCGMouseButtonLeft);
  197. [view mouseUp: [NSEvent eventWithCGEvent:cgEvent]];
  198. }
  199. if (auto* cb = getIvar<std::function<void()>*> (self, "callback"))
  200. cb->operator()();
  201. }
  202. };
  203. static NSDraggingSourceHelper draggingSourceHelper;
  204. bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComponent,
  205. std::function<void()> callback)
  206. {
  207. if (text.isEmpty())
  208. return false;
  209. if (auto* view = getNSViewForDragEvent (sourceComponent))
  210. {
  211. JUCE_AUTORELEASEPOOL
  212. {
  213. if (auto* event = [[view window] currentEvent])
  214. {
  215. id helper = [draggingSourceHelper.createInstance() init];
  216. NSDraggingSourceHelper::setText (helper, text);
  217. NSDraggingSourceHelper::setDragOperation (helper, NSDragOperationCopy);
  218. if (callback != nullptr)
  219. NSDraggingSourceHelper::setCompletionCallback (helper, callback);
  220. auto* pasteboardItem = [[NSPasteboardItem new] autorelease];
  221. [pasteboardItem setDataProvider: helper
  222. forTypes: [NSArray arrayWithObjects: NSPasteboardTypeString, nil]];
  223. auto* dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter: pasteboardItem] autorelease];
  224. NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: nsEmptyString()];
  225. [dragItem setDraggingFrame: getDragRect (view, event) contents: image];
  226. if (auto* session = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem]
  227. event: event
  228. source: helper])
  229. {
  230. session.animatesToStartingPositionsOnCancelOrFail = YES;
  231. session.draggingFormation = NSDraggingFormationNone;
  232. return true;
  233. }
  234. }
  235. }
  236. }
  237. return false;
  238. }
  239. bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool canMoveFiles,
  240. Component* sourceComponent, std::function<void()> callback)
  241. {
  242. if (files.isEmpty())
  243. return false;
  244. if (auto* view = getNSViewForDragEvent (sourceComponent))
  245. {
  246. JUCE_AUTORELEASEPOOL
  247. {
  248. if (auto* event = [[view window] currentEvent])
  249. {
  250. auto* dragItems = [[[NSMutableArray alloc] init] autorelease];
  251. for (auto& filename : files)
  252. {
  253. auto* nsFilename = juceStringToNS (filename);
  254. auto* fileURL = [NSURL fileURLWithPath: nsFilename];
  255. auto* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: fileURL];
  256. auto eventPos = [event locationInWindow];
  257. auto dragRect = [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
  258. fromView: nil];
  259. auto* dragImage = [[NSWorkspace sharedWorkspace] iconForFile: nsFilename];
  260. [dragItem setDraggingFrame: dragRect
  261. contents: dragImage];
  262. [dragItems addObject: dragItem];
  263. [dragItem release];
  264. }
  265. auto* helper = [draggingSourceHelper.createInstance() autorelease];
  266. if (callback != nullptr)
  267. NSDraggingSourceHelper::setCompletionCallback (helper, callback);
  268. NSDraggingSourceHelper::setDragOperation (helper, canMoveFiles ? NSDragOperationMove
  269. : NSDragOperationCopy);
  270. return [view beginDraggingSessionWithItems: dragItems
  271. event: event
  272. source: helper] != nullptr;
  273. }
  274. }
  275. }
  276. return false;
  277. }
  278. //==============================================================================
  279. bool Desktop::canUseSemiTransparentWindows() noexcept
  280. {
  281. return true;
  282. }
  283. Point<float> MouseInputSource::getCurrentRawMousePosition()
  284. {
  285. JUCE_AUTORELEASEPOOL
  286. {
  287. auto p = [NSEvent mouseLocation];
  288. return { (float) p.x, (float) (getMainScreenHeight() - p.y) };
  289. }
  290. }
  291. void MouseInputSource::setRawMousePosition (Point<float> newPosition)
  292. {
  293. // this rubbish needs to be done around the warp call, to avoid causing a
  294. // bizarre glitch..
  295. CGAssociateMouseAndMouseCursorPosition (false);
  296. CGWarpMouseCursorPosition (convertToCGPoint (newPosition));
  297. CGAssociateMouseAndMouseCursorPosition (true);
  298. }
  299. double Desktop::getDefaultMasterScale()
  300. {
  301. return 1.0;
  302. }
  303. Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
  304. {
  305. return upright;
  306. }
  307. //==============================================================================
  308. #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)
  309. #define JUCE_USE_IOPM_SCREENSAVER_DEFEAT 1
  310. #endif
  311. #if ! (defined (JUCE_USE_IOPM_SCREENSAVER_DEFEAT) || defined (__POWER__))
  312. extern "C" { extern OSErr UpdateSystemActivity (UInt8); } // Some versions of the SDK omit this function..
  313. #endif
  314. class ScreenSaverDefeater : public Timer
  315. {
  316. public:
  317. #if JUCE_USE_IOPM_SCREENSAVER_DEFEAT
  318. ScreenSaverDefeater()
  319. {
  320. startTimer (5000);
  321. timerCallback();
  322. }
  323. void timerCallback() override
  324. {
  325. if (Process::isForegroundProcess())
  326. {
  327. if (assertion == nullptr)
  328. assertion.reset (new PMAssertion());
  329. }
  330. else
  331. {
  332. assertion.reset();
  333. }
  334. }
  335. struct PMAssertion
  336. {
  337. PMAssertion() : assertionID (kIOPMNullAssertionID)
  338. {
  339. IOReturn res = IOPMAssertionCreateWithName (kIOPMAssertionTypePreventUserIdleDisplaySleep,
  340. kIOPMAssertionLevelOn,
  341. CFSTR ("JUCE Playback"),
  342. &assertionID);
  343. jassert (res == kIOReturnSuccess); ignoreUnused (res);
  344. }
  345. ~PMAssertion()
  346. {
  347. if (assertionID != kIOPMNullAssertionID)
  348. IOPMAssertionRelease (assertionID);
  349. }
  350. IOPMAssertionID assertionID;
  351. };
  352. std::unique_ptr<PMAssertion> assertion;
  353. #else
  354. ScreenSaverDefeater()
  355. {
  356. startTimer (10000);
  357. timerCallback();
  358. }
  359. void timerCallback() override
  360. {
  361. if (Process::isForegroundProcess())
  362. UpdateSystemActivity (1 /*UsrActivity*/);
  363. }
  364. #endif
  365. };
  366. static std::unique_ptr<ScreenSaverDefeater> screenSaverDefeater;
  367. void Desktop::setScreenSaverEnabled (const bool isEnabled)
  368. {
  369. if (isEnabled)
  370. screenSaverDefeater.reset();
  371. else if (screenSaverDefeater == nullptr)
  372. screenSaverDefeater.reset (new ScreenSaverDefeater());
  373. }
  374. bool Desktop::isScreenSaverEnabled()
  375. {
  376. return screenSaverDefeater == nullptr;
  377. }
  378. //==============================================================================
  379. struct DisplaySettingsChangeCallback : private DeletedAtShutdown
  380. {
  381. DisplaySettingsChangeCallback()
  382. {
  383. CGDisplayRegisterReconfigurationCallback (displayReconfigurationCallBack, nullptr);
  384. }
  385. ~DisplaySettingsChangeCallback()
  386. {
  387. CGDisplayRemoveReconfigurationCallback (displayReconfigurationCallBack, nullptr);
  388. clearSingletonInstance();
  389. }
  390. static void displayReconfigurationCallBack (CGDirectDisplayID, CGDisplayChangeSummaryFlags, void*)
  391. {
  392. const_cast<Displays&> (Desktop::getInstance().getDisplays()).refresh();
  393. }
  394. JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (DisplaySettingsChangeCallback)
  395. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DisplaySettingsChangeCallback)
  396. };
  397. JUCE_IMPLEMENT_SINGLETON (DisplaySettingsChangeCallback)
  398. static Rectangle<int> convertDisplayRect (NSRect r, CGFloat mainScreenBottom)
  399. {
  400. r.origin.y = mainScreenBottom - (r.origin.y + r.size.height);
  401. return convertToRectInt (r);
  402. }
  403. static Displays::Display getDisplayFromScreen (NSScreen* s, CGFloat& mainScreenBottom, const float masterScale)
  404. {
  405. Displays::Display d;
  406. d.isMain = (mainScreenBottom == 0);
  407. if (d.isMain)
  408. mainScreenBottom = [s frame].size.height;
  409. d.userArea = convertDisplayRect ([s visibleFrame], mainScreenBottom) / masterScale;
  410. d.totalArea = convertDisplayRect ([s frame], mainScreenBottom) / masterScale;
  411. d.scale = masterScale;
  412. #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
  413. if ([s respondsToSelector: @selector (backingScaleFactor)])
  414. d.scale *= s.backingScaleFactor;
  415. #endif
  416. NSSize dpi = [[[s deviceDescription] objectForKey: NSDeviceResolution] sizeValue];
  417. d.dpi = (dpi.width + dpi.height) / 2.0;
  418. return d;
  419. }
  420. void Displays::findDisplays (const float masterScale)
  421. {
  422. JUCE_AUTORELEASEPOOL
  423. {
  424. DisplaySettingsChangeCallback::getInstance();
  425. CGFloat mainScreenBottom = 0;
  426. for (NSScreen* s in [NSScreen screens])
  427. displays.add (getDisplayFromScreen (s, mainScreenBottom, masterScale));
  428. }
  429. }
  430. //==============================================================================
  431. bool juce_areThereAnyAlwaysOnTopWindows()
  432. {
  433. for (NSWindow* window in [NSApp windows])
  434. if ([window level] > NSNormalWindowLevel)
  435. return true;
  436. return false;
  437. }
  438. //==============================================================================
  439. static void selectImageForDrawing (const Image& image)
  440. {
  441. [NSGraphicsContext saveGraphicsState];
  442. [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (image)
  443. flipped: false]];
  444. }
  445. static void releaseImageAfterDrawing()
  446. {
  447. [[NSGraphicsContext currentContext] flushGraphics];
  448. [NSGraphicsContext restoreGraphicsState];
  449. }
  450. Image juce_createIconForFile (const File& file)
  451. {
  452. JUCE_AUTORELEASEPOOL
  453. {
  454. NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: juceStringToNS (file.getFullPathName())];
  455. Image result (Image::ARGB, (int) [image size].width, (int) [image size].height, true);
  456. selectImageForDrawing (result);
  457. [image drawAtPoint: NSMakePoint (0, 0)
  458. fromRect: NSMakeRect (0, 0, [image size].width, [image size].height)
  459. operation: NSCompositingOperationSourceOver fraction: 1.0f];
  460. releaseImageAfterDrawing();
  461. return result;
  462. }
  463. }
  464. static Image createNSWindowSnapshot (NSWindow* nsWindow)
  465. {
  466. JUCE_AUTORELEASEPOOL
  467. {
  468. CGImageRef screenShot = CGWindowListCreateImage (CGRectNull,
  469. kCGWindowListOptionIncludingWindow,
  470. (CGWindowID) [nsWindow windowNumber],
  471. kCGWindowImageBoundsIgnoreFraming);
  472. NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: screenShot];
  473. Image result (Image::ARGB, (int) [bitmapRep size].width, (int) [bitmapRep size].height, true);
  474. selectImageForDrawing (result);
  475. [bitmapRep drawAtPoint: NSMakePoint (0, 0)];
  476. releaseImageAfterDrawing();
  477. [bitmapRep release];
  478. CGImageRelease (screenShot);
  479. return result;
  480. }
  481. }
  482. Image createSnapshotOfNativeWindow (void*);
  483. Image createSnapshotOfNativeWindow (void* nativeWindowHandle)
  484. {
  485. if (id windowOrView = (id) nativeWindowHandle)
  486. {
  487. if ([windowOrView isKindOfClass: [NSWindow class]])
  488. return createNSWindowSnapshot ((NSWindow*) windowOrView);
  489. if ([windowOrView isKindOfClass: [NSView class]])
  490. return createNSWindowSnapshot ([(NSView*) windowOrView window]);
  491. }
  492. return {};
  493. }
  494. //==============================================================================
  495. void SystemClipboard::copyTextToClipboard (const String& text)
  496. {
  497. NSPasteboard* pb = [NSPasteboard generalPasteboard];
  498. [pb declareTypes: [NSArray arrayWithObject: NSStringPboardType]
  499. owner: nil];
  500. [pb setString: juceStringToNS (text)
  501. forType: NSStringPboardType];
  502. }
  503. String SystemClipboard::getTextFromClipboard()
  504. {
  505. return nsStringToJuce ([[NSPasteboard generalPasteboard] stringForType: NSStringPboardType]);
  506. }
  507. void Process::setDockIconVisible (bool isVisible)
  508. {
  509. ProcessSerialNumber psn { 0, kCurrentProcess };
  510. OSStatus err = TransformProcessType (&psn, isVisible ? kProcessTransformToForegroundApplication
  511. : kProcessTransformToUIElementApplication);
  512. jassert (err == 0);
  513. ignoreUnused (err);
  514. }
  515. bool Desktop::isOSXDarkModeActive()
  516. {
  517. return [[[NSUserDefaults standardUserDefaults] stringForKey: nsStringLiteral ("AppleInterfaceStyle")]
  518. isEqualToString: nsStringLiteral ("Dark")];
  519. }
  520. } // namespace juce