Chromium Code Reviews| 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" |
| 14 #include "ui/base/hit_test.h" | |
| 13 #include "ui/base/ime/input_method.h" | 15 #include "ui/base/ime/input_method.h" |
| 14 #include "ui/base/ime/input_method_factory.h" | 16 #include "ui/base/ime/input_method_factory.h" |
| 15 #include "ui/base/ui_base_switches_util.h" | 17 #include "ui/base/ui_base_switches_util.h" |
| 16 #include "ui/gfx/display.h" | 18 #include "ui/gfx/display.h" |
| 17 #include "ui/gfx/geometry/dip_util.h" | 19 #include "ui/gfx/geometry/dip_util.h" |
| 18 #import "ui/gfx/mac/coordinate_conversion.h" | 20 #import "ui/gfx/mac/coordinate_conversion.h" |
| 19 #import "ui/gfx/mac/nswindow_frame_controls.h" | 21 #import "ui/gfx/mac/nswindow_frame_controls.h" |
| 20 #include "ui/gfx/screen.h" | 22 #include "ui/gfx/screen.h" |
| 21 #import "ui/views/cocoa/bridged_content_view.h" | 23 #import "ui/views/cocoa/bridged_content_view.h" |
| 22 #import "ui/views/cocoa/cocoa_mouse_capture.h" | 24 #import "ui/views/cocoa/cocoa_mouse_capture.h" |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 39 @end | 41 @end |
| 40 | 42 |
| 41 @implementation ViewsCompositorSuperview | 43 @implementation ViewsCompositorSuperview |
| 42 - (NSView*)hitTest:(NSPoint)aPoint { | 44 - (NSView*)hitTest:(NSPoint)aPoint { |
| 43 return nil; | 45 return nil; |
| 44 } | 46 } |
| 45 @end | 47 @end |
| 46 | 48 |
| 47 namespace { | 49 namespace { |
| 48 | 50 |
| 51 enum RepostState { | |
|
tapted
2015/06/03 07:04:49
Move this to within the RepostEventIfHandledByWind
jackhou1
2015/06/03 08:13:52
Done.
| |
| 52 // Nothing reposted: hit-test new mouse-downs to see if they need to be | |
| 53 // ignored and reposted after changing draggability. | |
| 54 NONE, | |
| 55 // Expecting the next event to be the reposted event: let it go through. | |
| 56 EXPECTING_REPOST, | |
| 57 // If, while reposting, another mousedown was received: when the reposted | |
| 58 // event is seen, ignore it. | |
| 59 REPOST_CANCELLED, | |
| 60 }; | |
| 61 | |
| 49 int kWindowPropertiesKey; | 62 int kWindowPropertiesKey; |
| 50 | 63 |
| 51 float GetDeviceScaleFactorFromView(NSView* view) { | 64 float GetDeviceScaleFactorFromView(NSView* view) { |
| 52 gfx::Display display = | 65 gfx::Display display = |
| 53 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view); | 66 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view); |
| 54 DCHECK(display.is_valid()); | 67 DCHECK(display.is_valid()); |
| 55 return display.device_scale_factor(); | 68 return display.device_scale_factor(); |
| 56 } | 69 } |
| 57 | 70 |
| 58 // Returns true if bounds passed to window in SetBounds should be treated as | 71 // Returns true if bounds passed to window in SetBounds should be treated as |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 70 gfx::Size GetClientSizeForWindowSize(NSWindow* window, | 83 gfx::Size GetClientSizeForWindowSize(NSWindow* window, |
| 71 const gfx::Size& window_size) { | 84 const gfx::Size& window_size) { |
| 72 NSRect frame_rect = | 85 NSRect frame_rect = |
| 73 NSMakeRect(0, 0, window_size.width(), window_size.height()); | 86 NSMakeRect(0, 0, window_size.width(), window_size.height()); |
| 74 // Note gfx::Size will prevent dimensions going negative. They are allowed to | 87 // Note gfx::Size will prevent dimensions going negative. They are allowed to |
| 75 // be zero at this point, because Widget::GetMinimumSize() may later increase | 88 // be zero at this point, because Widget::GetMinimumSize() may later increase |
| 76 // the size. | 89 // the size. |
| 77 return gfx::Size([window contentRectForFrameRect:frame_rect].size); | 90 return gfx::Size([window contentRectForFrameRect:frame_rect].size); |
| 78 } | 91 } |
| 79 | 92 |
| 93 BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) { | |
| 94 id delegate = [[ns_event window] delegate]; | |
| 95 return | |
| 96 [delegate | |
| 97 respondsToSelector:@selector(shouldRepostPendingLeftMouseDown:)] && | |
| 98 [delegate shouldRepostPendingLeftMouseDown:[ns_event locationInWindow]]; | |
| 99 } | |
| 100 | |
| 101 // Check if a mouse-down event should drag the window. If so, repost the event. | |
| 102 NSEvent* RepostEventIfHandledByWindow(NSEvent* ns_event) { | |
| 103 // Which repost we're expecting to receive. | |
| 104 static RepostState repost_state = NONE; | |
| 105 // The event number of the reposted event. This let's us track whether an | |
| 106 // event is actually the repost since user-generated events have increasing | |
| 107 // event numbers. This is only valid while |repost_state != NONE|. | |
| 108 static NSInteger reposted_event_number; | |
| 109 | |
| 110 CGEventRef cg_event = [ns_event CGEvent]; | |
| 111 NSInteger event_number = [ns_event eventNumber]; | |
| 112 | |
| 113 // The logic here is a bit convoluted because we want to mitigate race | |
| 114 // conditions if somehow a different mouse-down occurs between reposts. | |
| 115 // Specifically, we want to avoid: | |
| 116 // - BridgedNativeWidget's draggability getting out of sync (e.g. if it is | |
| 117 // draggable outside of a repost cycle), | |
| 118 // - any repost loop. | |
| 119 | |
| 120 if (repost_state == NONE) { | |
| 121 // We're not in the middle of reposting, process this new event. | |
|
tapted
2015/06/03 07:04:49
nit: this comment kinda repeats the enum comment n
jackhou1
2015/06/03 08:13:52
Done.
| |
| 122 if (WindowWantsMouseDownReposted(ns_event)) { | |
| 123 repost_state = EXPECTING_REPOST; | |
| 124 reposted_event_number = [ns_event eventNumber]; | |
|
tapted
2015/06/03 07:04:49
= event_number?
jackhou1
2015/06/03 08:13:51
Done.
| |
| 125 CGEventPost(kCGSessionEventTap, cg_event); | |
| 126 return nil; | |
| 127 } | |
| 128 | |
| 129 return ns_event; | |
| 130 } | |
| 131 | |
| 132 if (repost_state == EXPECTING_REPOST) { | |
| 133 // Call through so that the window is made non-draggable again. | |
| 134 WindowWantsMouseDownReposted(ns_event); | |
| 135 | |
| 136 if (reposted_event_number == event_number) { | |
| 137 // Reposted event received. | |
| 138 repost_state = NONE; | |
| 139 return nil; | |
| 140 } | |
| 141 | |
| 142 // We were expecting a repost, but since this is a new mouse-down, cancel | |
| 143 // reposting and allow event to continue as usual. | |
| 144 repost_state = REPOST_CANCELLED; | |
| 145 return ns_event; | |
| 146 } | |
| 147 | |
| 148 if (repost_state == REPOST_CANCELLED) { | |
| 149 if (reposted_event_number == event_number) { | |
| 150 // Reposting was cancelled, now that we've received the event, we don't | |
| 151 // expect to see it again. | |
| 152 repost_state = NONE; | |
| 153 return nil; | |
| 154 } | |
| 155 | |
| 156 return ns_event; | |
| 157 } | |
| 158 | |
| 159 NOTREACHED(); | |
|
tapted
2015/06/03 07:04:49
instead of this, maybe
DCHECK_EQ(REPOST_CANCELLED
jackhou1
2015/06/03 08:13:52
Done.
| |
| 160 return ns_event; | |
| 161 } | |
| 162 | |
| 163 // Support window caption/draggable regions. | |
| 164 // In AppKit, non-client regions are set by overriding | |
| 165 // -[NSView mouseDownCanMoveWindow]. NSApplication caches this area as views are | |
| 166 // installed and performs window moving when mouse-downs land in the area. | |
| 167 // In Views, non-client regions are determined via hit-tests when the event | |
| 168 // occurs. | |
| 169 // To bridge the two models, we monitor mouse-downs with | |
| 170 // +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. This receives | |
| 171 // events after window dragging is handled, so for mouse-downs that land on a | |
| 172 // draggable point, we cancel the event and repost it at the CGSessionEventTap | |
| 173 // level so that window dragging will be handled again. | |
| 174 void SetupDragEventMonitor() { | |
| 175 static id monitor = nil; | |
| 176 if (monitor) | |
| 177 return; | |
| 178 | |
| 179 monitor = [NSEvent | |
| 180 addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask | |
| 181 handler:^NSEvent*(NSEvent* ns_event) { | |
| 182 return RepostEventIfHandledByWindow(ns_event); | |
| 183 }]; | |
| 184 } | |
| 185 | |
| 80 } // namespace | 186 } // namespace |
| 81 | 187 |
| 82 namespace views { | 188 namespace views { |
| 83 | 189 |
| 84 // static | 190 // static |
| 85 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( | 191 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( |
| 86 NSWindow* window, | 192 NSWindow* window, |
| 87 const gfx::Size& content_size) { | 193 const gfx::Size& content_size) { |
| 88 NSRect content_rect = | 194 NSRect content_rect = |
| 89 NSMakeRect(0, 0, content_size.width(), content_size.height()); | 195 NSMakeRect(0, 0, content_size.width(), content_size.height()); |
| 90 NSRect frame_rect = [window frameRectForContentRect:content_rect]; | 196 NSRect frame_rect = [window frameRectForContentRect:content_rect]; |
| 91 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); | 197 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); |
| 92 } | 198 } |
| 93 | 199 |
| 94 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) | 200 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) |
| 95 : native_widget_mac_(parent), | 201 : native_widget_mac_(parent), |
| 96 focus_manager_(nullptr), | 202 focus_manager_(nullptr), |
| 97 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init(). | 203 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init(). |
| 98 parent_(nullptr), | 204 parent_(nullptr), |
| 99 target_fullscreen_state_(false), | 205 target_fullscreen_state_(false), |
| 100 in_fullscreen_transition_(false), | 206 in_fullscreen_transition_(false), |
| 101 window_visible_(false), | 207 window_visible_(false), |
| 102 wants_to_be_visible_(false) { | 208 wants_to_be_visible_(false) { |
| 209 SetupDragEventMonitor(); | |
| 103 DCHECK(parent); | 210 DCHECK(parent); |
| 104 window_delegate_.reset( | 211 window_delegate_.reset( |
| 105 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); | 212 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); |
| 106 } | 213 } |
| 107 | 214 |
| 108 BridgedNativeWidget::~BridgedNativeWidget() { | 215 BridgedNativeWidget::~BridgedNativeWidget() { |
| 109 RemoveOrDestroyChildren(); | 216 RemoveOrDestroyChildren(); |
| 110 DCHECK(child_windows_.empty()); | 217 DCHECK(child_windows_.empty()); |
| 111 SetFocusManager(NULL); | 218 SetFocusManager(NULL); |
| 112 SetRootView(NULL); | 219 SetRootView(NULL); |
| (...skipping 412 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 525 if (is_key) { | 632 if (is_key) { |
| 526 widget->OnNativeFocus(); | 633 widget->OnNativeFocus(); |
| 527 widget->GetFocusManager()->RestoreFocusedView(); | 634 widget->GetFocusManager()->RestoreFocusedView(); |
| 528 } else { | 635 } else { |
| 529 widget->OnNativeBlur(); | 636 widget->OnNativeBlur(); |
| 530 widget->GetFocusManager()->StoreFocusedView(true); | 637 widget->GetFocusManager()->StoreFocusedView(true); |
| 531 } | 638 } |
| 532 } | 639 } |
| 533 } | 640 } |
| 534 | 641 |
| 642 bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown( | |
| 643 NSPoint location_in_window) { | |
| 644 if (!bridged_view_) | |
| 645 return false; | |
| 646 | |
| 647 if ([bridged_view_ mouseDownCanMoveWindow]) { | |
| 648 // This is a re-post, the movement has already started, so we can make the | |
| 649 // window non-draggable again. | |
| 650 SetDraggable(false); | |
| 651 return false; | |
| 652 } | |
| 653 | |
| 654 gfx::Point point(location_in_window.x, | |
| 655 NSHeight([window_ frame]) - location_in_window.y); | |
| 656 bool should_move_window = | |
| 657 native_widget_mac()->GetWidget()->GetNonClientComponent(point) == | |
| 658 HTCAPTION; | |
| 659 | |
| 660 // Check that the point is not obscured by non-content NSViews. | |
| 661 for (NSView* subview : [[bridged_view_ superview] subviews]) { | |
|
tapted
2015/06/03 07:04:49
does something like this work:
bool should_move_w
jackhou1
2015/06/03 08:13:52
The window controls are sometimes below the bridge
tapted
2015/06/03 09:12:57
Acknowledged.
| |
| 662 if (subview == bridged_view_.get()) | |
| 663 continue; | |
| 664 | |
| 665 if (![subview mouseDownCanMoveWindow] && | |
| 666 NSPointInRect(location_in_window, [subview frame])) { | |
| 667 should_move_window = false; | |
| 668 break; | |
| 669 } | |
| 670 } | |
| 671 | |
| 672 if (!should_move_window) | |
| 673 return false; | |
| 674 | |
| 675 // Make the window draggable, then return true to repost the event. | |
| 676 SetDraggable(true); | |
| 677 return true; | |
| 678 } | |
| 679 | |
| 535 void BridgedNativeWidget::OnSizeConstraintsChanged() { | 680 void BridgedNativeWidget::OnSizeConstraintsChanged() { |
| 536 // Don't modify the size constraints or fullscreen collection behavior while | 681 // Don't modify the size constraints or fullscreen collection behavior while |
| 537 // in fullscreen or during a transition. OnFullscreenTransitionComplete will | 682 // in fullscreen or during a transition. OnFullscreenTransitionComplete will |
| 538 // reset these after leaving fullscreen. | 683 // reset these after leaving fullscreen. |
| 539 if (target_fullscreen_state_ || in_fullscreen_transition_) | 684 if (target_fullscreen_state_ || in_fullscreen_transition_) |
| 540 return; | 685 return; |
| 541 | 686 |
| 542 Widget* widget = native_widget_mac()->GetWidget(); | 687 Widget* widget = native_widget_mac()->GetWidget(); |
| 543 gfx::Size min_size = widget->GetMinimumSize(); | 688 gfx::Size min_size = widget->GetMinimumSize(); |
| 544 gfx::Size max_size = widget->GetMaximumSize(); | 689 gfx::Size max_size = widget->GetMaximumSize(); |
| (...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 863 NSMutableDictionary* properties = objc_getAssociatedObject( | 1008 NSMutableDictionary* properties = objc_getAssociatedObject( |
| 864 window_, &kWindowPropertiesKey); | 1009 window_, &kWindowPropertiesKey); |
| 865 if (!properties) { | 1010 if (!properties) { |
| 866 properties = [NSMutableDictionary dictionary]; | 1011 properties = [NSMutableDictionary dictionary]; |
| 867 objc_setAssociatedObject(window_, &kWindowPropertiesKey, | 1012 objc_setAssociatedObject(window_, &kWindowPropertiesKey, |
| 868 properties, OBJC_ASSOCIATION_RETAIN); | 1013 properties, OBJC_ASSOCIATION_RETAIN); |
| 869 } | 1014 } |
| 870 return properties; | 1015 return properties; |
| 871 } | 1016 } |
| 872 | 1017 |
| 1018 void BridgedNativeWidget::SetDraggable(bool draggable) { | |
| 1019 [bridged_view_ setMouseDownCanMoveWindow:draggable]; | |
| 1020 // AppKit will not update its cache of mouseDownCanMoveWindow unless something | |
| 1021 // changes. Previously we tried adding an NSView and removing it, but for some | |
| 1022 // reason it required reposting the mouse-down event, and didn't always work. | |
| 1023 // Calling the below seems to be an effective solution. | |
| 1024 [window_ setMovableByWindowBackground:NO]; | |
| 1025 [window_ setMovableByWindowBackground:YES]; | |
| 1026 } | |
| 1027 | |
| 873 } // namespace views | 1028 } // namespace views |
| OLD | NEW |