Added a programs list.
This commit is contained in:
@@ -7,10 +7,19 @@ fileprivate let logger = Logger(
|
|||||||
category: String("Helpers")
|
category: String("Helpers")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
enum ViewConstants {
|
||||||
|
static let spacing2: CGFloat = 2
|
||||||
|
static let spacing5: CGFloat = 2
|
||||||
|
static let spacing10: CGFloat = 10
|
||||||
|
static let spacing20: CGFloat = 20
|
||||||
|
static let spacing40: CGFloat = 40
|
||||||
|
}
|
||||||
|
|
||||||
struct Program {
|
struct Program {
|
||||||
let path: String
|
let path: String
|
||||||
let name: String
|
let name: String
|
||||||
let ext: String
|
let ext: String
|
||||||
|
var img: NSImage?
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyName(virtualKeyCode: UInt16) -> String? {
|
func keyName(virtualKeyCode: UInt16) -> String? {
|
||||||
|
|||||||
20
src/Makefile
20
src/Makefile
@@ -11,7 +11,7 @@ SRCMODULES = Helpers.swift EditableNSTextField.swift EventMonitor.swift \
|
|||||||
GlobalEventTap.swift PopoverPanel.swift SearchViewController.swift \
|
GlobalEventTap.swift PopoverPanel.swift SearchViewController.swift \
|
||||||
SettingsViewController.swift HotKeyManager.swift \
|
SettingsViewController.swift HotKeyManager.swift \
|
||||||
KeyDetectorButton.swift PathManager.swift MyTableCellView.swift \
|
KeyDetectorButton.swift PathManager.swift MyTableCellView.swift \
|
||||||
AppDelegate.swift main.swift
|
ProgramTableViewCell.swift AppDelegate.swift main.swift
|
||||||
ARMOBJMODULES = $(addprefix ./arm64/,$(SRCMODULES:.swift=.o))
|
ARMOBJMODULES = $(addprefix ./arm64/,$(SRCMODULES:.swift=.o))
|
||||||
X86OBJMODULES = $(addprefix ./x86_64/,$(SRCMODULES:.swift=.o))
|
X86OBJMODULES = $(addprefix ./x86_64/,$(SRCMODULES:.swift=.o))
|
||||||
|
|
||||||
@@ -27,19 +27,21 @@ FRAMEWORKS = -framework AppKit -framework ServiceManagement
|
|||||||
# generates same exact executable, timestamps do change.
|
# generates same exact executable, timestamps do change.
|
||||||
./arm64/%.o: %.swift
|
./arm64/%.o: %.swift
|
||||||
swift -frontend -c -target arm64-apple-macos$(MACOS_VERSION) $(FLAGS) \
|
swift -frontend -c -target arm64-apple-macos$(MACOS_VERSION) $(FLAGS) \
|
||||||
-primary-file $< $(filter-out $<, $(SRCMODULES)) $(LIBS) $(FRAMEWORKS) -sdk $(SDK) \
|
-primary-file $< $(filter-out $<, $(SRCMODULES)) $(LIBS) \
|
||||||
-module-name $(EXEC) -o $@ -emit-module && touch $@
|
$(FRAMEWORKS) -sdk $(SDK) -module-name $(EXEC) -o $@ \
|
||||||
|
-emit-module && touch $@
|
||||||
|
|
||||||
ifdef UNIVERSAL
|
ifdef UNIVERSAL
|
||||||
./x86_64/%.o: %.swift
|
./x86_64/%.o: %.swift
|
||||||
@swift -frontend -c -target x86_64-apple-macos$(MACOS_VERSION) \
|
@swift -frontend -c -target x86_64-apple-macos$(MACOS_VERSION) \
|
||||||
$(FLAGS) -primary-file $< $(filter-out $<, $(SRCMODULES)) $(LIBS) $(FRAMEWORKS) \
|
$(FLAGS) -primary-file $< $(filter-out $<, $(SRCMODULES)) \
|
||||||
-sdk $(SDK) -module-name $(EXEC) -o $@ -emit-module && touch $@
|
$(LIBS) $(FRAMEWORKS) -sdk $(SDK) -module-name $(EXEC) -o $@ \
|
||||||
|
-emit-module && touch $@
|
||||||
endif
|
endif
|
||||||
|
|
||||||
./arm64/$(EXEC): $(ARMOBJMODULES)
|
./arm64/$(EXEC): $(ARMOBJMODULES)
|
||||||
@ld -syslibroot $(SDK) -lSystem $(FRAMEWORKS) -arch arm64 -macos_version_min \
|
@ld -syslibroot $(SDK) -lSystem $(FRAMEWORKS) -arch arm64 \
|
||||||
$(MACOS_VERSION).0 \
|
-macos_version_min $(MACOS_VERSION).0 \
|
||||||
/Library/Developer/CommandLineTools/usr/lib/swift/macosx/libswiftCompatibilityPacks.a \
|
/Library/Developer/CommandLineTools/usr/lib/swift/macosx/libswiftCompatibilityPacks.a \
|
||||||
-sectcreate __TEXT __info_plist Info.plist \
|
-sectcreate __TEXT __info_plist Info.plist \
|
||||||
-L /Library/Developer/CommandLineTools/usr/lib/swift/macosx -L \
|
-L /Library/Developer/CommandLineTools/usr/lib/swift/macosx -L \
|
||||||
@@ -51,8 +53,8 @@ endif
|
|||||||
|
|
||||||
ifdef UNIVERSAL
|
ifdef UNIVERSAL
|
||||||
./x86_64/$(EXEC): $(X86OBJMODULES)
|
./x86_64/$(EXEC): $(X86OBJMODULES)
|
||||||
@ld -syslibroot $(SDK) -lSystem $(FRAMEWORKS) -arch x86_64 -macos_version_min \
|
@ld -syslibroot $(SDK) -lSystem $(FRAMEWORKS) -arch x86_64 \
|
||||||
$(MACOS_VERSION).0 \
|
-macos_version_min $(MACOS_VERSION).0 \
|
||||||
/Library/Developer/CommandLineTools/usr/lib/swift/macosx/libswiftCompatibilityPacks.a \
|
/Library/Developer/CommandLineTools/usr/lib/swift/macosx/libswiftCompatibilityPacks.a \
|
||||||
-sectcreate __TEXT __info_plist Info.plist \
|
-sectcreate __TEXT __info_plist Info.plist \
|
||||||
-L /Library/Developer/CommandLineTools/usr/lib/swift/macosx -L \
|
-L /Library/Developer/CommandLineTools/usr/lib/swift/macosx -L \
|
||||||
|
|||||||
@@ -69,11 +69,13 @@ final class PathManager {
|
|||||||
atPath: path)
|
atPath: path)
|
||||||
for item in items {
|
for item in items {
|
||||||
let name = String(item.dropLast(4))
|
let name = String(item.dropLast(4))
|
||||||
|
|
||||||
if item.hasSuffix(".app") {
|
if item.hasSuffix(".app") {
|
||||||
if !programs.contains(where: { name == $0.name }) {
|
if !programs.contains(where: { name == $0.name }) {
|
||||||
programs.append(
|
programs.append(
|
||||||
Program(
|
Program(
|
||||||
path: path, name: name, ext: ".app"))
|
path: path, name: name, ext: ".app",
|
||||||
|
img: nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
78
src/ProgramTableViewCell.swift
Normal file
78
src/ProgramTableViewCell.swift
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import AppKit
|
||||||
|
|
||||||
|
class ProgramTableRowView: NSTableRowView {
|
||||||
|
override func drawSelection(in dirtyRect: NSRect) {
|
||||||
|
if self.selectionHighlightStyle != .none {
|
||||||
|
let selectionColor = NSColor.systemBlue
|
||||||
|
selectionColor.setFill()
|
||||||
|
self.bounds.fill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProgramTableViewCell: NSTableCellView {
|
||||||
|
var id: Int = -1
|
||||||
|
|
||||||
|
private(set) var isEditing = false
|
||||||
|
|
||||||
|
public var appIconImage: NSImageView = {
|
||||||
|
let image = NSImageView()
|
||||||
|
image.image =
|
||||||
|
NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath)
|
||||||
|
image.imageScaling = .scaleAxesIndependently
|
||||||
|
image.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return image
|
||||||
|
}()
|
||||||
|
|
||||||
|
public var titleField: NSTextField = {
|
||||||
|
let field = NSTextField(labelWithString: "")
|
||||||
|
field.lineBreakMode = .byTruncatingTail
|
||||||
|
field.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return field
|
||||||
|
}()
|
||||||
|
|
||||||
|
public var progPathLabel: NSTextField = {
|
||||||
|
let textField = NSTextField(labelWithString: "")
|
||||||
|
textField.cell?.lineBreakMode = .byTruncatingTail
|
||||||
|
textField.font = NSFont.systemFont(
|
||||||
|
ofSize: NSFontDescriptor.preferredFontDescriptor(
|
||||||
|
forTextStyle: .caption1).pointSize, weight: .medium)
|
||||||
|
textField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return textField
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame frameRect: NSRect) {
|
||||||
|
super.init(frame: frameRect)
|
||||||
|
|
||||||
|
addSubview(appIconImage)
|
||||||
|
addSubview(titleField)
|
||||||
|
addSubview(progPathLabel)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
appIconImage.widthAnchor.constraint(equalToConstant: 40),
|
||||||
|
appIconImage.heightAnchor.constraint(equalToConstant: 40),
|
||||||
|
appIconImage.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
appIconImage.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
appIconImage.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
|
||||||
|
titleField.topAnchor.constraint(
|
||||||
|
equalTo: appIconImage.topAnchor,
|
||||||
|
constant: ViewConstants.spacing2),
|
||||||
|
titleField.leadingAnchor.constraint(
|
||||||
|
equalTo: appIconImage.trailingAnchor,
|
||||||
|
constant: ViewConstants.spacing5),
|
||||||
|
titleField.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
|
||||||
|
progPathLabel.topAnchor.constraint(
|
||||||
|
equalTo: titleField.bottomAnchor),
|
||||||
|
progPathLabel.leadingAnchor.constraint(
|
||||||
|
equalTo: titleField.leadingAnchor),
|
||||||
|
progPathLabel.trailingAnchor.constraint(
|
||||||
|
equalTo: titleField.trailingAnchor),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,20 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
fileprivate enum ViewConstants {
|
|
||||||
static let spacing2: CGFloat = 2
|
|
||||||
static let spacing10: CGFloat = 10
|
|
||||||
static let spacing20: CGFloat = 20
|
|
||||||
static let spacing40: CGFloat = 40
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchViewController: NSViewController, NSTextFieldDelegate,
|
class SearchViewController: NSViewController, NSTextFieldDelegate,
|
||||||
NSPopoverDelegate
|
NSPopoverDelegate, NSTableViewDataSource, NSTableViewDelegate
|
||||||
{
|
{
|
||||||
fileprivate static let logger = Logger(
|
fileprivate static let logger = Logger(
|
||||||
subsystem: Bundle.main.bundleIdentifier!,
|
subsystem: Bundle.main.bundleIdentifier!,
|
||||||
category: String(describing: SearchViewController.self)
|
category: String(describing: SearchViewController.self)
|
||||||
)
|
)
|
||||||
|
|
||||||
var foundProgram: Program? = nil
|
private var keyboardEvents: EventMonitor?
|
||||||
|
|
||||||
|
private var foundProgram: Program? = nil
|
||||||
|
private var programsList: [Program] = []
|
||||||
|
|
||||||
|
private var programsTableViewSelection = 0
|
||||||
|
|
||||||
private var settingsPopover: NSPopover = {
|
private var settingsPopover: NSPopover = {
|
||||||
let popover = NSPopover()
|
let popover = NSPopover()
|
||||||
@@ -37,21 +35,11 @@ class SearchViewController: NSViewController, NSTextFieldDelegate,
|
|||||||
private var searchInput: EditableNSTextField = {
|
private var searchInput: EditableNSTextField = {
|
||||||
let textField = EditableNSTextField()
|
let textField = EditableNSTextField()
|
||||||
textField.placeholderString = "Search programs . . ."
|
textField.placeholderString = "Search programs . . ."
|
||||||
|
textField.usesSingleLineMode = false
|
||||||
textField.bezelStyle = .roundedBezel
|
textField.bezelStyle = .roundedBezel
|
||||||
textField.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return textField
|
|
||||||
}()
|
|
||||||
|
|
||||||
private var programsLabel: NSTextField = {
|
|
||||||
let textField = NSTextField()
|
|
||||||
textField.stringValue = ""
|
|
||||||
textField.isEditable = false
|
|
||||||
textField.isBezeled = false
|
|
||||||
textField.drawsBackground = false
|
|
||||||
textField.alignment = .left
|
|
||||||
textField.font = NSFont.systemFont(
|
textField.font = NSFont.systemFont(
|
||||||
ofSize: NSFontDescriptor.preferredFontDescriptor(
|
ofSize: NSFontDescriptor.preferredFontDescriptor(
|
||||||
forTextStyle: .body).pointSize, weight: .bold)
|
forTextStyle: .title3).pointSize, weight: .medium)
|
||||||
textField.translatesAutoresizingMaskIntoConstraints = false
|
textField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return textField
|
return textField
|
||||||
}()
|
}()
|
||||||
@@ -68,31 +56,70 @@ class SearchViewController: NSViewController, NSTextFieldDelegate,
|
|||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
private var tableScrollView: NSScrollView = {
|
||||||
|
let scroll = NSScrollView()
|
||||||
|
scroll.contentInsets = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||||
|
scroll.drawsBackground = false
|
||||||
|
scroll.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return scroll
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var programsTableView: MyNSTableView = {
|
||||||
|
let table = MyNSTableView()
|
||||||
|
|
||||||
|
table.style = NSTableView.Style.plain
|
||||||
|
table.backgroundColor = .clear
|
||||||
|
table.usesAutomaticRowHeights = true
|
||||||
|
|
||||||
|
table.headerView = nil
|
||||||
|
table.allowsMultipleSelection = false
|
||||||
|
table.allowsColumnReordering = false
|
||||||
|
table.allowsColumnResizing = false
|
||||||
|
table.allowsColumnSelection = false
|
||||||
|
table.addTableColumn(NSTableColumn(
|
||||||
|
identifier: NSUserInterfaceItemIdentifier("Program")))
|
||||||
|
|
||||||
|
table.doubleAction = #selector(tableDoubleClick)
|
||||||
|
|
||||||
|
table.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return table
|
||||||
|
}()
|
||||||
|
|
||||||
private func addSubviews() {
|
private func addSubviews() {
|
||||||
view.addSubview(appIconImage)
|
view.addSubview(appIconImage)
|
||||||
view.addSubview(searchInput)
|
view.addSubview(searchInput)
|
||||||
view.addSubview(programsLabel)
|
|
||||||
view.addSubview(settingsButton)
|
view.addSubview(settingsButton)
|
||||||
|
view.addSubview(tableScrollView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var viewBottomAnchorTable: NSLayoutConstraint?
|
||||||
|
var viewBottomAnchorImage: NSLayoutConstraint?
|
||||||
|
|
||||||
private func setConstraints() {
|
private func setConstraints() {
|
||||||
|
viewBottomAnchorTable = tableScrollView.bottomAnchor.constraint(
|
||||||
|
equalTo: view.bottomAnchor,
|
||||||
|
constant: -ViewConstants.spacing10)
|
||||||
|
viewBottomAnchorImage = appIconImage.bottomAnchor.constraint(
|
||||||
|
equalTo: view.bottomAnchor,
|
||||||
|
constant: -ViewConstants.spacing10)
|
||||||
|
|
||||||
|
viewBottomAnchorTable?.isActive = false
|
||||||
|
viewBottomAnchorImage?.isActive = true
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
appIconImage.widthAnchor.constraint(equalToConstant: 70),
|
appIconImage.widthAnchor.constraint(equalToConstant: 60),
|
||||||
appIconImage.heightAnchor.constraint(
|
appIconImage.heightAnchor.constraint(
|
||||||
equalTo: appIconImage.widthAnchor, multiplier: 1),
|
equalTo: appIconImage.widthAnchor, multiplier: 1),
|
||||||
|
|
||||||
appIconImage.topAnchor.constraint(equalTo: view.topAnchor,
|
appIconImage.topAnchor.constraint(equalTo: view.topAnchor,
|
||||||
constant: ViewConstants.spacing20),
|
constant: ViewConstants.spacing10),
|
||||||
appIconImage.bottomAnchor.constraint(
|
|
||||||
equalTo: view.bottomAnchor,
|
|
||||||
constant: -ViewConstants.spacing10),
|
|
||||||
appIconImage.leadingAnchor.constraint(
|
appIconImage.leadingAnchor.constraint(
|
||||||
equalTo: view.leadingAnchor,
|
equalTo: view.leadingAnchor,
|
||||||
constant: ViewConstants.spacing10),
|
constant: ViewConstants.spacing10),
|
||||||
|
|
||||||
searchInput.widthAnchor.constraint(equalToConstant: 300),
|
searchInput.widthAnchor.constraint(equalToConstant: 300),
|
||||||
searchInput.topAnchor.constraint(
|
searchInput.centerYAnchor.constraint(
|
||||||
equalTo: appIconImage.topAnchor),
|
equalTo: appIconImage.centerYAnchor),
|
||||||
searchInput.leadingAnchor.constraint(
|
searchInput.leadingAnchor.constraint(
|
||||||
equalTo: appIconImage.trailingAnchor,
|
equalTo: appIconImage.trailingAnchor,
|
||||||
constant: ViewConstants.spacing10),
|
constant: ViewConstants.spacing10),
|
||||||
@@ -106,24 +133,74 @@ class SearchViewController: NSViewController, NSTextFieldDelegate,
|
|||||||
equalTo: view.trailingAnchor,
|
equalTo: view.trailingAnchor,
|
||||||
constant: -ViewConstants.spacing10),
|
constant: -ViewConstants.spacing10),
|
||||||
|
|
||||||
programsLabel.topAnchor.constraint(
|
tableScrollView.heightAnchor.constraint(equalToConstant: 210),
|
||||||
equalTo: searchInput.bottomAnchor,
|
tableScrollView.topAnchor.constraint(
|
||||||
|
equalTo: appIconImage.bottomAnchor,
|
||||||
constant: ViewConstants.spacing10),
|
constant: ViewConstants.spacing10),
|
||||||
programsLabel.leadingAnchor.constraint(
|
tableScrollView.leadingAnchor.constraint(
|
||||||
equalTo: appIconImage.trailingAnchor,
|
equalTo: view.leadingAnchor),
|
||||||
constant: ViewConstants.spacing10),
|
tableScrollView.trailingAnchor.constraint(
|
||||||
programsLabel.trailingAnchor.constraint(
|
equalTo: view.trailingAnchor)
|
||||||
equalTo: searchInput.trailingAnchor),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
keyboardEvents = LocalEventMonitor(mask: [.keyDown], handler:
|
||||||
|
{ [weak self] event in
|
||||||
|
let key = event.keyCode
|
||||||
|
let modifiers = event.modifierFlags.rawValue
|
||||||
|
let command = NSEvent.ModifierFlags.command.rawValue
|
||||||
|
let shift = NSEvent.ModifierFlags.shift.rawValue
|
||||||
|
let control = NSEvent.ModifierFlags.control.rawValue
|
||||||
|
let option = NSEvent.ModifierFlags.option.rawValue
|
||||||
|
|
||||||
|
// TODO: Implement helper functions for modifiers.
|
||||||
|
if let controller = self {
|
||||||
|
if ((modifiers & control) == control &&
|
||||||
|
(modifiers & (command | shift | option)) == 0 &&
|
||||||
|
key == 35) || // P
|
||||||
|
(modifiers & (command | control | shift | option)) == 0 &&
|
||||||
|
(key == 126) // UP
|
||||||
|
{
|
||||||
|
controller.programsTableViewSelection -= 1
|
||||||
|
} else if ((modifiers & control) == control &&
|
||||||
|
(modifiers & (command | shift | option)) == 0 &&
|
||||||
|
key == 45) || // N
|
||||||
|
(modifiers & (command | control | shift | option)) == 0 &&
|
||||||
|
(key == 125) // DOWN
|
||||||
|
{
|
||||||
|
controller.programsTableViewSelection += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if controller.programsTableViewSelection >
|
||||||
|
controller.programsList.count-1
|
||||||
|
{
|
||||||
|
controller.programsTableViewSelection =
|
||||||
|
controller.programsList.count-1
|
||||||
|
} else if controller.programsTableViewSelection < 0 {
|
||||||
|
controller.programsTableViewSelection = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
let select = controller.programsTableViewSelection
|
||||||
|
self?.programsTableView.selectRowIndexes(
|
||||||
|
IndexSet(integer: select),
|
||||||
|
byExtendingSelection: false)
|
||||||
|
self?.programsTableView.scrollRowToVisible(select)
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
|
})
|
||||||
|
|
||||||
settingsPopover.delegate = self
|
settingsPopover.delegate = self
|
||||||
|
|
||||||
searchInput.delegate = self
|
searchInput.delegate = self
|
||||||
|
|
||||||
|
tableScrollView.documentView = programsTableView
|
||||||
|
programsTableView.dataSource = self
|
||||||
|
programsTableView.delegate = self
|
||||||
|
|
||||||
addSubviews()
|
addSubviews()
|
||||||
setConstraints()
|
setConstraints()
|
||||||
}
|
}
|
||||||
@@ -131,17 +208,37 @@ class SearchViewController: NSViewController, NSTextFieldDelegate,
|
|||||||
override func viewDidAppear() {
|
override func viewDidAppear() {
|
||||||
super.viewDidAppear()
|
super.viewDidAppear()
|
||||||
|
|
||||||
self.view.window?.center()
|
keyboardEvents?.start()
|
||||||
|
|
||||||
|
view.window?.center()
|
||||||
|
|
||||||
|
view.window?.makeFirstResponder(searchInput)
|
||||||
// searchInput should select all text whenever window appears.
|
// searchInput should select all text whenever window appears.
|
||||||
NSApp.sendAction(#selector(NSResponder.selectAll(_:)),
|
NSApp.sendAction(#selector(NSResponder.selectAll(_:)),
|
||||||
to: nil, from: self)
|
to: nil, from: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewDidDisappear() {
|
||||||
|
super.viewDidDisappear()
|
||||||
|
|
||||||
|
keyboardEvents?.stop()
|
||||||
|
}
|
||||||
|
|
||||||
override func loadView() {
|
override func loadView() {
|
||||||
self.view = NSView()
|
self.view = NSView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func reloadProgramsTableViewData() {
|
||||||
|
if programsList.count > 0 {
|
||||||
|
viewBottomAnchorTable?.isActive = true
|
||||||
|
viewBottomAnchorImage?.isActive = false
|
||||||
|
} else {
|
||||||
|
viewBottomAnchorTable?.isActive = false
|
||||||
|
viewBottomAnchorImage?.isActive = true
|
||||||
|
}
|
||||||
|
programsTableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func openSettings() {
|
func openSettings() {
|
||||||
// HACK: This is an interseting behavior. When NSPopover appears
|
// HACK: This is an interseting behavior. When NSPopover appears
|
||||||
@@ -154,38 +251,71 @@ class SearchViewController: NSViewController, NSTextFieldDelegate,
|
|||||||
of: settingsButton, preferredEdge: .maxY)
|
of: settingsButton, preferredEdge: .maxY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
private func tableDoubleClick() {
|
||||||
|
let program = programsList[programsTableView.clickedRow]
|
||||||
|
openProgram(program)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func openProgram(_ program: Program) {
|
||||||
|
let url = URL(fileURLWithPath: program.path)
|
||||||
|
.appendingPathComponent(program.name+program.ext)
|
||||||
|
let config = NSWorkspace.OpenConfiguration()
|
||||||
|
|
||||||
|
NSWorkspace.shared.openApplication(at: url,
|
||||||
|
configuration: config)
|
||||||
|
{ [weak self] application, error in
|
||||||
|
if let error = error {
|
||||||
|
Self.logger.debug("\(error.localizedDescription)")
|
||||||
|
} else {
|
||||||
|
Self.logger.debug("Program opened successfully")
|
||||||
|
// NOTE: This needs a window! Do not just copy-paste
|
||||||
|
// this block elsewhere.
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let window = self?.view.window {
|
||||||
|
window.resignKey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func controlTextDidChange(_ obj: Notification) {
|
func controlTextDidChange(_ obj: Notification) {
|
||||||
guard let searchInput = obj.object as? EditableNSTextField
|
guard let searchInput = obj.object as? EditableNSTextField
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
var list = ""
|
|
||||||
|
|
||||||
let programs = PathManager.shared.programs
|
let programs = PathManager.shared.programs
|
||||||
for program in programs {
|
|
||||||
|
programsList = []
|
||||||
|
for i in programs.indices {
|
||||||
|
var program = programs[i]
|
||||||
|
if programsList.count >= 10 {
|
||||||
|
break
|
||||||
|
}
|
||||||
if program.name.lowercased().contains(
|
if program.name.lowercased().contains(
|
||||||
searchInput.stringValue.lowercased())
|
searchInput.stringValue.lowercased())
|
||||||
{
|
{
|
||||||
if !list.isEmpty {
|
let url = URL(fileURLWithPath: program.path)
|
||||||
list += ", "
|
.appendingPathComponent(program.name+program.ext)
|
||||||
}
|
let image = NSWorkspace.shared.icon(forFile: url.path)
|
||||||
list += program.name + program.ext
|
program.img = image
|
||||||
foundProgram = program
|
programsList.append(program)
|
||||||
break
|
|
||||||
} else {
|
|
||||||
foundProgram = nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
reloadProgramsTableViewData()
|
||||||
|
|
||||||
if let program = foundProgram {
|
programsTableViewSelection = 0
|
||||||
programsLabel.stringValue =
|
programsTableView.selectRowIndexes(
|
||||||
program.name + program.ext + " (\(program.path))"
|
IndexSet(integer: programsTableViewSelection),
|
||||||
|
byExtendingSelection: false)
|
||||||
|
programsTableView.scrollRowToVisible(programsTableViewSelection)
|
||||||
|
|
||||||
|
if programsList.count > 0 {
|
||||||
|
let program = programsList[0]
|
||||||
let url = URL(fileURLWithPath: program.path)
|
let url = URL(fileURLWithPath: program.path)
|
||||||
.appendingPathComponent(program.name+program.ext)
|
.appendingPathComponent(program.name+program.ext)
|
||||||
appIconImage.image = NSWorkspace.shared.icon(forFile: url.path)
|
appIconImage.image = NSWorkspace.shared.icon(forFile: url.path)
|
||||||
} else {
|
} else {
|
||||||
programsLabel.stringValue = ""
|
|
||||||
|
|
||||||
appIconImage.image =
|
appIconImage.image =
|
||||||
NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath)
|
NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath)
|
||||||
}
|
}
|
||||||
@@ -195,32 +325,19 @@ class SearchViewController: NSViewController, NSTextFieldDelegate,
|
|||||||
doCommandBy commandSelector: Selector) -> Bool
|
doCommandBy commandSelector: Selector) -> Bool
|
||||||
{
|
{
|
||||||
if commandSelector == #selector(NSResponder.insertNewline(_:)) {
|
if commandSelector == #selector(NSResponder.insertNewline(_:)) {
|
||||||
if let program = foundProgram {
|
let program = programsList[programsTableViewSelection]
|
||||||
let url = URL(fileURLWithPath: program.path)
|
openProgram(program)
|
||||||
.appendingPathComponent(program.name+program.ext)
|
|
||||||
let config = NSWorkspace.OpenConfiguration()
|
|
||||||
|
|
||||||
NSWorkspace.shared.openApplication(at: url,
|
|
||||||
configuration: config)
|
|
||||||
{ [weak self] application, error in
|
|
||||||
if let error = error {
|
|
||||||
Self.logger.debug("\(error.localizedDescription)")
|
|
||||||
} else {
|
|
||||||
Self.logger.debug("Program opened successfully")
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
if let window = self?.view.window {
|
|
||||||
window.resignKey()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NSApp.sendAction(#selector(NSResponder.selectAll(_:)),
|
NSApp.sendAction(#selector(NSResponder.selectAll(_:)),
|
||||||
to: nil, from: self)
|
to: nil, from: self)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} else if commandSelector == #selector(NSResponder.insertTab(_:)) {
|
} else if commandSelector == #selector(NSResponder.insertTab(_:)) {
|
||||||
return true
|
return true
|
||||||
|
} else if commandSelector == #selector(NSResponder.moveUp(_:)) ||
|
||||||
|
commandSelector == #selector(NSResponder.moveDown(_:))
|
||||||
|
{
|
||||||
|
// Ignore arrows keys up or down because we use those to
|
||||||
|
// navigate the programs list.
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -233,4 +350,35 @@ class SearchViewController: NSViewController, NSTextFieldDelegate,
|
|||||||
func popoverWillClose(_ notification: Notification) {
|
func popoverWillClose(_ notification: Notification) {
|
||||||
searchInput.becomeFirstResponder()
|
searchInput.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||||
|
return programsList.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: NSTableView,
|
||||||
|
rowViewForRow row: Int) -> NSTableRowView?
|
||||||
|
{
|
||||||
|
return ProgramTableRowView()
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: NSTableView,
|
||||||
|
viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
|
||||||
|
{
|
||||||
|
let cell = ProgramTableViewCell()
|
||||||
|
let program = programsList[row]
|
||||||
|
|
||||||
|
// PERF: This is very slow, even with 10 items on the list! It has
|
||||||
|
// to be the image of concern. UIKit has reusable cells,
|
||||||
|
// is that possible? Or is fetching an image is slow?
|
||||||
|
cell.titleField.stringValue = program.name + program.ext
|
||||||
|
cell.progPathLabel.stringValue = program.path
|
||||||
|
cell.appIconImage.image = program.img
|
||||||
|
cell.id = row
|
||||||
|
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class MyNSTableView: NSTableView {
|
||||||
|
override var acceptsFirstResponder: Bool { false }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,6 @@ import Carbon
|
|||||||
import ServiceManagement
|
import ServiceManagement
|
||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
fileprivate enum ViewConstants {
|
|
||||||
static let spacing2: CGFloat = 2
|
|
||||||
static let spacing5: CGFloat = 2
|
|
||||||
static let spacing10: CGFloat = 10
|
|
||||||
static let spacing20: CGFloat = 20
|
|
||||||
static let spacing40: CGFloat = 40
|
|
||||||
}
|
|
||||||
|
|
||||||
class SettingsViewController: NSViewController, NSTextFieldDelegate,
|
class SettingsViewController: NSViewController, NSTextFieldDelegate,
|
||||||
KeyDetectorButtonDelegate, NSTableViewDataSource, NSTableViewDelegate,
|
KeyDetectorButtonDelegate, NSTableViewDataSource, NSTableViewDelegate,
|
||||||
MyTableCellViewDelegate
|
MyTableCellViewDelegate
|
||||||
@@ -51,13 +43,13 @@ class SettingsViewController: NSViewController, NSTextFieldDelegate,
|
|||||||
|
|
||||||
private var ctrlButton: NSButton = {
|
private var ctrlButton: NSButton = {
|
||||||
let button = NSButton()
|
let button = NSButton()
|
||||||
button.title = "⌃"
|
button.title = "⌃"
|
||||||
button.action = #selector(handleModifiers)
|
button.action = #selector(handleModifiers)
|
||||||
button.setButtonType(.pushOnPushOff)
|
button.setButtonType(.pushOnPushOff)
|
||||||
button.sizeToFit()
|
button.sizeToFit()
|
||||||
button.bezelStyle = .rounded
|
button.bezelStyle = .rounded
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var cmdButton: NSButton = {
|
private var cmdButton: NSButton = {
|
||||||
@@ -321,8 +313,6 @@ class SettingsViewController: NSViewController, NSTextFieldDelegate,
|
|||||||
pathsTableView.dataSource = self
|
pathsTableView.dataSource = self
|
||||||
pathsTableView.delegate = self
|
pathsTableView.delegate = self
|
||||||
|
|
||||||
pathsTableView.delegate = self
|
|
||||||
|
|
||||||
pathsControl.target = self
|
pathsControl.target = self
|
||||||
pathsControl.action = #selector(affectPaths(_:))
|
pathsControl.action = #selector(affectPaths(_:))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user