view browser_side/panels.py @ 19:e8e3704eb97f

Added basic chat panel - the chat panel show history, timestamp, and nickname (pretty similar to primitivus and wix chat window) - JID has be rewritten to work with pyjamas, and is now in browser_side directory - a widget can now be selected: the message send in uniBox will be sent to it if there is no explicit target prefix ("@something") - a basic status panel is added under the uniBox, but not used yet
author Goffi <goffi@goffi.org>
date Sat, 16 Apr 2011 01:46:01 +0200
parents 795d144fc1d2
children 8f4b1a8914c3
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.VerticalPanel import VerticalPanel
from pyjamas.ui.HorizontalPanel import HorizontalPanel
from pyjamas.ui.ScrollPanel import ScrollPanel
from pyjamas.ui.HTMLPanel import HTMLPanel
from pyjamas.ui.Grid import Grid
from pyjamas.ui.MenuBar import MenuBar
from pyjamas.ui.MenuItem import MenuItem
from pyjamas.ui.Label import Label
from pyjamas.ui.DropWidget import DropWidget
from pyjamas.ui.ClickListener import ClickHandler
from pyjamas.ui import HasAlignment
from pyjamas import Window
from pyjamas import DOM

from pyjamas.dnd import makeDraggable
from pyjamas.ui.DragWidget import DragWidget, DragContainer
from jid import JID
from datetime import datetime
from time import time

class MenuCmd:

    def __init__(self, object, handler):
        self._object  = object
        self._handler = handler

    def execute(self):
        handler = getattr(self._object, self._handler)
        handler()

class Menu(SimplePanel):

    def __init__(self):
        SimplePanel.__init__(self)

        menu_general = MenuBar(vertical=True)
        menu_general.addItem("Properties", MenuCmd(self, "onProperties"))

        menu_games = MenuBar(vertical=True)
        menu_games.addItem("Tarot", MenuCmd(self, "onTarotGame"))
        menu_games.addItem("Xiangqi", MenuCmd(self, "onXiangqiGame"))

        menubar = MenuBar(vertical=False)
        menubar.addItem(MenuItem("General", menu_general))
        menubar.addItem(MenuItem("Games", True, menu_games))
        self.add(menubar)

    def onProperties(self):
        Window.alert("Properties selected")

    def onTarotGame(self):
        Window.alert("Tarot selected")

    def onXiangqiGame(self):
        Window.alert("Xiangqi selected")

class DropCell(DropWidget):
    """Cell in the middle grid which replace itself with the dropped widget on DnD"""
    
    def __init__(self):
        DropWidget.__init__(self)
    
    def onDragEnter(self, event):
        print "drag enter"
        self.addStyleName('dragover')
        DOM.eventPreventDefault(event)

    def onDragLeave(self, event):
        print "drag leave"
        self.removeStyleName('dragover')

    def onDragOver(self, event):
        DOM.eventPreventDefault(event)

    def _getCellAndRow(self, grid, event):
        """Return cell and row index where the event is occuring"""
        cell = grid.getEventTargetCell(event)
        row = DOM.getParent(cell)
        return (row.rowIndex, cell.cellIndex)


    def onDrop(self, event):
        print "Empty Panel: onDrop"
        dt = event.dataTransfer
        #'text', 'text/plain', and 'Text' are equivalent.
        try:
            item = dt.getData("text/plain")
            item_type = dt.getData("type")
            print "message: %s" % item
            print "type: %s" % item_type
        except:
            print "no message found"
            item='&nbsp;'
            item_type = None
        DOM.eventPreventDefault(event)
        if item_type=="GROUP":
            _new_panel = MicroblogPanel(self.host, item)
            _new_panel.setAcceptedGroup(item)
        elif item_type=="CONTACT":
            _contact = JID(item)
            _new_panel = ChatPanel(self.host, _contact)
            _new_panel.historyPrint()
        elif item_type=="CONTACT_TITLE":
            _new_panel = MicroblogPanel(self.host, accept_all=True)
        self.host.mpanels.remove(self)
        self.host.mpanels.append(_new_panel)
        print "DEBUG"
        grid = self.getParent()
        row_idx, cell_idx = self._getCellAndRow(grid, event)
        self.removeFromParent()
        if self.host.selected == self:
            self.host.select(None)
        grid.setWidget(row_idx, cell_idx, _new_panel)
        #FIXME: delete object ? Check the right way with pyjamas

