src/eric7/EricWidgets/EricModelMenu.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
15 15
16 16
17 class EricModelMenu(QMenu): 17 class EricModelMenu(QMenu):
18 """ 18 """
19 Class implementing a menu populated from a QAbstractItemModel. 19 Class implementing a menu populated from a QAbstractItemModel.
20 20
21 @signal activated(QModelIndex) emitted when an action has been triggered 21 @signal activated(QModelIndex) emitted when an action has been triggered
22 """ 22 """
23
23 activated = pyqtSignal(QModelIndex) 24 activated = pyqtSignal(QModelIndex)
24 25
25 def __init__(self, parent=None): 26 def __init__(self, parent=None):
26 """ 27 """
27 Constructor 28 Constructor
28 29
29 @param parent reference to the parent widget (QWidget) 30 @param parent reference to the parent widget (QWidget)
30 """ 31 """
31 super().__init__(parent) 32 super().__init__(parent)
32 33
33 self.__maxRows = -1 34 self.__maxRows = -1
34 self.__firstSeparator = -1 35 self.__firstSeparator = -1
35 self.__maxWidth = -1 36 self.__maxWidth = -1
36 self.__statusBarTextRole = 0 37 self.__statusBarTextRole = 0
37 self.__separatorRole = 0 38 self.__separatorRole = 0
38 self.__model = None 39 self.__model = None
39 self.__root = QModelIndex() 40 self.__root = QModelIndex()
40 self.__dragStartPosition = QPoint() 41 self.__dragStartPosition = QPoint()
41 42
42 self.setAcceptDrops(True) 43 self.setAcceptDrops(True)
43 44
44 self._mouseButton = Qt.MouseButton.NoButton 45 self._mouseButton = Qt.MouseButton.NoButton
45 self._keyboardModifiers = Qt.KeyboardModifier.NoModifier 46 self._keyboardModifiers = Qt.KeyboardModifier.NoModifier
46 self.__dropRow = -1 47 self.__dropRow = -1
47 self.__dropIndex = None 48 self.__dropIndex = None
48 49
49 self.menuActions = [] 50 self.menuActions = []
50 51
51 # This is to ensure it will be shown on Mac OS X 52 # This is to ensure it will be shown on Mac OS X
52 self.addAction("--not populated--") 53 self.addAction("--not populated--")
53 54
54 self.aboutToShow.connect(self.__aboutToShow) 55 self.aboutToShow.connect(self.__aboutToShow)
55 self.triggered.connect(self.__actionTriggered) 56 self.triggered.connect(self.__actionTriggered)
56 57
57 def clear(self): 58 def clear(self):
58 """ 59 """
59 Public method to clear the menu. 60 Public method to clear the menu.
60 """ 61 """
61 super().clear() 62 super().clear()
62 self.menuActions.clear() 63 self.menuActions.clear()
63 64
64 def prePopulated(self): 65 def prePopulated(self):
65 """ 66 """
66 Public method to add any actions before the tree. 67 Public method to add any actions before the tree.
67 68
68 @return flag indicating if any actions were added 69 @return flag indicating if any actions were added
69 """ 70 """
70 return False 71 return False
71 72
72 def postPopulated(self): 73 def postPopulated(self):
73 """ 74 """
74 Public method to add any actions after the tree. 75 Public method to add any actions after the tree.
75 """ 76 """
76 pass 77 pass
77 78
78 def setModel(self, model): 79 def setModel(self, model):
79 """ 80 """
80 Public method to set the model for the menu. 81 Public method to set the model for the menu.
81 82
82 @param model reference to the model (QAbstractItemModel) 83 @param model reference to the model (QAbstractItemModel)
83 """ 84 """
84 self.__model = model 85 self.__model = model
85 86
86 def model(self): 87 def model(self):
87 """ 88 """
88 Public method to get a reference to the model. 89 Public method to get a reference to the model.
89 90
90 @return reference to the model (QAbstractItemModel) 91 @return reference to the model (QAbstractItemModel)
91 """ 92 """
92 return self.__model 93 return self.__model
93 94
94 def setMaxRows(self, rows): 95 def setMaxRows(self, rows):
95 """ 96 """
96 Public method to set the maximum number of entries to show. 97 Public method to set the maximum number of entries to show.
97 98
98 @param rows maximum number of entries to show (integer) 99 @param rows maximum number of entries to show (integer)
99 """ 100 """
100 self.__maxRows = rows 101 self.__maxRows = rows
101 102
102 def maxRows(self): 103 def maxRows(self):
103 """ 104 """
104 Public method to get the maximum number of entries to show. 105 Public method to get the maximum number of entries to show.
105 106
106 @return maximum number of entries to show (integer) 107 @return maximum number of entries to show (integer)
107 """ 108 """
108 return self.__maxRows 109 return self.__maxRows
109 110
110 def setFirstSeparator(self, offset): 111 def setFirstSeparator(self, offset):
111 """ 112 """
112 Public method to set the first separator. 113 Public method to set the first separator.
113 114
114 @param offset row number of the first separator (integer) 115 @param offset row number of the first separator (integer)
115 """ 116 """
116 self.__firstSeparator = offset 117 self.__firstSeparator = offset
117 118
118 def firstSeparator(self): 119 def firstSeparator(self):
119 """ 120 """
120 Public method to get the first separator. 121 Public method to get the first separator.
121 122
122 @return row number of the first separator (integer) 123 @return row number of the first separator (integer)
123 """ 124 """
124 return self.__firstSeparator 125 return self.__firstSeparator
125 126
126 def setRootIndex(self, index): 127 def setRootIndex(self, index):
127 """ 128 """
128 Public method to set the index of the root item. 129 Public method to set the index of the root item.
129 130
130 @param index index of the root item (QModelIndex) 131 @param index index of the root item (QModelIndex)
131 """ 132 """
132 self.__root = index 133 self.__root = index
133 134
134 def rootIndex(self): 135 def rootIndex(self):
135 """ 136 """
136 Public method to get the index of the root item. 137 Public method to get the index of the root item.
137 138
138 @return index of the root item (QModelIndex) 139 @return index of the root item (QModelIndex)
139 """ 140 """
140 return self.__root 141 return self.__root
141 142
142 def setStatusBarTextRole(self, role): 143 def setStatusBarTextRole(self, role):
143 """ 144 """
144 Public method to set the role of the status bar text. 145 Public method to set the role of the status bar text.
145 146
146 @param role role of the status bar text (integer) 147 @param role role of the status bar text (integer)
147 """ 148 """
148 self.__statusBarTextRole = role 149 self.__statusBarTextRole = role
149 150
150 def statusBarTextRole(self): 151 def statusBarTextRole(self):
151 """ 152 """
152 Public method to get the role of the status bar text. 153 Public method to get the role of the status bar text.
153 154
154 @return role of the status bar text (integer) 155 @return role of the status bar text (integer)
155 """ 156 """
156 return self.__statusBarTextRole 157 return self.__statusBarTextRole
157 158
158 def setSeparatorRole(self, role): 159 def setSeparatorRole(self, role):
159 """ 160 """
160 Public method to set the role of the separator. 161 Public method to set the role of the separator.
161 162
162 @param role role of the separator (integer) 163 @param role role of the separator (integer)
163 """ 164 """
164 self.__separatorRole = role 165 self.__separatorRole = role
165 166
166 def separatorRole(self): 167 def separatorRole(self):
167 """ 168 """
168 Public method to get the role of the separator. 169 Public method to get the role of the separator.
169 170
170 @return role of the separator (integer) 171 @return role of the separator (integer)
171 """ 172 """
172 return self.__separatorRole 173 return self.__separatorRole
173 174
174 def __aboutToShow(self): 175 def __aboutToShow(self):
175 """ 176 """
176 Private slot to show the menu. 177 Private slot to show the menu.
177 """ 178 """
178 self.clear() 179 self.clear()
179 180
180 if self.prePopulated(): 181 if self.prePopulated():
181 self.addSeparator() 182 self.addSeparator()
182 max_ = self.__maxRows 183 max_ = self.__maxRows
183 if max_ != -1: 184 if max_ != -1:
184 max_ += self.__firstSeparator 185 max_ += self.__firstSeparator
185 self.createMenu(self.__root, max_, self, self) 186 self.createMenu(self.__root, max_, self, self)
186 self.postPopulated() 187 self.postPopulated()
187 188
188 def createBaseMenu(self): 189 def createBaseMenu(self):
189 """ 190 """
190 Public method to get the menu that is used to populate sub menu's. 191 Public method to get the menu that is used to populate sub menu's.
191 192
192 @return reference to the menu (EricModelMenu) 193 @return reference to the menu (EricModelMenu)
193 """ 194 """
194 return EricModelMenu(self) 195 return EricModelMenu(self)
195 196
196 def createMenu(self, parent, max_, parentMenu=None, menu=None): 197 def createMenu(self, parent, max_, parentMenu=None, menu=None):
197 """ 198 """
198 Public method to put all the children of a parent into a menu of a 199 Public method to put all the children of a parent into a menu of a
199 given length. 200 given length.
200 201
201 @param parent index of the parent item (QModelIndex) 202 @param parent index of the parent item (QModelIndex)
202 @param max_ maximum number of entries (integer) 203 @param max_ maximum number of entries (integer)
203 @param parentMenu reference to the parent menu (QMenu) 204 @param parentMenu reference to the parent menu (QMenu)
204 @param menu reference to the menu to be populated (QMenu) 205 @param menu reference to the menu to be populated (QMenu)
205 """ 206 """
206 if menu is None: 207 if menu is None:
207 v = parent 208 v = parent
208 209
209 title = parent.data() 210 title = parent.data()
210 modelMenu = self.createBaseMenu() 211 modelMenu = self.createBaseMenu()
211 # triggered goes all the way up the menu structure 212 # triggered goes all the way up the menu structure
212 modelMenu.triggered.disconnect(modelMenu.__actionTriggered) 213 modelMenu.triggered.disconnect(modelMenu.__actionTriggered)
213 modelMenu.setTitle(title) 214 modelMenu.setTitle(title)
214 215
215 icon = parent.data(Qt.ItemDataRole.DecorationRole) 216 icon = parent.data(Qt.ItemDataRole.DecorationRole)
216 if icon == NotImplemented or icon is None: 217 if icon == NotImplemented or icon is None:
217 icon = UI.PixmapCache.getIcon("defaultIcon") 218 icon = UI.PixmapCache.getIcon("defaultIcon")
218 modelMenu.setIcon(icon) 219 modelMenu.setIcon(icon)
219 if parentMenu is not None: 220 if parentMenu is not None:
220 self.menuActions.append( 221 self.menuActions.append(parentMenu.addMenu(modelMenu).setData(v))
221 parentMenu.addMenu(modelMenu).setData(v))
222 modelMenu.setRootIndex(parent) 222 modelMenu.setRootIndex(parent)
223 modelMenu.setModel(self.__model) 223 modelMenu.setModel(self.__model)
224 return 224 return
225 225
226 if self.__model is None: 226 if self.__model is None:
227 return 227 return
228 228
229 end = self.__model.rowCount(parent) 229 end = self.__model.rowCount(parent)
230 if max_ != -1: 230 if max_ != -1:
231 end = min(max_, end) 231 end = min(max_, end)
232 232
233 for i in range(end): 233 for i in range(end):
234 idx = self.__model.index(i, 0, parent) 234 idx = self.__model.index(i, 0, parent)
235 if self.__model.hasChildren(idx): 235 if self.__model.hasChildren(idx):
236 self.createMenu(idx, -1, menu) 236 self.createMenu(idx, -1, menu)
237 else: 237 else:
238 if ( 238 if self.__separatorRole != 0 and idx.data(self.__separatorRole):
239 self.__separatorRole != 0 and
240 idx.data(self.__separatorRole)
241 ):
242 self.addSeparator() 239 self.addSeparator()
243 else: 240 else:
244 menu.addAction(self.__makeAction(idx)) 241 menu.addAction(self.__makeAction(idx))
245 242
246 if menu == self and i == self.__firstSeparator - 1: 243 if menu == self and i == self.__firstSeparator - 1:
247 self.addSeparator() 244 self.addSeparator()
248 245
249 def __makeAction(self, idx): 246 def __makeAction(self, idx):
250 """ 247 """
251 Private method to create an action. 248 Private method to create an action.
252 249
253 @param idx index of the item to create an action for (QModelIndex) 250 @param idx index of the item to create an action for (QModelIndex)
254 @return reference to the created action (QAction) 251 @return reference to the created action (QAction)
255 """ 252 """
256 icon = idx.data(Qt.ItemDataRole.DecorationRole) 253 icon = idx.data(Qt.ItemDataRole.DecorationRole)
257 if icon == NotImplemented or icon is None: 254 if icon == NotImplemented or icon is None:
258 icon = UI.PixmapCache.getIcon("defaultIcon") 255 icon = UI.PixmapCache.getIcon("defaultIcon")
259 action = self.makeAction(icon, idx.data(), self) 256 action = self.makeAction(icon, idx.data(), self)
260 action.setStatusTip(idx.data(self.__statusBarTextRole)) 257 action.setStatusTip(idx.data(self.__statusBarTextRole))
261 258
262 v = idx 259 v = idx
263 action.setData(v) 260 action.setData(v)
264 261
265 return action 262 return action
266 263
267 def makeAction(self, icon, text, parent): 264 def makeAction(self, icon, text, parent):
268 """ 265 """
269 Public method to create an action. 266 Public method to create an action.
270 267
271 @param icon icon of the action (QIcon) 268 @param icon icon of the action (QIcon)
272 @param text text of the action (string) 269 @param text text of the action (string)
273 @param parent reference to the parent object (QObject) 270 @param parent reference to the parent object (QObject)
274 @return reference to the created action (QAction) 271 @return reference to the created action (QAction)
275 """ 272 """
276 fm = QFontMetrics(self.font()) 273 fm = QFontMetrics(self.font())
277 if self.__maxWidth == -1: 274 if self.__maxWidth == -1:
278 try: 275 try:
279 self.__maxWidth = fm.horizontalAdvance("m") * 30 276 self.__maxWidth = fm.horizontalAdvance("m") * 30
280 except AttributeError: 277 except AttributeError:
281 self.__maxWidth = fm.width('m') * 30 278 self.__maxWidth = fm.width("m") * 30
282 smallText = fm.elidedText(text, Qt.TextElideMode.ElideMiddle, 279 smallText = fm.elidedText(text, Qt.TextElideMode.ElideMiddle, self.__maxWidth)
283 self.__maxWidth) 280
284
285 return QAction(icon, smallText, parent) 281 return QAction(icon, smallText, parent)
286 282
287 def __actionTriggered(self, action): 283 def __actionTriggered(self, action):
288 """ 284 """
289 Private slot to handle the triggering of an action. 285 Private slot to handle the triggering of an action.
290 286
291 @param action reference to the action that was triggered (QAction) 287 @param action reference to the action that was triggered (QAction)
292 """ 288 """
293 idx = self.index(action) 289 idx = self.index(action)
294 if idx.isValid(): 290 if idx.isValid():
295 self._keyboardModifiers = QApplication.keyboardModifiers() 291 self._keyboardModifiers = QApplication.keyboardModifiers()
296 self.activated[QModelIndex].emit(idx) 292 self.activated[QModelIndex].emit(idx)
297 293
298 def index(self, action): 294 def index(self, action):
299 """ 295 """
300 Public method to get the index of an action. 296 Public method to get the index of an action.
301 297
302 @param action reference to the action to get the index for (QAction) 298 @param action reference to the action to get the index for (QAction)
303 @return index of the action (QModelIndex) 299 @return index of the action (QModelIndex)
304 """ 300 """
305 if action is None: 301 if action is None:
306 return QModelIndex() 302 return QModelIndex()
307 303
308 idx = action.data() 304 idx = action.data()
309 if idx is None: 305 if idx is None:
310 return QModelIndex() 306 return QModelIndex()
311 307
312 if not isinstance(idx, QModelIndex): 308 if not isinstance(idx, QModelIndex):
313 return QModelIndex() 309 return QModelIndex()
314 310
315 return idx 311 return idx
316 312
317 def dragEnterEvent(self, evt): 313 def dragEnterEvent(self, evt):
318 """ 314 """
319 Protected method to handle drag enter events. 315 Protected method to handle drag enter events.
320 316
321 @param evt reference to the event (QDragEnterEvent) 317 @param evt reference to the event (QDragEnterEvent)
322 """ 318 """
323 if self.__model is not None: 319 if self.__model is not None:
324 mimeTypes = self.__model.mimeTypes() 320 mimeTypes = self.__model.mimeTypes()
325 for mimeType in mimeTypes: 321 for mimeType in mimeTypes:
326 if evt.mimeData().hasFormat(mimeType): 322 if evt.mimeData().hasFormat(mimeType):
327 evt.acceptProposedAction() 323 evt.acceptProposedAction()
328 324
329 super().dragEnterEvent(evt) 325 super().dragEnterEvent(evt)
330 326
331 def dropEvent(self, evt): 327 def dropEvent(self, evt):
332 """ 328 """
333 Protected method to handle drop events. 329 Protected method to handle drop events.
334 330
335 @param evt reference to the event (QDropEvent) 331 @param evt reference to the event (QDropEvent)
336 """ 332 """
337 if self.__model is not None: 333 if self.__model is not None:
338 act = self.actionAt(evt.position().toPoint()) 334 act = self.actionAt(evt.position().toPoint())
339 parentIndex = self.__root 335 parentIndex = self.__root
342 else: 338 else:
343 idx = self.index(act) 339 idx = self.index(act)
344 if not idx.isValid(): 340 if not idx.isValid():
345 super().dropEvent(evt) 341 super().dropEvent(evt)
346 return 342 return
347 343
348 row = idx.row() 344 row = idx.row()
349 if self.__model.hasChildren(idx): 345 if self.__model.hasChildren(idx):
350 parentIndex = idx 346 parentIndex = idx
351 row = self.__model.rowCount(idx) 347 row = self.__model.rowCount(idx)
352 348
353 self.__dropRow = row 349 self.__dropRow = row
354 self.__dropIndex = parentIndex 350 self.__dropIndex = parentIndex
355 evt.acceptProposedAction() 351 evt.acceptProposedAction()
356 self.__model.dropMimeData(evt.mimeData(), evt.dropAction(), 352 self.__model.dropMimeData(
357 row, 0, parentIndex) 353 evt.mimeData(), evt.dropAction(), row, 0, parentIndex
354 )
358 self.close() 355 self.close()
359 356
360 super().dropEvent(evt) 357 super().dropEvent(evt)
361 358
362 def mousePressEvent(self, evt): 359 def mousePressEvent(self, evt):
363 """ 360 """
364 Protected method handling mouse press events. 361 Protected method handling mouse press events.
365 362
366 @param evt reference to the event object (QMouseEvent) 363 @param evt reference to the event object (QMouseEvent)
367 """ 364 """
368 if evt.button() == Qt.MouseButton.LeftButton: 365 if evt.button() == Qt.MouseButton.LeftButton:
369 self.__dragStartPosition = evt.position().toPoint() 366 self.__dragStartPosition = evt.position().toPoint()
370 super().mousePressEvent(evt) 367 super().mousePressEvent(evt)
371 368
372 def mouseMoveEvent(self, evt): 369 def mouseMoveEvent(self, evt):
373 """ 370 """
374 Protected method to handle mouse move events. 371 Protected method to handle mouse move events.
375 372
376 @param evt reference to the event (QMouseEvent) 373 @param evt reference to the event (QMouseEvent)
377 """ 374 """
378 if self.__model is None: 375 if self.__model is None:
379 super().mouseMoveEvent(evt) 376 super().mouseMoveEvent(evt)
380 return 377 return
381 378
382 if not (evt.buttons() & Qt.MouseButton.LeftButton): 379 if not (evt.buttons() & Qt.MouseButton.LeftButton):
383 super().mouseMoveEvent(evt) 380 super().mouseMoveEvent(evt)
384 return 381 return
385 382
386 if self.__dragStartPosition.isNull(): 383 if self.__dragStartPosition.isNull():
387 super().mouseMoveEvent(evt) 384 super().mouseMoveEvent(evt)
388 return 385 return
389 386
390 manhattanLength = (evt.position().toPoint() - 387 manhattanLength = (
391 self.__dragStartPosition).manhattanLength() 388 evt.position().toPoint() - self.__dragStartPosition
389 ).manhattanLength()
392 if manhattanLength <= QApplication.startDragDistance(): 390 if manhattanLength <= QApplication.startDragDistance():
393 super().mouseMoveEvent(evt) 391 super().mouseMoveEvent(evt)
394 return 392 return
395 393
396 act = self.actionAt(self.__dragStartPosition) 394 act = self.actionAt(self.__dragStartPosition)
397 if act is None: 395 if act is None:
398 super().mouseMoveEvent(evt) 396 super().mouseMoveEvent(evt)
399 return 397 return
400 398
401 idx = self.index(act) 399 idx = self.index(act)
402 if not idx.isValid(): 400 if not idx.isValid():
403 super().mouseMoveEvent(evt) 401 super().mouseMoveEvent(evt)
404 return 402 return
405 403
406 drag = QDrag(self) 404 drag = QDrag(self)
407 drag.setMimeData(self.__model.mimeData([idx])) 405 drag.setMimeData(self.__model.mimeData([idx]))
408 actionRect = self.actionGeometry(act) 406 actionRect = self.actionGeometry(act)
409 drag.setPixmap(self.grab(actionRect)) 407 drag.setPixmap(self.grab(actionRect))
410 408
411 if drag.exec() == Qt.DropAction.MoveAction: 409 if drag.exec() == Qt.DropAction.MoveAction:
412 row = idx.row() 410 row = idx.row()
413 if self.__dropIndex == idx.parent() and self.__dropRow <= row: 411 if self.__dropIndex == idx.parent() and self.__dropRow <= row:
414 row += 1 412 row += 1
415 self.__model.removeRow(row, self.__root) 413 self.__model.removeRow(row, self.__root)
416 414
417 if not self.isAncestorOf(drag.target()): 415 if not self.isAncestorOf(drag.target()):
418 self.close() 416 self.close()
419 else: 417 else:
420 self.aboutToShow.emit() 418 self.aboutToShow.emit()
421 419
422 def mouseReleaseEvent(self, evt): 420 def mouseReleaseEvent(self, evt):
423 """ 421 """
424 Protected method handling mouse release events. 422 Protected method handling mouse release events.
425 423
426 @param evt reference to the event object (QMouseEvent) 424 @param evt reference to the event object (QMouseEvent)
427 """ 425 """
428 self._mouseButton = evt.button() 426 self._mouseButton = evt.button()
429 self._keyboardModifiers = evt.modifiers() 427 self._keyboardModifiers = evt.modifiers()
430 428
431 if evt.button() == Qt.MouseButton.LeftButton: 429 if evt.button() == Qt.MouseButton.LeftButton:
432 self.__dragStartPosition = QPoint() 430 self.__dragStartPosition = QPoint()
433 431
434 super().mouseReleaseEvent(evt) 432 super().mouseReleaseEvent(evt)
435 433
436 def resetFlags(self): 434 def resetFlags(self):
437 """ 435 """
438 Public method to reset the saved internal state. 436 Public method to reset the saved internal state.
439 """ 437 """
440 self._mouseButton = Qt.MouseButton.NoButton 438 self._mouseButton = Qt.MouseButton.NoButton
441 self._keyboardModifiers = Qt.KeyboardModifier.NoModifier 439 self._keyboardModifiers = Qt.KeyboardModifier.NoModifier
442 440
443 def removeEntry(self, idx): 441 def removeEntry(self, idx):
444 """ 442 """
445 Public method to remove a menu entry. 443 Public method to remove a menu entry.
446 444
447 @param idx index of the entry to be removed (QModelIndex) 445 @param idx index of the entry to be removed (QModelIndex)
448 """ 446 """
449 row = idx.row() 447 row = idx.row()
450 self.__model.removeRow(row, self.__root) 448 self.__model.removeRow(row, self.__root)
451 self.aboutToShow.emit() 449 self.aboutToShow.emit()

eric ide

mercurial