From 43e58d8af3635a31e2ff47bf28b4c655b352afcf Mon Sep 17 00:00:00 2001 From: igor Date: Sat, 31 May 2025 19:14:33 -0700 Subject: [PATCH] Settings window is no longer a popover. --- src/AppDelegate.swift | 19 +++++++++++----- src/PathManager.swift | 10 ++++----- src/SearchViewController.swift | 14 ++---------- src/SettingsViewController.swift | 37 +++++++++++++++++++++++++++++--- 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/src/AppDelegate.swift b/src/AppDelegate.swift index acfd2f0..268ff61 100644 --- a/src/AppDelegate.swift +++ b/src/AppDelegate.swift @@ -6,9 +6,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { let fileManager = FileManager.default let window = PopoverPanel(viewController: SearchViewController()) + let settingsWindow = + MenulessWindow(viewController: SettingsViewController()) let aboutWindow = MenulessWindow(viewController: AboutViewController()) func applicationDidFinishLaunching(_ notification: Notification) { + settingsWindow.title = "Settings" aboutWindow.level = .statusBar PathManager.shared.updateIndex() @@ -16,8 +19,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { window.delegate = self // NOTE: Here we check wether the program was launched by the - // system (e.g. launch-at-login). If it was not, then display - // the window. + // system (e.g. launch-at-login). If it was not, then display the + // window. if let event = NSAppleEventManager.shared().currentAppleEvent, !(event.eventID == kAEOpenApplication && event.paramDescriptor(forKeyword: keyAEPropData)? @@ -70,7 +73,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows: Bool) -> Bool { if !window.isKeyWindow { - window.makeKeyAndOrderFront(nil) + if !settingsWindow.isVisible { + window.makeKeyAndOrderFront(nil) + } } return true @@ -99,8 +104,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { aboutWindow.makeKeyAndOrderFront(nil) } - // NOTE: This function act like a callback is triggered by DirMonitor - // when file system events occur. + public func showSettingsWindow() { + settingsWindow.makeKeyAndOrderFront(nil) + } + + // NOTE: This function act like a callback and is triggered by + // DirMonitor when file system events occur. public func fsEventTriggered(_ path: String, _ flags: Int) { if containsFlags(key: kFSEventStreamEventFlagItemCreated, in: flags) || containsFlags(key: kFSEventStreamEventFlagItemRemoved, in: flags) || diff --git a/src/PathManager.swift b/src/PathManager.swift index d62dc09..536f3fb 100644 --- a/src/PathManager.swift +++ b/src/PathManager.swift @@ -13,8 +13,8 @@ final class PathManager { private var dirMonitor: DirMonitor? // NOTE: These are default paths where MacOS's default programs are - // stored. This list should be updated if something changes in - // newer MacOS version. + // stored. This list should be updated if something changes in newer + // MacOS versions. static let defaultPaths = [ "/Applications", "/System/Applications", @@ -102,8 +102,8 @@ final class PathManager { } // PERF: Optimize some more. Do not rebuild the entire array, instead - // remove or add only needed programs. Thereby, limiting the - // amount of allocations. + // remove or add only needed programs. Thereby, limiting the amount of + // allocations. public func rebuildIndex(at path: String) { paths[path] = [] paths[path] = indexDirs(at: path, deepness: 2) @@ -141,7 +141,7 @@ final class PathManager { refreshFilesystemWatchers() } - // Touch paths to load them into CPUs cache. + // Touch paths to load them into CPU's cache for performance. public func touchPaths() { for path in paths { _ = path diff --git a/src/SearchViewController.swift b/src/SearchViewController.swift index b415b2e..29ab3cb 100644 --- a/src/SearchViewController.swift +++ b/src/SearchViewController.swift @@ -9,8 +9,7 @@ fileprivate let windowCornerRadius = 15.0 fileprivate let maxItems = 20 class SearchViewController: NSViewController, NSTextFieldDelegate, - NSPopoverDelegate, NSTableViewDataSource, - NSTableViewDelegate + NSTableViewDataSource, NSTableViewDelegate { private var keyboardEvents: EventMonitor? @@ -20,13 +19,6 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, private var programsTableViewSelection = 0 - private var settingsPopover: NSPopover = { - let popover = NSPopover() - popover.contentViewController = SettingsViewController() - popover.behavior = .transient - return popover - }() - private var shadowView: ShadowView = { let view = ShadowView() view.translatesAutoresizingMaskIntoConstraints = false @@ -301,7 +293,6 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, return event } - settingsPopover.delegate = self searchInput.delegate = self tableScrollView.documentView = programsTableView @@ -364,8 +355,7 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, @objc func openSettings() { - settingsPopover.show(relativeTo: settingsButton.bounds, - of: settingsButton, preferredEdge: .maxY) + delegate.showSettingsWindow() } @objc diff --git a/src/SettingsViewController.swift b/src/SettingsViewController.swift index d7e4c89..6992e3b 100644 --- a/src/SettingsViewController.swift +++ b/src/SettingsViewController.swift @@ -2,11 +2,15 @@ import AppKit import Carbon import ServiceManagement +// TODO: Rework the paths table and selection. Right now, it is very +// disfunctional and error-prone. class SettingsViewController: NSViewController, NSTextFieldDelegate, KeyDetectorButtonDelegate, NSTableViewDataSource, NSTableViewDelegate, PathsTableCellViewDelegate { + private var keyboardEvents: EventMonitor? + private var recording = false // NOTE: This is the default shortcut. If you were to change it, don't @@ -346,6 +350,20 @@ class SettingsViewController: NSViewController, launchAtLoginToggle.target = self launchAtLoginToggle.action = #selector(affectLaunchAtLogin(_:)) + keyboardEvents = LocalEventMonitor(mask: [.keyDown]) + { [weak self] event in + let key = event.keyCode + let modifiers = event.modifierFlags.rawValue + + if modsContains(keys: OSCmd, in: modifiers) && + key == kVK_ANSI_Q || modsContainsNone(in: modifiers) + { + NSApplication.shared.terminate(self) + } + + return event + } + addSubviews() setConstraints() } @@ -372,11 +390,18 @@ class SettingsViewController: NSViewController, override func viewDidAppear() { super.viewDidAppear() + + keyboardEvents?.start() + + NSApp.setActivationPolicy(.regular) + self.view.window?.center() } override func viewWillDisappear() { super.viewWillDisappear() + keyboardEvents?.stop() + HotKeyManager.shared.registerHotKey(key: keyCode, modifiers: modifiers) @@ -399,6 +424,12 @@ class SettingsViewController: NSViewController, PathManager.shared.savePaths() } + override func viewDidDisappear() { + super.viewDidDisappear() + + NSApp.setActivationPolicy(.accessory) + } + override func loadView() { self.view = NSView() } @@ -578,9 +609,9 @@ class SettingsViewController: NSViewController, makeIfNecessary: false) as? PathsTableCellView { if isDirectory(text) { - cell.titleField.textColor = NSColor.green + cell.titleField.textColor = NSColor.systemGreen } else { - cell.titleField.textColor = NSColor.red + cell.titleField.textColor = NSColor.systemRed } } } @@ -599,7 +630,7 @@ class SettingsViewController: NSViewController, } // WARN: - // FIX: THere is a bug where the program crashes when adding a new + // FIX: There is a bug where the program crashes when adding a new // path. This happens because the settings popup is closed before // displaying the selection modal, as a result the new path item // is cleared (well, b/c it's empty) and the path gets set into