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