Skip to content
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
2 changes: 2 additions & 0 deletions Rectangle/ShortcutManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ class ShortcutManager {
shortcutIdentities = ShortcutCycle.shortcutIdentities(shortcutsByAction: currentShortcuts)
}
}

TodoManager.setShortcutBindingsSuspended(isRecording)
}

private func isRepeatAction(parameters: ExecutionParameters, windowElement: AccessibilityElement, windowId: CGWindowID) -> Bool {
Expand Down
12 changes: 10 additions & 2 deletions Rectangle/TodoMode/TodoManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class TodoManager {
static let toggleDefaultsKey = "toggleTodo"
static let reflowDefaultsKey = "reflowTodo"
static let defaultsKeys = [toggleDefaultsKey, reflowDefaultsKey]
private static var shortcutBindingsSuspended = false

static func setTodoMode(_ enabled: Bool, _ bringToFront: Bool = true) {
Defaults.todoMode.enabled = enabled
Expand Down Expand Up @@ -71,21 +72,28 @@ class TodoManager {
}

static func registerUnregisterToggleShortcut() {
if Defaults.todo.userEnabled {
if Defaults.todo.userEnabled && !shortcutBindingsSuspended {
registerToggleShortcut()
} else {
unregisterToggleShortcut()
}
}

static func registerUnregisterReflowShortcut() {
if Defaults.todo.userEnabled && Defaults.todoMode.enabled {
if Defaults.todo.userEnabled && Defaults.todoMode.enabled && !shortcutBindingsSuspended {
registerReflowShortcut()
} else {
unregisterReflowShortcut()
}
}

static func setShortcutBindingsSuspended(_ suspended: Bool) {
guard shortcutBindingsSuspended != suspended else { return }
shortcutBindingsSuspended = suspended
registerUnregisterToggleShortcut()
registerUnregisterReflowShortcut()
}

private static func isTodoShortcutBindable(_ defaultsKey: String) -> Bool {
guard let shortcut = shortcut(for: defaultsKey) else { return true }
return TodoShortcutConflict.conflict(for: shortcut, ignoringTodoDefaultsKey: defaultsKey) == nil
Expand Down
130 changes: 130 additions & 0 deletions RectangleTests/ShortcutRecordingObserverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,92 @@ import XCTest

class ShortcutRecordingObserverTests: XCTestCase {

private func withRegisteredTodoShortcuts(
_ assertions: (MASShortcutMonitor, MASShortcut, MASShortcut) throws -> Void
) throws {
let userDefaults = UserDefaults.standard
let previousToggleShortcut = userDefaults.object(forKey: TodoManager.toggleDefaultsKey)
let previousReflowShortcut = userDefaults.object(forKey: TodoManager.reflowDefaultsKey)
let previousTodoEnabled = Defaults.todo.enabled
let previousTodoModeEnabled = Defaults.todoMode.enabled
let binder = try XCTUnwrap(MASShortcutBinder.shared())
let previousBindingOptions = binder.bindingOptions
binder.bindingOptions = [NSBindingOption.valueTransformerName: MASDictionaryTransformerName]

TodoManager.setShortcutBindingsSuspended(true)
defer {
TodoManager.setShortcutBindingsSuspended(true)

if let previousToggleShortcut {
userDefaults.set(previousToggleShortcut, forKey: TodoManager.toggleDefaultsKey)
} else {
userDefaults.removeObject(forKey: TodoManager.toggleDefaultsKey)
}
if let previousReflowShortcut {
userDefaults.set(previousReflowShortcut, forKey: TodoManager.reflowDefaultsKey)
} else {
userDefaults.removeObject(forKey: TodoManager.reflowDefaultsKey)
}

Defaults.todo.enabled = previousTodoEnabled
Defaults.todoMode.enabled = previousTodoModeEnabled
TodoManager.setShortcutBindingsSuspended(false)
binder.bindingOptions = previousBindingOptions
}

let monitor = try XCTUnwrap(binder.shortcutMonitor)
let shortcuts = try availableTodoShortcuts(monitor: monitor)
let transformer = try XCTUnwrap(
ValueTransformer(forName: NSValueTransformerName(rawValue: MASDictionaryTransformerName))
)

userDefaults.set(
transformer.reverseTransformedValue(shortcuts.toggle),
forKey: TodoManager.toggleDefaultsKey
)
userDefaults.set(
transformer.reverseTransformedValue(shortcuts.reflow),
forKey: TodoManager.reflowDefaultsKey
)
Defaults.todo.enabled = true
Defaults.todoMode.enabled = true
TodoManager.setShortcutBindingsSuspended(false)

XCTAssertTrue(monitor.isShortcutRegistered(shortcuts.toggle))
XCTAssertTrue(monitor.isShortcutRegistered(shortcuts.reflow))

try assertions(monitor, shortcuts.toggle, shortcuts.reflow)
}

private func availableTodoShortcuts(
monitor: MASShortcutMonitor
) throws -> (toggle: MASShortcut, reflow: MASShortcut) {
let modifiers: NSEvent.ModifierFlags = [.command, .control, .option, .shift]
let keyCodes = [
kVK_ANSI_U,
kVK_ANSI_I,
kVK_ANSI_O,
kVK_ANSI_P,
kVK_ANSI_J,
kVK_ANSI_K,
kVK_ANSI_L,
kVK_ANSI_M
]
let windowShortcutIdentities = Set(
ShortcutCycle.shortcutsByAction().values.map { ShortcutCycle.ShortcutIdentity($0) }
)
let availableShortcuts = keyCodes
.map { MASShortcut(keyCode: $0, modifierFlags: modifiers) }
.filter {
!windowShortcutIdentities.contains(ShortcutCycle.ShortcutIdentity($0))
&& !monitor.isShortcutRegistered($0)
}

let toggle = try XCTUnwrap(availableShortcuts.first)
let reflow = try XCTUnwrap(availableShortcuts.dropFirst().first)
return (toggle, reflow)
}

func testPostsRecordingChangesForObservedShortcutViews() {
let observer = ShortcutRecordingObserver()
let firstShortcutView = MASShortcutView()
Expand Down Expand Up @@ -87,4 +173,48 @@ class ShortcutRecordingObserverTests: XCTestCase {
observer.recordingChanged(for: secondShortcutView, isRecording: false)
XCTAssertEqual(recordingChanges, [true, false])
}

func testTodoShortcutBindingsAreSuspendedAndRestored() throws {
try withRegisteredTodoShortcuts { monitor, toggleShortcut, reflowShortcut in
TodoManager.setShortcutBindingsSuspended(true)

XCTAssertFalse(monitor.isShortcutRegistered(toggleShortcut))
XCTAssertFalse(monitor.isShortcutRegistered(reflowShortcut))

TodoManager.setShortcutBindingsSuspended(false)

XCTAssertTrue(monitor.isShortcutRegistered(toggleShortcut))
XCTAssertTrue(monitor.isShortcutRegistered(reflowShortcut))
}
}

func testTodoShortcutBindingsCannotRebindWhileSuspended() throws {
try withRegisteredTodoShortcuts { monitor, toggleShortcut, reflowShortcut in
TodoManager.setShortcutBindingsSuspended(true)

TodoManager.registerUnregisterToggleShortcut()
TodoManager.registerUnregisterReflowShortcut()

XCTAssertFalse(monitor.isShortcutRegistered(toggleShortcut))
XCTAssertFalse(monitor.isShortcutRegistered(reflowShortcut))
}
}

func testResumingTodoShortcutBindingsHonorsTodoState() throws {
try withRegisteredTodoShortcuts { monitor, toggleShortcut, reflowShortcut in
TodoManager.setShortcutBindingsSuspended(true)
Defaults.todoMode.enabled = false
TodoManager.setShortcutBindingsSuspended(false)

XCTAssertTrue(monitor.isShortcutRegistered(toggleShortcut))
XCTAssertFalse(monitor.isShortcutRegistered(reflowShortcut))

TodoManager.setShortcutBindingsSuspended(true)
Defaults.todo.enabled = false
TodoManager.setShortcutBindingsSuspended(false)

XCTAssertFalse(monitor.isShortcutRegistered(toggleShortcut))
XCTAssertFalse(monitor.isShortcutRegistered(reflowShortcut))
}
}
}
Loading