Files reorganisation

This commit is contained in:
Goffi 2010-12-29 01:06:29 +01:00
commit e4452c4b03
125 changed files with 10 additions and 8 deletions

182
frontends/src/bridge/DBus.py Executable file
View file

@ -0,0 +1,182 @@
#!/usr/bin/python
#-*- coding: utf-8 -*-
"""
SAT communication bridge
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 bridge_frontend import BridgeFrontend
import dbus, dbus.glib
class BridgeExceptionNoService(Exception):
pass
class DBusBridgeFrontend(BridgeFrontend):
def __init__(self):
try:
self.sessions_bus = dbus.SessionBus()
self.db_object = self.sessions_bus.get_object('org.goffi.SAT',
'/org/goffi/SAT/bridge')
self.db_comm_iface = dbus.Interface(self.db_object,
dbus_interface='org.goffi.SAT.communication')
self.db_req_iface = dbus.Interface(self.db_object,
dbus_interface='org.goffi.SAT.request')
except dbus.exceptions.DBusException,e:
if e._dbus_error_name=='org.freedesktop.DBus.Error.ServiceUnknown':
raise BridgeExceptionNoService
else:
raise e
#props = self.db_comm_iface.getProperties()
def register(self, functionName, handler, iface="communication"):
if iface == "communication":
self.db_comm_iface.connect_to_signal(functionName, handler)
elif iface == "request":
self.db_req_iface.connect_to_signal(functionName, handler)
def getVersion(self):
return self.db_req_iface.getVersion()
def getProfileName(self, profile_key='@DEFAULT@'):
return self.db_req_iface.getProfileName(profile_key)
def getProfilesList(self):
return self.db_req_iface.getProfilesList()
def createProfile(self, name):
return self.db_req_iface.createProfile(name)
def deleteProfile(self, name):
return self.db_req_iface.deleteProfile(name)
def connect(self, profile_key='@DEFAULT@'):
return self.db_comm_iface.connect(profile_key)
def disconnect(self, profile_key='@DEFAULT@'):
return self.db_comm_iface.disconnect(profile_key)
def isConnected(self, profile_key='@DEFAULT@'):
return self.db_comm_iface.isConnected(profile_key)
def getContacts(self, profile_key='@DEFAULT@'):
return self.db_comm_iface.getContacts(profile_key)
def getPresenceStatus(self, profile_key='@DEFAULT@'):
return self.db_comm_iface.getPresenceStatus(profile_key)
def getWaitingSub(self, profile_key='@DEFAULT@'):
return self.db_comm_iface.getWaitingSub(profile_key)
def sendMessage(self, to, message, type='chat', profile_key='@DEFAULT@'):
return self.db_comm_iface.sendMessage(to, message, type, profile_key)
def setPresence(self, to="", show="", priority=0, statuses={}, profile_key='@DEFAULT@'):
return self.db_comm_iface.setPresence(to, show, priority, statuses, profile_key)
def subscription(self, type, entity, profile_key='@DEFAULT@'):
return self.db_comm_iface.subscription(type, entity, profile_key)
def setParam(self, name, value, category, profile_key='@DEFAULT@'):
return self.db_comm_iface.setParam(name, value, category, profile_key)
def getParamA(self, name, category, profile_key='@DEFAULT@'):
return self.db_comm_iface.getParamA(name, category, profile_key)
def getParamsUI(self, profile_key='@DEFAULT@'):
return self.db_comm_iface.getParamsUI(profile_key)
def getParams(self, profile_key='@DEFAULT@'):
return self.db_comm_iface.getParams(profile_key)
def getParamsForCategory(self, category, profile_key='@DEFAULT@'):
return self.db_comm_iface.getParamsForCategory(category, profile_key)
def getParamsCategories(self):
return self.db_comm_iface.getParamsCategories()
def getHistory(self, from_jid, to_jid, size):
return self.db_comm_iface.getHistory(from_jid, to_jid, size)
def addContact(self, jid, profile_key='@DEFAULT@'):
return self.db_comm_iface.addContact(jid, profile_key)
def delContact(self, jid, profile_key='@DEFAULT@'):
return self.db_comm_iface.delContact(jid, profile_key)
def launchAction(self, type, data, profile_key='@DEFAULT@'):
return self.db_req_iface.launchAction(type, data, profile_key)
def confirmationAnswer(self, id, accepted, data):
return self.db_req_iface.confirmationAnswer(id, accepted, data)
def getProgress(self, id):
return self.db_req_iface.getProgress(id)
def getMenus(self):
return self.db_req_iface.getMenus()
def getMenuHelp(self, category, name, type="NORMAL"):
return self.db_req_iface.getMenuHelp(category, name, type)
def callMenu(self, category, name, type="NORMAL", profile_key='@DEFAULT@'):
return self.db_req_iface.callMenu(category, name, type, profile_key)
#methods from plugins
def getRoomJoined(self, profile_key='@DEFAULT@'):
return self.db_comm_iface.getRoomJoined(profile_key)
def getRoomSubjects(self, profile_key='@DEFAULT@'):
return self.db_comm_iface.getRoomSubjects(profile_key)
def joinMUC(self, service, roomId, nick, profile_key='@DEFAULT@'):
return self.db_comm_iface.joinMUC(service, roomId, nick, profile_key)
def tarotGameCreate(self, room_jid, players, profile_key='@DEFAULT@'):
return self.db_comm_iface.tarotGameCreate(room_jid, players, profile_key)
def tarotGameReady(self, player, referee, profile_key='@DEFAULT@'):
return self.db_comm_iface.tarotGameReady(player, referee, profile_key)
def tarotGameContratChoosed(self, player, referee, contrat, profile_key='@DEFAULT@'):
return self.db_comm_iface.tarotGameContratChoosed(player, referee, contrat, profile_key)
def tarotGamePlayCards(self, player, referee, cards, profile_key='@DEFAULT@'):
return self.db_comm_iface.tarotGamePlayCards(player, referee, cards, profile_key)
def sendFile(self, to, path, profile_key='@DEFAULT@'):
return self.db_comm_iface.sendFile(to, path, profile_key)
def findGateways(self, target, profile_key='@DEFAULT@'):
return self.db_comm_iface.findGateways(target, profile_key)
def getCard(self, target, profile_key='@DEFAULT@'):
return self.db_comm_iface.getCard(target, profile_key)
def getCardCache(self, target):
return self.db_comm_iface.getCardCache(target)
def getAvatarFile(self, hash):
return self.db_comm_iface.getAvatarFile(hash)
def in_band_register(self, target, profile_key='@DEFAULT@'):
return self.db_comm_iface.in_band_register(target, profile_key)
def gatewayRegister(self, action, target, data, profile_key='@DEFAULT@'):
if data == None:
data = [('', '')] #XXX: we have to do this awful hack because python dbus need to guess the signature
return self.db_req_iface.gatewayRegister(action, target, data, profile_key)

View file

View file

@ -0,0 +1,28 @@
#!/usr/bin/python
#-*- coding: utf-8 -*-
"""
SAT communication bridge
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/>.
"""
class BridgeFrontend:
def __init__(self):
print "Bridge frontend initialization"
def register(self, functionName, handler):
raise NotImplementedError

164
frontends/src/jp/fr.po Normal file
View file

@ -0,0 +1,164 @@
# JP French Translation.
# Copyright (C) 2009, 2010 Jérôme Poisson
# This file is distributed under the same license as the jp package.
# Jérôme Poisson <goffi@goffi.org>, 2009, 2010.
# Goffi <goffi@goffi.org>, 2010.
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.0.2\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-03-05 19:08+1100\n"
"PO-Revision-Date: 2010-03-05 19:24+1100\n"
"Last-Translator: Goffi <goffi@goffi.org>\n"
"Language-Team: French <goffi@goffi.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: jp:63
msgid ""
"ProgressBar not available, please download it at http://pypi.python.org/pypi/"
"progressbar"
msgstr ""
"ProgressBar n'est pas disponible, veuillez le télécharger à http://pypi."
"python.org/pypi/progressbar"
#: jp:64
msgid ""
"Progress bar deactivated\n"
"--\n"
msgstr ""
"Barre de progression désactivée\n"
"--\n"
#: jp:77
msgid ""
"\n"
" %prog [options] [FILE1 FILE2 ...] JID\n"
" %prog -w [options] [JID1 JID2 ...]\n"
"\n"
" %prog --help for options list\n"
" "
msgstr ""
"\n"
" %prog [options] [FICHIER1 FICHIER2 ...] JID\n"
" %prog -w [options] [JID1 JID2 ...]\n"
"\n"
" %prog --help pour la liste des options\n"
" "
#: jp:86
msgid "Make a bzip2 tarball"
msgstr "Fait un fichier compressé bzip2"
#: jp:88
msgid "Wait for a file to be sent by a contact"
msgstr "Attend qu'un fichier soit envoyé par un contact"
#: jp:90
msgid "Accept multiple files (you'll have to stop manually)"
msgstr ""
"Accepte plusieurs fichiers (vous devrez arrêter le programme à la main)"
#: jp:92
msgid "Force overwritting of existing files"
msgstr "Force le remplacement des fichiers existants"
#: jp:94
msgid "Show progress bar"
msgstr "Affiche la barre de progression"
#: jp:96
msgid ""
"Separate xmpp messages: send one message per line instead of one message "
"alone."
msgstr ""
"Sépare les messages xmpp: envoi un message par ligne plutôt qu'un seul "
"message global."
#: jp:98
msgid "Add a new line at the beginning of the input (usefull for ascii art ;))"
msgstr ""
"Ajoute un saut de ligne au début de l'entrée (utile pour l'art ascii ;))"
#: jp:103
msgid "You must specify the destination JID (Jabber ID)"
msgstr "Vous devez préciser le JID (Jabber ID) de destination"
#: jp:112
#, python-format
msgid "%s is not a valid JID !"
msgstr "%s n'est pas un JID valide !"
#: jp:118
msgid "Option progress is not available, deactivated."
msgstr ""
"L'option « progress » (barre de progression) n'est pas disponible, elle est "
"désactivée."
#: jp:131
msgid "SAT is not conneted, please connect before using jp"
msgstr "SAT n'est pas connecté, veuillez le connecter avant d'utiliser jp"
#: jp:155
#, python-format
msgid "File [%s] doesn't exist !"
msgstr "Le fichier [%s] n'existe pas !"
#: jp:158
#, python-format
msgid "[%s] is a dir ! Please send files inside or use compression"
msgstr ""
"[%s] est un répertoire ! Veuillez envoyer les fichiers qu'il contient ou "
"utiliser la compression."
#: jp:164
#, python-format
msgid "tmp file (%s) already exists ! Please remove it"
msgstr "le fichier temporaire (%s) existe déjà ! Veuillez le supprimer"
#: jp:166
msgid "bz2 is an experimental option at an early dev stage, use with caution"
msgstr ""
"bz2 est une option expérimentale à un stade de développement peu avancé, "
"utilisez-là avec prudence"
#: jp:168
msgid "Starting compression, please wait..."
msgstr "Lancement de la compression, veuillez patienter..."
#: jp:172
#, python-format
msgid "Adding %s"
msgstr "Ajout de %s"
#: jp:175
msgid "OK !"
msgstr "C'est parti !"
#: jp:196
#, python-format
msgid "Accepted file [%(filename)s] from %(sender)s"
msgstr "Le fichier [%(filename)s] de %(sender)s a été accepté"
#: jp:200
#, python-format
msgid ""
"Refused file [%(filename)s] from %(sender)s: a file with the same name "
"already exist"
msgstr ""
"Le fichier [%(filename)s] de %(sender)s a été refusé: un fichier avec le "
"même nom existe déjà"
#: jp:209
msgid "FIXME: actionResult not implemented"
msgstr "CORRIGEZ-MOI: actionResult n'est pas implémenté"
#: jp:223
msgid "Progress: "
msgstr "Progression: "
#: jp:254
msgid "User interruption: good bye"
msgstr "Interrompu par l'utilisateur: au revoir"

Binary file not shown.

272
frontends/src/jp/jp Executable file
View file

@ -0,0 +1,272 @@
#! /usr/bin/python
# -*- coding: utf-8 -*-
"""
jp: a SAT command line tool
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/>.
"""
#consts
name = u"jp"
about = name+u""" v%s (c) Jérôme Poisson (aka Goffi) 2009, 2010
---
"""+name+u""" Copyright (C) 2009, 2010 Jérôme Poisson (aka Goffi)
This program comes with ABSOLUTELY NO WARRANTY;
This is free software, and you are welcome to redistribute it
under certain conditions.
---
This software is a command line tool for jabber
Get the latest version at http://www.goffi.org
"""
global pbar_available
pbar_available = True #checked before using ProgressBar
### logging ###
import logging
from logging import debug, info, error, warning
logging.basicConfig(level=logging.DEBUG,
format='%(message)s')
###
import gettext
gettext.install('jp', "i18n", unicode=True)
import sys
import os
from os.path import abspath, basename, dirname
from optparse import OptionParser
import pdb
from tools.jid import JID
import gobject
from sat_bridge_frontend.DBus import DBusBridgeFrontend,BridgeExceptionNoService
import tarfile
try:
from progressbar import ProgressBar, Percentage, Bar, ETA, FileTransferSpeed
except ImportError, e:
info (_('ProgressBar not available, please download it at http://pypi.python.org/pypi/progressbar'))
info (_('Progress bar deactivated\n--\n'))
pbar_available=False
class JP():
def __init__(self):
try:
self.bridge=DBusBridgeFrontend()
except BridgeExceptionNoService:
print(_(u"Can't connect to SàT backend, are you sure it's launched ?"))
import sys
sys.exit(1)
self.transfert_id = None
def check_options(self):
"""Check command line options"""
usage=_("""
%prog [options] [FILE1 FILE2 ...] JID
%prog -w [options] [JID1 JID2 ...]
%prog --help for options list
""")
version = unicode(self.bridge.getVersion())
parser = OptionParser(usage=usage,version=about % version)
parser.add_option("-p", "--profile", action="store", type="string", default='@DEFAULT@',
help=_("Use PROFILE profile key (default: %default)"))
parser.add_option("-b", "--bz2", action="store_true", default=False,
help=_("Make a bzip2 tarball"))
parser.add_option("-w", "--wait-file", action="store_true", default=False,
help=_("Wait for a file to be sent by a contact"))
parser.add_option("-m", "--multiple", action="store_true", default=False,
help=_("Accept multiple files (you'll have to stop manually)"))
parser.add_option("-f", "--force", action="store_true", default=False,
help=_("Force overwritting of existing files"))
parser.add_option("-g", "--progress", action="store_true", default=False,
help=_("Show progress bar"))
parser.add_option("-s", "--separate", action="store_true", default=False,
help=_("Separate xmpp messages: send one message per line instead of one message alone."))
parser.add_option("-n", "--new-line", action="store_true", default=False,
help=_("Add a new line at the beginning of the input (usefull for ascii art ;))"))
(self.options, args) = parser.parse_args()
if len(args) < 1 and not self.options.wait_file:
parser.error(_("You must specify the destination JID (Jabber ID)").encode('utf-8'))
if self.options.wait_file:
#several jid
self.dest_jids = args
else:
#one dest_jid, other args are files
self.dest_jid = JID(args[-1])
if not self.dest_jid.is_valid:
error (_("%s is not a valid JID !"), self.dest_jid)
exit(1)
self.files = args[:-1]
if not pbar_available and self.options.progress:
self.options.progress = False
error (_("Option progress is not available, deactivated."))
if self.options.progress or self.options.wait_file:
self.start_loop = True #We have to use loop for these options
else:
self.start_loop = False
return args
def check_jabber_status(self):
"""Check that jabber status is allright"""
self.profile = self.bridge.getProfileName(self.options.profile)
if not self.profile:
error(_("The profile asked doesn't exist"))
exit(1)
if not self.bridge.isConnected(self.profile):
error(_(u"SàT is not conneted, please connect before using jp"))
exit(1)
def send_stdin(self):
"""Send incomming data on stdin to jabber contact"""
header = "\n" if self.options.new_line else ""
if self.options.separate: #we send stdin in several messages
if header:
self.bridge.sendMessage(self.dest_jid, header, profile_key=self.profile)
while (True):
line = sys.stdin.readline()
if not line:
break
self.bridge.sendMessage(self.dest_jid, line.replace("\n",""), profile_key=self.profile)
else:
self.bridge.sendMessage(self.dest_jid, header + "".join(sys.stdin.readlines()), profile_key=self.profile)
def send_files(self):
"""Send files to jabber contact"""
for file in self.files:
if not os.path.exists(file):
error (_("File [%s] doesn't exist !") % file)
exit(1)
if not self.options.bz2 and os.path.isdir(file):
error (_("[%s] is a dir ! Please send files inside or use compression") % file)
exit(1)
if self.options.bz2:
tmpfile = (basename(self.files[0]) or basename(dirname(self.files[0])) ) + '.tar.bz2' #FIXME: tmp, need an algorithm to find a good name/path
if os.path.exists(tmpfile):
error (_("tmp file (%s) already exists ! Please remove it"), tmpfile)
exit(1)
warning(_("bz2 is an experimental option at an early dev stage, use with caution"))
#FIXME: check free space, writting perm, tmp dir, filename (watch for OS used)
info(_("Starting compression, please wait..."))
sys.stdout.flush()
bz2=tarfile.open(tmpfile, "w:bz2")
for file in self.files:
info(_("Adding %s"), file)
bz2.add(file)
bz2.close()
info(_("OK !"))
path = abspath(tmpfile)
self.transfert_id = self.bridge.sendFile(self.dest_jid, path, profile_key=self.profile)
else:
for file in self.files:
path = abspath(file)
self.transfert_id = self.bridge.sendFile(self.dest_jid, path, profile_key=self.profile) #FIXME: show progress only for last transfert_id
#TODO: manage ProgressBar
def askConfirmation(self, type, id, data):
"""CB used for file transfert, accept files depending on parameters"""
answer_data={}
if type == "FILE_TRANSFERT":
if self.dest_jids and not data['from'] in self.dest_jids:
return #file is not sent by a filtered jid
answer_data["dest_path"] = os.getcwd()+'/'+data['filename']
if self.options.force or not os.path.exists(answer_data["dest_path"]):
self.bridge.confirmationAnswer(id, True, answer_data)
info(_("Accepted file [%(filename)s] from %(sender)s") % {'filename':data['filename'], 'sender':data['from']})
self.transfert_id = id
else:
self.bridge.confirmationAnswer(id, False, answer_data)
warning(_("Refused file [%(filename)s] from %(sender)s: a file with the same name already exist") % {'filename':data['filename'], 'sender':data['from']})
if not self.options.multiple and not self.options.progress:
#we just accept one file
self.loop.quit()
def actionResult(self, type, id, data):
#FIXME
info (_("FIXME: actionResult not implemented"))
def wait_file(self):
"""Wait for a file and write it on local dir"""
self.bridge.register("askConfirmation", self.askConfirmation, "request")
def progressCB(self):
if self.transfert_id:
data = self.bridge.getProgress(self.transfert_id)
if data:
if not data['position']:
data['position'] = '0'
if not self.pbar:
#first answer, we must construct the bar
self.pbar = ProgressBar(int(data['size']),[_("Progress: "),Percentage()," ",Bar()," ",FileTransferSpeed()," ",ETA()])
self.pbar.start()
self.pbar.update(int(data['position']))
elif self.pbar:
self.pbar.finish()
if not self.options.multiple:
self.loop.quit()
return False
return True
def go(self):
self.check_options()
self.check_jabber_status()
if self.options.wait_file:
self.wait_file()
else:
if not self.files: #we send message only if there are no files to send
self.send_stdin()
else:
self.send_files()
if self.start_loop:
self.loop = gobject.MainLoop()
if self.options.progress:
self.pbar = None
gobject.timeout_add(10, self.progressCB)
try:
self.loop.run()
except KeyboardInterrupt:
info(_("User interruption: good bye"))
if __name__ == "__main__":
jp = JP()
jp.go()

142
frontends/src/jp/jp.po Normal file
View file

@ -0,0 +1,142 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-03-05 19:08+1100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: jp:63
msgid ""
"ProgressBar not available, please download it at http://pypi.python.org/pypi/"
"progressbar"
msgstr ""
#: jp:64
msgid ""
"Progress bar deactivated\n"
"--\n"
msgstr ""
#: jp:77
msgid ""
"\n"
" %prog [options] [FILE1 FILE2 ...] JID\n"
" %prog -w [options] [JID1 JID2 ...]\n"
"\n"
" %prog --help for options list\n"
" "
msgstr ""
#: jp:86
msgid "Make a bzip2 tarball"
msgstr ""
#: jp:88
msgid "Wait for a file to be sent by a contact"
msgstr ""
#: jp:90
msgid "Accept multiple files (you'll have to stop manually)"
msgstr ""
#: jp:92
msgid "Force overwritting of existing files"
msgstr ""
#: jp:94
msgid "Show progress bar"
msgstr ""
#: jp:96
msgid ""
"Separate xmpp messages: send one message per line instead of one message "
"alone."
msgstr ""
#: jp:98
msgid "Add a new line at the beginning of the input (usefull for ascii art ;))"
msgstr ""
#: jp:103
msgid "You must specify the destination JID (Jabber ID)"
msgstr ""
#: jp:112
#, python-format
msgid "%s is not a valid JID !"
msgstr ""
#: jp:118
msgid "Option progress is not available, deactivated."
msgstr ""
#: jp:131
msgid "SAT is not conneted, please connect before using jp"
msgstr ""
#: jp:155
#, python-format
msgid "File [%s] doesn't exist !"
msgstr ""
#: jp:158
#, python-format
msgid "[%s] is a dir ! Please send files inside or use compression"
msgstr ""
#: jp:164
#, python-format
msgid "tmp file (%s) already exists ! Please remove it"
msgstr ""
#: jp:166
msgid "bz2 is an experimental option at an early dev stage, use with caution"
msgstr ""
#: jp:168
msgid "Starting compression, please wait..."
msgstr ""
#: jp:172
#, python-format
msgid "Adding %s"
msgstr ""
#: jp:175
msgid "OK !"
msgstr ""
#: jp:196
#, python-format
msgid "Accepted file [%(filename)s] from %(sender)s"
msgstr ""
#: jp:200
#, python-format
msgid ""
"Refused file [%(filename)s] from %(sender)s: a file with the same name "
"already exist"
msgstr ""
#: jp:209
msgid "FIXME: actionResult not implemented"
msgstr ""
#: jp:223
msgid "Progress: "
msgstr ""
#: jp:254
msgid "User interruption: good bye"
msgstr ""

View file

@ -0,0 +1,335 @@
#!/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
from tools.games import TarotCard
from quick_frontend.quick_card_game import QuickCardGame
from xmlui import XMLUI
from urwid_satext import sat_widgets
class CardDisplayer(urwid.Text):
"""Show a card"""
signals = ['click']
def __init__(self, card):
self.__selected = False
self.card = card
urwid.Text.__init__(self, card.getAttrText())
def selectable(self):
return True
def keypress(self, size, key):
if key == ' ':
self.select(not self.__selected)
self._emit('click')
return key
def mouse_event(self, size, event, button, x, y, focus):
if urwid.is_mouse_press(event) and button == 1:
self.select(not self.__selected)
self._emit('click')
return True
return False
def select(self, state=True):
self.__selected = state
attr,txt = self.card.getAttrText()
if self.__selected:
attr+='_selected'
self.set_text((attr,txt))
self._invalidate()
def isSelected(self):
return self.__selected
def getCard(self):
return self.card
def render(self, size, focus=False):
canvas = urwid.CompositeCanvas(urwid.Text.render(self, size, focus))
if focus:
canvas.set_cursor((0,0))
return canvas
class Hand(urwid.WidgetWrap):
"""Used to display several cards, and manage a hand"""
signals = ['click']
def __init__(self, hand=[], selectable = False, on_click=None, user_data=None):
"""@param hand: list of Card"""
self.__selectable = selectable
self.columns = urwid.Columns([],dividechars=1)
if on_click:
urwid.connect_signal(self, 'click', on_click, user_data)
if hand:
self.update(hand)
urwid.WidgetWrap.__init__(self, self.columns)
def selectable(self):
return self.__selectable
def keypress(self, size, key):
if CardDisplayer in [wid.__class__ for wid in self.columns.widget_list]:
return self.columns.keypress(size,key)
else:
#No card displayed, we still have to manage the clicks
if key == ' ':
self._emit('click', None)
return key
def getSelected(self):
"""Return a list of selected cards"""
_selected = []
for wid in self.columns.widget_list:
if isinstance(wid, CardDisplayer) and wid.isSelected():
_selected.append(wid.getCard())
return _selected
def update(self, hand):
"""Update the hand displayed in this widget
@param hand: list of Card"""
del self.columns.widget_list[:]
del self.columns.column_types[:]
self.columns.widget_list.append(urwid.Text(''))
self.columns.column_types.append(('weight',1))
for card in hand:
widget = CardDisplayer(card)
self.columns.widget_list.append(widget)
self.columns.column_types.append(('fixed',3))
urwid.connect_signal(widget, 'click', self.__onClick)
self.columns.widget_list.append(urwid.Text(''))
self.columns.column_types.append(('weight',1))
self.columns.set_focus(1)
def __onClick(self,card_wid):
self._emit('click', card_wid)
class Card(TarotCard):
"""This class is used to represent a card, logically
and give a text representation with attributes"""
SIZE = 3 #size of a displayed card
def __init__(self, suit, value):
"""@param file: path of the PNG file"""
TarotCard.__init__(self, (suit, value))
def getAttrText(self):
"""return text representation of the card with attributes"""
try:
value = "%02i" % int(self.value)
except ValueError:
value = self.value[0].upper()+self.value[1]
if self.suit == "atout":
if self.value == "excuse":
suit = 'c'
else:
suit = 'A'
color = 'neutral'
elif self.suit == "pique":
suit = u'♠'
color = 'black'
elif self.suit == "trefle":
suit = u'♣'
color = 'black'
elif self.suit == "coeur":
suit = u'♥'
color = 'red'
elif self.suit == "carreau":
suit = u'♦'
color = 'red'
if self.bout:
color = 'special'
return ('card_%s' % color,u"%s%s" % (value,suit))
def getWidget(self):
"""Return a widget representing the card"""
return CardDisplayer(self)
class Table(urwid.FlowWidget):
"""Represent the cards currently on the table"""
def __init__(self):
self.top = self.left = self.bottom = self.right = None
def putCard(self, location, card):
"""Put a card on the table
@param location: where to put the card (top, left, bottom or right)
@param card: Card to play or None"""
assert location in ['top','left','bottom','right']
assert isinstance(card,Card) or card == None
if [getattr(self, place) for place in ['top','left','bottom','right']].count(None) == 0:
#If the table is full of card, we remove them
self.top = self.left = self.bottom = self.right = None
setattr(self, location, card)
self._invalidate()
def rows(self,size,focus=False):
return self.display_widget(size, focus).rows(size, focus)
def render(self, size, focus=False):
return self.display_widget(size, focus).render(size, focus)
def display_widget(self, size, focus):
cards={}
max_col, = size
separator = " - "
margin = max((max_col-Card.SIZE)/2,0) * ' '
margin_center = max((max_col-Card.SIZE*2-len(separator))/2,0) * ' '
for location in ['top', 'left', 'bottom', 'right']:
card = getattr(self,location)
cards[location] = card.getAttrText() if card else Card.SIZE * ' '
render_wid = [urwid.Text([margin,cards['top']]),
urwid.Text([margin_center,cards['left'],separator,cards['right']]),
urwid.Text([margin,cards['bottom']])]
return urwid.Pile(render_wid)
class CardGame(QuickCardGame,urwid.WidgetWrap):
"""Widget for card games"""
def __init__(self, parent, referee, players, player_nick):
QuickCardGame.__init__(self, parent, referee, players, player_nick)
self.loadCards()
self.top = urwid.Pile([urwid.Padding(urwid.Text(self.top_nick), 'center')])
#self.parent.host.debug()
self.table = Table()
self.center = urwid.Columns([('fixed',len(self.left_nick),urwid.Filler(urwid.Text(self.left_nick))),
urwid.Filler(self.table),
('fixed',len(self.right_nick),urwid.Filler(urwid.Text(self.right_nick)))
])
"""urwid.Pile([urwid.Padding(self.top_card_wid,'center'),
urwid.Columns([('fixed',len(self.left_nick),urwid.Text(self.left_nick)),
urwid.Padding(self.center_cards_wid,'center'),
('fixed',len(self.right_nick),urwid.Text(self.right_nick))
]),
urwid.Padding(self.bottom_card_wid,'center')
])"""
self.hand_wid = Hand(selectable = True, on_click = self.onClick)
self.main_frame = urwid.Frame(self.center,header=self.top, footer=self.hand_wid, focus_part='footer')
urwid.WidgetWrap.__init__(self,self.main_frame)
self.parent.host.bridge.tarotGameReady(player_nick, referee, profile_key = self.parent.host.profile)
def loadCards(self):
"""Load all the cards in memory"""
QuickCardGame.loadCards(self)
for value in map(str,range(1,22))+['excuse']:
card = Card('atout',value)
self.cards[card.suit, card.value]=card
self.deck.append(card)
for suit in ["pique", "coeur", "carreau", "trefle"]:
for value in map(str,range(1,11))+["valet","cavalier","dame","roi"]:
card = Card(suit,value)
self.cards[card.suit, card.value]=card
self.deck.append(card)
def newGame(self, hand):
"""Start a new game, with given hand"""
QuickCardGame.newGame(self, hand)
self.hand_wid.update(self.hand)
self.parent.host.redraw()
def contratSelected(self, data):
"""Called when the contrat has been choosed
@param data: form result"""
contrat = data[0][1]
QuickCardGame.contratSelected(self, contrat)
def chooseContrat(self, xml_data):
"""Called when the player as to select his contrat
@param xml_data: SàT xml representation of the form"""
misc = {'callback': self.contratSelected}
form = XMLUI(self.parent.host, xml_data, title = _('Please choose your contrat'), options = ['NO_CANCEL'], misc = misc)
form.show()
def showCards(self, game_stage, cards, data):
"""Display cards in the middle of the game (to show for e.g. chien ou poignée)"""
QuickCardGame.showCards(self, game_stage, cards, data)
self.center.widget_list[1] = urwid.Filler(Hand(self.to_show))
self.parent.host.redraw()
def myTurn(self):
QuickCardGame.myTurn(self)
def showScores(self, xml_data, winners, loosers):
"""Called when the player as to select hist contrat
@param xml_data: SàT xml representation of the form"""
form = XMLUI(self.parent.host, xml_data, title = _('You win \o/') if self.player_nick in winners else _('You loose :('), options = ['NO_CANCEL'])
form.show()
def invalidCards(self, phase, played_cards, invalid_cards):
"""Invalid cards have been played
@param phase: phase of the game
@param played_cards: all the cards played
@param invalid_cards: cards which are invalid"""
QuickCardGame.invalidCards(self, phase, played_cards, invalid_cards)
self.hand_wid.update(self.hand)
if self._autoplay==None: #No dialog if there is autoplay
self.parent.host.notify(_('Cards played are invalid !'))
self.parent.host.redraw()
def cardsPlayed(self, player, cards):
"""A card has been played by player"""
QuickCardGame.cardsPlayed(self, player, cards)
self.table.putCard(self.getPlayerLocation(player),self.played[player])
self.parent.host.redraw()
##EVENTS##
def onClick(self, hand, card_wid):
"""Called when user do an action on the hand"""
if not self.state in ['play','ecart','wait_for_ecart']:
#it's not our turn, we ignore the click
card_wid.select(False)
return
if isinstance(self.center.widget_list[1].original_widget, Hand): #if we have a hand displayed
self.center.widget_list[1] = urwid.Filler(self.table) #we show again the table
if self.state == "chien":
self.to_show = []
self.state = "wait"
elif self.state == "wait_for_ecart":
self.state = "ecart"
self.hand.extend(self.to_show)
self.hand.sort()
self.to_show = []
self.hand_wid.update(self.hand)
if self.state == "ecart":
if len(self.hand_wid.getSelected()) == 6:
pop_up_widget = sat_widgets.ConfirmDialog(_("Do you put these cards in chien ?"), yes_cb=self.onEcartDone, no_cb=self.parent.host.removePopUp)
self.parent.host.showPopUp(pop_up_widget)
elif self.state == "play":
card = card_wid.getCard()
self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, [(card.suit, card.value)], profile_key = self.parent.host.profile)
self.hand.remove(card)
self.hand_wid.update(self.hand)
self.state = "wait"
def onEcartDone(self,button):
"""Called when player has finished is écart"""
ecart = []
for card in self.hand_wid.getSelected():
ecart.append((card.suit, card.value))
self.hand.remove(card)
self.hand_wid.update(self.hand)
self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, ecart, profile_key = self.parent.host.profile)
self.state = "wait"
self.parent.host.removePopUp()

View file

@ -0,0 +1,279 @@
#!/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
from quick_frontend.quick_contact_list import QuickContactList
from quick_frontend.quick_chat import QuickChat
from urwid_satext import sat_widgets
import time
from tools.jid import JID
from card_game import CardGame
from urwid_satext.files_management import FileDialog
class ChatText(urwid.FlowWidget):
"""Manage the printing of chat message"""
def __init__(self, parent, timestamp, nick, my_mess, message, align='left'):
self.parent = parent
self.timestamp = time.localtime(timestamp)
self.nick = nick
self.my_mess = my_mess
self.message = unicode(message)
self.align = align
def selectable(self):
return True
def keypress(self, size, key):
return key
def rows(self,size,focus=False):
return self.display_widget(size, focus).rows(size, focus)
def render(self, size, focus=False):
canvas = urwid.CompositeCanvas(self.display_widget(size, focus).render(size, focus))
if focus:
canvas.set_cursor(self.get_cursor_coords(size))
return canvas
def get_cursor_coords(self, size):
#(maxcol,) = size
return 0, 0
def display_widget(self, size, focus):
render_txt = []
if self.parent.show_timestamp:
time_format = "%c" if self.timestamp < self.parent.day_change else "%H:%M" #if the message was sent before today, we print the full date
render_txt.append(('date',"[%s]" % time.strftime(time_format, self.timestamp).decode('utf-8')))
if self.parent.show_short_nick:
render_txt.append(('my_nick' if self.my_mess else 'other_nick',"**" if self.my_mess else "*"))
else:
render_txt.append(('my_nick' if self.my_mess else 'other_nick',"[%s] " % self.nick))
render_txt.append(self.message)
return urwid.Text(render_txt, align=self.align)
class Chat(urwid.WidgetWrap, QuickChat):
def __init__(self, target, host, type='one2one'):
self.target = target
QuickChat.__init__(self, target, host, type)
self.content = urwid.SimpleListWalker([])
self.text_list = urwid.ListBox(self.content)
self.chat_widget = urwid.Frame(self.text_list)
self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)])
self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)])
self.pile = urwid.Pile([self.chat_colums])
urwid.WidgetWrap.__init__(self, self.__getDecoration(self.pile))
self.setType(type)
self.day_change = time.strptime(time.strftime("%a %b %d 00:00:00 %Y")) #struct_time of day changing time
self.show_timestamp = True
self.show_short_nick = False
self.show_title = 1 #0: clip title; 1: full title; 2: no title
self.subject = None
def keypress(self, size, key):
if key == "meta p": #user wants to (un)hide the presents panel
if self.type == 'group':
widgets = self.chat_colums.widget_list
if self.present_panel in widgets:
self.__removePresentPanel()
else:
self.__appendPresentPanel()
elif key == "meta t": #user wants to (un)hide timestamp
self.show_timestamp = not self.show_timestamp
for wid in self.content:
wid._invalidate()
elif key == "meta n": #user wants to (not) use short nick
self.show_short_nick = not self.show_short_nick
for wid in self.content:
wid._invalidate()
elif key == "meta l": #user wants to (un)hide widget decoration
show = not isinstance(self._w, sat_widgets.LabelLine)
self.showDecoration(show)
self._invalidate()
elif key == "meta s": #user wants to (un)hide group's subject or change its apperance
if self.subject:
self.show_title = (self.show_title + 1) % 3
if self.show_title == 0:
self.setSubject(self.subject,'clip')
elif self.show_title == 1:
self.setSubject(self.subject,'space')
elif self.show_title == 2:
self.chat_widget.header = None
self._invalidate()
return super(Chat, self).keypress(size, key)
def getMenu(self):
"""Return Menu bar"""
menu = sat_widgets.Menu(self.host.loop)
if self.type == 'group':
game = _("Game")
menu.addMenu(game, "Tarot", self.onTarotRequest)
elif self.type == 'one2one':
menu.addMenu(_("Action"), _("Send file"), self.onSendFileRequest)
return menu
def setType(self, type):
QuickChat.setType(self, type)
if type == 'one2one':
self.historyPrint(profile=self.host.profile)
elif type == 'group':
if len(self.chat_colums.widget_list) == 1:
present_widget = self.__buildPresentList()
self.present_panel = sat_widgets.VerticalSeparator(present_widget)
self.__appendPresentPanel()
def __getDecoration(self, widget):
return sat_widgets.LabelLine(widget, sat_widgets.SurroundedText(unicode(self.target)))
def showDecoration(self, show=True):
"""Show/Hide the decoration around the chat window"""
if show:
main_widget = self.__getDecoration(self.pile)
else:
main_widget = self.pile
self._w = main_widget
def __buildPresentList(self):
self.present_wid = sat_widgets.GenericList([],option_type = sat_widgets.ClickableText)
return self.present_wid
def __appendPresentPanel(self):
self.chat_colums.widget_list.append(self.present_panel)
self.chat_colums.column_types.append(('weight', 2))
def __removePresentPanel(self):
self.chat_colums.set_focus(0) #necessary as the focus change to the next object, we can go out of range if we are on the last object of self.chat_colums
self.chat_colums.widget_list.remove(self.present_panel)
del self.chat_colums.column_types[-1]
def __appendGamePanel(self, widget):
assert (len(self.pile.widget_list) == 1)
self.pile.widget_list.insert(0,widget)
self.pile.item_types.insert(0,('weight', 1))
self.pile.widget_list.insert(1,urwid.Filler(urwid.Divider('-')))
self.pile.item_types.insert(1,('fixed', 1))
self.host.redraw()
def __removeGamePanel(self):
assert (len(self.pile.widget_list) == 3)
self.pile.set_focus(0) #necessary as the focus change to the next object, we can go out of range if we are on the last object of self.chat_colums
del self.pile.widget_list[0]
del self.pile.item_types[0]
self.host.redraw()
def setSubject(self, subject, wrap='space'):
"""Set title for a group chat"""
QuickChat.setSubject(self, subject)
self.subject = subject
self.subj_wid = urwid.Text(unicode(subject.replace('\n','|') if wrap == 'clip' else subject ),
align='left' if wrap=='clip' else 'center',wrap=wrap)
self.chat_widget.header = urwid.AttrMap(self.subj_wid,'title')
self.host.redraw()
def setPresents(self, param_nicks):
"""Set the users presents in the contact list for a group chat
@param nicks: list of nicknames
"""
nicks = [unicode(nick) for nick in param_nicks] #FIXME: should be done in DBus bridge
nicks.sort()
QuickChat.setPresents(self, nicks)
self.present_wid.changeValues(nicks)
self.host.redraw()
def replaceUser(self, param_nick):
"""Add user if it is not in the group list"""
nick = unicode(param_nick) #FIXME: should be done in DBus bridge
if "facebook" in nick:
self.host.debug()
QuickChat.replaceUser(self, nick)
presents = self.present_wid.getAllValues()
if nick not in presents:
presents.append(nick)
presents.sort()
self.present_wid.changeValues(presents)
self.host.redraw()
def removeUser(self, param_nick):
"""Remove a user from the group list"""
nick = unicode(param_nick) #FIXME: should be done in DBus bridge
QuickChat.removeUser(self, nick)
self.present_wid.deleteValue(nick)
self.host.redraw()
def printMessage(self, from_jid, msg, profile, timestamp=""):
assert isinstance(from_jid, JID)
try:
jid,nick,mymess = QuickChat.printMessage(self, from_jid, msg, profile, timestamp)
except TypeError:
return
my_jid = self.host.profiles[profile]['whoami']
self.content.append(ChatText(self, timestamp or None, nick, mymess, msg))
self.text_list.set_focus(len(self.content)-1)
self.host.redraw()
def printInfo(self, msg, type='normal'):
"""Print general info
@param msg: message to print
@type: one of:
normal: general info like "toto has joined the room"
me: "/me" information like "/me clenches his fist" ==> "toto clenches his fist"
"""
self.content.append(sat_widgets.ClickableText(msg))
self.text_list.set_focus(len(self.content)-1)
self.host.redraw()
def startGame(self, game_type, referee, players):
"""Configure the chat window to start a game"""
if game_type=="Tarot":
try:
self.tarot_wid = CardGame(self, referee, players, self.nick)
self.__appendGamePanel(self.tarot_wid)
except e:
self.host.debug()
def getGame(self, game_type):
"""Return class managing the game type"""
#TODO: check that the game is launched, and manage errors
if game_type=="Tarot":
return self.tarot_wid
#MENU EVENTS#
def onTarotRequest(self, menu):
if len(self.occupants) != 4:
self.host.showPopUp(sat_widgets.Alert(_("Can't start game"), _("You need to be exactly 4 peoples in the room to start a Tarot game"), ok_cb=self.host.removePopUp))
else:
self.host.bridge.tarotGameCreate(self.id, list(self.occupants), self.host.profile)
def onSendFileRequest(self, menu):
dialog = FileDialog(ok_cb=self.onFileSelected, cancel_cb=self.host.removePopUp)
self.host.showPopUp(dialog, 80, 80)
#MISC EVENTS#
def onFileSelected(self, filepath):
self.host.removePopUp()
full_jid = self.host.CM.get_full(self.target)
id = self.host.bridge.sendFile(full_jid, filepath)
self.host.addProgress(id,filepath)

View file

@ -0,0 +1,183 @@
#!/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
from quick_frontend.quick_contact_list import QuickContactList
from tools.jid import JID
from urwid_satext import sat_widgets
class ContactList(urwid.WidgetWrap, QuickContactList):
signals = ['click','change']
def __init__(self, host, CM, on_click=None, on_change=None, user_data=None):
self.host = host
self.selected = None
self.groups={}
self.alert_jid=set()
#we now build the widget
self.frame = urwid.Frame(self.__buildList())
self.main_widget = sat_widgets.LabelLine(self.frame, sat_widgets.SurroundedText(_("Contacts")))
urwid.WidgetWrap.__init__(self, self.main_widget)
if on_click:
urwid.connect_signal(self, 'click', on_click, user_data)
if on_change:
urwid.connect_signal(self, 'change', on_change, user_data)
QuickContactList.__init__(self, CM)
def __contains__(self, jid):
for group in self.groups:
if jid.short in self.groups[group][1]:
return True
return False
def setFocus(self, name):
"""give focus to the first group or contact with the given name"""
idx = 0
for widget in self.frame.body.body:
if widget.getValue() == name:
self.frame.body.set_focus(idx)
return
idx+=1
def putAlert(self, jid):
"""Put an alert on the jid to get attention from user (e.g. for new message)"""
self.alert_jid.add(jid.short)
self.frame.body = self.__buildList()
self.host.redraw()
def __groupClicked(self, group_wid):
group = self.groups[group_wid.getValue()]
group[0] = not group[0]
self.frame.body = self.__buildList()
self.host.redraw()
self.setFocus(group_wid.getValue())
def __contactClicked(self, contact_wid, selected):
self.selected = contact_wid.data
for widget in self.frame.body.body:
if widget.__class__ == sat_widgets.SelectableText:
widget.setState(widget.data == self.selected, invisible=True)
if self.selected in self.alert_jid:
self.alert_jid.remove(self.selected)
self.frame.body = self.__buildList()
self.host.redraw()
self._emit('click')
def __buildContact(self, content, param_contacts):
"""Add contact representation in widget list
@param content: widget list, e.g. SimpleListWalker
@param contacts: list of JID"""
contacts = list(param_contacts)
contacts.sort()
for contact in contacts:
jid=JID(contact)
name = self.CM.getAttr(jid,'name')
nick = self.CM.getAttr(jid,'nick')
display = nick or name or jid.node or jid.short
header = '(*) ' if contact in self.alert_jid else ''
widget = sat_widgets.SelectableText(('alert' if contact in self.alert_jid else 'default',display),
selected = contact==self.selected, header=header)
widget.data = contact
content.append(widget)
urwid.connect_signal(widget, 'change', self.__contactClicked)
def __buildList(self):
"""Build the main contact list widget"""
content = urwid.SimpleListWalker([])
group_keys = self.groups.keys()
group_keys.sort()
for key in group_keys:
unfolded = self.groups[key][0]
if key!=None:
header = '[-]' if unfolded else '[+]'
widget = sat_widgets.ClickableText(key,header=header+' ')
content.append(widget)
urwid.connect_signal(widget, 'click', self.__groupClicked)
if unfolded:
self.__buildContact(content, self.groups[key][1])
return urwid.ListBox(content)
def unselectAll(self):
"""Unselect all contacts"""
self.selected = None
for widget in self.frame.body.body:
if widget.__class__ == sat_widgets.SelectableText:
widget.setState(False, invisible=True)
def get_contact(self):
"""Return contact currently selected"""
return self.selected
def clear_contacts(self):
"""clear all the contact list"""
self.groups={}
self.selected = None
self.unselectAll()
self.frame.body = self.__buildList()
self.host.redraw()
def replace(self, jid, groups=[None]):
"""add a contact to the list if doesn't exist, else update it"""
assert isinstance(groups, list)
assert isinstance(jid, JID)
if not groups:
groups=[None]
for group in groups:
if not self.groups.has_key(group):
self.groups[group] = [True,set()] #[unfold,list_of_contacts]
self.groups[group][1].add(jid.short)
self.frame.body = self.__buildList()
self.host.redraw()
"""contacts = self.list_wid.getAllValues()
if jid.short not in contacts:
contacts.append(jid.short)
contacts.sort()
self.list_wid.changeValues(contacts)
self._emit('change')"""
def disconnect(self, jid):
"""mark a contact disconnected"""
self.remove(jid.short)
def remove(self, param_jid):
"""remove a contact from the list"""
groups_to_remove = []
jid = JID(param_jid)
for group in self.groups:
contacts = self.groups[group][1]
if jid.short in contacts:
contacts.remove(jid.short)
if not len(contacts):
groups_to_remove.append(group)
for group in groups_to_remove:
del self.groups[group]
self.frame.body = self.__buildList()
self.host.redraw()
def add(self, jid, param_groups=[None]):
"""add a contact to the list"""
self.replace(jid,param_groups)

View file

@ -0,0 +1,88 @@
#!/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
from urwid_satext import sat_widgets
from tools.jid import JID
from quick_frontend.quick_gateways import QuickGatewaysManager
class GatewaysManager(urwid.WidgetWrap, QuickGatewaysManager):
def __init__(self, host, gateways, title=_("Gateways manager"), server=None):
QuickGatewaysManager.__init__(self, host, gateways, server)
if server:
title+=" (%s)" % server
widget_list = urwid.SimpleListWalker([])
widget_list.append(urwid.Text(self.WARNING_MSG))
widget_list.append(urwid.Divider('-'))
for gateway in gateways:
self.addGateway(widget_list,gateway, gateways[gateway])
widget_list.append(urwid.Divider())
self.ext_serv = sat_widgets.AdvancedEdit(_("Use external XMPP server: "))
go_button = sat_widgets.CustomButton( _("GO !"),self.browseExternalServer)
ext_serv_col = urwid.Columns([self.ext_serv,('fixed',go_button.getSize(),go_button)])
widget_list.append(ext_serv_col)
list_wid = urwid.ListBox(widget_list)
decorated = sat_widgets.LabelLine(list_wid, sat_widgets.SurroundedText(title))
urwid.WidgetWrap.__init__(self, decorated)
def browseExternalServer(self, button):
"""Open the gateway manager on given server"""
server = self.ext_serv.get_edit_text()
if not server:
popup = sat_widgets.Alert(_("Error"), _("You must enter an external server JID"), ok_cb=self.host.removePopUp)
self.host.showPopUp(popup)
return
id = self.host.bridge.findGateways(server, self.host.profile)
self.host.current_action_ids.add(id)
self.host.current_action_ids_cb[id] = self.host.onGatewaysFound
self.host.removeWindow()
def addGateway(self, widget_list, gateway, param):
widget_col = []
widget_col.append(('weight',4,urwid.Text(unicode(param['name'])))) #FIXME: unicode to be remove when DBus bridge will not give dbus.String anymore
#Then the transport type message
widget_col.append(('weight',6,urwid.Text(self.getGatewayDesc(param['type']))))
#The buttons
reg_button = sat_widgets.CustomButton( _("Register"), self.onRegister)
reg_button.gateway_jid = JID(gateway)
widget_col.append(('fixed',reg_button.getSize(),reg_button))
unreg_button = sat_widgets.CustomButton( _("Unregister"), self.onUnregister)
unreg_button.gateway_jid = JID(gateway)
widget_col.append(('fixed',unreg_button.getSize(),unreg_button))
widget_list.append(urwid.Columns(widget_col,1))
def onRegister(self, button):
"""Called when register button is clicked"""
gateway_jid = button.gateway_jid
id = self.host.bridge.in_band_register(gateway_jid, self.host.profile)
self.host.current_action_ids.add(id)
def onUnregister(self, button):
"""Called when unregister button is clicked"""
gateway_jid = button.gateway_jid
id = self.host.bridge.gatewayRegister("CANCEL",gateway_jid, None, self.host.profile)
self.host.current_action_ids.add(id)

