Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(353)

Side by Side Diff: ui/base/cocoa/menu_controller.mm

Issue 2852233002: Mac[Views]: Make native menus more responsive by pumping private runloop modes. (Closed)
Patch Set: fix tests, respond to comments, selfnits Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 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/base/cocoa/menu_controller.h" 5 #import "ui/base/cocoa/menu_controller.h"
6 6
7 #include "base/bind.h"
7 #include "base/logging.h" 8 #include "base/logging.h"
9 #include "base/memory/ptr_util.h"
10 #include "base/memory/weak_ptr.h"
8 #include "base/strings/sys_string_conversions.h" 11 #include "base/strings/sys_string_conversions.h"
12 #include "base/threading/thread_task_runner_handle.h"
9 #include "ui/base/accelerators/accelerator.h" 13 #include "ui/base/accelerators/accelerator.h"
10 #include "ui/base/accelerators/platform_accelerator_cocoa.h" 14 #include "ui/base/accelerators/platform_accelerator_cocoa.h"
11 #include "ui/base/l10n/l10n_util_mac.h" 15 #include "ui/base/l10n/l10n_util_mac.h"
12 #include "ui/base/models/simple_menu_model.h" 16 #include "ui/base/models/simple_menu_model.h"
13 #import "ui/events/event_utils.h" 17 #import "ui/events/event_utils.h"
14 #include "ui/gfx/font_list.h" 18 #include "ui/gfx/font_list.h"
15 #include "ui/gfx/image/image.h" 19 #include "ui/gfx/image/image.h"
16 #include "ui/gfx/text_elider.h" 20 #include "ui/gfx/text_elider.h"
17 21
22 // Internal methods.
23 @interface MenuController ()
24
25 // Called via a private API hook shortly after the event that selects a menu
26 // item arrives.
27 - (void)itemWillBeSelected:(NSMenuItem*)sender;
28
29 // Called when the user chooses a particular menu item. AppKit sends this only
30 // after the menu has fully faded out. |sender| is the menu item chosen.
31 - (void)itemSelected:(id)sender;
32
33 // Called by the posted task to selected an item during menu fade out.
34 // |uiEventFlags| are the ui::EventFlags captured from the triggering NSEvent.
35 - (void)itemSelected:(id)sender uiEventFlags:(int)uiEventFlags;
36 @end
37
38 namespace {
39
40 // Object that calls -[MenuController itemSelected:uiEventFlags:] while a menu
41 // is fading out.
42 class PostedItemSelectedTask
Robert Sesek 2017/05/09 15:49:45 I think you can replace this class with a base::Ca
tapted 2017/05/10 03:49:06 Done (along with base::BindBlock to make the ObjC
43 : public base::SupportsWeakPtr<PostedItemSelectedTask> {
44 public:
45 // On construction, captures the flags from [NSApp currentEvent] and posts a
46 // task to Run() on the current thread's TaskRunner.
47 explicit PostedItemSelectedTask(NSMenuItem* item);
48
49 // Invokes -itemSelected:uiEventFlags: on [item_ target], then clears |item_|
50 // so that later calls to Run() will do nothing.
51 void Run();
52
53 private:
54 base::scoped_nsobject<NSMenuItem> item_;
55 const int ui_event_flags_;
56
57 DISALLOW_COPY_AND_ASSIGN(PostedItemSelectedTask);
58 };
59
60 } // namespace
61
18 NSString* const kMenuControllerMenuWillOpenNotification = 62 NSString* const kMenuControllerMenuWillOpenNotification =
19 @"MenuControllerMenuWillOpen"; 63 @"MenuControllerMenuWillOpen";
20 NSString* const kMenuControllerMenuDidCloseNotification = 64 NSString* const kMenuControllerMenuDidCloseNotification =
21 @"MenuControllerMenuDidClose"; 65 @"MenuControllerMenuDidClose";
22 66
23 @interface MenuController (Private) 67 @interface MenuController (Private)
24 - (void)addSeparatorToMenu:(NSMenu*)menu 68 - (void)addSeparatorToMenu:(NSMenu*)menu
25 atIndex:(int)index; 69 atIndex:(int)index;
26 @end 70 @end
27 71
28 @implementation MenuController 72 @interface ResponsiveNSMenuItem : NSMenuItem
73 @end
74
75 @implementation MenuController {
76 BOOL useWithPopUpButtonCell_; // If YES, 0th item is blank
77 BOOL isMenuOpen_;
78 BOOL postItemSelectedAsTask_;
79 std::unique_ptr<PostedItemSelectedTask> postedItemSelectedTask_;
80 }
29 81
30 @synthesize model = model_; 82 @synthesize model = model_;
31 @synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_; 83 @synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_;
84 @synthesize postItemSelectedAsTask = postItemSelectedAsTask_;
32 85
33 + (base::string16)elideMenuTitle:(const base::string16&)title 86 + (base::string16)elideMenuTitle:(const base::string16&)title
34 toWidth:(int)width { 87 toWidth:(int)width {
35 NSFont* nsfont = [NSFont menuBarFontOfSize:0]; // 0 means "default" 88 NSFont* nsfont = [NSFont menuBarFontOfSize:0]; // 0 means "default"
36 return gfx::ElideText(title, gfx::FontList(gfx::Font(nsfont)), width, 89 return gfx::ElideText(title, gfx::FontList(gfx::Font(nsfont)), width,
37 gfx::ELIDE_TAIL); 90 gfx::ELIDE_TAIL);
38 } 91 }
39 92
40 - (id)init { 93 - (id)init {
41 self = [super init]; 94 self = [super init];
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
105 // associated with the entry in the model identified by |modelIndex|. 158 // associated with the entry in the model identified by |modelIndex|.
106 - (void)addItemToMenu:(NSMenu*)menu 159 - (void)addItemToMenu:(NSMenu*)menu
107 atIndex:(NSInteger)index 160 atIndex:(NSInteger)index
108 fromModel:(ui::MenuModel*)model { 161 fromModel:(ui::MenuModel*)model {
109 base::string16 label16 = model->GetLabelAt(index); 162 base::string16 label16 = model->GetLabelAt(index);
110 int maxWidth = [self maxWidthForMenuModel:model modelIndex:index]; 163 int maxWidth = [self maxWidthForMenuModel:model modelIndex:index];
111 if (maxWidth != -1) 164 if (maxWidth != -1)
112 label16 = [MenuController elideMenuTitle:label16 toWidth:maxWidth]; 165 label16 = [MenuController elideMenuTitle:label16 toWidth:maxWidth];
113 166
114 NSString* label = l10n_util::FixUpWindowsStyleLabel(label16); 167 NSString* label = l10n_util::FixUpWindowsStyleLabel(label16);
115 base::scoped_nsobject<NSMenuItem> item( 168 base::scoped_nsobject<NSMenuItem> item([[ResponsiveNSMenuItem alloc]
116 [[NSMenuItem alloc] initWithTitle:label 169 initWithTitle:label
117 action:@selector(itemSelected:) 170 action:@selector(itemSelected:)
118 keyEquivalent:@""]); 171 keyEquivalent:@""]);
119 172
120 // If the menu item has an icon, set it. 173 // If the menu item has an icon, set it.
121 gfx::Image icon; 174 gfx::Image icon;
122 if (model->GetIconAt(index, &icon) && !icon.IsEmpty()) 175 if (model->GetIconAt(index, &icon) && !icon.IsEmpty())
123 [item setImage:icon.ToNSImage()]; 176 [item setImage:icon.ToNSImage()];
124 177
125 ui::MenuModel::ItemType type = model->GetTypeAt(index); 178 ui::MenuModel::ItemType type = model->GetTypeAt(index);
126 if (type == ui::MenuModel::TYPE_SUBMENU) { 179 if (type == ui::MenuModel::TYPE_SUBMENU) {
127 // Recursively build a submenu from the sub-model at this index. 180 // Recursively build a submenu from the sub-model at this index.
128 [item setTarget:nil]; 181 [item setTarget:nil];
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
193 base::scoped_nsobject<NSAttributedString> title( 246 base::scoped_nsobject<NSAttributedString> title(
194 [[NSAttributedString alloc] initWithString:[(id)item title] 247 [[NSAttributedString alloc] initWithString:[(id)item title]
195 attributes:attributes]); 248 attributes:attributes]);
196 [(id)item setAttributedTitle:title.get()]; 249 [(id)item setAttributedTitle:title.get()];
197 } 250 }
198 return model->IsEnabledAt(modelIndex); 251 return model->IsEnabledAt(modelIndex);
199 } 252 }
200 return NO; 253 return NO;
201 } 254 }
202 255
203 // Called when the user chooses a particular menu item. |sender| is the menu 256 - (void)itemWillBeSelected:(NSMenuItem*)sender {
204 // item chosen. 257 if (postItemSelectedAsTask_ && [sender action] == @selector(itemSelected:) &&
258 [[sender target]
259 respondsToSelector:@selector(itemSelected:uiEventFlags:)]) {
260 postedItemSelectedTask_ = base::MakeUnique<PostedItemSelectedTask>(sender);
261 }
262 }
263
205 - (void)itemSelected:(id)sender { 264 - (void)itemSelected:(id)sender {
265 if (postedItemSelectedTask_) {
266 postedItemSelectedTask_->Run();
267 postedItemSelectedTask_.reset();
268 } else {
269 [self itemSelected:sender
270 uiEventFlags:ui::EventFlagsFromNative([NSApp currentEvent])];
271 }
272 }
273
274 - (void)itemSelected:(id)sender uiEventFlags:(int)uiEventFlags {
206 NSInteger modelIndex = [sender tag]; 275 NSInteger modelIndex = [sender tag];
207 ui::MenuModel* model = 276 ui::MenuModel* model =
208 static_cast<ui::MenuModel*>( 277 static_cast<ui::MenuModel*>(
209 [[sender representedObject] pointerValue]); 278 [[sender representedObject] pointerValue]);
210 DCHECK(model); 279 DCHECK(model);
211 if (model) { 280 if (model)
212 int event_flags = ui::EventFlagsFromNative([NSApp currentEvent]); 281 model->ActivatedAt(modelIndex, uiEventFlags);
213 model->ActivatedAt(modelIndex, event_flags);
214 }
215 } 282 }
216 283
217 - (NSMenu*)menu { 284 - (NSMenu*)menu {
218 if (!menu_ && model_) { 285 if (!menu_ && model_) {
219 menu_.reset([[self menuFromModel:model_] retain]); 286 menu_.reset([[self menuFromModel:model_] retain]);
220 [menu_ setDelegate:self]; 287 [menu_ setDelegate:self];
221 // If this is to be used with a NSPopUpButtonCell, add an item at the 0th 288 // If this is to be used with a NSPopUpButtonCell, add an item at the 0th
222 // position that's empty. Doing it after the menu has been constructed won't 289 // position that's empty. Doing it after the menu has been constructed won't
223 // complicate creation logic, and since the tags are model indexes, they 290 // complicate creation logic, and since the tags are model indexes, they
224 // are unaffected by the extra item. 291 // are unaffected by the extra item.
(...skipping 22 matching lines...) Expand all
247 if (isMenuOpen_) { 314 if (isMenuOpen_) {
248 model_->MenuWillClose(); 315 model_->MenuWillClose();
249 isMenuOpen_ = NO; 316 isMenuOpen_ = NO;
250 } 317 }
251 [[NSNotificationCenter defaultCenter] 318 [[NSNotificationCenter defaultCenter]
252 postNotificationName:kMenuControllerMenuDidCloseNotification 319 postNotificationName:kMenuControllerMenuDidCloseNotification
253 object:self]; 320 object:self];
254 } 321 }
255 322
256 @end 323 @end
324
325 @interface NSMenuItem (Private)
326 // Private method which is invoked very soon after the event that activates a
327 // menu item is received. AppKit then spends 300ms or so flashing the menu item,
328 // and fading out the menu, in private run loop modes.
329 - (void)_sendItemSelectedNote;
330 @end
331
332 @implementation ResponsiveNSMenuItem
333 - (void)_sendItemSelectedNote {
334 if ([[self target] respondsToSelector:@selector(itemWillBeSelected:)])
335 [[self target] itemWillBeSelected:self];
336 [super _sendItemSelectedNote];
337 }
338 @end
339
340 namespace {
341
342 PostedItemSelectedTask::PostedItemSelectedTask(NSMenuItem* item)
343 : item_(item, base::scoped_policy::RETAIN),
344 ui_event_flags_(ui::EventFlagsFromNative([NSApp currentEvent])) {
345 base::ThreadTaskRunnerHandle::Get()->PostTask(
346 FROM_HERE, base::Bind(&PostedItemSelectedTask::Run, AsWeakPtr()));
347 }
348
349 void PostedItemSelectedTask::Run() {
350 if (!item_)
351 return;
352
353 id target = [item_ target];
354 if ([target respondsToSelector:@selector(itemSelected:uiEventFlags:)])
355 [target itemSelected:item_.get() uiEventFlags:ui_event_flags_];
356 else
357 NOTREACHED();
358
359 item_.reset();
360 }
361
362 } // namespace
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698