diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e6d04ed4..99c066f2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - DuckDB VARIANT columns now show their value as text instead of an empty cell. - A new database group now appears in the connection list right away instead of only after restarting the app. (#1704) - The SQL formatter keeps nested indentation for UNION, UNION ALL, INTERSECT, and EXCEPT inside a derived table or CTE, and puts the closing parenthesis of a subquery on its own line instead of collapsing it onto the last SELECT. (#1698) +- Toolbar button tooltips now show each action's real keyboard shortcut and follow your custom bindings, instead of a fixed value. The Switch Connection tooltip showed the wrong shortcut. (#1694) ## [0.51.1] - 2026-06-16 diff --git a/TablePro/Core/Services/Infrastructure/MainWindowToolbar+Buttons.swift b/TablePro/Core/Services/Infrastructure/MainWindowToolbar+Buttons.swift index b063bbb6b..9329c5709 100644 --- a/TablePro/Core/Services/Infrastructure/MainWindowToolbar+Buttons.swift +++ b/TablePro/Core/Services/Infrastructure/MainWindowToolbar+Buttons.swift @@ -17,7 +17,7 @@ struct ConnectionToolbarButton: View { } label: { Label("Connection", systemImage: "network") } - .help(String(localized: "Switch Connection (⌘⌥C)")) + .help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Switch Connection"), for: .switchConnection)) .popover(isPresented: $coordinator.isConnectionSwitcherShown, arrowEdge: .bottom) { ConnectionSwitcherPopover() } @@ -37,7 +37,7 @@ struct DatabaseToolbarButton: View { } label: { Label(containerName, systemImage: "cylinder") } - .help(String(format: String(localized: "Open %@ (⌘K)"), containerName)) + .help(AppSettingsManager.shared.keyboard.shortcutHint(String(format: String(localized: "Open %@"), containerName), for: .openDatabase)) .disabled( state.connectionState != .connected || PluginManager.shared.connectionMode(for: state.databaseType) == .fileBased @@ -89,7 +89,7 @@ struct RefreshToolbarButton: View { } label: { Label("Refresh", systemImage: "arrow.clockwise") } - .help(String(localized: "Refresh (⌘R)")) + .help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Refresh"), for: .refresh)) .disabled(state.connectionState != .connected) } } @@ -104,7 +104,7 @@ struct SaveChangesToolbarButton: View { } label: { Label("Save Changes", systemImage: "checkmark.circle.fill") } - .help(String(localized: "Save Changes (⌘S)")) + .help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Save Changes"), for: .saveChanges)) .disabled( !state.hasPendingChanges || state.connectionState != .connected @@ -124,7 +124,7 @@ struct QuickSwitcherToolbarButton: View { } label: { Label("Quick Switcher", systemImage: "magnifyingglass") } - .help(String(localized: "Quick Switcher (⇧⌘O)")) + .help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Quick Switcher"), for: .quickSwitcher)) .disabled(state.connectionState != .connected) } } @@ -139,7 +139,7 @@ struct NewTabToolbarButton: View { } label: { Label("New Tab", systemImage: "plus.rectangle") } - .help(String(localized: "New Query Tab (⌘T)")) + .help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "New Query Tab"), for: .newTab)) .disabled(state.connectionState != .connected) } } @@ -149,13 +149,14 @@ struct PreviewSQLToolbarButton: View { var body: some View { let state = coordinator.toolbarState + let langName = PluginManager.shared.queryLanguageName(for: state.databaseType) + let previewLabel = String(format: String(localized: "Preview %@"), langName) Button { coordinator.commandActions?.previewSQL() } label: { - let langName = PluginManager.shared.queryLanguageName(for: state.databaseType) - Label(String(format: String(localized: "Preview %@"), langName), systemImage: "eye") + Label(previewLabel, systemImage: "eye") } - .help(String(format: String(localized: "Preview %@ (⌘⇧P)"), PluginManager.shared.queryLanguageName(for: state.databaseType))) + .help(AppSettingsManager.shared.keyboard.shortcutHint(previewLabel, for: .previewSQL)) .disabled(!state.hasDataPendingChanges || state.connectionState != .connected) } } @@ -175,7 +176,7 @@ struct ResultsToolbarButton: View { : "rectangle.inset.filled" ) } - .help(String(localized: "Toggle Results (⌘⌥R)")) + .help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Toggle Results"), for: .toggleResults)) .disabled(state.connectionState != .connected || state.isTableTab) } } @@ -205,7 +206,7 @@ struct HistoryToolbarButton: View { } label: { Label("History", systemImage: "clock") } - .help(String(localized: "Toggle Query History (⌘Y)")) + .help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Toggle Query History"), for: .toggleHistory)) } } @@ -219,7 +220,7 @@ struct ExportToolbarButton: View { } label: { Label("Export", systemImage: "square.and.arrow.up") } - .help(String(localized: "Export Data (⌘⇧E)")) + .help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Export Data"), for: .export)) .disabled(state.connectionState != .connected) } } @@ -238,7 +239,7 @@ struct ImportToolbarButton: View { } label: { Label("Import", systemImage: "square.and.arrow.down") } - .help(String(localized: "Import Data (⌘⇧I)")) + .help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Import Data"), for: .importData)) .disabled(isDisabled || formats.isEmpty) } else { Menu { @@ -250,7 +251,7 @@ struct ImportToolbarButton: View { } label: { Label("Import", systemImage: "square.and.arrow.down") } - .help(String(localized: "Import Data (⌘⇧I)")) + .help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Import Data"), for: .importData)) .disabled(isDisabled) } } diff --git a/TablePro/Models/UI/KeyboardShortcutModels.swift b/TablePro/Models/UI/KeyboardShortcutModels.swift index 22d406df9..304a61232 100644 --- a/TablePro/Models/UI/KeyboardShortcutModels.swift +++ b/TablePro/Models/UI/KeyboardShortcutModels.swift @@ -375,6 +375,15 @@ struct KeyboardSettings: Codable, Equatable { return KeyboardShortcut(equivalent, modifiers: key.eventModifiers) } + /// A tooltip/help string that appends the action's resolved shortcut, e.g. + /// "Switch Connection (⌃⌘C)". Returns just the label when the shortcut is + /// cleared or unset. Reflects user overrides because it resolves through + /// `shortcut(for:)`. + func shortcutHint(_ label: String, for action: ShortcutAction) -> String { + guard let key = shortcut(for: action), !key.isCleared else { return label } + return "\(label) (\(key.displayString))" + } + // MARK: - Default Shortcuts /// Default shortcuts, applied when the user has no override. An action absent diff --git a/TablePro/Views/Components/PaginationControlsView.swift b/TablePro/Views/Components/PaginationControlsView.swift index 169b30cf8..de975bf86 100644 --- a/TablePro/Views/Components/PaginationControlsView.swift +++ b/TablePro/Views/Components/PaginationControlsView.swift @@ -139,10 +139,7 @@ struct PaginationControlsView: View { } private func helpText(_ label: String, for shortcut: ShortcutAction) -> String { - guard let combo = AppSettingsManager.shared.keyboard.shortcut(for: shortcut), !combo.isCleared else { - return label - } - return "\(label) (\(combo.displayString))" + AppSettingsManager.shared.keyboard.shortcutHint(label, for: shortcut) } private var pageIndicator: some View { diff --git a/TablePro/Views/Editor/QueryEditorView.swift b/TablePro/Views/Editor/QueryEditorView.swift index e6436beea..f9d67ecfa 100644 --- a/TablePro/Views/Editor/QueryEditorView.swift +++ b/TablePro/Views/Editor/QueryEditorView.swift @@ -142,10 +142,7 @@ struct QueryEditorView: View { // MARK: - Helpers private func shortcutHint(_ label: String, for action: ShortcutAction) -> String { - guard let combo = AppSettingsManager.shared.keyboard.shortcut(for: action), !combo.isCleared else { - return label - } - return "\(label) (\(combo.displayString))" + AppSettingsManager.shared.keyboard.shortcutHint(label, for: action) } @ViewBuilder diff --git a/TablePro/Views/Main/Child/MainStatusBarView.swift b/TablePro/Views/Main/Child/MainStatusBarView.swift index bcd976109..b6e17f14c 100644 --- a/TablePro/Views/Main/Child/MainStatusBarView.swift +++ b/TablePro/Views/Main/Child/MainStatusBarView.swift @@ -61,11 +61,7 @@ struct MainStatusBarView: View { } private func helpText(_ label: String, shortcut action: ShortcutAction) -> String { - guard let combo = AppSettingsManager.shared.keyboard.shortcut(for: action), - !combo.isCleared else { - return label - } - return "\(label) (\(combo.displayString))" + AppSettingsManager.shared.keyboard.shortcutHint(label, for: action) } private var columnsAccessibilityLabel: String { diff --git a/TableProTests/Models/KeyboardShortcutTests.swift b/TableProTests/Models/KeyboardShortcutTests.swift index dd16ba4aa..160fa14cd 100644 --- a/TableProTests/Models/KeyboardShortcutTests.swift +++ b/TableProTests/Models/KeyboardShortcutTests.swift @@ -251,3 +251,40 @@ struct KeyboardSettingsMigrationTests { #expect(settings.shortcut(for: .executeQuery)?.isCleared == true) } } + +@Suite("Shortcut hint") +struct ShortcutHintTests { + @Test("Switch Connection default hint shows Control+Command+C") + func switchConnectionDefaultHint() { + let hint = KeyboardSettings.default.shortcutHint( + String(localized: "Switch Connection"), + for: .switchConnection + ) + #expect(hint == "Switch Connection (⌃⌘C)") + } + + @Test("Hint reflects a user override") + func hintReflectsOverride() { + var settings = KeyboardSettings.default + settings.setShortcut(.character("j", command: true), for: .switchConnection) + let hint = settings.shortcutHint(String(localized: "Switch Connection"), for: .switchConnection) + #expect(hint == "Switch Connection (⌘J)") + } + + @Test("Cleared shortcut shows label only") + func clearedShortcutShowsLabelOnly() { + var settings = KeyboardSettings.default + settings.clearShortcut(for: .switchConnection) + let hint = settings.shortcutHint(String(localized: "Switch Connection"), for: .switchConnection) + #expect(hint == "Switch Connection") + } + + @Test("Action without a default shows label only") + func unsetShortcutShowsLabelOnly() { + let hint = KeyboardSettings.default.shortcutHint( + String(localized: "Manage Connections"), + for: .manageConnections + ) + #expect(hint == "Manage Connections") + } +}