From f3fe56250766965a0e54cafffdb2b4b7bca886a5 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 2 Aug 2021 14:06:01 -0700 Subject: [PATCH] Add a configuration dialog - Add configure dialog - Dynamically create rendering options - Load and save settings - Pass on options to the renderer --- resources/configuredialog.ui | 72 ++++++++++++++ src/ukatali/configuredialog.py | 165 +++++++++++++++++++++++++++++++++ src/ukatali/mainwindow.py | 21 ++++- 3 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 resources/configuredialog.ui create mode 100644 src/ukatali/configuredialog.py diff --git a/resources/configuredialog.ui b/resources/configuredialog.ui new file mode 100644 index 0000000..c96f327 --- /dev/null +++ b/resources/configuredialog.ui @@ -0,0 +1,72 @@ + + + ConfigureDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + Render Options + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ConfigureDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ConfigureDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/ukatali/configuredialog.py b/src/ukatali/configuredialog.py new file mode 100644 index 0000000..d8ffa7f --- /dev/null +++ b/src/ukatali/configuredialog.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +from functools import partial + +from PyQt5 import QtCore, QtGui, QtWidgets + +from chordpro.renderers.html import get_options, get_option_groups + + +def _coerce_bool(value): + """Coerce a value to be a boolean""" + if isinstance(value, str): + return value[0].lower() in ['t', 'y', '1'] + else: + return bool(value) + + +class ConfigureDialog(QtWidgets.QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.settings = QtCore.QSettings() + self.settings.beginGroup('render') + self.setup_ui() + self.setup_options() + + def setup_ui(self): + self.setObjectName('ConfigureDialog') + self.resize(600, 400) + self.configure_layout = QtWidgets.QVBoxLayout(self) + self.configure_layout.setObjectName("configure_layout") + self.category_layout = QtWidgets.QHBoxLayout() + self.category_layout.setObjectName('category_layout') + self.configure_layout.addLayout(self.category_layout) + self.category_list_widget = QtWidgets.QListWidget(self) + self.category_list_widget.setFixedWidth(100) + self.category_list_widget.addItems(['', '', '']) + self.category_list_widget.setObjectName('category_list_widget') + self.category_layout.addWidget(self.category_list_widget) + self.category_stack = QtWidgets.QStackedWidget(self) + self.category_stack.setObjectName('category_stack') + self.category_layout.addWidget(self.category_stack) + # General settings + self.general_page = QtWidgets.QWidget() + self.general_page.setObjectName('general_page') + self.category_stack.addWidget(self.general_page) + # Editor settings + self.editor_page = QtWidgets.QWidget() + self.editor_page.setObjectName('editor_page') + self.category_stack.addWidget(self.editor_page) + self.editor_layout = QtWidgets.QFormLayout(self.editor_page) + self.editor_font_combobox = QtWidgets.QFontComboBox(self.editor_page) + self.editor_layout.addRow('Font', self.editor_font_combobox) + # Renderer settings + self.renderer_page = QtWidgets.QWidget() + self.renderer_page.setObjectName('renderer_page') + self.category_stack.addWidget(self.renderer_page) + self.render_layout = QtWidgets.QVBoxLayout(self.renderer_page) + self.render_options_combobox = QtWidgets.QComboBox(self.renderer_page) + self.render_options_combobox.setObjectName("render_options_combobox") + self.render_layout.addWidget(self.render_options_combobox) + self.render_options_stack = QtWidgets.QStackedWidget(self) + self.render_options_stack.setObjectName("render_options_stack") + self.render_layout.addWidget(self.render_options_stack) + # Buttons + self.button_box = QtWidgets.QDialogButtonBox(self) + self.button_box.setOrientation(QtCore.Qt.Horizontal) + self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | + QtWidgets.QDialogButtonBox.Ok) + self.button_box.setObjectName("button_box") + self.configure_layout.addWidget(self.button_box) + + self.retranslate_ui() + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + self.category_list_widget.currentRowChanged.connect(self.category_stack.setCurrentIndex) + self.render_options_combobox.activated.connect(self.render_options_stack.setCurrentIndex) + + def retranslate_ui(self): + _translate = QtCore.QCoreApplication.translate + self.setWindowTitle(_translate("ConfigureDialog", "Configure")) + self.category_list_widget.item(0).setText(_translate('ConfigureDialog', 'General')) + self.category_list_widget.item(1).setText(_translate('ConfigureDialog', 'Editor')) + self.category_list_widget.item(2).setText(_translate('ConfigureDialog', 'Render')) + + def _set_value(self, name, value): + """Set an option value""" + self.option_values[name] = value + + def setup_options(self): + self.option_widgets = {} + self.option_values = {} + for group in get_option_groups(): + pretty_group = group.replace('_', ' ').title() + self.render_options_combobox.addItem(pretty_group) + page_widget = QtWidgets.QWidget() + page_layout = QtWidgets.QFormLayout(page_widget) + for name, details in get_options(group).items(): + pretty_name = name.replace(group, '').replace('_', ' ').title() + self.option_values[name] = details['default'] + widget = None + set_value = partial(self._set_value, name) + if details["type"] is int: + widget = QtWidgets.QSpinBox(page_widget) + if details['default']: + widget.setValue(details['default']) + widget.valueChanged.connect(set_value) + elif details["type"] is bool: + widget = QtWidgets.QCheckBox(page_widget) + widget.setChecked(_coerce_bool(details['default'])) + widget.toggled.connect(set_value) + elif 'font' in name: + widget = QtWidgets.QFontComboBox(page_widget) + if details['default']: + widget.setFont(QtGui.QFont(details['default'])) + widget.currentFontChanged.connect(set_value) + else: + widget = QtWidgets.QLineEdit(page_widget) + if details['default']: + widget.setText(details['default']) + widget.textChanged.connect(set_value) + if widget: + widget.setObjectName(name) + self.option_widgets[name] = widget + page_layout.addRow(pretty_name, widget) + if details['description']: + label = QtWidgets.QLabel(page_widget) + label.setText(details['description']) + page_layout.addRow('', label) + self.render_options_stack.addWidget(page_widget) + + def load_settings(self): + """Load the settings""" + for name, details in get_options().items(): + if self.settings.contains(name): + value = self.settings.value(name) + if value is None: + continue + if details["type"] is int: + value = int(value) + self.option_widgets[name].setValue(value) + elif details['type'] is bool: + value = _coerce_bool(value) + self.option_widgets[name].setChecked(value) + elif 'font' in name: + value = QtGui.QFont(value) + self.option_widgets[name].setCurrentFont(value) + else: + self.option_widgets[name].setText(value) + self.option_values[name] = value + + def save_settings(self): + """Save the settings""" + for name, value in self.option_values.items(): + if isinstance(value, QtGui.QFont): + value = value.family() + self.settings.setValue(name, value) + + def exec(self): + """Execute the dialog""" + self.load_settings() + return super().exec() + + def accept(self): + """The user clicked the "OK" button""" + self.save_settings() + return super().accept() diff --git a/src/ukatali/mainwindow.py b/src/ukatali/mainwindow.py index c2ac673..c9fe6b0 100644 --- a/src/ukatali/mainwindow.py +++ b/src/ukatali/mainwindow.py @@ -4,6 +4,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets, Qsci from chordpro import Song from chordpro.renderers.html import render +from ukatali.configuredialog import ConfigureDialog from ukatali.lexer import ChordProLexer @@ -12,6 +13,7 @@ class MainWindow(QtWidgets.QMainWindow): super().__init__(parent) self.setup_ui() self.filename = None + self.configure_dialog = ConfigureDialog(self) def setup_ui(self): self.setObjectName('MainWindow') @@ -168,10 +170,10 @@ class MainWindow(QtWidgets.QMainWindow): self.cut_action.triggered.connect(self.file_editor.cut) self.copy_action.triggered.connect(self.file_editor.copy) self.paste_action.triggered.connect(self.file_editor.paste) - # QtCore.QMetaObject.connectSlotsByName(self) self.open_action.triggered.connect(self.on_open_clicked) self.save_action.triggered.connect(self.on_save_clicked) self.save_as_action.triggered.connect(self.on_save_as_clicked) + self.configure_action.triggered.connect(self.on_configure_clicked) self.file_editor.textChanged.connect(self.on_text_changed) def retranslate_ui(self): @@ -268,10 +270,25 @@ class MainWindow(QtWidgets.QMainWindow): QtCore.QSettings().setValue('files/last-directory', str(Path(filename).parent)) self.on_save_clicked() + def on_configure_clicked(self): + """Show the configuration dialog""" + if self.configure_dialog.exec() == QtWidgets.QDialog.Accepted: + self.on_text_changed() + + def _get_render_options(self): + """Get all the render options from the settings""" + options = {} + settings = QtCore.QSettings() + settings.beginGroup('render') + for key in settings.allKeys(): + options[key] = settings.value(key) + return options + def on_text_changed(self): """Update the preview when the text changes""" + options = self._get_render_options() text = self.file_editor.text() song = Song() song.parse(text) - html = render(song) + html = render(song, options) self.preview_view.setHtml(html)