View file

@ -0,0 +1,509 @@
#!/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
from contact_list import ContactList
from chat import Chat
from gateways import GatewaysManager
from urwid_satext import sat_widgets
import logging
from logging import debug, info, error
import sys, os
from tools.jid import JID
from xmlui import XMLUI
from progress import Progress
### 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'),
('title_focus', 'white,bold', 'light gray', 'standout,underline'),
('selected', 'default', 'dark red'),
('selected_focus', 'default,bold', 'dark red'),
('default', 'default', 'default'),
('default_focus', 'default,bold', 'default'),
('alert', 'default,underline', 'default'),
('alert_focus', 'default,bold,underline', 'default'),
('date', 'light gray', 'default'),
('my_nick', 'dark red,bold', 'default'),
('other_nick', 'dark cyan,bold', 'default'),
('menubar', 'light gray,bold', 'dark red'),
('menubar_focus', 'light gray,bold', 'dark green'),
('selected_menu', 'light gray,bold', 'dark green'),
('menuitem', 'light gray,bold', 'dark red'),
('menuitem_focus', 'light gray,bold', 'dark green'),
('notifs', 'black,bold', 'yellow'),
('notifs_focus', 'dark red', 'yellow'),
('card_neutral', 'dark gray', 'white', 'standout,underline'),
('card_neutral_selected', 'dark gray', 'dark green', 'standout,underline'),
('card_special', 'brown', 'white', 'standout,underline'),
('card_special_selected', 'brown', 'dark green', 'standout,underline'),
('card_red', 'dark red', 'white', 'standout,underline'),
('card_red_selected', 'dark red', 'dark green', 'standout,underline'),
('card_black', 'black', 'white', 'standout,underline'),
('card_black_selected', 'black', 'dark green', 'standout,underline'),
('directory', 'dark cyan, bold', 'default'),
('directory_focus', 'dark cyan, bold', 'dark green'),
('separator', 'brown', 'default'),
('warning', 'light red', 'default'),
('progress_normal', 'default', 'black'),
('progress_complete', 'default', 'light red'),
]
class ChatList(QuickChatList):
"""This class manage the list of chat windows"""
def __init__(self, host):
QuickChatList.__init__(self, host)
def createChat(self, target):
return Chat(target, self.host)
class PrimitivusApp(QuickApp):
def __init__(self):
self.CM = QuickContactManagement() #FIXME: not the best place
QuickApp.__init__(self)
## main loop setup ##
self.main_widget = ProfileManager(self)
self.loop = urwid.MainLoop(self.main_widget, const_PALETTE, event_loop=urwid.GLibEventLoop(), input_filter=self.inputFilter, unhandled_input=self.keyHandler)
##misc setup##
self.chat_wins=ChatList(self)
self.notBar = sat_widgets.NotificationBar()
urwid.connect_signal(self.notBar,'change',self.onNotification)
self.progress_wid = Progress(self)
urwid.connect_signal(self.notBar.progress,'click',lambda x:self.addWindow(self.progress_wid))
self.__saved_overlay = None
def debug(self):
"""convenient method to reset screen and launch p(u)db"""
try:
import pudb
pudb.set_trace()
except:
import os,pdb
os.system('reset')
print 'Entered debug mode'
pdb.set_trace()
def writeLog(self, log, file_name='/tmp/primitivus_log'):
"""method to write log in a temporary file, useful for debugging"""
with open(file_name, 'a') as f:
f.write(log+"\n")
def redraw(self):
"""redraw the screen"""
self.loop.draw_screen()
def start(self):
self.i = 0
self.loop.set_alarm_in(0,lambda a,b: self.postInit())
self.loop.run()
def inputFilter(self, input, raw):
if self.__saved_overlay and input != ['ctrl s']:
return
for i in input:
if isinstance(i,tuple):
if i[0] == 'mouse press':
if i[1] == 4: #Mouse wheel up
input[input.index(i)] = 'up'
if i[1] == 5: #Mouse wheel down
input[input.index(i)] = 'down'
return input
def keyHandler(self, input):
if input == 'meta m':
"""User want to (un)hide the menu roller"""
try:
if self.main_widget.header == None:
self.main_widget.header = self.menu_roller
else:
self.main_widget.header = None
except AttributeError:
pass
elif input == 'ctrl n':
"""User wants to see next notification"""
self.notBar.showNext()
elif input == 'ctrl s':
"""User wants to (un)hide overlay window"""
if isinstance(self.loop.widget,urwid.Overlay):
self.__saved_overlay = self.loop.widget
self.loop.widget = self.main_widget
else:
if self.__saved_overlay:
self.loop.widget = self.__saved_overlay
self.__saved_overlay = None
elif input == 'ctrl d' and 'D' in self.bridge.getVersion(): #Debug only for dev versions
self.debug()
elif input == 'f2': #user wants to (un)hide the contact_list
try:
center_widgets = self.center_part.widget_list
if self.contactList in center_widgets:
self.center_part.set_focus(0) #necessary as the focus change to the next object, we can go out of range if we are on the last object of self.center_part
center_widgets.remove(self.contactList)
del self.center_part.column_types[0]
else:
center_widgets.insert(0, self.contactList)
self.center_part.column_types.insert(0, ('weight', 2))
except AttributeError:
#The main widget is not built (probably in Profile Manager)
pass
elif input == 'window resize':
width,height = self.loop.screen_size
if height<=5 and width<=35:
if not 'save_main_widget' in dir(self):
self.save_main_widget = self.loop.widget
self.loop.widget = urwid.Filler(urwid.Text(_("Pleeeeasse, I can't even breathe !")))
else:
if 'save_main_widget' in dir(self):
self.loop.widget = self.save_main_widget
del self.save_main_widget
try:
return self.menu_roller.checkShortcuts(input)
except AttributeError:
return input
def __buildMenuRoller(self):
menu = sat_widgets.Menu(self.loop)
general = _("General")
menu.addMenu(general, _("Connect"), self.onConnectRequest)
menu.addMenu(general, _("Disconnect"), self.onDisconnectRequest)
menu.addMenu(general, _("Parameters"), self.onParam)
menu.addMenu(general, _("About"), self.onAboutRequest)
menu.addMenu(general, _("Exit"), self.onExitRequest, 'ctrl x')
contact = _("Contact")
menu.addMenu(contact, _("Add contact"), self.onAddContactRequest)
menu.addMenu(contact, _("Remove contact"), self.onRemoveContactRequest)
communication = _("Communication")
menu.addMenu(communication, _("Join room"), self.onJoinRoomRequest, 'meta j')
menu.addMenu(communication, _("Find Gateways"), self.onFindGatewaysRequest, 'meta g')
#additionals menus
#FIXME: do this in a more generic way (in quickapp)
add_menus = self.bridge.getMenus()
def add_menu_cb(menu):
category, item = menu
id = self.bridge.callMenu(category, item, "NORMAL", self.profile)
self.current_action_ids.add(id)
for new_menu in add_menus:
category,item,type = new_menu
assert(type=="NORMAL") #TODO: manage other types
menu.addMenu(unicode(category), unicode(item), add_menu_cb)
menu_roller = sat_widgets.MenuRoller([(_('Main menu'),menu)])
return menu_roller
def __buildMainWidget(self):
self.contactList = ContactList(self, self.CM, on_click = self.contactSelected, on_change=lambda w: self.redraw())
#self.center_part = urwid.Columns([('weight',2,self.contactList),('weight',8,Chat('',self))])
self.center_part = urwid.Columns([('weight',2,self.contactList), ('weight',8,urwid.Filler(urwid.Text('')))])
self.editBar = sat_widgets.AdvancedEdit('> ')
self.editBar.setCompletionMethod(self._nick_completion)
urwid.connect_signal(self.editBar,'click',self.onTextEntered)
self.menu_roller = self.__buildMenuRoller()
self.main_widget = sat_widgets.FocusFrame(self.center_part, header=self.menu_roller, footer=self.editBar, focus_part='footer')
return self.main_widget
def _nick_completion(self, text, completion_data):
"""Completion method which complete pseudo in group chat
for params, see AdvancedEdit"""
contact = self.contactList.get_contact() ###Based on the fact that there is currently only one contact selectableat once
if contact:
chat = self.chat_wins[contact]
if chat.type != "group":
return text
space = text.rfind(" ")
start = text[space+1:]
nicks = list(chat.occupants)
nicks.sort()
try:
start_idx=nicks.index(completion_data['last_nick'])+1
if start_idx == len(nicks):
start_idx = 0
except (KeyError,ValueError):
start_idx = 0
for idx in range(start_idx,len(nicks)) + range(0,start_idx):
if nicks[idx].lower().startswith(start.lower()):
completion_data['last_nick'] = nicks[idx]
return text[:space+1] + nicks[idx] + (': ' if space < 0 else '')
return text
def plug_profile(self, profile_key='@DEFAULT@'):
self.loop.widget = self.__buildMainWidget()
QuickApp.plug_profile(self, profile_key)
def removePopUp(self, widget=None):
"Remove current pop-up, and if there is other in queue, show it"
self.loop.widget = self.main_widget
next_popup = self.notBar.getNextPopup()
if next_popup:
#we still have popup to show, we display it
self.showPopUp(next_popup)
def showPopUp(self, pop_up_widget, perc_width=40, perc_height=40):
"Show a pop-up window if possible, else put it in queue"
if not isinstance(self.loop.widget,urwid.Overlay):
display_widget = urwid.Overlay(pop_up_widget, self.main_widget, 'center', ('relative', perc_width), 'middle', ('relative', perc_height))
self.loop.widget = display_widget
self.redraw()
else:
self.notBar.addPopUp(pop_up_widget)
def notify(self, message):
""""Notify message to user via notification bar"""
self.notBar.addMessage(message)
def addWindow(self, widget):
"""Display a window if possible,
else add it in the notification bar queue
@param widget: BoxWidget"""
assert(len(self.center_part.widget_list)<=2)
wid_idx = len(self.center_part.widget_list)-1
self.center_part.widget_list[wid_idx] = widget
self.menu_roller.removeMenu(_('Chat menu'))
self.contactList.unselectAll()
self.redraw()
def removeWindow(self):
"""Remove window showed on the right column"""
#TODO: to a better Window management than this crappy hack
assert(len(self.center_part.widget_list)<=2)
wid_idx = len(self.center_part.widget_list)-1
self.center_part.widget_list[wid_idx] = urwid.Filler(urwid.Text(''))
self.center_part.set_focus(0)
self.redraw()
def addProgress (self, id, message):
"""Follow a SàT progress bar
@param id: SàT id of the progression
@param message: message to show to identify the progression"""
self.progress_wid.addProgress(id, message)
def setProgress(self, percentage):
"""Set the progression shown in notification bar"""
self.notBar.setProgress(percentage)
def contactSelected(self, contact_list):
contact = contact_list.get_contact()
if contact:
assert(len(self.center_part.widget_list)==2)
self.center_part.widget_list[1] = self.chat_wins[contact]
self.menu_roller.addMenu(_('Chat menu'), self.chat_wins[contact].getMenu())
def onTextEntered(self, editBar):
"""Called when text is entered in the main edit bar"""
contact = self.contactList.get_contact() ###Based on the fact that there is currently only one contact selectableat once
if contact:
chat = self.chat_wins[contact]
self.bridge.sendMessage(contact,
editBar.get_edit_text(),
type = "groupchat" if chat.type == 'group' else "chat",
profile_key=self.profile)
editBar.set_edit_text('')
def newMessage(self, from_jid, msg, type, to_jid, profile):
if not self.check_profile(profile):
return
QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile)
sender = JID(from_jid)
if JID(self.contactList.selected).short != sender.short:
self.contactList.putAlert(sender)
def _dialogOkCb(self, widget, data):
self.removePopUp()
answer_cb = data[0]
answer_data = [data[1]] if data[1] else []
answer_cb(True, *answer_data)
def _dialogCancelCb(self, widget, data):
self.removePopUp()
answer_cb = data[0]
answer_data = [data[1]] if data[1] else []
answer_cb(False, *answer_data)
def showDialog(self, message, title="", type="info", answer_cb = None, answer_data = None):
if type == 'info':
popup = sat_widgets.Alert(unicode(title), unicode(message), ok_cb=answer_cb or self.removePopUp) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
flags = wx.OK | wx.ICON_INFORMATION
elif type == 'error':
popup = sat_widgets.Alert(unicode(title), unicode(message), ok_cb=answer_cb or self.removePopUp) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
elif type == 'yes/no':
popup = sat_widgets.ConfirmDialog(unicode(message),
yes_cb=self._dialogOkCb, yes_value = (answer_cb, answer_data),
no_cb=self._dialogCancelCb, no_value = (answer_cb, answer_data))
else:
popup = sat_widgets.Alert(unicode(title), unicode(message), ok_cb=answer_cb or self.removePopUp) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
error(_('unmanaged dialog type: %s'), type)
self.showPopUp(popup)
def onNotification(self, notBar):
"""Called when a new notification has been received"""
if not isinstance(self.main_widget, sat_widgets.FocusFrame):
#if we are not in the main configuration, we ignore the notifications bar
return
if isinstance(self.main_widget.footer,sat_widgets.AdvancedEdit):
if not self.notBar.canHide():
#the notification bar is not visible and has usefull informations, we show it
pile = urwid.Pile([self.notBar, self.editBar])
self.main_widget.footer = pile
else:
if not isinstance(self.main_widget.footer, urwid.Pile):
error(_("INTERNAL ERROR: Unexpected class for main widget's footer"))
assert(False)
if self.notBar.canHide():
#No notification left, we can hide the bar
self.main_widget.footer = self.editBar
def actionResult(self, type, id, data):
if not id in self.current_action_ids:
debug (_('unknown id, ignoring'))
return
if type == "SUPPRESS":
self.current_action_ids.remove(id)
elif type == "XMLUI":
self.current_action_ids.remove(id)
debug (_("XML user interface received"))
misc = {}
#FIXME FIXME FIXME: must clean all this crap !
title = _('Form')
if data['type'] == 'registration':
title = _('Registration')
misc['target'] = data['target']
misc['action_back'] = self.bridge.gatewayRegister
ui = XMLUI(self, title=title, xml_data = data['xml'], misc = misc)
if data['type'] == 'registration':
ui.show('popup')
else:
ui.show('window')
elif type == "ERROR":
self.current_action_ids.remove(id)
self.showPopUp(sat_widgets.Alert(_("Error"), unicode(data["message"]), ok_cb=self.removePopUp)) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
elif type == "RESULT":
self.current_action_ids.remove(id)
if self.current_action_ids_cb.has_key(id):
callback = self.current_action_ids_cb[id]
del self.current_action_ids_cb[id]
callback(data)
elif type == "DICT_DICT":
self.current_action_ids.remove(id)
if self.current_action_ids_cb.has_key(id):
callback = self.current_action_ids_cb[id]
del self.current_action_ids_cb[id]
callback(data)
else:
error (_("FIXME FIXME FIXME: type [%s] not implemented") % type)
raise NotImplementedError
##DIALOGS CALLBACKS##
def onJoinRoom(self, button, edit):
self.removePopUp()
room_jid = JID(edit.get_edit_text())
if room_jid.is_valid():
self.bridge.joinMUC(room_jid.domain, room_jid.node, self.profiles[self.profile]['whoami'].node, self.profile)
else:
message = _("'%s' is an invalid JID !") % room_jid
error (message)
self.showPopUp(sat_widgets.Alert(_("Error"), message, ok_cb=self.removePopUp))
def onAddContact(self, button, edit):
self.removePopUp()
jid=JID(edit.get_edit_text())
if jid.is_valid():
self.bridge.addContact(jid.short, profile_key=self.profile)
else:
message = _("'%s' is an invalid JID !") % jid
error (message)
self.showPopUp(sat_widgets.Alert(_("Error"), message, ok_cb=self.removePopUp))
def onRemoveContact(self, button):
self.removePopUp()
info(_("Unsubscribing %s presence"),self.contactList.get_contact())
self.bridge.delContact(self.contactList.get_contact(), profile_key=self.profile)
#MENU EVENTS#
def onConnectRequest(self, menu):
self.bridge.connect(self.profile)
def onDisconnectRequest(self, menu):
self.bridge.disconnect(self.profile)
def onParam(self, menu):
params = XMLUI(self,xml_data=self.bridge.getParamsUI(self.profile))
self.addWindow(params)
def onExitRequest(self, menu):
QuickApp.onExit(self)
raise urwid.ExitMainLoop()
def onJoinRoomRequest(self, menu):
"""User wants to join a MUC room"""
pop_up_widget = sat_widgets.InputDialog(_("Entering a MUC room"), _("Please enter MUC's JID"), default_txt = 'room@muc_service.server.tld', cancel_cb=self.removePopUp, ok_cb=self.onJoinRoom)
self.showPopUp(pop_up_widget)
def onFindGatewaysRequest(self, e):
debug(_("Find Gateways request"))
id = self.bridge.findGateways(self.profiles[self.profile]['whoami'].domain, self.profile)
self.current_action_ids.add(id)
self.current_action_ids_cb[id] = self.onGatewaysFound
def onAddContactRequest(self, menu):
pop_up_widget = sat_widgets.InputDialog(_("Adding a contact"), _("Please enter new contact JID"), default_txt = 'name@server.tld', cancel_cb=self.removePopUp, ok_cb=self.onAddContact)
self.showPopUp(pop_up_widget)
def onRemoveContactRequest(self, menu):
contact = self.contactList.get_contact()
if not contact:
self.showPopUp(sat_widgets.Alert(_("Error"), _("You have not selected any contact to delete !"), ok_cb=self.removePopUp))
else:
pop_up_widget = sat_widgets.ConfirmDialog(_("Are you sure you want to delete the contact [%s] ?" % contact), yes_cb=self.onRemoveContact, no_cb=self.removePopUp)
self.showPopUp(pop_up_widget)
def onAboutRequest(self, menu):
self.showPopUp(sat_widgets.Alert(_("About"), const_APP_NAME + " v" + self.bridge.getVersion(), ok_cb=self.removePopUp))
#MISC CALLBACKS#
def onGatewaysFound(self, data):
"""Called when SàT has found the server gateways"""
target = data['__private__']['target']
del data['__private__']
gatewayManager = GatewaysManager(self, data, server=target)
self.addWindow(gatewayManager)
sat = PrimitivusApp()
sat.start()

