menu item is enabled, but still grayed out - objective-c

I have a menu with several items created in interface builder. It looks fine there and 'enabled' is checked. But when I run the application, all menu items are grayed out.
I've checked isEnabled, it returns true.
Also, menu items created programmatically (with initWithTitle and without interface builder) work just fine.
Am I missing something here? I'm really quite new to OS X development.

Remember to set your menu item's target and ensure that said target implements the menu item's action method.
menuItem.target = self;
If the menu item’s target is set, then NSMenu first checks to see if that object implements the item’s action method. If it does not, then the item is disabled. If the target does implement the item’s action method, NSMenu first checks to see if that object implements validateMenuItem: or validateUserInterfaceItem: method. If it does not, then the menu item is enabled. If it does, then the enabled status of the menu item is determined by the return value of the method.
If the menu item’s target is not set and the NSMenu object is not a contextual menu, then NSMenu uses the responder chain to determine the target. If there is no object in the responder chain that implements the item’s action, the item is disabled.
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MenuList/Articles/EnablingMenuItems.html

In case somebody might google this out and benefit, 'Action' method was declared without :(id)sender parameter:
-(IBAction) quit;
Strangely, setAction method in NSMenuItem ate it and didn't complain. Oh well.

Ah, the plague of using NSMenu...
Check out <NSMenuValidation>.
Usually the implementation will be as simple as:
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
return [menuItem isEnabled];
}

Related

NSComboBox close popup pogrammatically

Is there a way to close the NSComboBox popup programmatically?
There seem to be a bug with NSComboBox when the popup is open, and the control get's removed and then released. For some reason, the dealloc of the NSComboBox doesn't clear the popup before destructing.
Edit: Just to clarify - this bug only happens when the popup list is opened. Otherwise the control is released properly and cleanly.
Edit Edit:
This is how to reproduce it.
Create a new Cocoa application.
Turn off automatic reference counting.
In #(applicationDidFinishLaunching) create a new NSComboBox and store it to a member.
Add the combo box to the window's contentView.
Create a new menu item with a keyboard shortcut and bind to a selector.
In the menu item's selector: remove the combo box from the view and release the member. Don't forget to set it back to nil.
Run the application.
Click on the combo box to show the popup up.
Use the shortcut to remove the combo box.
You can perform a check in your code or grey out menu items by using the NSComboBox delegate methods -comboBoxWillPopUp: and -comboBoxWillDismiss: to control a BOOL.
The BOOL property can be used to control enabling of the menu item.
Set the delegate of the combo box.
To the interface of the delegate add
#property BOOL itemEnabled;
and to the implementation add
- (void)comboBoxWillPopUp:(NSNotification *)notification {
self.itemEnabled = NO;
}
- (void)comboBoxWillDismiss:(NSNotification *)notification {
self.itemEnabled = YES; //re-enabled when dismissed
}
Set initial value of itemEnabled to YES.
In the xib bind the Enabled attribute of the menu item to the delegate and the Model Key Path set to self.itemEnabled

How to enable main menu item "copy"?

