Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 | |
| OLD | NEW |