import copy
import numpy as np
from qtpy.QtCore import QLocale, Qt, QModelIndex
from pymodaq_utils import utils
from pymodaq_gui.qvariant import QVariant
from pymodaq_gui.qt_utils import decode_data
from pymodaq_data import Q_
from qtpy import QtWidgets, QtCore
from pyqtgraph.widgets.SpinBox import SpinBox
[docs]
class TableView(QtWidgets.QTableView):
def __init__(self, *args, **kwargs):
QLocale.setDefault(QLocale(QLocale.English, QLocale.UnitedStates))
super().__init__(*args, **kwargs)
self.setupview()
[docs]
def setupview(self):
self.setStyle(MyStyle())
self.verticalHeader().hide()
self.horizontalHeader().hide()
self.horizontalHeader().setStretchLastSection(True)
self.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
self.setSelectionMode(QtWidgets.QTableView.SingleSelection)
self.setDragEnabled(True)
self.setDropIndicatorShown(True)
self.setAcceptDrops(True)
self.viewport().setAcceptDrops(True)
self.setDefaultDropAction(QtCore.Qt.MoveAction)
self.setDragDropMode(QtWidgets.QTableView.InternalMove)
self.setDragDropOverwriteMode(False)
[docs]
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data, header, editable=True, parent=None, show_checkbox=False, cast=float):
QLocale.setDefault(QLocale(QLocale.English, QLocale.UnitedStates))
super().__init__(parent)
self.cast = cast
if isinstance(data, np.ndarray):
data_tot = []
for dat in data:
data_tot.append([self.cast(d) for d in dat])
data = data_tot
self._data = data # stored data as a list of list
self._checked = [False for _ in range(len(self._data))]
self._show_checkbox = show_checkbox
self.data_tmp = None
self.header = header
if not isinstance(editable, list):
self.editable = [editable for h in header]
else:
self.editable = editable
def __eq__(self, other):
if isinstance(other, TableModel):
return self._data == other._data
else:
return False
[docs]
def is_checked(self, row: int):
return self._checked[row]
@property
def raw_data(self):
return copy.deepcopy(self._data)
[docs]
def rowCount(self, *args, **kwargs):
return len(self._data)
[docs]
def columnCount(self, *args, **kwargs):
if self._data != []:
return len(self._data[0])
else:
return 0
[docs]
def get_data(self, row, col=None):
if col is None:
return self._data[row]
else:
return self._data[row][col]
[docs]
def get_data_all(self):
return self._data
[docs]
def clear(self):
while self.rowCount(self.index(-1, -1)) > 0:
self.remove_row(0)
[docs]
def set_data_all(self, data):
self.clear()
for row in data:
self.insert_data(self.rowCount(self.index(-1, -1)), [self.cast(d) for d in row])
[docs]
def data(self, index, role):
if index.isValid():
if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
dat = self._data[index.row()][index.column()]
return dat
elif role == Qt.ItemDataRole.CheckStateRole and index.column() == 0 and self._show_checkbox:
if self._checked[index.row()]:
return Qt.CheckState.Checked
else:
return Qt.CheckState.Unchecked
return QVariant()
# def setHeaderData(self, section, orientation, value):
# if section == 2 and orientation == Qt.Horizontal:
# names = self._data.columns
# self._data = self._data.rename(columns={names[section]: value})
# self.headerDataChanged.emit(orientation, 0, section)
[docs]
def flags(self, index):
f = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsDragEnabled
if index.column() < len(self.editable):
if self.editable[index.column()]:
f |= Qt.ItemFlag.ItemIsEditable
if index.column() == 0:
f |= Qt.ItemFlag.ItemIsUserCheckable
if not index.isValid():
f |= Qt.ItemFlag.ItemIsDropEnabled
return f
[docs]
def supportedDropActions(self):
return Qt.DropAction.MoveAction | Qt.DropAction.CopyAction
[docs]
def validate_data(self, row, col, value):
"""
to be subclassed in order to validate ranges of values for the cell defined by index
Parameters
----------
index: (QModelIndex)
value: (str or float or int or ...)
Returns
-------
bool: True if value is valid for the given row and col
"""
return True
[docs]
def setData(self, index, value, role):
if index.isValid():
if role == Qt.ItemDataRole.EditRole:
if self.validate_data(index.row(), index.column(), self.cast(value)):
self._data[index.row()][index.column()] = self.cast(value)
self.dataChanged.emit(index, index, [role])
return True
else:
return False
elif role == Qt.ItemDataRole.CheckStateRole:
self._checked[index.row()] = True if value == Qt.CheckState.Checked else False
self.dataChanged.emit(index, index, [role])
return True
return False
[docs]
def dropMimeData(self, data, action, row, column, parent):
if row == -1:
row = self.rowCount(parent)
self.data_tmp = [dat[2] for dat in decode_data(data.data("application/x-qabstractitemmodeldatalist"))]
self.insertRows(row, 1, parent)
return True
[docs]
def insert_data(self, row, data):
self.data_tmp = data
self.insertRows(row, 1, self.index(-1, -1))
[docs]
def insertRows(self, row, count, parent):
self.beginInsertRows(QtCore.QModelIndex(), row, row + count - 1)
for ind in range(count):
self._data.insert(row + ind, self.data_tmp)
self._checked.insert(row + ind, False)
self.endInsertRows()
return True
[docs]
def remove_row(self, row):
self.removeRows(row, 1, self.index(-1, -1))
[docs]
def removeRows(self, row, count, parent):
self.beginRemoveRows(QModelIndex(), row, row + count - 1)
for ind in range(count):
self._data.pop(row + ind)
self._checked.pop(row + ind)
self.endRemoveRows()
return True
[docs]
class BooleanDelegate(QtWidgets.QStyledItemDelegate):
"""
TO implement custom widget editor for cells in a tableview
"""
[docs]
def createEditor(self, parent, option, index):
boolean = QtWidgets.QCheckBox(parent)
return boolean
[docs]
def setEditorData(self, editor: QtWidgets.QCheckBox, index):
editor.setChecked(bool(index.data()))
[docs]
def setModelData(self, editor: QtWidgets.QCheckBox, model, index):
model.setData(index,
editor.isChecked(),
QtCore.Qt.EditRole)
[docs]
class SpinBoxDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None, decimals=4, min=-1e6, max=1e6, units=None):
self.decimals = decimals
self.min = min
self.max = max
self.units = units
super().__init__(parent)
[docs]
def createEditor(self, parent, option, index):
doubleSpinBox = SpinBox(parent)
doubleSpinBox.setDecimals(self.decimals)
doubleSpinBox.setMaximum(self.min)
doubleSpinBox.setMaximum(self.max)
if self.units is not None:
doubleSpinBox.setSuffix(self.units)
return doubleSpinBox
[docs]
def setEditorData(self, editor: SpinBox, index):
editor.setValue(Q_(index.data()).magnitude)
#editor.setSuffix(Q_(index.data()).units)
[docs]
def setModelData(self, editor: SpinBox, model, index):
model.setData(index,
f"{editor.value()} {editor.opts['suffix']}" if self.units is not None else f"{editor.value()}",
QtCore.Qt.EditRole)
[docs]
class MyStyle(QtWidgets.QProxyStyle):
[docs]
def drawPrimitive(self, element, option, painter, widget=None):
"""
Draw a line across the entire row rather than just the column
we're hovering over. This may not always work depending on global
style - for instance I think it won't work on OSX.
"""
if element == self.PE_IndicatorItemViewItemDrop and not option.rect.isNull():
option_new = QtWidgets.QStyleOption(option)
option_new.rect.setLeft(0)
if widget:
option_new.rect.setRight(widget.width())
option = option_new
super().drawPrimitive(element, option, painter, widget)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication([])
w = QtWidgets.QMainWindow()
table = TableView(w)
table.setItemDelegate(BooleanDelegate())
table.setModel(TableModel([[name, True, False, 1.2] for name in ['X_axis', 'Y_axis', 'theta_axis']],
header=['Actuator', 'Start', 'Stop', 'Step'],
editable=[False, True, True, True]))
w.setCentralWidget(table)
w.show()
#
#
# c = TreeFromToml()
# c.show_dialog()
sys.exit(app.exec_())