2
votes

enter image description here

I know this question has been asked many times but it seems no better solution for it.

Changing the allowsUserCustomization property doesn't help. It seems there is no API to customize the items in toolbar's context menu.

Finder app has no "Use Small Size" while Notes app has only "Customize Toolbar.."

I would like to know if there is any way to subclass or extend or do whatever to the NSToolbar to achieve the purpose?

Updated 1:

According to @Khundragpan and this post, problem 1 can be solved by:

    if let contextMenu = window?.contentView?.superview?.menu {
        for item in contextMenu.items {
            if item.title != "Customize Toolbar…" {
                contextMenu.removeItem(item)
            }
        }
    }

But I don't think it's the best way.

Update 2:

Another way to solve problem 1 (thanks to @1024jp to point out this file):

    if let contextMenu = window?.contentView?.superview?.menu {
        contextMenu.items.forEach({ (item) in
            if let action = item.action,
                NSStringFromSelector(action) != "runToolbarCustomizationPalette:" {
                contextMenu.removeItem(item)
            }
        })
    }

Update 3:

A ton of thanks to @1024jp for helping me. I'm able to remove those things with a few tips and tricks from him. Check the answer below.

1
you can get to toolbar menu via let toolBarMenu = window?.contentView?.superview?.menu. Now remove the menu items you don't want.Khundragpan
I think it's better to filter menuItems not by item's title but by item's action for localization. FYI, I do the similar thing in my app: github.com/coteditor/CotEditor/blob/3.0.0-beta.2/CotEditor/…1024jp
@JoãoOliveira Aha ok, then, it's more beautiful to remove the button in window(:willPositionSheet:using:), one of the NSWindowDelegate's methods. In this way, user don't need to see the button to be removed.1024jp
I've also improved my code to remove the button before showing the sheet. github.com/coteditor/CotEditor/blob/…1024jp
@1024jp I understand. Thank you very much!João Oliveira

1 Answers

5
votes

After 3 days, I finally did it. Here is the result.

enter image description here

enter image description here

Source Code in Swift 3

You can implement and make your own class, but here I just want to keep everything in a file.

This is the WindowController.swift file. You can set the custom class of your window controller and run. Again thanks to @1024jp for the tips.

//
//  WindowController.swift
//  The Toolbar
//
//  Created by João Oliveira on 22/09/2016.
//  Copyright © 2016 João Oliveira. All rights reserved.
//

import Cocoa

class WindowController: NSWindowController {

    override func windowDidLoad() {
        super.windowDidLoad()

        guard let window = window else { return }

        window.delegate = self

        window.toolbar = NSToolbar(identifier: "RestrictedToolbar")
        window.toolbar?.allowsUserCustomization = true
        window.toolbar?.displayMode = .iconOnly
        window.toolbar?.delegate = self

        keepOnlyCustomizableMenu()
    }

    // PROBLEM 1: Solution
    func keepOnlyCustomizableMenu() {
        if let contextMenu = window?.contentView?.superview?.menu {
            contextMenu.items.forEach({ (item) in
                if let action = item.action,
                    NSStringFromSelector(action) != "runToolbarCustomizationPalette:" {
                    contextMenu.removeItem(item)
                }
            })
        }
    }
}

// MARK: Window Delegate
// A ton of thanks to genius @1024jp
extension MyWindowController: NSWindowDelegate {

    // PROBLEM 2: Solution
    func window(_ window: NSWindow, willPositionSheet sheet: NSWindow, using rect: NSRect) -> NSRect {

        if sheet.className == "NSToolbarConfigPanel" {
            removeSizeAndDisplayMode(in: sheet)
        }

        return rect
    }

    func removeSizeAndDisplayMode(in sheet: NSWindow) {

        guard let views = sheet.contentView?.subviews else { return }

        // Hide Small Size Option
        views.lazy
            .flatMap { $0 as? NSButton }
            .filter { button -> Bool in
                guard let buttonTypeValue = button.cell?.value(forKey: "buttonType") as? UInt,
                    let buttonType = NSButtonType(rawValue: buttonTypeValue)
                    else { return false }
                return buttonType == .switch
            }
            .first?.isHidden = true

        // Hide Display Mode Option
        views.lazy
            .filter { view -> Bool in
                return view.subviews.count == 2
            }
            .first?.isHidden = true

        sheet.contentView?.needsDisplay = true
    }

}

// MARK: Toolbar Delegate
extension MyWindowController: NSToolbarDelegate {

    func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [String] {
        return [
            NSToolbarFlexibleSpaceItemIdentifier,
            NSToolbarSpaceItemIdentifier,
            NSToolbarToggleSidebarItemIdentifier
        ]
    }

    func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [String] {
        return [NSToolbarToggleSidebarItemIdentifier]
    }

    func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: String, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
        return nil
    }

}