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