class EmptyPanel(DropCell, SimplePanel):
    """Empty dropable panel"""

    def __init__(self, host):
        SimplePanel.__init__(self)
        self.host = host
        _panel = HTMLPanel("&nbsp;")
        self.add(_panel)
        self.setHeight('100%')
        DropCell.__init__(self)
    
class MicroblogEntry(SimplePanel):

    def __init__(self, body, author, timestamp):
        SimplePanel.__init__(self)

        _datetime = datetime.fromtimestamp(timestamp)

        panel = HTMLPanel("<div class='mb_entry_header'><span class='mb_entry_author'>%(author)s</span> on <span class='mb_entry_timestamp'>%(timestamp)s</span></div><div class='mb_entry_body'>%(body)s</div>" %
            {"author": author,
            "timestamp": _datetime,
            "body": body}
            )
        panel.setStyleName('microblogEntry')
        self.add(panel)

class MicroblogPanel(DropCell, ScrollPanel):

    def __init__(self,host, title='&nbsp;', accept_all=False):
        """Panel used to show microblog
        @param title: title of the panel
        @param accept_all: if true, show every message, without filtering jids"""
        self.host = host
        self.accept_all = accept_all
        title=title.replace('<','&lt;').replace('>','&gt;')
        self.accepted_groups = []
        _class = ['mb_panel_header']
        if title == '&nbsp;':
            _class.append('empty_header')
        ScrollPanel.__init__(self)
        self.vpanel = VerticalPanel()
        self.vpanel.add(HTMLPanel("<div class='%s'>%s</div>" % (','.join(_class),title)))
        self.vpanel.setWidth('100%')
        self.setHeight('100%')
        self.setStyleName('microblogPanel')
        self.add(self.vpanel)
        DropCell.__init__(self)

    def addEntry(self, text, author=None, timestamp=None):
        """Add an entry to the panel
        @param text: main text of the entry
        @param author: who wrote the entry
        @param date: when the entry was written"""
        _entry = MicroblogEntry(text, author, timestamp)
        self.vpanel.insert(_entry,1)

    def setAcceptedGroup(self, group):
        """Set the group which can be displayed in this panel
        @param group: string of the group, or list of string
        """
        if isinstance(group, list):
            self.accepted_groups.extend(group)
        else:
            self.accepted_groups.append(group)

    def isJidAccepted(self, jid):
        """Tell if a jid is actepted and show in this panel
        @param jid: jid
        @return: True if the jid is accepted"""
        if self.accept_all:
            return True
        for group in self.accepted_groups:
            if self.host.contactPanel.isContactInGroup(group, jid):
                return True
        return False

class StatusPanel(HTMLPanel):
    def __init__(self, status=''):
        self.status = status
        HTMLPanel.__init__(self, self.__getContent())

    def __getContent(self):
        return "<span class='status'>%(status)s</span>" % {'status':self.status}

    def changeStatus(self, new_status):
        self.status = new_status
        self.setHTML(self.__getContent())

class ChatText(HTMLPanel):

    def __init__(self, timestamp, nick, mymess, msg):
        _date = datetime.fromtimestamp(float(timestamp or time()))
        print "DEBUG"
        print timestamp
        print time()
        print _date
        _msg_class = ["chat_text_msg"]
        if mymess:
            _msg_class.append("chat_text_mymess")
        HTMLPanel.__init__(self, "<span class='chat_text_timestamp'>%(timestamp)s</span> <span class='chat_text_nick'>%(nick)s</span> <span class='%(msg_class)s'>%(msg)s</span>" %
            {"timestamp": _date.strftime("%H:%M"),
            "nick": "[%s]" % nick,
            "msg_class": ' '.join(_msg_class),
            "msg": msg}
            )
        self.setStyleName('chatText')
    
