| 
									
										
										
										
											2014-10-31 11:17:05 -07:00
										 |  |  | // Copyright (c) 2013 GitHub, Inc. | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
					
						
							| 
									
										
										
										
											2014-04-25 17:49:37 +08:00
										 |  |  | // Use of this source code is governed by the MIT license that can be | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  | // found in the LICENSE file. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-16 08:30:26 +08:00
										 |  |  | #import "atom/browser/ui/cocoa/atom_menu_controller.h" | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-10 12:39:05 +08:00
										 |  |  | #include "atom/browser/ui/atom_menu_model.h" | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  | #include "base/logging.h" | 
					
						
							|  |  |  | #include "base/strings/sys_string_conversions.h" | 
					
						
							| 
									
										
										
										
											2015-09-01 19:48:11 +08:00
										 |  |  | #include "base/strings/utf_string_conversions.h" | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  | #include "ui/base/accelerators/accelerator.h" | 
					
						
							|  |  |  | #include "ui/base/accelerators/platform_accelerator_cocoa.h" | 
					
						
							|  |  |  | #include "ui/base/l10n/l10n_util_mac.h" | 
					
						
							| 
									
										
										
										
											2015-07-29 12:01:27 +08:00
										 |  |  | #include "ui/events/cocoa/cocoa_event_utils.h" | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  | #include "ui/gfx/image/image.h" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-09-01 19:48:11 +08:00
										 |  |  | namespace { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct Role { | 
					
						
							|  |  |  |   SEL selector; | 
					
						
							|  |  |  |   const char* role; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | Role kRolesMap[] = { | 
					
						
							|  |  |  |   { @selector(orderFrontStandardAboutPanel:), "about" }, | 
					
						
							|  |  |  |   { @selector(hide:), "hide" }, | 
					
						
							|  |  |  |   { @selector(hideOtherApplications:), "hideothers" }, | 
					
						
							|  |  |  |   { @selector(unhideAllApplications:), "unhide" }, | 
					
						
							|  |  |  |   { @selector(arrangeInFront:), "front" }, | 
					
						
							|  |  |  |   { @selector(undo:), "undo" }, | 
					
						
							|  |  |  |   { @selector(redo:), "redo" }, | 
					
						
							|  |  |  |   { @selector(cut:), "cut" }, | 
					
						
							|  |  |  |   { @selector(copy:), "copy" }, | 
					
						
							|  |  |  |   { @selector(paste:), "paste" }, | 
					
						
							| 
									
										
										
										
											2016-03-07 15:53:09 -08:00
										 |  |  |   { @selector(delete:), "delete" }, | 
					
						
							| 
									
										
										
										
											2016-03-07 15:42:32 -08:00
										 |  |  |   { @selector(pasteAndMatchStyle:), "paste-and-match-style" }, | 
					
						
							| 
									
										
										
										
											2015-09-01 19:48:11 +08:00
										 |  |  |   { @selector(selectAll:), "selectall" }, | 
					
						
							|  |  |  |   { @selector(performMiniaturize:), "minimize" }, | 
					
						
							|  |  |  |   { @selector(performClose:), "close" }, | 
					
						
							| 
									
										
										
										
											2016-03-07 16:01:46 -08:00
										 |  |  |   { @selector(performZoom:), "zoom" }, | 
					
						
							| 
									
										
										
										
											2015-09-01 19:48:11 +08:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | }  // namespace | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | @implementation AtomMenuController | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @synthesize model = model_; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (id)init { | 
					
						
							| 
									
										
										
										
											2014-11-16 23:04:31 +08:00
										 |  |  |   if ((self = [super init])) | 
					
						
							|  |  |  |     [self menu]; | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  |   return self; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-14 23:03:02 +08:00
										 |  |  | - (id)initWithModel:(ui::MenuModel*)model { | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  |   if ((self = [super init])) { | 
					
						
							|  |  |  |     model_ = model; | 
					
						
							|  |  |  |     [self menu]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return self; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (void)dealloc { | 
					
						
							|  |  |  |   [menu_ setDelegate:nil]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Close the menu if it is still open. This could happen if a tab gets closed | 
					
						
							|  |  |  |   // while its context menu is still open. | 
					
						
							|  |  |  |   [self cancel]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   model_ = NULL; | 
					
						
							|  |  |  |   [super dealloc]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-11-16 20:24:29 +08:00
										 |  |  | - (void)populateWithModel:(ui::MenuModel*)model { | 
					
						
							|  |  |  |   if (!menu_) | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   model_ = model; | 
					
						
							|  |  |  |   [menu_ removeAllItems]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const int count = model->GetItemCount(); | 
					
						
							|  |  |  |   for (int index = 0; index < count; index++) { | 
					
						
							|  |  |  |     if (model->GetTypeAt(index) == ui::MenuModel::TYPE_SEPARATOR) | 
					
						
							|  |  |  |       [self addSeparatorToMenu:menu_ atIndex:index]; | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       [self addItemToMenu:menu_ atIndex:index fromModel:model]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  | - (void)cancel { | 
					
						
							|  |  |  |   if (isMenuOpen_) { | 
					
						
							|  |  |  |     [menu_ cancelTracking]; | 
					
						
							|  |  |  |     model_->MenuClosed(); | 
					
						
							|  |  |  |     isMenuOpen_ = NO; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Creates a NSMenu from the given model. If the model has submenus, this can | 
					
						
							|  |  |  | // be invoked recursively. | 
					
						
							|  |  |  | - (NSMenu*)menuFromModel:(ui::MenuModel*)model { | 
					
						
							|  |  |  |   NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const int count = model->GetItemCount(); | 
					
						
							|  |  |  |   for (int index = 0; index < count; index++) { | 
					
						
							|  |  |  |     if (model->GetTypeAt(index) == ui::MenuModel::TYPE_SEPARATOR) | 
					
						
							|  |  |  |       [self addSeparatorToMenu:menu atIndex:index]; | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       [self addItemToMenu:menu atIndex:index fromModel:model]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return menu; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Adds a separator item at the given index. As the separator doesn't need | 
					
						
							|  |  |  | // anything from the model, this method doesn't need the model index as the | 
					
						
							|  |  |  | // other method below does. | 
					
						
							|  |  |  | - (void)addSeparatorToMenu:(NSMenu*)menu | 
					
						
							|  |  |  |                    atIndex:(int)index { | 
					
						
							|  |  |  |   NSMenuItem* separator = [NSMenuItem separatorItem]; | 
					
						
							|  |  |  |   [menu insertItem:separator atIndex:index]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Adds an item or a hierarchical menu to the item at the |index|, | 
					
						
							|  |  |  | // associated with the entry in the model identified by |modelIndex|. | 
					
						
							|  |  |  | - (void)addItemToMenu:(NSMenu*)menu | 
					
						
							|  |  |  |               atIndex:(NSInteger)index | 
					
						
							| 
									
										
										
										
											2015-09-01 19:48:11 +08:00
										 |  |  |             fromModel:(ui::MenuModel*)ui_model { | 
					
						
							|  |  |  |   atom::AtomMenuModel* model = static_cast<atom::AtomMenuModel*>(ui_model); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-06-28 22:33:00 +08:00
										 |  |  |   base::string16 label16 = model->GetLabelAt(index); | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  |   NSString* label = l10n_util::FixUpWindowsStyleLabel(label16); | 
					
						
							| 
									
										
										
										
											2013-12-11 15:48:19 +08:00
										 |  |  |   base::scoped_nsobject<NSMenuItem> item( | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  |       [[NSMenuItem alloc] initWithTitle:label | 
					
						
							|  |  |  |                                  action:@selector(itemSelected:) | 
					
						
							|  |  |  |                           keyEquivalent:@""]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // If the menu item has an icon, set it. | 
					
						
							|  |  |  |   gfx::Image icon; | 
					
						
							|  |  |  |   if (model->GetIconAt(index, &icon) && !icon.IsEmpty()) | 
					
						
							|  |  |  |     [item setImage:icon.ToNSImage()]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ui::MenuModel::ItemType type = model->GetTypeAt(index); | 
					
						
							|  |  |  |   if (type == ui::MenuModel::TYPE_SUBMENU) { | 
					
						
							|  |  |  |     // Recursively build a submenu from the sub-model at this index. | 
					
						
							|  |  |  |     [item setTarget:nil]; | 
					
						
							|  |  |  |     [item setAction:nil]; | 
					
						
							|  |  |  |     ui::MenuModel* submenuModel = model->GetSubmenuModelAt(index); | 
					
						
							| 
									
										
										
										
											2015-08-10 12:39:05 +08:00
										 |  |  |     NSMenu* submenu = [self menuFromModel:submenuModel]; | 
					
						
							| 
									
										
										
										
											2013-08-14 22:33:18 +08:00
										 |  |  |     [submenu setTitle:[item title]]; | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  |     [item setSubmenu:submenu]; | 
					
						
							| 
									
										
										
										
											2013-08-14 22:33:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-09-01 19:48:11 +08:00
										 |  |  |     // Set submenu's role. | 
					
						
							|  |  |  |     base::string16 role = model->GetRoleAt(index); | 
					
						
							| 
									
										
										
										
											2015-12-30 19:36:02 -08:00
										 |  |  |     if (role == base::ASCIIToUTF16("window") && [submenu numberOfItems]) | 
					
						
							| 
									
										
										
										
											2013-08-14 22:33:18 +08:00
										 |  |  |       [NSApp setWindowsMenu:submenu]; | 
					
						
							| 
									
										
										
										
											2015-09-01 19:48:11 +08:00
										 |  |  |     else if (role == base::ASCIIToUTF16("help")) | 
					
						
							| 
									
										
										
										
											2013-10-17 10:15:57 +08:00
										 |  |  |       [NSApp setHelpMenu:submenu]; | 
					
						
							| 
									
										
										
										
											2015-12-30 19:36:02 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-09-01 19:48:11 +08:00
										 |  |  |     if (role == base::ASCIIToUTF16("services")) | 
					
						
							| 
									
										
										
										
											2014-09-05 13:07:05 +08:00
										 |  |  |       [NSApp setServicesMenu:submenu]; | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  |   } else { | 
					
						
							|  |  |  |     // The MenuModel works on indexes so we can't just set the command id as the | 
					
						
							|  |  |  |     // tag like we do in other menus. Also set the represented object to be | 
					
						
							|  |  |  |     // the model so hierarchical menus check the correct index in the correct | 
					
						
							|  |  |  |     // model. Setting the target to |self| allows this class to participate | 
					
						
							|  |  |  |     // in validation of the menu items. | 
					
						
							|  |  |  |     [item setTag:index]; | 
					
						
							|  |  |  |     NSValue* modelObject = [NSValue valueWithPointer:model]; | 
					
						
							|  |  |  |     [item setRepresentedObject:modelObject];  // Retains |modelObject|. | 
					
						
							|  |  |  |     ui::Accelerator accelerator; | 
					
						
							|  |  |  |     if (model->GetAcceleratorAt(index, &accelerator)) { | 
					
						
							|  |  |  |       const ui::PlatformAcceleratorCocoa* platformAccelerator = | 
					
						
							|  |  |  |           static_cast<const ui::PlatformAcceleratorCocoa*>( | 
					
						
							|  |  |  |               accelerator.platform_accelerator()); | 
					
						
							|  |  |  |       if (platformAccelerator) { | 
					
						
							|  |  |  |         [item setKeyEquivalent:platformAccelerator->characters()]; | 
					
						
							|  |  |  |         [item setKeyEquivalentModifierMask: | 
					
						
							|  |  |  |             platformAccelerator->modifier_mask()]; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-09-01 19:48:11 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Set menu item's role. | 
					
						
							|  |  |  |     base::string16 role = model->GetRoleAt(index); | 
					
						
							|  |  |  |     if (role.empty()) { | 
					
						
							|  |  |  |       [item setTarget:self]; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       for (const Role& pair : kRolesMap) { | 
					
						
							|  |  |  |         if (role == base::ASCIIToUTF16(pair.role)) { | 
					
						
							|  |  |  |           [item setAction:pair.selector]; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  |   } | 
					
						
							|  |  |  |   [menu insertItem:item atIndex:index]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Called before the menu is to be displayed to update the state (enabled, | 
					
						
							|  |  |  | // radio, etc) of each item in the menu. Also will update the title if | 
					
						
							|  |  |  | // the item is marked as "dynamic". | 
					
						
							|  |  |  | - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { | 
					
						
							|  |  |  |   SEL action = [item action]; | 
					
						
							|  |  |  |   if (action != @selector(itemSelected:)) | 
					
						
							|  |  |  |     return NO; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   NSInteger modelIndex = [item tag]; | 
					
						
							|  |  |  |   ui::MenuModel* model = | 
					
						
							|  |  |  |       static_cast<ui::MenuModel*>( | 
					
						
							|  |  |  |           [[(id)item representedObject] pointerValue]); | 
					
						
							|  |  |  |   DCHECK(model); | 
					
						
							|  |  |  |   if (model) { | 
					
						
							|  |  |  |     BOOL checked = model->IsItemCheckedAt(modelIndex); | 
					
						
							|  |  |  |     DCHECK([(id)item isKindOfClass:[NSMenuItem class]]); | 
					
						
							|  |  |  |     [(id)item setState:(checked ? NSOnState : NSOffState)]; | 
					
						
							|  |  |  |     [(id)item setHidden:(!model->IsVisibleAt(modelIndex))]; | 
					
						
							|  |  |  |     if (model->IsItemDynamicAt(modelIndex)) { | 
					
						
							|  |  |  |       // Update the label and the icon. | 
					
						
							|  |  |  |       NSString* label = | 
					
						
							|  |  |  |           l10n_util::FixUpWindowsStyleLabel(model->GetLabelAt(modelIndex)); | 
					
						
							|  |  |  |       [(id)item setTitle:label]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       gfx::Image icon; | 
					
						
							|  |  |  |       model->GetIconAt(modelIndex, &icon); | 
					
						
							|  |  |  |       [(id)item setImage:icon.IsEmpty() ? nil : icon.ToNSImage()]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return model->IsEnabledAt(modelIndex); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return NO; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Called when the user chooses a particular menu item. |sender| is the menu | 
					
						
							|  |  |  | // item chosen. | 
					
						
							|  |  |  | - (void)itemSelected:(id)sender { | 
					
						
							|  |  |  |   NSInteger modelIndex = [sender tag]; | 
					
						
							|  |  |  |   ui::MenuModel* model = | 
					
						
							|  |  |  |       static_cast<ui::MenuModel*>( | 
					
						
							|  |  |  |           [[sender representedObject] pointerValue]); | 
					
						
							|  |  |  |   DCHECK(model); | 
					
						
							|  |  |  |   if (model) { | 
					
						
							| 
									
										
										
										
											2015-07-29 12:01:27 +08:00
										 |  |  |     NSEvent* event = [NSApp currentEvent]; | 
					
						
							|  |  |  |     model->ActivatedAt(modelIndex, | 
					
						
							|  |  |  |                        ui::EventFlagsFromModifiers([event modifierFlags])); | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (NSMenu*)menu { | 
					
						
							| 
									
										
										
										
											2014-11-16 21:06:16 +08:00
										 |  |  |   if (menu_) | 
					
						
							|  |  |  |     return menu_.get(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   menu_.reset([[NSMenu alloc] initWithTitle:@""]); | 
					
						
							|  |  |  |   [menu_ setDelegate:self]; | 
					
						
							|  |  |  |   if (model_) | 
					
						
							|  |  |  |     [self populateWithModel:model_]; | 
					
						
							| 
									
										
										
										
											2013-08-14 22:24:21 +08:00
										 |  |  |   return menu_.get(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (BOOL)isMenuOpen { | 
					
						
							|  |  |  |   return isMenuOpen_; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (void)menuWillOpen:(NSMenu*)menu { | 
					
						
							|  |  |  |   isMenuOpen_ = YES; | 
					
						
							|  |  |  |   model_->MenuWillShow(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - (void)menuDidClose:(NSMenu*)menu { | 
					
						
							|  |  |  |   if (isMenuOpen_) { | 
					
						
							|  |  |  |     model_->MenuClosed(); | 
					
						
							|  |  |  |     isMenuOpen_ = NO; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @end |