source: orange/Orange/OrangeCanvas/application/canvastooldock.py @ 11120:20723dda0497

Revision 11120:20723dda0497, 10.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Added canvas dock component widgets.

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
16
17
18class SplitterResizer(QObject):
19    """An object able to control the size of a widget in a
20    QSpliter instance.
21
22    """
23
24    def __init__(self, parent=None):
25        QObject.__init__(self, parent)
26        self.__splitter = None
27        self.__widget = None
28        self.__animationEnabled = True
29        self.__size = -1
30        self.__expanded = False
31        self.__animation = QPropertyAnimation(self, "size_", self)
32
33        self.__action = QAction("toogle-expanded", self, checkable=True)
34        self.__action.triggered[bool].connect(self.setExpanded)
35
36    def setSize(self, size):
37        """Set the size of the controlled widget (either width or height
38        depending on the orientation).
39
40        """
41        if self.__size != size:
42            self.__size = size
43            self.__update()
44
45    def size(self):
46        """Return the size of the widget in the splitter (either height of
47        width) depending on the splitter orientation.
48
49        """
50        if self.__splitter and self.__widget:
51            index = self.__splitter.indexOf(self.__widget)
52            sizes = self.__splitter.sizes()
53            return sizes[index]
54        else:
55            return -1
56
57    size_ = Property(int, fget=size, fset=setSize)
58
59    def setAnimationEnabled(self, enable):
60        """Enable/disable animation.
61        """
62        self.__animation.setDuration(0 if enable else 200)
63
64    def animationEnabled(self):
65        return self.__animation.duration() == 0
66
67    def setSplitterAndWidget(self, splitter, widget):
68        """Set the QSplitter and QWidget instance the resizer should control.
69
70        .. note:: the widget must be in the splitter.
71
72        """
73        if splitter and widget and not splitter.indexOf(widget) > 0:
74            raise ValueError("Widget must be in a spliter.")
75
76        if self.__widget:
77            self.__widget.removeEventFilter()
78        self.__splitter = splitter
79        self.__widget = widget
80
81        if widget:
82            widget.installEventFilter(self)
83
84        self.__update()
85
86    def toogleExpandedAction(self):
87        """Return a QAction that can be used to toggle expanded state.
88        """
89        return self.__action
90
91    def open(self):
92        """Open the controlled widget (expand it to it sizeHint).
93        """
94        self.__expanded = True
95
96        if not (self.__splitter and self.__widget):
97            return
98
99        size = self.size()
100        if size > 0:
101            # Already has non zero size.
102            return
103
104        hint = self.__widget.sizeHint()
105
106        if self.__splitter.orientation() == Qt.Vertical:
107            end = hint.height()
108        else:
109            end = hint.width()
110
111        self.__animation.setStartValue(0)
112        self.__animation.setEndValue(end)
113        self.__animation.start()
114
115    def close(self):
116        """Close the controlled widget (shrink to size 0).
117        """
118        self.__expanded = False
119
120        if not (self.__splitter and self.__widget):
121            return
122
123        self.__animation.setStartValue(self.size())
124        self.__animation.setEndValue(0)
125        self.__animation.start()
126
127    def setExpanded(self, expanded):
128        """Set the expanded state.
129
130        """
131        if self.__expanded != expanded:
132            if expanded:
133                self.open()
134            else:
135                self.close()
136
137    def expanded(self):
138        """Return the expanded state.
139        """
140        return self.__expanded
141
142    def __update(self):
143        """Update the splitter sizes.
144        """
145        if self.__splitter and self.__widget:
146            splitter = self.__splitter
147            index = splitter.indexOf(self.__widget)
148            sizes = splitter.sizes()
149            current = sizes[index]
150            diff = current - self.__size
151            sizes[index] = self.__size
152            sizes[index - 1] = sizes[index - 1] + diff
153
154            self.__splitter.setSizes(sizes)
155
156    def eventFilter(self, obj, event):
157        if event.type() == QEvent.Resize:
158            if self.__splitter.orientation() == Qt.Vertical:
159                size = event.size().height()
160            else:
161                size = event.size().width()
162
163            if self.__expanded and size == 0:
164                self.__action.setChecked(False)
165                self.__expanded = False
166            elif not self.__expanded and size > 0:
167                self.__action.setChecked(True)
168                self.__expanded = True
169
170        return QObject.eventFilter(self, obj, event)
171
172
173class QuickHelpWidget(QTextEdit):
174    def __init__(self, *args, **kwargs):
175        QTextEdit.__init__(self, *args, **kwargs)
176
177        self.setReadOnly(True)
178
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        brush = item.background()
296        palette = button.palette()
297        palette.setColor(QPalette.Button, brush.color())
298        palette.setColor(QPalette.Window, brush.color())
299        button.setPalette(palette)
300        button.setProperty("quick-category-toolbutton", True)
301
302        style_sheet = ("QToolButton {\n"
303                       "    background-color: %s;\n"
304                       "    border: none;\n"
305                       "    border-bottom: 1px solid palette(dark);\n"
306                       "}")
307        button.setStyleSheet(style_sheet % brush.color().name())
308
309        return button
310
311    def __on_itemChanged(self, item):
312        root = self.__model.invisibleRootItem()
313        if item.parentItem() == root:
314            row = item.row()
315            action = self._gridSlots[row].action
316            action.setText(item.text())
317            action.setIcon(item.icon())
318            action.setToolTip(item.toolTip())
319
320    def __on_rowsInserted(self, parent, start, end):
321        root = self.__model.invisibleRootItem()
322        if root == parent:
323            for index in range(start, end + 1):
324                item = parent.child(index)
325                self.addAction(self.createActionForItem(item))
326
327    def __on_rowsRemoved(self, parent, start, end):
328        root = self.__model.invisibleRootItem()
329        if root == parent:
330            for index in range(end, start - 1, -1):
331                action = self._gridSlots[index].action
332                self.removeAction(action)
Note: See TracBrowser for help on using the repository browser.