# HG changeset patch # User Goffi # Date 1260256392 -3600 # Node ID c4badbf3dd975db216dc8bfeb34c47e059a4c18a # Parent 7b34ffa2ff4509297262fe919f2286b7a3ac0e44 wix.py renamed in wix and sortilege.py renamed in sortilege diff -r 7b34ffa2ff45 -r c4badbf3dd97 frontends/sortilege/sortilege --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/frontends/sortilege/sortilege Tue Dec 08 08:13:12 2009 +0100 @@ -0,0 +1,387 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +sortilege: a SAT frontend +Copyright (C) 2009 Jérôme Poisson (goffi@goffi.org) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + + +import 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 +from quick_frontend.quick_chat_list import QuickChatList +from quick_frontend.quick_contact_list import QuickContactList +from quick_frontend.quick_app import QuickApp + +### 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): + QuickContactList.__init__(self) + 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 replace(self, jid, name="", show="", status="", group=""): + """add a contact to the list""" + self.jid_ids[jid] = name or jid + self.update() + + def indexUp(self): + """increment select contact index""" + if self.__index < len(self.jid_ids)-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 remove(self, jid): + """remove a contact from the list""" + del self.jid_ids[jid] + if self.__index >= len(self.jid_ids) and self.__index > 0: #if select index is out of border, we put it on the last contact + self.__index = len(self.jid_ids)-1 + self.update() + + def update(self): + """redraw all the window""" + if self.isHidden(): + return + Window.update(self) + viewList=[] + for jid in self.jid_ids: + viewList.append(self.jid_ids[jid]) + viewList.sort() + begin=0 if self.__index ", 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 + + 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 ("Activation des couleurs") + curses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_BLACK) + curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) + else: + debug ("Desactivation des couleurs") + 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 ("hide de %s", self.currentChat) + self.chat_wins[self.currentChat].hide() + self.currentChat=chat + debug ("show de %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 choose: %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.whoami) + self.bridge.sendMessage(self.currentChat, text) + + def showDialog(self, message, title, type="info"): + if type==question: + raise NotImplementedError + pass + + + def presenceUpdate(self, jabber_id, type, show, status, priority): + QuickApp.presenceUpdate(self, jabber_id, type, show, status, priority) + 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): + QuickApp.newMessage(self, from_jid, msg, type, to_jid) + 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() diff -r 7b34ffa2ff45 -r c4badbf3dd97 frontends/wix/wix --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/frontends/wix/wix Tue Dec 08 08:13:12 2009 +0100 @@ -0,0 +1,48 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +wix: a SAT frontend +Copyright (C) 2009 Jérôme Poisson (goffi@goffi.org) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + + +import wx +from sat_bridge_frontend.DBus import DBusBridgeFrontend +import pdb +import logging +from logging import debug, info, error +from main_window import MainWindow + +### logging configuration FIXME: put this elsewhere ### +logging.basicConfig(level=logging.DEBUG, + format='%(message)s') +### + + +class SATApp(wx.App): + def __init__(self, redirect=False, filename=None, useBestVisual=False, clearSigInt=True): + super(SATApp,self).__init__(redirect, filename, useBestVisual, clearSigInt) + + def OnInit(self): + self.main = MainWindow() + self.main.Show(True) + self.SetTopWindow(self.main) + return True + + +sat = SATApp() +sat.MainLoop()