[app] add ui sub package for user interfaces
This commit is contained in:
0
nixview/ui/__init__.py
Normal file
0
nixview/ui/__init__.py
Normal file
259
nixview/ui/central_widget.py
Normal file
259
nixview/ui/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 nixview.file_handler import FileHandler, ItemDescriptor
|
||||
from nixview.plot_screen import PlotScreen
|
||||
import nixview.communicator as comm
|
||||
import nixview.constants as cnst
|
||||
from nixview.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(os.path.join(cnst.ICONS_FOLDER, "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()])
|
||||
149
nixview/ui/main_window.py
Normal file
149
nixview/ui/main_window.py
Normal file
@@ -0,0 +1,149 @@
|
||||
import os
|
||||
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 nixview.file_handler import FileHandler, NodeType
|
||||
import nixview.constants as cnst
|
||||
import nixview.communicator as comm
|
||||
from nixview.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(os.path.join(cnst.ICONS_FOLDER, '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(os.path.join(cnst.ICONS_FOLDER, "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(os.path.join(cnst.ICONS_FOLDER, "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(os.path.join(cnst.ICONS_FOLDER, "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(os.path.join(cnst.ICONS_FOLDER, "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(os.path.join(cnst.ICONS_FOLDER, "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)
|
||||
36
nixview/ui/plot_screen.py
Normal file
36
nixview/ui/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)
|
||||
Reference in New Issue
Block a user