Last active
September 23, 2022 11:09
-
-
Save kghose/815f5be37ebd8b7db319be8febd53c35 to your computer and use it in GitHub Desktop.
Code for: Embed Ace editor in a Python QT app
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
from PySide2.QtCore import QObject, QUrl, Signal, Slot | |
from PySide2.QtWidgets import QApplication | |
from PySide2.QtWebChannel import QWebChannel | |
from PySide2.QtWebEngineWidgets import QWebEngineView | |
# We create this file as follows | |
# First create a .qrc file so we can include the ace editor code and the qqwebchannel.js in a form | |
# usable by the application. Delightfully QT doesn't allow wildcards or directories in the .qrc | |
# file, so we use a bash script to add in all the 400 odd ace files to the .prc | |
# Second concatenate all the information into a Python module (which we have called embedrc) | |
import embedrc | |
html = """ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>Benten</title> | |
<script src="qrc:/qtwebchannel/qwebchannel.js" type="text/javascript"></script> | |
// This file comes bundled with Pyside2 and the resource bundler knows to include it ... | |
<script src="qrc:/ace-builds/src-min-noconflict/ace.js" type="text/javascript" charset="utf-8"></script> | |
<style type="text/css" media="screen"> | |
#editor { | |
position: absolute; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
} | |
</style> | |
<script> | |
document.addEventListener("DOMContentLoaded", function () { | |
// It is safest to set up this apparatus after the page has finished loading | |
'use strict'; | |
var placeholder = document.getElementById('editor'); | |
var editor = ace.edit("editor"); | |
editor.setTheme("ace/theme/light"); | |
editor.session.setMode("ace/mode/yaml"); | |
// https://stackoverflow.com/a/42122466/2512851 | |
var set_error_annotation = function(row, column, err_msg, type) { | |
editor.getSession().setAnnotations([{ | |
row: row, | |
column: column, | |
text: err_msg, // Or the Json reply from the parser | |
type: type // error, warning, and information | |
}]); | |
} | |
new QWebChannel(qt.webChannelTransport, function(channel) { | |
// All the functions we use to communicate with the Python code are here | |
var talkie = channel.objects.talkie; | |
// An example of receiving information pushed from the Python side | |
// It's really neat how this looks just like the Python code | |
talkie.send_text_js_side.connect(function(text) { | |
editor.session.setValue(text) | |
}) | |
// An example of sending information to the Python side | |
editor.session.on('change', function(delta) { | |
talkie.send_text_python_side(editor.getValue(), function(val) {} ); | |
// Python functions return a value, even if it is None. So we need to pass a | |
// dummy callback function to handle the return | |
}) | |
talkie.send_error_annotation.connect(set_error_annotation); | |
}); | |
}); | |
</script> | |
</head> | |
<body> | |
<div id="editor">In the beginning there was darkness</div> | |
</body> | |
</html>""" | |
class TalkyTalky(QObject): | |
send_text_js_side = Signal(str) | |
send_error_annotation = Signal(int, int, str, str) | |
def __init__(self, parent=None): | |
super().__init__(parent) | |
@Slot(str) | |
def send_text_python_side(self, message): | |
print(message) | |
if __name__ == '__main__': | |
app = QApplication(sys.argv) | |
view = QWebEngineView() | |
page = view.page() | |
talkie = TalkyTalky() | |
channel = QWebChannel(page) | |
# The channel object has to persist. Doing registerObject does not keep a reference apparently | |
channel.registerObject("talkie", talkie) | |
page.setWebChannel(channel) | |
page.setHtml(html, QUrl("qrc:/index.html")) | |
view.show() | |
from PySide2.QtCore import QTimer | |
# Invoking talkie.send_ ... at this point does not have any effect. I reason that this is | |
# because everything is not setup on the webview side and so the message is lost to the | |
# ether. Hence this delay in sending messages, to allow things to settle. Of course things | |
# don't need 5 s to settle, this is more for dramatic effect. | |
timer = QTimer() | |
timer.setSingleShot(True) | |
timer.setInterval(5000) | |
timer.timeout.connect(lambda x=None: talkie.send_text_js_side.emit("Type to fix world.")) | |
timer.start() | |
timer2 = QTimer() | |
timer2.setSingleShot(True) | |
timer2.setInterval(7000) | |
timer2.timeout.connect(lambda x=None: talkie.send_error_annotation.emit(0, 4, "World not created", "error")) | |
timer2.start() | |
app.exec_() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
git clone https://github.com/ajaxorg/ace-builds/ | |
QRC=./embed.qrc | |
echo '<!DOCTYPE RCC>' > $QRC | |
echo '<RCC version="1.0">' >> $QRC | |
echo ' <qresource>' >> $QRC | |
# Each file in the Ace source folder has to be added in individually | |
for a in $(find ace-builds/src-min-noconflict -d) | |
do | |
# if this is not a folder | |
if [ ! -d "$a" ]; then | |
echo ' <file>'$a'</file>' >> $QRC | |
fi | |
done | |
echo ' </qresource>' >> $QRC | |
echo '</RCC>' >> $QRC | |
pyside2-rcc embed.qrc -o embedrc.py |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The
qwebchannel.js
code can also be found at https://github.com/qt/qtwebchannel/blob/5.12/examples/webchannel/shared/qwebchannel.jsbut is not very well advertised. Fortunately it is distributed with the Python install and (presumably) with the C++ install too