QScintilla/SearchReplaceWidget.py

changeset 0
de9c2efb9d02
child 12
1d8dd9706f46
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2008 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the search and replace widget.
8 """
9
10 from PyQt4.QtCore import *
11 from PyQt4.QtGui import *
12
13 from Ui_SearchWidget import Ui_SearchWidget
14 from Ui_ReplaceWidget import Ui_ReplaceWidget
15
16 from E4Gui.E4Action import E4Action
17
18 import Preferences
19
20 import UI.PixmapCache
21
22 class SearchReplaceWidget(QWidget):
23 """
24 Class implementing the search and replace widget.
25
26 @signal searchListChanged emitted to indicate a change of the search list
27 """
28 def __init__(self, replace, vm, parent = None):
29 """
30 Constructor
31
32 @param replace flag indicating a replace widget is called
33 @param vm reference to the viewmanager object
34 @param parent parent widget of this widget (QWidget)
35 """
36 QWidget.__init__(self, parent)
37
38 self.viewmanager = vm
39 self.replace = replace
40
41 self.findHistory = vm.getSRHistory('search')
42 if replace:
43 self.replaceHistory = vm.getSRHistory('replace')
44 self.ui = Ui_ReplaceWidget()
45 whatsThis = self.trUtf8(r"""
46 <b>Find and Replace</b>
47 <p>This dialog is used to find some text and replace it with another text.
48 By checking the various checkboxes, the search can be made more specific. The search
49 string might be a regular expression. In a regular expression, special characters
50 interpreted are:</p>
51 """
52 )
53 else:
54 self.ui = Ui_SearchWidget()
55 whatsThis = self.trUtf8(r"""
56 <b>Find</b>
57 <p>This dialog is used to find some text. By checking the various checkboxes, the search
58 can be made more specific. The search string might be a regular expression. In a regular
59 expression, special characters interpreted are:</p>
60 """
61 )
62 self.ui.setupUi(self)
63 if not replace:
64 self.ui.wrapCheckBox.setChecked(True)
65
66 whatsThis += self.trUtf8(r"""
67 <table border="0">
68 <tr><td><code>.</code></td><td>Matches any character</td></tr>
69 <tr><td><code>\(</code></td><td>This marks the start of a region for tagging a match.</td></tr>
70 <tr><td><code>\)</code></td><td>This marks the end of a tagged region.</td></tr>
71 <tr><td><code>\n</code></td>
72 <td>Where <code>n</code> is 1 through 9 refers to the first through ninth tagged region
73 when replacing. For example, if the search string was <code>Fred\([1-9]\)XXX</code> and
74 the replace string was <code>Sam\1YYY</code>, when applied to <code>Fred2XXX</code> this
75 would generate <code>Sam2YYY</code>.</td></tr>
76 <tr><td><code>\&lt;</code></td>
77 <td>This matches the start of a word using Scintilla's definitions of words.</td></tr>
78 <tr><td><code>\&gt;</code></td>
79 <td>This matches the end of a word using Scintilla's definition of words.</td></tr>
80 <tr><td><code>\x</code></td>
81 <td>This allows you to use a character x that would otherwise have a special meaning. For
82 example, \[ would be interpreted as [ and not as the start of a character set.</td></tr>
83 <tr><td><code>[...]</code></td>
84 <td>This indicates a set of characters, for example, [abc] means any of the characters a,
85 b or c. You can also use ranges, for example [a-z] for any lower case character.</td></tr>
86 <tr><td><code>[^...]</code></td>
87 <td>The complement of the characters in the set. For example, [^A-Za-z] means any
88 character except an alphabetic character.</td></tr>
89 <tr><td><code>^</code></td>
90 <td>This matches the start of a line (unless used inside a set, see above).</td></tr>
91 <tr><td><code>$</code></td> <td>This matches the end of a line.</td></tr>
92 <tr><td><code>*</code></td>
93 <td>This matches 0 or more times. For example, <code>Sa*m</code> matches <code>Sm</code>,
94 <code>Sam</code>, <code>Saam</code>, <code>Saaam</code> and so on.</td></tr>
95 <tr><td><code>+</code></td>
96 <td>This matches 1 or more times. For example, <code>Sa+m</code> matches
97 <code>Sam</code>, <code>Saam</code>, <code>Saaam</code> and so on.</td></tr>
98 </table>
99 """)
100 self.setWhatsThis(whatsThis)
101
102 self.ui.closeButton.setIcon(UI.PixmapCache.getIcon("close.png"))
103 self.ui.findPrevButton.setIcon(UI.PixmapCache.getIcon("1leftarrow.png"))
104 self.ui.findNextButton.setIcon(UI.PixmapCache.getIcon("1rightarrow.png"))
105
106 if replace:
107 self.ui.replaceButton.setIcon(UI.PixmapCache.getIcon("editReplace.png"))
108 self.ui.replaceAllButton.setIcon(UI.PixmapCache.getIcon("editReplaceAll.png"))
109
110 self.connect(self.ui.findtextCombo.lineEdit(), SIGNAL("returnPressed()"),
111 self.__findByReturnPressed)
112 if replace:
113 self.connect(self.ui.replacetextCombo.lineEdit(), SIGNAL("returnPressed()"),
114 self.on_replaceButton_clicked)
115
116 self.havefound = False
117 self.__pos = None
118 self.__findBackwards = False
119 self.__selection = None
120 self.__finding = False
121
122 def on_findtextCombo_editTextChanged(self, txt):
123 """
124 Private slot to enable/disable the find buttons.
125 """
126 if not txt:
127 self.ui.findNextButton.setEnabled(False)
128 self.ui.findPrevButton.setEnabled(False)
129 if self.replace:
130 self.ui.replaceButton.setEnabled(False)
131 self.ui.replaceAllButton.setEnabled(False)
132 else:
133 self.ui.findNextButton.setEnabled(True)
134 self.ui.findPrevButton.setEnabled(True)
135 if self.replace:
136 self.ui.replaceButton.setEnabled(False)
137 self.ui.replaceAllButton.setEnabled(True)
138
139 @pyqtSlot()
140 def on_findNextButton_clicked(self):
141 """
142 Private slot to find the next occurrence of text.
143 """
144 self.findNext()
145
146 def findNext(self):
147 """
148 Public slot to find the next occurrence of text.
149 """
150 if not self.havefound or not self.ui.findtextCombo.currentText():
151 self.show(self.viewmanager.textForFind())
152 return
153
154 self.__findBackwards = False
155 txt = self.ui.findtextCombo.currentText()
156
157 # This moves any previous occurrence of this statement to the head
158 # of the list and updates the combobox
159 if txt in self.findHistory:
160 self.findHistory.remove(txt)
161 self.findHistory.insert(0, txt)
162 self.ui.findtextCombo.clear()
163 self.ui.findtextCombo.addItems(self.findHistory)
164 self.emit(SIGNAL('searchListChanged'))
165
166 ok = self.__findNextPrev(txt, False)
167 if ok:
168 if self.replace:
169 self.ui.replaceButton.setEnabled(True)
170 else:
171 QMessageBox.information(self, self.windowTitle(),
172 self.trUtf8("'{0}' was not found.").format(txt))
173
174 @pyqtSlot()
175 def on_findPrevButton_clicked(self):
176 """
177 Private slot to find the previous occurrence of text.
178 """
179 self.findPrev()
180
181 def findPrev(self):
182 """
183 Public slot to find the next previous of text.
184 """
185 if not self.havefound or not self.ui.findtextCombo.currentText():
186 self.show(self.viewmanager.textForFind())
187 return
188
189 self.__findBackwards = True
190 txt = self.ui.findtextCombo.currentText()
191
192 # This moves any previous occurrence of this statement to the head
193 # of the list and updates the combobox
194 if txt in self.findHistory:
195 self.findHistory.remove(txt)
196 self.findHistory.insert(0, txt)
197 self.ui.findtextCombo.clear()
198 self.ui.findtextCombo.addItems(self.findHistory)
199 self.emit(SIGNAL('searchListChanged'))
200
201 ok = self.__findNextPrev(txt, True)
202 if ok:
203 if self.replace:
204 self.ui.replaceButton.setEnabled(True)
205 else:
206 QMessageBox.information(self, self.windowTitle(),
207 self.trUtf8("'{0}' was not found.").format(txt))
208
209 def __findByReturnPressed(self):
210 """
211 Private slot to handle the returnPressed signal of the findtext combobox.
212 """
213 if self.__findBackwards:
214 self.findPrev()
215 else:
216 self.findNext()
217
218 def __markOccurrences(self, txt):
219 """
220 Private method to mark all occurrences of the search text.
221
222 @param txt text to search for (string)
223 """
224 aw = self.viewmanager.activeWindow()
225 lineFrom = 0
226 indexFrom = 0
227 lineTo = -1
228 indexTo = -1
229 if self.ui.selectionCheckBox.isChecked():
230 lineFrom = self.__selection[0]
231 indexFrom = self.__selection[1]
232 lineTo = self.__selection[2]
233 indexTo = self.__selection[3]
234
235 aw.clearSearchIndicators()
236 ok = aw.findFirstTarget(txt,
237 self.ui.regexpCheckBox.isChecked(),
238 self.ui.caseCheckBox.isChecked(),
239 self.ui.wordCheckBox.isChecked(),
240 lineFrom, indexFrom, lineTo, indexTo)
241 while ok:
242 tgtPos, tgtLen = aw.getFoundTarget()
243 aw.setSearchIndicator(tgtPos, tgtLen)
244 ok = aw.findNextTarget()
245
246 def __findNextPrev(self, txt, backwards):
247 """
248 Private method to find the next occurrence of the search text.
249
250 @param txt text to search for (string)
251 @param backwards flag indicating a backwards search (boolean)
252 @return flag indicating success (boolean)
253 """
254 self.__finding = True
255
256 if Preferences.getEditor("SearchMarkersEnabled"):
257 self.__markOccurrences(txt)
258
259 aw = self.viewmanager.activeWindow()
260 cline, cindex = aw.getCursorPosition()
261
262 ok = True
263 lineFrom, indexFrom, lineTo, indexTo = aw.getSelection()
264 if backwards:
265 if self.ui.selectionCheckBox.isChecked() and \
266 (lineFrom, indexFrom, lineTo, indexTo) == self.__selection:
267 # initial call
268 line = self.__selection[2]
269 index = self.__selection[3]
270 else:
271 line = lineFrom
272 index = indexFrom - 1
273 if self.ui.selectionCheckBox.isChecked() and \
274 line == self.__selection[0] and \
275 index >= 0 and \
276 index < self.__selection[1]:
277 ok = False
278
279 if ok and index < 0:
280 line -= 1
281 if self.ui.selectionCheckBox.isChecked():
282 if line < self.__selection[0]:
283 if self.ui.wrapCheckBox.isChecked():
284 line = self.__selection[2]
285 index = self.__selection[3]
286 else:
287 ok = False
288 else:
289 index = aw.lineLength(line)
290 else:
291 if line < 0:
292 if self.ui.wrapCheckBox.isChecked():
293 line = aw.lines() - 1
294 index = aw.lineLength(line)
295 else:
296 ok = False
297 else:
298 index = aw.lineLength(line)
299 else:
300 if self.ui.selectionCheckBox.isChecked() and \
301 (lineFrom, indexFrom, lineTo, indexTo) == self.__selection:
302 # initial call
303 line = self.__selection[0]
304 index = self.__selection[1]
305 else:
306 line = lineTo
307 index = indexTo
308
309 if ok:
310 ok = aw.findFirst(txt,
311 self.ui.regexpCheckBox.isChecked(),
312 self.ui.caseCheckBox.isChecked(),
313 self.ui.wordCheckBox.isChecked(),
314 self.ui.wrapCheckBox.isChecked(),
315 not backwards,
316 line, index)
317
318 if ok and self.ui.selectionCheckBox.isChecked():
319 lineFrom, indexFrom, lineTo, indexTo = aw.getSelection()
320 if (lineFrom == self.__selection[0] and indexFrom >= self.__selection[1]) or \
321 (lineFrom > self.__selection[0] and lineFrom < self.__selection[2]) or \
322 (lineFrom == self.__selection[2] and indexFrom <= self.__selection[3]):
323 ok = True
324 else:
325 if self.ui.wrapCheckBox.isChecked():
326 # try it again
327 if backwards:
328 line = self.__selection[2]
329 index = self.__selection[3]
330 else:
331 line = self.__selection[0]
332 index = self.__selection[1]
333 ok = aw.findFirst(txt,
334 self.ui.regexpCheckBox.isChecked(),
335 self.ui.caseCheckBox.isChecked(),
336 self.ui.wordCheckBox.isChecked(),
337 self.ui.wrapCheckBox.isChecked(),
338 not backwards,
339 line, index)
340 if ok:
341 lineFrom, indexFrom, lineTo, indexTo = aw.getSelection()
342 if (lineFrom == self.__selection[0] and \
343 indexFrom >= self.__selection[1]) or \
344 (lineFrom > self.__selection[0] and \
345 lineFrom < self.__selection[2]) or \
346 (lineFrom == self.__selection[2] \
347 and indexFrom <= self.__selection[3]):
348 ok = True
349 else:
350 ok = False
351 aw.selectAll(False)
352 aw.setCursorPosition(cline, cindex)
353 aw.ensureCursorVisible()
354 else:
355 ok = False
356 aw.selectAll(False)
357 aw.setCursorPosition(cline, cindex)
358 aw.ensureCursorVisible()
359
360 self.__finding = False
361
362 return ok
363
364 def __showFind(self, text = ''):
365 """
366 Private method to display this widget in find mode.
367
368 @param text text to be shown in the findtext edit (string)
369 """
370 self.replace = False
371
372 self.ui.findtextCombo.clear()
373 self.ui.findtextCombo.addItems(self.findHistory)
374 self.ui.findtextCombo.setEditText(text)
375 self.ui.findtextCombo.lineEdit().selectAll()
376 self.ui.findtextCombo.setFocus()
377
378 self.ui.caseCheckBox.setChecked(False)
379 self.ui.wordCheckBox.setChecked(False)
380 self.ui.wrapCheckBox.setChecked(True)
381 self.ui.regexpCheckBox.setChecked(False)
382
383 aw = self.viewmanager.activeWindow()
384 self.updateSelectionCheckBox(aw)
385
386 self.havefound = True
387 self.__findBackwards = False
388
389 def selectionChanged(self):
390 """
391 Public slot tracking changes of selected text.
392 """
393 aw = self.sender()
394 self.updateSelectionCheckBox(aw)
395
396 def updateSelectionCheckBox(self, editor):
397 """
398 Public slot to update the selection check box.
399
400 @param editor reference to the editor (Editor)
401 """
402 if not self.__finding:
403 if editor.hasSelectedText():
404 line1, index1, line2, index2 = editor.getSelection()
405 if line1 != line2:
406 self.ui.selectionCheckBox.setEnabled(True)
407 self.ui.selectionCheckBox.setChecked(True)
408 self.__selection = (line1, index1, line2, index2)
409 return
410
411 self.ui.selectionCheckBox.setEnabled(False)
412 self.ui.selectionCheckBox.setChecked(False)
413 self.__selection = None
414
415 @pyqtSlot()
416 def on_replaceButton_clicked(self):
417 """
418 Private slot to replace one occurrence of text.
419 """
420 self.__finding = True
421
422 # Check enabled status due to dual purpose usage of this method
423 if not self.ui.replaceButton.isEnabled():
424 return
425
426 ftxt = self.ui.findtextCombo.currentText()
427 rtxt = self.ui.replacetextCombo.currentText()
428
429 # This moves any previous occurrence of this statement to the head
430 # of the list and updates the combobox
431 if rtxt in self.replaceHistory:
432 self.replaceHistory.remove(rtxt)
433 self.replaceHistory.insert(0, rtxt)
434 self.ui.replacetextCombo.clear()
435 self.ui.replacetextCombo.addItems(self.replaceHistory)
436
437 aw = self.viewmanager.activeWindow()
438 aw.replace(rtxt)
439 ok = self.__findNextPrev(ftxt, self.__findBackwards)
440
441 if not ok:
442 self.ui.replaceButton.setEnabled(False)
443 QMessageBox.information(self, self.windowTitle(),
444 self.trUtf8("'{0}' was not found.").format(ftxt))
445
446 self.__finding = False
447
448 @pyqtSlot()
449 def on_replaceAllButton_clicked(self):
450 """
451 Private slot to replace all occurrences of text.
452 """
453 self.__finding = True
454
455 replacements = 0
456 ftxt = self.ui.findtextCombo.currentText()
457 rtxt = self.ui.replacetextCombo.currentText()
458
459 # This moves any previous occurrence of this statement to the head
460 # of the list and updates the combobox
461 if ftxt in self.findHistory:
462 self.findHistory.remove(ftxt)
463 self.findHistory.insert(0, ftxt)
464 self.ui.findtextCombo.clear()
465 self.ui.findtextCombo.addItems(self.findHistory)
466
467 if rtxt in self.replaceHistory:
468 self.replaceHistory.remove(rtxt)
469 self.replaceHistory.insert(0, rtxt)
470 self.ui.replacetextCombo.clear()
471 self.ui.replacetextCombo.addItems(self.replaceHistory)
472
473 aw = self.viewmanager.activeWindow()
474 cline, cindex = aw.getCursorPosition()
475 if self.ui.selectionCheckBox.isChecked():
476 line = self.__selection[0]
477 index = self.__selection[1]
478 else:
479 line = 0
480 index = 0
481 ok = aw.findFirst(ftxt,
482 self.ui.regexpCheckBox.isChecked(),
483 self.ui.caseCheckBox.isChecked(),
484 self.ui.wordCheckBox.isChecked(),
485 False, True, line, index)
486
487 if ok and self.ui.selectionCheckBox.isChecked():
488 lineFrom, indexFrom, lineTo, indexTo = aw.getSelection()
489 if (lineFrom == self.__selection[0] and indexFrom >= self.__selection[1]) or \
490 (lineFrom > self.__selection[0] and lineFrom < self.__selection[2]) or \
491 (lineFrom == self.__selection[2] and indexFrom <= self.__selection[3]):
492 ok = True
493 else:
494 ok = False
495 aw.selectAll(False)
496 aw.setCursorPosition(cline, cindex)
497 aw.ensureCursorVisible()
498
499 found = ok
500
501 aw.beginUndoAction()
502 wordWrap = self.ui.wrapCheckBox.isChecked()
503 self.ui.wrapCheckBox.setChecked(False)
504 while ok:
505 aw.replace(rtxt)
506 replacements += 1
507 ok = self.__findNextPrev(ftxt, self.__findBackwards)
508 self.__finding = True
509 aw.endUndoAction()
510 if wordWrap:
511 self.ui.wrapCheckBox.setChecked(True)
512 self.ui.replaceButton.setEnabled(False)
513
514 if found:
515 QMessageBox.information(self, self.windowTitle(),
516 self.trUtf8("Replaced {0} occurrences.")
517 .format(replacements))
518 else:
519 QMessageBox.information(self, self.windowTitle(),
520 self.trUtf8("Nothing replaced because '{0}' was not found.")
521 .format(ftxt))
522
523 aw.setCursorPosition(cline, cindex)
524 aw.ensureCursorVisible()
525
526 self.__finding = False
527
528 def __showReplace(self, text=''):
529 """
530 Private slot to display this widget in replace mode.
531
532 @param text text to be shown in the findtext edit
533 """
534 self.replace = True
535
536 self.ui.findtextCombo.clear()
537 self.ui.findtextCombo.addItems(self.findHistory)
538 self.ui.findtextCombo.setEditText(text)
539 self.ui.findtextCombo.lineEdit().selectAll()
540 self.ui.findtextCombo.setFocus()
541
542 self.ui.replacetextCombo.clear()
543 self.ui.replacetextCombo.addItems(self.replaceHistory)
544 self.ui.replacetextCombo.setEditText('')
545
546 self.ui.caseCheckBox.setChecked(False)
547 self.ui.wordCheckBox.setChecked(False)
548 self.ui.regexpCheckBox.setChecked(False)
549
550 self.havefound = True
551
552 aw = self.viewmanager.activeWindow()
553 self.updateSelectionCheckBox(aw)
554 if aw.hasSelectedText():
555 line1, index1, line2, index2 = aw.getSelection()
556 if line1 == line2:
557 aw.setSelection(line1, index1, line1, index1)
558 self.findNext()
559
560 def show(self, text = ''):
561 """
562 Overridden slot from QWidget.
563
564 @param text text to be shown in the findtext edit (string)
565 """
566 if self.replace:
567 self.__showReplace(text)
568 else:
569 self.__showFind(text)
570 QWidget.show(self)
571 self.activateWindow()
572
573 @pyqtSlot()
574 def on_closeButton_clicked(self):
575 """
576 Private slot to close the widget.
577 """
578 self.close()
579
580 def keyPressEvent(self, event):
581 """
582 Protected slot to handle key press events.
583
584 @param event reference to the key press event (QKeyEvent)
585 """
586 if event.key() == Qt.Key_Escape:
587 aw = self.viewmanager.activeWindow()
588 if aw:
589 aw.setFocus(Qt.ActiveWindowFocusReason)
590 event.accept()
591 self.close()

eric ide

mercurial