Exemple #1
0
    def test_prepend_kwarg(self):
        "You can prepend to a list source using kwargs"
        source = ListSource(
            data=[
                {'val1': 'first', 'val2': 111},
                {'val1': 'second', 'val2': 222},
                {'val1': 'third', 'val2': 333},
            ],
            accessors=['val1', 'val2']
        )

        self.assertEqual(len(source), 3)

        listener = Mock()
        source.add_listener(listener)

        # Insert the new element
        row = source.prepend(val1='new element', val2=999)

        self.assertEqual(len(source), 4)
        self.assertEqual(source[0], row)
        self.assertEqual(row.val1, 'new element')
        self.assertEqual(row.val2, 999)

        listener.insert.assert_called_once_with(index=0, item=row)
Exemple #2
0
    def test_append(self):
        "You can append to a list source using positional args"
        source = ListSource(
            data=[
                {'val1': 'first', 'val2': 111},
                {'val1': 'second', 'val2': 222},
                {'val1': 'third', 'val2': 333},
            ],
            accessors=['val1', 'val2']
        )

        self.assertEqual(len(source), 3)

        listener = Mock()
        source.add_listener(listener)

        # Append the new element
        row = source.append('new element', 999)

        self.assertEqual(len(source), 4)
        self.assertEqual(source[3], row)
        self.assertEqual(row.val1, 'new element')
        self.assertEqual(row.val2, 999)

        listener.insert.assert_called_once_with(index=3, item=row)
Exemple #3
0
    def test_init_with_dict(self):
        "A ListSource can be instantiated from dicts"
        source = ListSource(
            data=[
                {'val1': 'first', 'val2': 111},
                {'val1': 'second', 'val2': 222},
                {'val1': 'third', 'val2': 333},
            ],
            accessors=['val1', 'val2']
        )

        self.assertEqual(len(source), 3)

        self.assertEqual(source[0].val1, 'first')
        self.assertEqual(source[0].val2, 111)

        self.assertEqual(source[1].val1, 'second')
        self.assertEqual(source[1].val2, 222)

        listener = Mock()
        source.add_listener(listener)

        # Set element 1
        source[1] = {'val1': 'new element', 'val2': 999}

        self.assertEqual(len(source), 3)

        self.assertEqual(source[1].val1, 'new element')
        self.assertEqual(source[1].val2, 999)

        listener.insert.assert_called_once_with(index=1, item=source[1])
Exemple #4
0
    def test_init_with_list(self):
        "A ListSource can be instantiated from lists"
        source = ListSource(
            data=[
                ['first', 111],
                ['second', 222],
                ['third', 333],
            ],
            accessors=['val1', 'val2']
        )

        self.assertEqual(len(source), 3)

        self.assertEqual(source[0].val1, 'first')
        self.assertEqual(source[0].val2, 111)

        self.assertEqual(source[1].val1, 'second')
        self.assertEqual(source[1].val2, 222)

        listener = Mock()
        source.add_listener(listener)

        # Set element 1
        source[1] = ['new element', 999]

        self.assertEqual(len(source), 3)

        self.assertEqual(source[1].val1, 'new element')
        self.assertEqual(source[1].val2, 999)

        listener.insert.assert_called_once_with(index=1, item=source[1])
Exemple #5
0
    def test_init_with_dict(self):
        "A ListSource can be instantiated from dicts"
        source = ListSource(
            data=[
                {'val1': 'first', 'val2': 111},
                {'val1': 'second', 'val2': 222},
                {'val1': 'third', 'val2': 333},
            ],
            accessors=['val1', 'val2']
        )

        self.assertEqual(len(source), 3)

        self.assertEqual(source[0].val1, 'first')
        self.assertEqual(source[0].val2, 111)

        self.assertEqual(source[1].val1, 'second')
        self.assertEqual(source[1].val2, 222)

        listener = Mock()
        source.add_listener(listener)

        # Set element 1
        source[1] = {'val1': 'new element', 'val2': 999}

        self.assertEqual(len(source), 3)

        self.assertEqual(source[1].val1, 'new element')
        self.assertEqual(source[1].val2, 999)

        listener.insert.assert_called_once_with(index=1, item=source[1])
    def test_clear(self):
        "A list source can be cleared"
        source = ListSource(data=[
            {
                'val1': 'first',
                'val2': 111
            },
            {
                'val1': 'second',
                'val2': 222
            },
            {
                'val1': 'third',
                'val2': 333
            },
        ],
                            accessors=['val1', 'val2'])

        self.assertEqual(len(source), 3)

        listener = Mock()
        source.add_listener(listener)

        # Clear the list
        source.clear()
        self.assertEqual(len(source), 0)

        listener.clear.assert_called_once_with()
