Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove 'Start Dictation' and 'Special Characters' from menu

To enable Copy and Paste in my Cocoa app, I added two new menu items (copy and paste) to the menu and dragged the selector from each item to the first responder (copy and paste). However, two extra items show up below the Copy and Paste menu items: 'Start Dictation' and 'Special Characters' .

I haven't been able to figure out why they show up or how I remove them.

Optimally, I don't even want the copy and paste menu items to be visible. I just want the user of my app to be able to paste stuff (i.e. from an email, text doc etc) into a text field on one of the forms in my app.

like image 309
user1884325 Avatar asked Jan 26 '14 21:01

user1884325


5 Answers

Of the three approaches already mentioned, setting UserDefaults is probably the cleanest, though, as noted, it relies on undocumented UserDefaults entries.

The approach of removing the items by the index position (ie, the last position) in the menu relies on the assumption that they will always be at the end of the menu. That's probably a safe assumption, but Apple could do something else the future.

Relying on the menu item names is also problematic for localization reasons.

It's better to use the selector, as shown for Objective-C, but it won't work quite so simply in Swift, particularly for the "Start Dictation..." menu item. The method you need the selector for is startDictation(_:), but unlike in Objective-C you can't just type it like that. You need to specify an @objc type to which it belongs. So just search Apple's documentation, right? Good luck. It's undocumented, and the method isn't exposed in Swift.

My approach to solving that problem is to add a stub for that method in my AppDelegate, and then use that to get the selector. You really just need some type Swift can get its teeth into to form the selector. But when the selector is tossed over the wall to the Obj-C side of Cocoa, that type just disappears. All that matters is the method's name, how many parameters, their order, and names. In this case, like most action methods, it takes one parameter (for the sender):

@objc public func startDictation(_: Any) { }

The AppDelegate is handy for a quick and dirty implementation, but for real use I prefer to create a class that inherits for NSObject with private initializers specifically for stub methods like that. That way you ensure that they never pollute the responder chain. Basically make a non-instantiable bag of do-nothing methods you can use to make selectors.

Now we need a way to find the corresponding menu item, so I make an extension on NSMenu

public extension NSMenu
{
    func lastMenuItem(where condition: (NSMenuItem) -> Bool) -> NSMenuItem?
    {
        for item in items.reversed()
        {
            if let submenu = item.submenu
            {
                if let foundItem = submenu.lastMenuItem(where: condition) {
                    return foundItem
                }
            }
            else if condition(item) { return item }
        }

        return nil
    }
}

I choose to search the menu in reverse, under the assumption that if I decided to add my own items at some future date, they will most likely be before the items I'm removing. Then in AppDelegate:

func removeUnwantedAutomaticMenus()
{
    let unwantedActions: [Selector] =
    [
        #selector(AppDelegate.startDictation(_:)),
        #selector(NSApplication.orderFrontCharacterPalette(_:)),
    ]
    for action in unwantedActions {
        NSApp.mainMenu?.lastMenuItem { $0.action == action }?.isHidden = true
    }
}

As you can see, I choose to hide the item rather than remove them, but you could certainly remove them instead. All that's left is just to call removeUnwantedAutomaticMenus() in AppDelegate.applicationDidFinishLaunching.

If you create your menus programmatically, you can make this more robust by using the tag property in NSMenuItem to mark items you added, then check for that tag to make sure you don't remove/hide those instead of the automatically added ones.

Another approach is to subclass NSMenu, overriding its addItem and insertItem methods to check the NSMenuItem's tag before adding them. Simply don't add/insert any NSMenuItems with an incorrect tag, which Apple's automatically inserted items won't have. If you use Storyboards for your menus, it's kind of pain, because you have to make sure the class for each menu is set to your custom class, and the tag for each menu item is set correctly. If you create your menus programmatically it's much easier to ensure everything is set correctly.

like image 162
Chip Jarred Avatar answered Oct 08 '22 04:10

Chip Jarred


Quickest way to fix this is to set the title to "Edit " (with an extra space at the end).

In the interface builder select the Edit menu:

enter image description here

Then from the properties inspector, add an extra space to the title.

enter image description here

like image 27
RajV Avatar answered Oct 13 '22 20:10

RajV


As mentioned in Mac OS X Internals: A Systems Approach and Qt Mac (Re)move "Special Characters..." action in Edit menu, you can do something like this in main() before you load the nib (but it is not supported API):

[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSDisabledDictationMenuItem"];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSDisabledCharacterPaletteMenuItem"];
like image 9
Jeremy Huddleston Sequoia Avatar answered Oct 13 '22 19:10

Jeremy Huddleston Sequoia


Solution for Swift 4 using Storyboards

Add the following code to your AppDelegate:

func applicationWillFinishLaunching(_ notification: Notification) {
  UserDefaults.standard.set(true, forKey: "NSDisabledDictationMenuItem")
  UserDefaults.standard.set(true, forKey: "NSDisabledCharacterPaletteMenuItem")
}

The applicationWillFinishLaunching function is called early in the life-cycle of your application, before the menu is initialized. No need to manually hack the menu items.

like image 3
Zyphrax Avatar answered Oct 13 '22 18:10

Zyphrax


Here is the code I am using in my application to remove these automagically added entries to the Edit menu:

- (void) applicationDidFinishLaunching: (NSNotification*)aNotification
{
    NSMenu* edit = [[[[NSApplication sharedApplication] mainMenu] itemWithTitle: @"Edit"] submenu];
    if ([[edit itemAtIndex: [edit numberOfItems] - 1] action] == NSSelectorFromString(@"orderFrontCharacterPalette:"))
        [edit removeItemAtIndex: [edit numberOfItems] - 1];
    if ([[edit itemAtIndex: [edit numberOfItems] - 1] action] == NSSelectorFromString(@"startDictation:"))
        [edit removeItemAtIndex: [edit numberOfItems] - 1];
    if ([[edit itemAtIndex: [edit numberOfItems] - 1] isSeparatorItem])
        [edit removeItemAtIndex: [edit numberOfItems] - 1];
}

NOTE: This code needs to go in applicationDidFinishLaunching: or later, if you place it in applicationWillFinishLaunching: the entries won't yet be added to the Edit menu.

Also note, I am using NSSelectorFromString as using @selector causes "unknown selector" warnings. (Even with the warning the code does work, but I prefer to have no warnings in my code, so opted to use NSSelectorFromString to avoid them.)

like image 11
Paige DePol Avatar answered Oct 13 '22 18:10

Paige DePol