Skip to content

Instantly share code, notes, and snippets.

@kghose
Last active September 23, 2022 11:09
Show Gist options
  • Save kghose/815f5be37ebd8b7db319be8febd53c35 to your computer and use it in GitHub Desktop.
Save kghose/815f5be37ebd8b7db319be8febd53c35 to your computer and use it in GitHub Desktop.
Code for: Embed Ace editor in a Python QT app
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_()
#!/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
@kghose
Copy link
Author

kghose commented Mar 18, 2019

The qwebchannel.js code can also be found at https://github.com/qt/qtwebchannel/blob/5.12/examples/webchannel/shared/qwebchannel.js

but is not very well advertised. Fortunately it is distributed with the Python install and (presumably) with the C++ install too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment