1############################################################################# 2## 3## Copyright (C) 2015 Riverbank Computing Limited. 4## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 5## All rights reserved. 6## 7## This file is part of the examples of PyQt. 8## 9## $QT_BEGIN_LICENSE:LGPL$ 10## Commercial Usage 11## Licensees holding valid Qt Commercial licenses may use this file in 12## accordance with the Qt Commercial License Agreement provided with the 13## Software or, alternatively, in accordance with the terms contained in 14## a written agreement between you and Nokia. 15## 16## GNU Lesser General Public License Usage 17## Alternatively, this file may be used under the terms of the GNU Lesser 18## General Public License version 2.1 as published by the Free Software 19## Foundation and appearing in the file LICENSE.LGPL included in the 20## packaging of this file. Please review the following information to 21## ensure the GNU Lesser General Public License version 2.1 requirements 22## will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 23## 24## In addition, as a special exception, Nokia gives you certain additional 25## rights. These rights are described in the Nokia Qt LGPL Exception 26## version 1.1, included in the file LGPL_EXCEPTION.txt in this package. 27## 28## GNU General Public License Usage 29## Alternatively, this file may be used under the terms of the GNU 30## General Public License version 3.0 as published by the Free Software 31## Foundation and appearing in the file LICENSE.GPL included in the 32## packaging of this file. Please review the following information to 33## ensure the GNU General Public License version 3.0 requirements will be 34## met: http://www.gnu.org/copyleft/gpl.html. 35## 36## If you have questions regarding the use of this file, please contact 37## Nokia at qt-info@nokia.com. 38## $QT_END_LICENSE$ 39## 40############################################################################# 41 42 43from PyQt5.QtCore import QFileInfo, QPoint, QRect, qRound, Qt, QTime, QTimer 44from PyQt5.QtGui import (QFontMetricsF, QImage, QPainter, QPixmap, QPolygon, 45 QRegion) 46from PyQt5.QtWidgets import (QApplication, QFrame, QGraphicsScene, 47 QGraphicsView, QGraphicsWidget, QMessageBox, QWidget) 48 49from colors import Colors 50from demoitem import DemoItem 51from demotextitem import DemoTextItem 52from imageitem import ImageItem 53from menumanager import MenuManager 54 55 56class MainWindow(QGraphicsView): 57 def __init__(self, parent=None): 58 super(MainWindow, self).__init__(parent) 59 60 self.imagesDir = QFileInfo(__file__).absolutePath() + '/images' 61 62 self.updateTimer = QTimer(self) 63 self.demoStartTime = QTime() 64 self.fpsTime = QTime() 65 self.background = QPixmap() 66 67 self.scene = None 68 self.mainSceneRoot = None 69 self.frameTimeList = [] 70 self.fpsHistory = [] 71 72 self.currentFps = Colors.fps 73 self.fpsMedian = -1 74 self.fpsLabel = None 75 self.pausedLabel = None 76 self.doneAdapt = False 77 self.useTimer = False 78 self.updateTimer.setSingleShot(True) 79 self.companyLogo = None 80 self.qtLogo = None 81 82 self.setupWidget() 83 self.setupScene() 84 self.setupSceneItems() 85 self.drawBackgroundToPixmap() 86 87 def setupWidget(self): 88 desktop = QApplication.desktop() 89 screenRect = desktop.screenGeometry(desktop.primaryScreen()) 90 windowRect = QRect(0, 0, 800, 600) 91 92 if screenRect.width() < 800: 93 windowRect.setWidth(screenRect.width()) 94 95 if screenRect.height() < 600: 96 windowRect.setHeight(screenRect.height()) 97 98 windowRect.moveCenter(screenRect.center()) 99 self.setGeometry(windowRect) 100 self.setMinimumSize(80, 60) 101 102 self.setWindowTitle("PyQt Examples") 103 self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 104 self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 105 self.setFrameStyle(QFrame.NoFrame) 106 self.setRenderingSystem() 107 self.updateTimer.timeout.connect(self.tick) 108 109 def setRenderingSystem(self): 110 self.setCacheMode(QGraphicsView.CacheBackground) 111 self.setViewport(QWidget()) 112 113 def start(self): 114 self.switchTimerOnOff(True) 115 self.demoStartTime.restart() 116 MenuManager.instance().itemSelected(MenuManager.ROOT, 117 Colors.rootMenuName) 118 Colors.debug("- starting demo") 119 120 def enableMask(self, enable): 121 if not enable or Colors.noWindowMask: 122 self.clearMask() 123 else: 124 region = QPolygon([ 125 # North side. 126 0, 0, 127 800, 0, 128 # East side. 129 # 800, 70, 130 # 790, 90, 131 # 790, 480, 132 # 800, 500, 133 800, 600, 134 # South side. 135 700, 600, 136 670, 590, 137 130, 590, 138 100, 600, 139 0, 600, 140 # West side. 141 # 0, 550, 142 # 10, 530, 143 # 10, 520, 144 # 0, 520, 145 0, 0]) 146 147 self.setMask(QRegion(region)) 148 149 def setupScene(self): 150 self.scene = QGraphicsScene(self) 151 self.scene.setSceneRect(0, 0, 800, 600) 152 self.setScene(self.scene) 153 self.scene.setItemIndexMethod(QGraphicsScene.NoIndex) 154 155 def switchTimerOnOff(self, on): 156 ticker = MenuManager.instance().ticker 157 if ticker and ticker.scene(): 158 ticker.tickOnPaint = not on or Colors.noTimerUpdate 159 160 if on and not Colors.noTimerUpdate: 161 self.useTimer = True 162 self.fpsTime = QTime.currentTime() 163 self.updateTimer.start(int(1000 / Colors.fps)) 164 update_mode = QGraphicsView.NoViewportUpdate 165 else: 166 self.useTimer = False 167 self.updateTimer.stop() 168 169 if Colors.noTicker: 170 update_mode = QGraphicsView.MinimalViewportUpdate 171 else: 172 update_mode = QGraphicsView.SmartViewportUpdate 173 174 self.setViewportUpdateMode(update_mode) 175 176 def measureFps(self): 177 # Calculate time difference. 178 t = self.fpsTime.msecsTo(QTime.currentTime()) 179 if t == 0: 180 t = 0.01 181 182 self.currentFps = (1000.0 / t) 183 self.fpsHistory.append(self.currentFps) 184 self.fpsTime = QTime.currentTime() 185 186 # Calculate median. 187 size = len(self.fpsHistory) 188 189 if size == 10: 190 self.fpsHistory.sort() 191 self.fpsMedian = self.fpsHistory[int(size / 2)] 192 if self.fpsMedian == 0: 193 self.fpsMedian = 0.01 194 195 self.fpsHistory = [] 196 197 return True 198 199 return False 200 201 def forceFpsMedianCalculation(self): 202 # Used for adaption in case things are so slow that no median has yet 203 # been calculated. 204 if self.fpsMedian != -1: 205 return 206 207 size = len(self.fpsHistory) 208 209 if size == 0: 210 self.fpsMedian = 0.01 211 return 212 213 self.fpsHistory.sort() 214 self.fpsMedian = self.fpsHistory[size // 2] 215 if self.fpsMedian == 0: 216 self.fpsMedian = 0.01 217 218 def tick(self): 219 medianChanged = self.measureFps() 220 self.checkAdapt() 221 222 if medianChanged and self.fpsLabel and Colors.showFps: 223 self.fpsLabel.setText("FPS: %d" % int(self.currentFps)) 224 225 if MenuManager.instance().ticker: 226 MenuManager.instance().ticker.tick() 227 228 self.viewport().update() 229 230 if self.useTimer: 231 self.updateTimer.start(int(1000 / Colors.fps)) 232 233 def setupSceneItems(self): 234 if Colors.showFps: 235 self.fpsLabel = DemoTextItem("FPS: --", Colors.buttonFont(), 236 Qt.white, -1, None, DemoTextItem.DYNAMIC_TEXT) 237 self.fpsLabel.setZValue(1000) 238 self.fpsLabel.setPos(Colors.stageStartX, 239 600 - QFontMetricsF(Colors.buttonFont()).height() - 5) 240 241 self.mainSceneRoot = QGraphicsWidget() 242 self.scene.addItem(self.mainSceneRoot) 243 244 self.companyLogo = ImageItem( 245 QImage(self.imagesDir + '/trolltech-logo.png'), 246 1000, 1000, None, True, 0.5) 247 self.qtLogo = ImageItem(QImage(self.imagesDir + '/qtlogo_small.png'), 248 1000, 1000, None, True, 0.5) 249 self.companyLogo.setZValue(100) 250 self.qtLogo.setZValue(100) 251 self.pausedLabel = DemoTextItem("PAUSED", Colors.buttonFont(), 252 Qt.white, -1, None) 253 self.pausedLabel.setZValue(100) 254 fm = QFontMetricsF(Colors.buttonFont()) 255 self.pausedLabel.setPos(Colors.stageWidth - fm.width("PAUSED"), 256 590 - fm.height()) 257 self.pausedLabel.setRecursiveVisible(False) 258 259 def checkAdapt(self): 260 if self.doneAdapt or Colors.noTimerUpdate or self.demoStartTime.elapsed() < 2000: 261 return 262 263 self.doneAdapt = True 264 self.forceFpsMedianCalculation() 265 Colors.benchmarkFps = self.fpsMedian 266 Colors.debug("- benchmark: %d FPS" % int(Colors.benchmarkFps)) 267 268 if Colors.noAdapt: 269 return 270 271 if self.fpsMedian < 30: 272 ticker = MenuManager.instance().ticker 273 if ticker and ticker.scene(): 274 self.scene.removeItem(ticker) 275 Colors.noTimerUpdate = True 276 self.switchTimerOnOff(False) 277 278 if self.fpsLabel: 279 self.fpsLabel.setText("FPS: (%d)" % int(self.fpsMedian)) 280 281 Colors.debug("- benchmark adaption: removed ticker (fps < 30)") 282 283 if self.fpsMedian < 20: 284 Colors.noAnimations = True 285 Colors.debug("- benchmark adaption: animations switched off (fps < 20)") 286 287 Colors.adapted = True 288 289 def drawBackgroundToPixmap(self): 290 r = self.scene.sceneRect() 291 self.background = QPixmap(qRound(r.width()), qRound(r.height())) 292 self.background.fill(Qt.black) 293 painter = QPainter(self.background) 294 295 bg = QImage(self.imagesDir + '/demobg.png') 296 painter.drawImage(0, 0, bg) 297 298 def drawBackground(self, painter, rect): 299 painter.drawPixmap(QPoint(0, 0), self.background) 300 301 def toggleFullscreen(self): 302 if self.isFullScreen(): 303 self.enableMask(True) 304 self.showNormal() 305 if MenuManager.instance().ticker: 306 MenuManager.instance().ticker.pause(False) 307 else: 308 self.enableMask(False) 309 self.showFullScreen() 310 311 def keyPressEvent(self, event): 312 if event.key() == Qt.Key_Escape: 313 QApplication.quit() 314 elif event.key() == Qt.Key_F1: 315 s = "" 316 s += "\nAdapt: " 317 s += ["on", "off"][Colors.noAdapt] 318 s += "\nAdaption occured: " 319 s += ["no", "yes"][Colors.adapted] 320 w = QWidget() 321 s += "\nColor bit depth: %d" % w.depth() 322 s += "\nWanted FPS: %d" % Colors.fps 323 s += "\nBenchmarked FPS: "; 324 if Colors.benchmarkFps != -1: 325 s += "%d" % Colors.benchmarkFps 326 else: 327 s += "not calculated" 328 s += "\nAnimations: "; 329 s += ["on", "off"][Colors.noAnimations] 330 s += "\nBlending: "; 331 s += ["on", "off"][Colors.useEightBitPalette] 332 s += "\nTicker: "; 333 s += ["on", "off"][Colors.noTicker] 334 s += "\nPixmaps: "; 335 s += ["off", "on"][Colors.usePixmaps] 336 s += "\nRescale images on resize: "; 337 s += ["on", "off"][Colors.noRescale] 338 s += "\nTimer based updates: "; 339 s += ["on", "off"][Colors.noTimerUpdate] 340 s += "\nSeparate loop: "; 341 s += ["no", "yes"][Colors.useLoop] 342 s += "\nScreen sync: "; 343 s += ["yes", "no"][Colors.noScreenSync] 344 QMessageBox.information(None, "Current configuration", s) 345 346 super(MainWindow, self).keyPressEvent(event) 347 348 def focusInEvent(self, event): 349 if not Colors.pause: 350 return 351 352 if MenuManager.instance().ticker: 353 MenuManager.instance().ticker.pause(False) 354 355 code = MenuManager.instance().currentMenuCode 356 if code in (MenuManager.ROOT, MenuManager.MENU1): 357 self.switchTimerOnOff(True) 358 359 self.pausedLabel.setRecursiveVisible(False) 360 361 def focusOutEvent(self, event): 362 if not Colors.pause: 363 return 364 365 if MenuManager.instance().ticker: 366 MenuManager.instance().ticker.pause(True) 367 368 code = MenuManager.instance().currentMenuCode 369 if code in (MenuManager.ROOT, MenuManager.MENU1): 370 self.switchTimerOnOff(False) 371 372 self.pausedLabel.setRecursiveVisible(True) 373 374 def resizeEvent(self, event): 375 self.resetTransform() 376 self.scale(event.size().width() / 800.0, event.size().height() / 600.0) 377 378 super(MainWindow, self).resizeEvent(event) 379 380 DemoItem.setTransform(self.transform()) 381 382 if self.companyLogo: 383 r = self.scene.sceneRect() 384 ttb = self.companyLogo.boundingRect() 385 self.companyLogo.setPos(int((r.width() - ttb.width()) / 2), 386 595 - ttb.height()) 387 qtb = self.qtLogo.boundingRect() 388 self.qtLogo.setPos(802 - qtb.width(), 0) 389 390 # Changing size will almost always hurt FPS during the change so ignore 391 # it. 392 self.fpsHistory = [] 393