[app] add ui sub package for user interfaces

This commit is contained in:
2021-01-09 13:08:24 +01:00
parent 15c0c67bba
commit ad91a6d580
4 changed files with 0 additions and 0 deletions

0
nixview/ui/__init__.py Normal file
View File

View 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
View 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
View 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)