changeset 112:f551e44adb25

Primitivus first draft - Sortilège is recoded using urwid, and renamed in Primitivus as it is no more based on curses - soritlege code moved to sortilege_old, and deprecated - Primitivus first draft, begining of ProfileManager widget
author Goffi <goffi@goffi.org>
date Wed, 30 Jun 2010 14:24:24 +0800
parents 6c927140ba82
children e5ca22113280
files frontends/primitivus/primitivus frontends/primitivus/profile_manager.py frontends/sortilege/__init__.py frontends/sortilege/boxsizer.py frontends/sortilege/chat.py frontends/sortilege/editbox.py frontends/sortilege/sortilege frontends/sortilege/statusbar.py frontends/sortilege/window.py frontends/sortilege_old/__init__.py frontends/sortilege_old/boxsizer.py frontends/sortilege_old/chat.py frontends/sortilege_old/editbox.py frontends/sortilege_old/sortilege frontends/sortilege_old/statusbar.py frontends/sortilege_old/window.py
diffstat 14 files changed, 1148 insertions(+), 1036 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/primitivus/primitivus	Wed Jun 30 14:24:24 2010 +0800
@@ -0,0 +1,78 @@
+#!/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/>.
+"""
+
+
+from quick_frontend.quick_app import QuickApp
+from quick_frontend.quick_chat_list import QuickChatList
+from quick_frontend.quick_contact_list import QuickContactList
+from quick_frontend.quick_contact_management import QuickContactManagement
+import urwid
+from profile_manager import ProfileManager
+import pdb
+"""from window import Window
+from editbox import EditBox
+from statusbar import StatusBar
+from chat import Chat
+from tools.jid  import JID"""
+import logging
+from logging import debug, info, error
+#import locale
+import sys, os
+#from curses import ascii
+#import locale
+#from signal import signal, SIGWINCH 
+#import fcntl
+#import struct
+#import termios
+#from boxsizer import BoxSizer
+
+
+### logging configuration FIXME: put this elsewhere ###
+logging.basicConfig(level=logging.CRITICAL,  #TODO: configure it to put messages in a log file
+                    format='%(message)s')
+###
+
+const_APP_NAME      = "Primitivus"
+const_PALETTE = [('title', 'black', 'light gray', 'standout,underline'),]
+            
+class PrimitivusApp(QuickApp):
+    
+    def __init__(self):
+        QuickApp.__init__(self)  #XXX: yes it's an unusual place for the constructor of a parent class, but the init order is important
+        
+        ## main loop setup ##
+        self.main_widget = self.__buildMainWidget()
+        self.loop = urwid.MainLoop(self.main_widget, const_PALETTE, event_loop=urwid.GLibEventLoop(), unhandled_input=self.key_handler)
+
+    def start(self):
+        self.loop.run()
+
+    def key_handler(self, input):
+        if input in ('q', 'Q') or input == 'ctrl x':
+            raise urwid.ExitMainLoop()
+
+    def __buildMainWidget(self):
+        main_widget = ProfileManager(self)
+        return main_widget
+
+
+sat = PrimitivusApp()
+sat.start()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/primitivus/profile_manager.py	Wed Jun 30 14:24:24 2010 +0800
@@ -0,0 +1,34 @@
+#!/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
+
+
+class ProfileManager(urwid.WidgetWrap):
+
+    def __init__(self, host):
+        self.host = host
+        profiles = self.host.bridge.getProfilesList()
+        widgets = [urwid.Text(str(profiles))]
+        content = urwid.SimpleListWalker(widgets)
+        display_widget = urwid.Frame(urwid.ListBox(content),urwid.AttrMap(urwid.Text("Profile Manager",align='center'),'title'))
+        urwid.WidgetWrap.__init__(self, display_widget)
+
--- a/frontends/sortilege/boxsizer.py	Tue Jun 29 16:07:51 2010 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-"""
-sortilege: 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/>.
-"""
-
-
-from window import Window
-import os,pdb
-
-class BoxSizer:
-    """This class manage the position of the window like boxes."""
-
-
-
-    def __init__(self, parent):
-        self.__parent=parent
-        self.boxes=[]
-
-
-
-    def appendRow(self, win):
-        self.boxes.append([win])
-
-    def appendColum(self, index, win):
-        if len(self.boxes)<=index:
-            #TODO: throw an error here
-            return
-        self.boxes[index].append(win)
-
-    def update(self):
-        """Resize boxes"""
-        oriY=0
-        visible_row=[]
-        for row in self.boxes:
-            current_row=[]
-            oriX=0
-            for win in row:
-                x=win.getOriX()
-                y=win.getOriY()
-                w=win.getOriWidth()
-                h=win.getOriHeight()
-                if win.isHidden():
-                    if len(current_row)>1 and win is row[-1]:
-                        #if the last win is hidden, we expand previous visible one
-                        current_row[-1][2] = current_row[-1][2] + (win.getX() - oriX)+win.getWidth()
-                else:
-                    current_row.append([win, h+y-oriY, w+x-oriX, oriY, oriX])
-                    oriX=oriX+w
-                    
-            if oriX!=0:
-                oriY=oriY+h
-                visible_row.append(current_row)
-            elif visible_row:
-                #if all the row is empty, we take the space
-                for box in visible_row[-1]:
-                    box[1]=box[1]+h
-                oriY=oriY+h  #this only happen if it's not the first visible row
-
-        for row in visible_row:
-            for win in row:
-                win[0].resize(win[1], win[2], win[3], win[4])
--- a/frontends/sortilege/chat.py	Tue Jun 29 16:07:51 2010 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-"""
-sortilege: 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 os.path
-import pdb
-from logging import debug, info, error
-from window import Window
-import os
-from time import time, localtime, strftime
-import curses
-import curses.ascii as ascii
-from tools.jid  import JID
-from quick_frontend.quick_chat import QuickChat
-
-
-def C(k):
-    """return the value of Ctrl+key"""
-    return ord(ascii.ctrl(k))
-
-class Chat(Window, QuickChat):
-
-    def __init__(self, to_id, host):
-        QuickChat.__init__(self, to_id, host) 
-        self.__parent=host.stdscr
-        self.to_id=JID(to_id)
-        self.content=[]
-        self.__scollIdx=0
-        Window.__init__(self, self.__parent, self.__parent.getmaxyx()[0]-2, self.__parent.getmaxyx()[1]-30, 0,30, code=host.code)
-
-        #history
-        self.historyPrint(50, True, profile=self.host.profile)
-
-        self.hide()
-
-    def resize(self, height, width, y, x):
-        Window.resize(self, height, width, y, x)
-        self.update()
-
-    def resizeAdapt(self):
-        """Adapt window size to self.__parent size.
-        Must be called when self.__parent is resized."""
-        self.resize(self.__parent.getmaxyx()[0]-2, self.__parent.getmaxyx()[1]-30, 0,30)
-        self.update()
-
-    def __getHeader(self, line):
-        """Return the header of a line (eg: "[12:34] <toto> ")."""
-        header=''
-        if self.host.chatParams["timestamp"]:
-            header = header + '[%s] ' % strftime("%H:%M", localtime(float(line[0])))
-        if self.host.chatParams['short_nick']:
-            header = header + ('> ' if  line[1]==self.host.profiles[self.host.profile]['whoami'] else '** ')
-        else:
-            header = header + '<%s> ' % line[1]
-        return header
-
-    def update(self):
-        if self.isHidden():
-            return
-        Window.update(self)
-        content=[] #what is really printed
-        irange=range(len(self.content))
-        irange.reverse() #we print the text upward
-        for idx in irange:
-            header=self.__getHeader(self.content[idx])
-            msg=self.content[idx][2]
-            part=0  #part of the text
-            if JID(self.content[idx][1]).short==self.host.profiles[self.host.profile]['whoami'].short:
-                att_header=curses.color_pair(1)
-            else:
-                att_header=curses.color_pair(2)
-            
-            while (msg):
-                if part==0:
-                    hd=header
-                    att=att_header
-                    max=self.rWidth-len(header)
-                else:
-                    hd=""
-                    att=0
-                    max=self.rWidth
-
-                LF = msg.find('\n')   #we look for Line Feed
-                if LF != -1 and LF < max:
-                    max = LF
-                    next = max + 1  #we skip the LF
-                else:
-                    next = max
-                    
-                content.insert(part,[att,hd, msg[:max]])
-                msg=msg[next:]   #we erase treated part
-                part=part+1
-
-            if len(content)>=self.rHeight+self.__scollIdx:
-                #all the screen is filled, we can continue
-                break
-
-        if self.__scollIdx>0 and len(content)<self.rHeight+self.__scollIdx:
-            self.__scollIdx=abs(len(content)-self.rHeight)  #all the log fit on the screen, we must stop here
-            
-        idx=0
-        for line in content[-self.rHeight-self.__scollIdx : -self.__scollIdx or None]:
-            self.addYXStr(idx, 0, line[1], line[0])
-            self.addYXStr(idx, len(line[1]), line[2])
-            idx=idx+1
-
-        self.noutrefresh()
-    
-    def scrollIdxUp(self):
-        """increment scroll index"""
-        self.__scollIdx = self.__scollIdx + 1
-        self.update()
-    
-    def scrollIdxDown(self):
-        """decrement scroll index"""
-        if self.__scollIdx > 0:
-            self.__scollIdx = self.__scollIdx - 1
-        self.update()
-
-    def printMessage(self, jid, msg, profile, timestamp=""):
-        if timestamp=="":
-            current_time=time()
-            timestamp=str(current_time)
-            if self.last_history and current_time - float(self.last_history) < 5: #FIXME: Q&D fix to avoid double print on new chat window
-                return
-        self.content.append([timestamp,jid.short,msg])
-        self.update()
-    
-    def handleKey(self, k):
-        if k == C('p'):
-            self.scrollIdxUp()
-        elif k == C('n'):
-            self.scrollIdxDown()
-
--- a/frontends/sortilege/editbox.py	Tue Jun 29 16:07:51 2010 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,176 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-"""
-sortilege: 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 curses
-from curses import ascii
-from window import Window
-
-def C(k):
-    """return the value of Ctrl+key"""
-    return ord(ascii.ctrl(k))
-
-def A(k):
-    """return the value of Alt+key"""
-    return ord(ascii.alt(k))
-
-class EditBox(Window):
-    """This class manage the edition of text"""
-
-    def __init__(self, parent, header, code="utf-8"):
-        self.__header=header
-        self.__text = unicode()
-        self.__curs_pos=0
-        self.__buffer=str()
-        self.__replace_mode=False
-        self.__parent=parent
-        self.__code=code
-        
-        Window.__init__(self, self.__parent, 1, self.__parent.getmaxyx()[1], self.__parent.getmaxyx()[0]-1,0, code=code)
-        self.update()
-
-    def registerEnterCB(self, CB):
-        self.__enterCB=CB
-
-    def resizeAdapt(self):
-        """Adapt window size to self.__parent size.
-        Must be called when self.__parent is resized."""
-        self.resize(1, self.__parent.getmaxyx()[1], self.__parent.getmaxyx()[0]-1,0)
-        self.update()
-
-    def __getTextToPrint(self):
-        """return the text printed on the edit line"""
-        width = self.rWidth - len(self.__header) -1
-        if self.__curs_pos<width:
-            begin = 0 
-            end = width
-        else:
-            begin = self.__curs_pos-width
-            end = self.__curs_pos
-        return self.__header+self.__text[begin:end]
-
-    def update(self):
-        Window.update(self)
-        text = self.__getTextToPrint()
-        self.addYXStr(0, 0, text, limit=self.rWidth)
-        
-        self.noutrefresh()
-
-    def __dec_cur(self):
-        """move cursor on the left"""
-        if self.__curs_pos>0:
-            self.__curs_pos = self.__curs_pos - 1
-
-    def __inc_cur(self):
-        """move cursor on the right"""
-        if self.__curs_pos<len(self.__text):
-            self.__curs_pos = self.__curs_pos + 1
-    
-    def move_cur(self, x):
-        pos = x+len(self.__header)
-        if pos>=self.rWidth:
-            pos=self.rWidth-1
-        self.move(0, pos)
-
-    def clear_text(self):
-        """Clear the text of the edit box"""
-        self.__text=""
-        self.__curs_pos=0
-        
-    def replace_cur(self):
-        """must be called earch time the cursor is moved"""
-        self.move_cur(self.__curs_pos)
-        self.noutrefresh()
-
-    def activate(self, state=True):
-        cursor_mode = 1 if state else 0
-        curses.curs_set(cursor_mode)
-        Window.activate(self,state)
-
-    def handleKey(self, k):
-        if ascii.isgraph(k) or ascii.isblank(k):
-            pacman = 0 if not self.__replace_mode else 1
-            self.__text = self.__text[:self.__curs_pos] + chr(k) + self.__text[self.__curs_pos + pacman:]
-            self.__curs_pos = self.__curs_pos + 1
-
-        elif k==ascii.NL:
-            try:
-                self.__enterCB(self.__text)
-            except NameError:
-                pass # TODO: thrown an error here
-            self.clear_text()
-
-        elif k==curses.KEY_BACKSPACE:
-            self.__text = self.__text[:self.__curs_pos-1]+self.__text[self.__curs_pos:]
-            self.__dec_cur()
-
-        elif k==curses.KEY_DC:
-            self.__text = self.__text[:self.__curs_pos]+self.__text[self.__curs_pos+1:]
-
-        elif k==curses.KEY_IC:
-            self.__replace_mode = not self.__replace_mode
-
-        elif k==curses.KEY_LEFT:
-            self.__dec_cur()
-
-        elif k==curses.KEY_RIGHT:
-            self.__inc_cur()
-
-        elif k==curses.KEY_HOME or k==C('a'):
-            self.__curs_pos=0
-        
-        elif k==curses.KEY_END or k==C('e'):
-            self.__curs_pos=len(self.__text)
-
-        elif k==C('k'):
-            self.__text = self.__text[:self.__curs_pos]
-
-        elif k==C('w'):
-            before = self.__text[:self.__curs_pos]
-            pos = before.rstrip().rfind(" ")+1
-            self.__text = before[:pos] + self.__text[self.__curs_pos:]
-            self.__curs_pos = pos
-
-        elif k>255:
-            self.__buffer=""
-
-        else:  ## FIXME: dangerous code, must be checked ! (specialy buffer overflow) ##
-            #We now manage unicode
-            self.__buffer = self.__buffer+chr(k)
-            decoded=unicode()
-            if len(self.__buffer)>4:
-                self.__buffer=""
-                return
-            try:
-                decoded = self.__buffer.decode(self.__code)
-            except UnicodeDecodeError, e:
-                if e.reason!="unexpected end of data":
-                    self.__buffer=""
-                return
-            if len(self.__buffer)==1:  ## FIXME: awful ! only for test !
-                self.__buffer=""
-                return
-            self.__text = self.__text + decoded
-            self.__curs_pos = self.__curs_pos + 1
-            self.__buffer=""
-
-        self.update()
-
--- a/frontends/sortilege/sortilege	Tue Jun 29 16:07:51 2010 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,400 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-"""
-sortilege: 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/>.
-"""
-
-
-from quick_frontend.quick_app import QuickApp
-from quick_frontend.quick_chat_list import QuickChatList
-from quick_frontend.quick_contact_list import QuickContactList
-from quick_frontend.quick_contact_management import QuickContactManagement
-import curses
-import pdb
-from window import Window
-from editbox import EditBox
-from statusbar import StatusBar
-from chat import Chat
-from tools.jid  import JID
-import logging
-from logging import debug, info, error
-import locale
-import sys, os
-import gobject
-import time
-from curses import ascii
-import locale
-from signal import signal, SIGWINCH 
-import fcntl
-import struct
-import termios
-from boxsizer import BoxSizer
-
-
-### logging configuration FIXME: put this elsewhere ###
-logging.basicConfig(level=logging.CRITICAL,  #TODO: configure it top put messages in a log file
-                    format='%(message)s')
-###
-
-const_APP_NAME      = "Sortilège"
-const_CONTACT_WIDTH = 30
-
-def ttysize():
-    """This function return term size.
-    Comes from Donn Cave from python list mailing list"""
-    buf = 'abcdefgh'
-    buf = fcntl.ioctl(0, termios.TIOCGWINSZ, buf)
-    row, col, rpx, cpx = struct.unpack('hhhh', buf)
-    return row, col
-
-def C(k):
-    """return the value of Ctrl+key"""
-    return ord(ascii.ctrl(k))
-
-class ChatList(QuickChatList):
-    """This class manage the list of chat windows"""
-    
-    def __init__(self, host):
-        QuickChatList.__init__(self, host)
-        self.sizer=host.sizer
-
-    def createChat(self, name):
-        chat = Chat(name, self.host)
-        self.sizer.appendColum(0,chat)
-        self.sizer.update()
-        return chat 
-        
-class ContactList(Window, QuickContactList):
-    
-    def __init__(self, host, CM):
-        QuickContactList.__init__(self, CM)
-        self.host = host
-        self.jid_list = []
-        self.__index=0  #indicate which contact is selected (ie: where we are)
-        Window.__init__(self, stdscr, stdscr.getmaxyx()[0]-2,const_CONTACT_WIDTH,0,0, True, _("Contact List"), code=code)
-
-    def resize(self, height, width, y, x):
-        Window.resize(self, height, width, y, x)
-        self.update()
-
-    def resizeAdapt(self):
-        """Adapt window size to stdscr size.
-        Must be called when stdscr is resized."""
-        self.resize(stdscr.getmaxyx()[0]-2,const_CONTACT_WIDTH,0,0)
-        self.update()
-        
-    def registerEnterCB(self, CB):
-        self.__enterCB=CB
-
-    def clear_contacts(self):
-        """clear all the contact list"""
-        del self.jid_list[:]
-        self.__index = 0
-        self.update()  #FIXME: window is not updated correctly (contacts are still here until C-L)
-
-    def replace(self, jid, groups=None):
-        """add a contact to the list"""
-        name = self.CM.getAttr(jid,'name')
-        self.jid_list.append(jid.short)
-        self.update()
-
-    def indexUp(self):
-        """increment select contact index"""
-        if self.__index < len(self.jid_list)-1:  #we dont want to select a missing contact
-            self.__index = self.__index + 1
-        self.update()
-    
-    def indexDown(self):
-        """decrement select contact index"""
-        if self.__index > 0:
-            self.__index = self.__index - 1
-        self.update()
-
-    def disconnect(self, jid):
-        """for now, we just remove the contact"""
-        self.remove(jid)
-
-    def remove(self, jid):
-        """remove a contact from the list"""
-        self.jid_list.remove(jid.short)
-        if self.__index >= len(self.jid_list) and self.__index > 0:  #if select index is out of border, we put it on the last contact
-            self.__index = len(self.jid_list)-1
-        self.update()
-
-    def update(self):
-        """redraw all the window"""
-        if self.isHidden():
-            return
-        Window.update(self)
-        self.jid_list.sort()
-        begin=0 if self.__index<self.rHeight else self.__index-self.rHeight+1 
-        idx=0
-        for item in self.jid_list[begin:self.rHeight+begin]:
-            attr = curses.A_REVERSE if ( self.isActive() and (idx+begin) == self.__index ) else 0
-            centered = item.center(self.rWidth) ## it's nicer in the center :)
-            self.addYXStr(idx, 0, centered, attr)
-            idx = idx + 1
-
-        self.noutrefresh()
-
-    def handleKey(self, k):
-        if k == curses.KEY_UP:
-            self.indexDown()
-        elif k == curses.KEY_DOWN:
-            self.indexUp()
-        elif k == ascii.NL:
-            if not self.jid_list:
-                return
-            try:
-                self.__enterCB(self.jid_list[self.__index])
-            except NameError:
-                pass # TODO: thrown an error here
-            
-class SortilegeApp(QuickApp):
-    
-    def __init__(self):
-        #debug(const_APP_NAME+" init...")
-
-        ## unicode support ##
-        locale.setlocale(locale.LC_ALL, '')
-        global code
-        code = locale.getpreferredencoding()
-        self.code=code
-
-        ## main loop setup ##
-        self.loop=gobject.MainLoop()
-        gobject.io_add_watch(0, gobject.IO_IN, self.loopCB)
-
-        ## misc init stuff ##
-        self.CM = QuickContactManagement()
-        self.listWins=[]
-        self.chatParams={'timestamp':True,
-                         'color':True,
-                         'short_nick':False}
-
-    def start(self):
-        curses.wrapper(self.start_curses)
-
-    def start_curses(self, win):
-        global stdscr
-        stdscr = win
-        self.stdscr = stdscr
-        curses.raw() #we handle everything ourself
-        curses.curs_set(False)
-        stdscr.nodelay(True)
-
-        ## colours ##
-        self.color(True)
-
-        ## windows ##
-        self.contactList = ContactList(self, self.CM)
-        self.editBar = EditBox(stdscr, "> ", self.code)
-        self.editBar.activate(False)
-        self.statusBar = StatusBar(stdscr, self.code)
-        self.statusBar.hide(True)
-        self.addWin(self.contactList)
-        self.addWin(self.editBar)
-        self.addWin(self.statusBar)
-        self.sizer=BoxSizer(stdscr)
-        self.sizer.appendRow(self.contactList)
-        self.sizer.appendRow(self.statusBar)
-        self.sizer.appendRow(self.editBar)
-        self.currentChat=None
-
-        self.contactList.registerEnterCB(self.onContactChoosed)
-        self.editBar.registerEnterCB(self.onTextEntered)
-
-        self.chat_wins=ChatList(self)
-
-        QuickApp.__init__(self)  #XXX: yes it's an unusual place for the constructor of a parent class, but the init order is important
-        self.plug_profile()
-
-        signal (SIGWINCH, self.onResize) #we manage SIGWINCH ourselves, because the loop is not called otherwise
-
-        #last but not least, we adapt windows' sizes
-        self.sizer.update()
-        self.editBar.replace_cur()
-        curses.doupdate()
-
-        self.loop.run()
-
-    def addWin(self, win):
-        self.listWins.append(win)
-
-    def color(self, activate=True):
-        if activate:
-            debug (_("Activating colors"))
-            curses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_BLACK)
-            curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
-        else:
-            debug (_("Deactivating colors"))
-            curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
-            curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLACK)
-            
-
-    def showChat(self, chat):
-        debug (_("show chat"))
-        if self.currentChat:
-            debug (_("hiding %s"), self.currentChat)
-            self.chat_wins[self.currentChat].hide()
-        self.currentChat=chat
-        debug (_("showing %s"), self.currentChat)
-        self.chat_wins[self.currentChat].show()
-        self.chat_wins[self.currentChat].update()
-        
-
-    ### EVENTS ###
-
-    def onContactChoosed(self, jid_txt):
-        """Called when a contact is selected in contact list."""
-        jid=JID(jid_txt)
-        debug (_("contact choosed: %s"), jid)
-        self.showChat(jid.short)
-        self.statusBar.remove_item(jid.short)
-        if len(self.statusBar)==0:
-            self.statusBar.hide()
-            self.sizer.update()
-
-
-    def onTextEntered(self, text):
-        jid=JID(self.profiles[self.profile]['whoami'])
-        self.bridge.sendMessage(self.currentChat, text, profile_key=self.profile)
-
-    def showDialog(self, message, title, type="info"):
-        if type==question:
-            raise NotImplementedError
-        pass
-
-
-    def presenceUpdate(self, jabber_id, show, priority, statuses, profile):
-        QuickApp.presenceUpdate(self, jabber_id, show, priority, statuses, profile)
-        self.editBar.replace_cur()
-        curses.doupdate()
-
-    def askConfirmation(self, type, id, data):
-        #FIXME
-        info (_("FIXME: askConfirmation not implemented"))
-
-    def actionResult(self, type, id, data):
-        #FIXME
-        info (_("FIXME: actionResult not implemented"))
-
-    def newMessage(self, from_jid, msg, type, to_jid, profile):
-        QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile)
-        sender=JID(from_jid)
-        addr=JID(to_jid)
-        win = addr if sender.short == self.whoami.short else sender  #FIXME: duplicate code with QuickApp
-        if (self.currentChat==None):
-            self.currentChat=win.short
-            self.showChat(win.short)
-            
-        # we show the window in the status bar
-        if not self.currentChat == win.short:
-            self.statusBar.add_item(win.short)
-            self.statusBar.show()
-        self.sizer.update()
-        self.statusBar.update()
-
-        self.editBar.replace_cur()
-        curses.doupdate()
-
-    def onResize(self, sig, stack):
-        """Called on SIGWINCH.
-        resize the screen and launch the loop"""
-        height, width = ttysize()
-        curses.resizeterm(height, width)
-        gobject.idle_add(self.callOnceLoop)
-    
-    def callOnceLoop(self):
-        """Call the loop and return false (for not being called again by gobject mainloop).
-        Usefull for calling loop when there is no input in stdin"""
-        self.loopCB()
-        return False
-
-    def __key_handling(self, k):
-        """Handle key and transmit to active window."""
-
-        ### General keys, handled _everytime_ ###
-        if k == C('x'):
-            if os.getenv('TERM')=='screen':
-                os.system('screen -X remove')
-            else:
-                self.loop.quit()
-
-        ## windows navigation
-        elif k == C('l') and not self.contactList.isHidden():
-            """We go to the contact list"""
-            self.contactList.activate(not self.contactList.isActive())
-            if self.currentChat:
-                self.editBar.activate(not self.contactList.isActive())
-
-        elif k == curses.KEY_F2:
-            self.contactList.hide(not self.contactList.isHidden())
-            if self.contactList.isHidden():
-                self.contactList.activate(False) #TODO: auto deactivation when hiding ?
-                if self.currentChat:
-                    self.editBar.activate(True)
-            self.sizer.update()
-
-        ## Chat Params ##    
-        elif k == C('c'):
-            self.chatParams["color"] = not self.chatParams["color"]
-            self.color(self.chatParams["color"])
-        elif k == C('t'):
-            self.chatParams["timestamp"] = not self.chatParams["timestamp"]
-            self.chat_wins[self.currentChat].update()
-        elif k == C('s'):
-            self.chatParams["short_nick"] = not self.chatParams["short_nick"]
-            self.chat_wins[self.currentChat].update()
-
-        ## misc ##
-        elif k == curses.KEY_RESIZE:
-            stdscr.erase()
-            height, width = stdscr.getmaxyx()
-            if height<5 and width<35:
-                stdscr.addstr(_("Pleeeeasse, I can't even breathe !"))
-            else:
-                for win in self.listWins:
-                    win.resizeAdapt()
-                for win in self.chat_wins.keys():
-                    self.chat_wins[win].resizeAdapt()
-                self.sizer.update() # FIXME: everything need to be managed by the sizer
-
-        ## we now throw the key to win handlers ##
-        else: 
-            for win in self.listWins:
-                if win.isActive():
-                    win.handleKey(k)
-            if self.currentChat:
-                self.chat_wins[self.currentChat].handleKey(k)
-
-    def loopCB(self, source="", cb_condition=""):
-        """This callback is called by the main loop"""
-        #pressed = self.contactList.window.getch()
-        pressed = stdscr.getch()
-        if pressed != curses.ERR:
-            self.__key_handling(pressed)
-            self.editBar.replace_cur()
-            curses.doupdate()
-            
-
-        return True
-
-
-sat = SortilegeApp()
-sat.start()
--- a/frontends/sortilege/statusbar.py	Tue Jun 29 16:07:51 2010 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-"""
-sortilege: 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 curses
-from window import Window
-import os
-
-class StatusBar(Window):
-    """This class manage the edition of text"""
-
-    def __init__(self, parent, code="utf-8"):
-        self.__parent=parent
-        self.__code=code
-        self.__items=set()
-        
-        Window.__init__(self, self.__parent, 1, self.__parent.getmaxyx()[1], self.__parent.getmaxyx()[0]-2,0, code=code)
-
-    def __len__(self):
-        return len(self.__items)
-
-    def resizeAdapt(self):
-        """Adapt window size to self.__parent size.
-        Must be called when self.__parent is resized."""
-        self.resize(1, self.__parent.getmaxyx()[1], self.__parent.getmaxyx()[0]-2,0)
-        self.update()
-
-    def update(self):
-        if self.isHidden():
-            return
-        Window.update(self)
-        x=0
-        for item in self.__items:
-            pitem="[%s] " % item
-            self.addYXStr(0, x, pitem, curses.A_REVERSE)
-            x = x + len(pitem)
-            if x>=self.rWidth:
-                break
-        self.addYXStr(0, x, (self.rWidth-x)*" ", curses.A_REVERSE)
-        self.noutrefresh()
-
-    def clear_text(self):
-        """Clear the text of the edit box"""
-        del(self.__items[:])
-       
-    def add_item(self, item):
-        self.__items.add(item)
-        self.update()
-       
-    def remove_item(self, item):
-        if item in self.__items:
-            self.__items.remove(item)
-        self.update()
--- a/frontends/sortilege/window.py	Tue Jun 29 16:07:51 2010 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,159 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-"""
-sortilege: 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 curses
-import os
-import pdb
-
-
-class Window():
-    def __init__(self, parent, height, width, y, x, border=False, title="", code="utf-8"):
-        self.__border=border
-        self.__title=title
-        self.__active=False
-        self.__parent=parent
-        self.__code=code
-        self.__hide=False
-        
-        self.resize(height, width, y, x)
-        self.oriCoords=self.__coords #FIXME: tres moche, a faire en mieux
-
-    def hide(self, hide=True):
-        self.__hide=hide
-
-    def show(self):
-        self.__hide=False
-
-    def isHidden(self):
-        return self.__hide
-
-    def getY(self):
-        return self.__coords[2]
-
-    def getX(self):
-        return self.__coords[3]
-
-    def getHeight(self):
-        return self.__coords[0]
-
-    def getWidth(self):
-        return self.__coords[1]
-
-
-    #FIXME: tres moche, a faire en plus joli
-    def getOriY(self):
-        return self.oriCoords[2]
-
-    def getOriX(self):
-        return self.oriCoords[3]
-
-    def getOriHeight(self):
-        return self.oriCoords[0]
-
-    def getOriWidth(self):
-        return self.oriCoords[1]
-
-    def defInsideCoord(self):
-        """define the inside coordinates (win coordinates minus decorations)"""
-        height,width,y,x=self.__coords
-        self.oriX = x if not self.__border else x+1
-        self.oriY = y if not self.__border else y+1
-        self.endX = x+width if not self.__border else x+width-2
-        self.endY = y+height if not self.__border else y+height-2
-        self.rWidth = width if not self.__border else width-2
-        self.rHeight = height if not self.__border else height-2
-
-    def resize(self, height, width, y, x):
-        self.__coords=[height, width, y, x]
-
-        # we check that coordinates are under limits
-        self.__coordAdjust(self.__coords)
-        height,width,y,x=self.__coords
-       
-        self.window = self.__parent.subwin(height, width, y, x)
-        self.defInsideCoord()
-
-    def __coordAdjust(self, coords):
-        """Check that coordinates are under limits, adjust them else otherwise"""
-        height,width,y,x=coords
-        parentY, parentX = self.__parent.getbegyx()
-        parentHeight, parentWidth = self.__parent.getmaxyx()
-
-        if y < parentY:
-            y = parentY
-        if x < parentX:
-            x = parentX
-        if height > parentHeight - y:
-            height = parentHeight - y
-        if width > parentWidth - x:
-            width = parentWidth - x
-        coords[0], coords[1], coords[2], coords[3] = [height, width, y, x]
-
-
-    def activate(self,state=True):
-        """Declare this window as current active one"""
-        self.__active=state
-        self.update()
-
-    def isActive(self):
-        return self.__active
-
-    def addYXStr(self, y, x, text, attr = 0, limit=0):
-        if self.__border:
-            x=x+1
-            y=y+1
-        n = self.rWidth-x if not limit else limit
-        encoded = text.encode(self.__code)
-        adjust = len(encoded) - len(text) # hack because addnstr doesn't manage unicode
-        try:    
-            self.window.addnstr(y, x, encoded, n + adjust, attr) 
-        except:
-            #We have to catch error to write on last line last col FIXME: is there a better way ?
-            pass
-
-    def move(self, y, x):
-        self.window.move(y,x)
-    
-    def noutrefresh(self):
-        self.window.noutrefresh()
-
-    def update(self):
-        """redraw all the window"""
-        if self.__hide:
-            return
-        self.clear()
-
-    def border(self):
-        """redraw the border and title"""
-        y,x = self.window.getbegyx()
-        width = self.window.getmaxyx()[1]
-        if self.__border:
-            self.window.border()
-            if self.__title:
-                if len(self.__title)>width:
-                    self.__title=""
-                else:
-                    self.window.addstr(y,x+(width-len(self.__title))/2, self.__title)
-
-    def clear(self):
-        self.window.clear()
-        self.border()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/sortilege_old/boxsizer.py	Wed Jun 30 14:24:24 2010 +0800
@@ -0,0 +1,77 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+sortilege: 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/>.
+"""
+
+
+from window import Window
+import os,pdb
+
+class BoxSizer:
+    """This class manage the position of the window like boxes."""
+
+
+
+    def __init__(self, parent):
+        self.__parent=parent
+        self.boxes=[]
+
+
+
+    def appendRow(self, win):
+        self.boxes.append([win])
+
+    def appendColum(self, index, win):
+        if len(self.boxes)<=index:
+            #TODO: throw an error here
+            return
+        self.boxes[index].append(win)
+
+    def update(self):
+        """Resize boxes"""
+        oriY=0
+        visible_row=[]
+        for row in self.boxes:
+            current_row=[]
+            oriX=0
+            for win in row:
+                x=win.getOriX()
+                y=win.getOriY()
+                w=win.getOriWidth()
+                h=win.getOriHeight()
+                if win.isHidden():
+                    if len(current_row)>1 and win is row[-1]:
+                        #if the last win is hidden, we expand previous visible one
+                        current_row[-1][2] = current_row[-1][2] + (win.getX() - oriX)+win.getWidth()
+                else:
+                    current_row.append([win, h+y-oriY, w+x-oriX, oriY, oriX])
+                    oriX=oriX+w
+                    
+            if oriX!=0:
+                oriY=oriY+h
+                visible_row.append(current_row)
+            elif visible_row:
+                #if all the row is empty, we take the space
+                for box in visible_row[-1]:
+                    box[1]=box[1]+h
+                oriY=oriY+h  #this only happen if it's not the first visible row
+
+        for row in visible_row:
+            for win in row:
+                win[0].resize(win[1], win[2], win[3], win[4])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/sortilege_old/chat.py	Wed Jun 30 14:24:24 2010 +0800
@@ -0,0 +1,153 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+sortilege: 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 os.path
+import pdb
+from logging import debug, info, error
+from window import Window
+import os
+from time import time, localtime, strftime
+import curses
+import curses.ascii as ascii
+from tools.jid  import JID
+from quick_frontend.quick_chat import QuickChat
+
+
+def C(k):
+    """return the value of Ctrl+key"""
+    return ord(ascii.ctrl(k))
+
+class Chat(Window, QuickChat):
+
+    def __init__(self, to_id, host):
+        QuickChat.__init__(self, to_id, host) 
+        self.__parent=host.stdscr
+        self.to_id=JID(to_id)
+        self.content=[]
+        self.__scollIdx=0
+        Window.__init__(self, self.__parent, self.__parent.getmaxyx()[0]-2, self.__parent.getmaxyx()[1]-30, 0,30, code=host.code)
+
+        #history
+        self.historyPrint(50, True, profile=self.host.profile)
+
+        self.hide()
+
+    def resize(self, height, width, y, x):
+        Window.resize(self, height, width, y, x)
+        self.update()
+
+    def resizeAdapt(self):
+        """Adapt window size to self.__parent size.
+        Must be called when self.__parent is resized."""
+        self.resize(self.__parent.getmaxyx()[0]-2, self.__parent.getmaxyx()[1]-30, 0,30)
+        self.update()
+
+    def __getHeader(self, line):
+        """Return the header of a line (eg: "[12:34] <toto> ")."""
+        header=''
+        if self.host.chatParams["timestamp"]:
+            header = header + '[%s] ' % strftime("%H:%M", localtime(float(line[0])))
+        if self.host.chatParams['short_nick']:
+            header = header + ('> ' if  line[1]==self.host.profiles[self.host.profile]['whoami'] else '** ')
+        else:
+            header = header + '<%s> ' % line[1]
+        return header
+
+    def update(self):
+        if self.isHidden():
+            return
+        Window.update(self)
+        content=[] #what is really printed
+        irange=range(len(self.content))
+        irange.reverse() #we print the text upward
+        for idx in irange:
+            header=self.__getHeader(self.content[idx])
+            msg=self.content[idx][2]
+            part=0  #part of the text
+            if JID(self.content[idx][1]).short==self.host.profiles[self.host.profile]['whoami'].short:
+                att_header=curses.color_pair(1)
+            else:
+                att_header=curses.color_pair(2)
+            
+            while (msg):
+                if part==0:
+                    hd=header
+                    att=att_header
+                    max=self.rWidth-len(header)
+                else:
+                    hd=""
+                    att=0
+                    max=self.rWidth
+
+                LF = msg.find('\n')   #we look for Line Feed
+                if LF != -1 and LF < max:
+                    max = LF
+                    next = max + 1  #we skip the LF
+                else:
+                    next = max
+                    
+                content.insert(part,[att,hd, msg[:max]])
+                msg=msg[next:]   #we erase treated part
+                part=part+1
+
+            if len(content)>=self.rHeight+self.__scollIdx:
+                #all the screen is filled, we can continue
+                break
+
+        if self.__scollIdx>0 and len(content)<self.rHeight+self.__scollIdx:
+            self.__scollIdx=abs(len(content)-self.rHeight)  #all the log fit on the screen, we must stop here
+            
+        idx=0
+        for line in content[-self.rHeight-self.__scollIdx : -self.__scollIdx or None]:
+            self.addYXStr(idx, 0, line[1], line[0])
+            self.addYXStr(idx, len(line[1]), line[2])
+            idx=idx+1
+
+        self.noutrefresh()
+    
+    def scrollIdxUp(self):
+        """increment scroll index"""
+        self.__scollIdx = self.__scollIdx + 1
+        self.update()
+    
+    def scrollIdxDown(self):
+        """decrement scroll index"""
+        if self.__scollIdx > 0:
+            self.__scollIdx = self.__scollIdx - 1
+        self.update()
+
+    def printMessage(self, jid, msg, profile, timestamp=""):
+        if timestamp=="":
+            current_time=time()
+            timestamp=str(current_time)
+            if self.last_history and current_time - float(self.last_history) < 5: #FIXME: Q&D fix to avoid double print on new chat window
+                return
+        self.content.append([timestamp,jid.short,msg])
+        self.update()
+    
+    def handleKey(self, k):
+        if k == C('p'):
+            self.scrollIdxUp()
+        elif k == C('n'):
+            self.scrollIdxDown()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/sortilege_old/editbox.py	Wed Jun 30 14:24:24 2010 +0800
@@ -0,0 +1,176 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+sortilege: 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 curses
+from curses import ascii
+from window import Window
+
+def C(k):
+    """return the value of Ctrl+key"""
+    return ord(ascii.ctrl(k))
+
+def A(k):
+    """return the value of Alt+key"""
+    return ord(ascii.alt(k))
+
+class EditBox(Window):
+    """This class manage the edition of text"""
+
+    def __init__(self, parent, header, code="utf-8"):
+        self.__header=header
+        self.__text = unicode()
+        self.__curs_pos=0
+        self.__buffer=str()
+        self.__replace_mode=False
+        self.__parent=parent
+        self.__code=code
+        
+        Window.__init__(self, self.__parent, 1, self.__parent.getmaxyx()[1], self.__parent.getmaxyx()[0]-1,0, code=code)
+        self.update()
+
+    def registerEnterCB(self, CB):
+        self.__enterCB=CB
+
+    def resizeAdapt(self):
+        """Adapt window size to self.__parent size.
+        Must be called when self.__parent is resized."""
+        self.resize(1, self.__parent.getmaxyx()[1], self.__parent.getmaxyx()[0]-1,0)
+        self.update()
+
+    def __getTextToPrint(self):
+        """return the text printed on the edit line"""
+        width = self.rWidth - len(self.__header) -1
+        if self.__curs_pos<width:
+            begin = 0 
+            end = width
+        else:
+            begin = self.__curs_pos-width
+            end = self.__curs_pos
+        return self.__header+self.__text[begin:end]
+
+    def update(self):
+        Window.update(self)
+        text = self.__getTextToPrint()
+        self.addYXStr(0, 0, text, limit=self.rWidth)
+        
+        self.noutrefresh()
+
+    def __dec_cur(self):
+        """move cursor on the left"""
+        if self.__curs_pos>0:
+            self.__curs_pos = self.__curs_pos - 1
+
+    def __inc_cur(self):
+        """move cursor on the right"""
+        if self.__curs_pos<len(self.__text):
+            self.__curs_pos = self.__curs_pos + 1
+    
+    def move_cur(self, x):
+        pos = x+len(self.__header)
+        if pos>=self.rWidth:
+            pos=self.rWidth-1
+        self.move(0, pos)
+
+    def clear_text(self):
+        """Clear the text of the edit box"""
+        self.__text=""
+        self.__curs_pos=0
+        
+    def replace_cur(self):
+        """must be called earch time the cursor is moved"""
+        self.move_cur(self.__curs_pos)
+        self.noutrefresh()
+
+    def activate(self, state=True):
+        cursor_mode = 1 if state else 0
+        curses.curs_set(cursor_mode)
+        Window.activate(self,state)
+
+    def handleKey(self, k):
+        if ascii.isgraph(k) or ascii.isblank(k):
+            pacman = 0 if not self.__replace_mode else 1
+            self.__text = self.__text[:self.__curs_pos] + chr(k) + self.__text[self.__curs_pos + pacman:]
+            self.__curs_pos = self.__curs_pos + 1
+
+        elif k==ascii.NL:
+            try:
+                self.__enterCB(self.__text)
+            except NameError:
+                pass # TODO: thrown an error here
+            self.clear_text()
+
+        elif k==curses.KEY_BACKSPACE:
+            self.__text = self.__text[:self.__curs_pos-1]+self.__text[self.__curs_pos:]
+            self.__dec_cur()
+
+        elif k==curses.KEY_DC:
+            self.__text = self.__text[:self.__curs_pos]+self.__text[self.__curs_pos+1:]
+
+        elif k==curses.KEY_IC:
+            self.__replace_mode = not self.__replace_mode
+
+        elif k==curses.KEY_LEFT:
+            self.__dec_cur()
+
+        elif k==curses.KEY_RIGHT:
+            self.__inc_cur()
+
+        elif k==curses.KEY_HOME or k==C('a'):
+            self.__curs_pos=0
+        
+        elif k==curses.KEY_END or k==C('e'):
+            self.__curs_pos=len(self.__text)
+
+        elif k==C('k'):
+            self.__text = self.__text[:self.__curs_pos]
+
+        elif k==C('w'):
+            before = self.__text[:self.__curs_pos]
+            pos = before.rstrip().rfind(" ")+1
+            self.__text = before[:pos] + self.__text[self.__curs_pos:]
+            self.__curs_pos = pos
+
+        elif k>255:
+            self.__buffer=""
+
+        else:  ## FIXME: dangerous code, must be checked ! (specialy buffer overflow) ##
+            #We now manage unicode
+            self.__buffer = self.__buffer+chr(k)
+            decoded=unicode()
+            if len(self.__buffer)>4:
+                self.__buffer=""
+                return
+            try:
+                decoded = self.__buffer.decode(self.__code)
+            except UnicodeDecodeError, e:
+                if e.reason!="unexpected end of data":
+                    self.__buffer=""
+                return
+            if len(self.__buffer)==1:  ## FIXME: awful ! only for test !
+                self.__buffer=""
+                return
+            self.__text = self.__text + decoded
+            self.__curs_pos = self.__curs_pos + 1
+            self.__buffer=""
+
+        self.update()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/sortilege_old/sortilege	Wed Jun 30 14:24:24 2010 +0800
@@ -0,0 +1,400 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+sortilege: 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/>.
+"""
+
+
+from quick_frontend.quick_app import QuickApp
+from quick_frontend.quick_chat_list import QuickChatList
+from quick_frontend.quick_contact_list import QuickContactList
+from quick_frontend.quick_contact_management import QuickContactManagement
+import curses
+import pdb
+from window import Window
+from editbox import EditBox
+from statusbar import StatusBar
+from chat import Chat
+from tools.jid  import JID
+import logging
+from logging import debug, info, error
+import locale
+import sys, os
+import gobject
+import time
+from curses import ascii
+import locale
+from signal import signal, SIGWINCH 
+import fcntl
+import struct
+import termios
+from boxsizer import BoxSizer
+
+
+### logging configuration FIXME: put this elsewhere ###
+logging.basicConfig(level=logging.CRITICAL,  #TODO: configure it top put messages in a log file
+                    format='%(message)s')
+###
+
+const_APP_NAME      = "Sortilège"
+const_CONTACT_WIDTH = 30
+
+def ttysize():
+    """This function return term size.
+    Comes from Donn Cave from python list mailing list"""
+    buf = 'abcdefgh'
+    buf = fcntl.ioctl(0, termios.TIOCGWINSZ, buf)
+    row, col, rpx, cpx = struct.unpack('hhhh', buf)
+    return row, col
+
+def C(k):
+    """return the value of Ctrl+key"""
+    return ord(ascii.ctrl(k))
+
+class ChatList(QuickChatList):
+    """This class manage the list of chat windows"""
+    
+    def __init__(self, host):
+        QuickChatList.__init__(self, host)
+        self.sizer=host.sizer
+
+    def createChat(self, name):
+        chat = Chat(name, self.host)
+        self.sizer.appendColum(0,chat)
+        self.sizer.update()
+        return chat 
+        
+class ContactList(Window, QuickContactList):
+    
+    def __init__(self, host, CM):
+        QuickContactList.__init__(self, CM)
+        self.host = host
+        self.jid_list = []
+        self.__index=0  #indicate which contact is selected (ie: where we are)
+        Window.__init__(self, stdscr, stdscr.getmaxyx()[0]-2,const_CONTACT_WIDTH,0,0, True, _("Contact List"), code=code)
+
+    def resize(self, height, width, y, x):
+        Window.resize(self, height, width, y, x)
+        self.update()
+
+    def resizeAdapt(self):
+        """Adapt window size to stdscr size.
+        Must be called when stdscr is resized."""
+        self.resize(stdscr.getmaxyx()[0]-2,const_CONTACT_WIDTH,0,0)
+        self.update()
+        
+    def registerEnterCB(self, CB):
+        self.__enterCB=CB
+
+    def clear_contacts(self):
+        """clear all the contact list"""
+        del self.jid_list[:]
+        self.__index = 0
+        self.update()  #FIXME: window is not updated correctly (contacts are still here until C-L)
+
+    def replace(self, jid, groups=None):
+        """add a contact to the list"""
+        name = self.CM.getAttr(jid,'name')
+        self.jid_list.append(jid.short)
+        self.update()
+
+    def indexUp(self):
+        """increment select contact index"""
+        if self.__index < len(self.jid_list)-1:  #we dont want to select a missing contact
+            self.__index = self.__index + 1
+        self.update()
+    
+    def indexDown(self):
+        """decrement select contact index"""
+        if self.__index > 0:
+            self.__index = self.__index - 1
+        self.update()
+
+    def disconnect(self, jid):
+        """for now, we just remove the contact"""
+        self.remove(jid)
+
+    def remove(self, jid):
+        """remove a contact from the list"""
+        self.jid_list.remove(jid.short)
+        if self.__index >= len(self.jid_list) and self.__index > 0:  #if select index is out of border, we put it on the last contact
+            self.__index = len(self.jid_list)-1
+        self.update()
+
+    def update(self):
+        """redraw all the window"""
+        if self.isHidden():
+            return
+        Window.update(self)
+        self.jid_list.sort()
+        begin=0 if self.__index<self.rHeight else self.__index-self.rHeight+1 
+        idx=0
+        for item in self.jid_list[begin:self.rHeight+begin]:
+            attr = curses.A_REVERSE if ( self.isActive() and (idx+begin) == self.__index ) else 0
+            centered = item.center(self.rWidth) ## it's nicer in the center :)
+            self.addYXStr(idx, 0, centered, attr)
+            idx = idx + 1
+
+        self.noutrefresh()
+
+    def handleKey(self, k):
+        if k == curses.KEY_UP:
+            self.indexDown()
+        elif k == curses.KEY_DOWN:
+            self.indexUp()
+        elif k == ascii.NL:
+            if not self.jid_list:
+                return
+            try:
+                self.__enterCB(self.jid_list[self.__index])
+            except NameError:
+                pass # TODO: thrown an error here
+            
+class SortilegeApp(QuickApp):
+    
+    def __init__(self):
+        #debug(const_APP_NAME+" init...")
+
+        ## unicode support ##
+        locale.setlocale(locale.LC_ALL, '')
+        global code
+        code = locale.getpreferredencoding()
+        self.code=code
+
+        ## main loop setup ##
+        self.loop=gobject.MainLoop()
+        gobject.io_add_watch(0, gobject.IO_IN, self.loopCB)
+
+        ## misc init stuff ##
+        self.CM = QuickContactManagement()
+        self.listWins=[]
+        self.chatParams={'timestamp':True,
+                         'color':True,
+                         'short_nick':False}
+
+    def start(self):
+        curses.wrapper(self.start_curses)
+
+    def start_curses(self, win):
+        global stdscr
+        stdscr = win
+        self.stdscr = stdscr
+        curses.raw() #we handle everything ourself
+        curses.curs_set(False)
+        stdscr.nodelay(True)
+
+        ## colours ##
+        self.color(True)
+
+        ## windows ##
+        self.contactList = ContactList(self, self.CM)
+        self.editBar = EditBox(stdscr, "> ", self.code)
+        self.editBar.activate(False)
+        self.statusBar = StatusBar(stdscr, self.code)
+        self.statusBar.hide(True)
+        self.addWin(self.contactList)
+        self.addWin(self.editBar)
+        self.addWin(self.statusBar)
+        self.sizer=BoxSizer(stdscr)
+        self.sizer.appendRow(self.contactList)
+        self.sizer.appendRow(self.statusBar)
+        self.sizer.appendRow(self.editBar)
+        self.currentChat=None
+
+        self.contactList.registerEnterCB(self.onContactChoosed)
+        self.editBar.registerEnterCB(self.onTextEntered)
+
+        self.chat_wins=ChatList(self)
+
+        QuickApp.__init__(self)  #XXX: yes it's an unusual place for the constructor of a parent class, but the init order is important
+        self.plug_profile()
+
+        signal (SIGWINCH, self.onResize) #we manage SIGWINCH ourselves, because the loop is not called otherwise
+
+        #last but not least, we adapt windows' sizes
+        self.sizer.update()
+        self.editBar.replace_cur()
+        curses.doupdate()
+
+        self.loop.run()
+
+    def addWin(self, win):
+        self.listWins.append(win)
+
+    def color(self, activate=True):
+        if activate:
+            debug (_("Activating colors"))
+            curses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_BLACK)
+            curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
+        else:
+            debug (_("Deactivating colors"))
+            curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
+            curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLACK)
+            
+
+    def showChat(self, chat):
+        debug (_("show chat"))
+        if self.currentChat:
+            debug (_("hiding %s"), self.currentChat)
+            self.chat_wins[self.currentChat].hide()
+        self.currentChat=chat
+        debug (_("showing %s"), self.currentChat)
+        self.chat_wins[self.currentChat].show()
+        self.chat_wins[self.currentChat].update()
+        
+
+    ### EVENTS ###
+
+    def onContactChoosed(self, jid_txt):
+        """Called when a contact is selected in contact list."""
+        jid=JID(jid_txt)
+        debug (_("contact choosed: %s"), jid)
+        self.showChat(jid.short)
+        self.statusBar.remove_item(jid.short)
+        if len(self.statusBar)==0:
+            self.statusBar.hide()
+            self.sizer.update()
+
+
+    def onTextEntered(self, text):
+        jid=JID(self.profiles[self.profile]['whoami'])
+        self.bridge.sendMessage(self.currentChat, text, profile_key=self.profile)
+
+    def showDialog(self, message, title, type="info"):
+        if type==question:
+            raise NotImplementedError
+        pass
+
+
+    def presenceUpdate(self, jabber_id, show, priority, statuses, profile):
+        QuickApp.presenceUpdate(self, jabber_id, show, priority, statuses, profile)
+        self.editBar.replace_cur()
+        curses.doupdate()
+
+    def askConfirmation(self, type, id, data):
+        #FIXME
+        info (_("FIXME: askConfirmation not implemented"))
+
+    def actionResult(self, type, id, data):
+        #FIXME
+        info (_("FIXME: actionResult not implemented"))
+
+    def newMessage(self, from_jid, msg, type, to_jid, profile):
+        QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile)
+        sender=JID(from_jid)
+        addr=JID(to_jid)
+        win = addr if sender.short == self.whoami.short else sender  #FIXME: duplicate code with QuickApp
+        if (self.currentChat==None):
+            self.currentChat=win.short
+            self.showChat(win.short)
+            
+        # we show the window in the status bar
+        if not self.currentChat == win.short:
+            self.statusBar.add_item(win.short)
+            self.statusBar.show()
+        self.sizer.update()
+        self.statusBar.update()
+
+        self.editBar.replace_cur()
+        curses.doupdate()
+
+    def onResize(self, sig, stack):
+        """Called on SIGWINCH.
+        resize the screen and launch the loop"""
+        height, width = ttysize()
+        curses.resizeterm(height, width)
+        gobject.idle_add(self.callOnceLoop)
+    
+    def callOnceLoop(self):
+        """Call the loop and return false (for not being called again by gobject mainloop).
+        Usefull for calling loop when there is no input in stdin"""
+        self.loopCB()
+        return False
+
+    def __key_handling(self, k):
+        """Handle key and transmit to active window."""
+
+        ### General keys, handled _everytime_ ###
+        if k == C('x'):
+            if os.getenv('TERM')=='screen':
+                os.system('screen -X remove')
+            else:
+                self.loop.quit()
+
+        ## windows navigation
+        elif k == C('l') and not self.contactList.isHidden():
+            """We go to the contact list"""
+            self.contactList.activate(not self.contactList.isActive())
+            if self.currentChat:
+                self.editBar.activate(not self.contactList.isActive())
+
+        elif k == curses.KEY_F2:
+            self.contactList.hide(not self.contactList.isHidden())
+            if self.contactList.isHidden():
+                self.contactList.activate(False) #TODO: auto deactivation when hiding ?
+                if self.currentChat:
+                    self.editBar.activate(True)
+            self.sizer.update()
+
+        ## Chat Params ##    
+        elif k == C('c'):
+            self.chatParams["color"] = not self.chatParams["color"]
+            self.color(self.chatParams["color"])
+        elif k == C('t'):
+            self.chatParams["timestamp"] = not self.chatParams["timestamp"]
+            self.chat_wins[self.currentChat].update()
+        elif k == C('s'):
+            self.chatParams["short_nick"] = not self.chatParams["short_nick"]
+            self.chat_wins[self.currentChat].update()
+
+        ## misc ##
+        elif k == curses.KEY_RESIZE:
+            stdscr.erase()
+            height, width = stdscr.getmaxyx()
+            if height<5 and width<35:
+                stdscr.addstr(_("Pleeeeasse, I can't even breathe !"))
+            else:
+                for win in self.listWins:
+                    win.resizeAdapt()
+                for win in self.chat_wins.keys():
+                    self.chat_wins[win].resizeAdapt()
+                self.sizer.update() # FIXME: everything need to be managed by the sizer
+
+        ## we now throw the key to win handlers ##
+        else: 
+            for win in self.listWins:
+                if win.isActive():
+                    win.handleKey(k)
+            if self.currentChat:
+                self.chat_wins[self.currentChat].handleKey(k)
+
+    def loopCB(self, source="", cb_condition=""):
+        """This callback is called by the main loop"""
+        #pressed = self.contactList.window.getch()
+        pressed = stdscr.getch()
+        if pressed != curses.ERR:
+            self.__key_handling(pressed)
+            self.editBar.replace_cur()
+            curses.doupdate()
+            
+
+        return True
+
+
+sat = SortilegeApp()
+sat.start()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/sortilege_old/statusbar.py	Wed Jun 30 14:24:24 2010 +0800
@@ -0,0 +1,71 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+sortilege: 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 curses
+from window import Window
+import os
+
+class StatusBar(Window):
+    """This class manage the edition of text"""
+
+    def __init__(self, parent, code="utf-8"):
+        self.__parent=parent
+        self.__code=code
+        self.__items=set()
+        
+        Window.__init__(self, self.__parent, 1, self.__parent.getmaxyx()[1], self.__parent.getmaxyx()[0]-2,0, code=code)
+
+    def __len__(self):
+        return len(self.__items)
+
+    def resizeAdapt(self):
+        """Adapt window size to self.__parent size.
+        Must be called when self.__parent is resized."""
+        self.resize(1, self.__parent.getmaxyx()[1], self.__parent.getmaxyx()[0]-2,0)
+        self.update()
+
+    def update(self):
+        if self.isHidden():
+            return
+        Window.update(self)
+        x=0
+        for item in self.__items:
+            pitem="[%s] " % item
+            self.addYXStr(0, x, pitem, curses.A_REVERSE)
+            x = x + len(pitem)
+            if x>=self.rWidth:
+                break
+        self.addYXStr(0, x, (self.rWidth-x)*" ", curses.A_REVERSE)
+        self.noutrefresh()
+
+    def clear_text(self):
+        """Clear the text of the edit box"""
+        del(self.__items[:])
+       
+    def add_item(self, item):
+        self.__items.add(item)
+        self.update()
+       
+    def remove_item(self, item):
+        if item in self.__items:
+            self.__items.remove(item)
+        self.update()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/sortilege_old/window.py	Wed Jun 30 14:24:24 2010 +0800
@@ -0,0 +1,159 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+sortilege: 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 curses
+import os
+import pdb
+
+
+class Window():
+    def __init__(self, parent, height, width, y, x, border=False, title="", code="utf-8"):
+        self.__border=border
+        self.__title=title
+        self.__active=False
+        self.__parent=parent
+        self.__code=code
+        self.__hide=False
+        
+        self.resize(height, width, y, x)
+        self.oriCoords=self.__coords #FIXME: tres moche, a faire en mieux
+
+    def hide(self, hide=True):
+        self.__hide=hide
+
+    def show(self):
+        self.__hide=False
+
+    def isHidden(self):
+        return self.__hide
+
+    def getY(self):
+        return self.__coords[2]
+
+    def getX(self):
+        return self.__coords[3]
+
+    def getHeight(self):
+        return self.__coords[0]
+
+    def getWidth(self):
+        return self.__coords[1]
+
+
+    #FIXME: tres moche, a faire en plus joli
+    def getOriY(self):
+        return self.oriCoords[2]
+
+    def getOriX(self):
+        return self.oriCoords[3]
+
+    def getOriHeight(self):
+        return self.oriCoords[0]
+
+    def getOriWidth(self):
+        return self.oriCoords[1]
+
+    def defInsideCoord(self):
+        """define the inside coordinates (win coordinates minus decorations)"""
+        height,width,y,x=self.__coords
+        self.oriX = x if not self.__border else x+1
+        self.oriY = y if not self.__border else y+1
+        self.endX = x+width if not self.__border else x+width-2
+        self.endY = y+height if not self.__border else y+height-2
+        self.rWidth = width if not self.__border else width-2
+        self.rHeight = height if not self.__border else height-2
+
+    def resize(self, height, width, y, x):
+        self.__coords=[height, width, y, x]
+
+        # we check that coordinates are under limits
+        self.__coordAdjust(self.__coords)
+        height,width,y,x=self.__coords
+       
+        self.window = self.__parent.subwin(height, width, y, x)
+        self.defInsideCoord()
+
+    def __coordAdjust(self, coords):
+        """Check that coordinates are under limits, adjust them else otherwise"""
+        height,width,y,x=coords
+        parentY, parentX = self.__parent.getbegyx()
+        parentHeight, parentWidth = self.__parent.getmaxyx()
+
+        if y < parentY:
+            y = parentY
+        if x < parentX:
+            x = parentX
+        if height > parentHeight - y:
+            height = parentHeight - y
+        if width > parentWidth - x:
+            width = parentWidth - x
+        coords[0], coords[1], coords[2], coords[3] = [height, width, y, x]
+
+
+    def activate(self,state=True):
+        """Declare this window as current active one"""
+        self.__active=state
+        self.update()
+
+    def isActive(self):
+        return self.__active
+
+    def addYXStr(self, y, x, text, attr = 0, limit=0):
+        if self.__border:
+            x=x+1
+            y=y+1
+        n = self.rWidth-x if not limit else limit
+        encoded = text.encode(self.__code)
+        adjust = len(encoded) - len(text) # hack because addnstr doesn't manage unicode
+        try:    
+            self.window.addnstr(y, x, encoded, n + adjust, attr) 
+        except:
+            #We have to catch error to write on last line last col FIXME: is there a better way ?
+            pass
+
+    def move(self, y, x):
+        self.window.move(y,x)
+    
+    def noutrefresh(self):
+        self.window.noutrefresh()
+
+    def update(self):
+        """redraw all the window"""
+        if self.__hide:
+            return
+        self.clear()
+
+    def border(self):
+        """redraw the border and title"""
+        y,x = self.window.getbegyx()
+        width = self.window.getmaxyx()[1]
+        if self.__border:
+            self.window.border()
+            if self.__title:
+                if len(self.__title)>width:
+                    self.__title=""
+                else:
+                    self.window.addstr(y,x+(width-len(self.__title))/2, self.__title)
+
+    def clear(self):
+        self.window.clear()
+        self.border()