|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2004 - 2022 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, Qt, QUrl |
|
14 from PyQt6.QtGui import QDesktopServices, QImageReader |
|
15 from PyQt6.QtWidgets import ( |
|
16 QWidget, QHeaderView, QApplication, QTreeWidgetItem, QDialog, |
|
17 QDialogButtonBox, QVBoxLayout |
|
18 ) |
|
19 |
|
20 from EricWidgets.EricPathPicker import EricPathPickerModes |
|
21 |
|
22 from .Ui_FindLocationWidget import Ui_FindLocationWidget |
|
23 |
|
24 import UI.PixmapCache |
|
25 from Utilities import direntries |
|
26 import Utilities |
|
27 |
|
28 |
|
29 class FindLocationWidget(QWidget, Ui_FindLocationWidget): |
|
30 """ |
|
31 Class implementing a widget to search for files. |
|
32 |
|
33 The occurrences found are displayed in a QTreeWidget showing the |
|
34 filename and the pathname. The file will be opened upon a double click |
|
35 onto the respective entry of the list or by pressing the open button. |
|
36 |
|
37 @signal sourceFile(str) emitted to open a file in the editor |
|
38 @signal designerFile(str) emitted to open a Qt-Designer file |
|
39 @signal linguistFile(str) emitted to open a Qt-Linguist (*.ts) file |
|
40 @signal trpreview([str]) emitted to preview Qt-Linguist (*.qm) files |
|
41 @signal pixmapFile(str) emitted to open a pixmap file |
|
42 @signal svgFile(str) emitted to open a SVG file |
|
43 @signal umlFile(str) emitted to open an eric UML file |
|
44 """ |
|
45 sourceFile = pyqtSignal(str) |
|
46 designerFile = pyqtSignal(str) |
|
47 linguistFile = pyqtSignal(str) |
|
48 trpreview = pyqtSignal(list) |
|
49 pixmapFile = pyqtSignal(str) |
|
50 svgFile = pyqtSignal(str) |
|
51 umlFile = pyqtSignal(str) |
|
52 |
|
53 def __init__(self, project, parent=None): |
|
54 """ |
|
55 Constructor |
|
56 |
|
57 @param project reference to the project object |
|
58 @type Project |
|
59 @param parent parent widget of this dialog |
|
60 @type QWidget |
|
61 """ |
|
62 super().__init__(parent) |
|
63 self.setupUi(self) |
|
64 |
|
65 self.layout().setContentsMargins(0, 3, 0, 0) |
|
66 |
|
67 self.searchDirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE) |
|
68 |
|
69 self.fileList.headerItem().setText(self.fileList.columnCount(), "") |
|
70 |
|
71 self.stopButton.setEnabled(False) |
|
72 self.stopButton.setIcon(UI.PixmapCache.getIcon("stopLoading")) |
|
73 self.stopButton.setAutoDefault(False) |
|
74 self.stopButton.clicked.connect(self.__stopSearch) |
|
75 |
|
76 self.findButton.setIcon(UI.PixmapCache.getIcon("find")) |
|
77 self.findButton.setAutoDefault(False) |
|
78 self.findButton.clicked.connect(self.__searchFile) |
|
79 |
|
80 self.clearButton.setEnabled(False) |
|
81 self.clearButton.setIcon(UI.PixmapCache.getIcon("clear")) |
|
82 self.clearButton.setAutoDefault(False) |
|
83 self.clearButton.clicked.connect(self.__clearResults) |
|
84 |
|
85 self.openButton.setEnabled(False) |
|
86 self.openButton.setIcon(UI.PixmapCache.getIcon("open")) |
|
87 self.openButton.setAutoDefault(False) |
|
88 self.openButton.clicked.connect(self.__openFile) |
|
89 |
|
90 self.__project = project |
|
91 self.__project.projectOpened.connect(self.__projectOpened) |
|
92 self.__project.projectClosed.connect(self.__projectClosed) |
|
93 |
|
94 self.extsepLabel.setText(os.extsep) |
|
95 |
|
96 self.__shouldStop = False |
|
97 |
|
98 self.fileNameEdit.returnPressed.connect(self.__searchFile) |
|
99 self.fileExtEdit.returnPressed.connect(self.__searchFile) |
|
100 |
|
101 self.__projectClosed() |
|
102 |
|
103 @pyqtSlot() |
|
104 def __stopSearch(self): |
|
105 """ |
|
106 Private slot to handle the stop button being pressed. |
|
107 """ |
|
108 self.__shouldStop = True |
|
109 |
|
110 @pyqtSlot() |
|
111 def __openFile(self, itm=None): |
|
112 """ |
|
113 Private slot to open a file. |
|
114 |
|
115 It emits a signal depending on the file extension. |
|
116 |
|
117 @param itm item to be opened |
|
118 @type QTreeWidgetItem |
|
119 """ |
|
120 if itm is None: |
|
121 itm = self.fileList.currentItem() |
|
122 if itm is not None: |
|
123 fileName = itm.text(0) |
|
124 filePath = itm.text(1) |
|
125 fileExt = os.path.splitext(fileName)[1] |
|
126 fullName = os.path.join(filePath, fileName) |
|
127 |
|
128 if fileExt == ".ui": |
|
129 self.designerFile.emit(fullName) |
|
130 elif fileExt == ".ts": |
|
131 self.linguistFile.emit(fullName) |
|
132 elif fileExt == ".qm": |
|
133 self.trpreview.emit([fullName]) |
|
134 elif fileExt in (".egj", ".e5g"): |
|
135 self.umlFile.emit(fullName) |
|
136 elif fileExt == ".svg": |
|
137 self.svgFile.emit(fullName) |
|
138 elif fileExt[1:] in QImageReader.supportedImageFormats(): |
|
139 self.pixmapFile.emit(fullName) |
|
140 else: |
|
141 if Utilities.MimeTypes.isTextFile(fullName): |
|
142 self.sourceFile.emit(fullName) |
|
143 else: |
|
144 QDesktopServices.openUrl(QUrl(fullName)) |
|
145 |
|
146 @pyqtSlot() |
|
147 def __searchFile(self): |
|
148 """ |
|
149 Private slot to handle the search. |
|
150 """ |
|
151 fileName = self.fileNameEdit.text() |
|
152 fileExt = self.fileExtEdit.text() |
|
153 |
|
154 self.findStatusLabel.clear() |
|
155 |
|
156 patternFormat = ( |
|
157 "{0}{1}{2}" |
|
158 if "*" in fileName or "?" in fileName else |
|
159 "{0}*{1}{2}" |
|
160 ) |
|
161 |
|
162 fileNamePatterns = [patternFormat.format( |
|
163 fileName or "*", os.extsep, fileExt or "*")] |
|
164 |
|
165 if not fileExt: |
|
166 # search for files without extension as well |
|
167 if "*" in fileName or "?" in fileName: |
|
168 patternFormat = "{0}" |
|
169 else: |
|
170 patternFormat = "{0}*" |
|
171 |
|
172 fileNamePatterns.append(patternFormat.format(fileName or "*")) |
|
173 |
|
174 searchPaths = [] |
|
175 if ( |
|
176 self.searchDirCheckBox.isChecked() and |
|
177 self.searchDirPicker.text() != "" |
|
178 ): |
|
179 searchPaths.append(self.searchDirPicker.text()) |
|
180 if self.projectCheckBox.isChecked(): |
|
181 searchPaths.append(self.__project.getProjectPath()) |
|
182 if self.syspathCheckBox.isChecked(): |
|
183 searchPaths.extend(sys.path) |
|
184 |
|
185 self.fileList.clear() |
|
186 locations = {} |
|
187 self.__shouldStop = False |
|
188 self.stopButton.setEnabled(True) |
|
189 self.clearButton.setEnabled(False) |
|
190 QApplication.processEvents() |
|
191 |
|
192 for path in searchPaths: |
|
193 if os.path.isdir(path): |
|
194 files = direntries(path, True, fileNamePatterns, |
|
195 False, self.checkStop) |
|
196 if files: |
|
197 for file in files: |
|
198 fp, fn = os.path.split(file) |
|
199 if fn in locations: |
|
200 if fp in locations[fn]: |
|
201 continue |
|
202 else: |
|
203 locations[fn].append(fp) |
|
204 else: |
|
205 locations[fn] = [fp] |
|
206 QTreeWidgetItem(self.fileList, [fn, fp]) |
|
207 QApplication.processEvents() |
|
208 |
|
209 del locations |
|
210 self.stopButton.setEnabled(False) |
|
211 self.fileList.sortItems(self.fileList.sortColumn(), |
|
212 Qt.SortOrder.AscendingOrder) |
|
213 self.fileList.header().resizeSections( |
|
214 QHeaderView.ResizeMode.ResizeToContents) |
|
215 self.fileList.header().resizeSection(0, self.width() // 2) |
|
216 self.fileList.header().setStretchLastSection(True) |
|
217 |
|
218 self.findStatusLabel.setText(self.tr( |
|
219 "%n file(s) found", "", self.fileList.topLevelItemCount())) |
|
220 |
|
221 self.clearButton.setEnabled(self.fileList.topLevelItemCount() != 0) |
|
222 |
|
223 @pyqtSlot() |
|
224 def __clearResults(self): |
|
225 """ |
|
226 Private slot to clear the current search results. |
|
227 """ |
|
228 self.fileList.clear() |
|
229 self.clearButton.setEnabled(False) |
|
230 self.openButton.setEnabled(False) |
|
231 |
|
232 def checkStop(self): |
|
233 """ |
|
234 Public method to check, if the search should be stopped. |
|
235 |
|
236 @return flag indicating the search should be stopped |
|
237 @rtype bool |
|
238 """ |
|
239 QApplication.processEvents() |
|
240 return self.__shouldStop |
|
241 |
|
242 @pyqtSlot(str) |
|
243 def on_searchDirPicker_textChanged(self, text): |
|
244 """ |
|
245 Private slot to handle the textChanged signal of the search directory |
|
246 edit. |
|
247 |
|
248 @param text text of the search dir edit |
|
249 @type str |
|
250 """ |
|
251 self.searchDirCheckBox.setEnabled(text != "") |
|
252 |
|
253 @pyqtSlot(QTreeWidgetItem, int) |
|
254 def on_fileList_itemActivated(self, itm, column): |
|
255 """ |
|
256 Private slot to handle the double click on a file item. |
|
257 |
|
258 It emits the signal sourceFile or designerFile depending on the |
|
259 file extension. |
|
260 |
|
261 @param itm the double clicked listview item |
|
262 @type QTreeWidgetItem |
|
263 @param column column that was double clicked (ignored) |
|
264 @type int |
|
265 """ |
|
266 self.__openFile(itm) |
|
267 |
|
268 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) |
|
269 def on_fileList_currentItemChanged(self, current, previous): |
|
270 """ |
|
271 Private slot handling a change of the current item. |
|
272 |
|
273 @param current current item |
|
274 @type QTreeWidgetItem |
|
275 @param previous prevoius current item |
|
276 @type QTreeWidgetItem |
|
277 """ |
|
278 self.openButton.setEnabled(current is not None) |
|
279 |
|
280 @pyqtSlot() |
|
281 def __projectOpened(self): |
|
282 """ |
|
283 Private slot to handle a project being opened. |
|
284 """ |
|
285 self.projectCheckBox.setEnabled(True) |
|
286 self.projectCheckBox.setChecked(True) |
|
287 |
|
288 @pyqtSlot() |
|
289 def __projectClosed(self): |
|
290 """ |
|
291 Private slot to handle a project being closed. |
|
292 """ |
|
293 self.projectCheckBox.setEnabled(False) |
|
294 self.projectCheckBox.setChecked(False) |
|
295 |
|
296 @pyqtSlot() |
|
297 def activate(self): |
|
298 """ |
|
299 Public slot to activate this widget. |
|
300 """ |
|
301 self.fileNameEdit.selectAll() |
|
302 self.fileNameEdit.setFocus() |
|
303 |
|
304 |
|
305 class FindLocationDialog(QDialog): |
|
306 """ |
|
307 Class implementing a dialog to search for files. |
|
308 |
|
309 The occurrences found are displayed in a QTreeWidget showing the |
|
310 filename and the pathname. The file will be opened upon a double click |
|
311 onto the respective entry of the list or by pressing the open button. |
|
312 |
|
313 @signal sourceFile(str) emitted to open a file in the editor |
|
314 @signal designerFile(str) emitted to open a Qt-Designer file |
|
315 @signal linguistFile(str) emitted to open a Qt-Linguist (*.ts) file |
|
316 @signal trpreview([str]) emitted to preview Qt-Linguist (*.qm) files |
|
317 @signal pixmapFile(str) emitted to open a pixmap file |
|
318 @signal svgFile(str) emitted to open a SVG file |
|
319 @signal umlFile(str) emitted to open an eric UML file |
|
320 """ |
|
321 sourceFile = pyqtSignal(str) |
|
322 designerFile = pyqtSignal(str) |
|
323 linguistFile = pyqtSignal(str) |
|
324 trpreview = pyqtSignal(list) |
|
325 pixmapFile = pyqtSignal(str) |
|
326 svgFile = pyqtSignal(str) |
|
327 umlFile = pyqtSignal(str) |
|
328 |
|
329 def __init__(self, project, parent=None): |
|
330 """ |
|
331 Constructor |
|
332 |
|
333 @param project reference to the project object |
|
334 @type Project |
|
335 @param parent parent widget of this dialog (defaults to None) |
|
336 @type QWidget (optional) |
|
337 """ |
|
338 super().__init__(parent) |
|
339 self.setWindowFlags(Qt.WindowType.Window) |
|
340 |
|
341 self.__layout = QVBoxLayout() |
|
342 |
|
343 self.__findWidget = FindLocationWidget(project, self) |
|
344 self.__layout.addWidget(self.__findWidget) |
|
345 |
|
346 self.__buttonBox = QDialogButtonBox( |
|
347 QDialogButtonBox.StandardButton.Close, |
|
348 Qt.Orientation.Horizontal, |
|
349 self |
|
350 ) |
|
351 self.__buttonBox.button( |
|
352 QDialogButtonBox.StandardButton.Close).setAutoDefault(False) |
|
353 self.__layout.addWidget(self.__buttonBox) |
|
354 |
|
355 self.setLayout(self.__layout) |
|
356 self.resize(600, 800) |
|
357 |
|
358 # connect the widgets |
|
359 self.__findWidget.sourceFile.connect(self.sourceFile) |
|
360 self.__findWidget.designerFile.connect(self.designerFile) |
|
361 self.__findWidget.linguistFile.connect(self.linguistFile) |
|
362 self.__findWidget.trpreview.connect(self.trpreview) |
|
363 self.__findWidget.pixmapFile.connect(self.pixmapFile) |
|
364 self.__findWidget.svgFile.connect(self.svgFile) |
|
365 self.__findWidget.umlFile.connect(self.umlFile) |
|
366 |
|
367 self.__buttonBox.accepted.connect(self.accept) |
|
368 self.__buttonBox.rejected.connect(self.reject) |
|
369 |
|
370 def activate(self): |
|
371 """ |
|
372 Public method to activate the dialog. |
|
373 """ |
|
374 self.__findWidget.activate() |
|
375 |
|
376 self.raise_() |
|
377 self.activateWindow() |
|
378 self.show() |