Remove 'Layout Orientation' from NSTextView context menu - macos

I have a NSTextView which has a ruler attached showing line numbers. If the user uses the 'Layout Orientation' -> 'Vertical' context menu, things go wonky. The applications intended purpose does not support a Vertical orientation anyway, so I would like to remove this context menu.
So far I have subclassed an NSTextView and overwrote the defaultMenu action:
+ (NSMenu *) defaultMenu
{
// Get our default menu
NSMenu * contextMenu =
[NSTextView defaultMenu];
for(NSInteger menuItemIndex = contextMenu.itemArray.count - 1;
menuItemIndex != -1;
--menuItemIndex)
{
NSMenuItem * menuItem =
[contextMenu itemAtIndex: menuItemIndex];
NSLog(#"%ld %#, %#",
menuItemIndex,
NSStringFromSelector(menuItem.action),
menuItem.title);
} // End of menuItem loop
return contextMenu;
} // End of defaultMenu
My original thought was that I could remove the menu item with a specific selector, but unfortunately the 'Layout Orientation' is a submenu, so it has the submenuAction: selector.
I could still remove the menu by comparing the title, but that seems like a poor way to do this and would probably break in a localized environment.
Any suggestions as to PROPERLY go about removing menu items from the NSTextView context menu? (Removing by index also seems hacky, as that could possible break on different versions of the OS).

