Mercurial > libervia-backend
diff frontends/sortilege_old/sortilege @ 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 | frontends/sortilege/sortilege@6c927140ba82 |
children | 2a072735e459 |
line wrap: on
line diff
--- /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()