source: orange/Orange/OrangeCanvas/application/canvastooldock.py @ 11242:098134fa1ec1

Revision 11242:098134fa1ec1, 10.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 16 months ago (diff)

Save and restore dock help widget state.

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