Example #1
0
def main():
    root = Tk()
    sizex = 800
    sizey = 600
    posx = 100
    posy = 100
    root.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))

    root.grid()
    root.rowconfigure(0, weight=1)
    root.columnconfigure(0, weight=1)

    myframe = Frame(root, relief=GROOVE, bd=1)
    myframe.grid(row=0, column=0, sticky="NESW")

    myframe.rowconfigure(0, weight=1)
    myframe.columnconfigure(0, weight=1)
    myframe.columnconfigure(1, weight=0)

    canvas = Canvas(myframe)
    canvas.grid(row=0, column=0, sticky="NESW")

    frame = Frame(canvas)
    myscrollbar = Scrollbar(myframe, orient="vertical", command=canvas.yview)
    myscrollbar.grid(row=0, column=1, sticky="NESW")
    canvas.configure(yscrollcommand=myscrollbar.set)

    canvas.create_window((0, 0), window=frame, anchor='nw')
    frame.bind("<Configure>", lambda e, c=canvas: myfunction(c, e))
    data(frame)
    root.mainloop()
Example #2
0
class Table(object):
    """
    A display widget for a table of values, based on a ``MultiListbox``
    widget.  For many purposes, ``Table`` can be treated as a
    list-of-lists.  E.g., table[i] is a list of the values for row i;
    and table.append(row) adds a new row with the given lits of
    values.  Individual cells can be accessed using table[i,j], which
    refers to the j-th column of the i-th row.  This can be used to
    both read and write values from the table.  E.g.:

        >>> table[i,j] = 'hello'

    The column (j) can be given either as an index number, or as a
    column name.  E.g., the following prints the value in the 3rd row
    for the 'First Name' column:

        >>> print(table[3, 'First Name'])
        John

    You can configure the colors for individual rows, columns, or
    cells using ``rowconfig()``, ``columnconfig()``, and ``itemconfig()``.
    The color configuration for each row will be preserved if the
    table is modified; however, when new rows are added, any color
    configurations that have been made for *columns* will not be
    applied to the new row.

    Note: Although ``Table`` acts like a widget in some ways (e.g., it
    defines ``grid()``, ``pack()``, and ``bind()``), it is not itself a
    widget; it just contains one.  This is because widgets need to
    define ``__getitem__()``, ``__setitem__()``, and ``__nonzero__()`` in
    a way that's incompatible with the fact that ``Table`` behaves as a
    list-of-lists.

    :ivar _mlb: The multi-column listbox used to display this table's data.
    :ivar _rows: A list-of-lists used to hold the cell values of this
        table.  Each element of _rows is a row value, i.e., a list of
        cell values, one for each column in the row.
    """
    def __init__(self,
                 master,
                 column_names,
                 rows=None,
                 column_weights=None,
                 scrollbar=True,
                 click_to_sort=True,
                 reprfunc=None,
                 cnf={},
                 **kw):
        """
        Construct a new Table widget.

        :type master: Tkinter.Widget
        :param master: The widget that should contain the new table.
        :type column_names: list(str)
        :param column_names: A list of names for the columns; these
            names will be used to create labels for each column;
            and can be used as an index when reading or writing
            cell values from the table.
        :type rows: list(list)
        :param rows: A list of row values used to initialze the table.
            Each row value should be a tuple of cell values, one for
            each column in the row.
        :type scrollbar: bool
        :param scrollbar: If true, then create a scrollbar for the
            new table widget.
        :type click_to_sort: bool
        :param click_to_sort: If true, then create bindings that will
            sort the table's rows by a given column's values if the
            user clicks on that colum's label.
        :type reprfunc: function
        :param reprfunc: If specified, then use this function to
            convert each table cell value to a string suitable for
            display.  ``reprfunc`` has the following signature:
            reprfunc(row_index, col_index, cell_value) -> str
            (Note that the column is specified by index, not by name.)
        :param cnf, kw: Configuration parameters for this widget's
            contained ``MultiListbox``.  See ``MultiListbox.__init__()``
            for details.
        """
        self._num_columns = len(column_names)
        self._reprfunc = reprfunc
        self._frame = Frame(master)

        self._column_name_to_index = dict(
            (c, i) for (i, c) in enumerate(column_names))

        # Make a copy of the rows & check that it's valid.
        if rows is None: self._rows = []
        else: self._rows = [[v for v in row] for row in rows]
        for row in self._rows:
            self._checkrow(row)

        # Create our multi-list box.
        self._mlb = MultiListbox(self._frame, column_names, column_weights,
                                 cnf, **kw)
        self._mlb.pack(side='left', expand=True, fill='both')

        # Optional scrollbar
        if scrollbar:
            sb = Scrollbar(self._frame,
                           orient='vertical',
                           command=self._mlb.yview)
            self._mlb.listboxes[0]['yscrollcommand'] = sb.set
            #for listbox in self._mlb.listboxes:
            #    listbox['yscrollcommand'] = sb.set
            sb.pack(side='right', fill='y')
            self._scrollbar = sb

        # Set up sorting
        self._sortkey = None
        if click_to_sort:
            for i, l in enumerate(self._mlb.column_labels):
                l.bind('<Button-1>', self._sort)

        # Fill in our multi-list box.
        self._fill_table()

    #/////////////////////////////////////////////////////////////////
    #{ Widget-like Methods
    #/////////////////////////////////////////////////////////////////
    # These all just delegate to either our frame or our MLB.

    def pack(self, *args, **kwargs):
        """Position this table's main frame widget in its parent
        widget.  See ``Tkinter.Frame.pack()`` for more info."""
        self._frame.pack(*args, **kwargs)

    def grid(self, *args, **kwargs):
        """Position this table's main frame widget in its parent
        widget.  See ``Tkinter.Frame.grid()`` for more info."""
        self._frame.grid(*args, **kwargs)

    def focus(self):
        """Direct (keyboard) input foxus to this widget."""
        self._mlb.focus()

    def bind(self, sequence=None, func=None, add=None):
        """Add a binding to this table's main frame that will call
        ``func`` in response to the event sequence."""
        self._mlb.bind(sequence, func, add)

    def rowconfigure(self, row_index, cnf={}, **kw):
        """:see: ``MultiListbox.rowconfigure()``"""
        self._mlb.rowconfigure(row_index, cnf, **kw)

    def columnconfigure(self, col_index, cnf={}, **kw):
        """:see: ``MultiListbox.columnconfigure()``"""
        col_index = self.column_index(col_index)
        self._mlb.columnconfigure(col_index, cnf, **kw)

    def itemconfigure(self, row_index, col_index, cnf=None, **kw):
        """:see: ``MultiListbox.itemconfigure()``"""
        col_index = self.column_index(col_index)
        return self._mlb.itemconfigure(row_index, col_index, cnf, **kw)

    def bind_to_labels(self, sequence=None, func=None, add=None):
        """:see: ``MultiListbox.bind_to_labels()``"""
        return self._mlb.bind_to_labels(sequence, func, add)

    def bind_to_listboxes(self, sequence=None, func=None, add=None):
        """:see: ``MultiListbox.bind_to_listboxes()``"""
        return self._mlb.bind_to_listboxes(sequence, func, add)

    def bind_to_columns(self, sequence=None, func=None, add=None):
        """:see: ``MultiListbox.bind_to_columns()``"""
        return self._mlb.bind_to_columns(sequence, func, add)

    rowconfig = rowconfigure
    columnconfig = columnconfigure
    itemconfig = itemconfigure

    #/////////////////////////////////////////////////////////////////
    #{ Table as list-of-lists
    #/////////////////////////////////////////////////////////////////

    def insert(self, row_index, rowvalue):
        """
        Insert a new row into the table, so that its row index will be
        ``row_index``.  If the table contains any rows whose row index
        is greater than or equal to ``row_index``, then they will be
        shifted down.

        :param rowvalue: A tuple of cell values, one for each column
            in the new row.
        """
        self._checkrow(rowvalue)
        self._rows.insert(row_index, rowvalue)
        if self._reprfunc is not None:
            rowvalue = [
                self._reprfunc(row_index, j, v)
                for (j, v) in enumerate(rowvalue)
            ]
        self._mlb.insert(row_index, rowvalue)
        if self._DEBUG: self._check_table_vs_mlb()

    def extend(self, rowvalues):
        """
        Add new rows at the end of the table.

        :param rowvalues: A list of row values used to initialze the
            table.  Each row value should be a tuple of cell values,
            one for each column in the row.
        """
        for rowvalue in rowvalues:
            self.append(rowvalue)
        if self._DEBUG: self._check_table_vs_mlb()

    def append(self, rowvalue):
        """
        Add a new row to the end of the table.

        :param rowvalue: A tuple of cell values, one for each column
            in the new row.
        """
        self.insert(len(self._rows), rowvalue)
        if self._DEBUG: self._check_table_vs_mlb()

    def clear(self):
        """
        Delete all rows in this table.
        """
        self._rows = []
        self._mlb.delete(0, 'end')
        if self._DEBUG: self._check_table_vs_mlb()

    def __getitem__(self, index):
        """
        Return the value of a row or a cell in this table.  If
        ``index`` is an integer, then the row value for the ``index``th
        row.  This row value consists of a tuple of cell values, one
        for each column in the row.  If ``index`` is a tuple of two
        integers, ``(i,j)``, then return the value of the cell in the
        ``i``th row and the ``j``th column.
        """
        if isinstance(index, slice):
            raise ValueError('Slicing not supported')
        elif isinstance(index, tuple) and len(index) == 2:
            return self._rows[index[0]][self.column_index(index[1])]
        else:
            return tuple(self._rows[index])

    def __setitem__(self, index, val):
        """
        Replace the value of a row or a cell in this table with
        ``val``.

        If ``index`` is an integer, then ``val`` should be a row value
        (i.e., a tuple of cell values, one for each column).  In this
        case, the values of the ``index``th row of the table will be
        replaced with the values in ``val``.

        If ``index`` is a tuple of integers, ``(i,j)``, then replace the
        value of the cell in the ``i``th row and ``j``th column with
        ``val``.
        """
        if isinstance(index, slice):
            raise ValueError('Slicing not supported')

        # table[i,j] = val
        elif isinstance(index, tuple) and len(index) == 2:
            i, j = index[0], self.column_index(index[1])
            config_cookie = self._save_config_info([i])
            self._rows[i][j] = val
            if self._reprfunc is not None:
                val = self._reprfunc(i, j, val)
            self._mlb.listboxes[j].insert(i, val)
            self._mlb.listboxes[j].delete(i + 1)
            self._restore_config_info(config_cookie)

        # table[i] = val
        else:
            config_cookie = self._save_config_info([index])
            self._checkrow(val)
            self._rows[index] = list(val)
            if self._reprfunc is not None:
                val = [
                    self._reprfunc(index, j, v) for (j, v) in enumerate(val)
                ]
            self._mlb.insert(index, val)
            self._mlb.delete(index + 1)
            self._restore_config_info(config_cookie)

    def __delitem__(self, row_index):
        """
        Delete the ``row_index``th row from this table.
        """
        if isinstance(row_index, slice):
            raise ValueError('Slicing not supported')
        if isinstance(row_index, tuple) and len(row_index) == 2:
            raise ValueError('Cannot delete a single cell!')
        del self._rows[row_index]
        self._mlb.delete(row_index)
        if self._DEBUG: self._check_table_vs_mlb()

    def __len__(self):
        """
        :return: the number of rows in this table.
        """
        return len(self._rows)

    def _checkrow(self, rowvalue):
        """
        Helper function: check that a given row value has the correct
        number of elements; and if not, raise an exception.
        """
        if len(rowvalue) != self._num_columns:
            raise ValueError('Row %r has %d columns; expected %d' %
                             (rowvalue, len(rowvalue), self._num_columns))

    #/////////////////////////////////////////////////////////////////
    # Columns
    #/////////////////////////////////////////////////////////////////

    @property
    def column_names(self):
        """A list of the names of the columns in this table."""
        return self._mlb.column_names

    def column_index(self, i):
        """
        If ``i`` is a valid column index integer, then return it as is.
        Otherwise, check if ``i`` is used as the name for any column;
        if so, return that column's index.  Otherwise, raise a
        ``KeyError`` exception.
        """
        if isinstance(i, int) and 0 <= i < self._num_columns:
            return i
        else:
            # This raises a key error if the column is not found.
            return self._column_name_to_index[i]

    def hide_column(self, column_index):
        """:see: ``MultiListbox.hide_column()``"""
        self._mlb.hide_column(self.column_index(column_index))

    def show_column(self, column_index):
        """:see: ``MultiListbox.show_column()``"""
        self._mlb.show_column(self.column_index(column_index))

    #/////////////////////////////////////////////////////////////////
    # Selection
    #/////////////////////////////////////////////////////////////////

    def selected_row(self):
        """
        Return the index of the currently selected row, or None if
        no row is selected.  To get the row value itself, use
        ``table[table.selected_row()]``.
        """
        sel = self._mlb.curselection()
        if sel: return int(sel[0])
        else: return None

    def select(self, index=None, delta=None, see=True):
        """:see: ``MultiListbox.select()``"""
        self._mlb.select(index, delta, see)

    #/////////////////////////////////////////////////////////////////
    # Sorting
    #/////////////////////////////////////////////////////////////////

    def sort_by(self, column_index, order='toggle'):
        """
        Sort the rows in this table, using the specified column's
        values as a sort key.

        :param column_index: Specifies which column to sort, using
            either a column index (int) or a column's label name
            (str).

        :param order: Specifies whether to sort the values in
            ascending or descending order:

              - ``'ascending'``: Sort from least to greatest.
              - ``'descending'``: Sort from greatest to least.
              - ``'toggle'``: If the most recent call to ``sort_by()``
                sorted the table by the same column (``column_index``),
                then reverse the rows; otherwise sort in ascending
                order.
        """
        if order not in ('ascending', 'descending', 'toggle'):
            raise ValueError('sort_by(): order should be "ascending", '
                             '"descending", or "toggle".')
        column_index = self.column_index(column_index)
        config_cookie = self._save_config_info(index_by_id=True)

        # Sort the rows.
        if order == 'toggle' and column_index == self._sortkey:
            self._rows.reverse()
        else:
            self._rows.sort(key=operator.itemgetter(column_index),
                            reverse=(order == 'descending'))
            self._sortkey = column_index

        # Redraw the table.
        self._fill_table()
        self._restore_config_info(config_cookie, index_by_id=True, see=True)
        if self._DEBUG: self._check_table_vs_mlb()

    def _sort(self, event):
        """Event handler for clicking on a column label -- sort by
        that column."""
        column_index = event.widget.column_index

        # If they click on the far-left of far-right of a column's
        # label, then resize rather than sorting.
        if self._mlb._resize_column(event):
            return 'continue'

        # Otherwise, sort.
        else:
            self.sort_by(column_index)
            return 'continue'

    #/////////////////////////////////////////////////////////////////
    #{ Table Drawing Helpers
    #/////////////////////////////////////////////////////////////////

    def _fill_table(self, save_config=True):
        """
        Re-draw the table from scratch, by clearing out the table's
        multi-column listbox; and then filling it in with values from
        ``self._rows``.  Note that any cell-, row-, or column-specific
        color configuration that has been done will be lost.  The
        selection will also be lost -- i.e., no row will be selected
        after this call completes.
        """
        self._mlb.delete(0, 'end')
        for i, row in enumerate(self._rows):
            if self._reprfunc is not None:
                row = [self._reprfunc(i, j, v) for (j, v) in enumerate(row)]
            self._mlb.insert('end', row)

    def _get_itemconfig(self, r, c):
        return dict((k, self._mlb.itemconfig(r, c, k)[-1])
                    for k in ('foreground', 'selectforeground', 'background',
                              'selectbackground'))

    def _save_config_info(self, row_indices=None, index_by_id=False):
        """
        Return a 'cookie' containing information about which row is
        selected, and what color configurations have been applied.
        this information can the be re-applied to the table (after
        making modifications) using ``_restore_config_info()``.  Color
        configuration information will be saved for any rows in
        ``row_indices``, or in the entire table, if
        ``row_indices=None``.  If ``index_by_id=True``, the the cookie
        will associate rows with their configuration information based
        on the rows' python id.  This is useful when performing
        operations that re-arrange the rows (e.g. ``sort``).  If
        ``index_by_id=False``, then it is assumed that all rows will be
        in the same order when ``_restore_config_info()`` is called.
        """
        # Default value for row_indices is all rows.
        if row_indices is None:
            row_indices = list(range(len(self._rows)))

        # Look up our current selection.
        selection = self.selected_row()
        if index_by_id and selection is not None:
            selection = id(self._rows[selection])

        # Look up the color configuration info for each row.
        if index_by_id:
            config = dict((
                id(self._rows[r]),
                [self._get_itemconfig(r, c) for c in range(self._num_columns)])
                          for r in row_indices)
        else:
            config = dict((
                r,
                [self._get_itemconfig(r, c) for c in range(self._num_columns)])
                          for r in row_indices)

        return selection, config

    def _restore_config_info(self, cookie, index_by_id=False, see=False):
        """
        Restore selection & color configuration information that was
        saved using ``_save_config_info``.
        """
        selection, config = cookie

        # Clear the selection.
        if selection is None:
            self._mlb.selection_clear(0, 'end')

        # Restore selection & color config
        if index_by_id:
            for r, row in enumerate(self._rows):
                if id(row) in config:
                    for c in range(self._num_columns):
                        self._mlb.itemconfigure(r, c, config[id(row)][c])
                if id(row) == selection:
                    self._mlb.select(r, see=see)
        else:
            if selection is not None:
                self._mlb.select(selection, see=see)
            for r in config:
                for c in range(self._num_columns):
                    self._mlb.itemconfigure(r, c, config[r][c])

    #/////////////////////////////////////////////////////////////////
    # Debugging (Invariant Checker)
    #/////////////////////////////////////////////////////////////////

    _DEBUG = False
    """If true, then run ``_check_table_vs_mlb()`` after any operation
       that modifies the table."""

    def _check_table_vs_mlb(self):
        """
        Verify that the contents of the table's ``_rows`` variable match
        the contents of its multi-listbox (``_mlb``).  This is just
        included for debugging purposes, to make sure that the
        list-modifying operations are working correctly.
        """
        for col in self._mlb.listboxes:
            assert len(self) == col.size()
        for row in self:
            assert len(row) == self._num_columns
        assert self._num_columns == len(self._mlb.column_names)
        #assert self._column_names == self._mlb.column_names
        for i, row in enumerate(self):
            for j, cell in enumerate(row):
                if self._reprfunc is not None:
                    cell = self._reprfunc(i, j, cell)
                assert self._mlb.get(i)[j] == cell
