# HG changeset patch # User Goffi # Date 1278328416 -28800 # Node ID ded2431cea5a573481097d653874ce5004d4d400 # Parent 76055a209ed905220d3d2e57fff0ed95bdb91b9f Primitivus: chat window / text sending. Primitivus has now the most basics features \o/ - core: new getVersion method - primitivus: new debug key (C-d), only work if SàT is in dev version (D in version) - quick_app: new post_init method, used for automatique task like auto-plug - primitivus: lists now use genericList (Box) or List (Flow) - primitivus: List now manage correctly its size - primitivus: new FocusFrame widget which manage focus changing with 'tab' - primitivus: advancedEdit now manage 'click' signal - primitivus: contactList now manager 'change' and 'click' signals - primitivus: Chat window now working diff -r 76055a209ed9 -r ded2431cea5a frontends/primitivus/chat.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/frontends/primitivus/chat.py Mon Jul 05 19:13:36 2010 +0800 @@ -0,0 +1,48 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +Primitivus: a SAT frontend +Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.org) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +import urwid +from quick_frontend.quick_contact_list import QuickContactList +from quick_frontend.quick_chat import QuickChat +from custom_widgets import Password,List,InputDialog,ConfirmDialog,Alert,SelectableText + + +class Chat(urwid.WidgetWrap, QuickChat): + + def __init__(self, target, host, type='one2one'): + QuickChat.__init__(self, target, host, type) + self.content = urwid.SimpleListWalker([]) + self.text_list = urwid.ListBox(self.content) + main_widget = urwid.LineBox( + urwid.Frame(self.text_list, urwid.AttrMap(urwid.Text(str(target),'center'),'title')) + ) + urwid.WidgetWrap.__init__(self, main_widget) + self.setType(type) + + def setType(self, type): + QuickChat.setType(self, type) + if type == 'one2one': + self.historyPrint(profile=self.host.profile) + + def printMessage(self, from_jid, msg, profile, timestamp=""): + self.content.append(SelectableText("[%s] " % from_jid + msg)) + self.text_list.set_focus(len(self.content)-1) + self.host.redraw() diff -r 76055a209ed9 -r ded2431cea5a frontends/primitivus/contact_list.py --- a/frontends/primitivus/contact_list.py Sat Jul 03 13:56:44 2010 +0800 +++ b/frontends/primitivus/contact_list.py Mon Jul 05 19:13:36 2010 +0800 @@ -25,12 +25,12 @@ class ContactList(urwid.WidgetWrap, QuickContactList): + signals = ['click','change'] - def __init__(self, host, CM): - QuickContactList.__init__(self, CM) + def __init__(self, host, CM, on_click=None, on_change=None, user_data=None): self.host = host - self.list_wid = List([], style=['single'], align='left') + self.list_wid = List([], style=['single','no_first_select'], align='left', on_click=self.__contactClicked, on_change=on_change) #we now build the widget body_content = urwid.SimpleListWalker([self.list_wid]) @@ -38,21 +38,35 @@ frame = urwid.Frame(frame_body,urwid.AttrMap(urwid.Text(_("Contacts"),align='center'),'title')) self.main_widget = urwid.LineBox(frame) urwid.WidgetWrap.__init__(self, self.main_widget) + if on_click: + urwid.connect_signal(self, 'click', on_click, user_data) + if on_change: + urwid.connect_signal(self, 'change', on_change, user_data) + QuickContactList.__init__(self, CM) + + def __contactClicked(self, list_wid): + self._emit('click') + + def get_contact(self): + """Return contact currently selected""" + return self.list_wid.getSelectedValue() def clear_contacts(self): """clear all the contact list""" self.list_wid.changeValues([]) def replace(self, jid, groups=None): - """add a contact to the list if doesn't exist, else update it""" - contacts = self.list_wid.getValues() - if jid.short not in contacts: - contacts.append(jid.short) - contacts.sort() - self.list_wid.changeValues(contacts) + """add a contact to the list if doesn't exist, else update it""" + contacts = self.list_wid.getAllValues() + if jid.short not in contacts: + contacts.append(jid.short) + contacts.sort() + self.list_wid.changeValues(contacts) + self._emit('change') def disconnect(self, jid): """mark a contact disconnected""" + #self.host.debug() self.remove(jid) def remove(self, jid): diff -r 76055a209ed9 -r ded2431cea5a frontends/primitivus/custom_widgets.py --- a/frontends/primitivus/custom_widgets.py Sat Jul 03 13:56:44 2010 +0800 +++ b/frontends/primitivus/custom_widgets.py Mon Jul 05 19:13:36 2010 +0800 @@ -38,6 +38,7 @@ class AdvancedEdit(urwid.Edit): """Edit box with some custom improvments""" + signals = urwid.Edit.signals + ['click'] def keypress(self, size, key): #TODO: insert mode is not managed yet @@ -53,6 +54,8 @@ pos = before.rstrip().rfind(" ")+1 self.set_edit_text(before[:pos] + self.edit_text[self.edit_pos:]) self.set_edit_pos(pos) + elif key == 'enter': + self._emit('click') return super(AdvancedEdit, self).keypress(size, key) @@ -73,9 +76,9 @@ @param invisible: don't emit change signal if True""" assert(type(selected)==bool) self.__selected=selected + self._invalidate() if not invisible: self._emit("change", self.__selected) - self._invalidate() def getState(self): return self.__selected @@ -101,15 +104,32 @@ attr+="_focus" return urwid.Text((attr,self.text), align=self.align) -class List(urwid.WidgetWrap): - signals = ['change'] +class GenericList(urwid.WidgetWrap): + signals = ['click','change'] - def __init__(self, options, style=[], align='left', on_state_change=None, user_data=None): + def __init__(self, options, style=[], align='left', on_click=None, on_change=None, user_data=None): + """ + 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 + - 'no_first_select' nothing selected when list is first displayed + - 'can_select_none' if we can select nothing + @param align: alignement of text inside the list + @param on_click: method called when click signal is emited + @param user_data: data sent to the callback for click signal + """ self.single = 'single' in style + self.no_first_select = 'no_first_select' in style + self.can_select_none = 'can_select_none' in style self.align = align + self.first_display = True - if on_state_change: - urwid.connect_signal(self, 'change', on_state_change, user_data) + if on_click: + urwid.connect_signal(self, 'click', on_click, user_data) + + if on_change: + urwid.connect_signal(self, 'change', on_change, user_data) self.content = urwid.SimpleListWalker([]) self.list_box = urwid.ListBox(self.content) @@ -118,14 +138,14 @@ def __onStateChange(self, widget, selected): if self.single: - if not selected: + if not selected and not self.can_select_none: #if in single mode, it's forbidden to unselect a value widget.setState(True, invisible=True) return - else: + if selected: self.unselectAll(invisible=True) widget.setState(True, invisible=True) - self._emit("change") + self._emit("click") def unselectAll(self, invisible=False): @@ -136,45 +156,109 @@ def deleteValue(self, value): """Delete the first value equal to the param given""" - try: - self.content.remove(value) - except ValueError: - raise ValuError("%s ==> %s" % (str(value),str(self.content))) + for widget in self.content: + if widget.getValue() == value: + self.content.remove(widget) + self._emit('change') + return + raise ValueError("%s ==> %s" % (str(value),str(self.content))) - def getValue(self): + def getSelectedValue(self): """Convenience method to get the value selected as a string in single mode, or None""" - values = self.getValues() + values = self.getSelectedValues() return values[0] if values else None - def getValues(self): + def getAllValues(self): + """Return values of all items""" + return [widget.getValue() for widget in self.content] + + def getSelectedValues(self): + """Return values of selected items""" result = [] for widget in self.content: if widget.getState(): result.append(widget.getValue()) return result + def getDisplayWidget(self): + return self.list_box + def changeValues(self, new_values): """Change all value in one shot""" - widgets = [SelectableText(option, self.align) for option in new_values] - for widget in widgets: + if not self.first_display: + old_selected = self.getSelectedValues() + widgets = [] + for option in new_values: + widget = SelectableText(option, self.align) + if not self.first_display and option in old_selected: + widget.setState(True) + widgets.append(widget) urwid.connect_signal(widget, 'change', self.__onStateChange) self.content[:] = widgets - if self.single and new_values: + if self.first_display and self.single and new_values and not self.no_first_select: self.content[0].setState(True) - display_widget = urwid.BoxAdapter(self.list_box, min(len(new_values),5) or 1) + display_widget = self.getDisplayWidget() self._set_w(display_widget) - + self._emit('change') + self.first_display = False + def selectValue(self, value): self.unselectAll() idx = 0 for widget in self.content: - if widget.getValue() == value: + if widget.getSelectedValue() == value: widget.setState(True) self.list_box.set_focus(idx) return idx+=1 -class genericDialog(urwid.WidgetWrap): +class List(urwid.FlowWidget): + """FlowWidget list, same arguments as GenericList, with an additional one 'max_height'""" + signals = ['click','change'] + + def __init__(self, options, style=[], max_height=5, align='left', on_click=None, on_change=None, user_data=None): + self.genericList = GenericList(options, style, align, on_click, on_change, user_data) + self.max_height = max_height + + def selectable(self): + return True + + def keypress(self, size, key): + return self.displayWidget(size,True).keypress(size, key) + + def unselectAll(self, invisible=False): + return self.genericList.unselectAll(invisible) + + def deleteValue(self, value): + return self.genericList.deleteValue(value) + + def getSelectedValue(self): + return self.genericList.getSelectedValue() + + def getAllValues(self): + return self.genericList.getAllValues() + + def getSelectedValues(self): + return self.genericList.getSelectedValues() + + def changeValues(self, new_values): + return self.genericList.changeValues(new_values) + + def selectValue(self, value): + return self.genericList.selectValue(value) + + def render(self, size, focus=False): + return self.displayWidget(size, focus).render(size, focus) + + def rows(self, size, focus=False): + return self.displayWidget(size, focus).rows(size, focus) + + def displayWidget(self, size, focus): + list_size = sum([wid.rows(size, focus) for wid in self.genericList.content]) + height = min(list_size,self.max_height) + return urwid.BoxAdapter(self.genericList, height) + +class GenericDialog(urwid.WidgetWrap): def __init__(self, widgets_lst, title, style=[], **kwargs): frame_header = urwid.AttrMap(urwid.Text(title,'center'),'title') @@ -200,19 +284,35 @@ -class InputDialog(genericDialog): +class InputDialog(GenericDialog): def __init__(self, title, instrucions, style=['OK/CANCEL'], **kwargs): instr_wid = urwid.Text(instrucions+':') edit_box = urwid.Edit() - genericDialog.__init__(self, [instr_wid,edit_box], title, style, ok_value=edit_box, **kwargs) + GenericDialog.__init__(self, [instr_wid,edit_box], title, style, ok_value=edit_box, **kwargs) -class ConfirmDialog(genericDialog): +class ConfirmDialog(GenericDialog): def __init__(self, title, style=['YES/NO'], **kwargs): - genericDialog.__init__(self, [], title, style, yes_value=None, **kwargs) + GenericDialog.__init__(self, [], title, style, yes_value=None, **kwargs) -class Alert(genericDialog): +class Alert(GenericDialog): def __init__(self, title, message, style=['OK'], **kwargs): - genericDialog.__init__(self, [urwid.Text(message, 'center')], title, style, ok_value=None, **kwargs) + GenericDialog.__init__(self, [urwid.Text(message, 'center')], title, style, ok_value=None, **kwargs) + +class FocusFrame(urwid.Frame): + """Frame which manage "tab" key""" + + def keypress(self, size, key): + if key == 'tab': + focus_list = ('header','body','footer') + focus_idx = focus_list.index(self.focus_part) + for i in range(2): + focus_idx = (focus_idx + 1) % len(focus_list) + focus_name = focus_list[focus_idx] + widget = getattr(self,'_'+focus_name) + if widget!=None and widget.selectable(): + self.set_focus(focus_name) + + return urwid.Frame.keypress(self, size, key) diff -r 76055a209ed9 -r ded2431cea5a frontends/primitivus/primitivus --- a/frontends/primitivus/primitivus Sat Jul 03 13:56:44 2010 +0800 +++ b/frontends/primitivus/primitivus Mon Jul 05 19:13:36 2010 +0800 @@ -27,7 +27,8 @@ import urwid from profile_manager import ProfileManager from contact_list import ContactList -from custom_widgets import AdvancedEdit +from chat import Chat +from custom_widgets import AdvancedEdit,FocusFrame import pdb """from window import Window from editbox import EditBox @@ -60,6 +61,15 @@ ('default_focus', 'default,bold', 'default'), ] +class ChatList(QuickChatList): + """This class manage the list of chat windows""" + + def __init__(self, host): + QuickChatList.__init__(self, host) + + def createChat(self, target): + return Chat(target, self.host) + class PrimitivusApp(QuickApp): def __init__(self): @@ -70,17 +80,38 @@ self.main_widget = ProfileManager(self) self.loop = urwid.MainLoop(self.main_widget, const_PALETTE, event_loop=urwid.GLibEventLoop(), unhandled_input=self.key_handler) + ##misc setup## + self.chat_wins=ChatList(self) + + def debug(self): + """convenience method to reset screen and launch pdb""" + import os + os.system('reset') + print 'Entered debug mode' + pdb.set_trace() + + def redraw(self): + """redraw the screen""" + self.loop.draw_screen() + def start(self): + self.i = 0 + self.loop.set_alarm_in(0,lambda a,b: self.postInit()) self.loop.run() def key_handler(self, input): if input in ('q', 'Q') or input == 'ctrl x': raise urwid.ExitMainLoop() - + elif input == 'ctrl d' and 'D' in self.bridge.getVersion(): #Debug only for dev versions + self.debug() + def __buildMainWidget(self): - self.contactList = ContactList(self, self.CM) - self.center_part = urwid.Columns([self.contactList]) - self.main_widget = urwid.Frame(self.center_part, footer=AdvancedEdit('> '), focus_part='footer') + self.contactList = ContactList(self, self.CM, on_click = self.contactSelected, on_change=lambda w: self.redraw()) + #self.center_part = urwid.Columns([('weight',2,self.contactList),('weight',8,Chat('',self))]) + self.center_part = urwid.Columns([('weight',2,self.contactList), ('weight',8,urwid.Filler(urwid.Text('')))]) + editBar = AdvancedEdit('> ') + urwid.connect_signal(editBar,'click',self.onTextEntered) + self.main_widget = FocusFrame(self.center_part, footer=editBar, focus_part='footer') return self.main_widget def plug_profile(self, profile_key='@DEFAULT@'): @@ -94,6 +125,20 @@ display_widget = urwid.Overlay(pop_up_widget, self.main_widget, 'center', ('relative', 40), 'middle', ('relative', 40)) self.loop.widget = display_widget + def contactSelected(self, contact_list): + contact = contact_list.get_contact() + if contact: + assert(len(self.center_part.widget_list)==2) + #self.center_part.widget_list.append(self.chat_wins[contact]) + #self.center_part.column_types.append(('weight',8)) + self.center_part.widget_list[1] = self.chat_wins[contact] + + def onTextEntered(self, editBar): + contact = self.contactList.get_contact() + if contact: + self.bridge.sendMessage(contact, editBar.get_edit_text(), profile_key=self.profile) + editBar.set_edit_text('') + sat = PrimitivusApp() sat.start() diff -r 76055a209ed9 -r ded2431cea5a frontends/primitivus/profile_manager.py --- a/frontends/primitivus/profile_manager.py Sat Jul 03 13:56:44 2010 +0800 +++ b/frontends/primitivus/profile_manager.py Mon Jul 05 19:13:36 2010 +0800 @@ -20,7 +20,7 @@ """ import urwid -from custom_widgets import Password,List,InputDialog,ConfirmDialog,Alert +from custom_widgets import Password,List,InputDialog,ConfirmDialog,Alert,FocusFrame class ProfileManager(urwid.WidgetWrap): @@ -35,7 +35,7 @@ self.login_wid = urwid.Edit(_('Login:'),align='center') self.pass_wid = Password(_('Password:'),align='center') - self.list_profile = List(profiles, style=['single'], align='center', on_state_change=self.onProfileChange) + self.list_profile = List(profiles, style=['single'], align='center', on_click=self.onProfileChange) #toto = urwid.Padding(urwid.Text('toto'), align='center') @@ -74,7 +74,7 @@ self.host.removePopUp() def deleteProfile(self, button): - profile_name = self.list_profile.getValue() + profile_name = self.list_profile.getSelectedValue() if profile_name: self.host.bridge.deleteProfile(profile_name) self.__refillProfiles() @@ -86,11 +86,11 @@ self.host.showPopUp(pop_up_widget) def onDeleteProfile(self, e): - pop_up_widget = ConfirmDialog(_("Are you sure you want to delete the profile %s ?") % self.list_profile.getValue(), no_cb=self.cancelDialog, yes_cb=self.deleteProfile) + pop_up_widget = ConfirmDialog(_("Are you sure you want to delete the profile %s ?") % self.list_profile.getSelectedValue(), no_cb=self.cancelDialog, yes_cb=self.deleteProfile) self.host.showPopUp(pop_up_widget) def onProfileChange(self, list_wid): - profile_name = list_wid.getValue() + profile_name = list_wid.getSelectedValue() if profile_name: jabberID = self.host.bridge.getParamA("JabberID", "Connection", profile_key=profile_name) password = self.host.bridge.getParamA("Password", "Connection", profile_key=profile_name) @@ -98,7 +98,7 @@ self.pass_wid.set_edit_text(password) def onConnectProfile(self, button): - profile_name = self.list_profile.getValue() + profile_name = self.list_profile.getSelectedValue() if not profile_name: pop_up_widget = Alert(_('No profile selected'), _('You need to create and select a profile before connecting'), ok_cb=self.cancelDialog) self.host.showPopUp(pop_up_widget) diff -r 76055a209ed9 -r ded2431cea5a frontends/quick_frontend/quick_app.py --- a/frontends/quick_frontend/quick_app.py Sat Jul 03 13:56:44 2010 +0800 +++ b/frontends/quick_frontend/quick_app.py Mon Jul 05 19:13:36 2010 +0800 @@ -71,6 +71,14 @@ """Tell if the profile is currently followed by the application""" return profile in self.profiles.keys() + def postInit(self): + """Must be called after __init__, do all automatic task (auto plug profile)""" + if self.options.profile: + if not self.bridge.getProfileName(self.options.profile): + error(_("Trying to plug an unknown profile (%s)" % self.options.profile)) + else: + self.plug_profile(self.options.profile) + def check_options(self): """Check command line options""" usage=_(""" diff -r 76055a209ed9 -r ded2431cea5a frontends/quick_frontend/quick_contact_management.py --- a/frontends/quick_frontend/quick_contact_management.py Sat Jul 03 13:56:44 2010 +0800 +++ b/frontends/quick_frontend/quick_contact_management.py Mon Jul 05 19:13:36 2010 +0800 @@ -80,7 +80,8 @@ #no more resource available: the contact seems really disconnected del self.__contactlist[entity.short] except KeyError: - pass + error(_('INTERNAL ERROR: Key error')) + raise def update(self, entity, key, value): """Update attribute of contact diff -r 76055a209ed9 -r ded2431cea5a frontends/sat_bridge_frontend/DBus.py --- a/frontends/sat_bridge_frontend/DBus.py Sat Jul 03 13:56:44 2010 +0800 +++ b/frontends/sat_bridge_frontend/DBus.py Mon Jul 05 19:13:36 2010 +0800 @@ -40,6 +40,9 @@ elif iface == "request": self.db_req_iface.connect_to_signal(functionName, handler) + def getVersion(self): + return self.db_req_iface.getVersion() + def getProfileName(self, profile_key='@DEFAULT@'): return self.db_req_iface.getProfileName(profile_key) diff -r 76055a209ed9 -r ded2431cea5a frontends/wix/main_window.py --- a/frontends/wix/main_window.py Sat Jul 03 13:56:44 2010 +0800 +++ b/frontends/wix/main_window.py Mon Jul 05 19:13:36 2010 +0800 @@ -99,8 +99,8 @@ #events self.Bind(wx.EVT_CLOSE, self.onClose, self) + QuickApp.__init__(self) - #self.plug_profile() #gof: #menus self.createMenus() @@ -111,10 +111,9 @@ self.profile_pan = ProfileManager(self) #self.profile_pan.Hide() #gof: self.sizer.Add(self.profile_pan, 1, flag=wx.EXPAND) - if self.options.profile: #TODO: move this to quick_app - self.plug_profile(self.options.profile) - - + + self.postInit() + self.Show() def plug_profile(self, profile_key='@DEFAULT@'): diff -r 76055a209ed9 -r ded2431cea5a sat.tac --- a/sat.tac Sat Jul 03 13:56:44 2010 +0800 +++ b/sat.tac Mon Jul 05 19:13:36 2010 +0800 @@ -332,6 +332,7 @@ self.server_features=[] #XXX: temp dic, need to be transfered into self.memory in the future self.bridge=DBusBridge() + self.bridge.register("getVersion", lambda: self.get_const('client_version')) self.bridge.register("getProfileName", self.memory.getProfileName) self.bridge.register("getProfilesList", self.memory.getProfilesList) self.bridge.register("createProfile", self.memory.createProfile) diff -r 76055a209ed9 -r ded2431cea5a sat_bridge/DBus.py --- a/sat_bridge/DBus.py Sat Jul 03 13:56:44 2010 +0800 +++ b/sat_bridge/DBus.py Mon Jul 05 19:13:36 2010 +0800 @@ -105,6 +105,12 @@ ### methods ### + + @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX, + in_signature='', out_signature='s') + def getVersion(self): + return self.cb["getVersion"]() + @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX, in_signature='s', out_signature='s') def getProfileName(self, profile_key):