View file

@ -0,0 +1,124 @@
#!/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
from urwid_satext.sat_widgets import Password,List,InputDialog,ConfirmDialog,Alert,FocusFrame
from tools.jid import JID
class ProfileManager(urwid.WidgetWrap):
def __init__(self, host):
self.host = host
#profiles list
profiles = self.host.bridge.getProfilesList()
profiles.sort()
#login & password box must be created before list because of onProfileChange
self.login_wid = urwid.Edit(_('Login:'),align='center')
self.pass_wid = Password(_('Password:'),align='center')
self.list_profile = List(profiles, style=['single'], align='center', on_click=self.onProfileChange)
#new & delete buttons
buttons = [urwid.Button(_("New"), self.onNewProfile),
urwid.Button(_("Delete"), self.onDeleteProfile)]
buttons_flow = urwid.GridFlow(buttons, max([len(button.get_label()) for button in buttons])+4, 1, 1, 'center')
#second part: login information:
divider = urwid.Divider('-')
#connect button
connect_button = urwid.Button(_("Connect"), self.onConnectProfile)
#we now build the widget
body_content = urwid.SimpleListWalker([buttons_flow,self.list_profile,divider,self.login_wid, self.pass_wid, connect_button])
frame_body = urwid.ListBox(body_content)
frame = urwid.Frame(frame_body,urwid.AttrMap(urwid.Text(_("Profile Manager"),align='center'),'title'))
self.main_widget = urwid.LineBox(frame)
urwid.WidgetWrap.__init__(self, self.main_widget)
def __refillProfiles(self):
"""Update the list of profiles"""
profiles = self.host.bridge.getProfilesList()
profiles.sort()
self.list_profile.changeValues(profiles)
def cancelDialog(self, button):
self.host.removePopUp()
def newProfile(self, button, edit):
"""Create the profile"""
name = edit.get_edit_text()
self.host.bridge.createProfile(name)
self.__refillProfiles()
#We select the profile created in the list
self.list_profile.selectValue(name)
self.host.removePopUp()
def deleteProfile(self, button):
profile_name = self.list_profile.getSelectedValue()
if profile_name:
self.host.bridge.deleteProfile(profile_name)
self.__refillProfiles()
self.host.removePopUp()
def onNewProfile(self, e):
pop_up_widget = InputDialog(_("New profile"), _("Please enter a new profile name"), cancel_cb=self.cancelDialog, ok_cb=self.newProfile)
self.host.showPopUp(pop_up_widget)
def onDeleteProfile(self, e):
pop_up_widget = ConfirmDialog(_("Are you sure you want to delete the profile %s ?") % self.list_profile.getSelectedValue(), no_cb=self.cancelDialog, yes_cb=self.deleteProfile)
self.host.showPopUp(pop_up_widget)
def onProfileChange(self, list_wid):
profile_name = list_wid.getSelectedValue()
if profile_name:
jabberID = self.host.bridge.getParamA("JabberID", "Connection", profile_key=profile_name)
password = self.host.bridge.getParamA("Password", "Connection", profile_key=profile_name)
self.login_wid.set_edit_text(jabberID)
self.pass_wid.set_edit_text(password)
def onConnectProfile(self, button):
profile_name = self.list_profile.getSelectedValue()
if not profile_name:
pop_up_widget = Alert(_('No profile selected'), _('You need to create and select a profile before connecting'), ok_cb=self.cancelDialog)
self.host.showPopUp(pop_up_widget)
elif profile_name[0] == '@':
pop_up_widget = Alert(_('Bad profile name'), _("A profile name can't start with a @"), ok_cb=self.cancelDialog)
self.host.showPopUp(pop_up_widget)
else:
profile = self.host.bridge.getProfileName(profile_name)
assert(profile)
#TODO: move this to quick_app
old_jid = self.host.bridge.getParamA("JabberID", "Connection", profile_key=profile)
old_pass = self.host.bridge.getParamA("Password", "Connection", profile_key=profile)
new_jid = self.login_wid.get_edit_text()
new_pass = self.pass_wid.get_edit_text()
if old_jid != new_jid:
self.host.bridge.setParam("JabberID", new_jid, "Connection", profile)
self.host.bridge.setParam("Server", JID(new_jid).domain, "Connection", profile)
if old_pass != new_pass:
self.host.bridge.setParam("Password", new_pass, "Connection", profile)
self.host.plug_profile(profile)

