class TreeView(BoxLayout): """ Main middle col, parent selected n uncles left, child nodes on right """ def __init__(self, **kwargs): super(TreeView, self).__init__(**kwargs) self.parent_and_uncles_list_adapter = ListAdapter( data=[El.get("TEXT") for El in parentMap[selectedParent]], selection_mode='single', allow_empty_selection=False, cls=ListItemButton) self.add_widget(ListView(adapter=self.parent_and_uncles_list_adapter)) self.parent_and_uncles_list_adapter.get_view(selectedIndexList[-2]).trigger_action(duration=0) self.main_list_adapter = ListAdapter( data=[El.get("TEXT") for El in selectedParent], selection_mode='single', allow_empty_selection=False, cls=ListItemButton) self.add_widget(ListView(adapter=self.main_list_adapter)) self.main_list_adapter.get_view(selectedIndexList[-1]).trigger_action(duration=0) child_list_adapter = ListAdapter( data=[El.get("TEXT") for El in selectedParent[selectedIndexList[-1]]], selection_mode='single', allow_empty_selection=True, cls=ListItemButton) self.add_widget(ListView(adapter=child_list_adapter))
def __init__(self, settings, channel, **kwargs): super(ChannelSelectView, self).__init__(**kwargs) self.register_event_type('on_channel_selected') self.register_event_type('on_channel_cancel') data = [] channel_list = self.ids.channelList available_channels = list(settings.runtimeChannels.get_active_channels().iterkeys()) available_channels.sort() try: for available_channel in available_channels: data.append({'text': available_channel, 'is_selected': False}) args_converter = lambda row_index, rec: {'text': rec['text'], 'size_hint_y': None, 'height': dp(50)} list_adapter = ListAdapter(data=data, args_converter=args_converter, cls=ChannelItemButton, selection_mode='single', allow_empty_selection=True) channel_list.adapter = list_adapter #select the current channel index = 0 for item in list_adapter.data: if item['text'] == channel: view = list_adapter.get_view(index) view.trigger_action(duration=0) #duration=0 means make it an instant selection index += 1 list_adapter.bind(on_selection_change=self.on_select) self.channel = channel except Exception as e: Logger.error("ChannelSelectView: Error initializing: " + str(e))
def __init__(self, settings, channel, **kwargs): super(ChannelSelectView, self).__init__(**kwargs) self.register_event_type('on_channel_selected') self.register_event_type('on_channel_cancel') data = [] channel_list = self.ids.channelList available_channels = list( settings.runtimeChannels.get_active_channels().iterkeys()) available_channels.sort() try: for available_channel in available_channels: data.append({'text': available_channel, 'is_selected': False}) args_converter = lambda row_index, rec: { 'text': rec['text'], 'size_hint_y': None, 'height': dp(50) } list_adapter = ListAdapter(data=data, args_converter=args_converter, cls=ChannelItemButton, selection_mode='single', allow_empty_selection=True) channel_list.adapter = list_adapter #select the current channel index = 0 for item in list_adapter.data: if item['text'] == channel: view = list_adapter.get_view(index) view.trigger_action( duration=0 ) #duration=0 means make it an instant selection index += 1 list_adapter.bind(on_selection_change=self.on_select) self.channel = channel except Exception as e: Logger.error("ChannelSelectView: Error initializing: " + str(e))
class NewProjectDialog(BoxLayout): listview = ObjectProperty(None) ''':class:`~kivy.uix.listview.ListView` used for showing file paths. :data:`listview` is a :class:`~kivy.properties.ObjectProperty` ''' select_button = ObjectProperty(None) ''':class:`~kivy.uix.button.Button` used to select the list item. :data:`select_button` is a :class:`~kivy.properties.ObjectProperty` ''' cancel_button = ObjectProperty(None) ''':class:`~kivy.uix.button.Button` to cancel the dialog. :data:`cancel_button` is a :class:`~kivy.properties.ObjectProperty` ''' adapter = ObjectProperty(None) ''':class:`~kivy.uix.listview.ListAdapter` used for selecting files. :data:`adapter` is a :class:`~kivy.properties.ObjectProperty` ''' image = ObjectProperty(None) '''Type of :class:`~kivy.uix.image.Image` to display image of selected new template. :data:`image` is a :class:`~kivy.properties.ObjectProperty` ''' list_parent = ObjectProperty(None) '''Parent of listview. :data:`list_parent` is a :class:`~kivy.properties.ObjectProperty` ''' prev_selection = NumericProperty(0) '''to memorize the previous selection. :attr:`prev_selection` is a :class: `~kivy.properties.NumericProperty`, defaults to (0). ''' __events__ = ('on_select', 'on_cancel') def __init__(self, **kwargs): super(NewProjectDialog, self).__init__(**kwargs) item_strings = list(NEW_PROJECTS.keys()) item_strings.sort() self.adapter = ListAdapter(cls=Factory.DesignerListItemButton, data=item_strings, selection_mode='single', allow_empty_selection=False) self.adapter.check_for_empty_selection = self.check_for_empty_selection self.adapter.bind(on_selection_change=self.on_adapter_selection_change) self.listview = ListView(adapter=self.adapter) self.listview.size_hint = (0.5, 1) self.listview.pos_hint = {'top': 1} self.list_parent.add_widget(self.listview, 1) self.on_adapter_selection_change(self.adapter) def on_parent(self, *args): if self.parent: Window.bind(on_key_down=self._on_keyboard_down) else: Window.unbind(on_key_down=self._on_keyboard_down) def _on_keyboard_down(self, keyboard, key, codepoint, text, modifier, *args): '''To detect which key is pressed ''' if modifier: return False key_str = Keyboard.keycode_to_string(Window._system_keyboard, key) if key_str == 'up': v = self.adapter.get_view(self.prev_selection - 1) if v is not None: self.adapter.handle_selection(v) return True if key_str == 'down': v = self.adapter.get_view(self.prev_selection + 1) if v is not None: self.adapter.handle_selection(v) return True if key_str == 'enter': self.dispatch('on_select') return True def check_for_empty_selection(self, *args): if not self.adapter.allow_empty_selection: if len(self.adapter.selection) == 0: # Select the first item if we have it. v = self.adapter.get_view(self.prev_selection) if v is not None: self.adapter.handle_selection(v) def on_adapter_selection_change(self, adapter): '''Event handler for 'on_selection_change' event of adapter. ''' name = adapter.selection[0].text.lower() + '.png' name = name.replace(' and ', '_') image_source = join(NEW_TEMPLATE_IMAGE_PATH, name) _dir = get_kd_dir() image_source = join(_dir, image_source) parent = self.image.parent parent.remove_widget(self.image) self.image = Image(source=image_source) parent.add_widget(self.image) self.prev_selection = adapter.data.index(adapter.selection[0].text) def on_touch_down(self, touch): '''Used to determine where touch is down and to detect double tap. ''' if touch.is_double_tap: self.dispatch('on_select') return super(NewProjectDialog, self).on_touch_down(touch) def on_select(self, *args): '''Default Event Handler for 'on_select' event ''' pass def on_cancel(self, *args): '''Default Event Handler for 'on_cancel' event ''' pass def on_select_button(self, *args): '''Event Handler for 'on_release' of select button. ''' self.select_button.bind(on_press=partial(self.dispatch, 'on_select')) def on_cancel_button(self, *args): '''Event Handler for 'on_release' of cancel button. ''' self.cancel_button.bind(on_press=partial(self.dispatch, 'on_cancel'))
class CompletionBubble(Bubble): list_view = ObjectProperty(None, allownone=True) '''(internal) Reference a ListView with a list of SuggestionItems :data:`list_view` is a :class:`~kivy.properties.ObjectProperty` ''' adapter = ObjectProperty(None) '''(internal) Reference a ListView adapter :data:`adapter` is a :class:`~kivy.properties.ObjectProperty` ''' __events__ = ( 'on_complete', 'on_cancel', ) def __init__(self, **kwargs): super(CompletionBubble, self).__init__(**kwargs) Window.bind(on_touch_down=self.on_window_touch_down) def on_window_touch_down(self, win, touch): '''Disable the completion if the user clicks anywhere ''' if not self.collide_point(*touch.pos): self.dispatch('on_cancel') def _create_list_view(self, data): '''Create the ListAdapter ''' self.adapter = ListAdapter(data=data, args_converter=self._args_converter, cls=SuggestionItem, selection_mode='single', allow_empty_selection=False) self.adapter.bind(on_selection_change=self.on_selection_change) self.list_view = CompletionListView(adapter=self.adapter) self.add_widget(self.list_view) def _args_converter(self, index, completion): return { 'text': completion.name, 'is_selected': False, 'complete': completion.complete, 'selected_by_touch': self.selected_by_touch } def selected_by_touch(self, item): self.dispatch('on_complete', item.complete) def show_completions(self, completions, force_scroll=False): '''Update the Completion ListView with completions ''' if completions == []: fake_completion = type('obj', (object, ), { 'name': 'No suggestions', 'complete': '' }) completions.append(fake_completion) Window.bind(on_key_down=self.on_key_down) if not self.list_view: self._create_list_view(completions) else: self.adapter.data = completions if force_scroll: self.list_view.scroll_to(0) def on_selection_change(self, *args): pass def _scroll_item(self, new_index): '''Update the scroll view position to display the new_index item ''' item = self.adapter.get_view(new_index) if item: item.trigger_action(0) if new_index > 2 and new_index < len(self.adapter.data) - 1: self.list_view.scroll_to(new_index - 3) def on_key_down(self, instance, key, *args): '''Keyboard listener to grab key codes and interact with the Completion box ''' selected_item = self.adapter.selection[0] selected_index = selected_item.index if self.list_view.scrolled: # recreate list view after mouse scroll due to the bug kivy/#3418 self.remove_widget(self.list_view) self.list_view = None self.show_completions(self.adapter.data) return self.on_key_down(instance, key, args) if key == 273: # up if selected_index > 0: self._scroll_item(selected_index - 1) return True elif key == 274: # down if selected_index < len(self.adapter.data) - 1: self._scroll_item(selected_index + 1) return True elif key in [9, 13, 32]: # tab, enter or space self.dispatch('on_complete', selected_item.complete) return True else: # another key cancel the completion self.dispatch('on_cancel') return False def reposition(self, pos, line_height): '''Update the Bubble position. Try to display it in the best place of the screen ''' win = Window self.x = pos[0] - self.width / 2 self.y = pos[1] - self.height - line_height # fit in the screen horizontally if self.right > win.width: self.x = win.width - self.width if self.x < 0: self.x = 0 # fit in the screen vertically if self.y < 0: diff = abs(self.y) # check if we can move it to top new_y = pos[1] + line_height if new_y + self.height < win.height: # fit in the screen self.y = new_y else: # doesnt fit on top neither on bottom. Check the best place new_diff = abs(new_y + self.height - win.height) if new_diff < diff: # if we lose lest moving it to top self.y = new_y # compare the desired position with the actual position x_relative = self.x - (pos[0] - self.width / 2) x_range = self.width / 4 # consider 25% as the range def _get_hpos(): '''Compare the position of the widget with the parent to display the arrow in the correct position ''' _pos = 'mid' if x_relative == 0: _pos = 'mid' elif x_relative < -x_range: _pos = 'right' elif x_relative > x_range: _pos = 'left' return _pos if self.y == pos[1] - self.height - line_height: self.arrow_pos = 'top_' + _get_hpos() else: self.arrow_pos = 'bottom_' + _get_hpos() def on_complete(self, *args): '''Dispatch a completion selection ''' Window.unbind(on_key_down=self.on_key_down) def on_cancel(self, *args): '''Disable key listener on cancel ''' Window.unbind(on_key_down=self.on_key_down)
class NewProjectDialog(BoxLayout): listview = ObjectProperty(None) ''':class:`~kivy.uix.listview.ListView` used for showing file paths. :data:`listview` is a :class:`~kivy.properties.ObjectProperty` ''' select_button = ObjectProperty(None) ''':class:`~kivy.uix.button.Button` used to select the list item. :data:`select_button` is a :class:`~kivy.properties.ObjectProperty` ''' cancel_button = ObjectProperty(None) ''':class:`~kivy.uix.button.Button` to cancel the dialog. :data:`cancel_button` is a :class:`~kivy.properties.ObjectProperty` ''' adapter = ObjectProperty(None) ''':class:`~kivy.uix.listview.ListAdapter` used for selecting files. :data:`adapter` is a :class:`~kivy.properties.ObjectProperty` ''' image = ObjectProperty(None) '''Type of :class:`~kivy.uix.image.Image` to display image of selected new template. :data:`image` is a :class:`~kivy.properties.ObjectProperty` ''' list_parent = ObjectProperty(None) '''Parent of listview. :data:`list_parent` is a :class:`~kivy.properties.ObjectProperty` ''' prev_selection = NumericProperty(0) '''to memorize the previous selection. :attr:`prev_selection` is a :class: `~kivy.properties.NumericProperty`, defaults to (0). ''' __events__ = ('on_select', 'on_cancel') def __init__(self, **kwargs): super(NewProjectDialog, self).__init__(**kwargs) item_strings = list(NEW_PROJECTS.keys()) item_strings.sort() self.adapter = ListAdapter(cls=Factory.DesignerListItemButton, data=item_strings, selection_mode='single', allow_empty_selection=False) self.adapter.check_for_empty_selection = self.check_for_empty_selection self.adapter.bind(on_selection_change=self.on_adapter_selection_change) self.listview = ListView(adapter=self.adapter) self.listview.size_hint = (0.5, 1) self.listview.pos_hint = {'top': 1} self.list_parent.add_widget(self.listview, 1) self.on_adapter_selection_change(self.adapter) def on_parent(self, *args): if self.parent: Window.bind(on_key_down=self._on_keyboard_down) else: Window.unbind(on_key_down=self._on_keyboard_down) def _on_keyboard_down(self, keyboard, key, codepoint, text, modifier, *args): '''To detect which key is pressed ''' if modifier: return False key_str = Keyboard.keycode_to_string(Window._system_keyboard, key) if key_str == 'up': v = self.adapter.get_view(self.prev_selection - 1) if v is not None: self.adapter.handle_selection(v) return True if key_str == 'down': v = self.adapter.get_view(self.prev_selection + 1) if v is not None: self.adapter.handle_selection(v) return True if key_str == 'enter': self.dispatch('on_select') return True def check_for_empty_selection(self, *args): if not self.adapter.allow_empty_selection: if len(self.adapter.selection) == 0: # Select the first item if we have it. v = self.adapter.get_view(self.prev_selection) if v is not None: self.adapter.handle_selection(v) def on_adapter_selection_change(self, adapter): '''Event handler for 'on_selection_change' event of adapter. ''' name = adapter.selection[0].text.lower() + '.png' name = name.replace(' and ', '_') image_source = join(constants.NEW_TEMPLATE_IMAGE_PATH, name) image_source = join(get_kd_data_dir(), image_source) parent = self.image.parent parent.remove_widget(self.image) self.image = Image(source=image_source) parent.add_widget(self.image) self.prev_selection = adapter.data.index(adapter.selection[0].text) def on_touch_down(self, touch): '''Used to determine where touch is down and to detect double tap. ''' if touch.is_double_tap: self.dispatch('on_select') return super(NewProjectDialog, self).on_touch_down(touch) def on_select(self, *args): '''Default Event Handler for 'on_select' event ''' pass def on_cancel(self, *args): '''Default Event Handler for 'on_cancel' event ''' pass def on_select_button(self, *args): '''Event Handler for 'on_release' of select button. ''' self.select_button.bind(on_press=partial(self.dispatch, 'on_select')) def on_cancel_button(self, *args): '''Event Handler for 'on_release' of cancel button. ''' self.cancel_button.bind(on_press=partial(self.dispatch, 'on_cancel'))
class PopupListView(FlatPopup): ''' When a click on this button occur, a popup will be shown to pick a value. Arguments named popup_* will be passed down to the popup for customizations. To customize ListItemButton selected_color and deselected_color please use kv lang. ''' item_row_height = NumericProperty(dp(40)) '''Height of rows shown by the popup .. versionadded:: 1.0 :attr:`item_row_height` is a :class:`~kivy.properties.NumericProperty`, default to 40. ''' selected = ObjectProperty(None) '''Data selected. .. versionadded:: 1.0 :attr:`selected` is a :class:`~kivy.properties.ObjectProperty`. ''' selected = ObjectProperty(None) '''Index of selected data. .. versionadded:: 1.0 :attr:`selected` is a :class:`~kivy.properties.ObjectProperty`. ''' on_selection = ObjectProperty(None) '''Called whenever a value is selected, see 'on_selection_change' method code. .. versionadded:: 1.0 :attr:`on_selection` is a :class:`~kivy.properties.ObjectProperty`. ''' def __init__(self, list_data, **kargs): super(PopupListView, self).__init__(**kargs) self.list_data = self.build_list_data(list_data) self.content = self._build_list_view(kargs) def _build_list_view(self, kargs): self._list_view = ListView(adapter=self._build_adapter(), propagate_selection_to_data=True) return self._list_view def build_list_data(self, data): if len(data) > 0: if data[0].__class__ == str: l2dict = [{ 'is_selected': False, 'rowid': 0, 'label': x } for x in data] return self.build_list_data(l2dict) else: result = data if 'is_selected' not in data[0].keys(): result = [] for x in data: x['is_selected'] = False result.append(x) return result return [] def show_choices(self, *args): self.open() def adapter_converter(self): return lambda i, o : { \ 'is_selected' : o['is_selected'], \ 'size_hint_y' : None, \ 'height' : self.item_row_height, \ 'text' : o['label'], \ 'rowid' : o['rowid'] \ } def _build_adapter(self): self.list_adapter = ListAdapter( cls=ListItemButton, data=self.list_data, args_converter=self.adapter_converter(), selection_mode='single', allow_empty_selection=True) self.list_adapter.bind(on_selection_change=self.on_selection_change) return self.list_adapter def on_selection_change(self, adapter, *args): if (adapter.selection): self.selected_index = adapter.selection[0].index self.selected = self.list_data[self.selected_index] if self.on_selection: self.on_selection(self, self.selected, self.selected_index) self.dismiss() def select(self, i): self.list_adapter.get_view(i).trigger_action(duration=0)
class InputScreen(GridLayout): # Create interface def __init__(self, **kwargs): self.controller = kwargs.pop('controller', None) super(InputScreen, self).__init__(**kwargs) self.cols = 2 # Text input self.text = ShortcutTextInput(multiline=True, focus=True, keyboard_handler=self._on_keyboard_down) self.add_widget(self.text) self.text.bind(text=self._on_text) self.text.hint_text="Begin typing to test text prediction." # predictions obj_printer = lambda obj: "{} - {}".format(str(obj), self.current_prediction.get(obj, 0)) args_converter = lambda row_index, obj: {'text': obj_printer(obj), 'size_hint_y': None, 'height': 25} self.prediction_adapter = ListAdapter(data=[], args_converter=args_converter, selection_mode='single', allow_empty_selection=False, cls=ListItemLabel) self.prediction_list = ListView(adapter=self.prediction_adapter) self.add_widget(self.prediction_list) self.current_prediction = FreqDist() # Record history self.previous_record = None self.in_word = False # Handle non-standard shortcuts # Mainly prediction selection and confirmation def _on_keyboard_down(self, keyboard, keycode, text, modifiers): # print keycode if keycode[1] == 'tab': # Change selected prediction self.change_selection('shift' in modifiers) return True elif keycode[1] == 'up': self.change_selection(True) return True elif keycode[1] == 'down': self.change_selection(False) return True elif keycode[1] == 'enter': # Use the current prediction self._accept_prediction() return True elif keycode[1] == 'spacebar': # Record word self._accept_word(self._current_word()) return False elif keycode[1] == 'backspace': # Remove previous word from record self.undo_word() return False else: # Default behaviour return False # Select a different prediction to use def change_selection(self, direction): # Get current index selection = self.prediction_adapter.selection[0] index = self._selection_index(selection) # Deselect the current selection self._deselect_current() # whether to move back or forward if direction: index -= 1 else: index += 1 index = index % len(self.prediction_adapter.cached_views) # Select next self._select_at_index(index) # Get the index of the currently selected prediction def _selection_index(self, selection): for i, view in self.prediction_adapter.cached_views.iteritems(): if view == selection: return i # Deselect the currently selected prediction def _deselect_current(self): selection = self.prediction_adapter.selection[0] index = self._selection_index(selection) self.prediction_adapter.get_view(index).deselect() # Select the prediction with the given index def _select_at_index(self, index): view = self.prediction_adapter.get_view(index) view.select() self.prediction_adapter.selection[0] = view # Accept the selected prediction and insert it into the text field def _accept_prediction(self): offset = len(self._current_word()) selection = self.prediction_adapter.selection[0] index = self._selection_index(selection) word = self.prediction_adapter.data[index] addition = word[offset:] self._accept_word(word) self.text.insert_text(addition + ' ') # Set that we are no longer within a word def _accept_word(self, word): self.in_word = False # Set that we are now in a word again after backspacing def undo_word(self): if self.previous_record is not None: if self.previous_record == self._current_word(): self.previous_record = None self.in_word = True # Called when the text is changed to update the current predictions def _on_text(self, widget, text): # Record if we are typing a word if len(text) > 0 and text[-1] != ' ': self.in_word = True # Predict based on input prediction = self.controller.predict(str(text), self.in_word, MAX_PREDICTIONS) # Reset index if there is a prediction current_word = self._current_word() if len(current_word) > 0 and current_word not in prediction: prediction[current_word] = 0 # populate list self.current_prediction = prediction self.prediction_adapter.data = self.controller.prediction_list(prediction) # Select a randomly select a weighted next word # rand = self.controller.weighted_random_prediction(prediction) # index = self.prediction_adapter.data.index(rand) # self._deselect_current() # self._select_at_index(index) # Returns the word currently being typed def _current_word(self): return self.text.text.split(' ')[-1]
class CompletionBubble(Bubble): list_view = ObjectProperty(None, allownone=True) '''(internal) Reference a ListView with a list of SuggestionItems :data:`list_view` is a :class:`~kivy.properties.ObjectProperty` ''' adapter = ObjectProperty(None) '''(internal) Reference a ListView adapter :data:`adapter` is a :class:`~kivy.properties.ObjectProperty` ''' __events__ = ('on_complete', 'on_cancel', ) def __init__(self, **kwargs): super(CompletionBubble, self).__init__(**kwargs) Window.bind(on_touch_down=self.on_window_touch_down) def on_window_touch_down(self, win, touch): '''Disable the completion if the user clicks anywhere ''' if not self.collide_point(*touch.pos): self.dispatch('on_cancel') def _create_list_view(self, data): '''Create the ListAdapter ''' self.adapter = ListAdapter( data=data, args_converter=self._args_converter, cls=SuggestionItem, selection_mode='single', allow_empty_selection=False ) self.adapter.bind(on_selection_change=self.on_selection_change) self.list_view = CompletionListView(adapter=self.adapter) self.add_widget(self.list_view) def _args_converter(self, index, completion): return {'text': completion.name, 'is_selected': False, 'complete': completion.complete, 'selected_by_touch': self.selected_by_touch} def selected_by_touch(self, item): self.dispatch('on_complete', item.complete) def show_completions(self, completions, force_scroll=False): '''Update the Completion ListView with completions ''' if completions == []: fake_completion = type('obj', (object,), {'name': 'No suggestions', 'complete': ''}) completions.append(fake_completion) Window.bind(on_key_down=self.on_key_down) if not self.list_view: self._create_list_view(completions) else: self.adapter.data = completions if force_scroll: self.list_view.scroll_to(0) def on_selection_change(self, *args): pass def _scroll_item(self, new_index): '''Update the scroll view position to display the new_index item ''' item = self.adapter.get_view(new_index) if item: item.trigger_action(0) if new_index > 2 and new_index < len(self.adapter.data) - 1: self.list_view.scroll_to(new_index - 3) def on_key_down(self, instance, key, *args): '''Keyboard listener to grab key codes and interact with the Completion box ''' selected_item = self.adapter.selection[0] selected_index = selected_item.index if self.list_view.scrolled: # recreate list view after mouse scroll due to the bug kivy/#3418 self.remove_widget(self.list_view) self.list_view = None self.show_completions(self.adapter.data) return self.on_key_down(instance, key, args) if key == 273: # up if selected_index > 0: self._scroll_item(selected_index - 1) return True elif key == 274: # down if selected_index < len(self.adapter.data) - 1: self._scroll_item(selected_index + 1) return True elif key in [9, 13, 32]: # tab, enter or space self.dispatch('on_complete', selected_item.complete) return True else: # another key cancel the completion self.dispatch('on_cancel') return False def reposition(self, pos, line_height): '''Update the Bubble position. Try to display it in the best place of the screen ''' win = Window self.x = pos[0] - self.width / 2 self.y = pos[1] - self.height - line_height # fit in the screen horizontally if self.right > win.width: self.x = win.width - self.width if self.x < 0: self.x = 0 # fit in the screen vertically if self.y < 0: diff = abs(self.y) # check if we can move it to top new_y = pos[1] + line_height if new_y + self.height < win.height: # fit in the screen self.y = new_y else: # doesnt fit on top neither on bottom. Check the best place new_diff = abs(new_y + self.height - win.height) if new_diff < diff: # if we lose lest moving it to top self.y = new_y # compare the desired position with the actual position x_relative = self.x - (pos[0] - self.width / 2) x_range = self.width / 4 # consider 25% as the range def _get_hpos(): '''Compare the position of the widget with the parent to display the arrow in the correct position ''' _pos = 'mid' if x_relative == 0: _pos = 'mid' elif x_relative < -x_range: _pos = 'right' elif x_relative > x_range: _pos = 'left' return _pos if self.y == pos[1] - self.height - line_height: self.arrow_pos = 'top_' + _get_hpos() else: self.arrow_pos = 'bottom_' + _get_hpos() def on_complete(self, *args): '''Dispatch a completion selection ''' Window.unbind(on_key_down=self.on_key_down) def on_cancel(self, *args): '''Disable key listener on cancel ''' Window.unbind(on_key_down=self.on_key_down)