1#!/usr/bin/env python
2
3"""
4bubbleswidget.py
5
6A PyQt custom widget example for Qt Designer.
7
8Copyright (C) 2006 David Boddie <david@boddie.org.uk>
9Copyright (C) 2005-2006 Trolltech ASA. All rights reserved.
10
11This program is free software; you can redistribute it and/or modify
12it under the terms of the GNU General Public License as published by
13the Free Software Foundation; either version 2 of the License, or
14(at your option) any later version.
15
16This program is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU General Public License for more details.
20
21You should have received a copy of the GNU General Public License
22along with this program; if not, write to the Free Software
23Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
24"""
25
26import random
27
28from PyQt5.QtCore import (pyqtProperty, pyqtSignal, pyqtSlot, QPointF, QRectF,
29        QSize, QSizeF, Qt, QTimer)
30from PyQt5.QtGui import QBrush, QColor, QPainter, QPen, QRadialGradient
31from PyQt5.QtWidgets import QApplication, QWidget
32
33
34class BaseClass(QWidget):
35    """BaseClass(QWidget)
36
37    Provides a base custom widget class to show that properties implemented
38    in Python can be inherited and shown as belonging to distinct classes
39    in Qt Designer's Property Editor.
40    """
41
42    def __init__(self, parent=None):
43
44        super(BaseClass, self).__init__(parent)
45
46        self.resetAuthor()
47
48    # Define getter, setter and resetter methods for the author property.
49
50    def getAuthor(self):
51        return self._author
52
53    def setAuthor(self, name):
54        self._author = name
55
56    def resetAuthor(self):
57        self._author = "David Boddie"
58
59    author = pyqtProperty(str, getAuthor, setAuthor, resetAuthor)
60
61
62class Bubble:
63    """Bubble
64
65    Provides a class to represent individual bubbles in a BubblesWidget.
66    Each Bubble instance can render itself onto a paint device using a
67    QPainter passed to its drawBubble() method.
68    """
69
70    def __init__(self, position, radius, speed, innerColor, outerColor):
71
72        self.position = position
73        self.radius = radius
74        self.speed = speed
75        self.innerColor = innerColor
76        self.outerColor = outerColor
77        self.updateBrush()
78
79    def updateBrush(self):
80
81        gradient = QRadialGradient(
82                QPointF(self.radius, self.radius), self.radius,
83                QPointF(self.radius*0.5, self.radius*0.5))
84
85        gradient.setColorAt(0, QColor(255, 255, 255, 255))
86        gradient.setColorAt(0.25, self.innerColor)
87        gradient.setColorAt(1, self.outerColor)
88        self.brush = QBrush(gradient)
89
90    def drawBubble(self, painter):
91
92        painter.save()
93        painter.translate(self.position.x() - self.radius,
94                          self.position.y() - self.radius)
95        painter.setBrush(self.brush)
96        painter.drawEllipse(0.0, 0.0, 2*self.radius, 2*self.radius)
97        painter.restore()
98
99
100class BubblesWidget(BaseClass):
101    """BubblesWidget(BaseClass)
102
103    Provides a custom widget that shows a number of rising bubbles.
104    Various properties are defined so that the user can customize the
105    appearance of the widget, and change the number and behaviour of the
106    bubbles shown.
107    """
108
109    # We define two signals that are used to indicate changes to the status
110    # of the widget.
111    bubbleLeft = pyqtSignal()
112    bubblesRemaining = pyqtSignal(int)
113
114    def __init__(self, parent=None):
115
116        super(BubblesWidget, self).__init__(parent)
117
118        self.pen = QPen(QColor("#cccccc"))
119        self.bubbles = []
120        self.backgroundColor1 = self.randomColor()
121        self.backgroundColor2 = self.randomColor().darker(150)
122        self.newBubble = None
123
124        random.seed()
125
126        self.animation_timer = QTimer(self)
127        self.animation_timer.setSingleShot(False)
128        self.animation_timer.timeout.connect(self.animate)
129        self.animation_timer.start(25)
130
131        self.bubbleTimer = QTimer()
132        self.bubbleTimer.setSingleShot(False)
133        self.bubbleTimer.timeout.connect(self.expandBubble)
134
135        self.setMouseTracking(True)
136        self.setMinimumSize(QSize(200, 200))
137        self.setWindowTitle("Bubble Maker")
138
139    def paintEvent(self, event):
140
141        background = QRadialGradient(QPointF(self.rect().topLeft()), 500,
142                QPointF(self.rect().bottomRight()))
143        background.setColorAt(0, self.backgroundColor1)
144        background.setColorAt(1, self.backgroundColor2)
145
146        painter = QPainter()
147        painter.begin(self)
148        painter.setRenderHint(QPainter.Antialiasing)
149        painter.fillRect(event.rect(), QBrush(background))
150
151        painter.setPen(self.pen)
152
153        for bubble in self.bubbles:
154
155            if QRectF(bubble.position - QPointF(bubble.radius, bubble.radius),
156                      QSizeF(2*bubble.radius, 2*bubble.radius)).intersects(QRectF(event.rect())):
157                bubble.drawBubble(painter)
158
159        if self.newBubble:
160
161            self.newBubble.drawBubble(painter)
162
163        painter.end()
164
165    def mousePressEvent(self, event):
166
167        if event.button() == Qt.LeftButton and self.newBubble is None:
168
169            self.newBubble = Bubble(QPointF(event.pos()), 4.0,
170                                    1.0 + random.random() * 7,
171                                    self.randomColor(), self.randomColor())
172            self.bubbleTimer.start(50)
173            event.accept()
174
175    def mouseMoveEvent(self, event):
176
177        if self.newBubble:
178
179            self.update(
180                QRectF(self.newBubble.position - \
181                       QPointF(self.newBubble.radius + 1, self.newBubble.radius + 1),
182                       QSizeF(2*self.newBubble.radius + 2, 2*self.newBubble.radius + 2)).toRect()
183                )
184            self.newBubble.position = QPointF(event.pos())
185            self.update(
186                QRectF(self.newBubble.position - \
187                       QPointF(self.newBubble.radius + 1, self.newBubble.radius + 1),
188                       QSizeF(2*self.newBubble.radius + 2, 2*self.newBubble.radius + 2)).toRect()
189                )
190
191        event.accept()
192
193    def mouseReleaseEvent(self, event):
194
195        if self.newBubble:
196
197            self.bubbles.append(self.newBubble)
198            self.newBubble = None
199            self.bubbleTimer.stop()
200            self.bubblesRemaining.emit(len(self.bubbles))
201
202        event.accept()
203
204    def expandBubble(self):
205
206        if self.newBubble:
207
208            self.newBubble.radius = min(self.newBubble.radius + 4.0,
209                                        self.width()/8.0, self.height()/8.0)
210            self.update(
211                QRectF(self.newBubble.position - \
212                       QPointF(self.newBubble.radius + 1, self.newBubble.radius + 1),
213                       QSizeF(2*self.newBubble.radius + 2, 2*self.newBubble.radius + 2)).toRect()
214                )
215            self.newBubble.updateBrush()
216
217    def randomColor(self):
218
219        red = 205 + random.random() * 50
220        green = 205 + random.random() * 50
221        blue = 205 + random.random() * 50
222        alpha = 91 + random.random() * 100
223
224        return QColor(red, green, blue, alpha)
225
226    def animate(self):
227
228        bubbles = []
229        left = False
230        for bubble in self.bubbles:
231
232            bubble.position = bubble.position + QPointF(0, -bubble.speed)
233
234            self.update(
235                    QRectF(bubble.position - QPointF(bubble.radius + 1,
236                                    bubble.radius + 1),
237                            QSizeF(2*bubble.radius + 2, 2*bubble.radius + 2 + bubble.speed)).toRect())
238
239            if bubble.position.y() + bubble.radius > 0:
240                bubbles.append(bubble)
241            else:
242                self.bubbleLeft.emit()
243                left = True
244
245        if self.newBubble:
246            self.update(
247                    QRectF(self.newBubble.position - QPointF(
248                                    self.newBubble.radius + 1,
249                                    self.newBubble.radius + 1),
250                            QSizeF(2*self.newBubble.radius + 2, 2*self.newBubble.radius + 2)).toRect())
251
252        self.bubbles = bubbles
253        if left:
254            self.bubblesRemaining.emit(len(self.bubbles))
255
256    def sizeHint(self):
257
258        return QSize(200, 200)
259
260    # We provide getter and setter methods for the numberOfBubbles property.
261    def getBubbles(self):
262
263        return len(self.bubbles)
264
265    # The setBubbles() method can also be used as a slot.
266    @pyqtSlot(int)
267    def setBubbles(self, value):
268
269        value = max(0, value)
270
271        while len(self.bubbles) < value:
272
273            newBubble = Bubble(QPointF(random.random() * self.width(),
274                                       random.random() * self.height()),
275                               4.0 + random.random() * 20,
276                               1.0 + random.random() * 7,
277                               self.randomColor(), self.randomColor())
278            newBubble.updateBrush()
279            self.bubbles.append(newBubble)
280
281        self.bubbles = self.bubbles[:value]
282        self.bubblesRemaining.emit(value)
283        self.update()
284
285    numberOfBubbles = pyqtProperty(int, getBubbles, setBubbles)
286
287    # We provide getter and setter methods for the color1 and color2
288    # properties. The red, green and blue components for the QColor
289    # values stored in these properties can be edited individually in
290    # Qt Designer.
291
292    def getColor1(self):
293
294        return self.backgroundColor1
295
296    def setColor1(self, value):
297
298        self.backgroundColor1 = QColor(value)
299        self.update()
300
301    color1 = pyqtProperty(QColor, getColor1, setColor1)
302
303    def getColor2(self):
304
305        return self.backgroundColor2
306
307    def setColor2(self, value):
308
309        self.backgroundColor2 = QColor(value)
310        self.update()
311
312    color2 = pyqtProperty(QColor, getColor2, setColor2)
313
314    # The stop() and start() slots provide simple control over the animation
315    # of the bubbles in the widget.
316
317    @pyqtSlot()
318    def stop(self):
319
320        self.animation_timer.stop()
321
322    @pyqtSlot()
323    def start(self):
324
325        self.animation_timer.start(25)
326
327
328if __name__ == "__main__":
329
330    import sys
331
332    app = QApplication(sys.argv)
333    widget = BubblesWidget()
334    widget.show()
335    sys.exit(app.exec_())
336