From b6492c63a653ffd79649033f7117f83feedfd2c3 Mon Sep 17 00:00:00 2001 From: Jon Sterling Date: Fri, 7 Aug 2026 08:06:40 +0100 Subject: [PATCH] Shared inspector panel instance with swappable selected nodes controller --- .../DocumentWindowController+Toolbar.swift | 87 +++++++ .../DocumentWindowController.swift | 157 +++++++++++++ .../EditorViewController.swift | 0 .../NavigationZipper.swift | 0 Dissertation/App/EditorWindowController.swift | 221 ------------------ .../InspectorPanelController.swift | 45 ++++ .../InspectorViewController.swift | 27 +-- .../App/InspectorPanelController.swift | 23 -- .../NSTableCellView+SourceList.swift | 0 .../Source List}/OutlineViewController.swift | 0 .../SourceListOutlineView.swift | 0 .../SourceListTableCellView.swift | 0 .../SourceListViewController.swift | 0 .../ViewBasedOutlineViewController.swift | 0 Dissertation/{App => Model}/Document.swift | 4 +- .../DocumentTreeController.swift | 0 16 files changed, 301 insertions(+), 263 deletions(-) create mode 100644 Dissertation/App/Document Window/DocumentWindowController+Toolbar.swift create mode 100644 Dissertation/App/Document Window/DocumentWindowController.swift rename Dissertation/App/{ => Document Window}/EditorViewController.swift (100%) rename Dissertation/App/{ => Document Window}/NavigationZipper.swift (100%) delete mode 100644 Dissertation/App/EditorWindowController.swift create mode 100644 Dissertation/App/Inspector Panel/InspectorPanelController.swift rename Dissertation/App/{ => Inspector Panel}/InspectorViewController.swift (85%) delete mode 100644 Dissertation/App/InspectorPanelController.swift rename Dissertation/{Kit => App/Source List}/NSTableCellView+SourceList.swift (100%) rename Dissertation/{Kit => App/Source List}/OutlineViewController.swift (100%) rename Dissertation/App/{ => Source List}/SourceListOutlineView.swift (100%) rename Dissertation/App/{ => Source List}/SourceListTableCellView.swift (100%) rename Dissertation/App/{ => Source List}/SourceListViewController.swift (100%) rename Dissertation/{Kit => App/Source List}/ViewBasedOutlineViewController.swift (100%) rename Dissertation/{App => Model}/Document.swift (93%) rename Dissertation/{App => Model}/DocumentTreeController.swift (100%) diff --git a/Dissertation/App/Document Window/DocumentWindowController+Toolbar.swift b/Dissertation/App/Document Window/DocumentWindowController+Toolbar.swift new file mode 100644 index 0000000..c54fa86 --- /dev/null +++ b/Dissertation/App/Document Window/DocumentWindowController+Toolbar.swift @@ -0,0 +1,87 @@ +import AppKit + +extension NSToolbarItem.Identifier { + fileprivate static let toggleInspectorPanel: NSToolbarItem.Identifier = NSToolbarItem.Identifier("toggleInspectorPanel") + fileprivate static let addBlock: NSToolbarItem.Identifier = NSToolbarItem.Identifier("addBlock") + fileprivate static let jump: NSToolbarItem.Identifier = NSToolbarItem.Identifier("jump") + fileprivate static let backAndForward: NSToolbarItem.Identifier = NSToolbarItem.Identifier("backAndForward") + fileprivate static let back: NSToolbarItem.Identifier = NSToolbarItem.Identifier("back") + fileprivate static let forward: NSToolbarItem.Identifier = NSToolbarItem.Identifier("forward") +} + +extension DocumentWindowController: NSToolbarDelegate { + func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + [.flexibleSpace, .space, .toggleSidebar, .sidebarTrackingSeparator, .toggleInspectorPanel, .addBlock, .backAndForward] + } + + func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + [.flexibleSpace, .toggleSidebar, .sidebarTrackingSeparator, .backAndForward, .space, .flexibleSpace, .addBlock, .flexibleSpace, .toggleInspectorPanel] + } + + func toolbar( + _ toolbar: NSToolbar, + itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, + willBeInsertedIntoToolbar flag: Bool + ) -> NSToolbarItem? { + switch itemIdentifier { + case .toggleInspectorPanel: + let item = NSToolbarItem(itemIdentifier: itemIdentifier) + item.label = "Inspector" + item.paletteLabel = "Inspector" + item.image = NSImage(systemSymbolName: "info.circle.fill", accessibilityDescription: nil) + item.action = #selector(toggleInspectorPanel(_:)) + return item + case .addBlock: + let item = NSToolbarItem(itemIdentifier: itemIdentifier) + item.label = "Add" + item.paletteLabel = "Add" + item.image = NSImage(systemSymbolName: "document.badge.plus", accessibilityDescription: nil) + item.action = #selector(addBlock(_:)) + return item + case .backAndForward: + let group = NSToolbarItemGroup(itemIdentifier: itemIdentifier) + group.isNavigational = true + group.title = "Back/Forward" + group.paletteLabel = "Back/Forward" + + let backItem = NSToolbarItem(itemIdentifier: .back) + backItem.label = "Back" + + let forwardItem = NSToolbarItem(itemIdentifier: .forward) + forwardItem.label = "Forward" + + group.subitems = [backItem, forwardItem] + + backForwardSegmentedControl.setImage(NSImage(named: NSImage.goLeftTemplateName), forSegment: 0) + backForwardSegmentedControl.setImage(NSImage(named: NSImage.goRightTemplateName), forSegment: 1) + backForwardSegmentedControl.setWidth(40, forSegment: 0) + backForwardSegmentedControl.setWidth(40, forSegment: 1) + backForwardSegmentedControl.target = self + backForwardSegmentedControl.action = #selector(navigate(_:)) + + group.view = backForwardSegmentedControl + + return group + default: + return nil + } + } + + @objc func navigate(_ sender: NSSegmentedControl) { + switch sender.selectedSegment { + case 0: + navigationZipper.navigateBack(currentState: treeController.selectedObjects) + case 1: + navigationZipper.navigateForward(currentState: treeController.selectedObjects) + default: () + } + } + + @objc func addBlock(_ sender: Any) { + + } + + @objc func toggleInspectorPanel(_ sender: Any) { + InspectorPanelController.sharedInstance.toggle() + } +} diff --git a/Dissertation/App/Document Window/DocumentWindowController.swift b/Dissertation/App/Document Window/DocumentWindowController.swift new file mode 100644 index 0000000..9d590b8 --- /dev/null +++ b/Dissertation/App/Document Window/DocumentWindowController.swift @@ -0,0 +1,157 @@ +import AppKit + +class DocumentWindowController: NSWindowController { + let treeController: DocumentTreeController + let selectedNodesController: NSArrayController = NSArrayController() + var observations: [NSKeyValueObservation] = [] + let backForwardSegmentedControl = NSSegmentedControl() + let editorViewController: EditorViewController + let navigationZipper: NavigationZipper<[Any]> = NavigationZipper() + + deinit { + NotificationCenter.default.removeObserver(self, name: NSTextView.didChangeSelectionNotification, object: nil) + NotificationCenter.default.removeObserver(self, name: NSOutlineView.selectionDidChangeNotification, object: nil) + } + + required init(treeController: DocumentTreeController) { + self.treeController = treeController + + backForwardSegmentedControl.segmentStyle = .texturedRounded + backForwardSegmentedControl.trackingMode = .momentary + backForwardSegmentedControl.segmentCount = 2 + + let splitViewController = NSSplitViewController() + let sourceListViewController = SourceListViewController(treeController: treeController) + self.editorViewController = EditorViewController(treeController: treeController) + + let sidebarItem = NSSplitViewItem(sidebarWithViewController: sourceListViewController) + sidebarItem.canCollapse = true + sidebarItem.minimumThickness = 180 + sidebarItem.maximumThickness = 220 + + let contentItem = NSSplitViewItem(viewController: editorViewController) + contentItem.minimumThickness = 300 + + splitViewController.splitViewItems = [sidebarItem, contentItem] + + let window = NSWindow(contentViewController: splitViewController) + + super.init(window: window) + + navigationZipper.delegate = self + + let toolbar = NSToolbar(identifier: "EditorToolbar") + toolbar.displayMode = .iconOnly + toolbar.allowsUserCustomization = true + toolbar.delegate = self + + window.styleMask.insert([.fullSizeContentView, .unifiedTitleAndToolbar]) + window.setContentSize(NSSize(width: 800, height: 600)) + window.toolbar = toolbar + window.delegate = self +// +// let treeSelectionObserver = treeController.observe(\.selection) { [weak self] treeController, _ in +// self?.selectedNodesController.content = treeController.selectedObjects +// } +// +// observations.append(treeSelectionObserver) + + NotificationCenter.default.addObserver( + forName: NSOutlineView.selectionDidChangeNotification, + object: sourceListViewController.outlineView, + queue: nil + ) { [weak self] notification in + self?.sourceListSelectionDidChange(notification: notification) + } + + NotificationCenter.default.addObserver( + forName: NSTextView.didChangeSelectionNotification, + object: editorViewController.textView, + queue: nil + ) { [weak self] notification in + self?.textViewSelectionDidChange(notification: notification) + } + } + + private func sourceList(_ outlineView: NSOutlineView, _ indices: NSIndexSet) -> [Any] { + var representedObjects: [Any] = [] + for index in indices { + guard let treeNode = outlineView.item(atRow: index) as? NSTreeNode, let representedObject = treeNode.representedObject else { continue } + representedObjects.append(representedObject) + } + return representedObjects + } + + private func sourceListSelectionDidChange(notification: Notification) { + guard + !self.navigationZipper.isNavigating, + let outlineView = notification.object as? NSOutlineView, + let userInfo = notification.userInfo, + let previousSelectionIndices = userInfo[AnyHashable("NSTableViewPreviousRowSelectionUserInfoKey")] as? NSIndexSet, + let currentSelectionIndices = userInfo[AnyHashable("NSTableViewCurrentRowSelectionUserInfoKey")] as? NSIndexSet + else { return } + + let previousSelectedObjects = sourceList(outlineView, previousSelectionIndices) + navigationZipper.recordNavigation(from: previousSelectedObjects) + selectedNodesController.content = sourceList(outlineView, currentSelectionIndices) + } + + private func textViewSelectionDidChange(notification: Notification) { + guard let textView = notification.object as? CodeTextView else { return } + guard let firstBlock = treeController.visibleDocumentNodes.first else { return } + guard let blockRanges = textView.textStorage?.blockRanges(firstBlockID: firstBlock.id) else { return } + guard !editorViewController.isProgrammaticallyUpdatingText else { return } + + let selectedRanges = textView.selectedRanges + guard !selectedRanges.isEmpty else { + selectedNodesController.content = treeController.selectedObjects + return + } + + var selectedBlockIDs: Set = [] + + for selectedRangeObject in selectedRanges { + let selectedRange = selectedRangeObject.rangeValue + for (blockID, blockRange) in blockRanges { + if blockRange.intersection(selectedRange) != nil || selectedRange.lowerBound == blockRange.upperBound { + selectedBlockIDs.insert(blockID) + break + } + } + } + + var selectedBlocks: [DocumentNode] = [] + for block in treeController.visibleDocumentNodes { + if selectedBlockIDs.contains(block.id) { + selectedBlocks.append(block) + } + } + + selectedNodesController.content = selectedBlocks + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension DocumentWindowController: NSWindowDelegate { + func windowDidBecomeMain(_ notification: Notification) { + InspectorPanelController.sharedInstance.selectedNodesController = selectedNodesController + } +} + +extension DocumentWindowController: NavigationZipperDelegate<[Any]> { + func updateNavigationButtonEnabledState(zipper: NavigationZipper<[Any]>) { + backForwardSegmentedControl.setEnabled(zipper.canNavigateBack, forSegment: 0) + backForwardSegmentedControl.setEnabled(zipper.canNavigateForward, forSegment: 1) + } + + func navigationZipperEnabledStateDidChange(_ zipper: NavigationZipper<[Any]>) { + updateNavigationButtonEnabledState(zipper: zipper) + } + + func navigationZipper(_ zipper: NavigationZipper<[Any]>, navigateTo state: [Any]) { + treeController.setSelectedObjects(state) + } +} diff --git a/Dissertation/App/EditorViewController.swift b/Dissertation/App/Document Window/EditorViewController.swift similarity index 100% rename from Dissertation/App/EditorViewController.swift rename to Dissertation/App/Document Window/EditorViewController.swift diff --git a/Dissertation/App/NavigationZipper.swift b/Dissertation/App/Document Window/NavigationZipper.swift similarity index 100% rename from Dissertation/App/NavigationZipper.swift rename to Dissertation/App/Document Window/NavigationZipper.swift diff --git a/Dissertation/App/EditorWindowController.swift b/Dissertation/App/EditorWindowController.swift deleted file mode 100644 index f903d56..0000000 --- a/Dissertation/App/EditorWindowController.swift +++ /dev/null @@ -1,221 +0,0 @@ -import AppKit - -class EditorWindowController: NSWindowController { - let inspectorPanelController: InspectorPanelController - let treeController: DocumentTreeController - let selectedNodesController: NSArrayController = NSArrayController() - var observations: [NSKeyValueObservation] = [] - let backForwardSegmentedControl = NSSegmentedControl() - let editorViewController: EditorViewController - private let navigationZipper: NavigationZipper<[Any]> = NavigationZipper() - - deinit { - NotificationCenter.default.removeObserver(self, name: NSTextView.didChangeSelectionNotification, object: nil) - NotificationCenter.default.removeObserver(self, name: NSOutlineView.selectionDidChangeNotification, object: nil) - } - - required init(treeController: DocumentTreeController) { - self.treeController = treeController - self.inspectorPanelController = InspectorPanelController(selectedNodesController: selectedNodesController) - - backForwardSegmentedControl.segmentStyle = .texturedRounded - backForwardSegmentedControl.trackingMode = .momentary - backForwardSegmentedControl.segmentCount = 2 - - let splitViewController = NSSplitViewController() - let sourceListViewController = SourceListViewController(treeController: treeController) - self.editorViewController = EditorViewController(treeController: treeController) - - let sidebarItem = NSSplitViewItem(sidebarWithViewController: sourceListViewController) - sidebarItem.canCollapse = true - sidebarItem.minimumThickness = 180 - sidebarItem.maximumThickness = 220 - - let contentItem = NSSplitViewItem(viewController: editorViewController) - contentItem.minimumThickness = 300 - - splitViewController.splitViewItems = [sidebarItem, contentItem] - - let window = NSWindow(contentViewController: splitViewController) - - super.init(window: window) - - navigationZipper.delegate = self - - let toolbar = NSToolbar(identifier: "EditorToolbar") - toolbar.displayMode = .iconOnly - toolbar.allowsUserCustomization = true - toolbar.delegate = self - - window.styleMask.insert([.fullSizeContentView, .unifiedTitleAndToolbar]) - window.setContentSize(NSSize(width: 800, height: 600)) - window.toolbar = toolbar - - let treeSelectionObserver = treeController.observe(\.selection) { [weak self] treeController, _ in - self?.selectedNodesController.content = treeController.selectedObjects - } - - observations.append(treeSelectionObserver) - - NotificationCenter.default.addObserver(forName: NSOutlineView.selectionDidChangeNotification, object: sourceListViewController.outlineView, queue: nil) { [weak self] notification in - guard - let self, !self.navigationZipper.isNavigating, - let outlineView = notification.object as? NSOutlineView, - let userInfo = notification.userInfo, - let previousSelectionIndices = userInfo[AnyHashable("NSTableViewPreviousRowSelectionUserInfoKey")] as? NSIndexSet - else { return } - - var previousSelectedObjects: [Any] = [] - for index in previousSelectionIndices { - guard let treeNode = outlineView.item(atRow: index) as? NSTreeNode, let representedObject = treeNode.representedObject else { continue } - previousSelectedObjects.append(representedObject) - } - - self.navigationZipper.recordNavigation(from: previousSelectedObjects) - } - - NotificationCenter.default.addObserver(forName: NSTextView.didChangeSelectionNotification, object: editorViewController.textView, queue: nil) { [weak self] notification in - guard let textView = notification.object as? CodeTextView else { return } - guard let self else { return } - guard let firstBlock = self.treeController.visibleDocumentNodes.first else { return } - guard let blockRanges = textView.textStorage?.blockRanges(firstBlockID: firstBlock.id) else { return } - guard !self.editorViewController.isProgrammaticallyUpdatingText else { return } - - let selectedRanges = textView.selectedRanges - if selectedRanges.isEmpty { - self.selectedNodesController.content = treeController.selectedObjects - } - - var selectedBlockIDs: Set = [] - - for selectedRangeObject in selectedRanges { - let selectedRange = selectedRangeObject.rangeValue - for (blockID, blockRange) in blockRanges { - if blockRange.intersection(selectedRange) != nil || selectedRange.lowerBound == blockRange.upperBound { - selectedBlockIDs.insert(blockID) - break - } - } - } - - var selectedBlocks: [DocumentNode] = [] - for block in self.treeController.visibleDocumentNodes { - if selectedBlockIDs.contains(block.id) { - selectedBlocks.append(block) - } - } - - self.selectedNodesController.content = selectedBlocks - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -extension NSToolbarItem.Identifier { - fileprivate static let toggleInspectorPanel: NSToolbarItem.Identifier = NSToolbarItem.Identifier("toggleInspectorPanel") - fileprivate static let addBlock: NSToolbarItem.Identifier = NSToolbarItem.Identifier("addBlock") - fileprivate static let jump: NSToolbarItem.Identifier = NSToolbarItem.Identifier("jump") - fileprivate static let backAndForward: NSToolbarItem.Identifier = NSToolbarItem.Identifier("backAndForward") - fileprivate static let back: NSToolbarItem.Identifier = NSToolbarItem.Identifier("back") - fileprivate static let forward: NSToolbarItem.Identifier = NSToolbarItem.Identifier("forward") -} - -extension EditorWindowController: NSToolbarDelegate { - func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - [.flexibleSpace, .space, .toggleSidebar, .sidebarTrackingSeparator, .toggleInspectorPanel, .addBlock, .backAndForward] - } - - func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - [.flexibleSpace, .toggleSidebar, .sidebarTrackingSeparator, .backAndForward, .space, .flexibleSpace, .addBlock, .flexibleSpace, .toggleInspectorPanel] - } - - func toolbar( - _ toolbar: NSToolbar, - itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, - willBeInsertedIntoToolbar flag: Bool - ) -> NSToolbarItem? { - switch itemIdentifier { - case .toggleInspectorPanel: - let item = NSToolbarItem(itemIdentifier: itemIdentifier) - item.label = "Inspector" - item.paletteLabel = "Inspector" - item.image = NSImage(systemSymbolName: "info.circle.fill", accessibilityDescription: nil) - item.action = #selector(toggleInspectorPanel(_:)) - return item - case .addBlock: - let item = NSToolbarItem(itemIdentifier: itemIdentifier) - item.label = "Add" - item.paletteLabel = "Add" - item.image = NSImage(systemSymbolName: "document.badge.plus", accessibilityDescription: nil) - item.action = #selector(addBlock(_:)) - return item - case .backAndForward: - let group = NSToolbarItemGroup(itemIdentifier: itemIdentifier) - group.isNavigational = true - group.title = "Back/Forward" - group.paletteLabel = "Back/Forward" - - let backItem = NSToolbarItem(itemIdentifier: .back) - backItem.label = "Back" - - let forwardItem = NSToolbarItem(itemIdentifier: .forward) - forwardItem.label = "Forward" - - group.subitems = [backItem, forwardItem] - - backForwardSegmentedControl.setImage(NSImage(named: NSImage.goLeftTemplateName), forSegment: 0) - backForwardSegmentedControl.setImage(NSImage(named: NSImage.goRightTemplateName), forSegment: 1) - backForwardSegmentedControl.setWidth(40, forSegment: 0) - backForwardSegmentedControl.setWidth(40, forSegment: 1) - backForwardSegmentedControl.target = self - backForwardSegmentedControl.action = #selector(navigate(_:)) - - group.view = backForwardSegmentedControl - - return group - default: - return nil - } - } - - @objc func navigate(_ sender: NSSegmentedControl) { - switch sender.selectedSegment { - case 0: - navigationZipper.navigateBack(currentState: treeController.selectedObjects) - case 1: - navigationZipper.navigateForward(currentState: treeController.selectedObjects) - default: () - } - } - - @objc func addBlock(_ sender: Any) { - - } - - @objc func toggleInspectorPanel(_ sender: Any) { - guard let panel = inspectorPanelController.window else { return } - if panel.isVisible { - panel.close() - } else { - panel.orderFront(nil) - } - } -} - -extension EditorWindowController: NavigationZipperDelegate<[Any]> { - func updateNavigationButtonEnabledState(zipper: NavigationZipper<[Any]>) { - backForwardSegmentedControl.setEnabled(zipper.canNavigateBack, forSegment: 0) - backForwardSegmentedControl.setEnabled(zipper.canNavigateForward, forSegment: 1) - } - - func navigationZipperEnabledStateDidChange(_ zipper: NavigationZipper<[Any]>) { - updateNavigationButtonEnabledState(zipper: zipper) - } - - func navigationZipper(_ zipper: NavigationZipper<[Any]>, navigateTo state: [Any]) { - treeController.setSelectedObjects(state) - } -} diff --git a/Dissertation/App/Inspector Panel/InspectorPanelController.swift b/Dissertation/App/Inspector Panel/InspectorPanelController.swift new file mode 100644 index 0000000..f05322f --- /dev/null +++ b/Dissertation/App/Inspector Panel/InspectorPanelController.swift @@ -0,0 +1,45 @@ +import AppKit + +class InspectorPanelController: NSWindowController { + static let sharedInstance: InspectorPanelController = InspectorPanelController() + + var inspectorViewController: InspectorViewController? { + contentViewController as? InspectorViewController + } + + var selectedNodesController: NSArrayController? { + get { inspectorViewController?.selectedNodesController } + set { inspectorViewController?.selectedNodesController = newValue } + } + + init() { + let inspectorViewController = InspectorViewController() + + let inspectorPanel = NSPanel(contentViewController: inspectorViewController) + inspectorPanel.setContentSize(inspectorViewController.preferredMinimumSize) + inspectorPanel.contentMaxSize = inspectorViewController.preferredMaximumSize + inspectorPanel.contentMinSize = inspectorViewController.preferredMinimumSize + + inspectorPanel.styleMask = [.titled, .closable, .utilityWindow, .resizable] + inspectorPanel.isFloatingPanel = true + inspectorPanel.level = .floating + inspectorPanel.becomesKeyOnlyIfNeeded = true + + inspectorPanel.title = "Inspector" + + super.init(window: inspectorPanel) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func toggle() { + guard let window else { return } + if window.isVisible { + window.close() + } else { + window.orderFront(nil) + } + } +} diff --git a/Dissertation/App/InspectorViewController.swift b/Dissertation/App/Inspector Panel/InspectorViewController.swift similarity index 85% rename from Dissertation/App/InspectorViewController.swift rename to Dissertation/App/Inspector Panel/InspectorViewController.swift index 22aa306..958ea0e 100644 --- a/Dissertation/App/InspectorViewController.swift +++ b/Dissertation/App/Inspector Panel/InspectorViewController.swift @@ -1,15 +1,14 @@ import AppKit class InspectorViewController: NSViewController { - let selectedNodesController: NSArrayController + @objc dynamic var selectedNodesController: NSArrayController? @objc dynamic var wordCount: NSNumber? = nil @objc dynamic var charCount: NSNumber? = nil - init(selectedNodesController: NSArrayController) { - self.selectedNodesController = selectedNodesController + init() { super.init(nibName: nil, bundle: nil) } - + override var preferredMinimumSize: NSSize { NSSize(width: 225, height: 200) } @@ -83,11 +82,7 @@ class InspectorViewController: NSViewController { return selectedObjects.count != 1 } } - - deinit { - selectedNodesController.removeObserver(self, forKeyPath: "arrangedObjects.content") - } - + private var keyValueObservationContext = 0 override func loadView() { @@ -97,7 +92,7 @@ class InspectorViewController: NSViewController { view.additionalSafeAreaInsets = NSEdgeInsets(top: 14, left: 20, bottom: 14, right: 20) let configurationGridView = Self.formGridView() - configurationGridView.bind(.hidden, to: selectedNodesController, withKeyPath: "content", options: [.valueTransformer: SelectedNodesIsNotSingletonTransformer()]) + configurationGridView.bind(.hidden, to: self, withKeyPath: "selectedNodesController.content", options: [.valueTransformer: SelectedNodesIsNotSingletonTransformer()]) let informationGridView = Self.formGridView() @@ -107,10 +102,10 @@ class InspectorViewController: NSViewController { charCountField.bind(.value, to: self, withKeyPath: #keyPath(charCount), options: [.nullPlaceholder: "No selection"]) let titleField = Self.formTextField() - titleField.bind(.value, to: selectedNodesController, withKeyPath: "selection.title", options: [.nullPlaceholder: "Untitled", .continuouslyUpdatesValue: true]) + titleField.bind(.value, to: self, withKeyPath: "selectedNodesController.selection.title", options: [.nullPlaceholder: "Untitled", .continuouslyUpdatesValue: true]) let displaysSectionHeadingField = NSButton.init(checkboxWithTitle: "Displays section heading", target: nil, action: nil) - displaysSectionHeadingField.bind(.value, to: selectedNodesController, withKeyPath: "selection.displaysSectionHeading", options: [.continuouslyUpdatesValue: true]) + displaysSectionHeadingField.bind(.value, to: self, withKeyPath: "selectedNodesController.selection.displaysSectionHeading", options: [.continuouslyUpdatesValue: true]) let configurationHeaderRow = configurationGridView.addRow(with: [Self.headingLabel("Configuration:")]) configurationHeaderRow.cell(at: 0).xPlacement = .leading @@ -161,7 +156,7 @@ class InspectorViewController: NSViewController { stackView.heightAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.heightAnchor), ]) - selectedNodesController.addObserver(self, forKeyPath: "arrangedObjects.content", context: &keyValueObservationContext) + self.addObserver(self, forKeyPath: "selectedNodesController.arrangedObjects.content", context: &keyValueObservationContext) } @objc override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { @@ -169,15 +164,13 @@ class InspectorViewController: NSViewController { return super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) } - guard let object = object as? NSObject else { return } - - if object == selectedNodesController { + if keyPath == "selectedNodesController.arrangedObjects.content" { updateStatistics() } } func updateStatistics() { - guard let selectedObjects = selectedNodesController.content as? [DocumentNode], !selectedObjects.isEmpty else { + guard let selectedObjects = selectedNodesController?.content as? [DocumentNode], !selectedObjects.isEmpty else { wordCount = nil charCount = nil return diff --git a/Dissertation/App/InspectorPanelController.swift b/Dissertation/App/InspectorPanelController.swift deleted file mode 100644 index 2bb7ad2..0000000 --- a/Dissertation/App/InspectorPanelController.swift +++ /dev/null @@ -1,23 +0,0 @@ -import AppKit - -class InspectorPanelController: NSWindowController { - required init(selectedNodesController: NSArrayController) { - let viewController = InspectorViewController(selectedNodesController: selectedNodesController) - let panel = NSPanel(contentViewController: viewController) - panel.setContentSize(viewController.preferredMinimumSize) - panel.contentMaxSize = viewController.preferredMaximumSize - panel.contentMinSize = viewController.preferredMinimumSize - - panel.styleMask = [.titled, .closable, .utilityWindow, .resizable] - panel.isFloatingPanel = true - panel.level = .floating - panel.becomesKeyOnlyIfNeeded = true - - panel.title = "Inspector" - super.init(window: panel) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/Dissertation/Kit/NSTableCellView+SourceList.swift b/Dissertation/App/Source List/NSTableCellView+SourceList.swift similarity index 100% rename from Dissertation/Kit/NSTableCellView+SourceList.swift rename to Dissertation/App/Source List/NSTableCellView+SourceList.swift diff --git a/Dissertation/Kit/OutlineViewController.swift b/Dissertation/App/Source List/OutlineViewController.swift similarity index 100% rename from Dissertation/Kit/OutlineViewController.swift rename to Dissertation/App/Source List/OutlineViewController.swift diff --git a/Dissertation/App/SourceListOutlineView.swift b/Dissertation/App/Source List/SourceListOutlineView.swift similarity index 100% rename from Dissertation/App/SourceListOutlineView.swift rename to Dissertation/App/Source List/SourceListOutlineView.swift diff --git a/Dissertation/App/SourceListTableCellView.swift b/Dissertation/App/Source List/SourceListTableCellView.swift similarity index 100% rename from Dissertation/App/SourceListTableCellView.swift rename to Dissertation/App/Source List/SourceListTableCellView.swift diff --git a/Dissertation/App/SourceListViewController.swift b/Dissertation/App/Source List/SourceListViewController.swift similarity index 100% rename from Dissertation/App/SourceListViewController.swift rename to Dissertation/App/Source List/SourceListViewController.swift diff --git a/Dissertation/Kit/ViewBasedOutlineViewController.swift b/Dissertation/App/Source List/ViewBasedOutlineViewController.swift similarity index 100% rename from Dissertation/Kit/ViewBasedOutlineViewController.swift rename to Dissertation/App/Source List/ViewBasedOutlineViewController.swift diff --git a/Dissertation/App/Document.swift b/Dissertation/Model/Document.swift similarity index 93% rename from Dissertation/App/Document.swift rename to Dissertation/Model/Document.swift index debc7b9..34950ed 100644 --- a/Dissertation/App/Document.swift +++ b/Dissertation/Model/Document.swift @@ -48,13 +48,13 @@ class Document: NSDocument { ) treeController.setSelectionIndexPath([]) - let windowController = EditorWindowController(treeController: treeController) + let windowController = DocumentWindowController(treeController: treeController) addWindowController(windowController) if let window = windowController.window { window.setFrameAutosaveName("windowFrame") window.makeKeyAndOrderFront(nil) - windowController.inspectorPanelController.window?.orderFront(nil) + InspectorPanelController.sharedInstance.window?.orderFront(nil) } } diff --git a/Dissertation/App/DocumentTreeController.swift b/Dissertation/Model/DocumentTreeController.swift similarity index 100% rename from Dissertation/App/DocumentTreeController.swift rename to Dissertation/Model/DocumentTreeController.swift -- knotmirror 0.0.1