Gui refactored

This commit is contained in:
Balthasar Reuter
2018-06-14 21:59:00 +02:00
parent 0b7459065c
commit 6224926919
12 changed files with 569 additions and 660 deletions

View File

@@ -7,7 +7,7 @@ from PIL import Image, ImageOps
from .PictureDimensions import PictureDimensions
from . import gui
from .gui import GuiState
from .Worker import PictureSaver
@@ -113,7 +113,7 @@ class Photobooth:
def initRun(self):
self.setCameraIdle()
self._conn.send(gui.IdleState())
self._conn.send(GuiState.IdleState())
self.triggerOn()
def run(self):
@@ -131,7 +131,7 @@ class Photobooth:
self.trigger()
except RuntimeError as e:
logging.error('Camera error: %s', str(e))
self._conn.send(gui.ErrorState('Camera error', str(e)))
self._conn.send(GuiState.ErrorState('Cam error', str(e)))
self.recvAck()
except TeardownException:
@@ -149,22 +149,22 @@ class Photobooth:
def showCountdownPreview(self):
self._conn.send(gui.CountdownState())
self._conn.send(GuiState.CountdownState())
while not self._conn.poll():
picture = ImageOps.mirror(self._cap.getPreview())
self._conn.send(gui.PreviewState(picture=picture))
self._conn.send(GuiState.PreviewState(picture=picture))
self.recvAck()
def showCountdownNoPreview(self):
self._conn.send(gui.CountdownState())
self._conn.send(GuiState.CountdownState())
self.recvAck()
def showPose(self, num_picture):
self._conn.send(gui.PoseState(num_picture))
self._conn.send(GuiState.PoseState(num_picture))
def captureSinglePicture(self, num_picture):
@@ -199,24 +199,24 @@ class Photobooth:
logging.info('Photobooth triggered')
self._conn.send(gui.GreeterState())
self._conn.send(GuiState.GreeterState())
self.triggerOff()
self.setCameraActive()
self.recvAck()
pics = self.capturePictures()
self._conn.send(gui.AssembleState())
self._conn.send(GuiState.AssembleState())
img = self.assemblePictures(pics)
self._conn.send(gui.PictureState(img))
self._conn.send(GuiState.ReviewState(picture=img))
self.enqueueWorkerTasks(img)
self.setCameraIdle()
self.recvAck()
self._conn.send(gui.IdleState())
self._conn.send(GuiState.IdleState())
self.triggerOn()
def gpioTrigger(self):
@@ -225,7 +225,7 @@ class Photobooth:
def gpioExit(self):
self._conn.send(gui.TeardownState())
self._conn.send(GuiState.TeardownState())
def triggerOff(self):
@@ -235,4 +235,4 @@ class Photobooth:
def triggerOn(self):
self._lampOn()
self._gpioTrigger = lambda: self._conn.send(gui.TriggerState())
self._gpioTrigger = lambda: self._conn.send(GuiState.TriggerState())

View File

@@ -30,7 +30,7 @@ class PrintPostprocess(GuiPostprocess):
super().__init__(**kwargs)
Printer = lookup_and_import(printer.modules, printer_module, 'printer')
self._printer = Printer(page_size)
self._printer = Printer(page_size, True)
def get(self, picture):

View File

