feat: new settings experience (closes #5287)
This commit is contained in:
1 parent
93e2b35dc7
commit
3890beb9d3
28 files changed
+1841
-384
No files matched your search
@@ -290,7 +290,7 @@
|
||||
D04BAD451966B43720120D2E /* Menubar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAD905546AA93E5117B0A /* Menubar.swift */; };
|
||||
D04BADA3F2DAB820D6FC6E75 /* AppCenterApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = D04BA48BC82D3060C3A1AB11 /* AppCenterApplication.m */; };
|
||||
D04BADCDA9F9A6C3D6499877 /* SystemPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA7C6F2519091717F4B4E /* SystemPermissions.swift */; };
|
||||
D04BADE4DDF7597749FB13BD /* PreferencesWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA099FA155FB4D517D66B /* PreferencesWindow.swift */; };
|
||||
D04BADE4DDF7597749FB13BD /* SettingsWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA099FA155FB4D517D66B /* SettingsWindow.swift */; };
|
||||
D04BAE8B16A06A10E2FA94DE /* AccessibilityEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA8FDCF137D892114F5F3 /* AccessibilityEvents.swift */; };
|
||||
D04BAEE31B6FFCDC779E6C17 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D04BAC2FF99F629CD4ED20FC /* MainMenu.xib */; };
|
||||
D04BAF12DF5D15B9D7D316A4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA61693F710CD7BD054D7 /* InfoPlist.strings */; };
|
||||
@@ -578,7 +578,7 @@
|
||||
D04BA075E359F36C27CD5BAE /* package-lock.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "package-lock.json"; sourceTree = "<group>"; };
|
||||
D04BA084F638725465234830 /* favicon-16x16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "favicon-16x16.png"; sourceTree = "<group>"; };
|
||||
D04BA088B8A550D9367A4AAB /* it */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = it; path = InfoPlist.strings; sourceTree = "<group>"; };
|
||||
D04BA099FA155FB4D517D66B /* PreferencesWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWindow.swift; sourceTree = "<group>"; };
|
||||
D04BA099FA155FB4D517D66B /* SettingsWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsWindow.swift; sourceTree = "<group>"; };
|
||||
D04BA0AAAE82C72855DBBA26 /* update_appcast.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = update_appcast.sh; sourceTree = "<group>"; };
|
||||
D04BA0CE87BE264C52987ED1 /* 7 windows - 2 lines - wide window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - wide window.jpg"; sourceTree = "<group>"; };
|
||||
D04BA0E071D2EDFDB9A20523 /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
|
||||
@@ -1468,7 +1468,7 @@
|
||||
children = (
|
||||
D04BAAB92261FC04854FDDE9 /* App.swift */,
|
||||
D04BA62E30174C336E4080FA /* main-window */,
|
||||
D04BABCD12CA72B06290C248 /* preferences-window */,
|
||||
D04BABCD12CA72B06290C248 /* settings-window */,
|
||||
D04BAD905546AA93E5117B0A /* Menubar.swift */,
|
||||
D04BA26C75F76C277653C932 /* FeedbackWindow.swift */,
|
||||
D04BA2CBF267F267225CA671 /* generic-components */,
|
||||
@@ -1886,14 +1886,14 @@
|
||||
path = ru.lproj;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BABCD12CA72B06290C248 /* preferences-window */ = {
|
||||
D04BABCD12CA72B06290C248 /* settings-window */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BA2526DC6726E0F7ACF7C /* LabelAndControl.swift */,
|
||||
D04BA3FA52AA40B827A72F10 /* tabs */,
|
||||
D04BA099FA155FB4D517D66B /* PreferencesWindow.swift */,
|
||||
D04BA099FA155FB4D517D66B /* SettingsWindow.swift */,
|
||||
);
|
||||
path = "preferences-window";
|
||||
path = "settings-window";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BAC2B70BC798C57B0B492 /* l10n */ = {
|
||||
@@ -2543,7 +2543,7 @@
|
||||
D04BA56789A2AF52557A9BFF /* AppCenterCrashes.swift in Sources */,
|
||||
D04BADA3F2DAB820D6FC6E75 /* AppCenterApplication.m in Sources */,
|
||||
D04BAA16F21AC41A23DDA63D /* ControlsTab.swift in Sources */,
|
||||
D04BADE4DDF7597749FB13BD /* PreferencesWindow.swift in Sources */,
|
||||
D04BADE4DDF7597749FB13BD /* SettingsWindow.swift in Sources */,
|
||||
BF0C8CC5057406014FD612CC /* ATShortcut.swift in Sources */,
|
||||
BF0C8C7E96DB48120462DA00 /* TrafficLightButton.swift in Sources */,
|
||||
BF0C898511686611E4D7D81E /* Button.swift in Sources */,
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"About" = "About";
|
||||
|
||||
/* Menubar option. %@ is AltTab */
|
||||
"About %@" = "About %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Accessibility" = "Accessibility";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Acknowledgments" = "Acknowledgments";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Active app" = "Active app";
|
||||
|
||||
@@ -26,7 +20,7 @@
|
||||
"Advanced" = "Advanced";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"After release" = "After release";
|
||||
"After keys are released" = "After keys are released";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Align windows" = "Align windows";
|
||||
@@ -109,6 +103,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Are you sure you don’t want a response?" = "Are you sure you don’t want a response?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Arrow keys" = "Arrow keys";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Ask whether to send crash reports" = "Ask whether to send crash reports";
|
||||
|
||||
@@ -121,9 +118,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "Cancel";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel and hide" = "Cancel and hide";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Center" = "Center";
|
||||
|
||||
@@ -169,9 +163,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Disabled" = "Disabled";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Do nothing" = "Do nothing";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Done" = "Done";
|
||||
|
||||
@@ -241,6 +232,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Ignore shortcuts when active" = "Ignore shortcuts when active";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep open" = "Keep open";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Keep open and search" = "Keep open and search";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Language" = "Language";
|
||||
|
||||
@@ -262,6 +259,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Light" = "Light";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Lock search" = "Lock search";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Medium" = "Medium";
|
||||
|
||||
@@ -325,12 +325,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Preview selected window" = "Preview selected window";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Preview the selected window." = "Preview the selected window.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Quit" = "Quit";
|
||||
|
||||
/* Menubar option. %@ is AltTab */
|
||||
"Quit %@" = "Quit %@";
|
||||
|
||||
@@ -367,6 +361,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Screen showing AltTab" = "Screen showing AltTab";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Search" = "Search";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Select next window" = "Select next window";
|
||||
|
||||
@@ -562,6 +559,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"View existing discussions" = "View existing discussions";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Vim keys" = "Vim keys";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Vim keys already assigned to other actions:\n%@" = "Vim keys already assigned to other actions:\n%@";
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class ATShortcut {
|
||||
if triggerPhase == .down && (!App.app.appIsBeingUsed || index == nil || index == App.app.shortcutIndex) {
|
||||
return true
|
||||
}
|
||||
if triggerPhase == .up && App.app.appIsBeingUsed && (index == nil || index == App.app.shortcutIndex) && !App.app.forceDoNothingOnRelease && Preferences.shortcutStyle[App.app.shortcutIndex] == .focusOnRelease {
|
||||
if triggerPhase == .up && App.app.appIsBeingUsed && (index == nil || index == App.app.shortcutIndex) && !App.app.forceDoNothingOnRelease && Preferences.shortcutStyle == .focusOnRelease {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class ATShortcut {
|
||||
// Another issue is events being dropped by macOS, which we never receive
|
||||
// Knowing this, we handle these edge-cases by double checking if holdShortcut is UP, when any shortcut state is UP
|
||||
// If it is, then we trigger the holdShortcut action
|
||||
if App.app.appIsBeingUsed && !App.app.forceDoNothingOnRelease && Preferences.shortcutStyle[App.app.shortcutIndex] == .focusOnRelease {
|
||||
if App.app.appIsBeingUsed && !App.app.forceDoNothingOnRelease && Preferences.shortcutStyle == .focusOnRelease {
|
||||
if let currentHoldShortcut = ControlsTab.shortcuts[Preferences.indexToName("holdShortcut", App.app.shortcutIndex)],
|
||||
id == currentHoldShortcut.id {
|
||||
let currentModifiers = cocoaToCarbonFlags(ModifierFlags.current)
|
||||
|
||||
@@ -225,8 +225,8 @@ enum ShortcutStylePreference: CaseIterable, MacroPreference {
|
||||
var localizedString: LocalizedString {
|
||||
switch self {
|
||||
case .focusOnRelease: return NSLocalizedString("Focus selected window", comment: "")
|
||||
case .doNothingOnRelease: return NSLocalizedString("Do nothing", comment: "")
|
||||
case .searchOnRelease: return NSLocalizedString("Search", comment: "")
|
||||
case .doNothingOnRelease: return NSLocalizedString("Keep open", comment: "")
|
||||
case .searchOnRelease: return NSLocalizedString("Keep open and search", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ class Preferences {
|
||||
"previewFocusedWindow": "false",
|
||||
"screenRecordingPermissionSkipped": "false",
|
||||
"trackpadHapticFeedbackEnabled": "true",
|
||||
"settingsWindowShownOnFirstLaunch": "false",
|
||||
]
|
||||
|
||||
// system preferences
|
||||
@@ -129,6 +130,7 @@ class Preferences {
|
||||
static var blacklist: [BlacklistEntry] { CachedUserDefaults.json("blacklist", [BlacklistEntry].self) }
|
||||
static var previewSelectedWindow: Bool { CachedUserDefaults.bool("previewFocusedWindow") }
|
||||
static var screenRecordingPermissionSkipped: Bool { CachedUserDefaults.bool("screenRecordingPermissionSkipped") }
|
||||
static var settingsWindowShownOnFirstLaunch: Bool { CachedUserDefaults.bool("settingsWindowShownOnFirstLaunch") }
|
||||
|
||||
// macro values
|
||||
static var appearanceStyle: AppearanceStylePreference { CachedUserDefaults.macroPref("appearanceStyle", AppearanceStylePreference.allCases) }
|
||||
@@ -151,7 +153,7 @@ class Preferences {
|
||||
static var showFullscreenWindows: [ShowHowPreference] { ["showFullscreenWindows", "showFullscreenWindows2", "showFullscreenWindows3", "showFullscreenWindows4"].map { CachedUserDefaults.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var showWindowlessApps: [ShowHowPreference] { ["showWindowlessApps", "showWindowlessApps2", "showWindowlessApps3", "showWindowlessApps4"].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 shortcutStyle: ShortcutStylePreference { CachedUserDefaults.macroPref("shortcutStyle", 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) }
|
||||
@@ -172,6 +174,10 @@ class Preferences {
|
||||
UserDefaults.standard.register(defaults: defaultValues)
|
||||
}
|
||||
|
||||
static func markSettingsWindowShownOnFirstLaunch() {
|
||||
set("settingsWindowShownOnFirstLaunch", "true", false)
|
||||
}
|
||||
|
||||
static func set<T>(_ key: String, _ value: T, _ notify: Bool = true) where T: Encodable {
|
||||
UserDefaults.standard.set(key == "blacklist" ? jsonEncode(value) : value, forKey: key)
|
||||
CachedUserDefaults.cache.removeValue(forKey: key)
|
||||
|
||||
@@ -77,7 +77,7 @@ class TrackpadEvents {
|
||||
|
||||
private static func handleEventIfAppIsBeingUsed(_ fingersDown: Int, _ activeTouches: Set<NSTouch>, _ requiredFingers: Int) {
|
||||
if fingersDown <= TriggerSwipeDetector.maxFingersDownDuringTrigger - requiredFingers {
|
||||
if App.app.shortcutIndex == Preferences.gestureIndex && !App.app.forceDoNothingOnRelease && Preferences.shortcutStyle[App.app.shortcutIndex] == .focusOnRelease {
|
||||
if App.app.shortcutIndex == Preferences.gestureIndex && !App.app.forceDoNothingOnRelease && Preferences.shortcutStyle == .focusOnRelease {
|
||||
DispatchQueue.main.async {
|
||||
App.app.focusTarget()
|
||||
}
|
||||
|
||||
+39
-16
@@ -16,11 +16,13 @@ class App: AppCenterApplication {
|
||||
static let repository = "https://github.com/lwouis/alt-tab-macos"
|
||||
static let website = "https://alt-tab-macos.netlify.app"
|
||||
static let appIcon = CGImage.named("app.icns")
|
||||
static var supportProjectAction: Selector { #selector(App.app.supportProject) }
|
||||
static var app: App!
|
||||
var isTerminating = false
|
||||
var thumbnailsPanel: TilesPanel!
|
||||
var previewPanel: PreviewPanel!
|
||||
var preferencesWindow: PreferencesWindow!
|
||||
var settingsWindow: SettingsWindow!
|
||||
var aboutWindow: AboutWindow!
|
||||
var permissionsWindow: PermissionsWindow?
|
||||
var appIsBeingUsed = false
|
||||
var shortcutIndex = 0
|
||||
@@ -91,7 +93,8 @@ class App: AppCenterApplication {
|
||||
}
|
||||
|
||||
private func allSecondaryWindowsCanBecomeKey(_ canBecomeKey_: Bool) {
|
||||
preferencesWindow?.canBecomeKey_ = canBecomeKey_
|
||||
settingsWindow?.canBecomeKey_ = canBecomeKey_
|
||||
aboutWindow?.canBecomeKey_ = canBecomeKey_
|
||||
feedbackWindow?.canBecomeKey_ = canBecomeKey_
|
||||
permissionsWindow?.canBecomeKey_ = canBecomeKey_
|
||||
}
|
||||
@@ -158,8 +161,17 @@ class App: AppCenterApplication {
|
||||
showSecondaryWindow(getOrCreateFeedbackWindow())
|
||||
}
|
||||
|
||||
@objc func showPreferencesWindow() {
|
||||
showSecondaryWindow(getOrCreatePreferencesWindow())
|
||||
@objc func showSettingsWindow() {
|
||||
showSecondaryWindow(getOrCreateSettingsWindow())
|
||||
if settingsWindow?.isVisible != true {
|
||||
settingsWindow = SettingsWindow()
|
||||
showSecondaryWindow(settingsWindow)
|
||||
settingsWindow?.orderFrontRegardless()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func showAboutWindow() {
|
||||
showSecondaryWindow(getOrCreateAboutWindow())
|
||||
}
|
||||
|
||||
func showSecondaryWindow(_ window: NSWindow?) {
|
||||
@@ -173,11 +185,18 @@ class App: AppCenterApplication {
|
||||
}
|
||||
}
|
||||
|
||||
private func getOrCreatePreferencesWindow() -> PreferencesWindow {
|
||||
if preferencesWindow == nil {
|
||||
preferencesWindow = PreferencesWindow()
|
||||
private func getOrCreateSettingsWindow() -> SettingsWindow {
|
||||
if settingsWindow == nil {
|
||||
settingsWindow = SettingsWindow()
|
||||
}
|
||||
return preferencesWindow
|
||||
return settingsWindow
|
||||
}
|
||||
|
||||
private func getOrCreateAboutWindow() -> AboutWindow {
|
||||
if aboutWindow == nil {
|
||||
aboutWindow = AboutWindow()
|
||||
}
|
||||
return aboutWindow
|
||||
}
|
||||
|
||||
private func getOrCreateFeedbackWindow() -> FeedbackWindow {
|
||||
@@ -194,6 +213,14 @@ class App: AppCenterApplication {
|
||||
return permissionsWindow!
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func showSettingsWindowOnFirstLaunchIfNeeded() -> Bool {
|
||||
guard !Preferences.settingsWindowShownOnFirstLaunch else { return false }
|
||||
showSettingsWindow()
|
||||
Preferences.markSettingsWindowShownOnFirstLaunch()
|
||||
return true
|
||||
}
|
||||
|
||||
func showPermissionsWindow() {
|
||||
getOrCreatePermissionsWindow().show()
|
||||
}
|
||||
@@ -206,11 +233,6 @@ class App: AppCenterApplication {
|
||||
showUi(0)
|
||||
}
|
||||
|
||||
@objc func showAboutTab() {
|
||||
getOrCreatePreferencesWindow().selectTab("about")
|
||||
showPreferencesWindow()
|
||||
}
|
||||
|
||||
func cycleSelection(_ direction: Direction, allowWrap: Bool = true) {
|
||||
if direction == .up || direction == .down {
|
||||
thumbnailsPanel.tilesView.navigateUpOrDown(direction, allowWrap: allowWrap)
|
||||
@@ -296,7 +318,7 @@ class App: AppCenterApplication {
|
||||
}
|
||||
isFirstSummon = false
|
||||
self.shortcutIndex = shortcutIndex
|
||||
let shouldStartInSearchMode = Preferences.shortcutStyle[self.shortcutIndex] == .searchOnRelease
|
||||
let shouldStartInSearchMode = Preferences.shortcutStyle == .searchOnRelease
|
||||
thumbnailsPanel.tilesView.startSearchSession(shouldStartInSearchMode)
|
||||
if shouldStartInSearchMode {
|
||||
forceDoNothingOnRelease = true
|
||||
@@ -393,13 +415,14 @@ extension App: NSApplicationDelegate {
|
||||
CliEvents.observe()
|
||||
Logger.info { "Finished launching AltTab" }
|
||||
BenchmarkRunner.startIfNeeded()
|
||||
showSettingsWindowOnFirstLaunchIfNeeded()
|
||||
#if DEBUG
|
||||
// self.showPreferencesWindow()
|
||||
// self.showSettingsWindow()
|
||||
#endif
|
||||
}
|
||||
|
||||
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
|
||||
showPreferencesWindow()
|
||||
showSettingsWindow()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -24,13 +24,13 @@ class Menubar {
|
||||
permissionCalloutMenuItems = [permissionCalloutMenuItem, calloutSeparator]
|
||||
addMenuItem(NSLocalizedString("Show", comment: "Menubar option"), #selector(App.app.showUiFromShortcut0), "", "eye")
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
addMenuItem(NSLocalizedString("Settings…", comment: "Menubar option"), #selector(App.app.showPreferencesWindow), ",", "gear")
|
||||
addMenuItem(NSLocalizedString("Settings…", comment: "Menubar option"), #selector(App.app.showSettingsWindow), ",", "gear")
|
||||
addMenuItem(NSLocalizedString("Check for updates…", comment: "Menubar option"), #selector(App.app.checkForUpdatesNow), "", "checkmark.arrow.trianglehead.clockwise")
|
||||
addMenuItem(NSLocalizedString("Check permissions…", comment: "Menubar option"), #selector(App.app.checkPermissions), "", "hand.raised")
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
addMenuItem(String(format: NSLocalizedString("About %@", comment: "Menubar option. %@ is AltTab"), App.name), #selector(App.app.showAboutTab), "", "info.circle")
|
||||
addMenuItem(String(format: NSLocalizedString("About %@", comment: "Menubar option. %@ is AltTab"), App.name), #selector(App.app.showAboutWindow), "", "info.circle")
|
||||
addMenuItem(NSLocalizedString("Send feedback…", comment: "Menubar option"), #selector(App.app.showFeedbackPanel), "", "text.bubble")
|
||||
addMenuItem(NSLocalizedString("Support this project", comment: "Menubar option"), #selector(App.app.supportProject), "", "heart.fill", .red)
|
||||
addMenuItem(NSLocalizedString("Support this project", comment: "Menubar option"), App.supportProjectAction, "", "heart.fill", .red)
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
addMenuItem(String(format: NSLocalizedString("Quit %@", comment: "Menubar option. %@ is AltTab"), App.name), #selector(NSApplication.terminate(_:)), "q", nil) // "xmark.rectangle" is not necessary; macos automatically recognizes Quit
|
||||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
|
||||
|
||||
@@ -26,7 +26,7 @@ class ImageTextButtonView: NSStackView {
|
||||
static let spacing = CGFloat(5)
|
||||
static let cornerRadius = CGFloat(7)
|
||||
static let borderWidth = CGFloat(3)
|
||||
static let padding = CGFloat(2)
|
||||
static let padding = CGFloat(0)
|
||||
|
||||
var onClick: ActionClosure?
|
||||
var button: NSButton!
|
||||
@@ -81,22 +81,22 @@ class ImageTextButtonView: NSStackView {
|
||||
imageView.layer?.cornerRadius = cornerRadius - 3
|
||||
imageContainer.addSubview(imageView)
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.widthAnchor.constraint(equalToConstant: image.width),
|
||||
imageView.heightAnchor.constraint(equalToConstant: image.height),
|
||||
imageView.topAnchor.constraint(equalTo: imageContainer.topAnchor, constant: padding),
|
||||
imageView.bottomAnchor.constraint(equalTo: imageContainer.bottomAnchor, constant: -padding),
|
||||
imageView.leadingAnchor.constraint(equalTo: imageContainer.leadingAnchor, constant: padding),
|
||||
imageView.trailingAnchor.constraint(equalTo: imageContainer.trailingAnchor, constant: -padding),
|
||||
])
|
||||
button.addSubview(imageContainer)
|
||||
let imageAspectRatio = image.height / image.width
|
||||
NSLayoutConstraint.activate([
|
||||
imageContainer.centerXAnchor.constraint(equalTo: button.centerXAnchor),
|
||||
imageContainer.centerYAnchor.constraint(equalTo: button.centerYAnchor),
|
||||
imageContainer.widthAnchor.constraint(equalTo: button.widthAnchor),
|
||||
imageContainer.heightAnchor.constraint(equalTo: button.heightAnchor),
|
||||
imageContainer.topAnchor.constraint(equalTo: button.topAnchor),
|
||||
imageContainer.bottomAnchor.constraint(equalTo: button.bottomAnchor),
|
||||
imageContainer.leadingAnchor.constraint(equalTo: button.leadingAnchor),
|
||||
imageContainer.trailingAnchor.constraint(equalTo: button.trailingAnchor),
|
||||
button.topAnchor.constraint(equalTo: topAnchor, constant: ImageTextButtonView.borderWidth),
|
||||
button.leadingAnchor.constraint(equalTo: leadingAnchor, constant: ImageTextButtonView.borderWidth),
|
||||
button.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -ImageTextButtonView.borderWidth),
|
||||
button.heightAnchor.constraint(equalTo: button.widthAnchor, multiplier: imageAspectRatio),
|
||||
])
|
||||
button.identifier = NSUserInterfaceItemIdentifier(rawName)
|
||||
button.onAction = { control in
|
||||
|
||||
@@ -7,7 +7,7 @@ class TableGroupSetView: NSStackView {
|
||||
static let spacing = CGFloat(20)
|
||||
static let tableGroupSpacing = CGFloat(5)
|
||||
static let othersSpacing = CGFloat(10)
|
||||
static let titleTableGroupSpacing = CGFloat(30)
|
||||
static let titleTableGroupSpacing = CGFloat(20)
|
||||
static let padding = CGFloat(20)
|
||||
static let leftRightPadding = 2 * TableGroupSetView.padding
|
||||
|
||||
@@ -18,11 +18,16 @@ class TableGroupSetView: NSStackView {
|
||||
toolsViews: [NSView]? = nil,
|
||||
spacing: CGFloat = TableGroupSetView.spacing,
|
||||
tableGroupSpacing: CGFloat = TableGroupSetView.tableGroupSpacing,
|
||||
titleTableGroupSpacing: CGFloat = TableGroupSetView.titleTableGroupSpacing,
|
||||
othersSpacing: CGFloat = TableGroupSetView.othersSpacing,
|
||||
padding: CGFloat = TableGroupSetView.padding,
|
||||
topPadding: CGFloat? = nil,
|
||||
bottomPadding: CGFloat? = nil,
|
||||
othersAlignment: NSLayoutConstraint.Attribute = .trailing,
|
||||
toolsAlignment: NSLayoutConstraint.Attribute = .centerX) {
|
||||
self.init(frame: .zero)
|
||||
let resolvedTopPadding = topPadding ?? padding
|
||||
let resolvedBottomPadding = bottomPadding ?? padding
|
||||
self.spacing = spacing
|
||||
orientation = .vertical
|
||||
self.originalViews = originalViews
|
||||
@@ -33,39 +38,71 @@ class TableGroupSetView: NSStackView {
|
||||
if view is TableGroupView {
|
||||
if !lastViewWasTableGroup {
|
||||
// Only reset other views if we are switching from non-TableGroupView to TableGroupView
|
||||
addContinuousOthersToSetViews(&continuousOthers, views: &verticalViews, othersSpacing: othersSpacing, padding: padding, alignment: othersAlignment)
|
||||
addContinuousOthersToSetViews(&continuousOthers,
|
||||
views: &verticalViews,
|
||||
othersSpacing: othersSpacing,
|
||||
horizontalPadding: padding,
|
||||
topPadding: resolvedTopPadding,
|
||||
alignment: othersAlignment)
|
||||
}
|
||||
continuousTableGroups.append(view)
|
||||
lastViewWasTableGroup = true
|
||||
} else if view is IllustratedImageThemeView {
|
||||
lastViewWasTableGroup = false
|
||||
addContinuousTableGroupsToSetViews(&continuousTableGroups, views: &verticalViews, tableGroupSpacing: tableGroupSpacing, padding: padding)
|
||||
addContinuousOthersToSetViews(&continuousOthers, views: &verticalViews, othersSpacing: othersSpacing, padding: padding, alignment: othersAlignment)
|
||||
addToolsViewToSetViews([view], views: &verticalViews, padding: padding, alignment: toolsAlignment)
|
||||
addContinuousTableGroupsToSetViews(&continuousTableGroups,
|
||||
views: &verticalViews,
|
||||
tableGroupSpacing: tableGroupSpacing,
|
||||
titleTableGroupSpacing: titleTableGroupSpacing,
|
||||
horizontalPadding: padding,
|
||||
topPadding: resolvedTopPadding)
|
||||
addContinuousOthersToSetViews(&continuousOthers,
|
||||
views: &verticalViews,
|
||||
othersSpacing: othersSpacing,
|
||||
horizontalPadding: padding,
|
||||
topPadding: resolvedTopPadding,
|
||||
alignment: othersAlignment)
|
||||
addToolsViewToSetViews([view], views: &verticalViews, horizontalPadding: padding, topPadding: resolvedTopPadding, alignment: toolsAlignment)
|
||||
} else {
|
||||
if lastViewWasTableGroup {
|
||||
// Only reset table group views if we are switching from TableGroupView to non-TableGroupView
|
||||
addContinuousTableGroupsToSetViews(&continuousTableGroups, views: &verticalViews, tableGroupSpacing: tableGroupSpacing, padding: padding)
|
||||
addContinuousTableGroupsToSetViews(&continuousTableGroups,
|
||||
views: &verticalViews,
|
||||
tableGroupSpacing: tableGroupSpacing,
|
||||
titleTableGroupSpacing: titleTableGroupSpacing,
|
||||
horizontalPadding: padding,
|
||||
topPadding: resolvedTopPadding)
|
||||
}
|
||||
continuousOthers.append(view)
|
||||
lastViewWasTableGroup = false
|
||||
}
|
||||
}
|
||||
// Ensure any remaining views are added
|
||||
addContinuousTableGroupsToSetViews(&continuousTableGroups, views: &verticalViews, tableGroupSpacing: tableGroupSpacing, padding: padding)
|
||||
addContinuousOthersToSetViews(&continuousOthers, views: &verticalViews, othersSpacing: othersSpacing, padding: padding, alignment: othersAlignment)
|
||||
addContinuousTableGroupsToSetViews(&continuousTableGroups,
|
||||
views: &verticalViews,
|
||||
tableGroupSpacing: tableGroupSpacing,
|
||||
titleTableGroupSpacing: titleTableGroupSpacing,
|
||||
horizontalPadding: padding,
|
||||
topPadding: resolvedTopPadding)
|
||||
addContinuousOthersToSetViews(&continuousOthers,
|
||||
views: &verticalViews,
|
||||
othersSpacing: othersSpacing,
|
||||
horizontalPadding: padding,
|
||||
topPadding: resolvedTopPadding,
|
||||
alignment: othersAlignment)
|
||||
if let toolsViews {
|
||||
addToolsViewToSetViews(toolsViews, views: &verticalViews, padding: padding, alignment: toolsAlignment)
|
||||
addToolsViewToSetViews(toolsViews, views: &verticalViews, horizontalPadding: padding, topPadding: resolvedTopPadding, alignment: toolsAlignment)
|
||||
}
|
||||
if let lastStackView = verticalViews.last {
|
||||
lastStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding).isActive = true
|
||||
lastStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -resolvedBottomPadding).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
func addContinuousTableGroupsToSetViews(_ continuousTableGroups: inout [NSView],
|
||||
views: inout [NSView],
|
||||
tableGroupSpacing: CGFloat,
|
||||
padding: CGFloat) {
|
||||
titleTableGroupSpacing: CGFloat,
|
||||
horizontalPadding: CGFloat,
|
||||
topPadding: CGFloat) {
|
||||
if !continuousTableGroups.isEmpty {
|
||||
let stackView = NSStackView()
|
||||
stackView.orientation = .vertical
|
||||
@@ -76,10 +113,10 @@ class TableGroupSetView: NSStackView {
|
||||
var spacerHeight = CGFloat(0)
|
||||
if index == 0 && !views.isEmpty {
|
||||
// at first, height: spacing + padding
|
||||
spacerHeight = TableGroupSetView.titleTableGroupSpacing - spacing
|
||||
spacerHeight = titleTableGroupSpacing - spacing
|
||||
} else if index > 0 {
|
||||
// inner, height: tableGroupSpacing + padding
|
||||
spacerHeight = TableGroupSetView.titleTableGroupSpacing
|
||||
spacerHeight = titleTableGroupSpacing
|
||||
}
|
||||
let spacerView = NSView()
|
||||
spacerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
@@ -92,12 +129,17 @@ class TableGroupSetView: NSStackView {
|
||||
}
|
||||
continuousTableGroups.removeAll()
|
||||
addArrangedSubview(stackView)
|
||||
setStackViewConstraints(stackView, isFirst: views.isEmpty, padding: padding)
|
||||
setStackViewConstraints(stackView, isFirst: views.isEmpty, horizontalPadding: horizontalPadding, topPadding: topPadding)
|
||||
views.append(stackView)
|
||||
}
|
||||
}
|
||||
|
||||
func addContinuousOthersToSetViews(_ continuousOthers: inout [NSView], views: inout [NSView], othersSpacing: CGFloat, padding: CGFloat, alignment: NSLayoutConstraint.Attribute) {
|
||||
func addContinuousOthersToSetViews(_ continuousOthers: inout [NSView],
|
||||
views: inout [NSView],
|
||||
othersSpacing: CGFloat,
|
||||
horizontalPadding: CGFloat,
|
||||
topPadding: CGFloat,
|
||||
alignment: NSLayoutConstraint.Attribute) {
|
||||
if !continuousOthers.isEmpty {
|
||||
let stackView = NSStackView()
|
||||
stackView.orientation = .horizontal
|
||||
@@ -106,12 +148,16 @@ class TableGroupSetView: NSStackView {
|
||||
stackView.setViews(continuousOthers, in: .leading)
|
||||
continuousOthers.removeAll()
|
||||
addArrangedSubview(stackView)
|
||||
setStackViewConstraints(stackView, isFirst: views.isEmpty, padding: padding)
|
||||
setStackViewConstraints(stackView, isFirst: views.isEmpty, horizontalPadding: horizontalPadding, topPadding: topPadding)
|
||||
views.append(stackView)
|
||||
}
|
||||
}
|
||||
|
||||
func addToolsViewToSetViews(_ originalViews: [NSView], views: inout [NSView], padding: CGFloat, alignment: NSLayoutConstraint.Attribute) {
|
||||
func addToolsViewToSetViews(_ originalViews: [NSView],
|
||||
views: inout [NSView],
|
||||
horizontalPadding: CGFloat,
|
||||
topPadding: CGFloat,
|
||||
alignment: NSLayoutConstraint.Attribute) {
|
||||
if !originalViews.isEmpty {
|
||||
let stackView = NSStackView()
|
||||
stackView.orientation = .horizontal
|
||||
@@ -119,18 +165,18 @@ class TableGroupSetView: NSStackView {
|
||||
stackView.alignment = alignment
|
||||
stackView.setViews(originalViews, in: .leading)
|
||||
addArrangedSubview(stackView)
|
||||
setStackViewConstraints(stackView, isFirst: views.isEmpty, padding: padding)
|
||||
setStackViewConstraints(stackView, isFirst: views.isEmpty, horizontalPadding: horizontalPadding, topPadding: topPadding)
|
||||
views.append(stackView)
|
||||
}
|
||||
}
|
||||
|
||||
func setStackViewConstraints(_ stackView: NSStackView, isFirst: Bool, padding: CGFloat) {
|
||||
func setStackViewConstraints(_ stackView: NSStackView, isFirst: Bool, horizontalPadding: CGFloat, topPadding: CGFloat) {
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
if isFirst {
|
||||
stackView.topAnchor.constraint(equalTo: topAnchor, constant: padding).isActive = true
|
||||
stackView.topAnchor.constraint(equalTo: topAnchor, constant: topPadding).isActive = true
|
||||
}
|
||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding).isActive = true
|
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding).isActive = true
|
||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalPadding).isActive = true
|
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalPadding).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +276,7 @@ class TableGroupView: ClickHoverStackView {
|
||||
titleStackView.spacing = TableGroupView.rowIntraSpacing
|
||||
if let title {
|
||||
titleLabel.stringValue = title
|
||||
titleLabel.font = NSFont.boldSystemFont(ofSize: 13)
|
||||
titleLabel.font = NSFont.systemFont(ofSize: 13, weight: .medium)
|
||||
titleLabel.alignment = .natural
|
||||
titleLabel.lineBreakMode = .byWordWrapping
|
||||
titleLabel.maximumNumberOfLines = 0
|
||||
@@ -396,7 +442,7 @@ class TableGroupView: ClickHoverStackView {
|
||||
static func makeText(_ leftText: String?, bold: Bool = false) -> NSTextField {
|
||||
let leftLabel = NSTextField(labelWithString: leftText ?? "")
|
||||
if bold {
|
||||
leftLabel.font = NSFont.boldSystemFont(ofSize: 13)
|
||||
leftLabel.font = NSFont.systemFont(ofSize: 13, weight: .medium)
|
||||
}
|
||||
leftLabel.alignment = .left
|
||||
leftLabel.lineBreakMode = .byWordWrapping
|
||||
|
||||
@@ -1,14 +1,45 @@
|
||||
import Cocoa
|
||||
|
||||
class BlacklistView: NSScrollView {
|
||||
convenience init() {
|
||||
override class var isCompatibleWithResponsiveScrolling: Bool { true }
|
||||
|
||||
convenience init(width: CGFloat = 500, height: CGFloat = 378) {
|
||||
self.init(frame: .zero)
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
borderType = .bezelBorder
|
||||
hasHorizontalScroller = false
|
||||
hasVerticalScroller = true
|
||||
verticalScrollElasticity = .none
|
||||
usesPredominantAxisScrolling = true
|
||||
documentView = TableView(nil)
|
||||
fit(500, 378)
|
||||
fit(width, height)
|
||||
}
|
||||
|
||||
override func scrollWheel(with event: NSEvent) {
|
||||
guard shouldHandleVerticalScroll(event) else {
|
||||
super.scrollWheel(with: event)
|
||||
return
|
||||
}
|
||||
let y = contentView.bounds.origin.y
|
||||
super.scrollWheel(with: event)
|
||||
if abs(contentView.bounds.origin.y - y) < 0.5 {
|
||||
parentScrollView()?.scrollWheel(with: event)
|
||||
}
|
||||
}
|
||||
|
||||
private func shouldHandleVerticalScroll(_ event: NSEvent) -> Bool {
|
||||
abs(event.scrollingDeltaY) > abs(event.scrollingDeltaX) && abs(event.scrollingDeltaY) > 0.1
|
||||
}
|
||||
|
||||
private func parentScrollView() -> NSScrollView? {
|
||||
var parent = superview
|
||||
while let view = parent {
|
||||
if let scrollView = view as? NSScrollView {
|
||||
return scrollView
|
||||
}
|
||||
parent = view.superview
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ class TilesView: NSObject {
|
||||
}
|
||||
|
||||
private func configureSearchField() {
|
||||
searchField.placeholderString = NSLocalizedString("Search windows", comment: "")
|
||||
searchField.placeholderString = NSLocalizedString("Search", comment: "")
|
||||
searchField.sendsSearchStringImmediately = true
|
||||
searchField.sendsWholeSearchString = true
|
||||
searchField.bezelStyle = .roundedBezel
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
import Cocoa
|
||||
|
||||
class PreferencesWindow: NSWindow {
|
||||
static let width = CGFloat(520)
|
||||
var toolbarItems = [NSToolbarItem.Identifier: (Int, NSToolbarItem, NSView)]()
|
||||
var canBecomeKey_ = true
|
||||
override var canBecomeKey: Bool { canBecomeKey_ }
|
||||
var largestTabWidth: CGFloat!
|
||||
|
||||
convenience init() {
|
||||
self.init(contentRect: .zero, styleMask: [.titled, .miniaturizable, .closable], backing: .buffered, defer: false)
|
||||
setupWindow()
|
||||
setupView()
|
||||
}
|
||||
|
||||
private func setupWindow() {
|
||||
isReleasedWhenClosed = false
|
||||
hidesOnDeactivate = false
|
||||
addQuitButton()
|
||||
}
|
||||
|
||||
private func addQuitButton() {
|
||||
let quitButton = NSButton(title: NSLocalizedString("Quit", comment: ""), target: nil, action: #selector(NSApplication.terminate(_:)))
|
||||
let titleBarView = standardWindowButton(.closeButton)!.superview!
|
||||
titleBarView.addSubview(quitButton)
|
||||
quitButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
quitButton.topAnchor.constraint(equalTo: titleBarView.topAnchor, constant: 5).isActive = true
|
||||
if windowTitlebarLayoutDirection == .rightToLeft {
|
||||
quitButton.leftAnchor.constraint(equalTo: titleBarView.leftAnchor, constant: 8).isActive = true
|
||||
} else {
|
||||
quitButton.rightAnchor.constraint(equalTo: titleBarView.rightAnchor, constant: -8).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
private func setupView() {
|
||||
toolbar = NSToolbar(identifier: "1")
|
||||
toolbar!.delegate = self
|
||||
// toolbar breaks with the new default style on macOS 11; we force the classic style (see #914)
|
||||
if #available(macOS 11.0, *) { toolbarStyle = .preference }
|
||||
toolbar!.displayMode = .iconAndLabel
|
||||
toolbar!.showsBaselineSeparator = true
|
||||
[
|
||||
(0, NSLocalizedString("General", comment: ""), "general", "switch.2", GeneralTab.initTab()),
|
||||
(1, NSLocalizedString("Controls", comment: ""), "controls", "command", ControlsTab.initTab()),
|
||||
(2, NSLocalizedString("Appearance", comment: ""), "appearance", "paintpalette", AppearanceTab.initTab()),
|
||||
(3, NSLocalizedString("Policies", comment: ""), "policies", "antenna.radiowaves.left.and.right", PoliciesTab.initTab()),
|
||||
(4, NSLocalizedString("Blacklists", comment: ""), "blacklists", "hand.raised", BlacklistsTab.initTab()),
|
||||
(5, NSLocalizedString("About", comment: ""), "about", "info.circle", AboutTab.initTab()),
|
||||
(6, NSLocalizedString("Acknowledgments", comment: ""), "acknowledgments", "hand.thumbsup", AcknowledgmentsTab.initTab()),
|
||||
].forEach { makeToolbarItem($0.0, $0.1, $0.2, $0.3, $0.4) }
|
||||
largestTabWidth = Array(toolbarItems.values).reduce(CGFloat(0)) { max($0, $1.2.subviews[0].fittingSize.width) }
|
||||
Array(toolbarItems.values).forEach {
|
||||
$0.2.fit(largestTabWidth, $0.2.subviews[0].fittingSize.height)
|
||||
}
|
||||
refreshControlsFromPreferences()
|
||||
selectTab("general")
|
||||
}
|
||||
|
||||
private func refreshControlsFromPreferences() {
|
||||
GeneralTab.refreshControlsFromPreferences()
|
||||
PoliciesTab.refreshControlsFromPreferences()
|
||||
}
|
||||
|
||||
func selectTab(_ id: String) {
|
||||
toolbar!.selectedItemIdentifier = NSToolbarItem.Identifier(rawValue: id)
|
||||
tabItemClicked(toolbarItems[toolbar!.selectedItemIdentifier!]!.1)
|
||||
}
|
||||
|
||||
func makeToolbarItem(_ index: Int, _ label: String, _ id: String, _ image: String, _ view: NSView) {
|
||||
let identifier = NSToolbarItem.Identifier(rawValue: id)
|
||||
let item = NSToolbarItem(itemIdentifier: identifier)
|
||||
item.label = label
|
||||
if #available(macOS 11.0, *) {
|
||||
// each systemSymbolName has a different minimum macOS requirements; we need to make sure it exists
|
||||
if let image = NSImage(systemSymbolName: image, accessibilityDescription: nil) {
|
||||
item.image = image
|
||||
}
|
||||
}
|
||||
// it seems that new instances of NSToolbarItems have their image set to a 1x1 image
|
||||
if item.image == nil || item.image!.size == NSSize(width: 1, height: 1) {
|
||||
item.image = NSImage.initCopy(id)
|
||||
item.image!.isTemplate = true
|
||||
item.maxSize = NSSize(width: 22, height: 22)
|
||||
}
|
||||
item.target = self
|
||||
item.action = #selector(tabItemClicked)
|
||||
let wrapView = NSView(frame: .zero)
|
||||
wrapView.translatesAutoresizingMaskIntoConstraints = false
|
||||
wrapView.subviews = [view]
|
||||
view.centerXAnchor.constraint(equalTo: wrapView.centerXAnchor).isActive = true
|
||||
toolbarItems[identifier] = (index, item, wrapView)
|
||||
toolbar!.insertItem(withItemIdentifier: identifier, at: index)
|
||||
}
|
||||
|
||||
@objc func tabItemClicked(_ item: NSToolbarItem) {
|
||||
let item = toolbarItems[item.itemIdentifier]!
|
||||
contentView = item.2
|
||||
title = item.1.label
|
||||
// Reset focused ring
|
||||
makeFirstResponder(contentView?.subviews[0])
|
||||
recalculateKeyViewLoop()
|
||||
}
|
||||
|
||||
override func close() {
|
||||
hideAppIfLastWindowIsClosed()
|
||||
super.close()
|
||||
}
|
||||
}
|
||||
|
||||
extension PreferencesWindow: NSToolbarDelegate {
|
||||
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
|
||||
return toolbarItems[itemIdentifier]!.1
|
||||
}
|
||||
|
||||
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
return Array(toolbarItems.keys)
|
||||
}
|
||||
|
||||
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
return Array(toolbarItems.keys)
|
||||
}
|
||||
|
||||
func toolbarSelectableItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
return Array(toolbarItems.keys)
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import Cocoa
|
||||
|
||||
class AboutTab {
|
||||
static func initTab() -> NSView {
|
||||
let appIcon = LightImageView()
|
||||
appIcon.translatesAutoresizingMaskIntoConstraints = false
|
||||
appIcon.updateContents(.cgImage(App.appIcon), NSSize(width: 256, height: 256))
|
||||
appIcon.fit(256, 256)
|
||||
let appText = StackView([
|
||||
BoldLabel(App.name),
|
||||
NSTextField(wrappingLabelWithString: NSLocalizedString("Version", comment: "") + " " + App.version),
|
||||
NSTextField(wrappingLabelWithString: App.licence),
|
||||
HyperlinkLabel(NSLocalizedString("Source code repository", comment: ""), App.repository),
|
||||
HyperlinkLabel(NSLocalizedString("Latest releases", comment: ""), App.repository + "/releases"),
|
||||
], .vertical)
|
||||
appText.spacing = GridView.interPadding / 2
|
||||
let rowToSeparate = 3
|
||||
appText.views[rowToSeparate].topAnchor.constraint(equalTo: appText.views[rowToSeparate - 1].bottomAnchor, constant: GridView.interPadding).isActive = true
|
||||
let appInfo = NSStackView(views: [appIcon, appText])
|
||||
appIcon.translatesAutoresizingMaskIntoConstraints = false
|
||||
appInfo.spacing = GridView.interPadding
|
||||
appInfo.alignment = .centerY
|
||||
let sendFeedback = makeButtonWithIcon(NSLocalizedString("Send feedback…", comment: ""), #selector(App.app.showFeedbackPanel), "text.bubble")
|
||||
let supportProject = makeButtonWithIcon(NSLocalizedString("Support this project", comment: ""), #selector(App.app.supportProject), "heart.fill", .red)
|
||||
let grid = GridView([
|
||||
[appInfo],
|
||||
[sendFeedback],
|
||||
[supportProject],
|
||||
])
|
||||
let sendFeedbackCell = grid.cell(atColumnIndex: 0, rowIndex: 1)
|
||||
sendFeedbackCell.xPlacement = .center
|
||||
sendFeedbackCell.row!.topPadding = GridView.interPadding
|
||||
let supportProjectCell = grid.cell(atColumnIndex: 0, rowIndex: 2)
|
||||
supportProjectCell.xPlacement = .center
|
||||
grid.fit()
|
||||
return grid
|
||||
}
|
||||
|
||||
private static func makeButtonWithIcon(_ title: String, _ selector: Selector, _ symbolName: String?, _ color: NSColor? = nil) -> NSButton {
|
||||
let button = NSButton(title: title, target: nil, action: selector)
|
||||
if #available(macOS 26.0, *), let symbolName {
|
||||
button.image = NSImage(systemSymbolName: symbolName, accessibilityDescription: nil)
|
||||
button.imagePosition = .imageLeading
|
||||
if let color {
|
||||
button.image = button.image?.withSymbolConfiguration(.init(paletteColors: [color]))
|
||||
}
|
||||
}
|
||||
return button
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import Cocoa
|
||||
|
||||
class AcknowledgmentsTab {
|
||||
// going taller than this will crop the view on small screens or low scaling modes on retina displays
|
||||
static let maxTabHeight = CGFloat(450)
|
||||
|
||||
static func initTab() -> NSView {
|
||||
let textViews: [NSTextView] = ["Contributors", "Acknowledgments"].map {
|
||||
let markdownFileUrl = Bundle.main.url(forResource: $0, withExtension: "md")!
|
||||
var content = try! String(contentsOf: markdownFileUrl, encoding: .utf8)
|
||||
if content.last == "\n" {
|
||||
content.removeLast()
|
||||
}
|
||||
let attributedString = Markdown.toAttributedString(content)
|
||||
let textView = NSTextView()
|
||||
textView.textContainer!.widthTracksTextView = true
|
||||
textView.translatesAutoresizingMaskIntoConstraints = false
|
||||
textView.drawsBackground = false
|
||||
textView.isSelectable = true
|
||||
textView.isEditable = false
|
||||
textView.enabledTextCheckingTypes = 0
|
||||
textView.frame.size.width = 240
|
||||
textView.textStorage!.setAttributedString(attributedString)
|
||||
textView.layoutManager!.ensureLayout(for: textView.textContainer!)
|
||||
textView.frame = textView.layoutManager!.usedRect(for: textView.textContainer!)
|
||||
textView.fit(textView.frame.width, textView.frame.height)
|
||||
return textView
|
||||
}
|
||||
let subGrid = GridView([textViews])
|
||||
subGrid.fit()
|
||||
let scrollView = NSScrollView()
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.drawsBackground = false
|
||||
scrollView.hasVerticalScroller = true
|
||||
scrollView.hasHorizontalScroller = false
|
||||
scrollView.documentView = FlippedView(frame: .zero)
|
||||
scrollView.documentView!.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.documentView!.subviews = [subGrid]
|
||||
let totalWidth = subGrid.fittingSize.width
|
||||
scrollView.frame.size = NSSize(width: totalWidth, height: maxTabHeight)
|
||||
scrollView.contentView.frame.size = scrollView.frame.size
|
||||
scrollView.documentView!.frame.size = subGrid.fittingSize
|
||||
scrollView.fit(totalWidth, maxTabHeight)
|
||||
return scrollView
|
||||
}
|
||||
}
|
||||
+14
@@ -34,6 +34,7 @@ class MouseHoverView: NSView {
|
||||
class ClickHoverImageView: MouseHoverView {
|
||||
var infoCircle: NSView!
|
||||
var onClick: EventClosure?
|
||||
var searchableStrings = [String]()
|
||||
|
||||
init(infoCircle: NSView) {
|
||||
super.init(frame: .zero)
|
||||
@@ -92,9 +93,16 @@ class LabelAndControl: NSObject {
|
||||
}
|
||||
let view = NSStackView(views: buttonViews)
|
||||
view.orientation = .horizontal
|
||||
view.distribution = .fillEqually
|
||||
view.spacing = buttonSpacing
|
||||
view.alignment = .centerY
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
buttonViews.forEach {
|
||||
$0.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
$0.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -130,12 +138,18 @@ class LabelAndControl: NSObject {
|
||||
}
|
||||
|
||||
static func makeInfoButton(size: CGFloat = 16,
|
||||
searchableTooltipTexts: [String] = [],
|
||||
onClick: EventClosure? = nil,
|
||||
onMouseEntered: EventClosure? = nil,
|
||||
onMouseExited: EventClosure? = nil) -> ClickHoverImageView {
|
||||
let imageView = TileFontIconView(symbol: .circledInfo, size: size, color: .labelColor)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
let view = ClickHoverImageView(infoCircle: imageView)
|
||||
view.searchableStrings = Array(Set(searchableTooltipTexts.map {
|
||||
$0.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}.filter {
|
||||
!$0.isEmpty
|
||||
}))
|
||||
view.onClick = onClick
|
||||
view.onMouseEntered = onMouseEntered
|
||||
view.onMouseExited = onMouseExited
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,124 @@
|
||||
import Cocoa
|
||||
|
||||
class AboutTab {
|
||||
static func initTab() -> NSView {
|
||||
makeContentView()
|
||||
}
|
||||
|
||||
static func makeContentView(_ fitToContent: Bool = true, _ showFeedbackButton: Bool = true, _ centerHero: Bool = false) -> NSView {
|
||||
let appIcon = LightImageView()
|
||||
appIcon.translatesAutoresizingMaskIntoConstraints = false
|
||||
appIcon.updateContents(.cgImage(App.appIcon), NSSize(width: 128, height: 128))
|
||||
appIcon.fit(128, 128)
|
||||
let appText = StackView([
|
||||
BoldLabel(App.name),
|
||||
NSTextField(wrappingLabelWithString: NSLocalizedString("Version", comment: "") + " " + App.version),
|
||||
NSTextField(wrappingLabelWithString: App.licence),
|
||||
HyperlinkLabel(NSLocalizedString("Source code repository", comment: ""), App.repository),
|
||||
HyperlinkLabel(NSLocalizedString("Latest releases", comment: ""), App.repository + "/releases"),
|
||||
], .vertical)
|
||||
appText.spacing = GridView.interPadding / 2
|
||||
let rowToSeparate = 3
|
||||
appText.views[rowToSeparate].topAnchor.constraint(equalTo: appText.views[rowToSeparate - 1].bottomAnchor, constant: GridView.interPadding).isActive = true
|
||||
let appInfo = NSStackView(views: [appIcon, appText])
|
||||
appIcon.translatesAutoresizingMaskIntoConstraints = false
|
||||
appInfo.spacing = GridView.interPadding
|
||||
appInfo.alignment = .centerY
|
||||
let supportProject = makeSupportProjectButton()
|
||||
let rows = [[appInfo], [supportProject]]
|
||||
let grid = GridView(rows, 0)
|
||||
if centerHero {
|
||||
grid.cell(atColumnIndex: 0, rowIndex: 0).xPlacement = .center
|
||||
}
|
||||
let supportProjectCell = grid.cell(atColumnIndex: 0, rowIndex: showFeedbackButton ? 2 : 1)
|
||||
supportProjectCell.xPlacement = .center
|
||||
if fitToContent {
|
||||
grid.fit()
|
||||
}
|
||||
return grid
|
||||
}
|
||||
|
||||
static func makeSupportProjectButton() -> NSButton {
|
||||
let button = makeButtonWithIcon(NSLocalizedString("Support this project", comment: ""), App.supportProjectAction, "heart.fill", .red)
|
||||
styleSupportProjectButton(button)
|
||||
return button
|
||||
}
|
||||
|
||||
private static func styleSupportProjectButton(_ button: NSButton) {
|
||||
button.bezelStyle = .rounded
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.heightAnchor.constraint(equalToConstant: SettingsWindow.sidebarActionButtonHeight).isActive = true
|
||||
}
|
||||
|
||||
private static func makeButtonWithIcon(_ title: String, _ selector: Selector, _ symbolName: String?, _ color: NSColor? = nil) -> NSButton {
|
||||
let button = NSButton(title: title, target: nil, action: selector)
|
||||
if #available(macOS 26.0, *), let symbolName {
|
||||
button.image = NSImage(systemSymbolName: symbolName, accessibilityDescription: nil)
|
||||
button.imagePosition = .imageLeading
|
||||
if let color {
|
||||
button.image = button.image?.withSymbolConfiguration(.init(paletteColors: [color]))
|
||||
}
|
||||
}
|
||||
return button
|
||||
}
|
||||
}
|
||||
|
||||
class AboutWindow: NSPanel {
|
||||
private static let contentPadding = CGFloat(24)
|
||||
|
||||
var canBecomeKey_ = true
|
||||
override var canBecomeKey: Bool { canBecomeKey_ }
|
||||
|
||||
convenience init() {
|
||||
self.init(contentRect: NSRect(x: 0, y: 0, width: 600, height: 450), styleMask: [.utilityWindow, .titled, .closable], backing: .buffered, defer: false)
|
||||
setupWindow()
|
||||
setupView()
|
||||
}
|
||||
|
||||
private func setupWindow() {
|
||||
isReleasedWhenClosed = false
|
||||
hidesOnDeactivate = false
|
||||
title = String(format: NSLocalizedString("About %@", comment: ""), App.name)
|
||||
}
|
||||
|
||||
private func setupView() {
|
||||
let scrollView = NSScrollView()
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.drawsBackground = false
|
||||
scrollView.hasVerticalScroller = true
|
||||
scrollView.hasHorizontalScroller = false
|
||||
scrollView.scrollerStyle = .overlay
|
||||
let documentView = FlippedView(frame: .zero)
|
||||
documentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.documentView = documentView
|
||||
let stack = NSStackView()
|
||||
stack.orientation = .vertical
|
||||
stack.alignment = .leading
|
||||
stack.spacing = 30
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
let aboutView = AboutTab.makeContentView(false, false, true)
|
||||
let acknowledgmentsColumnWidth = frame.width - 2 * Self.contentPadding
|
||||
let acknowledgmentsView = AcknowledgmentsTab.makeContentView(columnWidth: acknowledgmentsColumnWidth, shouldFit: false, verticallyStacked: true)
|
||||
acknowledgmentsView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stack.addArrangedSubview(aboutView)
|
||||
stack.addArrangedSubview(acknowledgmentsView)
|
||||
documentView.addSubview(stack)
|
||||
contentView = scrollView
|
||||
NSLayoutConstraint.activate([
|
||||
stack.topAnchor.constraint(equalTo: documentView.topAnchor, constant: Self.contentPadding),
|
||||
stack.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: Self.contentPadding),
|
||||
stack.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -Self.contentPadding),
|
||||
stack.bottomAnchor.constraint(equalTo: documentView.bottomAnchor, constant: -Self.contentPadding),
|
||||
documentView.widthAnchor.constraint(equalTo: scrollView.contentView.widthAnchor),
|
||||
aboutView.leadingAnchor.constraint(equalTo: stack.leadingAnchor),
|
||||
aboutView.trailingAnchor.constraint(equalTo: stack.trailingAnchor),
|
||||
acknowledgmentsView.leadingAnchor.constraint(equalTo: stack.leadingAnchor),
|
||||
acknowledgmentsView.trailingAnchor.constraint(equalTo: stack.trailingAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
override func close() {
|
||||
hideAppIfLastWindowIsClosed()
|
||||
super.close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import Cocoa
|
||||
|
||||
class AcknowledgmentsTab {
|
||||
// going taller than this will crop the view on small screens or low scaling modes on retina displays
|
||||
static let maxTabHeight = CGFloat(450)
|
||||
|
||||
static func initTab() -> NSView {
|
||||
makeScrollableView(makeContentView(), maxTabHeight)
|
||||
}
|
||||
|
||||
static func makeContentView(columnWidth: CGFloat = 240, shouldFit: Bool = true, verticallyStacked: Bool = false) -> NSView {
|
||||
let sectionNames = ["Contributors", "Acknowledgments"]
|
||||
let textViews: [NSTextView] = sectionNames.map { makeTextView($0, columnWidth) }
|
||||
let content: NSView
|
||||
if verticallyStacked {
|
||||
let stack = NSStackView(views: textViews)
|
||||
stack.orientation = .vertical
|
||||
stack.alignment = .leading
|
||||
stack.spacing = GridView.interPadding
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
content = stack
|
||||
} else {
|
||||
content = GridView([textViews])
|
||||
}
|
||||
if shouldFit {
|
||||
content.fit()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
private static func makeTextView(_ sectionName: String, _ columnWidth: CGFloat) -> NSTextView {
|
||||
let markdownFileUrl = Bundle.main.url(forResource: sectionName, withExtension: "md")!
|
||||
var content = try! String(contentsOf: markdownFileUrl, encoding: .utf8)
|
||||
if content.last == "\n" {
|
||||
content.removeLast()
|
||||
}
|
||||
let attributedString = Markdown.toAttributedString(content)
|
||||
let textView = NSTextView()
|
||||
textView.textContainer!.widthTracksTextView = true
|
||||
textView.translatesAutoresizingMaskIntoConstraints = false
|
||||
textView.drawsBackground = false
|
||||
textView.isSelectable = true
|
||||
textView.isEditable = false
|
||||
textView.enabledTextCheckingTypes = 0
|
||||
textView.frame.size.width = columnWidth
|
||||
textView.textStorage!.setAttributedString(attributedString)
|
||||
textView.layoutManager!.ensureLayout(for: textView.textContainer!)
|
||||
textView.frame = textView.layoutManager!.usedRect(for: textView.textContainer!)
|
||||
textView.fit(textView.frame.width, textView.frame.height)
|
||||
return textView
|
||||
}
|
||||
|
||||
private static func makeScrollableView(_ content: NSView, _ maxHeight: CGFloat) -> NSView {
|
||||
let scrollView = NSScrollView()
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.drawsBackground = false
|
||||
scrollView.hasVerticalScroller = true
|
||||
scrollView.hasHorizontalScroller = false
|
||||
scrollView.documentView = FlippedView(frame: .zero)
|
||||
scrollView.documentView!.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.documentView!.subviews = [content]
|
||||
let totalWidth = content.fittingSize.width
|
||||
scrollView.frame.size = NSSize(width: totalWidth, height: maxHeight)
|
||||
scrollView.contentView.frame.size = scrollView.frame.size
|
||||
scrollView.documentView!.frame.size = content.fittingSize
|
||||
scrollView.fit(totalWidth, maxHeight)
|
||||
return scrollView
|
||||
}
|
||||
}
|
||||
+4
-6
@@ -3,7 +3,7 @@ import Sparkle
|
||||
|
||||
class BlacklistsTab {
|
||||
static func initTab() -> NSView {
|
||||
let blacklist = BlacklistView()
|
||||
let blacklist = BlacklistView(width: SettingsWindow.contentWidth - 2 * TableGroupView.padding)
|
||||
let add = NSSegmentedControl(images: [NSImage(named: NSImage.addTemplateName)!, NSImage(named: NSImage.removeTemplateName)!], trackingMode: .momentary, target: nil, action: nil)
|
||||
add.onAction = {
|
||||
let tableView = blacklist.documentView as! TableView
|
||||
@@ -12,7 +12,7 @@ class BlacklistsTab {
|
||||
dialog.allowsMultipleSelection = false
|
||||
dialog.allowedFileTypes = ["app"]
|
||||
dialog.canChooseDirectories = false
|
||||
dialog.beginSheetModal(for: App.app.preferencesWindow) {
|
||||
dialog.beginSheetModal(for: App.app.settingsWindow) {
|
||||
if $0 == .OK, let url = dialog.url, let bundleId = Bundle(url: url)?.bundleIdentifier {
|
||||
tableView.insertRow(bundleId)
|
||||
}
|
||||
@@ -21,11 +21,9 @@ class BlacklistsTab {
|
||||
tableView.removeSelectedRows()
|
||||
}
|
||||
}
|
||||
let table = TableGroupView(width: PreferencesWindow.width)
|
||||
let table = TableGroupView(width: SettingsWindow.contentWidth)
|
||||
_ = table.addRow(leftViews: [blacklist], secondaryViews: [add])
|
||||
let view = TableGroupSetView(originalViews: [table])
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.widthAnchor.constraint(equalToConstant: view.fittingSize.width).isActive = true
|
||||
let view = TableGroupSetView(originalViews: [table], bottomPadding: 0)
|
||||
return view
|
||||
}
|
||||
}
|
||||
+2
-6
@@ -16,8 +16,6 @@ class GeneralTab {
|
||||
])
|
||||
let language = TableGroupView.Row(leftTitle: NSLocalizedString("Language", comment: ""),
|
||||
rightViews: [LabelAndControl.makeDropdown("language", LanguagePreference.allCases, extraAction: setLanguageCallback)])
|
||||
let resetPreferences = NSButton(title: NSLocalizedString("Reset settings and restart…", comment: ""), target: self, action: #selector(GeneralTab.resetPreferences))
|
||||
if #available(macOS 11.0, *) { resetPreferences.hasDestructiveAction = true }
|
||||
for i in 0..<MenubarIconPreference.allCases.count {
|
||||
let image = NSImage.initCopy("menubar-\(i)")
|
||||
image.isTemplate = i < 2
|
||||
@@ -28,14 +26,12 @@ class GeneralTab {
|
||||
cell.arrowPosition = .arrowAtBottom
|
||||
cell.imagePosition = .imageOverlaps
|
||||
enableDraggingOffMenubarIcon(menuIconShownToggle)
|
||||
let table = TableGroupView(width: PreferencesWindow.width)
|
||||
let table = TableGroupView(width: SettingsWindow.contentWidth)
|
||||
table.addRow(startAtLogin)
|
||||
table.addRow(menubarIcon)
|
||||
table.addNewTable()
|
||||
table.addRow(language)
|
||||
let view = TableGroupSetView(originalViews: [table], toolsViews: [resetPreferences], toolsAlignment: .trailing)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.widthAnchor.constraint(equalToConstant: view.fittingSize.width).isActive = true
|
||||
let view = TableGroupSetView(originalViews: [table], bottomPadding: 0)
|
||||
return view
|
||||
}
|
||||
|
||||
+2
-5
@@ -15,14 +15,11 @@ class PoliciesTab {
|
||||
static func initTab() -> NSView {
|
||||
PoliciesTab.updatesPolicyDropdown = LabelAndControl.makeDropdown("updatePolicy", UpdatePolicyPreference.allCases)
|
||||
PoliciesTab.crashPolicyDropdown = LabelAndControl.makeDropdown("crashPolicy", CrashPolicyPreference.allCases)
|
||||
let table = TableGroupView(width: PreferencesWindow.width)
|
||||
let table = TableGroupView(width: SettingsWindow.contentWidth)
|
||||
table.addRow(leftText: NSLocalizedString("Updates policy", comment: ""), rightViews: [PoliciesTab.updatesPolicyDropdown])
|
||||
table.addRow(leftText: NSLocalizedString("Crash reports policy", comment: ""), rightViews: [PoliciesTab.crashPolicyDropdown])
|
||||
table.fit()
|
||||
let checkForUpdates = NSButton(title: NSLocalizedString("Check for updates now…", comment: ""), target: nil, action: #selector(PoliciesTab.checkForUpdatesNow))
|
||||
let view = TableGroupSetView(originalViews: [table, checkForUpdates])
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.widthAnchor.constraint(equalToConstant: view.fittingSize.width).isActive = true
|
||||
let view = TableGroupSetView(originalViews: [table, checkForUpdates], bottomPadding: 0)
|
||||
return view
|
||||
}
|
||||
|
||||
-1
@@ -14,7 +14,6 @@ class AnimationsSheet: SheetWindow {
|
||||
rightViews: LabelAndControl.makeSwitch("fadeOutAnimation"))
|
||||
table.addRow(leftText: NSLocalizedString("Fade in animation of Preview", comment: ""),
|
||||
rightViews: LabelAndControl.makeSwitch("previewFadeInAnimation"))
|
||||
table.fit()
|
||||
return table
|
||||
}
|
||||
}
|
||||
+90
-35
@@ -135,7 +135,6 @@ class ShowHideIllustratedView {
|
||||
table.onMouseExited = { event, view in
|
||||
IllustratedImageThemeView.resetImage(self.illustratedImageView, event, view)
|
||||
}
|
||||
table.fit()
|
||||
let view = TableGroupSetView(originalViews: [table], padding: 0)
|
||||
return view
|
||||
}
|
||||
@@ -158,7 +157,7 @@ class ShowHideIllustratedView {
|
||||
hideStatusIcons.supportedStyles = [.thumbnails, .titles]
|
||||
hideStatusIcons.leftViews = [TableGroupView.makeText(NSLocalizedString("Hide status icons", comment: ""))]
|
||||
hideStatusIcons.subTitle = NSLocalizedString("AltTab will show if the window is currently minimized or fullscreen with a status icon.", comment: "")
|
||||
hideStatusIcons.rightViews.append(LabelAndControl.makeInfoButton(onMouseEntered: { event, view in
|
||||
hideStatusIcons.rightViews.append(LabelAndControl.makeInfoButton(searchableTooltipTexts: [hideStatusIcons.subTitle!], onMouseEntered: { event, view in
|
||||
Popover.shared.show(event: event, positioningView: view, message: hideStatusIcons.subTitle!)
|
||||
}, onMouseExited: { event, view in
|
||||
Popover.shared.hide()
|
||||
@@ -195,7 +194,7 @@ class ShowHideIllustratedView {
|
||||
showTabsAsWindows.supportedStyles = [.thumbnails, .appIcons, .titles]
|
||||
showTabsAsWindows.leftViews = [TableGroupView.makeText(NSLocalizedString("Show standard tabs as windows", comment: ""))]
|
||||
showTabsAsWindows.subTitle = NSLocalizedString("Some apps like Finder or Preview use standard tabs which act like independent windows. Some other apps like web browsers use custom tabs which act in unique ways and are not actual windows. AltTab can't list those separately.", comment: "")
|
||||
showTabsAsWindows.rightViews.append(LabelAndControl.makeInfoButton(onMouseEntered: { event, view in
|
||||
showTabsAsWindows.rightViews.append(LabelAndControl.makeInfoButton(searchableTooltipTexts: [featureUnavailable, showTabsAsWindows.subTitle!], onMouseEntered: { event, view in
|
||||
if ShowHideIllustratedView.isDisabledOnApplications(showTabsAsWindows) {
|
||||
Popover.shared.show(event: event, positioningView: view, message: featureUnavailable)
|
||||
} else {
|
||||
@@ -210,32 +209,10 @@ class ShowHideIllustratedView {
|
||||
}
|
||||
}))
|
||||
showHideRows.append(showTabsAsWindows)
|
||||
var previewSelectedWindow = ShowHideRowInfo()
|
||||
previewSelectedWindow.rowId = "previewFocusedWindow"
|
||||
previewSelectedWindow.uncheckedImage = "hide_preview_focused_window"
|
||||
previewSelectedWindow.checkedImage = "show_preview_focused_window"
|
||||
previewSelectedWindow.supportedStyles = [.thumbnails, .appIcons, .titles]
|
||||
previewSelectedWindow.leftViews = [TableGroupView.makeText(NSLocalizedString("Preview selected window", comment: ""))]
|
||||
previewSelectedWindow.subTitle = NSLocalizedString("Preview the selected window.", comment: "")
|
||||
previewSelectedWindow.rightViews.append(LabelAndControl.makeInfoButton(onMouseEntered: { event, view in
|
||||
if ShowHideIllustratedView.isDisabledOnApplications(previewSelectedWindow) {
|
||||
Popover.shared.show(event: event, positioningView: view, message: featureUnavailable)
|
||||
} else {
|
||||
Popover.shared.show(event: event, positioningView: view, message: previewSelectedWindow.subTitle!)
|
||||
}
|
||||
}, onMouseExited: { event, view in
|
||||
Popover.shared.hide()
|
||||
}))
|
||||
previewSelectedWindow.rightViews.append(LabelAndControl.makeSwitch(previewSelectedWindow.rowId, extraAction: { sender in
|
||||
if !ShowHideIllustratedView.isDisabledOnApplications(previewSelectedWindow) {
|
||||
self.onCheckboxClicked(sender: sender, rowId: previewSelectedWindow.rowId)
|
||||
}
|
||||
}))
|
||||
showHideRows.append(previewSelectedWindow)
|
||||
}
|
||||
|
||||
static func isDisabledOnApplications(_ row: ShowHideRowInfo) -> Bool {
|
||||
let contains = ["showTabsAsWindows", "previewFocusedWindow"].contains(where: { $0 == row.rowId })
|
||||
let contains = ["showTabsAsWindows"].contains(where: { $0 == row.rowId })
|
||||
return contains && Preferences.onlyShowApplications()
|
||||
}
|
||||
|
||||
@@ -312,6 +289,10 @@ class ShowHideIllustratedView {
|
||||
class Popover: NSPopover {
|
||||
static let shared = Popover()
|
||||
private var hidingInitiated = true
|
||||
private var searchQuery = ""
|
||||
private var searchMatchRanges: ((String, String) -> [Range<Int>])?
|
||||
private var currentMessage = ""
|
||||
private weak var currentMessageLabel: NSTextField?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
@@ -328,6 +309,12 @@ class Popover: NSPopover {
|
||||
performClose(nil)
|
||||
}
|
||||
|
||||
func updateSearchContext(_ query: String, _ searchMatchRanges: @escaping (String, String) -> [Range<Int>]) {
|
||||
searchQuery = query
|
||||
self.searchMatchRanges = searchMatchRanges
|
||||
applySearchHighlightToCurrentMessage()
|
||||
}
|
||||
|
||||
func show(event: NSEvent, positioningView: NSView, message: String, extraView: NSView? = nil) {
|
||||
if !hidingInitiated { return }
|
||||
hidingInitiated = false
|
||||
@@ -348,6 +335,9 @@ class Popover: NSPopover {
|
||||
actualView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
|
||||
actualView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
|
||||
])
|
||||
currentMessage = message
|
||||
currentMessageLabel = label
|
||||
applySearchHighlightToCurrentMessage()
|
||||
contentViewController?.view = view
|
||||
// Convert the mouse location to the positioning view's coordinate system
|
||||
let locationInWindow = event.locationInWindow
|
||||
@@ -355,11 +345,45 @@ class Popover: NSPopover {
|
||||
let rect = CGRect(origin: locationInPositioningView, size: .zero)
|
||||
show(relativeTo: rect, of: positioningView, preferredEdge: .minX)
|
||||
}
|
||||
|
||||
private func applySearchHighlightToCurrentMessage() {
|
||||
guard let label = currentMessageLabel else { return }
|
||||
let attributes: [NSAttributedString.Key: Any] = [
|
||||
.font: label.font ?? NSFont.systemFont(ofSize: 12),
|
||||
.foregroundColor: label.textColor ?? NSColor.labelColor
|
||||
]
|
||||
let attributed = NSMutableAttributedString(string: currentMessage, attributes: attributes)
|
||||
guard let searchMatchRanges, !currentMessage.isEmpty else {
|
||||
label.attributedStringValue = attributed
|
||||
return
|
||||
}
|
||||
let ranges = searchMatchRanges(searchQuery, currentMessage)
|
||||
guard !ranges.isEmpty else {
|
||||
label.attributedStringValue = attributed
|
||||
return
|
||||
}
|
||||
ranges.compactMap {
|
||||
characterRangeToNSRange($0, in: currentMessage)
|
||||
}.forEach {
|
||||
attributed.addAttribute(.backgroundColor, value: NSColor.systemYellow.withAlphaComponent(0.5), range: $0)
|
||||
attributed.addAttribute(.foregroundColor, value: NSColor(calibratedWhite: 0.12, alpha: 1), range: $0)
|
||||
}
|
||||
label.attributedStringValue = attributed
|
||||
}
|
||||
|
||||
private func characterRangeToNSRange(_ range: Range<Int>, in text: String) -> NSRange? {
|
||||
if range.lowerBound < 0 || range.upperBound > text.count || range.isEmpty { return nil }
|
||||
let start = text.index(text.startIndex, offsetBy: range.lowerBound)
|
||||
let end = text.index(text.startIndex, offsetBy: range.upperBound)
|
||||
return NSRange(start..<end, in: text)
|
||||
}
|
||||
}
|
||||
|
||||
extension Popover: NSPopoverDelegate {
|
||||
func popoverWillClose(_ notification: Notification) {
|
||||
hidingInitiated = true
|
||||
currentMessage = ""
|
||||
currentMessageLabel = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,6 +392,7 @@ class AppearanceTab: NSObject {
|
||||
static var animationsButton: NSButton!
|
||||
static var customizeStyleSheet: CustomizeStyleSheet!
|
||||
static var animationsSheet: AnimationsSheet!
|
||||
static var previewSelectedWindowRowInfo: TableGroupView.RowInfo!
|
||||
|
||||
static func initTab() -> NSView {
|
||||
customizeStyleButton = NSButton(title: getCustomizeStyleButtonTitle(), target: self, action: #selector(showCustomizeStyleSheet))
|
||||
@@ -380,33 +405,63 @@ class AppearanceTab: NSObject {
|
||||
private static func makeView() -> NSStackView {
|
||||
let appearanceView = makeAppearanceView()
|
||||
let multipleScreensView = makeMultipleScreensView()
|
||||
let view = TableGroupSetView(originalViews: [appearanceView, multipleScreensView, animationsButton])
|
||||
let view = TableGroupSetView(originalViews: [appearanceView, multipleScreensView, animationsButton], titleTableGroupSpacing: 15, bottomPadding: 0)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.widthAnchor.constraint(equalToConstant: view.fittingSize.width).isActive = true
|
||||
return view
|
||||
}
|
||||
|
||||
private static func makeAppearanceView() -> NSView {
|
||||
let table = TableGroupView(title: NSLocalizedString("Appearance", comment: ""),
|
||||
subTitle: NSLocalizedString("Switch between 3 different styles. You can customize them.", comment: ""),
|
||||
width: PreferencesWindow.width)
|
||||
let table = TableGroupView(subTitle: NSLocalizedString("Switch between 3 different styles. You can customize them.", comment: ""),
|
||||
width: SettingsWindow.contentWidth)
|
||||
table.addRow(secondaryViews: [LabelAndControl.makeImageRadioButtons("appearanceStyle", AppearanceStylePreference.allCases, extraAction: { _ in
|
||||
toggleCustomizeStyleButton()
|
||||
updatePreviewSelectedWindowState()
|
||||
}, buttonSpacing: 10)], secondaryViewsAlignment: .centerX)
|
||||
table.addRow(leftText: NSLocalizedString("Size", comment: ""),
|
||||
rightViews: [LabelAndControl.makeSegmentedControl("appearanceSize", AppearanceSizePreference.allCases, segmentWidth: 100)])
|
||||
table.addRow(leftText: NSLocalizedString("Theme", comment: ""),
|
||||
rightViews: [LabelAndControl.makeSegmentedControl("appearanceTheme", AppearanceThemePreference.allCases, segmentWidth: 100)])
|
||||
addAfterKeysReleasedRow(table)
|
||||
addPreviewSelectedWindowRow(table)
|
||||
table.addRow(rightViews: customizeStyleButton)
|
||||
table.fit()
|
||||
return table
|
||||
}
|
||||
|
||||
private static func addAfterKeysReleasedRow(_ table: TableGroupView) {
|
||||
table.addRow(leftText: NSLocalizedString("After keys are released", comment: ""),
|
||||
rightViews: [LabelAndControl.makeDropdown("shortcutStyle", ShortcutStylePreference.allCases)])
|
||||
}
|
||||
|
||||
private static func addPreviewSelectedWindowRow(_ table: TableGroupView) {
|
||||
previewSelectedWindowRowInfo = table.addRow(leftText: NSLocalizedString("Preview selected window", comment: ""),
|
||||
rightViews: [LabelAndControl.makeSwitch("previewFocusedWindow")])
|
||||
updatePreviewSelectedWindowState()
|
||||
}
|
||||
|
||||
static func updatePreviewSelectedWindowState() {
|
||||
guard let rowInfo = previewSelectedWindowRowInfo else { return }
|
||||
let isEnabled = !isPreviewSelectedWindowDisabled()
|
||||
rowInfo.leftViews?.forEach { view in
|
||||
if let textField = view as? NSTextField {
|
||||
textField.textColor = isEnabled ? .textColor : .gray
|
||||
}
|
||||
}
|
||||
rowInfo.rightViews?.forEach { view in
|
||||
if let switchControl = view as? Switch {
|
||||
switchControl.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func isPreviewSelectedWindowDisabled() -> Bool {
|
||||
return Preferences.onlyShowApplications()
|
||||
}
|
||||
|
||||
private static func makeMultipleScreensView() -> NSView {
|
||||
let table = TableGroupView(title: NSLocalizedString("Multiple screens", comment: ""), width: PreferencesWindow.width)
|
||||
let table = TableGroupView(title: NSLocalizedString("Multiple screens", comment: ""), width: SettingsWindow.contentWidth)
|
||||
_ = table.addRow(leftText: NSLocalizedString("Show on", comment: ""),
|
||||
rightViews: LabelAndControl.makeDropdown("showOnScreen", ShowOnScreenPreference.allCases))
|
||||
table.fit()
|
||||
return table
|
||||
}
|
||||
|
||||
@@ -430,10 +485,10 @@ class AppearanceTab: NSObject {
|
||||
}
|
||||
|
||||
@objc static func showCustomizeStyleSheet() {
|
||||
App.app.preferencesWindow.beginSheet(customizeStyleSheet)
|
||||
App.app.settingsWindow.beginSheetWithSearchHighlight(customizeStyleSheet)
|
||||
}
|
||||
|
||||
@objc static func showAnimationsSheet() {
|
||||
App.app.preferencesWindow.beginSheet(animationsSheet)
|
||||
App.app.settingsWindow.beginSheetWithSearchHighlight(animationsSheet)
|
||||
}
|
||||
}
|
||||
+4
-5
@@ -53,15 +53,17 @@ class CustomizeStyleSheet: SheetWindow {
|
||||
}))
|
||||
titleTruncation = TableGroupView.Row(leftTitle: NSLocalizedString("Title truncation", comment: ""),
|
||||
rightViews: LabelAndControl.makeRadioButtons("titleTruncation", TitleTruncationPreference.allCases))
|
||||
let showAppWindowsInfo = LabelAndControl.makeInfoButton(onMouseEntered: { (event, view) in
|
||||
let showAppWindowsTooltip = NSLocalizedString("Show an item in the switcher for each window, or for each application. Windows will be focused, whereas applications will be activated.", comment: "")
|
||||
let showAppWindowsInfo = LabelAndControl.makeInfoButton(searchableTooltipTexts: [showAppWindowsTooltip], onMouseEntered: { (event, view) in
|
||||
Popover.shared.show(event: event, positioningView: view,
|
||||
message: NSLocalizedString("Show an item in the switcher for each window, or for each application. Windows will be focused, whereas applications will be activated.", comment: ""))
|
||||
message: showAppWindowsTooltip)
|
||||
}, onMouseExited: { (event, view) in
|
||||
Popover.shared.hide()
|
||||
})
|
||||
showAppsOrWindows = TableGroupView.Row(leftTitle: NSLocalizedString("Show in switcher", comment: ""),
|
||||
rightViews: LabelAndControl.makeRadioButtons("showAppsOrWindows", ShowAppsOrWindowsPreference.allCases, extraAction: { _ in
|
||||
self.showHideIllustratedView.setStateOnApplications()
|
||||
AppearanceTab.updatePreviewSelectedWindowState()
|
||||
self.toggleAppNamesWindowTitles()
|
||||
self.showAppsOrWindowsIllustratedImage()
|
||||
}) + [showAppWindowsInfo])
|
||||
@@ -87,7 +89,6 @@ class CustomizeStyleSheet: SheetWindow {
|
||||
table.onMouseExited = { event, view in
|
||||
IllustratedImageThemeView.resetImage(self.illustratedImageView, event, view)
|
||||
}
|
||||
table.fit()
|
||||
let view = TableGroupSetView(originalViews: [table], padding: 0)
|
||||
return view
|
||||
}
|
||||
@@ -101,7 +102,6 @@ class CustomizeStyleSheet: SheetWindow {
|
||||
table.onMouseExited = { event, view in
|
||||
IllustratedImageThemeView.resetImage(self.illustratedImageView, event, view)
|
||||
}
|
||||
table.fit()
|
||||
let view = TableGroupSetView(originalViews: [table], padding: 0)
|
||||
toggleAppNamesWindowTitles()
|
||||
return view
|
||||
@@ -111,7 +111,6 @@ class CustomizeStyleSheet: SheetWindow {
|
||||
let table = makeAppWindowTableGroupView()
|
||||
table.addNewTable()
|
||||
table.addRow(titleTruncation)
|
||||
table.fit()
|
||||
let view = TableGroupSetView(originalViews: [table], padding: 0)
|
||||
toggleAppNamesWindowTitles()
|
||||
return view
|
||||
+2
-2
@@ -17,12 +17,12 @@ class AdditionalControlsSheet: SheetWindow {
|
||||
ControlsTab.arrowKeysEnabledCallback(ControlsTab.arrowKeysCheckbox)
|
||||
ControlsTab.vimKeysEnabledCallback(ControlsTab.vimKeysCheckbox)
|
||||
let table1 = TableGroupView(title: NSLocalizedString("Additional controls", comment: ""),
|
||||
width: PreferencesWindow.width)
|
||||
width: SheetWindow.width)
|
||||
_ = table1.addRow(enableArrows)
|
||||
_ = table1.addRow(enableVimKeys)
|
||||
_ = table1.addRow(enableMouse)
|
||||
let table2 = TableGroupView(title: NSLocalizedString("Miscellaneous", comment: ""),
|
||||
width: PreferencesWindow.width)
|
||||
width: SheetWindow.width)
|
||||
_ = table2.addRow(enableCursorFollowFocus)
|
||||
_ = table2.addRow(enableTrackpadHapticFeedback)
|
||||
let view = TableGroupSetView(originalViews: [table1, table2], padding: 0)
|
||||
+9
-17
@@ -76,10 +76,7 @@ class ControlsTab {
|
||||
// trigger shortcutChanged for these shortcuts to trigger .restrictModifiers
|
||||
[holdShortcut, holdShortcut2, holdShortcut3].forEach { ControlsTab.shortcutChangedCallback($0[1] as! NSControl) }
|
||||
[nextWindowShortcut, nextWindowShortcut2, nextWindowShortcut3].forEach { ControlsTab.shortcutChangedCallback($0[0] as! NSControl) }
|
||||
let tabs = StackView(tableGroupViews, .vertical)
|
||||
tabs.translatesAutoresizingMaskIntoConstraints = false
|
||||
tabs.fit()
|
||||
let table = TableGroupView(hasHeader: true, width: PreferencesWindow.width)
|
||||
let table = TableGroupView(hasHeader: true, width: SettingsWindow.contentWidth)
|
||||
let tab = NSSegmentedControl(labels: [
|
||||
NSLocalizedString("Shortcut 1", comment: ""),
|
||||
NSLocalizedString("Shortcut 2", comment: ""),
|
||||
@@ -88,17 +85,15 @@ class ControlsTab {
|
||||
], trackingMode: .selectOne, target: self, action: #selector(switchTab(_:)))
|
||||
tab.selectedSegment = 0
|
||||
tab.segmentStyle = .automatic
|
||||
tab.widthAnchor.constraint(equalToConstant: PreferencesWindow.width).isActive = true
|
||||
tab.widthAnchor.constraint(equalToConstant: SettingsWindow.contentWidth).isActive = true
|
||||
table.addHeader(views: [tab])
|
||||
let additionalControlsButton = NSButton(title: NSLocalizedString("Additional controls…", comment: ""), target: self, action: #selector(ControlsTab.showAdditionalControlsSettings))
|
||||
let shortcutsButton = NSButton(title: NSLocalizedString("Shortcuts when active…", comment: ""), target: self, action: #selector(ControlsTab.showShortcutsSettings))
|
||||
let tools = StackView([additionalControlsButton, shortcutsButton], .horizontal)
|
||||
let view = TableGroupSetView(originalViews: [table, tab1View, tab2View, tab3View, tab6View], toolsViews: [tools], toolsAlignment: .trailing)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
let view = TableGroupSetView(originalViews: [table, tab1View, tab2View, tab3View, tab6View], toolsViews: [tools], bottomPadding: 0, toolsAlignment: .trailing)
|
||||
shortcutsWhenActiveSheet = ShortcutsWhenActiveSheet()
|
||||
additionalControlsSheet = AdditionalControlsSheet()
|
||||
ControlsTab.switchIndexTab(0)
|
||||
view.fit()
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -113,7 +108,7 @@ class ControlsTab {
|
||||
private static func gestureTab(_ index: Int) -> TableGroupView {
|
||||
let label = NSLocalizedString("You may need to disable some conflicting system gestures", comment: "")
|
||||
let button = NSButton(title: NSLocalizedString("Open Trackpad Settings…", comment: ""), target: self, action: #selector(openSystemGestures(_:)))
|
||||
let infoBtn = LabelAndControl.makeInfoButton(onMouseEntered: { event, view in
|
||||
let infoBtn = LabelAndControl.makeInfoButton(searchableTooltipTexts: [label], onMouseEntered: { event, view in
|
||||
Popover.shared.show(event: event, positioningView: view, message: label, extraView: button)
|
||||
})
|
||||
let gesture = LabelAndControl.makeDropdown("nextWindowGesture", GesturePreference.allCases)
|
||||
@@ -138,10 +133,8 @@ class ControlsTab {
|
||||
let showFullscreenWindows = LabelAndControl.makeDropdown(Preferences.indexToName("showFullscreenWindows", index), ShowHowPreference.allCases.filter { $0 != .showAtTheEnd }) // this filter is ok for serialization because the filtered value is last in the enum
|
||||
let showWindowlessApps = LabelAndControl.makeDropdown(Preferences.indexToName("showWindowlessApps", index), ShowHowPreference.allCases)
|
||||
let windowOrder = LabelAndControl.makeDropdown(Preferences.indexToName("windowOrder", index), WindowOrderPreference.allCases)
|
||||
let shortcutStyle = LabelAndControl.makeDropdown(Preferences.indexToName("shortcutStyle", index), ShortcutStylePreference.allCases)
|
||||
let table = TableGroupView(width: PreferencesWindow.width)
|
||||
let table = TableGroupView(width: SettingsWindow.contentWidth)
|
||||
table.addRow(TableGroupView.Row(leftTitle: NSLocalizedString("Trigger shortcut", comment: ""), rightViews: trigger))
|
||||
table.addRow(TableGroupView.Row(leftTitle: NSLocalizedString("After release", comment: ""), rightViews: [shortcutStyle]))
|
||||
table.addNewTable()
|
||||
table.addRow(leftViews: [TableGroupView.makeText(NSLocalizedString("Show windows from applications", comment: ""))], rightViews: [appsToShow])
|
||||
table.addRow(leftViews: [TableGroupView.makeText(NSLocalizedString("Show windows from Spaces", comment: ""))], rightViews: [spacesToShow])
|
||||
@@ -151,7 +144,6 @@ class ControlsTab {
|
||||
table.addRow(TableGroupView.Row(leftTitle: NSLocalizedString("Show fullscreen windows", comment: ""), rightViews: [showFullscreenWindows]))
|
||||
table.addRow(TableGroupView.Row(leftTitle: NSLocalizedString("Show apps with no open window", comment: ""), rightViews: [showWindowlessApps]))
|
||||
table.addRow(TableGroupView.Row(leftTitle: NSLocalizedString("Order windows by", comment: ""), rightViews: [windowOrder]))
|
||||
table.fit()
|
||||
return table
|
||||
}
|
||||
|
||||
@@ -171,11 +163,11 @@ class ControlsTab {
|
||||
}
|
||||
|
||||
@objc static func showShortcutsSettings() {
|
||||
App.app.preferencesWindow.beginSheet(shortcutsWhenActiveSheet)
|
||||
App.app.settingsWindow.beginSheetWithSearchHighlight(shortcutsWhenActiveSheet)
|
||||
}
|
||||
|
||||
@objc static func showAdditionalControlsSettings() {
|
||||
App.app.preferencesWindow.beginSheet(additionalControlsSheet)
|
||||
App.app.settingsWindow.beginSheetWithSearchHighlight(additionalControlsSheet)
|
||||
}
|
||||
|
||||
private static func addShortcut(_ triggerPhase: ShortcutTriggerPhase, _ scope: ShortcutScope, _ shortcut: Shortcut, _ controlId: String, _ index: Int?) {
|
||||
@@ -278,9 +270,9 @@ class ControlsTab {
|
||||
}
|
||||
}
|
||||
if !conflicts.isEmpty {
|
||||
// if the app is still launching (App.app.preferencesWindow == nil) and we have a conflict
|
||||
// if the app is still launching (App.app.settingsWindow == nil) and we have a conflict
|
||||
// then we don't show the user a dialog, and simply disable vim keys
|
||||
if App.app.preferencesWindow == nil || !shouldClearConflictingShortcuts(conflicts.map { $0.value }) {
|
||||
if App.app.settingsWindow == nil || !shouldClearConflictingShortcuts(conflicts.map { $0.value }) {
|
||||
return false
|
||||
}
|
||||
conflicts.forEach {
|
||||
Reference in new issue
Block a user