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.

2850 lines
107KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 7 technical preview.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For the technical preview this file cannot be licensed commercially.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #include "juce_mac_CGMetalLayerRenderer.h"
  14. @interface NSEvent (DeviceDelta)
  15. - (float)deviceDeltaX;
  16. - (float)deviceDeltaY;
  17. @end
  18. //==============================================================================
  19. namespace juce
  20. {
  21. using AppFocusChangeCallback = void (*)();
  22. extern AppFocusChangeCallback appFocusChangeCallback;
  23. using CheckEventBlockedByModalComps = bool (*) (NSEvent*);
  24. extern CheckEventBlockedByModalComps isEventBlockedByModalComps;
  25. //==============================================================================
  26. static constexpr int translateVirtualToAsciiKeyCode (int keyCode) noexcept
  27. {
  28. switch (keyCode)
  29. {
  30. // The virtual keycodes are from HIToolbox/Events.h
  31. case 0x00: return 'A';
  32. case 0x01: return 'S';
  33. case 0x02: return 'D';
  34. case 0x03: return 'F';
  35. case 0x04: return 'H';
  36. case 0x05: return 'G';
  37. case 0x06: return 'Z';
  38. case 0x07: return 'X';
  39. case 0x08: return 'C';
  40. case 0x09: return 'V';
  41. case 0x0B: return 'B';
  42. case 0x0C: return 'Q';
  43. case 0x0D: return 'W';
  44. case 0x0E: return 'E';
  45. case 0x0F: return 'R';
  46. case 0x10: return 'Y';
  47. case 0x11: return 'T';
  48. case 0x12: return '1';
  49. case 0x13: return '2';
  50. case 0x14: return '3';
  51. case 0x15: return '4';
  52. case 0x16: return '6';
  53. case 0x17: return '5';
  54. case 0x18: return '='; // kVK_ANSI_Equal
  55. case 0x19: return '9';
  56. case 0x1A: return '7';
  57. case 0x1B: return '-'; // kVK_ANSI_Minus
  58. case 0x1C: return '8';
  59. case 0x1D: return '0';
  60. case 0x1E: return ']'; // kVK_ANSI_RightBracket
  61. case 0x1F: return 'O';
  62. case 0x20: return 'U';
  63. case 0x21: return '['; // kVK_ANSI_LeftBracket
  64. case 0x22: return 'I';
  65. case 0x23: return 'P';
  66. case 0x25: return 'L';
  67. case 0x26: return 'J';
  68. case 0x27: return '"'; // kVK_ANSI_Quote
  69. case 0x28: return 'K';
  70. case 0x29: return ';'; // kVK_ANSI_Semicolon
  71. case 0x2A: return '\\'; // kVK_ANSI_Backslash
  72. case 0x2B: return ','; // kVK_ANSI_Comma
  73. case 0x2C: return '/'; // kVK_ANSI_Slash
  74. case 0x2D: return 'N';
  75. case 0x2E: return 'M';
  76. case 0x2F: return '.'; // kVK_ANSI_Period
  77. case 0x32: return '`'; // kVK_ANSI_Grave
  78. default: return keyCode;
  79. }
  80. }
  81. constexpr int extendedKeyModifier = 0x30000;
  82. //==============================================================================
  83. class NSViewComponentPeer : public ComponentPeer
  84. {
  85. public:
  86. NSViewComponentPeer (Component& comp, const int windowStyleFlags, NSView* viewToAttachTo)
  87. : ComponentPeer (comp, windowStyleFlags),
  88. safeComponent (&comp),
  89. isSharedWindow (viewToAttachTo != nil),
  90. lastRepaintTime (Time::getMillisecondCounter())
  91. {
  92. appFocusChangeCallback = appFocusChanged;
  93. isEventBlockedByModalComps = checkEventBlockedByModalComps;
  94. auto r = makeNSRect (component.getLocalBounds());
  95. view = [createViewInstance() initWithFrame: r];
  96. setOwner (view, this);
  97. [view registerForDraggedTypes: getSupportedDragTypes()];
  98. const auto options = NSTrackingMouseEnteredAndExited
  99. | NSTrackingMouseMoved
  100. | NSTrackingEnabledDuringMouseDrag
  101. | NSTrackingActiveAlways
  102. | NSTrackingInVisibleRect;
  103. const NSUniquePtr<NSTrackingArea> trackingArea { [[NSTrackingArea alloc] initWithRect: r
  104. options: options
  105. owner: view
  106. userInfo: nil] };
  107. [view addTrackingArea: trackingArea.get()];
  108. notificationCenter = [NSNotificationCenter defaultCenter];
  109. [notificationCenter addObserver: view
  110. selector: frameChangedSelector
  111. name: NSViewFrameDidChangeNotification
  112. object: view];
  113. [view setPostsFrameChangedNotifications: YES];
  114. #if USE_COREGRAPHICS_RENDERING
  115. if ((windowStyleFlags & ComponentPeer::windowRequiresSynchronousCoreGraphicsRendering) == 0)
  116. {
  117. if (@available (macOS 10.8, *))
  118. {
  119. [view setWantsLayer: YES];
  120. [[view layer] setDrawsAsynchronously: YES];
  121. }
  122. }
  123. #endif
  124. createCVDisplayLink();
  125. if (isSharedWindow)
  126. {
  127. window = [viewToAttachTo window];
  128. [viewToAttachTo addSubview: view];
  129. }
  130. else
  131. {
  132. r.origin.x = (CGFloat) component.getX();
  133. r.origin.y = (CGFloat) component.getY();
  134. r = flippedScreenRect (r);
  135. window = [createWindowInstance() initWithContentRect: r
  136. styleMask: getNSWindowStyleMask (windowStyleFlags)
  137. backing: NSBackingStoreBuffered
  138. defer: YES];
  139. setOwner (window, this);
  140. if (@available (macOS 10.10, *))
  141. [window setAccessibilityElement: YES];
  142. [window orderOut: nil];
  143. [window setDelegate: (id<NSWindowDelegate>) window];
  144. [window setOpaque: component.isOpaque()];
  145. if (! [window isOpaque])
  146. [window setBackgroundColor: [NSColor clearColor]];
  147. if (@available (macOS 10.9, *))
  148. [view setAppearance: [NSAppearance appearanceNamed: NSAppearanceNameAqua]];
  149. [window setHasShadow: ((windowStyleFlags & windowHasDropShadow) != 0)];
  150. if (component.isAlwaysOnTop())
  151. setAlwaysOnTop (true);
  152. [window setContentView: view];
  153. // We'll both retain and also release this on closing because plugin hosts can unexpectedly
  154. // close the window for us, and also tend to get cause trouble if setReleasedWhenClosed is NO.
  155. [window setReleasedWhenClosed: YES];
  156. [window retain];
  157. [window setExcludedFromWindowsMenu: (windowStyleFlags & windowIsTemporary) != 0];
  158. [window setIgnoresMouseEvents: (windowStyleFlags & windowIgnoresMouseClicks) != 0];
  159. setCollectionBehaviour (false);
  160. [window setRestorable: NO];
  161. #if defined (MAC_OS_X_VERSION_10_12) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12)
  162. if (@available (macOS 10.12, *))
  163. [window setTabbingMode: NSWindowTabbingModeDisallowed];
  164. #endif
  165. [notificationCenter addObserver: view
  166. selector: frameChangedSelector
  167. name: NSWindowDidMoveNotification
  168. object: window];
  169. [notificationCenter addObserver: view
  170. selector: frameChangedSelector
  171. name: NSWindowDidMiniaturizeNotification
  172. object: window];
  173. [notificationCenter addObserver: view
  174. selector: @selector (windowWillMiniaturize:)
  175. name: NSWindowWillMiniaturizeNotification
  176. object: window];
  177. [notificationCenter addObserver: view
  178. selector: @selector (windowDidDeminiaturize:)
  179. name: NSWindowDidDeminiaturizeNotification
  180. object: window];
  181. }
  182. auto alpha = component.getAlpha();
  183. if (alpha < 1.0f)
  184. setAlpha (alpha);
  185. setTitle (component.getName());
  186. getNativeRealtimeModifiers = []
  187. {
  188. if ([NSEvent respondsToSelector: @selector (modifierFlags)])
  189. NSViewComponentPeer::updateModifiers ([NSEvent modifierFlags]);
  190. return ModifierKeys::currentModifiers;
  191. };
  192. }
  193. ~NSViewComponentPeer() override
  194. {
  195. CVDisplayLinkStop (displayLink);
  196. dispatch_source_cancel (displaySource);
  197. [notificationCenter removeObserver: view];
  198. setOwner (view, nullptr);
  199. if ([view superview] != nil)
  200. {
  201. redirectWillMoveToWindow (nullptr);
  202. [view removeFromSuperview];
  203. }
  204. if (! isSharedWindow)
  205. {
  206. setOwner (window, nullptr);
  207. [window setContentView: nil];
  208. [window close];
  209. [window release];
  210. }
  211. [view release];
  212. }
  213. //==============================================================================
  214. void* getNativeHandle() const override { return view; }
  215. void setVisible (bool shouldBeVisible) override
  216. {
  217. if (isSharedWindow)
  218. {
  219. if (shouldBeVisible)
  220. [view setHidden: false];
  221. else if ([window firstResponder] != view || ([window firstResponder] == view && [window makeFirstResponder: nil]))
  222. [view setHidden: true];
  223. }
  224. else
  225. {
  226. if (shouldBeVisible)
  227. {
  228. ++insideToFrontCall;
  229. [window orderFront: nil];
  230. --insideToFrontCall;
  231. handleBroughtToFront();
  232. }
  233. else
  234. {
  235. [window orderOut: nil];
  236. }
  237. }
  238. }
  239. void setTitle (const String& title) override
  240. {
  241. JUCE_AUTORELEASEPOOL
  242. {
  243. if (! isSharedWindow)
  244. [window setTitle: juceStringToNS (title)];
  245. }
  246. }
  247. bool setDocumentEditedStatus (bool edited) override
  248. {
  249. if (! hasNativeTitleBar())
  250. return false;
  251. [window setDocumentEdited: edited];
  252. return true;
  253. }
  254. void setRepresentedFile (const File& file) override
  255. {
  256. if (! isSharedWindow)
  257. {
  258. [window setRepresentedFilename: juceStringToNS (file != File()
  259. ? file.getFullPathName()
  260. : String())];
  261. windowRepresentsFile = (file != File());
  262. }
  263. }
  264. void setBounds (const Rectangle<int>& newBounds, bool) override
  265. {
  266. auto r = makeNSRect (newBounds);
  267. auto oldViewSize = [view frame].size;
  268. if (isSharedWindow)
  269. {
  270. [view setFrame: r];
  271. }
  272. else
  273. {
  274. // Repaint behaviour of setFrame seemed to change in 10.11, and the drawing became synchronous,
  275. // causing performance issues. But sending an async update causes flickering in older versions,
  276. // hence this version check to use the old behaviour on pre 10.11 machines
  277. static bool isPre10_11 = SystemStats::getOperatingSystemType() <= SystemStats::MacOSX_10_10;
  278. [window setFrame: [window frameRectForContentRect: flippedScreenRect (r)]
  279. display: isPre10_11];
  280. }
  281. if (oldViewSize.width != r.size.width || oldViewSize.height != r.size.height)
  282. [view setNeedsDisplay: true];
  283. }
  284. Rectangle<int> getBounds (const bool global) const
  285. {
  286. auto r = [view frame];
  287. NSWindow* viewWindow = [view window];
  288. if (global && viewWindow != nil)
  289. {
  290. r = [[view superview] convertRect: r toView: nil];
  291. r = [viewWindow convertRectToScreen: r];
  292. r = flippedScreenRect (r);
  293. }
  294. return convertToRectInt (r);
  295. }
  296. Rectangle<int> getBounds() const override
  297. {
  298. return getBounds (! isSharedWindow);
  299. }
  300. Point<float> localToGlobal (Point<float> relativePosition) override
  301. {
  302. return relativePosition + getBounds (true).getPosition().toFloat();
  303. }
  304. using ComponentPeer::localToGlobal;
  305. Point<float> globalToLocal (Point<float> screenPosition) override
  306. {
  307. return screenPosition - getBounds (true).getPosition().toFloat();
  308. }
  309. using ComponentPeer::globalToLocal;
  310. void setAlpha (float newAlpha) override
  311. {
  312. if (isSharedWindow)
  313. [view setAlphaValue: (CGFloat) newAlpha];
  314. else
  315. [window setAlphaValue: (CGFloat) newAlpha];
  316. }
  317. void setMinimised (bool shouldBeMinimised) override
  318. {
  319. if (! isSharedWindow)
  320. {
  321. if (shouldBeMinimised)
  322. [window miniaturize: nil];
  323. else
  324. [window deminiaturize: nil];
  325. }
  326. }
  327. bool isMinimised() const override
  328. {
  329. return [window isMiniaturized];
  330. }
  331. NSWindowCollectionBehavior getCollectionBehavior (bool forceFullScreen) const
  332. {
  333. if (forceFullScreen)
  334. return NSWindowCollectionBehaviorFullScreenPrimary;
  335. // Some SDK versions don't define NSWindowCollectionBehaviorFullScreenNone
  336. constexpr auto fullScreenNone = (NSUInteger) (1 << 9);
  337. return (getStyleFlags() & (windowHasMaximiseButton | windowIsResizable)) == (windowHasMaximiseButton | windowIsResizable)
  338. ? NSWindowCollectionBehaviorFullScreenPrimary
  339. : fullScreenNone;
  340. }
  341. void setCollectionBehaviour (bool forceFullScreen) const
  342. {
  343. [window setCollectionBehavior: getCollectionBehavior (forceFullScreen)];
  344. }
  345. void setFullScreen (bool shouldBeFullScreen) override
  346. {
  347. if (isSharedWindow)
  348. return;
  349. if (shouldBeFullScreen)
  350. setCollectionBehaviour (true);
  351. if (isMinimised())
  352. setMinimised (false);
  353. if (shouldBeFullScreen != isFullScreen())
  354. [window toggleFullScreen: nil];
  355. }
  356. bool isFullScreen() const override
  357. {
  358. return ([window styleMask] & NSWindowStyleMaskFullScreen) != 0;
  359. }
  360. bool isKioskMode() const override
  361. {
  362. return isFullScreen() && ComponentPeer::isKioskMode();
  363. }
  364. static bool isWindowAtPoint (NSWindow* w, NSPoint screenPoint)
  365. {
  366. if ([NSWindow respondsToSelector: @selector (windowNumberAtPoint:belowWindowWithWindowNumber:)])
  367. return [NSWindow windowNumberAtPoint: screenPoint belowWindowWithWindowNumber: 0] == [w windowNumber];
  368. return true;
  369. }
  370. bool contains (Point<int> localPos, bool trueIfInAChildWindow) const override
  371. {
  372. NSRect viewFrame = [view frame];
  373. if (! (isPositiveAndBelow (localPos.getX(), viewFrame.size.width)
  374. && isPositiveAndBelow (localPos.getY(), viewFrame.size.height)))
  375. return false;
  376. if (! SystemStats::isRunningInAppExtensionSandbox())
  377. {
  378. if (NSWindow* const viewWindow = [view window])
  379. {
  380. NSRect windowFrame = [viewWindow frame];
  381. NSPoint windowPoint = [view convertPoint: NSMakePoint (localPos.x, localPos.y) toView: nil];
  382. NSPoint screenPoint = NSMakePoint (windowFrame.origin.x + windowPoint.x,
  383. windowFrame.origin.y + windowPoint.y);
  384. if (! isWindowAtPoint (viewWindow, screenPoint))
  385. return false;
  386. }
  387. }
  388. NSView* v = [view hitTest: NSMakePoint (viewFrame.origin.x + localPos.getX(),
  389. viewFrame.origin.y + localPos.getY())];
  390. return trueIfInAChildWindow ? (v != nil)
  391. : (v == view);
  392. }
  393. OptionalBorderSize getFrameSizeIfPresent() const override
  394. {
  395. if (! isSharedWindow)
  396. {
  397. BorderSize<int> b;
  398. NSRect v = [view convertRect: [view frame] toView: nil];
  399. NSRect w = [window frame];
  400. b.setTop ((int) (w.size.height - (v.origin.y + v.size.height)));
  401. b.setBottom ((int) v.origin.y);
  402. b.setLeft ((int) v.origin.x);
  403. b.setRight ((int) (w.size.width - (v.origin.x + v.size.width)));
  404. return OptionalBorderSize { b };
  405. }
  406. return {};
  407. }
  408. BorderSize<int> getFrameSize() const override
  409. {
  410. if (const auto frameSize = getFrameSizeIfPresent())
  411. return *frameSize;
  412. return {};
  413. }
  414. bool hasNativeTitleBar() const
  415. {
  416. return (getStyleFlags() & windowHasTitleBar) != 0;
  417. }
  418. bool setAlwaysOnTop (bool alwaysOnTop) override
  419. {
  420. if (! isSharedWindow)
  421. {
  422. [window setLevel: alwaysOnTop ? ((getStyleFlags() & windowIsTemporary) != 0 ? NSPopUpMenuWindowLevel
  423. : NSFloatingWindowLevel)
  424. : NSNormalWindowLevel];
  425. isAlwaysOnTop = alwaysOnTop;
  426. }
  427. return true;
  428. }
  429. void toFront (bool makeActiveWindow) override
  430. {
  431. if (isSharedWindow)
  432. {
  433. NSView* superview = [view superview];
  434. NSMutableArray* subviews = [NSMutableArray arrayWithArray: [superview subviews]];
  435. const auto isFrontmost = [[subviews lastObject] isEqual: view];
  436. if (! isFrontmost)
  437. {
  438. [view retain];
  439. [subviews removeObject: view];
  440. [subviews addObject: view];
  441. [superview setSubviews: subviews];
  442. [view release];
  443. }
  444. }
  445. if (window != nil && component.isVisible())
  446. {
  447. ++insideToFrontCall;
  448. if (makeActiveWindow)
  449. [window makeKeyAndOrderFront: nil];
  450. else
  451. [window orderFront: nil];
  452. if (insideToFrontCall <= 1)
  453. {
  454. Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
  455. handleBroughtToFront();
  456. }
  457. --insideToFrontCall;
  458. }
  459. }
  460. void toBehind (ComponentPeer* other) override
  461. {
  462. if (auto* otherPeer = dynamic_cast<NSViewComponentPeer*> (other))
  463. {
  464. if (isSharedWindow)
  465. {
  466. NSView* superview = [view superview];
  467. NSMutableArray* subviews = [NSMutableArray arrayWithArray: [superview subviews]];
  468. const auto otherViewIndex = [subviews indexOfObject: otherPeer->view];
  469. if (otherViewIndex == NSNotFound)
  470. return;
  471. const auto isBehind = [subviews indexOfObject: view] < otherViewIndex;
  472. if (! isBehind)
  473. {
  474. [view retain];
  475. [subviews removeObject: view];
  476. [subviews insertObject: view
  477. atIndex: otherViewIndex];
  478. [superview setSubviews: subviews];
  479. [view release];
  480. }
  481. }
  482. else if (component.isVisible())
  483. {
  484. [window orderWindow: NSWindowBelow
  485. relativeTo: [otherPeer->window windowNumber]];
  486. }
  487. }
  488. else
  489. {
  490. jassertfalse; // wrong type of window?
  491. }
  492. }
  493. void setIcon (const Image& newIcon) override
  494. {
  495. if (! isSharedWindow)
  496. {
  497. // need to set a dummy represented file here to show the file icon (which we then set to the new icon)
  498. if (! windowRepresentsFile)
  499. [window setRepresentedFilename: juceStringToNS (" ")]; // can't just use an empty string for some reason...
  500. auto img = NSUniquePtr<NSImage> { imageToNSImage (ScaledImage (newIcon)) };
  501. [[window standardWindowButton: NSWindowDocumentIconButton] setImage: img.get()];
  502. }
  503. }
  504. StringArray getAvailableRenderingEngines() override
  505. {
  506. StringArray s ("Software Renderer");
  507. #if USE_COREGRAPHICS_RENDERING
  508. s.add ("CoreGraphics Renderer");
  509. #endif
  510. return s;
  511. }
  512. int getCurrentRenderingEngine() const override
  513. {
  514. return usingCoreGraphics ? 1 : 0;
  515. }
  516. void setCurrentRenderingEngine (int index) override
  517. {
  518. #if USE_COREGRAPHICS_RENDERING
  519. if (usingCoreGraphics != (index > 0))
  520. {
  521. usingCoreGraphics = index > 0;
  522. [view setNeedsDisplay: true];
  523. }
  524. #else
  525. ignoreUnused (index);
  526. #endif
  527. }
  528. void redirectMouseDown (NSEvent* ev)
  529. {
  530. if (! Process::isForegroundProcess())
  531. Process::makeForegroundProcess();
  532. ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withFlags (getModifierForButtonNumber ([ev buttonNumber]));
  533. sendMouseEvent (ev);
  534. }
  535. void redirectMouseUp (NSEvent* ev)
  536. {
  537. ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutFlags (getModifierForButtonNumber ([ev buttonNumber]));
  538. sendMouseEvent (ev);
  539. showArrowCursorIfNeeded();
  540. }
  541. void redirectMouseDrag (NSEvent* ev)
  542. {
  543. ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withFlags (getModifierForButtonNumber ([ev buttonNumber]));
  544. sendMouseEvent (ev);
  545. }
  546. void redirectMouseMove (NSEvent* ev)
  547. {
  548. ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons();
  549. NSPoint windowPos = [ev locationInWindow];
  550. NSPoint screenPos = [[ev window] convertRectToScreen: NSMakeRect (windowPos.x, windowPos.y, 1.0f, 1.0f)].origin;
  551. if (isWindowAtPoint ([ev window], screenPos))
  552. sendMouseEvent (ev);
  553. else
  554. // moved into another window which overlaps this one, so trigger an exit
  555. handleMouseEvent (MouseInputSource::InputSourceType::mouse, MouseInputSource::offscreenMousePos, ModifierKeys::currentModifiers,
  556. getMousePressure (ev), MouseInputSource::defaultOrientation, getMouseTime (ev));
  557. showArrowCursorIfNeeded();
  558. }
  559. void redirectMouseEnter (NSEvent* ev)
  560. {
  561. sendMouseEnterExit (ev);
  562. }
  563. void redirectMouseExit (NSEvent* ev)
  564. {
  565. sendMouseEnterExit (ev);
  566. }
  567. static float checkDeviceDeltaReturnValue (float v) noexcept
  568. {
  569. // (deviceDeltaX can fail and return NaN, so need to sanity-check the result)
  570. v *= 0.5f / 256.0f;
  571. return (v > -1000.0f && v < 1000.0f) ? v : 0.0f;
  572. }
  573. void redirectMouseWheel (NSEvent* ev)
  574. {
  575. updateModifiers (ev);
  576. MouseWheelDetails wheel;
  577. wheel.deltaX = 0;
  578. wheel.deltaY = 0;
  579. wheel.isReversed = false;
  580. wheel.isSmooth = false;
  581. wheel.isInertial = false;
  582. @try
  583. {
  584. if ([ev respondsToSelector: @selector (isDirectionInvertedFromDevice)])
  585. wheel.isReversed = [ev isDirectionInvertedFromDevice];
  586. wheel.isInertial = ([ev momentumPhase] != NSEventPhaseNone);
  587. if ([ev respondsToSelector: @selector (hasPreciseScrollingDeltas)])
  588. {
  589. if ([ev hasPreciseScrollingDeltas])
  590. {
  591. const float scale = 0.5f / 256.0f;
  592. wheel.deltaX = scale * (float) [ev scrollingDeltaX];
  593. wheel.deltaY = scale * (float) [ev scrollingDeltaY];
  594. wheel.isSmooth = true;
  595. }
  596. }
  597. else if ([ev respondsToSelector: @selector (deviceDeltaX)])
  598. {
  599. wheel.deltaX = checkDeviceDeltaReturnValue ([ev deviceDeltaX]);
  600. wheel.deltaY = checkDeviceDeltaReturnValue ([ev deviceDeltaY]);
  601. }
  602. }
  603. @catch (...)
  604. {}
  605. if (wheel.deltaX == 0.0f && wheel.deltaY == 0.0f)
  606. {
  607. const float scale = 10.0f / 256.0f;
  608. wheel.deltaX = scale * (float) [ev deltaX];
  609. wheel.deltaY = scale * (float) [ev deltaY];
  610. }
  611. handleMouseWheel (MouseInputSource::InputSourceType::mouse, getMousePos (ev, view), getMouseTime (ev), wheel);
  612. }
  613. void redirectMagnify (NSEvent* ev)
  614. {
  615. const float invScale = 1.0f - (float) [ev magnification];
  616. if (invScale > 0.0f)
  617. handleMagnifyGesture (MouseInputSource::InputSourceType::mouse, getMousePos (ev, view), getMouseTime (ev), 1.0f / invScale);
  618. }
  619. void redirectCopy (NSObject*) { handleKeyPress (KeyPress ('c', ModifierKeys (ModifierKeys::commandModifier), 'c')); }
  620. void redirectPaste (NSObject*) { handleKeyPress (KeyPress ('v', ModifierKeys (ModifierKeys::commandModifier), 'v')); }
  621. void redirectCut (NSObject*) { handleKeyPress (KeyPress ('x', ModifierKeys (ModifierKeys::commandModifier), 'x')); }
  622. void redirectSelectAll (NSObject*) { handleKeyPress (KeyPress ('a', ModifierKeys (ModifierKeys::commandModifier), 'a')); }
  623. void redirectWillMoveToWindow (NSWindow* newWindow)
  624. {
  625. if (auto* currentWindow = [view window])
  626. {
  627. [notificationCenter removeObserver: view
  628. name: NSWindowDidMoveNotification
  629. object: currentWindow];
  630. [notificationCenter removeObserver: view
  631. name: NSWindowWillMiniaturizeNotification
  632. object: currentWindow];
  633. [notificationCenter removeObserver: view
  634. name: NSWindowDidBecomeKeyNotification
  635. object: currentWindow];
  636. [notificationCenter removeObserver: view
  637. name: NSWindowDidChangeScreenNotification
  638. object: currentWindow];
  639. }
  640. if (isSharedWindow && [view window] == window && newWindow == nullptr)
  641. {
  642. if (auto* comp = safeComponent.get())
  643. comp->setVisible (false);
  644. }
  645. }
  646. void sendMouseEvent (NSEvent* ev)
  647. {
  648. updateModifiers (ev);
  649. handleMouseEvent (MouseInputSource::InputSourceType::mouse, getMousePos (ev, view), ModifierKeys::currentModifiers,
  650. getMousePressure (ev), MouseInputSource::defaultOrientation, getMouseTime (ev));
  651. }
  652. bool handleKeyEvent (NSEvent* ev, bool isKeyDown)
  653. {
  654. auto unicode = nsStringToJuce ([ev characters]);
  655. auto keyCode = getKeyCodeFromEvent (ev);
  656. #if JUCE_DEBUG_KEYCODES
  657. DBG ("unicode: " + unicode + " " + String::toHexString ((int) unicode[0]));
  658. auto unmodified = nsStringToJuce ([ev charactersIgnoringModifiers]);
  659. DBG ("unmodified: " + unmodified + " " + String::toHexString ((int) unmodified[0]));
  660. #endif
  661. if (keyCode != 0 || unicode.isNotEmpty())
  662. {
  663. if (isKeyDown)
  664. {
  665. bool used = false;
  666. for (auto u = unicode.getCharPointer(); ! u.isEmpty();)
  667. {
  668. auto textCharacter = u.getAndAdvance();
  669. switch (keyCode)
  670. {
  671. case NSLeftArrowFunctionKey:
  672. case NSRightArrowFunctionKey:
  673. case NSUpArrowFunctionKey:
  674. case NSDownArrowFunctionKey:
  675. case NSPageUpFunctionKey:
  676. case NSPageDownFunctionKey:
  677. case NSEndFunctionKey:
  678. case NSHomeFunctionKey:
  679. case NSDeleteFunctionKey:
  680. textCharacter = 0;
  681. break; // (these all seem to generate unwanted garbage unicode strings)
  682. default:
  683. if (([ev modifierFlags] & NSEventModifierFlagCommand) != 0
  684. || (keyCode >= NSF1FunctionKey && keyCode <= NSF35FunctionKey))
  685. textCharacter = 0;
  686. break;
  687. }
  688. used = handleKeyUpOrDown (true) || used;
  689. used = handleKeyPress (keyCode, textCharacter) || used;
  690. }
  691. return used;
  692. }
  693. if (handleKeyUpOrDown (false))
  694. return true;
  695. }
  696. return false;
  697. }
  698. bool redirectKeyDown (NSEvent* ev)
  699. {
  700. // (need to retain this in case a modal loop runs in handleKeyEvent and
  701. // our event object gets lost)
  702. const NSUniquePtr<NSEvent> r ([ev retain]);
  703. updateKeysDown (ev, true);
  704. bool used = handleKeyEvent (ev, true);
  705. if (([ev modifierFlags] & NSEventModifierFlagCommand) != 0)
  706. {
  707. // for command keys, the key-up event is thrown away, so simulate one..
  708. updateKeysDown (ev, false);
  709. used = (isValidPeer (this) && handleKeyEvent (ev, false)) || used;
  710. }
  711. // (If we're running modally, don't allow unused keystrokes to be passed
  712. // along to other blocked views..)
  713. if (Component::getCurrentlyModalComponent() != nullptr)
  714. used = true;
  715. return used;
  716. }
  717. bool redirectKeyUp (NSEvent* ev)
  718. {
  719. updateKeysDown (ev, false);
  720. return handleKeyEvent (ev, false)
  721. || Component::getCurrentlyModalComponent() != nullptr;
  722. }
  723. void redirectModKeyChange (NSEvent* ev)
  724. {
  725. // (need to retain this in case a modal loop runs and our event object gets lost)
  726. const NSUniquePtr<NSEvent> r ([ev retain]);
  727. keysCurrentlyDown.clear();
  728. handleKeyUpOrDown (true);
  729. updateModifiers (ev);
  730. handleModifierKeysChange();
  731. }
  732. //==============================================================================
  733. void drawRect (NSRect r)
  734. {
  735. if (r.size.width < 1.0f || r.size.height < 1.0f)
  736. return;
  737. auto cg = []
  738. {
  739. if (@available (macOS 10.10, *))
  740. return (CGContextRef) [[NSGraphicsContext currentContext] CGContext];
  741. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  742. return (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
  743. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  744. }();
  745. drawRectWithContext (cg, r);
  746. }
  747. void drawRectWithContext (CGContextRef cg, NSRect r)
  748. {
  749. if (! component.isOpaque())
  750. CGContextClearRect (cg, CGContextGetClipBoundingBox (cg));
  751. float displayScale = 1.0f;
  752. NSScreen* screen = [[view window] screen];
  753. if ([screen respondsToSelector: @selector (backingScaleFactor)])
  754. displayScale = (float) screen.backingScaleFactor;
  755. auto invalidateTransparentWindowShadow = [this]
  756. {
  757. // transparent NSWindows with a drop-shadow need to redraw their shadow when the content
  758. // changes to avoid stale shadows being drawn behind the window
  759. if (! isSharedWindow && ! [window isOpaque] && [window hasShadow])
  760. [window invalidateShadow];
  761. };
  762. #if USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
  763. // This was a workaround for a CGContext not having a way of finding whether a rectangle
  764. // falls within its clip region. However Apple removed the capability of
  765. // [view getRectsBeingDrawn: ...] sometime around 10.13, so on later versions of macOS
  766. // numRects will always be 1 and you'll need to use a CoreGraphicsMetalLayerRenderer
  767. // to avoid CoreGraphics consolidating disparate rects.
  768. if (usingCoreGraphics && metalRenderer == nullptr)
  769. {
  770. const NSRect* rects = nullptr;
  771. NSInteger numRects = 0;
  772. [view getRectsBeingDrawn: &rects count: &numRects];
  773. if (numRects > 1)
  774. {
  775. for (int i = 0; i < numRects; ++i)
  776. {
  777. NSRect rect = rects[i];
  778. CGContextSaveGState (cg);
  779. CGContextClipToRect (cg, CGRectMake (rect.origin.x, rect.origin.y, rect.size.width, rect.size.height));
  780. renderRect (cg, rect, displayScale);
  781. CGContextRestoreGState (cg);
  782. }
  783. invalidateTransparentWindowShadow();
  784. return;
  785. }
  786. }
  787. #endif
  788. renderRect (cg, r, displayScale);
  789. invalidateTransparentWindowShadow();
  790. }
  791. void renderRect (CGContextRef cg, NSRect r, float displayScale)
  792. {
  793. #if USE_COREGRAPHICS_RENDERING
  794. if (usingCoreGraphics)
  795. {
  796. const auto height = getComponent().getHeight();
  797. CGContextConcatCTM (cg, CGAffineTransformMake (1, 0, 0, -1, 0, height));
  798. CoreGraphicsContext context (cg, (float) height);
  799. handlePaint (context);
  800. }
  801. else
  802. #endif
  803. {
  804. const Point<int> offset (-roundToInt (r.origin.x), -roundToInt (r.origin.y));
  805. auto clipW = (int) (r.size.width + 0.5f);
  806. auto clipH = (int) (r.size.height + 0.5f);
  807. RectangleList<int> clip;
  808. getClipRects (clip, offset, clipW, clipH);
  809. if (! clip.isEmpty())
  810. {
  811. Image temp (component.isOpaque() ? Image::RGB : Image::ARGB,
  812. roundToInt (clipW * displayScale),
  813. roundToInt (clipH * displayScale),
  814. ! component.isOpaque());
  815. {
  816. auto intScale = roundToInt (displayScale);
  817. if (intScale != 1)
  818. clip.scaleAll (intScale);
  819. auto context = component.getLookAndFeel()
  820. .createGraphicsContext (temp, offset * intScale, clip);
  821. if (intScale != 1)
  822. context->addTransform (AffineTransform::scale (displayScale));
  823. handlePaint (*context);
  824. }
  825. detail::ColorSpacePtr colourSpace { CGColorSpaceCreateWithName (kCGColorSpaceSRGB) };
  826. CGImageRef image = juce_createCoreGraphicsImage (temp, colourSpace.get());
  827. CGContextConcatCTM (cg, CGAffineTransformMake (1, 0, 0, -1, r.origin.x, r.origin.y + clipH));
  828. CGContextDrawImage (cg, CGRectMake (0.0f, 0.0f, clipW, clipH), image);
  829. CGImageRelease (image);
  830. }
  831. }
  832. }
  833. void repaint (const Rectangle<int>& area) override
  834. {
  835. // In 10.11 changes were made to the way the OS handles repaint regions, and it seems that it can
  836. // no longer be trusted to coalesce all the regions, or to even remember them all without losing
  837. // a few when there's a lot of activity.
  838. // As a work around for this, we use a RectangleList to do our own coalescing of regions before
  839. // asynchronously asking the OS to repaint them.
  840. deferredRepaints.add (area.toFloat());
  841. }
  842. static bool shouldThrottleRepaint()
  843. {
  844. return areAnyWindowsInLiveResize();
  845. }
  846. void setNeedsDisplayRectangles()
  847. {
  848. if (deferredRepaints.isEmpty())
  849. return;
  850. auto now = Time::getMillisecondCounter();
  851. auto msSinceLastRepaint = (lastRepaintTime >= now) ? now - lastRepaintTime
  852. : (std::numeric_limits<uint32>::max() - lastRepaintTime) + now;
  853. constexpr uint32 minimumRepaintInterval = 1000 / 30; // 30fps
  854. // When windows are being resized, artificially throttling high-frequency repaints helps
  855. // to stop the event queue getting clogged, and keeps everything working smoothly.
  856. // For some reason Logic also needs this throttling to record parameter events correctly.
  857. if (msSinceLastRepaint < minimumRepaintInterval && shouldThrottleRepaint())
  858. return;
  859. #if USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
  860. // We require macOS 10.14 to use the Metal layer renderer
  861. if (@available (macOS 10.14, *))
  862. {
  863. const auto& comp = getComponent();
  864. // If we are resizing we need to fall back to synchronous drawing to avoid artefacts
  865. if (areAnyWindowsInLiveResize())
  866. {
  867. if (metalRenderer != nullptr)
  868. {
  869. metalRenderer.reset();
  870. view.wantsLayer = NO;
  871. view.layer = nil;
  872. deferredRepaints = comp.getLocalBounds().toFloat();
  873. }
  874. }
  875. else
  876. {
  877. if (metalRenderer == nullptr)
  878. {
  879. view.wantsLayer = YES;
  880. view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
  881. view.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft;
  882. view.layer = [CAMetalLayer layer];
  883. metalRenderer = std::make_unique<CoreGraphicsMetalLayerRenderer> ((CAMetalLayer*) view.layer, getComponent());
  884. deferredRepaints = comp.getLocalBounds().toFloat();
  885. }
  886. }
  887. }
  888. #endif
  889. auto dispatchRectangles = [this] ()
  890. {
  891. if (@available (macOS 10.14, *))
  892. {
  893. if (metalRenderer != nullptr)
  894. {
  895. return metalRenderer->drawRectangleList ((CAMetalLayer*) view.layer,
  896. (float) [[view window] backingScaleFactor],
  897. view.frame,
  898. getComponent(),
  899. [this] (CGContextRef ctx, CGRect r) { drawRectWithContext (ctx, r); },
  900. deferredRepaints);
  901. }
  902. }
  903. for (auto& i : deferredRepaints)
  904. [view setNeedsDisplayInRect: makeNSRect (i)];
  905. return true;
  906. };
  907. if (dispatchRectangles())
  908. {
  909. lastRepaintTime = Time::getMillisecondCounter();
  910. deferredRepaints.clear();
  911. }
  912. }
  913. void performAnyPendingRepaintsNow() override
  914. {
  915. [view displayIfNeeded];
  916. }
  917. static bool areAnyWindowsInLiveResize() noexcept
  918. {
  919. for (NSWindow* w in [NSApp windows])
  920. if ([w inLiveResize])
  921. return true;
  922. return false;
  923. }
  924. //==============================================================================
  925. bool isBlockedByModalComponent()
  926. {
  927. if (auto* modal = Component::getCurrentlyModalComponent())
  928. {
  929. if (insideToFrontCall == 0
  930. && (! getComponent().isParentOf (modal))
  931. && getComponent().isCurrentlyBlockedByAnotherModalComponent())
  932. {
  933. return true;
  934. }
  935. }
  936. return false;
  937. }
  938. enum class KeyWindowChanged { no, yes };
  939. void sendModalInputAttemptIfBlocked (KeyWindowChanged keyChanged)
  940. {
  941. if (! isBlockedByModalComponent())
  942. return;
  943. if (auto* modal = Component::getCurrentlyModalComponent())
  944. {
  945. if (auto* otherPeer = modal->getPeer())
  946. {
  947. const auto modalPeerIsTemporary = (otherPeer->getStyleFlags() & ComponentPeer::windowIsTemporary) != 0;
  948. if (! modalPeerIsTemporary)
  949. return;
  950. // When a peer resigns key status, it might be because we just created a modal
  951. // component that is now key.
  952. // In this case, we should only dismiss the modal component if it isn't key,
  953. // implying that a third window has become key.
  954. const auto modalPeerIsKey = [NSApp keyWindow] == static_cast<NSViewComponentPeer*> (otherPeer)->window;
  955. if (keyChanged == KeyWindowChanged::yes && modalPeerIsKey)
  956. return;
  957. modal->inputAttemptWhenModal();
  958. }
  959. }
  960. }
  961. bool canBecomeKeyWindow()
  962. {
  963. return component.isVisible() && (getStyleFlags() & ComponentPeer::windowIgnoresKeyPresses) == 0;
  964. }
  965. bool canBecomeMainWindow()
  966. {
  967. return component.isVisible() && dynamic_cast<ResizableWindow*> (&component) != nullptr;
  968. }
  969. bool worksWhenModal() const
  970. {
  971. // In plugins, the host could put our plugin window inside a modal window, so this
  972. // allows us to successfully open other popups. Feels like there could be edge-case
  973. // problems caused by this, so let us know if you spot any issues..
  974. return ! JUCEApplication::isStandaloneApp();
  975. }
  976. void becomeKeyWindow()
  977. {
  978. handleBroughtToFront();
  979. grabFocus();
  980. }
  981. void resignKeyWindow()
  982. {
  983. viewFocusLoss();
  984. }
  985. bool windowShouldClose()
  986. {
  987. if (! isValidPeer (this))
  988. return YES;
  989. handleUserClosingWindow();
  990. return NO;
  991. }
  992. void redirectMovedOrResized()
  993. {
  994. handleMovedOrResized();
  995. setNeedsDisplayRectangles();
  996. }
  997. void windowDidChangeScreen()
  998. {
  999. updateCVDisplayLinkScreen();
  1000. }
  1001. void viewMovedToWindow()
  1002. {
  1003. if (isSharedWindow)
  1004. {
  1005. auto newWindow = [view window];
  1006. bool shouldSetVisible = (window == nullptr && newWindow != nullptr);
  1007. window = newWindow;
  1008. if (shouldSetVisible)
  1009. getComponent().setVisible (true);
  1010. }
  1011. if (auto* currentWindow = [view window])
  1012. {
  1013. [notificationCenter addObserver: view
  1014. selector: dismissModalsSelector
  1015. name: NSWindowWillMoveNotification
  1016. object: currentWindow];
  1017. [notificationCenter addObserver: view
  1018. selector: dismissModalsSelector
  1019. name: NSWindowWillMiniaturizeNotification
  1020. object: currentWindow];
  1021. [notificationCenter addObserver: view
  1022. selector: becomeKeySelector
  1023. name: NSWindowDidBecomeKeyNotification
  1024. object: currentWindow];
  1025. [notificationCenter addObserver: view
  1026. selector: resignKeySelector
  1027. name: NSWindowDidResignKeyNotification
  1028. object: currentWindow];
  1029. [notificationCenter addObserver: view
  1030. selector: @selector (windowDidChangeScreen:)
  1031. name: NSWindowDidChangeScreenNotification
  1032. object: currentWindow];
  1033. updateCVDisplayLinkScreen();
  1034. }
  1035. }
  1036. void dismissModals()
  1037. {
  1038. if (hasNativeTitleBar() || isSharedWindow)
  1039. sendModalInputAttemptIfBlocked (KeyWindowChanged::no);
  1040. }
  1041. void becomeKey()
  1042. {
  1043. component.repaint();
  1044. }
  1045. void resignKey()
  1046. {
  1047. viewFocusLoss();
  1048. sendModalInputAttemptIfBlocked (KeyWindowChanged::yes);
  1049. }
  1050. void liveResizingStart()
  1051. {
  1052. if (constrainer == nullptr)
  1053. return;
  1054. constrainer->resizeStart();
  1055. isFirstLiveResize = true;
  1056. setFullScreenSizeConstraints (*constrainer);
  1057. }
  1058. void liveResizingEnd()
  1059. {
  1060. if (constrainer != nullptr)
  1061. constrainer->resizeEnd();
  1062. }
  1063. NSRect constrainRect (const NSRect r)
  1064. {
  1065. if (constrainer == nullptr || isKioskMode() || isFullScreen())
  1066. return r;
  1067. const auto scale = getComponent().getDesktopScaleFactor();
  1068. auto pos = ScalingHelpers::unscaledScreenPosToScaled (scale, convertToRectInt (flippedScreenRect (r)));
  1069. const auto original = ScalingHelpers::unscaledScreenPosToScaled (scale, convertToRectInt (flippedScreenRect ([window frame])));
  1070. const auto screenBounds = Desktop::getInstance().getDisplays().getTotalBounds (true);
  1071. const bool inLiveResize = [window inLiveResize];
  1072. if (! inLiveResize || isFirstLiveResize)
  1073. {
  1074. isFirstLiveResize = false;
  1075. isStretchingTop = (pos.getY() != original.getY() && pos.getBottom() == original.getBottom());
  1076. isStretchingLeft = (pos.getX() != original.getX() && pos.getRight() == original.getRight());
  1077. isStretchingBottom = (pos.getY() == original.getY() && pos.getBottom() != original.getBottom());
  1078. isStretchingRight = (pos.getX() == original.getX() && pos.getRight() != original.getRight());
  1079. }
  1080. constrainer->checkBounds (pos, original, screenBounds,
  1081. isStretchingTop, isStretchingLeft, isStretchingBottom, isStretchingRight);
  1082. return flippedScreenRect (makeNSRect (ScalingHelpers::scaledScreenPosToUnscaled (scale, pos)));
  1083. }
  1084. static void showArrowCursorIfNeeded()
  1085. {
  1086. auto& desktop = Desktop::getInstance();
  1087. auto mouse = desktop.getMainMouseSource();
  1088. if (mouse.getComponentUnderMouse() == nullptr
  1089. && desktop.findComponentAt (mouse.getScreenPosition().roundToInt()) == nullptr)
  1090. {
  1091. [[NSCursor arrowCursor] set];
  1092. }
  1093. }
  1094. static void updateModifiers (NSEvent* e)
  1095. {
  1096. updateModifiers ([e modifierFlags]);
  1097. }
  1098. static void updateModifiers (const NSUInteger flags)
  1099. {
  1100. int m = 0;
  1101. if ((flags & NSEventModifierFlagShift) != 0) m |= ModifierKeys::shiftModifier;
  1102. if ((flags & NSEventModifierFlagControl) != 0) m |= ModifierKeys::ctrlModifier;
  1103. if ((flags & NSEventModifierFlagOption) != 0) m |= ModifierKeys::altModifier;
  1104. if ((flags & NSEventModifierFlagCommand) != 0) m |= ModifierKeys::commandModifier;
  1105. ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withOnlyMouseButtons().withFlags (m);
  1106. }
  1107. static void updateKeysDown (NSEvent* ev, bool isKeyDown)
  1108. {
  1109. updateModifiers (ev);
  1110. if (auto keyCode = getKeyCodeFromEvent (ev))
  1111. {
  1112. if (isKeyDown)
  1113. keysCurrentlyDown.addIfNotAlreadyThere (keyCode);
  1114. else
  1115. keysCurrentlyDown.removeFirstMatchingValue (keyCode);
  1116. }
  1117. }
  1118. static int getKeyCodeFromEvent (NSEvent* ev)
  1119. {
  1120. // Unfortunately, charactersIgnoringModifiers does not ignore the shift key.
  1121. // Using [ev keyCode] is not a solution either as this will,
  1122. // for example, return VK_KEY_Y if the key is pressed which
  1123. // is typically located at the Y key position on a QWERTY
  1124. // keyboard. However, on international keyboards this might not
  1125. // be the key labeled Y (for example, on German keyboards this key
  1126. // has a Z label). Therefore, we need to query the current keyboard
  1127. // layout to figure out what character the key would have produced
  1128. // if the shift key was not pressed
  1129. String unmodified = nsStringToJuce ([ev charactersIgnoringModifiers]);
  1130. auto keyCode = (int) unmodified[0];
  1131. if (keyCode == 0x19) // (backwards-tab)
  1132. keyCode = '\t';
  1133. else if (keyCode == 0x03) // (enter)
  1134. keyCode = '\r';
  1135. else
  1136. keyCode = (int) CharacterFunctions::toUpperCase ((juce_wchar) keyCode);
  1137. // The purpose of the keyCode is to provide information about non-printing characters to facilitate
  1138. // keyboard control over the application.
  1139. //
  1140. // So when keyCode is decoded as a printing character outside the ASCII range we need to replace it.
  1141. // This holds when the keyCode is larger than 0xff and not part one of the two MacOS specific
  1142. // non-printing ranges.
  1143. if (keyCode > 0xff
  1144. && ! (keyCode >= NSUpArrowFunctionKey && keyCode <= NSModeSwitchFunctionKey)
  1145. && ! (keyCode >= extendedKeyModifier))
  1146. {
  1147. keyCode = translateVirtualToAsciiKeyCode ([ev keyCode]);
  1148. }
  1149. if (([ev modifierFlags] & NSEventModifierFlagNumericPad) != 0)
  1150. {
  1151. const int numPadConversions[] = { '0', KeyPress::numberPad0, '1', KeyPress::numberPad1,
  1152. '2', KeyPress::numberPad2, '3', KeyPress::numberPad3,
  1153. '4', KeyPress::numberPad4, '5', KeyPress::numberPad5,
  1154. '6', KeyPress::numberPad6, '7', KeyPress::numberPad7,
  1155. '8', KeyPress::numberPad8, '9', KeyPress::numberPad9,
  1156. '+', KeyPress::numberPadAdd, '-', KeyPress::numberPadSubtract,
  1157. '*', KeyPress::numberPadMultiply, '/', KeyPress::numberPadDivide,
  1158. '.', KeyPress::numberPadDecimalPoint,
  1159. ',', KeyPress::numberPadDecimalPoint, // (to deal with non-english kbds)
  1160. '=', KeyPress::numberPadEquals };
  1161. for (int i = 0; i < numElementsInArray (numPadConversions); i += 2)
  1162. if (keyCode == numPadConversions [i])
  1163. keyCode = numPadConversions [i + 1];
  1164. }
  1165. return keyCode;
  1166. }
  1167. static int64 getMouseTime (NSEvent* e) noexcept
  1168. {
  1169. return (Time::currentTimeMillis() - Time::getMillisecondCounter())
  1170. + (int64) ([e timestamp] * 1000.0);
  1171. }
  1172. static float getMousePressure (NSEvent* e) noexcept
  1173. {
  1174. @try
  1175. {
  1176. if (e.type != NSEventTypeMouseEntered && e.type != NSEventTypeMouseExited)
  1177. return (float) e.pressure;
  1178. }
  1179. @catch (NSException* e) {}
  1180. @finally {}
  1181. return 0.0f;
  1182. }
  1183. static Point<float> getMousePos (NSEvent* e, NSView* view)
  1184. {
  1185. NSPoint p = [view convertPoint: [e locationInWindow] fromView: nil];
  1186. return { (float) p.x, (float) p.y };
  1187. }
  1188. static int getModifierForButtonNumber (const NSInteger num)
  1189. {
  1190. return num == 0 ? ModifierKeys::leftButtonModifier
  1191. : (num == 1 ? ModifierKeys::rightButtonModifier
  1192. : (num == 2 ? ModifierKeys::middleButtonModifier : 0));
  1193. }
  1194. static unsigned int getNSWindowStyleMask (const int flags) noexcept
  1195. {
  1196. unsigned int style = (flags & windowHasTitleBar) != 0 ? NSWindowStyleMaskTitled
  1197. : NSWindowStyleMaskBorderless;
  1198. if ((flags & windowHasMinimiseButton) != 0) style |= NSWindowStyleMaskMiniaturizable;
  1199. if ((flags & windowHasCloseButton) != 0) style |= NSWindowStyleMaskClosable;
  1200. if ((flags & windowIsResizable) != 0) style |= NSWindowStyleMaskResizable;
  1201. return style;
  1202. }
  1203. static NSArray* getSupportedDragTypes()
  1204. {
  1205. const auto type = []
  1206. {
  1207. #if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
  1208. if (@available (macOS 10.13, *))
  1209. return NSPasteboardTypeFileURL;
  1210. #endif
  1211. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  1212. return (NSString*) kUTTypeFileURL;
  1213. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  1214. }();
  1215. return [NSArray arrayWithObjects: type, (NSString*) kPasteboardTypeFileURLPromise, NSPasteboardTypeString, nil];
  1216. }
  1217. BOOL sendDragCallback (bool (ComponentPeer::* callback) (const DragInfo&), id <NSDraggingInfo> sender)
  1218. {
  1219. NSPasteboard* pasteboard = [sender draggingPasteboard];
  1220. NSString* contentType = [pasteboard availableTypeFromArray: getSupportedDragTypes()];
  1221. if (contentType == nil)
  1222. return false;
  1223. const auto p = localToGlobal (convertToPointFloat ([view convertPoint: [sender draggingLocation] fromView: nil]));
  1224. ComponentPeer::DragInfo dragInfo;
  1225. dragInfo.position = ScalingHelpers::screenPosToLocalPos (component, p).roundToInt();
  1226. if (contentType == NSPasteboardTypeString)
  1227. dragInfo.text = nsStringToJuce ([pasteboard stringForType: NSPasteboardTypeString]);
  1228. else
  1229. dragInfo.files = getDroppedFiles (pasteboard, contentType);
  1230. if (! dragInfo.isEmpty())
  1231. return (this->*callback) (dragInfo);
  1232. return false;
  1233. }
  1234. StringArray getDroppedFiles (NSPasteboard* pasteboard, NSString* contentType)
  1235. {
  1236. StringArray files;
  1237. NSString* iTunesPasteboardType = nsStringLiteral ("CorePasteboardFlavorType 0x6974756E"); // 'itun'
  1238. if ([contentType isEqualToString: (NSString*) kPasteboardTypeFileURLPromise]
  1239. && [[pasteboard types] containsObject: iTunesPasteboardType])
  1240. {
  1241. id list = [pasteboard propertyListForType: iTunesPasteboardType];
  1242. if ([list isKindOfClass: [NSDictionary class]])
  1243. {
  1244. NSDictionary* iTunesDictionary = (NSDictionary*) list;
  1245. NSArray* tracks = [iTunesDictionary valueForKey: nsStringLiteral ("Tracks")];
  1246. NSEnumerator* enumerator = [tracks objectEnumerator];
  1247. NSDictionary* track;
  1248. while ((track = [enumerator nextObject]) != nil)
  1249. {
  1250. if (id value = [track valueForKey: nsStringLiteral ("Location")])
  1251. {
  1252. NSURL* url = [NSURL URLWithString: value];
  1253. if ([url isFileURL])
  1254. files.add (nsStringToJuce ([url path]));
  1255. }
  1256. }
  1257. }
  1258. }
  1259. else
  1260. {
  1261. NSArray* items = [pasteboard readObjectsForClasses:@[[NSURL class]] options: nil];
  1262. for (unsigned int i = 0; i < [items count]; ++i)
  1263. {
  1264. NSURL* url = [items objectAtIndex: i];
  1265. if ([url isFileURL])
  1266. files.add (nsStringToJuce ([url path]));
  1267. }
  1268. }
  1269. return files;
  1270. }
  1271. //==============================================================================
  1272. void viewFocusGain()
  1273. {
  1274. if (currentlyFocusedPeer != this)
  1275. {
  1276. if (ComponentPeer::isValidPeer (currentlyFocusedPeer))
  1277. currentlyFocusedPeer->handleFocusLoss();
  1278. currentlyFocusedPeer = this;
  1279. handleFocusGain();
  1280. }
  1281. }
  1282. void viewFocusLoss()
  1283. {
  1284. if (currentlyFocusedPeer == this)
  1285. {
  1286. currentlyFocusedPeer = nullptr;
  1287. handleFocusLoss();
  1288. }
  1289. }
  1290. bool isFocused() const override
  1291. {
  1292. return (isSharedWindow || ! JUCEApplication::isStandaloneApp())
  1293. ? this == currentlyFocusedPeer
  1294. : [window isKeyWindow];
  1295. }
  1296. void grabFocus() override
  1297. {
  1298. if (window != nil)
  1299. {
  1300. [window makeKeyWindow];
  1301. [window makeFirstResponder: view];
  1302. viewFocusGain();
  1303. }
  1304. }
  1305. void textInputRequired (Point<int>, TextInputTarget&) override {}
  1306. void closeInputMethodContext() override
  1307. {
  1308. stringBeingComposed.clear();
  1309. const auto* inputContext = [NSTextInputContext currentInputContext];
  1310. if (inputContext != nil)
  1311. [inputContext discardMarkedText];
  1312. }
  1313. void resetWindowPresentation()
  1314. {
  1315. if (hasNativeTitleBar())
  1316. {
  1317. [window setStyleMask: (NSViewComponentPeer::getNSWindowStyleMask (getStyleFlags()))];
  1318. setTitle (getComponent().getName()); // required to force the OS to update the title
  1319. }
  1320. [NSApp setPresentationOptions: NSApplicationPresentationDefault];
  1321. setCollectionBehaviour (isFullScreen());
  1322. }
  1323. void setHasChangedSinceSaved (bool b) override
  1324. {
  1325. if (! isSharedWindow)
  1326. [window setDocumentEdited: b];
  1327. }
  1328. //==============================================================================
  1329. NSWindow* window = nil;
  1330. NSView* view = nil;
  1331. WeakReference<Component> safeComponent;
  1332. bool isSharedWindow = false;
  1333. #if USE_COREGRAPHICS_RENDERING
  1334. bool usingCoreGraphics = true;
  1335. #else
  1336. bool usingCoreGraphics = false;
  1337. #endif
  1338. bool isZooming = false, isFirstLiveResize = false, textWasInserted = false;
  1339. bool isStretchingTop = false, isStretchingLeft = false, isStretchingBottom = false, isStretchingRight = false;
  1340. bool windowRepresentsFile = false;
  1341. bool isAlwaysOnTop = false, wasAlwaysOnTop = false;
  1342. String stringBeingComposed;
  1343. NSNotificationCenter* notificationCenter = nil;
  1344. Rectangle<float> lastSizeBeforeZoom;
  1345. RectangleList<float> deferredRepaints;
  1346. uint32 lastRepaintTime;
  1347. static ComponentPeer* currentlyFocusedPeer;
  1348. static Array<int> keysCurrentlyDown;
  1349. static int insideToFrontCall;
  1350. static const SEL dismissModalsSelector;
  1351. static const SEL frameChangedSelector;
  1352. static const SEL asyncMouseDownSelector;
  1353. static const SEL asyncMouseUpSelector;
  1354. static const SEL becomeKeySelector;
  1355. static const SEL resignKeySelector;
  1356. private:
  1357. static NSView* createViewInstance();
  1358. static NSWindow* createWindowInstance();
  1359. void sendMouseEnterExit (NSEvent* ev)
  1360. {
  1361. if (auto* area = [ev trackingArea])
  1362. if (! [[view trackingAreas] containsObject: area])
  1363. return;
  1364. sendMouseEvent (ev);
  1365. }
  1366. static void setOwner (id viewOrWindow, NSViewComponentPeer* newOwner)
  1367. {
  1368. object_setInstanceVariable (viewOrWindow, "owner", newOwner);
  1369. }
  1370. void getClipRects (RectangleList<int>& clip, Point<int> offset, int clipW, int clipH)
  1371. {
  1372. const NSRect* rects = nullptr;
  1373. NSInteger numRects = 0;
  1374. [view getRectsBeingDrawn: &rects count: &numRects];
  1375. const Rectangle<int> clipBounds (clipW, clipH);
  1376. clip.ensureStorageAllocated ((int) numRects);
  1377. for (int i = 0; i < numRects; ++i)
  1378. clip.addWithoutMerging (clipBounds.getIntersection (Rectangle<int> (roundToInt (rects[i].origin.x) + offset.x,
  1379. roundToInt (rects[i].origin.y) + offset.y,
  1380. roundToInt (rects[i].size.width),
  1381. roundToInt (rects[i].size.height))));
  1382. }
  1383. static void appFocusChanged()
  1384. {
  1385. keysCurrentlyDown.clear();
  1386. if (isValidPeer (currentlyFocusedPeer))
  1387. {
  1388. if (Process::isForegroundProcess())
  1389. {
  1390. currentlyFocusedPeer->handleFocusGain();
  1391. ModalComponentManager::getInstance()->bringModalComponentsToFront();
  1392. }
  1393. else
  1394. {
  1395. currentlyFocusedPeer->handleFocusLoss();
  1396. }
  1397. }
  1398. }
  1399. static bool checkEventBlockedByModalComps (NSEvent* e)
  1400. {
  1401. if (Component::getNumCurrentlyModalComponents() == 0)
  1402. return false;
  1403. NSWindow* const w = [e window];
  1404. if (w == nil || [w worksWhenModal])
  1405. return false;
  1406. bool isKey = false, isInputAttempt = false;
  1407. switch ([e type])
  1408. {
  1409. case NSEventTypeKeyDown:
  1410. case NSEventTypeKeyUp:
  1411. isKey = isInputAttempt = true;
  1412. break;
  1413. case NSEventTypeLeftMouseDown:
  1414. case NSEventTypeRightMouseDown:
  1415. case NSEventTypeOtherMouseDown:
  1416. isInputAttempt = true;
  1417. break;
  1418. case NSEventTypeLeftMouseDragged:
  1419. case NSEventTypeRightMouseDragged:
  1420. case NSEventTypeLeftMouseUp:
  1421. case NSEventTypeRightMouseUp:
  1422. case NSEventTypeOtherMouseUp:
  1423. case NSEventTypeOtherMouseDragged:
  1424. if (Desktop::getInstance().getDraggingMouseSource(0) != nullptr)
  1425. return false;
  1426. break;
  1427. case NSEventTypeMouseMoved:
  1428. case NSEventTypeMouseEntered:
  1429. case NSEventTypeMouseExited:
  1430. case NSEventTypeCursorUpdate:
  1431. case NSEventTypeScrollWheel:
  1432. case NSEventTypeTabletPoint:
  1433. case NSEventTypeTabletProximity:
  1434. break;
  1435. case NSEventTypeFlagsChanged:
  1436. case NSEventTypeAppKitDefined:
  1437. case NSEventTypeSystemDefined:
  1438. case NSEventTypeApplicationDefined:
  1439. case NSEventTypePeriodic:
  1440. case NSEventTypeGesture:
  1441. case NSEventTypeMagnify:
  1442. case NSEventTypeSwipe:
  1443. case NSEventTypeRotate:
  1444. case NSEventTypeBeginGesture:
  1445. case NSEventTypeEndGesture:
  1446. case NSEventTypeQuickLook:
  1447. #if JUCE_64BIT
  1448. case NSEventTypeSmartMagnify:
  1449. case NSEventTypePressure:
  1450. #endif
  1451. #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
  1452. #if JUCE_64BIT
  1453. case NSEventTypeDirectTouch:
  1454. #endif
  1455. #if defined (MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
  1456. case NSEventTypeChangeMode:
  1457. #endif
  1458. #endif
  1459. default:
  1460. return false;
  1461. }
  1462. for (int i = ComponentPeer::getNumPeers(); --i >= 0;)
  1463. {
  1464. if (auto* peer = dynamic_cast<NSViewComponentPeer*> (ComponentPeer::getPeer (i)))
  1465. {
  1466. if ([peer->view window] == w)
  1467. {
  1468. if (isKey)
  1469. {
  1470. if (peer->view == [w firstResponder])
  1471. return false;
  1472. }
  1473. else
  1474. {
  1475. if (peer->isSharedWindow
  1476. ? NSPointInRect ([peer->view convertPoint: [e locationInWindow] fromView: nil], [peer->view bounds])
  1477. : NSPointInRect ([e locationInWindow], NSMakeRect (0, 0, [w frame].size.width, [w frame].size.height)))
  1478. return false;
  1479. }
  1480. }
  1481. }
  1482. }
  1483. if (isInputAttempt)
  1484. {
  1485. if (! [NSApp isActive])
  1486. [NSApp activateIgnoringOtherApps: YES];
  1487. if (auto* modal = Component::getCurrentlyModalComponent())
  1488. modal->inputAttemptWhenModal();
  1489. }
  1490. return true;
  1491. }
  1492. void setFullScreenSizeConstraints (const ComponentBoundsConstrainer& c)
  1493. {
  1494. if (@available (macOS 10.11, *))
  1495. {
  1496. const auto minSize = NSMakeSize (static_cast<float> (c.getMinimumWidth()),
  1497. 0.0f);
  1498. [window setMinFullScreenContentSize: minSize];
  1499. [window setMaxFullScreenContentSize: NSMakeSize (100000, 100000)];
  1500. }
  1501. }
  1502. //==============================================================================
  1503. void onDisplaySourceCallback()
  1504. {
  1505. setNeedsDisplayRectangles();
  1506. }
  1507. void onDisplayLinkCallback()
  1508. {
  1509. dispatch_source_merge_data (displaySource, 1);
  1510. }
  1511. static CVReturn displayLinkCallback (CVDisplayLinkRef, const CVTimeStamp*, const CVTimeStamp*,
  1512. CVOptionFlags, CVOptionFlags*, void* context)
  1513. {
  1514. static_cast<NSViewComponentPeer*> (context)->onDisplayLinkCallback();
  1515. return kCVReturnSuccess;
  1516. }
  1517. void updateCVDisplayLinkScreen()
  1518. {
  1519. auto viewDisplayID = (CGDirectDisplayID) [[window.screen.deviceDescription objectForKey: @"NSScreenNumber"] unsignedIntegerValue];
  1520. auto result = CVDisplayLinkSetCurrentCGDisplay (displayLink, viewDisplayID);
  1521. jassertquiet (result == kCVReturnSuccess);
  1522. }
  1523. void createCVDisplayLink()
  1524. {
  1525. displaySource = dispatch_source_create (DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
  1526. dispatch_source_set_event_handler (displaySource, ^(){ onDisplaySourceCallback(); });
  1527. dispatch_resume (displaySource);
  1528. auto cvReturn = CVDisplayLinkCreateWithActiveCGDisplays (&displayLink);
  1529. jassertquiet (cvReturn == kCVReturnSuccess);
  1530. cvReturn = CVDisplayLinkSetOutputCallback (displayLink, &displayLinkCallback, this);
  1531. jassertquiet (cvReturn == kCVReturnSuccess);
  1532. CVDisplayLinkStart (displayLink);
  1533. }
  1534. CVDisplayLinkRef displayLink = nullptr;
  1535. dispatch_source_t displaySource = nullptr;
  1536. std::unique_ptr<CoreGraphicsMetalLayerRenderer> metalRenderer;
  1537. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewComponentPeer)
  1538. };
  1539. int NSViewComponentPeer::insideToFrontCall = 0;
  1540. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  1541. const SEL NSViewComponentPeer::dismissModalsSelector = @selector (dismissModals);
  1542. const SEL NSViewComponentPeer::frameChangedSelector = @selector (frameChanged:);
  1543. const SEL NSViewComponentPeer::asyncMouseDownSelector = @selector (asyncMouseDown:);
  1544. const SEL NSViewComponentPeer::asyncMouseUpSelector = @selector (asyncMouseUp:);
  1545. const SEL NSViewComponentPeer::becomeKeySelector = @selector (becomeKey:);
  1546. const SEL NSViewComponentPeer::resignKeySelector = @selector (resignKey:);
  1547. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  1548. //==============================================================================
  1549. template <typename Base>
  1550. struct NSViewComponentPeerWrapper : public Base
  1551. {
  1552. explicit NSViewComponentPeerWrapper (const char* baseName)
  1553. : Base (baseName)
  1554. {
  1555. Base::template addIvar<NSViewComponentPeer*> ("owner");
  1556. }
  1557. static NSViewComponentPeer* getOwner (id self)
  1558. {
  1559. return getIvar<NSViewComponentPeer*> (self, "owner");
  1560. }
  1561. static id getAccessibleChild (id self)
  1562. {
  1563. if (auto* owner = getOwner (self))
  1564. if (auto* handler = owner->getComponent().getAccessibilityHandler())
  1565. return (id) handler->getNativeImplementation();
  1566. return nil;
  1567. }
  1568. };
  1569. struct JuceNSViewClass : public NSViewComponentPeerWrapper<ObjCClass<NSView>>
  1570. {
  1571. JuceNSViewClass() : NSViewComponentPeerWrapper ("JUCEView_")
  1572. {
  1573. addMethod (@selector (isOpaque), isOpaque);
  1574. addMethod (@selector (drawRect:), drawRect);
  1575. addMethod (@selector (mouseDown:), mouseDown);
  1576. addMethod (@selector (mouseUp:), mouseUp);
  1577. addMethod (@selector (mouseDragged:), mouseDragged);
  1578. addMethod (@selector (mouseMoved:), mouseMoved);
  1579. addMethod (@selector (mouseEntered:), mouseEntered);
  1580. addMethod (@selector (mouseExited:), mouseExited);
  1581. addMethod (@selector (rightMouseDown:), mouseDown);
  1582. addMethod (@selector (rightMouseDragged:), mouseDragged);
  1583. addMethod (@selector (rightMouseUp:), mouseUp);
  1584. addMethod (@selector (otherMouseDown:), mouseDown);
  1585. addMethod (@selector (otherMouseDragged:), mouseDragged);
  1586. addMethod (@selector (otherMouseUp:), mouseUp);
  1587. addMethod (@selector (scrollWheel:), scrollWheel);
  1588. addMethod (@selector (magnifyWithEvent:), magnify);
  1589. addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse);
  1590. addMethod (@selector (windowWillMiniaturize:), windowWillMiniaturize);
  1591. addMethod (@selector (windowDidDeminiaturize:), windowDidDeminiaturize);
  1592. addMethod (@selector (windowDidChangeScreen:), windowDidChangeScreen);
  1593. addMethod (@selector (wantsDefaultClipping), wantsDefaultClipping);
  1594. addMethod (@selector (worksWhenModal), worksWhenModal);
  1595. addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow);
  1596. addMethod (@selector (viewWillDraw), viewWillDraw);
  1597. addMethod (@selector (keyDown:), keyDown);
  1598. addMethod (@selector (keyUp:), keyUp);
  1599. addMethod (@selector (insertText:), insertText);
  1600. addMethod (@selector (doCommandBySelector:), doCommandBySelector);
  1601. addMethod (@selector (setMarkedText:selectedRange:), setMarkedText);
  1602. addMethod (@selector (unmarkText), unmarkText);
  1603. addMethod (@selector (hasMarkedText), hasMarkedText);
  1604. addMethod (@selector (conversationIdentifier), conversationIdentifier);
  1605. addMethod (@selector (attributedSubstringFromRange:), attributedSubstringFromRange);
  1606. addMethod (@selector (markedRange), markedRange);
  1607. addMethod (@selector (selectedRange), selectedRange);
  1608. addMethod (@selector (firstRectForCharacterRange:), firstRectForCharacterRange);
  1609. addMethod (@selector (characterIndexForPoint:), characterIndexForPoint);
  1610. addMethod (@selector (validAttributesForMarkedText), validAttributesForMarkedText);
  1611. addMethod (@selector (flagsChanged:), flagsChanged);
  1612. addMethod (@selector (becomeFirstResponder), becomeFirstResponder);
  1613. addMethod (@selector (resignFirstResponder), resignFirstResponder);
  1614. addMethod (@selector (acceptsFirstResponder), acceptsFirstResponder);
  1615. addMethod (@selector (draggingEntered:), draggingEntered);
  1616. addMethod (@selector (draggingUpdated:), draggingUpdated);
  1617. addMethod (@selector (draggingEnded:), draggingEnded);
  1618. addMethod (@selector (draggingExited:), draggingExited);
  1619. addMethod (@selector (prepareForDragOperation:), prepareForDragOperation);
  1620. addMethod (@selector (performDragOperation:), performDragOperation);
  1621. addMethod (@selector (concludeDragOperation:), concludeDragOperation);
  1622. addMethod (@selector (paste:), paste);
  1623. addMethod (@selector (copy:), copy);
  1624. addMethod (@selector (cut:), cut);
  1625. addMethod (@selector (selectAll:), selectAll);
  1626. addMethod (@selector (viewWillMoveToWindow:), willMoveToWindow);
  1627. addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement);
  1628. addMethod (@selector (accessibilityChildren), getAccessibilityChildren);
  1629. addMethod (@selector (accessibilityHitTest:), accessibilityHitTest);
  1630. addMethod (@selector (accessibilityFocusedUIElement), getAccessibilityFocusedUIElement);
  1631. // deprecated methods required for backwards compatibility
  1632. addMethod (@selector (accessibilityIsIgnored), getAccessibilityIsIgnored);
  1633. addMethod (@selector (accessibilityAttributeValue:), getAccessibilityAttributeValue);
  1634. addMethod (@selector (isFlipped), isFlipped);
  1635. addMethod (NSViewComponentPeer::dismissModalsSelector, dismissModals);
  1636. addMethod (NSViewComponentPeer::asyncMouseDownSelector, asyncMouseDown);
  1637. addMethod (NSViewComponentPeer::asyncMouseUpSelector, asyncMouseUp);
  1638. addMethod (NSViewComponentPeer::frameChangedSelector, frameChanged);
  1639. addMethod (NSViewComponentPeer::becomeKeySelector, becomeKey);
  1640. addMethod (NSViewComponentPeer::resignKeySelector, resignKey);
  1641. addMethod (@selector (performKeyEquivalent:), performKeyEquivalent);
  1642. addProtocol (@protocol (NSTextInput));
  1643. registerClass();
  1644. }
  1645. private:
  1646. static void mouseDown (id self, SEL s, NSEvent* ev)
  1647. {
  1648. if (JUCEApplicationBase::isStandaloneApp())
  1649. {
  1650. asyncMouseDown (self, s, ev);
  1651. }
  1652. else
  1653. {
  1654. // In some host situations, the host will stop modal loops from working
  1655. // correctly if they're called from a mouse event, so we'll trigger
  1656. // the event asynchronously..
  1657. [self performSelectorOnMainThread: NSViewComponentPeer::asyncMouseDownSelector
  1658. withObject: ev
  1659. waitUntilDone: NO];
  1660. }
  1661. }
  1662. static void mouseUp (id self, SEL s, NSEvent* ev)
  1663. {
  1664. if (JUCEApplicationBase::isStandaloneApp())
  1665. {
  1666. asyncMouseUp (self, s, ev);
  1667. }
  1668. else
  1669. {
  1670. // In some host situations, the host will stop modal loops from working
  1671. // correctly if they're called from a mouse event, so we'll trigger
  1672. // the event asynchronously..
  1673. [self performSelectorOnMainThread: NSViewComponentPeer::asyncMouseUpSelector
  1674. withObject: ev
  1675. waitUntilDone: NO];
  1676. }
  1677. }
  1678. static void asyncMouseDown (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseDown, ev); }
  1679. static void asyncMouseUp (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseUp, ev); }
  1680. static void mouseDragged (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseDrag, ev); }
  1681. static void mouseMoved (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseMove, ev); }
  1682. static void mouseEntered (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseEnter, ev); }
  1683. static void mouseExited (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseExit, ev); }
  1684. static void scrollWheel (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseWheel, ev); }
  1685. static void magnify (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMagnify, ev); }
  1686. static void copy (id self, SEL, NSObject* s) { callOnOwner (self, &NSViewComponentPeer::redirectCopy, s); }
  1687. static void paste (id self, SEL, NSObject* s) { callOnOwner (self, &NSViewComponentPeer::redirectPaste, s); }
  1688. static void cut (id self, SEL, NSObject* s) { callOnOwner (self, &NSViewComponentPeer::redirectCut, s); }
  1689. static void selectAll (id self, SEL, NSObject* s) { callOnOwner (self, &NSViewComponentPeer::redirectSelectAll, s); }
  1690. static void willMoveToWindow (id self, SEL, NSWindow* w) { callOnOwner (self, &NSViewComponentPeer::redirectWillMoveToWindow, w); }
  1691. static BOOL acceptsFirstMouse (id, SEL, NSEvent*) { return YES; }
  1692. static BOOL wantsDefaultClipping (id, SEL) { return YES; } // (this is the default, but may want to customise it in future)
  1693. static BOOL worksWhenModal (id self, SEL) { if (auto* p = getOwner (self)) return p->worksWhenModal(); return NO; }
  1694. static void drawRect (id self, SEL, NSRect r) { callOnOwner (self, &NSViewComponentPeer::drawRect, r); }
  1695. static void frameChanged (id self, SEL, NSNotification*) { callOnOwner (self, &NSViewComponentPeer::redirectMovedOrResized); }
  1696. static void viewDidMoveToWindow (id self, SEL) { callOnOwner (self, &NSViewComponentPeer::viewMovedToWindow); }
  1697. static void dismissModals (id self, SEL) { callOnOwner (self, &NSViewComponentPeer::dismissModals); }
  1698. static void becomeKey (id self, SEL) { callOnOwner (self, &NSViewComponentPeer::becomeKey); }
  1699. static void resignKey (id self, SEL) { callOnOwner (self, &NSViewComponentPeer::resignKey); }
  1700. static BOOL isFlipped (id, SEL) { return true; }
  1701. static void viewWillDraw (id self, SEL)
  1702. {
  1703. // Without setting contentsFormat macOS Big Sur will always set the invalid area
  1704. // to be the entire frame.
  1705. #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
  1706. if (@available (macOS 10.12, *))
  1707. {
  1708. CALayer* layer = ((NSView*) self).layer;
  1709. layer.contentsFormat = kCAContentsFormatRGBA8Uint;
  1710. }
  1711. #endif
  1712. sendSuperclassMessage<void> (self, @selector (viewWillDraw));
  1713. }
  1714. static void windowWillMiniaturize (id self, SEL, NSNotification*)
  1715. {
  1716. if (auto* p = getOwner (self))
  1717. {
  1718. if (p->isAlwaysOnTop)
  1719. {
  1720. // there is a bug when restoring minimised always on top windows so we need
  1721. // to remove this behaviour before minimising and restore it afterwards
  1722. p->setAlwaysOnTop (false);
  1723. p->wasAlwaysOnTop = true;
  1724. }
  1725. }
  1726. }
  1727. static void windowDidDeminiaturize (id self, SEL, NSNotification*)
  1728. {
  1729. if (auto* p = getOwner (self))
  1730. {
  1731. if (p->wasAlwaysOnTop)
  1732. p->setAlwaysOnTop (true);
  1733. p->redirectMovedOrResized();
  1734. }
  1735. }
  1736. static void windowDidChangeScreen (id self, SEL, NSNotification*)
  1737. {
  1738. if (auto* p = getOwner (self))
  1739. p->windowDidChangeScreen();
  1740. }
  1741. static BOOL isOpaque (id self, SEL)
  1742. {
  1743. auto* owner = getOwner (self);
  1744. return owner == nullptr || owner->getComponent().isOpaque();
  1745. }
  1746. //==============================================================================
  1747. static void keyDown (id self, SEL, NSEvent* ev)
  1748. {
  1749. if (auto* owner = getOwner (self))
  1750. {
  1751. auto* target = owner->findCurrentTextInputTarget();
  1752. owner->textWasInserted = false;
  1753. if (target != nullptr)
  1754. [(NSView*) self interpretKeyEvents: [NSArray arrayWithObject: ev]];
  1755. else
  1756. owner->stringBeingComposed.clear();
  1757. if (! (owner->textWasInserted || owner->redirectKeyDown (ev)))
  1758. sendSuperclassMessage<void> (self, @selector (keyDown:), ev);
  1759. }
  1760. }
  1761. static void keyUp (id self, SEL, NSEvent* ev)
  1762. {
  1763. auto* owner = getOwner (self);
  1764. if (! owner->redirectKeyUp (ev))
  1765. sendSuperclassMessage<void> (self, @selector (keyUp:), ev);
  1766. }
  1767. //==============================================================================
  1768. static void insertText (id self, SEL, id aString)
  1769. {
  1770. // This commits multi-byte text when return is pressed, or after every keypress for western keyboards
  1771. if (auto* owner = getOwner (self))
  1772. {
  1773. NSString* newText = [aString isKindOfClass: [NSAttributedString class]] ? [aString string] : aString;
  1774. if ([newText length] > 0)
  1775. {
  1776. if (auto* target = owner->findCurrentTextInputTarget())
  1777. {
  1778. target->insertTextAtCaret (nsStringToJuce (newText));
  1779. owner->textWasInserted = true;
  1780. }
  1781. }
  1782. owner->stringBeingComposed.clear();
  1783. }
  1784. }
  1785. static void doCommandBySelector (id, SEL, SEL) {}
  1786. static void setMarkedText (id self, SEL, id aString, NSRange)
  1787. {
  1788. if (auto* owner = getOwner (self))
  1789. {
  1790. owner->stringBeingComposed = nsStringToJuce ([aString isKindOfClass: [NSAttributedString class]]
  1791. ? [aString string] : aString);
  1792. if (auto* target = owner->findCurrentTextInputTarget())
  1793. {
  1794. auto currentHighlight = target->getHighlightedRegion();
  1795. target->insertTextAtCaret (owner->stringBeingComposed);
  1796. target->setHighlightedRegion (currentHighlight.withLength (owner->stringBeingComposed.length()));
  1797. owner->textWasInserted = true;
  1798. }
  1799. }
  1800. }
  1801. static void unmarkText (id self, SEL)
  1802. {
  1803. if (auto* owner = getOwner (self))
  1804. {
  1805. if (owner->stringBeingComposed.isNotEmpty())
  1806. {
  1807. if (auto* target = owner->findCurrentTextInputTarget())
  1808. {
  1809. target->insertTextAtCaret (owner->stringBeingComposed);
  1810. owner->textWasInserted = true;
  1811. }
  1812. owner->stringBeingComposed.clear();
  1813. }
  1814. }
  1815. }
  1816. static BOOL hasMarkedText (id self, SEL)
  1817. {
  1818. auto* owner = getOwner (self);
  1819. return owner != nullptr && owner->stringBeingComposed.isNotEmpty();
  1820. }
  1821. static long conversationIdentifier (id self, SEL)
  1822. {
  1823. return (long) (pointer_sized_int) self;
  1824. }
  1825. static NSAttributedString* attributedSubstringFromRange (id self, SEL, NSRange theRange)
  1826. {
  1827. if (auto* owner = getOwner (self))
  1828. {
  1829. if (auto* target = owner->findCurrentTextInputTarget())
  1830. {
  1831. Range<int> r ((int) theRange.location,
  1832. (int) (theRange.location + theRange.length));
  1833. return [[[NSAttributedString alloc] initWithString: juceStringToNS (target->getTextInRange (r))] autorelease];
  1834. }
  1835. }
  1836. return nil;
  1837. }
  1838. static NSRange markedRange (id self, SEL)
  1839. {
  1840. if (auto* owner = getOwner (self))
  1841. if (owner->stringBeingComposed.isNotEmpty())
  1842. return NSMakeRange (0, (NSUInteger) owner->stringBeingComposed.length());
  1843. return NSMakeRange (NSNotFound, 0);
  1844. }
  1845. static NSRange selectedRange (id self, SEL)
  1846. {
  1847. if (auto* owner = getOwner (self))
  1848. {
  1849. if (auto* target = owner->findCurrentTextInputTarget())
  1850. {
  1851. auto highlight = target->getHighlightedRegion();
  1852. if (! highlight.isEmpty())
  1853. return NSMakeRange ((NSUInteger) highlight.getStart(),
  1854. (NSUInteger) highlight.getLength());
  1855. }
  1856. }
  1857. return NSMakeRange (NSNotFound, 0);
  1858. }
  1859. static NSRect firstRectForCharacterRange (id self, SEL, NSRange)
  1860. {
  1861. if (auto* owner = getOwner (self))
  1862. if (auto* comp = dynamic_cast<Component*> (owner->findCurrentTextInputTarget()))
  1863. return flippedScreenRect (makeNSRect (comp->getScreenBounds()));
  1864. return NSZeroRect;
  1865. }
  1866. static NSUInteger characterIndexForPoint (id, SEL, NSPoint) { return NSNotFound; }
  1867. static NSArray* validAttributesForMarkedText (id, SEL) { return [NSArray array]; }
  1868. //==============================================================================
  1869. static void flagsChanged (id self, SEL, NSEvent* ev)
  1870. {
  1871. callOnOwner (self, &NSViewComponentPeer::redirectModKeyChange, ev);
  1872. }
  1873. static BOOL becomeFirstResponder (id self, SEL)
  1874. {
  1875. callOnOwner (self, &NSViewComponentPeer::viewFocusGain);
  1876. return YES;
  1877. }
  1878. static BOOL resignFirstResponder (id self, SEL)
  1879. {
  1880. callOnOwner (self, &NSViewComponentPeer::viewFocusLoss);
  1881. return YES;
  1882. }
  1883. static BOOL acceptsFirstResponder (id self, SEL)
  1884. {
  1885. auto* owner = getOwner (self);
  1886. return owner != nullptr && owner->canBecomeKeyWindow();
  1887. }
  1888. //==============================================================================
  1889. static NSDragOperation draggingEntered (id self, SEL s, id<NSDraggingInfo> sender)
  1890. {
  1891. return draggingUpdated (self, s, sender);
  1892. }
  1893. static NSDragOperation draggingUpdated (id self, SEL, id<NSDraggingInfo> sender)
  1894. {
  1895. if (auto* owner = getOwner (self))
  1896. if (owner->sendDragCallback (&NSViewComponentPeer::handleDragMove, sender))
  1897. return NSDragOperationGeneric;
  1898. return NSDragOperationNone;
  1899. }
  1900. static void draggingEnded (id self, SEL s, id<NSDraggingInfo> sender)
  1901. {
  1902. draggingExited (self, s, sender);
  1903. }
  1904. static void draggingExited (id self, SEL, id<NSDraggingInfo> sender)
  1905. {
  1906. callOnOwner (self, &NSViewComponentPeer::sendDragCallback, &NSViewComponentPeer::handleDragExit, sender);
  1907. }
  1908. static BOOL prepareForDragOperation (id, SEL, id<NSDraggingInfo>)
  1909. {
  1910. return YES;
  1911. }
  1912. static BOOL performDragOperation (id self, SEL, id<NSDraggingInfo> sender)
  1913. {
  1914. auto* owner = getOwner (self);
  1915. return owner != nullptr && owner->sendDragCallback (&NSViewComponentPeer::handleDragDrop, sender);
  1916. }
  1917. static void concludeDragOperation (id, SEL, id<NSDraggingInfo>) {}
  1918. //==============================================================================
  1919. static BOOL getIsAccessibilityElement (id, SEL)
  1920. {
  1921. return NO;
  1922. }
  1923. static NSArray* getAccessibilityChildren (id self, SEL)
  1924. {
  1925. return NSAccessibilityUnignoredChildrenForOnlyChild (getAccessibleChild (self));
  1926. }
  1927. static id accessibilityHitTest (id self, SEL, NSPoint point)
  1928. {
  1929. return [getAccessibleChild (self) accessibilityHitTest: point];
  1930. }
  1931. static id getAccessibilityFocusedUIElement (id self, SEL)
  1932. {
  1933. return [getAccessibleChild (self) accessibilityFocusedUIElement];
  1934. }
  1935. static BOOL getAccessibilityIsIgnored (id, SEL)
  1936. {
  1937. return YES;
  1938. }
  1939. static id getAccessibilityAttributeValue (id self, SEL, NSString* attribute)
  1940. {
  1941. if ([attribute isEqualToString: NSAccessibilityChildrenAttribute])
  1942. return getAccessibilityChildren (self, {});
  1943. return sendSuperclassMessage<id> (self, @selector (accessibilityAttributeValue:), attribute);
  1944. }
  1945. static bool tryPassingKeyEventToPeer (NSEvent* e)
  1946. {
  1947. if ([e type] != NSEventTypeKeyDown && [e type] != NSEventTypeKeyUp)
  1948. return false;
  1949. if (auto* focused = Component::getCurrentlyFocusedComponent())
  1950. {
  1951. if (auto* peer = dynamic_cast<NSViewComponentPeer*> (focused->getPeer()))
  1952. {
  1953. return [e type] == NSEventTypeKeyDown ? peer->redirectKeyDown (e)
  1954. : peer->redirectKeyUp (e);
  1955. }
  1956. }
  1957. return false;
  1958. }
  1959. static BOOL performKeyEquivalent (id self, SEL s, NSEvent* event)
  1960. {
  1961. // We try passing shortcut keys to the currently focused component first.
  1962. // If the component doesn't want the event, we'll fall back to the superclass
  1963. // implementation, which will pass the event to the main menu.
  1964. if (tryPassingKeyEventToPeer (event))
  1965. return YES;
  1966. return sendSuperclassMessage<BOOL> (self, s, event);
  1967. }
  1968. template <typename Func, typename... Args>
  1969. static void callOnOwner (id self, Func&& func, Args&&... args)
  1970. {
  1971. if (auto* owner = getOwner (self))
  1972. (owner->*func) (std::forward<Args> (args)...);
  1973. }
  1974. };
  1975. //==============================================================================
  1976. struct JuceNSWindowClass : public NSViewComponentPeerWrapper<ObjCClass<NSWindow>>
  1977. {
  1978. JuceNSWindowClass() : NSViewComponentPeerWrapper ("JUCEWindow_")
  1979. {
  1980. addMethod (@selector (canBecomeKeyWindow), canBecomeKeyWindow);
  1981. addMethod (@selector (canBecomeMainWindow), canBecomeMainWindow);
  1982. addMethod (@selector (becomeKeyWindow), becomeKeyWindow);
  1983. addMethod (@selector (resignKeyWindow), resignKeyWindow);
  1984. addMethod (@selector (windowShouldClose:), windowShouldClose);
  1985. addMethod (@selector (constrainFrameRect:toScreen:), constrainFrameRect);
  1986. addMethod (@selector (windowWillResize:toSize:), windowWillResize);
  1987. addMethod (@selector (windowDidExitFullScreen:), windowDidExitFullScreen);
  1988. addMethod (@selector (windowWillEnterFullScreen:), windowWillEnterFullScreen);
  1989. addMethod (@selector (windowWillExitFullScreen:), windowWillExitFullScreen);
  1990. addMethod (@selector (windowWillStartLiveResize:), windowWillStartLiveResize);
  1991. addMethod (@selector (windowDidEndLiveResize:), windowDidEndLiveResize);
  1992. addMethod (@selector (window:shouldPopUpDocumentPathMenu:), shouldPopUpPathMenu);
  1993. addMethod (@selector (isFlipped), isFlipped);
  1994. addMethod (@selector (windowWillUseStandardFrame:defaultFrame:), windowWillUseStandardFrame);
  1995. addMethod (@selector (windowShouldZoom:toFrame:), windowShouldZoomToFrame);
  1996. addMethod (@selector (accessibilityTitle), getAccessibilityTitle);
  1997. addMethod (@selector (accessibilityLabel), getAccessibilityLabel);
  1998. addMethod (@selector (accessibilityTopLevelUIElement), getAccessibilityWindow);
  1999. addMethod (@selector (accessibilityWindow), getAccessibilityWindow);
  2000. addMethod (@selector (accessibilityRole), getAccessibilityRole);
  2001. addMethod (@selector (accessibilitySubrole), getAccessibilitySubrole);
  2002. addMethod (@selector (keyDown:), keyDown);
  2003. addMethod (@selector (window:shouldDragDocumentWithEvent:from:withPasteboard:), shouldAllowIconDrag);
  2004. addMethod (@selector (toggleFullScreen:), toggleFullScreen);
  2005. addProtocol (@protocol (NSWindowDelegate));
  2006. registerClass();
  2007. }
  2008. private:
  2009. //==============================================================================
  2010. static BOOL isFlipped (id, SEL) { return true; }
  2011. // Key events will be processed by the peer's component.
  2012. // If the component is unable to use the event, it will be re-sent
  2013. // to performKeyEquivalent.
  2014. // performKeyEquivalent will send the event to the view's superclass,
  2015. // which will try passing the event to the main menu.
  2016. // If the event still hasn't been processed, it will be passed to the
  2017. // next responder in the chain, which will be the NSWindow for a peer
  2018. // that is on the desktop.
  2019. // If the NSWindow still doesn't handle the event, the Apple docs imply
  2020. // that the event should be sent to the NSApp for processing, but this
  2021. // doesn't seem to happen for keyDown events.
  2022. // Instead, the NSWindow attempts to process the event, fails, and
  2023. // triggers an annoying NSBeep.
  2024. // Overriding keyDown to "handle" the event seems to suppress the beep.
  2025. static void keyDown (id, SEL, NSEvent* ev)
  2026. {
  2027. ignoreUnused (ev);
  2028. #if JUCE_DEBUG_UNHANDLED_KEYPRESSES
  2029. DBG ("unhandled key down event with keycode: " << [ev keyCode]);
  2030. #endif
  2031. }
  2032. static NSRect windowWillUseStandardFrame (id self, SEL, NSWindow* window, NSRect r)
  2033. {
  2034. if (auto* owner = getOwner (self))
  2035. {
  2036. if (auto* constrainer = owner->getConstrainer())
  2037. {
  2038. if (auto* screen = [window screen])
  2039. {
  2040. const auto safeScreenBounds = convertToRectFloat (flippedScreenRect (owner->hasNativeTitleBar() ? r : [screen visibleFrame]));
  2041. const auto originalBounds = owner->getFrameSize().addedTo (owner->getComponent().getScreenBounds()).toFloat();
  2042. const auto expanded = originalBounds.withWidth ((float) constrainer->getMaximumWidth())
  2043. .withHeight ((float) constrainer->getMaximumHeight());
  2044. const auto constrained = expanded.constrainedWithin (safeScreenBounds);
  2045. return flippedScreenRect (makeNSRect ([&]
  2046. {
  2047. if (constrained == owner->getBounds().toFloat())
  2048. return owner->lastSizeBeforeZoom.toFloat();
  2049. owner->lastSizeBeforeZoom = owner->getBounds().toFloat();
  2050. return constrained;
  2051. }()));
  2052. }
  2053. }
  2054. }
  2055. return r;
  2056. }
  2057. static BOOL windowShouldZoomToFrame (id self, SEL, NSWindow*, NSRect)
  2058. {
  2059. if (auto* owner = getOwner (self))
  2060. if (owner->hasNativeTitleBar() && (owner->getStyleFlags() & ComponentPeer::windowIsResizable) == 0)
  2061. return NO;
  2062. return YES;
  2063. }
  2064. static BOOL canBecomeKeyWindow (id self, SEL)
  2065. {
  2066. auto* owner = getOwner (self);
  2067. return owner != nullptr
  2068. && owner->canBecomeKeyWindow()
  2069. && ! owner->isBlockedByModalComponent();
  2070. }
  2071. static BOOL canBecomeMainWindow (id self, SEL)
  2072. {
  2073. auto* owner = getOwner (self);
  2074. return owner != nullptr
  2075. && owner->canBecomeMainWindow()
  2076. && ! owner->isBlockedByModalComponent();
  2077. }
  2078. static void becomeKeyWindow (id self, SEL)
  2079. {
  2080. sendSuperclassMessage<void> (self, @selector (becomeKeyWindow));
  2081. if (auto* owner = getOwner (self))
  2082. {
  2083. if (owner->canBecomeKeyWindow())
  2084. {
  2085. owner->becomeKeyWindow();
  2086. return;
  2087. }
  2088. // this fixes a bug causing hidden windows to sometimes become visible when the app regains focus
  2089. if (! owner->getComponent().isVisible())
  2090. [(NSWindow*) self orderOut: nil];
  2091. }
  2092. }
  2093. static void resignKeyWindow (id self, SEL)
  2094. {
  2095. sendSuperclassMessage<void> (self, @selector (resignKeyWindow));
  2096. if (auto* owner = getOwner (self))
  2097. owner->resignKeyWindow();
  2098. }
  2099. static BOOL windowShouldClose (id self, SEL, id /*window*/)
  2100. {
  2101. auto* owner = getOwner (self);
  2102. return owner == nullptr || owner->windowShouldClose();
  2103. }
  2104. static NSRect constrainFrameRect (id self, SEL, NSRect frameRect, NSScreen* screen)
  2105. {
  2106. if (auto* owner = getOwner (self))
  2107. {
  2108. frameRect = sendSuperclassMessage<NSRect, NSRect, NSScreen*> (self, @selector (constrainFrameRect:toScreen:),
  2109. frameRect, screen);
  2110. frameRect = owner->constrainRect (frameRect);
  2111. }
  2112. return frameRect;
  2113. }
  2114. static NSSize windowWillResize (id self, SEL, NSWindow*, NSSize proposedFrameSize)
  2115. {
  2116. auto* owner = getOwner (self);
  2117. if (owner == nullptr || owner->isZooming)
  2118. return proposedFrameSize;
  2119. NSRect frameRect = flippedScreenRect ([(NSWindow*) self frame]);
  2120. frameRect.size = proposedFrameSize;
  2121. frameRect = owner->constrainRect (flippedScreenRect (frameRect));
  2122. owner->dismissModals();
  2123. return frameRect.size;
  2124. }
  2125. static void toggleFullScreen (id self, SEL name, id sender)
  2126. {
  2127. if (auto* owner = getOwner (self))
  2128. {
  2129. const auto isFullScreen = owner->isFullScreen();
  2130. if (! isFullScreen)
  2131. owner->lastSizeBeforeZoom = owner->getBounds().toFloat();
  2132. sendSuperclassMessage<void> (self, name, sender);
  2133. if (isFullScreen)
  2134. {
  2135. [NSApp setPresentationOptions: NSApplicationPresentationDefault];
  2136. owner->setBounds (owner->lastSizeBeforeZoom.toNearestInt(), false);
  2137. }
  2138. }
  2139. }
  2140. static void windowDidExitFullScreen (id self, SEL, NSNotification*)
  2141. {
  2142. if (auto* owner = getOwner (self))
  2143. owner->resetWindowPresentation();
  2144. }
  2145. static void windowWillExitFullScreen (id self, SEL, NSNotification*)
  2146. {
  2147. // The exit-fullscreen animation looks bad on Monterey if the window isn't resizable...
  2148. if (auto* owner = getOwner (self))
  2149. if (auto* window = owner->window)
  2150. [window setStyleMask: [window styleMask] | NSWindowStyleMaskResizable];
  2151. }
  2152. static void windowWillEnterFullScreen (id self, SEL, NSNotification*)
  2153. {
  2154. if (SystemStats::getOperatingSystemType() <= SystemStats::MacOSX_10_9)
  2155. return;
  2156. if (auto* owner = getOwner (self))
  2157. if (owner->hasNativeTitleBar() && (owner->getStyleFlags() & ComponentPeer::windowIsResizable) == 0)
  2158. [owner->window setStyleMask: NSWindowStyleMaskBorderless];
  2159. }
  2160. static void windowWillStartLiveResize (id self, SEL, NSNotification*)
  2161. {
  2162. if (auto* owner = getOwner (self))
  2163. owner->liveResizingStart();
  2164. }
  2165. static void windowDidEndLiveResize (id self, SEL, NSNotification*)
  2166. {
  2167. if (auto* owner = getOwner (self))
  2168. owner->liveResizingEnd();
  2169. }
  2170. static bool shouldPopUpPathMenu (id self, SEL, id /*window*/, NSMenu*)
  2171. {
  2172. if (auto* owner = getOwner (self))
  2173. return owner->windowRepresentsFile;
  2174. return false;
  2175. }
  2176. static bool shouldAllowIconDrag (id self, SEL, id /*window*/, NSEvent*, NSPoint, NSPasteboard*)
  2177. {
  2178. if (auto* owner = getOwner (self))
  2179. return owner->windowRepresentsFile;
  2180. return false;
  2181. }
  2182. static NSString* getAccessibilityTitle (id self, SEL)
  2183. {
  2184. return [self title];
  2185. }
  2186. static NSString* getAccessibilityLabel (id self, SEL)
  2187. {
  2188. return [getAccessibleChild (self) accessibilityLabel];
  2189. }
  2190. static id getAccessibilityWindow (id self, SEL)
  2191. {
  2192. return self;
  2193. }
  2194. static NSAccessibilityRole getAccessibilityRole (id, SEL)
  2195. {
  2196. return NSAccessibilityWindowRole;
  2197. }
  2198. static NSAccessibilityRole getAccessibilitySubrole (id self, SEL)
  2199. {
  2200. if (@available (macOS 10.10, *))
  2201. return [getAccessibleChild (self) accessibilitySubrole];
  2202. return nil;
  2203. }
  2204. };
  2205. NSView* NSViewComponentPeer::createViewInstance()
  2206. {
  2207. static JuceNSViewClass cls;
  2208. return cls.createInstance();
  2209. }
  2210. NSWindow* NSViewComponentPeer::createWindowInstance()
  2211. {
  2212. static JuceNSWindowClass cls;
  2213. return cls.createInstance();
  2214. }
  2215. //==============================================================================
  2216. ComponentPeer* NSViewComponentPeer::currentlyFocusedPeer = nullptr;
  2217. Array<int> NSViewComponentPeer::keysCurrentlyDown;
  2218. //==============================================================================
  2219. bool KeyPress::isKeyCurrentlyDown (int keyCode)
  2220. {
  2221. if (NSViewComponentPeer::keysCurrentlyDown.contains (keyCode))
  2222. return true;
  2223. if (keyCode >= 'A' && keyCode <= 'Z'
  2224. && NSViewComponentPeer::keysCurrentlyDown.contains ((int) CharacterFunctions::toLowerCase ((juce_wchar) keyCode)))
  2225. return true;
  2226. if (keyCode >= 'a' && keyCode <= 'z'
  2227. && NSViewComponentPeer::keysCurrentlyDown.contains ((int) CharacterFunctions::toUpperCase ((juce_wchar) keyCode)))
  2228. return true;
  2229. return false;
  2230. }
  2231. //==============================================================================
  2232. bool MouseInputSource::SourceList::addSource()
  2233. {
  2234. if (sources.size() == 0)
  2235. {
  2236. addSource (0, MouseInputSource::InputSourceType::mouse);
  2237. return true;
  2238. }
  2239. return false;
  2240. }
  2241. bool MouseInputSource::SourceList::canUseTouch()
  2242. {
  2243. return false;
  2244. }
  2245. //==============================================================================
  2246. void Desktop::setKioskComponent (Component* kioskComp, bool shouldBeEnabled, bool allowMenusAndBars)
  2247. {
  2248. auto* peer = dynamic_cast<NSViewComponentPeer*> (kioskComp->getPeer());
  2249. jassert (peer != nullptr); // (this should have been checked by the caller)
  2250. if (peer->hasNativeTitleBar())
  2251. {
  2252. if (shouldBeEnabled && ! allowMenusAndBars)
  2253. [NSApp setPresentationOptions: NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar];
  2254. else if (! shouldBeEnabled)
  2255. [NSApp setPresentationOptions: NSApplicationPresentationDefault];
  2256. peer->setFullScreen (true);
  2257. }
  2258. else
  2259. {
  2260. if (shouldBeEnabled)
  2261. {
  2262. [NSApp setPresentationOptions: (allowMenusAndBars ? (NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)
  2263. : (NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar))];
  2264. kioskComp->setBounds (getDisplays().getDisplayForRect (kioskComp->getScreenBounds())->totalArea);
  2265. peer->becomeKeyWindow();
  2266. }
  2267. else
  2268. {
  2269. peer->resetWindowPresentation();
  2270. }
  2271. }
  2272. }
  2273. void Desktop::allowedOrientationsChanged() {}
  2274. //==============================================================================
  2275. ComponentPeer* Component::createNewPeer (int styleFlags, void* windowToAttachTo)
  2276. {
  2277. return new NSViewComponentPeer (*this, styleFlags, (NSView*) windowToAttachTo);
  2278. }
  2279. //==============================================================================
  2280. const int KeyPress::spaceKey = ' ';
  2281. const int KeyPress::returnKey = 0x0d;
  2282. const int KeyPress::escapeKey = 0x1b;
  2283. const int KeyPress::backspaceKey = 0x7f;
  2284. const int KeyPress::leftKey = NSLeftArrowFunctionKey;
  2285. const int KeyPress::rightKey = NSRightArrowFunctionKey;
  2286. const int KeyPress::upKey = NSUpArrowFunctionKey;
  2287. const int KeyPress::downKey = NSDownArrowFunctionKey;
  2288. const int KeyPress::pageUpKey = NSPageUpFunctionKey;
  2289. const int KeyPress::pageDownKey = NSPageDownFunctionKey;
  2290. const int KeyPress::endKey = NSEndFunctionKey;
  2291. const int KeyPress::homeKey = NSHomeFunctionKey;
  2292. const int KeyPress::deleteKey = NSDeleteFunctionKey;
  2293. const int KeyPress::insertKey = -1;
  2294. const int KeyPress::tabKey = 9;
  2295. const int KeyPress::F1Key = NSF1FunctionKey;
  2296. const int KeyPress::F2Key = NSF2FunctionKey;
  2297. const int KeyPress::F3Key = NSF3FunctionKey;
  2298. const int KeyPress::F4Key = NSF4FunctionKey;
  2299. const int KeyPress::F5Key = NSF5FunctionKey;
  2300. const int KeyPress::F6Key = NSF6FunctionKey;
  2301. const int KeyPress::F7Key = NSF7FunctionKey;
  2302. const int KeyPress::F8Key = NSF8FunctionKey;
  2303. const int KeyPress::F9Key = NSF9FunctionKey;
  2304. const int KeyPress::F10Key = NSF10FunctionKey;
  2305. const int KeyPress::F11Key = NSF11FunctionKey;
  2306. const int KeyPress::F12Key = NSF12FunctionKey;
  2307. const int KeyPress::F13Key = NSF13FunctionKey;
  2308. const int KeyPress::F14Key = NSF14FunctionKey;
  2309. const int KeyPress::F15Key = NSF15FunctionKey;
  2310. const int KeyPress::F16Key = NSF16FunctionKey;
  2311. const int KeyPress::F17Key = NSF17FunctionKey;
  2312. const int KeyPress::F18Key = NSF18FunctionKey;
  2313. const int KeyPress::F19Key = NSF19FunctionKey;
  2314. const int KeyPress::F20Key = NSF20FunctionKey;
  2315. const int KeyPress::F21Key = NSF21FunctionKey;
  2316. const int KeyPress::F22Key = NSF22FunctionKey;
  2317. const int KeyPress::F23Key = NSF23FunctionKey;
  2318. const int KeyPress::F24Key = NSF24FunctionKey;
  2319. const int KeyPress::F25Key = NSF25FunctionKey;
  2320. const int KeyPress::F26Key = NSF26FunctionKey;
  2321. const int KeyPress::F27Key = NSF27FunctionKey;
  2322. const int KeyPress::F28Key = NSF28FunctionKey;
  2323. const int KeyPress::F29Key = NSF29FunctionKey;
  2324. const int KeyPress::F30Key = NSF30FunctionKey;
  2325. const int KeyPress::F31Key = NSF31FunctionKey;
  2326. const int KeyPress::F32Key = NSF32FunctionKey;
  2327. const int KeyPress::F33Key = NSF33FunctionKey;
  2328. const int KeyPress::F34Key = NSF34FunctionKey;
  2329. const int KeyPress::F35Key = NSF35FunctionKey;
  2330. const int KeyPress::numberPad0 = extendedKeyModifier + 0x20;
  2331. const int KeyPress::numberPad1 = extendedKeyModifier + 0x21;
  2332. const int KeyPress::numberPad2 = extendedKeyModifier + 0x22;
  2333. const int KeyPress::numberPad3 = extendedKeyModifier + 0x23;
  2334. const int KeyPress::numberPad4 = extendedKeyModifier + 0x24;
  2335. const int KeyPress::numberPad5 = extendedKeyModifier + 0x25;
  2336. const int KeyPress::numberPad6 = extendedKeyModifier + 0x26;
  2337. const int KeyPress::numberPad7 = extendedKeyModifier + 0x27;
  2338. const int KeyPress::numberPad8 = extendedKeyModifier + 0x28;
  2339. const int KeyPress::numberPad9 = extendedKeyModifier + 0x29;
  2340. const int KeyPress::numberPadAdd = extendedKeyModifier + 0x2a;
  2341. const int KeyPress::numberPadSubtract = extendedKeyModifier + 0x2b;
  2342. const int KeyPress::numberPadMultiply = extendedKeyModifier + 0x2c;
  2343. const int KeyPress::numberPadDivide = extendedKeyModifier + 0x2d;
  2344. const int KeyPress::numberPadSeparator = extendedKeyModifier + 0x2e;
  2345. const int KeyPress::numberPadDecimalPoint = extendedKeyModifier + 0x2f;
  2346. const int KeyPress::numberPadEquals = extendedKeyModifier + 0x30;
  2347. const int KeyPress::numberPadDelete = extendedKeyModifier + 0x31;
  2348. const int KeyPress::playKey = extendedKeyModifier + 0x00;
  2349. const int KeyPress::stopKey = extendedKeyModifier + 0x01;
  2350. const int KeyPress::fastForwardKey = extendedKeyModifier + 0x02;
  2351. const int KeyPress::rewindKey = extendedKeyModifier + 0x03;
  2352. } // namespace juce