@@ -0,0 +1,170 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from . import GuiState
class GuiSkeleton:
def __init__(self):
super().__init__()
@property
def idle(self):
return self._idle
@idle.setter
def idle(self, handle):
if not callable(handle):
raise ValueError('Function handle for "idle" must be callable')
self._idle = handle
@property
def trigger(self):
return self._trigger
@trigger.setter
def trigger(self, handle):
if not callable(handle):
raise ValueError('Function handle for "trigger" must be callable')
self._trigger = handle
@property
def greeter(self):
return self._greeter
@greeter.setter
def greeter(self, handle):
if not callable(handle):
raise ValueError('Function handle for "greeter" must be callable')
self._greeter = handle
@property
def countdown(self):
return self._countdown
@countdown.setter
def countdown(self, handle):
if not callable(handle):
raise ValueError(('Function handle for "countdown" must be '
'callable'))
self._countdown = handle
@property
def preview(self):
return self._preview
@preview.setter
def preview(self, handle):
if not callable(handle):
raise ValueError('Function handle for "preview" must be callable')
self._preview = handle
@property
def pose(self):
return self._pose
@pose.setter
def pose(self, handle):
if not callable(handle):
raise ValueError('Function handle for "pose" must be callable')
self._pose = handle
@property
def assemble(self):
return self._assemble
@assemble.setter
def assemble(self, handle):
if not callable(handle):
raise ValueError('Function handle for "assemble" must be callable')
self._assemble = handle
@property
def review(self):
return self._review
@review.setter
def review(self, handle):
if not callable(handle):
raise ValueError('Function handle for "review" must be callable')
self._review = handle
@property
def teardown(self):
return self._teardown
@teardown.setter
def teardown(self, handle):
if not callable(handle):
raise ValueError('Function handle for "teardown" must be callable')
self._teardown = handle
@property
def error(self):
return self._error
@error.setter
def error(self, handle):
if not callable(handle):
raise ValueError('Function handle for "error" must be callable')
self._error = handle
def handleState(self, state):
if not isinstance(state, GuiState.GuiState):
raise ValueError('Not a GuiState object received')
if isinstance(state, GuiState.IdleState):
self.idle(state)
elif isinstance(state, GuiState.TriggerState):
self.trigger(state)
elif isinstance(state, GuiState.GreeterState):
self.greeter(state)
elif isinstance(state, GuiState.CountdownState):
self.countdown(state)
elif isinstance(state, GuiState.PreviewState):
self.preview(state)
elif isinstance(state, GuiState.PoseState):
self.pose(state)
elif isinstance(state, GuiState.AssembleState):
self.assemble(state)
elif isinstance(state, GuiState.ReviewState):
self.review(state)
elif isinstance(state, GuiState.TeardownState):
self.teardown(state)
elif isinstance(state, GuiState.ErrorState):
self.error(state)
else:
raise ValueError('Unknown state received')

View File

@@ -144,6 +144,13 @@ class PreviewState(PictureState):
super().__init__(**kwargs)
class ReviewState(PictureState):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class TeardownState(GuiState):
def __init__(self, **kwargs):

View File

