From ee1518086a4173efc4f3ff4c4632bf5af620b0a9 Mon Sep 17 00:00:00 2001 From: Balthasar Reuter Date: Thu, 10 May 2018 23:23:04 +0200 Subject: [PATCH] Countdown prettier --- photobooth/gui/PyQt5Gui.py | 49 ++++- photobooth/gui/PyQt5GuiHelpers.py | 299 ++++++++++++++++++++++++++++++ 2 files changed, 340 insertions(+), 8 deletions(-) create mode 100644 photobooth/gui/PyQt5GuiHelpers.py diff --git a/photobooth/gui/PyQt5Gui.py b/photobooth/gui/PyQt5Gui.py index 831778d..347d6be 100644 --- a/photobooth/gui/PyQt5Gui.py +++ b/photobooth/gui/PyQt5Gui.py @@ -4,18 +4,21 @@ from PIL import ImageQt from PyQt5.QtCore import Qt, QObject, QPoint, QThread, QTimer, pyqtSignal -from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QFormLayout, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLayout, QLineEdit, QMainWindow, QMessageBox, QPushButton, QVBoxLayout) +from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QFormLayout, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLayout, QLineEdit, QMainWindow, QMessageBox, QPushButton, QVBoxLayout, QWidget) from PyQt5.QtGui import QImage, QPainter, QPixmap import math from PyQt5.QtGui import QBrush, QPen, QColor from PyQt5.QtCore import QRect +from .PyQt5GuiHelpers import QRoundProgressBar + from . import * from .. import camera, printer from ..printer.PrinterPyQt5 import PrinterPyQt5 as Printer + class PyQt5Gui(Gui): def __init__(self, argv, config): @@ -649,17 +652,20 @@ class PyQt5WaitMessage(QFrame): self.update() + class PyQt5CountdownMessage(QFrame): def __init__(self, time, action): super().__init__() - self._counter = time + self._step_size = 100 + self._counter = time * (1000 // self._step_size) self._action = action self._picture = None self.initFrame() + self.initProgressBar(time) def initFrame(self): @@ -667,6 +673,27 @@ class PyQt5CountdownMessage(QFrame): self.setStyleSheet('background-color: white;') + def initProgressBar(self, time): + + self._bar = QRoundProgressBar() + self._bar.setBarStyle(QRoundProgressBar.StyleLine) + self._bar.setFixedSize(200, 200) + + self._bar.setDataPenWidth(7) + self._bar.setOutlinePenWidth(10) + + self._bar.setDecimals(0) + self._bar.setFormat('%v') + + self._bar.setRange(0, time) + self._bar.setValue(time) + + + def updateProgressBar(self): + + self._bar.setValue(self._counter / (1000 // self._step_size)) + + @property def counter(self): @@ -694,28 +721,34 @@ class PyQt5CountdownMessage(QFrame): if self._picture != None: pix = QPixmap.fromImage(self._picture) - pix = pix.scaled(self.rect().size(), Qt.KeepAspectRatio, Qt.FastTransformation) - origin = ( (self.rect().width() - pix.width()) // 2, - (self.rect().height() - pix.height()) // 2 ) + pix = pix.scaled(self.size(), Qt.KeepAspectRatio, Qt.FastTransformation) + origin = ( (self.width() - pix.width()) // 2, + (self.height() - pix.height()) // 2 ) painter.drawPixmap(QPoint(*origin), pix) - painter.drawText(event.rect(), Qt.AlignCenter, str(self.counter)) + # painter.drawText(event.rect(), Qt.AlignCenter, str(self.counter)) painter.end() + offset = ( (self.width() - self._bar.width()) // 2, + (self.height() - self._bar.height()) // 2 ) + self._bar.render(self, QPoint(*offset), self._bar.visibleRegion(), QWidget.DrawChildren) + def showEvent(self, event): - self._timer = self.startTimer(1000) + self._timer = self.startTimer(self._step_size) def timerEvent(self, event): self._counter -= 1 - self.update() if self._counter == 0: self.killTimer(self._timer) self._action() + else: + self.updateProgressBar() + self.update() class PyQt5PictureMessage(QFrame): diff --git a/photobooth/gui/PyQt5GuiHelpers.py b/photobooth/gui/PyQt5GuiHelpers.py new file mode 100644 index 0000000..ff3b5ae --- /dev/null +++ b/photobooth/gui/PyQt5GuiHelpers.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Adaptation of QRoundProgressBar from +# https://sourceforge.net/projects/qroundprogressbar/ +# to PyQt5, using the PyQt4-version offered at +# https://stackoverflow.com/a/33583019 + +from math import ceil + +from PyQt5 import QtCore, QtGui, Qt, QtWidgets + +class QRoundProgressBar(QtWidgets.QWidget): + + StyleDonut = 1 + StylePie = 2 + StyleLine = 3 + + PositionLeft = 180 + PositionTop = 90 + PositionRight = 0 + PositionBottom = -90 + + UF_VALUE = 1 + UF_PERCENT = 2 + UF_MAX = 4 + + def __init__(self): + super().__init__() + self.min = 0 + self.max = 100 + self.value = 25 + + self.nullPosition = self.PositionTop + self.barStyle = self.StyleDonut + self.outlinePenWidth =1 + self.dataPenWidth = 1 + self.rebuildBrush = False + self.format = "%p%" + self.decimals = 1 + self.updateFlags = self.UF_PERCENT + self.gradientData = [] + self.donutThicknessRatio = 0.75 + + def setRange(self, min, max): + self.min = min + self.max = max + + if self.max < self.min: + self.max, self.min = self.min, self.max + + if self.value < self.min: + self.value = self.min + elif self.value > self.max: + self.value = self.max + + if not self.gradientData: + self.rebuildBrush = True + self.update() + + def setMinimum(self, min): + self.setRange(min, self.max) + + def setMaximum(self, max): + self.setRange(self.min, max) + + def setValue(self, val): + if self.value != val: + if val < self.min: + self.value = self.min + elif val > self.max: + self.value = self.max + else: + self.value = val + self.update() + + def setNullPosition(self, position): + if position != self.nullPosition: + self.nullPosition = position + if not self.gradientData: + self.rebuildBrush = True + self.update() + + def setBarStyle(self, style): + if style != self.barStyle: + self.barStyle = style + self.update() + + def setOutlinePenWidth(self, penWidth): + if penWidth != self.outlinePenWidth: + self.outlinePenWidth = penWidth + self.update() + + def setDataPenWidth(self, penWidth): + if penWidth != self.dataPenWidth: + self.dataPenWidth = penWidth + self.update() + + def setDataColors(self, stopPoints): + if stopPoints != self.gradientData: + self.gradientData = stopPoints + self.rebuildBrush = True + self.update() + + def setFormat(self, format): + if format != self.format: + self.format = format + self.valueFormatChanged() + + def resetFormat(self): + self.format = '' + self.valueFormatChanged() + + def setDecimals(self, count): + if count >= 0 and count != self.decimals: + self.decimals = count + self.valueFormatChanged() + + def setDonutThicknessRatio(self, val): + self.donutThicknessRatio = max(0., min(val, 1.)) + self.update() + + def paintEvent(self, event): + outerRadius = min(self.width(), self.height()) + baseRect = QtCore.QRectF(1, 1, outerRadius-2, outerRadius-2) + + buffer = QtGui.QImage(outerRadius, outerRadius, QtGui.QImage.Format_ARGB32) + buffer.fill(0) + + p = QtGui.QPainter(buffer) + p.setRenderHint(QtGui.QPainter.Antialiasing) + + # data brush + self.rebuildDataBrushIfNeeded() + + # background + # self.drawBackground(p, buffer.rect()) + + # base circle + self.drawBase(p, baseRect) + + # data circle + arcStep = 360.0 / (self.max - self.min) * self.value + self.drawValue(p, baseRect, self.value, arcStep) + + # center circle + innerRect, innerRadius = self.calculateInnerRect(baseRect, outerRadius) + self.drawInnerBackground(p, innerRect) + + # text + self.drawText(p, innerRect, innerRadius, ceil(self.value)) + + # finally draw the bar + p.end() + + painter = QtGui.QPainter(self) + painter.drawImage(0, 0, buffer) + + def drawBackground(self, p, baseRect): + p.fillRect(baseRect, self.palette().window()) + + def drawBase(self, p, baseRect): + bs = self.barStyle + if bs == self.StyleDonut: + p.setPen(QtGui.QPen(self.palette().shadow().color(), self.outlinePenWidth)) + p.setBrush(self.palette().base()) + p.drawEllipse(baseRect) + elif bs == self.StylePie: + p.setPen(QtGui.QPen(self.palette().base().color(), self.outlinePenWidth)) + p.setBrush(self.palette().base()) + p.drawEllipse(baseRect) + elif bs == self.StyleLine: + color = self.palette().base().color() + color.setAlpha(100) + brush = self.palette().base() + brush.setColor(color) + p.setPen(QtGui.QPen(self.palette().base().color(), self.outlinePenWidth)) + p.setBrush(brush) + # p.drawEllipse(baseRect) + # p.setPen(QtGui.QPen(self.palette().base().color(), self.outlinePenWidth)) + # p.setBrush(Qt.Qt.NoBrush) + p.drawEllipse(baseRect.adjusted(self.outlinePenWidth/2, self.outlinePenWidth/2, -self.outlinePenWidth/2, -self.outlinePenWidth/2)) + + + def drawValue(self, p, baseRect, value, arcLength): + # nothing to draw + if value == self.min: + return + + # for Line style + if self.barStyle == self.StyleLine: + p.setPen(QtGui.QPen(self.palette().highlight().color(), self.dataPenWidth)) + p.setBrush(Qt.Qt.NoBrush) + p.drawArc(baseRect.adjusted(self.outlinePenWidth/2, self.outlinePenWidth/2, -self.outlinePenWidth/2, -self.outlinePenWidth/2), + self.nullPosition * 16, + -arcLength * 16) + return + + # for Pie and Donut styles + dataPath = QtGui.QPainterPath() + dataPath.setFillRule(Qt.Qt.WindingFill) + + # pie segment outer + dataPath.moveTo(baseRect.center()) + dataPath.arcTo(baseRect, self.nullPosition, -arcLength) + dataPath.lineTo(baseRect.center()) + + p.setBrush(self.palette().highlight()) + p.setPen(QtGui.QPen(self.palette().shadow().color(), self.dataPenWidth)) + p.drawPath(dataPath) + + def calculateInnerRect(self, baseRect, outerRadius): + # for Line style + if self.barStyle == self.StyleLine: + innerRadius = outerRadius - self.outlinePenWidth + else: # for Pie and Donut styles + innerRadius = outerRadius * self.donutThicknessRatio + + delta = (outerRadius - innerRadius) / 2. + innerRect = QtCore.QRectF(delta, delta, innerRadius, innerRadius) + return innerRect, innerRadius + + def drawInnerBackground(self, p, innerRect): + if self.barStyle == self.StyleDonut: + p.setBrush(self.palette().alternateBase()) + + cmod = p.compositionMode() + p.setCompositionMode(QtGui.QPainter.CompositionMode_Source) + + p.drawEllipse(innerRect) + + p.setCompositionMode(cmod) + + def drawText(self, p, innerRect, innerRadius, value): + if not self.format: + return + + text = self.valueToText(value) + + # !!! to revise + f = self.font() + # f.setPixelSize(innerRadius * max(0.05, (0.35 - self.decimals * 0.08))) + # f.setPixelSize(innerRadius * 1.8 / len(text)) + f.setPixelSize(innerRadius * 0.8 / len(text)) + p.setFont(f) + + textRect = innerRect + p.setPen(self.palette().text().color()) + p.drawText(textRect, Qt.Qt.AlignCenter, text) + + def valueToText(self, value): + textToDraw = self.format + + format_string = '{' + ':.{}f'.format(self.decimals) + '}' + + if self.updateFlags & self.UF_VALUE: + textToDraw = textToDraw.replace("%v", format_string.format(value)) + + if self.updateFlags & self.UF_PERCENT: + percent = (value - self.min) / (self.max - self.min) * 100.0 + textToDraw = textToDraw.replace("%p", format_string.format(percent)) + + if self.updateFlags & self.UF_MAX: + m = self.max - self.min + 1 + textToDraw = textToDraw.replace("%m", format_string.format(m)) + + return textToDraw + + def valueFormatChanged(self): + self.updateFlags = 0; + + if "%v" in self.format: + self.updateFlags |= self.UF_VALUE + + if "%p" in self.format: + self.updateFlags |= self.UF_PERCENT + + if "%m" in self.format: + self.updateFlags |= self.UF_MAX + + self.update() + + def rebuildDataBrushIfNeeded(self): + if self.rebuildBrush: + self.rebuildBrush = False + + dataBrush = QtGui.QConicalGradient() + dataBrush.setCenter(0.5,0.5) + dataBrush.setCoordinateMode(QtGui.QGradient.StretchToDeviceMode) + + for pos, color in self.gradientData: + dataBrush.setColorAt(1.0 - pos, color) + + # angle + dataBrush.setAngle(self.nullPosition) + + p = self.palette() + p.setBrush(QtGui.QPalette.Highlight, dataBrush) + self.setPalette(p) \ No newline at end of file