eric6/E5Gui/E5ModelMenu.py

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

eric ide

mercurial