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/cancelable_callback.h" | |
| 7 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "base/mac/bind_objc_block.h" | |
| 10 #include "base/memory/ptr_util.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 |
| 18 NSString* const kMenuControllerMenuWillOpenNotification = | 22 NSString* const kMenuControllerMenuWillOpenNotification = |
| 19 @"MenuControllerMenuWillOpen"; | 23 @"MenuControllerMenuWillOpen"; |
| 20 NSString* const kMenuControllerMenuDidCloseNotification = | 24 NSString* const kMenuControllerMenuDidCloseNotification = |
| 21 @"MenuControllerMenuDidClose"; | 25 @"MenuControllerMenuDidClose"; |
| 22 | 26 |
| 23 @interface MenuController (Private) | 27 // Internal methods. |
| 24 - (void)addSeparatorToMenu:(NSMenu*)menu | 28 @interface MenuController () |
| 25 atIndex:(int)index; | 29 // Adds a separator item at the given index. As the separator doesn't need |
| 30 // anything from the model, this method doesn't need the model index as the | |
| 31 // other method below does. | |
| 32 - (void)addSeparatorToMenu:(NSMenu*)menu atIndex:(int)index; | |
| 33 | |
| 34 // Called via a private API hook shortly after the event that selects a menu | |
| 35 // item arrives. | |
| 36 - (void)itemWillBeSelected:(NSMenuItem*)sender; | |
| 37 | |
| 38 // Called when the user chooses a particular menu item. AppKit sends this only | |
| 39 // after the menu has fully faded out. |sender| is the menu item chosen. | |
| 40 - (void)itemSelected:(id)sender; | |
| 41 | |
| 42 // Called by the posted task to selected an item during menu fade out. | |
| 43 // |uiEventFlags| are the ui::EventFlags captured from the triggering NSEvent. | |
| 44 - (void)itemSelected:(id)sender uiEventFlags:(int)uiEventFlags; | |
| 26 @end | 45 @end |
| 27 | 46 |
| 28 @implementation MenuController | 47 @interface ResponsiveNSMenuItem : NSMenuItem |
| 48 @end | |
| 49 | |
| 50 @implementation MenuController { | |
| 51 BOOL useWithPopUpButtonCell_; // If YES, 0th item is blank | |
| 52 BOOL isMenuOpen_; | |
| 53 BOOL postItemSelectedAsTask_; | |
| 54 std::unique_ptr<base::CancelableClosure> postedItemSelectedTask_; | |
| 55 } | |
| 29 | 56 |
| 30 @synthesize model = model_; | 57 @synthesize model = model_; |
| 31 @synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_; | 58 @synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_; |
| 59 @synthesize postItemSelectedAsTask = postItemSelectedAsTask_; | |
| 32 | 60 |
| 33 + (base::string16)elideMenuTitle:(const base::string16&)title | 61 + (base::string16)elideMenuTitle:(const base::string16&)title |
| 34 toWidth:(int)width { | 62 toWidth:(int)width { |
| 35 NSFont* nsfont = [NSFont menuBarFontOfSize:0]; // 0 means "default" | 63 NSFont* nsfont = [NSFont menuBarFontOfSize:0]; // 0 means "default" |
| 36 return gfx::ElideText(title, gfx::FontList(gfx::Font(nsfont)), width, | 64 return gfx::ElideText(title, gfx::FontList(gfx::Font(nsfont)), width, |
| 37 gfx::ELIDE_TAIL); | 65 gfx::ELIDE_TAIL); |
| 38 } | 66 } |
| 39 | 67 |
| 40 - (id)init { | 68 - (id)init { |
| 41 self = [super init]; | 69 self = [super init]; |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 64 } | 92 } |
| 65 | 93 |
| 66 - (void)cancel { | 94 - (void)cancel { |
| 67 if (isMenuOpen_) { | 95 if (isMenuOpen_) { |
| 68 [menu_ cancelTracking]; | 96 [menu_ cancelTracking]; |
| 69 model_->MenuWillClose(); | 97 model_->MenuWillClose(); |
| 70 isMenuOpen_ = NO; | 98 isMenuOpen_ = NO; |
| 71 } | 99 } |
| 72 } | 100 } |
| 73 | 101 |
| 74 // Creates a NSMenu from the given model. If the model has submenus, this can | |
| 75 // be invoked recursively. | |
| 76 - (NSMenu*)menuFromModel:(ui::MenuModel*)model { | 102 - (NSMenu*)menuFromModel:(ui::MenuModel*)model { |
| 77 NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; | 103 NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; |
| 78 | 104 |
| 79 const int count = model->GetItemCount(); | 105 const int count = model->GetItemCount(); |
| 80 for (int index = 0; index < count; index++) { | 106 for (int index = 0; index < count; index++) { |
| 81 if (model->GetTypeAt(index) == ui::MenuModel::TYPE_SEPARATOR) | 107 if (model->GetTypeAt(index) == ui::MenuModel::TYPE_SEPARATOR) |
| 82 [self addSeparatorToMenu:menu atIndex:index]; | 108 [self addSeparatorToMenu:menu atIndex:index]; |
| 83 else | 109 else |
| 84 [self addItemToMenu:menu atIndex:index fromModel:model]; | 110 [self addItemToMenu:menu atIndex:index fromModel:model]; |
| 85 } | 111 } |
| 86 | 112 |
| 87 return menu; | 113 return menu; |
| 88 } | 114 } |
| 89 | 115 |
| 90 - (int)maxWidthForMenuModel:(ui::MenuModel*)model | 116 - (int)maxWidthForMenuModel:(ui::MenuModel*)model |
| 91 modelIndex:(int)modelIndex { | 117 modelIndex:(int)modelIndex { |
| 92 return -1; | 118 return -1; |
| 93 } | 119 } |
| 94 | 120 |
| 95 // Adds a separator item at the given index. As the separator doesn't need | |
| 96 // anything from the model, this method doesn't need the model index as the | |
| 97 // other method below does. | |
| 98 - (void)addSeparatorToMenu:(NSMenu*)menu | 121 - (void)addSeparatorToMenu:(NSMenu*)menu |
| 99 atIndex:(int)index { | 122 atIndex:(int)index { |
| 100 NSMenuItem* separator = [NSMenuItem separatorItem]; | 123 NSMenuItem* separator = [NSMenuItem separatorItem]; |
| 101 [menu insertItem:separator atIndex:index]; | 124 [menu insertItem:separator atIndex:index]; |
| 102 } | 125 } |
| 103 | 126 |
| 104 // Adds an item or a hierarchical menu to the item at the |index|, | |
| 105 // associated with the entry in the model identified by |modelIndex|. | |
| 106 - (void)addItemToMenu:(NSMenu*)menu | 127 - (void)addItemToMenu:(NSMenu*)menu |
| 107 atIndex:(NSInteger)index | 128 atIndex:(NSInteger)index |
| 108 fromModel:(ui::MenuModel*)model { | 129 fromModel:(ui::MenuModel*)model { |
| 109 base::string16 label16 = model->GetLabelAt(index); | 130 base::string16 label16 = model->GetLabelAt(index); |
| 110 int maxWidth = [self maxWidthForMenuModel:model modelIndex:index]; | 131 int maxWidth = [self maxWidthForMenuModel:model modelIndex:index]; |
| 111 if (maxWidth != -1) | 132 if (maxWidth != -1) |
| 112 label16 = [MenuController elideMenuTitle:label16 toWidth:maxWidth]; | 133 label16 = [MenuController elideMenuTitle:label16 toWidth:maxWidth]; |
| 113 | 134 |
| 114 NSString* label = l10n_util::FixUpWindowsStyleLabel(label16); | 135 NSString* label = l10n_util::FixUpWindowsStyleLabel(label16); |
| 115 base::scoped_nsobject<NSMenuItem> item( | 136 base::scoped_nsobject<NSMenuItem> item([[ResponsiveNSMenuItem alloc] |
| 116 [[NSMenuItem alloc] initWithTitle:label | 137 initWithTitle:label |
| 117 action:@selector(itemSelected:) | 138 action:@selector(itemSelected:) |
| 118 keyEquivalent:@""]); | 139 keyEquivalent:@""]); |
| 119 | 140 |
| 120 // If the menu item has an icon, set it. | 141 // If the menu item has an icon, set it. |
| 121 gfx::Image icon; | 142 gfx::Image icon; |
| 122 if (model->GetIconAt(index, &icon) && !icon.IsEmpty()) | 143 if (model->GetIconAt(index, &icon) && !icon.IsEmpty()) |
| 123 [item setImage:icon.ToNSImage()]; | 144 [item setImage:icon.ToNSImage()]; |
| 124 | 145 |
| 125 ui::MenuModel::ItemType type = model->GetTypeAt(index); | 146 ui::MenuModel::ItemType type = model->GetTypeAt(index); |
| 126 if (type == ui::MenuModel::TYPE_SUBMENU) { | 147 if (type == ui::MenuModel::TYPE_SUBMENU) { |
| 127 // Recursively build a submenu from the sub-model at this index. | 148 // Recursively build a submenu from the sub-model at this index. |
| 128 [item setTarget:nil]; | 149 [item setTarget:nil]; |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 149 if (platformAccelerator) { | 170 if (platformAccelerator) { |
| 150 [item setKeyEquivalent:platformAccelerator->characters()]; | 171 [item setKeyEquivalent:platformAccelerator->characters()]; |
| 151 [item setKeyEquivalentModifierMask: | 172 [item setKeyEquivalentModifierMask: |
| 152 platformAccelerator->modifier_mask()]; | 173 platformAccelerator->modifier_mask()]; |
| 153 } | 174 } |
| 154 } | 175 } |
| 155 } | 176 } |
| 156 [menu insertItem:item atIndex:index]; | 177 [menu insertItem:item atIndex:index]; |
| 157 } | 178 } |
| 158 | 179 |
| 159 // Called before the menu is to be displayed to update the state (enabled, | |
| 160 // radio, etc) of each item in the menu. Also will update the title if | |
| 161 // the item is marked as "dynamic". | |
| 162 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { | 180 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { |
| 163 SEL action = [item action]; | 181 SEL action = [item action]; |
| 164 if (action != @selector(itemSelected:)) | 182 if (action != @selector(itemSelected:)) |
| 165 return NO; | 183 return NO; |
| 166 | 184 |
| 167 NSInteger modelIndex = [item tag]; | 185 NSInteger modelIndex = [item tag]; |
| 168 ui::MenuModel* model = | 186 ui::MenuModel* model = |
| 169 static_cast<ui::MenuModel*>( | 187 static_cast<ui::MenuModel*>( |
| 170 [[(id)item representedObject] pointerValue]); | 188 [[(id)item representedObject] pointerValue]); |
| 171 DCHECK(model); | 189 DCHECK(model); |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 193 base::scoped_nsobject<NSAttributedString> title( | 211 base::scoped_nsobject<NSAttributedString> title( |
| 194 [[NSAttributedString alloc] initWithString:[(id)item title] | 212 [[NSAttributedString alloc] initWithString:[(id)item title] |
| 195 attributes:attributes]); | 213 attributes:attributes]); |
| 196 [(id)item setAttributedTitle:title.get()]; | 214 [(id)item setAttributedTitle:title.get()]; |
| 197 } | 215 } |
| 198 return model->IsEnabledAt(modelIndex); | 216 return model->IsEnabledAt(modelIndex); |
| 199 } | 217 } |
| 200 return NO; | 218 return NO; |
| 201 } | 219 } |
| 202 | 220 |
| 203 // Called when the user chooses a particular menu item. |sender| is the menu | 221 - (void)itemWillBeSelected:(NSMenuItem*)sender { |
| 204 // item chosen. | 222 if (postItemSelectedAsTask_ && [sender action] == @selector(itemSelected:) && |
| 223 [[sender target] | |
| 224 respondsToSelector:@selector(itemSelected:uiEventFlags:)]) { | |
| 225 const int uiEventFlags = ui::EventFlagsFromNative([NSApp currentEvent]); | |
| 226 postedItemSelectedTask_ = | |
| 227 base::MakeUnique<base::CancelableClosure>(base::BindBlock(^() { | |
|
Robert Sesek
2017/05/10 13:37:27
nit: can omit the () and just do |^{|.
tapted
2017/05/11 00:24:06
Done.
| |
| 228 id target = [sender target]; | |
| 229 if ([target respondsToSelector:@selector(itemSelected:uiEventFlags:)]) | |
| 230 [target itemSelected:sender uiEventFlags:uiEventFlags]; | |
| 231 else | |
| 232 NOTREACHED(); | |
| 233 })); | |
| 234 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
| 235 FROM_HERE, postedItemSelectedTask_->callback()); | |
| 236 } | |
| 237 } | |
| 238 | |
| 205 - (void)itemSelected:(id)sender { | 239 - (void)itemSelected:(id)sender { |
| 240 if (postedItemSelectedTask_) { | |
| 241 if (!postedItemSelectedTask_->IsCancelled()) | |
| 242 postedItemSelectedTask_->callback().Run(); | |
| 243 postedItemSelectedTask_.reset(); | |
| 244 } else { | |
| 245 [self itemSelected:sender | |
| 246 uiEventFlags:ui::EventFlagsFromNative([NSApp currentEvent])]; | |
| 247 } | |
| 248 } | |
| 249 | |
| 250 - (void)itemSelected:(id)sender uiEventFlags:(int)uiEventFlags { | |
| 206 NSInteger modelIndex = [sender tag]; | 251 NSInteger modelIndex = [sender tag]; |
| 207 ui::MenuModel* model = | 252 ui::MenuModel* model = |
| 208 static_cast<ui::MenuModel*>( | 253 static_cast<ui::MenuModel*>( |
| 209 [[sender representedObject] pointerValue]); | 254 [[sender representedObject] pointerValue]); |
| 210 DCHECK(model); | 255 DCHECK(model); |
| 211 if (model) { | 256 if (model) |
| 212 int event_flags = ui::EventFlagsFromNative([NSApp currentEvent]); | 257 model->ActivatedAt(modelIndex, uiEventFlags); |
| 213 model->ActivatedAt(modelIndex, event_flags); | 258 |
| 214 } | 259 // Cancel any posted task, but don't reset it, so that the correct path is |
| 260 // taken in -itemSelected:. | |
| 261 if (postedItemSelectedTask_) | |
| 262 postedItemSelectedTask_->Cancel(); | |
| 215 } | 263 } |
| 216 | 264 |
| 217 - (NSMenu*)menu { | 265 - (NSMenu*)menu { |
| 218 if (!menu_ && model_) { | 266 if (!menu_ && model_) { |
| 219 menu_.reset([[self menuFromModel:model_] retain]); | 267 menu_.reset([[self menuFromModel:model_] retain]); |
| 220 [menu_ setDelegate:self]; | 268 [menu_ setDelegate:self]; |
| 221 // If this is to be used with a NSPopUpButtonCell, add an item at the 0th | 269 // 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 | 270 // 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 | 271 // complicate creation logic, and since the tags are model indexes, they |
| 224 // are unaffected by the extra item. | 272 // are unaffected by the extra item. |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 247 if (isMenuOpen_) { | 295 if (isMenuOpen_) { |
| 248 model_->MenuWillClose(); | 296 model_->MenuWillClose(); |
| 249 isMenuOpen_ = NO; | 297 isMenuOpen_ = NO; |
| 250 } | 298 } |
| 251 [[NSNotificationCenter defaultCenter] | 299 [[NSNotificationCenter defaultCenter] |
| 252 postNotificationName:kMenuControllerMenuDidCloseNotification | 300 postNotificationName:kMenuControllerMenuDidCloseNotification |
| 253 object:self]; | 301 object:self]; |
| 254 } | 302 } |
| 255 | 303 |
| 256 @end | 304 @end |
| 305 | |
| 306 @interface NSMenuItem (Private) | |
| 307 // Private method which is invoked very soon after the event that activates a | |
| 308 // menu item is received. AppKit then spends 300ms or so flashing the menu item, | |
| 309 // and fading out the menu, in private run loop modes. | |
| 310 - (void)_sendItemSelectedNote; | |
| 311 @end | |
| 312 | |
| 313 @implementation ResponsiveNSMenuItem | |
| 314 - (void)_sendItemSelectedNote { | |
| 315 if ([[self target] respondsToSelector:@selector(itemWillBeSelected:)]) | |
| 316 [[self target] itemWillBeSelected:self]; | |
| 317 [super _sendItemSelectedNote]; | |
| 318 } | |
| 319 @end | |
| OLD | NEW |