# -*- coding: utf-8 -*- """ Common library for adding data to a QTableWidget. :copyright: Mazama Science, IRIS :license: GNU Lesser General Public License, Version 3 (http://www.gnu.org/copyleft/lesser.html) """ from pyweed.gui.MyNumericTableWidgetItem import MyNumericTableWidgetItem from PyQt5 import QtWidgets, QtCore from logging import getLogger LOGGER = getLogger(__name__) class Column(object): """ Defines a column for `TableItems` """ def __init__(self, label, description=None, width=None): """ :param label: The label to display in the column header :param description: If set, text will appear when the user hovers over the header :param width: Hardcode the column width, this is useful for fields that may have long values that would otherwise blow out the table width """ self.label = label self.description = description self.width = width class TableItems(object): """ Base helper class for adding a bunch of records to a QTableWidget. Subclasses just need to define the columns and how to turn a list of records into table cells. :example: >>> class ExampleTableItems(TableItems): >>> columns = [ >>> Column('Id'), >>> Column('Name', width=200), >>> Column('Email'), >>> Column('Age'), >>> ] >>> def rows(self, data): >>> for person in data: >>> yield [ >>> self.stringWidget(person.id), >>> self.stringWidget(person.name), >>> self.stringWidget(person.email), >>> self.numericWidget(person.age), >>> ] >>> items = ExampleTableItems(q_table_widget) >>> items.fill(list_of_people) """ #: Subclass should define the columns columns = None #: Table that the items are tied to table = None #: Subclass can set this to enforce a fixed row height (otherwise it will be sized to fit when it gets filled) rowHeight = None def __init__(self, table, *args): self.table = table def rows(self, data): """ Turn the data into rows (an iterable of lists) of QTableWidgetItems Subclasses should implement this """ pass def applyProps(self, widget, **props): """ Apply props to a newly created widget. This is used by the various widget methods to handle arbitrary keyword arguments by translating them into Qt setter calls. For example, passing `textAlignment=QtCore.Qt.AlignCenter` as a keyword argument will result in `widget.setTextAlignment(QtCore.Qt.AlignCenter)` """ # Look for Qt setter for any given prop name for prop, value in props.items(): setter = 'set%s%s' % (prop[:1].capitalize(), prop[1:]) if hasattr(widget, setter): try: getattr(widget, setter)(value) except Exception as e: LOGGER.error("Tried and failed to set %s: %s", prop, e) return widget def stringWidget(self, s, **props): """ Create a new item displaying the given string """ return self.applyProps(QtWidgets.QTableWidgetItem(s), **props) def numericWidget(self, i, text=None, **props): """ Create a new item displaying the given numeric value. Pass a string or a string format as `text` to customize how the number is actually displayed. The numeric value will still be used for sorting. """ if text is None: text = "%s" if '%' in text: text = text % i return self.applyProps(MyNumericTableWidgetItem(i, text), **props) def checkboxWidget(self, b, **props): """ Create a new checkbox widget showing the given boolean state """ checkboxItem = self.applyProps(QtWidgets.QTableWidgetItem(), **props) checkboxItem.setFlags(QtCore.Qt.ItemIsEnabled) if b: checkboxItem.setCheckState(QtCore.Qt.Checked) else: checkboxItem.setCheckState(QtCore.Qt.Unchecked) return checkboxItem def initColumns(self): # Only run this if the table columns aren't already set up if self.table.columnCount() != len(self.columns): self.table.setColumnCount(len(self.columns)) columnLabels = [c.label for c in self.columns] self.table.setHorizontalHeaderLabels(columnLabels) # Use the first column for identification self.table.setColumnHidden(0, True) # Set the tooltips for i, column in enumerate(self.columns): if column.description: self.table.horizontalHeaderItem(i).setToolTip(column.description) def fill(self, data): """ Fill the table """ # Clear existing contents self.table.setRowCount(0) # Initialize the table columns if needed self.initColumns() # Need to turn off sorting before inserting items self.table.setSortingEnabled(False) # Add new contents for rowidx, row in enumerate(self.rows(data)): self.table.insertRow(rowidx) if len(row) != len(self.columns): LOGGER.error("Row length doesn't match column count: %s / %s", str(row), str([c.label for c in self.columns])) for cellidx, cell in enumerate(row): self.table.setItem(rowidx, cellidx, cell) # Turn sorting back on self.table.setSortingEnabled(True) # Adjust the row heights if self.rowHeight: for i in range(self.table.rowCount()): self.table.setRowHeight(i, self.rowHeight) else: self.table.resizeRowsToContents() # Adjust the column widths for i, column in enumerate(self.columns): if column.width: self.table.setColumnWidth(i, column.width) else: self.table.resizeColumnToContents(i) def filter(self, filterFn): for row in range(self.table.rowCount()): if not filterFn or filterFn(row): self.table.showRow(row) else: self.table.hideRow(row)