examples/webenginewidgets/markdowneditor#
(You can also check this code in the repository)
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtCore import QObject, Property, Signal
class Document(QObject):
textChanged = Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self._text = ''
def text(self):
return self._text
def setText(self, t):
if t != self._text:
self._text = t
self.textChanged.emit(t)
text = Property(str, text, setText, notify=textChanged)
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
"""PySide6 Markdown Editor Example"""
import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QApplication
from mainwindow import MainWindow
import rc_markdowneditor
if __name__ == '__main__':
app = QApplication(sys.argv)
QCoreApplication.setOrganizationName("QtExamples")
window = MainWindow()
window.show()
sys.exit(app.exec())
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtCore import QDir, QFile, QIODevice, QUrl, Qt, Slot
from PySide6.QtGui import QFontDatabase
from PySide6.QtWebChannel import QWebChannel
from PySide6.QtWidgets import QDialog, QFileDialog, QMainWindow, QMessageBox
from ui_mainwindow import Ui_MainWindow
from document import Document
from previewpage import PreviewPage
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.m_file_path = ''
self.m_content = Document()
self._ui = Ui_MainWindow()
self._ui.setupUi(self)
font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
self._ui.editor.setFont(font)
self._ui.preview.setContextMenuPolicy(Qt.NoContextMenu)
self._page = PreviewPage(self)
self._ui.preview.setPage(self._page)
self._ui.editor.textChanged.connect(self.plainTextEditChanged)
self._channel = QWebChannel(self)
self._channel.registerObject("content", self.m_content)
self._page.setWebChannel(self._channel)
self._ui.preview.setUrl(QUrl("qrc:/index.html"))
self._ui.actionNew.triggered.connect(self.onFileNew)
self._ui.actionOpen.triggered.connect(self.onFileOpen)
self._ui.actionSave.triggered.connect(self.onFileSave)
self._ui.actionSaveAs.triggered.connect(self.onFileSaveAs)
self._ui.actionExit.triggered.connect(self.close)
self._ui.editor.document().modificationChanged.connect(self._ui.actionSave.setEnabled)
defaultTextFile = QFile(":/default.md")
defaultTextFile.open(QIODevice.ReadOnly)
data = defaultTextFile.readAll()
self._ui.editor.setPlainText(data.data().decode('utf8'))
@Slot()
def plainTextEditChanged(self):
self.m_content.setText(self._ui.editor.toPlainText())
@Slot(str)
def openFile(self, path):
f = QFile(path)
name = QDir.toNativeSeparators(path)
if not f.open(QIODevice.ReadOnly):
error = f.errorString()
QMessageBox.warning(self, self.windowTitle(),
f"Could not open file {name}: {error}")
return
self.m_file_path = path
data = f.readAll()
self._ui.editor.setPlainText(data.data().decode('utf8'))
self.statusBar().showMessage(f"Opened {name}")
def isModified(self):
return self._ui.editor.document().isModified()
@Slot()
def onFileNew(self):
if self.isModified():
m = "You have unsaved changes. Do you want to create a new document anyway?"
button = QMessageBox.question(self, self.windowTitle(), m)
if button != QMessageBox.Yes:
return
self.m_file_path = ''
self._ui.editor.setPlainText("## New document")
self._ui.editor.document().setModified(False)
@Slot()
def onFileOpen(self):
if self.isModified():
m = "You have unsaved changes. Do you want to open a new document anyway?"
button = QMessageBox.question(self, self.windowTitle(), m)
if button != QMessageBox.Yes:
return
dialog = QFileDialog(self)
dialog.setWindowTitle("Open MarkDown File")
dialog.setMimeTypeFilters(["text/markdown"])
dialog.setAcceptMode(QFileDialog.AcceptOpen)
if dialog.exec() == QDialog.Accepted:
self.openFile(dialog.selectedFiles()[0])
@Slot()
def onFileSave(self):
if not self.m_file_path:
self.onFileSaveAs()
if not self.m_file_path:
return
f = QFile(self.m_file_path)
name = QDir.toNativeSeparators(self.m_file_path)
if not f.open(QIODevice.WriteOnly | QIODevice.Text):
error = f.errorString()
QMessageBox.warning(self, self.windowTitle(),
f"Could not write to file {name}: {error}")
return
text = self._ui.editor.toPlainText()
f.write(bytes(text, encoding='utf8'))
f.close()
self._ui.editor.document().setModified(False)
self.statusBar().showMessage(f"Wrote {name}")
@Slot()
def onFileSaveAs(self):
dialog = QFileDialog(self)
dialog.setWindowTitle("Save MarkDown File")
dialog.setMimeTypeFilters(["text/markdown"])
dialog.setAcceptMode(QFileDialog.AcceptSave)
dialog.setDefaultSuffix("md")
if dialog.exec() != QDialog.Accepted:
return
path = dialog.selectedFiles()[0]
self.m_file_path = path
self.onFileSave()
def closeEvent(self, event):
if self.isModified():
m = "You have unsaved changes. Do you want to exit anyway?"
button = QMessageBox.question(self, self.windowTitle(), m)
if button != QMessageBox.Yes:
event.ignore()
else:
event.accept()
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MarkDown Editor</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QPlainTextEdit" name="editor"/>
<widget class="QWebEngineView" name="preview" native="true"/>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>26</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
<property name="title">
<string>&File</string>
</property>
<addaction name="actionNew"/>
<addaction name="actionOpen"/>
<addaction name="actionSave"/>
<addaction name="actionSaveAs"/>
<addaction name="separator"/>
<addaction name="actionExit"/>
</widget>
<addaction name="menu_File"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionOpen">
<property name="text">
<string>&Open...</string>
</property>
<property name="toolTip">
<string>Open document</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
</property>
</action>
<action name="actionSave">
<property name="text">
<string>&Save</string>
</property>
<property name="toolTip">
<string>Save current document</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="actionExit">
<property name="text">
<string>E&xit</string>
</property>
<property name="toolTip">
<string>Exit editor</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="actionSaveAs">
<property name="text">
<string>Save &As...</string>
</property>
<property name="toolTip">
<string>Save document under different name</string>
</property>
</action>
<action name="actionNew">
<property name="text">
<string>&New</string>
</property>
<property name="toolTip">
<string>Create new document</string>
</property>
<property name="shortcut">
<string>Ctrl+N</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtGui import QDesktopServices
from PySide6.QtWebEngineCore import QWebEnginePage
class PreviewPage(QWebEnginePage):
def __init__(self, parent=None):
super().__init__(parent)
def acceptNavigationRequest(self, url, type, isMainFrame):
# Only allow qrc:/index.html.
if url.scheme() == "qrc":
return True
QDesktopServices.openUrl(url)
return False
<RCC>
<qresource prefix="/">
<file>default.md</file>
<file>index.html</file>
<file>3rdparty/markdown.css</file>
<file>3rdparty/marked.js</file>
</qresource>
</RCC>