class Table(object):
    """
    A display widget for a table of values, based on a ``MultiListbox``
    widget.  For many purposes, ``Table`` can be treated as a
    list-of-lists.  E.g., table[i] is a list of the values for row i;
    and table.append(row) adds a new row with the given lits of
    values.  Individual cells can be accessed using table[i,j], which
    refers to the j-th column of the i-th row.  This can be used to
    both read and write values from the table.  E.g.:

        >>> table[i,j] = 'hello'

    The column (j) can be given either as an index number, or as a
    column name.  E.g., the following prints the value in the 3rd row
    for the 'First Name' column:

        >>> print(table[3, 'First Name'])
        John

    You can configure the colors for individual rows, columns, or
    cells using ``rowconfig()``, ``columnconfig()``, and ``itemconfig()``.
    The color configuration for each row will be preserved if the
    table is modified; however, when new rows are added, any color
    configurations that have been made for *columns* will not be
    applied to the new row.

    Note: Although ``Table`` acts like a widget in some ways (e.g., it
    defines ``grid()``, ``pack()``, and ``bind()``), it is not itself a
    widget; it just contains one.  This is because widgets need to
    define ``__getitem__()``, ``__setitem__()``, and ``__nonzero__()`` in
    a way that's incompatible with the fact that ``Table`` behaves as a
    list-of-lists.

    :ivar _mlb: The multi-column listbox used to display this table's data.
    :ivar _rows: A list-of-lists used to hold the cell values of this
        table.  Each element of _rows is a row value, i.e., a list of
        cell values, one for each column in the row.
    """
    def __init__(self, master, column_names, rows=None,
                 column_weights=None,
                 scrollbar=True, click_to_sort=True,
                 reprfunc=None, cnf={}, **kw):
        """
        Construct a new Table widget.

        :type master: Tkinter.Widget
        :param master: The widget that should contain the new table.
        :type column_names: list(str)
        :param column_names: A list of names for the columns; these
            names will be used to create labels for each column;
            and can be used as an index when reading or writing
            cell values from the table.
        :type rows: list(list)
        :param rows: A list of row values used to initialze the table.
            Each row value should be a tuple of cell values, one for
            each column in the row.
        :type scrollbar: bool
        :param scrollbar: If true, then create a scrollbar for the
            new table widget.
        :type click_to_sort: bool
        :param click_to_sort: If true, then create bindings that will
            sort the table's rows by a given column's values if the
            user clicks on that colum's label.
        :type reprfunc: function
        :param reprfunc: If specified, then use this function to
            convert each table cell value to a string suitable for
            display.  ``reprfunc`` has the following signature:
            reprfunc(row_index, col_index, cell_value) -> str
            (Note that the column is specified by index, not by name.)
        :param cnf, kw: Configuration parameters for this widget's
            contained ``MultiListbox``.  See ``MultiListbox.__init__()``
            for details.
        """
        self._num_columns = len(column_names)
        self._reprfunc = reprfunc
        self._frame = Frame(master)

        self._column_name_to_index = dict((c,i) for (i,c) in
                                          enumerate(column_names))

        # Make a copy of the rows & check that it's valid.
        if rows is None: self._rows = []
        else: self._rows = [[v for v in row] for row in rows]
        for row in self._rows: self._checkrow(row)

        # Create our multi-list box.
        self._mlb = MultiListbox(self._frame, column_names,
                                 column_weights, cnf, **kw)
        self._mlb.pack(side='left', expand=True, fill='both')

        # Optional scrollbar
        if scrollbar:
            sb = Scrollbar(self._frame, orient='vertical',
                           command=self._mlb.yview)
            self._mlb.listboxes[0]['yscrollcommand'] = sb.set
            #for listbox in self._mlb.listboxes:
            #    listbox['yscrollcommand'] = sb.set
            sb.pack(side='right', fill='y')
            self._scrollbar = sb

        # Set up sorting
        self._sortkey = None
        if click_to_sort:
            for i, l in enumerate(self._mlb.column_labels):
                l.bind('<Button-1>', self._sort)

        # Fill in our multi-list box.
        self._fill_table()

    #/////////////////////////////////////////////////////////////////
    #{ Widget-like Methods
    #/////////////////////////////////////////////////////////////////
    # These all just delegate to either our frame or our MLB.

    def pack(self, *args, **kwargs):
        """Position this table's main frame widget in its parent
        widget.  See ``Tkinter.Frame.pack()`` for more info."""
        self._frame.pack(*args, **kwargs)

    def grid(self, *args, **kwargs):
        """Position this table's main frame widget in its parent
        widget.  See ``Tkinter.Frame.grid()`` for more info."""
        self._frame.grid(*args, **kwargs)

    def focus(self):
        """Direct (keyboard) input foxus to this widget."""
        self._mlb.focus()

    def bind(self, sequence=None, func=None, add=None):
        """Add a binding to this table's main frame that will call
        ``func`` in response to the event sequence."""
        self._mlb.bind(sequence, func, add)

    def rowconfigure(self, row_index, cnf={}, **kw):
        """:see: ``MultiListbox.rowconfigure()``"""
        self._mlb.rowconfigure(row_index, cnf, **kw)

    def columnconfigure(self, col_index, cnf={}, **kw):
        """:see: ``MultiListbox.columnconfigure()``"""
        col_index = self.column_index(col_index)
        self._mlb.columnconfigure(col_index, cnf, **kw)

    def itemconfigure(self, row_index, col_index, cnf=None, **kw):
        """:see: ``MultiListbox.itemconfigure()``"""
        col_index = self.column_index(col_index)
        return self._mlb.itemconfigure(row_index, col_index, cnf, **kw)

    def bind_to_labels(self, sequence=None, func=None, add=None):
        """:see: ``MultiListbox.bind_to_labels()``"""
        return self._mlb.bind_to_labels(sequence, func, add)

    def bind_to_listboxes(self, sequence=None, func=None, add=None):
        """:see: ``MultiListbox.bind_to_listboxes()``"""
        return self._mlb.bind_to_listboxes(sequence, func, add)

    def bind_to_columns(self, sequence=None, func=None, add=None):
        """:see: ``MultiListbox.bind_to_columns()``"""
        return self._mlb.bind_to_columns(sequence, func, add)

    rowconfig = rowconfigure
    columnconfig = columnconfigure
    itemconfig = itemconfigure

    #/////////////////////////////////////////////////////////////////
    #{ Table as list-of-lists
    #/////////////////////////////////////////////////////////////////

    def insert(self, row_index, rowvalue):
        """
        Insert a new row into the table, so that its row index will be
        ``row_index``.  If the table contains any rows whose row index
        is greater than or equal to ``row_index``, then they will be
        shifted down.

        :param rowvalue: A tuple of cell values, one for each column
            in the new row.
        """
        self._checkrow(rowvalue)
        self._rows.insert(row_index, rowvalue)
        if self._reprfunc is not None:
            rowvalue = [self._reprfunc(row_index,j,v)
                        for (j,v) in enumerate(rowvalue)]
        self._mlb.insert(row_index, rowvalue)
        if self._DEBUG: self._check_table_vs_mlb()

    def extend(self, rowvalues):
        """
        Add new rows at the end of the table.

        :param rowvalues: A list of row values used to initialze the
            table.  Each row value should be a tuple of cell values,
            one for each column in the row.
        """
        for rowvalue in rowvalues: self.append(rowvalue)
        if self._DEBUG: self._check_table_vs_mlb()

    def append(self, rowvalue):
        """
        Add a new row to the end of the table.

        :param rowvalue: A tuple of cell values, one for each column
            in the new row.
        """
        self.insert(len(self._rows), rowvalue)
        if self._DEBUG: self._check_table_vs_mlb()

    def clear(self):
        """
        Delete all rows in this table.
        """
        self._rows = []
        self._mlb.delete(0, 'end')
        if self._DEBUG: self._check_table_vs_mlb()

    def __getitem__(self, index):
        """
        Return the value of a row or a cell in this table.  If
        ``index`` is an integer, then the row value for the ``index``th
        row.  This row value consists of a tuple of cell values, one
        for each column in the row.  If ``index`` is a tuple of two
        integers, ``(i,j)``, then return the value of the cell in the
        ``i``th row and the ``j``th column.
        """
        if isinstance(index, slice):
            raise ValueError('Slicing not supported')
        elif isinstance(index, tuple) and len(index)==2:
            return self._rows[index[0]][self.column_index(index[1])]
        else:
            return tuple(self._rows[index])

    def __setitem__(self, index, val):
        """
        Replace the value of a row or a cell in this table with
        ``val``.

        If ``index`` is an integer, then ``val`` should be a row value
        (i.e., a tuple of cell values, one for each column).  In this
        case, the values of the ``index``th row of the table will be
        replaced with the values in ``val``.

        If ``index`` is a tuple of integers, ``(i,j)``, then replace the
        value of the cell in the ``i``th row and ``j``th column with
        ``val``.
        """
        if isinstance(index, slice):
            raise ValueError('Slicing not supported')


        # table[i,j] = val
        elif isinstance(index, tuple) and len(index)==2:
            i, j = index[0], self.column_index(index[1])
            config_cookie = self._save_config_info([i])
            self._rows[i][j] = val
            if self._reprfunc is not None:
                val = self._reprfunc(i, j, val)
            self._mlb.listboxes[j].insert(i, val)
            self._mlb.listboxes[j].delete(i+1)
            self._restore_config_info(config_cookie)

        # table[i] = val
        else:
            config_cookie = self._save_config_info([index])
            self._checkrow(val)
            self._rows[index] = list(val)
            if self._reprfunc is not None:
                val = [self._reprfunc(index,j,v) for (j,v) in enumerate(val)]
            self._mlb.insert(index, val)
            self._mlb.delete(index+1)
            self._restore_config_info(config_cookie)

    def __delitem__(self, row_index):
        """
        Delete the ``row_index``th row from this table.
        """
        if isinstance(row_index, slice):
            raise ValueError('Slicing not supported')
        if isinstance(row_index, tuple) and len(row_index)==2:
            raise ValueError('Cannot delete a single cell!')
        del self._rows[row_index]
        self._mlb.delete(row_index)
        if self._DEBUG: self._check_table_vs_mlb()

    def __len__(self):
        """
        :return: the number of rows in this table.
        """
        return len(self._rows)

    def _checkrow(self, rowvalue):
        """
        Helper function: check that a given row value has the correct
        number of elements; and if not, raise an exception.
        """
        if len(rowvalue) != self._num_columns:
            raise ValueError('Row %r has %d columns; expected %d' %
                             (rowvalue, len(rowvalue), self._num_columns))

    #/////////////////////////////////////////////////////////////////
    # Columns
    #/////////////////////////////////////////////////////////////////

    @property
    def column_names(self):
        """A list of the names of the columns in this table."""
        return self._mlb.column_names

    def column_index(self, i):
        """
        If ``i`` is a valid column index integer, then return it as is.
        Otherwise, check if ``i`` is used as the name for any column;
        if so, return that column's index.  Otherwise, raise a
        ``KeyError`` exception.
        """
        if isinstance(i, int) and 0 <= i < self._num_columns:
            return i
        else:
            # This raises a key error if the column is not found.
            return self._column_name_to_index[i]

    def hide_column(self, column_index):
        """:see: ``MultiListbox.hide_column()``"""
        self._mlb.hide_column(self.column_index(column_index))

    def show_column(self, column_index):
        """:see: ``MultiListbox.show_column()``"""
        self._mlb.show_column(self.column_index(column_index))

    #/////////////////////////////////////////////////////////////////
    # Selection
    #/////////////////////////////////////////////////////////////////

    def selected_row(self):
        """
        Return the index of the currently selected row, or None if
        no row is selected.  To get the row value itself, use
        ``table[table.selected_row()]``.
        """
        sel = self._mlb.curselection()
        if sel: return int(sel[0])
        else: return None

    def select(self, index=None, delta=None, see=True):
        """:see: ``MultiListbox.select()``"""
        self._mlb.select(index, delta, see)

    #/////////////////////////////////////////////////////////////////
    # Sorting
    #/////////////////////////////////////////////////////////////////

    def sort_by(self, column_index, order='toggle'):
        """
        Sort the rows in this table, using the specified column's
        values as a sort key.

        :param column_index: Specifies which column to sort, using
            either a column index (int) or a column's label name
            (str).

        :param order: Specifies whether to sort the values in
            ascending or descending order:

              - ``'ascending'``: Sort from least to greatest.
              - ``'descending'``: Sort from greatest to least.
              - ``'toggle'``: If the most recent call to ``sort_by()``
                sorted the table by the same column (``column_index``),
                then reverse the rows; otherwise sort in ascending
                order.
        """
        if order not in ('ascending', 'descending', 'toggle'):
            raise ValueError('sort_by(): order should be "ascending", '
                             '"descending", or "toggle".')
        column_index = self.column_index(column_index)
        config_cookie = self._save_config_info(index_by_id=True)

        # Sort the rows.
        if order == 'toggle' and column_index == self._sortkey:
            self._rows.reverse()
        else:
            self._rows.sort(key=operator.itemgetter(column_index),
                            reverse=(order=='descending'))
            self._sortkey = column_index

        # Redraw the table.
        self._fill_table()
        self._restore_config_info(config_cookie, index_by_id=True, see=True)
        if self._DEBUG: self._check_table_vs_mlb()

    def _sort(self, event):
        """Event handler for clicking on a column label -- sort by
        that column."""
        column_index = event.widget.column_index

        # If they click on the far-left of far-right of a column's
        # label, then resize rather than sorting.
        if self._mlb._resize_column(event):
            return 'continue'

        # Otherwise, sort.
        else:
            self.sort_by(column_index)
            return 'continue'

    #/////////////////////////////////////////////////////////////////
    #{ Table Drawing Helpers
    #/////////////////////////////////////////////////////////////////

    def _fill_table(self, save_config=True):
        """
        Re-draw the table from scratch, by clearing out the table's
        multi-column listbox; and then filling it in with values from
        ``self._rows``.  Note that any cell-, row-, or column-specific
        color configuration that has been done will be lost.  The
        selection will also be lost -- i.e., no row will be selected
        after this call completes.
        """
        self._mlb.delete(0, 'end')
        for i, row in enumerate(self._rows):
            if self._reprfunc is not None:
                row = [self._reprfunc(i,j,v) for (j,v) in enumerate(row)]
            self._mlb.insert('end', row)

    def _get_itemconfig(self, r, c):
        return dict( (k, self._mlb.itemconfig(r, c, k)[-1])
                     for k in ('foreground', 'selectforeground',
                               'background', 'selectbackground') )

    def _save_config_info(self, row_indices=None, index_by_id=False):
        """
        Return a 'cookie' containing information about which row is
        selected, and what color configurations have been applied.
        this information can the be re-applied to the table (after
        making modifications) using ``_restore_config_info()``.  Color
        configuration information will be saved for any rows in
        ``row_indices``, or in the entire table, if
        ``row_indices=None``.  If ``index_by_id=True``, the the cookie
        will associate rows with their configuration information based
        on the rows' python id.  This is useful when performing
        operations that re-arrange the rows (e.g. ``sort``).  If
        ``index_by_id=False``, then it is assumed that all rows will be
        in the same order when ``_restore_config_info()`` is called.
        """
        # Default value for row_indices is all rows.
        if row_indices is None:
            row_indices = list(range(len(self._rows)))

        # Look up our current selection.
        selection = self.selected_row()
        if index_by_id and selection is not None:
            selection = id(self._rows[selection])

        # Look up the color configuration info for each row.
        if index_by_id:
            config = dict((id(self._rows[r]), [self._get_itemconfig(r, c)
                                        for c in range(self._num_columns)])
                          for r in row_indices)
        else:
            config = dict((r, [self._get_itemconfig(r, c)
                               for c in range(self._num_columns)])
                          for r in row_indices)


        return selection, config

    def _restore_config_info(self, cookie, index_by_id=False, see=False):
        """
        Restore selection & color configuration information that was
        saved using ``_save_config_info``.
        """
        selection, config = cookie

        # Clear the selection.
        if selection is None:
            self._mlb.selection_clear(0, 'end')

        # Restore selection & color config
        if index_by_id:
            for r, row in enumerate(self._rows):
                if id(row) in config:
                    for c in range(self._num_columns):
                        self._mlb.itemconfigure(r, c, config[id(row)][c])
                if id(row) == selection:
                    self._mlb.select(r, see=see)
        else:
            if selection is not None:
                self._mlb.select(selection, see=see)
            for r in config:
                for c in range(self._num_columns):
                    self._mlb.itemconfigure(r, c, config[r][c])

    #/////////////////////////////////////////////////////////////////
    # Debugging (Invariant Checker)
    #/////////////////////////////////////////////////////////////////

    _DEBUG = False
    """If true, then run ``_check_table_vs_mlb()`` after any operation
       that modifies the table."""

    def _check_table_vs_mlb(self):
        """
        Verify that the contents of the table's ``_rows`` variable match
        the contents of its multi-listbox (``_mlb``).  This is just
        included for debugging purposes, to make sure that the
        list-modifying operations are working correctly.
        """
        for col in self._mlb.listboxes:
            assert len(self) == col.size()
        for row in self:
            assert len(row) == self._num_columns
        assert self._num_columns == len(self._mlb.column_names)
        #assert self._column_names == self._mlb.column_names
        for i, row in enumerate(self):
            for j, cell in enumerate(row):
                if self._reprfunc is not None:
                    cell = self._reprfunc(i, j, cell)
                assert self._mlb.get(i)[j] == cell