@@ -1,318 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import multiprocessing as mp
import queue
import logging
from PIL import ImageQt
from PyQt5 import QtGui, QtCore, QtWidgets
import math
from .Qt5Gui import Frames
from .PyQt5GuiHelpers import QRoundProgressBar
from . import *
class PyQt5Gui(Gui):
def __init__(self, argv, config):
super().__init__()
global cfg
cfg = config
self._app = QtWidgets.QApplication(argv)
self._p = PyQt5MainWindow()
self._lastState = self.showStart
self._postprocessList = []
self._postprocessQueue = queue.Queue()
if cfg.getBool('Printer', 'enable'):
self._postprocessList.append( PrintPostprocess( cfg.get('Printer', 'module'),
(cfg.getInt('Printer', 'width'), cfg.getInt('Printer', 'height')) ) )
def run(self, camera_conn, worker_queue):
receiver = PyQt5Receiver([camera_conn])
receiver.notify.connect(self.handleState)
receiver.start()
self._conn = camera_conn
self._queue = worker_queue
self.showStart()
exit_code = self._app.exec_()
self._p = None
return exit_code
def close(self):
self._p.close()
def restart(self):
self._app.exit(123)
def sendAck(self):
self._conn.send('ack')
def sendCancel(self):
self._conn.send('cancel')
def sendTrigger(self):
self._conn.send('triggered')
def sendTeardown(self):
self._conn.send('teardown')
def handleKeypressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
self.handleState(TeardownState())
elif event.key() == QtCore.Qt.Key_Space:
self.handleState(TriggerState())
def handleKeypressEventNoTrigger(self, event):
if event.key() == QtCore.Qt.Key_Escape:
self.handleState(TeardownState())
def handleState(self, state):
if not isinstance(state, GuiState):
raise ValueError('Invalid data received')
if isinstance(state, IdleState):
self.showIdle()
elif isinstance(state, TriggerState):
self.sendTrigger()
elif isinstance(state, GreeterState):
global cfg
self._p.handleKeypressEvent = self.handleKeypressEventNoTrigger
# self._p.setCentralWidget( PyQt5GreeterMessage(
self._p.setCentralWidget( Frames.GreeterMessage(
cfg.getInt('Picture', 'num_x'), cfg.getInt('Picture', 'num_y') ) )
QtCore.QTimer.singleShot(cfg.getInt('Photobooth', 'greeter_time') * 1000, self.sendAck)
elif isinstance(state, CountdownState):
# self._p.setCentralWidget(PyQt5CountdownMessage(cfg.getInt('Photobooth', 'countdown_time'), self.sendAck))
countdown_time = cfg.getInt('Photobooth', 'countdown_time')
self._p.setCentralWidget(Frames.CountdownMessage(countdown_time,
self.sendAck))
elif isinstance(state, PreviewState):
self._p.centralWidget().picture = ImageQt.ImageQt(state.picture)
self._p.centralWidget().update()
elif isinstance(state, PoseState):
# self._p.setCentralWidget(PyQt5PoseMessage())
self._p.setCentralWidget(Frames.PoseMessage(state.num_picture,
cfg.getInt('Picture', 'num_x'), cfg.getInt('Picture', 'num_y')))
elif isinstance(state, AssembleState):
self._p.setCentralWidget(Frames.WaitMessage('Processing picture...'))
# self._p.setCentralWidget(PyQt5WaitMessage('Processing picture...'))
elif isinstance(state, PictureState):
img = ImageQt.ImageQt(state.picture)
# self._p.setCentralWidget(PyQt5PictureMessage(img))
self._p.setCentralWidget(Frames.PictureMessage(img))
QtCore.QTimer.singleShot(cfg.getInt('Photobooth', 'display_time') * 1000,
lambda : self.postprocessPicture(state.picture))
elif isinstance(state, TeardownState):
self._conn.send('teardown')
self.showStart()
elif isinstance(state, ErrorState):
self.showError(state.title, state.message)
else:
raise ValueError('Unknown state')
def postprocessPicture(self, picture):
for task in self._postprocessList:
self._postprocessQueue.put(task.get(picture))
self.handleQueue()
def handleQueue(self):
while True:
try:
task = self._postprocessQueue.get(block = False)
except queue.Empty:
self.sendAck()
break
else:
if isinstance(task, PrintState):
reply = QtWidgets.QMessageBox.question(self._p, 'Print picture?',
'Do you want to print the picture?',
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
task.handler()
QtWidgets.QMessageBox.information(self._p, 'Printing',
'Picture sent to printer.', QtWidgets.QMessageBox.Ok)
else:
raise ValueError('Unknown task')
def showStart(self):
self._p.handleKeypressEvent = lambda event : None
self._lastState = self.showStart
self._p.setCentralWidget(Frames.Start(self.showStartPhotobooth, self.showSettings, self.close))
if QtWidgets.QApplication.overrideCursor() != 0:
QtWidgets.QApplication.restoreOverrideCursor()
def showSettings(self):
global cfg
self._p.handleKeypressEvent = lambda event : None
self._lastState = self.showSettings
self._p.setCentralWidget(Frames.Settings(cfg, self.showSettings, self.showStart, self.restart))
def showStartPhotobooth(self):
self._lastState = self.showStartPhotobooth
self._conn.send('start')
# self._p.setCentralWidget(PyQt5WaitMessage('Starting the photobooth...'))
self._p.setCentralWidget(Frames.WaitMessage('Starting the photobooth...'))
if cfg.getBool('Gui', 'hide_cursor'):
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.BlankCursor)
def showIdle(self):
self._p.handleKeypressEvent = self.handleKeypressEvent
self._lastState = self.showIdle
self._p.setCentralWidget(Frames.IdleMessage())
# self._p.setCentralWidget(PyQt5IdleMessage())
def showError(self, title, message):
logging.error('%s: %s', title, message)
reply = QtWidgets.QMessageBox.warning(self._p, title, message,
QtWidgets.QMessageBox.Close | QtWidgets.QMessageBox.Retry, QtWidgets.QMessageBox.Retry)
if reply == QtWidgets.QMessageBox.Retry:
self.sendAck()
self._lastState()
else:
self.sendCancel()
self.showStart()
class PyQt5Receiver(QtCore.QThread):
notify = QtCore.pyqtSignal(object)
def __init__(self, conn):
super().__init__()
self._conn = conn
def handle(self, state):
self.notify.emit(state)
def run(self):
while self._conn:
for c in mp.connection.wait(self._conn):
try:
state = c.recv()
except EOFError:
break
else:
self.handle(state)
class PyQt5MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.handleKeypressEvent = lambda event : None
self.initUI()
@property
def handleKeypressEvent(self):
return self._handle_key
@handleKeypressEvent.setter
def handleKeypressEvent(self, func):
if not callable(func):
raise ValueError('Keypress event handler must be callable')
self._handle_key = func
def initUI(self):
global cfg
self.setWindowTitle('Photobooth')
if cfg.getBool('Gui', 'fullscreen'):
self.showFullScreen()
else:
self.resize(cfg.getInt('Gui', 'width'),
cfg.getInt('Gui', 'height'))
self.show()
def closeEvent(self, e):
reply = QtWidgets.QMessageBox.question(self, 'Confirmation', "Quit Photobooth?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
e.accept()
else:
e.ignore()
def keyPressEvent(self, event):
self.handleKeypressEvent(event)

View File

@@ -1,310 +0,0 @@
#!/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 * 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:
perc = (value - self.min) / (self.max - self.min) * 100.0
textToDraw = textToDraw.replace("%p", format_string.format(perc))
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)

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import queue
from .. import GuiState
from ..GuiPostprocess import PrintPostprocess
class Postprocessor:
def __init__(self, config):
super().__init__()
self._task_list = []
self._queue = queue.Queue()
if config.getBool('Printer', 'enable'):
module = config.get('Printer', 'module')
size = (config.getInt('Printer', 'width'),
config.getInt('Printer', 'height'))
self._task_list.append(PrintPostprocess(module, size))
def fill(self, picture):
for task in self._task_list:
self._queue.put(task.get(picture))
def work(self, msg_box):
while True:
try:
task = self._queue.get(block=False)
except queue.Empty:
return
if isinstance(task, GuiState.PrintState):
if msg_box.question('Print picture?',
'Do you want to print the picture?'):
task.handler()
msg_box.information('Printing...',
'Picture sent to printer.')
else:
raise ValueError('Unknown task')

