source: orange/Orange/OrangeCanvas/gui/dropshadow.py @ 11100:cf6f6744dd9b

Revision 11100:cf6f6744dd9b, 12.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 19 months ago (diff)

Added gui widget toolkit.

Line 
1"""
2A DropShadowWidget
3
4"""
5
6from PyQt4.QtGui import (
7    QWidget, QPainter, QPixmap, QGraphicsScene, QGraphicsRectItem,
8    QGraphicsDropShadowEffect, QColor, QPen, QPalette, QStyleOption,
9    QAbstractScrollArea, QToolBar
10)
11
12from PyQt4.QtCore import (
13    Qt, QPoint, QPointF, QRect, QRectF, QSize, QSizeF, QEvent
14)
15
16from PyQt4.QtCore import pyqtProperty as Property
17
18CACHED_SHADOW_RECT_SIZE = (50, 50)
19
20
21def render_drop_shadow_frame(pixmap, shadow_rect, shadow_color,
22                             offset, radius, rect_fill_color):
23    pixmap.fill(QColor(0, 0, 0, 0))
24    scene = QGraphicsScene()
25    rect = QGraphicsRectItem(shadow_rect)
26    rect.setBrush(QColor(rect_fill_color))
27    rect.setPen(QPen(Qt.NoPen))
28    scene.addItem(rect)
29    effect = QGraphicsDropShadowEffect(color=shadow_color,
30                                       blurRadius=radius,
31                                       offset=offset)
32
33    rect.setGraphicsEffect(effect)
34    scene.setSceneRect(QRectF(QPointF(0, 0), QSizeF(pixmap.size())))
35    painter = QPainter(pixmap)
36    scene.render(painter)
37    painter.end()
38    scene.clear()
39    scene.deleteLater()
40    return pixmap
41
42
43class DropShadowFrame(QWidget):
44    """A widget drawing a drop shadow effect around the geometry of
45    another widget (similar to QFocusFrame).
46
47    """
48    def __init__(self, parent=None, color=None, radius=5,
49                 **kwargs):
50        QWidget.__init__(self, parent, **kwargs)
51        self.setAttribute(Qt.WA_TransparentForMouseEvents, True)
52        self.setAttribute(Qt.WA_NoChildEventsForParent, True)
53        self.setFocusPolicy(Qt.NoFocus)
54
55        if color is None:
56            color = self.palette().color(QPalette.Dark)
57
58        self.__color = color
59        self.__radius = radius
60
61        self.__widget = None
62        self.__widgetParent = None
63        self.__updatePixmap()
64
65    def setColor(self, color):
66        """Set the color of the shadow.
67        """
68        if not isinstance(color, QColor):
69            color = QColor(color)
70
71        if self.__color != color:
72            self.__color = color
73            self.__updatePixmap()
74
75    def color(self):
76        return self.__color
77
78    color_ = Property(QColor, fget=color, fset=setColor, designable=True)
79
80    def setRadius(self, radius):
81        """Set the drop shadow blur radius.
82        """
83        if self.__radius != radius:
84            self.__radius = radius
85            self.__updateGeometry()
86            self.__updatePixmap()
87
88    def radius(self):
89        return self.__radius
90
91    radius_ = Property(int, fget=radius, fset=setRadius, designable=True)
92
93    def setWidget(self, widget):
94        """Set the widget to show the shadow around.
95        """
96        if self.__widget:
97            self.__widget.removeEventFilter(self)
98
99        self.__widget = widget
100
101        if self.__widget:
102            self.__widget.installEventFilter(self)
103            # Find the parent for the frame
104            # This is the top level window a toolbar or a viewport
105            # of a scroll area
106            parent = widget.parentWidget()
107            while not (isinstance(parent, (QAbstractScrollArea, QToolBar)) or \
108                       parent.isWindow()):
109                parent = parent.parentWidget()
110
111            if isinstance(parent, QAbstractScrollArea):
112                parent = parent.viewport()
113
114            self.__widgetParent = parent
115            self.setParent(parent)
116            self.stackUnder(widget)
117            self.__updateGeometry()
118            self.setVisible(widget.isVisible())
119
120    def widget(self):
121        """Return the widget taht was set by `setWidget`.
122        """
123        return self.__widget
124
125    def paintEvent(self, event):
126        # TODO: Use QPainter.drawPixmapFragments on Qt 4.7
127        opt = QStyleOption()
128        opt.initFrom(self)
129
130        pixmap = self.__shadowPixmap
131
132        shadow_rect = QRectF(opt.rect)
133        widget_rect = QRectF(self.widget().geometry())
134        widget_rect.moveTo(self.radius_, self.radius_)
135
136        left = top = right = bottom = self.radius_
137        pixmap_rect = QRectF(QPointF(0, 0), QSizeF(pixmap.size()))
138
139        # Shadow casting rectangle in the source pixmap.
140        pixmap_shadow_rect = pixmap_rect.adjusted(left, top, -right, -bottom)
141        source_rects = self.__shadowPixmapFragments(pixmap_rect,
142                                                   pixmap_shadow_rect)
143        target_rects = self.__shadowPixmapFragments(shadow_rect, widget_rect)
144
145        painter = QPainter(self)
146        for source, target in zip(source_rects, target_rects):
147            painter.drawPixmap(target, pixmap, source)
148        painter.end()
149
150    def eventFilter(self, obj, event):
151        etype = event.type()
152        if etype == QEvent.Move or etype == QEvent.Resize:
153            self.__updateGeometry()
154        elif etype == QEvent.Show:
155            self.__updateGeometry()
156            self.show()
157        elif etype == QEvent.Hide:
158            self.hide()
159        return False
160
161    def __updateGeometry(self):
162        """Update the shadow geometry to fit the widget's changed
163        geometry.
164        """
165        widget = self.__widget
166        parent = self.__widgetParent
167        radius = self.radius_
168        pos = widget.pos()
169        if parent != widget.parentWidget():
170            pos = widget.parentWidget().mapTo(parent, pos)
171
172        geom = QRect(pos, widget.size())
173        geom.adjust(-radius, -radius, radius, radius)
174        if geom != self.geometry():
175            self.setGeometry(geom)
176
177    def __updatePixmap(self):
178        """Update the cached shadow pixmap.
179        """
180        rect_size = QSize(50, 50)
181        left = top = right = bottom = self.radius_
182
183        # Size of the pixmap.
184        pixmap_size = QSize(rect_size.width() + left + right,
185                            rect_size.height() + top + bottom)
186        shadow_rect = QRect(QPoint(left, top), rect_size)
187        pixmap = QPixmap(pixmap_size)
188        pixmap.fill(QColor(0, 0, 0, 0))
189        rect_fill_color = self.palette().color(QPalette.Window)
190
191        pixmap = render_drop_shadow_frame(
192                      pixmap,
193                      QRectF(shadow_rect),
194                      shadow_color=self.color_,
195                      offset=QPointF(0, 0),
196                      radius=self.radius_,
197                      rect_fill_color=rect_fill_color
198                      )
199
200        self.__shadowPixmap = pixmap
201        self.update()
202
203    def __shadowPixmapFragments(self, pixmap_rect, shadow_rect):
204        """Return a list of 8 QRectF fragments for drawing a shadow.
205        """
206        s_left, s_top, s_right, s_bottom = \
207            shadow_rect.left(), shadow_rect.top(), \
208            shadow_rect.right(), shadow_rect.bottom()
209        s_width, s_height = shadow_rect.width(), shadow_rect.height()
210        p_width, p_height = pixmap_rect.width(), pixmap_rect.height()
211
212        top_left = QRectF(0.0, 0.0, s_left, s_top)
213        top = QRectF(s_left, 0.0, s_width, s_top)
214        top_right = QRectF(s_right, 0.0, p_width - s_width, s_top)
215        right = QRectF(s_right, s_top, p_width - s_right, s_height)
216        right_bottom = QRectF(shadow_rect.bottomRight(),
217                              pixmap_rect.bottomRight())
218        bottom = QRectF(shadow_rect.bottomLeft(),
219                        pixmap_rect.bottomRight() - \
220                        QPointF(p_width - s_right, 0.0))
221        bottom_left = QRectF(shadow_rect.bottomLeft() - QPointF(s_left, 0.0),
222                             pixmap_rect.bottomLeft() + QPointF(s_left, 0.0))
223        left = QRectF(pixmap_rect.topLeft() + QPointF(0.0, s_top),
224                      shadow_rect.bottomLeft())
225        return [top_left, top, top_right, right, right_bottom,
226                bottom, bottom_left, left]
227
228
229# A different obsolete implementation
230
231class _DropShadowWidget(QWidget):
232    """A frame widget drawing a drop shadow effect around its
233    contents.
234
235    """
236    def __init__(self, parent=None, offset=None, radius=None,
237                 color=None, **kwargs):
238        QWidget.__init__(self, parent, **kwargs)
239
240        # Bypass the overloaded method to set the default margins.
241        QWidget.setContentsMargins(self, 10, 10, 10, 10)
242
243        if offset is None:
244            offset = QPointF(0., 0.)
245        if radius is None:
246            radius = 20
247        if color is None:
248            color = QColor(Qt.black)
249
250        self.offset = offset
251        self.radius = radius
252        self.color = color
253        self._shadowPixmap = None
254        self._updateShadowPixmap()
255
256    def setOffset(self, offset):
257        """Set the drop shadow offset (`QPoint`)
258        """
259        self.offset = offset
260        self._updateShadowPixmap()
261        self.update()
262
263    def setRadius(self, radius):
264        """Set the drop shadow blur radius (`float`).
265        """
266        self.radius = radius
267        self._updateShadowPixmap()
268        self.update()
269
270    def setColor(self, color):
271        """Set the drop shadow color (`QColor`).
272        """
273        self.color = color
274        self._updateShadowPixmap()
275        self.update()
276
277    def setContentsMargins(self, *args, **kwargs):
278        QWidget.setContentsMargins(self, *args, **kwargs)
279        self._updateShadowPixmap()
280
281    def _updateShadowPixmap(self):
282        """Update the cached drop shadow pixmap.
283        """
284        # Rectangle casting the shadow
285        rect_size = QSize(*CACHED_SHADOW_RECT_SIZE)
286        left, top, right, bottom = self.getContentsMargins()
287        # Size of the pixmap.
288        pixmap_size = QSize(rect_size.width() + left + right,
289                            rect_size.height() + top + bottom)
290        shadow_rect = QRect(QPoint(left, top), rect_size)
291        pixmap = QPixmap(pixmap_size)
292        pixmap.fill(QColor(0, 0, 0, 0))
293        rect_fill_color = self.palette().color(QPalette.Window)
294
295        pixmap = render_drop_shadow_frame(pixmap, QRectF(shadow_rect),
296                                          shadow_color=self.color,
297                                          offset=self.offset,
298                                          radius=self.radius,
299                                          rect_fill_color=rect_fill_color)
300
301        self._shadowPixmap = pixmap
302
303    def paintEvent(self, event):
304        pixmap = self._shadowPixmap
305        widget_rect = QRectF(QPointF(0.0, 0.0), QSizeF(self.size()))
306        frame_rect = QRectF(self.contentsRect())
307        left, top, right, bottom = self.getContentsMargins()
308        pixmap_rect = QRectF(QPointF(0, 0), QSizeF(pixmap.size()))
309        # Shadow casting rectangle.
310        pixmap_shadow_rect = pixmap_rect.adjusted(left, top, -right, -bottom)
311        source_rects = self._shadowPixmapFragments(pixmap_rect,
312                                                   pixmap_shadow_rect)
313        target_rects = self._shadowPixmapFragments(widget_rect, frame_rect)
314        painter = QPainter(self)
315        for source, target in zip(source_rects, target_rects):
316            painter.drawPixmap(target, pixmap, source)
317        painter.end()
318
319    def _shadowPixmapFragments(self, pixmap_rect, shadow_rect):
320        """Return a list of 8 QRectF fragments for drawing a shadow.
321        """
322        s_left, s_top, s_right, s_bottom = \
323            shadow_rect.left(), shadow_rect.top(), \
324            shadow_rect.right(), shadow_rect.bottom()
325        s_width, s_height = shadow_rect.width(), shadow_rect.height()
326        p_width, p_height = pixmap_rect.width(), pixmap_rect.height()
327
328        top_left = QRectF(0.0, 0.0, s_left, s_top)
329        top = QRectF(s_left, 0.0, s_width, s_top)
330        top_right = QRectF(s_right, 0.0, p_width - s_width, s_top)
331        right = QRectF(s_right, s_top, p_width - s_right, s_height)
332        right_bottom = QRectF(shadow_rect.bottomRight(),
333                              pixmap_rect.bottomRight())
334        bottom = QRectF(shadow_rect.bottomLeft(),
335                        pixmap_rect.bottomRight() - \
336                        QPointF(p_width - s_right, 0.0))
337        bottom_left = QRectF(shadow_rect.bottomLeft() - QPointF(s_left, 0.0),
338                             pixmap_rect.bottomLeft() + QPointF(s_left, 0.0))
339        left = QRectF(pixmap_rect.topLeft() + QPointF(0.0, s_top),
340                      shadow_rect.bottomLeft())
341        return [top_left, top, top_right, right, right_bottom,
342                bottom, bottom_left, left]
Note: See TracBrowser for help on using the repository browser.