|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2004 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog to search for files. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import sys |
|
12 |
|
13 from PyQt6.QtCore import pyqtSignal, pyqtSlot |
|
14 from PyQt6.QtWidgets import ( |
|
15 QWidget, QHeaderView, QApplication, QTreeWidgetItem |
|
16 ) |
|
17 |
|
18 from EricWidgets.EricPathPicker import EricPathPickerModes |
|
19 |
|
20 from .Ui_FindLocationWidget import Ui_FindLocationWidget |
|
21 |
|
22 import UI.PixmapCache |
|
23 from Utilities import direntries |
|
24 import Utilities |
|
25 |
|
26 |
|
27 class FindLocationWidget(QWidget, Ui_FindLocationWidget): |
|
28 """ |
|
29 Class implementing a widget to search for files. |
|
30 |
|
31 The occurrences found are displayed in a QTreeWidget showing the |
|
32 filename and the pathname. The file will be opened upon a double click |
|
33 onto the respective entry of the list or by pressing the open button. |
|
34 |
|
35 @signal sourceFile(str) emitted to open a file in the editor |
|
36 @signal designerFile(str) emitted to open a Qt-Designer file |
|
37 """ |
|
38 sourceFile = pyqtSignal(str) |
|
39 designerFile = pyqtSignal(str) |
|
40 |
|
41 def __init__(self, project, parent=None): |
|
42 """ |
|
43 Constructor |
|
44 |
|
45 @param project reference to the project object |
|
46 @type Project |
|
47 @param parent parent widget of this dialog |
|
48 @type QWidget |
|
49 """ |
|
50 super().__init__(parent) |
|
51 self.setupUi(self) |
|
52 |
|
53 self.layout().setContentsMargins(0, 3, 0, 0) |
|
54 |
|
55 self.searchDirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE) |
|
56 |
|
57 self.fileList.headerItem().setText(self.fileList.columnCount(), "") |
|
58 |
|
59 self.stopButton.setEnabled(False) |
|
60 self.stopButton.clicked.connect(self.__stopSearch) |
|
61 self.stopButton.setIcon(UI.PixmapCache.getIcon("stopLoading")) |
|
62 |
|
63 self.findButton.setEnabled(False) |
|
64 self.findButton.clicked.connect(self.__searchFile) |
|
65 self.findButton.setIcon(UI.PixmapCache.getIcon("find")) |
|
66 |
|
67 self.openButton.setIcon(UI.PixmapCache.getIcon("open")) |
|
68 self.openButton.clicked.connect(self.__openFile) |
|
69 |
|
70 self.project = project |
|
71 self.extsepLabel.setText(os.extsep) |
|
72 |
|
73 self.__shouldStop = False |
|
74 |
|
75 self.fileNameEdit.returnPressed.connect(self.__searchFile) |
|
76 self.fileExtEdit.returnPressed.connect(self.__searchFile) |
|
77 |
|
78 @pyqtSlot() |
|
79 def __stopSearch(self): |
|
80 """ |
|
81 Private slot to handle the stop button being pressed. |
|
82 """ |
|
83 self.__shouldStop = True |
|
84 |
|
85 @pyqtSlot() |
|
86 def __openFile(self, itm=None): |
|
87 """ |
|
88 Private slot to open a file. |
|
89 |
|
90 It emits the signal sourceFile or designerFile depending on the |
|
91 file extension. |
|
92 |
|
93 @param itm item to be opened |
|
94 @type QTreeWidgetItem |
|
95 """ |
|
96 if itm is None: |
|
97 itm = self.fileList.currentItem() |
|
98 if itm is not None: |
|
99 fileName = itm.text(0) |
|
100 filePath = itm.text(1) |
|
101 |
|
102 # TODO: add more extensions and use mimetype |
|
103 # - *.ts, *.qm->*.ts |
|
104 # Utilities.MimeTypes.isTextFile(filename) |
|
105 if fileName.endswith('.ui'): |
|
106 self.designerFile.emit(os.path.join(filePath, fileName)) |
|
107 else: |
|
108 self.sourceFile.emit(os.path.join(filePath, fileName)) |
|
109 |
|
110 @pyqtSlot() |
|
111 def __searchFile(self): |
|
112 """ |
|
113 Private slot to handle the search. |
|
114 """ |
|
115 fileName = self.fileNameEdit.text() |
|
116 if not fileName: |
|
117 self.fileList.clear() |
|
118 return |
|
119 |
|
120 fileExt = self.fileExtEdit.text() |
|
121 if not fileExt and Utilities.isWindowsPlatform(): |
|
122 self.fileList.clear() |
|
123 return |
|
124 |
|
125 self.findStatusLabel.clear() |
|
126 |
|
127 patternFormat = fileExt and "{0}{1}{2}" or "{0}*{1}{2}" |
|
128 fileNamePattern = patternFormat.format( |
|
129 fileName, os.extsep, fileExt and fileExt or '*') |
|
130 |
|
131 searchPaths = [] |
|
132 if ( |
|
133 self.searchDirCheckBox.isChecked() and |
|
134 self.searchDirPicker.text() != "" |
|
135 ): |
|
136 searchPaths.append(self.searchDirPicker.text()) |
|
137 if self.projectCheckBox.isChecked(): |
|
138 searchPaths.append(self.project.ppath) |
|
139 if self.syspathCheckBox.isChecked(): |
|
140 searchPaths.extend(sys.path) |
|
141 |
|
142 self.fileList.clear() |
|
143 locations = {} |
|
144 self.__shouldStop = False |
|
145 self.stopButton.setEnabled(True) |
|
146 QApplication.processEvents() |
|
147 |
|
148 for path in searchPaths: |
|
149 if os.path.isdir(path): |
|
150 files = direntries(path, True, fileNamePattern, |
|
151 False, self.checkStop) |
|
152 if files: |
|
153 for file in files: |
|
154 fp, fn = os.path.split(file) |
|
155 if fn in locations: |
|
156 if fp in locations[fn]: |
|
157 continue |
|
158 else: |
|
159 locations[fn].append(fp) |
|
160 else: |
|
161 locations[fn] = [fp] |
|
162 QTreeWidgetItem(self.fileList, [fn, fp]) |
|
163 QApplication.processEvents() |
|
164 |
|
165 del locations |
|
166 self.stopButton.setEnabled(False) |
|
167 self.fileList.header().resizeSections( |
|
168 QHeaderView.ResizeMode.ResizeToContents) |
|
169 self.fileList.header().setStretchLastSection(True) |
|
170 |
|
171 self.findStatusLabel.setText(self.tr( |
|
172 "%n file(s) found", "", self.fileList.topLevelItemCount())) |
|
173 |
|
174 def checkStop(self): |
|
175 """ |
|
176 Public method to check, if the search should be stopped. |
|
177 |
|
178 @return flag indicating the search should be stopped |
|
179 @rtype bool |
|
180 """ |
|
181 QApplication.processEvents() |
|
182 return self.__shouldStop |
|
183 |
|
184 @pyqtSlot(str) |
|
185 def on_fileNameEdit_textChanged(self, text): |
|
186 """ |
|
187 Private slot to handle the textChanged signal of the file name edit. |
|
188 |
|
189 @param text (ignored) |
|
190 @type str |
|
191 """ |
|
192 self.findButton.setEnabled(bool(text)) |
|
193 |
|
194 @pyqtSlot(str) |
|
195 def on_searchDirPicker_textChanged(self, text): |
|
196 """ |
|
197 Private slot to handle the textChanged signal of the search directory |
|
198 edit. |
|
199 |
|
200 @param text text of the search dir edit |
|
201 @type str |
|
202 """ |
|
203 self.searchDirCheckBox.setEnabled(text != "") |
|
204 |
|
205 @pyqtSlot(QTreeWidgetItem, int) |
|
206 def on_fileList_itemActivated(self, itm, column): |
|
207 """ |
|
208 Private slot to handle the double click on a file item. |
|
209 |
|
210 It emits the signal sourceFile or designerFile depending on the |
|
211 file extension. |
|
212 |
|
213 @param itm the double clicked listview item |
|
214 @type QTreeWidgetItem |
|
215 @param column column that was double clicked (ignored) |
|
216 @type int |
|
217 """ |
|
218 self.__openFile(itm) |
|
219 |
|
220 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) |
|
221 def on_fileList_currentItemChanged(self, current, previous): |
|
222 """ |
|
223 Private slot handling a change of the current item. |
|
224 |
|
225 @param current current item |
|
226 @type QTreeWidgetItem |
|
227 @param previous prevoius current item |
|
228 @type QTreeWidgetItem |
|
229 """ |
|
230 self.openButton.setEnabled(current is not None) |
|
231 |
|
232 @pyqtSlot() |
|
233 def activate(self): |
|
234 """ |
|
235 Public slot to enable/disable the project checkbox. |
|
236 """ |
|
237 if self.project and self.project.isOpen(): |
|
238 self.projectCheckBox.setEnabled(True) |
|
239 self.projectCheckBox.setChecked(True) |
|
240 else: |
|
241 self.projectCheckBox.setEnabled(False) |
|
242 self.projectCheckBox.setChecked(False) |
|
243 |
|
244 self.fileNameEdit.selectAll() |
|
245 self.fileNameEdit.setFocus() |