source: orange/Orange/OrangeCanvas/application/widgettoolbox.py @ 11133:cde8738d4bbb

Revision 11133:cde8738d4bbb, 10.6 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Store widget's background brush in a custom user role.

Line 
1"""
2Widget Tool Box
3===============
4
5
6A tool box with a tool grid for each category.
7
8"""
9
10import logging
11
12from PyQt4.QtGui import (
13    QAbstractButton, QSizePolicy, QAction, QApplication, QDrag, QPalette,
14    QBrush
15)
16
17from PyQt4.QtCore import Qt, QModelIndex, QSize
18
19from PyQt4.QtCore import QObject, QEvent, QMimeData
20from PyQt4.QtCore import  pyqtSignal as Signal, pyqtProperty as Property
21
22from ..gui.toolbox import ToolBox, create_tab_gradient
23from ..gui.toolgrid import ToolGrid
24
25from ..registry.qt import QtWidgetRegistry
26
27log = logging.getLogger(__name__)
28
29
30def iter_item(item):
31    """Iterate over child items of a `QStandardItem`.
32    """
33    for i in range(item.rowCount()):
34        yield item.child(i)
35
36
37class WidgetToolGrid(ToolGrid):
38    """A Tool Grid with widget buttons. Populates the widget buttons
39    from a item model. Also adds support for drag operations.
40
41    """
42    def __init__(self, *args, **kwargs):
43        ToolGrid.__init__(self, *args, **kwargs)
44
45        self.__model = None
46        self.__rootIndex = None
47        self.__rootItem = None
48        self.__rootItem = None
49        self.__actionRole = QtWidgetRegistry.WIDGET_ACTION_ROLE
50
51        self.__dragListener = DragStartEventListener(self)
52        self.__dragListener.dragStartOperationRequested.connect(
53            self.__startDrag
54        )
55
56    def setModel(self, model, rootIndex=QModelIndex()):
57        """Set a model (`QStandardItemModel`) for the tool grid. The
58        widget actions are children of the rootIndex.
59
60        """
61        if self.__model is not None:
62            self.__model.rowsInserted.disconnect(self.__on_rowsInserted)
63            self.__model.rowsRemoved.disconnect(self.__on_rowsRemoved)
64            self.__model = None
65
66        self.__model = model
67        self.__rootIndex = rootIndex
68
69        if self.__model is not None:
70            self.__model.rowsInserted.connect(self.__on_rowsInserted)
71            self.__model.rowsRemoved.connect(self.__on_rowsRemoved)
72
73        self.__initFromModel(model, rootIndex)
74
75    def model(self):
76        """Return the model for the tool grid.
77        """
78        return self.__model
79
80    def rootIndex(self):
81        return self.__rootIndex
82
83    def setActionRole(self, role):
84        """Set the action role.
85        """
86        if self.__actionRole != role:
87            self.__actionRole = role
88            if self.__model:
89                self.__update()
90
91    def actionRole(self):
92        """Return the action role.
93        """
94        return self.__actionRole
95
96    def insertAction(self, index, action):
97        rval = ToolGrid.insertAction(self, index, action)
98        button = self.buttonForAction(action)
99        button.installEventFilter(self.__dragListener)
100        return rval
101
102    def removeAction(self, action):
103        button = self.buttonForAction(action)
104        button.removeEventFilter(self.__dragListener)
105        ToolGrid.removeAction(self, action)
106
107    def __initFromModel(self, model, rootIndex):
108        """Initialize the grid from the model with rootIndex as the root.
109        """
110        if not rootIndex.isValid():
111            rootItem = model.invisibleRootItem()
112        else:
113            rootItem = model.itemFromIndex(rootIndex)
114
115        self.__rootItem = rootItem
116
117        for i, item in enumerate(iter_item(rootItem)):
118            self.__insertItem(i, item)
119
120    def __insertItem(self, index, item):
121        """Insert a widget action (from a `QStandardItem`) at index.
122        """
123        value = item.data(self.__actionRole)
124        if value.isValid():
125            action = value.toPyObject()
126        else:
127            action = QAction(item.text(), self)
128            action.setIcon(item.icon())
129
130        self.insertAction(index, action)
131
132    def __update(self):
133        self.clear()
134        self.__initFromModel(self.__model, self.__rootIndex)
135
136    def __on_rowsInserted(self, parent, start, end):
137        """Insert items from range start:end into the grid.
138        """
139        item = self.__model.itemForIndex(parent)
140        if item == self.__rootItem:
141            for i in range(start, end + 1):
142                item = self.__rootItem.child(i)
143                self._insertItem(i, item)
144
145    def __on_rowsRemoved(self, parent, start, end):
146        """Remove items from range start:end from the grid.
147        """
148        item = self.__model.itemForIndex(parent)
149        if item == self.__rootItem:
150            for i in range(start - 1, end):
151                action = self._gridSlots[i].action
152                self.removeAction(action)
153
154    def __startDrag(self, button):
155        """Start a drag from button
156        """
157        action = button.defaultAction()
158        desc = action.data().toPyObject()  # Widget Description
159        icon = action.icon()
160        drag_data = QMimeData()
161        drag_data.setData(
162            "application/vnv.orange-canvas.registry.qualified-name",
163            desc.qualified_name
164        )
165        drag = QDrag(button)
166        drag.setPixmap(icon.pixmap(self.iconSize))
167        drag.setMimeData(drag_data)
168        drag.exec_(Qt.CopyAction)
169
170
171class DragStartEventListener(QObject):
172    """An event filter object that can be used to detect drag start
173    operation on buttons which otherwise do not support it.
174
175    """
176    dragStartOperationRequested = Signal(QAbstractButton)
177
178    def __init__(self, parent=None, **kwargs):
179        QObject.__init__(self, parent, **kwargs)
180        self.button = None
181        self.buttonDownObj = None
182        self.buttonDownPos = None
183
184    def eventFilter(self, obj, event):
185        if event.type() == QEvent.MouseButtonPress:
186            self.buttonDownPos = event.pos()
187            self.buttonDownObj = obj
188            self.button = event.button()
189
190        elif event.type() == QEvent.MouseMove and obj is self.buttonDownObj:
191            if (self.buttonDownPos - event.pos()).manhattanLength() > \
192                    QApplication.startDragDistance() and \
193                    not self.buttonDownObj.hitButton(event.pos()):
194                # Process the widget's mouse event, before starting the
195                # drag operation, so the widget can update its state.
196                obj.mouseMoveEvent(event)
197
198                self.dragStartOperationRequested.emit(obj)
199
200                self.buttonDownObj.setDown(False)
201                self.button = None
202                self.buttonDownPos = None
203                self.buttonDownObj = None
204                return True  # Already handled
205
206        return QObject.eventFilter(self, obj, event)
207
208
209class WidgetToolBox(ToolBox):
210
211    triggered = Signal(QAction)
212    hovered = Signal(QAction)
213
214    def __init__(self, parent=None):
215        ToolBox.__init__(self, parent)
216        self.__model = None
217        self.__iconSize = QSize(25, 25)
218        self.__buttonSize = QSize(50, 50)
219        self.setSizePolicy(QSizePolicy.Fixed,
220                           QSizePolicy.Expanding)
221
222    def setIconSize(self, size):
223        """Set the widget icon size.
224        """
225        self.__iconSize = size
226        for widget in  map(self.widget, range(self.count())):
227            widget.setIconSize(size)
228
229    def iconSize(self):
230        """Return the widget buttons icon size.
231        """
232        return self.__iconSize
233
234    iconSize_ = Property(QSize, fget=iconSize, fset=setIconSize,
235                         designable=True)
236
237    def setButtonSize(self, size):
238        """Set fixed widget button size.
239        """
240        self.__buttonSize = size
241        for widget in map(self.widget, range(self.count())):
242            widget.setButtonSize(size)
243
244    def buttonSize(self):
245        return self.__buttonSize
246
247    buttonSize_ = Property(QSize, fget=buttonSize, fset=setButtonSize,
248                           designable=True)
249
250    def setModel(self, model):
251        """Set the widget registry model for this toolbox.
252        """
253        if self.__model is not None:
254            self.__model.itemChanged.disconnect(self.__on_itemChanged)
255            self.__model.rowsInserted.disconnect(self.__on_rowsInserted)
256            self.__model.rowsRemoved.disconect(self.__on_rowsRemoved)
257
258        self.__model = model
259        if self.__model is not None:
260            self.__model.itemChanged.connect(self.__on_itemChanged)
261            self.__model.rowsInserted.connect(self.__on_rowsInserted)
262            self.__model.rowsRemoved.connect(self.__on_rowsRemoved)
263
264        self.__initFromModel(self.__model)
265
266    def __initFromModel(self, model):
267        for cat_item in iter_item(model.invisibleRootItem()):
268            self.__insertItem(cat_item, self.count())
269
270    def __insertItem(self, item, index):
271        """Insert category item at index.
272        """
273        grid = WidgetToolGrid()
274        grid.setModel(item.model(), item.index())
275
276        grid.actionTriggered.connect(self.triggered)
277        grid.actionHovered.connect(self.hovered)
278
279        grid.setIconSize(self.__iconSize)
280        grid.setButtonSize(self.__buttonSize)
281
282        text = item.text()
283        icon = item.icon()
284        tooltip = item.toolTip()
285
286        # Set the 'tab-title' property to text.
287        grid.setProperty("tab-title", text)
288        grid.setObjectName("widgets-toolbox-grid")
289
290        self.insertItem(index, grid, text, icon, tooltip)
291        button = self.tabButton(index)
292
293        # Set the 'highlight' color
294        if item.data(Qt.BackgroundRole).isValid():
295            brush = item.background()
296        elif item.data(QtWidgetRegistry.BACKGROUND_ROLE).isValid():
297            brush = item.data(QtWidgetRegistry.BACKGROUND_ROLE).toPyObject()
298        else:
299            brush = self.palette().brush(QPalette.Button)
300
301        if not brush.gradient():
302            gradient = create_tab_gradient(brush.color())
303            brush = QBrush(gradient)
304
305        palette = button.palette()
306        palette.setBrush(QPalette.Highlight, brush)
307        button.setPalette(palette)
308
309    def __on_itemChanged(self, item):
310        """Item contents have changed.
311        """
312        parent = item.parent()
313        if parent is self.__model.invisibleRootItem():
314            button = self.tabButton(item.row())
315            button.setIcon(item.icon())
316            button.setText(item.text())
317            button.setToolTip(item.toolTip())
318
319    def __on_rowsInserted(self, parent, start, end):
320        """Items have been inserted in the model.
321        """
322        # Only the top level items (categories) are handled here.
323        if not parent.isValid():
324            root = self.__model.invisibleRootItem()
325            for i in range(start, end + 1):
326                item = root.child(i)
327                self.__insertItem(item, i)
328
329    def __on_rowsRemoved(self, parent, start, end):
330        """Rows have been removed from the model.
331        """
332        # Only the top level items (categories) are handled here.
333        if not parent.isValid():
334            for i in range(end, start - 1, -1):
335                self.removeItem(i)
Note: See TracBrowser for help on using the repository browser.