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
This commit is contained in:
parent
991d367f85
commit
827bc0bd3c
6 changed files with 161 additions and 101 deletions
|
|
@ -95,7 +95,8 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
|
|||
nick = self.CM.getAttr(jid,'nick')
|
||||
display = nick or name or jid.node or jid.short
|
||||
header = '(*) ' if contact in self.alert_jid else ''
|
||||
widget = custom_widgets.SelectableText(display, selected = contact==self.selected, header=header, data=contact)
|
||||
widget = custom_widgets.SelectableText(display, selected = contact==self.selected, header=header)
|
||||
widget.data = contact
|
||||
if contact in self.alert_jid:
|
||||
widget.setAttribute('default','alert')
|
||||
content.append(widget)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
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 @@ class SurroundedText(urwid.FlowWidget):
|
|||
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 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 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 setState(self, selected, invisible=False):
|
||||
"""Change state
|
||||
|
|
@ -172,6 +211,8 @@ class SelectableText(urwid.FlowWidget):
|
|||
@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 @@ class SelectableText(urwid.FlowWidget):
|
|||
|
||||
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 @@ class CustomButton(ClickableText):
|
|||
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 @@ class NotificationBar(urwid.WidgetWrap):
|
|||
|
||||
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 @@ class NotificationBar(urwid.WidgetWrap):
|
|||
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 @@ class NotificationBar(urwid.WidgetWrap):
|
|||
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 @@ class NotificationBar(urwid.WidgetWrap):
|
|||
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 @@ class MenuBox(urwid.WidgetWrap):
|
|||
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 @@ class MenuBox(urwid.WidgetWrap):
|
|||
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 @@ class Menu(urwid.FlowWidget):
|
|||
|
||||
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 @@ class Menu(urwid.FlowWidget):
|
|||
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 @@ class Menu(urwid.FlowWidget):
|
|||
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 @@ class ColumnsRoller(urwid.FlowWidget):
|
|||
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 @@ class ColumnsRoller(urwid.FlowWidget):
|
|||
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 @@ class ColumnsRoller(urwid.FlowWidget):
|
|||
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 @@ class TabsContainer(urwid.WidgetWrap):
|
|||
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 @@ class TabsContainer(urwid.WidgetWrap):
|
|||
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')
|
||||
|
||||
|
|
|
|||
|
|
@ -106,13 +106,13 @@ class FilesViewer(urwid.WidgetWrap):
|
|||
directories.sort()
|
||||
files.sort()
|
||||
if os.path.abspath(path)!='/' and os.path.abspath(path) != '//':
|
||||
previous_wid = custom_widgets.ClickableText('..',default_attr='directory')
|
||||
previous_wid = custom_widgets.ClickableText(('directory','..'))
|
||||
urwid.connect_signal(previous_wid,'click',self.onPreviousDir)
|
||||
self.files_list.append(previous_wid)
|
||||
for directory in directories:
|
||||
if directory.startswith('.') and not self.show_hidden:
|
||||
continue
|
||||
dir_wid = custom_widgets.ClickableText(directory,default_attr='directory')
|
||||
dir_wid = custom_widgets.ClickableText(('directory',directory))
|
||||
urwid.connect_signal(dir_wid,'click',self.onDirClick)
|
||||
self.files_list.append(dir_wid)
|
||||
self.files_list.append(urwid.AttrMap(urwid.Divider('-'),'separator'))
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ logging.basicConfig(level=logging.CRITICAL, #TODO: configure it to put messages
|
|||
|
||||
const_APP_NAME = "Primitivus"
|
||||
const_PALETTE = [('title', 'black', 'light gray', 'standout,underline'),
|
||||
('title_focus', 'white,bold', 'light gray', 'standout,underline'),
|
||||
('selected', 'default', 'dark red'),
|
||||
('selected_focus', 'default,bold', 'dark red'),
|
||||
('default', 'default', 'default'),
|
||||
|
|
@ -55,6 +56,7 @@ const_PALETTE = [('title', 'black', 'light gray', 'standout,underline'),
|
|||
('my_nick', 'dark red,bold', 'default'),
|
||||
('other_nick', 'dark cyan,bold', 'default'),
|
||||
('menubar', 'light gray,bold', 'dark red'),
|
||||
('menubar_focus', 'light gray,bold', 'dark green'),
|
||||
('selected_menu', 'light gray,bold', 'dark green'),
|
||||
('menuitem', 'light gray,bold', 'dark red'),
|
||||
('menuitem_focus', 'light gray,bold', 'dark green'),
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ class XMLUI(urwid.WidgetWrap):
|
|||
ctrl = self.ctrl_list[ctrl_name]
|
||||
if isinstance(ctrl['control'], custom_widgets.List):
|
||||
data.append((ctrl_name, ctrl['control'].getSelectedValue()))
|
||||
if isinstance(ctrl['control'], urwid.CheckBox):
|
||||
elif isinstance(ctrl['control'], urwid.CheckBox):
|
||||
data.append((ctrl_name, "true" if ctrl['control'].get_state() else "false"))
|
||||
else:
|
||||
data.append((ctrl_name, ctrl['control'].get_edit_text()))
|
||||
|
|
|
|||
|
|
@ -55,8 +55,7 @@ class DBusBridgeFrontend(BridgeFrontend):
|
|||
return self.db_req_iface.getProfileName(profile_key)
|
||||
|
||||
def getProfilesList(self):
|
||||
return self.db_req_iface.getProfilesList()
|
||||
|
||||
return self.db_req_iface.getProfilesList()
|
||||
def createProfile(self, name):
|
||||
return self.db_req_iface.createProfile(name)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue