Why is this NSMenuItem getting disabled when the window loses focus? - objective-c

I am programmatically creating an NSMenu with a NSMenuItem. When the window of the application is active, the NSMenuItem is enabled:
However, as soon as the window loses focus the menu item becomes disabled:
Here's how I am creating the NSMenu:
- (void)_quit
{
[[NSApplication sharedApplication] terminate:nil];
}
- (NSMenu *)_setupMenu
{
NSMenu *statusMenu = [[NSMenu alloc] initWithTitle:#"Demo"];
NSMenuItem *quit = [[NSMenuItem alloc] initWithTitle:#"Quit" action:#selector(_quit) keyEquivalent:#""];
[statusMenu addItem:quit];
return statusMenu;
}
What is causing this issue? And how do I go about making it enabled regardless of whether the application is in focus or not?

Because menu items are enabled based on the responder chain.
In your case, you can use the terminate: selector instead of your own.
As this is declared in the NSApplication class, which is also part of the responder chain, the item will then be always active.
NSMenuItem *quit = [[NSMenuItem alloc] initWithTitle:#"Quit" action:#selector(terminate:) keyEquivalent:#""];
More on this here: Cocoa Event Handling Guide

Related

NSMenu with views in a modal NSWindow

I have an issue with selectors not being performed for custom views inside an NSMenuItem when they are displayed from a button within a modal NSWindow.
This appears to be a reproducible issue and I've simplified the issue as much as I can.
Modal window is displayed via.
[NSApp runModalForWindow:_modalWindow];
The modal window only has a button, and the button is attached to the following selector.
- (IBAction)modalButtonClicked:(id)sender
{
NSMenu* aMenu = [[NSMenu alloc] initWithTitle:#"Menu"];
NSMenuItem* aItemA = [[NSMenuItem alloc] initWithTitle:#"" action:nil keyEquivalent:#""];
NSMenuItem* aItemB = [[NSMenuItem alloc] initWithTitle:#"" action:nil keyEquivalent:#""];
NSMenuItem* aItemC = [[NSMenuItem alloc] initWithTitle:#"" action:nil keyEquivalent:#""];
[aItemA setView:[NSButton buttonWithTitle:#"Item A" target:self action:#selector(menuButtonClicked:)]];
[aItemB setView:[NSButton buttonWithTitle:#"Item B" target:self action:#selector(menuButtonClicked:)]];
[aItemC setView:[NSButton buttonWithTitle:#"Item C" target:self action:#selector(menuButtonClicked:)]];
[aMenu addItem:aItemA];
[aMenu addItem:aItemB];
[aMenu addItem:aItemC];
[NSMenu popUpContextMenu:aMenu withEvent:[NSApp currentEvent] forView:sender];
}
and the menu click event with a breakpoint:
- (void)menuButtonClicked:(id)sender
{
NSLog(#"%#", sender);
}
Clicking on the button will display a menu with 3 buttons, however nothing happens when you click any of those buttons. #(menuButtonClicked:) is never called. This is only an issue with modal windows but there's no obvious reason why.
The documention https://developer.apple.com/documentation/appkit/nsmenuitem/1514843-target?language=objc states:
To ensure that a menu item’s target can receive commands while a modal
dialog is open, the target object should return YES in worksWhenModal.
And indeed if one adds:
- (BOOL)worksWhenModal {
return YES;
}
then it works and your method menuButtonClicked gives out something like:
2019-10-03 22:47:27.892005+0200 MenuTest[12876:454071] <NSButton: 0x600003505760>

Detect click on context menu osx

How can I make sure a method gets called each time I expand a NSMenu. I have tried connecting the action from storyboard but the action only seem to get fired when i click menu items, not the menu itself.
[item setAction: #selector(play:)]
I would like to run a method when for instance the help menu gets expanded, to update the enabled and disabled content of that menu, since it is supposed to be different for logged in and not logged in users..
Update:
I added the NSMenuDelegate protocol in brackets in my
#interface ClientAppDelegate : NSObject<NSApplicationDelegate,NSMenuDelegate>
Adding menu items work but the menu does not seem to affect the delegate methods.
// Create the application on the UI thread.
- (void)createApplication:(id)object {
NSApplication* application = [NSApplication sharedApplication];
[NSBundle loadNibNamed:#"MainMenu" owner:NSApp];
// Set the delegate for application events.
[application setDelegate:self];
// Add the Tests menu.
NSMenu* menubar = [application mainMenu];
[menubar setDelegate:self];
// TIDAL Create Controlls
NSMenuItem *controlsItem = [[[NSMenuItem alloc] initWithTitle:#"Controls"
action:nil
keyEquivalent:#""] autorelease];
NSMenu *controlsMenu = [[[NSMenu alloc] initWithTitle:#"Controls"] autorelease];
AddMenuItem(controlsMenu, #"Pl", ID_L_PL);
[controlsItem setSubmenu:controlsMenu];
[menubar addItem:controlsItem];
......
-(void) menuWillOpen:(NSMenu *)menu{
wprintf(L"ITEM CLICK CAN I UPDATE MENU VISIBILITY HERE?");
}
-(void) menuNeedsUpdate:(NSMenu *)menu{
wprintf(L"ITEM CLICK CAN I UPDATE MENU VISIBILITY HERE?");
}
Update2:
So I started over, imaginging that the problem had something to do with references and the fact that i was developing in objective-c++. However i can not get it to work in just a minimal Obj-C example either, below is my code. The only callback that seems to work is the one that handles item clicks. May the problem be present due to the fact that I am using :
_menubar = [application mainMenu];
[_menubar setDelegate:self];
To get my menubar and setup my menu delegate?
//
// AppDelegate.m
// menuTest
//
// Created by David Karlsson on 27/02/15.
// Copyright (c) 2015 David Karlsson. All rights reserved.
//
#import "AppDelegate.h"
void AddMenuItem(NSMenu *menu, NSString* label, int idval) {
NSMenuItem* item = [menu addItemWithTitle:label
action:#selector(menuItemSelected:)
keyEquivalent:#""];
[item setTag:idval];
}
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (strong) IBOutlet NSMenu * menubar;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSLog(#"Launched menu test");
// Insert code here to initialize your application
NSApplication* application = [NSApplication sharedApplication];
//[NSBundle loadNibNamed:#"MainMenu" owner:NSApp];
// Set the delegate for application events.
[application setDelegate:self];
// Add the Tests menu.
_menubar = [application mainMenu];
[_menubar setDelegate:self];
NSMenuItem *controlsItem = [[NSMenuItem alloc] initWithTitle:#"Controls"
action:nil
keyEquivalent:#""];
NSMenu *controlsMenu = [[NSMenu alloc] initWithTitle:#"Controls"];
AddMenuItem(controlsMenu, #"1", 123);
AddMenuItem(controlsMenu, #"2", 124);
AddMenuItem(controlsMenu, #"3", 154);
[controlsItem setSubmenu:controlsMenu];
[_menubar addItem:controlsItem];
}
-(void) menuWillOpen:(NSMenu *)menu{
NSLog(#"ITEM CLICK CAN I UPDATE MENU VISIBILITY HERE?");
}
-(void) menuNeedsUpdate:(NSMenu *)menu{
NSLog(#"ITEM CLICK CAN I UPDATE MENU VISIBILITY HERE?");
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
NSLog(#"Teardown menu test");
}
- (IBAction)menuItemSelected:(id)sender {
// Retrieve the active RootWindow.
NSWindow* key_window = [[NSApplication sharedApplication] keyWindow];
if (!key_window){
return;
}
NSLog(#"CLICK");
}
#end
Set the menu's delegate, then use this object to implement menuNeedsUpdate: from the NSMenuDelegate protocol. This method is called just before a menu is shown and is specifically provided to allow you to make changes to the menu in question before it arrives on screen.
You want to implement parts of the NSMenu Delegate protocol and you might need an NSTimer that runs in modes that work during menu tracking in order to do live updates while the menu is visible.

NSMenuItem with custom view dose not respond after leaving application

My application resides in the status bar. When the application first starts my search field responds fine, and allows me to click and enter text and look like this.
But after clicking out of the application into another application like "xcode" and back into my menu the search field and the rest of the custom view appears dim and does not respond unless I click multiple times.
In my applicationDidFinishLaunching I set up the statusItem and the NSMenuItem with the custom view.
//set up hotkeys
[self registerHotKeys];
_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
NSImage *menuIcon = [NSImage imageNamed:#"Menu Icon"];
NSImage *highlightIcon = [NSImage imageNamed:#"Menu Icon"];
[highlightIcon setTemplate:YES];
[[self statusItem] setImage:menuIcon];
[[self statusItem] setAlternateImage:highlightIcon];
[[self statusItem] setMenu:[self menu]];
[[self statusItem] setHighlightMode:YES];
// search menu item
NSMenuItem *searchMenuItem = [[NSMenuItem alloc] initWithTitle:#"search" action:nil keyEquivalent:#""];
self.searchMenuItemView = [[SearchMenuItemView alloc] initWithNibName:#"SearchMenuItemView" bundle:nil];
self.searchMenuItemView.delegate = self;
[searchMenuItem setView:self.searchMenuItemView.view];
[searchMenuItem setTarget:self];
[searchMenuItem setEnabled:YES];
[_menu insertItem:searchMenuItem atIndex:0];
The view is coming from an UIViewController, but I have tried setting the view from just an NSView with no luck using "this" from someone with a similar issue but this did not solve mine. Auto enable is on.
Is there a way to bring the NSMenuItem back to the state it was in when the application is first lunched?

NSMenuItem's not selectable after opening window

I'm having an issue very similar to this thread.
I am programatically creating a NSMenu and adding my items. One selection of an item it shows a window. This works as intended. However, when I close the window I can no longer select any of the options in the menu.
AppDelegate.m
- (void)createMenu {
NSMenu *statusMenu = [[NSMenu alloc] initWithTitle:#""];
NSMenuItem *historyItem = [[NSMenuItem alloc] initWithTitle:#"History" action:#selector(onHistory:) keyEquivalent:#""];
[statusMenu addItem:historyItem];
NSImage *statusImage = [NSImage imageNamed:#"icon.png"];
[_item setImage:statusImage];
[_item setMenu:statusMenu];
}
- (void)onHistory:(id)sender {
OBHistoryWindowController *historyWindowController = [[OBHistoryWindowController alloc] initWithWindowNibName:#"OBHistoryWindowController"];
historyWindowController.managedContext = self.managedObjectContext;
[historyWindowController showWindow];
}
OBHistoryWindowController.m
- (void)showWindow {
[NSApp runModalForWindow:self.window];
}
I'm guessing I need to somehow on close of the window give focus back to the menu but I can't for the life of me figure out how.
It sounds like you haven't stopped the modal loop. As the docs for runModalForWindow: say, "You can exit the modal loop by calling the stopModal, stopModalWithCode:, or abortModal methods from your modal window code."

Custom view in NSMenuItem disables the NSPopUpButton selection

I want to customize an an NSPopUpButton so I have implemented an CustomMenuItemView which right now only has the following code (for testing purposes):
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor redColor] set];
NSRectFill(dirtyRect);
}
Now, for every NSMenuItem i add to the NSMenu in myPopUpButton.menu I set the view to my custom view:
NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:#"Some title" action:NULL keyEquivalent:#""];
menuItem.view = [[CustomMenuItemView alloc] initWithFrame:NSMakeRect(0, 0, 100, 25)];
When I run my program and open the popup button the menuitem selection seems disabled (i.e. nothing happens when I click on it).
I am guessing that it is not actually disabled; it just doesn't respond to events anymore. Do I need to add some event handling in my custom view? If so, how?
I solved the problem by adding the mouseUp method to my CustomMenuItemView:
- (void)mouseUp:(NSEvent*) event
{
NSMenu *menu = self.enclosingMenuItem.menu;
[menu cancelTracking];
[menu performActionForItemAtIndex:[menu indexOfItem:self.enclosingMenuItem]];
}