Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix hotkey handling in Bookmarks manager, Bookmarks popover #3278

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1767,6 +1767,8 @@
841BE93C2C6F1CB200E9C2B5 /* MenuItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841BE93A2C6F1CB200E9C2B5 /* MenuItemNode.swift */; };
841BE93E2C6F236000E9C2B5 /* BookmarkDragDropManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841BE93D2C6F235F00E9C2B5 /* BookmarkDragDropManager.swift */; };
841BE93F2C6F236000E9C2B5 /* BookmarkDragDropManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841BE93D2C6F235F00E9C2B5 /* BookmarkDragDropManager.swift */; };
8426108D2C9811F30070D5F9 /* KeyEquivalentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426108C2C9811EC0070D5F9 /* KeyEquivalentView.swift */; };
8426108E2C9811F30070D5F9 /* KeyEquivalentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426108C2C9811EC0070D5F9 /* KeyEquivalentView.swift */; };
843965122C6F2FFE004C8899 /* NSDragOperationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843965112C6F2FFE004C8899 /* NSDragOperationExtension.swift */; };
843965132C6F2FFE004C8899 /* NSDragOperationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843965112C6F2FFE004C8899 /* NSDragOperationExtension.swift */; };
843965152C737022004C8899 /* NSPasteboardExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843965142C737022004C8899 /* NSPasteboardExtension.swift */; };
Expand Down Expand Up @@ -3826,6 +3828,7 @@
8400DC4D2C6E2770006509D2 /* SteppedScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SteppedScrollView.swift; sourceTree = "<group>"; };
841BE93A2C6F1CB200E9C2B5 /* MenuItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuItemNode.swift; sourceTree = "<group>"; };
841BE93D2C6F235F00E9C2B5 /* BookmarkDragDropManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkDragDropManager.swift; sourceTree = "<group>"; };
8426108C2C9811EC0070D5F9 /* KeyEquivalentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyEquivalentView.swift; sourceTree = "<group>"; };
843965112C6F2FFE004C8899 /* NSDragOperationExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSDragOperationExtension.swift; sourceTree = "<group>"; };
843965142C737022004C8899 /* NSPasteboardExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPasteboardExtension.swift; sourceTree = "<group>"; };
848648A02C76F4B20082282D /* BookmarksBarMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksBarMenuViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6770,6 +6773,7 @@
B693953E26F04BE70015B914 /* FocusRingView.swift */,
B693954326F04BE90015B914 /* GradientView.swift */,
B6B140872ABDBCC1004F8E85 /* HoverTrackingArea.swift */,
8426108C2C9811EC0070D5F9 /* KeyEquivalentView.swift */,
B6B1E88A26D774090062C350 /* LinkButton.swift */,
B693954026F04BE80015B914 /* LoadingProgressView.swift */,
B693954426F04BE90015B914 /* LongPressButton.swift */,
Expand Down Expand Up @@ -11024,6 +11028,7 @@
021EA0812BD2A9D500772C9A /* TabsPreferences.swift in Sources */,
B6619EFC2B111CC600CD9186 /* InstructionsFormatParser.swift in Sources */,
3706FC08293F65D500E42796 /* CoreDataBookmarkImporter.swift in Sources */,
8426108E2C9811F30070D5F9 /* KeyEquivalentView.swift in Sources */,
3706FC09293F65D500E42796 /* SuggestionViewModel.swift in Sources */,
3706FC0A293F65D500E42796 /* BookmarkManagedObject.swift in Sources */,
B6ABC5972B4861D4008343B9 /* FocusableTextField.swift in Sources */,
Expand Down Expand Up @@ -12174,6 +12179,7 @@
AAE246F8270A406200BEEAEE /* FirePopoverCollectionViewHeader.swift in Sources */,
AAB7320926DD0CD9002FACF9 /* FireViewController.swift in Sources */,
4B92928C26670D1700AD2C21 /* OutlineSeparatorViewCell.swift in Sources */,
8426108D2C9811F30070D5F9 /* KeyEquivalentView.swift in Sources */,
4BB99D0426FE191E001E4761 /* SafariDataImporter.swift in Sources */,
4B9DB0262A983B24000927DB /* WaitlistViewModel.swift in Sources */,
987799F929999973005D8EB6 /* LocalBookmarkStore.swift in Sources */,
Expand Down
47 changes: 32 additions & 15 deletions DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,12 @@ final class BookmarkListViewController: NSViewController {
importButton.translatesAutoresizingMaskIntoConstraints = false
importButton.isHidden = true

view.addSubview(KeyEquivalentView(keyEquivalents: [
[.command, "f"]: { [weak self] in
return self?.handleCmdF($0) ?? false
}
]))

setupLayout()
}

