NSMenu doesn't start tracking - cocoa

I have a little cocoa app which usually operates in the background (as agent). Sometimes I'd like to be able to popup a contextmenu (no window or s.th. visible at this time).
As I'm only targetting Snow Leopard I tried this:
if (windows) {
NSMenu *theMenu = [[[NSMenu alloc] initWithTitle:#"test"] autorelease];
[theMenu setShowsStateColumn:NO];
[theMenu setAutoenablesItems:NO];
for (id item in windows) {
NSString *labelText = #"some text";
NSMenuItem *theMenuItem = [[[NSMenuItem alloc] initWithTitle:labelText
action:#selector(menuItemSelected:)
keyEquivalent:#""] autorelease];
[theMenuItem setTarget:self];
[theMenuItem setRepresentedObject:item];
[theMenuItem setEnabled:YES];
[theMenuItem setImage:icon];
[theMenu addItem:theMenuItem];
}
[theMenu popUpMenuPositioningItem:nil atLocation:[NSEvent mouseLocation] inView:nil];
}
The menu popsup perfectly but if I hover the items with the mouse cursor they don't highlight and I can't click them.
The menuItemSelected: method looks just like this:
-(IBAction)menuItemSelected:(id)sender {
}
Any idea what I'm doing wrong?

I suspect that the windowing system doesn't consider your application to be active, and thus doesn't send mouse events to the menu you've created.
As an experiment, try creating a dummy window before popping up the menu. I'd create an NSPanel, possibly with style NSNonActivatingPanelMask. makeKeyAndOrderFront: your window/panel, then pop up the menu and see what happens.
If this works, I'd stick with the approach and hide the window.

Related

Autohide Toolbar only in full screen mode in Cocoa

My goal is simple and yet I cannot find a solution in spite of lots of searching.
Basically, when my app is in full-screen (kiosk) mode, I want the toolbar only to auto-hide, but I want the menu bar hidden.
Apparently this combination is not valid. I've tried:
- (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions: (NSApplicationPresentationOptions)proposedOptions
{
return (NSApplicationPresentationFullScreen |
NSApplicationPresentationHideDock |
NSApplicationPresentationHideMenuBar |
NSApplicationPresentationAutoHideToolbar);
}
I get the following exception:
"... fullscreen presentation options must include NSApplicationPresentationAutoHideMenuBar if NSApplicationPresentationAutoHideToolbar is included"
Thing is, I don't want the menu bar displayed at all!
So, I'm presuming this is not possible using the standard presentation options. Any ideas how I might approach implementing this behaviour manually?
I'm thinking along the lines of: detect the mouse position and only show/hide the toolbar when the mouse is at/near the top of the screen.
I'm very new to Cocoa so not sure where I would start to achieve this. Any help much appreciated!
Many thanks,
John
I've got It to work, but only by using private APIs.
First I had to find out how to prevent the menubar from appearing. I discovered the functions _HIMenuBarPositionLock and _HIMenuBarPositionUnlock, from Carbon (link the app with Carbon.framework).
Then I had to create a custom subclass of NSToolbar, at awakeFromNib I register notification observers to lock and unlock the menubar when the window enters and exits fullscreen, respectively:
- (void)awakeFromNib
{
[super awakeFromNib];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillEnterFullScreenNotification object:[self _window] queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
// lock menubar position when entering fullscreen so It doesn't appear when the mouse is at the top of the screen
_HIMenuBarPositionLock();
}];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillExitFullScreenNotification object:[self _window] queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
// unlock menubar position when exiting fullscreen
_HIMenuBarPositionUnlock();
}];
[self _setupToolbarHotspotTrackingView];
}
_setupToolbarHotspotTrackingView is a method on SOToolbar which adds a view to the window, this view will be used to track the mouse location and show/hide the toolbar accordingly.
- (void)_setupToolbarHotspotTrackingView
{
NSView *contentView = [self _window].contentView;
self.toolbarHotspotTrackingView = [[SOToolbarTrackingView alloc] initWithFrame:contentView.bounds];
[contentView addSubview:self.toolbarHotspotTrackingView];
self.toolbarHotspotTrackingView.autoresizingMask = NSViewWidthSizable|NSViewHeightSizable;
self.toolbarHotspotTrackingView.toolbar = self;
}
I also had to override _attachesToMenuBar on SOToolbar so the animation works properly.
- (BOOL)_attachesToMenuBar
{
return NO;
}
SOToolbarTrackingView sets up a tracking area for mouse moved events and checks to see if the mouse is at the top of the window. It then calls some methods on the private class NSToolbarFullScreenWindowManager to show and hide the toolbar.
There's too much stuff to explain It all in detail here, I've uploaded my experimental project so you can take a look. Download the sample project here.

Popover NSStatusItem

I'm playing around with an idea and basically I want a NSStatusItem with a NSPopoverController. I read about all the problem people had but I just want to try it. Is there a clean way to do it by now? All the versions I've seen are at least 1 year old and suuuuper hacky.
This was my approach so far but if I click my app in the statusbar nothing happens...
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
//[self.statusItem setView:view];
[self.statusItem setTitle:#"Test"];
[self.statusItem setHighlightMode:YES];
[self.statusItem setAction:#selector(activatePopover:)];
}
-(IBAction)activatePopover:(id)sender
{
BOOL isEnabled = NO;
if (isEnabled) {
[self.popover showRelativeToRect:NSMakeRect(0, 0, 50, 50) ofView:statusItem.view preferredEdge:NSMinYEdge];
} else {
[self.popover close];
}
}
Any ideas how to get this running?
Thanks
This will not work without using a custom view on the status item. If you don't set a custom view, the view property will be empty (it only returns custom views, not whatever view NSStatusItem uses internally when you just use setTitle).
Unfortunately, as per Apple's docs, you'll need to provide your own view and handle clicks yourself if you want to use NSPopover.
I haven't seen a complete example that encompasses correct handling of this (the default implementation of status items does rather a lot which you will have to do all manually), and also fixes popover wonkynesses:
NSPopover, by default, won't become the key window (some controls won't work), unless you overwrite canBecomeKeyWindow of NSPopover's window
Correctly dismissing menus of other status items (you can call popUpStatusItemMenu with an empty menu to correctly focus your status item)
Drawing the highlighted background with drawStatusBarBackgroundInRect
Reacting to both left and right mouse clicks
Using NSRunningApplication.currentApplication.activateWithOptions to make sure all windows of your status item become active (otherwise your popover will, erratically, not be the receiver of keyboard input)
Dismissing the NSPopover with NSEvent.addGlobalMonitorForEventsMatchingMask (the built-in dismissal mechanism popovers come with doesn't work with status items)
Removing the status item on termination with NSStatusBar.systemStatusBar.removeStatusItem
I hope to have a blog post about this out sometime soon (note: I'm using RubyMotion, not Objective-C), that explains all these issues and hopefully provides an easier base to create menulets. I'll update this comment if I write that post.
Code:
-(void)initializeStatusBarItem
{
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
NSImage* image = [NSImage imageNamed:#"image"];
// [image setTemplate:YES];
self.statusItem.button.image = image;
self.statusItem.highlightMode = NO;
self.statusItem.button.action = #selector(statusBarItemDidClick:);
}
- (void)statusBarItemDidClick:(NSStatusBarButton *)sender{
MainViewController *mainView = [[MainViewController alloc] init];
self.popoverView = [[NSPopover alloc] init];
[self.popoverView setContentViewController:mainView];
self.popoverView.contentSize = CGSizeMake(300, 400);
self.popoverView.behavior = NSPopoverBehaviorTransient;
[self.popoverView showRelativeToRect:sender.bounds ofView:sender preferredEdge:NSMaxYEdge];
}

Toggle Keyboard Viewer on Mac OS X programmatically

I created a cocoa app which has a window with a text field to get user input, a small keyboard-icon button to bring up the keyboard viewer. When the user clicks OK or Cancel button to finish, i want to hide the keyboard viewer. What I've done is as follows:
//action for keyboard-icon button
-(IBAction)input:(id)sender
{
[self toggleKeyboard:YES];
}
//action for Cancel button
-(IBAction)cancel:(id)sender
{
[self toggleKeyboard:NO];
[NSApp abortModal];
[[self window] orderOut: self];
}
//action for OK button
-(IBAction)ok:(id)sender
{
[self toggleKeyboard:NO];
[NSApp stopModal];
[[self window] orderOut: self];
}
-(void)toggleKeyboard:(BOOL)show
{
NSDictionary *property = [NSDictionary dictionaryWithObject:(NSString*)kTISTypeKeyboardViewer
forKey:(NSString*)kTISPropertyInputSourceType];
NSArray *sources = (NSArray*)TISCreateInputSourceList((CFDictionaryRef)property, false);
TISInputSourceRef keyboardViewer = (TISInputSourceRef)[sources objectAtIndex:0];
if (show == YES)
{
TISSelectInputSource(keyboardViewer);
}
else
{
TISDeselectInputSource(keyboardViewer);
}
CFRelease((CFTypeRef)sources);
}
I can launch keyboard viewer successfully, but it cannot be hidden by TISDeselectInputSource at all times.
It is possible to programmatically hide the Keyboard Viewer. I made an AppleScript for my media center a little while ago that will toggle it. I'm not going to include the bit that shows it, partly because you've already figured that out, but mostly because the way I did it was to call some executable that I can't remember where I got it from.
Anyway, here's the AppleScript:
tell application "System Events"
if exists (process "Keyboard Viewer") then
click (process "Keyboard Viewer"'s window 1's buttons whose subrole is "AXCloseButton")
end if
end tell
This uses Accessibility, so you need to have "Enable access for assistive devices" turned on in order for it to work.
Note this same thing can be accomplished in a Objective-C / C++ app using the Accessibility API, which you could use to avoid having to call into AppleScript in your app.

NSMenu's mouse over event

I used NSMenu and NSStatusItem to display custom menu on status bar for a long time in this way:
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:18] retain];
[statusItem setImage:[NSImage imageNamed:#"myIcon"]];
[statusItem setMenu:myMenu];
[statusItem setHighlightMode:NO];
...
It works fine by start clicking its image icon.
But I noticed that apple's menu located on status bar can be triggered simple by mouse move over, like the airport menu, power menu, language menu and date/time menu.
They are all auto pop up when you mouse walk over.
How did they get it?
I have checked the "add tracking rect" for NSView with "mouseEntered" event, but things is not as difficult as that I think.
Any advice?
I think the statusItem is not a supported mouse event,but you can set a view to the statusItem and override the mouse even method to support the mouse event, just like this:
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
NSButton *nb = [[NSButton alloc] init];
[nb setImage:image];
[nb setAction:#selector(statusItemClick:)];
[_statusItem setView:nb];
here just an example to set a view to statusitem, if you want to support the mouse event , your view must extend same view and create NSTrackingArea and implement the moveMoved, mouseEntered and mouseExited methods (or whichever ones you want)

NSButton in NSToolbarItem (setView) when clicked in "Text only" forces mode to "Icon and Label"

I am trying to recreate the nice textured buttons like Finder, Safari and Transmission have in their toolbar. First I started by just dragging in a "Texture button" in the IB and such. All works well except for when a user sets the toolbar to "Text only" mode. When he then clicks the button the toolbar will enable "Icon and Label" on it's own. I have remove alles code and delegates from the toolbar to make sure it is not a code issue.
Then, just to make sure, I created a new project (no code at all) and I can reproduce the issue with a clean NSWindow with a NSToolbar with one NSToolbarItem with a NSButton in it.
Adding the NSButtons via code like:
- (NSArray*)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar {
return [NSArray arrayWithObject:#"myToolbarMenu"];
}
- (NSArray*)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar {
return [self toolbarAllowedItemIdentifiers:toolbar];
}
- (NSToolbarItem*)toolbar:(NSToolbar*)toolbar
itemForItemIdentifier:(NSString*)str
willBeInsertedIntoToolbar:(BOOL)flag
{
if ([str isEqualToString:#"myToolbarItem"] == YES) {
NSToolbarItem* item = [[NSToolbarItem alloc] initWithItemIdentifier:str];
[item setView:[[NSButton alloc] init]];
[item setMinSize:NSMakeSize(50,50)];
[item setMaxSize:NSMakeSize(50,50)];
[item setLabel:#"Text"];
return [item autorelease];
}
return nil;
}
But this also has the same effect: when I press a NSToolbarItem with a NSButton in it in "Text only mode" the toolbar itself forces it's mode to "Icon and Text".
Do you have any idea how I can make it work correctly or perhaps have an alternative to creating the nice looking toolbaritems like Safari etc have?
You need to add a menu representation to each NSToolbarItem that has a custom view. Below the line where you allocate the NSToolbarItem add this:
NSMenuItem *menuRep = [[NSMenuItem alloc] initWithTitle:#"Text" action:#selector(targetMethod:) keyEquivalent:#""];
[menuRep setTarget:<target>];
[item setMenuFormRepresentation:menuRep];
As long as the target is valid your items should stay as text-only buttons; otherwise they will be disabled. See Setting a Toolbar Item's Representation.
Normally you would also need to implement validateToolbarItem: in your target, but for custom view items you instead need to override validate: to do something appropriate. See Validating Toolbar Items.

Resources