How to display an NSMenu embedded in a NSPopover? - macos

Is there a way to have an NSMenu like object displayed as content of a NSPopover?
Essentially I'd like to reproduce what macOS Dock does when you right click on an app icon (I don't mind about the dark background here, I'm only interested in having the menu displayed in a popover-like window style with the arrow pointing to its target).
I have been looking into what NSPopUpButton does but I couldn't find a way to configure this component in such way; it has a arrowPosition but this is actually referred to the orientation of the arrow on the button itself.
Also NSMenu is an NSObject and again I can't see a clean way to grab its view and add it to a popover, so I guess it's not possible but maybe you have any better idea?
Thanks for any suggestion!

You could check the apple menu programming guide:
https://developer.apple.com/library/archive/samplecode/MenuItemView/Introduction/Intro.html#//apple_ref/doc/uid/DTS10004136
In AppDelegate you could see:
// -------------------------------------------------------------------------------
// applicationDockMenu:sender
// -------------------------------------------------------------------------------
// This NSApplication delegate method is called when the user clicks and holds on
// the application icon in the dock.
//
- (NSMenu *)applicationDockMenu:(NSApplication *)sender
{
return self.appDockMenu;
}

Related

Disabling the NSTableView row focus ring

How do you disable the focus ring around an NSTableView row when the user right-clicks on it? I can't get it to disappear. Setting focus ring of an individual NSTableViewCell in the table to None has no effect.
Subclass the table view and implement the following method:
- (void)drawContextMenuHighlightForRow:(NSInteger)row {
// do nothing
}
Note:
The blue outline is not the focus ring.
This is an undocumented private method Apple uses to draw the outline. Providing an empty implementation will prevent anything from being drawn, but I am not 100% sure that whether it can pass the review.
New:
Here is how I did it.
You can handle the menu manually. Subclass NSTableRowView or NSTableCellView, then use rightMouseDown: and mouseDown: (check for control key) and then notify your tableViewController (notification or delegate) of the click. Don't forget to pass the event as well, then you can display the menu with the event on the table view without the focus ring.
The above answer is easier, but it may not pass the review, as the author mentioned.
Plus you can show individual menu items for each row (if you have different sorts of views)
Old:
I think the focus ring is defined by NSTableRowView, not NSTableCellView, because it is responsible for the complete row. Try to change the focus ring there. You can subclass NSTableRowView and add it to the tableView via IB or NSTableViewDelegate's method:
- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
If your goal is just displaying a contextual menu, you also can try this.
Wrap the NSOutlineView within another NSView.
Override menuForEvent method on the wrapper view.
Do not set menu on outline-view.
Now the wrapper view shows the menu instead of your outline-view, so it won't show the focus ring. See How do you add context senstive menu to NSOutlineView (ie right click menu) for how to find a node at the menu event.

NSMenuDelegate methods not called for contextual menu

I have a Document based application. I want to add a contextual menu that displays context-sensitive info when the user right-clicks selected text in an NSTextView.
I have followed the advice in the Apple documentation and
Added an NSMenu as a root object in my XIB file.
Connected the NSMenu instance to the menu outlet of the NSTextView.
Connected an IBAction to the NSMenuItem inside the NSMenu.
So far so good. Every thing works as expected: the menu item appears and the action is called when it is selected.
I need to get the selected text from the NSTextView before the menu appears so that I can configure my menu item appropriately. According to the docs
If you need to customize the contextual menu, you can do so by setting
an appropriate object as the menu’s delegate and implementing the
menuWillOpen: method to customize the menu as you see fit just before
it appears.
I connect the delegate of the NSMenu to File's Owner. None of the delegate methods are called. ( menuWillOpen: is the only one I need, but I've tried others, too).
I set a breakpoint inside the IBAction that gets called when the menu item is selected. If I inspect the menu with the debugger I can see that the delegate is correctly set to the object that implements the delegate method.
Is there anything else to check? Anything I'm doing blatantly wrong?
Xcode v4.6.3
SDK v10.8
Deployment target 10.7
After some digging, this is what I found: NSTextView builds a different NSMenu instance to use as the contextual menu, probably by overriding -menuForEvent: or some similar internal method. This new menu copies the menu items from the menu you created in Interface Builder (in fact, it creates new menu item instances whose attributes are copied from the original menu items) but it does not copy the menu delegate, which is why your menu delegate does not receive -menuWillOpen:. I am not sure whether this is intentional or not. Reading that documentation quote you posted, it seems to be a bug.
What you can do is to set the delegate of your NSTextView instance to an object whose class conforms to NSTextViewDelegate (maybe your File’s Owner, which already conforms to NSMenuDelegate) and implement the following method:
- (NSMenu *)textView:(NSTextView *)view menu:(NSMenu *)menu forEvent:(NSEvent *)event atIndex:(NSUInteger)charIndex
{
// if the menu delegate is not self, set another object
[menu setDelegate:self];
return menu;
}
This will make sure that the contextual menu created by the text view uses your delegate.
NB: since NSTextView creates a different contextual menu, it could be the case that it might want to set the menu delegate to itself or some other internal object. In my tests the delegate is nil, so it looks like it’s safe. Alternatively, you could discard the proposed menu argument and return your own NSMenu instance with the delegate correctly set.
Finding this thread saved me a lot of time...thanks! Here's an implementation that works in an NSView in Swift. myNSMenu is an outlet from Storyboard to appDelegate and a subclass of NSMenu. Without the assignment of the delegate in the code below, the NSMenuDelegate functions were not called.
let appDelegate = NSApplication.sharedApplication().delegate as! AppDelegate
appDelegate.myNSMenu.delegate = appDelegate.myNSMenu
NSMenu.popUpContextMenu(appDelegate.myNSMenu, withEvent: theEvent, forView: self)