Expand Down Expand Up @@ -441,6 +447,7 @@ final class BookmarkListViewController: NSViewController {
self.outlineView.isHidden = isEmpty

if isEmpty {
self.hideSearchBar()
self.showEmptyStateView(for: .noBookmarks)
}
}
Expand Down Expand Up @@ -486,7 +493,9 @@ final class BookmarkListViewController: NSViewController {
private func hideSearchBar() {
isSearchVisible = false
outlineView.highlightedRow = nil
outlineView.makeMeFirstResponder()
if outlineView.isShown {
outlineView.makeMeFirstResponder()
}
searchBar.stringValue = ""
searchBar.removeFromSuperview()
boxDividerTopConstraint.isActive = true
Expand Down Expand Up @@ -521,6 +530,9 @@ final class BookmarkListViewController: NSViewController {
private func showEmptyStateView(for mode: BookmarksEmptyStateContent) {
emptyState.isHidden = false
outlineView.isHidden = true
if !isSearchVisible {
view.makeMeFirstResponder()
}
emptyStateTitle.stringValue = mode.title
emptyStateMessage.stringValue = mode.description
emptyStateImageView.image = mode.image
Expand All @@ -529,19 +541,23 @@ final class BookmarkListViewController: NSViewController {

// MARK: Actions

override func keyDown(with event: NSEvent) {
switch Int(event.keyCode) {
case kVK_ANSI_F where event.deviceIndependentFlags == .command:
guard bookmarkManager.list?.totalBookmarks != 0 else {
return
}
private func handleCmdF(_ event: NSEvent) -> Bool {
// start search on cmd+f when bookmarks are available
guard bookmarkManager.list?.totalBookmarks != 0 else {
__NSBeep()
return true
}

if isSearchVisible {
searchBar.makeMeFirstResponder()
} else {
showSearchBar()
}
if isSearchVisible {
searchBar.makeMeFirstResponder()
} else {
showSearchBar()
}
return true
}

override func keyDown(with event: NSEvent) {
switch Int(event.keyCode) {
case kVK_Return, kVK_ANSI_KeypadEnter, kVK_Space:
if outlineView.highlightedRow != nil {
// submit action when there‘s a highlighted row
Expand All @@ -556,9 +572,10 @@ final class BookmarkListViewController: NSViewController {
delegate?.closeBookmarksPopover(self)

default:
// start search when letters are typed
if let characters = event.characters,
!characters.isEmpty {
// start search when letters are typed when bookmarks are available
if event.deviceIndependentFlags.isEmpty,
let characters = event.characters, !characters.isEmpty,
bookmarkManager.list?.totalBookmarks != 0 {

showSearchBar()
searchBar.currentEditor()?.keyDown(with: event)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem
searchBar.placeholderString = UserText.bookmarksSearch
searchBar.delegate = self

view.addSubview(KeyEquivalentView(keyEquivalents: [
[.command, "f"]: { [weak self] in
return self?.handleCmdF($0) ?? false
}
]))

setupLayout()
}

Expand Down Expand Up @@ -281,14 +287,18 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem
override func keyDown(with event: NSEvent) {
if event.charactersIgnoringModifiers == String(UnicodeScalar(NSDeleteCharacter)!) {
deleteSelectedItems()
} else {
let commandKeyDown = event.modifierFlags.contains(.command)
if commandKeyDown && event.keyCode == 3 { // CMD + F
searchBar.makeMeFirstResponder()
}
}
}

private func handleCmdF(_ event: NSEvent) -> Bool {
guard case .nonEmpty = managementDetailViewModel.contentState else {
__NSBeep()
return true
}
searchBar.makeMeFirstResponder()
return true
}

fileprivate func reloadData() {
handleItemsVisibility()

Expand Down Expand Up @@ -668,6 +678,22 @@ extension BookmarkManagementDetailViewController: NSSearchFieldDelegate {
reloadData()
}
}

func control(_ control: NSControl, textView: NSTextView, doCommandBy selector: Selector) -> Bool {
guard control === searchBar else {
assertionFailure("Unexpected delegating control")
return false
}
switch selector {
case #selector(cancelOperation):
// handle Esc key press while in search mode
self.tableView.makeMeFirstResponder()
default:
return false
}
return true
}

}

#if DEBUG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,6 @@ final class BookmarkManagementSplitViewController: NSSplitViewController {
detailViewController.delegate = self
}

override func keyDown(with event: NSEvent) {
let commandKeyDown = event.modifierFlags.contains(.command)
if commandKeyDown && event.keyCode == 3 { // CMD + F
detailViewController.searchBar.makeMeFirstResponder()
} else {
super.keyDown(with: event)
}
}

}

extension BookmarkManagementSplitViewController: BookmarkManagementSidebarViewControllerDelegate {
Expand Down
82 changes: 82 additions & 0 deletions DuckDuckGo/Common/Extensions/NSEventExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ extension NSEvent {
modifierFlags.intersection(.deviceIndependentFlagsMask)
}

typealias KeyEquivalent = Set<KeyEquivalentElement>

var keyEquivalent: KeyEquivalent? {
KeyEquivalent(event: self)
}

/// is NSEvent representing right mouse down event or cntrl+mouse down event
static func isContextClick(_ event: NSEvent) -> Bool {
let isControlClick = event.type == .leftMouseDown && (event.modifierFlags.rawValue & NSEvent.ModifierFlags.control.rawValue != 0)
Expand Down Expand Up @@ -84,3 +90,79 @@ extension NSEvent {
}

}

enum KeyEquivalentElement: ExpressibleByStringLiteral, Hashable {
public typealias StringLiteralType = String

case charCode(String)
case command
case shift
case option
case control

static let backspace = KeyEquivalentElement.charCode("\u{8}")
static let tab = KeyEquivalentElement.charCode("\t")
static let left = KeyEquivalentElement.charCode("\u{2190}")
static let right = KeyEquivalentElement.charCode("\u{2192}")

init(stringLiteral value: String) {
self = .charCode(value)
}
}

extension NSEvent.KeyEquivalent: ExpressibleByStringLiteral, ExpressibleByUnicodeScalarLiteral, ExpressibleByExtendedGraphemeClusterLiteral {
public typealias StringLiteralType = String

public init(stringLiteral value: String) {
self = [.charCode(value)]
}

init?(event: NSEvent) {
guard [.keyDown, .keyUp].contains(event.type) else {
assertionFailure("wrong type of event \(event)")
return nil
}
guard let characters = event.characters else { return nil }
self = [.charCode(characters)]
if event.modifierFlags.contains(.command) {
self.insert(.command)
}
if event.modifierFlags.contains(.shift) {
self.insert(.shift)
}
if event.modifierFlags.contains(.option) {
self.insert(.option)
}
if event.modifierFlags.contains(.control) {
self.insert(.control)
}
}

var charCode: String {
for item in self {
if case .charCode(let value) = item {
return value
}
}
return ""
}

var modifierMask: NSEvent.ModifierFlags {
var result: NSEvent.ModifierFlags = []
for item in self {
switch item {
case .charCode: continue
case .command:
result.insert(.command)
case .shift:
result.insert(.shift)
case .option:
result.insert(.option)
case .control:
result.insert(.control)
}
}
return result
}

}
59 changes: 2 additions & 57 deletions DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extension NSMenuItem {
return item
}

convenience init(title string: String, action selector: Selector? = nil, target: AnyObject? = nil, keyEquivalent: [KeyEquivalentElement] = [], representedObject: Any? = nil, state: NSControl.StateValue = .off, items: [NSMenuItem]? = nil) {
convenience init(title string: String, action selector: Selector? = nil, target: AnyObject? = nil, keyEquivalent: NSEvent.KeyEquivalent = [], representedObject: Any? = nil, state: NSControl.StateValue = .off, items: [NSMenuItem]? = nil) {
self.init(title: string, action: selector, keyEquivalent: keyEquivalent.charCode)
if !keyEquivalent.modifierMask.isEmpty {
self.keyEquivalentModifierMask = keyEquivalent.modifierMask
Expand All @@ -40,7 +40,7 @@ extension NSMenuItem {
}
}

convenience init(title string: String, action selector: Selector? = nil, target: AnyObject? = nil, keyEquivalent: [KeyEquivalentElement] = [], representedObject: Any? = nil, state: NSControl.StateValue = .off, @MenuBuilder items: () -> [NSMenuItem]) {
convenience init(title string: String, action selector: Selector? = nil, target: AnyObject? = nil, keyEquivalent: NSEvent.KeyEquivalent = [], representedObject: Any? = nil, state: NSControl.StateValue = .off, @MenuBuilder items: () -> [NSMenuItem]) {
self.init(title: string, action: selector, target: target, keyEquivalent: keyEquivalent, representedObject: representedObject, state: state, items: items())
}

Expand Down Expand Up @@ -129,58 +129,3 @@ extension NSMenuItem {
}

}

enum KeyEquivalentElement: ExpressibleByStringLiteral {
public typealias StringLiteralType = String

case charCode(String)
case command
case shift
case option
case control

static let backspace = KeyEquivalentElement.charCode("\u{8}")
static let tab = KeyEquivalentElement.charCode("\t")
static let left = KeyEquivalentElement.charCode("\u{2190}")
static let right = KeyEquivalentElement.charCode("\u{2192}")

init(stringLiteral value: String) {
self = .charCode(value)
}
}

extension [KeyEquivalentElement]: ExpressibleByStringLiteral, ExpressibleByUnicodeScalarLiteral, ExpressibleByExtendedGraphemeClusterLiteral {
public typealias StringLiteralType = String

public init(stringLiteral value: String) {
self = [.charCode(value)]
}

var charCode: String {
for item in self {
if case .charCode(let value) = item {
return value
}
}
return ""
}

var modifierMask: NSEvent.ModifierFlags {
var result: NSEvent.ModifierFlags = []
for item in self {
switch item {
case .charCode: continue
case .command:
result.insert(.command)
case .shift:
result.insert(.shift)
case .option:
result.insert(.option)
case .control:
result.insert(.control)
}
}
return result
}

}
Loading
Loading