Exemple #7
0
    def test_init_with_list(self):
        "A ListSource can be instantiated from lists"
        source = ListSource(
            data=[
                ['first', 111],
                ['second', 222],
                ['third', 333],
            ],
            accessors=['val1', 'val2']
        )

        self.assertEqual(len(source), 3)

        self.assertEqual(source[0].val1, 'first')
        self.assertEqual(source[0].val2, 111)

        self.assertEqual(source[1].val1, 'second')
        self.assertEqual(source[1].val2, 222)

        listener = Mock()
        source.add_listener(listener)

        # Set element 1
        source[1] = ['new element', 999]

        self.assertEqual(len(source), 3)

        self.assertEqual(source[1].val1, 'new element')
        self.assertEqual(source[1].val2, 999)

        listener.insert.assert_called_once_with(index=1, item=source[1])
Exemple #8
0
    def test_remove(self):
        "You can remove an item from a list source"
        source = ListSource(
            data=[
                {'val1': 'first', 'val2': 111},
                {'val1': 'second', 'val2': 222},
                {'val1': 'third', 'val2': 333},
            ],
            accessors=['val1', 'val2']
        )

        self.assertEqual(len(source), 3)

        listener = Mock()
        source.add_listener(listener)

        # Remove the second element
        row = source.remove(source[1])

        self.assertEqual(len(source), 2)
        self.assertEqual(source[0].val1, 'first')
        self.assertEqual(source[0].val2, 111)

        self.assertEqual(source[1].val1, 'third')
        self.assertEqual(source[1].val2, 333)

        listener.remove.assert_called_once_with(item=row)
Exemple #9
0
    def test_append(self):
        "You can append to a list source using positional args"
        source = ListSource(
            data=[
                {'val1': 'first', 'val2': 111},
                {'val1': 'second', 'val2': 222},
                {'val1': 'third', 'val2': 333},
            ],
            accessors=['val1', 'val2']
        )

        self.assertEqual(len(source), 3)

        listener = Mock()
        source.add_listener(listener)

        # Append the new element
        row = source.append('new element', 999)

        self.assertEqual(len(source), 4)
        self.assertEqual(source[3], row)
        self.assertEqual(row.val1, 'new element')
        self.assertEqual(row.val2, 999)

        listener.insert.assert_called_once_with(index=3, item=row)
Exemple #10
0
    def test_remove(self):
        "You can remove an item from a list source"
        source = ListSource(
            data=[
                {'val1': 'first', 'val2': 111},
                {'val1': 'second', 'val2': 222},
                {'val1': 'third', 'val2': 333},
            ],
            accessors=['val1', 'val2']
        )

        self.assertEqual(len(source), 3)

        listener = Mock()
        source.add_listener(listener)

        # Remove the second element
        row = source.remove(source[1])

        self.assertEqual(len(source), 2)
        self.assertEqual(source[0].val1, 'first')
        self.assertEqual(source[0].val2, 111)

        self.assertEqual(source[1].val1, 'third')
        self.assertEqual(source[1].val2, 333)

        listener.remove.assert_called_once_with(item=row, index=1)
Exemple #11
0
    def test_prepend_kwarg(self):
        "You can prepend to a list source using kwargs"
        source = ListSource(
            data=[
                {'val1': 'first', 'val2': 111},
                {'val1': 'second', 'val2': 222},
                {'val1': 'third', 'val2': 333},
            ],
            accessors=['val1', 'val2']
        )

        self.assertEqual(len(source), 3)

        listener = Mock()
        source.add_listener(listener)

        # Insert the new element
        row = source.prepend(val1='new element', val2=999)

        self.assertEqual(len(source), 4)
        self.assertEqual(source[0], row)
        self.assertEqual(row.val1, 'new element')
        self.assertEqual(row.val2, 999)

        listener.insert.assert_called_once_with(index=0, item=row)
Exemple #12
0
    def test_clear(self):
        "A list source can be cleared"
        source = ListSource(
            data=[
                {'val1': 'first', 'val2': 111},
                {'val1': 'second', 'val2': 222},
                {'val1': 'third', 'val2': 333},
            ],
            accessors=['val1', 'val2']
        )

        self.assertEqual(len(source), 3)

        listener = Mock()
        source.add_listener(listener)

        # Clear the list
        source.clear()
        self.assertEqual(len(source), 0)

        listener.clear.assert_called_once_with()