View file

@ -0,0 +1,96 @@
#!/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
from urwid_satext import sat_widgets
from tools.jid import JID
class Progress(urwid.WidgetWrap):
def __init__(self, host):
self.host = host
self.progress_list = urwid.SimpleListWalker([])
self.progress_dict = {}
listbox = urwid.ListBox(self.progress_list)
buttons = []
buttons.append(sat_widgets.CustomButton(_('Clear progress list'), self.__onClear))
max_len = max([button.getSize() for button in buttons])
buttons_wid = urwid.GridFlow(buttons,max_len,1,0,'center')
main_wid = sat_widgets.FocusFrame(listbox, footer=buttons_wid)
urwid.WidgetWrap.__init__(self, main_wid)
def addProgress(self, id, message):
mess_wid = urwid.Text(message)
progr_wid = urwid.ProgressBar('progress_normal', 'progress_complete')
column = urwid.Columns([mess_wid, progr_wid])
self.progress_dict[id] = {'full':column,'progress':progr_wid,'state':'init'}
self.progress_list.append(column)
self.progressCB(self.host.loop, (id, message))
def progressCB(self, loop, data):
id, message = data
data = self.host.bridge.getProgress(id)
pbar = self.progress_dict[id]['progress']
if data:
if self.progress_dict[id]['state'] == 'init':
#first answer, we must construct the bar
self.progress_dict[id]['state'] = 'progress'
pbar.done = float(data['size'])
pbar.set_completion(float(data['position']))
self.updateNotBar()
else:
if self.progress_dict[id]['state'] == 'progress':
self.progress_dict[id]['state'] = 'done'
pbar.set_completion(pbar.done)
self.updateNotBar()
return
loop.set_alarm_in(1,self.progressCB, (id, message))
def __removeBar(self, id):
wid = self.progress_dict[id]['full']
self.progress_list.remove(wid)
del(self.progress_dict[id])
def __onClear(self, button):
to_remove = []
for id in self.progress_dict:
if self.progress_dict[id]['state'] == 'done':
to_remove.append(id)
for id in to_remove:
self.__removeBar(id)
self.updateNotBar()
def updateNotBar(self):
if not self.progress_dict:
self.host.setProgress(None)
return
progress = 0
nb_bars = 0
for id in self.progress_dict:
pbar = self.progress_dict[id]['progress']
progress += pbar.current/pbar.done*100
nb_bars+=1
av_progress = progress/float(nb_bars)
self.host.setProgress(av_progress)

