source: orange/Orange/OrangeCanvas/gui/dropshadow.py @ 11130:8f0f1f0946e9

Revision 11130:8f0f1f0946e9, 12.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Set the drop shadow frame mask to exclude the area over the 'widget'.

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, QRegion
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 QWidget.eventFilter(self, obj, event)
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        # Set the widget mask (punch a hole through to the `widget` instance.
178        rect = self.rect()
179
180        mask = QRegion(rect)
181        transparent = QRegion(rect.adjusted(radius, radius, -radius, -radius))
182
183        mask = mask.subtracted(transparent)
184        self.setMask(mask)
185
186    def __updatePixmap(self):
187        """Update the cached shadow pixmap.
188        """
189        rect_size = QSize(50, 50)
190        left = top = right = bottom = self.radius_
191
192        # Size of the pixmap.
193        pixmap_size = QSize(rect_size.width() + left + right,
194                            rect_size.height() + top + bottom)
195        shadow_rect = QRect(QPoint(left, top), rect_size)
196        pixmap = QPixmap(pixmap_size)
197        pixmap.fill(QColor(0, 0, 0, 0))
198        rect_fill_color = self.palette().color(QPalette.Window)
199
200        pixmap = render_drop_shadow_frame(
201                      pixmap,
202                      QRectF(shadow_rect),
203                      shadow_color=self.color_,
204                      offset=QPointF(0, 0),
205                      radius=self.radius_,
206                      rect_fill_color=rect_fill_color
207                      )
208
209        self.__shadowPixmap = pixmap
210        self.update()
211
212    def __shadowPixmapFragments(self, pixmap_rect, shadow_rect):
213        """Return a list of 8 QRectF fragments for drawing a shadow.
214        """
215        s_left, s_top, s_right, s_bottom = \
216            shadow_rect.left(), shadow_rect.top(), \
217            shadow_rect.right(), shadow_rect.bottom()
218        s_width, s_height = shadow_rect.width(), shadow_rect.height()
219        p_width, p_height = pixmap_rect.width(), pixmap_rect.height()
220
221        top_left = QRectF(0.0, 0.0, s_left, s_top)
222        top = QRectF(s_left, 0.0, s_width, s_top)
223        top_right = QRectF(s_right, 0.0, p_width - s_width, s_top)
224        right = QRectF(s_right, s_top, p_width - s_right, s_height)
225        right_bottom = QRectF(shadow_rect.bottomRight(),
226                              pixmap_rect.bottomRight())
227        bottom = QRectF(shadow_rect.bottomLeft(),
228                        pixmap_rect.bottomRight() - \
229                        QPointF(p_width - s_right, 0.0))
230        bottom_left = QRectF(shadow_rect.bottomLeft() - QPointF(s_left, 0.0),
231                             pixmap_rect.bottomLeft() + QPointF(s_left, 0.0))
232        left = QRectF(pixmap_rect.topLeft() + QPointF(0.0, s_top),
233                      shadow_rect.bottomLeft())
234        return [top_left, top, top_right, right, right_bottom,
235                bottom, bottom_left, left]
236
237
238# A different obsolete implementation
239
240class _DropShadowWidget(QWidget):
241    """A frame widget drawing a drop shadow effect around its
242    contents.
243
244    """
245    def __init__(self, parent=None, offset=None, radius=None,
246                 color=None, **kwargs):
247        QWidget.__init__(self, parent, **kwargs)
248
249        # Bypass the overloaded method to set the default margins.
250        QWidget.setContentsMargins(self, 10, 10, 10, 10)
251
252        if offset is None:
253            offset = QPointF(0., 0.)
254        if radius is None:
255            radius = 20
256        if color is None:
257            color = QColor(Qt.black)
258
259        self.offset = offset
260        self.radius = radius
261        self.color = color
262        self._shadowPixmap = None
263        self._updateShadowPixmap()
264
265    def setOffset(self, offset):
266        """Set the drop shadow offset (`QPoint`)
267        """
268        self.offset = offset
269        self._updateShadowPixmap()
270        self.update()
271
272    def setRadius(self, radius):
273        """Set the drop shadow blur radius (`float`).
274        """
275        self.radius = radius
276        self._updateShadowPixmap()
277        self.update()
278
279    def setColor(self, color):
280        """Set the drop shadow color (`QColor`).
281        """
282        self.color = color
283        self._updateShadowPixmap()
284        self.update()
285
286    def setContentsMargins(self, *args, **kwargs):
287        QWidget.setContentsMargins(self, *args, **kwargs)
288        self._updateShadowPixmap()
289
290    def _updateShadowPixmap(self):
291        """Update the cached drop shadow pixmap.
292        """
293        # Rectangle casting the shadow
294        rect_size = QSize(*CACHED_SHADOW_RECT_SIZE)
295        left, top, right, bottom = self.getContentsMargins()
296        # Size of the pixmap.
297        pixmap_size = QSize(rect_size.width() + left + right,
298                            rect_size.height() + top + bottom)
299        shadow_rect = QRect(QPoint(left, top), rect_size)
300        pixmap = QPixmap(pixmap_size)
301        pixmap.fill(QColor(0, 0, 0, 0))
302        rect_fill_color = self.palette().color(QPalette.Window)
303
304        pixmap = render_drop_shadow_frame(pixmap, QRectF(shadow_rect),
305                                          shadow_color=self.color,
306                                          offset=self.offset,
307                                          radius=self.radius,
308                                          rect_fill_color=rect_fill_color)
309
310        self._shadowPixmap = pixmap
311
312    def paintEvent(self, event):
313        pixmap = self._shadowPixmap
314        widget_rect = QRectF(QPointF(0.0, 0.0), QSizeF(self.size()))
315        frame_rect = QRectF(self.contentsRect())
316        left, top, right, bottom = self.getContentsMargins()
317        pixmap_rect = QRectF(QPointF(0, 0), QSizeF(pixmap.size()))
318        # Shadow casting rectangle.
319        pixmap_shadow_rect = pixmap_rect.adjusted(left, top, -right, -bottom)
320        source_rects = self._shadowPixmapFragments(pixmap_rect,
321                                                   pixmap_shadow_rect)
322        target_rects = self._shadowPixmapFragments(widget_rect, frame_rect)
323        painter = QPainter(self)
324        for source, target in zip(source_rects, target_rects):
325            painter.drawPixmap(target, pixmap, source)
326        painter.end()
327
328    def _shadowPixmapFragments(self, pixmap_rect, shadow_rect):
329        """Return a list of 8 QRectF fragments for drawing a shadow.
330        """
331        s_left, s_top, s_right, s_bottom = \
332            shadow_rect.left(), shadow_rect.top(), \
333            shadow_rect.right(), shadow_rect.bottom()
334        s_width, s_height = shadow_rect.width(), shadow_rect.height()
335        p_width, p_height = pixmap_rect.width(), pixmap_rect.height()
336
337        top_left = QRectF(0.0, 0.0, s_left, s_top)
338        top = QRectF(s_left, 0.0, s_width, s_top)
339        top_right = QRectF(s_right, 0.0, p_width - s_width, s_top)
340        right = QRectF(s_right, s_top, p_width - s_right, s_height)
341        right_bottom = QRectF(shadow_rect.bottomRight(),
342                              pixmap_rect.bottomRight())
343        bottom = QRectF(shadow_rect.bottomLeft(),
344                        pixmap_rect.bottomRight() - \
345                        QPointF(p_width - s_right, 0.0))
346        bottom_left = QRectF(shadow_rect.bottomLeft() - QPointF(s_left, 0.0),
347                             pixmap_rect.bottomLeft() + QPointF(s_left, 0.0))
348        left = QRectF(pixmap_rect.topLeft() + QPointF(0.0, s_top),
349                      shadow_rect.bottomLeft())
350        return [top_left, top, top_right, right, right_bottom,
351                bottom, bottom_left, left]
Note: See TracBrowser for help on using the repository browser.