E5Gui/E5ModelMenu.py

changeset 55
b5c84934de9c
child 461
34528aaedf1c
child 792
a13346916170
equal deleted inserted replaced
54:31463df17fd5 55:b5c84934de9c
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2010 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 E5ModelMenu(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 (E5ModelMenu)
177 """
178 return E5ModelMenu(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 = parent
191
192 title = parent.data()
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)
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):
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)
237 if icon == NotImplemented or icon is None:
238 icon = UI.PixmapCache.getIcon("defaultIcon.png")
239 action = self.makeAction(icon, idx.data(), self)
240 action.setStatusTip(idx.data(self.__statusBarTextRole))
241
242 v = 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 idx = action.data()
284 if idx is None:
285 return QModelIndex()
286
287 if not isinstance(idx, QModelIndex):
288 return QModelIndex()
289
290 return idx
291
292 def dragEnterEvent(self, evt):
293 """
294 Protected method to handle drag enter events.
295
296 @param evt reference to the event (QDragEnterEvent)
297 """
298 if self.__model is not None:
299 mimeTypes = self.__model.mimeTypes()
300 for mimeType in mimeTypes:
301 if evt.mimeData().hasFormat(mimeType):
302 evt.acceptProposedAction()
303
304 QMenu.dragEnterEvent(self, evt)
305
306 def dropEvent(self, evt):
307 """
308 Protected method to handle drop events.
309
310 @param evt reference to the event (QDropEvent)
311 """
312 if self.__model is not None:
313 act = self.actionAt(evt.pos())
314 parentIndex = self.__root
315 if act is None:
316 row = self.__model.rowCount(self.__root)
317 else:
318 idx = self.index(act)
319 if not idx.isValid():
320 QMenu.dropEvent(self, evt)
321 return
322
323 row = idx.row()
324 if self.__model.hasChildren(idx):
325 parentIndex = idx
326 row = self.__model.rowCount(idx)
327
328 self.__dropRow = row
329 self.__dropIndex = parentIndex
330 evt.acceptProposedAction()
331 self.__model.dropMimeData(evt.mimeData(), evt.dropAction(),
332 row, 0, parentIndex)
333 self.close()
334
335 QMenu.dropEvent(self, evt)
336
337 def mousePressEvent(self, evt):
338 """
339 Protected method handling mouse press events.
340
341 @param evt reference to the event object (QMouseEvent)
342 """
343 if evt.button() == Qt.LeftButton:
344 self.__dragStartPosition = evt.pos()
345 QMenu.mousePressEvent(self, evt)
346
347 def mouseMoveEvent(self, evt):
348 """
349 Protected method to handle mouse move events.
350
351 @param evt reference to the event (QMouseEvent)
352 """
353 if self.__model is None:
354 QMenu.mouseMoveEvent(self, evt)
355 return
356
357 if not (evt.buttons() & Qt.LeftButton):
358 QMenu.mouseMoveEvent(self, evt)
359 return
360
361 manhattanLength = (evt.pos() - self.__dragStartPosition).manhattanLength()
362 if manhattanLength <= QApplication.startDragDistance():
363 QMenu.mouseMoveEvent(self, evt)
364 return
365
366 act = self.actionAt(self.__dragStartPosition)
367 if act is None:
368 QMenu.mouseMoveEvent(self, evt)
369 return
370
371 idx = self.index(act)
372 if not idx.isValid():
373 QMenu.mouseMoveEvent(self, evt)
374 return
375
376 drag = QDrag(self)
377 drag.setMimeData(self.__model.mimeData([idx]))
378 actionRect = self.actionGeometry(act)
379 drag.setPixmap(QPixmap.grabWidget(self, actionRect))
380
381 if drag.exec_() == Qt.MoveAction:
382 row = idx.row()
383 if self.__dropIndex == idx.parent() and self.__dropRow <= row:
384 row += 1
385 self.__model.removeRow(row, self.__root)
386
387 if not self.isAncestorOf(drag.target()):
388 self.close()
389 else:
390 self.emit(SIGNAL("aboutToShow()"))
391
392 def mouseReleaseEvent(self, evt):
393 """
394 Protected method handling mouse release events.
395
396 @param evt reference to the event object (QMouseEvent)
397 """
398 self._mouseButton = evt.button()
399 self._keyboardModifiers = evt.modifiers()
400 QMenu.mouseReleaseEvent(self, evt)
401
402 def resetFlags(self):
403 """
404 Public method to reset the saved internal state.
405 """
406 self._mouseButton = Qt.NoButton
407 self._keyboardModifiers = Qt.KeyboardModifiers(Qt.NoModifier)
408
409 def removeEntry(self, idx):
410 """
411 Public method to remove a menu entry.
412
413 @param idx index of the entry to be removed (QModelIndex)
414 """
415 row = idx.row()
416 self.__model.removeRow(row, self.__root)
417 self.emit(SIGNAL("aboutToShow()"))

eric ide

mercurial