-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #354 from pfafflabatuiuc/monitr_revamp
New Monitr
- Loading branch information
Showing
14 changed files
with
4,682 additions
and
94 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,316 @@ | ||
""" | ||
Script obtained from: https://doc-snapshots.qt.io/qtforpython-dev/examples/example_widgets_itemviews_jsonmodel.html | ||
""" | ||
|
||
from typing import Any, List, Dict, Union, Optional | ||
from pathlib import Path | ||
|
||
from qtpy.QtCore import QAbstractItemModel, QModelIndex, QObject, Qt | ||
from qtpy.QtWidgets import QTreeView | ||
|
||
|
||
class TreeItem: | ||
"""A Json item corresponding to a line in QTreeView""" | ||
|
||
def __init__(self, parent: Optional["TreeItem"] = None): | ||
self._parent = parent | ||
self._key = "" | ||
self._value = "" | ||
self._value_type: Any = None | ||
self._children: List["TreeItem"] = [] | ||
|
||
def appendChild(self, item: "TreeItem") -> None: | ||
"""Add item as a child""" | ||
self._children.append(item) | ||
|
||
def child(self, row: int) -> "TreeItem": | ||
"""Return the child of the current item from the given row""" | ||
return self._children[row] | ||
|
||
def parent(self) -> Optional["TreeItem"]: | ||
"""Return the parent of the current item""" | ||
return self._parent | ||
|
||
def childCount(self) -> int: | ||
"""Return the number of children of the current item""" | ||
return len(self._children) | ||
|
||
def row(self) -> int: | ||
"""Return the row where the current item occupies in the parent""" | ||
return self._parent._children.index(self) if self._parent else 0 | ||
|
||
@property | ||
def key(self) -> str: | ||
"""Return the key name""" | ||
return self._key | ||
|
||
@key.setter | ||
def key(self, key: str) -> None: | ||
"""Set key name of the current item""" | ||
self._key = key | ||
|
||
@property | ||
def value(self) -> str: | ||
"""Return the value name of the current item""" | ||
return self._value | ||
|
||
@value.setter | ||
def value(self, value: str) -> None: | ||
"""Set value name of the current item""" | ||
self._value = value | ||
|
||
@property | ||
def value_type(self) -> Any: | ||
"""Return the python type of the item's value.""" | ||
return self._value_type | ||
|
||
@value_type.setter | ||
def value_type(self, value: Any) -> None: | ||
"""Set the python type of the item's value.""" | ||
self._value_type = value | ||
|
||
@classmethod | ||
def load( | ||
cls, value: Union[List, Dict], parent: Optional["TreeItem"] = None, sort: bool = True | ||
) -> "TreeItem": | ||
"""Create a 'root' TreeItem from a nested list or a nested dictonary | ||
Examples: | ||
with open("file.json") as file: | ||
data = json.dump(file) | ||
root = TreeItem.load(data) | ||
This method is a recursive function that calls itself. | ||
Returns: | ||
TreeItem: TreeItem | ||
""" | ||
rootItem = TreeItem(parent) | ||
rootItem.key = "root" | ||
|
||
if isinstance(value, dict): | ||
items = sorted(value.items()) if sort else value.items() | ||
|
||
for key, value in items: | ||
child = cls.load(value, rootItem) | ||
child.key = key | ||
child.value_type = type(value) | ||
rootItem.appendChild(child) | ||
|
||
elif isinstance(value, list): | ||
for index, value in enumerate(value): | ||
child = cls.load(value, rootItem) | ||
child.key = str(index) | ||
child.value_type = type(value) | ||
rootItem.appendChild(child) | ||
|
||
else: | ||
rootItem.value = value | ||
rootItem.value_type = type(value) | ||
|
||
return rootItem | ||
|
||
|
||
class JsonModel(QAbstractItemModel): | ||
""" An editable model of Json data """ | ||
|
||
def __init__(self, parent: Optional[QObject] = None): | ||
super().__init__(parent) | ||
|
||
self._rootItem = TreeItem() | ||
self._headers = ("key", "value") | ||
|
||
def clear(self) -> None: | ||
""" Clear data from the model """ | ||
self.load({}) | ||
|
||
def load(self, document: dict) -> bool: | ||
"""Load model from a nested dictionary returned by json.loads() | ||
Arguments: | ||
document (dict): JSON-compatible dictionary | ||
""" | ||
|
||
assert isinstance( | ||
document, (dict, list, tuple) | ||
), "`document` must be of dict, list or tuple, " f"not {type(document)}" | ||
|
||
self.beginResetModel() | ||
|
||
self._rootItem = TreeItem.load(document) | ||
self._rootItem.value_type = type(document) | ||
|
||
self.endResetModel() | ||
|
||
return True | ||
|
||
def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> Any: #type: ignore[override] | ||
"""Override from QAbstractItemModel | ||
Return data from a json item according index and role | ||
""" | ||
if not index.isValid(): | ||
return None | ||
|
||
item = index.internalPointer() | ||
|
||
if role == Qt.DisplayRole: | ||
if index.column() == 0: | ||
return item.key | ||
|
||
if index.column() == 1: | ||
return item.value | ||
|
||
elif role == Qt.EditRole: | ||
if index.column() == 1: | ||
return item.value | ||
|
||
def setData(self, index: QModelIndex, value: Any, role: Qt.ItemDataRole) -> bool: #type: ignore[override] | ||
"""Override from QAbstractItemModel | ||
Set json item according index and role | ||
Args: | ||
index (QModelIndex) | ||
value (Any) | ||
role (Qt.ItemDataRole) | ||
""" | ||
if role == Qt.EditRole: | ||
if index.column() == 1: | ||
item = index.internalPointer() | ||
item.value = str(value) | ||
|
||
if __binding__ in ("PySide", "PyQt4"): # type: ignore[name-defined] | ||
self.dataChanged.emit(index, index) | ||
else: | ||
self.dataChanged.emit(index, index, [Qt.EditRole]) | ||
|
||
return True | ||
|
||
return False | ||
|
||
def headerData( #type: ignore[override] | ||
self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole | ||
) -> Optional[str]: | ||
"""Override from QAbstractItemModel | ||
For the JsonModel, it returns only data for columns (orientation = Horizontal) | ||
""" | ||
if role != Qt.DisplayRole: | ||
return None | ||
|
||
if orientation == Qt.Horizontal: | ||
return self._headers[section] | ||
|
||
return None | ||
|
||
def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex: | ||
"""Override from QAbstractItemModel | ||
Return index according row, column and parent | ||
""" | ||
if not self.hasIndex(row, column, parent): | ||
return QModelIndex() | ||
|
||
if not parent.isValid(): | ||
parentItem = self._rootItem | ||
else: | ||
parentItem = parent.internalPointer() | ||
|
||
childItem = parentItem.child(row) | ||
if childItem: | ||
return self.createIndex(row, column, childItem) | ||
else: | ||
return QModelIndex() | ||
|
||
def parent(self, index: QModelIndex) -> QModelIndex: #type: ignore[override] | ||
"""Override from QAbstractItemModel | ||
Return parent index of index | ||
""" | ||
|
||
if not index.isValid(): | ||
return QModelIndex() | ||
|
||
childItem = index.internalPointer() | ||
parentItem = childItem.parent() | ||
|
||
if parentItem == self._rootItem: | ||
return QModelIndex() | ||
|
||
return self.createIndex(parentItem.row(), 0, parentItem) | ||
|
||
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int: | ||
"""Override from QAbstractItemModel | ||
Return row count from parent index | ||
""" | ||
if parent.column() > 0: | ||
return 0 | ||
|
||
if not parent.isValid(): | ||
parentItem = self._rootItem | ||
else: | ||
parentItem = parent.internalPointer() | ||
|
||
return parentItem.childCount() | ||
|
||
def columnCount(self, parent: QModelIndex = QModelIndex()) -> int: | ||
"""Override from QAbstractItemModel | ||
Return column number. For the model, it always return 2 columns | ||
""" | ||
return 2 | ||
|
||
def flags(self, index: QModelIndex) -> Qt.ItemFlags: | ||
"""Override from QAbstractItemModel | ||
Return flags of index | ||
""" | ||
flags = super(JsonModel, self).flags(index) | ||
|
||
if index.column() == 1: | ||
return Qt.ItemIsEditable | flags | ||
else: | ||
return flags | ||
|
||
def to_json(self, item: Optional["TreeItem"] = None) -> Any: | ||
|
||
if item is None: | ||
item = self._rootItem | ||
|
||
nchild = item.childCount() | ||
|
||
if item.value_type is dict: | ||
document = {} | ||
for i in range(nchild): | ||
ch = item.child(i) | ||
document[ch.key] = self.to_json(ch) | ||
return document | ||
|
||
elif item.value_type == list: | ||
document_list = [] | ||
for i in range(nchild): | ||
ch = item.child(i) | ||
document_list.append(self.to_json(ch)) | ||
return document_list | ||
|
||
else: | ||
return item.value | ||
|
||
|
||
class JsonTreeView(QTreeView): | ||
""" | ||
Basic treeview. Only difference is a path variable to store the path of the file this view is showing. | ||
:param path: The path of the file this view is showing. | ||
""" | ||
|
||
def __init__(self, path: Path, *args: Any, **kwargs: Any): | ||
super().__init__(*args, **kwargs) | ||
self.path = path |
Oops, something went wrong.