E4Gui/E4ModelMenu.py

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

eric ide

mercurial