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)
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)
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_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])
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()
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)
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)
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()
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)
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)
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)
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
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)