Best way to toggle "Show/Hide" menu item in Cocoa Desktop - objective-c

My app has an 'inspector' panel defined by a .xib file and a custom window controller class: AdjustmentsWindow.xib and AdjustmentsWindowController.m.
I want to have a Window -> Show Adjustments menu item in the application's main menu bar, that when selected will show the adjustments window. I dropped and NSObject instance into the xib containing the main menu, and changed its class to "AdjustmentsWindowController". I also hooked the menu item's action to the controller's -showWindow: method. So far so good: The window controller is instanced on app launch, and when you select the menu item it shows its window.
But I want the same menu item to double as 'Hide Adjustments' when the window is already visible (effectively toggling visibility). So here is what I did:
AdjustmentsWindowController.m:
- (void) windowDidLoad
{
[super windowDidLoad];
[[self window] setDelegate:self];
}
- (void) showWindow:(id)sender
{
// (Sent by 'original' menu item or 'restored' menu item)
[super showWindow:sender];
// Modify menu item:
NSMenuItem* item = (NSMenuItem*) sender;
[item setTitle:#"Hide Adjustments"];
[item setAction:#selector(hideWindow:)];
}
- (void) hideWindow:(id) sender
{
// (Sent by 'modified' menu item)
NSMenuItem* item = (NSMenuItem*) sender;
// Modify back to original state:
[item setTitle:#"Show Adjustments"];
[item setAction:#selector(showWindow:)];
[self close];
}
- (void) windowWillClose:(NSNotification *)notification
{
// (Sent when user manually closes window)
NSMenu* menu = [[NSApplication sharedApplication] mainMenu];
// Find menu item and restore to its original state
NSMenuItem* windowItem = [menu itemWithTitle:#"Window"];
if ([windowItem hasSubmenu]) {
NSMenu* submenu = [windowItem submenu];
NSMenuItem* item = [submenu itemWithTitle:#"Hide Adjustments"];
[item setTitle:#"Show Adjustments"];
[item setAction:#selector(showWindow:)];
}
}
My question is, is this the right/smartest/most elegant way to achieve this? I mean, this is pretty standard behaviour in cocoa apps (cf. Numbers' "Inspector"), how does everyone else do it?
One way to improve it would be to avoid the code duplication of restoring the menu item to its original title/action. Also, ideally I would replace the title strings with calls to NSLocalizedString(). But perhaps there's a more elegant, standard approach that I don't know...

Application Menu and Pop-up List Programming Topics said
validateMenuItem: is also a good place to toggle titles or set state on menu items to make sure they're always correct.

Related

NSEvent from a NSButton

I have a NSTableView and each row contains a button. I also have a menu associated with the table.
The issue is : I want to show the menu on click of button. If possible do not show on right click.
The action method is :
- (IBAction)showMenu:(NSButton *)button {
NSLog(#"show menu");
NSMenu *menu = [self.tableView menu];
NSEvent *event = [[NSEvent alloc] init];
[NSMenu popUpContextMenu:menu
withEvent:event
forView:button];
}
Here what to do with event? If I use nil then the menu is showed at bottom left corner, not next to the button.
Any guidance would be appreciated.
You could try using -[NSMenu popUpMenuPositioningItem:atLocation:inView:]. This method doesn't take an NSEvent argument. Instead, you give it a view and a location in the view's coordinate system, and the menu positions itself (or one of its items) over that location.
But I would suggest you don't use an NSButton at all. If you use an NSPopUpButton instead, it will take care of showing the menu at the correct location on a left-click.

NSMenu programmatically select item

I'm writing a plugin for application - custom keyboard shortcut. I can traverse through its views.
I need to open popup menu, select item in it, then open its submenu and select some item in submenu.
For now I'm only able to open top popup menu by sending performClick: to related NSPopUpButton element.
How can I programmatically select item in menu and open its submenu?
I've tried:
call selectItem: on the NSPopUpButton (and related NSMenu). No luck and I see a notion in the doc: "Note that while a menu is tracking user input, programmatic changes to the menu such as adding, removing, or changing items on the menu is not reflected"
send keyboard events (using this answer). No luck - may be because I'm holding some keys at the moment of sending those events
to find any info on how to do it via Accessibility API, but I just can't find anything on how to use it on current Application (or even on any other application, but with Objective-C)
Use the NSMenu method - (void)performActionForItemAtIndex:(NSInteger)index
NSUInteger idx = [[[menuItem menu] itemArray] indexOfObject:menuItem];
[[menuItem menu] performActionForItemAtIndex:idx];
For opening submenu: performActionForItemAtIndex:
For selecting and opening menu:
selectItemAtIndex: + performClick:
Do not call performActionForItemAtIndex: on item that does not have submenu cause you might trigger action that might have been set by someone else.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSMenu *menu = self.popup.menu;
NSMenuItem *item = [menu itemAtIndex:2];
[item setAction:#selector(terminate:)];
[item setTarget:NSApp];
}
- (IBAction)action:(id)sender {
//[self.popup.menu performActionForItemAtIndex:2]; -> if not submenu this will quit your app
[self.popup selectItemAtIndex:2]; //-> first select menu item
[self.popup performClick:nil]; //-> open menu - will not quit app
}
In addition to #LCC 's answer, you can also call indexOfItem on NSMenu
NSInteger index = [item.menu indexOfItem:item];
[item.menu performActionForItemAtIndex:index];

Using the segue from master detail application with custom button.

So, I have a master detail application. So far an object is created at launch and added to the master list, the detail view works fine as well.
What I would like to do, is to let the user add object to the master list by pressing a button. I don´t need a new view for that though, I want to use the standard Detail View for this part.
When the user taps the "Add" button, a new object should be created and then go into the detailed view of that object. I thought, "hey, why not use the standard segue as well?"
So I added a Bar Button Item to the MasterView.
This my code from MasterViewController.m:
-(IBAction)addNewItem:(UIStoryboardSegue *)segue{
if([[segue identifier] isEqualToString:#"showDetail"]){
CoolItem *newItem;
NSDate *today = [NSDate date];
newItem = [[CoolItem alloc]initWithDate:today];
[self.dataController addCoolItemWithItem:newItem];
DetailViewController *detailViewController = [segue destinationViewController];
detailViewController.coolItem = newItem;
}
}
"showDetail" is the identifier of the standard segue that comes with the master detail template.
The result: Nothing! The method is not called at button tap.
I´ve made sure my button is connected to this method, (it does however shows up as a unwind segue in the document outline).
Any ideas on what goes wrong?
I'm not sure what went wrong but I was able to achieve the desired functionality using the following code:
-(void)viewDidload {
...
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:#selector(Add:)];
}
-(IBAction)Add:(id)sender
{
// Create the new item and set it as active
...
[self performSegueWithIdentifier: #"SegueName" sender:self];
}
Hope this helps
Yaron

How to reset seachDisplayController View to original state programatically

I have the UISearchDisplayController page view as follows (original state)
After clicking on the searchbar and typing some search term, I get a list of results in the table
After clicking on an entry in the table, a new viewController is displayed on top of the stack
My intention is that on the click of the cancel button (on top left), the root view will display the original state of the searchDisplay controller
This is what I have tried
In my cancel method (triggered when I click on the top left hand cancel button)
- (void)cancel
{
[self dismissViewControllerAnimated:YES completion:NULL];
[self searchBarCancelButtonClicked:self.searchDisplayController.searchBar];
}
And in the searchbar delegate method I did this
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
self.searchBar.showsCancelButton = NO;
[self.searchBar resignFirstResponder];
self.searchBar.text = #"";
searchDisplayController.searchResultsTableView.hidden = YES;
[self searchDisplayControllerWillEndSearch:self.searchDisplayController];
}
All I manage to achieve after clicking on the cancel button is this
The original tableview seems to be missing (appears only if I click on the page)
How can I modify my methods to revert the search to its' original state?
Try [searchDisplayController setActive:NO animated:NO]
This should hide the search table.

How do you toggle the status item in the menubar on and off using a checkbox?

I have already created a status item for the menu bar but I would like to add a checkbox to make it able to be toggled on and off.
So when the check box is checked the status item is displayed and when the checkbox is not checked it is not displayed.
What code would i need to do this?
First in your controller class create an instance variable to hold the reference to this item:
NSStatusItem *item;
Then create a method to create this status item, when the box is checked:
- (BOOL)createStatusItem
{
NSStatusBar *bar = [NSStatusBar systemStatusBar];
//Replace NSVariableStatusItemLength with NSSquareStatusItemLength if you
//want the item to be square
item = [bar statusItemWithLength:NSVariableStatusItemLength];
if(!item)
return NO;
//As noted in the docs, the item must be retained as the receiver does not
//retain the item, so otherwise will be deallocated
[item retain];
//Set the properties of the item
[item setTitle:#"MenuItem"];
[item setHighlightMode:YES];
//If you want a menu to be shown when the user clicks on the item
[item setMenu:menu]; //Assuming 'menu' is a pointer to an NSMenu instance
return YES;
}
Then create a method to remove the item when it is unchecked:
- (void)removeStatusItem
{
NSStatusBar *bar = [NSStatusBar systemStatusBar];
[bar removeStatusItem:item];
[item release];
}
Now tie it all together by creating an action that is called when the checkbox is toggled:
- (IBAction)toggleStatusItem:(id)sender
{
BOOL checked = [sender state];
if(checked) {
BOOL createItem = [self createStatusItem];
if(!createItem) {
//Throw an error
[sender setState:NO];
}
}
else
[self removeStatusItem];
}
Then create the checkbox in IB and set the action to your toggleStatusItem: method; make sure that the checkbox is left unchecked.
Edit (In response to errors)
As stated above, you need to declare the NSStatusItem in the interface of the class that you have placed the createStatusItem and removeStatusItem methods; the reason that this becomes an instance variable rather than one local to the createStatusItem method is that there is no way to retrieve a pointer to an item that has already been added to the status bar in the Apple menu, and in order to remove the item once the checkbox is unchecked, you must store a pointer to this item. This will also solve your third error.
In response to your second error, I was merely demonstrating that if you want to add a menu to your status item when it is clicked, you must add the code for that yourself, retrieving a pointer to an NSMenu; I was showing how you could then add this menu item to the status bar item, if your pointer was called menu, hence my comment next to the line of code.
Get an outlet to your button you want to toggle and then create an action method that your checkbox points to that toggles the hidden property of the original button based on the check box status.