eric7/UI/FindFileDialog.py

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

eric ide

mercurial