NSWindow tracking

I would like to track each time a certain window appears (becomes visible to the user) in a OS X app. Where would be the most adequate place to call the tracker?
windowWillLoad, maybe?
I expected to find something like windowWillAppear but it seems I'm thinking too much iOS.
How about getting notification such as NSWindowDidBecomeMainNotification, By main I guess the one which is top most on screen directly visible by user.
see : Apple Documentation
Yes, one would expect that a window would notify its delegate or its controller with a windowWillAppear or windowDidAppear message, or post a documented notification like NSWindowDidAppearNotification. But alas, none of those exist. I filed a bug report with Apple and was given the advice to use a storyboard and a view controller instead. This is unhelpful in legacy apps that already use a bunch of window controllers and xibs.
You could subclass NSWindow and override orderWindow:relativeTo: to send a notification. Most, but not quite all, of the messages that make a window show itself ultimately go through this method, including orderBack:, orderFront:, makeKeyAndOrderFront:, and -[NSWindowController showWindow:]. But orderFrontRegardless does not go through orderWindow:relativeTo:, so you would also want to override that for completeness.
Another way to be notified is to make a subclass of NSViewController that controls some view that's always visible in the window. The view controller will receive viewWillAppear and viewDidAppear.
If you're subclassing NSWindow or NSViewController already for some other reason, either of these is a reasonable solution.
If you're not subclassing NSWindow already, and don't have an NSViewController subclass for a view that's always visible in the window, then another way is to use Cocoa bindings to connect the window's visible binding to a property one of your objects. For example, I have a custom NSWindowController subclass. I gave it a windowIsVisible property:
#interface MyWindowController ()
#property (nonatomic) BOOL windowIsVisible;
#end
and I implemented the accessors like this:
- (BOOL)windowIsVisible { return self.window.visible; }
- (void)setWindowIsVisible:(BOOL)windowIsVisible {
NSLog(#"window %# became %s", self.window, windowIsVisible ? "visible" : "hidden");
}
and in awakeFromNib, I bind the window's visible binding to the property like this:
- (void)awakeFromNib {
[super awakeFromNib];
[self.window bind:NSVisibleBinding toObject:self withKeyPath:NSStringFromSelector(#selector(windowIsVisible)) options:nil];
}
When the window becomes visible, the setWindowIsVisible: setter is called with an argument of YES. Note that if the whole app is hidden and reappears, the setter is called again, even though it wasn't called with argument NO when the app was hidden. So be careful not to assume the window was previously hidden.
Also, the binding might create a retain cycle, so you should probably unbind it when the window is closed, unless you want to keep the window and controller around. Note that the window does post NSWindowWillCloseNotification when it's closing, so you don't need any special magic to detect that.

-shortcutRecorder:keyComboDidChange: isn’t executed

I am writing a Cocoa application and I'd like to implement a global hotkey function.
I implemented the ShortcutRecorder.framework from Waffle Software and I added a customView to my xib. Then I subclassed the CustomView to SRRecorderControl. Now I see the Recorder in my Window, but how can I get the KeyCombo and how can I react on that?
I implemented the keyComboDidChange method with no luck to get the keycode. What am I doing wrong?
Here is my code for getting the keycode:
- (void)shortcutRecorder:(SRRecorderControl *)aRecorder keyComboDidChange:(KeyCombo)newKeyCombo
{
if (aRecorder == shortcutRecorder)
{
NSLog{"We got a new Key Combo");
}
}
shortcutrecorder is my IBOutlet btw.
Do I have to implement a protocol or setDelegate:self or something like that?
Edited to add
Actually I have declared my shortcutRecorder outlet in my Preferences.h. Then in the Identity Inspector I put "Preferences" as Custom Class for Files Owner in and I connect the delegate to my Shortcut Recorder... but the keyComboDidChange is never called ... I don't understand why.
Let me explain the steps I took to get it working:
Create a window xib
The file's owner of this class is, in my case, PreferencesWindowController
Create a referencing outlet from the window to the file's owner, by right clicking the window and dragging it to the file's owner
Add the custom view to your window
You have to connect the delegate of the ShortcutRecorder to the "File's owner". To do this, right-click the SRRecorderControl and drag the delegate to the "File's owner" on your left.
After this: the ShortcutRecorder only records the hotkey and leaves it to you what to do with it. You need to use the PTHotKeyCenter (which is shipped with ShortcutRecorder) or you could implement the shortcut handling yourself.
The ShortcutRecorder contains a great demo which demonstrates the use of the ShortcutRecorder in combination with the PTHotKeyCenter. It works like this:
Listen to events from the ShortcutRecorder (which you already do, but without setting the delegate)
Check if the globalHotKey variable is set
If so, unload the previous hotkey
Init a new hotkey with the settings from the ShortcutRecorder
Set the target and action to actually capture the hotkey, once pressed
Save the hotkey to the shared center (from this moment on, the hotkey will work)
Little sample, from their source:
if (globalHotKey != nil)
{
[[PTHotKeyCenter sharedCenter] unregisterHotKey: globalHotKey];
[globalHotKey release];
globalHotKey = nil;
}
globalHotKey = [[PTHotKey alloc] initWithIdentifier:#"SRTest"
keyCombo:[PTKeyCombo keyComboWithKeyCode:[shortcutRecorder keyCombo].code
modifiers:[shortcutRecorder cocoaToCarbonFlags: [shortcutRecorder keyCombo].flags]]];
[globalHotKey setTarget: self];
[globalHotKey setAction: #selector(hitHotKey:)];
[[PTHotKeyCenter sharedCenter] registerHotKey: globalHotKey];
The only thing left to do is the hotkey handler:
- (void)hitHotKey:(PTHotKey *)hotKey
{
NSLog(#"Hotkey pressed!");
}
You could easily save the hotkey settings to the UserDefaults to load them every time you application starts.
Actually I have declared my shortcutRecorder outlet in my Preferences.h. Then in the Identity Inspector I put "Preferences" as Custom Class for Files Owner in and I connect the delegate to my Shortcut Recorder... but the keyComboDidChange is never called ... I don't understand why -.-

Can we have popovers in Mac Cocoa apps?

Popovers are heavily used in iPad apps and I really like them. Now I think about how this could be implemented in AppKit on the mac because I have a use case for it.
Do I need a NSWindow subclass to accomplish the overlay or could I also use a normal view?
According to Apple's Developer Documentation, you can use built in popovers on OS X with the built-in NSPopover class:
Starting in OS X v10.7, AppKit provides support for popovers by way of the NSPopover class. A popover provides a means to display additional content related to existing content on the screen. The view containing the existing content—from which the popover arises—is referred to in this context as a positioning view. You use an anchor to express the relation between a popover and its positioning view.
Here's a link to the NSPopover class. You can also see an example of NSPopovers used in the Calendar (10.7+) app, and the Safari app (10.8+). The image below depicts a popover in the Calendar app (left) and Safari (right):
Here's how to setup an NSPopover, it is very simple and can be done mostly in interface builder.
Add an NSPopover Item to your XIB in interface builder. This will create the NSPopover and its view controller.
Next, drag a custom NSView into your XIB through interface builder. This will be the view for the popover view controller
Customize your NSView with any controls you need in your popover
In your header file (.h) add the following two lines of code:
#property (assign) IBOutlet NSPopover *popover;
- (IBAction)showPopover:(id)sender;
Don't forget to connect both the outlet and the action to your interface.
In your implementation, synthesize the popover and add the method for showPopover
In the showPopover method, add this line to show the popover:
[[self popover] showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge];
It's up to you to figure out how to dismiss the popover; because what fun it copy / pasting? You can either do it manually (hint: try using close) or change the behavior property and have the system do it (see the edit below).
Good luck, hope this helps!
Edit
As noted by David in his comment:
Another possibility for dismissing the popover is to set its behavior to Transient. This allows the user to click anywhere outside the popover to have it disappear
The behavior property of a popover sets how it appears and disappears. There are three behaviors:
NSPopoverBehaviorApplicationDefined - (Default) Your app must close the popover itself
NSPopoverBehaviorTransient - The popover is closed when any interface element is interacted with outside the popover
NSPopoverBehaviorSemitransient - The popover is closed when any interface element in the popover's presenting view is interacted with outside the popover.
Read more about this in Apple's Documentation.
If I understood you correctly, you want something like MAAttachedWindow (by Matt Gemmell), which is open source.
Alternatively, you can take a look at Popover example in the documentation. https://developer.apple.com/library/mac/samplecode/Popover/Introduction/Intro.html

Resources