Mercurial > libervia-backend
view frontends/sortilege/sortilege.py @ 0:c4bc297b82f0
sat:
- first public release, initial commit
author | goffi@necton2 |
---|---|
date | Sat, 29 Aug 2009 13:34:59 +0200 |
parents | |
children | bb72c29f3432 |
line wrap: on
line source
#!/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 <http://www.gnu.org/licenses/>. """ 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.rHeight else self.__index-self.rHeight+1 idx=0 for item in viewList[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_ids: return try: self.__enterCB(self.jid_ids.keys()[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.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.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 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 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()