src/eric7/Project/QuickFindFileDialog.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
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)
186 elif possible and count > possible[0][0]: 194 elif possible and count > possible[0][0]:
187 # better than all previous matches, discard them and 195 # better than all previous matches, discard them and
188 # keep this 196 # keep this
189 del possible[:] 197 del possible[:]
190 possible.append(record) 198 possible.append(record)
191 199
192 ordered = [] 200 ordered = []
193 for (_, in_order, name) in possible: 201 for (_, in_order, name) in possible:
194 try: 202 try:
195 age = os.stat(os.path.join(self.project.ppath, name)).st_mtime 203 age = os.stat(os.path.join(self.project.ppath, name)).st_mtime
196 except OSError: 204 except OSError:
197 # skipping, because it doesn't appear to exist... 205 # skipping, because it doesn't appear to exist...
198 continue 206 continue
199 ordered.append(( 207 ordered.append(
200 in_order, # we want closer match first 208 (
201 - age, # then approximately "most recently edited" 209 in_order, # we want closer match first
202 name 210 -age, # then approximately "most recently edited"
203 )) 211 name,
212 )
213 )
204 ordered.sort() 214 ordered.sort()
205 return ordered 215 return ordered
206 216
207 def __searchFile(self): 217 def __searchFile(self):
208 """ 218 """
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()

eric ide

mercurial