def __init__(self, items, **kwargs): kwargs['cols'] = 2 super(MasterDetailView, self).__init__(**kwargs) list_item_args_converter = \ lambda row_index, rec: {'text': rec['name'], 'size_hint_y': None, 'height': 25} dict_adapter = DictAdapter(sorted_keys=sorted(fruit_data.keys()), data=fruit_data, args_converter=list_item_args_converter, selection_mode='single', allow_empty_selection=False, cls=ListItemButton) master_list_view = ListView(adapter=dict_adapter, size_hint=(.3, 1.0)) self.add_widget(master_list_view) detail_view = FruitDetailView( fruit_name=dict_adapter.selection[0].text, size_hint=(.7, 1.0)) dict_adapter.bind(on_selection_change=detail_view.fruit_changed) self.add_widget(detail_view)
def test_dict_adapter_selection_cascade(self): # Categories of fruits: # categories = sorted(fruit_categories.keys()) categories_dict_adapter = \ DictAdapter(sorted_keys=categories, data=fruit_categories, args_converter=self.args_converter, selection_mode='single', allow_empty_selection=False, cls=ListItemButton) fruit_categories_list_view = \ ListView(adapter=categories_dict_adapter, size_hint=(.2, 1.0)) # Fruits, for a given category, are shown based on the fruit category # selected in the first categories list above. The selected item in # the first list is used as the key into a dict of lists of list # items to reset the data in FruitsDictAdapter's # fruit_category_changed() method. # # data is initially set to the first list of list items. # fruits_dict_adapter = \ FruitsDictAdapter( sorted_keys=fruit_categories[categories[0]]['fruits'], data=fruit_data, args_converter=self.args_converter, selection_mode='single', allow_empty_selection=False, cls=ListItemButton) categories_dict_adapter.bind( on_selection_change=fruits_dict_adapter.fruit_category_changed) fruits_list_view = ListView(adapter=fruits_dict_adapter, size_hint=(.2, 1.0)) # List views should have adapters set. self.assertEqual(fruit_categories_list_view.adapter, categories_dict_adapter) self.assertEqual(fruits_list_view.adapter, fruits_dict_adapter) # Each list adapter has allow_empty_selection=False, so each should # have one selected item. self.assertEqual(len(categories_dict_adapter.selection), 1) self.assertEqual(len(fruits_dict_adapter.selection), 1) # The selected list items should show is_selected True. self.assertEqual(categories_dict_adapter.selection[0].is_selected, True) self.assertEqual(fruits_dict_adapter.selection[0].is_selected, True) # And they should be red, for background_color. self.assertEqual(categories_dict_adapter.selection[0].background_color, [1.0, 0., 0., 1.0]) self.assertEqual(fruits_dict_adapter.selection[0].background_color, [1.0, 0., 0., 1.0])
def __init__(self, items, **kwargs): kwargs['cols'] = 2 super(MasterDetailView, self).__init__(**kwargs) list_item_args_converter = \ lambda row_index, rec: {'text': rec[1], 'size_hint_y': None, 'height': 25, 'id' : str(rec[0])} dict_adapter = DictAdapter(sorted_keys=[i[0] for i in myquery.query_result], data=myquery.q_result_dict, args_converter=list_item_args_converter, selection_mode='single', allow_empty_selection=False, cls=ListItemButton) master_list_view = ListView(adapter=dict_adapter, size_hint=(.3, 1.0)) self.add_widget(master_list_view) detail_view = PageDetailView( page_txt=dict_adapter.selection[0].text, page_id=dict_adapter.selection[0].id, size_hint=(.7, 1.0), tname=myquery.q_table) dict_adapter.bind(on_selection_change=detail_view.page_changed) self.add_widget(detail_view)
def test_dict_adapter_selection_cascade(self): # Categories of fruits: # categories = sorted(fruit_categories.keys()) categories_dict_adapter = \ DictAdapter(sorted_keys=categories, data=fruit_categories, args_converter=self.args_converter, selection_mode='single', allow_empty_selection=False, cls=ListItemButton) fruit_categories_list_view = \ ListView(adapter=categories_dict_adapter, size_hint=(.2, 1.0)) # Fruits, for a given category, are shown based on the fruit category # selected in the first categories list above. The selected item in # the first list is used as the key into a dict of lists of list # items to reset the data in FruitsDictAdapter's # fruit_category_changed() method. # # data is initially set to the first list of list items. # fruits_dict_adapter = \ FruitsDictAdapter( sorted_keys=fruit_categories[categories[0]]['fruits'], data=fruit_data, args_converter=self.args_converter, selection_mode='single', allow_empty_selection=False, cls=ListItemButton) categories_dict_adapter.bind( on_selection_change=fruits_dict_adapter.fruit_category_changed) fruits_list_view = ListView( adapter=fruits_dict_adapter, size_hint=(.2, 1.0)) # List views should have adapters set. self.assertEqual(fruit_categories_list_view.adapter, categories_dict_adapter) self.assertEqual(fruits_list_view.adapter, fruits_dict_adapter) # Each list adapter has allow_empty_selection=False, so each should # have one selected item. self.assertEqual(len(categories_dict_adapter.selection), 1) self.assertEqual(len(fruits_dict_adapter.selection), 1) # The selected list items should show is_selected True. self.assertEqual(categories_dict_adapter.selection[0].is_selected, True) self.assertEqual(fruits_dict_adapter.selection[0].is_selected, True) # And they should be red, for background_color. self.assertEqual(categories_dict_adapter.selection[0].background_color, [1.0, 0., 0., 1.0]) self.assertEqual(fruits_dict_adapter.selection[0].background_color, [1.0, 0., 0., 1.0])
def __init__(self, items, **kwargs): kwargs['cols'] = 2 kwargs['size_hint'] = (1.0, 1.0) super(MasterDetailView, self).__init__(**kwargs) list_item_args_converter = \ lambda row_index, rec: {'text': rec['name'], 'size_hint_y': None, 'height': 25} dict_adapter = DictAdapter(sorted_keys=sorted(fruit_data.keys()), data=fruit_data, args_converter=list_item_args_converter, selection_mode='single', allow_empty_selection=False, cls=ListItemButton) master_list_view = ListView(adapter=dict_adapter, size_hint=(.3, 1.0)) self.add_widget(master_list_view) detail_view = FruitDetailView( fruit_name=dict_adapter.selection[0].text, size_hint=(.7, 1.0)) dict_adapter.bind(on_selection_change=detail_view.fruit_changed) self.add_widget(detail_view)
class PlayerListView(ModalView): player_adapter = ObjectProperty(None) match_perf = ObjectProperty(None) def __init__(self, **kwargs): super(PlayerListView, self).__init__(**kwargs) # filled in later with call to reload() self.player_adapter = DictAdapter(sorted_keys=[], data={}, args_converter=self._player_converter, selection_mode='multiple', propagate_selection_to_data=True, template='CustomListItem') self.d2rp = None # set by parent def _player_converter(self, idx, rec): return {'player_label': rec['player_label'], 'is_selected': rec['is_selected'], 'size_hint_y': None, 'height': 25} def reload(self): if not self.d2rp: return hero_len = max([len(dotalocal.hero[h['hero']]) for h in self.d2rp.players]) p_str = {} for i,hero in enumerate(self.d2rp.players): hero_s = dotalocal.hero[hero['hero']] player = dotalocal.player(hero['player']) p_str[str(i)] = { 'player_label': ' {} | {}'.format(hero_s.ljust(hero_len), player), 'is_selected': False } self.player_adapter = DictAdapter(sorted_keys=[i for i in range(len(p_str))], data=p_str, args_converter=self._player_converter, selection_mode='multiple', propagate_selection_to_data=True, template='CustomListItem') self.player_adapter.bind(on_selection_change=self.selection_changed) def selection_changed(self, *args): self.match_perf.redraw() def get_selected(self): selected = [] for btn in self.player_adapter.selection: hero_s = btn.text.split('|')[0].strip() #for pl in self.d2rp.players: for pl in self.match_perf.match.all_players(): if dotalocal.hero[pl['hero']] == hero_s: selected.append(pl) break return selected
def __init__(self, **kwargs): kwargs['cols'] = 3 kwargs['size_hint'] = (1.0, 1.0) super(CascadingView, self).__init__(**kwargs) list_item_args_converter = \ lambda row_index, rec: {'text': rec['name'], 'size_hint_y': None, 'height': 25} # Fruit categories list on the left: # categories = sorted(fruit_categories.keys()) fruit_categories_list_adapter = \ DictAdapter( sorted_keys=categories, data=fruit_categories, args_converter=list_item_args_converter, selection_mode='single', allow_empty_selection=False, cls=ListItemButton) fruit_categories_list_view = \ ListView(adapter=fruit_categories_list_adapter, size_hint=(.2, 1.0)) self.add_widget(fruit_categories_list_view) # Fruits, for a given category, in the middle: # image_list_item_args_converter = \ lambda row_index, rec: {'text': rec['name'], 'size_hint_y': None, 'height': 32} fruits_list_adapter = \ FruitsDictAdapter( sorted_keys=fruit_categories[categories[0]]['fruits'], data=fruit_data, args_converter=image_list_item_args_converter, selection_mode='single', allow_empty_selection=False, template='ThumbnailedListItem') fruits_list_view = \ ListView(adapter=fruits_list_adapter, size_hint=(.2, 1.0)) fruit_categories_list_adapter.bind( on_selection_change=fruits_list_adapter.fruit_category_changed) self.add_widget(fruits_list_view) # Detail view, for a given fruit, on the right: # detail_view = FruitImageDetailView( fruit_name=fruits_list_adapter.selection[0].fruit_name, size_hint=(.6, 1.0)) fruits_list_adapter.bind( on_selection_change=detail_view.fruit_changed) self.add_widget(detail_view)
def __init__(self, **kwargs): kwargs['cols'] = 3 super(CascadingView, self).__init__(**kwargs) list_item_args_converter = \ lambda row_index, rec: {'text': rec['name'], 'size_hint_y': None, 'height': 25} # Fruit categories list on the left: # categories = sorted(fruit_categories.keys()) fruit_categories_list_adapter = \ DictAdapter( sorted_keys=categories, data=fruit_categories, args_converter=list_item_args_converter, selection_mode='single', allow_empty_selection=False, cls=ListItemButton) fruit_categories_list_view = \ ListView(adapter=fruit_categories_list_adapter, size_hint=(.2, 1.0)) self.add_widget(fruit_categories_list_view) # Fruits, for a given category, in the middle: # image_list_item_args_converter = \ lambda row_index, rec: {'text': rec['name'], 'size_hint_y': None, 'height': 32} fruits_list_adapter = \ FruitsDictAdapter( sorted_keys=fruit_categories[categories[0]]['fruits'], data=fruit_data, args_converter=image_list_item_args_converter, selection_mode='single', allow_empty_selection=False, template='ThumbnailedListItem') fruits_list_view = \ ListView(adapter=fruits_list_adapter, size_hint=(.2, 1.0)) fruit_categories_list_adapter.bind( on_selection_change=fruits_list_adapter.fruit_category_changed) self.add_widget(fruits_list_view) # Detail view, for a given fruit, on the right: # detail_view = FruitImageDetailView( fruit_name=fruits_list_adapter.selection[0].fruit_name, size_hint=(.6, 1.0)) fruits_list_adapter.bind( on_selection_change=detail_view.fruit_changed) self.add_widget(detail_view)
def on_enter(self): """ Repopulate the listview """ info = self.playlist.get_info data = { str(i): { 'text': item[0], 'source': item[1], 'album': info(item[0])["album"], 'track': info(item[0])["file"], 'is_selected': bool(i == self.playlist.current) } for i, item in enumerate(self.playlist.queue) } def args_converter(row_index, item): return { 'text': item['text'], 'size_hint_y': None, 'height': "50dp", 'cls_dicts': [{ 'cls': ZenListImage, 'kwargs': { 'source': item['source'], 'size_hint_x': 0.1, 'row_index': row_index } }, { 'cls': ZenListButton, 'kwargs': { 'text': item['track'], 'is_representing_cls': True, 'size_hint_x': 0.55, 'row_index': row_index } }, { 'cls': ZenListButton, 'kwargs': { 'text': item['album'], 'size_hint_x': 0.35, 'row_index': row_index } }] } dict_adapter = DictAdapter( sorted_keys=[str(i) for i in range(len(self.playlist.queue))], data=data, selection_mode='single', args_converter=args_converter, propagate_selection_to_data=True, cls=ZenListItem) self.listview.adapter = dict_adapter dict_adapter.bind(on_selection_change=self.selection_changed)
def __init__(self, **kwargs): super(PlaylistLayout, self).__init__(**kwargs) integers_dict = { str(i): { 'source': basename(self.playlist[i][0]), 'size': '%dx%d' % tuple(self.playlist[i][3]) \ if self.playlist[i][0].lower().endswith('.yuv') else '', 'format': self.playlist[i][1].upper() \ if self.playlist[i][0].lower().endswith('.yuv') else '', 'playlist': self.playlist[i], 'is_selected': False } for i in xrange(len(self.playlist)) } args_converter = lambda row_index, rec: { 'text': rec['source'], 'size_hint_y': None, 'height': 25, 'cls_dicts': [{ 'cls': ListItemButton, 'kwargs': { 'text': rec['source'] } }, { 'cls': ListItemButton, 'kwargs': { 'text': rec['size'], 'size_hint_x': None, 'width': 100 } }, { 'cls': ListItemButton, 'kwargs': { 'text': rec['format'], 'size_hint_x': None, 'width': 70 } }] } item_strings = [ "{0}".format(index) for index in xrange(len(self.playlist)) ] dict_adapter = DictAdapter(sorted_keys=item_strings, data=integers_dict, args_converter=args_converter, selection_mode='single', allow_empty_selection=False, cls=CompositeListItem) dict_adapter.bind(selection=self.setter('selection')) list_view = ListView(adapter=dict_adapter) self.layout.add_widget(list_view)
def fill_er_up(self, unused=None): dc = "deselected_color" sc = "selected_color" args_converter = \ lambda row_index, rec: \ {"size_hint_y": None, "height": 25, #"height": rec["height"], #"background_color": rec["status_colour"], "cls_dicts": [ {"cls": MyListItemButton, "kwargs": {"text": rec["id"]}}, #, dc: rec[dc]}}, {"cls": MyListItemButton, "kwargs": {"text": rec["black"]}}, {"cls": MyListItemButton, "kwargs": {"text": rec["white"]}}, {"cls": MyListItemButton, "kwargs": {"text": rec["date"]}}, {"cls": MyListItemButton, "kwargs": {"text": rec["size"]}}, {"cls": MyListItemButton, "kwargs": {"text": rec["rules"]}}, ]} # TODO: Another hack - parent.parent gm = self.parent.parent.gm pm = gm.get_players_mgr() if self.screen.show_finished: games = gm.get_recently_finished() else: games = gm.get_all_unfinished_preserved() # TODO: The "if" is a hack games_dict = { g.game_id: game_data(g, pm) for g in games if g } self.item_strings = ["{0}".format(g_id) for g_id in games_dict.iterkeys() ] dict_adapter = DictAdapter(data=games_dict, args_converter=args_converter, selection_mode="single", allow_empty_selection=False, # Not working? cls=TwoLevelCompositeListItem) dict_adapter.bind( on_selection_change=self.changed_selection) self.adapter = dict_adapter # Use the adapter in our ListView: list_view = ListView(adapter=dict_adapter) self.add_widget(list_view) self.view = list_view
def dump_data(self, **kwargs): from lvargsconv import args_convertor items = {str(i): kwargs['data'][i] for i in range(len(kwargs['data']))} print items dict_adapter = DictAdapter( data=items, args_converter=args_convertor, selection_mode='single', allow_empty_selection=True, cls=CustomListItem) dict_adapter.bind(on_selection_change=self.on_select) self.clear_widgets() self.add_widget(ListView(adapter=dict_adapter)) pass
def __init__(self, **kwargs): kwargs["cols"] = 3 super(CascadingView, self).__init__(**kwargs) list_item_args_converter = lambda row_index, rec: {"text": rec["name"], "size_hint_y": None, "height": 25} # Fruit categories list on the left: # categories = sorted(fruit_categories.keys()) fruit_categories_list_adapter = DictAdapter( sorted_keys=categories, data=fruit_categories, args_converter=list_item_args_converter, selection_mode="single", allow_empty_selection=False, cls=ListItemButton, ) fruit_categories_list_view = ListView(adapter=fruit_categories_list_adapter, size_hint=(0.2, 1.0)) self.add_widget(fruit_categories_list_view) # Fruits, for a given category, in the middle: # image_list_item_args_converter = lambda row_index, rec: {"text": rec["name"], "size_hint_y": None, "height": 32} fruits_list_adapter = FruitsDictAdapter( sorted_keys=fruit_categories[categories[0]]["fruits"], data=fruit_data, args_converter=image_list_item_args_converter, selection_mode="single", allow_empty_selection=False, template="ThumbnailedListItem", ) fruits_list_view = ListView(adapter=fruits_list_adapter, size_hint=(0.2, 1.0)) fruit_categories_list_adapter.bind(on_selection_change=fruits_list_adapter.fruit_category_changed) self.add_widget(fruits_list_view) # Detail view, for a given fruit, on the right: # detail_view = FruitImageDetailView(fruit_name=fruits_list_adapter.selection[0].fruit_name, size_hint=(0.6, 1.0)) fruits_list_adapter.bind(on_selection_change=detail_view.fruit_changed) self.add_widget(detail_view)
def __init__(self, **kwargs): self.level = self.level+ 1 subdirs={} if self.level <= 2: for subfolder in os.listdir(self.datadir): if os.path.isdir(os.path.join(self.datadir,subfolder)): subdirs[os.path.join(self.datadir,subfolder)] = {'text': subfolder,'is_selected': False} else: for subfolder in os.listdir(self.datadir): subdirs[os.path.join(self.datadir,subfolder)] = {'text': subfolder,'is_selected': False} args_converter = \ lambda row_index, rec: {'text': rec['text'], 'is_selected': rec['is_selected'], 'size_hint_y': 10, 'height': 40} f_adapter = DictAdapter(data=subdirs, args_converter=args_converter, sorted_keys=subdirs.keys(), template='FListItem') super(FView, self).__init__(adapter=f_adapter) f_adapter.bind(on_selection_change=self.callback)
def __init__(self, **kwargs): super(PlaylistLayout, self).__init__(**kwargs) integers_dict = { str(i): { 'source': basename(self.playlist[i][0]), 'size': '%dx%d' % tuple(self.playlist[i][3]) \ if self.playlist[i][0].lower().endswith('.yuv') else '', 'format': self.playlist[i][1].upper() \ if self.playlist[i][0].lower().endswith('.yuv') else '', 'playlist': self.playlist[i], 'is_selected': False } for i in xrange(len(self.playlist)) } args_converter = lambda row_index, rec: { 'text': rec['source'], 'size_hint_y': None, 'height': 25, 'cls_dicts': [ {'cls': ListItemButton, 'kwargs': {'text': rec['source']}}, {'cls': ListItemButton, 'kwargs': {'text': rec['size'], 'size_hint_x': None, 'width': 100}}, {'cls': ListItemButton, 'kwargs': {'text': rec['format'], 'size_hint_x': None, 'width': 70}} ] } item_strings = ["{0}".format(index) for index in xrange(len(self.playlist))] dict_adapter = DictAdapter(sorted_keys=item_strings, data=integers_dict, args_converter=args_converter, selection_mode='single', allow_empty_selection=False, cls=CompositeListItem) dict_adapter.bind(selection=self.setter('selection')) list_view = ListView(adapter=dict_adapter) self.layout.add_widget(list_view)
def __init__(self, **kwargs): kwargs['cols'] = 3 kwargs['size_hint'] = (1.0, 1.0) super(TwoUpView, self).__init__(**kwargs) list_item_args_converter = \ lambda row_index, rec: {'text': rec['name'], 'size_hint_y': None, 'height': 25} fruits_dict_adapter = \ DictAdapter( sorted_keys=sorted(fruit_data.keys()), data=fruit_data, args_converter=list_item_args_converter, selection_mode='multiple', allow_empty_selection=False, cls=ListItemButton) fruits_list_view = ListView(adapter=fruits_dict_adapter, size_hint=(.2, 1.0)) self.add_widget(fruits_list_view) fruits_dict_adapter2 = \ ReceivingFruitsDictAdapter( sorted_keys=[], data={}, args_converter=list_item_args_converter, selection_mode='single', allow_empty_selection=True, cls=ListItemButton) fruits_list_view2 = ListView(adapter=fruits_dict_adapter2, size_hint=(.2, 1.0)) fruits_dict_adapter.bind( on_selection_change=fruits_dict_adapter2.fruits_changed) self.add_widget(fruits_list_view2)
def on_enter(self): """ Repopulate the listview """ info = self.playlist.get_info data = {str(i): {'text': item[0], 'source': item[1], 'album': info(item[0])["album"], 'track': info(item[0])["file"], 'is_selected': bool(i == self.playlist.current)} for i, item in enumerate(self.playlist.queue)} def args_converter(row_index, item): return {'text': item['text'], 'size_hint_y': None, 'height': "60sp", 'cls_dicts': [{'cls': ZenListImage, 'kwargs': {'source': item['source'], 'size_hint_x': 0.1, 'row_index': row_index}}, {'cls': ZenListButton, 'kwargs': {'text': item['track'], 'is_representing_cls': True, 'size_hint_x': 0.55, 'row_index': row_index}}, {'cls': ZenListButton, 'kwargs': {'text': item['album'], 'size_hint_x': 0.35, 'row_index': row_index}}]} dict_adapter = DictAdapter( sorted_keys=[str(i) for i in range(len(self.playlist.queue))], data=data, selection_mode='single', args_converter=args_converter, propagate_selection_to_data=True, cls=ZenListItem) self.listview.adapter = dict_adapter dict_adapter.bind(on_selection_change=self.selection_changed)
class ObjectGraphRenderer(FloatLayout): # So far has no capability to handle window/editor rescaling. # Maybe fold into CropObjectRenderer? views_mask = DictProperty(dict()) '''If an edge is in the views mask and its entry is False, it will not be passed to the adapter for rendering.''' redraw = NumericProperty(0) '''Change this property to force redraw.''' def __init__(self, annot_model, graph, editor_widget, **kwargs): super(ObjectGraphRenderer, self).__init__(**kwargs) logging.info('ObjectGraphRenderer: initializing, with {0} edges' ' in the graph.'.format(len(graph.edges))) self.model = annot_model self.graph = graph self.size = editor_widget.size self.pos = editor_widget.pos editor_widget.bind(size=self._update_size) editor_widget.bind(pos=self._update_pos) self.edge_adapter = DictAdapter( data=self.graph.edges, args_converter=self.edge_converter, selection_mode='multiple', cls=EdgeView, ) self.view = EdgeListView(adapter=self.edge_adapter, size_hint=(None, None), size=self.size, pos=self.pos) self.edge_adapter.bind( on_selection_change=self.view.broadcast_selection) self.views_mask = dict() # Hacking the ListView # logging.debug('View children: {0}'.format(self.view.children)) self.view.remove_widget(self.view.children[0]) self.view.container = FloatLayout(pos=self.pos, size=self.size) self.view.add_widget(self.view.container) Window.bind(on_key_down=self.view.on_key_down) Window.bind(on_key_up=self.view.on_key_up) # Automating the redraw pipeline: # - when edge data changes, fill in adapter data, self.graph.bind(edges=self.update_edge_adapter_data) # - once adapter data changes, redraw self.edge_adapter.bind(data=self.do_redraw) self.add_widget(self.view) def update_edge_adapter_data(self, instance, edges): """Copy the graph's edges to adapter data. Also updates the mask. """ self._update_views_mask(edges) self.edge_adapter.cached_views = dict() new_data = {} for e, e_class in edges.items(): if (e in self.views_mask) and (self.views_mask[e] is False): logging.debug('ObjGraphRenderer: edge {0} masked out.' ''.format(e)) continue # The adapter creates its Views from the *values* of # its `data` dict. So, we must supply the edge as a part # of the dict value, not just the key. new_data[e] = (e, e_class) # This fires self.do_redraw(): self.edge_adapter.data = new_data def _update_views_mask(self, edges, show_new=True): """ All edges that were previously not in the mask are added, with the ``show_new`` argument controlling whether they will be shown or hidden by default. All edges that were in the mask and are not in the data anymore will be removed from the mask. All edges that were already in the mask have their status unchanged. """ new_mask = dict() for e, e_class in edges.items(): if e in self.views_mask: new_mask[e] = self.views_mask[e] else: new_mask[e] = show_new self.views_mask = new_mask def on_redraw(self, instance, pos): self.do_redraw() def do_redraw(self, *args, **kwargs): # Args and kwargs given so that it can be fired by an on_edges # event from the graph. logging.debug('ObjGraphRenderer: requested do_redraw, renderer' ' size: {0}' ''.format(self.redraw, self.size)) self.view.populate() # self.view.log_rendered_edges() def edge_converter(self, row_index, rec): """Interface between the edge and the EdgeView.""" # logging.info('EdgeRenderer: Requesting EdgeView kwargs for edge: {0}' # ''.format(rec)) e = rec[0] e_class = rec[1] # Get the connected CropObjects obj_from = self.model.cropobjects[e[0]] e_obj_from = self._cropobject_to_editor_world(obj_from) obj_to = self.model.cropobjects[e[1]] e_obj_to = self._cropobject_to_editor_world(obj_to) # Return the recomputed objects as kwargs for the EdgeView output = { 'is_selected': False, 'cropobject_from': e_obj_from, 'cropobject_to': e_obj_to, 'edge_label': e_class, } # logging.info('EdgeRenderer: edge_converter fired, output: {0},' # ' class: {1}' # ''.format(output, self.edge_adapter.get_cls())) return output def _cropobject_to_editor_world(self, obj): """Creates an equivalent of the given CropObject with coordinates in the current editor world (uses the App's scaler).""" # Recompute a counterpart of theirs to editor world mt, ml, mb, mr = obj.bounding_box et, el, eb, er = self.scaler.bbox_model2widget(mt, ml, mb, mr) obj_updated = copy.deepcopy(obj) # Editor world counts X from the *bottom* obj_updated.x = eb obj_updated.y = el obj_updated.width = er - el obj_updated.height = et - eb return obj_updated @property def rendered_views(self): """The list of actual rendered CropObjectViews that the CropObjectListView holds.""" return [cv for cv in self.view.container.children[:]] @property def scaler(self): return App.get_running_app().image_scaler def _update_size(self, instance, size): self.size = size logging.debug('ObjectGraphRenderer: setting size to {0}'.format( self.size)) def _update_pos(self, instance, pos): self.pos = pos logging.debug('ObjectGraphRenderer: setting pos to {0}'.format( self.pos)) def mask_all(self, label=None): if label is None: new_mask = {e: False for e in self.graph.edges} else: new_mask = {} for e, l in list(self.graph.edges.items()): if l == label: new_mask[e] = False else: new_mask[e] = self.views_mask[e] self.views_mask = new_mask self.update_edge_adapter_data(instance=None, edges=self.graph.edges) def mask(self, edges): """Only acts on the given set of edges, doesn't change masking for others.""" new_mask = {e: self.views_mask[e] for e in self.graph.edges} for e in edges: new_mask[e] = False self.views_mask = new_mask self.update_edge_adapter_data(instance=None, edges=self.graph.edges) def unmask_all(self, label=None): if label is None: new_mask = {e: True for e in self.graph.edges} else: new_mask = {} for e, l in list(self.graph.edges.items()): if l == label: new_mask[e] = True else: new_mask[e] = self.views_mask[e] self.views_mask = new_mask self.update_edge_adapter_data(instance=None, edges=self.graph.edges) def unmask(self, edges): """Only acts on the given set of edges, doesn't change masking for others.""" new_mask = {e: self.views_mask[e] for e in self.graph.edges} for e in edges: new_mask[e] = True self.views_mask = new_mask self.update_edge_adapter_data(instance=None, edges=self.graph.edges) def are_all_masked(self, edges=None): if edges is None: edges = self.graph.edges masked = [ e for e in edges if ((e in self.views_mask) and (self.views_mask[e] is False)) ] logging.debug('GraphRenderer: {0} masked, {1} total in mask' ''.format(len(masked), len(self.views_mask))) output = (len(masked) == len(edges)) return output
class CropObjectRenderer(FloatLayout): """The CropObjectRenderer class is responsible for listening to the cropobject dict in the model and rendering it upon itself. Its place is attached as an overlay of the editor widget (the image) with the same size and position. In order to force rendering the annotations, add 1 to the ``rendnerer.redraw`` property, which fires redrawing. """ # Maybe the ObjectGraphRenderer could be folded into this? # To work above the selectable_cropobjects? selectable_cropobjects = DictProperty() adapter = ObjectProperty() view = ObjectProperty() editor_widget = ObjectProperty() cropobject_keys = ListProperty() cropobject_keys_mask = DictProperty(None) mlclasses_colors = DictProperty() # The following properties are used to correctly resize # the intermediate CropObject structures. model_image_height = NumericProperty() model_image_width = NumericProperty() height_ratio_in = NumericProperty(1) old_height_ratio_in = NumericProperty(1) width_ratio_in = NumericProperty(1) old_width_ratio_in = NumericProperty(1) redraw = NumericProperty(0) '''Signals that the CropObjects need to be redrawn.''' def __init__(self, annot_model, editor_widget, **kwargs): super(CropObjectRenderer, self).__init__(**kwargs) # Bindings for model changes. # These bindings are what causes changes in the model to propagate # to the view. However, the DictProperty in the model does not # signal changes to the dicts it contains, only insert/delete states. # This implies that e.g. moving a CropObject does not trigger the # self.update_cropobject_data() binding. annot_model.bind(cropobjects=self.update_cropobject_data) # This is just a misc operation, to keep the renderer # in a valid state when the user loads a different MLClassList. annot_model.bind(mlclasses=self.recompute_mlclasses_color_dict) # Bindings for view changes editor_widget.bind(height=self.editor_height_changed) editor_widget.bind(width=self.editor_width_changed) self.size = editor_widget.size self.pos = editor_widget.pos # The self.selectable_cropobjects level of indirection only # handles numpy to kivy world conversion. This can be handled # in the adapter conversion method, maybe? self.adapter = DictAdapter( data=self.selectable_cropobjects, args_converter=self.selectable_cropobject_converter, selection_mode='multiple', cls=CropObjectView, ) self.view = CropObjectListView( adapter=self.adapter, size_hint=(None, None), size=self.size, #(self.size[0] / 2, self.size[1] / 2), pos=self.pos) self.adapter.bind(on_selection_change=self.view.broadcast_selection) # Keyboard event trapping implemented there. Window.bind(on_key_down=self.view.on_key_down) Window.bind(on_key_up=self.view.on_key_up) self.model_image_height = annot_model.image.shape[0] self.height_ratio_in = float( editor_widget.height) / annot_model.image.shape[0] self.model_image_width = annot_model.image.shape[1] self.width_ratio_in = float( editor_widget.width) / annot_model.image.shape[1] annot_model.bind(image=self.update_image_size) # self.view = ListView(item_strings=map(str, range(100))) self.add_widget(self.view) # The Renderer gets added to the editor externally, though, during # app build. That enables us to add or remove the renderer from # the active widget tree. self.redraw += 1 logging.info('Render: Initialized CropObjectRenderer, with size {0}' ' and position {1}, ratios {2}. Total keys: {3}' ''.format(self.size, self.pos, (self.height_ratio_in, self.width_ratio_in), len(self.cropobject_keys))) def on_redraw(self, instance, pos): """This signals that the CropObjects need to be re-drawn. For example, adding a CropObject necessitates this, or resizing the window.""" self.view.adapter.cached_views = dict() if self.cropobject_keys_mask is None: self.view.adapter.data = self.selectable_cropobjects else: self.view.adapter.data = { objid: c for objid, c in self.selectable_cropobjects.iteritems() if self.cropobject_keys_mask[objid] } logging.info( 'Render: After masking: {0} of {1} cropobjects remaining.' ''.format(len(self.view.adapter.data), len(self.selectable_cropobjects))) # self.view.slow_populate() self.view.populate() logging.info('Render: Redrawn {0} times'.format(self.redraw)) def update_image_size(self, instance, pos): prev_editor_height = self.height_ratio_in * self.model_image_height self.model_image_height = pos.shape[0] self.height_ratio_in = prev_editor_height / self.model_image_height prev_editor_width = self.width_ratio_in * self.model_image_width self.model_image_width = pos.shape[1] self.width_ratio_in = prev_editor_width / self.model_image_width def on_height_ratio_in(self, instance, pos): _n_items_changed = 0 if self.height_ratio_in == 0: return for objid, c in self.selectable_cropobjects.iteritems(): orig_c = copy.deepcopy(c) c.height *= self.height_ratio_in / self.old_height_ratio_in c.x *= self.height_ratio_in / self.old_height_ratio_in self.selectable_cropobjects[objid] = c if _n_items_changed < 0: logging.info( 'Render: resizing\n{0}\nto\n{1}' ''.format( ' | '.join( str(orig_c).replace('\t', '').split('\n')[1:-1]), ' | '.join(str(c).replace('\t', '').split('\n')[1:-1]))) _n_items_changed += 1 logging.info( 'Render: Redraw from on_height_ratio_in: ratio {0}, changed {1} items' ''.format(self.height_ratio_in / self.old_height_ratio_in, _n_items_changed)) self.old_height_ratio_in = self.height_ratio_in self.redraw += 1 def on_width_ratio_in(self, instance, pos): _n_items_changed = 0 if self.width_ratio_in == 0: return for objid, c in self.selectable_cropobjects.iteritems(): orig_c = copy.deepcopy(c) c.width *= self.width_ratio_in / self.old_width_ratio_in c.y *= self.width_ratio_in / self.old_width_ratio_in self.selectable_cropobjects[objid] = c if _n_items_changed < 0: logging.info( 'Render: resizing\n{0}\nto\n{1}' ''.format( ' | '.join( str(orig_c).replace('\t', '').split('\n')[1:-1]), ' | '.join(str(c).replace('\t', '').split('\n')[1:-1]))) _n_items_changed += 1 logging.info( 'Render: Redraw from on_width_ratio_in: ratio {0}, changed {1} items' ''.format(self.width_ratio_in / self.old_width_ratio_in, _n_items_changed)) self.old_width_ratio_in = self.width_ratio_in self.redraw += 1 def editor_height_changed(self, instance, pos): logging.info('Render: Editor height changed to {0}'.format(pos)) self.height_ratio_in = float(pos) / self.model_image_height def editor_width_changed(self, instance, pos): logging.info('Render: Editor width changed to {0}'.format(pos)) self.width_ratio_in = float(pos) / self.model_image_width def update_cropobject_data(self, instance, pos): """Fired on change in the current CropObject list: make sure the data structures underlying the CropObjectViews are in sync with the model. This is where the positioning magic happens. Once we fit the original CropObject to the widget, we're done. However, in the future, we need to re-do the positioning magic on CropObjectList import. Let's do it here now for testing the concepts... """ # Placeholder operation: just copy this for now. logging.info('Render: Updating CropObject data, {0} cropobjects' ' and {1} currently selectable.' ''.format(len(pos), len(self.selectable_cropobjects))) # Clear current cropobjects. Since ``pos`` is the entire # CropObject dictionary from the model and the CropObjects # will all be re-drawn anyway, we want selectable_cropobjects # to match it exactly. self.selectable_cropobjects = {} for objid in pos: corrected_position_cropobject = copy.deepcopy(pos[objid]) # X is vertical, Y is horizontal. # X is the upper left corner relative to the image. We need the # bottom left corner to be X. We first need to get the top-down # coordinate for the bottom corner (x + height), then flip it # around relative to the current editor height # (self.model_image_height - ...) then scale it down # (* self.height_ratio_in). corrected_position_cropobject.x = (self.model_image_height - (pos[objid].x + pos[objid].height)) *\ self.height_ratio_in corrected_position_cropobject.y = pos[objid].y * self.width_ratio_in corrected_position_cropobject.height = corrected_position_cropobject.height *\ self.height_ratio_in corrected_position_cropobject.width = corrected_position_cropobject.width *\ self.width_ratio_in self.selectable_cropobjects[objid] = corrected_position_cropobject # Inversion! # self.selectable_cropobjects[objid].y = self.height - pos[objid].y self.cropobject_keys_mask[objid] = True self.cropobject_keys = map(str, self.selectable_cropobjects.keys()) # The adapter data doesn't change automagically # when the DictProperty it was bound to changes. # Force redraw. logging.info('Render: Redrawing from update_cropobject_data') self.redraw += 1 def model_coords_to_editor_coords(self, x, y, height, width): """Converts coordinates of a model CropObject into the corresponding CropObjectView coordinates.""" x_out = (self.model_image_height - (x + height)) * self.height_ratio_in y_out = y * self.width_ratio_in height_out = height * self.height_ratio_in width_out = width * self.width_ratio_in return x_out, y_out, height_out, width_out def recompute_mlclasses_color_dict(self, instance, pos): """On MLClassList change, the color dictionary needs to be updated.""" logging.info('Render: Recomputing mlclasses color dict...') for clsid in pos: self.mlclasses_colors[clsid] = pos[clsid].color def selectable_cropobject_converter(self, row_index, rec): """Interfacing the CropObjectView and the intermediate data structure. Note that as it currently stands, this intermediate structure is also a CropObject, although the position params X and Y have been switched around.""" if max(self.mlclasses_colors[rec.clsid]) > 1.0: rgb = tuple( [float(x) / 255.0 for x in self.mlclasses_colors[rec.clsid]]) else: rgb = tuple([float(x) for x in self.mlclasses_colors[rec.clsid]]) output = { #'text': str(rec.objid), #'size_hint': (None, None), 'is_selected': False, 'selectable_cropobject': rec, 'rgb': rgb, } logging.debug( 'Render: Converter fired, input object {0}/{1}, with output:\n{2}' ''.format(row_index, rec, output)) return output def clear(self, instance, pos): logging.info('Render: clear() called with instance {0}, pos {1}' ''.format(instance, pos)) self.selectable_cropobjects = {} self.cropobject_keys = [] self.cropobject_keys_mask = {} self.redraw += 1 def mask_all(self): logging.info('Render: mask() called') self.view.unselect_all() # ...but they disappear anyway? self.cropobject_keys_mask = { objid: False for objid in self.selectable_cropobjects } self.redraw += 1 def unmask_all(self): logging.info('Render: mask() called') self.cropobject_keys_mask = { objid: True for objid in self.selectable_cropobjects } self.redraw += 1 def on_adapter(self, instance, pos): # logging.info('Render: Selectable cropobjects changed, populating view.') logging.info('Render: Something changed in the adapter!')
class PartnerUpdateView(GridLayout): partner_data = {'Child 1' : {'name' : 'Child 1', 'street' : None, 'street2' : None, 'type' : 'Unknown', 'zip' : None, 'city' : 'Some Village', 'phone' : '22 55 55 55'}} def __init__(self, **kwargs): kwargs['cols'] = 2 super(PartnerUpdateView, self).__init__(**kwargs) self.list_item_args_converter = \ lambda row_index, rec: {'text': rec['name'], 'size_hint_y': None, 'height': 25} self.dict_adapter = DictAdapter(sorted_keys=sorted(self.partner_data.keys()), data=self.partner_data, args_converter=self.list_item_args_converter, selection_mode='single', allow_empty_selection=False, cls=ListItemButton) self.master_list_view = ListView(adapter=self.dict_adapter, size_hint=(.3, 1.0)) self.add_widget(self.master_list_view) self.popup = self.make_comm_popup() detail_container = AnchorLayout() detail_view = PartnerDetailView(self, fruit_name=self.dict_adapter.selection[0].text, size_hint=(.7, None)) detail_container.add_widget(detail_view) self.dict_adapter.bind(on_selection_change=detail_view.partner_changed) communicate_button_layout = AnchorLayout(anchor_y = 'bottom', size_hint = (.7, None)) communicate_button = Button(text = 'Communication', size_hint_y=None,height=25) communicate_button.bind(on_press = self.popup.open) communicate_button_layout.add_widget(communicate_button) self.add_widget(detail_container) self.add_widget(communicate_button_layout) def make_comm_popup(self): prms = {} # TODO add server parameters from DB cont = BoxLayout(orientation = 'vertical') cont.add_widget(CommunicateContent(self, prms)) gl = GridLayout(cols=2) fetch_button = Button(text = 'Fetch data', size_hint_y=None, height=25) fetch_button.bind(on_press = self.fetch_data) send_button = Button(text = 'Send data', size_hint_y=None, height=25) send_button.bind(on_press = self.send_data) gl.add_widget(fetch_button) gl.add_widget(send_button) cont.add_widget(gl) return Popup(title = 'Communicate', content = cont, size_hint=(0.6, None), height=200) def fetch_data(self, instance): self.popup.dismiss() communicate.fetch_partners(self.server, self.user, self.password) self.partner_data = communicate.get_partners_from_db() print "DATA", self.dict_adapter.data.keys() for d in self.dict_adapter.data.items(): print "HEI", d #self.partner_data = {'hei' : {'name': 'hei'}} self.dict_adapter.sorted_keys = sorted(self.partner_data.keys()) #self.dict_adapter.data = [('hei', {'name' : 'hei'})] self.dict_adapter.data = self.partner_data #self.dict_adapter = DictAdapter(sorted_keys=sorted(partner_data.keys()), # data=partner_data, # args_converter=self.list_item_args_converter, # selection_mode='single', # allow_empty_selection=False, # cls=ListItemButton) #self.master_list_view = self.dict_adapter #self.dict_adapter.sorted_keys = sorted(partner_data.keys()) #self.dict_adapter.data = partner_data #self.add_widget(self.master_list_view) def send_data(self, instance): self.popup.dismiss() communicate.send_partners(self.server, self.user, self.password)