| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "ui/views/cocoa/bridged_native_widget.h" | 5 #import "ui/views/cocoa/bridged_native_widget.h" |
| 6 | 6 |
| 7 #import <objc/runtime.h> | 7 #import <objc/runtime.h> |
| 8 | 8 |
| 9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #import "base/mac/foundation_util.h" |
| 10 #include "base/mac/mac_util.h" | 11 #include "base/mac/mac_util.h" |
| 11 #import "base/mac/sdk_forward_declarations.h" | 12 #import "base/mac/sdk_forward_declarations.h" |
| 12 #include "base/thread_task_runner_handle.h" | 13 #include "base/thread_task_runner_handle.h" |
| 13 #import "ui/base/cocoa/constrained_window/constrained_window_animation.h" | 14 #import "ui/base/cocoa/constrained_window/constrained_window_animation.h" |
| 15 #include "ui/base/hit_test.h" |
| 14 #include "ui/base/ime/input_method.h" | 16 #include "ui/base/ime/input_method.h" |
| 15 #include "ui/base/ime/input_method_factory.h" | 17 #include "ui/base/ime/input_method_factory.h" |
| 16 #include "ui/base/ui_base_switches_util.h" | 18 #include "ui/base/ui_base_switches_util.h" |
| 17 #include "ui/gfx/display.h" | 19 #include "ui/gfx/display.h" |
| 18 #include "ui/gfx/geometry/dip_util.h" | 20 #include "ui/gfx/geometry/dip_util.h" |
| 19 #import "ui/gfx/mac/coordinate_conversion.h" | 21 #import "ui/gfx/mac/coordinate_conversion.h" |
| 20 #import "ui/gfx/mac/nswindow_frame_controls.h" | 22 #import "ui/gfx/mac/nswindow_frame_controls.h" |
| 21 #include "ui/gfx/screen.h" | 23 #include "ui/gfx/screen.h" |
| 22 #import "ui/views/cocoa/bridged_content_view.h" | 24 #import "ui/views/cocoa/bridged_content_view.h" |
| 23 #import "ui/views/cocoa/cocoa_mouse_capture.h" | 25 #import "ui/views/cocoa/cocoa_mouse_capture.h" |
| (...skipping 16 matching lines...) Expand all Loading... |
| 40 @end | 42 @end |
| 41 | 43 |
| 42 @implementation ViewsCompositorSuperview | 44 @implementation ViewsCompositorSuperview |
| 43 - (NSView*)hitTest:(NSPoint)aPoint { | 45 - (NSView*)hitTest:(NSPoint)aPoint { |
| 44 return nil; | 46 return nil; |
| 45 } | 47 } |
| 46 @end | 48 @end |
| 47 | 49 |
| 48 namespace { | 50 namespace { |
| 49 | 51 |
| 52 enum RepostState { |
| 53 // Nothing reposted: hit-test new mouse-downs to see if they need to be |
| 54 // ignored and reposted after changing draggability. |
| 55 NONE, |
| 56 // Expecting the next event to be the reposted event: let it go through. |
| 57 EXPECTING_REPOST, |
| 58 // If, while reposting, another mousedown was received: when the reposted |
| 59 // event is seen, ignore it. |
| 60 REPOST_CANCELLED, |
| 61 }; |
| 62 |
| 50 int kWindowPropertiesKey; | 63 int kWindowPropertiesKey; |
| 51 | 64 |
| 52 float GetDeviceScaleFactorFromView(NSView* view) { | 65 float GetDeviceScaleFactorFromView(NSView* view) { |
| 53 gfx::Display display = | 66 gfx::Display display = |
| 54 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view); | 67 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view); |
| 55 DCHECK(display.is_valid()); | 68 DCHECK(display.is_valid()); |
| 56 return display.device_scale_factor(); | 69 return display.device_scale_factor(); |
| 57 } | 70 } |
| 58 | 71 |
| 59 // Returns true if bounds passed to window in SetBounds should be treated as | 72 // Returns true if bounds passed to window in SetBounds should be treated as |
| (...skipping 11 matching lines...) Expand all Loading... |
| 71 gfx::Size GetClientSizeForWindowSize(NSWindow* window, | 84 gfx::Size GetClientSizeForWindowSize(NSWindow* window, |
| 72 const gfx::Size& window_size) { | 85 const gfx::Size& window_size) { |
| 73 NSRect frame_rect = | 86 NSRect frame_rect = |
| 74 NSMakeRect(0, 0, window_size.width(), window_size.height()); | 87 NSMakeRect(0, 0, window_size.width(), window_size.height()); |
| 75 // Note gfx::Size will prevent dimensions going negative. They are allowed to | 88 // Note gfx::Size will prevent dimensions going negative. They are allowed to |
| 76 // be zero at this point, because Widget::GetMinimumSize() may later increase | 89 // be zero at this point, because Widget::GetMinimumSize() may later increase |
| 77 // the size. | 90 // the size. |
| 78 return gfx::Size([window contentRectForFrameRect:frame_rect].size); | 91 return gfx::Size([window contentRectForFrameRect:frame_rect].size); |
| 79 } | 92 } |
| 80 | 93 |
| 94 BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) { |
| 95 id delegate = [[ns_event window] delegate]; |
| 96 return |
| 97 [delegate |
| 98 respondsToSelector:@selector(shouldRepostPendingLeftMouseDown:)] && |
| 99 [delegate shouldRepostPendingLeftMouseDown:[ns_event locationInWindow]]; |
| 100 } |
| 101 |
| 102 // Check if a mouse-down event should drag the window. If so, repost the event. |
| 103 NSEvent* RepostEventIfHandledByWindow(NSEvent* ns_event) { |
| 104 // Which repost we're expecting to receive. |
| 105 static RepostState repost_state = NONE; |
| 106 // The event number of the reposted event. This let's us track whether an |
| 107 // event is actually the repost since user-generated events have increasing |
| 108 // event numbers. This is only valid while |repost_state != NONE|. |
| 109 static NSInteger reposted_event_number; |
| 110 |
| 111 CGEventRef cg_event = [ns_event CGEvent]; |
| 112 NSInteger event_number = [ns_event eventNumber]; |
| 113 |
| 114 // The logic here is a bit convoluted because we want to mitigate race |
| 115 // conditions if somehow a different mouse-down occurs between reposts. |
| 116 // Specifically, we want to avoid: |
| 117 // - BridgedNativeWidget's draggability getting out of sync (e.g. if it is |
| 118 // draggable outside of a repost cycle), |
| 119 // - any repost loop. |
| 120 |
| 121 if (repost_state == NONE) { |
| 122 // We're not in the middle of reposting, process this new event. |
| 123 if (WindowWantsMouseDownReposted(ns_event)) { |
| 124 repost_state = EXPECTING_REPOST; |
| 125 reposted_event_number = [ns_event eventNumber]; |
| 126 CGEventPost(kCGSessionEventTap, cg_event); |
| 127 return nil; |
| 128 } |
| 129 |
| 130 return ns_event; |
| 131 } |
| 132 |
| 133 if (repost_state == EXPECTING_REPOST) { |
| 134 // Call through so that the window is made non-draggable again. |
| 135 WindowWantsMouseDownReposted(ns_event); |
| 136 |
| 137 if (reposted_event_number == event_number) { |
| 138 // Reposted event received. |
| 139 repost_state = NONE; |
| 140 return nil; |
| 141 } |
| 142 |
| 143 // We were expecting a repost, but since this is a new mouse-down, cancel |
| 144 // reposting and allow event to continue as usual. |
| 145 repost_state = REPOST_CANCELLED; |
| 146 return ns_event; |
| 147 } |
| 148 |
| 149 if (repost_state == REPOST_CANCELLED) { |
| 150 if (reposted_event_number == event_number) { |
| 151 // Reposting was cancelled, now that we've received the event, we don't |
| 152 // expect to see it again. |
| 153 repost_state = NONE; |
| 154 return nil; |
| 155 } |
| 156 |
| 157 return ns_event; |
| 158 } |
| 159 |
| 160 NOTREACHED(); |
| 161 return ns_event; |
| 162 } |
| 163 |
| 164 // Support window caption/draggable regions. |
| 165 // In AppKit, non-client regions are set by overriding |
| 166 // -[NSView mouseDownCanMoveWindow]. NSApplication caches this area as views are |
| 167 // installed and performs window moving when mouse-downs land in the area. |
| 168 // In Views, non-client regions are determined via hit-tests when the event |
| 169 // occurs. |
| 170 // To bridge the two models, we monitor mouse-downs with |
| 171 // +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. This receives |
| 172 // events after window dragging is handled, so for mouse-downs that land on a |
| 173 // draggable point, we cancel the event and repost it at the CGSessionEventTap |
| 174 // level so that window dragging will be handled again. |
| 175 void SetupDragEventMonitor() { |
| 176 static id monitor = nil; |
| 177 if (monitor) |
| 178 return; |
| 179 |
| 180 monitor = [NSEvent |
| 181 addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask |
| 182 handler:^NSEvent*(NSEvent* ns_event) { |
| 183 return RepostEventIfHandledByWindow(ns_event); |
| 184 }]; |
| 185 } |
| 186 |
| 81 } // namespace | 187 } // namespace |
| 82 | 188 |
| 83 namespace views { | 189 namespace views { |
| 84 | 190 |
| 85 // static | 191 // static |
| 86 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( | 192 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( |
| 87 NSWindow* window, | 193 NSWindow* window, |
| 88 const gfx::Size& content_size) { | 194 const gfx::Size& content_size) { |
| 89 NSRect content_rect = | 195 NSRect content_rect = |
| 90 NSMakeRect(0, 0, content_size.width(), content_size.height()); | 196 NSMakeRect(0, 0, content_size.width(), content_size.height()); |
| 91 NSRect frame_rect = [window frameRectForContentRect:content_rect]; | 197 NSRect frame_rect = [window frameRectForContentRect:content_rect]; |
| 92 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); | 198 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); |
| 93 } | 199 } |
| 94 | 200 |
| 95 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) | 201 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) |
| 96 : native_widget_mac_(parent), | 202 : native_widget_mac_(parent), |
| 97 focus_manager_(nullptr), | 203 focus_manager_(nullptr), |
| 98 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init(). | 204 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init(). |
| 99 parent_(nullptr), | 205 parent_(nullptr), |
| 100 target_fullscreen_state_(false), | 206 target_fullscreen_state_(false), |
| 101 in_fullscreen_transition_(false), | 207 in_fullscreen_transition_(false), |
| 102 window_visible_(false), | 208 window_visible_(false), |
| 103 wants_to_be_visible_(false) { | 209 wants_to_be_visible_(false) { |
| 210 SetupDragEventMonitor(); |
| 104 DCHECK(parent); | 211 DCHECK(parent); |
| 105 window_delegate_.reset( | 212 window_delegate_.reset( |
| 106 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); | 213 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); |
| 107 } | 214 } |
| 108 | 215 |
| 109 BridgedNativeWidget::~BridgedNativeWidget() { | 216 BridgedNativeWidget::~BridgedNativeWidget() { |
| 110 RemoveOrDestroyChildren(); | 217 RemoveOrDestroyChildren(); |
| 111 DCHECK(child_windows_.empty()); | 218 DCHECK(child_windows_.empty()); |
| 112 SetFocusManager(NULL); | 219 SetFocusManager(NULL); |
| 113 SetRootView(NULL); | 220 SetRootView(NULL); |
| (...skipping 427 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 541 if (is_key) { | 648 if (is_key) { |
| 542 widget->OnNativeFocus(); | 649 widget->OnNativeFocus(); |
| 543 widget->GetFocusManager()->RestoreFocusedView(); | 650 widget->GetFocusManager()->RestoreFocusedView(); |
| 544 } else { | 651 } else { |
| 545 widget->OnNativeBlur(); | 652 widget->OnNativeBlur(); |
| 546 widget->GetFocusManager()->StoreFocusedView(true); | 653 widget->GetFocusManager()->StoreFocusedView(true); |
| 547 } | 654 } |
| 548 } | 655 } |
| 549 } | 656 } |
| 550 | 657 |
| 658 bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown( |
| 659 NSPoint location_in_window) { |
| 660 if (!bridged_view_) |
| 661 return false; |
| 662 |
| 663 if ([bridged_view_ mouseDownCanMoveWindow]) { |
| 664 // This is a re-post, the movement has already started, so we can make the |
| 665 // window non-draggable again. |
| 666 SetDraggable(false); |
| 667 return false; |
| 668 } |
| 669 |
| 670 gfx::Point point(location_in_window.x, |
| 671 NSHeight([window_ frame]) - location_in_window.y); |
| 672 bool should_move_window = |
| 673 native_widget_mac()->GetWidget()->GetNonClientComponent(point) == |
| 674 HTCAPTION; |
| 675 |
| 676 // Check that the point is not obscured by non-content NSViews. |
| 677 for (NSView* subview : [[bridged_view_ superview] subviews]) { |
| 678 if (subview == bridged_view_.get()) |
| 679 continue; |
| 680 |
| 681 if (![subview mouseDownCanMoveWindow] && |
| 682 NSPointInRect(location_in_window, [subview frame])) { |
| 683 should_move_window = false; |
| 684 break; |
| 685 } |
| 686 } |
| 687 |
| 688 if (!should_move_window) |
| 689 return false; |
| 690 |
| 691 // Make the window draggable, then return true to repost the event. |
| 692 SetDraggable(true); |
| 693 return true; |
| 694 } |
| 695 |
| 551 void BridgedNativeWidget::OnSizeConstraintsChanged() { | 696 void BridgedNativeWidget::OnSizeConstraintsChanged() { |
| 552 // Don't modify the size constraints or fullscreen collection behavior while | 697 // Don't modify the size constraints or fullscreen collection behavior while |
| 553 // in fullscreen or during a transition. OnFullscreenTransitionComplete will | 698 // in fullscreen or during a transition. OnFullscreenTransitionComplete will |
| 554 // reset these after leaving fullscreen. | 699 // reset these after leaving fullscreen. |
| 555 if (target_fullscreen_state_ || in_fullscreen_transition_) | 700 if (target_fullscreen_state_ || in_fullscreen_transition_) |
| 556 return; | 701 return; |
| 557 | 702 |
| 558 Widget* widget = native_widget_mac()->GetWidget(); | 703 Widget* widget = native_widget_mac()->GetWidget(); |
| 559 gfx::Size min_size = widget->GetMinimumSize(); | 704 gfx::Size min_size = widget->GetMinimumSize(); |
| 560 gfx::Size max_size = widget->GetMaximumSize(); | 705 gfx::Size max_size = widget->GetMaximumSize(); |
| (...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 879 NSMutableDictionary* properties = objc_getAssociatedObject( | 1024 NSMutableDictionary* properties = objc_getAssociatedObject( |
| 880 window_, &kWindowPropertiesKey); | 1025 window_, &kWindowPropertiesKey); |
| 881 if (!properties) { | 1026 if (!properties) { |
| 882 properties = [NSMutableDictionary dictionary]; | 1027 properties = [NSMutableDictionary dictionary]; |
| 883 objc_setAssociatedObject(window_, &kWindowPropertiesKey, | 1028 objc_setAssociatedObject(window_, &kWindowPropertiesKey, |
| 884 properties, OBJC_ASSOCIATION_RETAIN); | 1029 properties, OBJC_ASSOCIATION_RETAIN); |
| 885 } | 1030 } |
| 886 return properties; | 1031 return properties; |
| 887 } | 1032 } |
| 888 | 1033 |
| 1034 void BridgedNativeWidget::SetDraggable(bool draggable) { |
| 1035 [bridged_view_ setMouseDownCanMoveWindow:draggable]; |
| 1036 // AppKit will not update its cache of mouseDownCanMoveWindow unless something |
| 1037 // changes. Previously we tried adding an NSView and removing it, but for some |
| 1038 // reason it required reposting the mouse-down event, and didn't always work. |
| 1039 // Calling the below seems to be an effective solution. |
| 1040 [window_ setMovableByWindowBackground:NO]; |
| 1041 [window_ setMovableByWindowBackground:YES]; |
| 1042 } |
| 1043 |
| 889 } // namespace views | 1044 } // namespace views |
| OLD | NEW |