eric7/E5Gui/E5ModelToolBar.py

branch
eric7
changeset 8312
800c432b34c8
parent 8222
5994b80b8760
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a tool bar populated from a QAbstractItemModel.
8 """
9
10 from PyQt5.QtCore import pyqtSignal, Qt, QModelIndex, QPoint, QEvent
11 from PyQt5.QtGui import QDrag, QIcon
12 from PyQt5.QtWidgets import QApplication, QToolBar, QToolButton
13
14
15 class E5ModelToolBar(QToolBar):
16 """
17 Class implementing a tool bar populated from a QAbstractItemModel.
18
19 @signal activated(QModelIndex) emitted when an action has been triggered
20 """
21 activated = pyqtSignal(QModelIndex)
22
23 def __init__(self, title=None, parent=None):
24 """
25 Constructor
26
27 @param title title for the tool bar (string)
28 @param parent reference to the parent widget (QWidget)
29 """
30 if title is not None:
31 super().__init__(title, parent)
32 else:
33 super().__init__(parent)
34
35 self.__model = None
36
37 self.__root = QModelIndex()
38 self.__dragStartPosition = QPoint()
39
40 if self.isVisible():
41 self._build()
42
43 self.setAcceptDrops(True)
44
45 self._mouseButton = Qt.MouseButton.NoButton
46 self._keyboardModifiers = Qt.KeyboardModifiers(
47 Qt.KeyboardModifier.NoModifier)
48 self.__dropRow = -1
49 self.__dropIndex = None
50
51 def setModel(self, model):
52 """
53 Public method to set the model for the tool bar.
54
55 @param model reference to the model (QAbstractItemModel)
56 """
57 if self.__model is not None:
58 self.__model.modelReset.disconnect(self._build)
59 self.__model.rowsInserted[QModelIndex, int, int].disconnect(
60 self._build)
61 self.__model.rowsRemoved[QModelIndex, int, int].disconnect(
62 self._build)
63 self.__model.dataChanged.disconnect(
64 self._build)
65
66 self.__model = model
67
68 if self.__model is not None:
69 self.__model.modelReset.connect(self._build)
70 self.__model.rowsInserted[QModelIndex, int, int].connect(
71 self._build)
72 self.__model.rowsRemoved[QModelIndex, int, int].connect(
73 self._build)
74 self.__model.dataChanged.connect(
75 self._build)
76
77 def model(self):
78 """
79 Public method to get a reference to the model.
80
81 @return reference to the model (QAbstractItemModel)
82 """
83 return self.__model
84
85 def setRootIndex(self, idx):
86 """
87 Public method to set the root index.
88
89 @param idx index to be set as the root index (QModelIndex)
90 """
91 self.__root = idx
92
93 def rootIndex(self):
94 """
95 Public method to get the root index.
96
97 @return root index (QModelIndex)
98 """
99 return self.__root
100
101 def _build(self):
102 """
103 Protected slot to build the tool bar.
104 """
105 if self.__model is None:
106 return
107
108 self.clear()
109
110 for i in range(self.__model.rowCount(self.__root)):
111 idx = self.__model.index(i, 0, self.__root)
112
113 title = idx.data(Qt.ItemDataRole.DisplayRole)
114 icon = idx.data(Qt.ItemDataRole.DecorationRole)
115 if icon == NotImplemented or icon is None:
116 icon = QIcon()
117 folder = self.__model.hasChildren(idx)
118
119 act = self.addAction(icon, title)
120 act.setData(idx)
121
122 button = self.widgetForAction(act)
123 button.installEventFilter(self)
124
125 if folder:
126 menu = self._createMenu()
127 menu.setModel(self.__model)
128 menu.setRootIndex(idx)
129 act.setMenu(menu)
130 button.setPopupMode(
131 QToolButton.ToolButtonPopupMode.InstantPopup)
132 button.setToolButtonStyle(
133 Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
134
135 def index(self, action):
136 """
137 Public method to get the index of an action.
138
139 @param action reference to the action to get the index for (QAction)
140 @return index of the action (QModelIndex)
141 """
142 if action is None:
143 return QModelIndex()
144
145 idx = action.data()
146 if idx is None:
147 return QModelIndex()
148
149 if not isinstance(idx, QModelIndex):
150 return QModelIndex()
151
152 return idx
153
154 def _createMenu(self):
155 """
156 Protected method to create the menu for a tool bar action.
157
158 @return menu for a tool bar action (E5ModelMenu)
159 """
160 from .E5ModelMenu import E5ModelMenu
161 return E5ModelMenu(self)
162
163 def eventFilter(self, obj, evt):
164 """
165 Public method to handle event for other objects.
166
167 @param obj reference to the object (QObject)
168 @param evt reference to the event (QEvent)
169 @return flag indicating that the event should be filtered out (boolean)
170 """
171 if evt.type() == QEvent.Type.MouseButtonRelease:
172 self._mouseButton = evt.button()
173 self._keyboardModifiers = evt.modifiers()
174 act = obj.defaultAction()
175 idx = self.index(act)
176 if idx.isValid():
177 self.activated[QModelIndex].emit(idx)
178 elif (
179 evt.type() == QEvent.Type.MouseButtonPress and
180 evt.buttons() & Qt.MouseButton.LeftButton
181 ):
182 self.__dragStartPosition = self.mapFromGlobal(evt.globalPos())
183
184 return False
185
186 def dragEnterEvent(self, evt):
187 """
188 Protected method to handle drag enter events.
189
190 @param evt reference to the event (QDragEnterEvent)
191 """
192 if self.__model is not None:
193 mimeTypes = self.__model.mimeTypes()
194 for mimeType in mimeTypes:
195 if evt.mimeData().hasFormat(mimeType):
196 evt.acceptProposedAction()
197
198 super().dragEnterEvent(evt)
199
200 def dropEvent(self, evt):
201 """
202 Protected method to handle drop events.
203
204 @param evt reference to the event (QDropEvent)
205 @exception RuntimeError raised to indicate an invalid model index
206 """
207 if self.__model is not None:
208 act = self.actionAt(evt.pos())
209 parentIndex = self.__root
210 if act is None:
211 row = self.__model.rowCount(self.__root)
212 else:
213 idx = self.index(act)
214 if not idx.isValid():
215 raise RuntimeError("invalid index")
216 row = idx.row()
217 if self.__model.hasChildren(idx):
218 parentIndex = idx
219 row = self.__model.rowCount(idx)
220
221 self.__dropRow = row
222 self.__dropIndex = parentIndex
223 evt.acceptProposedAction()
224 self.__model.dropMimeData(evt.mimeData(), evt.dropAction(),
225 row, 0, parentIndex)
226
227 super().dropEvent(evt)
228
229 def mouseMoveEvent(self, evt):
230 """
231 Protected method to handle mouse move events.
232
233 @param evt reference to the event (QMouseEvent)
234 @exception RuntimeError raised to indicate an invalid model index
235 """
236 if self.__model is None:
237 super().mouseMoveEvent(evt)
238 return
239
240 if not (evt.buttons() & Qt.MouseButton.LeftButton):
241 super().mouseMoveEvent(evt)
242 return
243
244 manhattanLength = (evt.pos() -
245 self.__dragStartPosition).manhattanLength()
246 if manhattanLength <= QApplication.startDragDistance():
247 super().mouseMoveEvent(evt)
248 return
249
250 act = self.actionAt(self.__dragStartPosition)
251 if act is None:
252 super().mouseMoveEvent(evt)
253 return
254
255 idx = self.index(act)
256 if not idx.isValid():
257 raise RuntimeError("invalid index")
258
259 drag = QDrag(self)
260 drag.setMimeData(self.__model.mimeData([idx]))
261 actionRect = self.actionGeometry(act)
262 drag.setPixmap(self.grab(actionRect))
263
264 if drag.exec() == Qt.DropAction.MoveAction:
265 row = idx.row()
266 if self.__dropIndex == idx.parent() and self.__dropRow <= row:
267 row += 1
268 self.__model.removeRow(row, self.__root)
269
270 def hideEvent(self, evt):
271 """
272 Protected method to handle hide events.
273
274 @param evt reference to the hide event (QHideEvent)
275 """
276 self.clear()
277 super().hideEvent(evt)
278
279 def showEvent(self, evt):
280 """
281 Protected method to handle show events.
282
283 @param evt reference to the hide event (QHideEvent)
284 """
285 if len(self.actions()) == 0:
286 self._build()
287 super().showEvent(evt)
288
289 def resetFlags(self):
290 """
291 Public method to reset the saved internal state.
292 """
293 self._mouseButton = Qt.MouseButton.NoButton
294 self._keyboardModifiers = Qt.KeyboardModifiers(
295 Qt.KeyboardModifier.NoModifier)

eric ide

mercurial