import AppKit 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 { fileprivate static let logger = Logger( subsystem: Bundle.main.bundleIdentifier!, category: String(describing: SearchViewController.self) ) var foundProgram: Program? = nil private var appIconImage: NSImageView = { //let image = NSImageView(image: NSApp.applicationIconImage) let image = NSImageView() image.image = NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath) image.imageScaling = .scaleAxesIndependently image.translatesAutoresizingMaskIntoConstraints = false return image }() private var searchInput: EditableNSTextField = { let textField = EditableNSTextField() textField.placeholderString = "Search programs . . ." 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(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .body).pointSize, weight: .bold) textField.translatesAutoresizingMaskIntoConstraints = false return textField }() private var settingsButton: NSButton = { let button = NSButton() button.image = systemImage("gearshape.fill", .title2, .large, .init(paletteColors: [.white, .systemRed])) button.isBordered = false button.action = #selector(openSettings) button.sizeToFit() button.toolTip = "Quit" button.translatesAutoresizingMaskIntoConstraints = false return button }() private func addSubviews() { view.addSubview(appIconImage) view.addSubview(searchInput) view.addSubview(programsLabel) view.addSubview(settingsButton) } private func setConstraints() { NSLayoutConstraint.activate([ appIconImage.widthAnchor.constraint(equalToConstant: 70), appIconImage.heightAnchor.constraint(equalTo: appIconImage.widthAnchor, multiplier: 1), appIconImage.topAnchor.constraint(equalTo: view.topAnchor, constant: ViewConstants.spacing20), appIconImage.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -ViewConstants.spacing10), appIconImage.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing10), searchInput.widthAnchor.constraint(equalToConstant: 300), searchInput.topAnchor.constraint(equalTo: appIconImage.topAnchor), searchInput.leadingAnchor.constraint(equalTo: appIconImage.trailingAnchor, constant: ViewConstants.spacing10), settingsButton.firstBaselineAnchor.constraint(equalTo: searchInput.firstBaselineAnchor), settingsButton.leadingAnchor.constraint(equalTo: searchInput.trailingAnchor, constant: ViewConstants.spacing10), settingsButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing10), programsLabel.topAnchor.constraint(equalTo: searchInput.bottomAnchor, constant: ViewConstants.spacing10), programsLabel.leadingAnchor.constraint(equalTo: appIconImage.trailingAnchor, constant: ViewConstants.spacing10), programsLabel.trailingAnchor.constraint(equalTo: searchInput.trailingAnchor), ]) } override func viewDidLoad() { super.viewDidLoad() searchInput.delegate = self addSubviews() setConstraints() } override func viewDidAppear() { super.viewDidAppear() self.view.window?.center() } override func loadView() { self.view = NSView() } //private func fetchIcon() { // for key in resultPaths.keys { // } //} @objc private func openSettings() { } func controlTextDidChange(_ obj: Notification) { var list = "" let programs = delegate.programs for program in programs { if program.name.lowercased().contains(searchInput.stringValue.lowercased()) { if !list.isEmpty { list += ", " } list += program.name + program.ext foundProgram = program break } else { foundProgram = nil } } if let program = foundProgram { programsLabel.stringValue = program.name + program.ext let url = URL(fileURLWithPath: program.path).appendingPathComponent(program.name+program.ext) appIconImage.image = NSWorkspace.shared.icon(forFile: url.path) } else { programsLabel.stringValue = "" appIconImage.image = NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath) } } func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { if commandSelector == #selector(NSResponder.insertNewline(_:)) { if let program = foundProgram { let url = URL(fileURLWithPath: program.path).appendingPathComponent(program.name+program.ext) let config = NSWorkspace.OpenConfiguration() NSWorkspace.shared.openApplication(at: url, configuration: config, completionHandler: { [weak self] application, error in if let error = error { Self.logger.debug("Failed to open application: \(error.localizedDescription)") } else { Self.logger.debug("Application opened successfully") DispatchQueue.main.async { if let window = self?.view.window { window.resignKey() } } } }) } // TODO: Send this whenever SearchViewController becomes visible. NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to: nil, from: self) return true } else if commandSelector == #selector(NSResponder.insertTab(_:)) { return true } return false } }