1010from typing import TYPE_CHECKING , Optional
1111
1212from PySide6 .QtCore import Qt , QTimer , Signal
13+ from PySide6 .QtGui import QKeySequence
1314from PySide6 .QtWidgets import (
1415 QCheckBox ,
1516 QDialog ,
@@ -62,27 +63,32 @@ def _setup_ui(self):
6263 grid .setSpacing (6 )
6364
6465 # Suchen
65- self .search_label = QLabel ("Suchen nach:" )
66+ self .search_label = QLabel ("&Suchen nach:" )
67+ self .search_label .setToolTip ("Suchfeld für Text oder regulären Ausdruck (Alt+S)" )
6668 self .search_input = QLineEdit ()
6769 self .search_input .setObjectName ("find_search_input" )
6870 self .search_input .setPlaceholderText ("Suchbegriff oder Regex..." )
6971 self .search_input .setClearButtonEnabled (True )
70- self .search_input .setToolTip ("Suchbegriff oder regulären Ausdruck eingeben (Enter für nächsten Treffer)" )
72+ self .search_input .setToolTip ("Suchbegriff oder regulären Ausdruck eingeben (Enter für nächsten, Shift+Enter für vorherigen Treffer)" )
7173 self .search_input .setAccessibleName ("Suchtext" )
7274 self .search_input .setAccessibleDescription ("Eingabefeld für den zu suchenden Text oder regulären Ausdruck" )
75+ self .search_label .setBuddy (self .search_input )
76+ self .search_input .installEventFilter (self )
7377 self .search_input .textChanged .connect (self ._schedule_update )
7478 self .search_input .returnPressed .connect (self .find_next )
7579
7680 self .btn_prev = QPushButton ("↑ Vorheriger" )
7781 self .btn_prev .setObjectName ("find_prev_btn" )
7882 self .btn_prev .setToolTip ("Vorherigen Treffer anspringen (Shift+Enter / Shift+F3)" )
7983 self .btn_prev .setAccessibleName ("Vorheriger Treffer" )
84+ self .btn_prev .setAccessibleDescription ("Springt zum vorherigen Treffer im Dokument (Shift+Enter oder Shift+F3)" )
8085 self .btn_prev .clicked .connect (self .find_prev )
8186
8287 self .btn_next = QPushButton ("↓ Nächster" )
8388 self .btn_next .setObjectName ("find_next_btn" )
8489 self .btn_next .setToolTip ("Nächsten Treffer anspringen (Enter / F3)" )
8590 self .btn_next .setAccessibleName ("Nächster Treffer" )
91+ self .btn_next .setAccessibleDescription ("Springt zum nächsten Treffer im Dokument (Enter oder F3)" )
8692 self .btn_next .clicked .connect (self .find_next )
8793
8894 grid .addWidget (self .search_label , 0 , 0 )
@@ -91,27 +97,33 @@ def _setup_ui(self):
9197 grid .addWidget (self .btn_next , 0 , 3 )
9298
9399 # Ersetzen
94- self .replace_label = QLabel ("Ersetzen durch:" )
100+ self .replace_label = QLabel ("Erset&zen durch:" )
101+ self .replace_label .setToolTip ("Eingabefeld für den Ersetzungstext (Alt+Z)" )
95102 self .replace_input = QLineEdit ()
96103 self .replace_input .setObjectName ("find_replace_input" )
97104 self .replace_input .setPlaceholderText ("Ersetzungstext (unterstützt Regex-Gruppen \\ 1, \\ 2)..." )
98105 self .replace_input .setClearButtonEnabled (True )
99106 self .replace_input .setToolTip ("Ersetzungstext eingeben (bei Regex: \\ 1, \\ 2 für Capture-Groups)" )
100107 self .replace_input .setAccessibleName ("Ersetzungstext" )
101108 self .replace_input .setAccessibleDescription ("Eingabefeld für den Text, durch den Treffer ersetzt werden" )
109+ self .replace_label .setBuddy (self .replace_input )
102110 self .replace_input .textChanged .connect (self ._schedule_update )
103111 self .replace_input .returnPressed .connect (self .replace_current )
104112
105113 self .btn_replace = QPushButton ("Ersetzen" )
106114 self .btn_replace .setObjectName ("replace_btn" )
115+ self .btn_replace .setShortcut (QKeySequence ("Alt+R" ))
107116 self .btn_replace .setToolTip ("Aktuellen Treffer ersetzen (Alt+R)" )
108117 self .btn_replace .setAccessibleName ("Treffer ersetzen" )
118+ self .btn_replace .setAccessibleDescription ("Ersetzt das aktuell ausgewählte Vorkommen durch den Ersetzungstext (Alt+R)" )
109119 self .btn_replace .clicked .connect (self .replace_current )
110120
111121 self .btn_replace_all = QPushButton ("Alle ersetzen" )
112122 self .btn_replace_all .setObjectName ("replace_all_btn" )
123+ self .btn_replace_all .setShortcut (QKeySequence ("Alt+A" ))
113124 self .btn_replace_all .setToolTip ("Alle Treffer im Dokument auf einmal ersetzen (Alt+A)" )
114125 self .btn_replace_all .setAccessibleName ("Alle Treffer ersetzen" )
126+ self .btn_replace_all .setAccessibleDescription ("Ersetzt alle Vorkommen im gesamten Dokument durch den Ersetzungstext (Alt+A)" )
115127 self .btn_replace_all .clicked .connect (self .replace_all )
116128
117129 grid .addWidget (self .replace_label , 1 , 0 )
@@ -127,22 +139,25 @@ def _setup_ui(self):
127139
128140 self .cb_case = QCheckBox ("Groß-/&Kleinschreibung" )
129141 self .cb_case .setObjectName ("find_case_checkbox" )
130- self .cb_case .setToolTip ("Groß- und Kleinschreibung exakt beachten" )
142+ self .cb_case .setToolTip ("Groß- und Kleinschreibung exakt beachten (Alt+K) " )
131143 self .cb_case .setAccessibleName ("Groß- und Kleinschreibung beachten" )
144+ self .cb_case .setAccessibleDescription ("Unterscheidet bei der Suche strikt zwischen Groß- und Kleinschreibung (Alt+K)" )
132145 self .cb_case .stateChanged .connect (self ._schedule_update )
133146 opt_layout .addWidget (self .cb_case )
134147
135148 self .cb_words = QCheckBox ("Ganzes &Wort" )
136149 self .cb_words .setObjectName ("find_words_checkbox" )
137- self .cb_words .setToolTip ("Nur ganze Wörter suchen (Wortgrenzen beachten)" )
150+ self .cb_words .setToolTip ("Nur ganze Wörter suchen (Wortgrenzen beachten, Alt+W )" )
138151 self .cb_words .setAccessibleName ("Nur ganze Wörter suchen" )
152+ self .cb_words .setAccessibleDescription ("Findet nur Vorkommen, die als eigenständige ganze Wörter vorliegen (Alt+W)" )
139153 self .cb_words .stateChanged .connect (self ._schedule_update )
140154 opt_layout .addWidget (self .cb_words )
141155
142156 self .cb_regex = QCheckBox ("Re&gex" )
143157 self .cb_regex .setObjectName ("find_regex_checkbox" )
144- self .cb_regex .setToolTip ("Suchbegriff als regulären Python-Ausdruck interpretieren" )
158+ self .cb_regex .setToolTip ("Suchbegriff als regulären Python-Ausdruck interpretieren (Alt+G) " )
145159 self .cb_regex .setAccessibleName ("Regulären Ausdruck verwenden" )
160+ self .cb_regex .setAccessibleDescription ("Interpretiert den Suchbegriff als regulären Python-Ausdruck mit Gruppen-Unterstützung (Alt+G)" )
146161 self .cb_regex .stateChanged .connect (self ._schedule_update )
147162 opt_layout .addWidget (self .cb_regex )
148163
@@ -154,15 +169,18 @@ def _setup_ui(self):
154169 self .status_label = QLabel ("Bereit" )
155170 self .status_label .setObjectName ("find_status_label" )
156171 self .status_label .setAccessibleName ("Suchstatus" )
172+ self .status_label .setAccessibleDescription ("Zeigt die Anzahl der gefundenen Treffer oder Syntaxfehler an" )
157173 status_bar_layout .addWidget (self .status_label )
158174 status_bar_layout .addStretch ()
159175
160176 self .btn_toggle_preview = QPushButton ("Voransicht" )
161177 self .btn_toggle_preview .setObjectName ("toggle_preview_btn" )
162178 self .btn_toggle_preview .setCheckable (True )
163179 self .btn_toggle_preview .setChecked (True )
164- self .btn_toggle_preview .setToolTip ("Voransicht der Treffer und Ersetzungen ein-/ausblenden" )
180+ self .btn_toggle_preview .setShortcut (QKeySequence ("Alt+V" ))
181+ self .btn_toggle_preview .setToolTip ("Voransicht der Treffer und Ersetzungen ein-/ausblenden (Alt+V)" )
165182 self .btn_toggle_preview .setAccessibleName ("Voransicht umschalten" )
183+ self .btn_toggle_preview .setAccessibleDescription ("Schaltet die tabellarische Voransicht aller Ersetzungen ein oder aus (Alt+V)" )
166184 self .btn_toggle_preview .toggled .connect (self ._on_toggle_preview )
167185 status_bar_layout .addWidget (self .btn_toggle_preview )
168186 root_layout .addLayout (status_bar_layout )
@@ -185,9 +203,10 @@ def _setup_ui(self):
185203 self .preview_table .setSelectionMode (QTableWidget .SelectionMode .SingleSelection )
186204 self .preview_table .setEditTriggers (QTableWidget .EditTrigger .NoEditTriggers )
187205 self .preview_table .setAccessibleName ("Voransichtstabelle" )
188- self .preview_table .setAccessibleDescription ("Tabelle der gefundenen Treffer mit Zeile, Spalte und Vorschau der Ersetzung" )
206+ self .preview_table .setAccessibleDescription ("Tabelle der gefundenen Treffer mit Zeile, Spalte und Vorschau der Ersetzung (Enter auf Zeile springt zum Treffer) " )
189207 self .preview_table .itemClicked .connect (self ._on_table_item_activated )
190208 self .preview_table .itemDoubleClicked .connect (self ._on_table_item_activated )
209+ self .preview_table .itemActivated .connect (self ._on_table_item_activated )
191210 preview_layout .addWidget (self .preview_table )
192211
193212 root_layout .addWidget (self .preview_group , 1 )
@@ -199,10 +218,24 @@ def _setup_ui(self):
199218 self .btn_close .setObjectName ("find_close_btn" )
200219 self .btn_close .setToolTip ("Suchdialog schließen (Esc)" )
201220 self .btn_close .setAccessibleName ("Dialog schließen" )
221+ self .btn_close .setAccessibleDescription ("Schließt den Suchen- und Ersetzen-Dialog und kehrt zum Editor zurück (Esc)" )
202222 self .btn_close .clicked .connect (self .close )
203223 bottom_layout .addWidget (self .btn_close )
204224 root_layout .addLayout (bottom_layout )
205225
226+ # Logische Tab-Reihenfolge (Barrierefreie Tastaturnavigation)
227+ self .setTabOrder (self .search_input , self .btn_prev )
228+ self .setTabOrder (self .btn_prev , self .btn_next )
229+ self .setTabOrder (self .btn_next , self .replace_input )
230+ self .setTabOrder (self .replace_input , self .btn_replace )
231+ self .setTabOrder (self .btn_replace , self .btn_replace_all )
232+ self .setTabOrder (self .btn_replace_all , self .cb_case )
233+ self .setTabOrder (self .cb_case , self .cb_words )
234+ self .setTabOrder (self .cb_words , self .cb_regex )
235+ self .setTabOrder (self .cb_regex , self .btn_toggle_preview )
236+ self .setTabOrder (self .btn_toggle_preview , self .preview_table )
237+ self .setTabOrder (self .preview_table , self .btn_close )
238+
206239 def _get_current_editor (self ) -> Optional ["CodeEditor" ]:
207240 if not self .main_window or not hasattr (self .main_window , "tab_widget" ):
208241 return None
@@ -266,7 +299,10 @@ def _update_live_results(self):
266299 if err :
267300 editor .clearSearchHighlight ()
268301 self .status_label .setText (f"Regex-Fehler: { err } " )
269- self .status_label .setStyleSheet ("color: #ff6b6b; font-weight: bold;" )
302+ # Barrierefreie Farbweiche für WCAG AA Kontrast (>= 4.5:1)
303+ is_dark = self .palette ().color (self .backgroundRole ()).lightness () < 128
304+ err_color = "#ff7b72" if is_dark else "#b91c1c"
305+ self .status_label .setStyleSheet (f"color: { err_color } ; font-weight: bold;" )
270306 self .preview_table .setRowCount (0 )
271307 return
272308
@@ -397,6 +433,14 @@ def replace_all(self):
397433 self .main_window .status_bar .showMessage (f"{ count } Vorkommen ersetzt." , 4000 )
398434 self ._update_live_results ()
399435
436+ def eventFilter (self , watched , event ):
437+ if watched == self .search_input and event .type () == event .Type .KeyPress :
438+ if event .key () in (Qt .Key .Key_Return , Qt .Key .Key_Enter ):
439+ if event .modifiers () & Qt .KeyboardModifier .ShiftModifier :
440+ self .find_prev ()
441+ return True
442+ return super ().eventFilter (watched , event )
443+
400444 def keyPressEvent (self , event ):
401445 if event .key () == Qt .Key .Key_Escape :
402446 self .close ()
@@ -413,4 +457,5 @@ def closeEvent(self, event):
413457 editor = self ._get_current_editor ()
414458 if editor :
415459 editor .clearSearchHighlight ()
460+ editor .setFocus ()
416461 event .accept ()
0 commit comments