From 27d4972c15fabca7d54a0c8b52ea81c454f15d2d Mon Sep 17 00:00:00 2001 From: Tae Won Ha Date: Tue, 26 Feb 2019 16:12:32 +0100 Subject: [PATCH] List all menu items --- Cartfile | 2 +- Cartfile.resolved | 2 +- VimR/VimR.xcodeproj/project.pbxproj | 16 ++ VimR/VimR/AppDelegate.swift | 70 ++++---- VimR/VimR/Base.lproj/MainMenu.xib | 58 +++---- VimR/VimR/PrefWindow.swift | 6 + VimR/VimR/ShortcutItem.swift | 46 +++++ VimR/VimR/ShortcutsPref.swift | 223 +++++++++++++++++++++++++ VimR/VimR/ShortcutsTableSubviews.swift | 87 ++++++++++ 9 files changed, 451 insertions(+), 59 deletions(-) create mode 100644 VimR/VimR/ShortcutItem.swift create mode 100644 VimR/VimR/ShortcutsPref.swift create mode 100644 VimR/VimR/ShortcutsTableSubviews.swift diff --git a/Cartfile b/Cartfile index 481e18e0..215f8b3d 100644 --- a/Cartfile +++ b/Cartfile @@ -10,6 +10,6 @@ github "sindresorhus/github-markdown-css" == 3.0.1 github "httpswift/swifter" == 1.4.5 github "a2/MessagePack.swift" == 3.0.0 github "elegantchaos/DictionaryCoding" == 1.0.6 -github "Kentzo/ShortcutRecorder" == 2.17.2 +github "qvacua/ShortcutRecorder" "master" github "Quick/Nimble" == 7.3.4 diff --git a/Cartfile.resolved b/Cartfile.resolved index 4383ccfd..f2f620a9 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,5 +1,4 @@ github "IBM-Swift/BlueSocket" "1.0.44" -github "Kentzo/ShortcutRecorder" "2.17.2" github "PureLayout/PureLayout" "v3.1.4" github "Quick/Nimble" "v7.3.4" github "ReactiveX/RxSwift" "4.4.1" @@ -12,5 +11,6 @@ github "qvacua/CocoaMarkdown" "7756ad96d5fb390c66531004868e828bb54d3609" github "qvacua/RxMessagePort" "v0.0.5" github "qvacua/RxMsgpackRpc" "v0.0.7" github "qvacua/RxNeovimApi" "0.3.4" +github "qvacua/ShortcutRecorder" "c978ce9bb482c31acfabe23baa2b075808a1fdd8" github "sindresorhus/github-markdown-css" "v3.0.1" github "sparkle-project/Sparkle" "1.21.3" diff --git a/VimR/VimR.xcodeproj/project.pbxproj b/VimR/VimR.xcodeproj/project.pbxproj index 3a5dd8e0..cdfde357 100644 --- a/VimR/VimR.xcodeproj/project.pbxproj +++ b/VimR/VimR.xcodeproj/project.pbxproj @@ -7,11 +7,14 @@ objects = { /* Begin PBXBuildFile section */ + 1929B02DAC94F0E47CFC8EA2 /* ShortcutsTableSubviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BCF66D08CC02A9233A88 /* ShortcutsTableSubviews.swift */; }; 1929B04CE8ECBD75CBBB0991 /* StringCommonsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B5D45C9792BBE76B8AFF /* StringCommonsTest.swift */; }; 1929B05B9D664052EC2D23EF /* FileOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BCE3E156C06EDF1F2806 /* FileOutlineView.swift */; }; + 1929B083D2176A9C615B8807 /* ShortcutsPref.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD256AA54518D37D6CBB /* ShortcutsPref.swift */; }; 1929B08C6230B9C5AB72DAF1 /* Pref128ToCurrentConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B5046239709E33516F5C /* Pref128ToCurrentConverter.swift */; }; 1929B0E0C3BC59F52713D5A2 /* FoundationCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B9AF20D7BD6E5C975128 /* FoundationCommons.swift */; }; 1929B0F599D1F62C7BE53D2C /* HttpServerMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1DC584C89C477E83FA2 /* HttpServerMiddleware.swift */; }; + 1929B148AA06340F22533377 /* ShortcutItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1E4B11A92D8DD171D93 /* ShortcutItem.swift */; }; 1929B1837C750CADB3A5BCB9 /* OpenQuicklyFileViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1558455B3A74D93EF2A /* OpenQuicklyFileViewRow.swift */; }; 1929B20CE35B43BB1CE023BA /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BC2F05E9A5C0DB039739 /* Theme.swift */; }; 1929B29B95AD176D57942E08 /* UiRootReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B457B9D0FA4D21F3751E /* UiRootReducer.swift */; }; @@ -25,6 +28,7 @@ 1929B462CD4935AFF6D69457 /* FileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B7CB4863F80230C32D3C /* FileItem.swift */; }; 1929B4B00D7BB191A9A6532D /* HtmlPreviewToolReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BE5AEA3D0980860EED50 /* HtmlPreviewToolReducer.swift */; }; 1929B4B70926DE113E6BF990 /* PreviewReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BE37AA2843779CAFA76F /* PreviewReducer.swift */; }; + 1929B4DD667E1E57EFA2AA57 /* ShortcutItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1E4B11A92D8DD171D93 /* ShortcutItem.swift */; }; 1929B4E54E2F13A7F5F2B682 /* BufferListReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B67A10E6BB2986B2416E /* BufferListReducer.swift */; }; 1929B4F0612224E594E89B92 /* AppearancePref.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B0FBFB766042CF06E463 /* AppearancePref.swift */; }; 1929B4FEE6EB56EF3F56B805 /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B34FC23D805A8B29E8F7 /* Context.swift */; }; @@ -56,6 +60,7 @@ 1929B990A143763A56CFCED0 /* PrefMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B364460D86F17E80943C /* PrefMiddleware.swift */; }; 1929BA715337FE26155B2071 /* BufferList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BA43449BA41666CD55ED /* BufferList.swift */; }; 1929BA76A1D97D8226F7CFB1 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B6AD3396160AA2C46919 /* Debouncer.swift */; }; + 1929BA8E1A7C425E0E03EB40 /* ShortcutsTableSubviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BCF66D08CC02A9233A88 /* ShortcutsTableSubviews.swift */; }; 1929BAAD7336FDFF1F78E749 /* ScorerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BF69B01107F358CF7EAD /* ScorerTest.swift */; }; 1929BAE4900D72A7877741B1 /* PrefWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BE168F31344B69E61B62 /* PrefWindow.swift */; }; 1929BAFF1E011321D3186EE6 /* UiRoot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD4149D5A25C82064DD8 /* UiRoot.swift */; }; @@ -296,6 +301,7 @@ 1929B14A5949FB64C4B2646F /* KeysPref.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeysPref.swift; sourceTree = ""; }; 1929B1558455B3A74D93EF2A /* OpenQuicklyFileViewRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenQuicklyFileViewRow.swift; sourceTree = ""; }; 1929B1DC584C89C477E83FA2 /* HttpServerMiddleware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServerMiddleware.swift; sourceTree = ""; }; + 1929B1E4B11A92D8DD171D93 /* ShortcutItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutItem.swift; sourceTree = ""; }; 1929B34FC23D805A8B29E8F7 /* Context.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = ""; }; 1929B364460D86F17E80943C /* PrefMiddleware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefMiddleware.swift; sourceTree = ""; }; 1929B365A6434354B568B04F /* FileMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileMonitor.swift; sourceTree = ""; }; @@ -340,6 +346,8 @@ 1929BBE0A534F2F6009D31BE /* AdvencedPref.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvencedPref.swift; sourceTree = ""; }; 1929BC2F05E9A5C0DB039739 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; 1929BCE3E156C06EDF1F2806 /* FileOutlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileOutlineView.swift; sourceTree = ""; }; + 1929BCF66D08CC02A9233A88 /* ShortcutsTableSubviews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsTableSubviews.swift; sourceTree = ""; }; + 1929BD256AA54518D37D6CBB /* ShortcutsPref.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsPref.swift; sourceTree = ""; }; 1929BD2CA8DD198A6BCDBCB7 /* ThemedTableSubviews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemedTableSubviews.swift; sourceTree = ""; }; 1929BD4149D5A25C82064DD8 /* UiRoot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UiRoot.swift; sourceTree = ""; }; 1929BD83A13BF133741766CC /* MainWindowReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowReducer.swift; sourceTree = ""; }; @@ -593,6 +601,7 @@ 1929B5E773BDB3B4EE9D00C1 /* Reducers */, 1929BFA93DC859DD76C46192 /* Middlewares */, 1929BFC0A5A9C6DB09BE1368 /* Types.swift */, + 1929B1E4B11A92D8DD171D93 /* ShortcutItem.swift */, ); name = UI; sourceTree = ""; @@ -631,6 +640,8 @@ 1929BBE0A534F2F6009D31BE /* AdvencedPref.swift */, 1929BB2AD21A10A0ECA66A5E /* ToolsPref.swift */, 1929B14A5949FB64C4B2646F /* KeysPref.swift */, + 1929BD256AA54518D37D6CBB /* ShortcutsPref.swift */, + 1929BCF66D08CC02A9233A88 /* ShortcutsTableSubviews.swift */, ); name = Preferences; sourceTree = ""; @@ -1147,6 +1158,9 @@ 1929B8F498D1E7C53F572CE2 /* KeysPref.swift in Sources */, 1929B5A2EE366F79ED32744C /* KeysPrefReducer.swift in Sources */, 1929BB67CAAD4F6CBD38DF0A /* RxRedux.swift in Sources */, + 1929B083D2176A9C615B8807 /* ShortcutsPref.swift in Sources */, + 1929B4DD667E1E57EFA2AA57 /* ShortcutItem.swift in Sources */, + 1929B02DAC94F0E47CFC8EA2 /* ShortcutsTableSubviews.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1178,6 +1192,8 @@ 1929B8E90A1378E494D481E7 /* PrefUtilsTest.swift in Sources */, 1929B20CE35B43BB1CE023BA /* Theme.swift in Sources */, 1929B9318D32146D58BB38EC /* AppKitCommons.swift in Sources */, + 1929B148AA06340F22533377 /* ShortcutItem.swift in Sources */, + 1929BA8E1A7C425E0E03EB40 /* ShortcutsTableSubviews.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/VimR/VimR/AppDelegate.swift b/VimR/VimR/AppDelegate.swift index 5d2965f4..a698c43a 100644 --- a/VimR/VimR/AppDelegate.swift +++ b/VimR/VimR/AppDelegate.swift @@ -9,6 +9,8 @@ import PureLayout import Sparkle import CocoaFontAwesome +let debugMenuItemIdentifier = NSUserInterfaceItemIdentifier("debug-menu-item") + @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate { @@ -60,12 +62,41 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele self.openNewMainWindowOnReactivation = initialAppState.openNewMainWindowOnReactivation self.useSnapshot = initialAppState.useSnapshotUpdate - let source = self.context.stateSource - self.uiRoot = UiRoot(source: source, emitter: self.context.actionEmitter, state: initialAppState) - super.init() NSUserNotificationCenter.default.delegate = self + // FIXME: GH-611: https://github.com/qvacua/vimr/issues/611 + // Check whether FontAwesome can be loaded. If not, show a warning. + // We don't know yet why this happens to some users. + DispatchQueue.main.async { + guard NSFont.fontAwesome(ofSize: 13) == nil else { + return + } + + let notification = NSUserNotification() + notification.title = "FontAwesome could not be loaded." + notification.subtitle = "Unfortunately we don't know yet what is causing this." + notification.informativeText = """ + We use the FontAwesome font for icons in the tools, e.g. the file browser. Those icons are now shown as ?. + You can track the progress on this issue at GitHub issue 611. + """ + NSUserNotificationCenter.default.deliver(notification) + } + } + + override func awakeFromNib() { + super.awakeFromNib() + + let source = self.context.stateSource + + // We want to build the menu items tree at some point, eg in the init() of + // ShortcutsPref. We have to do that *after* the MainMenu.xib is loaded. + // Therefore, we use optional var for the self.uiRoot. Ugly, but, well... + self.uiRoot = UiRoot( + source: source, + emitter: self.context.actionEmitter, + state: self.context.state + ) source .observeOn(MainScheduler.instance) @@ -86,30 +117,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele } }) .disposed(by: self.disposeBag) - - // FIXME: GH-611: https://github.com/qvacua/vimr/issues/611 - // Check whether FontAwesome can be loaded. If not, show a warning. - // We don't know yet why this happens to some users. - DispatchQueue.main.async { - guard NSFont.fontAwesome(ofSize: 13) == nil else { - return - } - - let notification = NSUserNotification() - notification.title = "FontAwesome could not be loaded." - notification.subtitle = "Unfortunately we don't know yet what is causing this." - notification.informativeText = """ - We use the FontAwesome font for icons in the tools, e.g. the file browser. Those icons are now shown as ?. - You can track the progress on this issue at GitHub issue 611. - """ - NSUserNotificationCenter.default.deliver(notification) - } } private let context: Context private let emit: (Action) -> Void - private let uiRoot: UiRoot + private var uiRoot: UiRoot? private var hasDirtyWindows = false private var hasMainWindows = false @@ -151,9 +164,12 @@ extension AppDelegate { func applicationDidFinishLaunching(_: Notification) { self.launching = false -#if DEBUG + #if DEBUG NSApp.mainMenu?.items.first { $0.identifier == debugMenuItemIdentifier }?.isHidden = false -#endif + + // GH-314 FIXME: Remove when finished. + self.showPrefWindow(self) + #endif } func applicationOpenUntitledFile(_ sender: NSApplication) -> Bool { @@ -186,7 +202,7 @@ extension AppDelegate { if alert.runModal() == .alertSecondButtonReturn { self.updateMainWindowTemplateBeforeQuitting() - self.uiRoot.prepareQuit() + self.uiRoot?.prepareQuit() return .terminateNow } @@ -195,7 +211,7 @@ extension AppDelegate { if self.hasMainWindows { self.updateMainWindowTemplateBeforeQuitting() - self.uiRoot.prepareQuit() + self.uiRoot?.prepareQuit() return .terminateNow } @@ -414,8 +430,6 @@ private enum VimRUrlAction: String { private let updater = SUUpdater() -private let debugMenuItemIdentifier = NSUserInterfaceItemIdentifier("debug-menu-item") - // Keep in sync with QueryParamKey in the `vimr` Python script. private let filePrefix = "file=" private let cwdPrefix = "cwd=" diff --git a/VimR/VimR/Base.lproj/MainMenu.xib b/VimR/VimR/Base.lproj/MainMenu.xib index 4100aad9..a38c959b 100644 --- a/VimR/VimR/Base.lproj/MainMenu.xib +++ b/VimR/VimR/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -73,45 +73,45 @@ - + - + - + - + - + - + - + - + @@ -123,39 +123,39 @@ - + - + - + - + - + - + - + @@ -171,17 +171,17 @@ - + - + - + @@ -190,7 +190,7 @@ - + @@ -203,25 +203,25 @@ - + - + - + - + @@ -233,19 +233,19 @@ - + - + - + @@ -276,7 +276,7 @@ - + diff --git a/VimR/VimR/PrefWindow.swift b/VimR/VimR/PrefWindow.swift index a3f98e29..4469fe96 100644 --- a/VimR/VimR/PrefWindow.swift +++ b/VimR/VimR/PrefWindow.swift @@ -30,6 +30,7 @@ class PrefWindow: NSObject, ToolsPref(source: source, emitter: emitter, state: state), AppearancePref(source: source, emitter: emitter, state: state), KeysPref(source: source, emitter: emitter, state: state), + ShortcutsPref(source: source, emitter: emitter, state: state), AdvancedPref(source: source, emitter: emitter, state: state), ] @@ -119,6 +120,11 @@ class PrefWindow: NSObject, paneContainer.autoPinEdge(.left, to: .right, of: categoryScrollView) self.currentPane = self.panes[0] + + // GH-314 FIXME: Remove when finished. + #if DEBUG + self.categoryView.selectRowIndexes(IndexSet(integer: 4), byExtendingSelection: false) + #endif } } diff --git a/VimR/VimR/ShortcutItem.swift b/VimR/VimR/ShortcutItem.swift new file mode 100644 index 00000000..deb890bd --- /dev/null +++ b/VimR/VimR/ShortcutItem.swift @@ -0,0 +1,46 @@ +/** + * Tae Won Ha - http://taewon.de - @hataewon + * See LICENSE + */ + +import Cocoa + +class ShortcutItem: NSObject, Comparable { + + static func <(lhs: ShortcutItem, rhs: ShortcutItem) -> Bool { + return lhs.title < rhs.title + } + + @objc dynamic var title: String + @objc dynamic var isLeaf: Bool + + @objc dynamic var childrenCount: Int { + return self.children?.count ?? -1 + } + + var isContainer: Bool { + return !self.isLeaf + } + + override var description: String { + return "" + } + + let item: NSMenuItem? + @objc dynamic var children: [ShortcutItem]? + + init( + title: String, + isLeaf: Bool, + item: NSMenuItem? + ) { + self.title = title + self.isLeaf = isLeaf + self.item = item + self.children = isLeaf ? nil : [] + } +} diff --git a/VimR/VimR/ShortcutsPref.swift b/VimR/VimR/ShortcutsPref.swift new file mode 100644 index 00000000..d0e04a62 --- /dev/null +++ b/VimR/VimR/ShortcutsPref.swift @@ -0,0 +1,223 @@ +/** + * Tae Won Ha - http://taewon.de - @hataewon + * See LICENSE + */ + +import Cocoa +import PureLayout +import RxSwift + +class DummyItem { + + let isGroup: Bool + let text: String + + init(_ text: String, _ isGroup: Bool) { + self.isGroup = isGroup + self.text = text + } +} + +class ShortcutsPref: PrefPane, + UiComponent, + NSOutlineViewDelegate { + + typealias StateType = AppState + + enum Action { + + case dummy + } + + @objc dynamic var content = [ShortcutItem]() + + override var displayName: String { + return "Shortcuts" + } + + override var pinToContainer: Bool { + return true + } + + required init( + source: Observable, + emitter: ActionEmitter, + state: StateType + ) { + self.emit = emitter.typedEmit() + + super.init(frame: .zero) + + self.addViews() + self.updateViews() + + self.initShortcutItems() + if let children = self.shortcutItemsRoot.children { + self.content.append(contentsOf: children) + } + + source + .observeOn(MainScheduler.instance) + .subscribe(onNext: { state in + }) + .disposed(by: self.disposeBag) + + initBindings() + self.shortcutList.expandItem(nil, expandChildren: true) + } + + private let emit: (Action) -> Void + private let disposeBag = DisposeBag() + + private let shortcutList = NSOutlineView.standardOutlineView() + private let shortcutScrollView = NSScrollView.standardScrollView() + + private let treeController = NSTreeController() + private let shortcutItemsRoot = ShortcutItem( + title: "root", isLeaf: false, item: nil + ) + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func initBindings() { + self.treeController.childrenKeyPath = "children" + self.treeController.leafKeyPath = "isLeaf" + self.treeController.countKeyPath = "childrenCount" + self.treeController.objectClass = ShortcutItem.self + self.treeController.avoidsEmptySelection = false + self.treeController.preservesSelection = true + self.treeController.sortDescriptors = [ + NSSortDescriptor(key: "title", ascending: true) + ] + self.treeController.bind(.contentArray, to: self, withKeyPath: "content") + self.shortcutList.bind(.content, + to: self.treeController, + withKeyPath: "arrangedObjects") + self.shortcutList.bind(.selectionIndexPaths, + to: self.treeController, + withKeyPath: "selectionIndexPaths") + } + + private func initShortcutItems() { + guard let mainMenu = NSApplication.shared.mainMenu else { return } + let firstLevel = mainMenu.items + .suffix(from: 1) + .filter { $0.identifier != debugMenuItemIdentifier } + + var queue = firstLevel.map { + ( + parent: self.shortcutItemsRoot, + shortcutItem: ShortcutItem(title: $0.title, isLeaf: false, item: $0) + ) + } + while (!queue.isEmpty) { + guard let entry = queue.popLast() else { break } + + if !entry.shortcutItem.isLeaf + || entry.shortcutItem.item? + .identifier? + .rawValue + .hasPrefix("com.qvacua.vimr.menuitems.") == true { + + entry.parent.children?.append(entry.shortcutItem) + } + + if entry.shortcutItem.isContainer, + let childMenuItems = entry.shortcutItem.item?.submenu?.items { + + let shortcutChildItems = childMenuItems + .filter { !$0.title.isEmpty } + .map { menuItem in + ( + parent: entry.shortcutItem, + shortcutItem: ShortcutItem(title: menuItem.title, + isLeaf: !menuItem.hasSubmenu, + item: menuItem) + ) + } + queue.append(contentsOf: shortcutChildItems) + } + } + } + + private func updateViews() { + } + + private func addViews() { + let paneTitle = self.paneTitleTextField(title: "Shortcuts") + + let shortcutList = self.shortcutList + shortcutList.delegate = self + + let shortcutScrollView = self.shortcutScrollView + shortcutScrollView.documentView = shortcutList + + self.addSubview(paneTitle) + self.addSubview(shortcutScrollView) + + paneTitle.autoPinEdge(toSuperviewEdge: .top, withInset: 18) + paneTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18) + paneTitle.autoPinEdge( + toSuperviewEdge: .right, withInset: 18, relation: .greaterThanOrEqual + ) + + shortcutScrollView.autoPinEdge( + .top, to: .bottom, of: paneTitle, withOffset: 18 + ) + shortcutScrollView.autoPinEdge(.left, to: .left, of: paneTitle) + shortcutScrollView.autoPinEdge(toSuperviewEdge: .right, withInset: 18) + shortcutScrollView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 18) + } +} + +// MARK: - Actions +extension ShortcutsPref { + + @objc func isLeftOptionMetaAction(_ sender: NSButton) { + self.emit(.dummy) + } +} + +// MARK: - NSOutlineViewDelegate +extension ShortcutsPref { + + func outlineView( + _ outlineView: NSOutlineView, + rowViewForItem item: Any + ) -> NSTableRowView? { + let view = self.shortcutList.makeView( + withIdentifier: NSUserInterfaceItemIdentifier("shortcut-row-view"), + owner: self + ) as? ShortcutTableRow + ?? ShortcutTableRow(withIdentifier: "shortcut-row-view") + + return view + } + + func outlineView( + _: NSOutlineView, + viewFor tableColumn: NSTableColumn?, + item: Any + ) -> NSView? { + let cellView = self.shortcutList.makeView( + withIdentifier: NSUserInterfaceItemIdentifier("shortcut-cell-view"), + owner: self + ) as? ShortcutTableCell + ?? ShortcutTableCell(withIdentifier: "shortcut-cell-view") + + let repObj = (item as? NSTreeNode)?.representedObject + guard let item = repObj as? ShortcutItem else { + return nil + } + + cellView.text = item.title + + return cellView + } + + func outlineView(_: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat { + return 24 + } +} diff --git a/VimR/VimR/ShortcutsTableSubviews.swift b/VimR/VimR/ShortcutsTableSubviews.swift new file mode 100644 index 00000000..0c207e4c --- /dev/null +++ b/VimR/VimR/ShortcutsTableSubviews.swift @@ -0,0 +1,87 @@ +/** + * Tae Won Ha - http://taewon.de - @hataewon + * See LICENSE + */ + +import Cocoa + +class ShortcutTableRow: NSTableRowView { + + init(withIdentifier identifier: String) { + super.init(frame: .zero) + self.identifier = NSUserInterfaceItemIdentifier(identifier) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class ShortcutTableCell: NSTableCellView { + + static let font = NSFont.systemFont(ofSize: 13) + + var isDir = false + + var attributedText: NSAttributedString { + get { + return self.textField!.attributedStringValue + } + + set { + self.textField?.attributedStringValue = newValue + self.addTextField() + } + } + + var text: String { + get { + return self.textField!.stringValue + } + + set { + self.textField?.stringValue = newValue + self.addTextField() + } + } + + init(withIdentifier identifier: String) { + super.init(frame: .zero) + + self.identifier = NSUserInterfaceItemIdentifier(identifier) + + self.textField = self._textField + + let textField = self._textField + textField.font = ShortcutTableCell.font + textField.isBordered = false + textField.isBezeled = false + textField.allowsEditingTextAttributes = false + textField.isEditable = false + textField.usesSingleLineMode = true + textField.drawsBackground = false + } + + func reset() -> ShortcutTableCell { + self.text = "" + self.removeAllSubviews() + return self + } + + private func addTextField() { + let textField = self._textField + + textField.removeFromSuperview() + self.addSubview(textField) + + textField.autoPinEdgesToSuperviewEdges( + with: NSEdgeInsets(top: 2, left: 4, bottom: 2, right: 2) + ) + } + + private let _textField = NSTextField(forAutoLayout: ()) + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +}