Postprocessing dialogue prettified
This commit is contained in:
@@ -46,6 +46,8 @@ greeter_time = 3
|
||||
countdown_time = 8
|
||||
# Display time of assembled picture (shown after last shot)
|
||||
display_time = 5
|
||||
# Timeout for postprocessing (shown after review)
|
||||
postprocess_time = 60
|
||||
|
||||
[Picture]
|
||||
# Basedir of output pictures
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Photobooth - a flexible photo booth software
|
||||
# Copyright (C) 2018 Balthasar Reuter <photobooth at re - web dot eu>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
|
||||
from .. import printer
|
||||
from ..util import lookup_and_import
|
||||
from .GuiState import PrintState
|
||||
|
||||
|
||||
class GuiPostprocess:
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
assert not kwargs
|
||||
|
||||
def get(self, picture):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def confirm(self, picture):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class PrintPostprocess(GuiPostprocess):
|
||||
|
||||
def __init__(self, printer_module, page_size, **kwargs):
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
Printer = lookup_and_import(printer.modules, printer_module, 'printer')
|
||||
self._printer = Printer(page_size, True)
|
||||
|
||||
def get(self, picture):
|
||||
|
||||
return PrintState(lambda: self.do(picture), False)
|
||||
|
||||
def confirm(self, picture):
|
||||
|
||||
return PrintState(lambda: None, True)
|
||||
|
||||
def do(self, picture):
|
||||
|
||||
logging.info('Printing picture')
|
||||
self._printer.print(picture)
|
||||
104
photobooth/gui/GuiPostprocessor.py
Normal file
104
photobooth/gui/GuiPostprocessor.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Photobooth - a flexible photo booth software
|
||||
# Copyright (C) 2018 Balthasar Reuter <photobooth at re - web dot eu>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import queue
|
||||
|
||||
from .. import printer
|
||||
from ..util import lookup_and_import
|
||||
|
||||
|
||||
class GuiPostprocessor:
|
||||
|
||||
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 get(self, picture):
|
||||
|
||||
tasks = [task.get(picture) for task in self._task_list]
|
||||
return tasks
|
||||
|
||||
|
||||
class PostprocessTask:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
super().__init__()
|
||||
|
||||
def get(self, picture):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class PostprocessItem:
|
||||
|
||||
def __init__(self, label, action):
|
||||
|
||||
super().__init__()
|
||||
self.label = label
|
||||
self.action = action
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
|
||||
return self._label
|
||||
|
||||
@label.setter
|
||||
def label(self, label):
|
||||
|
||||
if not isinstance(label, str):
|
||||
raise TypeError('Label must be a string')
|
||||
|
||||
self._label = label
|
||||
|
||||
@property
|
||||
def action(self):
|
||||
|
||||
return self._action
|
||||
|
||||
@action.setter
|
||||
def action(self, action):
|
||||
|
||||
if not callable(action):
|
||||
raise TypeError('Action must be callable')
|
||||
|
||||
self._action = action
|
||||
|
||||
|
||||
class PrintPostprocess(PostprocessTask):
|
||||
|
||||
def __init__(self, printer_module, page_size, **kwargs):
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
Printer = lookup_and_import(printer.modules, printer_module, 'printer')
|
||||
self._printer = Printer(page_size, True)
|
||||
|
||||
def get(self, picture):
|
||||
|
||||
return PostprocessItem('Print', lambda: self._printer.print(picture))
|
||||
@@ -309,6 +309,44 @@ class CountdownMessage(QtWidgets.QFrame):
|
||||
painter.end()
|
||||
|
||||
|
||||
class PostprocessMessage(Widgets.TransparentOverlay):
|
||||
|
||||
def __init__(self, parent, tasks, idle_handle, timeout=None,
|
||||
timeout_handle=None):
|
||||
|
||||
if timeout_handle is None:
|
||||
timeout_handle = idle_handle
|
||||
|
||||
super().__init__(parent, timeout, timeout_handle)
|
||||
self.setObjectName('PostprocessMessage')
|
||||
self.initFrame(tasks, idle_handle)
|
||||
|
||||
def initFrame(self, tasks, idle_handle):
|
||||
|
||||
def disableAndCall(button, handle):
|
||||
button.setEnabled(False)
|
||||
handle()
|
||||
|
||||
def createButton(task):
|
||||
button = QtWidgets.QPushButton(task.label)
|
||||
button.clicked.connect(lambda: disableAndCall(button, task.action))
|
||||
return button
|
||||
|
||||
buttons = [createButton(task) for task in tasks]
|
||||
buttons.append(QtWidgets.QPushButton('Start over'))
|
||||
buttons[-1].clicked.connect(idle_handle)
|
||||
|
||||
button_lay = QtWidgets.QGridLayout()
|
||||
for i, button in enumerate(buttons):
|
||||
pos = divmod(i, 2)
|
||||
button_lay.addWidget(button, *pos)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addWidget(QtWidgets.QLabel('Happy?'))
|
||||
layout.addLayout(button_lay)
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
class SetDateTime(QtWidgets.QFrame):
|
||||
|
||||
def __init__(self, cancel_action, restart_action):
|
||||
@@ -539,11 +577,18 @@ class Settings(QtWidgets.QFrame):
|
||||
displ_time.setValue(self._cfg.getInt('Photobooth', 'display_time'))
|
||||
self.add('Photobooth', 'display_time', displ_time)
|
||||
|
||||
postproc_time = QtWidgets.QSpinBox()
|
||||
postproc_time.setRange(0, 1000)
|
||||
postproc_time.setValue(self._cfg.getInt('Photobooth',
|
||||
'postprocess_time'))
|
||||
self.add('Photobooth', 'postprocess_time', postproc_time)
|
||||
|
||||
layout = QtWidgets.QFormLayout()
|
||||
layout.addRow('Show preview during countdown:', preview)
|
||||
layout.addRow('Greeter time before countdown [s]:', greet_time)
|
||||
layout.addRow('Countdown time [s]:', count_time)
|
||||
layout.addRow('Picture display time [s]:', displ_time)
|
||||
layout.addRow('Postprocess timeout [s]:', postproc_time)
|
||||
|
||||
widget = QtWidgets.QWidget()
|
||||
widget.setLayout(layout)
|
||||
@@ -734,6 +779,8 @@ class Settings(QtWidgets.QFrame):
|
||||
str(self.get('Photobooth', 'countdown_time').text()))
|
||||
self._cfg.set('Photobooth', 'display_time',
|
||||
str(self.get('Photobooth', 'display_time').text()))
|
||||
self._cfg.set('Photobooth', 'postprocess_time',
|
||||
str(self.get('Photobooth', 'postprocess_time').text()))
|
||||
|
||||
self._cfg.set('Camera', 'module',
|
||||
camera.modules[self.get('Camera',
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Photobooth - a flexible photo booth software
|
||||
# Copyright (C) 2018 Balthasar Reuter <photobooth at re - web dot eu>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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')
|
||||
@@ -28,10 +28,10 @@ from PIL import ImageQt
|
||||
|
||||
from .. import GuiState
|
||||
from ..GuiSkeleton import GuiSkeleton
|
||||
from ..GuiPostprocessor import GuiPostprocessor
|
||||
|
||||
from . import styles
|
||||
from . import Frames
|
||||
from . import Postprocessor
|
||||
from . import Receiver
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class PyQt5Gui(GuiSkeleton):
|
||||
self._initUI(argv)
|
||||
self._initReceiver()
|
||||
|
||||
self._postprocess = Postprocessor.Postprocessor(self._cfg)
|
||||
self._postprocess = GuiPostprocessor(self._cfg)
|
||||
|
||||
def run(self):
|
||||
|
||||
@@ -152,12 +152,6 @@ class PyQt5Gui(GuiSkeleton):
|
||||
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()
|
||||
@@ -239,22 +233,27 @@ class PyQt5Gui(GuiSkeleton):
|
||||
review_time = self._cfg.getInt('Photobooth', 'display_time') * 1000
|
||||
self._setWidget(Frames.PictureMessage(img))
|
||||
QtCore.QTimer.singleShot(review_time, lambda:
|
||||
self._postprocessPicture(state.picture))
|
||||
self._showPostprocess(state.picture))
|
||||
|
||||
def _showPostprocess(self, picture):
|
||||
|
||||
tasks = self._postprocess.get(picture)
|
||||
postproc_t = self._cfg.getInt('Photobooth', 'postprocess_time')
|
||||
|
||||
Frames.PostprocessMessage(self._gui.centralWidget(), tasks,
|
||||
self._sendAck, postproc_t * 1000)
|
||||
|
||||
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()
|
||||
|
||||
def exec(*handles):
|
||||
for handle in handles:
|
||||
handle()
|
||||
|
||||
MessageBox(self, MessageBox.RETRY, state.title, state.message,
|
||||
exec(self._sendAck, self._lastState),
|
||||
exec(self._sendCancel, self._showWelcomeScreen))
|
||||
|
||||
|
||||
class PyQt5MainWindow(QtWidgets.QMainWindow):
|
||||
@@ -296,23 +295,67 @@ class PyQt5MainWindow(QtWidgets.QMainWindow):
|
||||
self._handle_key(event)
|
||||
|
||||
|
||||
class MessageBox:
|
||||
class MessageBox(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
QUESTION = 1
|
||||
RETRY = 2
|
||||
INFORMATION = 3
|
||||
|
||||
super().__init__()
|
||||
def __init__(self, parent, type, title, message, *handles):
|
||||
|
||||
self._parent = parent
|
||||
super().__init__(parent)
|
||||
|
||||
def question(self, title, message):
|
||||
if type == MessageBox.QUESTION:
|
||||
self.question(title, message, *handles)
|
||||
elif type == MessageBox.RETRY:
|
||||
self.retry(title, message, *handles)
|
||||
else:
|
||||
raise ValueError('Unknown type specified')
|
||||
|
||||
reply = QtWidgets.QMessageBox.question(self._parent, title, message,
|
||||
QtWidgets.QMessageBox.Yes |
|
||||
QtWidgets.QMessageBox.No,
|
||||
QtWidgets.QMessageBox.No)
|
||||
return reply == QtWidgets.QMessageBox.Yes
|
||||
def question(self, title, message, *handles):
|
||||
|
||||
def information(self, title, message):
|
||||
lbl_title = QtWidgets.QLabel(title)
|
||||
lbl_title.setObjectName('title')
|
||||
|
||||
QtWidgets.QMessageBox.information(self._parent, title, message,
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
lbl_message = QtWidgets.QLabel(message)
|
||||
lbl_message.setObjectName('message')
|
||||
|
||||
btn_yes = QtWidgets.QPushButton('Yes')
|
||||
btn_yes.clicked.connect(handles[0])
|
||||
|
||||
btn_no = QtWidgets.QPushButton('No')
|
||||
btn_no.clicked.connect(handles[1])
|
||||
|
||||
lay_buttons = QtWidgets.QHBoxLayout()
|
||||
lay_buttons.addWidget(btn_yes)
|
||||
lay_buttons.addWidget(btn_no)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addWidget(lbl_title)
|
||||
layout.addWidget(lbl_message)
|
||||
layout.addLayout(lay_buttons)
|
||||
self.setLayout(layout)
|
||||
|
||||
def retry(self, title, message, *handles):
|
||||
|
||||
lbl_title = QtWidgets.QLabel(title)
|
||||
lbl_title.setObjectName('title')
|
||||
|
||||
lbl_message = QtWidgets.QLabel(message)
|
||||
lbl_message.setObjectName('message')
|
||||
|
||||
btn_retry = QtWidgets.QPushButton('Retry')
|
||||
btn_retry.clicked.connect(handles[0])
|
||||
|
||||
btn_cancel = QtWidgets.QPushButton('Cancel')
|
||||
btn_cancel.clicked.connect(handles[1])
|
||||
|
||||
lay_buttons = QtWidgets.QHBoxLayout()
|
||||
lay_buttons.addWidget(btn_retry)
|
||||
lay_buttons.addWidget(btn_cancel)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addWidget(lbl_title)
|
||||
layout.addWidget(lbl_message)
|
||||
layout.addLayout(lay_buttons)
|
||||
self.setLayout(layout)
|
||||
|
||||
@@ -178,3 +178,35 @@ class RoundProgressBar(QtWidgets.QWidget):
|
||||
self._drawText(painter, inner_rect, inner_radius)
|
||||
|
||||
painter.end()
|
||||
|
||||
|
||||
class TransparentOverlay(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent, timeout=None, timeout_handle=None):
|
||||
|
||||
super().__init__(parent)
|
||||
self.setObjectName('TransparentOverlay')
|
||||
|
||||
rect = parent.rect()
|
||||
rect.adjust(50, 50, -50, -50)
|
||||
self.setGeometry(rect)
|
||||
|
||||
if timeout is not None:
|
||||
self._handle = timeout_handle
|
||||
self._timer = self.startTimer(timeout)
|
||||
|
||||
self.show()
|
||||
|
||||
def paintEvent(self, event):
|
||||
|
||||
opt = QtWidgets.QStyleOption()
|
||||
opt.initFrom(self)
|
||||
painter = QtGui.QPainter(self)
|
||||
self.style().drawPrimitive(QtWidgets.QStyle.PE_Widget, opt, painter,
|
||||
self)
|
||||
painter.end()
|
||||
|
||||
def timerEvent(self, event):
|
||||
|
||||
self.killTimer(self._timer)
|
||||
self._handle()
|
||||
|
||||
@@ -24,14 +24,14 @@ QPushButton {
|
||||
}
|
||||
|
||||
QPushButton:pressed {
|
||||
background-color: #66ffffff;
|
||||
background-color: #66eeeeee;
|
||||
}
|
||||
|
||||
/* Idle Screen */
|
||||
|
||||
QFrame#IdleMessage {
|
||||
background-image: url(photobooth/gui/Qt5Gui/images/arrow.png);
|
||||
background-repeat:; no-repeat;
|
||||
background-repeat: no-repeat;
|
||||
padding: 80px 400px 120px 80px;
|
||||
}
|
||||
|
||||
@@ -80,9 +80,13 @@ QFrame#GreeterMessage QLabel#message {
|
||||
/* Countdown Screen */
|
||||
|
||||
QFrame#CountdownMessage {
|
||||
background-color: #eeeeee;
|
||||
border-style: outset;
|
||||
border-width: 2px;
|
||||
border-radius: 30px;
|
||||
border-color: #eeeeee;
|
||||
margin: 20px;
|
||||
padding: 30px;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
/* Pose Screen */
|
||||
@@ -109,12 +113,48 @@ QFrame#WaitMessage QLabel {
|
||||
qproperty-alignment: AlignCenter;
|
||||
}
|
||||
|
||||
/* Picture Screen*/
|
||||
/* Picture Screen */
|
||||
|
||||
QFrame#PictureMessage {
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
/* Overlay message */
|
||||
|
||||
QWidget#TransparentOverlay {
|
||||
background-color: #aaeeeeee;
|
||||
border-style: outset;
|
||||
border-width: 2px;
|
||||
border-radius: 30px;
|
||||
border-color: #eeeeee;
|
||||
color: #333333;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
/* Postprocess message */
|
||||
|
||||
QWidget#PostprocessMessage QLabel {
|
||||
color: #333333;
|
||||
font-size: 110px;
|
||||
qproperty-alignment: AlignCenter;
|
||||
}
|
||||
|
||||
QWidget#PostprocessMessage QPushButton {
|
||||
color: #333333;
|
||||
border-color: #333333;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
QWidget#PostprocessMessage QPushButton:pressed {
|
||||
background-color: #66eeeeee;
|
||||
}
|
||||
|
||||
QWidget#PostprocessMessage QPushButton:disabled {
|
||||
background-color: #66eeeeee;
|
||||
color: #33eeeeee;
|
||||
border-color: #33eeeeee;
|
||||
}
|
||||
|
||||
/* Customizing settings */
|
||||
|
||||
QTabWidget::pane {
|
||||
|
||||
@@ -53,6 +53,8 @@ class PrinterPyQt5(Printer):
|
||||
self._printer.setOutputFileName('print_%d.pdf' % self._counter)
|
||||
self._counter += 1
|
||||
|
||||
logging.info('Printing picture')
|
||||
|
||||
img = ImageQt.ImageQt(picture)
|
||||
img = img.scaled(self._printer.pageRect().size(),
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
|
||||
Reference in New Issue
Block a user