Files
exocerebrum/main.py
2021-06-12 18:58:10 +03:00

275 lines
7.7 KiB
Python

import json
import sys
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.QtWidgets import (
QApplication,
QDockWidget,
QFileDialog,
QMainWindow,
QPushButton,
QTreeView,
QVBoxLayout,
QWidget,
)
from PySide2.QtGui import QIcon, QImage, QPixmap
from PySide2.QtWebChannel import QWebChannel
from PySide2.QtWebEngineWidgets import QWebEngineView
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 = "Untitled"
parent: "TreeItem" = None
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):
UNTITLED_NODE_NAME = "Untitled"
def __init__(self):
super().__init__()
self.root = TreeItem.load(["root", [["first", [["second", [["third", []]]]]]]])
def index(self, row: int, column: int, parent=QModelIndex()) -> QModelIndex:
if not self.hasIndex(row, column, parent):
return QModelIndex()
parent_item = parent.internalPointer() if parent.isValid() else self.root
if len(parent_item.children) > row:
return self.createIndex(row, column, parent_item.children[row])
return QModelIndex()
def parent(self, index: QModelIndex) -> QModelIndex:
if not index.isValid():
return QModelIndex()
child_item = index.internalPointer()
parent_item = child_item.parent
if parent_item == self.root:
return QModelIndex()
return self.createIndex(parent_item.row(), 0, parent_item)
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
if index.isValid():
return (
Qt.ItemIsEnabled
| Qt.ItemIsSelectable
| Qt.ItemIsDragEnabled
| Qt.ItemIsDropEnabled
)
else:
return Qt.ItemIsEnabled
def rowCount(self, parent=QModelIndex()):
parent_item = parent.internalPointer() if parent.isValid() else self.root
return len(parent_item.children)
def columnCount(self, parent=QModelIndex()):
return 1
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 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 insertRows(self, row: int, count: int, parent: QModelIndex) -> bool:
item: TreeItem = parent.internalPointer() or self.root
self.beginInsertRows(parent, row, row + count)
item.children.insert(row, TreeItem(LibraryModel.UNTITLED_NODE_NAME, item, []))
self.endInsertRows()
return True
def removeRows(self, row: int, count: int, parent: QModelIndex) -> bool:
item: TreeItem = parent.internalPointer() or self.root
self.beginRemoveRows(parent, row, row + count)
del item.children[row]
self.endRemoveRows()
return True
def supportedDragActions(self) -> Qt.DropActions:
return Qt.CopyAction | Qt.MoveAction
def supportedDropActions(self) -> Qt.DropActions:
return Qt.CopyAction | Qt.MoveAction
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.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.library_model = LibraryModel()
self.library_view = QTreeView()
self.dock_layout.addWidget(self.library_view)
self.library_view.setModel(self.library_model)
self.library_view.setDragEnabled(True)
self.library_view.setAcceptDrops(True)
self.library_view.setDefaultDropAction(Qt.MoveAction)
self.library_view.setHeaderHidden(True)
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.new_node = QPushButton("New node")
self.dock_layout.addWidget(self.new_node)
self.new_node.clicked.connect(
lambda: self.library_model.insertRow(0, self.library_view.currentIndex())
)
self.delete_node = QPushButton("Delete node")
self.dock_layout.addWidget(self.delete_node)
self.delete_node.clicked.connect(
lambda: self.library_model.removeRow(
self.library_view.currentIndex().row(),
self.library_view.currentIndex().parent(),
)
)
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()