feat: add second shortcut to active the app (closes #237)

This commit is contained in:
Louis Pontoise authored and lwouis committed 2020-07-21 07:10:54 +09:00
1 parent cf7b07625a
commit a6285ba27c
8 files changed
+106 -42

No files matched your search

+14 -7
View File
@@ -11,6 +11,7 @@ class Preferences {
"fontHeight": "15",
"holdShortcut": "⌥",
"nextWindowShortcut": "⇥",
"nextWindowShortcut2": "`",
"previousWindowShortcut": "⇧",
"cancelShortcut": "⎋",
"closeWindowShortcut": "W",
@@ -20,8 +21,11 @@ class Preferences {
"arrowKeysEnabled": "true",
"mouseHoverEnabled": "true",
"showFullscreenWindows": "true",
"showFullscreenWindows2": "true",
"showMinimizedWindows": "true",
"showMinimizedWindows2": "true",
"showHiddenWindows": "true",
"showHiddenWindows2": "true",
"showTabsAsWindows": "false",
"windowDisplayDelay": "0",
"theme": "0",
@@ -29,8 +33,11 @@ class Preferences {
"titleTruncation": "0",
"alignThumbnails": "0",
"appsToShow": "0",
"appsToShow2": "1",
"spacesToShow": "0",
"spacesToShow2": "0",
"screensToShow": "0",
"screensToShow2": "0",
"fadeOutAnimation": "false",
"hideSpaceNumberLabels": "false",
"startAtLogin": "true",
@@ -57,7 +64,7 @@ class Preferences {
static var iconSize: CGFloat { defaults.cgfloat("iconSize") }
static var fontHeight: CGFloat { defaults.cgfloat("fontHeight") }
static var holdShortcut: String { defaults.string("holdShortcut") }
static var nextWindowShortcut: String { defaults.string("nextWindowShortcut") }
static var nextWindowShortcut: [String] { ["nextWindowShortcut", "nextWindowShortcut2"].map { defaults.string($0) } }
static var previousWindowShortcut: String { defaults.string("previousWindowShortcut") }
static var cancelShortcut: String { defaults.string("cancelShortcut") }
static var closeWindowShortcut: String { defaults.string("closeWindowShortcut") }
@@ -66,9 +73,9 @@ class Preferences {
static var hideShowAppShortcut: String { defaults.string("hideShowAppShortcut") }
static var arrowKeysEnabled: Bool { defaults.bool("arrowKeysEnabled") }
static var mouseHoverEnabled: Bool { defaults.bool("mouseHoverEnabled") }
static var showFullscreenWindows: Bool { defaults.bool("showFullscreenWindows") }
static var showMinimizedWindows: Bool { defaults.bool("showMinimizedWindows") }
static var showHiddenWindows: Bool { defaults.bool("showHiddenWindows") }
static var showFullscreenWindows: [Bool] { ["showFullscreenWindows", "showFullscreenWindows2"].map { defaults.bool($0) } }
static var showMinimizedWindows: [Bool] { ["showMinimizedWindows", "showMinimizedWindows2"].map { defaults.bool($0) } }
static var showHiddenWindows: [Bool] { ["showHiddenWindows", "showHiddenWindows2"].map { defaults.bool($0) } }
static var showTabsAsWindows: Bool { defaults.bool("showTabsAsWindows") }
static var windowDisplayDelay: DispatchTimeInterval { DispatchTimeInterval.milliseconds(defaults.int("windowDisplayDelay")) }
static var fadeOutAnimation: Bool { defaults.bool("fadeOutAnimation") }
@@ -84,9 +91,9 @@ class Preferences {
static var showOnScreen: ShowOnScreenPreference { defaults.macroPref("showOnScreen", ShowOnScreenPreference.allCases) }
static var titleTruncation: TitleTruncationPreference { defaults.macroPref("titleTruncation", TitleTruncationPreference.allCases) }
static var alignThumbnails: AlignThumbnailsPreference { defaults.macroPref("alignThumbnails", AlignThumbnailsPreference.allCases) }
static var appsToShow: AppsToShowPreference { defaults.macroPref("appsToShow", AppsToShowPreference.allCases) }
static var spacesToShow: SpacesToShowPreference { defaults.macroPref("spacesToShow", SpacesToShowPreference.allCases) }
static var screensToShow: ScreensToShowPreference { defaults.macroPref("screensToShow", ScreensToShowPreference.allCases) }
static var appsToShow: [AppsToShowPreference] { ["appsToShow", "appsToShow2"].map { defaults.macroPref($0, AppsToShowPreference.allCases) } }
static var spacesToShow: [SpacesToShowPreference] { ["spacesToShow", "spacesToShow2"].map { defaults.macroPref($0, SpacesToShowPreference.allCases) } }
static var screensToShow: [ScreensToShowPreference] { ["screensToShow", "screensToShow2"].map { defaults.macroPref($0, ScreensToShowPreference.allCases) } }
// derived values
static var cellBorderWidth: CGFloat { theme.themeParameters.cellBorderWidth }
+6 -6
View File
@@ -96,12 +96,12 @@ class Windows {
static func refreshIfWindowShouldBeShownToTheUser(_ window: Window, _ screen: NSScreen) {
window.shouldShowTheUser =
!(!Preferences.showFullscreenWindows && window.isFullscreen) &&
!(!Preferences.showMinimizedWindows && window.isMinimized) &&
!(!Preferences.showHiddenWindows && window.isHidden) &&
!(Preferences.appsToShow == .active && window.application.runningApplication != NSWorkspace.shared.frontmostApplication) &&
!(Preferences.spacesToShow == .active && window.spaceId != Spaces.currentSpaceId) &&
!(Preferences.screensToShow == .showingAltTab && !isOnScreen(window, screen)) &&
!(!Preferences.showFullscreenWindows[App.app.shortcutIndex] && window.isFullscreen) &&
!(!Preferences.showMinimizedWindows[App.app.shortcutIndex] && window.isMinimized) &&
!(!Preferences.showHiddenWindows[App.app.shortcutIndex] && window.isHidden) &&
!(Preferences.appsToShow[App.app.shortcutIndex] == .active && window.application.runningApplication != NSWorkspace.shared.frontmostApplication) &&
!(Preferences.spacesToShow[App.app.shortcutIndex] == .active && window.spaceId != Spaces.currentSpaceId) &&
!(Preferences.screensToShow[App.app.shortcutIndex] == .showingAltTab && !isOnScreen(window, screen)) &&
(Preferences.showTabsAsWindows || !window.isTabbed) &&
!Preferences.dontShowBlacklist.contains(window.application.runningApplication.bundleIdentifier ?? "")
}
+10 -2
View File
@@ -21,6 +21,7 @@ class App: NSApplication, NSApplicationDelegate {
var isFirstSummon = true
var appIsBeingUsed = false
var shortcutsShouldBeDisabled = false
var shortcutIndex = 0
override init() {
super.init()
@@ -33,6 +34,9 @@ class App: NSApplication, NSApplicationDelegate {
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
#if DEBUG
UserDefaults.standard.set(true, forKey: "NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints")
#endif
#if !DEBUG
PFMoveToApplicationsFolderIfNecessary()
#endif
@@ -53,6 +57,9 @@ class App: NSApplication, NSApplicationDelegate {
// TODO: undeterministic; events in the queue may still be processing; good enough for now
DispatchQueue.main.async { () -> () in Windows.sortByLevel() }
self.preloadWindows()
#if DEBUG
self.preferencesWindowController.show()
#endif
}
}
@@ -164,7 +171,7 @@ class App: NSApplication, NSApplicationDelegate {
@objc func showUi() {
appIsBeingUsed = true
DispatchQueue.main.async { () -> () in self.showUiOrCycleSelection() }
DispatchQueue.main.async { () -> () in self.showUiOrCycleSelection(0) }
}
func cycleSelection(_ direction: Direction) {
@@ -215,7 +222,7 @@ class App: NSApplication, NSApplicationDelegate {
}
}
func showUiOrCycleSelection() {
func showUiOrCycleSelection(_ shortcutIndex: Int) {
debugPrint("showUiOrCycleSelection")
if isFirstSummon {
debugPrint("showUiOrCycleSelection: isFirstSummon")
@@ -231,6 +238,7 @@ class App: NSApplication, NSApplicationDelegate {
Spaces.idsAndIndexes = Spaces.allIdsAndIndexes()
Windows.updateSpaces()
let screen = Screen.preferred()
self.shortcutIndex = shortcutIndex
Windows.refreshWhichWindowsToShowTheUser(screen)
if (!Windows.list.contains { $0.shouldShowTheUser }) { hideUi(); return }
Windows.updateFocusedWindowIndex(0)
@@ -6,6 +6,7 @@ class CustomRecorderControl: RecorderControl, RecorderControlDelegate {
convenience init(_ shortcutString: String, _ clearable: Bool) {
self.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
self.clearable = clearable
delegate = self
allowsEscapeToCancelRecording = false
+9 -6
View File
@@ -1,15 +1,18 @@
import Cocoa
class StackView: NSStackView {
convenience init(_ views: [NSView], _ orientation: NSUserInterfaceLayoutOrientation = .horizontal, top: CGFloat = 0, right: CGFloat = 0, bottom: CGFloat = 0, left: CGFloat = 0) {
convenience init(_ views: [NSView], _ orientation: NSUserInterfaceLayoutOrientation = .horizontal, _ shouldFit: Bool = true, top: CGFloat = 0, right: CGFloat = 0, bottom: CGFloat = 0, left: CGFloat = 0) {
self.init(views: views)
translatesAutoresizingMaskIntoConstraints = false
edgeInsets = NSEdgeInsets(top: top, left: left, bottom: bottom, right: right)
alignment = orientation == .horizontal ? .firstBaseline : .leading
// workaround: for some reason, horizontal stackviews with a RecorderControl have extra fittingSize.height
if orientation == .horizontal && (views.contains { $0 is CustomRecorderControl }) {
fit(fittingSize.width, fittingSize.height - 7)
} else {
fit()
if shouldFit {
// workaround: for some reason, horizontal stackviews with a RecorderControl have extra fittingSize.height
if orientation == .horizontal && (views.contains { $0 is CustomRecorderControl }) {
fit(fittingSize.width, fittingSize.height - 7)
} else {
fit()
}
}
}
}
@@ -1,10 +1,18 @@
import Cocoa
class TextField: NSTextField {
// NSTextField has 2px insets left and right by default; we remove those
let insets = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
override var alignmentRectInsets: NSEdgeInsets { insets }
convenience init(_ labelWithString: String) {
self.init(labelWithString: labelWithString)
translatesAutoresizingMaskIntoConstraints = false
}
convenience init(_ attributedString: NSAttributedString) {
self.init(labelWithAttributedString: TextField.forceLeftToRight(attributedString))
translatesAutoresizingMaskIntoConstraints = false
}
// we know the content to display should be left-to-right, so we force it to avoid displayed it right-to-left
@@ -16,7 +24,4 @@ class TextField: NSTextField {
forced.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: forced.length))
return forced
}
// NSTextField has 2px insets left and right by default; we remove those
override var alignmentRectInsets: NSEdgeInsets { insets }
}
@@ -17,6 +17,7 @@ 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 = (Preferences.getString(rawName)! as NSString).boolValue ? .on : .off
let views = makeLabelWithProvidedControl(labelText, rawName, checkbox, labelPosition: labelPosition, extraAction: extraAction)
return views
@@ -39,6 +40,7 @@ class LabelAndControl: NSObject {
static func makeDropDown(_ rawName: String, _ macroPreferences: [MacroPreference]) -> NSPopUpButton {
let popUp = NSPopUpButton()
popUp.translatesAutoresizingMaskIntoConstraints = false
popUp.addItems(withTitles: macroPreferences.map { $0.localizedString })
popUp.selectItem(at: Int(Preferences.getString(rawName)!)!)
return popUp
@@ -97,7 +99,7 @@ class LabelAndControl: NSObject {
}
static func makeLabel(_ labelText: String, _ labelPosition: LabelPosition = .leftWithoutSeparator) -> NSTextField {
let label = NSTextField(wrappingLabelWithString: labelText)
let label = TextField(labelText)
label.isSelectable = false
label.usesSingleLineMode = true
label.alignment = .right
@@ -150,3 +152,14 @@ class LabelAndControl: NSObject {
enum ControlIdentifierDiscriminator: String {
case SUFFIX = "_suffix"
}
class TabView: NSTabView {
// without the left/right value, the whole view is shifted to the right for some reason
// let insets = NSEdgeInsets(top: 0, left: 7, bottom: 0, right: 7)
// override var alignmentRectInsets: NSEdgeInsets { get { insets } }
convenience init() {
self.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
}
}
+44 -17
View File
@@ -11,7 +11,8 @@ class GeneralTab: NSViewController, PreferencePane {
static var shortcutsDependentOnHoldShortcut = [NSControl]()
static var shortcutsActionsBlocks = [
"holdShortcut": { App.app.focusTarget() },
"nextWindowShortcut": { App.app.showUiOrCycleSelection() },
"nextWindowShortcut": { App.app.showUiOrCycleSelection(0) },
"nextWindowShortcut2": { App.app.showUiOrCycleSelection(1) },
"previousWindowShortcut": { App.app.cycleSelection(.trailing) },
"→": { App.app.cycleSelection(.right) },
"←": { App.app.cycleSelection(.left) },
@@ -29,7 +30,6 @@ class GeneralTab: NSViewController, PreferencePane {
let hideMenubarIcon = LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide menubar icon:", comment: ""), "hideMenubarIcon", extraAction: hideMenubarIconCallback)
var holdShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Hold", comment: ""), "holdShortcut", Preferences.holdShortcut, false, labelPosition: .leftWithoutSeparator)
holdShortcut.append(LabelAndControl.makeLabel(NSLocalizedString("and press:", comment: "")))
let nextWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Select next window", comment: ""), "nextWindowShortcut", Preferences.nextWindowShortcut, labelPosition: .right)
let previousWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Select previous window", comment: ""), "previousWindowShortcut", Preferences.previousWindowShortcut, labelPosition: .right)
let cancelShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Cancel and hide", comment: ""), "cancelShortcut", Preferences.cancelShortcut, labelPosition: .right)
let closeWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Close window", comment: ""), "closeWindowShortcut", Preferences.closeWindowShortcut, labelPosition: .right)
@@ -41,36 +41,46 @@ class GeneralTab: NSViewController, PreferencePane {
let holdAndPress = StackView(holdShortcut)
let checkboxesExplanations = LabelAndControl.makeLabel(NSLocalizedString("Select windows using:", comment: ""))
let checkboxes = StackView([StackView(enableArrows), StackView(enableMouse)], .vertical)
let appsToShow = dropdown("appsToShow", AppsToShowPreference.allCases)
let spacesToShow = dropdown("spacesToShow", SpacesToShowPreference.allCases)
let screensToShow = dropdown("screensToShow", ScreensToShowPreference.allCases)
let showFullscreenWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Fullscreen", comment: ""), "showFullscreenWindows", labelPosition: .right))
let showMinimizedWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Minimized", comment: ""), "showMinimizedWindows", labelPosition: .right))
let showHiddenWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hidden", comment: ""), "showHiddenWindows", labelPosition: .right))
let shortcuts = StackView([previousWindowShortcut, cancelShortcut, closeWindowShortcut, minDeminWindowShortcut, quitAppShortcut, hideShowAppShortcut].map { (view: [NSView]) in StackView(view) }, .vertical)
let thenRelease = LabelAndControl.makeLabel(NSLocalizedString("then release:", comment: ""))
let orPress = LabelAndControl.makeLabel(NSLocalizedString("or press:", comment: ""))
let focusSelectedWindow = LabelAndControl.makeLabel(NSLocalizedString("Focus selected window", comment: ""))
let toShowDropdowns = StackView([appsToShow, spacesToShow, screensToShow], .vertical)
let toShowCheckboxes = StackView([showMinimizedWindows, showHiddenWindows, showFullscreenWindows], .vertical)
let toShowExplanations = LabelAndControl.makeLabel(NSLocalizedString("Show the following windows:", comment: ""))
let toShow = StackView([toShowDropdowns, toShowCheckboxes], .vertical)
let tab1 = NSTabViewItem(identifier: 1)
tab1.label = "1"
let (nextWindowShortcut, tab1View) = toShowSection("")
tab1.view = tab1View
let tab2 = NSTabViewItem(identifier: 2)
tab2.label = "2"
let (nextWindowShortcut2, tab2View) = toShowSection("2")
tab2.view = tab2View
let tabView = TabView()
tabView.addTabViewItem(tab1)
tabView.addTabViewItem(tab2)
tabView.tabPosition = .right
tabView.heightAnchor.constraint(equalToConstant: tabView.fittingSize.height - 8).isActive = true
let vertical1 = StackView([toShowExplanations, holdAndPress], .vertical, false, top: 5, bottom: 3)
vertical1.alignment = .trailing
vertical1.distribution = .equalCentering
vertical1.heightAnchor.constraint(equalToConstant: tabView.fittingSize.height - 14).isActive = true
let grid = GridView([
startAtLogin,
hideMenubarIcon,
[holdAndPress, StackView(nextWindowShortcut)],
[vertical1, tabView],
[thenRelease, focusSelectedWindow],
[orPress, shortcuts],
[checkboxesExplanations, checkboxes],
[toShowExplanations, toShow],
])
grid.column(at: 0).xPlacement = .trailing
[2, 5, 6].forEach { grid.row(at: $0).topPadding = GridView.interPadding }
grid.row(at: 2).rowAlignment = .none
[2, 5].forEach { grid.row(at: $0).topPadding = GridView.interPadding }
grid.fit()
GeneralTab.shortcutsDependentOnHoldShortcut.append(contentsOf: [enableArrows[0] as! NSControl] +
[nextWindowShortcut, previousWindowShortcut, cancelShortcut, closeWindowShortcut, minDeminWindowShortcut, quitAppShortcut, hideShowAppShortcut].map { $0[0] as! NSControl })
[nextWindowShortcut, nextWindowShortcut2, previousWindowShortcut, cancelShortcut,
closeWindowShortcut, minDeminWindowShortcut, quitAppShortcut, hideShowAppShortcut].map { $0[0] as! NSControl })
GeneralTab.arrowKeysEnabledCallback(enableArrows[0] as! NSControl)
startAtLoginCallback(startAtLogin[1] as! NSControl)
hideMenubarIconCallback(hideMenubarIcon[1] as! NSControl)
@@ -78,6 +88,23 @@ class GeneralTab: NSViewController, PreferencePane {
view = grid
}
private func toShowSection(_ postfix: String) -> ([NSView], NSView) {
let appsToShow = dropdown("appsToShow" + postfix, AppsToShowPreference.allCases)
let spacesToShow = dropdown("spacesToShow" + postfix, SpacesToShowPreference.allCases)
let screensToShow = dropdown("screensToShow" + postfix, ScreensToShowPreference.allCases)
let showMinimizedWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Minimized", comment: ""), "showMinimizedWindows" + postfix, labelPosition: .right))
let showHiddenWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hidden", comment: ""), "showHiddenWindows" + postfix, labelPosition: .right))
let showFullscreenWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Fullscreen", comment: ""), "showFullscreenWindows" + postfix, labelPosition: .right))
let nextWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Select next window", comment: ""), "nextWindowShortcut" + postfix, Preferences.nextWindowShortcut[postfix == "" ? 0 : 1], labelPosition: .right)
let toShowDropdowns = StackView([appsToShow, spacesToShow, screensToShow, showMinimizedWindows, showHiddenWindows, showFullscreenWindows], .vertical, false)
toShowDropdowns.spacing = 8
toShowDropdowns.fit()
let tab = StackView([toShowDropdowns, StackView(nextWindowShortcut)], .vertical, false)
tab.spacing = GridView.padding
tab.fit()
return (nextWindowShortcut, tab)
}
private func dropdown(_ rawName: String, _ macroPreferences: [MacroPreference]) -> NSControl {
let dropdown = LabelAndControl.makeDropDown(rawName, macroPreferences)
return LabelAndControl.setupControl(dropdown, rawName)
@@ -86,7 +113,7 @@ class GeneralTab: NSViewController, PreferencePane {
private static func addShortcut(_ type: KeyEventType, _ shortcut: Shortcut, _ controlId: String) {
removeShortcutIfExists(controlId, type) // remove the previous shortcut
shortcutActions[controlId] = ShortcutAction(shortcut: shortcut, actionHandler: { _ in
let isShortcutInitiatingTheApp = controlId == "nextWindowShortcut"
let isShortcutInitiatingTheApp = ["nextWindowShortcut", "nextWindowShortcut2"].contains(controlId)
if isShortcutInitiatingTheApp {
App.app.appIsBeingUsed = true
}
@@ -158,7 +185,7 @@ class GeneralTab: NSViewController, PreferencePane {
let _ = LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst.takeRetainedValue(), nil, nil, itemUrl, nil, nil).takeRetainedValue()
}
}
private func hideMenubarIconCallback(_ sender: NSControl) {
App.statusItem.isVisible = (sender as! NSButton).state == .off
}