eric7/UI/FindFileWidget.py

branch
eric7
changeset 8614
4a3a68e51b92
parent 8358
144a6b854f70
child 8632
f25cd4b94eb0
equal deleted inserted replaced
8613:2af21036b293 8614:4a3a68e51b92
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to search for text in files.
8 """
9
10 import os
11 import re
12
13 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint
14 from PyQt6.QtGui import QCursor
15 from PyQt6.QtWidgets import (
16 QWidget, QApplication, QMenu, QTreeWidgetItem, QComboBox
17 )
18
19 from EricWidgets.EricApplication import ericApp
20 from EricWidgets import EricMessageBox
21 from EricWidgets.EricPathPicker import EricPathPickerModes
22
23 from .Ui_FindFileWidget import Ui_FindFileWidget
24
25 import Preferences
26 import UI.PixmapCache
27 import Utilities
28
29
30 class FindFileWidget(QWidget, Ui_FindFileWidget):
31 """
32 Class implementing a widget to search for text in files and replace it
33 with some other text.
34
35 The occurrences found are displayed in a tree showing the file name,
36 the line number and the text found. The file will be opened upon a double
37 click onto the respective entry of the list. If the widget is in replace
38 mode the line below shows the text after replacement. Replacements can
39 be authorized by ticking them on. Pressing the replace button performs
40 all ticked replacement operations.
41
42 @signal sourceFile(str, int, str, int, int) emitted to open a source file
43 at a specificline
44 @signal designerFile(str) emitted to open a Qt-Designer file
45 """
46 sourceFile = pyqtSignal(str, int, str, int, int)
47 designerFile = pyqtSignal(str)
48
49 lineRole = Qt.ItemDataRole.UserRole + 1
50 startRole = Qt.ItemDataRole.UserRole + 2
51 endRole = Qt.ItemDataRole.UserRole + 3
52 replaceRole = Qt.ItemDataRole.UserRole + 4
53 md5Role = Qt.ItemDataRole.UserRole + 5
54
55 def __init__(self, project, parent=None):
56 """
57 Constructor
58
59 @param project reference to the project object
60 @type Project
61 @param parent parent widget of this dialog (defaults to None)
62 @type QWidget (optional)
63 """
64 super().__init__(parent)
65 self.setupUi(self)
66 self.setWindowFlags(Qt.WindowType.Window)
67
68 self.layout().setContentsMargins(0, 3, 0, 0)
69
70 self.dirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
71 self.dirPicker.setInsertPolicy(QComboBox.InsertPolicy.InsertAtTop)
72 self.dirPicker.setSizeAdjustPolicy(
73 QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
74
75 self.stopButton.setEnabled(False)
76 self.stopButton.clicked.connect(self.__stopSearch)
77 self.stopButton.setIcon(UI.PixmapCache.getIcon("stopLoading"))
78
79 self.findButton.setEnabled(False)
80 self.findButton.clicked.connect(self.__doSearch)
81 self.findButton.setIcon(UI.PixmapCache.getIcon("find"))
82
83 self.replaceButton.setIcon(UI.PixmapCache.getIcon("editReplace"))
84
85 self.modeToggleButton.clicked.connect(self.__toggleReplaceMode)
86
87 self.findProgressLabel.setMaximumWidth(550)
88
89 self.searchHistory = Preferences.toList(
90 Preferences.Prefs.settings.value(
91 "FindFileWidget/SearchHistory"))
92 self.findtextCombo.lineEdit().setClearButtonEnabled(True)
93 self.findtextCombo.lineEdit().returnPressed.connect(self.__doSearch)
94 self.findtextCombo.setCompleter(None)
95 self.findtextCombo.addItems(self.searchHistory)
96 self.findtextCombo.setEditText("")
97
98 self.replaceHistory = Preferences.toList(
99 Preferences.Prefs.settings.value(
100 "FindFileWidget/ReplaceHistory"))
101 self.replacetextCombo.lineEdit().setClearButtonEnabled(True)
102 self.replacetextCombo.lineEdit().returnPressed.connect(self.__doSearch)
103 self.replacetextCombo.setCompleter(None)
104 self.replacetextCombo.addItems(self.replaceHistory)
105 self.replacetextCombo.setEditText("")
106
107 self.dirHistory = Preferences.toList(
108 Preferences.Prefs.settings.value(
109 "FindFileWidget/DirectoryHistory"))
110 self.dirPicker.addItems(self.dirHistory)
111 self.dirPicker.setText("")
112
113 self.excludeHiddenCheckBox.setChecked(Preferences.toBool(
114 Preferences.Prefs.settings.value(
115 "FindFileWidget/ExcludeHidden", True)
116 ))
117
118 self.project = project
119 self.project.projectOpened.connect(self.__projectOpened)
120 self.project.projectClosed.connect(self.__projectClosed)
121
122 self.__standardListFont = self.findList.font()
123 self.findList.headerItem().setText(self.findList.columnCount(), "")
124 self.findList.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder)
125 self.__section0Size = self.findList.header().sectionSize(0)
126 self.findList.setExpandsOnDoubleClick(False)
127
128 # Qt Designer form files
129 self.filterForms = r'.*\.ui$'
130 self.formsExt = ['*.ui']
131
132 # Corba interface files
133 self.filterInterfaces = r'.*\.idl$'
134 self.interfacesExt = ['*.idl']
135
136 # Protobuf protocol files
137 self.filterProtocols = r'.*\.proto$'
138 self.protocolsExt = ['*.proto']
139
140 # Qt resources files
141 self.filterResources = r'.*\.qrc$'
142 self.resourcesExt = ['*.qrc']
143
144 self.__cancelSearch = False
145 self.__lastFileItem = None
146 self.__populating = False
147
148 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
149 self.customContextMenuRequested.connect(self.__contextMenuRequested)
150
151 self.__replaceMode = True
152 self.__toggleReplaceMode()
153
154 def __createItem(self, file, line, text, start, end, replTxt="", md5=""):
155 """
156 Private method to create an entry in the file list.
157
158 @param file filename of file
159 @type str
160 @param line line number
161 @type int
162 @param text text found
163 @type str
164 @param start start position of match
165 @type int
166 @param end end position of match
167 @type int
168 @param replTxt text with replacements applied (defaults to "")
169 @type str (optional)
170 @param md5 MD5 hash of the file (defaults to "")
171 @type str (optional)
172 """
173 if self.__lastFileItem is None:
174 # It's a new file
175 self.__lastFileItem = QTreeWidgetItem(self.findList, [file])
176 self.__lastFileItem.setFirstColumnSpanned(True)
177 self.__lastFileItem.setExpanded(True)
178 if self.__replaceMode:
179 self.__lastFileItem.setFlags(
180 self.__lastFileItem.flags() |
181 Qt.ItemFlag.ItemIsUserCheckable |
182 Qt.ItemFlag.ItemIsAutoTristate)
183 self.__lastFileItem.setData(0, self.md5Role, md5)
184
185 itm = QTreeWidgetItem(self.__lastFileItem)
186 itm.setTextAlignment(0, Qt.AlignmentFlag.AlignRight)
187 itm.setData(0, Qt.ItemDataRole.DisplayRole, line)
188 itm.setData(1, Qt.ItemDataRole.DisplayRole, text)
189 itm.setData(0, self.lineRole, line)
190 itm.setData(0, self.startRole, start)
191 itm.setData(0, self.endRole, end)
192 itm.setData(0, self.replaceRole, replTxt)
193 if self.__replaceMode:
194 itm.setFlags(itm.flags() |
195 Qt.ItemFlag.ItemIsUserCheckable)
196 itm.setCheckState(0, Qt.CheckState.Checked)
197 self.replaceButton.setEnabled(True)
198
199 def activate(self, replaceMode=False, txt="", searchDir="",
200 openFiles=False):
201 """
202 Public method to activate the widget with a given mode, a text
203 to search for and some search parameters.
204
205 @param replaceMode flag indicating replacement mode (defaults to False)
206 @type bool (optional)
207 @param txt text to be searched for (defaults to "")
208 @type str (optional)
209 @param searchDir directory to search in (defaults to "")
210 @type str (optional)
211 @param openFiles flag indicating to operate on open files only
212 (defaults to False)
213 @type bool (optional)
214 """
215 if self.project.isOpen():
216 self.projectButton.setEnabled(True)
217 self.projectButton.setChecked(True)
218 else:
219 self.projectButton.setEnabled(False)
220 self.dirButton.setChecked(True)
221
222 self.findtextCombo.setEditText(txt)
223 self.findtextCombo.lineEdit().selectAll()
224 self.findtextCombo.setFocus()
225
226 if self.__replaceMode != replaceMode:
227 self.__toggleReplaceMode()
228
229 if searchDir:
230 self.setSearchDirectory(searchDir)
231 if openFiles:
232 self.setOpenFiles()
233
234 @pyqtSlot()
235 def __toggleReplaceMode(self):
236 """
237 Private slot to toggle the dialog mode.
238 """
239 self.__replaceMode = not self.__replaceMode
240
241 # change some interface elements and properties
242 self.findList.clear()
243 if self.__replaceMode:
244 self.replaceButton.show()
245 self.replaceLabel.show()
246 self.replacetextCombo.show()
247
248 self.replaceButton.setEnabled(False)
249 self.replacetextCombo.setEditText("")
250
251 font = Preferences.getEditorOtherFonts("MonospacedFont")
252 self.findList.setFont(font)
253
254 self.modeToggleButton.setIcon(UI.PixmapCache.getIcon("1uparrow"))
255 else:
256 self.replaceLabel.hide()
257 self.replacetextCombo.hide()
258 self.replaceButton.hide()
259
260 self.findList.setFont(self.__standardListFont)
261
262 self.modeToggleButton.setIcon(UI.PixmapCache.getIcon("1downarrow"))
263
264 @pyqtSlot()
265 def __projectOpened(self):
266 """
267 Private slot to react to the opening of a project.
268 """
269 self.projectButton.setEnabled(True)
270 self.projectButton.setChecked(True)
271
272 @pyqtSlot()
273 def __projectClosed(self):
274 """
275 Private slot to react to the closing of a project.
276 """
277 self.projectButton.setEnabled(False)
278 if self.projectButton.isChecked():
279 self.dirButton.setChecked(True)
280
281 @pyqtSlot(str)
282 def on_findtextCombo_editTextChanged(self, text):
283 """
284 Private slot to handle the editTextChanged signal of the find
285 text combo.
286
287 @param text (ignored)
288 """
289 self.__enableFindButton()
290
291 @pyqtSlot(str)
292 def on_replacetextCombo_editTextChanged(self, text):
293 """
294 Private slot to handle the editTextChanged signal of the replace
295 text combo.
296
297 @param text (ignored)
298 """
299 self.__enableFindButton()
300
301 @pyqtSlot(str)
302 def on_dirPicker_editTextChanged(self, text):
303 """
304 Private slot to handle the textChanged signal of the directory
305 picker.
306
307 @param text (ignored)
308 """
309 self.__enableFindButton()
310
311 @pyqtSlot()
312 def on_projectButton_clicked(self):
313 """
314 Private slot to handle the selection of the 'Project' radio button.
315 """
316 self.__enableFindButton()
317
318 @pyqtSlot()
319 def on_dirButton_clicked(self):
320 """
321 Private slot to handle the selection of the 'Directory' radio button.
322 """
323 self.__enableFindButton()
324
325 @pyqtSlot()
326 def on_openFilesButton_clicked(self):
327 """
328 Private slot to handle the selection of the 'Open Files' radio button.
329 """
330 self.__enableFindButton()
331
332 @pyqtSlot()
333 def on_filterCheckBox_clicked(self):
334 """
335 Private slot to handle the selection of the file filter check box.
336 """
337 self.__enableFindButton()
338
339 @pyqtSlot(str)
340 def on_filterEdit_textEdited(self, text):
341 """
342 Private slot to handle the textChanged signal of the file filter edit.
343
344 @param text (ignored)
345 """
346 self.__enableFindButton()
347
348 @pyqtSlot()
349 def __enableFindButton(self):
350 """
351 Private slot called to enable the find button.
352 """
353 if (
354 self.findtextCombo.currentText() == "" or
355 (self.dirButton.isChecked() and
356 (self.dirPicker.currentText() == "" or
357 not os.path.exists(os.path.abspath(
358 self.dirPicker.currentText())))) or
359 (self.filterCheckBox.isChecked() and
360 self.filterEdit.text() == "")
361 ):
362 self.findButton.setEnabled(False)
363 else:
364 self.findButton.setEnabled(True)
365
366 def __stripEol(self, txt):
367 """
368 Private method to strip the eol part.
369
370 @param txt line of text that should be treated
371 @type str
372 @return text with eol stripped
373 @rtype str
374 """
375 return txt.replace("\r", "").replace("\n", "")
376
377 @pyqtSlot()
378 def __stopSearch(self):
379 """
380 Private slot to handle the stop button being pressed.
381 """
382 self.__cancelSearch = True
383
384 @pyqtSlot()
385 def __doSearch(self):
386 """
387 Private slot to handle the find button being pressed.
388 """
389 if (
390 self.__replaceMode and
391 not ericApp().getObject("ViewManager").checkAllDirty()
392 ):
393 return
394
395 self.__cancelSearch = False
396
397 if self.filterCheckBox.isChecked():
398 fileFilter = self.filterEdit.text()
399 fileFilterList = [
400 "^{0}$".format(filter.replace(".", r"\.").replace("*", ".*"))
401 for filter in fileFilter.split(";")
402 ]
403 filterRe = re.compile("|".join(fileFilterList))
404
405 if self.projectButton.isChecked():
406 if self.filterCheckBox.isChecked():
407 files = [
408 self.project.getRelativePath(file)
409 for file in
410 self.__getFileList(
411 self.project.getProjectPath(),
412 filterRe,
413 excludeHiddenDirs=self.excludeHiddenCheckBox
414 .isChecked(),
415 )
416 ]
417 else:
418 files = []
419 if self.sourcesCheckBox.isChecked():
420 files += self.project.pdata["SOURCES"]
421 if self.formsCheckBox.isChecked():
422 files += self.project.pdata["FORMS"]
423 if self.interfacesCheckBox.isChecked():
424 files += self.project.pdata["INTERFACES"]
425 if self.protocolsCheckBox.isChecked():
426 files += self.project.pdata["PROTOCOLS"]
427 if self.resourcesCheckBox.isChecked():
428 files += self.project.pdata["RESOURCES"]
429 elif self.dirButton.isChecked():
430 if not self.filterCheckBox.isChecked():
431 filters = []
432 if (
433 self.project.isOpen() and
434 os.path.abspath(self.dirPicker.currentText()).startswith(
435 self.project.getProjectPath())
436 ):
437 if self.sourcesCheckBox.isChecked():
438 filters.extend([
439 "^{0}$".format(
440 assoc.replace(".", r"\.").replace("*", ".*")
441 ) for assoc in
442 self.project.getFiletypeAssociations("SOURCES")
443 ])
444 if self.formsCheckBox.isChecked():
445 filters.extend([
446 "^{0}$".format(
447 assoc.replace(".", r"\.").replace("*", ".*")
448 ) for assoc in
449 self.project.getFiletypeAssociations("FORMS")
450 ])
451 if self.interfacesCheckBox.isChecked():
452 filters.extend([
453 "^{0}$".format(
454 assoc.replace(".", r"\.").replace("*", ".*")
455 ) for assoc in
456 self.project.getFiletypeAssociations("INTERFACES")
457 ])
458 if self.protocolsCheckBox.isChecked():
459 filters.extend([
460 "^{0}$".format(
461 assoc.replace(".", r"\.").replace("*", ".*")
462 ) for assoc in
463 self.project.getFiletypeAssociations("PROTOCOLS")
464 ])
465 if self.resourcesCheckBox.isChecked():
466 filters.extend([
467 "^{0}$".format(
468 assoc.replace(".", r"\.").replace("*", ".*")
469 ) for assoc in
470 self.project.getFiletypeAssociations("RESOURCES")
471 ])
472 else:
473 if self.sourcesCheckBox.isChecked():
474 filters.extend([
475 "^{0}$".format(
476 assoc.replace(".", r"\.").replace("*", ".*"))
477 for assoc in list(
478 Preferences.getEditorLexerAssocs().keys())
479 if assoc not in
480 self.formsExt + self.interfacesExt +
481 self.protocolsExt + self.resourcesExt
482 ])
483 if self.formsCheckBox.isChecked():
484 filters.append(self.filterForms)
485 if self.interfacesCheckBox.isChecked():
486 filters.append(self.filterInterfaces)
487 if self.protocolsCheckBox.isChecked():
488 filters.append(self.filterProtocols)
489 if self.resourcesCheckBox.isChecked():
490 filters.append(self.filterResources)
491 filterString = "|".join(filters)
492 filterRe = re.compile(filterString)
493 files = self.__getFileList(
494 os.path.abspath(self.dirPicker.currentText()),
495 filterRe,
496 excludeHiddenDirs=self.excludeHiddenCheckBox.isChecked(),
497 excludeHiddenFiles=self.excludeHiddenCheckBox.isChecked(),
498 )
499 elif self.openFilesButton.isChecked():
500 vm = ericApp().getObject("ViewManager")
501 vm.checkAllDirty()
502 files = vm.getOpenFilenames()
503
504 self.findList.clear()
505 QApplication.processEvents()
506 QApplication.processEvents()
507 self.findProgress.setMaximum(len(files))
508
509 # retrieve the values
510 reg = self.regexpCheckBox.isChecked()
511 wo = self.wordCheckBox.isChecked()
512 cs = self.caseCheckBox.isChecked()
513 ct = self.findtextCombo.currentText()
514 txt = ct if reg else re.escape(ct)
515 if wo:
516 txt = "\\b{0}\\b".format(txt)
517 flags = re.UNICODE
518 if not cs:
519 flags |= re.IGNORECASE
520 try:
521 search = re.compile(txt, flags)
522 except re.error as why:
523 EricMessageBox.critical(
524 self,
525 self.tr("Invalid search expression"),
526 self.tr("""<p>The search expression is not valid.</p>"""
527 """<p>Error: {0}</p>""").format(str(why)))
528 self.stopButton.setEnabled(False)
529 self.findButton.setEnabled(True)
530 return
531 # reset the findtextCombo
532 if ct in self.searchHistory:
533 self.searchHistory.remove(ct)
534 self.searchHistory.insert(0, ct)
535 self.findtextCombo.clear()
536 self.findtextCombo.addItems(self.searchHistory)
537 Preferences.Prefs.settings.setValue(
538 "FindFileWidget/SearchHistory",
539 self.searchHistory[:30])
540 Preferences.Prefs.settings.setValue(
541 "FindFileWidget/ExcludeHidden",
542 self.excludeHiddenCheckBox.isChecked())
543
544 if self.__replaceMode:
545 replTxt = self.replacetextCombo.currentText()
546 if replTxt in self.replaceHistory:
547 self.replaceHistory.remove(replTxt)
548 self.replaceHistory.insert(0, replTxt)
549 self.replacetextCombo.clear()
550 self.replacetextCombo.addItems(self.replaceHistory)
551 Preferences.Prefs.settings.setValue(
552 "FindFileWidget/ReplaceHistory",
553 self.replaceHistory[:30])
554
555 if self.dirButton.isChecked():
556 searchDir = self.dirPicker.currentText()
557 if searchDir in self.dirHistory:
558 self.dirHistory.remove(searchDir)
559 self.dirHistory.insert(0, searchDir)
560 self.dirPicker.clear()
561 self.dirPicker.addItems(self.dirHistory)
562 self.dirPicker.setText(self.dirHistory[0])
563 Preferences.Prefs.settings.setValue(
564 "FindFileWidget/DirectoryHistory",
565 self.dirHistory[:30])
566
567 # set the button states
568 self.stopButton.setEnabled(True)
569 self.findButton.setEnabled(False)
570
571 # now go through all the files
572 self.__populating = True
573 self.findList.setUpdatesEnabled(False)
574 occurrences = 0
575 fileOccurrences = 0
576 for progress, file in enumerate(files, start=1):
577 self.__lastFileItem = None
578 found = False
579 if self.__cancelSearch:
580 break
581
582 self.findProgressLabel.setPath(file)
583
584 fn = (
585 os.path.join(self.project.ppath, file)
586 if self.projectButton.isChecked() else
587 file
588 )
589 # read the file and split it into textlines
590 try:
591 text, encoding, hashStr = Utilities.readEncodedFileWithHash(fn)
592 lines = text.splitlines(True)
593 except (UnicodeError, OSError):
594 self.findProgress.setValue(progress)
595 continue
596
597 # now perform the search and display the lines found
598 for count, line in enumerate(lines, start=1):
599 if self.__cancelSearch:
600 break
601
602 contains = search.search(line)
603 if contains:
604 occurrences += 1
605 found = True
606 start = contains.start()
607 end = contains.end()
608 if self.__replaceMode:
609 rline = search.sub(replTxt, line)
610 else:
611 rline = ""
612 line = self.__stripEol(line)
613 if len(line) > 1024:
614 line = "{0} ...".format(line[:1024])
615 if self.__replaceMode:
616 if len(rline) > 1024:
617 rline = "{0} ...".format(line[:1024])
618 line = "- {0}\n+ {1}".format(
619 line, self.__stripEol(rline))
620 self.__createItem(file, count, line, start, end,
621 rline, hashStr)
622
623 QApplication.processEvents()
624
625 if found:
626 fileOccurrences += 1
627 self.findProgress.setValue(progress)
628
629 if not files:
630 self.findProgress.setMaximum(1)
631 self.findProgress.setValue(1)
632
633 resultFormat = self.tr("{0} / {1}", "occurrences / files")
634 self.findProgressLabel.setPath(resultFormat.format(
635 self.tr("%n occurrence(s)", "", occurrences),
636 self.tr("%n file(s)", "", fileOccurrences)))
637
638 self.findList.setUpdatesEnabled(True)
639 self.findList.sortItems(self.findList.sortColumn(),
640 self.findList.header().sortIndicatorOrder())
641 self.findList.resizeColumnToContents(1)
642 if self.__replaceMode:
643 self.findList.header().resizeSection(0, self.__section0Size + 30)
644 self.findList.header().setStretchLastSection(True)
645 self.__populating = False
646
647 self.stopButton.setEnabled(False)
648 self.findButton.setEnabled(True)
649
650 @pyqtSlot(QTreeWidgetItem, int)
651 def on_findList_itemDoubleClicked(self, itm, column):
652 """
653 Private slot to handle the double click on a file item.
654
655 It emits the signal sourceFile or designerFile depending on the file
656 extension.
657
658 @param itm the double clicked tree item
659 @type QTreeWidgetItem
660 @param column column that was double clicked (ignored)
661 @type int
662 """
663 if itm.parent():
664 file = itm.parent().text(0)
665 line = itm.data(0, self.lineRole)
666 start = itm.data(0, self.startRole)
667 end = itm.data(0, self.endRole)
668 else:
669 file = itm.text(0)
670 line = 1
671 start = 0
672 end = 0
673
674 fn = os.path.join(self.project.ppath, file) if self.project else file
675 if fn.endswith('.ui'):
676 self.designerFile.emit(fn)
677 else:
678 self.sourceFile.emit(fn, line, "", start, end)
679
680 def __getFileList(self, path, filterRe, excludeHiddenDirs=False,
681 excludeHiddenFiles=False):
682 """
683 Private method to get a list of files to search.
684
685 @param path the root directory to search in
686 @type str
687 @param filterRe regular expression defining the filter
688 criteria
689 @type regexp object
690 @param excludeHiddenDirs flag indicating to exclude hidden directories
691 @type bool
692 @param excludeHiddenFiles flag indicating to exclude hidden files
693 @type bool
694 @return list of files to be processed
695 @rtype list of str
696 """
697 path = os.path.abspath(path)
698 files = []
699 for dirname, dirs, filenames in os.walk(path):
700 files.extend([
701 os.path.join(dirname, f) for f in filenames
702 if (not (excludeHiddenFiles and f.startswith(".")) and
703 re.match(filterRe, f))
704 ])
705 if excludeHiddenDirs:
706 for d in dirs[:]:
707 if d .startswith("."):
708 dirs.remove(d)
709 return files
710
711 def setSearchDirectory(self, searchDir):
712 """
713 Public slot to set the name of the directory to search in.
714
715 @param searchDir name of the directory to search in
716 @type str
717 """
718 self.dirButton.setChecked(True)
719 self.dirPicker.setEditText(Utilities.toNativeSeparators(searchDir))
720
721 @pyqtSlot()
722 def setOpenFiles(self):
723 """
724 Public slot to set the mode to search in open files.
725 """
726 self.openFilesButton.setChecked(True)
727
728 @pyqtSlot()
729 def on_replaceButton_clicked(self):
730 """
731 Private slot to perform the requested replace actions.
732 """
733 self.findProgress.setMaximum(self.findList.topLevelItemCount())
734 self.findProgress.setValue(0)
735
736 for index in range(self.findList.topLevelItemCount()):
737 itm = self.findList.topLevelItem(index)
738 if itm.checkState(0) in [Qt.CheckState.PartiallyChecked,
739 Qt.CheckState.Checked]:
740 file = itm.text(0)
741 origHash = itm.data(0, self.md5Role)
742
743 self.findProgressLabel.setPath(file)
744
745 if self.projectButton.isChecked():
746 fn = os.path.join(self.project.ppath, file)
747 else:
748 fn = file
749
750 # read the file and split it into textlines
751 try:
752 text, encoding, hashStr = (
753 Utilities.readEncodedFileWithHash(fn)
754 )
755 lines = text.splitlines(True)
756 except (UnicodeError, OSError) as err:
757 EricMessageBox.critical(
758 self,
759 self.tr("Replace in Files"),
760 self.tr(
761 """<p>Could not read the file <b>{0}</b>."""
762 """ Skipping it.</p><p>Reason: {1}</p>""")
763 .format(fn, str(err))
764 )
765 self.findProgress.setValue(index)
766 continue
767
768 # Check the original and the current hash. Skip the file,
769 # if hashes are different.
770 if origHash != hashStr:
771 EricMessageBox.critical(
772 self,
773 self.tr("Replace in Files"),
774 self.tr(
775 """<p>The current and the original hash of the"""
776 """ file <b>{0}</b> are different. Skipping it."""
777 """</p><p>Hash 1: {1}</p><p>Hash 2: {2}</p>""")
778 .format(fn, origHash, hashStr)
779 )
780 self.findProgress.setValue(index)
781 continue
782
783 # replace the lines authorized by the user
784 for cindex in range(itm.childCount()):
785 citm = itm.child(cindex)
786 if citm.checkState(0) == Qt.CheckState.Checked:
787 line = citm.data(0, self.lineRole)
788 rline = citm.data(0, self.replaceRole)
789 lines[line - 1] = rline
790
791 # write the file
792 txt = "".join(lines)
793 try:
794 Utilities.writeEncodedFile(fn, txt, encoding)
795 except (OSError, Utilities.CodingError, UnicodeError) as err:
796 EricMessageBox.critical(
797 self,
798 self.tr("Replace in Files"),
799 self.tr(
800 """<p>Could not save the file <b>{0}</b>."""
801 """ Skipping it.</p><p>Reason: {1}</p>""")
802 .format(fn, str(err))
803 )
804
805 self.findProgress.setValue(index + 1)
806
807 self.findProgressLabel.setPath("")
808
809 self.findList.clear()
810 self.replaceButton.setEnabled(False)
811 self.findButton.setEnabled(True)
812
813 @pyqtSlot(QPoint)
814 def __contextMenuRequested(self, pos):
815 """
816 Private slot to handle the context menu request.
817
818 @param pos position the context menu shall be shown
819 @type QPoint
820 """
821 menu = QMenu(self)
822
823 menu.addAction(self.tr("Open"), self.__openFile)
824 menu.addAction(self.tr("Copy Path to Clipboard"),
825 self.__copyToClipboard)
826
827 menu.exec(QCursor.pos())
828
829 @pyqtSlot()
830 def __openFile(self):
831 """
832 Private slot to open the currently selected entry.
833 """
834 itm = self.findList.selectedItems()[0]
835 self.on_findList_itemDoubleClicked(itm, 0)
836
837 @pyqtSlot()
838 def __copyToClipboard(self):
839 """
840 Private slot to copy the path of an entry to the clipboard.
841 """
842 itm = self.findList.selectedItems()[0]
843 fn = itm.parent().text(0) if itm.parent() else itm.text(0)
844
845 cb = QApplication.clipboard()
846 cb.setText(fn)

eric ide

mercurial