Source code for pymodaq_gui.utils.widgets.table

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 headerData(self, section, orientation, role): if role == Qt.ItemDataRole.DisplayRole: if orientation == Qt.Orientation.Horizontal: if section >= len(self.header): return QVariant() else: return self.header[section] else: return section else: return QVariant()
[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_())