feat: migrate to standard os-backed preferences (closes #161)
This commit is contained in:
1 parent
069382c119
commit
e28c43f227
9 files changed
+105
-248
No files matched your search
@@ -42,9 +42,9 @@ class DebugProfile {
|
||||
}
|
||||
|
||||
private static func appPreferences() -> String {
|
||||
return nestedSeparator + Preferences.rawValues
|
||||
return nestedSeparator + Preferences.all
|
||||
.sorted { $0.0 < $1.0 }
|
||||
.map { $0 + intraSeparator + $1 }
|
||||
.map { $0.key + intraSeparator + Preferences.getAsString($0.key)! }
|
||||
.joined(separator: nestedSeparator)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,193 +1,113 @@
|
||||
import Cocoa
|
||||
import Carbon.HIToolbox.Events
|
||||
|
||||
let defaults = UserDefaults.standard
|
||||
|
||||
class Preferences {
|
||||
// the following constant are not exposed as preferences but may be in the future, probably through macro preferences
|
||||
static let windowMaterial = NSVisualEffectView.Material.dark
|
||||
static let fontColor = NSColor.white
|
||||
static let windowPadding = CGFloat(23)
|
||||
static let interCellPadding = CGFloat(5)
|
||||
static let intraCellPadding = CGFloat(5)
|
||||
static let fontIconSize = CGFloat(20)
|
||||
|
||||
static let themeMacro = MacroPreferenceHelper<(CGFloat, CGFloat, CGFloat, NSColor, NSColor)>([
|
||||
MacroPreference(" macOS", (0, 5, 20, .clear, NSColor(red: 0, green: 0, blue: 0, alpha: 0.4))),
|
||||
MacroPreference("❖ Windows 10", (2, 0, 0, .white, .clear))
|
||||
])
|
||||
static let metaKeyMacro = MacroPreferenceHelper<([Int], NSEvent.ModifierFlags)>([
|
||||
MacroPreference("⌥ option", ([kVK_Option, kVK_RightOption], .option)),
|
||||
MacroPreference("⌃ control", ([kVK_Control, kVK_RightControl], .control)),
|
||||
MacroPreference("⌘ command", ([kVK_Command, kVK_RightCommand], .command))
|
||||
])
|
||||
static let showOnScreenMacro = MacroPreferenceHelper<ShowOnScreenPreference>([
|
||||
MacroPreference("Main screen", ShowOnScreenPreference.main),
|
||||
MacroPreference("Screen including mouse", ShowOnScreenPreference.mouse),
|
||||
])
|
||||
|
||||
static var defaults: [String: String] = [
|
||||
"maxScreenUsage": "80",
|
||||
"minCellsPerRow": "5",
|
||||
"maxCellsPerRow": "10",
|
||||
"minRows": "3",
|
||||
"iconSize": "32",
|
||||
"fontHeight": "15",
|
||||
"tabKeyCode": String(kVK_Tab),
|
||||
"metaKey": metaKeyMacro.macros[0].label,
|
||||
"windowDisplayDelay": "0",
|
||||
"theme": themeMacro.macros[0].label,
|
||||
"showOnScreen": showOnScreenMacro.macros[0].label,
|
||||
"hideSpaceNumberLabels": String(false),
|
||||
// default values
|
||||
static var defaultValues: [String : Any] = [
|
||||
"maxScreenUsage": Float(80),
|
||||
"minCellsPerRow": Float(5),
|
||||
"maxCellsPerRow": Float(10),
|
||||
"minRows": Float(3),
|
||||
"iconSize": Float(32),
|
||||
"fontHeight": Float(15),
|
||||
"tabKeyCode": kVK_Tab,
|
||||
"windowDisplayDelay": 0,
|
||||
"metaKey": MacroPreferences.metaKeyList.keys.first!,
|
||||
"theme": MacroPreferences.themeList.keys.first!,
|
||||
"showOnScreen": MacroPreferences.showOnScreenList.keys.first!,
|
||||
"hideSpaceNumberLabels": false,
|
||||
]
|
||||
static var rawValues = [String: String]()
|
||||
|
||||
static var cellBorderWidth: CGFloat!
|
||||
static var cellCornerRadius: CGFloat!
|
||||
static var tabKeyCode: UInt16!
|
||||
static var highlightBorderColor: NSColor!
|
||||
static var highlightBackgroundColor: NSColor!
|
||||
static var metaKeyCodes: [UInt16]!
|
||||
static var metaModifierFlag: NSEvent.ModifierFlags!
|
||||
static var windowDisplayDelay: DispatchTimeInterval!
|
||||
static var windowCornerRadius: CGFloat!
|
||||
static var showOnScreen: ShowOnScreenPreference!
|
||||
static var hideSpaceNumberLabels: Bool!
|
||||
static var maxScreenUsage: CGFloat!
|
||||
static var iconSize: CGFloat!
|
||||
static var fontHeight: CGFloat!
|
||||
static var font: NSFont!
|
||||
static var minCellsPerRow: CGFloat!
|
||||
static var maxCellsPerRow: CGFloat!
|
||||
static var minRows: CGFloat!
|
||||
// constant values
|
||||
// not exposed as preferences now but may be in the future, probably through macro preferences
|
||||
static var windowMaterial: NSVisualEffectView.Material { .dark }
|
||||
static var fontColor: NSColor { .white }
|
||||
static var windowPadding: CGFloat { 23 }
|
||||
static var interCellPadding: CGFloat { 5 }
|
||||
static var intraCellPadding: CGFloat { 5 }
|
||||
static var fontIconSize: CGFloat { 20 }
|
||||
|
||||
private static let defaultsFile = fileFromPreferencesFolder("alt-tab-macos-defaults.json")
|
||||
private static let userFile = fileFromPreferencesFolder("alt-tab-macos.json")
|
||||
// persisted values
|
||||
static var maxScreenUsage: CGFloat { CGFloat(defaults.float(forKey: "maxScreenUsage") / 100) }
|
||||
static var minCellsPerRow: CGFloat { CGFloat(defaults.float(forKey: "minCellsPerRow")) }
|
||||
static var maxCellsPerRow: CGFloat { CGFloat(defaults.float(forKey: "maxCellsPerRow")) }
|
||||
static var minRows: CGFloat { CGFloat(defaults.float(forKey: "minRows")) }
|
||||
static var iconSize: CGFloat { CGFloat(defaults.float(forKey: "iconSize")) }
|
||||
static var fontHeight: CGFloat { CGFloat(defaults.float(forKey: "fontHeight")) }
|
||||
static var tabKeyCode: UInt16 { UInt16(defaults.integer(forKey: "tabKeyCode")) }
|
||||
static var windowDisplayDelay: DispatchTimeInterval { DispatchTimeInterval.milliseconds(defaults.integer(forKey: "windowDisplayDelay")) }
|
||||
static var hideSpaceNumberLabels: Bool { defaults.bool(forKey: "hideSpaceNumberLabels") }
|
||||
|
||||
static func loadFromDiskAndUpdateValues() {
|
||||
do {
|
||||
try saveDefaultsToDisk()
|
||||
let preferencesExist = FileManager.default.fileExists(atPath: userFile.path)
|
||||
if !preferencesExist {
|
||||
try FileManager.default.copyItem(at: defaultsFile, to: userFile)
|
||||
}
|
||||
rawValues = try loadFromDisk(userFile)
|
||||
if preferencesExist {
|
||||
let compatiblePreferences = rawValues.filter { defaults[$0.key] != nil }
|
||||
rawValues = defaults.merging(compatiblePreferences) { (_, new) in new }
|
||||
}
|
||||
try rawValues.forEach { try updateAndValidateFromString($0.key, $0.value) }
|
||||
if preferencesExist {
|
||||
try saveRawToDisk()
|
||||
}
|
||||
} catch {
|
||||
debugPrint("Error loading preferences", error)
|
||||
if (FileManager.default.fileExists(atPath: userFile.path)) {
|
||||
try! FileManager.default.removeItem(at: userFile)
|
||||
}
|
||||
loadFromDiskAndUpdateValues()
|
||||
}
|
||||
// macro values
|
||||
static var theme: Theme { MacroPreferences.themeList[defaults.string(forKey: "theme")!]! }
|
||||
static var metaKey: MetaKey { MacroPreferences.metaKeyList[defaults.string(forKey: "metaKey")!]! }
|
||||
static var showOnScreen: ShowOnScreenPreference { MacroPreferences.showOnScreenList[defaults.string(forKey: "showOnScreen")!]! }
|
||||
|
||||
// derived values
|
||||
static var cellBorderWidth: CGFloat { theme.cellBorderWidth }
|
||||
static var cellCornerRadius: CGFloat { theme.cellCornerRadius }
|
||||
static var windowCornerRadius: CGFloat { theme.windowCornerRadius }
|
||||
static var highlightBorderColor: NSColor { theme.highlightBorderColor }
|
||||
static var highlightBackgroundColor: NSColor { theme.highlightBackgroundColor }
|
||||
static var metaKeyCodes: [UInt16] { metaKey.keyCodes.map { UInt16($0) } }
|
||||
static var metaModifierFlag: NSEvent.ModifierFlags { metaKey.modifierFlag }
|
||||
static var font: NSFont { NSFont.systemFont(ofSize: fontHeight) }
|
||||
|
||||
static func registerDefaults() {
|
||||
defaults.register(defaults: defaultValues)
|
||||
}
|
||||
|
||||
static func updateAndValidateFromString(_ valueName: String, _ value: String) throws {
|
||||
switch valueName {
|
||||
case "maxScreenUsage":
|
||||
maxScreenUsage = try CGFloat(CGFloat(value).orThrow() / 100)
|
||||
case "minCellsPerRow":
|
||||
minCellsPerRow = try CGFloat(value).orThrow()
|
||||
case "maxCellsPerRow":
|
||||
maxCellsPerRow = try CGFloat(value).orThrow()
|
||||
case "minRows":
|
||||
minRows = try CGFloat(value).orThrow()
|
||||
case "iconSize":
|
||||
iconSize = try CGFloat(value).orThrow()
|
||||
case "fontHeight":
|
||||
fontHeight = try CGFloat(value).orThrow()
|
||||
font = NSFont.systemFont(ofSize: fontHeight)
|
||||
case "tabKeyCode":
|
||||
tabKeyCode = try UInt16(value).orThrow()
|
||||
case "metaKey":
|
||||
let p = try metaKeyMacro.labelToMacro[value].orThrow()
|
||||
metaKeyCodes = p.preferences.0.map { UInt16($0) }
|
||||
metaModifierFlag = p.preferences.1
|
||||
case "theme":
|
||||
let p = try themeMacro.labelToMacro[value].orThrow()
|
||||
cellBorderWidth = p.preferences.0
|
||||
cellCornerRadius = p.preferences.1
|
||||
windowCornerRadius = p.preferences.2
|
||||
highlightBorderColor = p.preferences.3
|
||||
highlightBackgroundColor = p.preferences.4
|
||||
case "windowDisplayDelay":
|
||||
windowDisplayDelay = DispatchTimeInterval.milliseconds(try Int(value).orThrow())
|
||||
case "showOnScreen":
|
||||
let p = try showOnScreenMacro.labelToMacro[value].orThrow()
|
||||
showOnScreen = p.preferences
|
||||
case "hideSpaceNumberLabels":
|
||||
hideSpaceNumberLabels = try Bool(value).orThrow()
|
||||
default:
|
||||
throw NSError.make(domain: "Preferences", message: "Tried to update an unknown preference: '\(valueName)' = '\(value)'")
|
||||
}
|
||||
rawValues[valueName] = value
|
||||
static func get(_ key: String) -> Any? {
|
||||
defaults.object(forKey: key)
|
||||
}
|
||||
|
||||
static func saveRawToDisk() throws {
|
||||
ProcessInfo.processInfo.disableSuddenTermination()
|
||||
try saveToDisk(rawValues, userFile)
|
||||
ProcessInfo.processInfo.enableSuddenTermination()
|
||||
static func getAsString(_ key: String) -> String? {
|
||||
defaults.string(forKey: key)
|
||||
}
|
||||
|
||||
private static func preferencesVersion(_ url: URL) throws -> Int {
|
||||
return try Int(loadFromDisk(url)["version"] ?? "0").orThrow()
|
||||
static func set(_ key: String, _ value: Any?) {
|
||||
defaults.set(value, forKey: key)
|
||||
}
|
||||
|
||||
private static func loadFromDisk(_ url: URL) throws -> [String: String] {
|
||||
return try JSONDecoder().decode([String: String].self, from: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
private static func saveDefaultsToDisk() throws {
|
||||
try saveToDisk(defaults, defaultsFile)
|
||||
}
|
||||
|
||||
private static func saveToDisk(_ values: [String: String], _ path: URL) throws {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
try encoder
|
||||
.encode(values)
|
||||
.write(to: path)
|
||||
}
|
||||
|
||||
private static func fileFromPreferencesFolder(_ fileName: String) -> URL {
|
||||
return FileManager.default
|
||||
.urls(for: .libraryDirectory, in: .userDomainMask)
|
||||
.first!
|
||||
.appendingPathComponent("Preferences", isDirectory: true)
|
||||
.appendingPathComponent(fileName)
|
||||
}
|
||||
static var all: [String: Any] { defaults.dictionaryRepresentation() }
|
||||
}
|
||||
|
||||
struct MacroPreference<T> {
|
||||
struct Theme {
|
||||
let label: String
|
||||
let preferences: T
|
||||
|
||||
init(_ label: String, _ preferences: T) {
|
||||
self.label = label
|
||||
self.preferences = preferences
|
||||
}
|
||||
let cellBorderWidth: CGFloat
|
||||
let cellCornerRadius: CGFloat
|
||||
let windowCornerRadius: CGFloat
|
||||
let highlightBorderColor: NSColor
|
||||
let highlightBackgroundColor: NSColor
|
||||
}
|
||||
|
||||
class MacroPreferenceHelper<T> {
|
||||
let macros: [MacroPreference<T>]
|
||||
var labels = [String]()
|
||||
var labelToMacro = [String: MacroPreference<T>]()
|
||||
|
||||
init(_ array: [MacroPreference<T>]) {
|
||||
self.macros = array
|
||||
array.forEach {
|
||||
labelToMacro[$0.label] = $0
|
||||
labels.append($0.label)
|
||||
}
|
||||
}
|
||||
struct MetaKey {
|
||||
let label: String
|
||||
let keyCodes: [Int]
|
||||
let modifierFlag: NSEvent.ModifierFlags
|
||||
}
|
||||
|
||||
enum ShowOnScreenPreference {
|
||||
case main
|
||||
case mouse
|
||||
}
|
||||
|
||||
// macros are collection of values derived from a single key
|
||||
// we don't want to store every value in UserDefaults as the user could change them and contradict the macro
|
||||
class MacroPreferences {
|
||||
static let themeList = [
|
||||
" macOS": Theme(label: " macOS", cellBorderWidth: 0, cellCornerRadius: 5, windowCornerRadius: 20, highlightBorderColor: .clear, highlightBackgroundColor: NSColor(red: 0, green: 0, blue: 0, alpha: 0.4)),
|
||||
"❖ Windows 10": Theme(label: "❖ Windows 10", cellBorderWidth: 2, cellCornerRadius: 0, windowCornerRadius: 0, highlightBorderColor: .white, highlightBackgroundColor: .clear),
|
||||
]
|
||||
static let metaKeyList = [
|
||||
"⌥ option": MetaKey(label: "⌥ option", keyCodes: [kVK_Option, kVK_RightOption], modifierFlag: .option),
|
||||
"⌃ control": MetaKey(label: "⌃ control", keyCodes: [kVK_Control, kVK_RightControl], modifierFlag: .control),
|
||||
"⌘ command": MetaKey(label: "⌘ command", keyCodes: [kVK_Command, kVK_RightCommand], modifierFlag: .command)
|
||||
]
|
||||
static let showOnScreenList = [
|
||||
"Main screen": ShowOnScreenPreference.main,
|
||||
"Screen including mouse": ShowOnScreenPreference.mouse,
|
||||
]
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import Cocoa
|
||||
|
||||
class Screen {
|
||||
static func preferred() -> NSScreen {
|
||||
switch Preferences.showOnScreen! {
|
||||
switch Preferences.showOnScreen {
|
||||
case .mouse: return withMouse() ?? NSScreen.main!; // .main as fall-back
|
||||
case .main: return NSScreen.main!;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class App: NSApplication, NSApplicationDelegate, NSWindowDelegate {
|
||||
#endif
|
||||
SystemPermissions.ensureAccessibilityCheckboxIsChecked()
|
||||
SystemPermissions.ensureScreenRecordingCheckboxIsChecked()
|
||||
Preferences.loadFromDiskAndUpdateValues()
|
||||
Preferences.registerDefaults()
|
||||
statusItem = Menubar.make(self)
|
||||
loadMainMenuXib()
|
||||
initPreferencesDependentComponents()
|
||||
|
||||
@@ -4,7 +4,7 @@ class LabelAndControl: NSObject {
|
||||
static var callbackTarget: PreferencesWindow!
|
||||
|
||||
static func makeLabelWithInput(_ labelText: String, _ rawName: String, _ width: CGFloat, _ suffixText: String? = nil, _ suffixUrl: String? = nil, _ validator: ((String) -> Bool)? = nil) -> [NSView] {
|
||||
let input = TextField(Preferences.rawValues[rawName]!)
|
||||
let input = TextField(Preferences.getAsString(rawName)!)
|
||||
input.validationHandler = validator
|
||||
input.delegate = input
|
||||
input.visualizeValidationState()
|
||||
@@ -15,19 +15,19 @@ class LabelAndControl: NSObject {
|
||||
|
||||
static func makeLabelWithCheckbox(_ labelText: String, _ rawName: String) -> [NSView] {
|
||||
let checkbox = NSButton(checkboxWithTitle: "", target: nil, action: nil)
|
||||
setControlValue(checkbox, Preferences.rawValues[rawName]!)
|
||||
setControlValue(checkbox, Preferences.getAsString(rawName)!)
|
||||
return makeLabelWithProvidedControl(labelText, rawName, checkbox)
|
||||
}
|
||||
|
||||
static func makeLabelWithDropdown(_ labelText: String, _ rawName: String, _ values: [String], _ suffixText: String? = nil) -> [NSView] {
|
||||
let popUp = NSPopUpButton()
|
||||
popUp.addItems(withTitles: values)
|
||||
popUp.selectItem(withTitle: Preferences.rawValues[rawName]!)
|
||||
popUp.selectItem(withTitle: Preferences.getAsString(rawName)!)
|
||||
return makeLabelWithProvidedControl(labelText, rawName, popUp, suffixText)
|
||||
}
|
||||
|
||||
static func makeLabelWithSlider(_ labelText: String, _ rawName: String, _ minValue: Double, _ maxValue: Double, _ numberOfTickMarks: Int, _ allowsTickMarkValuesOnly: Bool, _ unitText: String = "") -> [NSView] {
|
||||
let value = Preferences.rawValues[rawName]!
|
||||
let value = Preferences.getAsString(rawName)!
|
||||
let suffixText = value + " " + unitText
|
||||
let slider = NSSlider()
|
||||
slider.minValue = minValue
|
||||
@@ -74,8 +74,6 @@ class LabelAndControl: NSObject {
|
||||
return suffix
|
||||
}
|
||||
|
||||
|
||||
|
||||
static func getControlValue(_ control: NSControl) -> String {
|
||||
if control is NSPopUpButton {
|
||||
return (control as! NSPopUpButton).titleOfSelectedItem!
|
||||
@@ -107,13 +105,10 @@ class LabelAndControl: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static func updateSuffixWithValue(_ control: NSControl, _ value: String) {
|
||||
let suffixIdentifierPredicate = { (view: NSView) -> Bool in
|
||||
view.identifier?.rawValue == control.identifier!.rawValue + ControlIdentifierDiscriminator.SUFFIX.rawValue
|
||||
}
|
||||
|
||||
if let suffixView: NSTextField = control.superview?.subviews.first(where: suffixIdentifierPredicate) as? NSTextField {
|
||||
let regex = try! NSRegularExpression(pattern: "^[0-9]+") // first decimal
|
||||
let range = NSMakeRange(0, suffixView.stringValue.count)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Cocoa
|
||||
|
||||
class PreferencesWindow: NSWindow {
|
||||
var windowCloseRequested = false
|
||||
let tabViewController = TabViewController()
|
||||
|
||||
override init(contentRect: NSRect, styleMask style: StyleMask, backing backingStoreType: BackingStoreType, defer flag: Bool) {
|
||||
@@ -17,29 +16,10 @@ class PreferencesWindow: NSWindow {
|
||||
}
|
||||
|
||||
func controlWasChanged(_ senderControl: NSControl) {
|
||||
let key: String = senderControl.identifier!.rawValue
|
||||
let previousValue: String = Preferences.rawValues[key]!
|
||||
let newValue: String = LabelAndControl.getControlValue(senderControl)
|
||||
let invalidTextField = senderControl is TextField && !(senderControl as! TextField).isValid()
|
||||
|
||||
if (invalidTextField && !windowCloseRequested) || (newValue == previousValue && !invalidTextField) {
|
||||
return
|
||||
}
|
||||
|
||||
let newValue = LabelAndControl.getControlValue(senderControl)
|
||||
LabelAndControl.updateControlExtras(senderControl, newValue)
|
||||
|
||||
do {
|
||||
// TODO: remove conditional as soon a Preference does validation on its own
|
||||
if invalidTextField && windowCloseRequested {
|
||||
throw NSError.make(domain: "Preferences", message: "Please enter a valid value for '" + key + "'")
|
||||
}
|
||||
try Preferences.updateAndValidateFromString(key, newValue)
|
||||
try Preferences.saveRawToDisk()
|
||||
(App.shared as! App).initPreferencesDependentComponents()
|
||||
} catch let error {
|
||||
debugPrint("PreferencesWindow: save: error", key, newValue, error)
|
||||
showSaveErrorSheetModal(error as NSError, senderControl, key, previousValue) // allows recursive call by user choice
|
||||
}
|
||||
Preferences.set(senderControl.identifier!.rawValue, newValue)
|
||||
(App.shared as! App).initPreferencesDependentComponents()
|
||||
}
|
||||
|
||||
private func setupWindow() {
|
||||
@@ -56,47 +36,4 @@ class PreferencesWindow: NSWindow {
|
||||
tabViewController.addTabViewItem(UpdatesTab.make())
|
||||
tabViewController.addTabViewItem(AboutTab.make())
|
||||
}
|
||||
|
||||
private func challengeNextInvalidEditableTextField() {
|
||||
let invalidFields = (contentView?
|
||||
.findNestedViews(subclassOf: TextField.self)
|
||||
.filter({ !$0.isValid() })
|
||||
)
|
||||
let focusedField = invalidFields?.filter({ $0.currentEditor() != nil }).first
|
||||
let fieldToNotify = focusedField ?? invalidFields?.first
|
||||
fieldToNotify?.delegate?.controlTextDidChange?(Notification(name: NSControl.textDidChangeNotification, object: fieldToNotify))
|
||||
|
||||
if fieldToNotify != focusedField {
|
||||
makeFirstResponder(fieldToNotify)
|
||||
}
|
||||
}
|
||||
|
||||
private func showSaveErrorSheetModal(_ nsError: NSError, _ control: NSControl, _ key: String, _ previousValue: String) {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = NSLocalizedString("Could not save Preference", comment: "")
|
||||
alert.informativeText = nsError.localizedDescription + "\n"
|
||||
alert.addButton(withTitle: NSLocalizedString("Edit", comment: ""))
|
||||
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
|
||||
alert.addButton(withTitle: NSLocalizedString("Check again", comment: ""))
|
||||
|
||||
alert.beginSheetModal(for: self, completionHandler: { (modalResponse: NSApplication.ModalResponse) -> Void in
|
||||
if modalResponse == NSApplication.ModalResponse.alertFirstButtonReturn {
|
||||
debugPrint("PreferencesWindow: save: error: user choice: edit")
|
||||
self.windowCloseRequested = false
|
||||
}
|
||||
if modalResponse == NSApplication.ModalResponse.alertSecondButtonReturn {
|
||||
debugPrint("PreferencesWindow: save: error: user choice: cancel -> revert value and eventually close window")
|
||||
try! Preferences.updateAndValidateFromString(key, previousValue)
|
||||
LabelAndControl.setControlValue(control, previousValue)
|
||||
LabelAndControl.updateControlExtras(control, previousValue)
|
||||
if self.windowCloseRequested {
|
||||
self.close()
|
||||
}
|
||||
}
|
||||
if modalResponse == NSApplication.ModalResponse.alertThirdButtonReturn {
|
||||
debugPrint("PreferencesWindow: save: error: user choice: check again")
|
||||
self.controlWasChanged(control)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,14 @@ class AppearanceTab {
|
||||
|
||||
private static func makeView() -> NSGridView {
|
||||
let view = GridView.make([
|
||||
LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Theme", comment: ""), "theme", Preferences.themeMacro.labels),
|
||||
LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Theme", comment: ""), "theme", MacroPreferences.themeList.values.map { $0.label }),
|
||||
LabelAndControl.makeLabelWithSlider(NSLocalizedString("Max size on screen", comment: ""), "maxScreenUsage", 10, 100, 10, true, "%"),
|
||||
LabelAndControl.makeLabelWithSlider(NSLocalizedString("Min windows per row", comment: ""), "minCellsPerRow", 1, 20, 20, true),
|
||||
LabelAndControl.makeLabelWithSlider(NSLocalizedString("Max windows per row", comment: ""), "maxCellsPerRow", 1, 40, 20, true),
|
||||
LabelAndControl.makeLabelWithSlider(NSLocalizedString("Min rows of windows", comment: ""), "minRows", 1, 20, 20, true),
|
||||
LabelAndControl.makeLabelWithSlider(NSLocalizedString("Window app icon size", comment: ""), "iconSize", 0, 64, 11, false, "px"),
|
||||
LabelAndControl.makeLabelWithSlider(NSLocalizedString("Window title font size", comment: ""), "fontHeight", 0, 64, 11, false, "px"),
|
||||
LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Show on", comment: ""), "showOnScreen", Preferences.showOnScreenMacro.labels),
|
||||
LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Show on", comment: ""), "showOnScreen", Array(MacroPreferences.showOnScreenList.keys)),
|
||||
LabelAndControl.makeLabelWithSlider(NSLocalizedString("Apparition delay", comment: ""), "windowDisplayDelay", 0, 2000, 11, false, "ms"),
|
||||
LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide space number labels", comment: ""), "hideSpaceNumberLabels"),
|
||||
])
|
||||
|
||||
@@ -22,7 +22,7 @@ class ShortcutsTab {
|
||||
}
|
||||
|
||||
let view = GridView.make([
|
||||
LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Alt key", comment: ""), "metaKey", Preferences.metaKeyMacro.labels),
|
||||
LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Alt key", comment: ""), "metaKey", MacroPreferences.metaKeyList.values.map { $0.label }),
|
||||
LabelAndControl.makeLabelWithInput(NSLocalizedString("Tab key", comment: ""), "tabKeyCode", 33, NSLocalizedString("KeyCodes Reference", comment: ""), "https://eastmanreference.com/complete-list-of-applescript-key-codes", tabKeyCodeValidator),
|
||||
])
|
||||
view.column(at: 0).xPlacement = .trailing
|
||||
|
||||
@@ -6,6 +6,8 @@ class UpdatesTab: NSObject {
|
||||
static var periodicallyCheck: NSButton!
|
||||
static var periodicallyInstall: NSButton!
|
||||
static var policyObserver = PolicyObserver()
|
||||
// this helps prevent double-dipping (i.e. user updates the UI > changes the preference > updates the UI)
|
||||
static var policyLock = false
|
||||
|
||||
static func make() -> NSTabViewItem {
|
||||
return TabViewItem.make(NSLocalizedString("Updates", comment: ""), NSImage.refreshTemplateName, makeView())
|
||||
@@ -49,13 +51,16 @@ class UpdatesTab: NSObject {
|
||||
|
||||
@objc
|
||||
static func updatePolicyCallback() {
|
||||
policyLock = true
|
||||
SUUpdater.shared().automaticallyDownloadsUpdates = periodicallyInstall.state == .on
|
||||
SUUpdater.shared().automaticallyChecksForUpdates = periodicallyInstall.state == .on || periodicallyCheck.state == .on
|
||||
policyLock = false
|
||||
}
|
||||
}
|
||||
|
||||
class PolicyObserver: NSObject {
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
|
||||
guard !UpdatesTab.policyLock else { return }
|
||||
if SUUpdater.shared().automaticallyDownloadsUpdates {
|
||||
UpdatesTab.periodicallyInstall.state = .on
|
||||
// Sparkle UI "Automatically download and install updates in the future" doesn't activate periodical checks; we do it manually
|
||||
|
||||
Reference in new issue
Block a user