Exemple #13
0
class DetailedList(Widget):
    """ A widget to hold data in a list form. Rows are selectable and can be deleted.
    A updated function can be invoked by pulling the list down.

    Args:
        id (str): An identifier for this widget.
        data (list of `str`): List of strings which to display on the widget.
        on_delete (``callable``): Function that is invoked on row deletion.
        on_refresh (``callable``): Function that is invoked on user initialised refresh.
        on_select (``callable``): Function that is invoked on row selection.
        style (:obj:`Style`): An optional style object. If no style is provided then
            a new one will be created for the widget.
        factory (:obj:`module`): A python module that is capable to return a
            implementation of this class with the same name. (optional & normally not needed)

    Examples:
        >>> import toga
        >>> def selection_handler(widget, selection):
        >>>     print('Row {} of widget {} was selected.'.format(selection, widget))
        >>>
        >>> dlist = toga.DetailedList(data=['Item 0', 'Item 1', 'Item 2'], on_select=selection_handler)
    """
    MIN_HEIGHT = 100
    MIN_WIDTH = 100

    def __init__(self,
                 id=None,
                 data=None,
                 on_delete=None,
                 on_refresh=None,
                 on_select=None,
                 style=None,
                 factory=None):
        super().__init__(id=id, style=style, factory=factory)
        self._data = None
        self._impl = self.factory.DetailedList(interface=self)

        self.data = data
        self.on_delete = on_delete
        self.on_refresh = on_refresh
        self.on_select = on_select

    @property
    def data(self):
        """ The data source of the widget. It accepts table data
        in the form of ``list``, ``tuple``, or :obj:`ListSource`

        Returns:
            Returns a (:obj:`ListSource`).
        """
        return self._data

    @data.setter
    def data(self, data):
        if data is None:
            self._data = ListSource(data=[],
                                    accessors=['icon', 'label1', 'label2'])
        elif isinstance(data, (list, tuple)):
            self._data = ListSource(data=data,
                                    accessors=['icon', 'label1', 'label2'])
        else:
            self._data = data

        self._data.add_listener(self._impl)
        self._impl.change_source(source=self._data)

    def scroll_to_top(self):
        """Scroll the view so that the top of the list (first row) is visible
        """
        self.scroll_to_row(0)

    def scroll_to_row(self, row):
        """Scroll the view so that the specified row index is visible.

        Args:
            row: The index of the row to make visible. Negative values refer
                 to the nth last row (-1 is the last row, -2 second last,
                 and so on)
        """
        if row >= 0:
            self._impl.scroll_to_row(row)
        else:
            self._impl.scroll_to_row(len(self.data) + row)

    def scroll_to_bottom(self):
        """Scroll the view so that the bottom of the list (last row) is visible
        """
        self.scroll_to_row(-1)

    @property
    def on_delete(self):
        """ The function invoked on row deletion. The delete handler must accept two arguments.
        The first is a ref. to the widget and the second the row that is about to be deleted.

        Examples:
            >>> def delete_handler(widget, row):
            >>>     print('row ', row, 'is going to be deleted from widget', widget)

        Returns:
            The function that is invoked when deleting a row.
        """
        return self._on_delete

    @on_delete.setter
    def on_delete(self, handler: callable):
        self._on_delete = wrapped_handler(self, handler)
        self._impl.set_on_delete(self._on_delete)

    @property
    def on_refresh(self):
        """
        Returns:
            The function to be invoked on user initialised refresh.
        """
        return self._on_refresh

    @on_refresh.setter
    def on_refresh(self, handler: callable):
        self._on_refresh = wrapped_handler(self, handler,
                                           self._impl.after_on_refresh)
        self._impl.set_on_refresh(self._on_refresh)

    @property
    def on_select(self):
        """ The handler function must accept two arguments, widget and row.

        Returns:
            The function to be invoked on selecting a row.
        """
        return self._on_select

    @on_select.setter
    def on_select(self, handler: callable):
        self._on_select = wrapped_handler(self, handler)
        self._impl.set_on_select(self._on_select)