View file

@ -0,0 +1,261 @@
#!/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
from logging import debug, info, warning, error
from urwid_satext import sat_widgets
from xml.dom import minidom
class Pairs(urwid.WidgetWrap):
def __init__(self, weight_0='1', weight_1='1'):
self.idx = 0
self.weight_0 = weight_0
self.weight_1 = weight_1
columns = urwid.Columns([urwid.Text(''), urwid.Text('')])
#XXX: empty Text hack needed because Pile doesn't support empty list
urwid.WidgetWrap.__init__(self,columns)
def append(self, widget):
pile = self._w.widget_list[self.idx]
if isinstance(pile, urwid.Text):
self._w.widget_list[self.idx] = urwid.Pile([widget])
if self.idx == 1:
self._w.set_focus(1)
else:
pile.widget_list.append(widget)
pile.item_types.append(('weight',getattr(self,'weight_'+str(self.idx))))
self.idx = (self.idx + 1) % 2
class InvalidXMLUI(Exception):
pass
class XMLUI(urwid.WidgetWrap):
def __init__(self, host, xml_data, title = None, options = [], misc={}):
self.host = host
self.title = title
self.options = options
self.misc = misc
self.__dest = "window"
self.ctrl_list = {} # usefull to access ctrl
widget = self.constructUI(xml_data)
urwid.WidgetWrap.__init__(self,widget)
def __parseElems(self, node, parent):
"""Parse elements inside a <layout> tags, and add them to the parent"""
for elem in node.childNodes:
if elem.nodeName != "elem":
message=_("Unmanaged tag")
error(message)
raise Exception(message)
id = elem.getAttribute("id")
name = elem.getAttribute("name")
type = elem.getAttribute("type")
value = elem.getAttribute("value") if elem.hasAttribute('value') else u''
if type=="empty":
ctrl = urwid.Text('')
elif type=="text":
try:
value = elem.childNodes[0].wholeText
except IndexError:
warning (_("text node has no child !"))
ctrl = urwid.Text(value)
elif type=="label":
ctrl = urwid.Text(value+": ")
elif type=="string":
ctrl = sat_widgets.AdvancedEdit(edit_text = value)
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
elif type=="password":
ctrl = sat_widgets.Password(edit_text = value)
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
elif type=="textbox":
ctrl = sat_widgets.AdvancedEdit(edit_text = value, multiline=True)
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
elif type=="bool":
ctrl = urwid.CheckBox('', state = value=="true")
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
elif type=="list":
style=[] if elem.getAttribute("multi")=='yes' else ['single']
ctrl = sat_widgets.List(options=[option.getAttribute("value") for option in elem.getElementsByTagName("option")], style=style)
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
elif type=="button":
callback_id = elem.getAttribute("callback_id")
ctrl = sat_widgets.CustomButton(value, on_press=self.onButtonPress)
ctrl.param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")])
else:
error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type) #FIXME !
raise NotImplementedError
if self.type == 'param':
if isinstance(ctrl,urwid.Edit) or isinstance(ctrl,urwid.CheckBox):
urwid.connect_signal(ctrl,'change',self.onParamChange)
ctrl._param_category = self._current_category
ctrl._param_name = name
parent.append(ctrl)
def __parseChilds(self, current, elem, wanted = ['layout'], data = None):
"""Recursively parse childNodes of an elemen
@param current: widget container with 'append' method
@param elem: element from which childs will be parsed
@param wanted: list of tag names that can be present in the childs to be SàT XMLUI compliant"""
for node in elem.childNodes:
if wanted and not node.nodeName in wanted:
raise InvalidXMLUI
if node.nodeName == "layout":
type = node.getAttribute('type')
if type == "tabs":
tab_cont = sat_widgets.TabsContainer()
self.__parseChilds(current, node, ['category'], tab_cont)
current.append(tab_cont)
elif type == "vertical":
self.__parseElems(node, current)
elif type == "pairs":
pairs = Pairs()
self.__parseElems(node, pairs)
current.append(pairs)
else:
warning(_("Unknown layout, using default one"))
self.__parseElems(node, current)
elif node.nodeName == "category":
name = node.getAttribute('name')
label = node.getAttribute('label')
if not name or not isinstance(data,sat_widgets.TabsContainer):
raise InvalidXMLUI
if self.type == 'param':
self._current_category = name #XXX: awful hack because params need category and we don't keep parent
tab_cont = data
listbox = tab_cont.addTab(label or name)
self.__parseChilds(listbox.body, node, ['layout'])
else:
message=_("Unknown tag")
error(message)
raise NotImplementedError
def constructUI(self, xml_data):
ret_wid = urwid.ListBox(urwid.SimpleListWalker([]))
cat_dom = minidom.parseString(xml_data.encode('utf-8'))
top=cat_dom.documentElement
self.type = top.getAttribute("type")
self.title = top.getAttribute("title") or self.title
if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window']:
raise InvalidXMLUI
if self.type == 'param':
self.param_changed = set()
self.__parseChilds(ret_wid.body, cat_dom.documentElement)
assert ret_wid.body
if isinstance(ret_wid.body[0],sat_widgets.TabsContainer):
ret_wid = ret_wid.body[0] #xxx: awfull hack cause TabsContainer is a BoxWidget, can't be inside a ListBox
if self.type == 'form':
buttons = []
buttons.append(urwid.Button(_('Submit'),self.onFormSubmitted))
if not 'NO_CANCEL' in self.options:
buttons.append(urwid.Button(_('Cancel'),self.onFormCancelled))
max_len = max([len(button.get_label()) for button in buttons])
grid_wid = urwid.GridFlow(buttons,max_len+4,1,0,'center')
ret_wid.body.append(grid_wid)
elif self.type == 'param':
assert(isinstance(ret_wid,sat_widgets.TabsContainer))
buttons = []
buttons.append(sat_widgets.CustomButton(_('Save'),self.onSaveParams))
buttons.append(sat_widgets.CustomButton(_('Cancel'),lambda x:self.host.removeWindow()))
max_len = max([button.getSize() for button in buttons])
grid_wid = urwid.GridFlow(buttons,max_len,1,0,'center')
ret_wid.addFooter(grid_wid)
return ret_wid
def show(self,show_type = 'popup'):
"""Show the constructed UI
@param show_type: how to show the UI:
- popup
- window"""
self.__dest = "popup"
decorated = sat_widgets.LabelLine(self, sat_widgets.SurroundedText(self.title or ''))
if show_type == 'popup':
self.host.showPopUp(decorated)
elif show_type == 'window':
self.host.addWindow(decorated)
else:
error(_('INTERNAL ERROR: Unmanaged show_type (%s)') % show_type)
assert(False)
self.host.redraw()
##EVENTS##
def onButtonPress(self, button):
callback_id, fields = button.param_id
data = {"callback_id":callback_id}
for field in fields:
ctrl = self.ctrl_list[field]
if isinstance(ctrl['control'],sat_widgets.List):
data[field] = '\t'.join(ctrl['control'].getSelectedValues())
else:
data[field] = ctrl['control'].getValue()
id = self.host.bridge.launchAction("button", data, profile_key = self.host.profile)
self.host.current_action_ids.add(id)
def onParamChange(self, widget, extra=None):
"""Called when type is param and a widget to save is modified"""
assert(self.type == "param")
self.param_changed.add(widget)
def onFormSubmitted(self, button):
data = []
for ctrl_name in self.ctrl_list:
ctrl = self.ctrl_list[ctrl_name]
if isinstance(ctrl['control'], sat_widgets.List):
data.append((ctrl_name, ctrl['control'].getSelectedValue()))
elif isinstance(ctrl['control'], urwid.CheckBox):
data.append((ctrl_name, "true" if ctrl['control'].get_state() else "false"))
else:
data.append((ctrl_name, ctrl['control'].get_edit_text()))
if self.misc.has_key('action_back'): #FIXME FIXME FIXME: WTF ! Must be cleaned
raise NotImplementedError
self.host.debug()
elif self.misc.has_key('callback'):
self.misc['callback'](data)
else:
warning (_("The form data is not sent back, the type is not managed properly"))
self.host.removePopUp()
def onFormCancelled(self, button):
if self.__dest == 'window':
self.host.removeWindow()
else:
self.host.removePopUp()
def onSaveParams(self, button):
for ctrl in self.param_changed:
if isinstance(ctrl, urwid.CheckBox):
value = "true" if ctrl.get_state() else "false"
else:
value = ctrl.get_edit_text()
self.host.bridge.setParam(ctrl._param_name, value, ctrl._param_category, profile_key = self.host.profile)
self.host.removeWindow()

