source: orange/Orange/OrangeCanvas/gui/toolbar.py @ 11137:444c1ffd33dc

Revision 11137:444c1ffd33dc, 9.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Don't set negative fixed sizes on the buttons in a toolbar.

Line 
1"""
2A custom toolbar.
3
4"""
5from __future__ import division
6
7import logging
8
9from collections import namedtuple
10
11from PyQt4.QtGui import (
12    QWidget, QToolBar, QToolButton, QAction, QBoxLayout, QStyle, QStylePainter,
13    QStyleOptionToolBar, QSizePolicy
14)
15
16from PyQt4.QtCore import Qt, QSize, QPoint, QEvent, QSignalMapper
17
18from PyQt4.QtCore import pyqtSignal as Signal, \
19                         pyqtProperty as Property
20
21log = logging.getLogger(__name__)
22
23
24class DynamicResizeToolBar(QToolBar):
25    """A QToolBar subclass that dynamically resizes its toolbuttons
26    to fit available space (this is done by setting fixed size on the
27    button instances).
28
29    .. note:: the class does not support `QWidgetAction`s, separators, etc.
30
31    """
32
33    def __init__(self, parent=None, *args, **kwargs):
34        QToolBar.__init__(self, *args, **kwargs)
35
36#        if self.orientation() == Qt.Horizontal:
37#            self.setSizePolicy(QSizePolicy.Fixed,
38#                               QSizePolicy.MinimumExpanding)
39#        else:
40#            self.setSizePolicy(QSizePolicy.MinimumExpanding,
41#                               QSizePolicy.Fixed)
42
43    def resizeEvent(self, event):
44        QToolBar.resizeEvent(self, event)
45        size = event.size()
46        self.__layout(size)
47
48    def actionEvent(self, event):
49        QToolBar.actionEvent(self, event)
50        if event.type() == QEvent.ActionAdded or \
51                event.type() == QEvent.ActionRemoved:
52            self.__layout(self.size())
53
54    def sizeHint(self):
55        hint = QToolBar.sizeHint(self)
56        width, height = hint.width(), hint.height()
57        dx1, dy1, dw1, dh1 = self.getContentsMargins()
58        dx2, dy2, dw2, dh2 = self.layout().getContentsMargins()
59        dx, dy = dx1 + dx2, dy1 + dy2
60        dw, dh = dw1 + dw2, dh1 + dh2
61
62        count = len(self.actions())
63        spacing = self.layout().spacing()
64        space_spacing = max(count - 1, 0) * spacing
65
66        if self.orientation() == Qt.Horizontal:
67            width = int(height * 1.618) * count + space_spacing + dw + dx
68        else:
69            height = int(width * 1.618) * count + space_spacing + dh + dy
70        return QSize(width, height)
71
72    def __layout(self, size):
73        """Layout the buttons to fit inside size.
74        """
75        mygeom = self.geometry()
76        mygeom.setSize(size)
77
78        # Adjust for margins (both the widgets and the layouts.
79        dx, dy, dw, dh = self.getContentsMargins()
80        mygeom.adjust(dx, dy, -dw, -dh)
81
82        dx, dy, dw, dh = self.layout().getContentsMargins()
83        mygeom.adjust(dx, dy, -dw, -dh)
84
85        actions = self.actions()
86        widgets = map(self.widgetForAction, actions)
87
88        orientation = self.orientation()
89        if orientation == Qt.Horizontal:
90            widgets = sorted(widgets, key=lambda w: w.pos().x())
91        else:
92            widgets = sorted(widgets, key=lambda w: w.pos().y())
93
94        spacing = self.layout().spacing()
95        uniform_layout_helper(widgets, mygeom, orientation,
96                              spacing=spacing)
97
98
99def uniform_layout_helper(items, contents_rect, expanding, spacing):
100    """Set fixed sizes on 'items' so they can be lay out in
101    contents rect anf fil the whole space.
102
103    """
104    if len(items) == 0:
105        return
106
107    spacing_space = (len(items) - 1) * spacing
108
109    if expanding == Qt.Horizontal:
110        space = contents_rect.width() - spacing_space
111        setter = lambda w, s: w.setFixedWidth(max(s, 0))
112    else:
113        space = contents_rect.height() - spacing_space
114        setter = lambda w, s: w.setFixedHeight(max(s, 0))
115
116    base_size = space / len(items)
117    remainder = space % len(items)
118
119    for i, item in enumerate(items):
120        item_size = base_size + (1 if i < remainder else 0)
121        setter(item, item_size)
122
123
124########
125# Unused
126########
127
128_ToolBarSlot = namedtuple(
129    "_ToolBarAction",
130    ["index",
131     "action",
132     "button",
133     ]
134)
135
136
137class ToolBarButton(QToolButton):
138    def __init__(self, *args, **kwargs):
139        QToolButton.__init__(self, *args, **kwargs)
140
141
142class ToolBar(QWidget):
143
144    actionTriggered = Signal()
145    actionHovered = Signal()
146
147    def __init__(self, parent=None, toolButtonStyle=Qt.ToolButtonFollowStyle,
148                 orientation=Qt.Horizontal, iconSize=None, **kwargs):
149        QWidget.__init__(self, parent, **kwargs)
150
151        self.__actions = []
152        self.__toolButtonStyle = toolButtonStyle
153        self.__orientation = orientation
154
155        if iconSize is not None:
156            pm = self.style().pixelMetric(QStyle.PM_ToolBarIconSize)
157            iconSize = QSize(pm, pm)
158
159        self.__iconSize = iconSize
160
161        if orientation == Qt.Horizontal:
162            layout = QBoxLayout(QBoxLayout.LeftToRight)
163        else:
164            layout = QBoxLayout(QBoxLayout.TopToBottom)
165
166        layout.setContentsMargins(0, 0, 0, 0)
167        layout.setSpacing(0)
168        self.setLayout(layout)
169
170        self.__signalMapper = QSignalMapper()
171
172    def setToolButtonStyle(self, style):
173        if self.__toolButtonStyle != style:
174            for slot in self.__actions:
175                slot.button.setToolButtonStyle(style)
176            self.__toolButtonStyle = style
177
178    def toolButtonStyle(self):
179        return self.__toolButtonStyle
180
181    toolButtonStyle_ = Property(int, fget=toolButtonStyle,
182                                fset=setToolButtonStyle)
183
184    def setOrientation(self, orientation):
185        if self.__orientation != orientation:
186            if orientation == Qt.Horizontal:
187                self.layout().setDirection(QBoxLayout.LeftToRight)
188            else:
189                self.layout().setDirection(QBoxLayout.TopToBottom)
190            sp = self.sizePolicy()
191            sp.transpose()
192            self.setSizePolicy(sp)
193            self.__orientation = orientation
194
195    def orientation(self):
196        return self.__orientation
197
198    orientation_ = Property(int, fget=orientation, fset=setOrientation)
199
200    def setIconSize(self, size):
201        if self.__iconSize != size:
202            for slot in self.__actions:
203                slot.button.setIconSize(size)
204            self.__iconSize = size
205
206    def iconSize(self):
207        return self.__iconSize
208
209    iconSize_ = Property(QSize, fget=iconSize, fset=setIconSize)
210
211    def actionEvent(self, event):
212        action = event.action()
213        if event.type() == QEvent.ActionAdded:
214            if event.before() is not None:
215                index = self._indexForAction(event.before()) + 1
216            else:
217                index = self.count()
218
219            already_added = True
220            try:
221                self._indexForAction(action)
222            except IndexError:
223                already_added = False
224
225            if already_added:
226                log.error("Action ('%s') already inserted", action.text())
227                return
228
229            self.__insertAction(index, action)
230
231        elif event.type() == QEvent.ActionRemoved:
232            try:
233                index = self._indexForAction(event.action())
234            except IndexError:
235                log.error("Action ('%s') is not in the toolbar", action.text())
236                return
237
238            self.__removeAction(index)
239
240        elif event.type() == QEvent.ActionChanged:
241            pass
242
243        return QWidget.actionEvent(self, event)
244
245    def count(self):
246        return len(self.__actions)
247
248    def actionAt(self, point):
249        widget = self.childAt(QPoint)
250        if isinstance(widget, QToolButton):
251            return widget.defaultAction()
252
253    def _indexForAction(self, action):
254        for i, slot in enumerate(self.__actions):
255            if slot.action is action:
256                return i
257        raise IndexError("Action not in the toolbar")
258
259    def __insertAction(self, index, action):
260        """Insert action into index.
261        """
262        log.debug("Inserting action '%s' at %i.", action.text(), index)
263        button = ToolBarButton(self)
264        button.setDefaultAction(action)
265        button.setToolButtonStyle(self.toolButtonStyle_)
266        button.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
267        button.triggered[QAction].connect(self.actionTriggered)
268        self.__signalMapper.setMapping(button, action)
269        slot = _ToolBarSlot(index, action, button)
270        self.__actions.insert(index, slot)
271
272        for i in range(index + 1, len(self.__actions)):
273            self.__actions[i] = self.__actions[i]._replace(index=i)
274
275        self.layout().insertWidget(index, button, stretch=1)
276
277    def __removeAction(self, index):
278        """Remove action at index.
279        """
280        slot = self.__actions.pop(index)
281        log.debug("Removing action '%s'.", slot.action.text())
282        for i in range(index, len(self.__actions)):
283            self.__actions[i] = self.__actions[i]._replace(index=i)
284        self.layout().takeAt(index)
285        slot.button.hide()
286        slot.button.setParent(None)
287        slot.button.deleteLater()
288
289    def paintEvent(self, event):
290        try:
291            painter = QStylePainter(self)
292            opt = QStyleOptionToolBar()
293            opt.initFrom(self)
294
295            opt.features = QStyleOptionToolBar.None
296            opt.positionOfLine = QStyleOptionToolBar.OnlyOne
297            opt.positionWithinLine = QStyleOptionToolBar.OnlyOne
298
299            painter.drawControl(QStyle.CE_ToolBar, opt)
300            print self.style()
301        except Exception:
302            log.critical("Error", exc_info=1)
303        painter.end()
Note: See TracBrowser for help on using the repository browser.