| 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/controls/menu/menu_runner_impl_cocoa.h" | 5 #import "ui/views/controls/menu/menu_runner_impl_cocoa.h" |
| 6 | 6 |
| 7 #import "base/mac/foundation_util.h" |
| 7 #include "base/mac/sdk_forward_declarations.h" | 8 #include "base/mac/sdk_forward_declarations.h" |
| 9 #include "base/strings/sys_string_conversions.h" |
| 8 #import "ui/base/cocoa/menu_controller.h" | 10 #import "ui/base/cocoa/menu_controller.h" |
| 9 #include "ui/base/models/menu_model.h" | 11 #include "ui/base/models/menu_model.h" |
| 10 #include "ui/events/base_event_utils.h" | 12 #include "ui/events/base_event_utils.h" |
| 11 #include "ui/events/event_utils.h" | 13 #include "ui/events/event_utils.h" |
| 12 #include "ui/gfx/geometry/rect.h" | 14 #include "ui/gfx/geometry/rect.h" |
| 13 #include "ui/gfx/mac/coordinate_conversion.h" | 15 #include "ui/gfx/mac/coordinate_conversion.h" |
| 16 #import "ui/views/cocoa/bridged_native_widget.h" |
| 14 #include "ui/views/controls/menu/menu_runner_impl_adapter.h" | 17 #include "ui/views/controls/menu/menu_runner_impl_adapter.h" |
| 18 #include "ui/views/widget/native_widget_mac.h" |
| 15 #include "ui/views/widget/widget.h" | 19 #include "ui/views/widget/widget.h" |
| 16 | 20 |
| 21 // Extension of base's MenuController to observe when menu items are activated. |
| 22 @interface ViewsMenuController : MenuController |
| 23 // Sets an NSWindow to pump compositor frames before returning from |
| 24 // -[MenuController itemSelected:]. NSMenu blocks the UI thread until the NSMenu |
| 25 // highlight and fade animation completes, so this allows the compositing work |
| 26 // to be interleaved with the animation delay. |
| 27 - (void)setWindowToUpdate:(NSWindow*)window; |
| 28 @end |
| 29 |
| 17 namespace views { | 30 namespace views { |
| 18 namespace internal { | 31 namespace internal { |
| 19 namespace { | 32 namespace { |
| 20 | 33 |
| 21 // The menu run types that should show a native NSMenu rather than a toolkit- | 34 // The menu run types that should show a native NSMenu rather than a toolkit- |
| 22 // views menu. Only supported when the menu is backed by a ui::MenuModel. | 35 // views menu. Only supported when the menu is backed by a ui::MenuModel. |
| 23 const int kNativeRunTypes = MenuRunner::CONTEXT_MENU | MenuRunner::COMBOBOX; | 36 const int kNativeRunTypes = MenuRunner::CONTEXT_MENU | MenuRunner::COMBOBOX; |
| 24 | 37 |
| 25 const CGFloat kNativeCheckmarkWidth = 18; | 38 const CGFloat kNativeCheckmarkWidth = 18; |
| 26 const CGFloat kNativeMenuItemHeight = 18; | 39 const CGFloat kNativeMenuItemHeight = 18; |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 94 return new MenuRunnerImplAdapter(menu_model, on_menu_closed_callback); | 107 return new MenuRunnerImplAdapter(menu_model, on_menu_closed_callback); |
| 95 } | 108 } |
| 96 | 109 |
| 97 MenuRunnerImplCocoa::MenuRunnerImplCocoa( | 110 MenuRunnerImplCocoa::MenuRunnerImplCocoa( |
| 98 ui::MenuModel* menu, | 111 ui::MenuModel* menu, |
| 99 const base::Closure& on_menu_closed_callback) | 112 const base::Closure& on_menu_closed_callback) |
| 100 : running_(false), | 113 : running_(false), |
| 101 delete_after_run_(false), | 114 delete_after_run_(false), |
| 102 closing_event_time_(base::TimeTicks()), | 115 closing_event_time_(base::TimeTicks()), |
| 103 on_menu_closed_callback_(on_menu_closed_callback) { | 116 on_menu_closed_callback_(on_menu_closed_callback) { |
| 104 menu_controller_.reset( | 117 menu_controller_.reset([[ViewsMenuController alloc] initWithModel:menu |
| 105 [[MenuController alloc] initWithModel:menu useWithPopUpButtonCell:NO]); | 118 useWithPopUpButtonCell:NO]); |
| 106 } | 119 } |
| 107 | 120 |
| 108 bool MenuRunnerImplCocoa::IsRunning() const { | 121 bool MenuRunnerImplCocoa::IsRunning() const { |
| 109 return running_; | 122 return running_; |
| 110 } | 123 } |
| 111 | 124 |
| 112 void MenuRunnerImplCocoa::Release() { | 125 void MenuRunnerImplCocoa::Release() { |
| 113 if (IsRunning()) { | 126 if (IsRunning()) { |
| 114 if (delete_after_run_) | 127 if (delete_after_run_) |
| 115 return; // We already canceled. | 128 return; // We already canceled. |
| 116 | 129 |
| 117 delete_after_run_ = true; | 130 delete_after_run_ = true; |
| 118 [menu_controller_ cancel]; | 131 [menu_controller_ cancel]; |
| 119 } else { | 132 } else { |
| 120 delete this; | 133 delete this; |
| 121 } | 134 } |
| 122 } | 135 } |
| 123 | 136 |
| 137 CGEventRef Callback(CGEventTapProxy proxy, |
| 138 CGEventType type, |
| 139 CGEventRef event, |
| 140 void* userInfo) { |
| 141 DLOG(INFO) << "event: " << type; |
| 142 return NULL; |
| 143 } |
| 144 |
| 145 void MyRunLoopObserver(CFRunLoopObserverRef observer, |
| 146 CFRunLoopActivity activity, |
| 147 void* info) { |
| 148 NSString* name = static_cast<NSString*>(info); |
| 149 DLOG(INFO) << base::SysNSStringToUTF8(name) << " flags: " << activity; |
| 150 } |
| 151 |
| 152 class ScopedRunLoopSpy { |
| 153 public: |
| 154 ScopedRunLoopSpy() { |
| 155 base::ScopedCFTypeRef<CFArrayRef> names( |
| 156 CFRunLoopCopyAllModes(CFRunLoopGetCurrent())); |
| 157 const CFIndex count = CFArrayGetCount(names); |
| 158 for (CFIndex i = 0; i < count; ++i) { |
| 159 base::scoped_nsobject<NSString> name( |
| 160 base::mac::CFToNSCast( |
| 161 static_cast<CFStringRef>(CFArrayGetValueAtIndex(names, i))), |
| 162 base::scoped_policy::RETAIN); |
| 163 NSLog(@"%@", name.get()); |
| 164 CFRunLoopObserverContext context = {0}; |
| 165 context.info = name; |
| 166 CFRunLoopObserverRef observer = CFRunLoopObserverCreate( |
| 167 NULL, kCFRunLoopAllActivities, YES, /* repeat */ |
| 168 0, &MyRunLoopObserver, &context); |
| 169 modes[name] = observer; |
| 170 CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, |
| 171 base::mac::NSToCFCast(name.get())); |
| 172 } |
| 173 } |
| 174 |
| 175 ~ScopedRunLoopSpy() { |
| 176 for (const auto& entry : modes) { |
| 177 CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), entry.second, |
| 178 base::mac::NSToCFCast(entry.first.get())); |
| 179 CFRelease(entry.second); |
| 180 } |
| 181 } |
| 182 |
| 183 private: |
| 184 std::map<base::scoped_nsobject<NSString>, CFRunLoopObserverRef> modes; |
| 185 |
| 186 DISALLOW_COPY_AND_ASSIGN(ScopedRunLoopSpy); |
| 187 }; |
| 188 |
| 124 void MenuRunnerImplCocoa::RunMenuAt(Widget* parent, | 189 void MenuRunnerImplCocoa::RunMenuAt(Widget* parent, |
| 125 MenuButton* button, | 190 MenuButton* button, |
| 126 const gfx::Rect& bounds, | 191 const gfx::Rect& bounds, |
| 127 MenuAnchorPosition anchor, | 192 MenuAnchorPosition anchor, |
| 128 int32_t run_types) { | 193 int32_t run_types) { |
| 129 DCHECK(run_types & kNativeRunTypes); | 194 DCHECK(run_types & kNativeRunTypes); |
| 130 DCHECK(!IsRunning()); | 195 DCHECK(!IsRunning()); |
| 131 DCHECK(parent); | 196 DCHECK(parent); |
| 132 closing_event_time_ = base::TimeTicks(); | 197 closing_event_time_ = base::TimeTicks(); |
| 133 running_ = true; | 198 running_ = true; |
| 134 | 199 |
| 200 CGEventMask event_mask = CGEventMaskBit(kCGEventLeftMouseUp); |
| 201 CFMachPortRef port = |
| 202 CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap, |
| 203 kCGEventTapOptionListenOnly, // passive listener. |
| 204 event_mask, &Callback, this); |
| 205 auto* source = CFMachPortCreateRunLoopSource(NULL, port, 0); |
| 206 CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopCommonModes); |
| 207 CGEventTapEnable(port, true); |
| 208 |
| 135 if (run_types & MenuRunner::CONTEXT_MENU) { | 209 if (run_types & MenuRunner::CONTEXT_MENU) { |
| 136 [NSMenu popUpContextMenu:[menu_controller_ menu] | 210 [NSMenu popUpContextMenu:[menu_controller_ menu] |
| 137 withEvent:[NSApp currentEvent] | 211 withEvent:[NSApp currentEvent] |
| 138 forView:parent->GetNativeView()]; | 212 forView:parent->GetNativeView()]; |
| 139 } else if (run_types & MenuRunner::COMBOBOX) { | 213 } else if (run_types & MenuRunner::COMBOBOX) { |
| 214 [menu_controller_ setWindowToUpdate:parent->GetNativeWindow()]; |
| 140 NSMenuItem* checked_item = FirstCheckedItem(menu_controller_); | 215 NSMenuItem* checked_item = FirstCheckedItem(menu_controller_); |
| 141 base::scoped_nsobject<NSView> anchor_view( | 216 base::scoped_nsobject<NSView> anchor_view( |
| 142 CreateMenuAnchorView(parent->GetNativeWindow(), bounds, checked_item)); | 217 CreateMenuAnchorView(parent->GetNativeWindow(), bounds, checked_item)); |
| 143 NSMenu* menu = [menu_controller_ menu]; | 218 NSMenu* menu = [menu_controller_ menu]; |
| 144 [menu setMinimumWidth:bounds.width() + kNativeCheckmarkWidth]; | 219 [menu setMinimumWidth:bounds.width() + kNativeCheckmarkWidth]; |
| 220 ScopedRunLoopSpy spy; |
| 145 [menu popUpMenuPositioningItem:checked_item | 221 [menu popUpMenuPositioningItem:checked_item |
| 146 atLocation:NSZeroPoint | 222 atLocation:NSZeroPoint |
| 147 inView:anchor_view]; | 223 inView:anchor_view]; |
| 224 DLOG(INFO) << "method return"; |
| 148 [anchor_view removeFromSuperview]; | 225 [anchor_view removeFromSuperview]; |
| 149 } else { | 226 } else { |
| 150 NOTREACHED(); | 227 NOTREACHED(); |
| 151 } | 228 } |
| 152 | 229 |
| 230 CGEventTapEnable(port, false); |
| 231 CFRelease(source); |
| 232 CFRelease(port); |
| 233 |
| 153 closing_event_time_ = ui::EventTimeForNow(); | 234 closing_event_time_ = ui::EventTimeForNow(); |
| 154 running_ = false; | 235 running_ = false; |
| 155 | 236 |
| 156 if (delete_after_run_) { | 237 if (delete_after_run_) { |
| 157 delete this; | 238 delete this; |
| 158 return; | 239 return; |
| 159 } | 240 } |
| 160 | 241 |
| 161 // Don't invoke the callback if Release() was called, since that usually means | 242 // Don't invoke the callback if Release() was called, since that usually means |
| 162 // the owning instance is being destroyed. | 243 // the owning instance is being destroyed. |
| 163 if (!on_menu_closed_callback_.is_null()) | 244 if (!on_menu_closed_callback_.is_null()) |
| 164 on_menu_closed_callback_.Run(); | 245 on_menu_closed_callback_.Run(); |
| 165 } | 246 } |
| 166 | 247 |
| 167 void MenuRunnerImplCocoa::Cancel() { | 248 void MenuRunnerImplCocoa::Cancel() { |
| 168 [menu_controller_ cancel]; | 249 [menu_controller_ cancel]; |
| 169 } | 250 } |
| 170 | 251 |
| 171 base::TimeTicks MenuRunnerImplCocoa::GetClosingEventTime() const { | 252 base::TimeTicks MenuRunnerImplCocoa::GetClosingEventTime() const { |
| 172 return closing_event_time_; | 253 return closing_event_time_; |
| 173 } | 254 } |
| 174 | 255 |
| 175 MenuRunnerImplCocoa::~MenuRunnerImplCocoa() { | 256 MenuRunnerImplCocoa::~MenuRunnerImplCocoa() { |
| 176 } | 257 } |
| 177 | 258 |
| 178 } // namespace internal | 259 } // namespace internal |
| 179 } // namespace views | 260 } // namespace views |
| 261 |
| 262 @implementation ViewsMenuController { |
| 263 base::scoped_nsobject<NSWindow> windowToUpdate_; |
| 264 BOOL compositorPumped_; |
| 265 } |
| 266 |
| 267 - (void)setWindowToUpdate:(NSWindow*)window { |
| 268 windowToUpdate_.reset(window, base::scoped_policy::RETAIN); |
| 269 } |
| 270 |
| 271 - (views::BridgedNativeWidget*)widget { |
| 272 return windowToUpdate_ |
| 273 ? views::NativeWidgetMac::GetBridgeForNativeWindow(windowToUpdate_) |
| 274 : nullptr; |
| 275 } |
| 276 |
| 277 // MenuController overrides. |
| 278 |
| 279 - (BOOL)processItemSelectedEarly:(id)sender { |
| 280 // Items that end in "..." indicate that a new window will be opened. Process |
| 281 // those "late" so the windows transition at the correct times. |
| 282 if ([[sender title] hasSuffix:@"…"]) |
| 283 return NO; |
| 284 |
| 285 [self itemSelected:sender]; |
| 286 if (views::BridgedNativeWidget* nativeWidget = [self widget]) |
| 287 nativeWidget->PumpCompositor(); |
| 288 return YES; |
| 289 } |
| 290 |
| 291 @end |
| OLD | NEW |