Files
photobooth/photobooth/PyQt5Gui.py

460 lines
13 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import Gui
from PIL import ImageQt
from PyQt5.QtCore import Qt, QObject, QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QFormLayout, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLayout, QLineEdit, QMainWindow, QMessageBox, QPushButton, QVBoxLayout)
from PyQt5.QtGui import QImage, QPainter, QPixmap
class PyQt5Gui(Gui.Gui):
def __init__(self, argv, config):
super().__init__()
global cfg
cfg = config
self._app = QApplication(argv)
self._p = PyQt5MainWindow()
def run(self, send, recv):
receiver = PyQt5Receiver(recv)
receiver.notify.connect(self.handleState)
receiver.start()
self._transport = send
self._p.transport = send
self.showStart()
return self._app.exec_()
def close(self):
self._p.close()
def handleKeypressEvent(self, event):
if event.key() == Qt.Key_Escape:
self.showStart()
elif event.key() == Qt.Key_Space:
self._p.handleKeypressEvent = lambda event : None
self._transport.send('triggered')
def handleState(self, state):
if not isinstance(state, Gui.GuiState):
raise ValueError('Invalid data received')
if isinstance(state, Gui.IdleState):
self.showIdle()
elif isinstance(state, Gui.PoseState):
global cfg
num_pictures = ( cfg.getInt('Photobooth', 'num_pictures_x') *
cfg.getInt('Photobooth', 'num_pictures_y') )
self._p.setCentralWidget(
PyQt5PictureMessage('Will capture {} pictures!'.format(num_pictures)))
elif isinstance(state, Gui.PreviewState):
img = ImageQt.ImageQt(state.picture)
self._p.setCentralWidget(PyQt5PictureMessage(state.message, img))
elif isinstance(state, Gui.PictureState):
img = ImageQt.ImageQt(state.picture)
self._p.setCentralWidget(PyQt5PictureMessage('', img))
elif isinstance(state, Gui.ErrorState):
self.showError(state.title, state.message)
else:
raise ValueError('Unknown state')
def showStart(self):
self._p.handleKeypressEvent = lambda event : None
self._p.setCentralWidget(PyQt5Start(self))
def showSettings(self):
self._p.handleKeypressEvent = lambda event : None
self._p.setCentralWidget(PyQt5Settings(self))
def showIdle(self):
self._p.handleKeypressEvent = self.handleKeypressEvent
self._p.setCentralWidget(PyQt5PictureMessage('Hit the button!'))
def showError(self, title, message):
if QMessageBox.warning(self._p, title,message, QMessageBox.Ok,
QMessageBox.Ok) == QMessageBox.Ok:
self.showIdle()
class PyQt5Receiver(QThread):
notify = pyqtSignal(object)
def __init__(self, transport):
super().__init__()
self._transport = transport
def handle(self, state):
self.notify.emit(state)
def run(self):
while True:
try:
state = self._transport.recv()
except EOFError:
break
else:
self.handle(state)
class PyQt5MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.handleKeypressEvent = lambda event : None
self.initUI()
@property
def transport(self):
return self._transport
@transport.setter
def transport(self, new_transport):
if not hasattr(new_transport, 'send'):
raise ValueError('PyQt5MainWindow.transport must provide send()')
self._transport = new_transport
@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 showMessage(self, message, picture=None):
content = PyQt5PictureMessage(message, picture)
self.setCentralWidget(content)
def closeEvent(self, e):
reply = QMessageBox.question(self, 'Confirmation', "Quit Photobooth?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
self.transport.close()
e.accept()
else:
e.ignore()
def keyPressEvent(self, event):
self.handleKeypressEvent(event)
class PyQt5Start(QFrame):
def __init__(self, gui):
super().__init__()
self.initFrame(gui)
def initFrame(self, gui):
grid = QGridLayout()
grid.setSpacing(100)
self.setLayout(grid)
btnStart = QPushButton('Start Photobooth')
btnStart.resize(btnStart.sizeHint())
btnStart.clicked.connect(gui.showIdle)
grid.addWidget(btnStart, 0, 0)
btnSettings = QPushButton('Settings')
btnSettings.resize(btnSettings.sizeHint())
btnSettings.clicked.connect(gui.showSettings)
grid.addWidget(btnSettings, 0, 1)
btnQuit = QPushButton('Quit')
btnQuit.resize(btnQuit.sizeHint())
btnQuit.clicked.connect(gui.close)
grid.addWidget(btnQuit, 0, 2)
class PyQt5Settings(QFrame):
def __init__(self, gui):
super().__init__()
self._gui = gui
self.initFrame()
def initFrame(self):
self._value_widgets = {}
grid = QGridLayout()
grid.addWidget(self.createGuiSettings(), 0, 0)
grid.addWidget(self.createGpioSettings(), 1, 0)
grid.addWidget(self.createCameraSettings(), 0, 1)
grid.addWidget(self.createPhotoboothSettings(), 1, 1)
layout = QVBoxLayout()
layout.addLayout(grid)
layout.addStretch(1)
layout.addWidget(self.createButtons())
self.setLayout(layout)
def createGuiSettings(self):
global cfg
self._value_widgets['Gui'] = {}
self._value_widgets['Gui']['fullscreen'] = QCheckBox('Enable fullscreen')
if cfg.getBool('Gui', 'fullscreen'):
self._value_widgets['Gui']['fullscreen'].toggle()
self._value_widgets['Gui']['width'] = QLineEdit(cfg.get('Gui', 'width'))
self._value_widgets['Gui']['height'] = QLineEdit(cfg.get('Gui', 'height'))
layout = QFormLayout()
layout.addRow(self._value_widgets['Gui']['fullscreen'])
layout.addRow(QLabel('Width:'), self._value_widgets['Gui']['width'])
layout.addRow(QLabel('Height:'), self._value_widgets['Gui']['height'])
widget = QGroupBox('Interface settings (restart required)')
widget.setLayout(layout)
return widget
def createGpioSettings(self):
global cfg
self._value_widgets['Gpio'] = {}
self._value_widgets['Gpio']['enable'] = QCheckBox('Enable GPIO (restart required)')
if cfg.getBool('Gpio', 'enable'):
self._value_widgets['Gpio']['enable'].toggle()
self._value_widgets['Gpio']['exit_channel'] = QLineEdit(cfg.get('Gpio', 'exit_channel'))
self._value_widgets['Gpio']['trigger_channel'] = QLineEdit(cfg.get('Gpio', 'trigger_channel'))
self._value_widgets['Gpio']['lamp_channel'] = QLineEdit(cfg.get('Gpio', 'lamp_channel'))
layout = QFormLayout()
layout.addRow(self._value_widgets['Gpio']['enable'])
layout.addRow(QLabel('Exit channel:'), self._value_widgets['Gpio']['exit_channel'])
layout.addRow(QLabel('Trigger channel'), self._value_widgets['Gpio']['trigger_channel'])
layout.addRow(QLabel('Lamp channel'), self._value_widgets['Gpio']['lamp_channel'])
widget = QGroupBox('GPIO settings')
widget.setLayout(layout)
return widget
def createCameraSettings(self):
global cfg
wrapper = QComboBox()
wrapper.addItem('command line')
wrapper.addItem('piggyphoto')
wrapper.addItem('gphoto2-cffi')
current_wrapper = cfg.get('Camera', 'gphoto2_wrapper')
if current_wrapper == 'commandline':
wrapper.setCurrentIndex(0)
elif current_wrapper == 'piggyphoto':
wrapper.setCurrentIndex(1)
elif current_wrapper == 'gphoto2-cffi':
wrapper.setCurrentIndex(2)
else:
wrapper.setCurrentIndex(-1)
self._value_widgets['Camera'] = {}
self._value_widgets['Camera']['gphoto2_wrapper'] = wrapper
layout = QFormLayout()
layout.addRow(QLabel('gPhoto2 wrapper:'), self._value_widgets['Camera']['gphoto2_wrapper'])
widget = QGroupBox('Camera settings')
widget.setLayout(layout)
return widget
def createPhotoboothSettings(self):
global cfg
self._value_widgets['Photobooth'] = {}
self._value_widgets['Photobooth']['show_preview'] = QCheckBox('Show preview while countdown (restart required)')
if cfg.getBool('Photobooth', 'show_preview'):
self._value_widgets['Photobooth']['show_preview'].toggle()
self._value_widgets['Photobooth']['num_pictures_x'] = QLineEdit(cfg.get('Photobooth', 'num_pictures_x'))
self._value_widgets['Photobooth']['num_pictures_y'] = QLineEdit(cfg.get('Photobooth', 'num_pictures_y'))
layout = QFormLayout()
layout.addRow(self._value_widgets['Photobooth']['show_preview'])
sublayout = QHBoxLayout()
sublayout.addWidget(QLabel('Number of pictures'))
sublayout.addWidget(self._value_widgets['Photobooth']['num_pictures_x'])
sublayout.addWidget(QLabel('x'))
sublayout.addWidget(self._value_widgets['Photobooth']['num_pictures_y'])
layout.addRow(sublayout)
widget = QGroupBox('Photobooth settings')
widget.setLayout(layout)
return widget
def createButtons(self):
layout = QHBoxLayout()
layout.addStretch(1)
btnSave = QPushButton('Save')
btnSave.resize(btnSave.sizeHint())
btnSave.clicked.connect(self.storeConfig)
layout.addWidget(btnSave)
btnCancel = QPushButton('Cancel')
btnCancel.resize(btnCancel.sizeHint())
btnCancel.clicked.connect(self._gui.showStart)
layout.addWidget(btnCancel)
btnRestore = QPushButton('Restore defaults')
btnRestore.resize(btnRestore.sizeHint())
btnRestore.clicked.connect(self.restoreDefaults)
layout.addWidget(btnRestore)
widget = QGroupBox()
widget.setLayout(layout)
return widget
def storeConfig(self):
global cfg
cfg.set('Gui', 'fullscreen', str(self._value_widgets['Gui']['fullscreen'].isChecked()))
cfg.set('Gui', 'width', self._value_widgets['Gui']['width'].text())
cfg.set('Gui', 'height', self._value_widgets['Gui']['height'].text())
cfg.set('Gpio', 'enable', str(self._value_widgets['Gpio']['enable'].isChecked()))
cfg.set('Gpio', 'exit_channel', self._value_widgets['Gpio']['exit_channel'].text())
cfg.set('Gpio', 'trigger_channel', self._value_widgets['Gpio']['trigger_channel'].text())
cfg.set('Gpio', 'lamp_channel', self._value_widgets['Gpio']['lamp_channel'].text())
cfg.set('Photobooth', 'show_preview', str(self._value_widgets['Photobooth']['show_preview'].isChecked()))
cfg.set('Photobooth', 'num_pictures_x', self._value_widgets['Photobooth']['num_pictures_x'].text())
cfg.set('Photobooth', 'num_pictures_y', self._value_widgets['Photobooth']['num_pictures_y'].text())
wrapper_idx2val = [ 'commandline', 'piggyphoto', 'gphoto2-cffi' ]
cfg.set('Camera', 'gphoto2_wrapper', wrapper_idx2val[self._value_widgets['Camera']['gphoto2_wrapper'].currentIndex()])
cfg.write()
self._gui.showStart()
def restoreDefaults(self):
global cfg
cfg.defaults()
self._gui.showSettings()
class PyQt5PictureMessage(QFrame):
def __init__(self, message, picture=None):
super().__init__()
self._message = message
self._picture = picture
self.initFrame()
def initFrame(self):
self.setStyleSheet('background-color: white;')
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
if self._picture != None:
if isinstance(self._picture, QImage):
pix = QPixmap.fromImage(self._picture)
else:
pix = QPixmap(self._picture)
pix = pix.scaled(self.rect().size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
painter.drawPixmap(pix.rect(), pix, pix.rect())
painter.drawText(event.rect(), Qt.AlignCenter, self._message)
painter.end()