Skip to content

Commit

Permalink
Merge pull request #354 from pfafflabatuiuc/monitr_revamp
Browse files Browse the repository at this point in the history
New Monitr
  • Loading branch information
wpfff committed Dec 6, 2022
2 parents ff79998 + 9ee78c9 commit 72a8f4c
Show file tree
Hide file tree
Showing 14 changed files with 4,682 additions and 94 deletions.
316 changes: 316 additions & 0 deletions plottr/apps/json_viewer.py
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

0 comments on commit 72a8f4c

Please sign in to comment.