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.

622 lines
22KB

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