#!/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 Affero 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
"""
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.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()