Exemple #14
0
class DetailedList(Widget):
    """ A widget to hold data in a list form. Rows are selectable and can be deleted.
    A updated function can be invoked by pulling the list down.

    Args:
        id (str): An identifier for this widget.
        data (list of `str`): List of strings which to display on the widget.
        on_delete (``callable``): Function that is invoked on row deletion.
        on_refresh (``callable``): Function that is invoked on user initialised refresh.
        on_select (``callable``): Function that is invoked on row selection.
        style (:obj:`Style`): An optional style object. If no style is provided then
            a new one will be created for the widget.
        factory (:obj:`module`): A python module that is capable to return a
            implementation of this class with the same name. (optional & normally not needed)

    Examples:
        >>> import toga
        >>> def selection_handler(widget, selection):
        >>>     print('Row {} of widget {} was selected.'.format(selection, widget))
        >>>
        >>> dlist = toga.DetailedList(data=['Item 0', 'Item 1', 'Item 2'], on_select=selection_handler)
    """
    MIN_HEIGHT = 100
    MIN_WIDTH = 100

    def __init__(self, id=None, data=None, on_delete=None, on_refresh=None, on_select=None, style=None, factory=None):
        super().__init__(id=id, style=style, factory=factory)
        self._data = None
        self._impl = self.factory.DetailedList(interface=self)

        self.data = data
        self.on_delete = on_delete
        self.on_refresh = on_refresh
        self.on_select = on_select

    @property
    def data(self):
        """ The data source of the widget. It accepts table data
        in the form of ``list``, ``tuple``, or :obj:`ListSource`

        Returns:
            Returns a (:obj:`ListSource`).
        """
        return self._data

    @data.setter
    def data(self, data):
        if data is None:
            self._data = ListSource(data=[], accessors=['icon', 'label1', 'label2'])
        elif isinstance(data, (list, tuple)):
            self._data = ListSource(data=data, accessors=['icon', 'label1', 'label2'])
        else:
            self._data = data

        self._data.add_listener(self._impl)
        self._impl.change_source(source=self._data)

    def scroll_to_top(self):
        """Scroll the view so that the top of the list (first row) is visible
        """
        self.scroll_to_row(0)

    def scroll_to_row(self, row):
        """Scroll the view so that the specified row index is visible.

        Args:
            row: The index of the row to make visible. Negative values refer
                 to the nth last row (-1 is the last row, -2 second last,
                 and so on)
        """
        if row >= 0:
            self._impl.scroll_to_row(row)
        else:
            self._impl.scroll_to_row(len(self.data) + row)

    def scroll_to_bottom(self):
        """Scroll the view so that the bottom of the list (last row) is visible
        """
        self.scroll_to_row(-1)

    @property
    def on_delete(self):
        """ The function invoked on row deletion. The delete handler must accept two arguments.
        The first is a ref. to the widget and the second the row that is about to be deleted.

        Examples:
            >>> def delete_handler(widget, row):
            >>>     print('row ', row, 'is going to be deleted from widget', widget)

        Returns:
            The function that is invoked when deleting a row.
        """
        return self._on_delete

    @on_delete.setter
    def on_delete(self, handler: callable):
        self._on_delete = wrapped_handler(self, handler)
        self._impl.set_on_delete(self._on_delete)

    @property
    def on_refresh(self):
        """
        Returns:
            The function to be invoked on user initialised refresh.
        """
        return self._on_refresh

    @on_refresh.setter
    def on_refresh(self, handler: callable):
        self._on_refresh = wrapped_handler(self, handler, self._impl.after_on_refresh)
        self._impl.set_on_refresh(self._on_refresh)

    @property
    def on_select(self):
        """ The handler function must accept two arguments, widget and row.

        Returns:
            The function to be invoked on selecting a row.
        """
        return self._on_select

    @on_select.setter
    def on_select(self, handler: callable):
        self._on_select = wrapped_handler(self, handler)
        self._impl.set_on_select(self._on_select)