Example #4
0
    def __init__(self, root, *args, **kw):
        GUIDialog.__init__(self, master=root, *args, **kw)
        self.qom_type_var = root.qom_type_var

        self.title(_("Device Tree"))
        self.grid()

        self.columnconfigure(0, weight=1, minsize=300)
        self.columnconfigure(2, weight=1, minsize=100)
        self.rowconfigure(0, weight=1)

        geom = "+" + str(int(root.winfo_rootx())) \
             + "+" + str(int(root.winfo_rooty()))
        self.geometry(geom)

        self.focus()

        self.device_tree = dt = VarTreeview(self, selectmode="browse")
        dt["columns"] = "Macros"

        dt.heading("#0", text=_("Devices"))
        dt.heading("Macros", text=_("Macros"))

        dt.bind("<ButtonPress-1>", self.on_b1_press_dt)

        dt.grid(row=0, column=0, sticky="NEWS")

        add_scrollbars_native(self, dt)

        column_fr = Frame(self, borderwidth=0)
        column_fr.grid(row=0, column=2, rowspan=2, sticky="SEWN")
        column_fr.columnconfigure(0, weight=1)
        column_fr.rowconfigure(0, weight=1)
        column_fr.rowconfigure(1, weight=1, minsize=100)

        fr_at = VarLabelFrame(column_fr, text=_("Architecture filter"))
        fr_at.grid(row=0, column=0, sticky="SEWN")

        self.fr_qt = VarLabelFrame(column_fr, text=_("Select QOM type"))
        self.fr_qt.grid(row=1, column=0, sticky="SEWN")

        self.add_button = VarButton(column_fr,
                                    text=_("Select"),
                                    command=self.on_select_qom_type)
        self.add_button.grid(row=2, column=0, sticky="EW")
        self.add_button.config(state="disabled")

        qtype_dt = self.qtype_dt = qvd_get(
            root.mach.project.build_path,
            version=root.mach.project.target_version).qvc.device_tree

        arch_buttons = Frame(fr_at, borderwidth=0)
        arch_buttons.pack(fill="x")

        arch_buttons.columnconfigure(0, weight=1, minsize=60)
        arch_buttons.columnconfigure(1, weight=1, minsize=60)
        arch_buttons.columnconfigure(2, weight=1, minsize=60)

        bt_all_arches = VarButton(arch_buttons,
                                  text=_("All"),
                                  command=self.select_arches)
        bt_all_arches.grid(row=0, column=0, sticky="EW")

        bt_none_arches = VarButton(arch_buttons,
                                   text=_("None"),
                                   command=self.deselect_arches)
        bt_none_arches.grid(row=0, column=1, sticky="EW")

        bt_invert_arches = VarButton(arch_buttons,
                                     text=_("Invert"),
                                     command=self.invert_arches)
        bt_invert_arches.grid(row=0, column=2, sticky="EW")

        if not qtype_dt.arches:
            bt_all_arches.config(state="disabled")
            bt_none_arches.config(state="disabled")
            bt_invert_arches.config(state="disabled")

        arch_selector_outer = Frame(fr_at, borderwidth=0)
        arch_selector_outer.pack(fill="both", anchor="w")
        arch_selector, scrolltag = add_scrollbars_with_tags(
            arch_selector_outer, GUIFrame)

        ac = self.arches_checkbox = []
        for a in sorted(list(qtype_dt.arches)):
            v = IntVar()
            c = Checkbutton(arch_selector,
                text = a,
                variable = v,
                command = lambda arch = a, var = v: \
                    self.on_toggle_arch(arch, var)
            )

            c.pack(expand=1, anchor="w")
            c.bindtags((scrolltag, ) + c.bindtags())
            c.select()
            ac.append(c)

        # key: item
        # value: ItemDesc
        self.detached_items = {}
        self.all_items = {}

        self.disabled_arches = set()

        self.qom_create_tree("", qtype_dt.children)