Files reorganisation
This commit is contained in:
parent
c8b5f1db90
commit
e4452c4b03
125 changed files with 10 additions and 8 deletions
182
frontends/src/bridge/DBus.py
Executable file
182
frontends/src/bridge/DBus.py
Executable 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)
|
||||
|
||||
|
||||
0
frontends/src/bridge/__init__.py
Normal file
0
frontends/src/bridge/__init__.py
Normal file
28
frontends/src/bridge/bridge_frontend.py
Executable file
28
frontends/src/bridge/bridge_frontend.py
Executable 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
164
frontends/src/jp/fr.po
Normal 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"
|
||||
BIN
frontends/src/jp/i18n/fr/LC_MESSAGES/jp.mo
Normal file
BIN
frontends/src/jp/i18n/fr/LC_MESSAGES/jp.mo
Normal file
Binary file not shown.
272
frontends/src/jp/jp
Executable file
272
frontends/src/jp/jp
Executable 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
142
frontends/src/jp/jp.po
Normal 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 ""
|
||||
335
frontends/src/primitivus/card_game.py
Normal file
335
frontends/src/primitivus/card_game.py
Normal 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()
|
||||
279
frontends/src/primitivus/chat.py
Normal file
279
frontends/src/primitivus/chat.py
Normal 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)
|
||||
183
frontends/src/primitivus/contact_list.py
Normal file
183
frontends/src/primitivus/contact_list.py
Normal 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)
|
||||
|
||||
88
frontends/src/primitivus/gateways.py
Normal file
88
frontends/src/primitivus/gateways.py
Normal 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)
|
||||
509
frontends/src/primitivus/primitivus
Executable file
509
frontends/src/primitivus/primitivus
Executable 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()
|
||||
|
||||
124
frontends/src/primitivus/profile_manager.py
Normal file
124
frontends/src/primitivus/profile_manager.py
Normal 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)
|
||||
|
||||
96
frontends/src/primitivus/progress.py
Normal file
96
frontends/src/primitivus/progress.py
Normal 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)
|
||||
|
||||
261
frontends/src/primitivus/xmlui.py
Normal file
261
frontends/src/primitivus/xmlui.py
Normal 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()
|
||||
0
frontends/src/quick_frontend/__init__.py
Normal file
0
frontends/src/quick_frontend/__init__.py
Normal file
437
frontends/src/quick_frontend/quick_app.py
Normal file
437
frontends/src/quick_frontend/quick_app.py
Normal 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
|
||||
156
frontends/src/quick_frontend/quick_card_game.py
Normal file
156
frontends/src/quick_frontend/quick_card_game.py
Normal 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()
|
||||
|
||||
127
frontends/src/quick_frontend/quick_chat.py
Normal file
127
frontends/src/quick_frontend/quick_chat.py
Normal 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'))
|
||||
41
frontends/src/quick_frontend/quick_chat_list.py
Normal file
41
frontends/src/quick_frontend/quick_chat_list.py
Normal 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
Loading…
Reference in a new issue