Compare commits

...

9 Commits

4 changed files with 358 additions and 10 deletions

View 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>

View File

@ -0,0 +1,207 @@
# -*- 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.setup_ui()
self.setup_options()
self.editor_values = {}
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)
# 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)
self.editor_font_combobox.currentFontChanged.connect(self.on_editor_font_changed)
self.editor_size_spinbox.valueChanged.connect(self.on_editor_size_changed)
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"""
# Editor settings
self.settings.beginGroup('editor')
if self.settings.contains('font'):
value = self.settings.value('font')
self.editor_values['font'] = value
self.editor_font_combobox.setCurrentFont(QtGui.QFont(value))
if self.settings.contains('size'):
value = self.settings.value('size')
self.editor_values['size'] = int(value)
self.editor_size.setValue(int(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()
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()
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_editor_font_changed(self, font):
"""Set the new font if the font has changed"""
self.editor_values['font'] = font.family()
def on_editor_size_changed(self, size):
"""Set the new size if the size has changed"""
self.editor_values['size'] = size

View File

@ -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]

View File

@ -1,17 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
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
from ukatali.configuredialog import ConfigureDialog
from ukatali.lexer import ChordProLexer from ukatali.lexer import ChordProLexer
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.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 +172,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
@ -230,8 +237,7 @@ class MainWindow(QtWidgets.QMainWindow):
last_directory = QtCore.QSettings().value('files/last-directory') last_directory = QtCore.QSettings().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:
@ -257,8 +263,7 @@ class MainWindow(QtWidgets.QMainWindow):
last_directory = QtCore.QSettings().value('files/last-directory') last_directory = QtCore.QSettings().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:
@ -268,10 +273,74 @@ class MainWindow(QtWidgets.QMainWindow):
QtCore.QSettings().setValue('files/last-directory', str(Path(filename).parent)) QtCore.QSettings().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:
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():
value = settings.value(key)
if value:
options[key] = value
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) 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 QtCore.QSettings().value('files/last-directory'):
last_directory = QtCore.QSettings().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)
QtCore.QSettings().setValue('files/last-directory', str(filename.parent))
self.preview_view.page().printToPdf(str(filename), QtGui.QPageLayout(QtGui.QPageSize(QtGui.QPageSize.Letter),
QtGui.QPageLayout.Portrait,
QtCore.QMarginsF()))
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(QtPrintSupport.QPrinter(), self)
if print_dialog.exec() == QtWidgets.QDialog.Accepted:
self.preview_view.page().print(print_dialog.printer(), _on_print_finished)