merged branch frontends_multi_profiles

This commit is contained in:
Goffi 2015-03-18 10:52:28 +01:00
commit 6760c927d3
73 changed files with 4611 additions and 4514 deletions

View file

@ -49,9 +49,6 @@ $ sat
$ sat stop
- to launch Primitivus, enter:
$ primitivus
then create a profile.
- to launch Wix, enter
$ wix
then create a profile.
- to use jp, follow its help:
$ jp --help

View file

@ -5,6 +5,6 @@ global-include *.po *.mo
global-include CHANGELOG COPYING* INSTALL README*
global-include *.sh
include src/sat.*
include frontends/src/jp/jp frontends/src/primitivus/primitivus frontends/src/wix/wix
include frontends/src/jp/jp frontends/src/primitivus/primitivus
include src/bridge/bridge_constructor/mediawiki_template.tpl
prune src/bridge/bridge_constructor/generated

7
README
View file

@ -28,7 +28,6 @@ In addition, SàT has been built with ethic in mind, a social contract show the
For the moment, the frontends are:
* wix: based on WxWidgets, a minimal and old fashioned desktop frontend
* primitivus: a console interface client, for screen lovers
* jp: the powerful command line toolkit.
* libervia: the web frontend. It's in a different package, check libervia on the wiki: http://wiki.goffi.org/wiki/Libervia
@ -81,7 +80,6 @@ http://wiki.goffi.org/wiki/Primitivus
SàT is the acronym (yes another one :( ) in tribute to the song Salut à Toi from the Bérurier Noir band. I recommend also the excellent cover from Les Ogres de Barback.
jp stands for "Jabber coPy" or "JumP".
Primitivus is based on Urwid, and, according to their FAQ, "ur" is a German prefix for "ancestral or primal" (primitivus is a latin word).
wix come of course from the WX of wxwidgets and just sounds cool :).
** /!\ PRIVACY WARNING /!\ **
@ -107,9 +105,6 @@ SàT is heavily based on the twisted framework (http://twistedmatrix.com/trac/),
wokkel:
SàT use a library with high level enhancements on top of twisted, which is called wokkel (http://wokkel.ik.nu). Lot of thanks to Ralph Meijer and all other contributors.
wxWidgets & wxPython:
The Wix frontend is made with wxWidgets (www.wxwidgets.org) and its python version wxPython (wxPython), which is a really handy UI toolkit who works on most popular platforms. It can be found on probably all major Gnu/Linux distributions.
Urwid:
Primitivus is based on Urwid (http://excess.org/urwid/) which saved me a lot of time. It's really a great library to easily make a sophisticated interface.
@ -162,7 +157,7 @@ Salut à Toi has received contributions from:
- Matteo Cypriani <mcy@lm7.fr>: jp's mainloop update + doc improvements + various fixes. He's also the other co-maintainer of the Debian package.
- Olly Betts <olly@survex.com>: icon fix in Wix
- Olly Betts <olly@survex.com>: icon fix in Wix [N.B: Wix has been removed]
Many thanks to them.

View file

@ -49,12 +49,6 @@ DBus
Suggested: Python X Library (for user notifications in a graphical session)
---------------------------------
Dependencies for the Wix frontend
---------------------------------
wxPython
--------------------------------------
Dependencies for the Libervia frontend
--------------------------------------
@ -107,7 +101,7 @@ The case of SàT Media is particular are there is a compilation of media from se
Packages and versions
#####################
We have packaged Wix, Primitivus, and Jp with the core backend, as we think theses frontends should be shipped with each SàT distribution. But allowing them to be installed separately would be good: for example, Wix will be useless for a server or non-graphical workstation.
We have packaged Primitivus and Jp with the core backend, as we think theses frontends should be shipped with each SàT distribution.
Libervia is distributed as a separate package because of its specificities (it's a huge project by itself, it uses Pyjamas Python => JS Compiler, etc.). But it's part of the same project.

View file

@ -5,7 +5,7 @@ NOTE: *.po files are in i18n directory
To translate a file, you can use a dedicated tool as the excellent gtranslator:
- use the template .po file (e.g. sat.po) and name it to your translated language (e.g. fr.po for french); you can preferably generate a new template directly from the source with the following command (eventually adapted, the following command use zsh's globbing syntax), launched from root sat dir after having emptied the build directory:
> xgettext -L python -d sat --keyword=D_ -p i18n **/*(.py|.tac|primitivus|wix)(.) **/jp(.)
> xgettext -L python -d sat --keyword=D_ -p i18n **/*(.py|.tac|primitivus)(.) **/jp(.)
- then to start a new translation, copy i18n/sat.po to your language file, e.g. cd i18n; cp sat.po fr.po

View file

@ -102,10 +102,10 @@ class DBusBridgeFrontend(BridgeFrontend):
async = False
if kwargs:
if 'callback' in kwargs and 'errback' in kwargs:
if 'callback' in kwargs:
async = True
_callback = kwargs.pop('callback')
_errback = kwargs.pop('errback')
_errback = kwargs.pop('errback', lambda failure: log.error(unicode(failure)))
elif len(args) >= 2 and callable(args[-1]) and callable(args[-2]):
async = True
args = list(args)
@ -122,138 +122,511 @@ class DBusBridgeFrontend(BridgeFrontend):
return method(*args, **kwargs)
return getPluginMethod
def addContact(self, entity_jid, profile_key="@DEFAULT@"):
return self.db_core_iface.addContact(entity_jid, profile_key)
def addContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.addContact(entity_jid, profile_key, **kwargs)
def asyncConnect(self, profile_key="@DEFAULT@", password='', callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return self.db_core_iface.asyncConnect(profile_key, password, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def asyncCreateProfile(self, profile, password='', callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return self.db_core_iface.asyncCreateProfile(profile, password, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def asyncDeleteProfile(self, profile, callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return self.db_core_iface.asyncDeleteProfile(profile, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def asyncGetParamA(self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return unicode(self.db_core_iface.asyncGetParamA(name, category, attribute, security_limit, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler))
def confirmationAnswer(self, id, accepted, data, profile):
return self.db_core_iface.confirmationAnswer(id, accepted, data, profile)
def confirmationAnswer(self, id, accepted, data, profile, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.confirmationAnswer(id, accepted, data, profile, **kwargs)
def delContact(self, entity_jid, profile_key="@DEFAULT@"):
return self.db_core_iface.delContact(entity_jid, profile_key)
def delContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return self.db_core_iface.delContact(entity_jid, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def discoInfos(self, entity_jid, profile_key, callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return self.db_core_iface.discoInfos(entity_jid, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def discoItems(self, entity_jid, profile_key, callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return self.db_core_iface.discoItems(entity_jid, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def disconnect(self, profile_key="@DEFAULT@"):
return self.db_core_iface.disconnect(profile_key)
def disconnect(self, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.disconnect(profile_key, **kwargs)
def getConfig(self, section, name):
return unicode(self.db_core_iface.getConfig(section, name))
def getConfig(self, section, name, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return unicode(self.db_core_iface.getConfig(section, name, **kwargs))
def getContacts(self, profile_key="@DEFAULT@"):
return self.db_core_iface.getContacts(profile_key)
def getContacts(self, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return self.db_core_iface.getContacts(profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def getContactsFromGroup(self, group, profile_key="@DEFAULT@"):
return self.db_core_iface.getContactsFromGroup(group, profile_key)
def getContactsFromGroup(self, group, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.getContactsFromGroup(group, profile_key, **kwargs)
def getEntityData(self, jid, keys, profile):
return self.db_core_iface.getEntityData(jid, keys, profile)
def getEntitiesData(self, jids, keys, profile, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.getEntitiesData(jids, keys, profile, **kwargs)
def getEntityData(self, jid, keys, profile, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.getEntityData(jid, keys, profile, **kwargs)
def getHistory(self, from_jid, to_jid, limit, between=True, search='', profile="@NONE@", callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return self.db_core_iface.getHistory(from_jid, to_jid, limit, between, search, profile, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def getLastResource(self, contact_jid, profile_key="@DEFAULT@"):
return unicode(self.db_core_iface.getLastResource(contact_jid, profile_key))
def getMainResource(self, contact_jid, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return unicode(self.db_core_iface.getMainResource(contact_jid, profile_key, **kwargs))
def getMenuHelp(self, menu_id, language):
return unicode(self.db_core_iface.getMenuHelp(menu_id, language))
def getMenuHelp(self, menu_id, language, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return unicode(self.db_core_iface.getMenuHelp(menu_id, language, **kwargs))
def getMenus(self, language, security_limit):
return self.db_core_iface.getMenus(language, security_limit)
def getMenus(self, language, security_limit, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.getMenus(language, security_limit, **kwargs)
def getParamA(self, name, category, attribute="value", profile_key="@DEFAULT@"):
return unicode(self.db_core_iface.getParamA(name, category, attribute, profile_key))
def getParamA(self, name, category, attribute="value", profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return unicode(self.db_core_iface.getParamA(name, category, attribute, profile_key, **kwargs))
def getParams(self, security_limit=-1, app='', profile_key="@DEFAULT@", callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return unicode(self.db_core_iface.getParams(security_limit, app, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler))
def getParamsCategories(self, ):
return self.db_core_iface.getParamsCategories()
def getParamsCategories(self, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.getParamsCategories(**kwargs)
def getParamsForCategory(self, category, security_limit=-1, app='', profile_key="@DEFAULT@", callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return unicode(self.db_core_iface.getParamsForCategory(category, security_limit, app, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler))
def getParamsUI(self, security_limit=-1, app='', profile_key="@DEFAULT@", callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return unicode(self.db_core_iface.getParamsUI(security_limit, app, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler))
def getPresenceStatuses(self, profile_key="@DEFAULT@"):
return self.db_core_iface.getPresenceStatuses(profile_key)
def getPresenceStatuses(self, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.getPresenceStatuses(profile_key, **kwargs)
def getProfileName(self, profile_key="@DEFAULT@"):
return unicode(self.db_core_iface.getProfileName(profile_key))
def getProfileName(self, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return unicode(self.db_core_iface.getProfileName(profile_key, **kwargs))
def getProfilesList(self, ):
return self.db_core_iface.getProfilesList()
def getProfilesList(self, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.getProfilesList(**kwargs)
def getProgress(self, id, profile):
return self.db_core_iface.getProgress(id, profile)
def getProgress(self, id, profile, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.getProgress(id, profile, **kwargs)
def getReady(self, callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return self.db_core_iface.getReady(timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def getVersion(self, ):
return unicode(self.db_core_iface.getVersion())
def getVersion(self, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return unicode(self.db_core_iface.getVersion(**kwargs))
def getWaitingConf(self, profile_key):
return self.db_core_iface.getWaitingConf(profile_key)
def getWaitingConf(self, profile_key, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.getWaitingConf(profile_key, **kwargs)
def getWaitingSub(self, profile_key="@DEFAULT@"):
return self.db_core_iface.getWaitingSub(profile_key)
def getWaitingSub(self, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.getWaitingSub(profile_key, **kwargs)
def isConnected(self, profile_key="@DEFAULT@"):
return self.db_core_iface.isConnected(profile_key)
def isConnected(self, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.isConnected(profile_key, **kwargs)
def launchAction(self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return self.db_core_iface.launchAction(callback_id, data, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def loadParamsTemplate(self, filename):
return self.db_core_iface.loadParamsTemplate(filename)
def loadParamsTemplate(self, filename, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.loadParamsTemplate(filename, **kwargs)
def paramsRegisterApp(self, xml, security_limit=-1, app=''):
return self.db_core_iface.paramsRegisterApp(xml, security_limit, app)
def paramsRegisterApp(self, xml, security_limit=-1, app='', callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.paramsRegisterApp(xml, security_limit, app, **kwargs)
def saveParamsTemplate(self, filename):
return self.db_core_iface.saveParamsTemplate(filename)
def saveParamsTemplate(self, filename, callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.saveParamsTemplate(filename, **kwargs)
def sendMessage(self, to_jid, message, subject='', mess_type="auto", extra={}, profile_key="@NONE@", callback=None, errback=None):
error_handler = None if callback is None else lambda err:errback(dbus_to_bridge_exception(err))
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return self.db_core_iface.sendMessage(to_jid, message, subject, mess_type, extra, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
def setParam(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@"):
return self.db_core_iface.setParam(name, value, category, security_limit, profile_key)
def setParam(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.setParam(name, value, category, security_limit, profile_key, **kwargs)
def setPresence(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@"):
return self.db_core_iface.setPresence(to_jid, show, statuses, profile_key)
def setPresence(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.setPresence(to_jid, show, statuses, profile_key, **kwargs)
def subscription(self, sub_type, entity, profile_key="@DEFAULT@"):
return self.db_core_iface.subscription(sub_type, entity, profile_key)
def subscription(self, sub_type, entity, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
kwargs={}
if callback is not None:
kwargs['timeout'] = const_TIMEOUT
kwargs['reply_handler'] = callback
kwargs['error_handler'] = error_handler
return self.db_core_iface.subscription(sub_type, entity, profile_key, **kwargs)
def updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@"):
return self.db_core_iface.updateContact(entity_jid, name, groups, profile_key)
def updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@", callback=None, errback=None):
if callback is None:
error_handler = None
else:
if errback is None:
errback = log.error
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
return self.db_core_iface.updateContact(entity_jid, name, groups, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)

View file

@ -1,80 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# generic module for SàT frontends
# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sat.core import constants
from sat.core.i18n import _, D_
try:
from collections import OrderedDict # only available from python 2.7
except ImportError:
try:
from ordereddict import OrderedDict
except ImportError:
pass # libervia can not import external libraries
def getPresence():
"""We cannot do it directly in the Const class, if it is not encapsulated
in a method we get a JS runtime SyntaxError: "missing ) in parenthetical".
# TODO: merge this definition with those in primitivus.constants and wix.constants
"""
try:
presence = OrderedDict([("", _("Online")),
("chat", _("Free for chat")),
("away", _("Away from keyboard")),
("dnd", _("Do not disturb")),
("xa", _("Extended away"))])
except TypeError:
presence = {"": _("Online"),
"chat": _("Free for chat"),
"away": _("Away from keyboard"),
"dnd": _("Do not disturb"),
"xa": _("Extended away")
}
return presence
class Const(constants.Const):
PRESENCE = getPresence()
# from plugin_misc_text_syntaxes
SYNTAX_XHTML = "XHTML"
SYNTAX_CURRENT = "@CURRENT@"
SYNTAX_TEXT = "text"
# XMLUI
SAT_FORM_PREFIX = "SAT_FORM_"
SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_" # used to have unique elements names
XMLUI_STATUS_VALIDATED = "validated"
XMLUI_STATUS_CANCELLED = constants.Const.XMLUI_DATA_CANCELLED
# MUC
ALL_OCCUPANTS = 1
MUC_USER_STATES = {
"active": u'✔',
"inactive": u'☄',
"gone": u'✈',
"composing": u'✎',
"paused": u"⦷"
}
# Roster
GROUP_NOT_IN_ROSTER = D_('Not in roster')

View file

@ -245,11 +245,11 @@ class Jp(object):
callback()
def get_full_jid(self, param_jid):
"""Return the full jid if possible (add last resource when find a bare jid)"""
"""Return the full jid if possible (add main resource when find a bare jid)"""
_jid = JID(param_jid)
if not _jid.resource:
#if the resource is not given, we try to add the last known resource
last_resource = self.bridge.getLastResource(param_jid, self.profile)
last_resource = self.bridge.getMainResource(param_jid, self.profile)
if last_resource:
return "%s/%s" % (_jid.bare, last_resource)
return param_jid

View file

@ -23,13 +23,15 @@ log = logging.getLogger(__name__)
import urwid
from urwid_satext import sat_widgets
from urwid_satext.files_management import FileDialog
from sat_frontends.quick_frontend import quick_widgets
from sat_frontends.quick_frontend.quick_chat import QuickChat
from sat_frontends.primitivus.card_game import CardGame
from sat_frontends.quick_frontend.quick_utils import escapePrivate, unescapePrivate
from sat_frontends.quick_frontend import quick_games
from sat_frontends.primitivus import game_tarot
from sat_frontends.primitivus.constants import Const as C
from sat_frontends.primitivus.keys import action_key_map as a_key
from sat_frontends.primitivus.widget import PrimitivusWidget
import time
from sat_frontends.tools.jid import JID
from sat_frontends.tools import jid
class ChatText(urwid.FlowWidget):
@ -80,19 +82,27 @@ class ChatText(urwid.FlowWidget):
return txt_widget
class Chat(urwid.WidgetWrap, QuickChat):
class Chat(PrimitivusWidget, QuickChat):
def __init__(self, target, host, type_='one2one'):
self.target = target
QuickChat.__init__(self, target, host, type_)
def __init__(self, host, target, type_=C.CHAT_ONE2ONE, profiles=None):
QuickChat.__init__(self, host, target, type_, profiles=profiles)
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_)
PrimitivusWidget.__init__(self, self.pile, self.target)
# we must adapt the behaviour with the type
if type_ == C.CHAT_ONE2ONE:
self.historyPrint(profile=self.profile)
elif type_ == C.CHAT_GROUP:
if len(self.chat_colums.contents) == 1:
present_widget = self._buildPresentList()
self.present_panel = sat_widgets.VerticalSeparator(present_widget)
self._appendPresentPanel()
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
@ -101,12 +111,12 @@ class Chat(urwid.WidgetWrap, QuickChat):
def keypress(self, size, key):
if key == a_key['OCCUPANTS_HIDE']: #user wants to (un)hide the presents panel
if self.type == 'group':
if self.type == C.CHAT_GROUP:
widgets = [widget for (widget, options) in self.chat_colums.contents]
if self.present_panel in widgets:
self.__removePresentPanel()
self._removePresentPanel()
else:
self.__appendPresentPanel()
self._appendPresentPanel()
elif key == a_key['TIMESTAMP_HIDE']: #user wants to (un)hide timestamp
self.show_timestamp = not self.show_timestamp
for wid in self.content:
@ -115,10 +125,6 @@ class Chat(urwid.WidgetWrap, QuickChat):
self.show_short_nick = not self.show_short_nick
for wid in self.content:
wid._invalidate()
elif key == a_key['DECORATION_HIDE']: #user wants to (un)hide widget decoration
show = not isinstance(self._w, sat_widgets.LabelLine)
self.showDecoration(show)
self._invalidate()
elif key == a_key['SUBJECT_SWITCH']: #user wants to (un)hide group's subject or change its apperance
if self.subject:
self.show_title = (self.show_title + 1) % 3
@ -130,67 +136,30 @@ class Chat(urwid.WidgetWrap, QuickChat):
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':
if self.type == C.CHAT_GROUP:
self.host.addMenus(menu, C.MENU_ROOM, {'room_jid': self.target.bare})
game = _("Game")
menu.addMenu(game, "Tarot", self.onTarotRequest)
elif self.type == 'one2one':
self.host.addMenus(menu, C.MENU_SINGLE, {'jid': unescapePrivate(self.target)})
elif self.type == C.CHAT_ONE2ONE:
self.host.addMenus(menu, C.MENU_SINGLE, {'jid': self.target})
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.contents) == 1:
present_widget = self.__buildPresentList()
self.present_panel = sat_widgets.VerticalSeparator(present_widget)
self.__appendPresentPanel()
def __getDecoration(self, widget):
return sat_widgets.LabelLine(widget, self.__getSurrendedText())
def __getSurrendedText(self):
"""Get the text to be displayed as the dialog title."""
if not hasattr(self, "surrended_text"):
self.__setSurrendedText()
return self.surrended_text
def __setSurrendedText(self, state=None):
"""Set the text to be displayed as the dialog title
@param stat: chat state of the contact
"""
text = unicode(unescapePrivate(self.target))
if state:
text += " (" + state + ")"
self.surrended_text = sat_widgets.SurroundedText(text)
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 updateChatState(self, state, nick=None):
"""Set the chat state (XEP-0085) of the contact. Leave nick to None
to set the state for a one2one conversation, or give a nickname or
C.ALL_OCCUPANTS to set the state of a participant within a MUC.
@param state: the new chat state
@param nick: None for one2one, the MUC user nick or C.ALL_OCCUPANTS
"""
if nick:
assert(self.type == 'group')
occupants = self.occupants if nick == C.ALL_OCCUPANTS else [nick]
def updateChatState(self, from_jid, state):
if self.type == C.CHAT_GROUP:
if from_jid == C.ENTITY_ALL:
occupants = self.occupants
else:
nick = from_jid.resource
if not nick:
log.debug("no nick found for chatstate")
return
occupants = [nick]
options = self.present_wid.getAllValues()
for index in xrange(0, len(options)):
nick = options[index].value
@ -199,46 +168,51 @@ class Chat(urwid.WidgetWrap, QuickChat):
self.present_wid.changeValues(options)
self.host.redraw()
else:
assert(self.type == 'one2one')
self.__setSurrendedText(state)
self.showDecoration()
self.host.redraw()
self.title_dynamic = '({})'.format(state)
def _presentClicked(self, list_wid, clicked_wid):
assert(self.type == 'group')
assert self.type == C.CHAT_GROUP
nick = clicked_wid.getValue().value
if nick == self.getUserNick():
#We ignore click on our own nick
#We ignore clicks on our own nick
return
#we have a click on a nick, we add the private conversation to the contact_list
full_jid = JID("%s/%s" % (self.target.bare, nick))
new_jid = escapePrivate(full_jid)
if new_jid not in self.host.contact_list:
self.host.contact_list.add(new_jid, [C.GROUP_NOT_IN_ROSTER])
contact_list = self.host.contact_lists[self.profile]
full_jid = jid.JID("%s/%s" % (self.target.bare, nick))
#we have a click on a nick, we need to create the widget if it doesn't exists
self.getOrCreatePrivateWidget(full_jid)
#now we select the new window
self.host.contact_list.setFocus(full_jid, True)
contact_list.setFocus(full_jid, True)
def __buildPresentList(self):
def _buildPresentList(self):
self.present_wid = sat_widgets.GenericList([],option_type = sat_widgets.ClickableText, on_click=self._presentClicked)
return self.present_wid
def __appendPresentPanel(self):
def _appendPresentPanel(self):
self.chat_colums.contents.append((self.present_panel,('weight', 2, False)))
def __removePresentPanel(self):
def _removePresentPanel(self):
for widget, options in self.chat_colums.contents:
if widget is self.present_panel:
self.chat_colums.contents.remove((widget, options))
break
def __appendGamePanel(self, widget):
def addGamePanel(self, widget):
"""Insert a game panel to this Chat dialog.
@param widget (Widget): the game panel
"""
assert (len(self.pile.contents) == 1)
self.pile.contents.insert(0,(widget,('weight', 1)))
self.pile.contents.insert(1,(urwid.Filler(urwid.Divider('-'),('fixed', 1))))
self.pile.contents.insert(0, (widget, ('weight', 1)))
self.pile.contents.insert(1, (urwid.Filler(urwid.Divider('-'), ('fixed', 1))))
self.host.redraw()
def __removeGamePanel(self):
def removeGamePanel(self, widget):
"""Remove the game panel from this Chat dialog.
@param widget (Widget): the game panel
"""
assert (len(self.pile.contents) == 3)
del self.pile.contents[0]
self.host.redraw()
@ -252,12 +226,11 @@ class Chat(urwid.WidgetWrap, QuickChat):
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
def setPresents(self, nicks):
"""Set the occupants of a group chat.
@param nicks (list[unicode]): sorted 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()
@ -290,13 +263,22 @@ class Chat(urwid.WidgetWrap, QuickChat):
self.text_list.focus_position = len(self.content) - 1 # scroll down
self.host.redraw()
def printMessage(self, from_jid, msg, profile, timestamp=None):
assert isinstance(from_jid, JID)
try:
jid, nick, mymess = QuickChat.printMessage(self, from_jid, msg, profile, timestamp)
except TypeError:
return
def onPrivateCreated(self, widget):
self.host.contact_lists[widget.profile].specialResourceVisible(widget.target)
def printMessage(self, from_jid, msg, extra=None, profile=C.PROF_KEY_NONE):
assert isinstance(from_jid, jid.JID)
if extra is None:
extra = {}
try:
timestamp = float(extra['timestamp'])
except KeyError:
timestamp=None
try:
nick, mymess = QuickChat.printMessage(self, from_jid, msg, extra, profile)
except TypeError:
# None is returned, the message is managed
return
new_text = ChatText(self, timestamp, nick, mymess, msg)
if timestamp and self.content:
@ -323,7 +305,7 @@ class Chat(urwid.WidgetWrap, QuickChat):
# all messages and not only with the messages coming from the history.
self._notify(from_jid, msg)
def printInfo(self, msg, type_='normal', timestamp=None):
def printInfo(self, msg, type_='normal', extra=None):
"""Print general info
@param msg: message to print
@type_: one of:
@ -331,6 +313,12 @@ class Chat(urwid.WidgetWrap, QuickChat):
me: "/me" information like "/me clenches his fist" ==> "toto clenches his fist"
@param timestamp (float): number of seconds since epoch
"""
if extra is None:
extra = {}
try:
timestamp = float(extra['timestamp'])
except KeyError:
timestamp=None
_widget = ChatText(self, timestamp, None, False, msg, is_info=True)
self.content.append(_widget)
self._notify(msg=msg)
@ -348,30 +336,18 @@ class Chat(urwid.WidgetWrap, QuickChat):
self.text_list.focus_position = len(self.content) - 1
self.host.redraw()
if not self.host.x_notify.hasFocus():
if self.type == "one2one":
if self.type == C.CHAT_ONE2ONE:
self.host.x_notify.sendNotification(_("Primitivus: %s is talking to you") % from_jid)
elif self.getUserNick().lower() in msg.lower():
self.host.x_notify.sendNotification(_("Primitivus: %(user)s mentioned you in room '%(room)s'") % {'user': from_jid, 'room': self.target})
def startGame(self, game_type, referee, players):
"""Configure the chat window to start a game"""
if game_type=="Tarot":
self.tarot_wid = CardGame(self, referee, players, self.nick)
self.__appendGamePanel(self.tarot_wid)
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):
# TODO: move this to plugin_misc_tarot with dynamic 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)
self.host.bridge.tarotGameCreate(self.id, list(self.occupants), self.profile)
def onSendFileRequest(self, menu):
# TODO: move this to core with dynamic menus
@ -388,11 +364,15 @@ class Chat(urwid.WidgetWrap, QuickChat):
self.host.showDialog(_(u"File has a unicode error in its name, it's not yet managed by SàT"), title=_("Can't send file"), type_="error")
return
#FIXME: check last_resource: what if self.target.resource exists ?
last_resource = self.host.bridge.getLastResource(unicode(self.target.bare), self.host.profile)
last_resource = self.host.bridge.getMainResource(unicode(self.target.bare), self.profile)
if last_resource:
full_jid = JID("%s/%s" % (self.target.bare, last_resource))
full_jid = jid.JID("%s/%s" % (self.target.bare, last_resource))
else:
full_jid = self.target
progress_id = self.host.bridge.sendFile(full_jid, filepath, {}, self.host.profile)
progress_id = self.host.bridge.sendFile(full_jid, filepath, {}, self.profile)
self.host.addProgress(progress_id,filepath)
self.host.showDialog(_(u"You file request has been sent, we are waiting for your contact answer"), title=_("File request sent"))
quick_widgets.register(QuickChat, Chat)
quick_widgets.register(quick_games.Tarot, game_tarot.TarotGame)

View file

@ -68,6 +68,8 @@ class Const(constants.Const):
('show_dnd_focus', 'dark red, bold', 'default'),
('show_xa', 'dark red', 'default'),
('show_xa_focus', 'dark red, bold', 'default'),
('resource', 'light blue', 'default'),
('resource_main', 'dark blue', 'default'),
('status', 'yellow', 'default'),
('status_focus', 'yellow, bold', 'default'),
('param_selected','default, bold', 'dark red'),
@ -85,3 +87,15 @@ class Const(constants.Const):
CONFIG_SECTION = APP_NAME.lower()
CONFIG_OPT_KEY_PREFIX = "KEY_"
MENU_ID_MAIN = "MAIN_MENU"
MENU_ID_WIDGET = "WIDGET_MENU"
MODE_NORMAL = 'NORMAL'
MODE_INSERTION = 'INSERTION'
MODE_COMMAND = 'COMMAND'
GROUP_DATA_FOLDED = 'folded'
# contacts and contact list
ALERT_HEADER='(*) '

View file

@ -21,36 +21,25 @@ from sat.core.i18n import _
import urwid
from urwid_satext import sat_widgets
from sat_frontends.quick_frontend.quick_contact_list import QuickContactList
from sat_frontends.quick_frontend.quick_utils import unescapePrivate
from sat_frontends.tools.jid import JID
from sat_frontends.primitivus.status import StatusBar
from sat_frontends.primitivus.constants import Const as C
from sat_frontends.primitivus.keys import action_key_map as a_key
from sat_frontends.primitivus.widget import PrimitivusWidget
from sat_frontends.tools import jid
from sat.core import log as logging
log = logging.getLogger(__name__)
class ContactList(urwid.WidgetWrap, QuickContactList):
class ContactList(PrimitivusWidget, QuickContactList):
signals = ['click','change']
def __init__(self, host, on_click=None, on_change=None, user_data=None):
QuickContactList.__init__(self)
self.host = host
self.selected = None
self.groups={}
self.alert_jid=set()
self.show_status = False
self.show_disconnected = False
self.show_empty_groups = True
# TODO: this may lead to two successive UI refresh and needs an optimization
self.host.bridge.asyncGetParamA(C.SHOW_EMPTY_GROUPS, "General", profile_key=host.profile, callback=self.showEmptyGroups)
self.host.bridge.asyncGetParamA(C.SHOW_OFFLINE_CONTACTS, "General", profile_key=host.profile, callback=self.showOfflineContacts)
def __init__(self, host, on_click=None, on_change=None, user_data=None, profile=None):
QuickContactList.__init__(self, host, profile)
#we now build the widget
self.host.status_bar = StatusBar(host)
self.frame = sat_widgets.FocusFrame(self.__buildList(), None, self.host.status_bar)
self.main_widget = sat_widgets.LabelLine(self.frame, sat_widgets.SurroundedText(_("Contacts")))
urwid.WidgetWrap.__init__(self, self.main_widget)
self.status_bar = StatusBar(host)
self.frame = sat_widgets.FocusFrame(self._buildList(), None, self.status_bar)
PrimitivusWidget.__init__(self, self.frame, _(u'Contacts'))
if on_click:
urwid.connect_signal(self, 'click', on_click, user_data)
if on_change:
@ -59,16 +48,14 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
def update(self):
"""Update display, keep focus"""
widget, position = self.frame.body.get_focus()
self.frame.body = self.__buildList()
self.frame.body = self._buildList()
if position:
try:
self.frame.body.focus_position = position
except IndexError:
pass
self.host.redraw()
def update_jid(self, jid):
self.update()
self._invalidate()
self.host.redraw() # FIXME: check if can be avoided
def keypress(self, size, key):
# FIXME: we have a temporary behaviour here: FOCUS_SWITCH change focus globally in the parent,
@ -81,18 +68,18 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
self.show_status = not self.show_status
self.update()
elif key == a_key['DISCONNECTED_HIDE']: #user wants to (un)hide disconnected contacts
self.host.bridge.setParam(C.SHOW_OFFLINE_CONTACTS, C.str(not self.show_disconnected), "General", profile_key=self.host.profile)
self.host.bridge.setParam(C.SHOW_OFFLINE_CONTACTS, C.str(not self.show_disconnected), "General", profile_key=self.profile)
elif key == a_key['RESOURCES_HIDE']: #user wants to (un)hide contacts resources
self.showResources(not self.show_resources)
self.update()
return super(ContactList, self).keypress(size, key)
def __contains__(self, jid):
for group in self.groups:
if jid.bare in self.groups[group][1]:
return True
return False
# modify the contact list
def setFocus(self, text, select=False):
"""give focus to the first element that matches the given text. You can also
pass in text a sat_frontends.tools.jid.JID (it's a subclass of unicode).
@param text: contact group name, contact or muc userhost, muc private dialog jid
@param select: if True, the element is also clicked
"""
@ -103,12 +90,8 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
# contact group
value = widget.getValue()
elif isinstance(widget, sat_widgets.SelectableText):
if widget.data.startswith(C.PRIVATE_PREFIX):
# muc private dialog
value = widget.getValue()
else:
# contact or muc
value = widget.data
# contact or muc
value = widget.data
else:
# Divider instance
continue
@ -116,243 +99,214 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
if text.strip() == value.strip():
self.frame.body.focus_position = idx
if select:
self.__contactClicked(widget, True)
self._contactClicked(False, widget, True)
return
except AttributeError:
pass
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.bare)
log.debug(u"Not element found for {} in setFocus".format(text))
def specialResourceVisible(self, entity):
"""Assure a resource of a special entity is visible and clickable
Mainly used to display private conversation in MUC rooms
@param entity: full jid of the resource to show
"""
assert isinstance(entity, jid.JID)
if entity not in self._special_extras:
self._special_extras.add(entity)
self.update()
# events
def _groupClicked(self, group_wid):
group = group_wid.getValue()
data = self.getGroupData(group)
data[C.GROUP_DATA_FOLDED] = not data.setdefault(C.GROUP_DATA_FOLDED, False)
self.setFocus(group)
self.update()
def __groupClicked(self, group_wid):
group = self.groups[group_wid.getValue()]
group[0] = not group[0]
self.update()
self.setFocus(group_wid.getValue())
def _contactClicked(self, use_bare_jid, contact_wid, selected):
"""Method called when a contact is clicked
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.host.modeHint('INSERTION')
@param use_bare_jid: True if use_bare_jid is set in self._buildEntityWidget.
If True, all jids in self._alerts with the same bare jid has contact_wid.data will be removed
@param contact_wid: widget of the contact, must have the entity set in data attribute
@param selected: boolean returned by the widget, telling if it is selected
"""
entity = contact_wid.data
if use_bare_jid:
to_remove = set()
for alert_entity in self._alerts:
if alert_entity.bare == entity.bare:
to_remove.add(alert_entity)
self._alerts.difference_update(to_remove)
else:
self._alerts.discard(entity)
self.host.modeHint(C.MODE_INSERTION)
self.update()
self._emit('click')
self._emit('click', entity)
def onPresenceUpdate(self, entity, show, priority, statuses, profile):
super(ContactList, self).onPresenceUpdate(entity, show, priority, statuses, profile)
self.update()
def onNickUpdate(self, entity, new_nick, profile):
self.update()
# Methods to build the widget
def _buildEntityWidget(self, entity, keys=None, use_bare_jid=False, with_alert=True, with_show_attr=True, markup_prepend=None, markup_append = None):
"""Build one contact markup data
@param entity (jid.JID): entity to build
@param keys (iterable): value to markup, in preferred order.
The first available key will be used.
If key starts with "cache_", it will be checked in cache,
else, getattr will be done on entity with the key (e.g. getattr(entity, 'node')).
If nothing full or keys is None, full entity is used.
@param use_bare_jid (bool): if True, use bare jid for alerts and selected comparisons
@param with_alert (bool): if True, show alert if entity is in self._alerts
@param with_show_attr (bool): if True, show color corresponding to presence status
@param markup_prepend (list): markup to prepend to the generated one before building the widget
@param markup_append (list): markup to append to the generated one before building the widget
@return (list): markup data are expected by Urwid text widgets
"""
markup = []
if use_bare_jid:
alerts = {entity.bare for entity in self._alerts}
selected = {entity.bare for entity in self._selected}
else:
alerts = self._alerts
selected = self._selected
if keys is None:
entity_txt = entity
else:
cache = self.getCache(entity)
for key in keys:
if key.startswith('cache_'):
entity_txt = cache.get(key[6:])
else:
entity_txt = getattr(entity, key)
if entity_txt:
break
if not entity_txt:
entity_txt = entity
if with_show_attr:
show = self.getCache(entity, C.PRESENCE_SHOW)
if show is None:
show = C.PRESENCE_UNAVAILABLE
show_icon, entity_attr = C.PRESENCE.get(show, ('', 'default'))
markup.insert(0, u"{} ".format(show_icon))
else:
entity_attr = 'default'
if with_alert and entity in alerts:
entity_attr = 'alert'
header = C.ALERT_HEADER
else:
header = ''
markup.append((entity_attr, entity_txt))
if markup_prepend:
markup.insert(0, markup_prepend)
if markup_append:
markup.extend(markup_append)
widget = sat_widgets.SelectableText(markup,
selected = entity in selected,
header = header)
widget.data = entity
widget.comp = entity_txt.lower() # value to use for sorting
urwid.connect_signal(widget, 'change', self._contactClicked, user_args=[use_bare_jid])
return widget
def _buildEntities(self, content, entities):
"""Add entity representation in widget list
def __buildContact(self, content, contacts):
"""Add contact representation in widget list
@param content: widget list, e.g. SimpleListWalker
@param contacts (list): list of JID userhosts"""
if not contacts:
@param entities (iterable): iterable of JID to display
"""
if not entities:
return
widgets = [] # list of built widgets
for contact in contacts:
if contact.startswith(C.PRIVATE_PREFIX):
contact_disp = ('alert' if contact in self.alert_jid else "show_normal", unescapePrivate(contact))
show_icon = ''
status = ''
for entity in entities:
if entity in self._specials or not self.entityToShow(entity):
continue
markup_extra = []
if self.show_resources:
for resource in self.getCache(entity, C.CONTACT_RESOURCES):
resource_disp = ('resource_main' if resource == self.getCache(entity, C.CONTACT_MAIN_RESOURCE) else 'resource', "\n " + resource)
markup_extra.append(resource_disp)
if self.show_status:
status = self.getCache(jid.JID('%s/%s' % (entity, resource)), 'status')
status_disp = ('status', "\n " + status) if status else ""
markup_extra.append(status_disp)
else:
jid = JID(contact)
name = self.getCache(jid, 'name')
nick = self.getCache(jid, 'nick')
status = self.getCache(jid, 'status')
show = self.getCache(jid, 'show')
if show is None:
show = "unavailable"
if not self.contactToShow(contact):
continue
show_icon, show_attr = C.PRESENCE.get(show, ('', 'default'))
contact_disp = ('alert' if contact in self.alert_jid else show_attr, nick or name or jid.node or jid.bare)
display = [show_icon + " ", contact_disp]
if self.show_status:
status_disp = ('status', "\n " + status) if status else ""
display.append(status_disp)
header = '(*) ' if contact in self.alert_jid else ''
widget = sat_widgets.SelectableText(display,
selected=contact == self.selected,
header=header)
widget.data = contact
widget.comp = contact_disp[1].lower() # value to use for sorting
if self.show_status:
status = self.getCache(entity, 'status')
status_disp = ('status', "\n " + status) if status else ""
markup_extra.append(status_disp)
widget = self._buildEntityWidget(entity, ('cache_nick', 'cache_name', 'node'), use_bare_jid=True, markup_append=markup_extra)
widgets.append(widget)
widgets.sort(key=lambda widget: widget.comp)
for widget in widgets:
content.append(widget)
urwid.connect_signal(widget, 'change', self.__contactClicked)
def __buildSpecials(self, content):
def _buildSpecials(self, content):
"""Build the special entities"""
specials = self.specials.keys()
specials = list(self._specials)
specials.sort()
for special in specials:
jid=JID(special)
name = self.getCache(jid, 'name')
nick = self.getCache(jid, 'nick')
special_disp = ('alert' if special in self.alert_jid else 'default', nick or name or jid.node or jid.bare)
display = [ " " , special_disp]
header = '(*) ' if special in self.alert_jid else ''
widget = sat_widgets.SelectableText(display,
selected = special==self.selected,
header=header)
widget.data = special
extra_shown = set()
for entity in specials:
# the special widgets
widget = self._buildEntityWidget(entity, ('cache_nick', 'cache_name', 'node'), with_show_attr=False)
content.append(widget)
urwid.connect_signal(widget, 'change', self.__contactClicked)
def __buildList(self):
# resources which must be displayed (e.g. MUC private conversations)
extras = [extra for extra in self._special_extras if extra.bare == entity.bare]
extras.sort()
for extra in extras:
widget = self._buildEntityWidget(extra, ('resource',), markup_prepend = ' ')
content.append(widget)
extra_shown.add(extra)
# entities which must be visible but not resource of current special entities
for extra in self._special_extras.difference(extra_shown):
widget = self._buildEntityWidget(extra, ('resource',))
content.append(widget)
def _buildList(self):
"""Build the main contact list widget"""
content = urwid.SimpleListWalker([])
self.__buildSpecials(content)
if self.specials:
self._buildSpecials(content)
if self._specials:
content.append(urwid.Divider('='))
group_keys = self.groups.keys()
group_keys.sort(key=lambda x: x.lower() if x else x)
for key in group_keys:
unfolded = self.groups[key][0]
contacts = list(self.groups[key][1])
if key is not None and (self.nonEmptyGroup(contacts) or self.show_empty_groups):
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, contacts)
return urwid.ListBox(content)
def contactToShow(self, contact):
"""Tell if the contact should be showed or hidden.
@param contact (str): JID userhost of the contact
@return: True if that contact should be showed in the list"""
show = self.getCache(JID(contact), 'show')
return (show is not None and show != "unavailable") or \
self.show_disconnected or contact in self.alert_jid or contact == self.selected
def nonEmptyGroup(self, contacts):
"""Tell if a contact group contains some contacts to show.
@param contacts (list[str]): list of JID userhosts
@return: bool
"""
for contact in contacts:
if self.contactToShow(contact):
return True
return False
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 getContact(self):
"""Return contact currently selected"""
return self.selected
def clearContacts(self):
"""clear all the contact list"""
QuickContactList.clearContacts(self)
self.groups={}
self.selected = None
self.unselectAll()
self.update()
def replace(self, jid, groups=None, attributes=None):
"""Add a contact to the list if doesn't exist, else update it.
This method can be called with groups=None for the purpose of updating
the contact's attributes (e.g. nickname). In that case, the groups
attribute must not be set to the default group but ignored. If not,
you may move your contact from its actual group(s) to the default one.
None value for 'groups' has a different meaning than [None] which is for the default group.
@param jid (JID)
@param groups (list): list of groups or None to ignore the groups membership.
@param attributes (dict)
"""
QuickContactList.replace(self, jid, groups, attributes) # eventually change the nickname
if jid.bare in self.specials:
return
if groups is None:
self.update()
return
assert isinstance(jid, JID)
assert isinstance(groups, list)
if groups == []:
groups = [None] # [None] is the default group
for group in [group for group in self.groups if group not in groups]:
try: # remove the contact from a previous group
self.groups[group][1].remove(jid.bare)
except KeyError:
pass
groups = list(self._groups)
groups.sort(key=lambda x: x.lower() if x else x)
for group in groups:
if group not in self.groups:
self.groups[group] = [True, set()] # [unfold, list_of_contacts]
self.groups[group][1].add(jid.bare)
self.update()
data = self.getGroupData(group)
folded = data.get(C.GROUP_DATA_FOLDED, False)
jids = list(data['jids'])
if group is not None and (self.anyEntityToShow(jids) or self.show_empty_groups):
header = '[-]' if not folded else '[+]'
widget = sat_widgets.ClickableText(group, header=header + ' ')
content.append(widget)
urwid.connect_signal(widget, 'click', self._groupClicked)
if not folded:
self._buildEntities(content, jids)
not_in_roster = set(self._cache).difference(self._roster).difference(self._specials).difference((self.whoami.bare,))
if not_in_roster:
content.append(urwid.Divider('-'))
self._buildEntities(content, not_in_roster)
def remove(self, jid):
"""remove a contact from the list"""
QuickContactList.remove(self, jid)
groups_to_remove = []
for group in self.groups:
contacts = self.groups[group][1]
if jid.bare in contacts:
contacts.remove(jid.bare)
if not len(contacts):
groups_to_remove.append(group)
for group in groups_to_remove:
del self.groups[group]
self.update()
def add(self, jid, param_groups=None):
"""add a contact to the list"""
self.replace(jid, param_groups if param_groups else [None])
def setSpecial(self, special_jid, special_type, show=False):
"""Set entity as a special
@param special_jid: jid of the entity
@param special_type: special type (e.g.: "MUC")
@param show: True to display the dialog to chat with this entity
"""
QuickContactList.setSpecial(self, special_jid, special_type, show)
if None in self.groups:
folded, group_jids = self.groups[None]
for group_jid in group_jids:
if JID(group_jid).bare == special_jid.bare:
group_jids.remove(group_jid)
break
self.update()
if show:
# also display the dialog for this room
self.setFocus(special_jid, True)
self.host.redraw()
def updatePresence(self, jid, show, priority, statuses):
#XXX: for the moment, we ignore presence updates for special entities
if jid.bare not in self.specials:
QuickContactList.updatePresence(self, jid, show, priority, statuses)
def showOfflineContacts(self, show):
show = C.bool(show)
if self.show_disconnected == show:
return
self.show_disconnected = show
self.update()
def showEmptyGroups(self, show):
show = C.bool(show)
if self.show_empty_groups == show:
return
self.show_empty_groups = show
self.update()
return urwid.ListBox(content)

View file

@ -0,0 +1,348 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Primitivus: a SAT frontend
# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sat.core.i18n import _
import urwid
from urwid_satext import sat_widgets
from sat_frontends.tools.games import TarotCard
from sat_frontends.quick_frontend.quick_game_tarot import QuickTarotGame
from sat_frontends.primitivus import xmlui
from sat_frontends.primitivus.keys import action_key_map as a_key
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 == a_key['CARD_SELECT']:
self.select(not self.__selected)
self._emit('click')
return key
def mouse_event(self, size, event, button, x, y, focus):
if urwid.is_mouse_event(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 == a_key['CARD_SELECT']:
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"""
try:
del self.columns.widget_list[:]
del self.columns.column_types[:]
except IndexError:
pass
self.columns.contents.append((urwid.Text(''), ('weight', 1, False)))
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.contents.append((urwid.Text(''), ('weight', 1, False)))
self.columns.focus_position = 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 TarotGame(QuickTarotGame, urwid.WidgetWrap):
"""Widget for card games"""
def __init__(self, parent, referee, players):
QuickTarotGame.__init__(self, parent, referee, players)
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(self.player_nick, referee, self.parent.profile)
def loadCards(self):
"""Load all the cards in memory"""
QuickTarotGame.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 tarotGameNewHandler(self, hand):
"""Start a new game, with given hand"""
if hand is []: # reset the display after the scores have been showed
self.resetRound()
for location in ['top', 'left', 'bottom', 'right']:
self.table.putCard(location, None)
self.parent.host.redraw()
self.parent.host.bridge.tarotGameReady(self.player_nick, self.referee, self.parent.profile)
return
QuickTarotGame.tarotGameNewHandler(self, hand)
self.hand_wid.update(self.hand)
self.parent.host.redraw()
def tarotGameChooseContratHandler(self, xml_data):
"""Called when the player has to select his contrat
@param xml_data: SàT xml representation of the form"""
form = xmlui.create(self.parent.host, xml_data, title=_('Please choose your contrat'), flags=['NO_CANCEL'], profile=self.parent.profile)
form.show(valign='top')
def tarotGameShowCardsHandler(self, game_stage, cards, data):
"""Display cards in the middle of the game (to show for e.g. chien ou poignée)"""
QuickTarotGame.tarotGameShowCardsHandler(self, game_stage, cards, data)
self.center.widget_list[1] = urwid.Filler(Hand(self.to_show))
self.parent.host.redraw()
def tarotGameYourTurnHandler(self):
QuickTarotGame.tarotGameYourTurnHandler(self)
def tarotGameScoreHandler(self, xml_data, winners, loosers):
"""Called when the round is over, display the scores
@param xml_data: SàT xml representation of the form"""
if not winners and not loosers:
title = _("Draw game")
else:
title = _('You win \o/') if self.player_nick in winners else _('You loose :(')
form = xmlui.create(self.parent.host, xml_data, title=title, flags=['NO_CANCEL'], profile=self.parent.profile)
form.show()
def tarotGameInvalidCardsHandler(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"""
QuickTarotGame.tarotGameInvalidCardsHandler(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 tarotGameCardsPlayedHandler(self, player, cards):
"""A card has been played by player"""
QuickTarotGame.tarotGameCardsPlayedHandler(self, player, cards)
self.table.putCard(self.getPlayerLocation(player), self.played[player])
self._checkState()
self.parent.host.redraw()
def _checkState(self):
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)
##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
self._checkState()
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)], self.parent.profile)
self.hand.remove(card)
self.hand_wid.update(self.hand)
self.state = "wait"
def onEcartDone(self, button):
"""Called when player has finished his é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, self.parent.profile)
self.state = "wait"
self.parent.host.removePopUp()

View file

@ -42,15 +42,18 @@ action_key_map.update(
("menu_global", "APP_QUIT"): 'ctrl x',
("menu_global", "ROOM_JOIN"): 'meta j',
# primitivus widgets
("primitivus_widget", "DECORATION_HIDE"): "meta l",
# contact list
("contact_list", "STATUS_HIDE"): "meta s",
("contact_list", "DISCONNECTED_HIDE"): "meta d",
("contact_list", "RESOURCES_HIDE"): "meta r",
# chat panel
("chat_panel", "OCCUPANTS_HIDE"): "meta p",
("chat_panel", "TIMESTAMP_HIDE"): "meta t",
("chat_panel", "SHORT_NICKNAME"): "meta n",
("chat_panel", "DECORATION_HIDE"): "meta l",
("chat_panel", "SUBJECT_SWITCH"): "meta s",
#card game

View file

@ -27,9 +27,10 @@ log = logging.getLogger(__name__)
import urwid
from urwid_satext import sat_widgets
from urwid_satext.files_management import FileDialog
from sat_frontends.bridge.DBus import DBusBridgeFrontend
from sat_frontends.quick_frontend.quick_app import QuickApp
from sat_frontends.quick_frontend.quick_chat_list import QuickChatList
from sat_frontends.quick_frontend.quick_utils import getNewPath, unescapePrivate
from sat_frontends.quick_frontend import quick_utils
from sat_frontends.quick_frontend import quick_chat
from sat_frontends.primitivus.profile_manager import ProfileManager
from sat_frontends.primitivus.contact_list import ContactList
from sat_frontends.primitivus.chat import Chat
@ -39,35 +40,27 @@ from sat_frontends.primitivus.notify import Notify
from sat_frontends.primitivus.keys import action_key_map as a_key
from sat_frontends.primitivus import config
from sat_frontends.tools.misc import InputHistory
from sat_frontends.constants import Const as commonConst # FIXME
from sat_frontends.tools.jid import JID
from sat_frontends.tools import jid
from os.path import join
import signal
class ChatList(QuickChatList):
"""This class manage the list of chat windows"""
def createChat(self, target):
return Chat(target, self.host)
class EditBar(sat_widgets.ModalEdit):
"""
The modal edit bar where you would enter messages and commands.
"""
def __init__(self, app):
modes = {None: ('NORMAL', u''),
a_key['MODE_INSERTION']: ('INSERTION', u'> '),
a_key['MODE_COMMAND']: ('COMMAND', u':')} #XXX: captions *MUST* be unicode
def __init__(self, host):
modes = {None: (C.MODE_NORMAL, u''),
a_key['MODE_INSERTION']: (C.MODE_INSERTION, u'> '),
a_key['MODE_COMMAND']: (C.MODE_COMMAND, u':')} #XXX: captions *MUST* be unicode
super(EditBar, self).__init__(modes)
self.app = app
self.host = host
self.setCompletionMethod(self._text_completion)
urwid.connect_signal(self, 'click', self.onTextEntered)
def _text_completion(self, text, completion_data, mode):
if mode == 'INSERTION':
if mode == C.MODE_INSERTION:
return self._nick_completion(text, completion_data)
else:
return text
@ -75,41 +68,42 @@ class EditBar(sat_widgets.ModalEdit):
def _nick_completion(self, text, completion_data):
"""Completion method which complete pseudo in group chat
for params, see AdvancedEdit"""
contact = self.app.contact_list.getContact() ###Based on the fact that there is currently only one contact selectable at once
if contact:
chat = self.app.chat_wins[contact]
if chat.type != "group":
return text
space = text.rfind(" ")
start = text[space+1:]
nicks = list(chat.occupants)
nicks = []
for profile, clist in self.host.contact_lists.iteritems():
for contact in clist.getContacts():
chat = self.host.widgets.getWidget(quick_chat.QuickChat, contact, profile)
if chat.type != C.CHAT_GROUP:
continue
space = text.rfind(" ")
start = text[space + 1:]
nicks.extend(list(chat.occupants))
if nicks:
nicks.sort()
try:
start_idx=nicks.index(completion_data['last_nick'])+1
start_idx = nicks.index(completion_data['last_nick']) + 1
if start_idx == len(nicks):
start_idx = 0
except (KeyError,ValueError):
except (KeyError, ValueError):
start_idx = 0
for idx in range(start_idx,len(nicks)) + range(0,start_idx):
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[:space + 1] + nicks[idx] + (': ' if space < 0 else '')
return text
def onTextEntered(self, editBar):
"""Called when text is entered in the main edit bar"""
if self.mode == 'INSERTION':
contact = self.app.contact_list.getContact() ###Based on the fact that there is currently only one contact selectableat once
if contact:
chat = self.app.chat_wins[contact]
self.app.sendMessage(contact,
if self.mode == C.MODE_INSERTION:
if isinstance(self.host.selected_widget, quick_chat.QuickChat):
chat_widget = self.host.selected_widget
self.host.sendMessage(chat_widget.target,
editBar.get_edit_text(),
mess_type = "groupchat" if chat.type == 'group' else "chat",
errback=lambda failure: self.app.notify(_("Error while sending message (%s)") % failure),
profile_key=self.app.profile
mess_type = "groupchat" if chat_widget.type == 'group' else "chat", # TODO: put this in QuickChat
errback=lambda failure: self.host.notify(_("Error while sending message ({})").format(failure)),
profile_key=chat_widget.profile
)
editBar.set_edit_text('')
elif self.mode == 'COMMAND':
elif self.mode == C.MODE_COMMAND:
self.commandHandler()
def commandHandler(self):
@ -118,42 +112,44 @@ class EditBar(sat_widgets.ModalEdit):
tokens = self.get_edit_text().split(' ')
command, args = tokens[0], tokens[1:]
if command == 'quit':
self.app.onExit()
self.host.onExit()
raise urwid.ExitMainLoop()
elif command == 'messages':
wid = sat_widgets.GenericList(logging.memoryGet())
self.app.addWindow(wid)
elif command == 'presence':
values = [value for value in commonConst.PRESENCE.keys()]
values = [value if value else 'online' for value in values] # the empty value actually means 'online'
if args and args[0] in values:
presence = '' if args[0] == 'online' else args[0]
self.app.status_bar.onChange(user_data=sat_widgets.ClickableText(commonConst.PRESENCE[presence]))
else:
self.app.status_bar.onPresenceClick()
elif command == 'status':
if args:
self.app.status_bar.onChange(user_data=sat_widgets.AdvancedEdit(args[0]))
else:
self.app.status_bar.onStatusClick()
self.host.selectWidget(wid)
# elif command == 'presence':
# values = [value for value in commonConst.PRESENCE.keys()]
# values = [value if value else 'online' for value in values] # the empty value actually means 'online'
# if args and args[0] in values:
# presence = '' if args[0] == 'online' else args[0]
# self.host.status_bar.onChange(user_data=sat_widgets.ClickableText(commonConst.PRESENCE[presence]))
# else:
# self.host.status_bar.onPresenceClick()
# elif command == 'status':
# if args:
# self.host.status_bar.onChange(user_data=sat_widgets.AdvancedEdit(args[0]))
# else:
# self.host.status_bar.onStatusClick()
elif command == 'history':
try:
limit = int(args[0])
except (IndexError, ValueError):
limit = 50
win = self.app.chat_wins[JID(self.app.contact_list.selected).bare]
win.clearHistory()
if limit > 0:
win.historyPrint(size=limit, profile=self.app.profile)
widget = self.host.selected_widget
if isinstance(widget, quick_chat.QuickChat):
try:
limit = int(args[0])
except (IndexError, ValueError):
limit = 50
widget.clearHistory()
if limit > 0:
widget.historyPrint(size=limit, profile=widget.profile)
elif command == 'search':
pattern = " ".join(args)
if not pattern:
self.app.notif_bar.addMessage(D_("Please specify the globbing pattern to search for"))
win = self.app.chat_wins[JID(self.app.contact_list.selected).bare]
win.clearHistory()
win.printInfo(D_("Results for searching the globbing pattern: %s") % pattern, timestamp=0)
win.historyPrint(size=C.HISTORY_LIMIT_NONE, search=pattern, profile=self.app.profile)
win.printInfo(D_("Type ':history <lines>' to reset the chat history"))
widget = self.host.selected_widget
if isinstance(widget, quick_chat.QuickChat):
pattern = " ".join(args)
if not pattern:
self.host.notif_bar.addMessage(D_("Please specify the globbing pattern to search for"))
widget.clearHistory()
widget.printInfo(D_("Results for searching the globbing pattern: %s") % pattern, timestamp=0)
widget.historyPrint(size=C.HISTORY_LIMIT_NONE, search=pattern, profile=widget.profile)
widget.printInfo(D_("Type ':history <lines>' to reset the chat history"))
else:
return
self.set_edit_text('')
@ -167,22 +163,24 @@ class EditBar(sat_widgets.ModalEdit):
and move the index of the temporary history stack."""
if key == a_key['MODAL_ESCAPE']:
# first save the text to the current mode, then change to NORMAL
self.app._updateInputHistory(self.get_edit_text(), mode=self.mode)
self.app._updateInputHistory(mode='NORMAL')
if self._mode == 'NORMAL' and key in self._modes:
self.app._updateInputHistory(mode=self._modes[key][0])
self.host._updateInputHistory(self.get_edit_text(), mode=self.mode)
self.host._updateInputHistory(mode=C.MODE_NORMAL)
if self._mode == C.MODE_NORMAL and key in self._modes:
self.host._updateInputHistory(mode=self._modes[key][0])
if key == a_key['HISTORY_PREV']:
self.app._updateInputHistory(self.get_edit_text(), -1, self._historyCb, self.mode)
self.host._updateInputHistory(self.get_edit_text(), -1, self._historyCb, self.mode)
return
elif key == a_key['HISTORY_NEXT']:
self.app._updateInputHistory(self.get_edit_text(), +1, self._historyCb, self.mode)
self.host._updateInputHistory(self.get_edit_text(), +1, self._historyCb, self.mode)
return
elif key == a_key['EDIT_ENTER']:
self.app._updateInputHistory(self.get_edit_text(), mode=self.mode)
self.host._updateInputHistory(self.get_edit_text(), mode=self.mode)
else:
contact = self.app.contact_list.getContact()
if contact:
self.app.bridge.chatStateComposing(unescapePrivate(contact), self.app.profile)
if (self._mode == C.MODE_INSERTION
and isinstance(self.host.selected_widget, quick_chat.QuickChat)
and key not in sat_widgets.FOCUS_KEYS):
self.host.bridge.chatStateComposing(self.host.selected_widget.target, self.host.selected_widget.profile)
return super(EditBar, self).keypress(size, key)
@ -281,18 +279,19 @@ class PrimitivusTopWidget(sat_widgets.FocusPile):
class PrimitivusApp(QuickApp, InputHistory):
def __init__(self):
QuickApp.__init__(self)
QuickApp.__init__(self, create_bridge=DBusBridgeFrontend, check_options=quick_utils.check_options)
## main loop setup ##
self.main_widget = ProfileManager(self)
self.loop = urwid.MainLoop(self.main_widget, C.PALETTE, event_loop=urwid.GLibEventLoop(), input_filter=self.inputFilter, unhandled_input=self.keyHandler)
##misc setup##
self.chat_wins = ChatList(self)
self._visible_widgets = set()
self.notif_bar = sat_widgets.NotificationBar()
urwid.connect_signal(self.notif_bar, 'change', self.onNotification)
self.progress_wid = Progress(self)
urwid.connect_signal(self.notif_bar.progress, 'click', lambda x: self.addWindow(self.progress_wid))
self.progress_wid = self.widgets.getOrCreateWidget(Progress, None, on_new_widget=None)
urwid.connect_signal(self.notif_bar.progress, 'click', lambda x: self.selectWidget(self.progress_wid))
self.__saved_overlay = None
self.x_notify = Notify()
@ -300,6 +299,10 @@ class PrimitivusApp(QuickApp, InputHistory):
# we already manage exit with a_key['APP_QUIT'], so we don't want C-c
signal.signal(signal.SIGINT, signal.SIG_IGN)
@property
def visible_widgets(self):
return self._visible_widgets
@property
def mode(self):
return self.editBar.mode
@ -351,7 +354,7 @@ class PrimitivusApp(QuickApp, InputHistory):
self._early_popup = popup
else:
self.showPopUp(popup)
super(PrimitivusApp, self).postInit()
super(PrimitivusApp, self).postInit(self.main_widget)
def inputFilter(self, input_, raw):
if self.__saved_overlay and input_ != a_key['OVERLAY_HIDE']:
@ -387,14 +390,14 @@ class PrimitivusApp(QuickApp, InputHistory):
elif input_ == a_key['DEBUG'] and 'D' in self.bridge.getVersion(): #Debug only for dev versions
self.debug()
elif input_ == a_key['CONTACTS_HIDE']: #user wants to (un)hide the contact_list
elif input_ == a_key['CONTACTS_HIDE']: #user wants to (un)hide the contact lists
try:
for wid, options in self.center_part.contents:
if self.contact_list is wid:
if self.contact_lists_pile is wid:
self.center_part.contents.remove((wid, options))
break
else:
self.center_part.contents.insert(0, (self.contact_list, ('weight', 2, False)))
self.center_part.contents.insert(0, (self.contact_lists_pile, ('weight', 2, False)))
except AttributeError:
#The main widget is not built (probably in Profile Manager)
pass
@ -413,17 +416,18 @@ class PrimitivusApp(QuickApp, InputHistory):
except AttributeError:
return input_
def addMenus(self, menu, type_, menu_data=None):
def addMenus(self, menu, type_filter, menu_data=None):
"""Add cached menus to instance
@param menu: sat_widgets.Menu instance
@param type_: menu type like is sat.core.sat_main.importMenu
@param type_filter: menu type like is sat.core.sat_main.importMenu
@param menu_data: data to send with these menus
"""
menus = self.profiles[self.profile]['menus'].get(type_,[])
def add_menu_cb(callback_id):
self.launchAction(callback_id, menu_data, profile_key = self.profile)
for id_, path, path_i18n in menus:
self.launchAction(callback_id, menu_data, profile=self.current_profile)
for id_, type_, path, path_i18n, extra in self.bridge.getMenus("", C.NO_SECURITY_LIMIT ): # TODO: manage extra
if type_ != type_filter:
continue
if len(path) != 2:
raise NotImplementedError("Menu with a path != 2 are not implemented yet")
menu.addMenu(path_i18n[0], path_i18n[1], lambda dummy,id_=id_: add_menu_cb(id_))
@ -445,23 +449,27 @@ class PrimitivusApp(QuickApp, InputHistory):
#FIXME: do this in a more generic way (in quickapp)
self.addMenus(menu, C.MENU_GLOBAL)
menu_roller = sat_widgets.MenuRoller([(_('Main menu'),menu)])
menu_roller = sat_widgets.MenuRoller([(_('Main menu'), menu, C.MENU_ID_MAIN)])
return menu_roller
def _buildMainWidget(self):
self.contact_list = ContactList(self, on_click=self.contactSelected, on_change=lambda w: self.redraw())
#self.center_part = urwid.Columns([('weight',2,self.contact_list),('weight',8,Chat('',self))])
self.center_part = urwid.Columns([('weight', 2, self.contact_list), ('weight', 8, urwid.Filler(urwid.Text('')))])
self.contact_lists_pile = urwid.Pile([])
#self.center_part = urwid.Columns([('weight',2,self.contact_lists[profile]),('weight',8,Chat('',self))])
self.center_part = urwid.Columns([('weight', 2, self.contact_lists_pile), ('weight', 8, urwid.Filler(urwid.Text('')))])
self.editBar = EditBar(self)
self.menu_roller = self._buildMenuRoller()
self.main_widget = PrimitivusTopWidget(self.center_part, self.menu_roller, self.notif_bar, self.editBar)
return self.main_widget
def plug_profile_1(self, profile_key='@DEFAULT@'):
def addContactList(self, profile):
contact_list = ContactList(self, on_click=self.contactSelected, on_change=lambda w: self.redraw(), profile=profile)
self.contact_lists_pile.contents.append((contact_list, ('weight', 1)))
return contact_list
def plugging_profiles(self):
self.loop.widget = self._buildMainWidget()
self.redraw()
QuickApp.plug_profile_1(self, profile_key)
try:
# if a popup arrived before main widget is build, we need to show it now
self.showPopUp(self._early_popup)
@ -492,21 +500,38 @@ class PrimitivusApp(QuickApp, InputHistory):
self.notif_bar.addMessage(message)
self.redraw()
def addWindow(self, widget):
"""Display a window if possible,
def newWidget(self, widget):
self.selectWidget(widget)
def selectWidget(self, widget):
"""Display a widget if possible,
else add it in the notification bar queue
@param widget: BoxWidget"""
assert(len(self.center_part.widget_list)<=2)
@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.contact_list.unselectAll()
try:
self.menu_roller.removeMenu(C.MENU_ID_WIDGET)
except KeyError:
log.debug("No menu to delete")
self.selected_widget = widget
self._visible_widgets = set([widget]) # XXX: we can only have one widget visible at the time for now
for contact_list in self.contact_lists.itervalues():
contact_list.unselectAll()
for wid in self.visible_widgets:
if isinstance(wid, Chat):
contact_list = self.contact_lists[wid.profile]
contact_list.select(wid.target)
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)
#TODO: better Window management than this 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.focus_position = 0
@ -522,23 +547,14 @@ class PrimitivusApp(QuickApp, InputHistory):
"""Set the progression shown in notification bar"""
self.notif_bar.setProgress(percentage)
def contactSelected(self, contact_list):
contact = contact_list.getContact()
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 newMessageHandler(self, from_jid, to_jid, msg, _type, extra, profile):
QuickApp.newMessageHandler(self, from_jid, to_jid, msg, _type, extra, profile)
if not from_jid in self.contact_list and from_jid.bare != self.profiles[profile]['whoami'].bare:
#XXX: needed to show entities which haven't sent any
# presence information and which are not in roster
self.contact_list.replace(from_jid, [C.GROUP_NOT_IN_ROSTER])
if self.contact_list.selected is None or JID(self.contact_list.selected).bare != from_jid.bare:
self.contact_list.putAlert(from_jid)
def contactSelected(self, contact_list, entity):
if entity.resource:
# we have clicked on a private MUC conversation
chat_widget = self.widgets.getOrCreateWidget(Chat, entity, on_new_widget=None, force_hash = Chat.getPrivateHash(contact_list.profile, entity), profile=contact_list.profile)
else:
chat_widget = self.widgets.getOrCreateWidget(Chat, entity, on_new_widget=None, profile=contact_list.profile)
self.selectWidget(chat_widget)
self.menu_roller.addMenu(_('Chat menu'), chat_widget.getMenu(), C.MENU_ID_WIDGET)
def _dialogOkCb(self, widget, data):
self.removePopUp()
@ -552,7 +568,6 @@ class PrimitivusApp(QuickApp, InputHistory):
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
@ -578,11 +593,15 @@ class PrimitivusApp(QuickApp, InputHistory):
else:
self.main_widget.show('notif_bar')
def launchAction(self, callback_id, data=None, profile_key="@NONE@"):
def launchAction(self, callback_id, data=None, callback=None, profile=C.PROF_KEY_NONE):
""" Launch a dynamic action
@param callback_id: id of the action to launch
@param data: data needed only for certain actions
@param profile_key: %(doc_profile_key)s
@param callback: if not None and 'validated' key is present, it will be called with the following parameters:
- callback_id
- data
- profile
@param profile: %(doc_profile)s
"""
if data is None:
@ -593,31 +612,27 @@ class PrimitivusApp(QuickApp, InputHistory):
# action was a one shot, nothing to do
pass
elif "xmlui" in data:
ui = xmlui.create(self, xml_data=data['xmlui'])
ui = xmlui.create(self, xml_data=data['xmlui'], callback=callback, profile=profile)
ui.show()
elif "authenticated_profile" in data:
assert("caller" in data)
if data["caller"] == "profile_manager":
assert(isinstance(self.main_widget, ProfileManager))
self.main_widget.getXMPPParams(data['authenticated_profile'])
elif data["caller"] == "plug_profile":
self.plug_profile_1(data['authenticated_profile'])
else:
raise NotImplementedError
elif 'validated' in data:
pass # this key is managed below
else:
self.showPopUp(sat_widgets.Alert(_("Error"), _(u"Unmanaged action result"), ok_cb=self.removePopUp))
if callback and 'validated' in data:
callback(callback_id, data, profile)
def action_eb(failure):
self.showPopUp(sat_widgets.Alert(failure.fullname, failure.message, ok_cb=self.removePopUp))
self.bridge.launchAction(callback_id, data, profile_key, callback=action_cb, errback=action_eb)
self.bridge.launchAction(callback_id, data, profile, callback=action_cb, errback=action_eb)
def askConfirmationHandler(self, confirmation_id, confirmation_type, data, profile):
answer_data={}
def dir_selected_cb(path):
dest_path = join(path, data['filename'])
answer_data["dest_path"] = getNewPath(dest_path)
answer_data["dest_path"] = quick_utils.getNewPath(dest_path)
self.addProgress(confirmation_id, dest_path)
accept_cb(None)
@ -660,7 +675,7 @@ class PrimitivusApp(QuickApp, InputHistory):
title = _('Registration')
misc['target'] = data['target']
misc['action_back'] = self.bridge.gatewayRegister
ui = xmlui.create(self, title=title, xml_data = data['xml'], misc = misc)
ui = xmlui.create(self, title=title, xml_data=data['xml'], misc=misc, profile=profile)
if data['type'] == 'registration':
ui.show('popup')
else:
@ -684,32 +699,39 @@ class PrimitivusApp(QuickApp, InputHistory):
log.error (_("FIXME FIXME FIXME: type [%s] not implemented") % type_)
raise NotImplementedError
def roomJoinedHandler(self, room_jid_s, room_nicks, user_nick, profile):
super(PrimitivusApp, self).roomJoinedHandler(room_jid_s, room_nicks, user_nick, profile)
self.contact_lists[profile].setFocus(jid.JID(room_jid_s), True)
##DIALOGS CALLBACKS##
def onJoinRoom(self, button, edit):
self.removePopUp()
room_jid = JID(edit.get_edit_text())
room_jid = jid.JID(edit.get_edit_text())
if room_jid.is_valid():
self.bridge.joinMUC(room_jid, self.profiles[self.profile]['whoami'].node, {}, self.profile)
self.bridge.joinMUC(room_jid, self.profiles[self.current_profile].whoami.node, {}, self.current_profile)
else:
message = _("'%s' is an invalid JID !") % room_jid
message = _("'%s' is an invalid jid.JID !") % room_jid
log.error (message)
self.showPopUp(sat_widgets.Alert(_("Error"), message, ok_cb=self.removePopUp))
#MENU EVENTS#
def onConnectRequest(self, menu):
QuickApp.asyncConnect(self, self.profile)
QuickApp.asyncConnect(self, self.current_profile)
def onDisconnectRequest(self, menu):
self.bridge.disconnect(self.profile)
self.disconnect(self.current_profile)
def onParam(self, menu):
def success(params):
ui = xmlui.create(self, xml_data=params)
ui = xmlui.create(self, xml_data=params, profile=self.current_profile)
ui.show()
def failure(error):
self.showPopUp(sat_widgets.Alert(_("Error"), _("Can't get parameters (%s)") % error, ok_cb=self.removePopUp))
self.bridge.getParamsUI(app=C.APP_NAME, profile_key=self.profile, callback=success, errback=failure)
self.bridge.getParamsUI(app=C.APP_NAME, profile_key=self.current_profile, callback=success, errback=failure)
def onExitRequest(self, menu):
QuickApp.onExit(self)
@ -725,12 +747,12 @@ class PrimitivusApp(QuickApp, InputHistory):
#MISC CALLBACKS#
def setStatusOnline(self, online=True, show="", statuses={}):
def setStatusOnline(self, online=True, show="", statuses={}, profile=C.PROF_KEY_NONE):
if not online or not statuses:
self.status_bar.setPresenceStatus(show if online else 'unavailable', '')
self.contact_lists[profile].status_bar.setPresenceStatus(show if online else 'unavailable', '')
return
try:
self.status_bar.setPresenceStatus(show, statuses['default'])
self.contact_lists[profile].status_bar.setPresenceStatus(show, statuses['default'])
except (KeyError, TypeError):
pass

View file

@ -18,29 +18,28 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sat.core.i18n import _
from sat.core import log as logging
log = logging.getLogger(__name__)
from sat_frontends.quick_frontend.quick_profile_manager import QuickProfileManager
from sat_frontends.primitivus.constants import Const as C
import urwid
from urwid_satext.sat_widgets import AdvancedEdit, Password, List, InputDialog, ConfirmDialog, Alert
from sat_frontends.primitivus.keys import action_key_map as a_key
from urwid_satext import sat_widgets
import urwid
class ProfileManager(urwid.WidgetWrap):
class ProfileManager(QuickProfileManager, urwid.WidgetWrap):
def __init__(self, host):
self.host = host
#profiles list
profiles = self.host.bridge.getProfilesList()
profiles.sort()
def __init__(self, host, autoconnect=None):
QuickProfileManager.__init__(self, host, autoconnect)
#login & password box must be created before list because of onProfileChange
self.login_wid = AdvancedEdit(_('Login:'), align='center')
self.pass_wid = Password(_('Password:'), align='center')
self.login_wid = sat_widgets.AdvancedEdit(_('Login:'), align='center')
self.pass_wid = sat_widgets.Password(_('Password:'), align='center')
self.selected_profile = None # allow to reselect the previous selection until the profile is authenticated
style = ['single']
if self.host.options.profile:
style.append('no_first_select')
self.list_profile = List(profiles, style=style, align='center', on_change=self.onProfileChange)
style = ['no_first_select']
profiles = host.bridge.getProfilesList()
profiles.sort()
self.list_profile = sat_widgets.List(profiles, style=style, align='center', on_change=self.onProfileChange)
#new & delete buttons
buttons = [urwid.Button(_("New"), self.onNewProfile),
@ -51,15 +50,18 @@ class ProfileManager(urwid.WidgetWrap):
divider = urwid.Divider('-')
#connect button
connect_button = urwid.Button(_("Connect"), self.onConnectProfile)
connect_button = sat_widgets.CustomButton(_("Connect"), self.onConnectProfiles, align='center')
#we now build the widget
list_walker = urwid.SimpleFocusListWalker([buttons_flow,self.list_profile,divider,self.login_wid, self.pass_wid, connect_button])
list_walker = urwid.SimpleFocusListWalker([buttons_flow,self.list_profile, divider, self.login_wid, self.pass_wid, connect_button])
frame_body = urwid.ListBox(list_walker)
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)
self.go(autoconnect)
def keypress(self, size, key):
if key == a_key['APP_QUIT']:
self.host.onExit()
@ -80,117 +82,96 @@ class ProfileManager(urwid.WidgetWrap):
return
return super(ProfileManager, self).keypress(size, key)
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.asyncCreateProfile(name, callback=lambda: self._newProfileCreated(name), errback=self._profileCreationFailure)
self.host.bridge.asyncCreateProfile(name, callback=lambda: self.newProfileCreated(name), errback=self.profileCreationFailure)
def _newProfileCreated(self, name):
self.__refillProfiles()
#We select the profile created in the list
self.list_profile.selectValue(name)
def newProfileCreated(self, profile):
self.host.removePopUp()
self.refillProfiles()
self.list_profile.selectValue(profile)
self.current.profile=profile
self.getConnectionParams(profile)
self.host.redraw()
def _profileCreationFailure(self, reason):
def profileCreationFailure(self, reason):
self.host.removePopUp()
if reason == "ConflictError":
message = _("A profile with this name already exists")
elif reason == "CancelError":
message = _("Profile creation cancelled by backend")
else:
message = _("Unknown reason (%s)") % reason
popup = Alert(_("Can't create profile"), message, ok_cb=self.host.removePopUp)
self.host.showPopUp(popup)
message = self._getErrorMessage(reason)
self.alert(_("Can't create profile"), message)
def deleteProfile(self, button):
profile_name = self.list_profile.getSelectedValue()
if profile_name:
self.host.bridge.asyncDeleteProfile(profile_name, callback=self.__refillProfiles)
self._deleteProfile()
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)
pop_up_widget = sat_widgets.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)
if self.current.profile:
pop_up_widget = sat_widgets.ConfirmDialog(_("Are you sure you want to delete the profile {} ?").format(self.current.profile), no_cb=self.cancelDialog, yes_cb=self.deleteProfile)
self.host.showPopUp(pop_up_widget)
def getXMPPParams(self, profile):
"""This is called from PrimitivusApp.launchAction when the profile has been authenticated.
def onConnectProfiles(self, button):
"""Connect the profiles and start the main widget
@param profile: %(doc_profile)s
@param button: the connect button
"""
def setJID(jabberID):
self.login_wid.set_edit_text(jabberID)
self.host.redraw()
self._onConnectProfiles()
def setPassword(password):
self.pass_wid.set_edit_text(password)
self.host.redraw()
def resetFields(self):
"""Set profile to None, and reset fields"""
super(ProfileManager, self).resetFields()
self.list_profile.unselectAll(invisible=True)
self.list_profile.selectValue(profile, move_focus=False)
self.selected_profile = profile
self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile, callback=setJID, errback=self.getParamError)
self.host.bridge.asyncGetParamA("Password", "Connection", profile_key=profile, callback=setPassword, errback=self.getParamError)
def setProfiles(self, profiles):
"""Update the list of profiles"""
self.list_profile.changeValues(profiles)
self.host.redraw()
def getProfiles(self):
return self.list_profile.getSelectedValues()
def getJID(self):
return self.login_wid.get_edit_text()
def getPassword(self):
return self.pass_wid.get_edit_text()
def setJID(self, jid_):
self.login_wid.set_edit_text(jid_)
self.current.login = jid_
self.host.redraw() # FIXME: redraw should be avoided
def setPassword(self, password):
self.pass_wid.set_edit_text(password)
self.current.password = password
self.host.redraw()
def alert(self, title, message):
popup = sat_widgets.Alert(title, message, ok_cb=self.host.removePopUp)
self.host.showPopUp(popup)
def onProfileChange(self, list_wid):
"""This is called when a profile is selected in the profile list.
@param list_wid: the List widget who sent the event
"""
profile_name = list_wid.getSelectedValue()
if not profile_name or profile_name == self.selected_profile:
return # avoid infinite loop
if self.selected_profile:
list_wid.selectValue(self.selected_profile, move_focus=False)
else:
list_wid.unselectAll(invisible=True)
self.host.redraw()
self.host.profile = profile_name # FIXME: EXTREMELY DIRTY, needed for sat_frontends.tools.xmlui.XMLUI._xmluiLaunchAction
self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, {'caller': 'profile_manager'}, profile_key=profile_name)
self.updateConnectionParams()
focused = list_wid.focus
selected = focused.getState()
if not selected: # profile was just unselected
return
focused.setState(False, invisible=True) # we don't want the widget to be selected until we are sure we can access it
def authenticate_cb(callback_id, data, profile):
if C.bool(data['validated']):
self.current.profile = profile
focused.setState(True, invisible=True)
self.getConnectionParams(profile)
self.host.redraw()
self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=focused.text)
def onConnectProfile(self, button):
profile_name = self.list_profile.getSelectedValue()
assert(profile_name == self.selected_profile) # if not, there's a bug somewhere...
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
self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile,
callback=lambda old_jid: self.__old_jidReceived(old_jid, profile), errback=self.getParamError)
def __old_jidReceived(self, old_jid, profile):
self.host.bridge.asyncGetParamA("Password", "Connection", profile_key=profile,
callback=lambda old_pass: self.__old_passReceived(old_jid, old_pass, profile), errback=self.getParamError)
def __old_passReceived(self, old_jid, old_pass, profile):
"""Check if we have new jid/pass, save them if it is the case, and plug 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_key=profile)
if old_pass != new_pass:
self.host.bridge.setParam("Password", new_pass, "Connection", profile_key=profile)
self.host.plug_profile(profile)
def getParamError(self, ignore):
popup = Alert("Error", _("Can't get profile parameter"), ok_cb=self.host.removePopUp)
self.host.showPopUp(popup)

View file

@ -20,11 +20,15 @@
from sat.core.i18n import _
import urwid
from urwid_satext import sat_widgets
from sat_frontends.quick_frontend import quick_widgets
class Progress(urwid.WidgetWrap):
class Progress(urwid.WidgetWrap, quick_widgets.QuickWidget):
PROFILES_ALLOW_NONE = True
def __init__(self, host):
def __init__(self, host, target, profiles):
assert target is None and profiles is None
quick_widgets.QuickWidget.__init__(self, host, target)
self.host = host
self.progress_list = urwid.SimpleListWalker([])
self.progress_dict = {}

View file

@ -20,7 +20,7 @@
from sat.core.i18n import _
import urwid
from urwid_satext import sat_widgets
from sat_frontends.constants import Const as commonConst
from sat_frontends.quick_frontend.constants import Const as commonConst
from sat_frontends.primitivus.constants import Const
@ -39,7 +39,7 @@ class StatusBar(urwid.Columns):
urwid.connect_signal(self.status, 'click', self.onStatusClick)
def onPresenceClick(self, sender=None):
if not self.host.bridge.isConnected(self.host.profile):
if not self.host.bridge.isConnected(self.host.current_profile): # FIXME: manage multi-profiles
return
options = [commonConst.PRESENCE[presence] for presence in commonConst.PRESENCE]
list_widget = sat_widgets.GenericList(options=options, option_type=sat_widgets.ClickableText, on_click=self.onChange)
@ -47,7 +47,7 @@ class StatusBar(urwid.Columns):
self.host.showPopUp(decorated)
def onStatusClick(self, sender=None):
if not self.host.bridge.isConnected(self.host.profile):
if not self.host.bridge.isConnected(self.host.current_profile): # FIXME: manage multi-profiles
return
pop_up_widget = sat_widgets.InputDialog(_('Set your status'), _('New status'), default_txt=self.status.get_text(),
cancel_cb=self.host.removePopUp, ok_cb=self.onChange)
@ -61,7 +61,8 @@ class StatusBar(urwid.Columns):
elif isinstance(user_data, sat_widgets.AdvancedEdit):
new = (previous[0], new_value[0])
if new != previous:
self.host.bridge.setPresence(show=new[0], statuses={'default': new[1]}, profile_key=self.host.profile) #FIXME: manage multilingual statuses
for profile in self.host.profiles: # FIXME: for now all the profiles share the same status
self.host.bridge.setPresence(show=new[0], statuses={'default': new[1]}, profile_key=profile) # FIXME: manage multilingual statuses
self.setPresenceStatus(new[0], new[1])
self.host.removePopUp()

View file

@ -0,0 +1,102 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Primitivus: a SAT frontend
# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sat.core import log as logging
log = logging.getLogger(__name__)
import urwid
from urwid_satext import sat_widgets
from sat_frontends.primitivus.keys import action_key_map as a_key
class PrimitivusWidget(urwid.WidgetWrap):
"""Base widget for Primitivus"""
def __init__(self, w, title=''):
self._title = title
self._title_dynamic = None
self._original_widget = w
urwid.WidgetWrap.__init__(self, self._getDecoration(w))
@property
def title(self):
"""Text shown in title bar of the widget"""
# profiles currently managed by frontend
try:
all_profiles = self.host.profiles
except AttributeError:
all_profiles = []
# profiles managed by the widget
try:
profiles = self.profiles
except AttributeError:
try:
profiles = [self.profile]
except AttributeError:
profiles = []
title_elts = []
if self._title:
title_elts.append(self._title)
if self._title_dynamic:
title_elts.append(self._title_dynamic)
if len(all_profiles)>1 and profiles:
title_elts.append(u'[{}]'.format(u', '.join(profiles)))
return sat_widgets.SurroundedText(u' '.join(title_elts))
@title.setter
def title(self, value):
self._title = value
if self.decorationVisible:
self.showDecoration()
@property
def title_dynamic(self):
"""Dynamic part of title"""
return self._title_dynamic
@title_dynamic.setter
def title_dynamic(self, value):
self._title_dynamic = value
if self.decorationVisible:
self.showDecoration()
@property
def decorationVisible(self):
"""True if the decoration is visible"""
return isinstance(self._w, sat_widgets.LabelLine)
def keypress(self, size, key):
if key == a_key['DECORATION_HIDE']: #user wants to (un)hide widget decoration
show = not self.decorationVisible
self.showDecoration(show)
else:
return super(PrimitivusWidget, self).keypress(size, key)
def _getDecoration(self, widget):
return sat_widgets.LabelLine(widget, self.title)
def showDecoration(self, show=True):
"""Show/Hide the decoration around the window"""
self._w = self._getDecoration(self._original_widget) if show else self._original_widget
def getMenu(self):
raise NotImplementedError

View file

@ -26,6 +26,7 @@ from urwid_satext import files_management
from sat.core.log import getLogger
log = getLogger(__name__)
from sat_frontends.primitivus.constants import Const as C
from sat_frontends.primitivus.widget import PrimitivusWidget
from sat_frontends.tools import xmlui
@ -355,14 +356,14 @@ class WidgetFactory(GenericFactory):
return cls
class XMLUIPanel(xmlui.XMLUIPanel, urwid.WidgetWrap):
class XMLUIPanel(xmlui.XMLUIPanel, PrimitivusWidget):
widget_factory = WidgetFactory()
def __init__(self, host, parsed_xml, title = None, flags = None):
def __init__(self, host, parsed_xml, title=None, flags=None, callback=None, profile=C.PROF_KEY_NONE):
self.widget_factory._xmlui_main = self
self._dest = None
xmlui.XMLUIPanel.__init__(self, host, parsed_xml, title, flags)
urwid.WidgetWrap.__init__(self, self.main_cont)
xmlui.XMLUIPanel.__init__(self, host, parsed_xml, title, flags, callback, profile)
PrimitivusWidget.__init__(self, self.main_cont, self.xmlui_title)
def constructUI(self, parsed_dom):
def postTreat():
@ -412,11 +413,10 @@ class XMLUIPanel(xmlui.XMLUIPanel, urwid.WidgetWrap):
raise ValueError('Invalid show_type [%s]' % show_type)
self._dest = show_type
decorated = sat_widgets.LabelLine(self, sat_widgets.SurroundedText(self.title or ''))
if show_type == 'popup':
self.host.showPopUp(decorated, valign=valign)
self.host.showPopUp(self, valign=valign)
elif show_type == 'window':
self.host.addWindow(decorated)
self.host.newWidget(self)
else:
assert(False)
self.host.redraw()

View file

@ -17,9 +17,58 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sat_frontends import constants
from sat.core import constants
from sat.core.i18n import _
from collections import OrderedDict # only available from python 2.7
class Const(constants.Const):
PRIVATE_PREFIX = "@private@"
PRESENCE = OrderedDict([("", _("Online")),
("chat", _("Free for chat")),
("away", _("Away from keyboard")),
("dnd", _("Do not disturb")),
("xa", _("Extended away"))])
# from plugin_misc_text_syntaxes
SYNTAX_XHTML = "XHTML"
SYNTAX_CURRENT = "@CURRENT@"
SYNTAX_TEXT = "text"
# XMLUI
SAT_FORM_PREFIX = "SAT_FORM_"
SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_" # used to have unique elements names
XMLUI_STATUS_VALIDATED = "validated"
XMLUI_STATUS_CANCELLED = constants.Const.XMLUI_DATA_CANCELLED
# MUC
ALL_OCCUPANTS = 1
MUC_USER_STATES = {
"active": u'✔',
"inactive": u'☄',
"gone": u'✈',
"composing": u'✎',
"paused": u"⦷"
}
# Roster
CONTACT_GROUPS = 'groups'
CONTACT_RESOURCES = 'resources'
CONTACT_MAIN_RESOURCE = 'main_resource'
CONTACT_SPECIAL = 'special'
CONTACT_SPECIAL_GROUP = 'group' # group chat special entity
CONTACT_SPECIAL_ALLOWED = (CONTACT_SPECIAL_GROUP,) # set of allowed values for special flag
CONTACT_DATA_FORBIDDEN = {CONTACT_GROUPS, CONTACT_RESOURCES, CONTACT_MAIN_RESOURCE} # set of forbidden names for contact data
# Chats
CHAT_ONE2ONE = 'one2one'
CHAT_GROUP = 'group'
# Widgets management
# FIXME: should be in quick_frontend.constant, but Libervia doesn't inherit from it
WIDGET_NEW = 'NEW'
WIDGET_KEEP = 'KEEP'
WIDGET_RAISE = 'RAISE'
WIDGET_RECREATE = 'RECREATE'
LISTENERS = {'avatar', 'nick', 'presence', 'profilePlugged', 'disconnect', 'gotMenus', 'menu'}

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