View File

@@ -0,0 +1,278 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
from PyQt5 import QtCore
from PyQt5 import QtWidgets
from PIL import ImageQt
from .. import GuiState
from ..GuiSkeleton import GuiSkeleton
from . import Frames
from . import Postprocessor
from . import Receiver
class PyQt5Gui(GuiSkeleton):
def __init__(self, argv, config, camera_conn, worker_queue):
super().__init__()
self._cfg = config
self._conn = camera_conn
self._registerCallbacks()
self._initUI(argv)
self._initReceiver()
self._postprocess = Postprocessor.Postprocessor(self._cfg)
def run(self):
self._showWelcomeScreen()
exit_code = self._app.exec_()
self._gui = None
return exit_code
def close(self):
self._gui.close()
def restart(self):
self._app.exit(123)
def _registerCallbacks(self):
self.idle = self._showIdle
self.trigger = self._sendTrigger
self.greeter = self._showGreeter
self.countdown = self._showCountdown
self.preview = self._showPreview
self.pose = self._showPose
self.assemble = self._showAssemble
self.review = self._showReview
self.teardown = self._sendTeardown
self.error = self._showError
def _initUI(self, argv):
self._disableTrigger()
self._app = QtWidgets.QApplication(argv)
self._gui = PyQt5MainWindow(self._cfg, self._handleKeypressEvent)
def _initReceiver(self):
self._receiver = Receiver.Receiver([self._conn])
self._receiver.notify.connect(self.handleState)
self._receiver.start()
def _setWidget(self, widget):
self._gui.setCentralWidget(widget)
def _enableEscape(self):
self._is_escape = True
def _disableEscape(self):
self._is_escape = False
def _enableTrigger(self):
self._is_trigger = True
def _disableTrigger(self):
self._is_trigger = False
def _sendStart(self):
self._conn.send('start')
def _sendTrigger(self, state):
self._conn.send('triggered')
def _sendAck(self):
self._conn.send('ack')
def _sendCancel(self):
self._conn.send('cancel')
def _sendTeardown(self, state):
self._conn.send('teardown')
self._showWelcomeScreen()
def _handleKeypressEvent(self, event):
if self._is_escape and event.key() == QtCore.Qt.Key_Escape:
self.handleState(GuiState.TeardownState())
elif self._is_trigger and event.key() == QtCore.Qt.Key_Space:
self.handleState(GuiState.TriggerState())
def _postprocessPicture(self, picture):
self._postprocess.fill(picture)
self._postprocess.work(MessageBox(self._gui))
self._sendAck()
def _showWelcomeScreen(self):
self._disableTrigger()
self._disableEscape()
self._lastHandle = self._showWelcomeScreen
self._setWidget(Frames.Start(self._showStart, self._showSettings,
self.close))
if QtWidgets.QApplication.overrideCursor() != 0:
QtWidgets.QApplication.restoreOverrideCursor()
def _showSettings(self):
self._disableTrigger()
self._disableEscape()
self._lastHandle = self._showSettings
self._setWidget(Frames.Settings(self._cfg, self._showSettings,
self._showWelcomeScreen, self.restart))
def _showStart(self, state):
self._disableTrigger()
self._enableEscape()
self._lastHandle = self._showWelcomeScreen
self._sendStart()
self._setWidget(Frames.WaitMessage('Starting the photobooth...'))
if self._cfg.getBool('Gui', 'hide_cursor'):
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.BlankCursor)
def _showIdle(self, state):
self._enableEscape()
self._enableTrigger()
self._lastHandle = self._showIdle
self._setWidget(Frames.IdleMessage())
def _showGreeter(self, state):
self._enableEscape()
self._disableTrigger()
num_pic = (self._cfg.getInt('Picture', 'num_x'),
self._cfg.getInt('Picture', 'num_x'))
greeter_time = self._cfg.getInt('Photobooth', 'greeter_time') * 1000
self._setWidget(Frames.GreeterMessage(*num_pic))
QtCore.QTimer.singleShot(greeter_time, self._sendAck)
def _showCountdown(self, state):
countdown_time = self._cfg.getInt('Photobooth', 'countdown_time')
self._setWidget(Frames.CountdownMessage(countdown_time, self._sendAck))
def _showPreview(self, state):
self._gui.centralWidget().picture = ImageQt.ImageQt(state.picture)
self._gui.centralWidget().update()
def _showPose(self, state):
num_pic = (self._cfg.getInt('Picture', 'num_x'),
self._cfg.getInt('Picture', 'num_x'))
self._setWidget(Frames.PoseMessage(state.num_picture, *num_pic))
def _showAssemble(self, state):
self._setWidget(Frames.WaitMessage('Processing picture...'))
def _showReview(self, state):
img = ImageQt.ImageQt(state.picture)
review_time = self._cfg.getInt('Photobooth', 'display_time') * 1000
self._setWidget(Frames.PictureMessage(img))
QtCore.QTimer.singleShot(review_time, lambda:
self._postprocessPicture(state.picture))
def _showError(self, state):
logging.error('%s: %s', state.title, state.message)
reply = QtWidgets.QMessageBox.warning(self._gui, state.title,
state.message,
QtWidgets.QMessageBox.Close |
QtWidgets.QMessageBox.Retry,
QtWidgets.QMessageBox.Retry)
if reply == QtWidgets.QMessageBox.Retry:
self._sendAck()
self._lastState()
else:
self._sendCancel()
self._showWelcomeScreen()
class PyQt5MainWindow(QtWidgets.QMainWindow):
def __init__(self, config, keypress_handler):
super().__init__()
self._cfg = config
self._handle_key = keypress_handler
self._initUI()
def _initUI(self):
self.setWindowTitle('Photobooth')
if self._cfg.getBool('Gui', 'fullscreen'):
self.showFullScreen()
else:
self.resize(self._cfg.getInt('Gui', 'width'),
self._cfg.getInt('Gui', 'height'))
self.show()
def closeEvent(self, e):
reply = QtWidgets.QMessageBox.question(self, 'Confirmation',
"Quit Photobooth?",
QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
e.accept()
else:
e.ignore()
def keyPressEvent(self, event):
self._handle_key(event)
class MessageBox:
def __init__(self, parent):
super().__init__()
self._parent = parent
def question(self, title, message):
reply = QtWidgets.QMessageBox.question(self._parent, title, message,
QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.No)
return reply == QtWidgets.QMessageBox.Yes
def information(self, title, message):
QtWidgets.QMessageBox.information(self._parent, title, message,
QtWidgets.QMessageBox.Ok)

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import multiprocessing as mp
from PyQt5 import QtCore
class Receiver(QtCore.QThread):
notify = QtCore.pyqtSignal(object)
def __init__(self, conn):
super().__init__()
self._conn = conn
def handle(self, state):
self.notify.emit(state)
def run(self):
while self._conn:
for c in mp.connection.wait(self._conn):
try:
state = c.recv()
except EOFError:
break
else:
self.handle(state)

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from .PyQt5Gui import PyQt5Gui # noqa

View File

@@ -1,19 +1,21 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from .GuiState import * # noqa
from .GuiPostprocess import * # noqa
# from .GuiState import * # noqa
# from .GuiPostprocess import * # noqa
from . import GuiState # noqa
# Available gui modules as tuples of (config name, module name, class name)
modules = (('PyQt5', 'PyQt5Gui', 'PyQt5Gui'), )
modules = (('PyQt5', 'Qt5Gui', 'PyQt5Gui'), )
class Gui:
# class Gui:
def __init__(self):
# def __init__(self):
pass
# pass
def run(self, camera_conn, worker_queue):
# def run(self, camera_conn, worker_queue):
raise NotImplementedError()
# raise NotImplementedError()

View File

@@ -32,7 +32,7 @@ class CameraProcess(mp.Process):
def run_camera(self):
try:
# try:
cap = lookup_and_import(
camera.modules, self.cfg.get('Camera', 'module'), 'camera')
@@ -40,14 +40,14 @@ class CameraProcess(mp.Process):
self.cfg, cap, self.conn, self.worker_queue)
return photobooth.run()
except BaseException as e:
self.conn.send(gui.ErrorState('Camera error', str(e)))
event = self.conn.recv()
if str(event) in ('cancel', 'ack'):
return 123
else:
logging.error('Unknown event received: %s', str(event))
raise RuntimeError('Unknown event received', str(event))
# except BaseException as e:
# self.conn.send(gui.GuiState.ErrorState('Camera error', str(e)))
# event = self.conn.recv()
# if str(event) in ('cancel', 'ack'):
# return 123
# else:
# logging.error('Unknown event received: %s', str(event))
# raise RuntimeError('Unknown event received', str(event))
def run(self):
@@ -96,7 +96,7 @@ class GuiProcess(mp.Process):
Gui = lookup_and_import(gui.modules, self.cfg.get('Gui', 'module'),
'gui')
sys.exit(Gui(self.argv, self.cfg).run(self.conn, self.queue))
sys.exit(Gui(self.argv, self.cfg, self.conn, self.queue).run())
def run(argv):