2006-06-01 00:01:24 +02:00
|
|
|
#!/usr/bin/env python
|
2006-05-12 08:34:20 +02:00
|
|
|
|
2006-05-23 07:55:27 +02:00
|
|
|
import sha
|
2006-05-17 01:21:52 +02:00
|
|
|
|
2006-05-12 08:34:20 +02:00
|
|
|
import dbus
|
|
|
|
import dbus.service
|
|
|
|
import dbus.glib
|
|
|
|
|
|
|
|
import pygtk
|
|
|
|
pygtk.require('2.0')
|
2006-05-23 07:55:27 +02:00
|
|
|
import gtk, gobject, pango
|
2006-05-12 08:34:20 +02:00
|
|
|
|
2006-05-22 03:31:57 +02:00
|
|
|
from sugar.chat.Emoticons import Emoticons
|
2006-06-15 22:51:30 +02:00
|
|
|
from sugar.chat.ChatToolbar import ChatToolbar
|
|
|
|
from sugar.chat.ChatEditor import ChatEditor
|
2006-06-18 09:00:23 +02:00
|
|
|
from sugar.presence.PresenceService import PresenceService
|
2006-05-12 08:34:20 +02:00
|
|
|
import richtext
|
|
|
|
|
2006-06-01 00:01:24 +02:00
|
|
|
PANGO_SCALE = 1024 # Where is this defined?
|
2006-05-23 08:59:27 +02:00
|
|
|
|
2006-06-16 22:29:51 +02:00
|
|
|
class Chat(gtk.VBox):
|
2006-06-15 17:18:33 +02:00
|
|
|
SERVICE_TYPE = "_olpc_chat._tcp"
|
|
|
|
SERVICE_PORT = 6100
|
|
|
|
|
2006-06-18 20:45:04 +02:00
|
|
|
TEXT_MODE = 0
|
|
|
|
SKETCH_MODE = 1
|
|
|
|
|
2006-06-15 22:51:30 +02:00
|
|
|
def __init__(self):
|
2006-06-16 22:29:51 +02:00
|
|
|
gtk.VBox.__init__(self, False, 6)
|
2006-05-12 08:34:20 +02:00
|
|
|
|
2006-06-23 00:07:54 +02:00
|
|
|
self._pservice = PresenceService.get_instance()
|
|
|
|
self._pservice.start()
|
|
|
|
|
2006-06-16 22:29:51 +02:00
|
|
|
self._stream_writer = None
|
|
|
|
self.set_border_width(12)
|
2006-06-15 05:24:11 +02:00
|
|
|
|
2006-05-12 08:34:20 +02:00
|
|
|
chat_vbox = gtk.VBox()
|
|
|
|
chat_vbox.set_spacing(6)
|
|
|
|
|
2006-05-23 18:42:17 +02:00
|
|
|
self._chat_sw = gtk.ScrolledWindow()
|
|
|
|
self._chat_sw.set_shadow_type(gtk.SHADOW_IN)
|
|
|
|
self._chat_sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
|
2006-05-12 08:34:20 +02:00
|
|
|
self._chat_view = richtext.RichTextView()
|
|
|
|
self._chat_view.connect("link-clicked", self.__link_clicked_cb)
|
|
|
|
self._chat_view.set_editable(False)
|
|
|
|
self._chat_view.set_cursor_visible(False)
|
2006-05-23 07:55:27 +02:00
|
|
|
self._chat_view.set_pixels_above_lines(7)
|
|
|
|
self._chat_view.set_left_margin(5)
|
2006-05-23 18:42:17 +02:00
|
|
|
self._chat_sw.add(self._chat_view)
|
2006-05-12 08:34:20 +02:00
|
|
|
self._chat_view.show()
|
2006-05-23 18:42:17 +02:00
|
|
|
chat_vbox.pack_start(self._chat_sw)
|
|
|
|
self._chat_sw.show()
|
2006-05-12 08:34:20 +02:00
|
|
|
|
2006-06-16 22:29:51 +02:00
|
|
|
self.pack_start(chat_vbox)
|
2006-06-15 22:51:30 +02:00
|
|
|
chat_vbox.show()
|
|
|
|
|
2006-06-18 20:45:04 +02:00
|
|
|
self._mode = Chat.TEXT_MODE
|
|
|
|
self._editor = ChatEditor(self, ChatEditor.TEXT_MODE)
|
2006-06-15 22:51:30 +02:00
|
|
|
|
2006-06-18 21:35:44 +02:00
|
|
|
toolbar = ChatToolbar(self._editor)
|
2006-06-16 22:29:51 +02:00
|
|
|
self.pack_start(toolbar, False)
|
2006-06-15 22:51:30 +02:00
|
|
|
toolbar.show()
|
|
|
|
|
2006-06-16 22:29:51 +02:00
|
|
|
self.pack_start(self._editor, False)
|
2006-06-15 22:51:30 +02:00
|
|
|
self._editor.show()
|
|
|
|
|
2006-06-23 20:11:26 +02:00
|
|
|
self.connect("key-press-event", self.__key_press_event_cb)
|
|
|
|
|
|
|
|
def __key_press_event_cb(self, window, event):
|
|
|
|
if event.keyval == gtk.keysyms.s and \
|
|
|
|
event.state & gtk.gdk.CONTROL_MASK:
|
|
|
|
if self.get_mode() == Chat.SKETCH_MODE:
|
|
|
|
self.set_mode(Chat.TEXT_MODE)
|
|
|
|
elif self.get_mode() == Chat.TEXT_MODE:
|
|
|
|
self.set_mode(Chat.SKETCH_MODE)
|
|
|
|
|
2006-06-18 20:45:04 +02:00
|
|
|
def get_mode(self):
|
|
|
|
return self._mode
|
|
|
|
|
|
|
|
def set_mode(self, mode):
|
|
|
|
self._mode = mode
|
|
|
|
if self._mode == Chat.TEXT_MODE:
|
|
|
|
self._editor.set_mode(ChatEditor.TEXT_MODE)
|
|
|
|
elif self._mode == Chat.SKETCH_MODE:
|
|
|
|
self._editor.set_mode(ChatEditor.SKETCH_MODE)
|
|
|
|
|
2006-06-15 17:18:33 +02:00
|
|
|
def __get_browser_shell(self):
|
2006-06-15 05:24:11 +02:00
|
|
|
bus = dbus.SessionBus()
|
|
|
|
proxy_obj = bus.get_object('com.redhat.Sugar.Browser', '/com/redhat/Sugar/Browser')
|
|
|
|
self._browser_shell = dbus.Interface(proxy_obj, 'com.redhat.Sugar.BrowserShell')
|
2006-05-12 08:34:20 +02:00
|
|
|
|
|
|
|
def __link_clicked_cb(self, view, address):
|
2006-06-15 05:24:11 +02:00
|
|
|
self.__get_browser_shell().open_browser(address)
|
2006-05-12 08:34:20 +02:00
|
|
|
|
2006-05-23 18:48:29 +02:00
|
|
|
def _scroll_chat_view_to_bottom(self):
|
|
|
|
# Only scroll to bottom if the view is already close to the bottom
|
|
|
|
vadj = self._chat_sw.get_vadjustment()
|
|
|
|
if vadj.value + vadj.page_size > vadj.upper * 0.8:
|
|
|
|
vadj.value = vadj.upper - vadj.page_size
|
|
|
|
self._chat_sw.set_vadjustment(vadj)
|
2006-05-23 18:42:17 +02:00
|
|
|
|
2006-05-23 18:37:31 +02:00
|
|
|
def _message_inserted(self):
|
2006-05-23 18:48:29 +02:00
|
|
|
gobject.idle_add(self._scroll_chat_view_to_bottom)
|
2006-05-12 08:34:20 +02:00
|
|
|
|
2006-06-18 09:00:23 +02:00
|
|
|
def _insert_buddy(self, buf, buddy):
|
2006-05-23 08:59:27 +02:00
|
|
|
# Stuff in the buddy icon, if we have one for this buddy
|
2006-05-22 21:25:10 +02:00
|
|
|
icon = buddy.get_icon_pixbuf()
|
|
|
|
if icon:
|
2006-05-23 07:55:27 +02:00
|
|
|
rise = int(icon.get_height() / 4) * -1
|
|
|
|
|
2006-06-18 09:00:23 +02:00
|
|
|
hash_string = "%s-%s" % (buddy.get_nick_name(), buddy.get_address())
|
2006-05-23 08:59:27 +02:00
|
|
|
sha_hash = sha.new()
|
|
|
|
sha_hash.update(hash_string)
|
|
|
|
tagname = "buddyicon-%s" % sha_hash.hexdigest()
|
2006-05-23 07:55:27 +02:00
|
|
|
|
|
|
|
if not buf.get_tag_table().lookup(tagname):
|
|
|
|
buf.create_tag(tagname, rise=(rise * PANGO_SCALE))
|
|
|
|
|
2006-05-22 08:32:34 +02:00
|
|
|
aniter = buf.get_end_iter()
|
2006-05-22 21:25:10 +02:00
|
|
|
buf.insert_pixbuf(aniter, icon)
|
2006-05-23 08:59:27 +02:00
|
|
|
aniter.backward_char()
|
2006-05-23 07:55:27 +02:00
|
|
|
enditer = buf.get_end_iter()
|
|
|
|
buf.apply_tag_by_name(tagname, aniter, enditer)
|
2006-05-23 06:15:14 +02:00
|
|
|
|
2006-05-23 08:59:27 +02:00
|
|
|
# Stick in the buddy's nickname
|
2006-05-23 07:55:27 +02:00
|
|
|
if not buf.get_tag_table().lookup("nickname"):
|
|
|
|
buf.create_tag("nickname", weight=pango.WEIGHT_BOLD)
|
2006-05-22 08:10:30 +02:00
|
|
|
aniter = buf.get_end_iter()
|
2006-05-23 07:55:27 +02:00
|
|
|
offset = aniter.get_offset()
|
2006-06-18 09:00:23 +02:00
|
|
|
buf.insert(aniter, " " + buddy.get_nick_name() + ": ")
|
2006-05-23 07:55:27 +02:00
|
|
|
enditer = buf.get_iter_at_offset(offset)
|
|
|
|
buf.apply_tag_by_name("nickname", aniter, enditer)
|
2006-05-22 23:59:42 +02:00
|
|
|
|
2006-06-18 09:00:23 +02:00
|
|
|
def _insert_rich_message(self, buddy, msg):
|
2006-05-22 03:31:57 +02:00
|
|
|
msg = Emoticons.get_instance().replace(msg)
|
2006-05-22 08:10:30 +02:00
|
|
|
|
2006-05-23 06:15:14 +02:00
|
|
|
buf = self._chat_view.get_buffer()
|
2006-06-18 09:00:23 +02:00
|
|
|
self._insert_buddy(buf, buddy)
|
2006-05-12 08:34:20 +02:00
|
|
|
|
|
|
|
serializer = richtext.RichTextSerializer()
|
2006-05-15 20:48:08 +02:00
|
|
|
serializer.deserialize(msg, buf)
|
|
|
|
aniter = buf.get_end_iter()
|
|
|
|
buf.insert(aniter, "\n")
|
2006-05-22 23:59:42 +02:00
|
|
|
|
|
|
|
self._message_inserted()
|
2006-05-12 08:34:20 +02:00
|
|
|
|
2006-06-18 21:13:50 +02:00
|
|
|
def _insert_sketch(self, buddy, svgdata):
|
2006-05-20 00:05:59 +02:00
|
|
|
"""Insert a sketch object into the chat buffer."""
|
2006-05-20 02:45:17 +02:00
|
|
|
pbl = gtk.gdk.PixbufLoader("svg")
|
|
|
|
pbl.write(svgdata)
|
|
|
|
pbl.close()
|
|
|
|
pbuf = pbl.get_pixbuf()
|
|
|
|
|
|
|
|
buf = self._chat_view.get_buffer()
|
2006-05-22 08:10:30 +02:00
|
|
|
|
2006-06-18 21:13:50 +02:00
|
|
|
self._insert_buddy(buf, buddy)
|
2006-05-22 08:10:30 +02:00
|
|
|
|
2006-05-23 08:59:27 +02:00
|
|
|
rise = int(pbuf.get_height() / 3) * -1
|
|
|
|
sha_hash = sha.new()
|
|
|
|
sha_hash.update(svgdata)
|
|
|
|
tagname = "sketch-%s" % sha_hash.hexdigest()
|
|
|
|
if not buf.get_tag_table().lookup(tagname):
|
|
|
|
buf.create_tag(tagname, rise=(rise * PANGO_SCALE))
|
|
|
|
|
2006-05-20 02:45:17 +02:00
|
|
|
aniter = buf.get_end_iter()
|
|
|
|
buf.insert_pixbuf(aniter, pbuf)
|
2006-05-23 08:59:27 +02:00
|
|
|
aniter.backward_char()
|
|
|
|
enditer = buf.get_end_iter()
|
|
|
|
buf.apply_tag_by_name(tagname, aniter, enditer)
|
2006-05-20 02:45:17 +02:00
|
|
|
aniter = buf.get_end_iter()
|
|
|
|
buf.insert(aniter, "\n")
|
2006-05-20 02:01:03 +02:00
|
|
|
|
2006-05-23 05:08:41 +02:00
|
|
|
self._message_inserted()
|
|
|
|
|
2006-05-20 02:01:03 +02:00
|
|
|
def _get_first_richtext_chunk(self, msg):
|
2006-05-22 20:27:35 +02:00
|
|
|
"""Scan the message for the first richtext-tagged chunk and return it."""
|
2006-05-20 02:01:03 +02:00
|
|
|
rt_last = -1
|
|
|
|
tag_rt_start = "<richtext>"
|
|
|
|
tag_rt_end = "</richtext>"
|
|
|
|
rt_first = msg.find(tag_rt_start)
|
|
|
|
length = -1
|
|
|
|
if rt_first >= 0:
|
|
|
|
length = len(msg)
|
|
|
|
rt_last = msg.find(tag_rt_end, rt_first)
|
|
|
|
if rt_first >= 0 and rt_last >= (rt_first + len(tag_rt_start)) and length > 0:
|
|
|
|
return msg[rt_first:rt_last + len(tag_rt_end)]
|
|
|
|
return None
|
|
|
|
|
|
|
|
def _get_first_sketch_chunk(self, msg):
|
2006-05-22 20:27:35 +02:00
|
|
|
"""Scan the message for the first SVG-tagged chunk and return it."""
|
2006-05-20 02:01:03 +02:00
|
|
|
svg_last = -1
|
|
|
|
tag_svg_start = "<svg"
|
|
|
|
tag_svg_end = "</svg>"
|
2006-05-20 02:45:17 +02:00
|
|
|
desc_start = msg.find("<?xml version='1.0' encoding='UTF-8'?>")
|
|
|
|
if desc_start < 0:
|
|
|
|
return None
|
|
|
|
ignore = msg.find("<!DOCTYPE svg")
|
|
|
|
if ignore < 0:
|
|
|
|
return None
|
2006-05-20 02:01:03 +02:00
|
|
|
svg_first = msg.find(tag_svg_start)
|
|
|
|
length = -1
|
|
|
|
if svg_first >= 0:
|
|
|
|
length = len(msg)
|
|
|
|
svg_last = msg.find(tag_svg_end, svg_first)
|
|
|
|
if svg_first >= 0 and svg_last >= (svg_first + len(tag_svg_start)) and length > 0:
|
2006-05-20 02:45:17 +02:00
|
|
|
return msg[desc_start:svg_last + len(tag_svg_end)]
|
2006-05-20 02:01:03 +02:00
|
|
|
return None
|
2006-05-20 00:05:59 +02:00
|
|
|
|
2006-06-23 00:07:54 +02:00
|
|
|
def recv_message(self, message):
|
2006-05-20 00:05:59 +02:00
|
|
|
"""Insert a remote chat message into the chat buffer."""
|
2006-06-23 00:07:54 +02:00
|
|
|
[nick, msg] = Chat.deserialize_message(message)
|
|
|
|
buddy = self._pservice.get_buddy_by_nick_name(nick)
|
2006-05-23 18:48:29 +02:00
|
|
|
if not buddy:
|
2006-06-23 00:07:54 +02:00
|
|
|
logging.error('The buddy %s is not present.' % (nick))
|
2006-05-23 18:48:29 +02:00
|
|
|
return
|
|
|
|
|
2006-06-18 20:14:59 +02:00
|
|
|
# FIXME a better way to compare buddies?
|
2006-06-23 00:07:54 +02:00
|
|
|
owner = self._pservice.get_owner()
|
2006-06-18 20:14:59 +02:00
|
|
|
if buddy.get_nick_name() == owner.get_nick_name():
|
|
|
|
return
|
|
|
|
|
2006-05-20 02:01:03 +02:00
|
|
|
chunk = self._get_first_richtext_chunk(msg)
|
|
|
|
if chunk:
|
2006-06-18 09:00:23 +02:00
|
|
|
self._insert_rich_message(buddy, chunk)
|
2006-05-20 02:01:03 +02:00
|
|
|
return
|
|
|
|
|
|
|
|
chunk = self._get_first_sketch_chunk(msg)
|
|
|
|
if chunk:
|
2006-06-18 09:00:23 +02:00
|
|
|
self._insert_sketch(buddy, chunk)
|
2006-05-20 02:01:03 +02:00
|
|
|
return
|
|
|
|
|
|
|
|
def send_sketch(self, svgdata):
|
|
|
|
if not svgdata or not len(svgdata):
|
|
|
|
return
|
2006-06-22 18:52:51 +02:00
|
|
|
if self._stream_writer:
|
|
|
|
self._stream_writer.write(self.serialize_message(svgdata))
|
2006-06-18 09:00:23 +02:00
|
|
|
owner = PresenceService.get_instance().get_owner()
|
2006-06-22 18:52:51 +02:00
|
|
|
if owner:
|
|
|
|
self._insert_sketch(owner, svgdata)
|
2006-05-20 00:05:59 +02:00
|
|
|
|
|
|
|
def send_text_message(self, text):
|
|
|
|
"""Send a chat message and insert it into the local buffer."""
|
2006-05-19 21:52:44 +02:00
|
|
|
if len(text) <= 0:
|
|
|
|
return
|
2006-06-22 18:52:51 +02:00
|
|
|
if self._stream_writer:
|
|
|
|
self._stream_writer.write(self.serialize_message(text))
|
2006-06-18 09:00:23 +02:00
|
|
|
owner = PresenceService.get_instance().get_owner()
|
2006-06-22 18:52:51 +02:00
|
|
|
if owner:
|
|
|
|
self._insert_rich_message(owner, text)
|
2006-06-18 20:14:59 +02:00
|
|
|
|
|
|
|
def serialize_message(self, message):
|
|
|
|
owner = PresenceService.get_instance().get_owner()
|
|
|
|
return owner.get_nick_name() + '||' + message
|
|
|
|
|
2006-06-23 00:07:54 +02:00
|
|
|
def deserialize_message(message):
|
2006-06-18 20:14:59 +02:00
|
|
|
return message.split('||', 1)
|
2006-06-23 00:07:54 +02:00
|
|
|
|
|
|
|
deserialize_message = staticmethod(deserialize_message)
|