source: orange/Orange/OrangeCanvas/application/canvastooldock.py @ 11243:e788addef69c

Revision 11243:e788addef69c, 10.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 16 months ago (diff)

Implemented a different way to handle toolbox/canvas hover/selection help text.

Now using a QStatusTip subclass to notify the top level window.

RevLine 
[11120]1"""
2Orange Canvas Tool Dock widget
3
4"""
5from PyQt4.QtGui import (
6    QWidget, QSplitter, QVBoxLayout, QTextEdit, QAction, QPalette,
7    QSizePolicy
8)
9
10from PyQt4.QtCore import Qt, QSize, QObject, QPropertyAnimation, QEvent
11from PyQt4.QtCore import pyqtProperty as Property
12
13from ..gui.toolgrid import ToolGrid
14from ..gui.toolbar import DynamicResizeToolBar
[11243]15from ..gui.quickhelp import QuickHelp
[11120]16from .widgettoolbox import WidgetToolBox, iter_item
[11243]17
[11133]18from ..registry.qt import QtWidgetRegistry
[11120]19
20
21class SplitterResizer(QObject):
22    """An object able to control the size of a widget in a
23    QSpliter instance.
24
25    """
26
27    def __init__(self, parent=None):
28        QObject.__init__(self, parent)
29        self.__splitter = None
30        self.__widget = None
31        self.__animationEnabled = True
32        self.__size = -1
33        self.__expanded = False
34        self.__animation = QPropertyAnimation(self, "size_", self)
35
36        self.__action = QAction("toogle-expanded", self, checkable=True)
37        self.__action.triggered[bool].connect(self.setExpanded)
38
39    def setSize(self, size):
40        """Set the size of the controlled widget (either width or height
41        depending on the orientation).
42
43        """
44        if self.__size != size:
45            self.__size = size
46            self.__update()
47
48    def size(self):
49        """Return the size of the widget in the splitter (either height of
50        width) depending on the splitter orientation.
51
52        """
53        if self.__splitter and self.__widget:
54            index = self.__splitter.indexOf(self.__widget)
55            sizes = self.__splitter.sizes()
56            return sizes[index]
57        else:
58            return -1
59
60    size_ = Property(int, fget=size, fset=setSize)
61
62    def setAnimationEnabled(self, enable):
63        """Enable/disable animation.
64        """
65        self.__animation.setDuration(0 if enable else 200)
66
67    def animationEnabled(self):
68        return self.__animation.duration() == 0
69
70    def setSplitterAndWidget(self, splitter, widget):
71        """Set the QSplitter and QWidget instance the resizer should control.
72
73        .. note:: the widget must be in the splitter.
74
75        """
76        if splitter and widget and not splitter.indexOf(widget) > 0:
77            raise ValueError("Widget must be in a spliter.")
78
79        if self.__widget:
80            self.__widget.removeEventFilter()
81        self.__splitter = splitter
82        self.__widget = widget
83
84        if widget:
85            widget.installEventFilter(self)
86
87        self.__update()
88
89    def toogleExpandedAction(self):
90        """Return a QAction that can be used to toggle expanded state.
91        """
92        return self.__action
93
94    def open(self):
95        """Open the controlled widget (expand it to it sizeHint).
96        """
97        self.__expanded = True
[11242]98        self.__action.setChecked(True)
[11120]99
100        if not (self.__splitter and self.__widget):
101            return
102
103        size = self.size()
104        if size > 0:
105            # Already has non zero size.
106            return
107
108        hint = self.__widget.sizeHint()
109
110        if self.__splitter.orientation() == Qt.Vertical:
111            end = hint.height()
112        else:
113            end = hint.width()
114
115        self.__animation.setStartValue(0)
116        self.__animation.setEndValue(end)
117        self.__animation.start()
118
119    def close(self):
120        """Close the controlled widget (shrink to size 0).
121        """
122        self.__expanded = False
[11242]123        self.__action.setChecked(False)
[11120]124
125        if not (self.__splitter and self.__widget):
126            return
127
128        self.__animation.setStartValue(self.size())
129        self.__animation.setEndValue(0)
130        self.__animation.start()
131
132    def setExpanded(self, expanded):
133        """Set the expanded state.
134
135        """
136        if self.__expanded != expanded:
137            if expanded:
138                self.open()
139            else:
140                self.close()
141
142    def expanded(self):
143        """Return the expanded state.
144        """
145        return self.__expanded
146
147    def __update(self):
148        """Update the splitter sizes.
149        """
150        if self.__splitter and self.__widget:
151            splitter = self.__splitter
152            index = splitter.indexOf(self.__widget)
153            sizes = splitter.sizes()
154            current = sizes[index]
155            diff = current - self.__size
156            sizes[index] = self.__size
157            sizes[index - 1] = sizes[index - 1] + diff
158
159            self.__splitter.setSizes(sizes)
160
161    def eventFilter(self, obj, event):
162        if event.type() == QEvent.Resize:
163            if self.__splitter.orientation() == Qt.Vertical:
164                size = event.size().height()
165            else:
166                size = event.size().width()
167
168            if self.__expanded and size == 0:
169                self.__action.setChecked(False)
170                self.__expanded = False
171            elif not self.__expanded and size > 0:
172                self.__action.setChecked(True)
173                self.__expanded = True
174
175        return QObject.eventFilter(self, obj, event)
176
177
[11243]178class QuickHelpWidget(QuickHelp):
[11120]179    def minimumSizeHint(self):
180        """Reimplemented to allow the Splitter to resize the widget
181        with a continuous animation.
182
183        """
184        hint = QTextEdit.minimumSizeHint(self)
185        return QSize(hint.width(), 0)
186
187
188class CanvasToolDock(QWidget):
189    """Canvas dock widget with widget toolbox, quick help and
190    canvas actions.
191
192    """
193    def __init__(self, parent=None, **kwargs):
194        QWidget.__init__(self, parent, **kwargs)
195
196        self.__setupUi()
197
198    def __setupUi(self):
199        layout = QVBoxLayout()
200        layout.setContentsMargins(0, 0, 0, 0)
201        layout.setSpacing(0)
202
203        self.toolbox = WidgetToolBox()
204
205        self.help = QuickHelpWidget(objectName="quick-help")
206
207        self.__splitter = QSplitter()
208        self.__splitter.setOrientation(Qt.Vertical)
209
210        self.__splitter.addWidget(self.toolbox)
211        self.__splitter.addWidget(self.help)
212
213        self.toolbar = DynamicResizeToolBar()
214        self.toolbar.setMovable(False)
215        self.toolbar.setFloatable(False)
216
217        self.toolbar.setSizePolicy(QSizePolicy.Ignored,
218                                   QSizePolicy.Preferred)
219
220        layout.addWidget(self.__splitter, 10)
221        layout.addWidget(self.toolbar)
222
223        self.setLayout(layout)
224        self.__splitterResizer = SplitterResizer()
225        self.__splitterResizer.setSplitterAndWidget(self.__splitter, self.help)
226
227    def setQuickHelpVisible(self, state):
228        """Set the quick help box visibility status.
229        """
230        self.__splitterResizer.setExpanded(state)
231
232    def quickHelpVisible(self):
233        return self.__splitterResizer.expanded()
234
235    def setQuickHelpAnimationEnabled(self, enabled):
236        """Enable/disable the quick help animation.
237        """
238        self.__splitterResizer.setAnimationEnabled(enabled)
239
240    def toogleQuickHelpAction(self):
241        """Return a checkable QAction for help show/hide.
242        """
243        return self.__splitterResizer.toogleExpandedAction()
244
245
246class QuickCategoryToolbar(ToolGrid):
247    """A toolbar with category buttons.
248    """
249    def __init__(self, parent=None, buttonSize=None, iconSize=None):
250        ToolGrid.__init__(self, parent, 1, buttonSize, iconSize,
251                          Qt.ToolButtonIconOnly)
252        self.__model = None
253
254    def setColumnCount(self, count):
255        raise Exception("Cannot set the column count on a Toolbar")
256
257    def setModel(self, model):
258        """Set the registry model.
259        """
260        if self.__model is not None:
261            self.__model.itemChanged.disconnect(self.__on_itemChanged)
262            self.__model.rowsInserted.disconnect(self.__on_rowsInserted)
263            self.__model.rowsRemoved.disconnect(self.__on_rowsRemoved)
264            self.clear()
265
266        self.__model = model
267        if self.__model is not None:
268            self.__model.itemChanged.connect(self.__on_itemChanged)
269            self.__model.rowsInserted.connect(self.__on_rowsInserted)
270            self.__model.rowsRemoved.connect(self.__on_rowsRemoved)
271            self.__initFromModel(model)
272
273    def __initFromModel(self, model):
274        """Initialize the toolbar from the model.
275        """
276        root = model.invisibleRootItem()
277        for item in iter_item(root):
278            action = self.createActionForItem(item)
279            self.addAction(action)
280
281    def createActionForItem(self, item):
282        """Create the QAction instance for item.
283        """
284        action = QAction(item.icon(), item.text(), self,
285                         toolTip=item.toolTip())
286        action.setData(item)
287        return action
288
289    def createButtonForAction(self, action):
290        """Create a button for the action.
291        """
292        button = ToolGrid.createButtonForAction(self, action)
293
294        item = action.data().toPyObject()
[11133]295        if item.data(Qt.BackgroundRole).isValid():
296            brush = item.background()
297        elif item.data(QtWidgetRegistry.BACKGROUND_ROLE).isValid():
298            brush = item.data(QtWidgetRegistry.BACKGROUND_ROLE).toPyObject()
299        else:
300            brush = self.palette().brush(QPalette.Button)
301
[11120]302        palette = button.palette()
303        palette.setColor(QPalette.Button, brush.color())
304        palette.setColor(QPalette.Window, brush.color())
305        button.setPalette(palette)
306        button.setProperty("quick-category-toolbutton", True)
307
308        style_sheet = ("QToolButton {\n"
309                       "    background-color: %s;\n"
310                       "    border: none;\n"
311                       "    border-bottom: 1px solid palette(dark);\n"
312                       "}")
313        button.setStyleSheet(style_sheet % brush.color().name())
314
315        return button
316
317    def __on_itemChanged(self, item):
318        root = self.__model.invisibleRootItem()
319        if item.parentItem() == root:
320            row = item.row()
321            action = self._gridSlots[row].action
322            action.setText(item.text())
323            action.setIcon(item.icon())
324            action.setToolTip(item.toolTip())
325
326    def __on_rowsInserted(self, parent, start, end):
327        root = self.__model.invisibleRootItem()
328        if root == parent:
329            for index in range(start, end + 1):
330                item = parent.child(index)
331                self.addAction(self.createActionForItem(item))
332
333    def __on_rowsRemoved(self, parent, start, end):
334        root = self.__model.invisibleRootItem()
335        if root == parent:
336            for index in range(end, start - 1, -1):
337                action = self._gridSlots[index].action
338                self.removeAction(action)
Note: See TracBrowser for help on using the repository browser.