forked from IPKM/nmreval
BUGFIX: VFT;
change to src layout
This commit is contained in:
494
src/gui_qt/data/datawidget/datawidget.py
Normal file
494
src/gui_qt/data/datawidget/datawidget.py
Normal file
@ -0,0 +1,494 @@
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
from nmreval.lib.colors import available_cycles
|
||||
|
||||
from .properties import PropWidget
|
||||
from ...Qt import QtWidgets, QtGui, QtCore
|
||||
from ..._py.datawidget import Ui_DataWidget
|
||||
from ...lib import make_action_icons
|
||||
from ...lib.delegates import HeaderDelegate
|
||||
|
||||
|
||||
class DataTree(QtWidgets.QTreeWidget):
|
||||
stateChanged = QtCore.pyqtSignal(list, list) # selected, deselected
|
||||
keyChanged = QtCore.pyqtSignal(str, str) # id, text
|
||||
positionChanged = QtCore.pyqtSignal(QtWidgets.QTreeWidgetItem)
|
||||
deleteItem = QtCore.pyqtSignal(list)
|
||||
moveItem = QtCore.pyqtSignal(list, str, str, int) # items, from, to, new row
|
||||
copyItem = QtCore.pyqtSignal(list, str)
|
||||
saveFits = QtCore.pyqtSignal(list)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.setColumnCount(1)
|
||||
self.invisibleRootItem().setFlags(self.invisibleRootItem().flags() ^ QtCore.Qt.ItemIsDropEnabled)
|
||||
|
||||
self.itemChanged.connect(self.data_change)
|
||||
self.itemClicked.connect(self.new_selection)
|
||||
|
||||
self.setColumnCount(2)
|
||||
|
||||
self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
|
||||
self.setDragDropMode(QtWidgets.QTreeView.InternalMove)
|
||||
self.setDefaultDropAction(QtCore.Qt.IgnoreAction)
|
||||
self.setSelectionMode(QtWidgets.QTreeView.ExtendedSelection)
|
||||
self.setSelectionBehavior(QtWidgets.QTreeView.SelectRows)
|
||||
|
||||
self._checked_graphs = set()
|
||||
self._checked_sets = set()
|
||||
self.management = None
|
||||
|
||||
header = QtWidgets.QHeaderView(QtCore.Qt.Horizontal, self)
|
||||
self.setHeader(header)
|
||||
header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
||||
header.setVisible(False)
|
||||
header.moveSection(1, 0)
|
||||
self.setColumnWidth(1, 16)
|
||||
self.setItemDelegateForColumn(1, HeaderDelegate())
|
||||
|
||||
def add_graph(self, idd: str, name: str):
|
||||
item = QtWidgets.QTreeWidgetItem()
|
||||
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEditable |
|
||||
QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)
|
||||
item.setText(0, name)
|
||||
item.setData(0, QtCore.Qt.UserRole, idd)
|
||||
item.setCheckState(0, QtCore.Qt.Checked)
|
||||
|
||||
self.addTopLevelItem(item)
|
||||
self._checked_graphs.add(idd)
|
||||
item.setExpanded(True)
|
||||
|
||||
def add_item(self, items: Union[tuple, List[tuple]], gid: str):
|
||||
if isinstance(items, tuple):
|
||||
items = [items]
|
||||
|
||||
for row in range(self.invisibleRootItem().childCount()):
|
||||
graph = self.invisibleRootItem().child(row)
|
||||
if graph.data(0, QtCore.Qt.UserRole) == gid:
|
||||
for (idd, name) in items:
|
||||
item = QtWidgets.QTreeWidgetItem([name])
|
||||
item.setData(0, QtCore.Qt.UserRole, idd)
|
||||
item.setCheckState(0, QtCore.Qt.Checked)
|
||||
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsEditable |
|
||||
QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)
|
||||
graph.addChild(item)
|
||||
self._checked_sets.add(idd)
|
||||
|
||||
self.resizeColumnToContents(0)
|
||||
break
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem)
|
||||
def data_change(self, item: QtWidgets.QTreeWidgetItem) -> Tuple[set, set]:
|
||||
idd = item.data(0, QtCore.Qt.UserRole)
|
||||
is_selected = item.checkState(0) == QtCore.Qt.Checked
|
||||
to_be_hidden = set()
|
||||
to_be_shown = set()
|
||||
|
||||
# item is top-level item / graph
|
||||
if item.parent() is None:
|
||||
was_selected = idd in self._checked_graphs
|
||||
|
||||
# check state changed to selected
|
||||
if is_selected != was_selected:
|
||||
# check state changed to checked
|
||||
if is_selected:
|
||||
self._checked_graphs.add(idd)
|
||||
iterator = QtWidgets.QTreeWidgetItemIterator(item)
|
||||
iterator += 1
|
||||
self.blockSignals(True)
|
||||
for i in range(item.childCount()):
|
||||
child = item.child(i)
|
||||
child.setCheckState(0, QtCore.Qt.Checked)
|
||||
to_be_shown.add(child.data(0, QtCore.Qt.UserRole))
|
||||
self._checked_sets.add(child.data(0, QtCore.Qt.UserRole))
|
||||
self.blockSignals(False)
|
||||
|
||||
# check state change to unchecked
|
||||
else:
|
||||
self._checked_graphs.remove(idd)
|
||||
self.blockSignals(True)
|
||||
for i in range(item.childCount()):
|
||||
child = item.child(i)
|
||||
child.setCheckState(0, QtCore.Qt.Unchecked)
|
||||
to_be_hidden.add(child.data(0, QtCore.Qt.UserRole))
|
||||
try:
|
||||
self._checked_sets.remove(child.data(0, QtCore.Qt.UserRole))
|
||||
except KeyError:
|
||||
pass
|
||||
self.blockSignals(False)
|
||||
|
||||
else:
|
||||
self.keyChanged.emit(idd, item.text(0))
|
||||
|
||||
# item is a set
|
||||
else:
|
||||
was_selected = idd in self._checked_sets
|
||||
if is_selected != was_selected:
|
||||
if is_selected:
|
||||
to_be_shown.add(idd)
|
||||
self._checked_sets.add(idd)
|
||||
|
||||
else:
|
||||
to_be_hidden.add(idd)
|
||||
try:
|
||||
self._checked_sets.remove(idd)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
else:
|
||||
self.keyChanged.emit(idd, item.text(0))
|
||||
|
||||
if to_be_shown or to_be_hidden:
|
||||
self.stateChanged.emit(list(to_be_shown), list(to_be_hidden))
|
||||
|
||||
return to_be_shown, to_be_hidden
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem)
|
||||
def new_selection(self, item: QtWidgets.QTreeWidgetItem):
|
||||
if item.parent() is None:
|
||||
self.management.select_window(item.data(0, QtCore.Qt.UserRole))
|
||||
|
||||
def dropEvent(self, evt: QtGui.QDropEvent):
|
||||
dropped_index = self.indexAt(evt.pos())
|
||||
if not dropped_index.isValid():
|
||||
return
|
||||
|
||||
to_parent = self.itemFromIndex(dropped_index).parent()
|
||||
append = False
|
||||
if not to_parent:
|
||||
# dropped on graph item -> append items
|
||||
to_parent = self.itemFromIndex(dropped_index)
|
||||
append = True
|
||||
|
||||
# index may change
|
||||
persistent_drop = QtCore.QPersistentModelIndex(dropped_index)
|
||||
|
||||
tobemoved = []
|
||||
take_from = []
|
||||
for it in self.selectedItems():
|
||||
from_parent = it.parent()
|
||||
if from_parent is None:
|
||||
continue
|
||||
|
||||
from_parent.removeChild(it)
|
||||
tobemoved.append(it)
|
||||
take_from.append(from_parent.data(0, QtCore.Qt.UserRole))
|
||||
|
||||
pos = QtCore.QModelIndex(persistent_drop)
|
||||
if self.dropIndicatorPosition() == QtWidgets.QAbstractItemView.BelowItem:
|
||||
pos = pos.sibling(pos.row()+1, 0)
|
||||
|
||||
row = pos.row()
|
||||
if (row == -1) or append:
|
||||
to_parent.addChildren(tobemoved)
|
||||
else:
|
||||
to_parent.insertChildren(row, tobemoved)
|
||||
|
||||
self.management.move_sets([it.data(0, QtCore.Qt.UserRole) for it in tobemoved],
|
||||
to_parent.data(0, QtCore.Qt.UserRole), take_from,
|
||||
pos=-1 if append else row)
|
||||
|
||||
def move_sets(self, sid: str, gid_in: str, gid_out: str):
|
||||
self.blockSignals(True)
|
||||
to_parent = None
|
||||
from_parent = None
|
||||
it = None
|
||||
|
||||
iterator = QtWidgets.QTreeWidgetItemIterator(self)
|
||||
while iterator.value():
|
||||
item = iterator.value()
|
||||
if item is not None:
|
||||
data = item.data(0, QtCore.Qt.UserRole)
|
||||
if data == gid_out:
|
||||
from_parent = item
|
||||
|
||||
elif data == gid_in:
|
||||
to_parent = item
|
||||
|
||||
elif data == sid:
|
||||
it = item
|
||||
|
||||
iterator += 1
|
||||
|
||||
if (from_parent is None) or (to_parent is None) or (it is None):
|
||||
print('Komisch')
|
||||
return
|
||||
|
||||
from_parent.removeChild(it)
|
||||
to_parent.addChild(it)
|
||||
|
||||
self.blockSignals(False)
|
||||
|
||||
def set_name(self, sid, name):
|
||||
iterator = QtWidgets.QTreeWidgetItemIterator(self)
|
||||
while iterator.value():
|
||||
item = iterator.value()
|
||||
if item is not None:
|
||||
data = item.data(0, QtCore.Qt.UserRole)
|
||||
if data == sid:
|
||||
if name != item.text(0):
|
||||
item.setText(0, name)
|
||||
break
|
||||
|
||||
iterator += 1
|
||||
|
||||
def keyPressEvent(self, evt: QtGui.QKeyEvent):
|
||||
if evt.key() == QtCore.Qt.Key_Delete:
|
||||
rm_sets = []
|
||||
rm_graphs = []
|
||||
for idx in self.selectedIndexes():
|
||||
if idx.column() == 1:
|
||||
continue
|
||||
item = self.itemFromIndex(idx)
|
||||
if item.parent() is None:
|
||||
for c_i in range(item.childCount()):
|
||||
rm_sets.append(item.child(c_i).data(0, QtCore.Qt.UserRole))
|
||||
rm_graphs.append(item.data(0, QtCore.Qt.UserRole))
|
||||
else:
|
||||
rm_sets.append(item.data(0, QtCore.Qt.UserRole))
|
||||
|
||||
# self.deleteItem.emit(rm_sets+rm_graphs)
|
||||
self.management.delete_sets(rm_sets+rm_graphs)
|
||||
|
||||
elif evt.key() == QtCore.Qt.Key_Space:
|
||||
sets = []
|
||||
from_parent = []
|
||||
|
||||
for idx in self.selectedIndexes():
|
||||
if idx.column() != 0:
|
||||
continue
|
||||
item = self.itemFromIndex(idx)
|
||||
|
||||
if item.parent() is None:
|
||||
is_selected = item.checkState(0)
|
||||
self.blockSignals(True)
|
||||
for i in range(item.childCount()):
|
||||
child = item.child(i)
|
||||
from_parent.append(child)
|
||||
self.blockSignals(False)
|
||||
if is_selected == QtCore.Qt.Checked:
|
||||
item.setCheckState(0, QtCore.Qt.Unchecked)
|
||||
else:
|
||||
item.setCheckState(0, QtCore.Qt.Checked)
|
||||
|
||||
else:
|
||||
sets.append(item)
|
||||
|
||||
for it in sets:
|
||||
if it in from_parent:
|
||||
continue
|
||||
it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked)
|
||||
else:
|
||||
super().keyPressEvent(evt)
|
||||
|
||||
def mousePressEvent(self, evt: QtGui.QMouseEvent):
|
||||
# disable drag-and-drop when column 1 ('header') is clicked
|
||||
idx = self.indexAt(evt.pos())
|
||||
self.setDragEnabled(idx.column() == 0)
|
||||
super().mousePressEvent(evt)
|
||||
|
||||
def remove_item(self, ids: list):
|
||||
iterator = QtWidgets.QTreeWidgetItemIterator(self)
|
||||
while iterator.value():
|
||||
item = iterator.value()
|
||||
_id = item.data(0, QtCore.Qt.UserRole)
|
||||
if _id in ids:
|
||||
try:
|
||||
idx = item.parent().indexOfChild(item)
|
||||
item.parent().takeChild(idx)
|
||||
if _id in self._checked_sets:
|
||||
self._checked_sets.remove(_id)
|
||||
except AttributeError:
|
||||
idx = self.invisibleRootItem().indexOfChild(item)
|
||||
self.invisibleRootItem().takeChild(idx)
|
||||
self._checked_graphs.remove(_id)
|
||||
|
||||
iterator += 1
|
||||
|
||||
def contextMenuEvent(self, evt, color_list=None):
|
||||
menu = QtWidgets.QMenu()
|
||||
_ = menu.addAction('Hello')
|
||||
_.setEnabled(False)
|
||||
menu.addSeparator()
|
||||
|
||||
idx = self.selectedIndexes()
|
||||
|
||||
if self.invisibleRootItem().childCount() == 0 and len(idx) == 0:
|
||||
rdn_action = menu.addAction('Randomness')
|
||||
|
||||
action = menu.exec(evt.globalPos())
|
||||
|
||||
if action == rdn_action:
|
||||
import webbrowser
|
||||
webbrowser.open('https://en.wikipedia.org/wiki/Special:Random')
|
||||
|
||||
else:
|
||||
del_action = menu.addAction('Exterminate')
|
||||
cp_action = menu.addAction('Replicate')
|
||||
cat_action = menu.addAction('Join us!')
|
||||
plt_action = None
|
||||
save_action = None
|
||||
menu.addSeparator()
|
||||
col_menu = menu.addMenu('Color cycle')
|
||||
for c in available_cycles.keys():
|
||||
col_menu.addAction(c)
|
||||
|
||||
idx = {}
|
||||
has_fits = False
|
||||
for i in self.selectedIndexes():
|
||||
if i.column() == 0:
|
||||
continue
|
||||
|
||||
item = self.itemFromIndex(i)
|
||||
parent = item.parent()
|
||||
if parent is None:
|
||||
continue
|
||||
|
||||
else:
|
||||
graph_id = parent.data(0, QtCore.Qt.UserRole)
|
||||
if graph_id not in idx:
|
||||
idx[graph_id] = []
|
||||
# collect sets in their graph
|
||||
idx[graph_id].append(item.data(0, QtCore.Qt.UserRole))
|
||||
data = self.management[item.data(0, QtCore.Qt.UserRole)]
|
||||
if data.mode == 'fit':
|
||||
has_fits = True
|
||||
|
||||
if has_fits:
|
||||
menu.addSeparator()
|
||||
plt_action = menu.addAction('Plot fit parameter')
|
||||
save_action = menu.addAction('Save fit parameter')
|
||||
|
||||
action = menu.exec(evt.globalPos())
|
||||
|
||||
s = []
|
||||
for gid, sets in idx.items():
|
||||
s.extend(sets)
|
||||
|
||||
if action == del_action:
|
||||
self.management.delete_sets(s)
|
||||
|
||||
elif action == cp_action:
|
||||
for gid, sets in idx.items():
|
||||
self.management.copy_sets(sets, gid)
|
||||
|
||||
elif action == cat_action:
|
||||
self.management.cat(s)
|
||||
|
||||
elif action == plt_action:
|
||||
self.management.make_fit_parameter(s)
|
||||
|
||||
elif action == save_action:
|
||||
self.saveFits.emit(s)
|
||||
|
||||
elif action.parent() == col_menu:
|
||||
self.management.set_cycle(s, action.text())
|
||||
|
||||
evt.accept()
|
||||
|
||||
def highlight(self, gid: str):
|
||||
iterator = QtWidgets.QTreeWidgetItemIterator(self)
|
||||
while iterator.value():
|
||||
item = iterator.value()
|
||||
if item is not None:
|
||||
if item.data(0, QtCore.Qt.UserRole) == gid:
|
||||
item.setBackground(0, QtGui.QBrush(QtGui.QColor('gray')))
|
||||
else:
|
||||
item.setBackground(0, QtGui.QBrush())
|
||||
iterator += 1
|
||||
|
||||
def uncheck_sets(self, sets: List[str]):
|
||||
self.blockSignals(True)
|
||||
iterator = QtWidgets.QTreeWidgetItemIterator(self)
|
||||
while iterator.value():
|
||||
item = iterator.value()
|
||||
if item is not None:
|
||||
if item.data(0, QtCore.Qt.UserRole) in sets:
|
||||
item.setCheckState(0, QtCore.Qt.Unchecked)
|
||||
iterator += 1
|
||||
self.blockSignals(False)
|
||||
|
||||
|
||||
class DataWidget(QtWidgets.QWidget, Ui_DataWidget):
|
||||
keyChanged = QtCore.pyqtSignal(str, str)
|
||||
deleteItem = QtCore.pyqtSignal(list)
|
||||
startShowProperty = QtCore.pyqtSignal(list)
|
||||
propertyChanged = QtCore.pyqtSignal(list, str, str, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.setupUi(self)
|
||||
self.tree = DataTree(self)
|
||||
self.verticalLayout.addWidget(self.tree)
|
||||
self.tree.selectionModel().selectionChanged.connect(lambda x, y: self.show_property(x))
|
||||
|
||||
self.tree.keyChanged.connect(lambda x, y: self.keyChanged.emit(x, y))
|
||||
|
||||
self.proptable = PropWidget(self)
|
||||
self.propwidget.addWidget(self.proptable)
|
||||
self.propwidget.setText('Properties')
|
||||
self.propwidget.expansionChanged.connect(self.show_property)
|
||||
self.proptable.propertyChanged.connect(self.change_property)
|
||||
|
||||
make_action_icons(self)
|
||||
|
||||
def add_graph(self, idd: str, name: str):
|
||||
self.tree.blockSignals(True)
|
||||
self.tree.add_graph(idd, name)
|
||||
self.tree.blockSignals(False)
|
||||
|
||||
def add_item(self, idd: str, name: str, gid: str):
|
||||
self.tree.blockSignals(True)
|
||||
self.tree.add_item((idd, name), gid)
|
||||
self.tree.blockSignals(False)
|
||||
|
||||
def add_item_list(self, loi: list, gid: str):
|
||||
self.tree.blockSignals(True)
|
||||
self.tree.add_item(loi, gid)
|
||||
self.tree.blockSignals(False)
|
||||
|
||||
def remove_item(self, key):
|
||||
self.tree.remove_item(key)
|
||||
|
||||
def show_property(self, _: QtCore.QModelIndex = None):
|
||||
if not self.propwidget.isExpanded():
|
||||
return
|
||||
|
||||
sid = []
|
||||
for i in self.tree.selectedIndexes():
|
||||
if i.column() == 0:
|
||||
sid.append(i.data(role=QtCore.Qt.UserRole))
|
||||
|
||||
self.startShowProperty.emit(sid)
|
||||
|
||||
@QtCore.pyqtSlot(dict)
|
||||
def set_properties(self, props: dict):
|
||||
self.proptable.populate(props)
|
||||
|
||||
def change_property(self, key1, key2, value):
|
||||
ids = [item.data(0, QtCore.Qt.UserRole) for item in self.tree.selectedItems()]
|
||||
if key2 == 'Value':
|
||||
try:
|
||||
value = float(value)
|
||||
except ValueError:
|
||||
QtWidgets.QMessageBox.warning(self, 'Invalid entry',
|
||||
'Value %r is not a valid number for `value`.' % value)
|
||||
return
|
||||
|
||||
self.propertyChanged.emit(ids, key1, key2, value)
|
||||
|
||||
def uncheck_sets(self, sets: List[str]):
|
||||
self.tree.uncheck_sets(sets)
|
||||
|
||||
def set_name(self, sid, value):
|
||||
self.tree.set_name(sid, value)
|
||||
|
||||
@property
|
||||
def management(self):
|
||||
return self.tree.management
|
||||
|
||||
@management.setter
|
||||
def management(self, value):
|
||||
self.tree.management = value
|
Reference in New Issue
Block a user