Exemple #15
0
class Table(Widget):
    """ A Table Widget allows the display of data in the from of columns and rows.

    Args:
        headings (``list`` of ``str``): The list of headings for the table.
        id (str): An identifier for this widget.
        data (``list`` of ``tuple``): The data to be displayed on the table.
        accessors: A list of methods, same length as ``headings``, that describes
            how to extract the data value for each column from the row. (Optional)
        style (:obj:`Style`): An optional style object.
            If no style is provided` then a new one will be created for the widget.
        on_select (``callable``): A function to be invoked on selecting a row of the table.
        factory (:obj:`module`): A python module that is capable to return a
            implementation of this class with the same name. (optional & normally not needed)

    Examples:
        >>> headings = ['Head 1', 'Head 2', 'Head 3']
        >>> data = []
        >>> table = Table(headings, data=data)

        # Data can be in several forms.
        # A list of dictionaries, where the keys match the heading names:
        >>> data = [{'head_1': 'value 1', 'head_2': 'value 2', 'head_3': 'value3'}),
        >>>         {'head_1': 'value 1', 'head_2': 'value 2', 'head_3': 'value3'}]

        # A list of lists. These will be mapped to the headings in order:
        >>> data = [('value 1', 'value 2', 'value3'),
        >>>         ('value 1', 'value 2', 'value3')]

        # A list of values. This is only accepted if there is a single heading.
        >>> data = ['item 1', 'item 2', 'item 3']
    """
    MIN_WIDTH = 100
    MIN_HEIGHT = 100

    def __init__(self,
                 headings,
                 id=None,
                 style=None,
                 data=None,
                 accessors=None,
                 multiple_select=False,
                 on_select=None,
                 factory=None):
        super().__init__(id=id, style=style, factory=factory)
        self.headings = headings
        self._accessors = build_accessors(headings, accessors)
        self._multiple_select = multiple_select
        self._on_select = None
        self._selection = None
        self._data = None

        self._impl = self.factory.Table(interface=self)
        self.data = data

        self.on_select = on_select

    @property
    def data(self):
        """ The data source of the widget. It accepts table data
        in the form of ``list``, ``tuple``, or :obj:`ListSource`

        Returns:
            Returns a (:obj:`ListSource`).
        """
        return self._data

    @data.setter
    def data(self, data):
        if data is None:
            self._data = ListSource(accessors=self._accessors, data=[])
        elif isinstance(data, (list, tuple)):
            self._data = ListSource(accessors=self._accessors, data=data)
        else:
            self._data = data

        self._data.add_listener(self._impl)
        self._impl.change_source(source=self._data)

    @property
    def multiple_select(self):
        """Does the table allow multiple rows to be selected?"""
        return self._multiple_select

    @property
    def selection(self):
        """The current selection of the table.

        A value of None indicates no selection.
        If the table allows multiple selection, returns a list of
        selected data nodes. Otherwise, returns a single data node.
        """
        return self._selection

    def scroll_to_top(self):
        """Scroll the view so that the top of the list (first row) is visible
        """
        self._impl.scroll_to_row(0)

    def scroll_to_row(self, row):
        """Scroll the view so that the specified row index is visible.

        Args:
            row: The index of the row to make visible. Negative values refer
                 to the nth last row (-1 is the last row, -2 second last,
                 and so on)
        """
        if row >= 0:
            self._impl.scroll_to_row(row)
        else:
            self._impl.scroll_to_row(len(self.data) + row + 1)

    def scroll_to_bottom(self):
        """Scroll the view so that the bottom of the list (last row) is visible
        """
        self._impl.scroll_to_row(-1)

    @property
    def on_select(self):
        """ The callback function that is invoked when a row of the table is selected.
        The provided callback function has to accept two arguments table (``:obj:Table`)
        and row (``int`` or ``None``).

        Returns:
            (``callable``) The callback function.
        """
        return self._on_select

    @on_select.setter
    def on_select(self, handler):
        """
        Set the function to be executed on node selection

        :param handler:     callback function
        :type handler:      ``callable``
        """
        self._on_select = wrapped_handler(self, handler)
        self._impl.set_on_select(self._on_select)
