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
This commit is contained in:
Goffi 2010-07-05 19:13:36 +08:00
commit a21a658c33
11 changed files with 281 additions and 56 deletions

View file

@ -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 <http://www.gnu.org/licenses/>.
"""
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()

View file

@ -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):

View file

@ -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)

View file

@ -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()

View file

@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
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)

View file

@ -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=_("""

View file

@ -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

View file

@ -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)

View file

@ -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@'):

View file

@ -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)

View file

@ -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):