View file

View file

@ -0,0 +1,437 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
helper class for making 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 logging import debug, info, error
from tools.jid import JID
from sat_bridge_frontend.DBus import DBusBridgeFrontend,BridgeExceptionNoService
from optparse import OptionParser
import pdb
import gettext
gettext.install('sat_frontend', "../i18n", unicode=True)
class QuickApp():
"""This class contain the main methods needed for the frontend"""
def __init__(self, single_profile=True):
self.rosterList = {}
self.profiles = {}
self.single_profile = single_profile
self.check_options()
## bridge ##
try:
self.bridge=DBusBridgeFrontend()
except BridgeExceptionNoService:
print(_(u"Can't connect to SàT backend, are you sure it's launched ?"))
import sys
sys.exit(1)
self.bridge.register("connected", self.connected)
self.bridge.register("disconnected", self.disconnected)
self.bridge.register("newContact", self.newContact)
self.bridge.register("newMessage", self.newMessage)
self.bridge.register("newAlert", self.newAlert)
self.bridge.register("presenceUpdate", self.presenceUpdate)
self.bridge.register("roomJoined", self.roomJoined)
self.bridge.register("roomUserJoined", self.roomUserJoined)
self.bridge.register("roomUserLeft", self.roomUserLeft)
self.bridge.register("roomNewSubject", self.roomNewSubject)
self.bridge.register("tarotGameStarted", self.tarotGameStarted)
self.bridge.register("tarotGameNew", self.tarotGameNew)
self.bridge.register("tarotGameChooseContrat", self.tarotChooseContrat)
self.bridge.register("tarotGameShowCards", self.tarotShowCards)
self.bridge.register("tarotGameYourTurn", self.tarotMyTurn)
self.bridge.register("tarotGameScore", self.tarotScore)
self.bridge.register("tarotGameCardsPlayed", self.tarotCardsPlayed)
self.bridge.register("tarotGameInvalidCards", self.tarotInvalidCards)
self.bridge.register("subscribe", self.subscribe)
self.bridge.register("paramUpdate", self.paramUpdate)
self.bridge.register("contactDeleted", self.contactDeleted)
self.bridge.register("updatedValue", self.updatedValue, "request")
self.bridge.register("askConfirmation", self.askConfirmation, "request")
self.bridge.register("actionResult", self.actionResult, "request")
self.bridge.register("actionResultExt", self.actionResult, "request")
self.current_action_ids = set()
self.current_action_ids_cb = {}
def check_profile(self, profile):
"""Tell if the profile is currently followed by the application"""
return profile in self.profiles.keys()
def postInit(self):
"""Must be called after initialization is done, do all automatic task (auto plug profile)"""
if self.options.profile:
if not self.bridge.getProfileName(self.options.profile):
error(_("Trying to plug an unknown profile (%s)" % self.options.profile))
else:
self.plug_profile(self.options.profile)
def check_options(self):
"""Check command line options"""
usage=_("""
%prog [options]
%prog --help for options list
""")
parser = OptionParser(usage=usage)
parser.add_option("-p", "--profile", help=_("Select the profile to use"))
(self.options, args) = parser.parse_args()
if self.options.profile:
self.options.profile = self.options.profile.decode('utf-8')
return args
def plug_profile(self, profile_key='@DEFAULT@'):
"""Tell application which profile must be used"""
if self.single_profile and self.profiles:
error(_('There is already one profile plugged (we are in single profile mode) !'))
return
profile = self.bridge.getProfileName(profile_key)
if not profile:
error(_("The profile asked doesn't exist"))
return
if self.profiles.has_key(profile):
warning(_("The profile is already plugged"))
return
self.profiles[profile]={}
if self.single_profile:
self.profile = profile
###now we get the essential params###
self.profiles[profile]['whoami']=JID(self.bridge.getParamA("JabberID","Connection", profile))
autoconnect = self.bridge.getParamA("autoconnect","Connection", profile) == "true"
self.profiles[profile]['watched']=self.bridge.getParamA("Watched", "Misc", profile).split() #TODO: put this in a plugin
## misc ##
self.profiles[profile]['onlineContact'] = set() #FIXME: temporary
#TODO: gof: manage multi-profiles here
if not self.bridge.isConnected(profile):
self.setStatusOnline(False)
else:
self.setStatusOnline(True)
### now we fill the contact list ###
for contact in self.bridge.getContacts(profile):
self.newContact(contact[0], contact[1], contact[2], profile)
presences = self.bridge.getPresenceStatus(profile)
for contact in presences:
for res in presences[contact]:
jabber_id = contact+('/'+res if res else '')
show = presences[contact][res][0]
priority = presences[contact][res][1]
statuses = presences[contact][res][2]
self.presenceUpdate(jabber_id, show, priority, statuses, profile)
#The waiting subscription requests
waitingSub = self.bridge.getWaitingSub(profile)
for sub in waitingSub:
self.subscribe(waitingSub[sub], sub, profile)
#Now we open the MUC window where we already are:
for room_args in self.bridge.getRoomJoined(profile):
self.roomJoined(*room_args, profile=profile)
for subject_args in self.bridge.getRoomSubjects(profile):
self.roomNewSubject(*subject_args, profile=profile)
if autoconnect and not self.bridge.isConnected(profile_key):
#Does the user want autoconnection ?
self.bridge.connect(profile_key)
def unplug_profile(self, profile):
"""Tell the application to not follow anymore the profile"""
if not profile in self.profiles:
warning (_("This profile is not plugged"))
return
self.profiles.remove(profile)
def clear_profile(self):
self.profiles.clear()
def connected(self, profile):
"""called when the connection is made"""
if not self.check_profile(profile):
return
debug(_("Connected"))
self.setStatusOnline(True)
def disconnected(self, profile):
"""called when the connection is closed"""
if not self.check_profile(profile):
return
debug(_("Disconnected"))
self.CM.clear()
self.contactList.clear_contacts()
self.setStatusOnline(False)
def newContact(self, JabberId, attributes, groups, profile):
if not self.check_profile(profile):
return
entity=JID(JabberId)
self.rosterList[entity.short]=(dict(attributes), list(groups))
def newMessage(self, from_jid, msg, type, to_jid, profile):
if not self.check_profile(profile):
return
sender=JID(from_jid)
addr=JID(to_jid)
win = addr if sender.short == self.profiles[profile]['whoami'].short else sender
self.current_action_ids = set()
self.current_action_ids_cb = {}
self.chat_wins[win.short].printMessage(sender, msg, profile)
def newAlert(self, msg, title, alert_type, profile):
if not self.check_profile(profile):
return
assert alert_type in ['INFO','ERROR']
self.showDialog(unicode(msg),unicode(title),alert_type.lower())
def setStatusOnline(self, online=True):
pass
def presenceUpdate(self, jabber_id, show, priority, statuses, profile):
if not self.check_profile(profile):
return
debug (_("presence update for %(jid)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]") % {'jid':jabber_id, 'show':show, 'priority':priority, 'statuses':statuses, 'profile':profile});
from_jid=JID(jabber_id)
debug ("from_jid.short=%(from_jid)s whoami.short=%(whoami)s" % {'from_jid':from_jid.short, 'whoami':self.profiles[profile]['whoami'].short})
if from_jid.short==self.profiles[profile]['whoami'].short:
if not type:
self.setStatusOnline(True)
elif type=="unavailable":
self.setStatusOnline(False)
return
if show != 'unavailable':
name=""
groups = []
if self.rosterList.has_key(from_jid.short):
if self.rosterList[from_jid.short][0].has_key("name"):
name=self.rosterList[from_jid.short][0]["name"]
groups=self.rosterList[from_jid.short][1]
#FIXME: must be moved in a plugin
if from_jid.short in self.profiles[profile]['watched'] and not from_jid.short in self.profiles[profile]['onlineContact']:
self.showAlert(_("Watched jid [%s] is connected !") % from_jid.short)
self.profiles[profile]['onlineContact'].add(from_jid) #FIXME onlineContact is useless with CM, must be removed
self.CM.add(from_jid)
self.CM.update(from_jid, 'name', name)
self.CM.update(from_jid, 'show', show)
self.CM.update(from_jid, 'statuses', statuses)
self.CM.update(from_jid, 'groups', groups)
cache = self.bridge.getCardCache(from_jid)
if cache.has_key('nick'):
self.CM.update(from_jid, 'nick', cache['nick'])
if cache.has_key('avatar'):
self.CM.update(from_jid, 'avatar', self.bridge.getAvatarFile(cache['avatar']))
self.contactList.replace(from_jid, self.CM.getAttr(from_jid, 'groups'))
if show=="unavailable" and from_jid in self.profiles[profile]['onlineContact']:
self.profiles[profile]['onlineContact'].remove(from_jid)
self.CM.remove(from_jid)
if not self.CM.isConnected(from_jid):
self.contactList.disconnect(from_jid)
def roomJoined(self, room_id, room_service, room_nicks, user_nick, profile):
"""Called when a MUC room is joined"""
if not self.check_profile(profile):
return
debug (_("Room [%(room_name)s] joined by %(profile)s, users presents:%(users)s") % {'room_name':room_id+'@'+room_service, 'profile': profile, 'users':room_nicks})
room_jid=room_id+'@'+room_service
self.chat_wins[room_jid].setUserNick(user_nick)
self.chat_wins[room_jid].setType("group")
self.chat_wins[room_jid].id = room_jid
self.chat_wins[room_jid].setPresents(list(set([user_nick]+room_nicks)))
def roomUserJoined(self, room_id, room_service, user_nick, user_data, profile):
"""Called when an user joined a MUC room"""
if not self.check_profile(profile):
return
room_jid=room_id+'@'+room_service
if self.chat_wins.has_key(room_jid):
self.chat_wins[room_jid].replaceUser(user_nick)
debug (_("user [%(user_nick)s] joined room [%(room_jid)s]") % {'user_nick':user_nick, 'room_jid':room_jid})
def roomUserLeft(self, room_id, room_service, user_nick, user_data, profile):
"""Called when an user joined a MUC room"""
if not self.check_profile(profile):
return
room_jid=room_id+'@'+room_service
if self.chat_wins.has_key(room_jid):
self.chat_wins[room_jid].removeUser(user_nick)
debug (_("user [%(user_nick)s] left room [%(room_jid)s]") % {'user_nick':user_nick, 'room_jid':room_jid})
def roomNewSubject(self, room_id, room_service, subject, profile):
"""Called when subject of MUC room change"""
if not self.check_profile(profile):
return
room_jid=room_id+'@'+room_service
if self.chat_wins.has_key(room_jid):
self.chat_wins[room_jid].setSubject(subject)
debug (_("new subject for room [%(room_jid)s]: %(subject)s") % {'room_jid':room_jid, "subject":subject})
def tarotGameStarted(self, room_jid, referee, players, profile):
if not self.check_profile(profile):
return
debug (_("Tarot Game Started \o/"))
if self.chat_wins.has_key(room_jid):
self.chat_wins[room_jid].startGame("Tarot", referee, players)
debug (_("new Tarot game started by [%(referee)s] in room [%(room_jid)s] with %(players)s") % {'referee':referee, 'room_jid':room_jid, 'players':[str(player) for player in players]})
def tarotGameNew(self, room_jid, hand, profile):
if not self.check_profile(profile):
return
debug (_("New Tarot Game"))
if self.chat_wins.has_key(room_jid):
self.chat_wins[room_jid].getGame("Tarot").newGame(hand)
def tarotChooseContrat(self, room_jid, xml_data, profile):
"""Called when the player has to select his contrat"""
if not self.check_profile(profile):
return
debug (_("Tarot: need to select a contrat"))
if self.chat_wins.has_key(room_jid):
self.chat_wins[room_jid].getGame("Tarot").chooseContrat(xml_data)
def tarotShowCards(self, room_jid, game_stage, cards, data, profile):
if not self.check_profile(profile):
return
debug (_("Show cards"))
if self.chat_wins.has_key(room_jid):
self.chat_wins[room_jid].getGame("Tarot").showCards(game_stage, cards, data)
def tarotMyTurn(self, room_jid, profile):
if not self.check_profile(profile):
return
debug (_("My turn to play"))
if self.chat_wins.has_key(room_jid):
self.chat_wins[room_jid].getGame("Tarot").myTurn()
def tarotScore(self, room_jid, xml_data, winners, loosers, profile):
"""Called when the game is finished and the score are updated"""
if not self.check_profile(profile):
return
debug (_("Tarot: score received"))
if self.chat_wins.has_key(room_jid):
self.chat_wins[room_jid].getGame("Tarot").showScores(xml_data, winners, loosers)
def tarotCardsPlayed(self, room_jid, player, cards, profile):
if not self.check_profile(profile):
return
debug (_("Card(s) played (%(player)s): %(cards)s") % {"player":player, "cards":cards})
if self.chat_wins.has_key(room_jid):
self.chat_wins[room_jid].getGame("Tarot").cardsPlayed(player, cards)
def tarotInvalidCards(self, room_jid, phase, played_cards, invalid_cards, profile):
if not self.check_profile(profile):
return
debug (_("Cards played are not valid: %s") % invalid_cards)
if self.chat_wins.has_key(room_jid):
self.chat_wins[room_jid].getGame("Tarot").invalidCards(phase, played_cards, invalid_cards)
def _subscribe_cb(self, answer, data):
entity, profile = data
if answer:
self.bridge.subscription("subscribed", entity.short, profile_key = profile)
else:
self.bridge.subscription("unsubscribed", entity.short, profile_key = profile)
def subscribe(self, type, raw_jid, profile):
"""Called when a subsciption management signal is received"""
if not self.check_profile(profile):
return
entity = JID(raw_jid)
if type=="subscribed":
# this is a subscription confirmation, we just have to inform user
self.showDialog(_("The contact %s has accepted your subscription") % entity.short, _('Subscription confirmation'))
elif type=="unsubscribed":
# this is a subscription refusal, we just have to inform user
self.showDialog(_("The contact %s has refused your subscription") % entity.short, _('Subscription refusal'), 'error')
elif type=="subscribe":
# this is a subscriptionn request, we have to ask for user confirmation
answer = self.showDialog(_("The contact %s wants to subscribe to your presence.\nDo you accept ?") % entity.short, _('Subscription confirmation'), 'yes/no', answer_cb = self._subscribe_cb, answer_data=(entity, profile))
def showDialog(self, message, title, type="info", answer_cb = None):
raise NotImplementedError
def showAlert(self, message):
pass #FIXME
def paramUpdate(self, name, value, namespace, profile):
if not self.check_profile(profile):
return
debug(_("param update: [%(namespace)s] %(name)s = %(value)s") % {'namespace':namespace, 'name':name, 'value':value})
if (namespace,name) == ("Connection", "JabberID"):
debug (_("Changing JID to %s"), value)
self.profiles[profile]['whoami']=JID(value)
elif (namespace,name) == ("Misc", "Watched"):
self.profiles[profile]['watched']=value.split()
def contactDeleted(self, jid, profile):
if not self.check_profile(profile):
return
target = JID(jid)
self.CM.remove(target)
self.contactList.remove(self.CM.get_full(target))
try:
self.profiles[profile]['onlineContact'].remove(target.short)
except KeyError:
pass
def updatedValue(self, name, data):
if name == "card_nick":
target = JID(data['jid'])
if target in self.contactList:
self.CM.update(target, 'nick', data['nick'])
self.contactList.replace(target)
elif name == "card_avatar":
target = JID(data['jid'])
if target in self.contactList:
filename = self.bridge.getAvatarFile(data['avatar'])
self.CM.update(target, 'avatar', filename)
self.contactList.replace(target)
def askConfirmation(self, type, id, data):
raise NotImplementedError
def actionResult(self, type, id, data):
raise NotImplementedError
def onExit(self):
"""Must be called when the frontend is terminating"""
#TODO: mange multi-profile here
try:
autodisconnect = self.bridge.getParamA("autodisconnect","Connection", self.profile) == "true"
if autodisconnect and self.bridge.isConnected(self.profile):
#Does the user want autodisconnection ?
self.bridge.disconnect(self.profile)
except:
pass

