12 |
12 |
13 import os |
13 import os |
14 |
14 |
15 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent |
15 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent |
16 from PyQt6.QtWidgets import ( |
16 from PyQt6.QtWidgets import ( |
17 QWidget, QHeaderView, QApplication, QDialogButtonBox, QTreeWidgetItem |
17 QWidget, |
|
18 QHeaderView, |
|
19 QApplication, |
|
20 QDialogButtonBox, |
|
21 QTreeWidgetItem, |
18 ) |
22 ) |
19 |
23 |
20 from .Ui_QuickFindFile import Ui_QuickFindFile |
24 from .Ui_QuickFindFile import Ui_QuickFindFile |
21 |
25 |
22 |
26 |
23 class QuickFindFileDialog(QWidget, Ui_QuickFindFile): |
27 class QuickFindFileDialog(QWidget, Ui_QuickFindFile): |
24 """ |
28 """ |
25 Class implementing the Quick Find File by Name Dialog. |
29 Class implementing the Quick Find File by Name Dialog. |
26 |
30 |
27 This dialog provides a slightly more streamlined behaviour |
31 This dialog provides a slightly more streamlined behaviour |
28 than the standard FindFileNameDialog in that it tries to |
32 than the standard FindFileNameDialog in that it tries to |
29 match any name in the project against (fragmentary) bits of |
33 match any name in the project against (fragmentary) bits of |
30 file names. |
34 file names. |
31 |
35 |
32 @signal sourceFile(str) emitted to open a file in the editor |
36 @signal sourceFile(str) emitted to open a file in the editor |
33 @signal designerFile(str) emitted to open a Qt-Designer file |
37 @signal designerFile(str) emitted to open a Qt-Designer file |
34 @signal linguistFile(str) emitted to open a Qt translation file |
38 @signal linguistFile(str) emitted to open a Qt translation file |
35 """ |
39 """ |
|
40 |
36 sourceFile = pyqtSignal(str) |
41 sourceFile = pyqtSignal(str) |
37 designerFile = pyqtSignal(str) |
42 designerFile = pyqtSignal(str) |
38 linguistFile = pyqtSignal(str) |
43 linguistFile = pyqtSignal(str) |
39 |
44 |
40 def __init__(self, project, parent=None): |
45 def __init__(self, project, parent=None): |
41 """ |
46 """ |
42 Constructor |
47 Constructor |
43 |
48 |
44 @param project reference to the project object |
49 @param project reference to the project object |
45 @type Project |
50 @type Project |
46 @param parent parent widget of this dialog |
51 @param parent parent widget of this dialog |
47 @type QWidget |
52 @type QWidget |
48 """ |
53 """ |
49 super().__init__(parent) |
54 super().__init__(parent) |
50 self.setupUi(self) |
55 self.setupUi(self) |
51 |
56 |
52 self.fileList.headerItem().setText(self.fileList.columnCount(), "") |
57 self.fileList.headerItem().setText(self.fileList.columnCount(), "") |
53 self.fileNameEdit.returnPressed.connect( |
58 self.fileNameEdit.returnPressed.connect(self.on_fileNameEdit_returnPressed) |
54 self.on_fileNameEdit_returnPressed) |
|
55 self.installEventFilter(self) |
59 self.installEventFilter(self) |
56 |
60 |
57 self.stopButton = self.buttonBox.addButton( |
61 self.stopButton = self.buttonBox.addButton( |
58 self.tr("Stop"), QDialogButtonBox.ButtonRole.ActionRole) |
62 self.tr("Stop"), QDialogButtonBox.ButtonRole.ActionRole |
|
63 ) |
59 self.project = project |
64 self.project = project |
60 |
65 |
61 def eventFilter(self, source, event): |
66 def eventFilter(self, source, event): |
62 """ |
67 """ |
63 Public method to handle event for another object. |
68 Public method to handle event for another object. |
64 |
69 |
65 @param source object to handle events for |
70 @param source object to handle events for |
66 @type QObject |
71 @type QObject |
67 @param event event to handle |
72 @param event event to handle |
68 @type QEvent |
73 @type QEvent |
69 @return flag indicating that the event was handled |
74 @return flag indicating that the event was handled |
70 @rtype bool |
75 @rtype bool |
71 """ |
76 """ |
72 if event.type() == QEvent.Type.KeyPress: |
77 if event.type() == QEvent.Type.KeyPress: |
73 |
78 |
74 # Anywhere in the dialog, make hitting escape cancel it |
79 # Anywhere in the dialog, make hitting escape cancel it |
75 if event.key() == Qt.Key.Key_Escape: |
80 if event.key() == Qt.Key.Key_Escape: |
76 self.close() |
81 self.close() |
77 |
82 |
78 # Anywhere in the dialog, make hitting up/down choose next item |
83 # Anywhere in the dialog, make hitting up/down choose next item |
79 # Note: This doesn't really do anything, as other than the text |
84 # Note: This doesn't really do anything, as other than the text |
80 # input there's nothing that doesn't handle up/down already. |
85 # input there's nothing that doesn't handle up/down already. |
81 elif ( |
86 elif event.key() == Qt.Key.Key_Up or event.key() == Qt.Key.Key_Down: |
82 event.key() == Qt.Key.Key_Up or |
|
83 event.key() == Qt.Key.Key_Down |
|
84 ): |
|
85 current = self.fileList.currentItem() |
87 current = self.fileList.currentItem() |
86 index = self.fileList.indexOfTopLevelItem(current) |
88 index = self.fileList.indexOfTopLevelItem(current) |
87 if event.key() == Qt.Key.Key_Up: |
89 if event.key() == Qt.Key.Key_Up: |
88 if index != 0: |
90 if index != 0: |
89 self.fileList.setCurrentItem( |
91 self.fileList.setCurrentItem( |
90 self.fileList.topLevelItem(index - 1)) |
92 self.fileList.topLevelItem(index - 1) |
|
93 ) |
91 else: |
94 else: |
92 if index < (self.fileList.topLevelItemCount() - 1): |
95 if index < (self.fileList.topLevelItemCount() - 1): |
93 self.fileList.setCurrentItem( |
96 self.fileList.setCurrentItem( |
94 self.fileList.topLevelItem(index + 1)) |
97 self.fileList.topLevelItem(index + 1) |
|
98 ) |
95 return QWidget.eventFilter(self, source, event) |
99 return QWidget.eventFilter(self, source, event) |
96 |
100 |
97 def on_buttonBox_clicked(self, button): |
101 def on_buttonBox_clicked(self, button): |
98 """ |
102 """ |
99 Private slot called by a button of the button box clicked. |
103 Private slot called by a button of the button box clicked. |
100 |
104 |
101 @param button button that was clicked (QAbstractButton) |
105 @param button button that was clicked (QAbstractButton) |
102 """ |
106 """ |
103 if button == self.stopButton: |
107 if button == self.stopButton: |
104 self.shouldStop = True |
108 self.shouldStop = True |
105 elif ( |
109 elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Open): |
106 button == |
|
107 self.buttonBox.button(QDialogButtonBox.StandardButton.Open) |
|
108 ): |
|
109 self.__openFile() |
110 self.__openFile() |
110 |
111 |
111 def __openFile(self, itm=None): |
112 def __openFile(self, itm=None): |
112 """ |
113 """ |
113 Private slot to open a file. |
114 Private slot to open a file. |
114 |
115 |
115 It emits the signal sourceFile or designerFile depending on the |
116 It emits the signal sourceFile or designerFile depending on the |
116 file extension. |
117 file extension. |
117 |
118 |
118 @param itm item to be opened |
119 @param itm item to be opened |
119 @type QTreeWidgetItem |
120 @type QTreeWidgetItem |
120 @return flag indicating a file was opened |
121 @return flag indicating a file was opened |
121 @rtype bool |
122 @rtype bool |
122 """ |
123 """ |
124 itm = self.fileList.currentItem() |
125 itm = self.fileList.currentItem() |
125 if itm is not None: |
126 if itm is not None: |
126 filePath = itm.text(1) |
127 filePath = itm.text(1) |
127 fileName = itm.text(0) |
128 fileName = itm.text(0) |
128 fullPath = os.path.join(self.project.ppath, filePath, fileName) |
129 fullPath = os.path.join(self.project.ppath, filePath, fileName) |
129 |
130 |
130 if fullPath.endswith('.ui'): |
131 if fullPath.endswith(".ui"): |
131 self.designerFile.emit(fullPath) |
132 self.designerFile.emit(fullPath) |
132 elif fullPath.endswith(('.ts', '.qm')): |
133 elif fullPath.endswith((".ts", ".qm")): |
133 self.linguistFile.emit(fullPath) |
134 self.linguistFile.emit(fullPath) |
134 else: |
135 else: |
135 self.sourceFile.emit(fullPath) |
136 self.sourceFile.emit(fullPath) |
136 return True |
137 return True |
137 |
138 |
138 return False |
139 return False |
139 |
140 |
140 def __generateLocations(self): |
141 def __generateLocations(self): |
141 """ |
142 """ |
142 Private method to generate a set of locations that can be searched. |
143 Private method to generate a set of locations that can be searched. |
143 |
144 |
144 @yield set of files in our project |
145 @yield set of files in our project |
145 @ytype str |
146 @ytype str |
146 """ |
147 """ |
147 for typ in ["SOURCES", "FORMS", "INTERFACES", "PROTOCOLS", "RESOURCES", |
148 for typ in [ |
148 "TRANSLATIONS", "OTHERS"]: |
149 "SOURCES", |
|
150 "FORMS", |
|
151 "INTERFACES", |
|
152 "PROTOCOLS", |
|
153 "RESOURCES", |
|
154 "TRANSLATIONS", |
|
155 "OTHERS", |
|
156 ]: |
149 entries = self.project.pdata.get(typ) |
157 entries = self.project.pdata.get(typ) |
150 yield from entries[:] |
158 yield from entries[:] |
151 |
159 |
152 def __sortedMatches(self, items, searchTerm): |
160 def __sortedMatches(self, items, searchTerm): |
153 """ |
161 """ |
154 Private method to find the subset of items which match a search term. |
162 Private method to find the subset of items which match a search term. |
155 |
163 |
156 @param items list of items to be scanned for the search term |
164 @param items list of items to be scanned for the search term |
157 @type list of str |
165 @type list of str |
158 @param searchTerm search term to be searched for |
166 @param searchTerm search term to be searched for |
159 @type str |
167 @type str |
160 @return sorted subset of items which match searchTerm in |
168 @return sorted subset of items which match searchTerm in |
161 relevance order (i.e. the most likely match first) |
169 relevance order (i.e. the most likely match first) |
162 @rtype list of tuple of bool, int and str |
170 @rtype list of tuple of bool, int and str |
163 """ |
171 """ |
164 fragments = searchTerm.split() |
172 fragments = searchTerm.split() |
165 |
173 |
166 possible = [ |
174 possible = [ |
167 # matches, in_order, file name |
175 # matches, in_order, file name |
168 ] |
176 ] |
169 |
177 |
170 for entry in items: |
178 for entry in items: |
171 count = 0 |
179 count = 0 |
172 match_order = [] |
180 match_order = [] |
173 for fragment in fragments: |
181 for fragment in fragments: |
174 index = entry.find(fragment) |
182 index = entry.find(fragment) |
210 """ |
220 """ |
211 fileName = self.fileNameEdit.text().strip() |
221 fileName = self.fileNameEdit.text().strip() |
212 if not fileName: |
222 if not fileName: |
213 self.fileList.clear() |
223 self.fileList.clear() |
214 return |
224 return |
215 |
225 |
216 ordered = self.__sortedMatches(self.__generateLocations(), fileName) |
226 ordered = self.__sortedMatches(self.__generateLocations(), fileName) |
217 |
227 |
218 found = False |
228 found = False |
219 self.fileList.clear() |
229 self.fileList.clear() |
220 locations = {} |
230 locations = {} |
221 |
231 |
222 for _in_order, _age, name in ordered: |
232 for _in_order, _age, name in ordered: |
223 found = True |
233 found = True |
224 QTreeWidgetItem(self.fileList, [os.path.basename(name), |
234 QTreeWidgetItem( |
225 os.path.dirname(name)]) |
235 self.fileList, [os.path.basename(name), os.path.dirname(name)] |
|
236 ) |
226 QApplication.processEvents() |
237 QApplication.processEvents() |
227 |
238 |
228 del locations |
239 del locations |
229 self.stopButton.setEnabled(False) |
240 self.stopButton.setEnabled(False) |
230 self.fileList.header().resizeSections( |
241 self.fileList.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents) |
231 QHeaderView.ResizeMode.ResizeToContents) |
|
232 self.fileList.header().setStretchLastSection(True) |
242 self.fileList.header().setStretchLastSection(True) |
233 |
243 |
234 if found: |
244 if found: |
235 self.fileList.setCurrentItem(self.fileList.topLevelItem(0)) |
245 self.fileList.setCurrentItem(self.fileList.topLevelItem(0)) |
236 |
246 |
237 def on_fileNameEdit_textChanged(self, text): |
247 def on_fileNameEdit_textChanged(self, text): |
238 """ |
248 """ |
239 Private slot to handle the textChanged signal of the file name edit. |
249 Private slot to handle the textChanged signal of the file name edit. |
240 |
250 |
241 @param text (ignored) |
251 @param text (ignored) |
242 """ |
252 """ |
243 self.__searchFile() |
253 self.__searchFile() |
244 |
254 |
245 def on_fileNameEdit_returnPressed(self): |
255 def on_fileNameEdit_returnPressed(self): |
246 """ |
256 """ |
247 Private slot to handle enter being pressed on the file name edit box. |
257 Private slot to handle enter being pressed on the file name edit box. |
248 """ |
258 """ |
249 if self.__openFile(): |
259 if self.__openFile(): |
250 self.close() |
260 self.close() |
251 |
261 |
252 def on_fileList_itemActivated(self, itm, column): |
262 def on_fileList_itemActivated(self, itm, column): |
253 """ |
263 """ |
254 Private slot to handle the double click on a file item. |
264 Private slot to handle the double click on a file item. |
255 |
265 |
256 It emits the signal sourceFile or designerFile depending on the |
266 It emits the signal sourceFile or designerFile depending on the |
257 file extension. |
267 file extension. |
258 |
268 |
259 @param itm the double clicked listview item (QTreeWidgetItem) |
269 @param itm the double clicked listview item (QTreeWidgetItem) |
260 @param column column that was double clicked (integer) (ignored) |
270 @param column column that was double clicked (integer) (ignored) |
261 """ |
271 """ |
262 self.__openFile(itm) |
272 self.__openFile(itm) |
263 |
273 |
264 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) |
274 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) |
265 def on_fileList_currentItemChanged(self, current, previous): |
275 def on_fileList_currentItemChanged(self, current, previous): |
266 """ |
276 """ |
267 Private slot handling a change of the current item. |
277 Private slot handling a change of the current item. |
268 |
278 |
269 @param current current item (QTreeWidgetItem) |
279 @param current current item (QTreeWidgetItem) |
270 @param previous prevoius current item (QTreeWidgetItem) |
280 @param previous prevoius current item (QTreeWidgetItem) |
271 """ |
281 """ |
272 self.buttonBox.button(QDialogButtonBox.StandardButton.Open).setEnabled( |
282 self.buttonBox.button(QDialogButtonBox.StandardButton.Open).setEnabled( |
273 current is not None) |
283 current is not None |
|
284 ) |
274 |
285 |
275 def show(self): |
286 def show(self): |
276 """ |
287 """ |
277 Public method to enable/disable the project checkbox. |
288 Public method to enable/disable the project checkbox. |
278 """ |
289 """ |
279 self.fileNameEdit.selectAll() |
290 self.fileNameEdit.selectAll() |
280 self.fileNameEdit.setFocus() |
291 self.fileNameEdit.setFocus() |
281 |
292 |
282 super().show() |
293 super().show() |