# -*- coding: utf-8 -*- from pathlib import Path from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets, Qsci, QtPrintSupport from igitar import Song from igitar.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.is_lyrics_mode = False self.configure_dialog = ConfigureDialog(self) def setup_ui(self): self.setObjectName('MainWindow') self.resize(800, 600) self.editor_widget = QtWidgets.QWidget(self) self.editor_widget.setObjectName('editor_widget') self.editor_layout = QtWidgets.QVBoxLayout(self.editor_widget) self.editor_layout.setContentsMargins(0, 0, 0, 0) self.editor_layout.setSpacing(0) self.editor_layout.setObjectName('editor_layout') self.file_editor = Qsci.QsciScintilla(self.editor_widget) self.file_editor.setFont(QtGui.QFont('monospace')) self.file_editor.setColor(QtWidgets.QApplication.palette().windowText().color()) self.file_editor.setCaretWidth(2) self.file_editor.setCaretForegroundColor(QtWidgets.QApplication.palette().windowText().color()) self.file_editor.setMarginsBackgroundColor(QtWidgets.QApplication.palette().window().color()) self.file_editor.setMarginsForegroundColor(QtWidgets.QApplication.palette().windowText().color()) self.file_editor.setMarginLineNumbers(1, True) self.file_editor.setMarginWidth(1, '000') self.file_editor.setObjectName('file_editor') self.chordpro_lexer = ChordProLexer(self.file_editor) self.file_editor.setLexer(self.chordpro_lexer) self.editor_layout.addWidget(self.file_editor) self.setCentralWidget(self.editor_widget) self.menubar = QtWidgets.QMenuBar(self) self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 28)) self.menubar.setObjectName('menubar') self.file_menu = QtWidgets.QMenu(self.menubar) self.file_menu.setObjectName('file_menu') self.edit_menu = QtWidgets.QMenu(self.menubar) self.edit_menu.setObjectName('edit_menu') self.settings_menu = QtWidgets.QMenu(self.menubar) self.settings_menu.setObjectName('settings_menu') self.help_menu = QtWidgets.QMenu(self.menubar) self.help_menu.setObjectName('help_menu') self.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(self) self.statusbar.setObjectName('statusbar') self.setStatusBar(self.statusbar) self.preview_dock = QtWidgets.QDockWidget(self) self.preview_dock.setObjectName('preview_dock') self.preview_dock_contents = QtWidgets.QWidget() self.preview_dock_contents.setObjectName('preview_dock_contents') self.preview_layout = QtWidgets.QVBoxLayout(self.preview_dock_contents) self.preview_layout.setContentsMargins(0, 0, 0, 0) self.preview_layout.setSpacing(0) self.preview_layout.setObjectName('preview_layout') self.preview_toolbar = QtWidgets.QToolBar(self.preview_dock_contents) self.preview_toolbar.setMovable(False) self.preview_toolbar.setObjectName('preview_toolbar') self.preview_layout.addWidget(self.preview_toolbar) self.preview_view = QtWebEngineWidgets.QWebEngineView(self.preview_dock_contents) self.preview_view.setUrl(QtCore.QUrl('about:blank')) self.preview_view.setObjectName('preview_view') self.preview_layout.addWidget(self.preview_view) self.preview_dock.setWidget(self.preview_dock_contents) self.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.preview_dock) self.toolbar = QtWidgets.QToolBar(self) self.toolbar.setMovable(False) self.toolbar.setObjectName('toolbar') self.addToolBar(QtCore.Qt.TopToolBarArea, self.toolbar) self.new_action = QtWidgets.QAction(self) icon = QtGui.QIcon.fromTheme('document-new') self.new_action.setIcon(icon) self.new_action.setObjectName('new_action') self.open_action = QtWidgets.QAction(self) icon = QtGui.QIcon.fromTheme('document-open') self.open_action.setIcon(icon) self.open_action.setObjectName('open_action') self.save_action = QtWidgets.QAction(self) icon = QtGui.QIcon.fromTheme('document-save') self.save_action.setIcon(icon) self.save_action.setObjectName('save_action') self.save_as_action = QtWidgets.QAction(self) icon = QtGui.QIcon.fromTheme('document-save-as') self.save_as_action.setIcon(icon) self.save_as_action.setObjectName('save_as_action') self.print_action = QtWidgets.QAction(self) icon = QtGui.QIcon.fromTheme('document-print') self.print_action.setIcon(icon) self.print_action.setObjectName('print_action') self.export_pdf_action = QtWidgets.QAction(self) icon = QtGui.QIcon.fromTheme('application-pdf') self.export_pdf_action.setIcon(icon) self.export_pdf_action.setObjectName('export_pdf_action') self.export_html_action = QtWidgets.QAction(self) icon = QtGui.QIcon.fromTheme('text-html') self.export_html_action.setIcon(icon) self.export_html_action.setObjectName('export_html_action') self.undo_action = QtWidgets.QAction(self) icon = QtGui.QIcon.fromTheme('edit-undo') self.undo_action.setIcon(icon) self.undo_action.setObjectName('undo_action') self.redo_action = QtWidgets.QAction(self) icon = QtGui.QIcon.fromTheme('edit-redo') self.redo_action.setIcon(icon) self.redo_action.setObjectName('redo_action') self.cut_action = QtWidgets.QAction(self) icon = QtGui.QIcon.fromTheme('edit-cut') self.cut_action.setIcon(icon) self.cut_action.setObjectName('cut_action') self.copy_action = QtWidgets.QAction(self) self.copy_action.setIcon(QtGui.QIcon.fromTheme('edit-copy')) self.copy_action.setObjectName('copy_action') self.paste_action = QtWidgets.QAction(self) self.paste_action.setIcon(QtGui.QIcon.fromTheme('edit-paste')) self.paste_action.setObjectName('paste_action') self.configure_action = QtWidgets.QAction(self) self.configure_action.setIcon(QtGui.QIcon.fromTheme('configure')) self.configure_action.setObjectName('configure_action') self.about_action = QtWidgets.QAction(self) self.about_action.setIcon(QtGui.QIcon.fromTheme('help-about')) self.about_action.setObjectName('about_action') self.exit_action = QtWidgets.QAction(self) self.exit_action.setIcon(QtGui.QIcon.fromTheme('application-exit')) self.exit_action.setObjectName('exit_action') self.hide_chords_action = QtWidgets.QAction(self) self.hide_chords_action.setCheckable(True) self.hide_chords_action.setChecked(False) self.hide_chords_action.setIcon(QtGui.QIcon.fromTheme('view-hidden')) self.hide_chords_action.setObjectName('hide_chords_action') self.file_menu.addAction(self.new_action) self.file_menu.addAction(self.open_action) self.file_menu.addAction(self.save_action) self.file_menu.addAction(self.save_as_action) self.file_menu.addSeparator() self.export_menu = self.file_menu.addMenu('') self.export_menu.setObjectName('export_menu') self.export_menu.addAction(self.export_pdf_action) self.export_menu.addAction(self.export_html_action) self.file_menu.addAction(self.print_action) self.file_menu.addSeparator() self.file_menu.addAction(self.exit_action) self.edit_menu.addAction(self.undo_action) self.edit_menu.addAction(self.redo_action) self.edit_menu.addSeparator() self.edit_menu.addAction(self.cut_action) self.edit_menu.addAction(self.copy_action) self.edit_menu.addAction(self.paste_action) self.settings_menu.addAction(self.configure_action) self.help_menu.addAction(self.about_action) self.menubar.addAction(self.file_menu.menuAction()) self.menubar.addAction(self.edit_menu.menuAction()) self.menubar.addAction(self.settings_menu.menuAction()) self.menubar.addAction(self.help_menu.menuAction()) self.toolbar.addAction(self.new_action) self.toolbar.addAction(self.open_action) self.toolbar.addAction(self.save_action) self.toolbar.addSeparator() self.toolbar.addAction(self.export_pdf_action) self.toolbar.addAction(self.print_action) self.toolbar.addSeparator() self.toolbar.addAction(self.undo_action) self.toolbar.addAction(self.redo_action) self.toolbar.addSeparator() self.toolbar.addAction(self.cut_action) self.toolbar.addAction(self.copy_action) self.toolbar.addAction(self.paste_action) self.toolbar.addSeparator() self.toolbar.addAction(self.configure_action) self.preview_toolbar.addAction(self.hide_chords_action) self.retranslate_ui() self.exit_action.triggered.connect(self.close) self.undo_action.triggered.connect(self.file_editor.undo) self.redo_action.triggered.connect(self.file_editor.redo) 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) self.new_action.triggered.connect(self.on_new_clicked) 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_pdf_action.triggered.connect(self.on_export_pdf_clicked) self.export_html_action.triggered.connect(self.on_export_html_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) self.hide_chords_action.triggered.connect(self.on_hide_chords_clicked) def retranslate_ui(self): _translate = QtCore.QCoreApplication.translate self.setWindowTitle(_translate('MainWindow', 'Untitled') + '[*] - Ukatali') self.file_menu.setTitle(_translate('MainWindow', '&File')) self.edit_menu.setTitle(_translate('MainWindow', '&Edit')) self.settings_menu.setTitle(_translate('MainWindow', '&Settings')) self.help_menu.setTitle(_translate('MainWindow', '&Help')) self.preview_dock.setWindowTitle(_translate('MainWindow', 'Preview')) self.toolbar.setWindowTitle(_translate('MainWindow', 'toolbar')) self.new_action.setText(_translate('MainWindow', '&New')) self.new_action.setToolTip(_translate('MainWindow', 'Create a new ChordPro file')) self.new_action.setShortcut(_translate('MainWindow', 'Ctrl+N')) self.open_action.setText(_translate('MainWindow', '&Open...')) self.open_action.setToolTip(_translate('MainWindow', 'Open an existing ChordPro file')) self.open_action.setShortcut(_translate('MainWindow', 'Ctrl+O')) self.save_action.setText(_translate('MainWindow', '&Save')) self.save_action.setToolTip(_translate('MainWindow', 'Save the current ChordPro file')) self.save_action.setShortcut(_translate('MainWindow', 'Ctrl+S')) self.save_as_action.setText(_translate('MainWindow', 'Save &As...')) self.save_as_action.setToolTip(_translate('MainWindow', 'Save the current file under a new name')) self.save_as_action.setShortcut(_translate('MainWindow', 'Ctrl+Shift+S')) self.print_action.setText(_translate('MainWindow', '&Print')) self.print_action.setToolTip(_translate('MainWindow', 'Print the current ChordPro file')) self.print_action.setShortcut(_translate('MainWindow', 'Ctrl+P')) self.export_menu.setTitle(_translate('MainWindow', '&Export')) self.export_pdf_action.setText(_translate('MainWindow', 'Export to &PDF...')) self.export_pdf_action.setToolTip(_translate('MainWindow', 'Export the current file as a PDF file')) self.export_pdf_action.setShortcut(_translate('MainWindow', 'Ctrl+E')) self.export_html_action.setText(_translate('MainWindow', 'Export to &HTML...')) self.export_html_action.setToolTip(_translate('MainWindow', 'Export the current file as an HTML file')) self.undo_action.setText(_translate('MainWindow', '&Undo')) self.undo_action.setToolTip(_translate('MainWindow', 'Undo the last edit')) self.undo_action.setShortcut(_translate('MainWindow', 'Ctrl+Z')) self.redo_action.setText(_translate('MainWindow', '&Redo')) self.redo_action.setToolTip(_translate('MainWindow', 'Redo the last undone action')) self.redo_action.setShortcut(_translate('MainWindow', 'Ctrl+Shift+Z')) self.cut_action.setText(_translate('MainWindow', 'Cu&t')) self.cut_action.setToolTip(_translate('MainWindow', 'Cut the currently selected text')) self.cut_action.setShortcut(_translate('MainWindow', 'Ctrl+X')) self.copy_action.setText(_translate('MainWindow', '&Copy')) self.copy_action.setToolTip(_translate('MainWindow', 'Copy the currently selected text to the clipboard')) self.copy_action.setShortcut(_translate('MainWindow', 'Ctrl+C')) self.paste_action.setText(_translate('MainWindow', '&Paste')) self.paste_action.setToolTip(_translate('MainWindow', 'Paste the current clipboard text into the editor')) self.paste_action.setShortcut(_translate('MainWindow', 'Ctrl+V')) self.configure_action.setText(_translate('MainWindow', '&Configure...')) self.configure_action.setToolTip(_translate('MainWindow', 'Configure Ukatali')) self.about_action.setText(_translate('MainWindow', '&About')) self.about_action.setToolTip(_translate('MainWindow', 'About Ukatali')) self.exit_action.setText(_translate('MainWindow', 'E&xit')) self.exit_action.setToolTip(_translate('MainWindow', 'Quit Ukatali')) self.exit_action.setShortcut(_translate('MainWindow', 'Alt+F4')) self.hide_chords_action.setText(_translate('MainWindow', 'Hide Chords')) def on_new_clicked(self): """Start a new file""" if self.file_editor.isModified() and QtWidgets.QMessageBox.question(self, 'Save file?', 'The current file is not saved, do you ' 'want to save it now?') \ == QtWidgets.QMessageBox.Yes: self.on_save_clicked() self.file_editor.setText('') self.file_editor.setModified(False) self.filename = '' self.setWindowTitle('Untitled[*] - Ukatali') self.setWindowModified(False) def on_open_clicked(self): """Open the file""" 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=CHORDPRO_FILTERS) if isinstance(filename, tuple): filename = filename[0] if not filename: return self.filename = filename file_path = Path(filename) if file_path.exists(): self.settings.setValue('files/last-directory', str(file_path.parent)) self.file_editor.setText(file_path.open().read()) self.setWindowTitle('{}[*] - Ukatali'.format(file_path.name)) self.setWindowModified(False) def on_save_clicked(self): """Save the file""" if not self.filename: self.on_save_as_clicked() else: with open(self.filename, 'w') as fd: fd.write(self.file_editor.text()) self.setWindowTitle('{}[*] - Ukatali'.format(Path(self.filename).name)) self.setWindowModified(False) def on_save_as_clicked(self): """Save the file""" 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=CHORDPRO_FILTERS) if isinstance(filename, tuple): filename = filename[0] if not filename: return self.filename = filename self.settings.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 and self.file_editor.text(): self.on_text_changed() def _get_render_options(self, is_lyrics_mode=False): """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 if is_lyrics_mode and 'indent' in name: options[name] = 0 elif is_lyrics_mode and 'is_hidden' in name: options[name] = True 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(self.is_lyrics_mode) text = self.file_editor.text() song = Song() try: song.parse(text) return render(song, options, extra_styles) except Exception: return None def on_text_changed(self): """Update the preview when the text changes""" html = self._render_song() if html: self.preview_view.setHtml(html) def on_export_pdf_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(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_export_html_clicked(self): """Export the current song to HTML""" if self.filename: last_directory = str(Path(self.filename).with_suffix('.html')) 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 HTML', last_directory, filter='HTML files (.html)*') if isinstance(filename, tuple): filename = filename[0] if not filename: return filename = Path(filename) self.settings.setValue('files/last-directory', str(filename.parent)) # Export to HTML try: with filename.open('w') as html_file: html = self._render_song() if html: html_file.write(html) QtWidgets.QMessageBox.information(self, 'Export to HTML Successful', 'Successfully exported to "{}"'.format(filename.name)) else: QtWidgets.QMessageBox.critical(self, 'Error Exporting to HTML', 'There was an error while exporting to HTML') except Exception as e: QtWidgets.QMessageBox.critical(self, 'Error Exporting to HTML', 'There was an error while exporting to HTML:\n{e}'.format(e=e)) 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_finished(self, is_success): print(is_success) def on_print_clicked(self): """Print the current song""" printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution) print_dialog = QtPrintSupport.QPrintDialog(printer, self.preview_view.page().view()) if print_dialog.exec() == QtWidgets.QDialog.Accepted: self.preview_view.page().print(printer, self.on_print_finished) def on_hide_chords_clicked(self, is_checked): """Hide the chords in the preview window""" self.is_lyrics_mode = is_checked if self.file_editor.text(): self.on_text_changed()