[app] move code to nixview package
This commit is contained in:
259
nixview/central_widget.py
Normal file
259
nixview/central_widget.py
Normal file
@@ -0,0 +1,259 @@
|
||||
import os
|
||||
from PyQt5.QtWidgets import QComboBox, QFrame, QGroupBox, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QSplitter, QStackedLayout, QAbstractItemView, QTextEdit, QVBoxLayout, QWidget, QGridLayout
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtCore import QItemSelectionModel, Qt, QSettings, pyqtSignal
|
||||
|
||||
from file_handler import FileHandler, ItemDescriptor
|
||||
from plot_screen import PlotScreen
|
||||
import communicator as comm
|
||||
import constants as cnst
|
||||
from tree_model import NixTreeView, TreeModel, TreeType
|
||||
|
||||
class CentralWidget(QWidget):
|
||||
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._splash = SplashScreen()
|
||||
self._file_view = FileView(self)
|
||||
self._plot_screen = PlotScreen(self)
|
||||
self._plot_screen.close_signal.connect(self.on_plot_close)
|
||||
self._stack = QStackedLayout(self)
|
||||
self._stack.addWidget(self._splash)
|
||||
self._stack.addWidget(self._file_view)
|
||||
self._stack.addWidget(self._plot_screen)
|
||||
self.setLayout(self._stack)
|
||||
|
||||
def show_file_content(self):
|
||||
self._stack.setCurrentIndex(1)
|
||||
self._file_view.update()
|
||||
|
||||
def plot_item(self, item):
|
||||
self._stack.setCurrentIndex(2)
|
||||
self._plot_screen.plot(item)
|
||||
|
||||
def on_plot_close(self):
|
||||
self._stack.setCurrentIndex(1)
|
||||
|
||||
def reset(self):
|
||||
self._file_view.reset()
|
||||
self._splash.reset()
|
||||
self._stack.setCurrentIndex(0)
|
||||
|
||||
|
||||
class FileView(QWidget):
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self._file_handler = FileHandler()
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
self.setLayout(vbox)
|
||||
|
||||
main_splitter = QSplitter(Qt.Vertical)
|
||||
self.layout().addWidget(main_splitter)
|
||||
|
||||
self._info = EntityInfo(self)
|
||||
main_splitter.addWidget(self._info)
|
||||
|
||||
self._data_tree = NixTreeView(self)
|
||||
|
||||
self._tree_type_combo = QComboBox()
|
||||
self._tree_type_combo.adjustSize()
|
||||
self._tree_type_combo.addItems([TreeType.Data.value, TreeType.Full.value, TreeType.Metadata.value])
|
||||
self._tree_type_combo.currentTextChanged.connect(self.update)
|
||||
|
||||
hbox = QHBoxLayout()
|
||||
hbox.addWidget(QLabel("Tree type:"))
|
||||
hbox.addWidget(self._tree_type_combo)
|
||||
hbox.addStretch()
|
||||
data_group = QGroupBox("Data")
|
||||
data_vbox = QVBoxLayout()
|
||||
data_vbox.setContentsMargins(1, 10, 1, 1)
|
||||
|
||||
data_vbox.addLayout(hbox)
|
||||
data_vbox.addWidget(self._data_tree)
|
||||
data_group.setLayout(data_vbox)
|
||||
|
||||
main_splitter.addWidget(data_group)
|
||||
main_splitter.setSizes([200, 600])
|
||||
vbox.addWidget(main_splitter)
|
||||
|
||||
def dataTreeSelection(self, current_index, last_index):
|
||||
if not current_index.isValid():
|
||||
return
|
||||
item = current_index.internalPointer()
|
||||
comm.communicator.item_selected.emit(item)
|
||||
self._info.setEntityInfo(item.node_descriptor)
|
||||
|
||||
def update(self):
|
||||
tt = TreeType.Data
|
||||
if self._tree_type_combo.currentText() == TreeType.Data.value:
|
||||
tt = TreeType.Data
|
||||
elif self._tree_type_combo.currentText() == TreeType.Full.value:
|
||||
tt = TreeType.Full
|
||||
elif self._tree_type_combo.currentText() == TreeType.Metadata.value:
|
||||
tt = TreeType.Metadata
|
||||
self._info.setEntityInfo(None)
|
||||
data_model = TreeModel(self._file_handler, tt)
|
||||
self._data_tree.setModel(data_model)
|
||||
selection_model = QItemSelectionModel(data_model)
|
||||
self._data_tree.setSelectionModel(selection_model)
|
||||
selection_model.currentChanged.connect(self.dataTreeSelection)
|
||||
for i in range(data_model.columnCount(None)):
|
||||
self._data_tree.resizeColumnToContents(i)
|
||||
self._info.setFileInfo(self._file_handler.file_descriptor)
|
||||
|
||||
def reset(self):
|
||||
pass
|
||||
|
||||
|
||||
class EntityInfo(QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent=parent)
|
||||
self._file_handler = FileHandler()
|
||||
self.setLayout(QHBoxLayout())
|
||||
|
||||
self._metadata_tree = NixTreeView()
|
||||
|
||||
mdata_grp = QGroupBox("Metadata")
|
||||
mdata_grp.setLayout(QVBoxLayout())
|
||||
mdata_grp.layout().setContentsMargins(1, 10, 1, 1)
|
||||
mdata_grp.layout().addWidget(self._metadata_tree)
|
||||
|
||||
file_info_grp = QGroupBox("File info")
|
||||
file_info_grp.setLayout(QVBoxLayout())
|
||||
file_info_grp.layout().setContentsMargins(1, 10, 1, 1)
|
||||
self._file_info = QTextEdit("File information")
|
||||
self._file_info.setEnabled(True)
|
||||
self._file_info.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse)
|
||||
self._file_info.setFrameShape(QFrame.NoFrame)
|
||||
self._file_info.setLineWrapMode(QTextEdit.WidgetWidth)
|
||||
file_info_grp.layout().addWidget(self._file_info)
|
||||
|
||||
entity_info_grp = QGroupBox("Entity info")
|
||||
entity_info_grp.setLayout(QVBoxLayout())
|
||||
entity_info_grp.layout().setContentsMargins(1, 10, 1, 1)
|
||||
self._entity_info = QTextEdit("Entity information")
|
||||
self._file_info.setEnabled(True)
|
||||
self._file_info.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse)
|
||||
self._file_info.setFrameShape(QFrame.NoFrame)
|
||||
self._file_info.setLineWrapMode(QTextEdit.WidgetWidth)
|
||||
entity_info_grp.layout().addWidget(self._entity_info)
|
||||
|
||||
self._splitter = QSplitter(Qt.Horizontal)
|
||||
self._splitter.addWidget(file_info_grp)
|
||||
self._splitter.addWidget(entity_info_grp)
|
||||
self._splitter.addWidget(mdata_grp)
|
||||
self._splitter.setSizes([200, 400, 0])
|
||||
self._splitter.setStretchFactor(0, 0)
|
||||
self._splitter.setStretchFactor(1, 1)
|
||||
self._splitter.setStretchFactor(2, 1)
|
||||
|
||||
self.layout().addWidget(self._splitter)
|
||||
|
||||
|
||||
def setFileInfo(self, file_info):
|
||||
if file_info is not None:
|
||||
self._file_info.setText(file_info.toHtml())
|
||||
|
||||
def setEntityInfo(self, entity_info):
|
||||
if entity_info is None or not isinstance(entity_info, ItemDescriptor):
|
||||
self._splitter.setSizes([200, 400, 0])
|
||||
self._entity_info.setText("")
|
||||
self._metadata_tree.setModel(None)
|
||||
return
|
||||
|
||||
if entity_info.metadata_id is not None:
|
||||
self._splitter.setSizes([200, 400, 400])
|
||||
else:
|
||||
self._splitter.setSizes([200, 400, 0])
|
||||
self._entity_info.setText(entity_info.to_html())
|
||||
metadata_model = TreeModel(self._file_handler, TreeType.Metadata, root_section_id=entity_info.metadata_id)
|
||||
self._metadata_tree.setModel(metadata_model)
|
||||
for i in range(metadata_model.columnCount(None)):
|
||||
self._metadata_tree.resizeColumnToContents(i)
|
||||
|
||||
|
||||
class SplashScreen(QWidget):
|
||||
keyPressed = pyqtSignal(int)
|
||||
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
#self.setStyleSheet("background-color: white;")
|
||||
|
||||
layout = QGridLayout()
|
||||
layout.setColumnStretch(0, 2)
|
||||
layout.setColumnStretch(1, 1)
|
||||
layout.setColumnStretch(2, 1)
|
||||
layout.setColumnStretch(3, 1)
|
||||
layout.setColumnStretch(4, 2)
|
||||
|
||||
layout.setRowStretch(0, 1)
|
||||
layout.setRowStretch(1, 0)
|
||||
layout.setRowStretch(2, 1)
|
||||
layout.setRowStretch(3, 2)
|
||||
self.setLayout(layout)
|
||||
|
||||
label = QLabel()
|
||||
label.setPixmap(QPixmap("./icons/nixview_transparent.png"))
|
||||
label.setMaximumWidth(300)
|
||||
label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(label, 1, 1, 1, 3, Qt.AlignCenter)
|
||||
|
||||
frame = QFrame()
|
||||
l = QVBoxLayout()
|
||||
l.addWidget(QLabel("Recently opened files:"))
|
||||
self._file_list = QListWidget(self)
|
||||
self._file_list.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self._file_list.itemClicked.connect(self._on_file_clicked)
|
||||
self._file_list.setFrameShape(QFrame.NoFrame)
|
||||
self.keyPressed.connect(self._on_key_pressed)
|
||||
l.addWidget(self._file_list)
|
||||
frame.setLayout(l)
|
||||
layout.addWidget(frame, 3, 1, 1, 3)
|
||||
self._file_map = {}
|
||||
self._read_recent_files()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
super(SplashScreen, self).keyPressEvent(event)
|
||||
if event.key() == Qt.Key_Return:
|
||||
self.keyPressed.emit(event.key())
|
||||
|
||||
def _create_short_filename(self, original, index, max_len=40):
|
||||
short = original
|
||||
parts = original.split(os.sep)
|
||||
if len(parts) == 1:
|
||||
short = "%i: %s" % (index, short[:max_len])
|
||||
else:
|
||||
post = parts[-1]
|
||||
if len(post) > max_len - 4:
|
||||
post = post[:max_len - 4]
|
||||
short = str("%i: " % index) + "... " + post
|
||||
else:
|
||||
short = str("%i: " % index) + " ... ".join([original[:max_len - len(post) - 4], post])
|
||||
return short
|
||||
|
||||
def _read_recent_files(self):
|
||||
settings = QSettings(cnst.organization, cnst.application)
|
||||
filenames = settings.value(cnst.settings_recent_files_key, [])
|
||||
del settings
|
||||
|
||||
for i, f in enumerate(filenames):
|
||||
shortname = self._create_short_filename(f, i + 1, max_len=38)
|
||||
self._file_map[shortname] = f
|
||||
item = QListWidgetItem(shortname)
|
||||
item.setToolTip(f)
|
||||
self._file_list.addItem(item)
|
||||
|
||||
def reset(self):
|
||||
self._file_list.clear()
|
||||
self._read_recent_files()
|
||||
|
||||
def _on_file_clicked(self, item):
|
||||
comm.communicator.open_recent.emit(self._file_map[item.text()])
|
||||
|
||||
def _on_key_pressed(self, key):
|
||||
item = self._file_list.currentItem()
|
||||
if item is not None and key == Qt.Key_Return:
|
||||
comm.communicator.open_recent.emit(self._file_map[item.text()])
|
||||
12
nixview/communicator.py
Normal file
12
nixview/communicator.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from tree_model import NixTreeItem
|
||||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
|
||||
class Communicate(QObject):
|
||||
"""
|
||||
Small class for across app communication. Defines signals, to which other classes can connect.
|
||||
"""
|
||||
open_recent = pyqtSignal(str)
|
||||
|
||||
item_selected = pyqtSignal(NixTreeItem)
|
||||
|
||||
communicator = Communicate()
|
||||
7
nixview/constants.py
Normal file
7
nixview/constants.py
Normal file
@@ -0,0 +1,7 @@
|
||||
organization = "nixio"
|
||||
application = "nixview"
|
||||
version = 0.1
|
||||
|
||||
settings_recent_files_key = "/".join([organization, application, "recent_files"])
|
||||
settings_recent_file_max_count_key = "/".join([organization, application, "recent_files_max_count"])
|
||||
settings_recent_file_max_count = 10
|
||||
357
nixview/file_handler.py
Normal file
357
nixview/file_handler.py
Normal file
@@ -0,0 +1,357 @@
|
||||
import os
|
||||
import nixio as nix
|
||||
from enum import Enum
|
||||
import datetime as dt
|
||||
|
||||
|
||||
class ItemDescriptor():
|
||||
def __init__(self, name=None, id=None, type=None, value=None, definition=None, block_id=None, entity_type=None, shape=None, metadata=None, data_type=None, source_id=None, created_at=None, updated_at=None) -> None:
|
||||
super().__init__()
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.id = id
|
||||
self.block_id= block_id
|
||||
self.definition = definition
|
||||
self.value = value
|
||||
self.entity_type = entity_type
|
||||
self.data_type = data_type
|
||||
self.shape = shape
|
||||
self.metadata_id = metadata
|
||||
self.source_id = source_id
|
||||
self.created_at = None
|
||||
self.updated_at = None
|
||||
|
||||
def to_html(self):
|
||||
descr = "<html><h4>%s: %s</h4>" % (self.type, self.name)
|
||||
descr += "<ol style='list-style-type:none'>"
|
||||
descr += "<li><small><b>id:</b> %s</small></li>" % (self.id)
|
||||
descr += "<li><small><b>entity type:</b> %s</small></li>" % (self.entity_type.value)
|
||||
descr += "<li><small><b>data type:</b> %s</small></li>" % (str(self.data_type))
|
||||
descr += "<li><small><b>data shape:</b> %s</small></li>" % (str(self.shape))
|
||||
descr += "<hr>"
|
||||
descr += "<p><small><b>definition:</b> %s</small></p>" % (self.definition)
|
||||
descr += "<hr>"
|
||||
descr += "<li><small><b>metadata id:</b> %s</small></li>" % (self.metadata_id)
|
||||
descr += "<li><small><b>source id:</b> %s</small></li>" % (self.source_id)
|
||||
descr += "<hr>"
|
||||
descr += "<li><small><b>created at:</b> %s</small></li>" % (str(dt.datetime.fromtimestamp(self.created_at)) if self.created_at else "")
|
||||
descr += "<li><small><b>updated at:</b> %s</small></li>" % (str(dt.datetime.fromtimestamp(self.updated_at)) if self.updated_at else "")
|
||||
descr += "</ol>"
|
||||
|
||||
descr += "</html>"
|
||||
return descr
|
||||
|
||||
|
||||
class FileDescriptor():
|
||||
def __init__(self, filename, format, version, created_at, updated_at, size) -> None:
|
||||
super().__init__()
|
||||
self.name = filename
|
||||
self.size = None
|
||||
self.format = format
|
||||
self.version = version
|
||||
self.created_at = created_at
|
||||
self.updated_at = updated_at
|
||||
self.size = size
|
||||
self.block_count = 0
|
||||
self.data_array_count = 0
|
||||
self.tag_count = 0
|
||||
self.group_count = 0
|
||||
self.data_frame_count = 0
|
||||
|
||||
def toHtml(self):
|
||||
def namAndPath(filename):
|
||||
parts = filename.split(os.sep)
|
||||
name = parts[-1]
|
||||
path = ""
|
||||
if len(parts) > 1:
|
||||
path = os.sep.join(parts[:-1])
|
||||
return name, path
|
||||
|
||||
name, path = namAndPath(self.name)
|
||||
descr = "<html><h4>%s</h4>" % name
|
||||
descr += "<ol style='list-style-type:none'>"
|
||||
descr += "<li><small><b>location:</b> %s</small></li>" % (path if len(path) > 1 else ".")
|
||||
descr += "<li><small><b>format:</b> %s</small></li>" % (self.format)
|
||||
descr += "<li><small><b>nix format version:</b> %s</small></li>" % (str(self.version))
|
||||
descr += "<li><small><b>file size:</b> %.2f MB</small></li>" % (self.size)
|
||||
descr += "<hr>"
|
||||
descr += "<li>File contents</li>"
|
||||
descr += "<li><small><b>blocks:</b> %i</small></li>" % self.block_count
|
||||
descr += "<li><small><b>groups:</b> %i</small></li>" % self.group_count
|
||||
descr += "<li><small><b>data arrays:</b> %i</small></li>" % self.data_array_count
|
||||
descr += "<li><small><b>data frames:</b> %i</small></li>" % self.data_frame_count
|
||||
descr += "<li><small><b>tags:</b> %i</small></li>" % self.tag_count
|
||||
descr += "<hr>"
|
||||
descr += "<li><small><b>created at:</b> %s</small></li>" % (str(dt.datetime.fromtimestamp(self.created_at)))
|
||||
descr += "<li><small><b>updated at:</b> %s</small></li>" % (str(dt.datetime.fromtimestamp(self.updated_at)))
|
||||
descr += "</ol>"
|
||||
descr += "</html>"
|
||||
return descr
|
||||
|
||||
|
||||
class NodeType(Enum):
|
||||
Root = "root"
|
||||
Section = "Section"
|
||||
Block = "Block"
|
||||
DataArray = "Data Array"
|
||||
DataFrame = "Data Frame"
|
||||
Property = "Property"
|
||||
Dimension = "Dimension"
|
||||
Source = "Source"
|
||||
Tag = "Tag"
|
||||
MultiTag = "Multi Tag"
|
||||
Group = "Group"
|
||||
Feature="Feature"
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
_instances = {}
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
return cls._instances[cls]
|
||||
|
||||
|
||||
class EntityBuffer():
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._buffer = {}
|
||||
|
||||
def put(self, entity):
|
||||
if not hasattr(entity, "id"):
|
||||
return
|
||||
id = entity.id
|
||||
if id not in self._buffer.keys():
|
||||
self._buffer[id] = entity
|
||||
|
||||
def has(self, id):
|
||||
return id in self._buffer.keys()
|
||||
|
||||
def get(self, id):
|
||||
if self.has(id):
|
||||
return self._buffer[id]
|
||||
else:
|
||||
return None
|
||||
|
||||
def clear(self):
|
||||
self._buffer.clear()
|
||||
|
||||
|
||||
class FileHandler(metaclass=Singleton):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._filename = None
|
||||
self._nix_file = None
|
||||
self._file_requests = []
|
||||
self._entity_buffer = EntityBuffer()
|
||||
self._file_descriptor = None
|
||||
self._file_version = None
|
||||
|
||||
def open(self, filename):
|
||||
self.close()
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return False, "File %s could not be found!" % filename
|
||||
try:
|
||||
self._nix_file = nix.File.open(filename, nix.FileMode.ReadOnly)
|
||||
self._filename = filename
|
||||
self._file_descriptor = FileDescriptor(self.filename, self._nix_file.format, self._nix_file.version,
|
||||
self._nix_file.created_at, self._nix_file.updated_at, os.path.getsize(self.filename)/1e+6)
|
||||
self.file_descriptor.block_count = len(self._nix_file.blocks)
|
||||
for b in self._nix_file.blocks:
|
||||
self.file_descriptor.data_array_count += len(b.data_arrays)
|
||||
self.file_descriptor.group_count += len(b.groups)
|
||||
self.file_descriptor.tag_count += len(b.tags)
|
||||
self.file_descriptor.tag_count += len(b.multi_tags)
|
||||
if hasattr(b, "data_frames"):
|
||||
self.file_descriptor.data_frame_count += len(b.data_frames)
|
||||
self._file_version = self._nix_file.version
|
||||
return True, "Successfully opened file %s." % filename.split(os.sep)[-1]
|
||||
except RuntimeError as e:
|
||||
return False, "Failed to open file %s! \n Error message is: %s" % (filename, e)
|
||||
except OSError as e:
|
||||
return False, "Failed to open file %s! \n Error message is: %s\n Probably no nix file?!" % (filename, e)
|
||||
|
||||
def close(self):
|
||||
if self._nix_file is not None and self._nix_file.is_open():
|
||||
self._nix_file.close()
|
||||
self._nix_file = None
|
||||
self._file_requests = []
|
||||
self._entity_buffer.clear()
|
||||
self._file_descriptor = None
|
||||
self._file_version = None
|
||||
|
||||
@property
|
||||
def file_descriptor(self):
|
||||
return self._file_descriptor
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
return self._nix_file is not None and self._nix_file.is_open()
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return self._filename
|
||||
|
||||
def request_section_descriptor(self, id):
|
||||
fs = self._entity_buffer.get(id)
|
||||
if fs is None:
|
||||
found_section = self._nix_file.find_sections(lambda s: s.id == id)
|
||||
fs = found_section[0] if len(found_section) > 0 else None
|
||||
if fs is None:
|
||||
return None
|
||||
else:
|
||||
item = ItemDescriptor(fs.name, fs.id, fs.type, definition=fs.definition, entity_type=NodeType.Section)
|
||||
return item
|
||||
|
||||
def request_metadata(self, root_id=None, depth=1):
|
||||
"""[summary]
|
||||
|
||||
Args:
|
||||
root_id ([type], optional): [description]. Defaults to None.
|
||||
depth (int, optional): [description]. Defaults to 1.
|
||||
"""
|
||||
def get_subsections(section):
|
||||
sub_sections = []
|
||||
for s in section.sections:
|
||||
self._entity_buffer.put(s)
|
||||
sub_sections.append(ItemDescriptor(s.name, s.id, s.type, definition=s.definition, entity_type=NodeType.Section))
|
||||
return sub_sections
|
||||
|
||||
def get_properties(section):
|
||||
props = []
|
||||
for p in section.props:
|
||||
value = ""
|
||||
if self._file_version < (1,1,1):
|
||||
vals = p.values
|
||||
if len(vals) > 1:
|
||||
value += "["
|
||||
value += ",".join(map(str, [v.value for v in vals]))
|
||||
value += "]"
|
||||
else:
|
||||
value = str(vals[0].value)
|
||||
else:
|
||||
vals = p.values
|
||||
value += "["
|
||||
value += ",".join(map(str, [v.value for v in vals]))
|
||||
value += "]"
|
||||
if p.unit is not None:
|
||||
value += " " + p.unit
|
||||
props.append(ItemDescriptor(p.name, p.id, value=value, entity_type=NodeType.Property))
|
||||
return props
|
||||
|
||||
sections = []
|
||||
properties = []
|
||||
if root_id is None:
|
||||
sections = get_subsections(self._nix_file)
|
||||
else:
|
||||
fs = self._entity_buffer.get(root_id)
|
||||
if fs is None:
|
||||
found_section = self._nix_file.find_sections(lambda s: s.id == root_id)
|
||||
fs = found_section[0] if len(found_section) > 0 else None
|
||||
if fs is None:
|
||||
return sections, properties
|
||||
sections.extend(get_subsections(fs))
|
||||
properties.extend(get_properties(fs))
|
||||
return sections, properties
|
||||
|
||||
def _entity_info(self, entities, block_id, entity_type):
|
||||
infos = []
|
||||
for e in entities:
|
||||
self._entity_buffer.put(e)
|
||||
itd = ItemDescriptor(e.name, e.id, e.type, definition=e.definition, entity_type=entity_type, block_id=block_id)
|
||||
section = e.metadata if hasattr(e, "metadata") else None
|
||||
itd.metadata_id = section.id if section is not None else None
|
||||
itd.data_type = e.data_type if hasattr(e, "data_type") else None
|
||||
itd.created_at = e.created_at if hasattr(e, "created_at") else None
|
||||
itd.updated_at = e.updated_at if hasattr(e, "updated") else None
|
||||
itd.shape = e.shape if hasattr(e, "shape") else None
|
||||
src = e.source if hasattr(e, "source") else None
|
||||
itd.source_id = src.id if src is not None else None
|
||||
infos.append(itd)
|
||||
# TODO set the value to something meaningful for the various entity types
|
||||
return infos
|
||||
|
||||
def request_blocks(self):
|
||||
return self._entity_info(self._nix_file.blocks, None, NodeType.Block)
|
||||
|
||||
def get_block(self, id):
|
||||
b = b = self._entity_buffer.get(id)
|
||||
if not b:
|
||||
b = self._nix_file.blocks[id]
|
||||
return b
|
||||
|
||||
def request_data_arrays(self, block_id):
|
||||
b = self.get_block(block_id)
|
||||
return self._entity_info(b.data_arrays, block_id, NodeType.DataArray)
|
||||
|
||||
def request_tags(self, block_id):
|
||||
b = self.get_block(block_id)
|
||||
tags = self._entity_info(b.tags, block_id, NodeType.Tag)
|
||||
tags.extend(self._entity_info(b.multi_tags, block_id, NodeType.MultiTag))
|
||||
return tags
|
||||
|
||||
def request_references(self, block_id, tag_id, is_mtag):
|
||||
b = self.get_block(block_id)
|
||||
t = self._entity_buffer.get(tag_id)
|
||||
if t is None:
|
||||
if is_mtag:
|
||||
t = b.multi_tags[tag_id]
|
||||
else:
|
||||
t = b.tags[tag_id]
|
||||
return self._entity_info(t.references, block_id, NodeType.DataArray)
|
||||
|
||||
def request_features(self, block_id, tag_id, is_mtag):
|
||||
b = self.get_block(block_id)
|
||||
t = self._entity_buffer.get(tag_id)
|
||||
if t is None:
|
||||
if is_mtag:
|
||||
t = b.multi_tags[tag_id]
|
||||
else:
|
||||
t = b.tags[tag_id]
|
||||
feats = []
|
||||
for f in t.features:
|
||||
itd = ItemDescriptor(f.data.name, f.id, f.link_type, definition=f.data.definition, block_id=block_id, entity_type=NodeType.Feature)
|
||||
feats.append(itd)
|
||||
return feats
|
||||
|
||||
def request_dimensions(self, block_id, array_id):
|
||||
da = self._entity_buffer.get(array_id)
|
||||
if da is None:
|
||||
b = self.get_block(block_id)
|
||||
da = b.data_arrays[array_id]
|
||||
dimensions = []
|
||||
for i, d in enumerate(da.dimensions):
|
||||
dim_name = "%i. dim: %s" % (i+1, d.label if hasattr(d, "label") else "")
|
||||
dim_type= "%s %s" % (d.dimension_type, "dimension")
|
||||
dimensions.append(ItemDescriptor(dim_name, type=dim_type, entity_type=NodeType.Dimension, block_id=block_id))
|
||||
return dimensions
|
||||
|
||||
def request_data_frames(self, block_id):
|
||||
if self._nix_file.version[1] >= 2:
|
||||
b = self.get_block(block_id)
|
||||
return self._entity_info(b.data_frames, block_id, NodeType.DataFrame)
|
||||
return []
|
||||
|
||||
def request_groups(self, block_id):
|
||||
b = self.get_block(block_id)
|
||||
return self._entity_info(b.groups, block_id, NodeType.Group)
|
||||
|
||||
def request_sources(self, block_id, parent_source_id=None):
|
||||
def get_subsources(src):
|
||||
sub_sources = []
|
||||
for s in src.sources:
|
||||
self._entity_buffer.put(s)
|
||||
sub_sources.append(ItemDescriptor(s.name, s.id, s.type, definition=s.definition, entity_type=NodeType.Source))
|
||||
return sub_sources
|
||||
b = self.get_block(block_id)
|
||||
if parent_source_id is None:
|
||||
return self._entity_info(b.sources, block_id, NodeType.Source)
|
||||
else:
|
||||
srcs = b.find_sources(lambda s: s.id == parent_source_id)
|
||||
sources = []
|
||||
for src in srcs:
|
||||
sources.extend(get_subsources(src))
|
||||
return sources
|
||||
147
nixview/main_window.py
Normal file
147
nixview/main_window.py
Normal file
@@ -0,0 +1,147 @@
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QWidget, QFileDialog, QMainWindow, QMenuBar, QToolBar, QAction, QStatusBar, QSizePolicy
|
||||
from PyQt5.QtGui import QIcon, QKeySequence
|
||||
from PyQt5.QtCore import QSize, QSettings, Qt
|
||||
|
||||
from file_handler import FileHandler, NodeType
|
||||
import constants as cnst
|
||||
import communicator as comm
|
||||
from central_widget import CentralWidget
|
||||
|
||||
class NixView(QMainWindow):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NixView, self).__init__(*args, **kwargs)
|
||||
self._current_item = None
|
||||
self.setWindowTitle("NixView")
|
||||
self.setWindowIcon(QIcon('./icons/nixview.icns'))
|
||||
|
||||
self._file_handler = FileHandler()
|
||||
self.setStatusBar(QStatusBar(self))
|
||||
self.setMenuBar(QMenuBar(self))
|
||||
self.create_actions()
|
||||
|
||||
self._cw = CentralWidget(self)
|
||||
self.setCentralWidget(self._cw)
|
||||
|
||||
comm.communicator.open_recent.connect(self.on_open_recent)
|
||||
comm.communicator.item_selected.connect(self.on_item_selected)
|
||||
self.show()
|
||||
|
||||
def on_open_recent(self, event):
|
||||
self.open_file(event)
|
||||
|
||||
def on_item_selected(self, current):
|
||||
self._current_item = current
|
||||
current_item_type = current._node_descriptor.entity_type
|
||||
enable = current_item_type == NodeType.MultiTag or current_item_type == NodeType.DataArray or current_item_type == NodeType.Tag
|
||||
self._plot_action.setEnabled(enable)
|
||||
self._table_action.setEnabled(enable)
|
||||
|
||||
def create_actions(self):
|
||||
self._file_open_action = QAction(QIcon("./icons/nix_open.png"), "Open", self)
|
||||
self._file_open_action.setStatusTip("Open nix file")
|
||||
self._file_open_action.setShortcut(QKeySequence("Ctrl+o"))
|
||||
self._file_open_action.triggered.connect(self.on_file_open)
|
||||
|
||||
self._file_close_action = QAction(QIcon("./icons/nix_close.png"), "Close", self)
|
||||
self._file_close_action.setStatusTip("Close current nix file")
|
||||
self._file_close_action.setShortcut(QKeySequence("Ctrl+w"))
|
||||
self._file_close_action.setEnabled(False)
|
||||
self._file_close_action.triggered.connect(self.on_file_close)
|
||||
|
||||
self._quit_action = QAction(QIcon("./icons/quit.png"), "Quit", self)
|
||||
self._quit_action.setStatusTip("Close current file and quit")
|
||||
self._quit_action.setShortcut(QKeySequence("Ctrl+q"))
|
||||
self._quit_action.triggered.connect(self.on_quit)
|
||||
|
||||
self._plot_action = QAction(QIcon("./icons/nix_plot.png"), "Plot", self)
|
||||
self._plot_action.setStatusTip("Plot currently selected entity")
|
||||
self._plot_action.setShortcut(QKeySequence("Ctrl+p"))
|
||||
self._plot_action.setEnabled(False)
|
||||
self._plot_action.triggered.connect(self.on_item_plot)
|
||||
|
||||
self._table_action = QAction(QIcon("./icons/nix_table.png"), "Show table", self)
|
||||
self._table_action.setStatusTip("Show data as table")
|
||||
self._table_action.setShortcut(QKeySequence("Ctrl+t"))
|
||||
self._table_action.setEnabled(False)
|
||||
# self._table_action.triggered.connect(self.on_file_close)
|
||||
self.create_toolbar()
|
||||
self.create_menu()
|
||||
|
||||
def create_toolbar(self):
|
||||
self._toolbar = QToolBar("My main toolbar")
|
||||
#self._toolbar.setStyleSheet("QToolButton:!hover {background-color:none}")
|
||||
self._toolbar.setAllowedAreas(Qt.LeftToolBarArea | Qt.TopToolBarArea)
|
||||
self._toolbar.setIconSize(QSize(32, 32))
|
||||
|
||||
self._toolbar.addAction(self._file_open_action)
|
||||
self._toolbar.addAction(self._file_close_action)
|
||||
self._toolbar.addSeparator()
|
||||
self._toolbar.addAction(self._plot_action)
|
||||
self._toolbar.addAction(self._table_action)
|
||||
|
||||
empty = QWidget()
|
||||
empty.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self._toolbar.addWidget(empty)
|
||||
self._toolbar.addSeparator()
|
||||
self._toolbar.addAction(self._quit_action)
|
||||
|
||||
self.addToolBar(Qt.LeftToolBarArea, self._toolbar)
|
||||
|
||||
def create_menu(self):
|
||||
menu = self.menuBar()
|
||||
file_menu = menu.addMenu("&File")
|
||||
file_menu.addAction(self._file_open_action)
|
||||
file_menu.addAction(self._file_close_action)
|
||||
file_menu.addSeparator()
|
||||
file_menu.addAction(self._quit_action)
|
||||
|
||||
plot_menu = menu.addMenu("&Plot")
|
||||
plot_menu.addAction(self._plot_action)
|
||||
plot_menu.addAction(self._table_action)
|
||||
|
||||
self.setMenuBar(menu)
|
||||
|
||||
def _update_recent_files(self, filename):
|
||||
settings = QSettings(cnst.organization, cnst.application)
|
||||
recent_file_max_count = settings.value(cnst.settings_recent_file_max_count_key, 10, type=int)
|
||||
filenames = settings.value(cnst.settings_recent_files_key, [])
|
||||
new_filenames = [filename]
|
||||
if filename in filenames:
|
||||
del filenames[filenames.index(filename)]
|
||||
new_filenames.extend(filenames)
|
||||
settings.setValue(cnst.settings_recent_files_key, new_filenames[:recent_file_max_count])
|
||||
del settings
|
||||
|
||||
def open_file(self, filename):
|
||||
self._current_item = None
|
||||
success, msg = self._file_handler.open(filename)
|
||||
self.statusBar().showMessage(msg, 5000)
|
||||
if success:
|
||||
self._file_close_action.setEnabled(success)
|
||||
self._cw.show_file_content()
|
||||
self._update_recent_files(filename)
|
||||
|
||||
def on_file_open(self, s):
|
||||
dlg = QFileDialog(self, 'Open nix data file', '', "NIX files (*.h5 *.nix)")
|
||||
dlg.setFileMode(QFileDialog.ExistingFile)
|
||||
filenames = None
|
||||
if dlg.exec_():
|
||||
filenames = dlg.selectedFiles()
|
||||
self.open_file(filenames[0])
|
||||
|
||||
def on_file_close(self, s):
|
||||
self._file_handler.close()
|
||||
self._cw.reset()
|
||||
self._current_item = None
|
||||
self._file_close_action.setEnabled(False)
|
||||
self.statusBar().showMessage("Successfully closed current file", 1000)
|
||||
|
||||
def on_quit(self, s):
|
||||
self._file_handler.close()
|
||||
sys.exit()
|
||||
|
||||
def on_item_plot(self, s):
|
||||
if self._current_item is not None:
|
||||
self._cw.plot_item(self._current_item)
|
||||
21
nixview/nixview.py
Executable file
21
nixview/nixview.py
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/python3
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from main_window import NixView
|
||||
import argparse
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="NixView. Viewer for NIX data files. For more on nix see https://nixio.readthedocs.io/en/master")
|
||||
parser.add_argument("file", nargs="?", default="", type=str, help="The nix file that should be opened.")
|
||||
args = parser.parse_args()
|
||||
app = QApplication(sys.argv)
|
||||
window = NixView()
|
||||
window.setMinimumWidth(800)
|
||||
window.setMinimumHeight(600)
|
||||
if len(args.file.strip()) > 0:
|
||||
window.open_file(args.file)
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
36
nixview/plot_screen.py
Normal file
36
nixview/plot_screen.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QPushButton, QVBoxLayout, QWidget
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, Qt
|
||||
import matplotlib
|
||||
matplotlib.use('Qt5Agg')
|
||||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
|
||||
from matplotlib.figure import Figure
|
||||
|
||||
class MplCanvas(FigureCanvasQTAgg):
|
||||
|
||||
def __init__(self, parent=None, width=5, height=4, dpi=100):
|
||||
fig = Figure(figsize=(width, height), dpi=dpi)
|
||||
self.axes = fig.add_subplot(111)
|
||||
super(MplCanvas, self).__init__(fig)
|
||||
|
||||
|
||||
class PlotScreen(QWidget):
|
||||
close_signal = pyqtSignal()
|
||||
|
||||
def __init__(self, parent) -> None:
|
||||
super().__init__(parent=parent)
|
||||
sc = MplCanvas(self, width=5, height=4, dpi=100)
|
||||
sc.axes.plot([0,1,2,3,4], [10,1,20,3,40])
|
||||
|
||||
self.setLayout(QVBoxLayout())
|
||||
self.layout().addWidget(sc)
|
||||
|
||||
close_btn = QPushButton("close")
|
||||
close_btn.clicked.connect(self.on_close)
|
||||
|
||||
self.layout().addWidget(close_btn)
|
||||
|
||||
def on_close(self):
|
||||
self.close_signal.emit()
|
||||
|
||||
def plot(self, item):
|
||||
print("plot!", item)
|
||||
384
nixview/tree_model.py
Normal file
384
nixview/tree_model.py
Normal file
@@ -0,0 +1,384 @@
|
||||
from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt, QSize
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QTreeView, QTreeWidgetItem, QAbstractItemView, QHeaderView
|
||||
from enum import Enum
|
||||
|
||||
from file_handler import ItemDescriptor, NodeType
|
||||
|
||||
column_names = ['Name', 'Type', 'Value', 'Description', 'ID',]
|
||||
|
||||
|
||||
class TreeType(Enum):
|
||||
Full = "full view"
|
||||
Metadata = "metadata view"
|
||||
Data = "data view"
|
||||
|
||||
|
||||
class NixTreeItem(QTreeWidgetItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent=None):
|
||||
super().__init__(parent)
|
||||
self._node_descriptor = node_descriptor
|
||||
self._file_handler = file_handler
|
||||
|
||||
self._parent_item = parent
|
||||
self._child_items = []
|
||||
self._is_loaded = False
|
||||
|
||||
@property
|
||||
def node_descriptor(self):
|
||||
return self._node_descriptor
|
||||
|
||||
@property
|
||||
def entity_type(self):
|
||||
return self._node_descriptor.entity_type
|
||||
|
||||
def child(self, row):
|
||||
if row < len(self._child_items):
|
||||
return self._child_items[row]
|
||||
return None
|
||||
|
||||
def childCount(self):
|
||||
n = 0 if self._is_loaded else 1
|
||||
return max(n, len(self._child_items))
|
||||
|
||||
def columnCount(self):
|
||||
return len(column_names)
|
||||
|
||||
def data(self, column):
|
||||
if column == 0:
|
||||
return self._node_descriptor.name
|
||||
elif column == 1:
|
||||
return self._node_descriptor.type
|
||||
elif column == 2:
|
||||
return self._node_descriptor.value
|
||||
elif column == 3:
|
||||
return self._node_descriptor.definition
|
||||
elif column == 4:
|
||||
return self._node_descriptor.id
|
||||
else:
|
||||
return None
|
||||
|
||||
def parent(self):
|
||||
return self._parent_item
|
||||
|
||||
def row(self):
|
||||
if self._parent_item:
|
||||
return self._parent_item._child_items.index(self)
|
||||
return 0
|
||||
|
||||
|
||||
class FileTreeItem(NixTreeItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent=None):
|
||||
super().__init__(node_descriptor, file_handler=file_handler, parent=parent)
|
||||
self._is_loaded = False
|
||||
|
||||
def load_children(self):
|
||||
self._child_items = []
|
||||
sections, _ = self._file_handler.request_metadata()
|
||||
for s in sections:
|
||||
self._child_items.append(SectionTreeItem(s, self._file_handler, parent=self))
|
||||
blocks = self._file_handler.request_blocks()
|
||||
for b in blocks:
|
||||
self._child_items.append(BlockTreeItem(b, self._file_handler, parent=self))
|
||||
self._is_loaded = True
|
||||
|
||||
|
||||
class DataTreeItem(NixTreeItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent=None):
|
||||
super().__init__(node_descriptor, file_handler=file_handler, parent=parent)
|
||||
self._is_loaded = False
|
||||
|
||||
def load_children(self):
|
||||
self._child_items = []
|
||||
blocks = self._file_handler.request_blocks()
|
||||
for b in blocks:
|
||||
self._child_items.append(BlockTreeItem(b, self._file_handler, parent=self))
|
||||
self._is_loaded = True
|
||||
|
||||
|
||||
class MetadataTreeItem(NixTreeItem):
|
||||
"""
|
||||
Root item for a metadata tree.
|
||||
|
||||
Args:
|
||||
|
||||
"""
|
||||
def __init__(self, node_descriptor, file_handler, parent=None, root_section_id=None):
|
||||
super().__init__(node_descriptor, file_handler=file_handler, parent=parent)
|
||||
self._root_section_id = root_section_id
|
||||
self._is_loaded = False
|
||||
|
||||
def load_children(self):
|
||||
self._child_items = []
|
||||
if self._root_section_id is not None:
|
||||
item = self._file_handler.request_section_descriptor(self._root_section_id)
|
||||
if item is not None:
|
||||
self._child_items.append(SectionTreeItem(item, self._file_handler, parent=self))
|
||||
else:
|
||||
sections, _ = self._file_handler.request_metadata(root_id=self._root_section_id)
|
||||
for s in sections:
|
||||
self._child_items.append(SectionTreeItem(s, self._file_handler, parent=self))
|
||||
self._is_loaded = True
|
||||
|
||||
|
||||
class BlockTreeItem(NixTreeItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent=None):
|
||||
super().__init__(node_descriptor, file_handler, parent=parent)
|
||||
self.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |Qt.ItemIsEditable)
|
||||
self._is_loaded = False
|
||||
|
||||
def load_children(self):
|
||||
self._child_items = []
|
||||
arrays = self._file_handler.request_data_arrays(self._node_descriptor.id)
|
||||
for a in arrays:
|
||||
self._child_items.append(DataArrayTreeItem(a, self._file_handler, parent=self))
|
||||
|
||||
for t in self._file_handler.request_tags(self._node_descriptor.id):
|
||||
self._child_items.append(TagTreeItem(t, self._file_handler, parent=self))
|
||||
|
||||
data_frames = self._file_handler.request_data_frames(self._node_descriptor.id)
|
||||
for df in data_frames:
|
||||
self._child_items.append(DataFrameTreeItem(df, self._file_handler, parent=self))
|
||||
|
||||
sources = self._file_handler.request_sources(self._node_descriptor.id)
|
||||
for s in sources:
|
||||
self._child_items.append(SourceTreeItem(s, self._file_handler, parent=self))
|
||||
self._is_loaded = True
|
||||
|
||||
|
||||
class SourceTreeItem(NixTreeItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent):
|
||||
super().__init__(node_descriptor, file_handler, parent=parent)
|
||||
self._is_loaded = False
|
||||
|
||||
def load_children(self):
|
||||
sources = self._file_handler.request_sources(self._node_descriptor.block_id, self._node_descriptor.id)
|
||||
for s in sources:
|
||||
self._child_items.append(SourceTreeItem(s, self._file_handler, parent=self))
|
||||
self._is_loaded = True
|
||||
|
||||
|
||||
class GroupTreeItem(NixTreeItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent):
|
||||
super().__init__(node_descriptor, file_handler, parent=parent)
|
||||
self._is_loaded = False
|
||||
|
||||
def load_children(self):
|
||||
self._child_items = []
|
||||
arrays = self._file_handler.request_data_arrays(self._node_descriptor.id)
|
||||
for a in arrays:
|
||||
self._child_items.append(DataArrayTreeItem(a, self._file_handler, parent=self))
|
||||
|
||||
for t in self._file_handler.request_tags(self._node_descriptor.id):
|
||||
self._child_items.append(TagTreeItem(t, self._file_handler, parent=self))
|
||||
|
||||
data_frames = self._file_handler.request_data_frames(self._node_descriptor.id)
|
||||
for df in data_frames:
|
||||
self._child_items.append(DataFrameTreeItem(df, self._file_handler, parent=self))
|
||||
|
||||
sources = self._file_handler.request_sources(self._node_descriptor.id)
|
||||
for s in sources:
|
||||
self._child_items.append(SourceTreeItem(s, self._file_handler, parent=self))
|
||||
self._is_loaded = True
|
||||
|
||||
|
||||
class DataFrameTreeItem(NixTreeItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent=None):
|
||||
super().__init__(node_descriptor, file_handler, parent=parent)
|
||||
self._is_loaded = True
|
||||
|
||||
|
||||
class FeatureTreeItem(NixTreeItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent=None):
|
||||
super().__init__(node_descriptor, file_handler, parent=parent)
|
||||
self._is_loaded = True
|
||||
|
||||
|
||||
class DataArrayTreeItem(NixTreeItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent=None):
|
||||
super().__init__(node_descriptor, file_handler, parent=parent)
|
||||
self._is_loaded = False
|
||||
|
||||
def load_children(self):
|
||||
self._child_items = []
|
||||
dimensions = self._file_handler.request_dimensions(self._node_descriptor.block_id, self._node_descriptor.id)
|
||||
for d in dimensions:
|
||||
self._child_items.append(DimensionTreeItem(d, self._file_handler, parent=self))
|
||||
self._is_loaded = True
|
||||
|
||||
|
||||
class DimensionTreeItem(NixTreeItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent=None):
|
||||
super().__init__(node_descriptor, file_handler, parent=parent)
|
||||
self._is_loaded = True
|
||||
|
||||
|
||||
class SectionTreeItem(NixTreeItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent=None):
|
||||
super().__init__(node_descriptor, file_handler=file_handler, parent=parent)
|
||||
self.setFlags(Qt.ItemIsSelectable)
|
||||
|
||||
self._is_loaded = False
|
||||
|
||||
def load_children(self):
|
||||
self._child_items = []
|
||||
sections, properties = self._file_handler.request_metadata(self._node_descriptor.id)
|
||||
for s in sections:
|
||||
self._child_items.append(SectionTreeItem(s, self._file_handler, parent=self))
|
||||
for p in properties:
|
||||
self._child_items.append(PropertyTreeItem(p, self._file_handler, parent=self))
|
||||
self._is_loaded = True
|
||||
|
||||
|
||||
class PropertyTreeItem(NixTreeItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent=None):
|
||||
super().__init__(node_descriptor, file_handler, parent=parent)
|
||||
self._is_loaded = True
|
||||
|
||||
def childCount(self):
|
||||
return 0
|
||||
|
||||
|
||||
class TagTreeItem(NixTreeItem):
|
||||
def __init__(self, node_descriptor, file_handler, parent=None):
|
||||
super().__init__(node_descriptor, file_handler, parent=parent)
|
||||
self._is_loaded = False
|
||||
|
||||
def load_children(self):
|
||||
self._child_items = []
|
||||
references = self._file_handler.request_references(self._node_descriptor.block_id, self._node_descriptor.id, self._node_descriptor.entity_type == NodeType.MultiTag)
|
||||
for r in references:
|
||||
self._child_items.append(DataArrayTreeItem(r, self._file_handler, self))
|
||||
|
||||
features = self._file_handler.request_features(self._node_descriptor.block_id, self._node_descriptor.id, self._node_descriptor.entity_type == NodeType.MultiTag)
|
||||
for f in features:
|
||||
self._child_items.append(FeatureTreeItem(f, self._file_handler, self))
|
||||
self._is_loaded = True
|
||||
|
||||
|
||||
class TreeModel(QAbstractItemModel):
|
||||
|
||||
def __init__(self, file_handler, tree_type=TreeType.Full, parent=None, root_section_id=None):
|
||||
super(TreeModel, self).__init__(parent)
|
||||
nd = ItemDescriptor(file_handler.filename, type="Root item")
|
||||
self.type_icons = {NodeType.Block: QIcon("./icons/nix_block_1d.png"),
|
||||
NodeType.Source: QIcon("./icons/nix_source.png"),
|
||||
NodeType.DataArray: QIcon("./icons/nix_data_array.png"),
|
||||
NodeType.Dimension: QIcon("./icons/nix_dimension.png"),
|
||||
NodeType.DataFrame: QIcon("./icons/nix_data_frame.png"),
|
||||
NodeType.Section: QIcon("./icons/nix_section.png"),
|
||||
NodeType.Property: QIcon("./icons/nix_property.png"),
|
||||
NodeType.Tag: QIcon("./icons/nix_tag.png"),
|
||||
NodeType.MultiTag: QIcon("./icons/nix_tag.png"),
|
||||
NodeType.Group: QIcon("./icons/nix_group.png"),
|
||||
NodeType.Feature: QIcon("./icons/nix_feature.png")}
|
||||
|
||||
if tree_type == TreeType.Full:
|
||||
self.root_item = FileTreeItem(nd, file_handler, parent=None)
|
||||
elif tree_type == TreeType.Metadata:
|
||||
self.root_item = MetadataTreeItem(nd, file_handler, parent=None, root_section_id=root_section_id)
|
||||
else:
|
||||
self.root_item = DataTreeItem(nd, file_handler, parent=None)
|
||||
self.root_item.load_children()
|
||||
|
||||
def columnCount(self, parent):
|
||||
return len(column_names)
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return None
|
||||
item = index.internalPointer()
|
||||
|
||||
if role == Qt.DisplayRole:
|
||||
return item.data(index.column())
|
||||
elif role == Qt.DecorationRole and index.column() == 0:
|
||||
if item.entity_type in self.type_icons.keys():
|
||||
return self.type_icons[item.entity_type]
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
def canFetchMore(self, index):
|
||||
if not index.isValid():
|
||||
return False
|
||||
item = index.internalPointer()
|
||||
return not item._is_loaded
|
||||
|
||||
def fetchMore(self, index):
|
||||
item = index.internalPointer()
|
||||
item.load_children()
|
||||
|
||||
def flags(self, index):
|
||||
if not index.isValid():
|
||||
return Qt.NoItemFlags
|
||||
|
||||
return Qt.ItemIsEnabled | Qt.ItemIsUserCheckable
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
||||
return column_names[section]
|
||||
return None
|
||||
|
||||
def index(self, row, column, parent_index):
|
||||
idx = QModelIndex()
|
||||
|
||||
if not self.hasIndex(row, column, parent_index):
|
||||
return idx
|
||||
|
||||
if not parent_index.isValid():
|
||||
parentItem = self.root_item
|
||||
else:
|
||||
parentItem = parent_index.internalPointer()
|
||||
|
||||
childItem = parentItem.child(row)
|
||||
if childItem:
|
||||
idx = self.createIndex(row, column, childItem)
|
||||
return idx
|
||||
|
||||
def parent(self, child_index):
|
||||
if not child_index.isValid():
|
||||
return QModelIndex()
|
||||
|
||||
child_item = child_index.internalPointer()
|
||||
parent_item = child_item.parent()
|
||||
if parent_item is None:
|
||||
return QModelIndex()
|
||||
# return self.createIndex(0, 0, self.root_item)
|
||||
|
||||
return self.createIndex(parent_item.row(), 0, parent_item)
|
||||
|
||||
def rowCount(self, parent_index):
|
||||
if parent_index.column() > 0:
|
||||
return 0
|
||||
|
||||
if not parent_index.isValid():
|
||||
parentItem = self.root_item
|
||||
else:
|
||||
parentItem = parent_index.internalPointer()
|
||||
|
||||
return parentItem.childCount()
|
||||
|
||||
|
||||
class NixTreeView(QTreeView):
|
||||
icon_size = QSize(30, 30)
|
||||
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self.expanded.connect(self.columnResize)
|
||||
self.collapsed.connect(self.columnResize)
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setUniformRowHeights(True) # Allows for scrolling optimizations.
|
||||
self.setWindowTitle("Data Tree")
|
||||
self.setIconSize(self.icon_size)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectItems)
|
||||
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
|
||||
header = self.header()
|
||||
header.setStretchLastSection(True)
|
||||
header.setFirstSectionMovable(False)
|
||||
header.setSectionResizeMode(QHeaderView.ResizeToContents)
|
||||
|
||||
def columnResize(self, index):
|
||||
for i in range(len(column_names)):
|
||||
self.resizeColumnToContents(i)
|
||||
Reference in New Issue
Block a user