My main menu item "copy" is not clickable:
But I enable it in Xcode:
I haven't any Outlets of Main menu items in code.
What I can do?
“Enabling Menu Items” in Application Menu and Pop-up List Programming Topics says this:
By default, every time a user event occurs, NSMenu automatically enables and disables each visible menu item. You can also force a menu to update using NSMenu’s update method.
and this:
If the menu item’s target is not set (that is, if it is nil—typically if the menu item is connected to First Responder) and the NSMenu object is not a contextual menu, then NSMenu uses the responder chain (described in “The Responder Chain” in Cocoa Event Handling Guide) to determine the target. If there is no object in the responder chain that implements the item’s action, the item is disabled.
If there is an object in the responder chain that implements the item’s action, NSMenu then checks to see if that object implements the validateMenuItem: or validateUserInterfaceItem: method. If it does not, then the menu item is enabled. If it does, then the enabled status of the menu item is determined by the return value of the method.
By default (when you create a project using the “Cocoa Application” template), the Copy menu item's target is First Responder (nil) and the action is copy:. So you need to implement the copy: method on some item in your responder chain. That is sufficient to enable the menu item. If you want more precise control of when the menu item is enabled, you can also implement validateMenuItem: to check which menu item is being validated and return YES or NO as appropriate.
For example, the application delegate is in the responder chain. So you can add this method to CMAppDelegate:
- (IBAction)copy:(id)sender {
NSLog(#"%# %s", self, __func__);
}
That should be sufficient to enable the Copy menu item. Of course, choosing Edit > Copy will just log a message to the console. It's up to you to actually write the code that copies whatever the user has selected.
If you want more granular control, try giving the app delegate an outlet connected to the Copy menu item:
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#property (strong) IBOutlet NSMenuItem *copyMenuItem;
#end
Hook up the outlet in MainMenu.xib. Then you can implement validateMenuItem: like this:
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
if (menuItem == self.copyMenuItem) {
NSLog(#"%# %s %#", self, __func__, menuItem);
return [self shouldEnableCopyMenuItem];
}
return NO;
}

Disable menu highlight when pressing shortcut key

I would like to disable the Application "menu highlight" that happens when you press a shortcut key assigned to an NSMenuItem that belongs to the specific menu in question.
The issue is that in the application you use the keyboard quite a bit and having the menus becoming highlighted all the time becomes a bit annoying but I still want to have the menus (including the shortcuts) there as it shows the user which actions that can be used.
Declare a custom NSMenuItem subclass and start using that custom class instead of NSMenuItem.
In this class you should override this method:
- (BOOL)isHighlighted
{
return NO;
}
This way you will not have the menu item highlighted.
EDIT
Try this:
[item setOnStateImage: item.offStateImage];
FFR: Look up the following methods in the docs:
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
Will work for both selecting the menu item and the associated command key.
Within your NSDocument provide a body for validateMenuItem
such as,
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
SEL theAction = [menuItem action];
if (theAction == #selector(openPreferencesPanel:)) {
return !_isCurrentlyModal; //A BOOL in MyDocument
}
return [super validateMenuItem:menuItem]; // Keep this for proper cut, paste, etc validation
}
In your case, the above selector might be highlight:. Check the nib/xib and inspect it. It might be attached to the First Responder. Copy the method name.
Also have a gander at for more general items (buttons, etc) and also includes menu items.
- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem

Validating fonts and colors NSToolbarItem items