Each submenu menuitem has a menu. Scan the submenus for action changeLayoutOrientation:.
You can disable the layout orientation menu items by implementing validateUserInterfaceItem:.
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem {
if ([anItem action] == #selector(changeLayoutOrientation:))
return NO;
return [super validateUserInterfaceItem:anItem];
}

Related

Allow views in childWindow to become key without losing focus on parentWindow

I added a childWindow of a custom subclass of NSWindow to a parentWindow (also a custom subclass of NSWindow). The childWindow has the NSBorderlessWindowMask and canBecomeKeyWindow: is overridden to return YES and canBecomeMainWindow: to return NO.
The childWindow is set to resize with the parentWindow. So I want to create the illusion that the views of the childWindow are part of the parentWindow. The main idea is to arrange the document windows created by the document-based application within a main window to provide a tabbed interface (just like in a browser) to switch between the documents.
My problem is that whenever I click in one of the views of the childWindow, the parentWindow (the main window) looses focus and the traffic light buttons are getting greyed out. This is obviously contrary to what I want to achieve.
I found this answer:
Make NSView in NSPanel first responder without key window status
But even if I override isKeyWindow: (of the main window) to always return YES, the title bar gets greyed out nonetheless when I click into the childWindow.
I also tried to follow this advice:
http://www.cocoabuilder.com/archive/cocoa/143945-non-focused-child-window.html
But I'm not sure what "include the child window in its responder chain just ahead of its nextResponder" means. With canBecomeKeyWindow: to return NO (for the childWindow), the views within the child never can become key and are always greyed out.
Any clue what I am doing wrong?
One addition: Is it possible to make the views in the childWindow FirstResponder without giving the childWindow key-status?
I got this working by mimicking the behaviour of NSPopover. On investigation I found that a popover (which uses a private NSPanel subclass "_NSPopoverWindow") believed it was the key & main window, even though it is not the window returned from [NSApp keyWindow].
Create your own custom NSPanel subclass, attach it to the parent window, and then override the following methods as so:
- (BOOL)isKeyWindow {
return YES;
}
- (BOOL)isMainWindow {
return YES;
}
- (BOOL)canBecomeKeyWindow {
return YES;
}
- (BOOL)canBecomeMainWindow {
return YES;
}
- (void)makeKeyWindow {
[super makeKeyWindow];
[self.parentWindow makeKeyWindow];
}
- (void)makeMainWindow {
[super makeMainWindow];
[self.parentWindow makeMainWindow];
}
- (void)becomeKeyWindow {
[super becomeKeyWindow];
}
- (void)becomeMainWindow {
[super becomeMainWindow];
[self.parentWindow becomeMainWindow];
}
- (void)resignMainWindow {
}
- (void)resignKeyWindow {
}

NSView custom context menu and keys

i have an NSCollectionView in my application's main window that manages a collection of custom NSView items. Each custom view has a context menu assigned to it. I want to add shortcut keys to some of the items, for example to associate a "delete" key with "remove item from collection" action. I've added key equivalents to context menu items through IB but the question is how do i make the collection items respond to the pressed keys?
I know that i can achieve this by adding this menu to the NSApp's main menu and keep track of the selected item. Is there any other way besides that?
You could add something like this to your NSCollectionView subclass:
- (BOOL)performKeyEquivalent:(NSEvent *)theEvent
{
BOOL rv = NO;
id firstResponder = self.window.firstResponder;
if ([firstResponder isKindOfClass:[NSView class]] && [firstResponder isDescendantOf:self]) {
// Note: performKeyEquivalent: messages come DOWN the view hierarchy, not UP the responder chain.
// Perform the key equivalent
}
if (!rv) {
rv = [super performKeyEquivalent:theEvent];
}
return rv;
}

How to put a disclosure triangle's title to the right of the triangle?

In the HIG's example of how to use disclosure triangles, it shows a label directly to the right of the triangle.
However, when I throw one of these onto my view in Interface Builder, the text is centered on top of the triangle. I've searched the NSButton API docs, and poked at everything I can find in IB, but nothing I try will put the text to the right of the triangle. What am I missing?
What I generally do is use 2 buttons: one disclosure button and another button for the label:
While you can use a text field for the label, I prefer using a button and setting the button to call performClick: on the disclosure triangle. This makes for a much larger target area to be able to click on than a tiny triangle. (Users with trackpads will thank you).
To set up the button, change it so it looks like this:
Then set its action:
I'm not sure if there's an actual way to get the button to show both properly (without subclassing I mean), since I've generally just used separate items to give the effect. (I just checked and there is indeed a Carbon disclosure control that has both the triangle and the label built-in).
The Carbon control has the right idea where clicking on the label will automatically trigger the control. In some places (notably the re-written Cocoa Finder), you can see that you don't get that behavior for free (unless you use a button like I've shown). I still have an open bug on that one (rdar://6828042): BugID 6828042: 10.6 (10A335) Finder: Inspector's disclsr. triangle's text label not toggleable". ;-)
Have you tried just using a triangle and using a separate label?
The disclosure triangle widget is drawn by the button's bezel, centered in the available space. To create a disclosure triangle button which also has a title, you just need to subclass NSButtonCell and make sure the bezel is restricted to the left side of the button and that the title avoids the bezel. Then add your button in IB, expand it and set your title, and set the class of the cell. Unfortunately, IB won't know how to display your subclass and will put the triangle in the middle of the button. Just make sure it's big enough.
In Objective-C:
#interface TitledDisclosureTriangleButtonCell : NSButtonCell
#end
#define TRIANGLE_PADDING 15.f
#implementation TitledDisclosureTriangleButtonCell
- (NSRect)titleRectForBounds:(NSRect)theRect
{
NSRect titleRect = [super titleRectForBounds:theRect];
titleRect.origin.x = TRIANGLE_PADDING;
titleRect.size.width = NSWidth(titleRect) - TRIANGLE_PADDING;
return titleRect;
}
- (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView
{
NSRect bezelFrame = frame;
bezelFrame.size.width = TRIANGLE_PADDING;
[super drawBezelWithFrame:bezelFrame inView:controlView];
}
#end
And in Swift:
let TRIANGLE_PADDING: CGFloat = 15
class TitledDisclosureTriangleButtonCell: NSButtonCell
{
override func titleRectForBounds(theRect: NSRect) -> NSRect {
var titleRect = super.titleRectForBounds(theRect)
titleRect.origin.x = TRIANGLE_PADDING
titleRect.size.width = titleRect.size.width - TRIANGLE_PADDING
return titleRect
}
override func drawBezelWithFrame(frame: NSRect, inView controlView: NSView) {
var bezelFrame = frame
bezelFrame.size.width = TRIANGLE_PADDING
super.drawBezelWithFrame(bezelFrame, inView: controlView)
}
}

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.

Is there an equivalent technique in Cocoa for the synchronous TrackPopupMenu in Windows?

In response to a rightMouse event I want to call a function that displays a context menu, runs it, and responds to the selected menu item. In Windows I can use TrackPopupMenu with the TPM_RETURNCMD flag.
What is the easiest way to implement this in Cocoa? It seems NSMenu:popUpContextMenu wants to post an event to the specified NSView. Must I create a dummy view and wait for the event before returning? If so, how do I "wait" or flush events given I am not returning to my main ?
The 'proper' way to do this in Cocoa is to have your menu item's target and action perform the required method. However, if you must do it within your initial call, you can use [NSView nextEventMatchingMask:] to continually fetch new events that interest you, handle them, and loop. Here's an example which just waits until the right mouse button is released. You'll probably want to use a more complex mask argument, and continually call [NSView nextEventMatchingMask:] until you get what you want.
NSEvent *localEvent = [[self window] nextEventMatchingMask: NSRightMouseUpMask];
I think you'll find the 'proper' way to go much easier.
It appears that popUpContextMenu is already synchronous. Since I didn't see a way to use NSMenu without having it send a notification to an NSView I came up with a scheme that instantiates a temporary NSView. The goal is to display a popup menu and return the selected item in the context of a single function call. Following is code snippets of my proposed solution:
// Dummy View class used to receive Menu Events
#interface DVFBaseView : NSView
{
NSMenuItem* nsMenuItem;
}
- (void) OnMenuSelection:(id)sender;
- (NSMenuItem*)MenuItem;
#end
#implementation DVFBaseView
- (NSMenuItem*)MenuItem
{
return nsMenuItem;
}
- (void)OnMenuSelection:(id)sender
{
nsMenuItem = sender;
}
#end
// Calling Code (in response to rightMouseDown event in my main NSView
void HandleRButtonDown (NSPoint pt)
{
NSRect graphicsRect; // contains an origin, width, height
graphicsRect = NSMakeRect(200, 200, 50, 100);
//-----------------------------
// Create Menu and Dummy View
//-----------------------------
nsMenu = [[[NSMenu alloc] initWithTitle:#"Contextual Menu"] autorelease];
nsView = [[[DVFBaseView alloc] initWithFrame:graphicsRect] autorelease];
NSMenuItem* item = [nsMenu addItemWithTitle:#"Menu Item# 1" action:#selector(OnMenuSelection:) keyEquivalent:#""];
[item setTag:ID_FIRST];
item = [nsMenu addItemWithTitle:#"Menu Item #2" action:#selector(OnMenuSelection:) keyEquivalent:#""];
[item setTag:ID_SECOND];
//---------------------------------------------------------------------------------------------
// Providing a valid windowNumber is key in getting the Menu to display in the proper location
//---------------------------------------------------------------------------------------------
int windowNumber = [(NSWindow*)myWindow windowNumber];
NSRect frame = [(NSWindow*)myWindow frame];
NSPoint wp = {pt.x, frame.size.height - pt.y}; // Origin in lower left
NSEvent* event = [NSEvent otherEventWithType:NSApplicationDefined
location:wp
modifierFlags:NSApplicationDefined
timestamp: (NSTimeInterval) 0
windowNumber: windowNumber
context: [NSGraphicsContext currentContext]
subtype:0
data1: 0
data2: 0];
[NSMenu popUpContextMenu:nsMenu withEvent:event forView:nsView];
NSMenuItem* MenuItem = [nsView MenuItem];
switch ([MenuItem tag])
{
case ID_FIRST: HandleFirstCommand(); break;
case ID_SECOND: HandleSecondCommand(); break;
}
}
There is no direct equivalent, except in Carbon, which is deprecated.
For detecting the right-click, follow these instructions. They ensure that you will properly detect right-clicks and right-holds and display the menu when you should and not display it when you shouldn't.
For following events, you might try [[NSRunLoop currentRunLoop] runMode:NSEventTrackingRunLoopMode untilDate:[NSDate distantFuture]]. You will need to call this repeatedly until the user has chosen one of the menu items.
Using nextEventMatchingMask:NSRightMouseUpMask will not work in all, or even most, cases. If the user right-clicks on your control, the right mouse button will go up immediately after it goes down, without selecting a menu item, and the menu item selection will probably (though not necessarily) happen through the left mouse button. Better to just run the run loop repeatedly until the user either selects something or dismisses the menu.
I don't know how to tell that the user has dismissed the menu without selecting anything.

Resources