Skip to content
Closed
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
115 changes: 84 additions & 31 deletions Free Ruler/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
guard let self = self else { return }

self.groupedRulerController = controller
self.updateDisplay()

guard let settingsController = self.rulerSettingsController,
settingsController.window?.isVisible == true else { return }
Expand Down Expand Up @@ -318,9 +319,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

func updateUnitMenu() {
pixelsMenuItem?.state = prefs.unit == .pixels ? .on : .off
millimetersMenuItem?.state = prefs.unit == .millimeters ? .on : .off
inchesMenuItem?.state = prefs.unit == .inches ? .on : .off
let unit = activeRulerSettings.unit
pixelsMenuItem?.state = unit == .pixels ? .on : .off
millimetersMenuItem?.state = unit == .millimeters ? .on : .off
inchesMenuItem?.state = unit == .inches ? .on : .off
}

func redrawRulers() {
Expand All @@ -341,15 +343,28 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

func updateFloatRulersMenuItem() {
floatRulersMenuItem?.state = prefs.floatRulers ? .on : .off
floatRulersMenuItem?.state = activeRulerSettings.floatRulers ? .on : .off
}

func updateGroupRulersMenuItem() {
groupRulersMenuItem?.state = prefs.groupRulers ? .on : .off
}

func updateRulerShadowMenuItem() {
rulerShadowMenuItem?.state = prefs.rulerShadow ? .on : .off
rulerShadowMenuItem?.state = activeRulerSettings.rulerShadow ? .on : .off
}

private var activeRulerSettings: RulerSettings {
return rulerManager.activeController?.state.settings ?? RulerSettings(defaults: prefs)
}

@discardableResult
private func updateActiveRulerSettings(_ update: (inout RulerSettings) -> Void) -> Bool {
guard let controller = rulerManager.activeController else { return false }

controller.updateSettings(update)
updateDisplay()
return true
}

func createRulersIfNeeded() {
Expand Down Expand Up @@ -393,6 +408,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if rulerManager.hasRulers {
let controller = rulerManager.activeController ?? rulerManager.createRuler()
controller.toggleWing(orientation)
updateDisplay()
updateMouseTickTimer()
return
}
Expand Down Expand Up @@ -622,28 +638,43 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

@IBAction func setUnitPixels(_ sender: Any) {
prefs.unit = .pixels
setUnit(.pixels)
}
@IBAction func setUnitMillimetres(_ sender: Any) {
prefs.unit = .millimeters
setUnit(.millimeters)
}
@IBAction func setUnitInches(_ sender: Any) {
prefs.unit = .inches
setUnit(.inches)
}
@IBAction func cycleUnits(_ sender: Any) {
switch prefs.unit {
let nextUnit: Unit
switch activeRulerSettings.unit {
case .pixels:
prefs.unit = .millimeters
nextUnit = .millimeters
case .millimeters:
prefs.unit = .inches
nextUnit = .inches
case .inches:
prefs.unit = .pixels
nextUnit = .pixels
}

showHotkeyBezel(format: .unitsFormat, unitLabel(prefs.unit), on: bezelScreen(for: sender))
setUnit(nextUnit)
showHotkeyBezel(format: .unitsFormat, unitLabel(nextUnit), on: bezelScreen(for: sender))
}

@IBAction func toggleFloatRulers(_ sender: Any) {
if let controller = rulerManager.activeController {
let shouldFloat = !controller.state.settings.floatRulers
controller.updateSettings { settings in
settings.floatRulers = shouldFloat
}
updateFloatRulersMenuItem()
showHotkeyBezel(
shouldFloat ? .rulersFloated : .rulersUnfloated,
on: bezelScreen(for: sender)
)
return
}

prefs.floatRulers = !prefs.floatRulers
showHotkeyBezel(
prefs.floatRulers ? .rulersFloated : .rulersUnfloated,
Expand All @@ -658,6 +689,19 @@ class AppDelegate: NSObject, NSApplicationDelegate {
showGroupRulersHotkeyBezel(on: bezelScreen(for: sender))
}
@IBAction func toggleRulerShadow(_ sender: Any) {
if let controller = rulerManager.activeController {
let shouldShowShadow = !controller.state.settings.rulerShadow
controller.updateSettings { settings in
settings.rulerShadow = shouldShowShadow
}
updateRulerShadowMenuItem()
showHotkeyBezel(
shouldShowShadow ? .shadowEnabled : .shadowDisabled,
on: bezelScreen(for: sender)
)
return
}

prefs.rulerShadow = !prefs.rulerShadow
showHotkeyBezel(
prefs.rulerShadow ? .shadowEnabled : .shadowDisabled,
Expand Down Expand Up @@ -746,16 +790,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

@IBAction func resetRulerPositions(_ sender: Any) {
if rulerManager.hasRulers {
prefs.zeroCorner = Prefs.defaultZeroCorner
for controller in rulerManager.controllers {
controller.state.settings.zeroCorner = Prefs.defaultZeroCorner
controller.state.layout = RulerLayoutState.defaults(
zeroCorner: Prefs.defaultZeroCorner
)
controller.state.visibility = RulerWingVisibility()
controller.show()
}
if let controller = rulerManager.activeController {
controller.state.settings.zeroCorner = Prefs.defaultZeroCorner
controller.state.layout = RulerLayoutState.defaults(
zeroCorner: Prefs.defaultZeroCorner
)
controller.state.visibility = RulerWingVisibility()
controller.show()
updateDisplay()
updateMouseTickTimer()
return
}
Expand Down Expand Up @@ -804,11 +846,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

if let controller = rulerManager.activeController {
let flippedCorner = prefs.zeroCorner.flipped(along: orientation)
let flippedCorner = controller.state.settings.zeroCorner.flipped(along: orientation)
controller.prepareForZeroCornerChange(to: flippedCorner)
controller.state.settings.zeroCorner = flippedCorner
prefs.zeroCorner = flippedCorner
controller.redrawForPreferenceChange()
updateDisplay()
return
}

Expand Down Expand Up @@ -859,6 +900,16 @@ class AppDelegate: NSObject, NSApplicationDelegate {
applyRulerWindowMode()
}

private func setUnit(_ unit: Unit) {
if updateActiveRulerSettings({ settings in
settings.unit = unit
}) {
return
}

prefs.unit = unit
}

func isRulerWindowShown(_ window: RulerWindow) -> Bool {
return window.isVisible || window.parent != nil || rulers.contains {
$0.rulerWindow.childWindows?.contains(window) == true
Expand Down Expand Up @@ -1009,15 +1060,16 @@ extension AppDelegate: NSMenuItemValidation {
case #selector(openRulerSettings(_:)):
return rulerManager.activeController != nil
case #selector(closeKeyWindow(_:)):
return NSApp.keyWindow?.isVisible == true
return rulerManager.activeController != nil || NSApp.keyWindow?.isVisible == true
case #selector(toggleGroupRulers(_:)):
return !rulerManager.hasRulers
case #selector(toggleHorizontalRuler(_:)):
if let controller = rulerManager.activeController {
menuItem.title = controller.state.isWingVisible(.horizontal)
let isVisible = controller.state.isWingVisible(.horizontal)
menuItem.title = isVisible
? NSLocalizedString("Hide Horizontal Ruler", comment: "Menu item title to hide the horizontal ruler")
: NSLocalizedString("Show Horizontal Ruler", comment: "Menu item title to show the horizontal ruler")
return true
return !isVisible || controller.state.isWingVisible(.vertical)
}

let ruler = existingRulerController(orientation: .horizontal)
Expand All @@ -1027,10 +1079,11 @@ extension AppDelegate: NSMenuItemValidation {
return canToggleRulerVisibility
case #selector(toggleVerticalRuler(_:)):
if let controller = rulerManager.activeController {
menuItem.title = controller.state.isWingVisible(.vertical)
let isVisible = controller.state.isWingVisible(.vertical)
menuItem.title = isVisible
? NSLocalizedString("Hide Vertical Ruler", comment: "Menu item title to hide the vertical ruler")
: NSLocalizedString("Show Vertical Ruler", comment: "Menu item title to show the vertical ruler")
return true
return !isVisible || controller.state.isWingVisible(.horizontal)
}

let ruler = existingRulerController(orientation: .vertical)
Expand Down
116 changes: 116 additions & 0 deletions FreeRulerTests/RulerCoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2513,6 +2513,122 @@ final class RulerCoreTests: XCTestCase {
XCTAssertTrue(second.groupedWindow.isRuleVisible(.vertical))
}

func testManagedCommandsApplySettingsToActiveRulerOnly() {
withRestoredRulerPreferences {
prefs.unit = .pixels
prefs.floatRulers = true
prefs.rulerShadow = false
let appDelegate = AppDelegate()
let first = appDelegate.rulerManager.createRuler(
defaults: RulerSettings(unit: .pixels, floatRulers: true, rulerShadow: false)
)
let second = appDelegate.rulerManager.createRuler(
defaults: RulerSettings(unit: .millimeters, floatRulers: true, rulerShadow: false)
)
defer {
first.hide()
second.hide()
}

appDelegate.rulerManager.markActive(first)
appDelegate.setUnitInches(self)
appDelegate.toggleFloatRulers(self)
appDelegate.toggleRulerShadow(self)

XCTAssertEqual(first.state.settings.unit, .inches)
XCTAssertFalse(first.state.settings.floatRulers)
XCTAssertTrue(first.state.settings.rulerShadow)
XCTAssertEqual(first.groupedWindow.horizontalRule.unit, .inches)
XCTAssertFalse(first.groupedWindow.isFloatingPanel)
XCTAssertTrue(first.groupedWindow.hasShadow)
XCTAssertEqual(second.state.settings.unit, .millimeters)
XCTAssertTrue(second.state.settings.floatRulers)
XCTAssertFalse(second.state.settings.rulerShadow)
XCTAssertEqual(prefs.unit, .pixels)
XCTAssertTrue(prefs.floatRulers)
XCTAssertFalse(prefs.rulerShadow)
}
}

func testManagedFlipAndResetUseActiveRulerWithoutChangingDefaults() {
withRestoredRulerPreferences {
prefs.zeroCorner = .topRight
let appDelegate = AppDelegate()
let first = appDelegate.rulerManager.createRuler(
defaults: RulerSettings(zeroCorner: .bottomLeft)
)
let second = appDelegate.rulerManager.createRuler(
defaults: RulerSettings(zeroCorner: .topLeft)
)
defer {
first.hide()
second.hide()
}

second.setWing(.vertical, isVisible: false)
appDelegate.rulerManager.markActive(second)
appDelegate.flipRulers(along: .horizontal)

XCTAssertEqual(second.state.settings.zeroCorner, .topRight)
XCTAssertEqual(second.groupedWindow.horizontalRule.zeroCorner, .topRight)
XCTAssertEqual(first.state.settings.zeroCorner, .bottomLeft)
XCTAssertEqual(prefs.zeroCorner, .topRight)

appDelegate.resetRulerPositions(self)

XCTAssertEqual(second.state.settings.zeroCorner, Prefs.defaultZeroCorner)
XCTAssertTrue(second.state.isWingVisible(.horizontal))
XCTAssertTrue(second.state.isWingVisible(.vertical))
XCTAssertEqual(first.state.settings.zeroCorner, .bottomLeft)
XCTAssertEqual(prefs.zeroCorner, .topRight)
}
}

func testManagedWingCommandsDoNotHideLastVisibleWing() {
let appDelegate = AppDelegate()
let controller = appDelegate.rulerManager.createRuler()
defer {
controller.hide()
}

appDelegate.rulerManager.markActive(controller)
controller.setWing(.vertical, isVisible: false)
appDelegate.toggleHorizontalRuler(self)

XCTAssertTrue(controller.state.isWingVisible(.horizontal))
XCTAssertFalse(controller.state.isWingVisible(.vertical))
}

func testManagedMenuValidationReflectsActiveRulerState() {
let appDelegate = AppDelegate()
let controller = appDelegate.rulerManager.createRuler()
defer {
controller.hide()
}
appDelegate.rulerManager.markActive(controller)
controller.setWing(.vertical, isVisible: false)

let closeItem = NSMenuItem(
title: "",
action: #selector(AppDelegate.closeKeyWindow(_:)),
keyEquivalent: ""
)
let horizontalItem = NSMenuItem(
title: "",
action: #selector(AppDelegate.toggleHorizontalRuler(_:)),
keyEquivalent: ""
)
let verticalItem = NSMenuItem(
title: "",
action: #selector(AppDelegate.toggleVerticalRuler(_:)),
keyEquivalent: ""
)

XCTAssertTrue(appDelegate.validateMenuItem(closeItem))
XCTAssertFalse(appDelegate.validateMenuItem(horizontalItem))
XCTAssertTrue(appDelegate.validateMenuItem(verticalItem))
}

func testUngroupedHorizontalFlipDoesNotMoveRulerWindows() {
withRestoredZeroCornerPreference {
let previousGroupRulers = prefs.groupRulers
Expand Down