|
16 | 16 | import os |
17 | 17 | from typing import TypeVar, cast |
18 | 18 |
|
19 | | -from PySide6.QtCore import QDir, QFile, QModelIndex |
20 | | -from PySide6.QtGui import QAction |
| 19 | +from PySide6.QtCore import QDir, QFile, QModelIndex, Qt |
| 20 | +from PySide6.QtGui import QAction, QKeySequence, QShortcut |
21 | 21 | from PySide6.QtUiTools import QUiLoader |
22 | 22 | from PySide6.QtWidgets import ( |
23 | 23 | QApplication, |
|
30 | 30 | QMessageBox, |
31 | 31 | QPlainTextEdit, |
32 | 32 | QPushButton, |
| 33 | + QTabWidget, |
33 | 34 | QTreeView, |
34 | 35 | ) |
35 | 36 |
|
@@ -161,6 +162,20 @@ def _load_ui(self) -> None: |
161 | 162 | self._action_preferences = _require( |
162 | 163 | self.ui.findChild(QAction, "actionPreferences"), "actionPreferences" |
163 | 164 | ) |
| 165 | + # Tab widget — needed by focus-navigation shortcuts to reveal Find/Replace tab. |
| 166 | + self._tab_widget = _require( |
| 167 | + self.ui.findChild(QTabWidget, "tabWidget"), "tabWidget" |
| 168 | + ) |
| 169 | + # Find/Replace is the last tab; cache its index so shortcuts stay correct |
| 170 | + # if tabs are reordered. Falls back to -1 (no-op) if tab is not found. |
| 171 | + self._find_replace_tab_index = next( |
| 172 | + ( |
| 173 | + i |
| 174 | + for i in range(self._tab_widget.count()) |
| 175 | + if self._tab_widget.tabText(i).lower().startswith("find") |
| 176 | + ), |
| 177 | + -1, |
| 178 | + ) |
164 | 179 |
|
165 | 180 | def _setup_file_tree(self) -> None: |
166 | 181 | """Configure QFileSystemModel rooted at the user's home directory.""" |
@@ -224,8 +239,37 @@ def _connect_signals(self) -> None: |
224 | 239 | lambda _: self._update_title() |
225 | 240 | ) |
226 | 241 |
|
| 242 | + # Keyboard shortcuts not present in the .ui file. |
| 243 | + # (Ctrl+S/O/Q/Shift+S are already wired via QAction shortcuts in main_window.ui.) |
| 244 | + # ApplicationShortcut context: fires even when a child widget holds focus, |
| 245 | + # rather than requiring the window itself to be active. Necessary for focus |
| 246 | + # navigation shortcuts that must work regardless of which widget is focused. |
| 247 | + ctrl_f = QShortcut(QKeySequence("Ctrl+F"), self.ui) |
| 248 | + ctrl_f.setContext(Qt.ShortcutContext.ApplicationShortcut) |
| 249 | + ctrl_f.activated.connect(self._focus_find_edit) |
| 250 | + |
| 251 | + ctrl_h = QShortcut(QKeySequence("Ctrl+H"), self.ui) |
| 252 | + ctrl_h.setContext(Qt.ShortcutContext.ApplicationShortcut) |
| 253 | + ctrl_h.activated.connect(self._focus_replace_edit) |
| 254 | + |
| 255 | + f3 = QShortcut(QKeySequence("F3"), self.ui) |
| 256 | + f3.setContext(Qt.ShortcutContext.ApplicationShortcut) |
| 257 | + f3.activated.connect(self._on_find_clicked) |
| 258 | + |
227 | 259 | # ---------------------------------------------------------- user actions |
228 | 260 |
|
| 261 | + def _focus_find_edit(self) -> None: |
| 262 | + """Switch to Find/Replace tab and focus the find field (Ctrl+F target).""" |
| 263 | + if self._find_replace_tab_index >= 0: |
| 264 | + self._tab_widget.setCurrentIndex(self._find_replace_tab_index) |
| 265 | + self._find_edit.setFocus() |
| 266 | + |
| 267 | + def _focus_replace_edit(self) -> None: |
| 268 | + """Switch to Find/Replace tab and focus the replace field (Ctrl+H target).""" |
| 269 | + if self._find_replace_tab_index >= 0: |
| 270 | + self._tab_widget.setCurrentIndex(self._find_replace_tab_index) |
| 271 | + self._replace_edit.setFocus() |
| 272 | + |
229 | 273 | def _on_tree_item_clicked(self, index: QModelIndex) -> None: |
230 | 274 | """Load file on tree click; ignore directory clicks.""" |
231 | 275 | path = self._fs_model.filePath(index) |
|
0 commit comments