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
This commit is contained in:
parent
29f166165a
commit
bb334971a2
72
resources/configuredialog.ui
Normal file
72
resources/configuredialog.ui
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ConfigureDialog</class>
|
||||||
|
<widget class="QDialog" name="ConfigureDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>300</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="configureLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="renderOptionsGroupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Render Options</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="renderOptionsLayout"/>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>ConfigureDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>ConfigureDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
338
src/ukatali/configuredialog.py
Normal file
338
src/ukatali/configuredialog.py
Normal file
@ -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)
|
@ -127,7 +127,7 @@ class ChordProLexer(Qsci.QsciLexerCustom):
|
|||||||
def styleText(self, start, end):
|
def styleText(self, start, end):
|
||||||
"""Style the text"""
|
"""Style the text"""
|
||||||
|
|
||||||
keywords = ['chorus'] + \
|
keywords = ['verse', 'chorus', 'bridge'] + \
|
||||||
[d for t in KNOWN_DIRECTIVES for d in t] + \
|
[d for t in KNOWN_DIRECTIVES for d in t] + \
|
||||||
['start_of_' + vt for vt in KNOWN_VERSE_TYPES] + \
|
['start_of_' + vt for vt in KNOWN_VERSE_TYPES] + \
|
||||||
['end_of_' + vt for vt in KNOWN_VERSE_TYPES]
|
['end_of_' + vt for vt in KNOWN_VERSE_TYPES]
|
||||||
|
@ -1,17 +1,25 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
from pathlib import Path
|
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 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.lexer import ChordProLexer
|
||||||
|
from ukatali.util import coerce_bool
|
||||||
|
|
||||||
|
CHORDPRO_FILTERS = 'ChordPro files (*.txt *.cho *.chordpro *.chopro);;All files (*)'
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QtWidgets.QMainWindow):
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
self.settings = QtCore.QSettings()
|
||||||
self.filename = None
|
self.filename = None
|
||||||
|
self.configure_dialog = ConfigureDialog(self)
|
||||||
|
|
||||||
def setup_ui(self):
|
def setup_ui(self):
|
||||||
self.setObjectName('MainWindow')
|
self.setObjectName('MainWindow')
|
||||||
@ -168,11 +176,14 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.cut_action.triggered.connect(self.file_editor.cut)
|
self.cut_action.triggered.connect(self.file_editor.cut)
|
||||||
self.copy_action.triggered.connect(self.file_editor.copy)
|
self.copy_action.triggered.connect(self.file_editor.copy)
|
||||||
self.paste_action.triggered.connect(self.file_editor.paste)
|
self.paste_action.triggered.connect(self.file_editor.paste)
|
||||||
# QtCore.QMetaObject.connectSlotsByName(self)
|
|
||||||
self.open_action.triggered.connect(self.on_open_clicked)
|
self.open_action.triggered.connect(self.on_open_clicked)
|
||||||
self.save_action.triggered.connect(self.on_save_clicked)
|
self.save_action.triggered.connect(self.on_save_clicked)
|
||||||
self.save_as_action.triggered.connect(self.on_save_as_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.file_editor.textChanged.connect(self.on_text_changed)
|
||||||
|
self.preview_view.page().pdfPrintingFinished.connect(self.on_pdf_finished)
|
||||||
|
|
||||||
def retranslate_ui(self):
|
def retranslate_ui(self):
|
||||||
_translate = QtCore.QCoreApplication.translate
|
_translate = QtCore.QCoreApplication.translate
|
||||||
@ -226,12 +237,11 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
def on_open_clicked(self):
|
def on_open_clicked(self):
|
||||||
"""Open the file"""
|
"""Open the file"""
|
||||||
if QtCore.QSettings().value('files/last-directory'):
|
if self.settings.value('files/last-directory'):
|
||||||
last_directory = QtCore.QSettings().value('files/last-directory')
|
last_directory = self.settings.value('files/last-directory')
|
||||||
else:
|
else:
|
||||||
last_directory = ''
|
last_directory = ''
|
||||||
filename = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', last_directory,
|
filename = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', last_directory, filter=CHORDPRO_FILTERS)
|
||||||
filter='All files *')
|
|
||||||
if isinstance(filename, tuple):
|
if isinstance(filename, tuple):
|
||||||
filename = filename[0]
|
filename = filename[0]
|
||||||
if not filename:
|
if not filename:
|
||||||
@ -240,7 +250,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.filename = filename
|
self.filename = filename
|
||||||
file_path = Path(filename)
|
file_path = Path(filename)
|
||||||
if file_path.exists():
|
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())
|
self.file_editor.setText(file_path.open().read())
|
||||||
|
|
||||||
def on_save_clicked(self):
|
def on_save_clicked(self):
|
||||||
@ -253,25 +263,108 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
def on_save_as_clicked(self):
|
def on_save_as_clicked(self):
|
||||||
"""Save the file"""
|
"""Save the file"""
|
||||||
if QtCore.QSettings().value('files/last-directory'):
|
if self.settings.value('files/last-directory'):
|
||||||
last_directory = QtCore.QSettings().value('files/last-directory')
|
last_directory = self.settings.value('files/last-directory')
|
||||||
else:
|
else:
|
||||||
last_directory = ''
|
last_directory = ''
|
||||||
filename = QtWidgets.QFileDialog.getSaveFileName(self, 'Save file', last_directory,
|
filename = QtWidgets.QFileDialog.getSaveFileName(self, 'Save file', last_directory, filter=CHORDPRO_FILTERS)
|
||||||
filter='All files *')
|
|
||||||
if isinstance(filename, tuple):
|
if isinstance(filename, tuple):
|
||||||
filename = filename[0]
|
filename = filename[0]
|
||||||
if not filename:
|
if not filename:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.filename = filename
|
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()
|
self.on_save_clicked()
|
||||||
|
|
||||||
def on_text_changed(self):
|
def on_configure_clicked(self):
|
||||||
"""Update the preview when the text changes"""
|
"""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()
|
text = self.file_editor.text()
|
||||||
song = Song()
|
song = Song()
|
||||||
song.parse(text)
|
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)
|
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)
|
||||||
|
25
src/ukatali/util.py
Normal file
25
src/ukatali/util.py
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user