diff --git a/frontends/primitivus/chat.py b/frontends/primitivus/chat.py
new file mode 100644
index 00000000..e6339986
--- /dev/null
+++ b/frontends/primitivus/chat.py
@@ -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 --git a/frontends/primitivus/contact_list.py b/frontends/primitivus/contact_list.py
index 70ceaa49..510f43a4 100644
--- a/frontends/primitivus/contact_list.py
+++ b/frontends/primitivus/contact_list.py
@@ -25,12 +25,12 @@ from custom_widgets import Password,List,InputDialog,ConfirmDialog,Alert
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 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
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 --git a/frontends/primitivus/custom_widgets.py b/frontends/primitivus/custom_widgets.py
index 7ceebbea..d9b8e2e3 100644
--- a/frontends/primitivus/custom_widgets.py
+++ b/frontends/primitivus/custom_widgets.py
@@ -38,6 +38,7 @@ class Password(urwid.Edit):
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 @@ class AdvancedEdit(urwid.Edit):
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 @@ class SelectableText(urwid.FlowWidget):
@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 @@ class SelectableText(urwid.FlowWidget):
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 @@ class List(urwid.WidgetWrap):
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 @@ class List(urwid.WidgetWrap):
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 genericDialog(urwid.WidgetWrap):
-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 --git a/frontends/primitivus/primitivus b/frontends/primitivus/primitivus
index dfa579e7..9b07ec57 100755
--- a/frontends/primitivus/primitivus
+++ b/frontends/primitivus/primitivus
@@ -27,7 +27,8 @@ from quick_frontend.quick_contact_management import QuickContactManagement
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 @@ const_PALETTE = [('title', 'black', 'light gray', 'standout,underline'),
('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 @@ class PrimitivusApp(QuickApp):
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 @@ class PrimitivusApp(QuickApp):
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 --git a/frontends/primitivus/profile_manager.py b/frontends/primitivus/profile_manager.py
index 4685694f..e6f12d7a 100644
--- a/frontends/primitivus/profile_manager.py
+++ b/frontends/primitivus/profile_manager.py
@@ -20,7 +20,7 @@ along with this program. If not, see .
"""
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 @@ class ProfileManager(urwid.WidgetWrap):
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 @@ class ProfileManager(urwid.WidgetWrap):
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 @@ class ProfileManager(urwid.WidgetWrap):
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 @@ class ProfileManager(urwid.WidgetWrap):
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 --git a/frontends/quick_frontend/quick_app.py b/frontends/quick_frontend/quick_app.py
index 7183ffba..55e0ca02 100644
--- a/frontends/quick_frontend/quick_app.py
+++ b/frontends/quick_frontend/quick_app.py
@@ -71,6 +71,14 @@ class QuickApp():
"""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 --git a/frontends/quick_frontend/quick_contact_management.py b/frontends/quick_frontend/quick_contact_management.py
index 273d7679..db91d034 100644
--- a/frontends/quick_frontend/quick_contact_management.py
+++ b/frontends/quick_frontend/quick_contact_management.py
@@ -80,7 +80,8 @@ class QuickContactManagement():
#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 --git a/frontends/sat_bridge_frontend/DBus.py b/frontends/sat_bridge_frontend/DBus.py
index eef10e41..fb901485 100755
--- a/frontends/sat_bridge_frontend/DBus.py
+++ b/frontends/sat_bridge_frontend/DBus.py
@@ -40,6 +40,9 @@ class DBusBridgeFrontend(BridgeFrontend):
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 --git a/frontends/wix/main_window.py b/frontends/wix/main_window.py
index 24b1ed97..30384d84 100644
--- a/frontends/wix/main_window.py
+++ b/frontends/wix/main_window.py
@@ -99,8 +99,8 @@ class MainWindow(wx.Frame, QuickApp):
#events
self.Bind(wx.EVT_CLOSE, self.onClose, self)
+
QuickApp.__init__(self)
- #self.plug_profile() #gof:
#menus
self.createMenus()
@@ -111,10 +111,9 @@ class MainWindow(wx.Frame, QuickApp):
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 --git a/sat.tac b/sat.tac
index 7b75f3a2..6aa1a9da 100644
--- a/sat.tac
+++ b/sat.tac
@@ -332,6 +332,7 @@ class SAT(service.Service):
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 --git a/sat_bridge/DBus.py b/sat_bridge/DBus.py
index c5f1361f..d84d1aab 100755
--- a/sat_bridge/DBus.py
+++ b/sat_bridge/DBus.py
@@ -105,6 +105,12 @@ class DbusObject(dbus.service.Object):
### 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):