source: orange/Orange/OrangeCanvas/gui/dropshadow.py @ 11366:7f9332b11252

Revision 11366:7f9332b11252, 13.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Added rst documentation for the canvas gui package.

Fixing docstrings in the process.

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