"""
Provides all dialogs used in ALS GUI
"""
import logging
from pathlib import Path
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QDialog, QFileDialog, QMessageBox, QApplication
import als.model.data
from als import config
from als.code_utilities import log
from als.logic import Controller
from als.model.data import VERSION, DYNAMIC_DATA, WORKER_STATUS_BUSY
from generated.about_ui import Ui_AboutDialog
from generated.prefs_ui import Ui_PrefsDialog
from generated.save_wait_ui import Ui_SaveWaitDialog
_LOGGER = logging.getLogger(__name__)
[docs]class PreferencesDialog(QDialog):
"""
Our main preferences dialog box
"""
@log
def __init__(self, parent=None):
super().__init__(parent)
self._ui = Ui_PrefsDialog()
self._ui.setupUi(self)
self._ui.ln_scan_folder_path.setText(config.get_scan_folder_path())
self._ui.ln_work_folder_path.setText(config.get_work_folder_path())
self._ui.ln_web_server_port.setText(str(config.get_www_server_port_number()))
self._ui.spn_webpage_refresh_period.setValue(config.get_www_server_refresh_period())
self._ui.chk_debug_logs.setChecked(config.is_debug_log_on())
config_to_image_save_type_mapping = {
als.model.data.IMAGE_SAVE_TYPE_JPEG: self._ui.radioSaveJpeg,
als.model.data.IMAGE_SAVE_TYPE_PNG: self._ui.radioSavePng,
als.model.data.IMAGE_SAVE_TYPE_TIFF: self._ui.radioSaveTiff
}
config_to_image_save_type_mapping[config.get_image_save_format()].setChecked(True)
self._show_missing_folders()
@log
def _show_missing_folders(self):
"""
Draw a red border around text fields containing a path to a missing folder
"""
for ui_field in [self._ui.ln_work_folder_path, self._ui.ln_scan_folder_path]:
if not Path(ui_field.text()).is_dir():
ui_field.setStyleSheet("border: 1px solid orange")
else:
ui_field.setStyleSheet("border: 1px")
[docs] @log
@pyqtSlot()
def accept(self):
"""checks and stores user settings"""
config.set_scan_folder_path(self._ui.ln_scan_folder_path.text())
config.set_work_folder_path(self._ui.ln_work_folder_path.text())
web_server_port_number_str = self._ui.ln_web_server_port.text()
if web_server_port_number_str.isdigit() and 1024 <= int(web_server_port_number_str) <= 65535:
config.set_www_server_port_number(web_server_port_number_str)
else:
message = "Web server port number must be a number between 1024 and 65535"
error_box("Wrong value", message)
_LOGGER.error(f"Port number validation failed : {message}")
self._ui.ln_web_server_port.setFocus()
self._ui.ln_web_server_port.selectAll()
return
config.set_www_server_refresh_period(self._ui.spn_webpage_refresh_period.value())
config.set_debug_log(self._ui.chk_debug_logs.isChecked())
image_save_type_to_config_mapping = {
self._ui.radioSaveJpeg: als.model.data.IMAGE_SAVE_TYPE_JPEG,
self._ui.radioSavePng: als.model.data.IMAGE_SAVE_TYPE_PNG,
self._ui.radioSaveTiff: als.model.data.IMAGE_SAVE_TYPE_TIFF
}
for radio_button, image_save_type in image_save_type_to_config_mapping.items():
if radio_button.isChecked():
config.set_image_save_format(image_save_type)
break
PreferencesDialog._save_config()
super().accept()
[docs] @pyqtSlot(name="on_btn_browse_scan_clicked")
@log
def browse_scan(self):
"""Opens a folder dialog to choose scan folder"""
scan_folder_path = QFileDialog.getExistingDirectory(self,
_("Select scan folder"),
self._ui.ln_scan_folder_path.text())
if scan_folder_path:
self._ui.ln_scan_folder_path.setText(scan_folder_path)
self._show_missing_folders()
[docs] @pyqtSlot(name="on_btn_browse_work_clicked")
@log
def browse_work(self):
"""Opens a folder dialog to choose work folder"""
work_folder_path = QFileDialog.getExistingDirectory(self,
_("Select work folder"),
self._ui.ln_work_folder_path.text())
if work_folder_path:
self._ui.ln_work_folder_path.setText(work_folder_path)
self._show_missing_folders()
@staticmethod
@log
def _save_config():
try:
config.save()
except config.CouldNotSaveConfig as save_error:
error_box(save_error.message, f"Your settings could not be saved\n\nDetails : {save_error.details}")
[docs]class AboutDialog(QDialog):
"""
Our about dialog box
"""
@log
def __init__(self, parent=None):
super().__init__(parent)
self._ui = Ui_AboutDialog()
self._ui.setupUi(self)
self._ui.lblVersionValue.setText(VERSION)
[docs]class SaveWaitDialog(QDialog):
"""
Dialog shown while waiting for all pending image saves to complete
"""
@log
def __init__(self, controller: Controller, parent=None):
super().__init__(parent)
self._ui = Ui_SaveWaitDialog()
self._ui.setupUi(self)
self._controller = controller
self.update_display(_)
self._controller.add_model_observer(self)
[docs] @log
def update_display(self, _):
"""
Update display
"""
remaining_image_count = self.count_remaining_images()
self._ui.lbl_remaining_saves.setText(str(remaining_image_count))
if remaining_image_count == 0:
self._controller.remove_model_observer(self)
self.close()
[docs] @log
def count_remaining_images(self):
"""
Count images that still need to be saved.
We count 1 image to save for each image in the queues and each worker still Busy and also
take 'save every image' setting and web server status into account
:return: the number of images remaining to be saved
:rtype: int
"""
remaining_image_save_count = 0
for status in [
DYNAMIC_DATA.pre_processor_status,
DYNAMIC_DATA.stacker_status,
DYNAMIC_DATA.post_processor_status,
]:
if status == WORKER_STATUS_BUSY:
remaining_image_save_count += 1
for queue_size in [
DYNAMIC_DATA.pre_process_queue.qsize(),
DYNAMIC_DATA.stacker_queue.qsize(),
DYNAMIC_DATA.process_queue.qsize(),
]:
remaining_image_save_count += queue_size
additional_saves_per_image = [
self._controller.get_save_every_image(), DYNAMIC_DATA.web_server_is_running].count(True)
remaining_image_save_count *= 1 + additional_saves_per_image
remaining_image_save_count += 1 if DYNAMIC_DATA.saver_status == WORKER_STATUS_BUSY else 0
remaining_image_save_count += DYNAMIC_DATA.save_queue.qsize()
return remaining_image_save_count
[docs] @log
@pyqtSlot()
def on_btn_quit_clicked(self):
"""
Qt slot called when user clicks 'Discard unsaved images and quit'
"""
self.close()
[docs]@log
def question(title, message, default_yes: bool = True):
"""
Asks a question to user in a Qt MessageBox and return True/False as Yes/No
:param title: Title of the box
:param message: Message displayed in the box
:param default_yes: set 'yes' button as the default button
:type default_yes: bool
:return: True if user replies "Yes", False otherwise
"""
default_button = QMessageBox.Yes if default_yes else QMessageBox.No
return QMessageBox.Yes == QMessageBox.question(
QApplication.activeWindow(),
title,
message,
QMessageBox.Yes | QMessageBox.No,
default_button)
[docs]@log
def warning_box(title, message):
"""
Displays a waring Qt MessageBox
:param title: Title of the box
:param message: Message displayed in the box
:return: None
"""
message_box('Warning : ' + title, message, QMessageBox.Warning)
[docs]@log
def error_box(title, message):
"""
Displays an error Qt MessageBox
:param title: Title of the box
:param message: Message displayed in the box
:return: None
"""
message_box('Error : ' + title, message, QMessageBox.Critical)
[docs]@log
def message_box(title, message, icon=QMessageBox.Information):
"""
Displays a Qt MessageBox with custom icon : Info by default
:param title: Title of the box
:param message: Message displayed in the box
:param icon: The icon to show
:return: None
"""
box = QMessageBox()
box.setIcon(icon)
box.setWindowTitle(title)
box.setText(message)
box.exec()