Exemple #16
0
class DetailedList(Widget):
    """ A widget to hold data in a list form. Rows are selectable and can be deleted.
    A updated function can be invoked by pulling the list down.

    Args:
        id (str): An identifier for this widget.
        data (list of `dict`): List of dictionaries with required 'icon', 'title', and
            'subtitle' keys as well as optional custom keys to store additional
            info like 'pk' for a database primary key (think django ORM)
        on_delete (``callable``): Function that is invoked on row deletion.
        on_refresh (``callable``): Function that is invoked on user initialised refresh.
        on_select (``callable``): Function that is invoked on row selection.
        style (:obj:`Style`): An optional style object. If no style is provided then
            a new one will be created for the widget.
        factory (:obj:`module`): A python module that is capable to return a
            implementation of this class with the same name. (optional & normally not needed)

    Examples:
        >>> import toga
        >>> def selection_handler(widget, row):
        >>>     print('Row {} of widget {} was selected.'.format(row, widget))
        >>>
        >>> dlist = toga.DetailedList(
        ...     data=[
        ...         {
        ...             'icon': '',
        ...             'title': 'John Doe',
        ...             'subtitle': 'Employee of the Month',
        ...             'pk': 100
        ...          }
        ...      ],
        ...      on_select=selection_handler
        ... )
    """

    MIN_HEIGHT = 100
    MIN_WIDTH = 100

    def __init__(
        self,
        id=None,
        data=None,
        on_delete=None,
        on_refresh=None,
        on_select=None,
        style=None,
        factory=None,
    ):
        super().__init__(id=id, style=style, factory=factory)
        self._data = None
        self._on_delete = None
        self._on_refresh = None
        # at least _on_select must be defined before setting data for the Gtk impl
        self._on_select = None
        self._impl = self.factory.DetailedList(interface=self)

        self.data = data
        self.on_delete = on_delete
        self.on_refresh = on_refresh
        self.on_select = on_select

    @property
    def data(self):
        """ The data source of the widget. It accepts data
        in the form of ``list`` of ``dict`` or :obj:`ListSource`

        Returns:
            Returns a (:obj:`ListSource`).
        """
        return self._data

    @data.setter
    def data(self, data):
        if data is None:
            self._data = ListSource(data=[],
                                    accessors=["icon", "title", "subtitle"])
        elif isinstance(data, (list, tuple)):
            self._data = ListSource(data=data,
                                    accessors=["icon", "title", "subtitle"])
        else:
            self._data = data

        self._data.add_listener(self._impl)
        self._impl.change_source(source=self._data)

    def scroll_to_top(self):
        """Scroll the view so that the top of the list (first row) is visible
        """
        self.scroll_to_row(0)

    def scroll_to_row(self, row):
        """Scroll the view so that the specified row index is visible.

        Args:
            row: The index of the row to make visible. Negative values refer
                 to the nth last row (-1 is the last row, -2 second last,
                 and so on)
        """
        if row >= 0:
            self._impl.scroll_to_row(row)
        else:
            self._impl.scroll_to_row(len(self.data) + row)

    def scroll_to_bottom(self):
        """Scroll the view so that the bottom of the list (last row) is visible
        """
        self.scroll_to_row(-1)

    @property
    def on_delete(self):
        """ The function invoked on row deletion. The delete handler must accept two arguments.
        The first is a ref. to the widget and the second the row that is about to be deleted.

        Examples:
            >>> def delete_handler(widget, row):
            >>>     print('row ', row, 'is going to be deleted from widget', widget)

        Returns:
            The function that is invoked when deleting a row.
        """
        return self._on_delete

    @on_delete.setter
    def on_delete(self, handler: callable):
        self._on_delete = wrapped_handler(self, handler)
        self._impl.set_on_delete(self._on_delete)

    @property
    def on_refresh(self):
        """
        Returns:
            The function to be invoked on user initialised refresh.
        """
        return self._on_refresh

    @on_refresh.setter
    def on_refresh(self, handler: callable):
        self._on_refresh = wrapped_handler(self, handler,
                                           self._impl.after_on_refresh)
        self._impl.set_on_refresh(self._on_refresh)

    @property
    def selection(self):
        """The current selection.

        A value of None indicates no selection.
        """
        return self._impl.get_selection()

    @property
    def on_select(self):
        """ The handler function must accept two arguments, widget and row.

        Returns:
            The function to be invoked on selecting a row.
        """
        return self._on_select

    @on_select.setter
    def on_select(self, handler: callable):
        self._on_select = wrapped_handler(self, handler)
        self._impl.set_on_select(self._on_select)
