Changing NSMenuItem depending on user selection - cocoa

I am working on a Cocoa document based application. I have a menu item in the file menu named "Export Things…". This menu item is connected to the first responder, and calls a selector in MyDocument. So far so good.
I would like to change the title of this menu item depending on user selection. If the user has nothing selected, it should read "Export All Things…", when the user has some things selected it should read "Export Selected Things…". When no document is open, it should just read "Export Things…".
Where and when should I change this menu item? I figure I can just change the menu item using setTitle:, but how do I get a reference to the NSMenuItem?

You can implement -validateMenuItem: in the responder class that handles the menu item's action. It should return a BOOL (which indicates whether the item is enabled), but you also get a reference to the NSMenuItem as a parameter that you can use to change the title.
To decide which menu item you're dealing with, you should inspect its action, e.g.
- (BOOL)validateMenuItem:(NSMenuItem *)item {
if ([item action] == #selector(export:)) {
if (hasSelection) {
[item setTitle:NSLocalizedString(#"Export Selected Things",nil)];
} else {
[item setTitle:NSLocalizedString(#"Export All Things",nil)];
}
}
return YES;
}

Related

Select Item in NSCollectionView on mouseDown, not mouseUp?

I'd like to make it so my NSCollectionView's selection behavior matches the icon-view in Finder. Finder will select and highlight elements when the mouse button is clicked down, but NSCollectionView's built in behavior appears to use mouse up to trigger a selection.
Is there a way to make NSCollectionView act like Finder in this regard?
Per pfandtrade's comment above, it looks like the highlight state of an NSCollectionViewItem will be changed before that item is selected.
mouseDown = NSCollectionViewItem's highlightState is set to forSelection
mouseUp = NSCOllectionViewItem's highlightState is set to none, but isSelected property is then set to true.
I added the following to my NSCollectionViewItem subclass:
override var highlightState: NSCollectionViewItemHighlightState{
didSet{
if self.highlightState == .forSelection{
self.showSelectedHighlight() //my stylization function
}
}
}

Drag selecting in NSCollectionView from inside of items

I have a series of items that I am showing them in a NSCollectionView. The selection and multiple selection are both enabled.
The user can select items by dragging (i.e. marking items by drag). however this works when the user start dragging from collection view background or the space between items (and not on the items) but I want make it possible when dragging starts on the items too.
I want something like this photo if we consider text and image as a single item.
image source: http://osxdaily.com/2013/09/16/select-multiple-files-mac-os-x/
Thank you in advance.
Implement hitTest(_:) in the class of the item view to make the items "see through" for clicks. Return the collection view instead of the item view when the user clicks in the item view.
override func hitTest(_ point: NSPoint) -> NSView? {
var view = super.hitTest(point)
if view == self {
repeat {
view = view!.superview
} while view != nil && !(view is NSCollectionView)
}
return view;
}

Remove 'Layout Orientation' from NSTextView context menu

I have a NSTextView which has a ruler attached showing line numbers. If the user uses the 'Layout Orientation' -> 'Vertical' context menu, things go wonky. The applications intended purpose does not support a Vertical orientation anyway, so I would like to remove this context menu.
So far I have subclassed an NSTextView and overwrote the defaultMenu action:
+ (NSMenu *) defaultMenu
{
// Get our default menu
NSMenu * contextMenu =
[NSTextView defaultMenu];
for(NSInteger menuItemIndex = contextMenu.itemArray.count - 1;
menuItemIndex != -1;
--menuItemIndex)
{
NSMenuItem * menuItem =
[contextMenu itemAtIndex: menuItemIndex];
NSLog(#"%ld %#, %#",
menuItemIndex,
NSStringFromSelector(menuItem.action),
menuItem.title);
} // End of menuItem loop
return contextMenu;
} // End of defaultMenu
My original thought was that I could remove the menu item with a specific selector, but unfortunately the 'Layout Orientation' is a submenu, so it has the submenuAction: selector.
I could still remove the menu by comparing the title, but that seems like a poor way to do this and would probably break in a localized environment.
Any suggestions as to PROPERLY go about removing menu items from the NSTextView context menu? (Removing by index also seems hacky, as that could possible break on different versions of the OS).
Each submenu menuitem has a menu. Scan the submenus for action changeLayoutOrientation:.
You can disable the layout orientation menu items by implementing validateUserInterfaceItem:.
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem {
if ([anItem action] == #selector(changeLayoutOrientation:))
return NO;
return [super validateUserInterfaceItem:anItem];
}

NSView custom context menu and keys

i have an NSCollectionView in my application's main window that manages a collection of custom NSView items. Each custom view has a context menu assigned to it. I want to add shortcut keys to some of the items, for example to associate a "delete" key with "remove item from collection" action. I've added key equivalents to context menu items through IB but the question is how do i make the collection items respond to the pressed keys?
I know that i can achieve this by adding this menu to the NSApp's main menu and keep track of the selected item. Is there any other way besides that?
You could add something like this to your NSCollectionView subclass:
- (BOOL)performKeyEquivalent:(NSEvent *)theEvent
{
BOOL rv = NO;
id firstResponder = self.window.firstResponder;
if ([firstResponder isKindOfClass:[NSView class]] && [firstResponder isDescendantOf:self]) {
// Note: performKeyEquivalent: messages come DOWN the view hierarchy, not UP the responder chain.
// Perform the key equivalent
}
if (!rv) {
rv = [super performKeyEquivalent:theEvent];
}
return rv;
}

Is there a way to assign multiple key equivalents to a Menu Item in Cocoa (via IB or programmatically)?

Specifically, I want my "New" menu item to respond to both Cmd+N and Cmd+T since it will open a new document in a tab.* How can I do this either in Interface Builder or programmatically?
* I can explain the reasoning further if needed, but I'm hoping to avoid a discussion of the merits and rather focus on how to do it, not why to do it.
Make a second one (easiest way being to duplicate it) and set it as hidden. It won't show up when the user pulls open the menu, but as long as it's enabled, its key equivalents should still be in effect.
A simple way to have two or more Key Equivalents for an action is to duplicate the NSMenuItem and add a special Tag for these "alternatives" menu items.
Then set the AppDelegate the delegate (NSMenuDelegate) of the corresponding enclosing NSMenu (where the inner items need the visibility to be updated).
Hidden menu items (or items with a hidden superitem) do not appear in
a menu and do not participate in command key matching.
When the NSMenu open, hides this alternates NSMenuItem, when it close, display them.
Example in Swift 3:
class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
NSApp.mainMenu?.item(withTitle: "View")?.submenu?.item(withTitle: "Zoom")?.submenu?.delegate = self
}
func toggleVisibility(_ visible: Bool, ofAlternatesKeyEquivalentsItems items: [NSMenuItem]) {
for item in items.filter({ $0.tag == 2 }) {
item.isHidden = !visible
}
}
func menuWillOpen(_ menu: NSMenu) {
if menu.title == "Zoom" {
toggleVisibility(false, ofAlternatesKeyEquivalentsItems: menu.items)
}
}
func menuDidClose(_ menu: NSMenu) {
if menu.title == "Zoom" {
toggleVisibility(true, ofAlternatesKeyEquivalentsItems: menu.items)
}
}
}

Resources