From bb334971a29fcfba476e874995add8c1ec505b03 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, better output and PDF exporting - Add configure dialog - Dynamically create rendering options - Load and save settings - Pass on options to the renderer - Add editor settings to the configure dialog - Add exporting to PDF and printing - Add options for PDF export to the configure dialog --- resources/configuredialog.ui | 72 +++++++ src/ukatali/configuredialog.py | 338 +++++++++++++++++++++++++++++++++ src/ukatali/lexer.py | 2 +- src/ukatali/mainwindow.py | 125 ++++++++++-- src/ukatali/util.py | 25 +++ 5 files changed, 545 insertions(+), 17 deletions(-) create mode 100644 resources/configuredialog.ui create mode 100644 src/ukatali/configuredialog.py create mode 100644 src/ukatali/util.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..08bf2de --- /dev/null +++ b/src/ukatali/configuredialog.py @@ -0,0 +1,338 @@ +# -*- coding: utf-8 -*- +from functools import partial + +from PyQt5 import QtCore, QtGui, QtWidgets +from chordpro.renderers.html import get_options, get_option_groups + +from ukatali.util import coerce_bool, convert_units + +SUPPORTED_SIZES = { + QtGui.QPageSize.A4: 'A4', + QtGui.QPageSize.A5: 'A5', + QtGui.QPageSize.B5: 'B5', + QtGui.QPageSize.B6: 'B6', + QtGui.QPageSize.Letter: 'Letter', + QtGui.QPageSize.Legal: 'Legal', + QtGui.QPageSize.ExecutiveStandard: 'Executive' +} +SUPPORTED_UNITS = { + QtGui.QPageSize.Millimeter: 'mm', + QtGui.QPageSize.Point: 'pt', + QtGui.QPageSize.Inch: 'in' +} +SUPPORTED_ORIENTATIONS = { + 'Portrait': QtGui.QPageLayout.Portrait, + 'Landscape': QtGui.QPageLayout.Landscape +} + + +class ConfigureDialog(QtWidgets.QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.settings = QtCore.QSettings() + self.option_widgets = {} + self.option_values = {} + self.editor_values = {} + self.export_values = {} + 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_layout.setObjectName('editor_layout') + self.editor_font_combobox = QtWidgets.QFontComboBox(self.editor_page) + self.editor_font_combobox.setFontFilters(QtWidgets.QFontComboBox.MonospacedFonts) + self.editor_font_combobox.setCurrentFont(QtGui.QFont('monospace')) + self.editor_font_combobox.setObjectName('editor_font_combobox') + self.editor_layout.addRow('Font', self.editor_font_combobox) + self.editor_size_spinbox = QtWidgets.QSpinBox(self.editor_page) + self.editor_size_spinbox.setValue(10) + self.editor_size_spinbox.setObjectName('editor_size_spinbox') + self.editor_layout.addRow('Size', self.editor_size_spinbox) + # 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) + # Export settings + self.export_page = QtWidgets.QWidget() + self.export_page.setObjectName('export_page') + self.category_stack.addWidget(self.export_page) + self.export_layout = QtWidgets.QFormLayout(self.export_page) + self.export_layout.setObjectName('export_layout') + self.export_size_combobox = QtWidgets.QComboBox(self.export_page) + for idx, (size_id, size_name) in enumerate(SUPPORTED_SIZES.items()): + self.export_size_combobox.addItem(size_name) + self.export_size_combobox.setItemData(idx, size_id) + self.export_size_combobox.setCurrentIndex(0) + self.export_size_combobox.setObjectName('export_size_combobox') + self.export_layout.addRow('Page size', self.export_size_combobox) + self.export_orientation_layout = QtWidgets.QHBoxLayout() + self.export_orientation_layout.setObjectName('export_orientation_layout') + self.export_orientation_portrait_radio = QtWidgets.QRadioButton(self.export_page) + self.export_orientation_portrait_radio.setChecked(True) + self.export_orientation_portrait_radio.setObjectName('export_orientation_portrait_radio') + self.export_orientation_layout.addWidget(self.export_orientation_portrait_radio) + self.export_orientation_landscape_radio = QtWidgets.QRadioButton(self.export_page) + self.export_orientation_landscape_radio.setObjectName('export_orientation_landscape_radio') + self.export_orientation_layout.addWidget(self.export_orientation_landscape_radio) + self.export_layout.addRow('Orientation', self.export_orientation_layout) + self.export_unit_combobox = QtWidgets.QComboBox(self.export_page) + for idx, (unit_id, unit_name) in enumerate(SUPPORTED_UNITS.items()): + self.export_unit_combobox.addItem(unit_name) + self.export_unit_combobox.setItemData(idx, unit_id) + self.export_unit_combobox.setCurrentIndex(0) + self.export_unit_combobox.setObjectName('export_unit_combobox') + self.export_layout.addRow('Unit', self.export_unit_combobox) + self.export_margin_top_spinbox = QtWidgets.QDoubleSpinBox(self.export_page) + self.export_margin_top_spinbox.setValue(20) + self.export_margin_top_spinbox.setSuffix(self.export_unit_combobox.currentText()) + self.export_margin_top_spinbox.setObjectName('export_margin_top_spinbox') + self.export_layout.addRow('Top margin', self.export_margin_top_spinbox) + self.export_margin_left_spinbox = QtWidgets.QDoubleSpinBox(self.export_page) + self.export_margin_left_spinbox.setValue(20) + self.export_margin_left_spinbox.setSuffix(self.export_unit_combobox.currentText()) + self.export_margin_left_spinbox.setObjectName('export_margin_left_spinbox') + self.export_layout.addRow('Left margin', self.export_margin_left_spinbox) + self.export_margin_right_spinbox = QtWidgets.QDoubleSpinBox(self.export_page) + self.export_margin_right_spinbox.setValue(20) + self.export_margin_right_spinbox.setSuffix(self.export_unit_combobox.currentText()) + self.export_margin_right_spinbox.setObjectName('export_margin_right_spinbox') + self.export_layout.addRow('Right margin', self.export_margin_right_spinbox) + self.export_margin_bottom_spinbox = QtWidgets.QDoubleSpinBox(self.export_page) + self.export_margin_bottom_spinbox.setValue(20) + self.export_margin_bottom_spinbox.setSuffix(self.export_unit_combobox.currentText()) + self.export_margin_bottom_spinbox.setObjectName('export_margin_bottom_spinbox') + self.export_layout.addRow('Bottom margin', self.export_margin_bottom_spinbox) + # 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) + on_editor_font_changed = partial(self._set_editor_value, 'font') + self.editor_font_combobox.currentFontChanged.connect(on_editor_font_changed) + on_editor_size_changed = partial(self._set_editor_value, 'size') + self.editor_size_spinbox.valueChanged.connect(on_editor_size_changed) + on_export_size_changed = partial(self._set_export_value, 'page_size') + self.export_size_combobox.currentIndexChanged.connect(on_export_size_changed) + self.export_unit_combobox.currentIndexChanged.connect(self.on_export_unit_changed) + on_export_margin_left_changed = partial(self._set_export_value, 'margin_left') + self.export_margin_left_spinbox.valueChanged.connect(on_export_margin_left_changed) + on_export_margin_right_changed = partial(self._set_export_value, 'margin_right') + self.export_margin_right_spinbox.valueChanged.connect(on_export_margin_right_changed) + on_export_margin_top_changed = partial(self._set_export_value, 'margin_top') + self.export_margin_top_spinbox.valueChanged.connect(on_export_margin_top_changed) + on_export_margin_bottom_changed = partial(self._set_export_value, 'margin_bottom') + self.export_margin_bottom_spinbox.valueChanged.connect(on_export_margin_bottom_changed) + on_export_orientation_portait_clicked = partial(self._set_export_value, 'orientation') + self.export_orientation_portrait_radio.toggled.connect(on_export_orientation_portait_clicked) + + 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')) + self.category_list_widget.item(3).setText(_translate('ConfigureDialog', 'PDF')) + self.export_orientation_portrait_radio.setText(_translate('ConfigureDialog', 'Portrait')) + self.export_orientation_landscape_radio.setText(_translate('ConfigureDialog', 'Landscape')) + + def _set_option_value(self, name, value): + """Set an option value""" + self.option_values[name] = value + + def _set_editor_value(self, name, value): + """Set an editor value""" + self.editor_values[name] = value + + def _set_export_value(self, name, value): + """Set an export value""" + if name == 'page_size': + value = self.export_size_combobox.itemData(value) + elif name == 'units': + value = self.export_unit_combobox.itemData(value) + elif name == 'orientation': + value = QtGui.QPageLayout.Portrait if value else QtGui.QPageLayout.Landscape + self.export_values[name] = value + + def setup_options(self): + 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_option_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""" + # Editor settings + self.settings.beginGroup('editor') + if self.settings.contains('font'): + value = QtGui.QFont(self.settings.value('font')) + self.editor_values['font'] = value + self.editor_font_combobox.setCurrentFont(value) + if self.settings.contains('size'): + value = int(self.settings.value('size')) + self.editor_values['size'] = value + self.editor_size.setValue(value) + self.settings.endGroup() + # Renderer settings + self.settings.beginGroup('render') + 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 + self.settings.endGroup() + # PDF settings + self.settings.beginGroup('export') + if self.settings.contains('page_size'): + value = int(self.settings.value('page_size')) + self.export_values['page_size'] = value + self.export_size_combobox.setCurrentText(SUPPORTED_SIZES[value]) + if self.settings.contains('unit'): + value = int(self.settings.value('unit')) + self.export_values['unit'] = value + self.export_unit_combobox.setCurrentText(SUPPORTED_UNITS[value]) + if self.settings.contains('orientation'): + value = int(self.settings.value('orientation')) + self.export_values['orientation'] = value + self.export_orientation_portrait_radio.setChecked(value == QtGui.QPageLayout.Portrait) + margins = [ + ('margin_left', self.export_margin_left_spinbox), + ('margin_right', self.export_margin_right_spinbox), + ('margin_top', self.export_margin_top_spinbox), + ('margin_bottom', self.export_margin_bottom_spinbox), + ] + for name, spinbox in margins: + if self.settings.contains(name): + value = float(self.settings.value(name)) + self.export_values[name] = value + spinbox.setValue(value) + self.settings.endGroup() + + def save_settings(self): + """Save the settings""" + # Editor settings + self.settings.beginGroup('editor') + for name, value in self.editor_values.items(): + if isinstance(value, QtGui.QFont): + value = value.family() + self.settings.setValue(name, value) + self.settings.endGroup() + # Renderer settings + self.settings.beginGroup('render') + for name, value in self.option_values.items(): + if isinstance(value, QtGui.QFont): + value = value.family() + self.settings.setValue(name, value) + self.settings.endGroup() + # Export settings + self.settings.beginGroup('export') + for name, value in self.export_values.items(): + self.settings.setValue(name, value) + self.settings.endGroup() + + 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() + + def on_export_unit_changed(self, value): + self._set_export_value('unit', self.export_unit_combobox.itemData(value)) + new_suffix = self.export_unit_combobox.itemText(value) + for spinbox in [self.export_margin_bottom_spinbox, self.export_margin_left_spinbox, + self.export_margin_right_spinbox, self.export_margin_top_spinbox]: + old_suffix = spinbox.suffix() + spinbox.setSuffix(new_suffix) + spinbox.setValue(convert_units(spinbox.value(), old_suffix, new_suffix)) + if new_suffix == 'in': + spinbox.setSingleStep(0.1) + else: + spinbox.setSingleStep(1.0) diff --git a/src/ukatali/lexer.py b/src/ukatali/lexer.py index cba42ae..c692afb 100644 --- a/src/ukatali/lexer.py +++ b/src/ukatali/lexer.py @@ -127,7 +127,7 @@ class ChordProLexer(Qsci.QsciLexerCustom): def styleText(self, start, end): """Style the text""" - keywords = ['chorus'] + \ + keywords = ['verse', 'chorus', 'bridge'] + \ [d for t in KNOWN_DIRECTIVES for d in t] + \ ['start_of_' + vt for vt in KNOWN_VERSE_TYPES] + \ ['end_of_' + vt for vt in KNOWN_VERSE_TYPES] diff --git a/src/ukatali/mainwindow.py b/src/ukatali/mainwindow.py index c2ac673..22e0c05 100644 --- a/src/ukatali/mainwindow.py +++ b/src/ukatali/mainwindow.py @@ -1,17 +1,25 @@ # -*- coding: utf-8 -*- +import os from pathlib import Path -from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets, Qsci +from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets, Qsci, QtPrintSupport from chordpro import Song -from chordpro.renderers.html import render +from chordpro.renderers.html import render, get_options + +from ukatali.configuredialog import ConfigureDialog from ukatali.lexer import ChordProLexer +from ukatali.util import coerce_bool + +CHORDPRO_FILTERS = 'ChordPro files (*.txt *.cho *.chordpro *.chopro);;All files (*)' class MainWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.setup_ui() + self.settings = QtCore.QSettings() self.filename = None + self.configure_dialog = ConfigureDialog(self) def setup_ui(self): self.setObjectName('MainWindow') @@ -168,11 +176,14 @@ 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.export_action.triggered.connect(self.on_export_clicked) + self.print_action.triggered.connect(self.on_print_clicked) + self.configure_action.triggered.connect(self.on_configure_clicked) self.file_editor.textChanged.connect(self.on_text_changed) + self.preview_view.page().pdfPrintingFinished.connect(self.on_pdf_finished) def retranslate_ui(self): _translate = QtCore.QCoreApplication.translate @@ -226,12 +237,11 @@ class MainWindow(QtWidgets.QMainWindow): def on_open_clicked(self): """Open the file""" - if QtCore.QSettings().value('files/last-directory'): - last_directory = QtCore.QSettings().value('files/last-directory') + if self.settings.value('files/last-directory'): + last_directory = self.settings.value('files/last-directory') else: last_directory = '' - filename = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', last_directory, - filter='All files *') + filename = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', last_directory, filter=CHORDPRO_FILTERS) if isinstance(filename, tuple): filename = filename[0] if not filename: @@ -240,7 +250,7 @@ class MainWindow(QtWidgets.QMainWindow): self.filename = filename file_path = Path(filename) if file_path.exists(): - QtCore.QSettings().setValue('files/last-directory', str(file_path.parent)) + self.settings.setValue('files/last-directory', str(file_path.parent)) self.file_editor.setText(file_path.open().read()) def on_save_clicked(self): @@ -253,25 +263,108 @@ class MainWindow(QtWidgets.QMainWindow): def on_save_as_clicked(self): """Save the file""" - if QtCore.QSettings().value('files/last-directory'): - last_directory = QtCore.QSettings().value('files/last-directory') + if self.settings.value('files/last-directory'): + last_directory = self.settings.value('files/last-directory') else: last_directory = '' - filename = QtWidgets.QFileDialog.getSaveFileName(self, 'Save file', last_directory, - filter='All files *') + filename = QtWidgets.QFileDialog.getSaveFileName(self, 'Save file', last_directory, filter=CHORDPRO_FILTERS) if isinstance(filename, tuple): filename = filename[0] if not filename: return self.filename = filename - QtCore.QSettings().setValue('files/last-directory', str(Path(filename).parent)) + self.settings.setValue('files/last-directory', str(Path(filename).parent)) self.on_save_clicked() - def on_text_changed(self): - """Update the preview when the text changes""" + def on_configure_clicked(self): + """Show the configuration dialog""" + if self.configure_dialog.exec() == QtWidgets.QDialog.Accepted and self.file_editor.text(): + self.on_text_changed() + + def _get_render_options(self): + """Get all the render options from the settings""" + options = {} + self.settings.beginGroup('render') + 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) + elif details['type'] is bool: + value = coerce_bool(value) + options[name] = value + self.settings.endGroup() + return options + + def _render_song(self, extra_styles=None): + """Render the current song and return the HTML""" + options = self._get_render_options() text = self.file_editor.text() song = Song() song.parse(text) - html = render(song) + if song.metadata.get('copyright'): + extra_styles = extra_styles or '' + extra_styles += os.linesep + extra_styles += '@page {{ @bottom-left {{ content: "{}"; }} }}'.format(song.metadata.get('copyright')) + return render(song, options, extra_styles) + + def on_text_changed(self): + """Update the preview when the text changes""" + html = self._render_song() self.preview_view.setHtml(html) + + def on_export_clicked(self): + """Export the current song to PDF""" + if self.filename: + last_directory = str(Path(self.filename).with_suffix('.pdf')) + elif self.settings.value('files/last-directory'): + last_directory = self.settings.value('files/last-directory') + else: + last_directory = '' + filename = QtWidgets.QFileDialog.getSaveFileName(self, 'Export to PDF', last_directory, + filter='PDF files (.pdf)*') + if isinstance(filename, tuple): + filename = filename[0] + if not filename: + return + filename = Path(filename) + self.settings.setValue('files/last-directory', str(filename.parent)) + # Load the page layout from settings + self.settings.beginGroup('export') + page_size = QtGui.QPageSize(int(self.settings.value('page_size', QtGui.QPageSize.A4))) + orientation = int(self.settings.value('orientation', QtGui.QPageLayout.Portrait)) + unit = int(self.settings.value('unit', QtGui.QPageLayout.Millimeter)) + margin_left = float(self.settings.value('margin_left', 20)) + margin_right = float(self.settings.value('margin_right', 20)) + margin_top = float(self.settings.value('margin_top', 20)) + margin_bottom = float(self.settings.value('margin_bottom', 20)) + self.settings.endGroup() + margins = QtCore.QMarginsF(QtCore.QMargins(margin_left, margin_top, margin_right, margin_bottom)) + page_layout = QtGui.QPageLayout(page_size, orientation, margins, unit) + # Export to PDF + self.preview_view.page().printToPdf(str(filename), page_layout) + + def on_pdf_finished(self, filename, is_success): + """A slot to notify the user when the PDF is done""" + if is_success: + QtWidgets.QMessageBox.information(self, 'Export to PDF Successful', + 'Successfully exported to "{}"'.format(Path(filename).name)) + else: + QtWidgets.QMessageBox.critical(self, 'Error Exporting to PDF', 'There was an error while exporting to PDF') + + def on_print_clicked(self): + """Print the current song""" + + def _on_print_finished(is_success): + print(is_success) + + # print(QtPrintSupport.QPrinterInfo.defaultPrinterName()) + # default_printer = QtPrintSupport.QPrinterInfo.defaultPrinter() + # print(default_printer) + printer = QtPrintSupport.QPrinter() + print_dialog = QtPrintSupport.QPrintDialog(printer, self.preview_view.page().view()) + if print_dialog.exec() == QtWidgets.QDialog.Accepted: + self.preview_view.page().print(printer, _on_print_finished) diff --git a/src/ukatali/util.py b/src/ukatali/util.py new file mode 100644 index 0000000..15a62af --- /dev/null +++ b/src/ukatali/util.py @@ -0,0 +1,25 @@ +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) + + +def convert_units(value, old_unit, new_unit): + """Convert a value from one unit to another""" + if old_unit == 'in' and new_unit == 'mm': + return value * 25.4 + if old_unit == 'mm' and new_unit == 'in': + return value / 25.4 + if old_unit == 'in' and new_unit == 'pt': + return value * 72.0 + if old_unit == 'pt' and new_unit == 'in': + return value / 72.0 + if old_unit == 'mm' and new_unit == 'pt': + # Convert to in and then to pt + return (value / 25.4) * 72.0 + if old_unit == 'pt' and new_unit == 'mm': + # Convert to in and then to mm + return (value / 72.0) * 25.4 + return value