# HG changeset patch # User Goffi # Date 1418234915 -3600 # Node ID ed2675f92f7c4ddd8912775a88a2a823cce5de04 # Parent f083eca930471d0375aa13864fa6f910fb47083a menus management improvment diff -r f083eca93047 -r ed2675f92f7c urwid_satext/sat_widgets.py --- a/urwid_satext/sat_widgets.py Fri Sep 19 18:23:33 2014 +0200 +++ b/urwid_satext/sat_widgets.py Wed Dec 10 19:08:35 2014 +0100 @@ -22,6 +22,10 @@ import encodings utf8decode = lambda s: encodings.codecs.utf_8_decode(s)[0] +import uuid + +import collections + from urwid.util import is_mouse_press #XXX: is_mouse_press is not included in urwid in 1.0.0 from .keys import action_key_map as a_key @@ -215,8 +219,8 @@ @param selected: is the text selected ? """ self.focus_attr = focus_attr - self.__selected = False - self.__was_focused = False + self._selected = False + self._was_focused = False self.header = header self.text = text urwid.WidgetWrap.__init__(self, urwid.Text("",align=align)) @@ -242,7 +246,7 @@ def set_text(self, text): """/!\ set_text doesn't change self.selected_txt !""" self.text = text - self.setState(self.__selected,invisible=True) + self.setState(self._selected,invisible=True) def setSelectedText(self, text=None): """Text to display when selected @@ -250,12 +254,12 @@ if text == None: text = ('selected',self.getValue()) self.selected_txt = text - if self.__selected: - self.setState(self.__selected) + if self._selected: + self.setState(self._selected) - def __set_txt(self): + def _set_txt(self): txt_list = [self.header] - txt = self.selected_txt if self.__selected else self.text + txt = self.selected_txt if self._selected else self.text if isinstance(txt,list): txt_list.extend(txt) else: @@ -265,31 +269,33 @@ def setState(self, selected, invisible=False): """Change state + @param selected: boolean state value - @param invisible: don't emit change signal if True""" - assert(type(selected)==bool) - self.__selected=selected - self.__set_txt() - self.__was_focused = False + @param invisible: don't emit change signal if True + """ + assert type(selected)==bool + self._selected=selected + self._set_txt() + self._was_focused = False self._invalidate() if not invisible: - self._emit("change", self.__selected) + self._emit("change", self._selected) def getState(self): - return self.__selected + return self._selected def selectable(self): return True def keypress(self, size, key): if key in (a_key['TEXT_SELECT'], a_key['TEXT_SELECT2']): - self.setState(not self.__selected) + self.setState(not self._selected) else: return key def mouse_event(self, size, event, button, x, y, focus): if is_mouse_press(event) and button == 1: - self.setState(not self.__selected) + self.setState(not self._selected) return True return False @@ -297,11 +303,11 @@ def render(self, size, focus=False): attr_list = self._w.base_widget._attrib if not focus: - if self.__was_focused: - self.__set_txt() - self.__was_focused = False + if self._was_focused: + self._set_txt() + self._was_focused = False else: - if not self.__was_focused: + if not self._was_focused: if not attr_list: attr_list.append((self.focus_attr,len(self._w.base_widget.text))) else: @@ -315,7 +321,7 @@ attr+="_focus" attr_list[idx] = (attr,attr_len) self._w.base_widget._invalidate() - self.__was_focused = True #bloody ugly hack :) + self._was_focused = True #bloody ugly hack :) return self._w.render(size, focus) @@ -337,11 +343,11 @@ class CustomButton(ClickableText): - def __init__(self, label, on_press=None, user_data=None, left_border = "[ ", right_border = " ]"): + def __init__(self, label, on_press=None, user_data=None, left_border="[ ", right_border=" ]", align="left"): self.label = label self.left_border = left_border self.right_border = right_border - super(CustomButton, self).__init__([left_border, label, right_border]) + super(CustomButton, self).__init__([left_border, label, right_border], align=align) self.size = len(self.get_text()) if on_press: urwid.connect_signal(self, 'click', on_press, user_data) @@ -359,13 +365,14 @@ class ListOption(unicode): - """ Class similar to unicode, but which make the difference between value and label + """Unicode which manage label and value + + This class similar to unicode, but which make the difference between value and label label is show when use as unicode, the .value attribute contain the actual value Can be initialised with: - basestring (label = value = given string) - a tuple with (value, label) XXX: comparaison is made against value, not the label which is the one displayed - """ def __new__(cls, option): @@ -446,12 +453,12 @@ return super(UnselectableListBox, self).render(size, focus) -class GenericList(urwid.WidgetWrap): +class GenericList(urwid.ListBox): signals = ['click','change'] def __init__(self, options, style=None, align='left', option_type = SelectableText, on_click=None, on_change=None, user_data=None): - """ - Widget managing list of string and their selection + """Widget managing list of string and their selection + @param options: list of strings used for options @param style: list of string: - 'single' if only one must be selected @@ -477,11 +484,10 @@ urwid.connect_signal(self, 'change', on_change, user_data) self.content = urwid.SimpleListWalker([]) - self.list_box = urwid.ListBox(self.content) - urwid.WidgetWrap.__init__(self, self.list_box) + super(GenericList, self).__init__(self.content) self.changeValues(options) - def __onStateChange(self, widget, selected): + def _onStateChange(self, widget, selected): if self.single: if not selected and not self.can_select_none: #if in single mode, it's forbidden to unselect a value @@ -492,7 +498,7 @@ widget.setState(True, invisible=True) self._emit("change") - def __onClick(self, widget): + def _onClick(self, widget): self._emit("click", widget) def unselectAll(self, invisible=False): @@ -527,9 +533,6 @@ result.append(widget.getValue()) return result - def getDisplayWidget(self): - return self.list_box - def changeValues(self, new_values): """Change all values in one shot""" new_values = ListOption.fromOptions(new_values) @@ -541,7 +544,7 @@ if not self.first_display and option in old_selected: widget.setState(True) widgets.append(widget) - for signal, callback in (('change', self.__onStateChange), ('click', self.__onClick)): + for signal, callback in (('change', self._onStateChange), ('click', self._onClick)): try: urwid.connect_signal(widget, signal, callback) except NameError: @@ -549,8 +552,6 @@ self.content[:] = widgets if self.first_display and self.single and new_values and not self.no_first_select: self.content[0].setState(True) - display_widget = self.getDisplayWidget() - self._set_w(display_widget) self._emit('change') self.first_display = False @@ -558,8 +559,10 @@ """Select the first item which has the given value. @param value - @param move_focus (boolean): True to move the focus on the selected value, - False to leave the focus position unchanged. + @param move_focus (bool): + - True to move the focus on the selected value, + - False to leave the focus position unchanged. + """ self.unselectAll() idx = 0 @@ -567,7 +570,7 @@ if widget.getValue() == value: widget.setState(True) if move_focus: - self.list_box.focus_position = idx + self.focus_position = idx return idx+=1 @@ -589,7 +592,7 @@ if widget.getValue() == value: widget.setState(True) if move_focus: - self.list_box.focus_position = idx + self.focus_position = idx idx += 1 @@ -868,75 +871,122 @@ self.__buildOverlay(button.get_label(), self.x_orig + self._w.base_widget.getStartCol(button)) +MenuItem = collections.namedtuple('MenuItem', ('name', 'widget')) class MenuRoller(urwid.WidgetWrap): - def __init__(self,menus_list): + def __init__(self, menus_list): """Create a MenuRoller - @param menus_list: list of tuple with (name, Menu_instance), name can be None + + @param menus_list: list of tuples which can be either: + - (name, Menu instance) + - (name, Menu instance, id) """ - assert (menus_list) - self.selected = 0 - self.name_list = [] - self.menus = {} + assert menus_list + self.selected = None + self.menu_items = collections.OrderedDict() self.columns = urwid.Columns([urwid.Text(''),urwid.Text('')]) urwid.WidgetWrap.__init__(self, self.columns) for menu_tuple in menus_list: - name,menu = menu_tuple - self.addMenu(name, menu) + try: + name, menu, id_ = menu_tuple + except ValueError: + name, menu = menu_tuple + id_ = None + self.addMenu(name, menu, id_) def _showSelected(self): """show menu selected""" - name_txt = u'\u21c9 '+self.name_list[self.selected]+u' \u21c7 ' - current_name = ClickableText(name_txt) - name_len = len(name_txt) - current_menu = self.menus[self.name_list[self.selected]] - current_menu.setOrigX(name_len) - self.columns.contents[0] = (current_name, ('given', name_len, False)) - self.columns.contents[1] = (current_menu, ('weight', 1, False)) + if self.selected is None: + self.columns.contents[0] = (urwid.Text(''), ('given', 0, False)) + self.columns.contents[1] = (urwid.Text(''), ('weight', 1, False)) + else: + menu_item = self.menu_items[self.selected] + name_txt = u'\u21c9 ' + menu_item.name + u' \u21c7 ' + current_name = ClickableText(name_txt) + name_len = len(name_txt) + current_menu = menu_item.widget + current_menu.setOrigX(name_len) + self.columns.contents[0] = (current_name, ('given', name_len, False)) + self.columns.contents[1] = (current_menu, ('weight', 1, False)) def keypress(self, size, key): + menu_ids = self.menu_items.keys() + try: + idx = menu_ids.index(self.selected) + except ValueError: + return super(MenuRoller, self).keypress(size, key) + if key==a_key['MENU_ROLLER_UP']: if self.columns.get_focus_column()==0: - if self.selected > 0: - self.selected -= 1 + if idx > 0: + self.selected = menu_ids[idx-1] self._showSelected() return elif key==a_key['MENU_ROLLER_DOWN']: if self.columns.get_focus_column()==0: - if self.selected < len(self.name_list)-1: - self.selected += 1 + if idx < len(menu_ids)-1: + self.selected = menu_ids[idx+1] self._showSelected() return elif key==a_key['MENU_ROLLER_RIGHT']: if self.columns.get_focus_column()==0 and \ (isinstance(self.columns.contents[1][0], urwid.Text) or \ - self.menus[self.name_list[self.selected]].getMenuSize()==0): + self.menu_items[self.selected].widget.getMenuSize()==0): return #if we have no menu or the menu is empty, we don't go the right column return super(MenuRoller, self).keypress(size, key) - def addMenu(self, name_param, menu): - name = name_param or '' - if name not in self.name_list: - self.name_list.append(name) - self.menus[name] = menu - if self.name_list[self.selected] == name: + def addMenu(self, name, widget, menu_id=None): + """Add a menu + + @param name: name of the menu to add, it name already exists, menu is not added + @param widget: instance of Menu + @param menu_id: id to use of this menu, or None to generate + @return: menu_id + """ + names = {menu_item.name: id_ for id_, menu_item in self.menu_items.iteritems()} + + if name not in names: + id_ = menu_id or str(uuid.uuid4()) + if id_ in self.menu_items: + raise ValueError('Conflict: the id [{}] is already used'.format(id_)) + self.menu_items[id_] = MenuItem(name, widget) + else: + id_ = names[name] + menu_item = self.menu_items[id_] + if menu_item.widget is not widget: + raise ValueError("The menu with id [{}] exists and doesn't contain the given instance. Use replaceMenu if you want to change the menu.".format(id_)) + if self.selected is None: + self.selected = id_ + self._showSelected() + return id_ + + def replaceMenu(self, name, widget, menu_id): + """Add a menu or replace it if the id already exists + + @param name: name of the menu to add, it name already exists, menu is not added + @param widget: instance of Menu + @param menu_id: id or the menu + """ + assert menu_id is not None + if menu_id in self.menu_items: + del self.menu_items[menu_id] + self.addMenu(name, widget, menu_id) + if self.selected == menu_id: self._showSelected() #if we are on the menu, we update it - def removeMenu(self, name): - if name in self.name_list: - self.name_list.remove(name) - if name in self.menus.keys(): - del self.menus[name] - self.selected = 0 - self._showSelected() + def removeMenu(self, menu_id): + del self.menu_items[menu_id] + if self.selected == menu_id: + self.selected = self.menu_items.items[0] if self.menu_items else None + self._showSelected() def checkShortcuts(self, key): - for menu in self.name_list: - key = self.menus[menu].checkShortcuts(key) + for menu_item in self.menu_items.values(): + key = menu_item.widget.checkShortcuts(key) return key