| | |
| | | static let tagLauncherQuickSearchDismissRequested = Notification.Name("TagLauncherQuickSearchDismissRequested") |
| | | static let tagLauncherQuickSearchVisibilityChanged = Notification.Name("TagLauncherQuickSearchVisibilityChanged") |
| | | static let tagLauncherHotkeyRegistrationChanged = Notification.Name("TagLauncherHotkeyRegistrationChanged") |
| | | static let tagLauncherProEntitlementChanged = Notification.Name("TagLauncherProEntitlementChanged") |
| | | } |
| | | |
| | | enum QuickSearchOpenSource { |
| | |
| | | } |
| | | return "Key \(keyCode)" |
| | | } |
| | | } |
| | | |
| | | static var printableKeyCodes: Set<Int> { |
| | | Set(keyCodeToPrintableScalar.keys) |
| | | } |
| | | |
| | | private static let keyCodeToPrintableScalar: [Int: Character] = [ |
| | |
| | | if panel.frame != frame { |
| | | panel.setFrame(frame, display: true) |
| | | } |
| | | NSApp.activate(ignoringOtherApps: true) |
| | | panel.makeKeyAndOrderFront(nil) |
| | | panel.orderFrontRegardless() |
| | | } |
| | |
| | | |
| | | let panel = QuickSearchPanel( |
| | | contentRect: .zero, |
| | | styleMask: [.borderless, .fullSizeContentView, .nonactivatingPanel], |
| | | styleMask: [.borderless, .fullSizeContentView], |
| | | backing: .buffered, |
| | | defer: false |
| | | ) |
| | |
| | | override var canBecomeKey: Bool { true } |
| | | override var canBecomeMain: Bool { false } |
| | | |
| | | override func sendEvent(_ event: NSEvent) { |
| | | if event.type == .leftMouseDown, |
| | | let row = quickSearchResultRow(at: event.locationInWindow) { |
| | | row.launchFromMouseClick() |
| | | return |
| | | } |
| | | super.sendEvent(event) |
| | | } |
| | | |
| | | override func constrainFrameRect(_ frameRect: NSRect, to screen: NSScreen?) -> NSRect { |
| | | frameRect |
| | | } |
| | | |
| | | private func quickSearchResultRow(at windowPoint: NSPoint) -> QuickSearchResultRowView? { |
| | | guard let contentView else { return nil } |
| | | if let row = contentView.quickSearchResultRowFromHitTest(atWindowPoint: windowPoint) { |
| | | return row |
| | | } |
| | | return contentView.quickSearchResultRowDescendant(containingWindowPoint: windowPoint) |
| | | } |
| | | } |
| | | |
| | | private extension NSView { |
| | | func quickSearchResultRowFromHitTest(atWindowPoint windowPoint: NSPoint) -> QuickSearchResultRowView? { |
| | | let localPoint = convert(windowPoint, from: nil) |
| | | var hitView = hitTest(localPoint) |
| | | while let candidate = hitView { |
| | | if let row = candidate as? QuickSearchResultRowView { |
| | | return row |
| | | } |
| | | hitView = candidate.superview |
| | | } |
| | | return nil |
| | | } |
| | | |
| | | func quickSearchResultRowDescendant(containingWindowPoint windowPoint: NSPoint) -> QuickSearchResultRowView? { |
| | | for subview in subviews.reversed() { |
| | | if let row = subview as? QuickSearchResultRowView, |
| | | row.containsWindowPointForLaunch(windowPoint) { |
| | | return row |
| | | } |
| | | if let row = subview.quickSearchResultRowDescendant(containingWindowPoint: windowPoint) { |
| | | return row |
| | | } |
| | | } |
| | | return nil |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | override func mouseDown(with event: NSEvent) { |
| | | if let result { |
| | | DispatchQueue.main.async { [onLaunch] in |
| | | onLaunch(result) |
| | | } |
| | | } else { |
| | | if !launchFromMouseClick() { |
| | | super.mouseDown(with: event) |
| | | } |
| | | } |
| | | |
| | | @discardableResult |
| | | func launchFromMouseClick() -> Bool { |
| | | guard let result else { return false } |
| | | DispatchQueue.main.async { [onLaunch, result] in |
| | | onLaunch(result) |
| | | } |
| | | return true |
| | | } |
| | | |
| | | func containsWindowPointForLaunch(_ windowPoint: NSPoint) -> Bool { |
| | | guard window != nil else { return false } |
| | | var view: NSView? = self |
| | | while let candidate = view { |
| | | if candidate.isHidden || candidate.alphaValue <= 0 { |
| | | return false |
| | | } |
| | | view = candidate.superview |
| | | } |
| | | let localPoint = convert(windowPoint, from: nil) |
| | | return bounds.contains(localPoint) && visibleRect.contains(localPoint) |
| | | } |
| | | |
| | | private func setup() { |
| | |
| | | field.placeholderString = placeholder |
| | | field.delegate = context.coordinator |
| | | field.onCommand = onCommand |
| | | field.target = context.coordinator |
| | | field.action = #selector(Coordinator.submitAction(_:)) |
| | | context.coordinator.onCommand = onCommand |
| | | field.setAccessibilityLabel(tr("quickSearch.inputAccessibility")) |
| | | context.coordinator.field = field |
| | |
| | | } |
| | | field.placeholderString = placeholder |
| | | field.onCommand = onCommand |
| | | field.target = context.coordinator |
| | | field.action = #selector(Coordinator.submitAction(_:)) |
| | | context.coordinator.onCommand = onCommand |
| | | if context.coordinator.lastFocusToken != focusToken { |
| | | context.coordinator.lastFocusToken = focusToken |
| | |
| | | text.wrappedValue = field.stringValue |
| | | } |
| | | |
| | | @objc func submitAction(_ sender: Any?) { |
| | | onCommand?(.submit) |
| | | } |
| | | |
| | | func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { |
| | | switch commandSelector { |
| | | case #selector(NSResponder.moveUp(_:)): |