feat: improve performance by caching preferences better

This commit is contained in:
lwouis committed 2025-01-04 01:18:50 +01:00
1 parent c2ccccd093
commit d653a54fc3
6 files changed
+365 -346

No files matched your search

+4
View File
@@ -80,6 +80,7 @@
BF0C8052AE41B1B10E42BFCE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C875C983226CB16DBD90F /* InfoPlist.strings */; };
BF0C8053A58DFB383A3E3351 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8E237569FA2A8FE20707 /* InfoPlist.strings */; };
BF0C807D26E465F467B066F7 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C849EBA5A547912BD8BB9 /* Localizable.strings */; };
BF0C80C69EFEF93C18539925 /* PreferencesMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C8F58B38F29B2662E6C2D /* PreferencesMigrations.swift */; };
BF0C80FCECC1386EB73B66E5 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8E9B9EF1485A14AE3316 /* Localizable.strings */; };
BF0C812C068CD751CE9BFB67 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8D5993267A74D4875C1A /* Localizable.strings */; };
BF0C81480366BAD71BC9A158 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8ABA423E72218605CA4F /* InfoPlist.strings */; };
@@ -475,6 +476,7 @@
BF0C8EAF02309B9C489E65F4 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
BF0C8EC400A560FDF99F8C35 /* el */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = el; path = Localizable.strings; sourceTree = "<group>"; };
BF0C8EE17F3ABD7A6D44BFFE /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = Localizable.strings; sourceTree = "<group>"; };
BF0C8F58B38F29B2662E6C2D /* PreferencesMigrations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesMigrations.swift; sourceTree = "<group>"; };
BF0C8FC083A2443F3EAF5DB7 /* preferences-blacklist.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "preferences-blacklist.jpg"; sourceTree = "<group>"; };
C0712B3BEA2B3780398C0999 /* Pods_alt_tab_macos.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_alt_tab_macos.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C7E6F409CA442F83B7BE71AD /* Pods_unit_tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_unit_tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1198,6 +1200,7 @@
BF0C8D606B53D315F2D9927B /* KeyRepeatTimer.swift */,
BF0C8D57C86AF492D43A576D /* AppearanceTestable.swift */,
BF0C8998862E0E04D60CACBC /* MacroPreferences.swift */,
BF0C8F58B38F29B2662E6C2D /* PreferencesMigrations.swift */,
);
path = logic;
sourceTree = "<group>";
@@ -2321,6 +2324,7 @@
BF0C8A49891371E4027E0641 /* MacroPreferences.swift in Sources */,
BF0C8E366BC25171C0A3881C /* ApplicationServices.HIServices.framework.swift in Sources */,
BF0C803A13C409E7F01FBCAB /* SkyLight.framework.swift in Sources */,
BF0C80C69EFEF93C18539925 /* PreferencesMigrations.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
+2 -2
View File
@@ -33,8 +33,8 @@ class KeyRepeatTimer {
private static func activateTimerForRepeatingKey(_ atShortcut: ATShortcut, _ block: @escaping () -> Void) {
if ((timer == nil || !timer!.isValid) && atShortcut.state != .up) {
let repeatRate = ticksToSeconds(UserDefaults.standard.string(forKey: "KeyRepeat") ?? "6")
let initialDelay = ticksToSeconds(UserDefaults.standard.string(forKey: "InitialKeyRepeat") ?? "25")
let repeatRate = ticksToSeconds(CachedUserDefaults.globalString("KeyRepeat") ?? "6")
let initialDelay = ticksToSeconds(CachedUserDefaults.globalString("InitialKeyRepeat") ?? "25")
timer = Timer(fire: Date(timeIntervalSinceNow: initialDelay), interval: repeatRate, repeats: true) { _ in
handleEvent(atShortcut, block)
}
+100 -328
View File
@@ -90,80 +90,71 @@ class Preferences {
static var finderShowsQuitMenuItem: Bool { UserDefaults(suiteName: "com.apple.Finder")?.bool(forKey: "QuitMenuItem") ?? false }
// persisted values
static var holdShortcut: [String] { ["holdShortcut", "holdShortcut2", "holdShortcut3"].map { UserDefaults.standard.string($0) } }
static var nextWindowShortcut: [String] { ["nextWindowShortcut", "nextWindowShortcut2", "nextWindowShortcut3"].map { UserDefaults.standard.string($0) } }
static var nextWindowGesture: GesturePreference { UserDefaults.standard.macroPref("nextWindowGesture", GesturePreference.allCases) }
static var focusWindowShortcut: String { UserDefaults.standard.string("focusWindowShortcut") }
static var previousWindowShortcut: String { UserDefaults.standard.string("previousWindowShortcut") }
static var cancelShortcut: String { UserDefaults.standard.string("cancelShortcut") }
static var closeWindowShortcut: String { UserDefaults.standard.string("closeWindowShortcut") }
static var minDeminWindowShortcut: String { UserDefaults.standard.string("minDeminWindowShortcut") }
static var toggleFullscreenWindowShortcut: String { UserDefaults.standard.string("toggleFullscreenWindowShortcut") }
static var quitAppShortcut: String { UserDefaults.standard.string("quitAppShortcut") }
static var hideShowAppShortcut: String { UserDefaults.standard.string("hideShowAppShortcut") }
static var holdShortcut: [String] { ["holdShortcut", "holdShortcut2", "holdShortcut3"].map { CachedUserDefaults.string($0) } }
static var nextWindowShortcut: [String] { ["nextWindowShortcut", "nextWindowShortcut2", "nextWindowShortcut3"].map { CachedUserDefaults.string($0) } }
static var nextWindowGesture: GesturePreference { CachedUserDefaults.macroPref("nextWindowGesture", GesturePreference.allCases) }
static var focusWindowShortcut: String { CachedUserDefaults.string("focusWindowShortcut") }
static var previousWindowShortcut: String { CachedUserDefaults.string("previousWindowShortcut") }
static var cancelShortcut: String { CachedUserDefaults.string("cancelShortcut") }
static var closeWindowShortcut: String { CachedUserDefaults.string("closeWindowShortcut") }
static var minDeminWindowShortcut: String { CachedUserDefaults.string("minDeminWindowShortcut") }
static var toggleFullscreenWindowShortcut: String { CachedUserDefaults.string("toggleFullscreenWindowShortcut") }
static var quitAppShortcut: String { CachedUserDefaults.string("quitAppShortcut") }
static var hideShowAppShortcut: String { CachedUserDefaults.string("hideShowAppShortcut") }
// periphery:ignore
static var arrowKeysEnabled: Bool { UserDefaults.standard.bool("arrowKeysEnabled") }
static var arrowKeysEnabled: Bool { CachedUserDefaults.bool("arrowKeysEnabled") }
// periphery:ignore
static var vimKeysEnabled: Bool { UserDefaults.standard.bool("vimKeysEnabled") }
static var mouseHoverEnabled: Bool { UserDefaults.standard.bool("mouseHoverEnabled") }
static var cursorFollowFocusEnabled: Bool { UserDefaults.standard.bool("cursorFollowFocusEnabled") }
static var showTabsAsWindows: Bool { UserDefaults.standard.bool("showTabsAsWindows") }
static var hideColoredCircles: Bool { UserDefaults.standard.bool("hideColoredCircles") }
static var windowDisplayDelay: DispatchTimeInterval { DispatchTimeInterval.milliseconds(UserDefaults.standard.int("windowDisplayDelay")) }
static var fadeOutAnimation: Bool { UserDefaults.standard.bool("fadeOutAnimation") }
static var hideSpaceNumberLabels: Bool { UserDefaults.standard.bool("hideSpaceNumberLabels") }
static var hideStatusIcons: Bool { UserDefaults.standard.bool("hideStatusIcons") }
static var hideAppBadges: Bool { UserDefaults.standard.bool("hideAppBadges") }
static var hideWindowlessApps: Bool { UserDefaults.standard.bool("hideWindowlessApps") }
static var vimKeysEnabled: Bool { CachedUserDefaults.bool("vimKeysEnabled") }
static var mouseHoverEnabled: Bool { CachedUserDefaults.bool("mouseHoverEnabled") }
static var cursorFollowFocusEnabled: Bool { CachedUserDefaults.bool("cursorFollowFocusEnabled") }
static var showTabsAsWindows: Bool { CachedUserDefaults.bool("showTabsAsWindows") }
static var hideColoredCircles: Bool { CachedUserDefaults.bool("hideColoredCircles") }
static var windowDisplayDelay: DispatchTimeInterval { DispatchTimeInterval.milliseconds(CachedUserDefaults.int("windowDisplayDelay")) }
static var fadeOutAnimation: Bool { CachedUserDefaults.bool("fadeOutAnimation") }
static var hideSpaceNumberLabels: Bool { CachedUserDefaults.bool("hideSpaceNumberLabels") }
static var hideStatusIcons: Bool { CachedUserDefaults.bool("hideStatusIcons") }
static var hideAppBadges: Bool { CachedUserDefaults.bool("hideAppBadges") }
static var hideWindowlessApps: Bool { CachedUserDefaults.bool("hideWindowlessApps") }
// periphery:ignore
static var startAtLogin: Bool { UserDefaults.standard.bool("startAtLogin") }
static var blacklist: [BlacklistEntry] { UserDefaults.standard.json("blacklist", [BlacklistEntry].self) }
static var previewFocusedWindow: Bool { UserDefaults.standard.bool("previewFocusedWindow") }
static var screenRecordingPermissionSkipped: Bool { UserDefaults.standard.bool("screenRecordingPermissionSkipped") }
static var startAtLogin: Bool { CachedUserDefaults.bool("startAtLogin") }
static var blacklist: [BlacklistEntry] { CachedUserDefaults.json("blacklist", [BlacklistEntry].self) }
static var previewFocusedWindow: Bool { CachedUserDefaults.bool("previewFocusedWindow") }
static var screenRecordingPermissionSkipped: Bool { CachedUserDefaults.bool("screenRecordingPermissionSkipped") }
// macro values
static var appearanceStyle: AppearanceStylePreference { UserDefaults.standard.macroPref("appearanceStyle", AppearanceStylePreference.allCases) }
static var appearanceSize: AppearanceSizePreference { UserDefaults.standard.macroPref("appearanceSize", AppearanceSizePreference.allCases) }
static var appearanceTheme: AppearanceThemePreference { UserDefaults.standard.macroPref("appearanceTheme", AppearanceThemePreference.allCases) }
static var appearanceVisibility: AppearanceVisibilityPreference { UserDefaults.standard.macroPref("appearanceVisibility", AppearanceVisibilityPreference.allCases) }
static var appearanceStyle: AppearanceStylePreference { CachedUserDefaults.macroPref("appearanceStyle", AppearanceStylePreference.allCases) }
static var appearanceSize: AppearanceSizePreference { CachedUserDefaults.macroPref("appearanceSize", AppearanceSizePreference.allCases) }
static var appearanceTheme: AppearanceThemePreference { CachedUserDefaults.macroPref("appearanceTheme", AppearanceThemePreference.allCases) }
static var appearanceVisibility: AppearanceVisibilityPreference { CachedUserDefaults.macroPref("appearanceVisibility", AppearanceVisibilityPreference.allCases) }
// periphery:ignore
static var theme: ThemePreference { ThemePreference.macOs/*UserDefaults.standard.macroPref("theme", ThemePreference.allCases)*/ }
static var showOnScreen: ShowOnScreenPreference { UserDefaults.standard.macroPref("showOnScreen", ShowOnScreenPreference.allCases) }
static var titleTruncation: TitleTruncationPreference { UserDefaults.standard.macroPref("titleTruncation", TitleTruncationPreference.allCases) }
static var alignThumbnails: AlignThumbnailsPreference { UserDefaults.standard.macroPref("alignThumbnails", AlignThumbnailsPreference.allCases) }
static var showAppsOrWindows: ShowAppsOrWindowsPreference { UserDefaults.standard.macroPref("showAppsOrWindows", ShowAppsOrWindowsPreference.allCases) }
static var showTitles: ShowTitlesPreference { UserDefaults.standard.macroPref("showTitles", ShowTitlesPreference.allCases) }
static var updatePolicy: UpdatePolicyPreference { UserDefaults.standard.macroPref("updatePolicy", UpdatePolicyPreference.allCases) }
static var crashPolicy: CrashPolicyPreference { UserDefaults.standard.macroPref("crashPolicy", CrashPolicyPreference.allCases) }
static var appsToShow: [AppsToShowPreference] { ["appsToShow", "appsToShow2", "appsToShow3", "appsToShow4"].map { UserDefaults.standard.macroPref($0, AppsToShowPreference.allCases) } }
static var spacesToShow: [SpacesToShowPreference] { ["spacesToShow", "spacesToShow2", "spacesToShow3", "spacesToShow4"].map { UserDefaults.standard.macroPref($0, SpacesToShowPreference.allCases) } }
static var screensToShow: [ScreensToShowPreference] { ["screensToShow", "screensToShow2", "screensToShow3", "screensToShow4"].map { UserDefaults.standard.macroPref($0, ScreensToShowPreference.allCases) } }
static var showMinimizedWindows: [ShowHowPreference] { ["showMinimizedWindows", "showMinimizedWindows2", "showMinimizedWindows3", "showMinimizedWindows4"].map { UserDefaults.standard.macroPref($0, ShowHowPreference.allCases) } }
static var showHiddenWindows: [ShowHowPreference] { ["showHiddenWindows", "showHiddenWindows2", "showHiddenWindows3", "showHiddenWindows4"].map { UserDefaults.standard.macroPref($0, ShowHowPreference.allCases) } }
static var showFullscreenWindows: [ShowHowPreference] { ["showFullscreenWindows", "showFullscreenWindows2", "showFullscreenWindows3", "showFullscreenWindows4"].map { UserDefaults.standard.macroPref($0, ShowHowPreference.allCases) } }
static var windowOrder: [WindowOrderPreference] { ["windowOrder", "windowOrder2", "windowOrder3", "windowOrder4"].map { UserDefaults.standard.macroPref($0, WindowOrderPreference.allCases) } }
static var shortcutStyle: [ShortcutStylePreference] { ["shortcutStyle", "shortcutStyle2", "shortcutStyle3", "shortcutStyle4"].map { UserDefaults.standard.macroPref($0, ShortcutStylePreference.allCases) } }
static var menubarIcon: MenubarIconPreference { UserDefaults.standard.macroPref("menubarIcon", MenubarIconPreference.allCases) }
static var menubarIconShown: Bool { UserDefaults.standard.bool("menubarIconShown") }
static var language: LanguagePreference { UserDefaults.standard.macroPref("language", LanguagePreference.allCases) }
static var theme: ThemePreference { ThemePreference.macOs/*CachedUserDefaults.macroPref("theme", ThemePreference.allCases)*/ }
static var showOnScreen: ShowOnScreenPreference { CachedUserDefaults.macroPref("showOnScreen", ShowOnScreenPreference.allCases) }
static var titleTruncation: TitleTruncationPreference { CachedUserDefaults.macroPref("titleTruncation", TitleTruncationPreference.allCases) }
static var alignThumbnails: AlignThumbnailsPreference { CachedUserDefaults.macroPref("alignThumbnails", AlignThumbnailsPreference.allCases) }
static var showAppsOrWindows: ShowAppsOrWindowsPreference { CachedUserDefaults.macroPref("showAppsOrWindows", ShowAppsOrWindowsPreference.allCases) }
static var showTitles: ShowTitlesPreference { CachedUserDefaults.macroPref("showTitles", ShowTitlesPreference.allCases) }
static var updatePolicy: UpdatePolicyPreference { CachedUserDefaults.macroPref("updatePolicy", UpdatePolicyPreference.allCases) }
static var crashPolicy: CrashPolicyPreference { CachedUserDefaults.macroPref("crashPolicy", CrashPolicyPreference.allCases) }
static var appsToShow: [AppsToShowPreference] { ["appsToShow", "appsToShow2", "appsToShow3", "appsToShow4"].map { CachedUserDefaults.macroPref($0, AppsToShowPreference.allCases) } }
static var spacesToShow: [SpacesToShowPreference] { ["spacesToShow", "spacesToShow2", "spacesToShow3", "spacesToShow4"].map { CachedUserDefaults.macroPref($0, SpacesToShowPreference.allCases) } }
static var screensToShow: [ScreensToShowPreference] { ["screensToShow", "screensToShow2", "screensToShow3", "screensToShow4"].map { CachedUserDefaults.macroPref($0, ScreensToShowPreference.allCases) } }
static var showMinimizedWindows: [ShowHowPreference] { ["showMinimizedWindows", "showMinimizedWindows2", "showMinimizedWindows3", "showMinimizedWindows4"].map { CachedUserDefaults.macroPref($0, ShowHowPreference.allCases) } }
static var showHiddenWindows: [ShowHowPreference] { ["showHiddenWindows", "showHiddenWindows2", "showHiddenWindows3", "showHiddenWindows4"].map { CachedUserDefaults.macroPref($0, ShowHowPreference.allCases) } }
static var showFullscreenWindows: [ShowHowPreference] { ["showFullscreenWindows", "showFullscreenWindows2", "showFullscreenWindows3", "showFullscreenWindows4"].map { CachedUserDefaults.macroPref($0, ShowHowPreference.allCases) } }
static var windowOrder: [WindowOrderPreference] { ["windowOrder", "windowOrder2", "windowOrder3", "windowOrder4"].map { CachedUserDefaults.macroPref($0, WindowOrderPreference.allCases) } }
static var shortcutStyle: [ShortcutStylePreference] { ["shortcutStyle", "shortcutStyle2", "shortcutStyle3", "shortcutStyle4"].map { CachedUserDefaults.macroPref($0, ShortcutStylePreference.allCases) } }
static var menubarIcon: MenubarIconPreference { CachedUserDefaults.macroPref("menubarIcon", MenubarIconPreference.allCases) }
static var menubarIconShown: Bool { CachedUserDefaults.bool("menubarIconShown") }
static var language: LanguagePreference { CachedUserDefaults.macroPref("language", LanguagePreference.allCases) }
static let gestureIndex = 3
static func initialize() {
removeCorruptedPreferences()
migratePreferences()
PreferencesMigrations.removeCorruptedPreferences()
PreferencesMigrations.migratePreferences()
registerDefaults()
}
static func removeCorruptedPreferences() {
// from v5.1.0+, there are crash reports of users somehow having their hold shortcuts set to ""
["holdShortcut", "holdShortcut2", "holdShortcut3", "holdShortcut4", "holdShortcut5"].forEach {
if let s = UserDefaults.standard.string(forKey: $0), s == "" {
UserDefaults.standard.removeObject(forKey: $0)
}
}
}
static func resetAll() {
UserDefaults.standard.removePersistentDomain(forName: App.id)
}
@@ -172,249 +163,18 @@ class Preferences {
UserDefaults.standard.register(defaults: defaultValues)
}
static func getString(_ key: String) -> String? {
UserDefaults.standard.string(forKey: key)
}
static func set<T>(_ key: String, _ value: T) where T: Encodable {
UserDefaults.standard.set(key == "blacklist" ? jsonEncode(value) : value, forKey: key)
UserDefaults.cache.removeValue(forKey: key)
CachedUserDefaults.cache.removeValue(forKey: key)
}
static func remove(_ key: String) {
UserDefaults.standard.removeObject(forKey: key)
UserDefaults.cache.removeValue(forKey: key)
CachedUserDefaults.cache.removeValue(forKey: key)
}
static var all: [String: Any] { UserDefaults.standard.persistentDomain(forName: App.id)! }
static func migratePreferences() {
let preferencesKey = "preferencesVersion"
if let versionInPlist = UserDefaults.standard.string(forKey: preferencesKey) {
if versionInPlist != "#VERSION#" && versionInPlist.compare(App.version, options: .numeric) != .orderedDescending {
updateToNewPreferences(versionInPlist)
}
}
UserDefaults.standard.set(App.version, forKey: preferencesKey)
}
private static func updateToNewPreferences(_ versionInPlist: String) {
// x.compare(y) is .orderedDescending if x > y
if versionInPlist.compare("7.13.1", options: .numeric) != .orderedDescending {
migrateGestures()
if versionInPlist.compare("7.8.0", options: .numeric) != .orderedDescending {
migrateMenubarIconWithNewShownToggle()
if versionInPlist.compare("7.0.0", options: .numeric) != .orderedDescending {
migratePreferencesIndexes()
if versionInPlist.compare("6.43.0", options: .numeric) != .orderedDescending {
migrateBlacklists()
if versionInPlist.compare("6.28.1", options: .numeric) != .orderedDescending {
migrateMinMaxWindowsWidthInRow()
if versionInPlist.compare("6.27.1", options: .numeric) != .orderedDescending {
// "Start at login" new implem doesn't use Login Items; we remove the entry from previous versions
(Preferences.self as AvoidDeprecationWarnings.Type).migrateLoginItem()
if versionInPlist.compare("6.23.0", options: .numeric) != .orderedDescending {
// "Show windows from:" got the "Active Space" option removed
migrateShowWindowsFrom()
if versionInPlist.compare("6.18.1", options: .numeric) != .orderedDescending {
// nextWindowShortcut used to be able to have modifiers already present in holdShortcut; we remove these
migrateNextWindowShortcuts()
// dropdowns preferences used to store English text; now they store indexes
migrateDropdownsFromTextToIndexes()
// the "Hide menubar icon" checkbox was replaced with a dropdown of: icon1, icon2, hidden
migrateMenubarIconFromCheckboxToDropdown()
// "Show minimized/hidden/fullscreen windows" checkboxes were replaced with dropdowns
migrateShowWindowsCheckboxToDropdown()
// "Max size on screen" was split into max width and max height
migrateMaxSizeOnScreenToWidthAndHeight()
}
}
}
}
}
}
}
}
}
// we split gestures from disabled, 3-finger, 4-finger to: disabled, 3-finger-horizontal, 3-finger-vertical, 4-finger-horizontal, 4-finger-vertical
// no need to map 0 -> 0 (disabled -> disabled)
// no need to map 1 -> 1 (3-finger -> 3-finger-horizontal)
// we need to map 2 -> 3 (4-finger -> 4-finger-horizontal)
private static func migrateGestures() {
if let old = UserDefaults.standard.string(forKey: "nextWindowGesture") {
if old == "2" { // 2 (4-finger) -> 3 (4-finger-horizontal)
UserDefaults.standard.set("3", forKey: "nextWindowGesture")
}
}
}
/// we added the new menubarIconShown toggle. It replaces menubarIcon having value "3" which would hide the icon
/// there are now 2 preferences : menubarIconShown is a boolean, and menubarIcon has values 0, 1, 2
private static func migrateMenubarIconWithNewShownToggle() {
if let old = UserDefaults.standard.string(forKey: "menubarIcon") {
if old == "3" {
UserDefaults.standard.set("0", forKey: "menubarIcon")
UserDefaults.standard.set("false", forKey: "menubarIconShown")
}
}
}
// we want to rely on preferences numbers to match the enum indexes. This migration realigns existing desyncs
private static func migratePreferencesIndexes() {
// migrate spacesToShow from 1 to 2. 1 was removed a while ago. 1=active => 2=>visible
["", "2", "3", "4", "5"].forEach { suffix in
if let spacesToShow = UserDefaults.standard.string(forKey: "spacesToShow" + suffix) {
if spacesToShow == "1" {
UserDefaults.standard.set("2", forKey: "spacesToShow" + suffix)
}
}
}
// migrate spacesToShow from 0 to 2 and 2 to 0. 0 used to be end, 2 used to be start; they got switch for the UI order
["", "2", "3", "4", "5"].forEach { suffix in
if let spacesToShow = UserDefaults.standard.string(forKey: "titleTruncation" + suffix) {
if spacesToShow == "0" {
UserDefaults.standard.set("2", forKey: "titleTruncation" + suffix)
}
if spacesToShow == "2" {
UserDefaults.standard.set("0", forKey: "titleTruncation" + suffix)
}
}
}
}
private static func migrateBlacklists() {
var entries = [BlacklistEntry]()
if let old = UserDefaults.standard.string(forKey: "dontShowBlacklist") {
entries.append(contentsOf: oldBlacklistEntriesToNewOnes(old, .always, .none))
}
if let old = UserDefaults.standard.string(forKey: "disableShortcutsBlacklist") {
let onlyFullscreen = UserDefaults.standard.bool(forKey: "disableShortcutsBlacklistOnlyFullscreen")
entries.append(contentsOf: oldBlacklistEntriesToNewOnes(old, .none, onlyFullscreen ? .whenFullscreen : .always))
}
if entries.count > 0 {
UserDefaults.standard.set(Preferences.jsonEncode(entries), forKey: "blacklist")
["dontShowBlacklist", "disableShortcutsBlacklist", "disableShortcutsBlacklistOnlyFullscreen"].forEach {
UserDefaults.standard.removeObject(forKey: $0)
}
}
}
private static func oldBlacklistEntriesToNewOnes(_ old: String, _ hide: BlacklistHidePreference, _ ignore: BlacklistIgnorePreference) -> [BlacklistEntry] {
old.split(separator: "\n").compactMap { (e) -> BlacklistEntry? in
let line = e.trimmingCharacters(in: .whitespaces)
if line.isEmpty {
return nil
}
return BlacklistEntry(bundleIdentifier: line, hide: hide, ignore: ignore)
}
}
private static func migrateMinMaxWindowsWidthInRow() {
["windowMinWidthInRow", "windowMaxWidthInRow"].forEach {
if let old = UserDefaults.standard.string(forKey: $0) {
if old == "0" {
UserDefaults.standard.set("1", forKey: $0)
}
}
}
}
@available(OSX, deprecated: 10.11)
static func migrateLoginItem() {
do {
if let loginItemsWrapped = LSSharedFileListCreate(nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil) {
let loginItems = loginItemsWrapped.takeRetainedValue()
if let loginItemsSnapshotWrapped = LSSharedFileListCopySnapshot(loginItems, nil) {
let loginItemsSnapshot = loginItemsSnapshotWrapped.takeRetainedValue() as! [LSSharedFileListItem]
let itemName = Bundle.main.bundleURL.lastPathComponent as CFString
let itemUrl = URL(fileURLWithPath: Bundle.main.bundlePath) as CFURL
loginItemsSnapshot.forEach {
if (LSSharedFileListItemCopyDisplayName($0).takeRetainedValue() == itemName) ||
(LSSharedFileListItemCopyResolvedURL($0, 0, nil)?.takeRetainedValue() == itemUrl) {
LSSharedFileListItemRemove(loginItems, $0)
}
}
}
}
throw AxError.runtimeError // remove compiler warning
} catch {
// the LSSharedFile API is deprecated, and has a runtime crash on M1 Monterey
// we catch any exception to void the app crashing
}
}
private static func migrateShowWindowsFrom() {
["", "2"].forEach { suffix in
if let spacesToShow = UserDefaults.standard.string(forKey: "spacesToShow" + suffix) {
if spacesToShow == "2" {
UserDefaults.standard.set("1", forKey: "screensToShow" + suffix)
UserDefaults.standard.set("1", forKey: "spacesToShow" + suffix)
} else if spacesToShow == "1" {
UserDefaults.standard.set("1", forKey: "screensToShow" + suffix)
}
}
}
}
private static func migrateNextWindowShortcuts() {
["", "2"].forEach { suffix in
if let oldHoldShortcut = UserDefaults.standard.string(forKey: "holdShortcut" + suffix),
let oldNextWindowShortcut = UserDefaults.standard.string(forKey: "nextWindowShortcut" + suffix) {
let nextWindowShortcutCleanedUp = oldHoldShortcut.reduce(oldNextWindowShortcut, { $0.replacingOccurrences(of: String($1), with: "") })
if oldNextWindowShortcut != nextWindowShortcutCleanedUp {
UserDefaults.standard.set(nextWindowShortcutCleanedUp, forKey: "nextWindowShortcut" + suffix)
}
}
}
}
private static func migrateMaxSizeOnScreenToWidthAndHeight() {
if let old = UserDefaults.standard.string(forKey: "maxScreenUsage") {
UserDefaults.standard.set(old, forKey: "maxWidthOnScreen")
UserDefaults.standard.set(old, forKey: "maxHeightOnScreen")
}
}
private static func migrateShowWindowsCheckboxToDropdown() {
["showMinimizedWindows", "showHiddenWindows", "showFullscreenWindows"]
.flatMap { [$0, $0 + "2"] }
.forEach {
if let old = UserDefaults.standard.string(forKey: $0) {
if old == "true" {
UserDefaults.standard.set(ShowHowPreference.show.indexAsString, forKey: $0)
} else if old == "false" {
UserDefaults.standard.set(ShowHowPreference.hide.indexAsString, forKey: $0)
}
}
}
}
private static func migrateDropdownsFromTextToIndexes() {
migratePreferenceValue("theme", [" macOS": "0", "❖ Windows 10": "1"])
// "Main screen" was renamed to "Active screen"
migratePreferenceValue("showOnScreen", ["Main screen": "0", "Active screen": "0", "Screen including mouse": "1"])
migratePreferenceValue("alignThumbnails", ["Left": "0", "Center": "1"])
migratePreferenceValue("appsToShow", ["All apps": "0", "Active app": "1"])
migratePreferenceValue("spacesToShow", ["All spaces": "0", "Active space": "1"])
migratePreferenceValue("screensToShow", ["All screens": "0", "Screen showing AltTab": "1"])
}
private static func migrateMenubarIconFromCheckboxToDropdown() {
if let old = UserDefaults.standard.string(forKey: "hideMenubarIcon") {
if old == "true" {
UserDefaults.standard.set("3", forKey: "menubarIcon")
}
}
}
static func migratePreferenceValue(_ preference: String, _ oldAndNew: [String: String]) {
if let old = UserDefaults.standard.string(forKey: preference),
let new = oldAndNew[old] {
UserDefaults.standard.set(new, forKey: preference)
}
}
static func onlyShowApplications() -> Bool {
return Preferences.showAppsOrWindows == .applications && Preferences.appearanceStyle != .thumbnails
}
@@ -458,66 +218,78 @@ class Preferences {
}
}
extension UserDefaults {
static var cache = [String: String]()
class CachedUserDefaults {
static var cache = [String: Any]()
func getFromCacheOrFetchAndCache(_ key: String) -> String {
if let c = UserDefaults.cache[key] {
return c
/// retrieve strings in the globalDomain (e.g. defaults read -g KeyRepeat)
/// these may be nil since we they don't have default values from AltTab
static func globalString(_ key: String) -> String? {
if let cached = CachedUserDefaults.cache[key] {
return cached as? String
}
let v = UserDefaults.standard.string(forKey: key)!
UserDefaults.cache[key] = v
return v
}
func getThenConvertOrReset<T>(_ key: String, _ getterFn: (String) -> T?) -> T {
let stringValue = getFromCacheOrFetchAndCache(key)
if let v = getterFn(stringValue) {
return v
if let string = UserDefaults.standard.string(forKey: key) {
CachedUserDefaults.cache[key] = string
}
removeObject(forKey: key)
UserDefaults.cache.removeValue(forKey: key)
let stringValue2 = getFromCacheOrFetchAndCache(key)
let v = getterFn(stringValue2)!
return v
return nil
}
func string(_ key: String) -> String {
return getFromCacheOrFetchAndCache(key)
static func string(_ key: String) -> String {
if let cachedFinalValue = CachedUserDefaults.cache[key] {
return cachedFinalValue as! String
}
let finalValue = UserDefaults.standard.string(forKey: key)!
CachedUserDefaults.cache[key] = finalValue
return finalValue
}
func int(_ key: String) -> Int {
static func int(_ key: String) -> Int {
return getThenConvertOrReset(key, { s in Int(s) })
}
func bool(_ key: String) -> Bool {
static func bool(_ key: String) -> Bool {
return getThenConvertOrReset(key, { s in Bool(s) })
}
func double(_ key: String) -> Double {
static func double(_ key: String) -> Double {
return getThenConvertOrReset(key, { s in Double(s) })
}
func macroPref<A>(_ key: String, _ macroPreferences: [A]) -> A {
static func macroPref<A>(_ key: String, _ macroPreferences: [A]) -> A {
return getThenConvertOrReset(key, { s in Int(s).flatMap { macroPreferences[safe: $0] } })
}
func json<T>(_ key: String, _ type: T.Type) -> T where T: Decodable {
/// some UI elements (e.g. dropdown, radios) need an int. We find the right int from the MacroPreference index
static func intFromMacroPref(_ key: String, _ macroPreferences: [MacroPreference]) -> Int {
let macroPref = macroPref(key, macroPreferences)
return macroPreferences.firstIndex { $0.localizedString == macroPref.localizedString }!
}
static func json<T>(_ key: String, _ type: T.Type) -> T where T: Decodable {
return getThenConvertOrReset(key, { s in jsonDecode(s, type) })
}
private func jsonDecode<T>(_ value: String, _ type: T.Type) -> T? where T: Decodable {
private static func getThenConvertOrReset<T>(_ key: String, _ getterFn: (String) -> T?) -> T {
if let cachedFinalValue = CachedUserDefaults.cache[key] {
return cachedFinalValue as! T
}
let stringValue = UserDefaults.standard.string(forKey: key)!
if let finalValue = getterFn(stringValue) {
CachedUserDefaults.cache[key] = finalValue
return finalValue
}
// value couldn't be read properly; we remove it and work with the default
UserDefaults.standard.removeObject(forKey: key)
let defaultStringValue = UserDefaults.standard.string(forKey: key)!
let defaultFinalValue = getterFn(defaultStringValue)!
CachedUserDefaults.cache[key] = defaultFinalValue
return defaultFinalValue
}
private static func jsonDecode<T>(_ value: String, _ type: T.Type) -> T? where T: Decodable {
return value.data(using: .utf8).flatMap { try? JSONDecoder().decode(type, from: $0) }
}
}
/// workaround to silence compiler warning
private protocol AvoidDeprecationWarnings {
static func migrateLoginItem()
}
extension Preferences: AvoidDeprecationWarnings {}
struct BlacklistEntry: Codable {
var bundleIdentifier: String
var hide: BlacklistHidePreference
+244
View File
@@ -0,0 +1,244 @@
class PreferencesMigrations {
static func removeCorruptedPreferences() {
// from v5.1.0+, there are crash reports of users somehow having their hold shortcuts set to ""
["holdShortcut", "holdShortcut2", "holdShortcut3", "holdShortcut4", "holdShortcut5"].forEach {
if let s = UserDefaults.standard.string(forKey: $0), s == "" {
UserDefaults.standard.removeObject(forKey: $0)
}
}
}
static func migratePreferences() {
let preferencesKey = "preferencesVersion"
if let versionInPlist = UserDefaults.standard.string(forKey: preferencesKey) {
if versionInPlist != "#VERSION#" && versionInPlist.compare(App.version, options: .numeric) != .orderedDescending {
updateToNewPreferences(versionInPlist)
}
}
UserDefaults.standard.set(App.version, forKey: preferencesKey)
}
private static func updateToNewPreferences(_ versionInPlist: String) {
// x.compare(y) is .orderedDescending if x > y
if versionInPlist.compare("7.13.1", options: .numeric) != .orderedDescending {
migrateGestures()
if versionInPlist.compare("7.8.0", options: .numeric) != .orderedDescending {
migrateMenubarIconWithNewShownToggle()
if versionInPlist.compare("7.0.0", options: .numeric) != .orderedDescending {
migratePreferencesIndexes()
if versionInPlist.compare("6.43.0", options: .numeric) != .orderedDescending {
migrateBlacklists()
if versionInPlist.compare("6.28.1", options: .numeric) != .orderedDescending {
migrateMinMaxWindowsWidthInRow()
if versionInPlist.compare("6.27.1", options: .numeric) != .orderedDescending {
// "Start at login" new implem doesn't use Login Items; we remove the entry from previous versions
(PreferencesMigrations.self as AvoidDeprecationWarnings.Type).migrateLoginItem()
if versionInPlist.compare("6.23.0", options: .numeric) != .orderedDescending {
// "Show windows from:" got the "Active Space" option removed
migrateShowWindowsFrom()
if versionInPlist.compare("6.18.1", options: .numeric) != .orderedDescending {
// nextWindowShortcut used to be able to have modifiers already present in holdShortcut; we remove these
migrateNextWindowShortcuts()
// dropdowns preferences used to store English text; now they store indexes
migrateDropdownsFromTextToIndexes()
// the "Hide menubar icon" checkbox was replaced with a dropdown of: icon1, icon2, hidden
migrateMenubarIconFromCheckboxToDropdown()
// "Show minimized/hidden/fullscreen windows" checkboxes were replaced with dropdowns
migrateShowWindowsCheckboxToDropdown()
// "Max size on screen" was split into max width and max height
migrateMaxSizeOnScreenToWidthAndHeight()
}
}
}
}
}
}
}
}
}
// we split gestures from disabled, 3-finger, 4-finger to: disabled, 3-finger-horizontal, 3-finger-vertical, 4-finger-horizontal, 4-finger-vertical
// no need to map 0 -> 0 (disabled -> disabled)
// no need to map 1 -> 1 (3-finger -> 3-finger-horizontal)
// we need to map 2 -> 3 (4-finger -> 4-finger-horizontal)
private static func migrateGestures() {
if let old = UserDefaults.standard.string(forKey: "nextWindowGesture") {
if old == "2" { // 2 (4-finger) -> 3 (4-finger-horizontal)
UserDefaults.standard.set("3", forKey: "nextWindowGesture")
}
}
}
/// we added the new menubarIconShown toggle. It replaces menubarIcon having value "3" which would hide the icon
/// there are now 2 preferences : menubarIconShown is a boolean, and menubarIcon has values 0, 1, 2
private static func migrateMenubarIconWithNewShownToggle() {
if let old = UserDefaults.standard.string(forKey: "menubarIcon") {
if old == "3" {
UserDefaults.standard.set("0", forKey: "menubarIcon")
UserDefaults.standard.set("false", forKey: "menubarIconShown")
}
}
}
// we want to rely on preferences numbers to match the enum indexes. This migration realigns existing desyncs
private static func migratePreferencesIndexes() {
// migrate spacesToShow from 1 to 2. 1 was removed a while ago. 1=active => 2=>visible
["", "2", "3", "4", "5"].forEach { suffix in
if let spacesToShow = UserDefaults.standard.string(forKey: "spacesToShow" + suffix) {
if spacesToShow == "1" {
UserDefaults.standard.set("2", forKey: "spacesToShow" + suffix)
}
}
}
// migrate spacesToShow from 0 to 2 and 2 to 0. 0 used to be end, 2 used to be start; they got switch for the UI order
["", "2", "3", "4", "5"].forEach { suffix in
if let spacesToShow = UserDefaults.standard.string(forKey: "titleTruncation" + suffix) {
if spacesToShow == "0" {
UserDefaults.standard.set("2", forKey: "titleTruncation" + suffix)
}
if spacesToShow == "2" {
UserDefaults.standard.set("0", forKey: "titleTruncation" + suffix)
}
}
}
}
private static func migrateBlacklists() {
var entries = [BlacklistEntry]()
if let old = UserDefaults.standard.string(forKey: "dontShowBlacklist") {
entries.append(contentsOf: oldBlacklistEntriesToNewOnes(old, .always, .none))
}
if let old = UserDefaults.standard.string(forKey: "disableShortcutsBlacklist") {
let onlyFullscreen = UserDefaults.standard.bool(forKey: "disableShortcutsBlacklistOnlyFullscreen")
entries.append(contentsOf: oldBlacklistEntriesToNewOnes(old, .none, onlyFullscreen ? .whenFullscreen : .always))
}
if entries.count > 0 {
UserDefaults.standard.set(Preferences.jsonEncode(entries), forKey: "blacklist")
["dontShowBlacklist", "disableShortcutsBlacklist", "disableShortcutsBlacklistOnlyFullscreen"].forEach {
UserDefaults.standard.removeObject(forKey: $0)
}
}
}
private static func oldBlacklistEntriesToNewOnes(_ old: String, _ hide: BlacklistHidePreference, _ ignore: BlacklistIgnorePreference) -> [BlacklistEntry] {
old.split(separator: "\n").compactMap { (e) -> BlacklistEntry? in
let line = e.trimmingCharacters(in: .whitespaces)
if line.isEmpty {
return nil
}
return BlacklistEntry(bundleIdentifier: line, hide: hide, ignore: ignore)
}
}
private static func migrateMinMaxWindowsWidthInRow() {
["windowMinWidthInRow", "windowMaxWidthInRow"].forEach {
if let old = UserDefaults.standard.string(forKey: $0) {
if old == "0" {
UserDefaults.standard.set("1", forKey: $0)
}
}
}
}
@available(OSX, deprecated: 10.11)
static func migrateLoginItem() {
do {
if let loginItemsWrapped = LSSharedFileListCreate(nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil) {
let loginItems = loginItemsWrapped.takeRetainedValue()
if let loginItemsSnapshotWrapped = LSSharedFileListCopySnapshot(loginItems, nil) {
let loginItemsSnapshot = loginItemsSnapshotWrapped.takeRetainedValue() as! [LSSharedFileListItem]
let itemName = Bundle.main.bundleURL.lastPathComponent as CFString
let itemUrl = URL(fileURLWithPath: Bundle.main.bundlePath) as CFURL
loginItemsSnapshot.forEach {
if (LSSharedFileListItemCopyDisplayName($0).takeRetainedValue() == itemName) ||
(LSSharedFileListItemCopyResolvedURL($0, 0, nil)?.takeRetainedValue() == itemUrl) {
LSSharedFileListItemRemove(loginItems, $0)
}
}
}
}
throw AxError.runtimeError // remove compiler warning
} catch {
// the LSSharedFile API is deprecated, and has a runtime crash on M1 Monterey
// we catch any exception to void the app crashing
}
}
private static func migrateShowWindowsFrom() {
["", "2"].forEach { suffix in
if let spacesToShow = UserDefaults.standard.string(forKey: "spacesToShow" + suffix) {
if spacesToShow == "2" {
UserDefaults.standard.set("1", forKey: "screensToShow" + suffix)
UserDefaults.standard.set("1", forKey: "spacesToShow" + suffix)
} else if spacesToShow == "1" {
UserDefaults.standard.set("1", forKey: "screensToShow" + suffix)
}
}
}
}
private static func migrateNextWindowShortcuts() {
["", "2"].forEach { suffix in
if let oldHoldShortcut = UserDefaults.standard.string(forKey: "holdShortcut" + suffix),
let oldNextWindowShortcut = UserDefaults.standard.string(forKey: "nextWindowShortcut" + suffix) {
let nextWindowShortcutCleanedUp = oldHoldShortcut.reduce(oldNextWindowShortcut, { $0.replacingOccurrences(of: String($1), with: "") })
if oldNextWindowShortcut != nextWindowShortcutCleanedUp {
UserDefaults.standard.set(nextWindowShortcutCleanedUp, forKey: "nextWindowShortcut" + suffix)
}
}
}
}
private static func migrateMaxSizeOnScreenToWidthAndHeight() {
if let old = UserDefaults.standard.string(forKey: "maxScreenUsage") {
UserDefaults.standard.set(old, forKey: "maxWidthOnScreen")
UserDefaults.standard.set(old, forKey: "maxHeightOnScreen")
}
}
private static func migrateShowWindowsCheckboxToDropdown() {
["showMinimizedWindows", "showHiddenWindows", "showFullscreenWindows"]
.flatMap { [$0, $0 + "2"] }
.forEach {
if let old = UserDefaults.standard.string(forKey: $0) {
if old == "true" {
UserDefaults.standard.set(ShowHowPreference.show.indexAsString, forKey: $0)
} else if old == "false" {
UserDefaults.standard.set(ShowHowPreference.hide.indexAsString, forKey: $0)
}
}
}
}
private static func migrateDropdownsFromTextToIndexes() {
migratePreferenceValue("theme", [" macOS": "0", "❖ Windows 10": "1"])
// "Main screen" was renamed to "Active screen"
migratePreferenceValue("showOnScreen", ["Main screen": "0", "Active screen": "0", "Screen including mouse": "1"])
migratePreferenceValue("alignThumbnails", ["Left": "0", "Center": "1"])
migratePreferenceValue("appsToShow", ["All apps": "0", "Active app": "1"])
migratePreferenceValue("spacesToShow", ["All spaces": "0", "Active space": "1"])
migratePreferenceValue("screensToShow", ["All screens": "0", "Screen showing AltTab": "1"])
}
private static func migrateMenubarIconFromCheckboxToDropdown() {
if let old = UserDefaults.standard.string(forKey: "hideMenubarIcon") {
if old == "true" {
UserDefaults.standard.set("3", forKey: "menubarIcon")
}
}
}
private static func migratePreferenceValue(_ preference: String, _ oldAndNew: [String: String]) {
if let old = UserDefaults.standard.string(forKey: preference),
let new = oldAndNew[old] {
UserDefaults.standard.set(new, forKey: preference)
}
}
}
/// workaround to silence compiler warning
private protocol AvoidDeprecationWarnings {
static func migrateLoginItem()
}
extension PreferencesMigrations: AvoidDeprecationWarnings {}
-1
View File
@@ -208,7 +208,6 @@ class App: AppCenterApplication {
guard appIsBeingUsed else { return }
Windows.updateFocusedWindowIndex()
guard appIsBeingUsed else { return }
print("updateItemsAndLayout")
thumbnailsPanel.thumbnailsView.updateItemsAndLayout()
guard appIsBeingUsed else { return }
thumbnailsPanel.setContentSize(thumbnailsPanel.thumbnailsView.frame.size)
+15 -15
View File
@@ -63,20 +63,20 @@ class LabelAndControl: NSObject {
// periphery:ignore
static func makeLabelWithImageRadioButtons(_ labelText: String,
_ rawName: String,
_ values: [ImageMacroPreference],
_ macroPreferences: [ImageMacroPreference],
extraAction: ActionClosure? = nil,
buttonSpacing: CGFloat = 15) -> [NSView] {
let view = makeImageRadioButtons(rawName, values, extraAction: extraAction, buttonSpacing: buttonSpacing)
let view = makeImageRadioButtons(rawName, macroPreferences, extraAction: extraAction, buttonSpacing: buttonSpacing)
return [makeLabel(labelText), view]
}
static func makeImageRadioButtons(_ rawName: String,
_ values: [ImageMacroPreference],
_ macroPreferences: [ImageMacroPreference],
extraAction: ActionClosure? = nil,
buttonSpacing: CGFloat = 15) -> NSStackView {
var buttons = [ImageTextButtonView]()
let buttonViews = values.enumerated().map { (index, preference) -> ImageTextButtonView in
let state: NSControl.StateValue = UserDefaults.standard.int(rawName) == index ? .on : .off
let buttonViews = macroPreferences.enumerated().map { (index, preference) -> ImageTextButtonView in
let state: NSControl.StateValue = CachedUserDefaults.intFromMacroPref(rawName, macroPreferences) == index ? .on : .off
let buttonView = ImageTextButtonView(title: preference.localizedString, rawName: rawName, image: preference.image, state: state)
buttons.append(buttonView)
buttonView.onClick = { control in
@@ -109,13 +109,13 @@ class LabelAndControl: NSObject {
static func makeLabelWithCheckbox(_ labelText: String, _ rawName: String, extraAction: ActionClosure? = nil, labelPosition: LabelPosition = .leftWithSeparator) -> [NSView] {
let checkbox = NSButton(checkboxWithTitle: labelPosition == .right ? labelText : " ", target: nil, action: nil)
checkbox.translatesAutoresizingMaskIntoConstraints = false
checkbox.state = UserDefaults.standard.bool(rawName) ? .on : .off
checkbox.state = CachedUserDefaults.bool(rawName) ? .on : .off
let views = makeLabelWithProvidedControl(labelText, rawName, checkbox, labelPosition: labelPosition, extraAction: extraAction)
return views
}
static func makeSwitch(_ rawName: String, extraAction: ActionClosure? = nil) -> Switch {
let button = Switch(UserDefaults.standard.bool(rawName))
let button = Switch(CachedUserDefaults.bool(rawName))
_ = setupControl(button, rawName, extraAction: extraAction)
return button
}
@@ -124,7 +124,7 @@ class LabelAndControl: NSObject {
static func makeCheckbox(_ rawName: String, extraAction: ActionClosure? = nil) -> NSButton {
let checkbox = NSButton(checkboxWithTitle: "", target: nil, action: nil)
checkbox.translatesAutoresizingMaskIntoConstraints = false
checkbox.state = UserDefaults.standard.bool(rawName) ? .on : .off
checkbox.state = CachedUserDefaults.bool(rawName) ? .on : .off
_ = setupControl(checkbox, rawName, extraAction: extraAction)
return checkbox
}
@@ -179,7 +179,7 @@ class LabelAndControl: NSObject {
extraAction?(textArea)
}
textArea.identifier = NSUserInterfaceItemIdentifier(rawName)
textArea.stringValue = UserDefaults.standard.string(rawName)
textArea.stringValue = CachedUserDefaults.string(rawName)
return [textArea]
}
@@ -188,7 +188,7 @@ class LabelAndControl: NSObject {
popUp.addItems(withTitles: macroPreferences.map {
$0.localizedString
})
popUp.selectItem(at: UserDefaults.standard.int(rawName))
popUp.selectItem(at: CachedUserDefaults.intFromMacroPref(rawName, macroPreferences))
return popUp
}
@@ -218,7 +218,7 @@ class LabelAndControl: NSObject {
return macroPreferences.map {
let button = NSButton(radioButtonWithTitle: $0.localizedString, target: nil, action: nil)
button.translatesAutoresizingMaskIntoConstraints = false
button.state = UserDefaults.standard.int(rawName) == i ? .on : .off
button.state = CachedUserDefaults.intFromMacroPref(rawName, macroPreferences) == i ? .on : .off
_ = setupControl(button, rawName, String(i), extraAction: extraAction)
i += 1
return button
@@ -242,7 +242,7 @@ class LabelAndControl: NSObject {
button.setImage(symbolImage, forSegment: i)
}
}
let selectedSegment = UserDefaults.standard.int(rawName)
let selectedSegment = CachedUserDefaults.intFromMacroPref(rawName, macroPreferences)
if selectedSegment >= 0 && selectedSegment < macroPreferences.count {
button.selectedSegment = selectedSegment
}
@@ -254,10 +254,10 @@ class LabelAndControl: NSObject {
static func makeLabelWithSlider(_ labelText: String, _ rawName: String, _ minValue: Double, _ maxValue: Double,
_ numberOfTickMarks: Int = 0, _ allowsTickMarkValuesOnly: Bool = false,
_ unitText: String = "", width: CGFloat = 200, extraAction: ActionClosure? = nil) -> [NSView] {
let value = UserDefaults.standard.double(rawName)
let value = CachedUserDefaults.int(rawName)
let formatter = MeasurementFormatter()
formatter.numberFormatter = NumberFormatter()
let suffixText = formatter.string(from: Measurement(value: value, unit: Unit(symbol: unitText)))
let suffixText = formatter.string(from: Measurement(value: Double(value), unit: Unit(symbol: unitText)))
let slider = NSSlider()
slider.minValue = minValue
slider.maxValue = maxValue
@@ -304,7 +304,7 @@ class LabelAndControl: NSObject {
static func controlWasChanged(_ senderControl: NSControl, _ controlId: String?) {
if let newValue = LabelAndControl.getControlValue(senderControl, controlId) {
if let oldValue = Preferences.getString(senderControl.identifier!.rawValue), newValue == oldValue {
if let oldValue = UserDefaults.standard.string(forKey: senderControl.identifier!.rawValue), newValue == oldValue {
return
}
if senderControl is NSSlider {