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.

Line 
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
15from ..gui.quickhelp import QuickHelp
16from .widgettoolbox import WidgetToolBox, iter_item
17
18from ..registry.qt import QtWidgetRegistry
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
98        self.__action.setChecked(True)
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
123        self.__action.setChecked(False)
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
178class QuickHelpWidget(QuickHelp):
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()
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
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.