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 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
70 gfx::Size GetClientSizeForWindowSize(NSWindow* window, | 72 gfx::Size GetClientSizeForWindowSize(NSWindow* window, |
71 const gfx::Size& window_size) { | 73 const gfx::Size& window_size) { |
72 NSRect frame_rect = | 74 NSRect frame_rect = |
73 NSMakeRect(0, 0, window_size.width(), window_size.height()); | 75 NSMakeRect(0, 0, window_size.width(), window_size.height()); |
74 // Note gfx::Size will prevent dimensions going negative. They are allowed to | 76 // Note gfx::Size will prevent dimensions going negative. They are allowed to |
75 // be zero at this point, because Widget::GetMinimumSize() may later increase | 77 // be zero at this point, because Widget::GetMinimumSize() may later increase |
76 // the size. | 78 // the size. |
77 return gfx::Size([window contentRectForFrameRect:frame_rect].size); | 79 return gfx::Size([window contentRectForFrameRect:frame_rect].size); |
78 } | 80 } |
79 | 81 |
| 82 BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) { |
| 83 id delegate = [[ns_event window] delegate]; |
| 84 return |
| 85 [delegate |
| 86 respondsToSelector:@selector(shouldRepostPendingLeftMouseDown:)] && |
| 87 [delegate shouldRepostPendingLeftMouseDown:[ns_event locationInWindow]]; |
| 88 } |
| 89 |
| 90 // Check if a mouse-down event should drag the window. If so, repost the event |
| 91 // twice. It doesn't seem to work the first time it's reposted. |
| 92 NSEvent* DoubleRepostEventIfHandledByWindow(NSEvent* ns_event) { |
| 93 // Which repost we're expecting to receive. |
| 94 static int expected_repost_count = 0; |
| 95 // The event number of the reposted event. This let's us track whether an |
| 96 // event is actually a repost since user-generated events have increasing |
| 97 // event numbers. |
| 98 static NSInteger reposted_event_number = -1; |
| 99 |
| 100 CGEventRef cg_event = [ns_event CGEvent]; |
| 101 NSInteger event_number = [ns_event eventNumber]; |
| 102 |
| 103 // The logic here is a bit convoluted because we want to mitigate race |
| 104 // conditions if somehow a different mousedown occurs between reposts. |
| 105 // Specifically, we want to avoid: |
| 106 // - BridgedNativeWidget's draggability getting out of sync (e.g. it's |
| 107 // draggable outside of a repost cycle), |
| 108 // - any repost loop. |
| 109 if (reposted_event_number == event_number) { |
| 110 // This is a reposted event. |
| 111 if (expected_repost_count == 0) { |
| 112 // Reposting was cancelled, now that we've received it, we don't expect |
| 113 // to see it again. |
| 114 reposted_event_number = -1; |
| 115 } else if (expected_repost_count == 1) { |
| 116 // There has been no intermediate mouse-down, repost the event again. |
| 117 expected_repost_count = 2; |
| 118 CGEventPost(kCGSessionEventTap, cg_event); |
| 119 } else if (expected_repost_count == 2) { |
| 120 expected_repost_count = 0; |
| 121 reposted_event_number = -1; |
| 122 // This is the final repost, call through to make the window |
| 123 // non-draggable. |
| 124 WindowWantsMouseDownReposted(ns_event); |
| 125 } else { |
| 126 NOTREACHED(); |
| 127 } |
| 128 return nil; // Ignore the event. |
| 129 } |
| 130 |
| 131 // This is a new event. |
| 132 if (expected_repost_count > 0) { |
| 133 // We were expecting a repost, but since this is a new mouse-down, cancel |
| 134 // reposting and allow event to continue as usual. |
| 135 expected_repost_count = 0; |
| 136 // Call through so that the window is made non-draggable again. |
| 137 WindowWantsMouseDownReposted(ns_event); |
| 138 return ns_event; |
| 139 } |
| 140 |
| 141 // We're not in the middle of reposting, process this new event. |
| 142 if (WindowWantsMouseDownReposted(ns_event)) { |
| 143 expected_repost_count = 1; |
| 144 reposted_event_number = [ns_event eventNumber]; |
| 145 CGEventPost(kCGSessionEventTap, cg_event); |
| 146 return nil; |
| 147 } |
| 148 |
| 149 return ns_event; |
| 150 } |
| 151 |
| 152 // Support window caption/draggable regions. |
| 153 // In AppKit, non-client regions are set by overriding |
| 154 // -[NSView mouseDownCanMoveWindow]. NSApplication caches this area as views are |
| 155 // installed and performs window moving when mouse-downs land in the area. |
| 156 // In Views, non-client regions are determined via hit-tests when the event |
| 157 // occurs. |
| 158 // To bridge the two models, we monitor mouse-downs with |
| 159 // +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. This receives |
| 160 // events after window dragging is handled, so for mouse-downs that land on a |
| 161 // draggable point, we cancel the event and repost it at the CGSessionEventTap |
| 162 // level so that window dragging will be handled again. |
| 163 void SetupDragEventMonitor() { |
| 164 static id monitor = nil; |
| 165 if (monitor) |
| 166 return; |
| 167 |
| 168 monitor = [NSEvent |
| 169 addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask |
| 170 handler:^NSEvent*(NSEvent* ns_event) { |
| 171 return DoubleRepostEventIfHandledByWindow(ns_event); |
| 172 }]; |
| 173 } |
| 174 |
80 } // namespace | 175 } // namespace |
81 | 176 |
82 namespace views { | 177 namespace views { |
83 | 178 |
84 // static | 179 // static |
85 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( | 180 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( |
86 NSWindow* window, | 181 NSWindow* window, |
87 const gfx::Size& content_size) { | 182 const gfx::Size& content_size) { |
88 NSRect content_rect = | 183 NSRect content_rect = |
89 NSMakeRect(0, 0, content_size.width(), content_size.height()); | 184 NSMakeRect(0, 0, content_size.width(), content_size.height()); |
90 NSRect frame_rect = [window frameRectForContentRect:content_rect]; | 185 NSRect frame_rect = [window frameRectForContentRect:content_rect]; |
91 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); | 186 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); |
92 } | 187 } |
93 | 188 |
94 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) | 189 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) |
95 : native_widget_mac_(parent), | 190 : native_widget_mac_(parent), |
96 focus_manager_(nullptr), | 191 focus_manager_(nullptr), |
97 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init(). | 192 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init(). |
98 parent_(nullptr), | 193 parent_(nullptr), |
99 target_fullscreen_state_(false), | 194 target_fullscreen_state_(false), |
100 in_fullscreen_transition_(false), | 195 in_fullscreen_transition_(false), |
101 window_visible_(false), | 196 window_visible_(false), |
102 wants_to_be_visible_(false) { | 197 wants_to_be_visible_(false) { |
| 198 SetupDragEventMonitor(); |
103 DCHECK(parent); | 199 DCHECK(parent); |
104 window_delegate_.reset( | 200 window_delegate_.reset( |
105 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); | 201 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); |
106 } | 202 } |
107 | 203 |
108 BridgedNativeWidget::~BridgedNativeWidget() { | 204 BridgedNativeWidget::~BridgedNativeWidget() { |
109 RemoveOrDestroyChildren(); | 205 RemoveOrDestroyChildren(); |
110 DCHECK(child_windows_.empty()); | 206 DCHECK(child_windows_.empty()); |
111 SetFocusManager(NULL); | 207 SetFocusManager(NULL); |
112 SetRootView(NULL); | 208 SetRootView(NULL); |
(...skipping 412 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
525 if (is_key) { | 621 if (is_key) { |
526 widget->OnNativeFocus(); | 622 widget->OnNativeFocus(); |
527 widget->GetFocusManager()->RestoreFocusedView(); | 623 widget->GetFocusManager()->RestoreFocusedView(); |
528 } else { | 624 } else { |
529 widget->OnNativeBlur(); | 625 widget->OnNativeBlur(); |
530 widget->GetFocusManager()->StoreFocusedView(true); | 626 widget->GetFocusManager()->StoreFocusedView(true); |
531 } | 627 } |
532 } | 628 } |
533 } | 629 } |
534 | 630 |
| 631 bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown( |
| 632 NSPoint location_in_window) { |
| 633 if (!bridged_view_) |
| 634 return false; |
| 635 |
| 636 if ([bridged_view_ mouseDownCanMoveWindow]) { |
| 637 // This is a re-post, the movement has already started, so we can make the |
| 638 // window non-draggable again. |
| 639 SetDraggable(false); |
| 640 return false; |
| 641 } |
| 642 |
| 643 gfx::Point point(location_in_window.x, |
| 644 NSHeight([window_ frame]) - location_in_window.y); |
| 645 bool should_move_window = |
| 646 native_widget_mac()->GetWidget()->GetNonClientComponent(point) == |
| 647 HTCAPTION; |
| 648 |
| 649 if (!should_move_window) |
| 650 return false; |
| 651 |
| 652 // Make the window draggable, then return true to repost the event. |
| 653 SetDraggable(true); |
| 654 return true; |
| 655 } |
| 656 |
535 void BridgedNativeWidget::OnSizeConstraintsChanged() { | 657 void BridgedNativeWidget::OnSizeConstraintsChanged() { |
536 // Don't modify the size constraints or fullscreen collection behavior while | 658 // Don't modify the size constraints or fullscreen collection behavior while |
537 // in fullscreen or during a transition. OnFullscreenTransitionComplete will | 659 // in fullscreen or during a transition. OnFullscreenTransitionComplete will |
538 // reset these after leaving fullscreen. | 660 // reset these after leaving fullscreen. |
539 if (target_fullscreen_state_ || in_fullscreen_transition_) | 661 if (target_fullscreen_state_ || in_fullscreen_transition_) |
540 return; | 662 return; |
541 | 663 |
542 Widget* widget = native_widget_mac()->GetWidget(); | 664 Widget* widget = native_widget_mac()->GetWidget(); |
543 gfx::Size min_size = widget->GetMinimumSize(); | 665 gfx::Size min_size = widget->GetMinimumSize(); |
544 gfx::Size max_size = widget->GetMaximumSize(); | 666 gfx::Size max_size = widget->GetMaximumSize(); |
(...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
863 NSMutableDictionary* properties = objc_getAssociatedObject( | 985 NSMutableDictionary* properties = objc_getAssociatedObject( |
864 window_, &kWindowPropertiesKey); | 986 window_, &kWindowPropertiesKey); |
865 if (!properties) { | 987 if (!properties) { |
866 properties = [NSMutableDictionary dictionary]; | 988 properties = [NSMutableDictionary dictionary]; |
867 objc_setAssociatedObject(window_, &kWindowPropertiesKey, | 989 objc_setAssociatedObject(window_, &kWindowPropertiesKey, |
868 properties, OBJC_ASSOCIATION_RETAIN); | 990 properties, OBJC_ASSOCIATION_RETAIN); |
869 } | 991 } |
870 return properties; | 992 return properties; |
871 } | 993 } |
872 | 994 |
| 995 void BridgedNativeWidget::SetDraggable(bool draggable) { |
| 996 [bridged_view_ setMouseDownCanMoveWindow:draggable]; |
| 997 // AppKit will not update its cache of mouseDownCanMoveWindow unless something |
| 998 // changes. |
| 999 base::scoped_nsobject<NSView> temp_view( |
| 1000 [[NSView alloc] initWithFrame:[bridged_view_ bounds]]); |
| 1001 [bridged_view_ addSubview:temp_view]; |
| 1002 [temp_view removeFromSuperview]; |
| 1003 } |
| 1004 |
873 } // namespace views | 1005 } // namespace views |
OLD | NEW |