"""
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 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 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)
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
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 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()