Exemple #17
0
class Table(Widget):
    """ A Table Widget allows the display of data in the form of columns and rows.

    Args:
        headings (``list`` of ``str``): The list of headings for the table.
        id (str): An identifier for this widget.
        data (``list`` of ``tuple``): The data to be displayed on the table.
        accessors: A list of methods, same length as ``headings``, that describes
            how to extract the data value for each column from the row. (Optional)
        style (:obj:`Style`): An optional style object.
            If no style is provided` then a new one will be created for the widget.
        on_select (``callable``): A function to be invoked on selecting a row of the table.
        missing_value (``str`` or ``None``): value for replacing a missing value
            in the data source. (Default: None). When 'None', a warning message
            will be shown.
        factory (:obj:`module`): A python module that is capable to return a
            implementation of this class with the same name. (optional & normally not needed)

    Examples:
        >>> headings = ['Head 1', 'Head 2', 'Head 3']
        >>> data = []
        >>> table = Table(headings, data=data)

        Data can be in several forms.
        A list of dictionaries, where the keys match the heading names:

        >>> data = [{'head_1': 'value 1', 'head_2': 'value 2', 'head_3': 'value3'}),
        >>>         {'head_1': 'value 1', 'head_2': 'value 2', 'head_3': 'value3'}]

        A list of lists. These will be mapped to the headings in order:

        >>> data = [('value 1', 'value 2', 'value3'),
        >>>         ('value 1', 'value 2', 'value3')]

        A list of values. This is only accepted if there is a single heading.

        >>> data = ['item 1', 'item 2', 'item 3']
    """
    MIN_WIDTH = 100
    MIN_HEIGHT = 100

    def __init__(self, headings, id=None, style=None, data=None, accessors=None,
                 multiple_select=False, on_select=None, missing_value=None,
                 factory=None):
        super().__init__(id=id, style=style, factory=factory)
        self.headings = headings[:]
        self._accessors = build_accessors(self.headings, accessors)
        self._multiple_select = multiple_select
        self._on_select = None
        self._selection = None
        self._data = None
        if missing_value is None:
            print("WARNING: Using empty string for missing value in data. "
                  "Define a 'missing_value' on the table to silence this message")
        self._missing_value = missing_value or ''

        self._impl = self.factory.Table(interface=self)
        self.data = data

        self.on_select = on_select

    @property
    def data(self):
        """ The data source of the widget. It accepts table data
        in the form of ``list``, ``tuple``, or :obj:`ListSource`

        Returns:
            Returns a (:obj:`ListSource`).
        """
        return self._data

    @data.setter
    def data(self, data):
        if data is None:
            self._data = ListSource(accessors=self._accessors, data=[])
        elif isinstance(data, (list, tuple)):
            self._data = ListSource(accessors=self._accessors, data=data)
        else:
            self._data = data

        self._data.add_listener(self._impl)
        self._impl.change_source(source=self._data)

    @property
    def multiple_select(self):
        """Does the table allow multiple rows to be selected?"""
        return self._multiple_select

    @property
    def selection(self):
        """The current selection of the table.

        A value of None indicates no selection.
        If the table allows multiple selection, returns a list of
        selected data nodes. Otherwise, returns a single data node.
        """
        return self._selection

    def scroll_to_top(self):
        """Scroll the view so that the top of the list (first row) is visible
        """
        self.scroll_to_row(0)

    def scroll_to_row(self, row):
        """Scroll the view so that the specified row index is visible.

        Args:
            row: The index of the row to make visible. Negative values refer
                 to the nth last row (-1 is the last row, -2 second last,
                 and so on)
        """
        if row >= 0:
            self._impl.scroll_to_row(row)
        else:
            self._impl.scroll_to_row(len(self.data) + row)

    def scroll_to_bottom(self):
        """Scroll the view so that the bottom of the list (last row) is visible
        """
        self.scroll_to_row(-1)

    @property
    def on_select(self):
        """ The callback function that is invoked when a row of the table is selected.
        The provided callback function has to accept two arguments table (``:obj:Table`)
        and row (``int`` or ``None``).

        Returns:
            (``callable``) The callback function.
        """
        return self._on_select

    @on_select.setter
    def on_select(self, handler):
        """
        Set the function to be executed on node selection

        :param handler: callback function
        :type handler: ``callable``
        """
        self._on_select = wrapped_handler(self, handler)
        self._impl.set_on_select(self._on_select)

    def add_column(self, heading, accessor=None):
        """
        Add a new column to the table

        :param heading: title of the column
        :type heading: ``string``
        :param accessor: accessor of this new column
        :type heading: ``string``
        """

        if not accessor:
            accessor = to_accessor(heading)

        if accessor in self._accessors:
            raise ValueError('Accessor "{}" is already in use'.format(accessor))

        self.headings.append(heading)
        self._accessors.append(accessor)

        self._impl.add_column(heading, accessor)

    def remove_column(self, column):
        """
        Remove a table column.

        :param column: accessor or position (>0)
        :type column: ``string``
        :type column: ``int``
        """

        if isinstance(column, str):
            # Column is a string; use as-is
            accessor = column
        else:
            try:
                accessor = self._accessors[column]
            except IndexError:
                # Column specified as an integer, but the integer is out of range.
                raise ValueError("Column {} does not exist".format(column))
            except TypeError:
                # Column specified as something other than int or str
                raise ValueError("Column must be an integer or string")

        try:
            # Remove column
            self._impl.remove_column(accessor)
            del self.headings[self._accessors.index(accessor)]
            self._accessors.remove(accessor)
        except KeyError:
            raise ValueError('Invalid column: "{}"'.format(column))

    @property
    def missing_value(self):
        return self._missing_value
