From b9666a7503e82303208a459691b53cf9836c168d Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 30 Jul 2021 14:33:37 -0700 Subject: [PATCH] Add syntax highlighting and dynamic previewing --- .editorconfig | 17 ++ setup.cfg | 18 ++ src/ukatali/lexer.py | 168 +++++++++++++++ src/ukatali/mainwindow.py | 435 +++++++++++++++++++++----------------- 4 files changed, 442 insertions(+), 196 deletions(-) create mode 100644 .editorconfig create mode 100644 setup.cfg create mode 100644 src/ukatali/lexer.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b891ab2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{html,js,json,py,yaml,yml}] +indent_style = space + +[*.py] +indent_size = 4 +max_line_length = 120 + +[*.{html,js,json,yaml,yml}] +indent_size = 2 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..000c625 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,18 @@ +[pep8] +exclude=resources.py,vlc.py +max-line-length = 120 +ignore = E402,E722,W503,W504 + +[flake8] +exclude=resources.py,vlc.py +max-line-length = 120 +ignore = E402,W503,W504 + +[pycodestyle] +exclude = resources.py,vlc.py +max-line-length = 120 +# Ignoring: +# E402 module level import not at top of file +# W503 line break before binary operator +# W504 line break after binary operator +ignore = E402,W503,W504 diff --git a/src/ukatali/lexer.py b/src/ukatali/lexer.py new file mode 100644 index 0000000..cba42ae --- /dev/null +++ b/src/ukatali/lexer.py @@ -0,0 +1,168 @@ +import re + +from PyQt5 import QtCore, QtGui, QtWidgets, Qsci +from chordpro.constants import KNOWN_DIRECTIVES, KNOWN_VERSE_TYPES + +PALETTE_ROLES = { + 'window': QtGui.QPalette.WindowText, + 'background': QtGui.QPalette.Background, + 'windowtext': QtGui.QPalette.WindowText, + 'foreground': QtGui.QPalette.Foreground, + 'base': QtGui.QPalette.Base, + 'alternatebase': QtGui.QPalette.AlternateBase, + 'tooltipbase': QtGui.QPalette.ToolTipBase, + 'tooltiptext': QtGui.QPalette.ToolTipText, + 'placeholderText': QtGui.QPalette.PlaceholderText, + 'text': QtGui.QPalette.Text, + 'button': QtGui.QPalette.Button, + 'buttontext': QtGui.QPalette.ButtonText, + 'brighttext': QtGui.QPalette.BrightText, + 'light': QtGui.QPalette.Light, + 'midlight': QtGui.QPalette.Midlight, + 'dark': QtGui.QPalette.Dark, + 'mid': QtGui.QPalette.Mid, + 'shadow': QtGui.QPalette.Shadow, + 'highlight': QtGui.QPalette.Highlight, + 'highlightedtext': QtGui.QPalette.HighlightedText, + 'link': QtGui.QPalette.Link, + 'linkvisited': QtGui.QPalette.LinkVisited +} + + +class ChordProStyle(object): + Default = 0 + Keyword = 1 + Identifier = 2 + Chord = 3 + + @staticmethod + def to_string(style): + """Return the name for the style""" + return { + ChordProStyle.Default: 'Default', + ChordProStyle.Keyword: 'Keyword', + ChordProStyle.Identifier: 'Identifier', + ChordProStyle.Chord: 'Chord' + }.get(style) + + +class ChordProLexer(Qsci.QsciLexerCustom): + + def __init__(self, parent): + super().__init__(parent) + self.settings = QtCore.QSettings() + self.settings.beginGroup('syntax') + self.init_style() + + def _get_color(self, name, default): + """Get a QColor object from the settings""" + color = self.settings.value(name, default) + if isinstance(color, str): + if color.startswith('palette:'): + palette_role = PALETTE_ROLES[color.split(':', 1)[1].lower()] + color = QtWidgets.QApplication.palette().color(palette_role) + else: + color = QtGui.QColor(color) + return color + + def _get_font(self, name): + """Get a QFont object from the settings""" + font = self.settings.value(name, None) + if not font: + font = QtGui.QFont('monospace') + elif isinstance(font, str): + font = QtGui.QFont.fromString(font) + return font + + def init_style(self): + """Set up the colours and fonts""" + # Defaults + self.setDefaultColor(self._get_color('default-foreground', 'palette:windowtext')) + self.setDefaultPaper(self._get_color('default-background', 'palette:base')) + self.setDefaultFont(self._get_font('default-font')) + self.setColor(self._get_color('default-foreground', 'palette:windowtext'), + ChordProStyle.Default) + self.setPaper(self._get_color('default-background', 'palette:base'), + ChordProStyle.Default) + self.setFont(self._get_font('default-font'), ChordProStyle.Default) + # Keywords + kwfg = self._get_color('keywords-foreground', '#009fe3') + if kwfg: + self.setColor(kwfg, ChordProStyle.Keyword) + kwbg = self._get_color('keywords-background', None) + if kwbg: + self.setPaper(kwbg, ChordProStyle.Keyword) + kwft = self._get_font('keywords-font') + if kwft: + self.setFont(kwft, ChordProStyle.Keyword) + # Identifiers + idfg = self._get_color('identifiers-foreground', '#fdadff') + if idfg: + self.setColor(idfg, ChordProStyle.Identifier) + idbg = self._get_color('identifiers-background', None) + if idbg: + self.setPaper(idbg, ChordProStyle.Identifier) + idft = self._get_font('identifiers-font') + if idft: + self.setFont(idft, ChordProStyle.Identifier) + # Chords + chfg = self._get_color('chords-foreground', '#73ff6c') + if chfg: + self.setColor(chfg, ChordProStyle.Chord) + chbg = self._get_color('chords-background', None) + if chbg: + self.setPaper(chbg, ChordProStyle.Chord) + chft = self._get_font('chords-font') + if chft: + self.setFont(chft, ChordProStyle.Chord) + + def language(self): + """Return the language name""" + return "ChordPro" + + def description(self, style): + """Return the name for the style number""" + return ChordProStyle.to_string(style) + + def styleText(self, start, end): + """Style the text""" + + keywords = ['chorus'] + \ + [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] + is_directive = False + is_identifier = False + is_chord = False + pattern = re.compile(r'\s+|\w+|\W') + + self.startStyling(start) + text = self.parent().text()[start:end] + + token_list = [(token, len(bytearray(token, "utf-8"))) for token in pattern.findall(text)] + + for idx, token in enumerate(token_list): + if token[0] == '{': + is_directive = True + self.setStyling(token[1], ChordProStyle.Keyword) + elif is_directive and token[0] in keywords: + self.setStyling(token[1], ChordProStyle.Keyword) + elif is_directive and token[0] == ':': + is_identifier = True + self.setStyling(token[1], ChordProStyle.Keyword) + elif is_directive and token[0] == '}': + is_directive = False + is_identifier = False + self.setStyling(token[1], ChordProStyle.Keyword) + elif is_identifier: + self.setStyling(token[1], ChordProStyle.Identifier) + elif token[0] == '[': + is_chord = True + self.setStyling(token[1], ChordProStyle.Chord) + elif is_chord and token[0] == ']': + is_chord = False + self.setStyling(token[1], ChordProStyle.Chord) + elif is_chord: + self.setStyling(token[1], ChordProStyle.Chord) + else: + self.setStyling(token[1], ChordProStyle.Default) diff --git a/src/ukatali/mainwindow.py b/src/ukatali/mainwindow.py index 02e23b7..c2ac673 100644 --- a/src/ukatali/mainwindow.py +++ b/src/ukatali/mainwindow.py @@ -2,219 +2,227 @@ from pathlib import Path from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets, Qsci -OPEN_COUNT = 0 +from chordpro import Song +from chordpro.renderers.html import render +from ukatali.lexer import ChordProLexer class MainWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.setup_ui() + self.filename = None def setup_ui(self): - self.setObjectName("MainWindow") + self.setObjectName('MainWindow') self.resize(800, 600) - self.editorWidget = QtWidgets.QWidget(self) - self.editorWidget.setObjectName("editorWidget") - self.editorLayout = QtWidgets.QVBoxLayout(self.editorWidget) - self.editorLayout.setContentsMargins(0, 0, 0, 0) - self.editorLayout.setSpacing(0) - self.editorLayout.setObjectName("editorLayout") - self.fileEditor = Qsci.QsciScintilla(self.editorWidget) - self.fileEditor.setFont(QtGui.QFont("monospace")) - self.fileEditor.setColor(QtWidgets.QApplication.palette().windowText().color()) - self.fileEditor.setCaretWidth(2) - self.fileEditor.setCaretForegroundColor(QtWidgets.QApplication.palette().windowText().color()) - self.fileEditor.setMarginsBackgroundColor(QtWidgets.QApplication.palette().window().color()) - self.fileEditor.setMarginsForegroundColor(QtWidgets.QApplication.palette().windowText().color()) - self.fileEditor.setMarginLineNumbers(1, True) - self.fileEditor.setMarginWidth(1, "000") - self.fileEditor.setObjectName("fileEditor") - self.editorLayout.addWidget(self.fileEditor) - self.setCentralWidget(self.editorWidget) + 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.fileMenu = QtWidgets.QMenu(self.menubar) - self.fileMenu.setObjectName("fileMenu") - self.editMenu = QtWidgets.QMenu(self.menubar) - self.editMenu.setObjectName("editMenu") - self.settingsMenu = QtWidgets.QMenu(self.menubar) - self.settingsMenu.setObjectName("settingsMenu") - self.helpMenu = QtWidgets.QMenu(self.menubar) - self.helpMenu.setObjectName("helpMenu") + 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.statusbar.setObjectName('statusbar') self.setStatusBar(self.statusbar) - self.previewDock = QtWidgets.QDockWidget(self) - self.previewDock.setObjectName("previewDock") - self.previewDockContents = QtWidgets.QWidget() - self.previewDockContents.setObjectName("previewDockContents") - self.previewLayout = QtWidgets.QVBoxLayout(self.previewDockContents) - self.previewLayout.setContentsMargins(0, 0, 0, 0) - self.previewLayout.setSpacing(0) - self.previewLayout.setObjectName("previewLayout") - self.previewView = QtWebEngineWidgets.QWebEngineView(self.previewDockContents) - self.previewView.setUrl(QtCore.QUrl("about:blank")) - self.previewView.setObjectName("previewView") - self.previewLayout.addWidget(self.previewView) - self.previewDock.setWidget(self.previewDockContents) - self.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.previewDock) - self.toolBar = QtWidgets.QToolBar(self) - self.toolBar.setObjectName("toolBar") - self.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar) - self.newAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("document-new") - self.newAction.setIcon(icon) - self.newAction.setObjectName("newAction") - self.openAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("document-open") - self.openAction.setIcon(icon) - self.openAction.setObjectName("openAction") - self.saveAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("document-save") - self.saveAction.setIcon(icon) - self.saveAction.setObjectName("saveAction") - self.saveAsAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("document-save-as") - self.saveAsAction.setIcon(icon) - self.saveAsAction.setObjectName("saveAsAction") - self.printAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("document-print") - self.printAction.setIcon(icon) - self.printAction.setObjectName("printAction") - self.exportAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("document-export") - self.exportAction.setIcon(icon) - self.exportAction.setObjectName("exportAction") - self.undoAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("edit-undo") - self.undoAction.setIcon(icon) - self.undoAction.setObjectName("undoAction") - self.redoAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("edit-redo") - self.redoAction.setIcon(icon) - self.redoAction.setObjectName("redoAction") - self.cutAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("edit-cut") - self.cutAction.setIcon(icon) - self.cutAction.setObjectName("cutAction") - self.copyAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("edit-copy") - self.copyAction.setIcon(icon) - self.copyAction.setObjectName("copyAction") - self.pasteAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("edit-paste") - self.pasteAction.setIcon(icon) - self.pasteAction.setObjectName("pasteAction") - self.configureAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("configure") - self.configureAction.setIcon(icon) - self.configureAction.setObjectName("configureAction") - self.aboutAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("help-about") - self.aboutAction.setIcon(icon) - self.aboutAction.setObjectName("aboutAction") - self.exitAction = QtWidgets.QAction(self) - icon = QtGui.QIcon.fromTheme("application-exit") - self.exitAction.setIcon(icon) - self.exitAction.setObjectName("exitAction") - self.fileMenu.addAction(self.newAction) - self.fileMenu.addAction(self.openAction) - self.fileMenu.addAction(self.saveAction) - self.fileMenu.addAction(self.saveAsAction) - self.fileMenu.addSeparator() - self.fileMenu.addAction(self.exportAction) - self.fileMenu.addAction(self.printAction) - self.fileMenu.addSeparator() - self.fileMenu.addAction(self.exitAction) - self.editMenu.addAction(self.undoAction) - self.editMenu.addAction(self.redoAction) - self.editMenu.addSeparator() - self.editMenu.addAction(self.cutAction) - self.editMenu.addAction(self.copyAction) - self.editMenu.addAction(self.pasteAction) - self.settingsMenu.addAction(self.configureAction) - self.helpMenu.addAction(self.aboutAction) - self.menubar.addAction(self.fileMenu.menuAction()) - self.menubar.addAction(self.editMenu.menuAction()) - self.menubar.addAction(self.settingsMenu.menuAction()) - self.menubar.addAction(self.helpMenu.menuAction()) - self.toolBar.addAction(self.newAction) - self.toolBar.addAction(self.openAction) - self.toolBar.addAction(self.saveAction) - self.toolBar.addSeparator() - self.toolBar.addAction(self.exportAction) - self.toolBar.addAction(self.printAction) - self.toolBar.addSeparator() - self.toolBar.addAction(self.undoAction) - self.toolBar.addAction(self.redoAction) - self.toolBar.addSeparator() - self.toolBar.addAction(self.cutAction) - self.toolBar.addAction(self.copyAction) - self.toolBar.addAction(self.pasteAction) - self.toolBar.addSeparator() - self.toolBar.addAction(self.configureAction) + 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_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.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_action = QtWidgets.QAction(self) + icon = QtGui.QIcon.fromTheme('document-export') + self.export_action.setIcon(icon) + self.export_action.setObjectName('export_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) + icon = QtGui.QIcon.fromTheme('edit-copy') + self.copy_action.setIcon(icon) + self.copy_action.setObjectName('copy_action') + self.paste_action = QtWidgets.QAction(self) + icon = QtGui.QIcon.fromTheme('edit-paste') + self.paste_action.setIcon(icon) + self.paste_action.setObjectName('paste_action') + self.configure_action = QtWidgets.QAction(self) + icon = QtGui.QIcon.fromTheme('configure') + self.configure_action.setIcon(icon) + self.configure_action.setObjectName('configure_action') + self.about_action = QtWidgets.QAction(self) + icon = QtGui.QIcon.fromTheme('help-about') + self.about_action.setIcon(icon) + self.about_action.setObjectName('about_action') + self.exit_action = QtWidgets.QAction(self) + icon = QtGui.QIcon.fromTheme('application-exit') + self.exit_action.setIcon(icon) + self.exit_action.setObjectName('exit_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.file_menu.addAction(self.export_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_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.retranslate_ui() - self.exitAction.triggered.connect(self.close) - self.undoAction.triggered.connect(self.fileEditor.undo) - self.redoAction.triggered.connect(self.fileEditor.redo) - self.cutAction.triggered.connect(self.fileEditor.cut) - self.copyAction.triggered.connect(self.fileEditor.copy) - self.pasteAction.triggered.connect(self.fileEditor.paste) + 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) # QtCore.QMetaObject.connectSlotsByName(self) - self.openAction.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_as_action.triggered.connect(self.on_save_as_clicked) + self.file_editor.textChanged.connect(self.on_text_changed) def retranslate_ui(self): _translate = QtCore.QCoreApplication.translate - self.setWindowTitle(_translate("MainWindow", "Ukatali")) - self.fileMenu.setTitle(_translate("MainWindow", "&File")) - self.editMenu.setTitle(_translate("MainWindow", "&Edit")) - self.settingsMenu.setTitle(_translate("MainWindow", "&Settings")) - self.helpMenu.setTitle(_translate("MainWindow", "&Help")) - self.previewDock.setWindowTitle(_translate("MainWindow", "Preview")) - self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar")) - self.newAction.setText(_translate("MainWindow", "&New")) - self.newAction.setToolTip(_translate("MainWindow", "Create a new ChordPro file")) - self.newAction.setShortcut(_translate("MainWindow", "Ctrl+N")) - self.openAction.setText(_translate("MainWindow", "&Open...")) - self.openAction.setToolTip(_translate("MainWindow", "Open an existing ChordPro file")) - self.openAction.setShortcut(_translate("MainWindow", "Ctrl+O")) - self.saveAction.setText(_translate("MainWindow", "&Save")) - self.saveAction.setToolTip(_translate("MainWindow", "Save the current ChordPro file")) - self.saveAction.setShortcut(_translate("MainWindow", "Ctrl+S")) - self.saveAsAction.setText(_translate("MainWindow", "Save &As...")) - self.saveAsAction.setToolTip(_translate("MainWindow", "Save the current file under a new name")) - self.saveAsAction.setShortcut(_translate("MainWindow", "Ctrl+Shift+S")) - self.printAction.setText(_translate("MainWindow", "&Print")) - self.printAction.setToolTip(_translate("MainWindow", "Print the current ChordPro file")) - self.printAction.setShortcut(_translate("MainWindow", "Ctrl+P")) - self.exportAction.setText(_translate("MainWindow", "&Export...")) - self.exportAction.setToolTip(_translate("MainWindow", "Export the current file as a different format")) - self.exportAction.setShortcut(_translate("MainWindow", "Ctrl+E")) - self.undoAction.setText(_translate("MainWindow", "&Undo")) - self.undoAction.setToolTip(_translate("MainWindow", "Undo the last edit")) - self.undoAction.setShortcut(_translate("MainWindow", "Ctrl+Z")) - self.redoAction.setText(_translate("MainWindow", "&Redo")) - self.redoAction.setToolTip(_translate("MainWindow", "Redo the last undone action")) - self.redoAction.setShortcut(_translate("MainWindow", "Ctrl+Shift+Z")) - self.cutAction.setText(_translate("MainWindow", "Cu&t")) - self.cutAction.setToolTip(_translate("MainWindow", "Cut the currently selected text")) - self.cutAction.setShortcut(_translate("MainWindow", "Ctrl+X")) - self.copyAction.setText(_translate("MainWindow", "&Copy")) - self.copyAction.setToolTip(_translate("MainWindow", "Copy the currently selected text to the clipboard")) - self.copyAction.setShortcut(_translate("MainWindow", "Ctrl+C")) - self.pasteAction.setText(_translate("MainWindow", "&Paste")) - self.pasteAction.setToolTip(_translate("MainWindow", "Paste the current clipboard text into the editor")) - self.pasteAction.setShortcut(_translate("MainWindow", "Ctrl+V")) - self.configureAction.setText(_translate("MainWindow", "&Configure...")) - self.configureAction.setToolTip(_translate("MainWindow", "Configure Ukatali")) - self.aboutAction.setText(_translate("MainWindow", "&About")) - self.aboutAction.setToolTip(_translate("MainWindow", "About Ukatali")) - self.exitAction.setText(_translate("MainWindow", "E&xit")) - self.exitAction.setToolTip(_translate("MainWindow", "Quit Ukatali")) - self.exitAction.setShortcut(_translate("MainWindow", "Alt+F4")) + self.setWindowTitle(_translate('MainWindow', '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_action.setText(_translate('MainWindow', '&Export...')) + self.export_action.setToolTip(_translate('MainWindow', 'Export the current file as a different format')) + self.export_action.setShortcut(_translate('MainWindow', 'Ctrl+E')) + 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')) def on_open_clicked(self): """Open the file""" @@ -222,13 +230,48 @@ class MainWindow(QtWidgets.QMainWindow): last_directory = QtCore.QSettings().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='All files *') if isinstance(filename, tuple): filename = filename[0] if not filename: return + + self.filename = filename file_path = Path(filename) if file_path.exists(): QtCore.QSettings().setValue('files/last-directory', str(file_path.parent)) - self.fileEditor.setText(file_path.open().read()) + self.file_editor.setText(file_path.open().read()) + + 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()) + + def on_save_as_clicked(self): + """Save the file""" + if QtCore.QSettings().value('files/last-directory'): + last_directory = QtCore.QSettings().value('files/last-directory') + else: + last_directory = '' + filename = QtWidgets.QFileDialog.getSaveFileName(self, 'Save file', last_directory, + filter='All files *') + 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.on_save_clicked() + + def on_text_changed(self): + """Update the preview when the text changes""" + text = self.file_editor.text() + song = Song() + song.parse(text) + html = render(song) + self.preview_view.setHtml(html)