add main window; webview; webchannel; pdf.js; wip sidebar
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vscode
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "pdf.js"]
|
||||
path = pdf.js
|
||||
url = https://github.com/mozilla/pdf.js.git
|
||||
40
controller.js
Normal file
40
controller.js
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
const h = (name, props = {}, ...children) => {
|
||||
const element = document.createElement(name);
|
||||
|
||||
for (const [key, value] of Object.entries(props))
|
||||
key.startsWith('on')
|
||||
? element.addEventListener(key.substring(2), value)
|
||||
: element.setAttribute(key, value);
|
||||
|
||||
for (const child of children)
|
||||
element.appendChild(
|
||||
typeof(child) === 'string'
|
||||
? document.createTextNode(child)
|
||||
: child);
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
const qs = s => document.querySelector(s);
|
||||
const qsa = s => Array.from(document.querySelectorAll(s))
|
||||
|
||||
const on_command = command => {
|
||||
switch(command.type) {
|
||||
case 'alert': alert(command.message); break;
|
||||
case 'load_pdf': pdfjsLib.getDocument(command.url).promise.then(pdf => PDFViewerApplication.load(pdf)); break;
|
||||
}
|
||||
};
|
||||
|
||||
let call_python;
|
||||
|
||||
new QWebChannel(qt.webChannelTransport, channel => {
|
||||
call_python = (argument, callback) =>
|
||||
channel.objects.bridge.call_python(JSON.stringify(argument),
|
||||
result => callback && callback(JSON.parse(result)));
|
||||
|
||||
channel.objects.bridge.call_javascript.connect(
|
||||
result => on_command(JSON.parse(result)));
|
||||
|
||||
call_python({'type': 'ready'});
|
||||
});
|
||||
229
main.py
Normal file
229
main.py
Normal file
@@ -0,0 +1,229 @@
|
||||
import sys
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from PySide2.QtCore import QAbstractItemModel, QModelIndex, QObject, QUrl, Qt, Signal, Slot
|
||||
from PySide2.QtGui import QIcon, QImage, QPicture, QPixmap
|
||||
from PySide2.QtWebChannel import QWebChannel
|
||||
from PySide2.QtWebEngineWidgets import QWebEngineView
|
||||
from PySide2.QtWidgets import (
|
||||
QApplication,
|
||||
QDockWidget,
|
||||
QFileDialog,
|
||||
QMainWindow,
|
||||
QPushButton,
|
||||
QTreeView,
|
||||
QVBoxLayout,
|
||||
QWidget
|
||||
)
|
||||
|
||||
def qurl_from_local(fpath):
|
||||
return QUrl.fromLocalFile(str(Path(fpath).absolute()))
|
||||
|
||||
def file_url_from_local(fpath):
|
||||
return f'file://{Path(fpath).absolute()}'
|
||||
|
||||
class Bridge(QObject):
|
||||
@Slot(str, result=str)
|
||||
def call_python(self, data):
|
||||
return json.dumps(self.handler(json.loads(data)))
|
||||
|
||||
call_javascript = Signal(str)
|
||||
|
||||
@dataclass
|
||||
class TreeItem:
|
||||
name: str
|
||||
parent: "TreeItem"
|
||||
children: list["TreeItem"]
|
||||
|
||||
def row(self):
|
||||
return self.parent.children.index(self) if self.parent else 0
|
||||
|
||||
@staticmethod
|
||||
def load(value, parent=None):
|
||||
name, children = value
|
||||
item = TreeItem(name, parent, [])
|
||||
for child in children:
|
||||
if len(child) == 2:
|
||||
item.children.append(TreeItem.load(child, item))
|
||||
return item
|
||||
|
||||
class LibraryModel(QAbstractItemModel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.root = TreeItem.load(
|
||||
['root', [
|
||||
['first', [
|
||||
['second', [
|
||||
['third', []]]]]]]])
|
||||
|
||||
def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> Any:
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
if role == Qt.DisplayRole or role == Qt.EditRole:
|
||||
item = index.internalPointer()
|
||||
return item.name
|
||||
elif role == Qt.DecorationRole:
|
||||
img = QImage()
|
||||
img.load('test.png')
|
||||
return QIcon(QPixmap.fromImage(img))
|
||||
|
||||
def insertRows(self, row: int, count: int, parent: QModelIndex) -> bool:
|
||||
item: TreeItem = parent.internalPointer()
|
||||
|
||||
self.beginInsertRows(parent, row, row)
|
||||
|
||||
item.children.insert(row, TreeItem('', item, []))
|
||||
|
||||
self.endInsertRows()
|
||||
|
||||
return True
|
||||
|
||||
def setData(self, index: QModelIndex, value: str, role: Qt.ItemDataRole):
|
||||
if not index.isValid():
|
||||
return False
|
||||
|
||||
item: TreeItem = index.internalPointer()
|
||||
item.name = value
|
||||
|
||||
self.dataChanged.emit(index, index, Qt.EditRole)
|
||||
|
||||
return True
|
||||
|
||||
def index(self, row: int, column: int, parent=QModelIndex()) -> QModelIndex:
|
||||
if not self.hasIndex(row, column, parent):
|
||||
return QModelIndex()
|
||||
|
||||
if not parent.isValid():
|
||||
parentItem = self.root
|
||||
else:
|
||||
parentItem = parent.internalPointer()
|
||||
|
||||
if len(parentItem.children) > row:
|
||||
return self.createIndex(row, column, parentItem.children[row])
|
||||
else:
|
||||
return QModelIndex()
|
||||
|
||||
def parent(self, index: QModelIndex) -> QModelIndex:
|
||||
if not index.isValid():
|
||||
return QModelIndex()
|
||||
|
||||
childItem = index.internalPointer()
|
||||
parentItem = childItem.parent
|
||||
|
||||
if parentItem == self.root:
|
||||
return QModelIndex()
|
||||
|
||||
return self.createIndex(parentItem.row(), 0, parentItem)
|
||||
|
||||
def rowCount(self, parent=QModelIndex()):
|
||||
if not parent.isValid():
|
||||
parentItem = self.root
|
||||
else:
|
||||
parentItem = parent.internalPointer()
|
||||
|
||||
return len(parentItem.children)
|
||||
|
||||
def columnCount(self, parent=QModelIndex()):
|
||||
return 1
|
||||
|
||||
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
|
||||
flags = super().flags(index)
|
||||
|
||||
return Qt.ItemIsEditable | flags
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.widget = QWidget()
|
||||
self.setCentralWidget(self.widget)
|
||||
|
||||
self._layout = QVBoxLayout()
|
||||
self.widget.setLayout(self._layout)
|
||||
|
||||
self.web_view = QWebEngineView()
|
||||
self._layout.addWidget(self.web_view)
|
||||
|
||||
self.library_model = LibraryModel()
|
||||
self.library_view = QTreeView()
|
||||
self.library_view.setHeaderHidden(True)
|
||||
self.library_view.setModel(self.library_model)
|
||||
|
||||
def on_insert_row(p: QModelIndex, f, l):
|
||||
self.library_view.setExpanded(p, True)
|
||||
self.library_view.edit(p.child(f, 0))
|
||||
|
||||
self.library_model.rowsInserted.connect(on_insert_row)
|
||||
|
||||
self.dock_widget = QDockWidget('Library')
|
||||
|
||||
self.dock_qwidget = QWidget()
|
||||
self.dock_widget.setWidget(self.dock_qwidget)
|
||||
|
||||
self.dock_layout = QVBoxLayout()
|
||||
self.dock_qwidget.setLayout(self.dock_layout)
|
||||
|
||||
self.new_node = QPushButton("New node")
|
||||
self.dock_layout.addWidget(self.library_view)
|
||||
self.dock_layout.addWidget(self.new_node)
|
||||
self.new_node.clicked.connect(lambda:
|
||||
self.library_model.insertRow(0, self.library_view.currentIndex())
|
||||
)
|
||||
|
||||
|
||||
self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_widget)
|
||||
|
||||
self.web_view.load(qurl_from_local('pdf.js/build/generic/web/viewer.html'))
|
||||
|
||||
def on_load():
|
||||
def load_javascript_file(filepath):
|
||||
self.web_view.page().runJavaScript(
|
||||
'var s = document.createElement("script");'
|
||||
f's.src="{filepath}";'
|
||||
'document.body.appendChild(s);'
|
||||
)
|
||||
|
||||
load_javascript_file('qrc:///qtwebchannel/qwebchannel.js')
|
||||
load_javascript_file(file_url_from_local('controller.js'))
|
||||
|
||||
self.web_view.loadFinished.connect(on_load)
|
||||
|
||||
self.channel = QWebChannel()
|
||||
self.web_view.page().setWebChannel(self.channel)
|
||||
|
||||
self.bridge = Bridge()
|
||||
self.channel.registerObject('bridge', self.bridge)
|
||||
|
||||
def on_command(command):
|
||||
if command['type'] == 'ready':
|
||||
print('webchannel ready')
|
||||
|
||||
self.bridge.handler = on_command
|
||||
|
||||
def call_javascript(command):
|
||||
self.bridge.call_javascript.emit(json.dumps(command))
|
||||
|
||||
self.button = QPushButton('Load PDF')
|
||||
self._layout.addWidget(self.button)
|
||||
|
||||
self.button.clicked.connect(lambda:
|
||||
call_javascript({'type': 'load_pdf', 'url': file_url_from_local(QFileDialog.getOpenFileName()[0])}))
|
||||
|
||||
def main():
|
||||
app = QApplication()
|
||||
|
||||
window = MainWindow()
|
||||
|
||||
availableGeometry = app.desktop().availableGeometry(window)
|
||||
window.resize(availableGeometry.width() * 2 / 3, availableGeometry.height() * 2 / 3)
|
||||
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1
pdf.js
Submodule
1
pdf.js
Submodule
Submodule pdf.js added at 7b4fa0a038
Reference in New Issue
Block a user