View file

@ -0,0 +1,156 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
helper class for making 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 logging import debug, info, error
from tools.jid import JID
class QuickCardGame():
def __init__(self, parent, referee, players, player_nick):
self._autoplay = None #XXX: use 0 to activate fake play, None else
self.parent = parent
self.referee = referee
self.players = players
self.played = {}
for player in players:
self.played[player] = None
self.player_nick = player_nick
self.bottom_nick = unicode(self.player_nick)
idx = self.players.index(self.player_nick)
idx = (idx + 1) % len(self.players)
self.right_nick = unicode(self.players[idx])
idx = (idx + 1) % len(self.players)
self.top_nick = unicode(self.players[idx])
idx = (idx + 1) % len(self.players)
self.left_nick = unicode(self.players[idx])
self.bottom_nick = unicode(player_nick)
self.selected = [] #Card choosed by the player (e.g. during ecart)
self.hand_size = 13 #number of cards in a hand
self.hand = []
self.to_show = []
self.state = None
def getPlayerLocation(self, nick):
"""return player location (top,bottom,left or right)"""
for location in ['top','left','bottom','right']:
if getattr(self,'%s_nick' % location) == nick:
return location
assert(False)
def loadCards(self):
"""Load all the cards in memory
@param dir: directory where the PNG files are"""
self.cards={}
self.deck=[]
self.cards["atout"]={} #As Tarot is a french game, it's more handy & logical to keep french names
self.cards["pique"]={} #spade
self.cards["coeur"]={} #heart
self.cards["carreau"]={} #diamond
self.cards["trefle"]={} #club
def newGame(self, hand):
"""Start a new game, with given hand"""
assert (len(self.hand) == 0)
for suit, value in hand:
self.hand.append(self.cards[suit, value])
self.hand.sort()
self.state = "init"
def contratSelected(self, contrat):
"""Called when the contrat has been choosed
@param data: form result"""
self.parent.host.bridge.tarotGameContratChoosed(self.player_nick, self.referee, contrat or 'Passe', self.parent.host.profile)
def chooseContrat(self, xml_data):
"""Called when the player as to select his contrat
@param xml_data: SàT xml representation of the form"""
raise NotImplementedError
def showCards(self, game_stage, cards, data):
"""Display cards in the middle of the game (to show for e.g. chien ou poignée)"""
self.to_show = []
for suit, value in cards:
self.to_show.append(self.cards[suit, value])
if game_stage == "chien" and data['attaquant'] == self.player_nick:
self.state = "wait_for_ecart"
else:
self.state = "chien"
def myTurn(self):
"""Called when we have to play :)"""
if self.state == "chien":
self.to_show = []
self.state = "play"
self.__fakePlay()
def __fakePlay(self):
"""Convenience method for stupid autoplay
/!\ don't forgot to comment any interactive dialog for invalid card"""
if self._autoplay == None:
return
if self._autoplay >= len(self.hand):
self._autoplay = 0
card = self.hand[self._autoplay]
self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, [(card.suit, card.value)], profile_key = self.parent.host.profile)
del self.hand[self._autoplay]
self.state = "wait"
self._autoplay+=1
def showScores(self, xml_data, winners, loosers):
"""Called at the end of a game
@param xml_data: SàT xml representation of the scores
@param winners: list of winners' nicks
@param loosers: list of loosers' nicks"""
raise NotImplementedError
def cardsPlayed(self, player, cards):
"""A card has been played by player"""
if self.to_show:
self.to_show = []
pl_cards = []
if self.played[player] != None: #FIXME
for pl in self.played:
self.played[pl] = None
for suit, value in cards:
pl_cards.append(self.cards[suit, value])
self.played[player] = pl_cards[0]
def invalidCards(self, phase, played_cards, invalid_cards):
"""Invalid cards have been played
@param phase: phase of the game
@param played_cards: all the cards played
@param invalid_cards: cards which are invalid"""
if phase == "play":
self.state = "play"
elif phase == "ecart":
self.state = "ecart"
else:
error ('INTERNAL ERROR: unmanaged game phase')
for suit, value in played_cards:
self.hand.append(self.cards[suit, value])
self.hand.sort()
self.__fakePlay()

View file

@ -0,0 +1,127 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
helper class for making 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 logging import debug, info, warning, error
from tools.jid import JID
class QuickChat():
def __init__(self, target, host, type='one2one'):
self.target = target
self.host = host
self.type = type
self.id = ""
self.nick = None
self.occupants = set()
def setType(self, type):
"""Set the type of the chat
@param type: can be 'one2one' for single conversation or 'group' for chat à la IRC
"""
self.type = type
def setPresents(self, nicks):
"""Set the users presents in the contact list for a group chat
@param nicks: list of nicknames
"""
debug (_("Adding users %s to room") % nicks)
if self.type != "group":
error (_("[INTERNAL] trying to set presents nicks for a non group chat window"))
raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
self.occupants.update(nicks)
def replaceUser(self, nick):
"""Add user if it is not in the group list"""
debug (_("Replacing user %s") % nick)
if self.type != "group":
error (_("[INTERNAL] trying to replace user for a non group chat window"))
raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
len_before = len(self.occupants)
self.occupants.add(nick)
if len_before != len(self.occupants):
self.printInfo("=> %s has joined the room" % nick)
def setUserNick(self, nick):
"""Set the nick of the user, usefull for e.g. change the color of the user"""
self.nick = nick
def removeUser(self, nick):
"""Remove a user from the group list"""
debug(_("Removing user %s") % nick)
if self.type != "group":
error (_("[INTERNAL] trying to remove user for a non group chat window"))
raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
self.occupants.remove(nick)
self.printInfo("<= %s has left the room" % nick)
def setSubject(self, subject):
"""Set title for a group chat"""
debug(_("Setting subject to %s") % subject)
if self.type != "group":
error (_("[INTERNAL] trying to set subject for a non group chat window"))
raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
def historyPrint(self, size=20, keep_last=False, profile='@NONE@'):
"""Print the initial history"""
debug (_("now we print history"))
history=self.host.bridge.getHistory(self.host.profiles[profile]['whoami'].short, self.target, 20)
stamps=history.keys()
stamps.sort()
for stamp in stamps:
self.printMessage(JID(history[stamp][0]), history[stamp][1], profile, stamp)
if keep_last: ##FIXME hack for sortilege
self.last_history = stamps[-1] if stamps else None
def _get_nick(self, jid):
"""Return nick of this jid when possible"""
return jid.resource if self.type == "group" else (self.host.CM.getAttr(jid,'nick') or self.host.CM.getAttr(jid,'name') or jid.node)
def printMessage(self, from_jid, msg, profile, timestamp):
"""Print message in chat window. Must be implemented by child class"""
jid=JID(from_jid)
nick = self._get_nick(jid)
mymess = (jid.resource == self.nick) if self.type == "group" else (jid.short == self.host.profiles[profile]['whoami'].short) #mymess = True if message comes from local user
if msg.startswith('/me '):
self.printInfo('* %s %s' % (nick, msg[4:]),type='me')
return
return jid, nick, mymess
def printInfo(self, msg, type='normal'):
"""Print general info
@param msg: message to print
@type: one of:
normal: general info like "toto has joined the room"
me: "/me" information like "/me clenches his fist" ==> "toto clenches his fist"
"""
raise NotImplementedError
def startGame(self, game_type, referee, players):
"""Configure the chat window to start a game"""
#No need to raise an error as game are not mandatory
warning(_('startGame is not implemented in this frontend'))
def getGame(self, game_type):
"""Return class managing the game type"""
#No need to raise an error as game are not mandatory
warning(_('getGame is not implemented in this frontend'))

View file

@ -0,0 +1,41 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
helper class for making 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 tools.jid import JID
class QuickChatList(dict):
"""This class is used to manage the list of chat windows.
It act as a dict, but create a chat window when the name is found for the first time."""
def __init__(self, host):
dict.__init__(self)
self.host = host
def __getitem__(self,to_jid):
target=JID(to_jid)
if not self.has_key(target.short):
#we have to create the chat win
self[target.short] = self.createChat(target)
return dict.__getitem__(self,target.short)
def createChat(self, target):
raise NotImplementedError

Some files were not shown because too many files have changed in this diff Show more