Files
exocerebrum/main.py

229 lines
6.7 KiB
Python

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()