Question

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.

Was it helpful?

Solution 2

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"];

OTHER TIPS

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.)

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

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.

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.

In Apple Swift you can do it like this:

var EditMenu = NSApplication.sharedApplication().mainMenu!.itemWithTitle("Edit")
if (EditMenu != nil) // Edit-Menu exists, otherwise you would run into an exception when proceeding
{
    var Count: Int = EditMenu!.submenu!.numberOfItems
    if (EditMenu!.submenu!.itemAtIndex(Count - 1)!.title == "Special Characters…")
    {
        EditMenu!.submenu!.removeItemAtIndex(Count - 1)
    }
    if (EditMenu!.submenu!.itemAtIndex(Count - 2)!.title == "Start Dictation…")
    {
        EditMenu!.submenu!.removeItemAtIndex(Count - 2)
    }
    println("Titel = '\(EditMenu!.submenu!.itemAtIndex(Count - 3)!.title)'")
    if (EditMenu!.submenu!.itemAtIndex(Count - 3)!.title == "")
    {
        EditMenu!.submenu!.removeItemAtIndex(Count - 3)
    }
}

Just replace "Edit" with "Bearbeiten" for the German version. The Separator Menu Item returns an empty string as the title.

For Swift 4 and Xcode 9.2 this would be:

static let EDIT_MENU_TITLE = "Edit"
static let SPECIAL_CHARACTERS_TITLE = "Emoji & Symbols"
static let DICTATION_MENU_TITLE = "Start Dictation…"

And then I use the following function to enable/disable the Edit menu:

func enableEditingMenu( enabled: Bool ) {
    let m = NSApplication.shared().mainMenu
    let mi = m?.item(withTitle: MenuController.EDIT_MENU_TITLE )
    mi?.isEnabled = enabled
    if (mi != nil) { // Edit-Menu exists, otherwise you would run into an exception when proceeding
        let Count: Int = mi!.submenu!.numberOfItems
        if (mi!.submenu!.item(at: Count - 1)!.title == MenuController.SPECIAL_CHARACTERS_TITLE) {
            mi!.submenu!.removeItem(at: Count - 1)
        }
        if (mi!.submenu!.item(at: Count - 2)!.title == MenuController.DICTATION_MENU_TITLE) {
            mi!.submenu!.removeItem(at: Count - 2)
        }
        if (mi!.submenu!.item(at: Count - 3)!.title == "") {
            mi!.submenu!.removeItem(at: Count - 3)
        }
    }
}

Which is called like this in a view controller where I need it enabled/disabled:

override func viewWillAppear() {
    let mc = MenuController()
    mc.enableEditingMenu( enabled: true )
}

override func viewWillDisappear() {
    let mc = MenuController()
    mc.enableEditingMenu( enabled: false )
}

I also ensure it is disabled from within AppDelegate.swift

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top