Exemple #18
0
class Table(Widget):
    """ A Table Widget allows the display of data in the form of columns and rows.

    Args:
        headings (``list`` of ``str``): The list of headings for the table.
        id (str): An identifier for this widget.
        data (``list`` of ``tuple``): The data to be displayed on the table.
        accessors: A list of methods, same length as ``headings``, that describes
            how to extract the data value for each column from the row. (Optional)
        style (:obj:`Style`): An optional style object.
            If no style is provided` then a new one will be created for the widget.
        on_select (``callable``): A function to be invoked on selecting a row of the table.
        factory (:obj:`module`): A python module that is capable to return a
            implementation of this class with the same name. (optional & normally not needed)

    Examples:
        >>> headings = ['Head 1', 'Head 2', 'Head 3']
        >>> data = []
        >>> table = Table(headings, data=data)

        # Data can be in several forms.
        # A list of dictionaries, where the keys match the heading names:
        >>> data = [{'head_1': 'value 1', 'head_2': 'value 2', 'head_3': 'value3'}),
        >>>         {'head_1': 'value 1', 'head_2': 'value 2', 'head_3': 'value3'}]

        # A list of lists. These will be mapped to the headings in order:
        >>> data = [('value 1', 'value 2', 'value3'),
        >>>         ('value 1', 'value 2', 'value3')]

        # A list of values. This is only accepted if there is a single heading.
        >>> data = ['item 1', 'item 2', 'item 3']
    """
    MIN_WIDTH = 100
    MIN_HEIGHT = 100

    def __init__(self, headings, id=None, style=None, data=None, accessors=None,
                 multiple_select=False, on_select=None, factory=None):
        super().__init__(id=id, style=style, factory=factory)
        self.headings = headings
        self._accessors = build_accessors(headings, accessors)
        self._multiple_select = multiple_select
        self._on_select = None
        self._selection = None
        self._data = None

        self._impl = self.factory.Table(interface=self)
        self.data = data

        self.on_select = on_select

    @property
    def data(self):
        """ The data source of the widget. It accepts table data
        in the form of ``list``, ``tuple``, or :obj:`ListSource`

        Returns:
            Returns a (:obj:`ListSource`).
        """
        return self._data

    @data.setter
    def data(self, data):
        if data is None:
            self._data = ListSource(accessors=self._accessors, data=[])
        elif isinstance(data, (list, tuple)):
            self._data = ListSource(accessors=self._accessors, data=data)
        else:
            self._data = data

        self._data.add_listener(self._impl)
        self._impl.change_source(source=self._data)

    @property
    def multiple_select(self):
        """Does the table allow multiple rows to be selected?"""
        return self._multiple_select

    @property
    def selection(self):
        """The current selection of the table.

        A value of None indicates no selection.
        If the table allows multiple selection, returns a list of
        selected data nodes. Otherwise, returns a single data node.
        """
        return self._selection

    def scroll_to_top(self):
        """Scroll the view so that the top of the list (first row) is visible
        """
        self.scroll_to_row(0)

    def scroll_to_row(self, row):
        """Scroll the view so that the specified row index is visible.

        Args:
            row: The index of the row to make visible. Negative values refer
                 to the nth last row (-1 is the last row, -2 second last,
                 and so on)
        """
        if row >= 0:
            self._impl.scroll_to_row(row)
        else:
            self._impl.scroll_to_row(len(self.data) + row)

    def scroll_to_bottom(self):
        """Scroll the view so that the bottom of the list (last row) is visible
        """
        self.scroll_to_row(-1)

    @property
    def on_select(self):
        """ The callback function that is invoked when a row of the table is selected.
        The provided callback function has to accept two arguments table (``:obj:Table`)
        and row (``int`` or ``None``).

        Returns:
            (``callable``) The callback function.
        """
        return self._on_select

    @on_select.setter
    def on_select(self, handler):
        """
        Set the function to be executed on node selection

        :param handler:     callback function
        :type handler:      ``callable``
        """
        self._on_select = wrapped_handler(self, handler)
        self._impl.set_on_select(self._on_select)