view libervia.py @ 26:824516b247e6

browser side: added ContactsChooser dialog
author Goffi <goffi@goffi.org>
date Sat, 07 May 2011 23:52:44 +0200
parents 0ce2a57b34ca
children 258dfaa1035f
line wrap: on
line source

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
Libervia: a Salut à Toi frontend
Copyright (C) 2011  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/>.
"""

import pyjd # this is dummy in pyjs
from pyjamas.ui.SimplePanel import SimplePanel
from pyjamas.ui.RootPanel import RootPanel
from pyjamas.ui.AutoComplete import AutoCompleteTextBox
from pyjamas.ui.PopupPanel import PopupPanel
from pyjamas.ui.HTML import HTML
from pyjamas.Timer import Timer
from pyjamas import Window
from pyjamas.JSONService import JSONProxy
from pyjamas.ui.KeyboardListener import KEY_ENTER
from browser_side.register import RegisterPanel, RegisterBox
from browser_side.contact import ContactPanel
from browser_side.panels import MainPanel, EmptyPanel, MicroblogPanel, ChatPanel, StatusPanel
from browser_side.jid import JID


class LiberviaJsonProxy(JSONProxy):
    def __init__(self, *args, **kwargs):
        JSONProxy.__init__(self, *args, **kwargs)
        self.handler=self
        self.cb={}

    def call(self, method, cb, *args):
        if cb:
            self.cb[method] = cb
        self.callMethod(method,args)

    def onRemoteResponse(self, response, request_info):
        if self.cb.has_key(request_info.method):
            self.cb[request_info.method](response)
            del self.cb[request_info.method]
        
    def onRemoteError(self, code, errobj, request_info):
        if code != 0:
            Window.alert("Internal server error")
        else:
            if isinstance(errobj['message'],dict):
                Window.alert("Error %s: %s" % (errobj['message']['faultCode'], errobj['message']['faultString']))
            else:
                Window.alert("Error: %s" % errobj['message'])

class RegisterCall(LiberviaJsonProxy):
    def __init__(self):
        LiberviaJsonProxy.__init__(self, "/register_api",
                        ["isRegistered","isConnected","connect"])
        self.handler=self
        self.cb={}

class BridgeCall(LiberviaJsonProxy):
    def __init__(self):
        LiberviaJsonProxy.__init__(self, "/json_api",
                        ["getContacts", "sendMessage", "sendMblog", "getMblogNodes", "getProfileJid", "getHistory", "getPresenceStatus"])

class BridgeSignals(LiberviaJsonProxy):
    def __init__(self):
        LiberviaJsonProxy.__init__(self, "/json_signal_api",
                        ["getSignals"])


class UniBox(AutoCompleteTextBox):

    def __init__(self, host):
        AutoCompleteTextBox.__init__(self)
        self._popup = None
        self._timer = Timer(notify=self._timeCb)
        self.host = host

    def addKey(self, key):
        self.getCompletionItems().completions.append(key)

    def showWarning(self, target_data):
        type, target = target_data
        if type == "PUBLIC":
            msg = "This message will be PUBLIC and everybody will be able to see it, even people you don't know"
            style = "targetPublic"
        elif type == "GROUP":
            msg = "This message will be published for all the people of the group <span class='warningTarget'>%s</span>" % (target or '')
            style = "targetGroup"
        elif type == "STATUS":
            msg = "This will be your new status message"
            style = "targetStatus"
        elif type == "ONE2ONE":
            msg = "This message will be sent to your contact <span class='warningTarget'>%s</span>" % target
            style = "targetOne2One"
        else:
            print "WARNING: undetermined target for this message"
            return
        contents = HTML(msg)

        self._popup = PopupPanel(autoHide=False, modal=False)
        self._popup.target_data = target_data
        self._popup.add(contents)
        self._popup.setStyleName("warningPopup")
        if style:
            self._popup.addStyleName(style)

        left = 0
        top  = 0 #max(0, self.getAbsoluteTop() - contents.getOffsetHeight() - 2)   
        self._popup.setPopupPosition(left, top)
        self._popup.setPopupPosition(left, top)
        self._popup.show()

    def _timeCb(self, timer):
        if self._popup:
            self._popup.hide()
            del self._popup
            self._popup = None

    def _getTarget(self, txt):
        """Say who will receive the messsage
        Return a tuple (target_type, target info)"""
        type = None
        target = None
        if txt.startswith('@@: '):
            type = "PUBLIC"
        elif txt.startswith('@'):
            type = "GROUP"
            _end = txt.find(': ')
            if _end == -1:
                type = "STATUS"
            else:
                target = txt[1:_end] #only one target group is managed for the moment
                if not target in self.host.contactPanel.getGroups():
                    target = None
        elif self.host.selected == None:
            type = "STATUS"
        elif isinstance(self.host.selected, ChatPanel):
            type = "ONE2ONE"
            target = str(self.host.selected.target)
        else:
            print self.host.selected
            type = "UNKNOWN"
        return (type, target)

    def onKeyPress(self, sender, keycode, modifiers):
        _txt = self.getText()
        if not self._popup:
            self.showWarning(self._getTarget(_txt))
        else:
            _target = self._getTarget(_txt)
            if _target != self._popup.target_data:
                self._timeCb(None) #we remove the popup
                self.showWarning(_target)

        self._timer.schedule(2000)

        if keycode == KEY_ENTER and not self.visible:
            if _txt:
                if _txt.startswith('@'):
                    self.host.bridge.call('sendMblog', None, self.getText())
                elif self.host.selected == None:
                    self.host.bridge.call('setStatus', None, _txt)
                elif isinstance(self.host.selected, ChatPanel):
                    _chat = self.host.selected
                    self.host.bridge.call('sendMessage', None, str(_chat.target), _txt, '', 'chat')
            self.setText('')
            self._timeCb(None) #we remove the popup

    def complete(self):
        #self.visible=False #XXX: self.visible is not unset in pyjamas when ENTER is pressed and a completion is done
        #XXX: fixed directly on pyjamas, if the patch is accepted, no need to walk around this
        return AutoCompleteTextBox.complete(self)


class SatWebFrontend:
    def onModuleLoad(self):
        self.whoami = None
        self.bridge = BridgeCall()
        self.bridge_signals = BridgeSignals()
        self.selected = None
        self.uniBox = UniBox(self)
        self.uniBox.addKey("@@: ")
        self.statusPanel = StatusPanel(self)
        self.contactPanel = ContactPanel(self)
        self.panel = MainPanel(self)
        self.discuss_panel = self.panel.discuss_panel
        self.tab_panel = self.panel.tab_panel 
        self.mpanels = [EmptyPanel(self), MicroblogPanel(self, accept_all=True), EmptyPanel(self)]
        self.discuss_panel.changePanel(0,self.mpanels[0])
        self.discuss_panel.changePanel(1,self.mpanels[1])
        self.discuss_panel.changePanel(2,self.mpanels[2])
        self._dialog = None
        RootPanel().add(self.panel)
        self._register = RegisterCall()
        self._register.call('isRegistered',self._isRegisteredCB)

    def select(self, widget):
        """Define the selected widget"""
        if self.selected:
            self.selected.removeStyleName('selected_widget')
        self.selected = widget
        if widget:
            self.selected.addStyleName('selected_widget')

    def _isRegisteredCB(self, registered):
        if not registered:
            self._dialog = RegisterBox(self.logged,centered=True)
            self._dialog.show()
        else:
            self._register.call('isConnected', self._isConnectedCB)

    def _isConnectedCB(self, connected):
        if not connected:
            self._register.call('connect',self.logged)
        else:
            self.logged()

    def logged(self):
        if self._dialog:
            self._dialog.hide()
            del self._dialog # don't work if self._dialog is None
        
        #it's time to fill the page
        self.bridge.call('getContacts', self._getContactsCB)
        self.bridge_signals.call('getSignals', self._getSignalsCB)
        #We want to know our own jid
        self.bridge.call('getProfileJid', self._getProfileJidCB)

    def _getContactsCB(self, contacts_data):
        for contact in contacts_data:
            jid, attributes, groups = contact
            self.contactPanel.addContact(jid, attributes, groups)

    def _getSignalsCB(self, signal_data):
        bridge_signals = BridgeSignals()
        bridge_signals.call('getSignals', self._getSignalsCB)
        print('Got signal ==> name: %s, params: %s' % (signal_data[0],signal_data[1]))
        name,args = signal_data
        if name == 'personalEvent':
            self._personalEventCb(*args)
        elif name == 'newMessage':
            self._newMessageCb(*args)
        elif name == 'presenceUpdate':
            self._presenceUpdateCb(*args)

    def _getProfileJidCB(self, jid):
        self.whoami = JID(jid)
        #we can now ask our status
        self.bridge.call('getPresenceStatus', self._getPresenceStatusCB)



    ## Signals callbacks ##

    def _personalEventCb(self, sender, event_type, data, profile):
        if event_type == "MICROBLOG":
            if not data.has_key('content'):
                print ("WARNING: No content found in microblog data")
                return
            if data.has_key('groups'):
                _groups = set(data['groups'].split() if data['groups'] else [])
            else:
                _groups=None
            for panel in self.mpanels:
                if isinstance(panel,MicroblogPanel) and (panel.isJidAccepted(sender) or _groups == None or _groups.intersection(panel.accepted_groups)):
                    content = data['content']
                    author = data.get('author')
                    timestamp = float(data.get('timestamp',0)) #XXX: int doesn't work here
                    panel.addEntry(content, author, timestamp)

    def _newMessageCb(self, from_jid, msg, msg_type, to_jid):
        _from = JID(from_jid)
        _to = JID(to_jid)
        for panel in self.mpanels:
            if isinstance(panel,ChatPanel) and (panel.target.bare == _from.bare or panel.target.bare == _to.bare):
                panel.printMessage(_from, msg)

    def _presenceUpdateCb(self, entity, show, priority, statuses):
        _entity = JID(entity)
        #XXX: QnD way to only get our status
        if self.whoami and self.whoami.bare == _entity.bare and statuses:
            self.statusPanel.changeStatus(statuses.values()[0])
            
    def _getPresenceStatusCB(self, presence_data):
        #XXX we are only interested in our own presence so far
        if self.whoami and presence_data.has_key(self.whoami.bare):
            myjid = self.whoami.bare
            if presence_data[myjid]:
                args = presence_data[myjid].values()[0]
                self._presenceUpdateCb(myjid, *args)

if __name__ == '__main__':
    pyjd.setup("http://localhost:8080/libervia.html")
    app = SatWebFrontend()
    app.onModuleLoad()
    pyjd.run()