|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2008 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the search and replace widget. |
|
8 """ |
|
9 |
|
10 import re |
|
11 import contextlib |
|
12 |
|
13 from PyQt6.QtCore import pyqtSignal, Qt, pyqtSlot, QEvent |
|
14 from PyQt6.QtWidgets import ( |
|
15 QWidget, QHBoxLayout, QToolButton, QScrollArea, QSizePolicy, QFrame |
|
16 ) |
|
17 |
|
18 from .Editor import Editor |
|
19 |
|
20 from EricGui.EricAction import EricAction |
|
21 from EricWidgets import EricMessageBox |
|
22 |
|
23 import Preferences |
|
24 |
|
25 import UI.PixmapCache |
|
26 |
|
27 |
|
28 class SearchReplaceWidget(QWidget): |
|
29 """ |
|
30 Class implementing the search and replace widget. |
|
31 |
|
32 @signal searchListChanged() emitted to indicate a change of the search list |
|
33 """ |
|
34 searchListChanged = pyqtSignal() |
|
35 |
|
36 def __init__(self, replace, vm, parent=None, sliding=False): |
|
37 """ |
|
38 Constructor |
|
39 |
|
40 @param replace flag indicating a replace widget is called |
|
41 @param vm reference to the viewmanager object |
|
42 @param parent parent widget of this widget (QWidget) |
|
43 @param sliding flag indicating the widget is embedded in the |
|
44 sliding widget (boolean) |
|
45 """ |
|
46 super().__init__(parent) |
|
47 |
|
48 self.__viewmanager = vm |
|
49 self.__isMiniEditor = vm is parent |
|
50 self.__replace = replace |
|
51 self.__sliding = sliding |
|
52 if sliding: |
|
53 self.__topWidget = parent |
|
54 |
|
55 self.findHistory = vm.getSRHistory('search') |
|
56 if replace: |
|
57 from .Ui_ReplaceWidget import Ui_ReplaceWidget |
|
58 self.replaceHistory = vm.getSRHistory('replace') |
|
59 self.ui = Ui_ReplaceWidget() |
|
60 whatsThis = self.tr( |
|
61 """<b>Find and Replace</b> |
|
62 <p>This dialog is used to find some text and replace it with another text. |
|
63 By checking the various checkboxes, the search can be made more specific. |
|
64 The search string might be a regular expression. In a regular expression, |
|
65 special characters interpreted are:</p> |
|
66 """ |
|
67 ) |
|
68 else: |
|
69 from .Ui_SearchWidget import Ui_SearchWidget |
|
70 self.ui = Ui_SearchWidget() |
|
71 whatsThis = self.tr( |
|
72 """<b>Find</b> |
|
73 <p>This dialog is used to find some text. By checking the various checkboxes, |
|
74 the search can be made more specific. The search string might be a regular |
|
75 expression. In a regular expression, special characters interpreted are:</p> |
|
76 """ |
|
77 ) |
|
78 self.ui.setupUi(self) |
|
79 if not replace: |
|
80 self.ui.wrapCheckBox.setChecked(True) |
|
81 |
|
82 whatsThis += self.tr( |
|
83 """<table border="0"> |
|
84 <tr><td><code>.</code></td><td>Matches any character</td></tr> |
|
85 <tr><td><code>(</code></td><td>This marks the start of a region for tagging a |
|
86 match.</td></tr> |
|
87 <tr><td><code>)</code></td><td>This marks the end of a tagged region. |
|
88 </td></tr> |
|
89 <tr><td><code>\\n</code></td> |
|
90 <td>Where <code>n</code> is 1 through 9 refers to the first through ninth |
|
91 tagged region when replacing. For example, if the search string was |
|
92 <code>Fred([1-9])XXX</code> and the replace string was |
|
93 <code>Sam\\1YYY</code>, when applied to <code>Fred2XXX</code> this would |
|
94 generate <code>Sam2YYY</code>.</td></tr> |
|
95 <tr><td><code>\\<</code></td> |
|
96 <td>This matches the start of a word using Scintilla's definitions of words. |
|
97 </td></tr> |
|
98 <tr><td><code>\\></code></td> |
|
99 <td>This matches the end of a word using Scintilla's definition of words. |
|
100 </td></tr> |
|
101 <tr><td><code>\\x</code></td> |
|
102 <td>This allows you to use a character x that would otherwise have a special |
|
103 meaning. For example, \\[ would be interpreted as [ and not as the start of a |
|
104 character set.</td></tr> |
|
105 <tr><td><code>[...]</code></td> |
|
106 <td>This indicates a set of characters, for example, [abc] means any of the |
|
107 characters a, b or c. You can also use ranges, for example [a-z] for any lower |
|
108 case character.</td></tr> |
|
109 <tr><td><code>[^...]</code></td> |
|
110 <td>The complement of the characters in the set. For example, [^A-Za-z] means |
|
111 any character except an alphabetic character.</td></tr> |
|
112 <tr><td><code>^</code></td> |
|
113 <td>This matches the start of a line (unless used inside a set, see above). |
|
114 </td></tr> |
|
115 <tr><td><code>$</code></td> <td>This matches the end of a line.</td></tr> |
|
116 <tr><td><code>*</code></td> |
|
117 <td>This matches 0 or more times. For example, <code>Sa*m</code> matches |
|
118 <code>Sm</code>, <code>Sam</code>, <code>Saam</code>, <code>Saaam</code> |
|
119 and so on.</td></tr> |
|
120 <tr><td><code>+</code></td> |
|
121 <td>This matches 1 or more times. For example, <code>Sa+m</code> matches |
|
122 <code>Sam</code>, <code>Saam</code>, <code>Saaam</code> and so on.</td></tr> |
|
123 </table> |
|
124 <p>When using the Extended (C++11) regular expression mode more features are |
|
125 available, generally similar to regular expression support in JavaScript. See |
|
126 the documentation of your C++ runtime for details on what is supported.<p> |
|
127 """ |
|
128 ) |
|
129 self.setWhatsThis(whatsThis) |
|
130 |
|
131 # set icons |
|
132 self.ui.closeButton.setIcon(UI.PixmapCache.getIcon("close")) |
|
133 self.ui.findPrevButton.setIcon( |
|
134 UI.PixmapCache.getIcon("1leftarrow")) |
|
135 self.ui.findNextButton.setIcon( |
|
136 UI.PixmapCache.getIcon("1rightarrow")) |
|
137 self.ui.extendButton.setIcon( |
|
138 UI.PixmapCache.getIcon("2rightarrow")) |
|
139 |
|
140 if replace: |
|
141 self.ui.replaceButton.setIcon( |
|
142 UI.PixmapCache.getIcon("editReplace")) |
|
143 self.ui.replaceSearchButton.setIcon( |
|
144 UI.PixmapCache.getIcon("editReplaceSearch")) |
|
145 self.ui.replaceAllButton.setIcon( |
|
146 UI.PixmapCache.getIcon("editReplaceAll")) |
|
147 |
|
148 # set line edit completers |
|
149 self.ui.findtextCombo.setCompleter(None) |
|
150 self.ui.findtextCombo.lineEdit().returnPressed.connect( |
|
151 self.__findByReturnPressed) |
|
152 if replace: |
|
153 self.ui.replacetextCombo.setCompleter(None) |
|
154 self.ui.replacetextCombo.lineEdit().returnPressed.connect( |
|
155 self.on_replaceButton_clicked) |
|
156 |
|
157 self.ui.findtextCombo.lineEdit().textEdited.connect(self.__quickSearch) |
|
158 self.ui.caseCheckBox.toggled.connect( |
|
159 self.__updateQuickSearchMarkers) |
|
160 self.ui.wordCheckBox.toggled.connect( |
|
161 self.__updateQuickSearchMarkers) |
|
162 self.ui.regexpCheckBox.toggled.connect( |
|
163 self.__updateQuickSearchMarkers) |
|
164 |
|
165 self.__findtextComboStyleSheet = ( |
|
166 self.ui.findtextCombo.styleSheet() |
|
167 ) |
|
168 |
|
169 # define actions |
|
170 self.findNextAct = EricAction( |
|
171 self.tr('Find Next'), |
|
172 self.tr('Find Next'), |
|
173 0, 0, self, 'search_widget_find_next') |
|
174 self.findNextAct.triggered.connect(self.on_findNextButton_clicked) |
|
175 self.findNextAct.setShortcutContext( |
|
176 Qt.ShortcutContext.WidgetWithChildrenShortcut) |
|
177 |
|
178 self.findPrevAct = EricAction( |
|
179 self.tr('Find Prev'), |
|
180 self.tr('Find Prev'), |
|
181 0, 0, self, 'search_widget_find_prev') |
|
182 self.findPrevAct.triggered.connect(self.on_findPrevButton_clicked) |
|
183 self.findPrevAct.setShortcutContext( |
|
184 Qt.ShortcutContext.WidgetWithChildrenShortcut) |
|
185 |
|
186 if replace: |
|
187 self.replaceAndSearchAct = EricAction( |
|
188 self.tr("Replace and Search"), |
|
189 self.tr("Replace and Search"), |
|
190 0, 0, self, "replace_widget_replace_search") |
|
191 self.replaceAndSearchAct.triggered.connect( |
|
192 self.on_replaceSearchButton_clicked) |
|
193 self.replaceAndSearchAct.setEnabled(False) |
|
194 self.replaceAndSearchAct.setShortcutContext( |
|
195 Qt.ShortcutContext.WidgetWithChildrenShortcut) |
|
196 |
|
197 self.replaceSelectionAct = EricAction( |
|
198 self.tr("Replace Occurrence"), |
|
199 self.tr("Replace Occurrence"), |
|
200 0, 0, self, "replace_widget_replace_occurrence") |
|
201 self.replaceSelectionAct.triggered.connect( |
|
202 self.on_replaceButton_clicked) |
|
203 self.replaceSelectionAct.setEnabled(False) |
|
204 self.replaceSelectionAct.setShortcutContext( |
|
205 Qt.ShortcutContext.WidgetWithChildrenShortcut) |
|
206 |
|
207 self.replaceAllAct = EricAction( |
|
208 self.tr("Replace All"), |
|
209 self.tr("Replace All"), |
|
210 0, 0, self, "replace_widget_replace_all") |
|
211 self.replaceAllAct.triggered.connect( |
|
212 self.on_replaceAllButton_clicked) |
|
213 self.replaceAllAct.setEnabled(False) |
|
214 self.replaceAllAct.setShortcutContext( |
|
215 Qt.ShortcutContext.WidgetWithChildrenShortcut) |
|
216 |
|
217 self.addAction(self.findNextAct) |
|
218 self.addAction(self.findPrevAct) |
|
219 if replace: |
|
220 self.addAction(self.replaceAndSearchAct) |
|
221 self.addAction(self.replaceSelectionAct) |
|
222 self.addAction(self.replaceAllAct) |
|
223 |
|
224 # disable search and replace buttons and actions |
|
225 self.__setFindNextEnabled(False) |
|
226 self.__setFindPrevEnabled(False) |
|
227 if replace: |
|
228 self.__setReplaceAndSearchEnabled(False) |
|
229 self.__setReplaceSelectionEnabled(False) |
|
230 self.__setReplaceAllEnabled(False) |
|
231 |
|
232 self.adjustSize() |
|
233 |
|
234 self.havefound = False |
|
235 self.__pos = None |
|
236 self.__findBackwards = False |
|
237 self.__selections = [] |
|
238 self.__finding = False |
|
239 |
|
240 def __setShortcuts(self): |
|
241 """ |
|
242 Private method to set the local action's shortcuts to the same key |
|
243 sequences as in the view manager. |
|
244 """ |
|
245 if not self.__isMiniEditor: |
|
246 self.findNextAct.setShortcuts( |
|
247 self.__viewmanager.searchNextAct.shortcuts()) |
|
248 self.findPrevAct.setShortcuts( |
|
249 self.__viewmanager.searchPrevAct.shortcuts()) |
|
250 |
|
251 if self.__replace: |
|
252 self.replaceAndSearchAct.setShortcuts( |
|
253 self.__viewmanager.replaceAndSearchAct.shortcuts()) |
|
254 self.replaceSelectionAct.setShortcuts( |
|
255 self.__viewmanager.replaceSelectionAct.shortcuts()) |
|
256 self.replaceAllAct.setShortcuts( |
|
257 self.__viewmanager.replaceAllAct.shortcuts()) |
|
258 |
|
259 def __setFindNextEnabled(self, enable): |
|
260 """ |
|
261 Private method to set the enabled state of "Find Next". |
|
262 |
|
263 @param enable flag indicating the enable state to be set |
|
264 @type bool |
|
265 """ |
|
266 self.ui.findNextButton.setEnabled(enable) |
|
267 self.findNextAct.setEnabled(enable) |
|
268 |
|
269 def __setFindPrevEnabled(self, enable): |
|
270 """ |
|
271 Private method to set the enabled state of "Find Prev". |
|
272 |
|
273 @param enable flag indicating the enable state to be set |
|
274 @type bool |
|
275 """ |
|
276 self.ui.findPrevButton.setEnabled(enable) |
|
277 self.findPrevAct.setEnabled(enable) |
|
278 |
|
279 def __setReplaceAndSearchEnabled(self, enable): |
|
280 """ |
|
281 Private method to set the enabled state of "Replace And Search". |
|
282 |
|
283 @param enable flag indicating the enable state to be set |
|
284 @type bool |
|
285 """ |
|
286 self.ui.replaceSearchButton.setEnabled(enable) |
|
287 self.replaceAndSearchAct.setEnabled(enable) |
|
288 |
|
289 def __setReplaceSelectionEnabled(self, enable): |
|
290 """ |
|
291 Private method to set the enabled state of "Replace Occurrence". |
|
292 |
|
293 @param enable flag indicating the enable state to be set |
|
294 @type bool |
|
295 """ |
|
296 self.ui.replaceButton.setEnabled(enable) |
|
297 self.replaceSelectionAct.setEnabled(enable) |
|
298 |
|
299 def __setReplaceAllEnabled(self, enable): |
|
300 """ |
|
301 Private method to set the enabled state of "Replace All". |
|
302 |
|
303 @param enable flag indicating the enable state to be set |
|
304 @type bool |
|
305 """ |
|
306 self.ui.replaceAllButton.setEnabled(enable) |
|
307 self.replaceAllAct.setEnabled(enable) |
|
308 |
|
309 def changeEvent(self, evt): |
|
310 """ |
|
311 Protected method handling state changes. |
|
312 |
|
313 @param evt event containing the state change (QEvent) |
|
314 """ |
|
315 if evt.type() == QEvent.Type.FontChange: |
|
316 self.adjustSize() |
|
317 |
|
318 def __selectionBoundary(self, selections=None): |
|
319 """ |
|
320 Private method to calculate the current selection boundary. |
|
321 |
|
322 @param selections optional parameter giving the selections to |
|
323 calculate the boundary for (list of tuples of four integer) |
|
324 @return tuple of start line and index and end line and index |
|
325 (tuple of four integer) |
|
326 """ |
|
327 if selections is None: |
|
328 selections = self.__selections |
|
329 if selections: |
|
330 lineNumbers = ( |
|
331 [sel[0] for sel in selections] + |
|
332 [sel[2] for sel in selections] |
|
333 ) |
|
334 indexNumbers = ( |
|
335 [sel[1] for sel in selections] + |
|
336 [sel[3] for sel in selections] |
|
337 ) |
|
338 startLine, startIndex, endLine, endIndex = ( |
|
339 min(lineNumbers), min(indexNumbers), |
|
340 max(lineNumbers), max(indexNumbers)) |
|
341 else: |
|
342 startLine, startIndex, endLine, endIndex = -1, -1, -1, -1 |
|
343 |
|
344 return startLine, startIndex, endLine, endIndex |
|
345 |
|
346 @pyqtSlot(str) |
|
347 def on_findtextCombo_editTextChanged(self, txt): |
|
348 """ |
|
349 Private slot to enable/disable the find buttons. |
|
350 |
|
351 @param txt text of the find text combo |
|
352 @type str |
|
353 """ |
|
354 enable = bool(txt) |
|
355 |
|
356 self.__setFindNextEnabled(enable) |
|
357 self.__setFindPrevEnabled(enable) |
|
358 self.ui.extendButton.setEnabled(enable) |
|
359 if self.__replace: |
|
360 self.__setReplaceSelectionEnabled(False) |
|
361 self.__setReplaceAndSearchEnabled(False) |
|
362 self.__setReplaceAllEnabled(enable) |
|
363 |
|
364 @pyqtSlot(str) |
|
365 def __quickSearch(self, txt): |
|
366 """ |
|
367 Private slot to search for the entered text while typing. |
|
368 |
|
369 @param txt text of the search edit |
|
370 @type str |
|
371 """ |
|
372 aw = self.__viewmanager.activeWindow() |
|
373 aw.hideFindIndicator() |
|
374 if Preferences.getEditor("QuickSearchMarkersEnabled"): |
|
375 self.__quickSearchMarkOccurrences(txt) |
|
376 |
|
377 lineFrom, indexFrom, lineTo, indexTo = aw.getSelection() |
|
378 posixMode = (Preferences.getEditor("SearchRegexpMode") == 0 and |
|
379 self.ui.regexpCheckBox.isChecked()) |
|
380 cxx11Mode = (Preferences.getEditor("SearchRegexpMode") == 1 and |
|
381 self.ui.regexpCheckBox.isChecked()) |
|
382 ok = aw.findFirst( |
|
383 txt, |
|
384 self.ui.regexpCheckBox.isChecked(), |
|
385 self.ui.caseCheckBox.isChecked(), |
|
386 self.ui.wordCheckBox.isChecked(), |
|
387 self.ui.wrapCheckBox.isChecked(), |
|
388 not self.__findBackwards, |
|
389 lineFrom, indexFrom, |
|
390 posix=posixMode, |
|
391 cxx11=cxx11Mode |
|
392 ) |
|
393 if ok: |
|
394 sline, sindex, eline, eindex = aw.getSelection() |
|
395 aw.showFindIndicator(sline, sindex, eline, eindex) |
|
396 |
|
397 if not txt: |
|
398 ok = True # reset the color in case of an empty text |
|
399 |
|
400 self.__setSearchEditColors(ok) |
|
401 |
|
402 def __quickSearchMarkOccurrences(self, txt): |
|
403 """ |
|
404 Private method to mark all occurrences of the search text. |
|
405 |
|
406 @param txt text to search for (string) |
|
407 """ |
|
408 aw = self.__viewmanager.activeWindow() |
|
409 |
|
410 lineFrom = 0 |
|
411 indexFrom = 0 |
|
412 lineTo = -1 |
|
413 indexTo = -1 |
|
414 |
|
415 aw.clearSearchIndicators() |
|
416 posixMode = (Preferences.getEditor("SearchRegexpMode") == 0 and |
|
417 self.ui.regexpCheckBox.isChecked()) |
|
418 cxx11Mode = (Preferences.getEditor("SearchRegexpMode") == 1 and |
|
419 self.ui.regexpCheckBox.isChecked()) |
|
420 ok = aw.findFirstTarget( |
|
421 txt, |
|
422 self.ui.regexpCheckBox.isChecked(), |
|
423 self.ui.caseCheckBox.isChecked(), |
|
424 self.ui.wordCheckBox.isChecked(), |
|
425 lineFrom, indexFrom, lineTo, indexTo, |
|
426 posix=posixMode, |
|
427 cxx11=cxx11Mode |
|
428 ) |
|
429 while ok: |
|
430 tgtPos, tgtLen = aw.getFoundTarget() |
|
431 aw.setSearchIndicator(tgtPos, tgtLen) |
|
432 ok = aw.findNextTarget() |
|
433 |
|
434 def __setSearchEditColors(self, ok): |
|
435 """ |
|
436 Private method to set the search edit colors. |
|
437 |
|
438 @param ok flag indicating a match |
|
439 @type bool |
|
440 """ |
|
441 if not ok: |
|
442 self.ui.findtextCombo.setStyleSheet( |
|
443 "color: #000000; background-color: #ff6666;" |
|
444 ) |
|
445 else: |
|
446 self.ui.findtextCombo.setStyleSheet( |
|
447 self.__findtextComboStyleSheet) |
|
448 |
|
449 @pyqtSlot() |
|
450 def on_extendButton_clicked(self): |
|
451 """ |
|
452 Private slot to handle the quicksearch extend action. |
|
453 """ |
|
454 aw = self.__viewmanager.activeWindow() |
|
455 if aw is None: |
|
456 return |
|
457 |
|
458 txt = self.ui.findtextCombo.currentText() |
|
459 if not txt: |
|
460 return |
|
461 |
|
462 line, index = aw.getCursorPosition() |
|
463 text = aw.text(line) |
|
464 |
|
465 rx = re.compile(r'[^\w_]') |
|
466 match = rx.search(text, index) |
|
467 if match: |
|
468 end = match.start() |
|
469 if end > index: |
|
470 ext = text[index:end] |
|
471 txt += ext |
|
472 self.ui.findtextCombo.setEditText(txt) |
|
473 self.ui.findtextCombo.lineEdit().selectAll() |
|
474 self.__quickSearch(txt) |
|
475 |
|
476 @pyqtSlot(bool) |
|
477 def __updateQuickSearchMarkers(self, on): |
|
478 """ |
|
479 Private slot to handle the selection of the various check boxes. |
|
480 |
|
481 @param on status of the check box (ignored) |
|
482 @type bool |
|
483 """ |
|
484 txt = self.ui.findtextCombo.currentText() |
|
485 self.__quickSearch(txt) |
|
486 |
|
487 @pyqtSlot() |
|
488 def on_findNextButton_clicked(self): |
|
489 """ |
|
490 Private slot to find the next occurrence of text. |
|
491 """ |
|
492 self.findNext() |
|
493 |
|
494 def findNext(self): |
|
495 """ |
|
496 Public slot to find the next occurrence of text. |
|
497 """ |
|
498 if not self.havefound or not self.ui.findtextCombo.currentText(): |
|
499 if self.__replace: |
|
500 self.__viewmanager.showReplaceWidget() |
|
501 else: |
|
502 self.__viewmanager.showSearchWidget() |
|
503 return |
|
504 |
|
505 self.__findBackwards = False |
|
506 txt = self.ui.findtextCombo.currentText() |
|
507 |
|
508 # This moves any previous occurrence of this statement to the head |
|
509 # of the list and updates the combobox |
|
510 if txt in self.findHistory: |
|
511 self.findHistory.remove(txt) |
|
512 self.findHistory.insert(0, txt) |
|
513 self.ui.findtextCombo.clear() |
|
514 self.ui.findtextCombo.addItems(self.findHistory) |
|
515 self.searchListChanged.emit() |
|
516 |
|
517 ok = self.__findNextPrev(txt, False) |
|
518 self.__setSearchEditColors(ok) |
|
519 if ok: |
|
520 if self.__replace: |
|
521 self.__setReplaceSelectionEnabled(True) |
|
522 self.__setReplaceAndSearchEnabled(True) |
|
523 else: |
|
524 EricMessageBox.information( |
|
525 self, self.windowTitle(), |
|
526 self.tr("'{0}' was not found.").format(txt)) |
|
527 |
|
528 @pyqtSlot() |
|
529 def on_findPrevButton_clicked(self): |
|
530 """ |
|
531 Private slot to find the previous occurrence of text. |
|
532 """ |
|
533 self.findPrev() |
|
534 |
|
535 def findPrev(self): |
|
536 """ |
|
537 Public slot to find the next previous of text. |
|
538 """ |
|
539 if not self.havefound or not self.ui.findtextCombo.currentText(): |
|
540 self.show(self.__viewmanager.textForFind()) |
|
541 return |
|
542 |
|
543 self.__findBackwards = True |
|
544 txt = self.ui.findtextCombo.currentText() |
|
545 |
|
546 # This moves any previous occurrence of this statement to the head |
|
547 # of the list and updates the combobox |
|
548 if txt in self.findHistory: |
|
549 self.findHistory.remove(txt) |
|
550 self.findHistory.insert(0, txt) |
|
551 self.ui.findtextCombo.clear() |
|
552 self.ui.findtextCombo.addItems(self.findHistory) |
|
553 self.searchListChanged.emit() |
|
554 |
|
555 ok = self.__findNextPrev(txt, True) |
|
556 self.__setSearchEditColors(ok) |
|
557 if ok: |
|
558 if self.__replace: |
|
559 self.__setReplaceSelectionEnabled(True) |
|
560 self.__setReplaceAndSearchEnabled(True) |
|
561 else: |
|
562 EricMessageBox.information( |
|
563 self, self.windowTitle(), |
|
564 self.tr("'{0}' was not found.").format(txt)) |
|
565 |
|
566 def __findByReturnPressed(self): |
|
567 """ |
|
568 Private slot to handle the returnPressed signal of the findtext |
|
569 combobox. |
|
570 """ |
|
571 if self.__findBackwards: |
|
572 self.findPrev() |
|
573 else: |
|
574 self.findNext() |
|
575 |
|
576 def __markOccurrences(self, txt): |
|
577 """ |
|
578 Private method to mark all occurrences of the search text. |
|
579 |
|
580 @param txt text to search for (string) |
|
581 """ |
|
582 aw = self.__viewmanager.activeWindow() |
|
583 lineFrom = 0 |
|
584 indexFrom = 0 |
|
585 lineTo = -1 |
|
586 indexTo = -1 |
|
587 if self.ui.selectionCheckBox.isChecked(): |
|
588 lineFrom, indexFrom, lineTo, indexTo = self.__selectionBoundary() |
|
589 posixMode = (Preferences.getEditor("SearchRegexpMode") == 0 and |
|
590 self.ui.regexpCheckBox.isChecked()) |
|
591 cxx11Mode = (Preferences.getEditor("SearchRegexpMode") == 1 and |
|
592 self.ui.regexpCheckBox.isChecked()) |
|
593 |
|
594 aw.clearSearchIndicators() |
|
595 ok = aw.findFirstTarget( |
|
596 txt, |
|
597 self.ui.regexpCheckBox.isChecked(), |
|
598 self.ui.caseCheckBox.isChecked(), |
|
599 self.ui.wordCheckBox.isChecked(), |
|
600 lineFrom, indexFrom, lineTo, indexTo, |
|
601 posix=posixMode, cxx11=cxx11Mode) |
|
602 while ok: |
|
603 tgtPos, tgtLen = aw.getFoundTarget() |
|
604 if tgtLen == 0: |
|
605 break |
|
606 if len(self.__selections) > 1: |
|
607 lineFrom, indexFrom = aw.lineIndexFromPosition(tgtPos) |
|
608 lineTo, indexTo = aw.lineIndexFromPosition(tgtPos + tgtLen) |
|
609 for sel in self.__selections: |
|
610 if ( |
|
611 lineFrom == sel[0] and |
|
612 indexFrom >= sel[1] and |
|
613 indexTo <= sel[3] |
|
614 ): |
|
615 indicate = True |
|
616 break |
|
617 else: |
|
618 indicate = False |
|
619 else: |
|
620 indicate = True |
|
621 if indicate: |
|
622 aw.setSearchIndicator(tgtPos, tgtLen) |
|
623 ok = aw.findNextTarget() |
|
624 with contextlib.suppress(AttributeError): |
|
625 aw.updateMarkerMap() |
|
626 # ignore it for MiniEditor |
|
627 |
|
628 def __findNextPrev(self, txt, backwards): |
|
629 """ |
|
630 Private method to find the next occurrence of the search text. |
|
631 |
|
632 @param txt text to search for (string) |
|
633 @param backwards flag indicating a backwards search (boolean) |
|
634 @return flag indicating success (boolean) |
|
635 """ |
|
636 self.__finding = True |
|
637 |
|
638 if Preferences.getEditor("SearchMarkersEnabled"): |
|
639 self.__markOccurrences(txt) |
|
640 |
|
641 aw = self.__viewmanager.activeWindow() |
|
642 aw.hideFindIndicator() |
|
643 cline, cindex = aw.getCursorPosition() |
|
644 |
|
645 ok = True |
|
646 lineFrom, indexFrom, lineTo, indexTo = aw.getSelection() |
|
647 boundary = self.__selectionBoundary() |
|
648 if backwards: |
|
649 if ( |
|
650 self.ui.selectionCheckBox.isChecked() and |
|
651 (lineFrom, indexFrom, lineTo, indexTo) == boundary |
|
652 ): |
|
653 # initial call |
|
654 line, index = boundary[2:] |
|
655 else: |
|
656 if (lineFrom, indexFrom) == (-1, -1): |
|
657 # no selection present |
|
658 line = cline |
|
659 index = cindex |
|
660 else: |
|
661 line = lineFrom |
|
662 index = indexFrom |
|
663 if ( |
|
664 self.ui.selectionCheckBox.isChecked() and |
|
665 line == boundary[0] and |
|
666 index >= 0 and |
|
667 index < boundary[1] |
|
668 ): |
|
669 ok = False |
|
670 |
|
671 if ok and index < 0: |
|
672 line -= 1 |
|
673 if self.ui.selectionCheckBox.isChecked(): |
|
674 if line < boundary[0]: |
|
675 if self.ui.wrapCheckBox.isChecked(): |
|
676 line, index = boundary[2:] |
|
677 else: |
|
678 ok = False |
|
679 else: |
|
680 index = aw.lineLength(line) |
|
681 else: |
|
682 if line < 0: |
|
683 if self.ui.wrapCheckBox.isChecked(): |
|
684 line = aw.lines() - 1 |
|
685 index = aw.lineLength(line) |
|
686 else: |
|
687 ok = False |
|
688 else: |
|
689 index = aw.lineLength(line) |
|
690 else: |
|
691 if ( |
|
692 self.ui.selectionCheckBox.isChecked() and |
|
693 (lineFrom, indexFrom, lineTo, indexTo) == boundary |
|
694 ): |
|
695 # initial call |
|
696 line, index = boundary[:2] |
|
697 else: |
|
698 line = lineTo |
|
699 index = indexTo |
|
700 |
|
701 if ok: |
|
702 posixMode = (Preferences.getEditor("SearchRegexpMode") == 0 and |
|
703 self.ui.regexpCheckBox.isChecked()) |
|
704 cxx11Mode = (Preferences.getEditor("SearchRegexpMode") == 1 and |
|
705 self.ui.regexpCheckBox.isChecked()) |
|
706 ok = aw.findFirst( |
|
707 txt, |
|
708 self.ui.regexpCheckBox.isChecked(), |
|
709 self.ui.caseCheckBox.isChecked(), |
|
710 self.ui.wordCheckBox.isChecked(), |
|
711 self.ui.wrapCheckBox.isChecked(), |
|
712 not backwards, |
|
713 line, index, |
|
714 posix=posixMode, |
|
715 cxx11=cxx11Mode) |
|
716 |
|
717 if ok and self.ui.selectionCheckBox.isChecked(): |
|
718 lineFrom, indexFrom, lineTo, indexTo = aw.getSelection() |
|
719 if len(self.__selections) > 1: |
|
720 for sel in self.__selections: |
|
721 if ( |
|
722 lineFrom == sel[0] and |
|
723 indexFrom >= sel[1] and |
|
724 indexTo <= sel[3] |
|
725 ): |
|
726 ok = True |
|
727 break |
|
728 else: |
|
729 ok = False |
|
730 elif ( |
|
731 (lineFrom == boundary[0] and indexFrom >= boundary[1]) or |
|
732 (lineFrom > boundary[0] and lineFrom < boundary[2]) or |
|
733 (lineFrom == boundary[2] and indexFrom <= boundary[3]) |
|
734 ): |
|
735 ok = True |
|
736 else: |
|
737 ok = False |
|
738 if not ok and len(self.__selections) > 1: |
|
739 # try again |
|
740 while ( |
|
741 not ok and |
|
742 ((backwards and lineFrom >= boundary[0]) or |
|
743 (not backwards and lineFrom <= boundary[2])) |
|
744 ): |
|
745 for ind in range(len(self.__selections)): |
|
746 if lineFrom == self.__selections[ind][0]: |
|
747 after = indexTo > self.__selections[ind][3] |
|
748 if backwards: |
|
749 if after: |
|
750 line, index = self.__selections[ind][2:] |
|
751 else: |
|
752 if ind > 0: |
|
753 line, index = ( |
|
754 self.__selections[ind - 1][2:] |
|
755 ) |
|
756 else: |
|
757 if after: |
|
758 if ind < len(self.__selections) - 1: |
|
759 line, index = ( |
|
760 self.__selections[ind + 1][:2] |
|
761 ) |
|
762 else: |
|
763 line, index = self.__selections[ind][:2] |
|
764 break |
|
765 else: |
|
766 break |
|
767 ok = aw.findFirst( |
|
768 txt, |
|
769 self.ui.regexpCheckBox.isChecked(), |
|
770 self.ui.caseCheckBox.isChecked(), |
|
771 self.ui.wordCheckBox.isChecked(), |
|
772 self.ui.wrapCheckBox.isChecked(), |
|
773 not backwards, |
|
774 line, index, |
|
775 posix=posixMode, |
|
776 cxx11=cxx11Mode) |
|
777 if ok: |
|
778 lineFrom, indexFrom, lineTo, indexTo = ( |
|
779 aw.getSelection() |
|
780 ) |
|
781 if ( |
|
782 lineFrom < boundary[0] or |
|
783 lineFrom > boundary[2] or |
|
784 indexFrom < boundary[1] or |
|
785 indexFrom > boundary[3] or |
|
786 indexTo < boundary[1] or |
|
787 indexTo > boundary[3] |
|
788 ): |
|
789 ok = False |
|
790 break |
|
791 if not ok: |
|
792 if self.ui.wrapCheckBox.isChecked(): |
|
793 # try it again |
|
794 if backwards: |
|
795 line, index = boundary[2:] |
|
796 else: |
|
797 line, index = boundary[:2] |
|
798 ok = aw.findFirst( |
|
799 txt, |
|
800 self.ui.regexpCheckBox.isChecked(), |
|
801 self.ui.caseCheckBox.isChecked(), |
|
802 self.ui.wordCheckBox.isChecked(), |
|
803 self.ui.wrapCheckBox.isChecked(), |
|
804 not backwards, |
|
805 line, index, |
|
806 posix=posixMode, |
|
807 cxx11=cxx11Mode) |
|
808 if ok: |
|
809 lineFrom, indexFrom, lineTo, indexTo = ( |
|
810 aw.getSelection() |
|
811 ) |
|
812 if len(self.__selections) > 1: |
|
813 for sel in self.__selections: |
|
814 if ( |
|
815 lineFrom == sel[0] and |
|
816 indexFrom >= sel[1] and |
|
817 indexTo <= sel[3] |
|
818 ): |
|
819 ok = True |
|
820 break |
|
821 else: |
|
822 ok = False |
|
823 elif ( |
|
824 (lineFrom == boundary[0] and |
|
825 indexFrom >= boundary[1]) or |
|
826 (lineFrom > boundary[0] and |
|
827 lineFrom < boundary[2]) or |
|
828 (lineFrom == boundary[2] and |
|
829 indexFrom <= boundary[3]) |
|
830 ): |
|
831 ok = True |
|
832 else: |
|
833 ok = False |
|
834 else: |
|
835 ok = False |
|
836 |
|
837 if not ok: |
|
838 aw.selectAll(False) |
|
839 aw.setCursorPosition(cline, cindex) |
|
840 aw.ensureCursorVisible() |
|
841 |
|
842 if ok: |
|
843 sline, sindex, eline, eindex = aw.getSelection() |
|
844 aw.showFindIndicator(sline, sindex, eline, eindex) |
|
845 |
|
846 self.__finding = False |
|
847 |
|
848 return ok |
|
849 |
|
850 def __showFind(self, text=''): |
|
851 """ |
|
852 Private method to display this widget in find mode. |
|
853 |
|
854 @param text text to be shown in the findtext edit (string) |
|
855 """ |
|
856 self.__replace = False |
|
857 |
|
858 self.__setSearchEditColors(True) |
|
859 self.ui.findtextCombo.clear() |
|
860 self.ui.findtextCombo.addItems(self.findHistory) |
|
861 self.ui.findtextCombo.setEditText(text) |
|
862 self.ui.findtextCombo.lineEdit().selectAll() |
|
863 self.ui.findtextCombo.setFocus() |
|
864 self.on_findtextCombo_editTextChanged(text) |
|
865 |
|
866 self.ui.caseCheckBox.setChecked(False) |
|
867 self.ui.wordCheckBox.setChecked(False) |
|
868 self.ui.wrapCheckBox.setChecked(True) |
|
869 self.ui.regexpCheckBox.setChecked(False) |
|
870 |
|
871 aw = self.__viewmanager.activeWindow() |
|
872 self.updateSelectionCheckBox(aw) |
|
873 |
|
874 self.havefound = True |
|
875 self.__findBackwards = False |
|
876 |
|
877 self.__setShortcuts() |
|
878 |
|
879 def selectionChanged(self, editor): |
|
880 """ |
|
881 Public slot tracking changes of selected text. |
|
882 |
|
883 @param editor reference to the editor |
|
884 @type Editor |
|
885 """ |
|
886 self.updateSelectionCheckBox(editor) |
|
887 |
|
888 @pyqtSlot(Editor) |
|
889 def updateSelectionCheckBox(self, editor): |
|
890 """ |
|
891 Public slot to update the selection check box. |
|
892 |
|
893 @param editor reference to the editor |
|
894 @type Editor |
|
895 """ |
|
896 if not self.__finding and isinstance(editor, Editor): |
|
897 if editor.hasSelectedText(): |
|
898 selections = editor.getSelections() |
|
899 line1, index1, line2, index2 = ( |
|
900 self.__selectionBoundary(selections) |
|
901 ) |
|
902 if line1 != line2: |
|
903 self.ui.selectionCheckBox.setEnabled(True) |
|
904 self.ui.selectionCheckBox.setChecked(True) |
|
905 self.__selections = selections |
|
906 return |
|
907 |
|
908 self.ui.selectionCheckBox.setEnabled(False) |
|
909 self.ui.selectionCheckBox.setChecked(False) |
|
910 self.__selections = [] |
|
911 |
|
912 def replace(self): |
|
913 """ |
|
914 Public method to replace the current selection. |
|
915 """ |
|
916 if self.ui.replaceButton.isEnabled(): |
|
917 self.__doReplace(False) |
|
918 |
|
919 def replaceSearch(self): |
|
920 """ |
|
921 Public method to replace the current selection and search again. |
|
922 """ |
|
923 if self.ui.replaceSearchButton.isEnabled(): |
|
924 self.__doReplace(True) |
|
925 |
|
926 @pyqtSlot() |
|
927 def on_replaceButton_clicked(self): |
|
928 """ |
|
929 Private slot to replace one occurrence of text. |
|
930 """ |
|
931 self.__doReplace(False) |
|
932 |
|
933 @pyqtSlot() |
|
934 def on_replaceSearchButton_clicked(self): |
|
935 """ |
|
936 Private slot to replace one occurrence of text and search for the next |
|
937 one. |
|
938 """ |
|
939 self.__doReplace(True) |
|
940 |
|
941 def __doReplace(self, searchNext): |
|
942 """ |
|
943 Private method to replace one occurrence of text. |
|
944 |
|
945 @param searchNext flag indicating to search for the next occurrence |
|
946 (boolean). |
|
947 """ |
|
948 self.__finding = True |
|
949 |
|
950 # Check enabled status due to dual purpose usage of this method |
|
951 if ( |
|
952 not self.ui.replaceButton.isEnabled() and |
|
953 not self.ui.replaceSearchButton.isEnabled() |
|
954 ): |
|
955 return |
|
956 |
|
957 ftxt = self.ui.findtextCombo.currentText() |
|
958 rtxt = self.ui.replacetextCombo.currentText() |
|
959 |
|
960 # This moves any previous occurrence of this statement to the head |
|
961 # of the list and updates the combobox |
|
962 if rtxt in self.replaceHistory: |
|
963 self.replaceHistory.remove(rtxt) |
|
964 self.replaceHistory.insert(0, rtxt) |
|
965 self.ui.replacetextCombo.clear() |
|
966 self.ui.replacetextCombo.addItems(self.replaceHistory) |
|
967 |
|
968 aw = self.__viewmanager.activeWindow() |
|
969 aw.hideFindIndicator() |
|
970 aw.replace(rtxt) |
|
971 |
|
972 if searchNext: |
|
973 ok = self.__findNextPrev(ftxt, self.__findBackwards) |
|
974 self.__setSearchEditColors(ok) |
|
975 |
|
976 if not ok: |
|
977 self.__setReplaceSelectionEnabled(False) |
|
978 self.__setReplaceAndSearchEnabled(False) |
|
979 EricMessageBox.information( |
|
980 self, self.windowTitle(), |
|
981 self.tr("'{0}' was not found.").format(ftxt)) |
|
982 else: |
|
983 self.__setReplaceSelectionEnabled(False) |
|
984 self.__setReplaceAndSearchEnabled(False) |
|
985 self.__setSearchEditColors(True) |
|
986 |
|
987 self.__finding = False |
|
988 |
|
989 def replaceAll(self): |
|
990 """ |
|
991 Public method to replace all occurrences. |
|
992 """ |
|
993 if self.ui.replaceAllButton.isEnabled(): |
|
994 self.on_replaceAllButton_clicked() |
|
995 |
|
996 @pyqtSlot() |
|
997 def on_replaceAllButton_clicked(self): |
|
998 """ |
|
999 Private slot to replace all occurrences of text. |
|
1000 """ |
|
1001 self.__finding = True |
|
1002 |
|
1003 replacements = 0 |
|
1004 ftxt = self.ui.findtextCombo.currentText() |
|
1005 rtxt = self.ui.replacetextCombo.currentText() |
|
1006 |
|
1007 # This moves any previous occurrence of this statement to the head |
|
1008 # of the list and updates the combobox |
|
1009 if ftxt in self.findHistory: |
|
1010 self.findHistory.remove(ftxt) |
|
1011 self.findHistory.insert(0, ftxt) |
|
1012 self.ui.findtextCombo.clear() |
|
1013 self.ui.findtextCombo.addItems(self.findHistory) |
|
1014 |
|
1015 if rtxt in self.replaceHistory: |
|
1016 self.replaceHistory.remove(rtxt) |
|
1017 self.replaceHistory.insert(0, rtxt) |
|
1018 self.ui.replacetextCombo.clear() |
|
1019 self.ui.replacetextCombo.addItems(self.replaceHistory) |
|
1020 |
|
1021 aw = self.__viewmanager.activeWindow() |
|
1022 aw.hideFindIndicator() |
|
1023 cline, cindex = aw.getCursorPosition() |
|
1024 boundary = self.__selectionBoundary() |
|
1025 if self.ui.selectionCheckBox.isChecked(): |
|
1026 line, index = boundary[:2] |
|
1027 else: |
|
1028 line = 0 |
|
1029 index = 0 |
|
1030 posixMode = (Preferences.getEditor("SearchRegexpMode") == 0 and |
|
1031 self.ui.regexpCheckBox.isChecked()) |
|
1032 cxx11Mode = (Preferences.getEditor("SearchRegexpMode") == 1 and |
|
1033 self.ui.regexpCheckBox.isChecked()) |
|
1034 ok = aw.findFirst( |
|
1035 ftxt, |
|
1036 self.ui.regexpCheckBox.isChecked(), |
|
1037 self.ui.caseCheckBox.isChecked(), |
|
1038 self.ui.wordCheckBox.isChecked(), |
|
1039 False, True, line, index, |
|
1040 posix=posixMode, |
|
1041 cxx11=cxx11Mode) |
|
1042 |
|
1043 if ok and self.ui.selectionCheckBox.isChecked(): |
|
1044 lineFrom, indexFrom, lineTo, indexTo = aw.getSelection() |
|
1045 if len(self.__selections) > 1: |
|
1046 for sel in self.__selections: |
|
1047 if ( |
|
1048 lineFrom == sel[0] and |
|
1049 indexFrom >= sel[1] and |
|
1050 indexTo <= sel[3] |
|
1051 ): |
|
1052 ok = True |
|
1053 break |
|
1054 else: |
|
1055 ok = False |
|
1056 elif ( |
|
1057 (lineFrom == boundary[0] and indexFrom >= boundary[1]) or |
|
1058 (lineFrom > boundary[0] and lineFrom < boundary[2]) or |
|
1059 (lineFrom == boundary[2] and indexFrom <= boundary[3]) |
|
1060 ): |
|
1061 ok = True |
|
1062 else: |
|
1063 ok = False |
|
1064 if not ok and len(self.__selections) > 1: |
|
1065 # try again |
|
1066 while not ok and lineFrom <= boundary[2]: |
|
1067 for ind in range(len(self.__selections)): |
|
1068 if lineFrom == self.__selections[ind][0]: |
|
1069 after = indexTo > self.__selections[ind][3] |
|
1070 if after: |
|
1071 if ind < len(self.__selections) - 1: |
|
1072 line, index = ( |
|
1073 self.__selections[ind + 1][:2] |
|
1074 ) |
|
1075 else: |
|
1076 line, index = self.__selections[ind][:2] |
|
1077 break |
|
1078 else: |
|
1079 break |
|
1080 ok = aw.findFirst( |
|
1081 ftxt, |
|
1082 self.ui.regexpCheckBox.isChecked(), |
|
1083 self.ui.caseCheckBox.isChecked(), |
|
1084 self.ui.wordCheckBox.isChecked(), |
|
1085 False, True, line, index, |
|
1086 posix=posixMode, |
|
1087 cxx11=cxx11Mode) |
|
1088 if ok: |
|
1089 lineFrom, indexFrom, lineTo, indexTo = ( |
|
1090 aw.getSelection() |
|
1091 ) |
|
1092 if ( |
|
1093 lineFrom < boundary[0] or |
|
1094 lineFrom > boundary[2] or |
|
1095 indexFrom < boundary[1] or |
|
1096 indexFrom > boundary[3] or |
|
1097 indexTo < boundary[1] or |
|
1098 indexTo > boundary[3] |
|
1099 ): |
|
1100 ok = False |
|
1101 break |
|
1102 |
|
1103 if not ok: |
|
1104 aw.selectAll(False) |
|
1105 aw.setCursorPosition(cline, cindex) |
|
1106 aw.ensureCursorVisible() |
|
1107 |
|
1108 found = ok |
|
1109 |
|
1110 aw.beginUndoAction() |
|
1111 wordWrap = self.ui.wrapCheckBox.isChecked() |
|
1112 self.ui.wrapCheckBox.setChecked(False) |
|
1113 while ok: |
|
1114 aw.replace(rtxt) |
|
1115 replacements += 1 |
|
1116 ok = self.__findNextPrev(ftxt, self.__findBackwards) |
|
1117 self.__finding = True |
|
1118 aw.endUndoAction() |
|
1119 if wordWrap: |
|
1120 self.ui.wrapCheckBox.setChecked(True) |
|
1121 self.__setReplaceSelectionEnabled(False) |
|
1122 self.__setReplaceAndSearchEnabled(False) |
|
1123 |
|
1124 if found: |
|
1125 EricMessageBox.information( |
|
1126 self, self.windowTitle(), |
|
1127 self.tr("Replaced {0} occurrences.") |
|
1128 .format(replacements)) |
|
1129 else: |
|
1130 EricMessageBox.information( |
|
1131 self, self.windowTitle(), |
|
1132 self.tr("Nothing replaced because '{0}' was not found.") |
|
1133 .format(ftxt)) |
|
1134 |
|
1135 aw.setCursorPosition(cline, cindex) |
|
1136 aw.ensureCursorVisible() |
|
1137 |
|
1138 self.__finding = False |
|
1139 |
|
1140 def __showReplace(self, text=''): |
|
1141 """ |
|
1142 Private slot to display this widget in replace mode. |
|
1143 |
|
1144 @param text text to be shown in the findtext edit |
|
1145 """ |
|
1146 self.__replace = True |
|
1147 |
|
1148 self.__setSearchEditColors(True) |
|
1149 self.ui.findtextCombo.clear() |
|
1150 self.ui.findtextCombo.addItems(self.findHistory) |
|
1151 self.ui.findtextCombo.setEditText(text) |
|
1152 self.ui.findtextCombo.lineEdit().selectAll() |
|
1153 self.ui.findtextCombo.setFocus() |
|
1154 self.on_findtextCombo_editTextChanged(text) |
|
1155 |
|
1156 self.ui.replacetextCombo.clear() |
|
1157 self.ui.replacetextCombo.addItems(self.replaceHistory) |
|
1158 self.ui.replacetextCombo.setEditText('') |
|
1159 |
|
1160 self.ui.caseCheckBox.setChecked(False) |
|
1161 self.ui.wordCheckBox.setChecked(False) |
|
1162 self.ui.regexpCheckBox.setChecked(False) |
|
1163 |
|
1164 self.havefound = True |
|
1165 |
|
1166 aw = self.__viewmanager.activeWindow() |
|
1167 self.updateSelectionCheckBox(aw) |
|
1168 if aw.hasSelectedText(): |
|
1169 line1, index1, line2, index2 = aw.getSelection() |
|
1170 if line1 == line2: |
|
1171 aw.setSelection(line1, index1, line1, index1) |
|
1172 self.findNext() |
|
1173 |
|
1174 self.__setShortcuts() |
|
1175 |
|
1176 def show(self, text=''): |
|
1177 """ |
|
1178 Public slot to show the widget. |
|
1179 |
|
1180 @param text text to be shown in the findtext edit (string) |
|
1181 """ |
|
1182 if self.__replace: |
|
1183 self.__showReplace(text) |
|
1184 else: |
|
1185 self.__showFind(text) |
|
1186 super().show() |
|
1187 self.activateWindow() |
|
1188 |
|
1189 @pyqtSlot() |
|
1190 def on_closeButton_clicked(self): |
|
1191 """ |
|
1192 Private slot to close the widget. |
|
1193 """ |
|
1194 aw = self.__viewmanager.activeWindow() |
|
1195 if aw: |
|
1196 aw.hideFindIndicator() |
|
1197 |
|
1198 if self.__sliding: |
|
1199 self.__topWidget.close() |
|
1200 else: |
|
1201 self.close() |
|
1202 |
|
1203 def keyPressEvent(self, event): |
|
1204 """ |
|
1205 Protected slot to handle key press events. |
|
1206 |
|
1207 @param event reference to the key press event (QKeyEvent) |
|
1208 """ |
|
1209 if event.key() == Qt.Key.Key_Escape: |
|
1210 aw = self.__viewmanager.activeWindow() |
|
1211 if aw: |
|
1212 aw.setFocus(Qt.FocusReason.ActiveWindowFocusReason) |
|
1213 aw.hideFindIndicator() |
|
1214 event.accept() |
|
1215 if self.__sliding: |
|
1216 self.__topWidget.close() |
|
1217 else: |
|
1218 self.close() |
|
1219 |
|
1220 |
|
1221 class SearchReplaceSlidingWidget(QWidget): |
|
1222 """ |
|
1223 Class implementing the search and replace widget with sliding behavior. |
|
1224 |
|
1225 @signal searchListChanged() emitted to indicate a change of the search list |
|
1226 """ |
|
1227 searchListChanged = pyqtSignal() |
|
1228 |
|
1229 def __init__(self, replace, vm, parent=None): |
|
1230 """ |
|
1231 Constructor |
|
1232 |
|
1233 @param replace flag indicating a replace widget is called |
|
1234 @param vm reference to the viewmanager object |
|
1235 @param parent parent widget of this widget (QWidget) |
|
1236 """ |
|
1237 super().__init__(parent) |
|
1238 |
|
1239 self.__searchReplaceWidget = SearchReplaceWidget( |
|
1240 replace, vm, self, True) |
|
1241 |
|
1242 self.__layout = QHBoxLayout(self) |
|
1243 self.setLayout(self.__layout) |
|
1244 self.__layout.setContentsMargins(0, 0, 0, 0) |
|
1245 self.__layout.setAlignment(Qt.AlignmentFlag.AlignTop) |
|
1246 |
|
1247 self.__leftButton = QToolButton(self) |
|
1248 self.__leftButton.setArrowType(Qt.ArrowType.LeftArrow) |
|
1249 self.__leftButton.setSizePolicy( |
|
1250 QSizePolicy.Policy.Minimum, QSizePolicy.Policy.MinimumExpanding) |
|
1251 self.__leftButton.setAutoRepeat(True) |
|
1252 |
|
1253 self.__scroller = QScrollArea(self) |
|
1254 self.__scroller.setWidget(self.__searchReplaceWidget) |
|
1255 self.__scroller.setSizePolicy( |
|
1256 QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) |
|
1257 self.__scroller.setFrameShape(QFrame.Shape.NoFrame) |
|
1258 self.__scroller.setVerticalScrollBarPolicy( |
|
1259 Qt.ScrollBarPolicy.ScrollBarAlwaysOff) |
|
1260 self.__scroller.setHorizontalScrollBarPolicy( |
|
1261 Qt.ScrollBarPolicy.ScrollBarAlwaysOff) |
|
1262 self.__scroller.setWidgetResizable(False) |
|
1263 |
|
1264 self.__rightButton = QToolButton(self) |
|
1265 self.__rightButton.setArrowType(Qt.ArrowType.RightArrow) |
|
1266 self.__rightButton.setSizePolicy( |
|
1267 QSizePolicy.Policy.Minimum, QSizePolicy.Policy.MinimumExpanding) |
|
1268 self.__rightButton.setAutoRepeat(True) |
|
1269 |
|
1270 self.__layout.addWidget(self.__leftButton) |
|
1271 self.__layout.addWidget(self.__scroller) |
|
1272 self.__layout.addWidget(self.__rightButton) |
|
1273 |
|
1274 self.setMaximumHeight(self.__searchReplaceWidget.sizeHint().height()) |
|
1275 self.adjustSize() |
|
1276 |
|
1277 self.__searchReplaceWidget.searchListChanged.connect( |
|
1278 self.searchListChanged) |
|
1279 self.__leftButton.clicked.connect(self.__slideLeft) |
|
1280 self.__rightButton.clicked.connect(self.__slideRight) |
|
1281 |
|
1282 def changeEvent(self, evt): |
|
1283 """ |
|
1284 Protected method handling state changes. |
|
1285 |
|
1286 @param evt event containing the state change (QEvent) |
|
1287 """ |
|
1288 if evt.type() == QEvent.Type.FontChange: |
|
1289 self.setMaximumHeight( |
|
1290 self.__searchReplaceWidget.sizeHint().height()) |
|
1291 self.adjustSize() |
|
1292 |
|
1293 def findNext(self): |
|
1294 """ |
|
1295 Public slot to find the next occurrence of text. |
|
1296 """ |
|
1297 self.__searchReplaceWidget.findNext() |
|
1298 |
|
1299 def findPrev(self): |
|
1300 """ |
|
1301 Public slot to find the next previous of text. |
|
1302 """ |
|
1303 self.__searchReplaceWidget.findPrev() |
|
1304 |
|
1305 def replace(self): |
|
1306 """ |
|
1307 Public method to replace the current selection. |
|
1308 """ |
|
1309 self.__searchReplaceWidget.replace() |
|
1310 |
|
1311 def replaceSearch(self): |
|
1312 """ |
|
1313 Public method to replace the current selection and search again. |
|
1314 """ |
|
1315 self.__searchReplaceWidget.replaceSearch() |
|
1316 |
|
1317 def replaceAll(self): |
|
1318 """ |
|
1319 Public method to replace all occurrences. |
|
1320 """ |
|
1321 self.__searchReplaceWidget.replaceAll() |
|
1322 |
|
1323 def selectionChanged(self, editor): |
|
1324 """ |
|
1325 Public slot tracking changes of selected text. |
|
1326 |
|
1327 @param editor reference to the editor |
|
1328 @type Editor |
|
1329 """ |
|
1330 self.__searchReplaceWidget.updateSelectionCheckBox(editor) |
|
1331 |
|
1332 @pyqtSlot(Editor) |
|
1333 def updateSelectionCheckBox(self, editor): |
|
1334 """ |
|
1335 Public slot to update the selection check box. |
|
1336 |
|
1337 @param editor reference to the editor (Editor) |
|
1338 """ |
|
1339 self.__searchReplaceWidget.updateSelectionCheckBox(editor) |
|
1340 |
|
1341 def show(self, text=''): |
|
1342 """ |
|
1343 Public slot to show the widget. |
|
1344 |
|
1345 @param text text to be shown in the findtext edit (string) |
|
1346 """ |
|
1347 self.__searchReplaceWidget.show(text) |
|
1348 super().show() |
|
1349 self.__enableScrollerButtons() |
|
1350 |
|
1351 def __slideLeft(self): |
|
1352 """ |
|
1353 Private slot to move the widget to the left, i.e. show contents to the |
|
1354 right. |
|
1355 """ |
|
1356 self.__slide(True) |
|
1357 |
|
1358 def __slideRight(self): |
|
1359 """ |
|
1360 Private slot to move the widget to the right, i.e. show contents to |
|
1361 the left. |
|
1362 """ |
|
1363 self.__slide(False) |
|
1364 |
|
1365 def __slide(self, toLeft): |
|
1366 """ |
|
1367 Private method to move the sliding widget. |
|
1368 |
|
1369 @param toLeft flag indicating to move to the left (boolean) |
|
1370 """ |
|
1371 scrollBar = self.__scroller.horizontalScrollBar() |
|
1372 stepSize = scrollBar.singleStep() |
|
1373 if toLeft: |
|
1374 stepSize = -stepSize |
|
1375 newValue = scrollBar.value() + stepSize |
|
1376 if newValue < 0: |
|
1377 newValue = 0 |
|
1378 elif newValue > scrollBar.maximum(): |
|
1379 newValue = scrollBar.maximum() |
|
1380 scrollBar.setValue(newValue) |
|
1381 self.__enableScrollerButtons() |
|
1382 |
|
1383 def __enableScrollerButtons(self): |
|
1384 """ |
|
1385 Private method to set the enabled state of the scroll buttons. |
|
1386 """ |
|
1387 scrollBar = self.__scroller.horizontalScrollBar() |
|
1388 self.__leftButton.setEnabled(scrollBar.value() > 0) |
|
1389 self.__rightButton.setEnabled(scrollBar.value() < scrollBar.maximum()) |
|
1390 |
|
1391 def resizeEvent(self, evt): |
|
1392 """ |
|
1393 Protected method to handle resize events. |
|
1394 |
|
1395 @param evt reference to the resize event (QResizeEvent) |
|
1396 """ |
|
1397 self.__enableScrollerButtons() |
|
1398 |
|
1399 super().resizeEvent(evt) |