class ChatPanel(DropCell, ClickHandler, ScrollPanel):

    def __init__(self, host, target, type='one2one'):
        """Panel used for conversation (one 2 one or group chat)
        @param host: SatWebFrontend instance
        @param target: entity (JID) with who we have a conversation (contact's jid for one 2 one chat, or MUC room)
        @param type: one2one for simple conversation, group for MUC"""
        self.host = host
        if not target:
            print "ERROR: Empty target !"
            return
        self.target = target
        title="%s" % target.bare
        title.replace('<','&lt;').replace('>','&gt;')
        _class = ['mb_panel_header']
        ScrollPanel.__init__(self)
        self.content = VerticalPanel()
        self.content.add(HTMLPanel("<div class='%s'>%s</div>" % (','.join(_class),title)))
        self.content.setWidth('100%')
        self.setHeight('100%')
        self.setStyleName('chatPanel')
        self.add(self.content)
        DropCell.__init__(self)
        ClickHandler.__init__(self)
        self.addClickListener(self)

    def onClick(self, sender, event):
        self.host.select(self)
    
    def setUserNick(self, nick):
        """Set the nick of the user, usefull for e.g. change the color of the user"""
        self.nick = nick
    
    def historyPrint(self, size=20):
        """Print the initial history"""
        def getHistoryCB(history):
            stamps=history.keys()
            stamps.sort()
            for stamp in stamps: 
                self.printMessage(history[stamp][0], history[stamp][1], stamp)
        self.host.bridge.call('getHistory', getHistoryCB, self.host.whoami.bare, str(self.target), 20)
    
    def printMessage(self, from_jid, msg, timestamp=None):
        """Print message in chat window. Must be implemented by child class"""
        _jid=JID(from_jid)
        nick = _jid.node 
        mymess = _jid.bare == self.host.whoami.bare #mymess = True if message comes from local user
        """if msg.startswith('/me '):
            self.printInfo('* %s %s' % (nick, msg[4:]),type='me')
            return"""
        self.content.add(ChatText(timestamp, nick, mymess, msg))

class MiddlePannel(HorizontalPanel):
    
    def __init__(self, host):
        self.host=host
        HorizontalPanel.__init__(self)
        self._left = self.host.contactPanel
        self._right = Grid(1,3)
        self._right.setWidth('100%')
        self._right.setHeight('100%')
        self.add(self._left)
        self.setCellWidth(self._left, "15%")
        self.add(self._right)
        self.setCellWidth(self._right, "85%")
        self.setHeight('100%')

    def changePanel(self, idx, panel):
        print "panel:",panel
        print "idx:",idx
        self._right.setWidget(0,idx,panel)

class MainPanel(VerticalPanel):

    def __init__(self, host):
        self.host=host
        VerticalPanel.__init__(self)

        self.setHorizontalAlignment(HasAlignment.ALIGN_LEFT)
        self.setVerticalAlignment(HasAlignment.ALIGN_TOP)

        menu = Menu()
        uni_box = host.uniBox
        status = host.statusPanel
        self.middle_panel = MiddlePannel(self.host)
        self.middle_panel.setWidth('100%')

        self.add(menu)
        self.add(uni_box)
        self.add(status)
        self.add(self.middle_panel)
        
        self.setCellHeight(menu, "5%")
        self.setCellHeight(uni_box, "5%")
        self.setCellVerticalAlignment(uni_box, HasAlignment.ALIGN_CENTER)
        self.setCellHorizontalAlignment(uni_box, HasAlignment.ALIGN_CENTER)
        self.setCellHeight(self.middle_panel, "90%")
        self.setCellWidth(self.middle_panel, "100%")

        self.setWidth("100%")
        self.setHeight("100%")