Mercurial > libervia-backend
diff frontends/primitivus/custom_widgets.py @ 192:879beacb8e16
Primitivus: major changes in SelectableText, menu can now be used with mouse, TabsContainer show wich tab is selected
- SelectableText is a WidgetWrap of urwid.Text, and manage attributes and complexe combination like urwid.Text (list of tuples (attribute,text))
- Menu now use a ColumnsRoller, and can't anymore be displayed on 2 rows, avoiding potential bug when display MenuBox
- Mouse can be used in menu. Right click on MenuBox make it disappear
- Tab container now display which tab is selected by putting a 'title' attribute on the corresponding button
/!\ as SelectableText is heavily used, there can be regressions
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 19 Aug 2010 21:11:22 +0800 |
parents | 33e618d385cf |
children | 92e4ddd580ae |
line wrap: on
line diff
--- a/frontends/primitivus/custom_widgets.py Wed Aug 18 22:34:35 2010 +0800 +++ b/frontends/primitivus/custom_widgets.py Thu Aug 19 21:11:22 2010 +0800 @@ -21,6 +21,7 @@ import urwid from urwid.escape import utf8decode +from logging import debug, info, warning, error class Password(urwid.Edit): """Edit box which doesn't show what is entered (show '*' or other char instead)""" @@ -128,43 +129,81 @@ render_text = middle * self.car + self.text + (maxcol - len(self.text) - middle) * self.car return urwid.Text(render_text) -class SelectableText(urwid.FlowWidget): +class SelectableText(urwid.WidgetWrap): """Text which can be selected with space""" signals = ['change'] - def __init__(self, text, align='left', header='', select_attr=None, default_attr=None, selected = False, data=None): - self.text=unicode(text) - self.header=header - if data: - self.data=data - if select_attr: - self.selected = select_attr - if default_attr: - self.default = default_attr - self.align = align - self.__selected=selected + def __init__(self, text, align='left', header='', focus_attr='default_focus', selected_text=None, selected=False, data=None): + """@param text: same as urwid.Text's text parameter + @param align: same as urwid.Text's align parameter + @select_attr: attrbute to use when selected + @param selected: is the text selected ?""" + self.focus_attr = focus_attr + self.__selected = False + self.__was_focused = False + self.header = self.__valid_text(header) + self.default_txt = self.__valid_text(text) + urwid.WidgetWrap.__init__(self, urwid.Text("",align=align)) + self.setSelectedText(selected_text) + self.setState(selected) + + def __valid_text(self, text): + """Tmp method needed until dbus and urwid are more friends""" + if isinstance(text,basestring): + return unicode(text) + elif isinstance(text,tuple): + return (unicode(text[0]),text[1]) + elif isinstance(text,list): + for idx in range(len(text)): + elem = text[idx] + if isinstance(elem,basestring): + text[idx] = unicode(elem) + if isinstance(elem,tuple): + text[idx] = (unicode(elem[0]),elem[1]) + else: + warning (_('WARNING: unknown text type')) + return text def getValue(self): - return self.text + if isinstance(self.default_txt,basestring): + return self.default_txt + list_attr = self.default_txt if isinstance(self.default_txt, list) else [self.default_txt] + txt = "" + for attr in list_attr: + if isinstance(attr,tuple): + txt+=attr[1] + else: + txt+=attr + return txt def get_text(self): """for compatibility with urwid.Text""" return self.getValue() def set_text(self, text): - self.text=unicode(text) - self._invalidate() + """/!\ set_text doesn't change self.selected_txt !""" + self.default_txt = self.__valid_text(text) + self.setState(self.__selected,invisible=True) - def setAttribute(self, name, value): - """Change attribut used for rendering widget - @param name: one of - -default: when not selected - -selected: when selected - @param value: name of the attribute - /!\ the attribute name followed by _focus is used when widget has focus""" - assert name in ['default', 'selected'] - self.__setattr__(name,value) - self._invalidate() + def setSelectedText(self, text=None): + """Text to display when selected + @text: text as in urwid.Text or None for default value""" + if text == None: + text = ('selected',self.default_txt) + self.selected_txt = self.__valid_text(text) + if self.__selected: + self.setState(self.__selected) + + + def __set_txt(self): + txt_list = [self.header] + txt = self.selected_txt if self.__selected else self.default_txt + if isinstance(txt,list): + txt_list.extend(txt) + else: + txt_list.append(txt) + self._w.base_widget.set_text(txt_list) + def setState(self, selected, invisible=False): """Change state @@ -172,6 +211,8 @@ @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) @@ -195,39 +236,46 @@ return False - def rows(self,size,focus=False): - return self.display_widget(size, focus).rows(size, focus) - def render(self, size, focus=False): - return self.display_widget(size, focus).render(size, focus) - - def display_widget(self, size, focus): - try: - select_attr = self.selected - except AttributeError: - select_attr = 'selected' - try: - default_attr = self.default - except AttributeError: - default_attr = 'default' - attr = select_attr if self.__selected else default_attr - if focus: - attr+="_focus" - return urwid.Text((attr,self.header+self.text), align=self.align) + attr_list = self._w.base_widget._attrib + if not focus: + if self.__was_focused: + self.__set_txt() + self.__was_focused = False + else: + if not self.__was_focused: + if not attr_list: + attr_list.append((self.focus_attr,len(self._w.base_widget.text))) + else: + for idx in range(len(attr_list)): + attr,attr_len = attr_list[idx] + if attr == None: + attr = self.focus_attr + attr_list[idx] = (attr,attr_len) + else: + if not attr.endswith('_focus'): + attr+="_focus" + attr_list[idx] = (attr,attr_len) + self._w.base_widget._invalidate() + self.__was_focused = True #bloody ugly hack :) + return self._w.render(size, focus) class ClickableText(SelectableText): signals = SelectableText.signals + ['click'] def setState(self, selected, invisible=False): - self._emit('click') + super(ClickableText,self).setState(False,True) + if not invisible: + self._emit('click') class CustomButton(ClickableText): def __init__(self, label, on_press=None, user_data=None, left_border = "[ ", right_border = " ]"): self.label = label - render_txt = "%s%s%s" % (left_border, label, right_border) - self.size = len(render_txt) - super(CustomButton, self).__init__(render_txt) + self.left_border = left_border + self.right_border = right_border + super(CustomButton, self).__init__([left_border, label, right_border]) + self.size = len(self.get_text()) if on_press: urwid.connect_signal(self, 'click', on_press, user_data) @@ -236,7 +284,11 @@ return self.size def get_label(self): - return self.label + return self.label[1] if isinstance(self.label,tuple) else self.label + + def set_label(self, label): + self.label = label + self.set_text([self.left_border, label, self.right_border]) class GenericList(urwid.WidgetWrap): signals = ['click','change'] @@ -404,16 +456,16 @@ def __init__(self): self.waitNotifs = urwid.Text('') - self.message = ClickableText('', default_attr='notifs') + self.message = ClickableText('') urwid.connect_signal(self.message, 'click', lambda wid: self.showNext()) - self.progress = ClickableText('', default_attr='notifs') + self.progress = ClickableText('') self.columns = urwid.Columns([('fixed',6,self.waitNotifs),self.message,('fixed',4,self.progress)]) urwid.WidgetWrap.__init__(self, urwid.AttrMap(self.columns,'notifs')) self.notifs = [] def __modQueue(self): """must be called each time the notifications queue is changed""" - self.waitNotifs.set_text("(%i)" % len(self.notifs) if self.notifs else '') + self.waitNotifs.set_text(('notifs',"(%i)" % len(self.notifs) if self.notifs else '')) self._emit('change') def setProgress(self,percentage): @@ -421,7 +473,7 @@ if percentage == None: self.progress.set_text('') else: - self.progress.set_text('%02i%%' % percentage) + self.progress.set_text(('notifs','%02i%%' % percentage)) self._emit('change') def addPopUp(self, pop_up_widget): @@ -432,7 +484,7 @@ def addMessage(self, message): "Add a message to the notificatio bar" if not self.message.get_text(): - self.message.set_text(message) + self.message.set_text(('notifs',message)) self._invalidate() self._emit('change') else: @@ -448,7 +500,7 @@ break if found: self.notifs.remove(found) - self.message.set_text(found[1]) + self.message.set_text(('notifs',found[1])) self.__modQueue() else: self.message.set_text('') @@ -482,7 +534,7 @@ def __init__(self,parent,items): self.parent = parent self.selected = None - content = urwid.SimpleListWalker([ClickableText(text,default_attr='menuitem') for text in items]) + content = urwid.SimpleListWalker([ClickableText(('menuitem',text)) for text in items]) for wid in content: urwid.connect_signal(wid, 'click', self.onClick) @@ -501,27 +553,33 @@ self.parent.keypress(size,'up') self.parent.keypress(size,key) return super(MenuBox,self).keypress(size,key) + + def mouse_event(self, size, event, button, x, y, focus): + if button == 3: + self.parent.keypress(size,'up') + return True + return super(MenuBox,self).mouse_event(size, event, button, x, y, focus) def onClick(self, wid): self.selected = wid.getValue() self._emit('click') -class Menu(urwid.FlowWidget): +class Menu(urwid.WidgetWrap): def __init__(self,loop, x_orig=0): """Menu widget @param loop: main loop of urwid @param x_orig: absolute start of the abscissa """ - super(Menu, self).__init__() self.loop = loop self.menu_keys = [] self.menu = {} self.x_orig = x_orig self.shortcuts = {} #keyboard shortcuts - self.focus_menu = 0 self.save_bottom = None - + col_rol = ColumnsRoller() + urwid.WidgetWrap.__init__(self, urwid.AttrMap(col_rol,'menubar')) + def selectable(self): return True @@ -543,29 +601,20 @@ self.save_bottom = self.loop.widget menu_box = MenuBox(self,[item[0] for item in self.menu[menu_key]]) - urwid.connect_signal(menu_box, 'click', self.onClick) + urwid.connect_signal(menu_box, 'click', self.onItemClick) self.loop.widget = urwid.Overlay(urwid.AttrMap(menu_box,'menubar'),self.save_bottom,('fixed left', columns),max_len+2,('fixed top',1),None) def keypress(self, size, key): - if key == 'right' and self.focus_menu < len(self.menu)-1: - self.focus_menu += 1 - self._invalidate() - elif key == 'left' and self.focus_menu > 0: - self.focus_menu -= 1 - self._invalidate() - return - elif key == 'down': - if self.menu_keys and not self.save_bottom: - column = sum([len(menu)+4 for menu in self.menu_keys[0:self.focus_menu]],self.focus_menu+self.x_orig) - self.__buildOverlay(self.menu_keys[self.focus_menu],column) + if key == 'down': + key = 'enter' elif key == 'up': if self.save_bottom: self.loop.widget = self.save_bottom self.save_bottom = None - return key - + return self._w.base_widget.keypress(size, key) + def checkShortcuts(self, key): for shortcut in self.shortcuts.keys(): if key == shortcut: @@ -581,32 +630,19 @@ if not category in self.menu.keys(): self.menu_keys.append(category) self.menu[category] = [] + button = CustomButton(('menubar',category), self.onCategoryClick, + left_border = ('menubar',"[ "), + right_border = ('menubar'," ]")) + self._w.base_widget.addWidget(button,button.getSize()) self.menu[category].append((item, callback)) if shortcut: assert(shortcut not in self.shortcuts.keys()) self.shortcuts[shortcut] = (category, item, callback) - def rows(self,size,focus=False): - return self.display_widget(size, focus).rows(size, focus) - - def render(self, size, focus=False): - return self.display_widget(size, focus).render(size, focus) - - def display_widget(self, size, focus): - render_txt = [] - idx = 0 - for menu in self.menu_keys: - if focus and idx == self.focus_menu: - render_txt.append(('selected_menu', '[ %s ]' % menu)) - render_txt.append(' ') - else: - render_txt.append('[ %s ] ' % menu) - idx += 1 - return urwid.AttrMap(urwid.Text(render_txt), 'menubar') - - def onClick(self, widget): - category = self.menu_keys[self.focus_menu] + def onItemClick(self, widget): + category = self._w.base_widget.getSelected().get_label() item = widget.getValue() + callback = None for menu_item in self.menu[category]: if item == menu_item[0]: callback = menu_item[1] @@ -614,6 +650,11 @@ if callback: self.keypress(None,'up') callback((category, item)) + + def onCategoryClick(self, button): + self.__buildOverlay(button.get_label(), + self.x_orig + self._w.base_widget.getStartCol(button)) + class MenuRoller(urwid.WidgetWrap): @@ -751,6 +792,15 @@ if len(self.widget_list) == 1: self.set_focus(0) + def getStartCol(self, widget): + """Return the column of the left corner of the widget""" + start_col = 0 + for wid in self.widget_list[self.__start:]: + if wid[1] == widget: + return start_col + start_col+=wid[0] + return None + def selectable(self): try: return self.widget_list[self.focus_column][1].selectable() @@ -772,6 +822,10 @@ return self.widget_list[self.focus_column][1].keypress(size,key) return key + def getSelected(self): + """Return selected widget""" + return self.widget_list[self.focus_column][1] + def set_focus(self, idx): if idx>len(self.widget_list)-1: idx = len(self.widget_list)-1 @@ -802,7 +856,7 @@ end_wid-=1 cols_left = maxcol - total_wid - + self.__start = start_wid #we need to keep it for getStartCol return _prev,_next,start_wid,end_wid,cols_left @@ -882,7 +936,7 @@ signals = ['click'] def __init__(self): - #self._current_tab = 0 + self._current_tab = None self._buttons_cont = ColumnsRoller() self.tabs = [] self.__frame = FocusFrame(urwid.Filler(urwid.Text('')),urwid.Pile([self._buttons_cont,urwid.Divider(u"─")])) @@ -907,6 +961,10 @@ error(_("INTERNAL ERROR: Tab not found")) assert(False) self.__frame.body = tab[1] + button.set_label(('title',button.get_label())) + if self._current_tab: + self._current_tab.set_label(self._current_tab.get_label()) + self._current_tab = button if not invisible: self._emit('click')