LCDRange
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
PyQt4 conversion of Qt Tutorial 12
Added a widget label.
The differences from 111_battle.pyw are:
- modified initialiser to accept a string to label the widget
- added methods text() and setText() to get and set the
widget's label
- added a main() for testing
last modified: 2012-01-21 jg
ref:
http://doc.trolltech.com/3.3/tutorial1-12.html
'''
from PyQt4.QtGui import (QWidget, QLabel, QLCDNumber, QSlider, QVBoxLayout)
from PyQt4.QtCore import (Qt, pyqtSlot)
class LCDRange(QWidget):
'''
A two digit QLCDNumber and QSlider widget.
'''
def __init__(self, wlabel='', parent=None):
super(LCDRange, self).__init__(parent)
self.label = QLabel(wlabel)
self.label.setAlignment(Qt.AlignCenter)
lcd = QLCDNumber(2, self);
self.slider = QSlider(Qt.Horizontal, self);
self.slider.setRange(0, 99);
self.slider.setValue(0);
self.slider.valueChanged.connect(lcd.display)
layout = QVBoxLayout()
layout.addWidget(lcd)
layout.addWidget(self.slider)
layout.addWidget(self.label)
self.setLayout(layout)
# set the widget focus to the 'slider' object
self.setFocusProxy(self.slider)
def text(self):
return self.label.text()
def setText(self, txt):
self.label.setText(txt)
def value(self):
return self.slider.value()
@pyqtSlot(int)
def setValue(self, value):
self.slider.setValue(value)
# set the 'slider' range, if min and max values are not
# between 0 and 99, print a warning message and leave
# the slider values as they were
def setRange(self, minVal, maxVal):
if (minVal < 0 or maxVal > 99 or minVal > maxVal) :
qWarning("LCDRange.setRange({0},{1})\n"
"\tRange must be 0..99\n"
"\tand minVal must not be greater than maxVal".format(minVal, maxVal))
return
self.slider.setRange(minVal, maxVal)
def main():
import sys
from PyQt4.QtGui import (QApplication)
app = QApplication(sys.argv) # required
w = LCDRange(wlabel="ANGLE")
w.setGeometry(100, 100, 500, 355)
w.show()
sys.exit(app.exec_()) # start main event loop, exit when app closed
if __name__ == '__main__':
main()
CannonField
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
PyQt4 conversion of Qt Tutorial 12
The differences from 111_battle.pyw are:
- added hit() and miss() signals
- added newTarget() and targetRect() methods
- added new attribute 'target' to hold the target's center point
- modified paintEvent() and added paintTarget()
- added a main() for testing
NOTES:
=====
The orignal C++ code set up the 'firstTarget' variable as a static
method variable within the newTarget() method. As the variable is
only accessed once, it's been setup here as a class attribute.
last modified: 2012-01-21 jg
ref:
http://doc.trolltech.com/3.3/tutorial1-12.html
'''
from math import (cos, sin)
from random import (seed, randrange)
from PyQt4.QtGui import (QWidget, QColor, QPainter, QSizePolicy, QPixmap)
from PyQt4.QtCore import (Qt, pyqtSignal, pyqtSlot, QRect, QTimer, QPoint, QTime)
class CannonField(QWidget):
cRect = None # class attribute, only one can exist
bRect = QRect(33, -4, 15, 8) # class attribute, cannon barrel definition
firstTarget = True # first time we're creating a target
def __init__(self, parent=None):
super(QWidget, self).__init__(parent)
self.setObjectName('cannonField')
self.ang = 45
self.f = 0 # force
self.target = QPoint(0, 0)
# add timer to handle shooting
self.autoShootTimer = QTimer(self)
self.autoShootTimer.timeout.connect(self._moveShot)
# set background colour
pal = self.palette()
pal.setColor(self.backgroundRole(), QColor(250, 250, 200))
self.setPalette(pal)
self.setAutoFillBackground(True)
self.newTarget() # create a target
# custom signals
angleChanged = pyqtSignal(int, name="angleChanged")
forceChanged = pyqtSignal(int, name="forceChanged")
missed = pyqtSignal(name="missed")
hit = pyqtSignal(name="hit")
def newTarget(self):
if CannonField.firstTarget:
CannonField.firstTarget = False
midnight = QTime(0, 0, 0)
seed(midnight.secsTo(QTime.currentTime()))
r = self._targetRect()
self.target = QPoint(200 + randrange(0, 190),
10 + randrange(0, 255))
self.repaint(r.unite(self._targetRect()))
def shoot(self):
''' Shoots a 'shot' unless one is in the air. '''
if self.autoShootTimer.isActive(): return
self.timerCount = 0
self.shoot_ang = self.ang
self.shoot_f = self.f
self.autoShootTimer.start(50)
# define the screen area containing the cannon
# using a 'private' method
def _cannonRect(self):
if not CannonField.cRect: # create once
r = QRect(0, 0, 50, 50)
r.moveBottomLeft(self.rect().bottomLeft())
return r
else: # already defined so return existing rectangle
return CannonField.cRect
@pyqtSlot()
def _moveShot(self):
# moves the 'shot' every 50 milliseconds when the
# timer is fired in shoot()
r = self._shotRect()
self.timerCount += 1
shotR = self._shotRect()
if shotR.intersects(self._targetRect()):
self.autoShootTimer.stop()
self.hit.emit()
elif (shotR.x() > self.width()) or (shotR.y() > self.height()):
self.autoShootTimer.stop()
self.missed.emit()
else:
r = r.unite(shotR)
self.repaint(r)
def _shotRect(self):
# identifies where the 'shot' is on the screen and returns
# its bounding rectangle
gravity = 4
time = self.timerCount / 4.0
velocity = self.shoot_f
radians = self.shoot_ang * 3.14159265 / 180
velx = velocity * cos(radians)
vely = velocity * sin(radians)
x0 = (CannonField.bRect.right() + 5) * cos(radians)
y0 = (CannonField.bRect.right() + 5) * sin(radians)
x = x0 + velx * time
y = y0 + vely * time - 0.5 * gravity * time * time
r = QRect(0, 0, 6, 6)
r.moveCenter(QPoint(int(x), self.height() - 1 - int(y)))
return r
def _targetRect(self):
r = QRect(0, 0, 20, 10)
r.moveCenter(QPoint(self.target.x(), self.height() - 1 - self.target.y()))
return r
def setAngle(self, degrees):
if degrees < 5: degrees = 5
elif degrees > 70: degrees = 70
elif self.ang == degrees: return
self.ang = degrees
self.repaint(self._cannonRect())
self.angleChanged.emit(self.ang)
# handle the force of the cannon shot
def setForce(self, newton):
if newton < 0: newton = 0
elif self.f == newton: return
self.f = newton
self.forceChanged.emit(self.f)
def paintEvent(self, event):
updateR = event.rect()
p = QPainter(self)
if updateR.intersects(self._cannonRect()):
self.paintCannon(p)
if self.autoShootTimer.isActive() and updateR.intersects(self._shotRect()):
self.paintShot(p)
if updateR.intersects(self._targetRect()):
self.paintTarget(p)
def paintTarget(self, p):
p.setBrush(Qt.red)
p.setPen(Qt.black)
p.drawRect(self._targetRect())
def paintShot(self, p):
p.setBrush(Qt.black)
p.setPen(Qt.NoPen)
p.drawRect(self._shotRect())
def paintCannon(self, p):
cr = self._cannonRect()
pix = QPixmap(cr.size())
pix.fill(self, cr.topLeft())
tmp = QPainter(pix)
tmp.setBrush(Qt.blue) # brush colour for filling object
tmp.setPen(Qt.NoPen) # no special edges
# set QPainter's origin (0,0) coords to the bottom-left
tmp.translate(0, pix.height() - 1)
# draw a quarter circle in the bottom left corner
tmp.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
# rotate counter-clockwise 'ang' degrees around the origin
# and draw the cannon's barrel
tmp.rotate(-self.ang)
tmp.drawRect(CannonField.bRect)
tmp.end()
# paint the pixmap on the screen
p.drawPixmap(cr.topLeft(), pix)
def sizePolicy(self):
return QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
def main():
import sys
from PyQt4.QtGui import (QApplication)
app = QApplication(sys.argv) # required
w = CannonField()
w.setGeometry(100, 100, 500, 355)
w.show()
sys.exit(app.exec_()) # start main event loop, exit when app closed
if __name__ == '__main__':
main()
MyWidget
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
PyQt4 conversion of Qt Tutorial 12
In this example, we extend our LCDRange class to include a text label.
We also provide something to shoot at.
The differences from 111_battle.pyw are:
1. Refactored LCDRange and CannonField, moving them to separate modules.
Makes them easier to modify and test
2. MyWdiget
- modified calls to LCDRange to provide widget names
BEHAVIOUR:
=========
The LCDRange widgets look a bit strange - the built-in layout management in
QVBox gives the labels too much space and the rest not enough.
We'll fix that in the next chapter.
last modified: 2012-01-21 jg
ref:
http://doc.trolltech.com/3.3/tutorial1-12.html
'''
import sys
from PyQt4.QtGui import (QApplication, QWidget, QPushButton, QFont,
QVBoxLayout, QGridLayout, QHBoxLayout)
from t112_lcdrange import (LCDRange)
from t112_cannon import (CannonField)
class MyWidget(QWidget):
def __init__(self, parent=None, name=''):
super(MyWidget, self).__init__(parent)
if name:
self.setObjectName(name)
quitBtn = QPushButton('&Quit', self)
quitBtn.setFont(QFont("Times", 18, QFont.Bold))
quitBtn.clicked.connect(QApplication.instance().quit)
angle = LCDRange(wlabel="ANGLE")
angle.setRange(5, 70)
# add LCDRange widget to handle force
force = LCDRange(wlabel="FORCE")
force.setRange(10, 50)
cannonField = CannonField(self)
angle.slider.valueChanged.connect(cannonField.setAngle)
cannonField.angleChanged.connect(angle.setValue)
# add event handling for 'force'
force.slider.valueChanged.connect(cannonField.setForce)
cannonField.forceChanged.connect(force.setValue)
shootBtn = QPushButton('&Shoot', self)
shootBtn.setFont(QFont("Times", 18, QFont.Bold))
shootBtn.clicked.connect(cannonField.shoot)
grid = QGridLayout()
grid.addWidget(quitBtn, 0, 0)
grid.addWidget(cannonField, 1, 1)
grid.setColumnStretch(1, 10)
# add the angle and force widgets
leftBox = QVBoxLayout()
grid.addLayout(leftBox, 1, 0)
leftBox.addWidget(angle)
leftBox.addWidget(force)
self.setLayout(grid)
# add the 'shoot' button
topBox = QHBoxLayout()
grid.addLayout(topBox, 0, 1)
topBox.addWidget(shootBtn)
topBox.addStretch(1)
angle.setValue(60)
force.setValue(25)
angle.setFocus() # give the LCDRange object keyboard focus
def main():
app = QApplication(sys.argv) # required
w = MyWidget()
w.setGeometry(100, 100, 500, 355)
w.show()
sys.exit(app.exec_()) # start main event loop, exit when app closed
if __name__ == '__main__':
main()