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()