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

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: Pump private run loops 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
« no previous file with comments | « ui/base/cocoa/menu_controller.h ('k') | ui/base/cocoa/menu_controller_unittest.mm » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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"
11 #include "base/message_loop/message_loop.h"
8 #include "base/strings/sys_string_conversions.h" 12 #include "base/strings/sys_string_conversions.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.
Robert Sesek 2017/05/08 22:42:10 Make it clear that these are ui::EventFlags and no
tapted 2017/05/09 04:57:42 Done.
34 - (void)itemSelected:(id)sender eventFlags:(int)eventFlags;
35 @end
36
37 namespace {
38
39 // Object that calls -[MenuController itemSelected:eventFlags:] while a menu is
40 // fading out.
41 class PostedItemSelectedTask
42 : public base::SupportsWeakPtr<PostedItemSelectedTask> {
43 public:
44 // On construction, captures the flags from [NSApp currentEvent] and posts a
45 // task to Run() to MessageLoop::current().
46 explicit PostedItemSelectedTask(NSMenuItem* item);
47
48 // Invokes -itemSelected:eventFlags: on [item_ target], then clears |item_| so
49 // that later calls to Run() will do nothing.
50 void Run();
51
52 private:
53 base::scoped_nsobject<NSMenuItem> item_;
54 const int event_flags_;
55
56 DISALLOW_COPY_AND_ASSIGN(PostedItemSelectedTask);
57 };
58
59 } // namespace
60
18 NSString* const kMenuControllerMenuWillOpenNotification = 61 NSString* const kMenuControllerMenuWillOpenNotification =
19 @"MenuControllerMenuWillOpen"; 62 @"MenuControllerMenuWillOpen";
20 NSString* const kMenuControllerMenuDidCloseNotification = 63 NSString* const kMenuControllerMenuDidCloseNotification =
21 @"MenuControllerMenuDidClose"; 64 @"MenuControllerMenuDidClose";
22 65
23 @interface MenuController (Private) 66 @interface MenuController (Private)
24 - (void)addSeparatorToMenu:(NSMenu*)menu 67 - (void)addSeparatorToMenu:(NSMenu*)menu
25 atIndex:(int)index; 68 atIndex:(int)index;
26 @end 69 @end
27 70
28 @implementation MenuController 71 @interface ResponsiveNSMenuItem : NSMenuItem
72 @end
73
74 @implementation MenuController {
75 BOOL useWithPopUpButtonCell_; // If YES, 0th item is blank
76 BOOL isMenuOpen_;
77 BOOL postItemSelectedAsTask_;
78 std::unique_ptr<PostedItemSelectedTask> postedItemSelectedTask_;
79 }
29 80
30 @synthesize model = model_; 81 @synthesize model = model_;
31 @synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_; 82 @synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_;
83 @synthesize postItemSelectedAsTask = postItemSelectedAsTask_;
32 84
33 + (base::string16)elideMenuTitle:(const base::string16&)title 85 + (base::string16)elideMenuTitle:(const base::string16&)title
34 toWidth:(int)width { 86 toWidth:(int)width {
35 NSFont* nsfont = [NSFont menuBarFontOfSize:0]; // 0 means "default" 87 NSFont* nsfont = [NSFont menuBarFontOfSize:0]; // 0 means "default"
36 return gfx::ElideText(title, gfx::FontList(gfx::Font(nsfont)), width, 88 return gfx::ElideText(title, gfx::FontList(gfx::Font(nsfont)), width,
37 gfx::ELIDE_TAIL); 89 gfx::ELIDE_TAIL);
38 } 90 }
39 91
40 - (id)init { 92 - (id)init {
41 self = [super init]; 93 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|. 157 // associated with the entry in the model identified by |modelIndex|.
106 - (void)addItemToMenu:(NSMenu*)menu 158 - (void)addItemToMenu:(NSMenu*)menu
107 atIndex:(NSInteger)index 159 atIndex:(NSInteger)index
108 fromModel:(ui::MenuModel*)model { 160 fromModel:(ui::MenuModel*)model {
109 base::string16 label16 = model->GetLabelAt(index); 161 base::string16 label16 = model->GetLabelAt(index);
110 int maxWidth = [self maxWidthForMenuModel:model modelIndex:index]; 162 int maxWidth = [self maxWidthForMenuModel:model modelIndex:index];
111 if (maxWidth != -1) 163 if (maxWidth != -1)
112 label16 = [MenuController elideMenuTitle:label16 toWidth:maxWidth]; 164 label16 = [MenuController elideMenuTitle:label16 toWidth:maxWidth];
113 165
114 NSString* label = l10n_util::FixUpWindowsStyleLabel(label16); 166 NSString* label = l10n_util::FixUpWindowsStyleLabel(label16);
115 base::scoped_nsobject<NSMenuItem> item( 167 base::scoped_nsobject<NSMenuItem> item([[ResponsiveNSMenuItem alloc]
116 [[NSMenuItem alloc] initWithTitle:label 168 initWithTitle:label
117 action:@selector(itemSelected:) 169 action:@selector(itemSelected:)
118 keyEquivalent:@""]); 170 keyEquivalent:@""]);
119 171
120 // If the menu item has an icon, set it. 172 // If the menu item has an icon, set it.
121 gfx::Image icon; 173 gfx::Image icon;
122 if (model->GetIconAt(index, &icon) && !icon.IsEmpty()) 174 if (model->GetIconAt(index, &icon) && !icon.IsEmpty())
123 [item setImage:icon.ToNSImage()]; 175 [item setImage:icon.ToNSImage()];
124 176
125 ui::MenuModel::ItemType type = model->GetTypeAt(index); 177 ui::MenuModel::ItemType type = model->GetTypeAt(index);
126 if (type == ui::MenuModel::TYPE_SUBMENU) { 178 if (type == ui::MenuModel::TYPE_SUBMENU) {
127 // Recursively build a submenu from the sub-model at this index. 179 // Recursively build a submenu from the sub-model at this index.
128 [item setTarget:nil]; 180 [item setTarget:nil];
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
193 base::scoped_nsobject<NSAttributedString> title( 245 base::scoped_nsobject<NSAttributedString> title(
194 [[NSAttributedString alloc] initWithString:[(id)item title] 246 [[NSAttributedString alloc] initWithString:[(id)item title]
195 attributes:attributes]); 247 attributes:attributes]);
196 [(id)item setAttributedTitle:title.get()]; 248 [(id)item setAttributedTitle:title.get()];
197 } 249 }
198 return model->IsEnabledAt(modelIndex); 250 return model->IsEnabledAt(modelIndex);
199 } 251 }
200 return NO; 252 return NO;
201 } 253 }
202 254
203 // Called when the user chooses a particular menu item. |sender| is the menu 255 - (void)itemWillBeSelected:(NSMenuItem*)sender {
204 // item chosen. 256 if (postItemSelectedAsTask_ && [sender action] == @selector(itemSelected:) &&
257 [[sender target] respondsToSelector:@selector(itemSelected:eventFlags:)])
Robert Sesek 2017/05/08 22:42:10 nit: braces
tapted 2017/05/09 04:57:42 Done.
258 postedItemSelectedTask_ = base::MakeUnique<PostedItemSelectedTask>(sender);
259 }
260
205 - (void)itemSelected:(id)sender { 261 - (void)itemSelected:(id)sender {
262 if (postedItemSelectedTask_) {
263 postedItemSelectedTask_->Run();
264 postedItemSelectedTask_.reset();
265 } else {
266 [self itemSelected:sender
267 eventFlags:ui::EventFlagsFromNative([NSApp currentEvent])];
268 }
269 }
270
271 - (void)itemSelected:(id)sender eventFlags:(int)eventFlags {
206 NSInteger modelIndex = [sender tag]; 272 NSInteger modelIndex = [sender tag];
207 ui::MenuModel* model = 273 ui::MenuModel* model =
208 static_cast<ui::MenuModel*>( 274 static_cast<ui::MenuModel*>(
209 [[sender representedObject] pointerValue]); 275 [[sender representedObject] pointerValue]);
210 DCHECK(model); 276 DCHECK(model);
211 if (model) { 277 if (model)
212 int event_flags = ui::EventFlagsFromNative([NSApp currentEvent]); 278 model->ActivatedAt(modelIndex, eventFlags);
213 model->ActivatedAt(modelIndex, event_flags);
214 }
215 } 279 }
216 280
217 - (NSMenu*)menu { 281 - (NSMenu*)menu {
218 if (!menu_ && model_) { 282 if (!menu_ && model_) {
219 menu_.reset([[self menuFromModel:model_] retain]); 283 menu_.reset([[self menuFromModel:model_] retain]);
220 [menu_ setDelegate:self]; 284 [menu_ setDelegate:self];
221 // If this is to be used with a NSPopUpButtonCell, add an item at the 0th 285 // 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 286 // 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 287 // complicate creation logic, and since the tags are model indexes, they
224 // are unaffected by the extra item. 288 // are unaffected by the extra item.
(...skipping 22 matching lines...) Expand all
247 if (isMenuOpen_) { 311 if (isMenuOpen_) {
248 model_->MenuWillClose(); 312 model_->MenuWillClose();
249 isMenuOpen_ = NO; 313 isMenuOpen_ = NO;
250 } 314 }
251 [[NSNotificationCenter defaultCenter] 315 [[NSNotificationCenter defaultCenter]
252 postNotificationName:kMenuControllerMenuDidCloseNotification 316 postNotificationName:kMenuControllerMenuDidCloseNotification
253 object:self]; 317 object:self];
254 } 318 }
255 319
256 @end 320 @end
321
322 @interface NSMenuItem (Private)
323 // Private method which is invoked very soon after the event that activates a
324 // menu item is received. AppKit then spends 300ms or so flashing the menu item,
325 // and fading out the menu, blocking the UI thread the entire time.
326 - (void)_sendItemSelectedNote;
327 @end
328
329 @implementation ResponsiveNSMenuItem
330 - (void)_sendItemSelectedNote {
331 if ([[self target] respondsToSelector:@selector(itemWillBeSelected:)])
332 [[self target] itemWillBeSelected:self];
333 [super _sendItemSelectedNote];
334 }
335 @end
336
337 namespace {
338
339 PostedItemSelectedTask::PostedItemSelectedTask(NSMenuItem* item)
340 : item_(item, base::scoped_policy::RETAIN),
341 event_flags_(ui::EventFlagsFromNative([NSApp currentEvent])) {
342 base::MessageLoop::current()->task_runner()->PostTask(
Robert Sesek 2017/05/08 22:42:10 The new thing, which I can never remember, is: ba
tapted 2017/05/09 04:57:42 Done. (I wish there was just base::PostUITask(..)
343 FROM_HERE, base::Bind(&PostedItemSelectedTask::Run, AsWeakPtr()));
344 }
345
346 void PostedItemSelectedTask::Run() {
347 if (!item_)
348 return;
349
350 id target = [item_ target];
351 if ([target respondsToSelector:@selector(itemSelected:eventFlags:)])
352 [target itemSelected:item_.get() eventFlags:event_flags_];
353 else
354 NOTREACHED();
355
356 item_.reset();
357 }
358
359 } // namespace
OLDNEW
« no previous file with comments | « ui/base/cocoa/menu_controller.h ('k') | ui/base/cocoa/menu_controller_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698