Detect click on context menu osx - objective-c

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.

Related

How to create a custom NSView for NSSavePanel in Cocoa MacOS objective C?

I need to add a save extension selector with a text label next to it to my NSSavePanel. In the screenshot attached I try to demonstrate that I succeeded in adding an NSComboBox to my panel with the function setAccessoryView. However I have no idea how to create a custom NSView, which includes both an NSComboBox and an NSTextView or equivalent. I found no tutorials on the internet (or if I found one it was extremely outdated) showing how to create custom NSViews in objective-C in Cocoa on MacOS.
How can I create a custom NSView containing a combobox and a text label? Or how can I add two "stock" NSViews to the same NSSavePanel? Please be as detailed in your answer as possible, as I have very limited objective-c experience.
You asked how to create an NSView in Objective-C with an NSTextField and an NSComboBox as subviews.
Basically, you could define them in Interface Builder and programmatically set the resulting view in Objective-C as the accessoryView of the NSSavePanel. Alternatively, the custom NSView could be created entirely in Objective-C, which is probably the easier option here.
After instantiating an NSView, you can use addSubview: to add an NSTextField and an NSComboBox accordingly. Then you can use NSLayoutConstraints to set up Auto Layout, which takes care of sizing the accessoryView and arranging the subviews properly based on the width of the dialog.
If you create the views programmatically and use Auto Layout, you must explicitly set translatesAutoresizingMaskIntoConstraints to NO.
Should you want to set the allowedContentTypes, a textual mapping of the displayed extension to UTType via a NSDictionary might be useful.
If you set the delegate of the NSComboBox to self, then you will be informed about changes of the user selection in the NSComboBox via comboBoxSelectionDidChange:.
If the things discussed are implemented appropriately in code, it might look something like this for a self-contained example:
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import "ViewController.h"
#interface ViewController () <NSComboBoxDelegate>
#property (nonatomic, strong) NSSavePanel *savePanel;
#property (nonatomic, strong) NSDictionary<NSString *, UTType*> *typeMapping;
#end
#implementation ViewController
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
_typeMapping = #{
#"jpeg": UTTypeJPEG,
#"png": UTTypePNG,
#"tiff": UTTypeTIFF
};
}
return self;
}
- (NSView *)accessoryView {
NSTextField *label = [NSTextField labelWithString:#"Filetypes:"];
label.textColor = NSColor.lightGrayColor;
label.font = [NSFont systemFontOfSize:NSFont.smallSystemFontSize];
label.alignment = NSTextAlignmentRight;
NSComboBox *comboBox = [NSComboBox new];
comboBox.editable = NO;
for (NSString *extension in self.typeMapping.allKeys) {
[comboBox addItemWithObjectValue:extension];
}
[comboBox setDelegate:self];
NSView *view = [NSView new];
[view addSubview:label];
[view addSubview:comboBox];
comboBox.translatesAutoresizingMaskIntoConstraints = NO;
label.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:#[
[label.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-12],
[label.widthAnchor constraintEqualToConstant:64.0],
[label.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:0.0],
[comboBox.topAnchor constraintEqualToAnchor:view.topAnchor constant:8.0],
[comboBox.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:8.0],
[comboBox.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-8.0],
[comboBox.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:-20.0],
]];
return view;
}
- (void)comboBoxSelectionDidChange:(NSNotification *)notification {
NSComboBox *comboBox = notification.object;
NSString *selectedItem = comboBox.objectValueOfSelectedItem;
NSLog(#"### set allowedContentTypes to %# (%#)", selectedItem, self.typeMapping[selectedItem]);
[self.savePanel setAllowedContentTypes:#[ self.typeMapping[selectedItem] ]];
}
- (IBAction)onSave:(id)sender {
NSWindow *window = NSApplication.sharedApplication.windows.firstObject;
self.savePanel = [NSSavePanel new];
self.savePanel.accessoryView = [self accessoryView];
[self.savePanel beginSheetModalForWindow:window completionHandler:^(NSModalResponse result) {
if (result != NSModalResponseOK) {
return;
}
NSURL *fileURL = self.savePanel.URL;
NSLog(#"### selectedFile: %#", fileURL);
}];
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
}
#end
Finally, a screenshot of the above demo code in action looks like this:
Press Cmd-N to add a new file to your project. Choose a View file to add a xib file that has a custom view.
Open the xib file and add the controls to the custom view. Press the Add button in the project window toolbar to access the user interface elements.
Use the NSNib class to load the xib file and get the custom view.

Binding a window created with the Cocoa Interface builder to a programmatically created one

I have an Objective C application which starts with loading a window created with Interface Builder:
//in main()
[NSApplication sharedApplication];
[NSBundle loadNibNamed:#"MainMenu" owner:NSApp];
[NSApp run];
In MainMenu.xib I have a window with a button. I want to create programmatically the second window when that button is pressed.
//in MainMenu.xib Controller.h
#class SecondWindowController;
#interface Controller: NSWindowController {
#private
SecondWindowController *sw;
}
- (IBAction)onButtonPress:(id)object;
#end
//in MainMenu.xib Controller.m
#import "SecondWindowController.h"
#implementation Controller
- (IBAction)onButtonPress:(id)object {
sw = [[SecondWindowController alloc] initWithWindowNibName:#"SecondWindow"];
[sw showWindow:self];
[[self window] orderOut:self];
}
#end
Where SecondWindowController inherits from NSWindowController. In SecondWindowController.h I have:
- (id)initWithWindow:(NSWindow*)window {
self = [super initWithWindow:window];
if (self) {
NSRect window_rect = { {custom_height1, custom_width1},
{custom_height2, custom_width2} };
NSWindow* secondWindow = [[NSWindow alloc]
initWithContentRect:window_rect
styleMask: ...
backing: NSBackingStoreBuffered
defer:NO];
}
return self;
}
And in the SecondWindow.xib I have nothing. When the button of the first window is pressed the first window disappears and the application closes. The reason I don't want to use the Interface builder for the second window is that I want to programmatically initialize it. Is this possible and if so what is the right way to accomplish this?
OK, I was initially confused with your use of initWithWindowNibName:#"SecondWindow" which will attempt to load the window from a NIB file, which you later mention you don't want to do.
Please use this to create your window:
- (IBAction)onButtonPress:(id)object {
if (!sw)
sw = [[SecondWindowController alloc] init];
[sw showWindow:self];
[[self window] orderOut:self];
}
Which will avoid creating multiple copies of the window controller, which you don't want (if you do then you'll need to store them in an array). Note the name sw is incorrect by convention; use either _sw or create setter/getter methods and use self.sw.
Initialize SecondWindowController like this:
- (id)init {
NSRect window_rect = NSMakeRect(custom_x, custom_y,
custom_width, custom_height);
NSWindow* secondWindow = [[NSWindow alloc]
initWithContentRect:window_rect
styleMask: ...
backing: NSBackingStoreBuffered
defer:NO];
self = [super initWithWindow:secondWindow];
if (self) {
// other stuff
}
return self;
}
Note: your variable names for origin/size of the new window were wrong; please review them.

How to remove NSUserNotification

I am using NSUserNotificationCenter to display scheduled Notifications. I have notifications of my app in the right side notification panel so whenever I click on the notification it'll launch the app but it won't remove notification from the panel.
When app is not running and I clicked on the notification, applicationDidFinishLaunching is called and it won't remove notification because didActivateNotification delegate is not called.
When application is already running and I clicked on the notification, appShouldHandleReopen is called. And won't call the delegate.
-(void)userNotificationCenter:(id)center didActivateNotification:(id)notification
I checked behavior in all the standard app they will remove notification when user click on the notifications.
I have implemented it as follows:
/* will create notification and set the delegate*/
#interface NotificationViewController : NSViewController
{
id delegate;
}
-(void)showNotification;
#end
#implementation NotificationViewController
- (void)createNotification:(NSString*)str
{
Class UserNotificationClass=NSClassFromString(#"NSUserNotification");
id notification = [[UserNotificationClass alloc] init];
//Set the title of the notification
[notification setTitle:#"My Notification"];
[notification setSubtitle:#"Test"];
[notification setHasActionButton:YES];
[notification setActionButtonTitle:#"OK"];
[notification setOtherButtonTitle:#"CANCEL"];
//Set the text of the notification
[notification setInformativeText:str];
[notification setDeliveryDate:[NSDate dateWithTimeInterval:0.1 sinceDate:[NSDate date]]];
[notification setSoundName:#"NSUserNotificationDefaultSoundName"];
[notification setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:str,#"string",nil]];
Class UserNotificationCenterClass=NSClassFromString(#"NSUserNotificationCenter");
id center = [UserNotificationCenterClass defaultUserNotificationCenter];
[center setDelegate:self.delegate];
//Scheldule our NSUserNotification
[center scheduleNotification:notification];
[notification release];
}
#end
/* Class which implements NSUserNotificationDelegate*/
#interface NotificationHandler : NSObject <NSUserNotificationCenterDelegate>
{
}
- (void)createNotification:(NSString*)str;
#end
#implementation NotificationHandler
- (void)createNotification:(NSString*)str
{
NotificationHandler *notificationHandler = [[NotificationHandler alloc] initWithNibName:#"NotificationHandler" bundle:[NSBundle mainBundle]];
notificationHandler.delegate = self;
[notificationHandler showNotification];
}
- (void)userNotificationCenter:(id)center didActivateNotification:(id)notification
{
[center removeDeliveredNotification:notification];
}
- (BOOL)userNotificationCenter:(id)center shouldPresentNotification:(id)notification
{
return YES;
}
#end
However this is not working in my case. Please let me know some pointer on it...
Other thing I observed:
When I implement NSUserNotificationDelegate into AppDelegate it'll work for 2nd case i.e. (When application is already running and I clicked on the notification) - in this case notification is removed.
And for deleting notification for 1st case i.e. (When app is not running and I clicked on the notification, appDidFinishLaunching is called) I wrote following code in appDidFinishLaunching.
NSUserNotification *userNoti = [[notification userInfo] valueForKey:#"NSApplicationLaunchUserNotificationKey"];
[[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification:userNoti];
Can anybody let me know whats the reason, why this works in the AppDelegate but not in my above implementation or Am I doing something wrong in first one?
Is my AppDelegate implementation fine to handle my both scenarios?
Thanks in Advance.

Objective-C: How to switch the NSView by button click

I have different xib files with NSViewController attached to them. (Screenshot below)
One of xib file called StartMenuViewController which has a button. I want to click that button and change the view to DetectingUSBViewController.(Screenshot below)
The IBAction of that button is in StartMenuViewController.m file.
And I use AppController.m to control my main xib view.(NSWindow + NSView) (Screenshot below)
When the application runs, I try to initialize the StartMenuViewController fist by doing the following thing in my AppController.m file.
-(void)awakeFromNib{
[self initialize];
}
-(void) initialize
{
#autoreleasepool {
//mainViewController is a NSViewController and _mainView is a NSView which connect with Custom View in main xib
self.mainViewController = [[[StartMenuViewController alloc]initWithNibName:StartMenuView bundle:nil]autorelease];
[_mainView addSubview:[_mainViewController view]];
}
}
It works fine and it will show the StartMenuViewController.xib on the window at first, but I do not know how to change the view after clicking the button(FIND USB DRIVE). I want the current view changes to DetectingUSBViewController.xib.
Simplest way possible, assuming you have tied your USB button properly in, do the following :
- (IBAction)usbButton:(UIButton *)sender {
DetectingUSBViewController *second = [[DetectingUSBViewController alloc] initWithNibName:#"DetectingUSBView" bundle:nil];
[self presentViewController:second animated:YES completion:nil];
}
load the DetectingUSBViewController in startMenuViewController as DetectingUSBViewController* v1 = [[ViewCont1 alloc] initWithNibName:#"ViewCont1" bundle:nil]; now add or replace the view as [v1 view] in view where you want to add/replace.
You need to hook up your button to send an IBAction
You need a 'View for DetectingUSBViewController.xib'
=> one way (iOS like) is to use a ViewController. Subclass NSViewController and then alloc init a DetectingUSBViewController
Add the view. Don't present the VC (as there is no such thing in OSX)
//button click action
- (IBAction)usbButton:(UIButton *)sender {
//! Retain the VC
Self.detectingUSBViewController = [[DetectingUSBViewController alloc] initWithNibName:#"DetectingUSBView" bundle:nil];
//add the view
[_mainView addSubview:[_detectingUSBViewController view]];
}

going back to AppDelegate to recreate a uitabbarcontroller

I have an app that is based on a tab bar view with a welcome screen (that leads to either signin or sign up process). basically, if you are logged in - you go straight to the tabbar view and if not, you go to the welcome screen, where you can chose to either go to sign in or sign up. assuming that you go to either sign in or sign up, i would like the tab bar view to reappear, however, all the declarations are in the AppDelegate. how can I "go back" and call the tabbatcontroller? is the structure / flow of my classes correct at all?
so to repeat:
user signed in -> first view is tab bar view
user logged out -> welcome screen view --> sign in / up screen view --> tab bar view
what i am looking for is what do i need to write in this action method that is called once the user clicks on "sign in" in the sign in page:
-(IBAction)done:(id)sender {
?????
}
for reference, my appDelegate is:
if(user signed in)
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIViewController *viewController1 = [[FirstTab alloc] initWithNibName:#"FirstTab" bundle:NSBundle.mainBundle];
UIViewController *viewController2 = [[SecondTab alloc] initWithNibName:#"SecondTab" bundle:NSBundle.mainBundle];
UINavigationController *secondNavController = [[UINavigationController alloc]initWithRootViewController:viewController2];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, secondNavController, nil];
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
}
else
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
SigninTabBarTemplateViewController *landingPage = [[SigninTabBarTemplateViewController alloc] initWithNibName:#"SigninTabBarTemplateViewController" bundle:nil];
self.window.rootViewController = (UIViewController *) landingPage;
[self.window makeKeyAndVisible];
}
There are many options you can consider.
This can be easily achieved with the use of delegate. If you want to close the VC that you presented modally, give it a delegate property. The delegate will be sent a message when required, letting it dismiss the VC. A good way to go with delegate is to write a custom procotol.
For example :
// the delegate will conform to this protocol
#protocol SignInVCDelegate
// this method will be called when required
//
-(void)signInCompleted;
#end
Now, make the object you want conforms to that protocol, for example the app delegate.
// .h
#import "SignInVCDelegate.h"
#interface YourAppDelegate : NSObject <..., SignInDelegate> {
...
SignInVC *signIn;
...
}
-(void)signInCompleted;
#end
The implementation looks like this :
// .m
...
-(void)signInCompleted {
...
[signIn.view removeFromSuperview];
}
-(BOOL)applicationDidFinishLaunching {
if(!logged) {
...
[signIn setDelegate:self];
[self.tabBarController presentModalViewController:signIn
animated:YES];
}
}
Now give signInVC a delegate property, which will be set before being presented modally, and send the delegate a message when the sign in process is completed.
// in .h
#property(retain) id <SignInDelegate>delegate;
// in .m
#synthesize delegate;
-(IBAction)validateSignIn {
...
[delegate signInCompleted];
}
You can write any method you want, this example is simplist, and it is useful to give the delegate some informations. In this case for example you could pass a user name, or user id, or what ever you want.
Another simple option is using notifications. This option lets any object informed when something happen, as long as it register for it. Given the same objects as the previous example, the app delegate will register for the notification, while the sign in view controller will post it.
// in app delegate .m
-(BOOL)applicationDidFinishLaunching {
...
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(signInCompleted)
name:#"UserSignedInNotification"
object:nil];
}
// in SignInVC.m
-(IBAction)validateSignIn {
...
[[NSNotificationCenter defaultCenter]
postNotificationName:#"UserSignedInNotification"
object:self];
}
More informations about delegates and notifications in Communicating with Objects.
You could try doing something like this in the method where you know the user has successfully logged in. (Assuming SignedInTabbarViewController is your TabBarController)
SignedInTabbarViewController *signedInTabbarViewController = [[SignedInTabbarViewController alloc] init];
id mainDelegate = [[UIApplication sharedApplication] delegate];
[self.navigationController.view removeFromSuperview];
if( [mainDelegate respondsToSelector:#selector(setViewController:)]) {
[mainDelegate setViewController:signedInTabbarViewController];
}
UIWindow *mainWindow = [mainDelegate window];
[mainWindow addSubview: signedInTabbarViewController.view];
[signedInTabbarViewController release];