Source code for schrodinger.ui.qt.tablewidget

"""
This is a table whose cells contain Tkinter widgets such as Buttons and
Checkbuttons.  Take a look at prepwizard.py or vsw_gui.py for application
example.

Simple example:

table = widgettable.TableWidget(parent, hull_width=200, hull_height=200, showtitlebar=True)
table.pack(side=LEFT)

col1 = table.addColumn(columntitle='On', width=50)
col2 = table.addColumn(columntitle='Name')


for i in range(10):
    row = table.insertRow()
    cell1 = row.cell[0]
    cell2 = row.cell[1]
    <modify cell1 or cell2 as necessary>

Copyright Schrodinger, LLC. All rights reserved.

"""

# Contributors: Matvey Adzhigirey, Pat Lorton, Byungchan Kim

import sys
import warnings
import weakref

from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import Qt

msg = "schrodinger.ui.qt.tablewidget module is obsolete. Please use the schrodinger.ui.qt.table modules instead."
warnings.warn(msg, DeprecationWarning, stacklevel=2)


[docs]class VerticalHeader:
[docs] def __init__(self, table, header): self.table = table self.tablewidget = table.tablewidget self.header = header
[docs] def setStretchLastSection(self, stretch): self.header.setStretchLastSection(stretch)
[docs]class HorizontalHeader:
[docs] def __init__(self, table, header): self.table = table self.tablewidget = table.tablewidget self.header = header
[docs] def setStretchLastSection(self, stretch): self.header.setStretchLastSection(stretch)
[docs]class Cell(object): """ References to instances of this class should not be stored. They are designed to be created dynamically. """
[docs] def __init__(self, table, rowi, coli): self.table = table # column.table is already a weak reference self.tablewidget = table.tablewidget self.rowi = rowi self.coli = coli item = self.tablewidget.item(self.rowi, self.coli) if item is None: raise IndexError("Table has no cell at row %i, column %i" % (rowi, coli))
[docs] def item(self): """ Returns QTableWidgetItem instance for this cell """ return self.tablewidget.item(self.rowi, self.coli)
def _getColumn(self): return Column(self.table, self.coli) column = property(_getColumn) def _getRow(self): return Row(self.table, self.rowi) row = property(_getRow) def __repr__(self): return "Cell(%i, %i)" % (self.rowi, self.coli)
[docs] def isSelected(self): self.item().isSelected()
[docs] def select(self): self.item().setSelected(True)
[docs] def deselect(self): self.item().setSelected(False)
[docs] def getText(self): return self.item().text()
[docs] def text(self): return self.item().text()
[docs] def setText(self, text): self.item().setText(text)
[docs] def setTextAlignment(self, alignment): self.item().setTextAlignment(alignment)
[docs] def setData(self, role, value): self.item().setData(role, value)
[docs] def setToolTip(self, text): self.item().setToolTip(text)
[docs] def checkState(self): return self.item().checkState()
[docs] def setCheckState(self, state): self.item().setCheckState(state)
[docs] def setCheckable(self, checkable): """ Whether to add a checkbutton to this cell. """ item = self.item() if checkable: item.setFlags(item.flags() | Qt.ItemIsUserCheckable) else: item.setFlags(item.flags() & ~Qt.ItemIsUserCheckable)
[docs] def setSelectable(self, selectable): item = self.item() if selectable: item.setFlags(item.flags() | Qt.ItemIsSelectable) else: item.setFlags(item.flags() & ~Qt.ItemIsSelectable)
[docs] def setEnabled(self, enabled): """ Whether the user can interact with this cell. """ item = self.item() if enabled: item.setFlags(item.flags() | Qt.ItemIsEnabled) else: item.setFlags(item.flags() & ~Qt.ItemIsEnabled)
[docs] def setEditable(self, editable): item = self.item() if editable: item.setFlags(item.flags() | Qt.ItemIsEditable) else: item.setFlags(item.flags() & ~Qt.ItemIsEditable)
[docs]class Column(object): """ References to instances of this class should not be stored. They are designed to be created dynamically. """
[docs] def __init__(self, table, coli): self.table = table self.tablewidget = table.tablewidget self.coli = coli
[docs] def setHeader(self, label): self.tablewidget.horizontalHeaderItem(self.coli).setText(label)
[docs] def setHeaderToolTip(self, text): self.tablewidget.horizontalHeaderItem(self.coli).setToolTip(text)
def _getCellIterator(self): cells = [] num_rows = self.table.rowCount() for rowi in range(num_rows): cell = Cell(self.table, rowi, self.coli) cells.append(cell) return CellIterator(cells) cell = property(_getCellIterator) def __repr__(self): return "Column(%i)" % self.coli
[docs] def remove(self): self.tablewidget.removeColumn(self.coli)
[docs] def select(self): self.tablewidget.selectColumn(self.coli)
[docs] def isHidden(self): return self.tablewidget.isColumnHidden(self.coli)
[docs] def resizeToContents(self): self.tablewidget.resizeColumnToContents(self.coli)
[docs] def getWidth(self): return self.tablewidget.columnWidth(self.coli)
[docs] def setWidth(self, width): return self.tablewidget.setColumnWidth(self.coli, width)
[docs] def sortBy(self, ascending=True): if ascending: self.tablewidget.sortByColumn(self.coli, Qt.AscendingOrder) else: self.tablewidget.sortByColumn(self.coli, Qt.DescendingOrder)
[docs]class Row(object): """ References to instances of this class should not be stored. They are designed to be created dynamically. """
[docs] def __init__(self, table, rowi): self.table = table self.tablewidget = table.tablewidget self.rowi = rowi
[docs] def setHeader(self, label): self.tablewidget.verticalHeaderItem(self.rowi).setText(label)
def _getCellIterator(self): cells = [] num_cols = self.table.columnCount() for coli in range(num_cols): cell = Cell(self.table, self.rowi, coli) cells.append(cell) return CellIterator(cells) cell = property(_getCellIterator) def __repr__(self): return "Row(%i)" % self.rowi
[docs] def remove(self): self.tablewidget.removeRow(self.rowi)
[docs] def select(self): self.tablewidget.selectRow(self.rowi)
[docs] def deselect(self): for cell in self.cell: cell.deselect()
[docs] def selectOnly(self): self.table.clearSelection() self.select()
[docs] def isSelected(self): selection_model = self.tablewidget.selectionModel() return selection_model.isRowSelected(self.rowi, QtCore.QModelIndex())
[docs] def isHidden(self): return self.tablewidget.isRowHidden(self.rowi)
[docs] def hide(self): self.tablewidget.setRowHidden(self.rowi, True)
[docs] def show(self): self.tablewidget.setRowHidden(self.rowi, False)
[docs] def resizeToContents(self): self.tablewidget.resizeRowToContents(self.rowi)
[docs] def getHeight(self): return self.tablewidget.rowHeight(self.rowi)
[docs] def setHeight(self, height): return self.tablewidget.setRowHeight(self.rowi, height)
[docs]class CellIterator(object): """ column.cell row.cell Class for iterating over cells in a row or column and for getting a cell Cells are indexed starting with 0 Ex: cell = row.cell[0] or: cell = column.cell[0] """
[docs] def __init__(self, cells): self.cell_list = cells
[docs] def __len__(self): return len(self.cell_list)
def __iter__(self): for cell in self.cell_list: yield cell def __getitem__(self, num): return self.cell_list[num]
[docs]class RowIterator: """ table.row Class for iterating over rows and for getting a row object Rows are indexed starting with 0 Ex: row = table.row[rowi] """
[docs] def __init__(self, table): self.table = weakref.proxy(table)
[docs] def __len__(self): return self.table.rowCount()
def __iter__(self): # Iterate over a copy to allow deletion of rows while iterating num_rows = self.table.rowCount() for rowi in range(num_rows): yield Row(self.table, rowi) def __getitem__(self, rowi): if rowi < 0 or rowi >= self.table.rowCount(): raise IndexError("Table has no row %s" % rowi) return Row(self.table, rowi) def __delitem__(self, rowi): """ For: del table.row[rowi] """ raise NotImplementedError()
[docs]class ColumnIterator(object): """ table.column Class for iterating over columns and getting a column Columns are indexed starting with 0 Ex: column = table.column[coli] """
[docs] def __init__(self, table): self.table = weakref.proxy(table)
[docs] def __len__(self): return self.table.columnCount()
def __iter__(self): # Iterate over a copy to allow deletion of columns while iterating for coli in range(self.table.columnCount()): yield Column(self.table, coli) def __getitem__(self, coli): #print 'NUM:', self.table.columnCount() #print 'ACCESSING:', coli if coli < 0 or coli >= self.table.columnCount(): raise IndexError("Table has no column %s" % coli) return Column(self.table, coli)
############################################################################# # ############################################################################# class _BaseTable(object): """ The table class with scroll bars """ def __init__( self, tablewidget, multiselect=False, cellselect=False, # Select cells instead of rows sortable=False, # Set this to True to allow column sorting by clicking on column headers stretch_last=False, # Whether to stretch the last column ): # FIXME if sortable, then adding rows to the table messes it up. The work-around in watermap_result_gui.py # is to set sortable to False before adding rows and the reset it to True. This needs to be fixed. self.tablewidget = tablewidget self._select_callback_function = None self.cellselect = cellselect if self.cellselect: self.tablewidget.setSelectionBehavior( QtWidgets.QAbstractItemView.SelectItems) else: self.tablewidget.setSelectionBehavior( QtWidgets.QAbstractItemView.SelectRows) if multiselect: self.tablewidget.setSelectionMode( QtWidgets.QAbstractItemView.ExtendedSelection) else: self.tablewidget.setSelectionMode( QtWidgets.QAbstractItemView.SingleSelection) if sortable: self.tablewidget.setSortingEnabled(True) self.tablewidget.selectionModel().selectionChanged.connect( self._selectionChanged) if stretch_last: self.tablewidget.horizontalHeader().setStretchLastSection(True) def _selectionChanged(self, ignored, ignored2): if self._select_callback_function: self._select_callback_function() def setSelectCallback(self, function): """ Use this to specify the command that should be called when selection is changed. Use selectedRows() or selectedCells() function to find out selecteda row(s) or cell(s). <function> should be a callable (no arguments). """ self._select_callback_function = function def addColumn(self, columntitle='', width=None): """ columntitle - title for the column width - width """ num_cols = self.columnCount() self.tablewidget.insertColumn(num_cols) col = self.column[num_cols] item = QtWidgets.QTableWidgetItem() item.setText(columntitle) self.tablewidget.setHorizontalHeaderItem(num_cols, item) if width: col.setWidth(width) return col def insertRow(self, after_row=None, fill=True): if after_row is None: after_row = self.rowCount() QtWidgets.QTableWidget.insertRow(self.tablewidget, after_row) row = self.row[after_row] if fill: for col in self.column: item = QtWidgets.QTableWidgetItem() self.tablewidget.setItem(after_row, col.coli, item) return row def addRow(self): import warnings msg = 'TableWidget.addRow() is obsolete. Use insertRow() method instead.' warnings.warn(msg, DeprecationWarning, stacklevel=2) return self.insertRow() def selected(self): """ Return a list of selected Cell objects (if cellselect) or selected Row objects (if rows are being selected). """ import warnings msg = 'TableWidget.selected() is obsolete. Use selectedRows() or selectedCells() instead.' warnings.warn(msg, DeprecationWarning, stacklevel=2) selection_model = self.selectionModel() if self.cellselect: return self.selectedCells() else: return self.selectedRows() def selectedRows(self): """ Return a list of selected Row objects """ if self.cellselect: raise RuntimeError( "selectedRows() can only be used cellselect is False") selection_model = self.selectionModel() items = selection_model.selectedRows() rows = [] for item in items: row = Row(self, item.row()) rows.append(row) return rows def selectedCells(self): """ Return a list of selected Cell objects. """ selection_model = self.selectionModel() if not self.cellselect: raise RuntimeError( "selectedRows() can only be used cellselect is False") items = selection_model.selectedIndexes() cells = [] for item in items: cell = Cell(self, item.row(), item.column()) cells.append(cell) return cells def removeSelectedRows(self): """ Remove rows which have at least one item selected """ selection_model = self.selectionModel() items = selection_model.selectedRows() rows_to_delete = [] for item in items: rowi = item.row() rows_to_delete.append(rowi) # Remove rows from the end: rows_to_delete.sort(reverse=True) for rowi in rows_to_delete: self.tablewidget.removeRow(rowi) def removeAllRows(self): self.model().removeRows(0, self.tablewidget.rowCount()) # FIXME currently selection callback is not called def selectOnlyRows(self, rows): """ Select only specified rows (list of row numbers) """ model = self.model() selection_model = self.selectionModel() selection = QtCore.QItemSelection() for rowi in rows: # Add this row to ItemSelection: top_left = model.index(rowi, 0, QtCore.QModelIndex()) bottom_right = model.index(rowi, 0, QtCore.QModelIndex()) selection.select(top_left, bottom_right) selection_model.select( selection, QtCore.QItemSelectionModel.ClearAndSelect | QtCore.QItemSelectionModel.Rows) def getCell(self, rowi, coli): """ Returns Cell object for cell at specified position """ return Cell(self, rowi, coli) def _getCellIterator(self): cells = [] num_rows = self.rowCount() num_cols = self.columnCount() for rowi in range(num_rows): for coli in range(num_cols): cell = Cell(self, rowi, coli) cells.append(cell) return CellIterator(cells) cell = property(_getCellIterator) def _getRowIterator(self): return RowIterator(self) row = property(_getRowIterator) def _getColumnIterator(self): return ColumnIterator(self) column = property(_getColumnIterator) def _getHorizontalHeader(self): return HorizontalHeader(self, self.tablewidget.horizontalHeader()) horizontal_header = property(_getHorizontalHeader) def _getVerticalHeader(self): return VerticalHeader(self, self.tablewidget.verticalHeader()) vertical_header = property(_getVerticalHeader)
[docs]class TableWidget(_BaseTable): """ The table class with scroll bars """
[docs] def __init__( self, tablewidget, multiselect=False, cellselect=False, # Select cells instead of rows sortable=False, # Set this to True to allow column sorting by clicking on column headers stretch_last=False, # Whether to stretch the last column ): _BaseTable.__init__(self, tablewidget, multiselect, cellselect, sortable, stretch_last)
# for any method/variable, try to use the variables of _BaseTable OR QTableWidget: def __getattr__(self, name): """ Called when attribute is requested which _BaseTable does not have. Returns an attribute of the linked QTableWidget. Raises exception if attribute can't be found. """ return getattr(self.tablewidget, name)
[docs]class SelfContainedTable(QtWidgets.QTableWidget, _BaseTable):
[docs] def __init__(self, parent=None): QtWidgets.QTableWidget.__init__(self, parent) _BaseTable.__init__(self, self)
[docs]def main(): app = QtWidgets.QApplication(sys.argv) w = MyWindow() w.show() sys.exit(app.exec())
[docs]class MyWindow(QtWidgets.QWidget):
[docs] def __init__(self, *args): QtWidgets.QWidget.__init__(self, *args) # create table with wrapper: base_table = QtWidgets.QTableWidget(self) table_wrapper = TableWidget(base_table) self.setupTable(table_wrapper) for column in table_wrapper.column: print("COLUMN:", column) for cell in column.cell: print(' CELL:', cell) for row in table_wrapper.row: print("ROW:", row) for cell in row.cell: print(' CELL:', cell) for cell in table_wrapper.cell: print("CELL:", cell) # Create a self-contained table: self_contained_table = SelfContainedTable(self) self.setupTable(self_contained_table) # layout layout = QtWidgets.QVBoxLayout() layout.addWidget(base_table) layout.addWidget(self_contained_table) self.setLayout(layout)
[docs] def setupTable(self, table): #table.setColumnCount(5) #table.setHorizontalHeaderLabels(["date", "time", "empty", "size", "filename"]) table.addColumn(columntitle='date') table.addColumn(columntitle='time') table.addColumn(columntitle='empty') table.addColumn(columntitle='size') table.addColumn(columntitle='filename') for i in range(5): table.insertRow() # set row height for row in table.row: row.setHeight(18) for cell in row.cell: cell.setText(str(cell)) # set column width to fit contents table.resizeColumnsToContents()
# enable sorting # this doesn't work #table.setSortingEnabled(True) if __name__ == "__main__": main()