Using Cocoa with latest SDK on OSX 10.6.6
I have an NSToolbar with custom toolbar items and also the built in fonts and colors NSToolbarItem items (NSToolbarShowFontsItem and NSToolbarShowColorsItem identifiers).
I need to be able to enable/disable those in various situations. Problem is validateToolbarItem: is never called for these items (it is being called for my other toolbar items).
The documentation is not very clear about this:
The toolbar automatically takes care
of darkening an image item when it is
clicked and fading it when it is
disabled. All your code has to do is
validate the item. If an image item
has a valid target/action pair, then
the toolbar will call
NSToolbarItemValidation’s
validateToolbarItem: on target if the
target implements it; otherwise the
item is enabled by default.
I don't explicitly set target/action for these two toolbar items, I want to use their default behavior. Does it mean I can't validate these items? Or is there any other way I can do this?
Thanks.
After some trial and error, I think I was able to figure this out and find a reasonable workaround. I will post a quick answer here for future reference for others facing the same problem.
This is just one more of Cocoa's design flaws. NSToolbar has a hardcoded behavior to set the target/action for NSToolbarShowFontsItem and NSToolbarShowColorsItem to NSApplication so as the documentation hints it will never invoke validateToolbarItem: for these NSToolbarItem items.
If you need those toolbar items validated, the trivial thing to do is not to use the default fonts/colors toolbar items but to roll your own, calling the same NSApplication actions (see below).
If using the default ones, it is possible to redirect the target/action of them to your object and then invoke the original actions
- (void) toolbarWillAddItem:(NSNotification *)notification {
NSToolbarItem *addedItem = [[notification userInfo] objectForKey: #"item"];
if([[addedItem itemIdentifier] isEqual: NSToolbarShowFontsItemIdentifier]) {
[addedItem setTarget:self];
[addedItem setAction:#selector(toolbarOpenFontPanel:)];
} else if ([[addedItem itemIdentifier] isEqual: NSToolbarShowColorsItemIdentifier]) {
[addedItem setTarget:self];
[addedItem setAction:#selector(toolbarOpenColorPanel:)];
}
}
Now validateToolbarItem: will be called:
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem {
//validate item here
}
And here are the actions that will be invoked:
-(IBAction)toolbarOpenFontPanel:(id)sender {
[NSApp orderFrontFontPanel:sender];
}
-(IBAction)toolbarOpenColorPanel:(id)sender {
[NSApp orderFrontColorPanel:sender];
}
I guess the engineers who designed this never thought one would want to validate fonts/colors toolbar items. Go figure.

Getting NSArrayController item for right click in NSCollectionView

I'm trying to create a file explorer using nscollectionview and am currently implementing a right click menu for each item (i.e. copy/delete/rename/etc). I currently have:
An NSCollectionView linked with an NSArrayController which holds a custom object
A subclass of NSBox as the view for each item, this also tracks mouse events and passes them to the controller
The controller has an NSMenu outlet (rcMenu) and also an NSView outlet (itemView) for the NSBox subclass that should be where the menu popup
The code for calling the menu is:
[NSMenu popUpContextMenu:rcMenu withEvent:event forView:itemView];
Once run, this works in that the menu pops up when right clicking the item in the collection view, but on inspecting the event that's passed to the controller, there's not really anything I could use to find out which item was right clicked other than the x,y coordinates (which seem to be for the NSWindow rather than the item or NSCollectionView). What I really want is the object in the NSArrayController that had it's view right clicked.
Is this down to me setting it up incorrectly, is there an easy way to figure it out, or is it just that tough to work it out?
You might try setting the menu of each collection view item's view. Most likely, you'll do this by overriding +defaultMenu in your item view class. Once you do that, comment out the popUpContextMenu:withEvent:forView: message and see whether you can get away without it.
Furthermore, it would then not be too hard to serve up different menus for different kinds of items (e.g., folders vs. packages vs. files, and different types of files at that). You'd probably have to override -menuForEvent: instead of +defaultMenu.
I found an other solution that might help.
For this solution I made a subclass of NSCollectionViewItem and NSView, respectively (and for the ease of explaining) ItemViewController and ItemView.
I'm assuming you work with IB where you have already bound your NSCollectionView to the ContentArray of your NSArrayController (also bind the selectionIndexes).
Next add an ViewController object to the NIB and make sure its custom class is set to the ItemViewController. Now connect it to the itemPrototype outlet of your NSCollectionView.
Next add a Custom View object to the NIB and set its custom class to ItemView. Connect its outlet to the view property of your ItemViewController.
In the interface file of ItemView create a representedObject-like property. With this I mean something like:
#property (nonatomic, assign) id someRepresentedObjectPropertyName
This will be the property which will represent the item in your NSArrayController.
Now go to the implementation file of ItemViewController and override the -setRepresentedObject: method. In here we will first let the ItemViewController handle setting its representedObject, afterwards we assign the same representedObject to the property we made in ItemView. The override would look like:
-(void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
//Do some appropiate checking on the representedObject...
if (self.view != nil) {
[(ItemView *)self.view setSomeRepresentedObjectPropertyName:self.representedObject];
}
}
Now if you go back to the implementation of ItemView you can override the method -rightMouseUp: and build/set-up a NSMenu there and use the -popUpMenuPositioning...: method. The someRepresentedObjectPropertyName property of ItemView should be set to the correct item in your NSArrayController.
EDIT:
Instead of overriding -setRepresentedObject you could also bind the ItemView's someRepresentedObjectPropertyName to representedObject.someRepresentedObjectPropertyName