Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAneesh Dogra <lionaneesh@gmail.com>2012-12-12 22:18:15 (GMT)
committer Aneesh Dogra <lionaneesh@gmail.com>2012-12-12 22:19:56 (GMT)
commitd405452a918f863351c12a59df454b1d50fabc98 (patch)
treed9855d0292120364698418991037e74d29a52ff2
parentc91b738d8e5016d344ebeab9ea6a9479eac3642a (diff)
Add Gtk3 version of MiniChat.
Still Lacks :- 1) CSS styles. 2) Scrollbar
-rw-r--r--MiniChat_gtk3/MANIFEST10
-rw-r--r--MiniChat_gtk3/activity/activity.info8
-rw-r--r--MiniChat_gtk3/activity/chat.svg6
-rw-r--r--MiniChat_gtk3/minichat.py281
-rw-r--r--MiniChat_gtk3/out0
-rw-r--r--MiniChat_gtk3/po/MiniChat.pot50
-rwxr-xr-xMiniChat_gtk3/setup.py23
-rw-r--r--MiniChat_gtk3/textchannel.py137
8 files changed, 515 insertions, 0 deletions
diff --git a/MiniChat_gtk3/MANIFEST b/MiniChat_gtk3/MANIFEST
new file mode 100644
index 0000000..51c6abf
--- /dev/null
+++ b/MiniChat_gtk3/MANIFEST
@@ -0,0 +1,10 @@
+
+setup.py
+
+
+
+activity/activity.info
+minichat.py
+textchannel.py
+activity/chat.svg
+po/MiniChat.pot
diff --git a/MiniChat_gtk3/activity/activity.info b/MiniChat_gtk3/activity/activity.info
new file mode 100644
index 0000000..4622799
--- /dev/null
+++ b/MiniChat_gtk3/activity/activity.info
@@ -0,0 +1,8 @@
+[Activity]
+name = Mini Chat
+bundle_id = net.flossmanuals.MiniChat
+icon = chat
+exec = sugar-activity minichat.MiniChat
+show_launcher = yes
+activity_version = 1
+license = GPLv2+
diff --git a/MiniChat_gtk3/activity/chat.svg b/MiniChat_gtk3/activity/chat.svg
new file mode 100644
index 0000000..b0df51b
--- /dev/null
+++ b/MiniChat_gtk3/activity/chat.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="activity-chat">
+ <path d="M9.263,48.396c0.682,1.152,6.027,0.059,8.246-1.463 c2.102-1.432,3.207-2.596,4.336-2.596c1.133,0,12.54,0.92,20.935-5.715c7.225-5.707,9.773-13.788,4.52-21.437 c-5.252-7.644-13.832-9.08-20.878-8.56C16.806,9.342,4.224,16.91,4.677,28.313c0.264,6.711,3.357,9.143,4.922,10.703 c1.562,1.566,4.545,1.566,2.992,5.588C11.981,46.183,8.753,47.522,9.263,48.396z" display="inline" fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5"/>
+</g></svg><!-- " -->
diff --git a/MiniChat_gtk3/minichat.py b/MiniChat_gtk3/minichat.py
new file mode 100644
index 0000000..6238ccc
--- /dev/null
+++ b/MiniChat_gtk3/minichat.py
@@ -0,0 +1,281 @@
+# minichat.py
+# Copyright 2007-2008 One Laptop Per Child
+# Copyright 2012 Aneesh Dogra <lionaneesh@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gi.repository import GObject
+from gettext import gettext as _
+from gi.repository import Gtk
+from gi.repository import Pango
+import logging
+from sugar3.activity.activity import Activity, SCOPE_PRIVATE
+from sugar3.activity.widgets import ActivityToolbar, StopButton
+from sugar3.graphics.alert import NotifyAlert
+from sugar3.presence.presenceservice import PresenceService
+from sugar3.graphics.style import (Color, COLOR_BLACK, COLOR_WHITE,
+ COLOR_BUTTON_GREY, FONT_BOLD, FONT_NORMAL)
+from sugar3.graphics.xocolor import XoColor
+from sugar3.graphics.palette import Palette
+
+from textchannel import TextChannelWrapper
+
+logger = logging.getLogger('minichat-activity')
+
+class MiniChat(Activity):
+ def __init__(self, handle):
+ Activity.__init__(self, handle)
+
+ toolbox = ActivityToolbar(self)
+
+ stop_button = StopButton(self)
+ stop_button.show()
+ toolbox.insert(stop_button, -1)
+
+ self.set_toolbar_box(toolbox)
+ toolbox.show()
+
+ self.scroller = Gtk.ScrolledWindow()
+ self.scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
+
+ root = self.make_root()
+ self.set_canvas(root)
+ root.show_all()
+ self.entry.grab_focus()
+
+ self.pservice = PresenceService()
+ self.owner = self.pservice.get_owner()
+
+ # Track last message, to combine several messages:
+ self._last_msg = None
+ self._last_msg_sender = None
+ self.text_channel = None
+
+ if self.shared_activity:
+ # we are joining the activity
+ self.connect('joined', self._joined_cb)
+ if self.get_shared():
+ # we have already joined
+ self._joined_cb()
+ else:
+ # we are creating the activity
+ if not self.metadata or self.metadata.get('share-scope',
+ SCOPE_PRIVATE) == SCOPE_PRIVATE:
+ # if we are in private session
+ self._alert(_('Off-line'), _('Share, or invite someone.'))
+ self.connect('shared', self._shared_cb)
+
+ def _shared_cb(self, activity):
+ logger.debug('Chat was shared')
+ self._setup()
+
+ def _joined_cb(self, activity):
+ """Joined a shared activity."""
+ if not self.shared_activity:
+ return
+ logger.debug('Joined a shared chat')
+ for buddy in self.shared_activity.get_joined_buddies():
+ self._buddy_already_exists(buddy)
+ self._setup()
+
+ def _setup(self):
+ self.text_channel = TextChannelWrapper(
+ self.shared_activity.telepathy_text_chan,
+ self.shared_activity.telepathy_conn)
+ self.text_channel.set_received_callback(self._received_cb)
+ self._alert(_('On-line'), _('Connected'))
+ self.shared_activity.connect('buddy-joined', self._buddy_joined_cb)
+ self.shared_activity.connect('buddy-left', self._buddy_left_cb)
+ self.entry.set_sensitive(True)
+ self.entry.grab_focus()
+
+ def _received_cb(self, buddy, text):
+ """Show message that was received."""
+ if buddy:
+ nick = buddy.nick
+ else:
+ nick = '???'
+ logger.debug('Received message from %s: %s', nick, text)
+ self.add_text(buddy, text)
+
+ def _alert(self, title, text=None):
+ alert = NotifyAlert(timeout=5)
+ alert.props.title = title
+ alert.props.msg = text
+ self.add_alert(alert)
+ alert.connect('response', self._alert_cancel_cb)
+ alert.show()
+
+ def _alert_cancel_cb(self, alert, response_id):
+ self.remove_alert(alert)
+
+ def _buddy_joined_cb (self, activity, buddy):
+ """Show a buddy who joined"""
+ if buddy == self.owner:
+ return
+ if buddy:
+ nick = buddy.nick
+ else:
+ nick = '???'
+ self.add_text(buddy, buddy.nick+' '+_('joined the chat'),
+ status_message=True)
+
+ def _buddy_left_cb (self, activity, buddy):
+ """Show a buddy who joined"""
+ if buddy == self.owner:
+ return
+ if buddy:
+ nick = buddy.nick
+ else:
+ nick = '???'
+ self.add_text(buddy, buddy.nick+' '+_('left the chat'),
+ status_message=True)
+
+ def _buddy_already_exists(self, buddy):
+ """Show a buddy already in the chat."""
+ if buddy == self.owner:
+ return
+ if buddy:
+ nick = buddy.nick
+ else:
+ nick = '???'
+ self.add_text(buddy, buddy.nick+' '+_('is here'),
+ status_message=True)
+
+ def make_root(self):
+ vbox = Gtk.VBox()
+
+ self.conversation = Gtk.VBox()
+ self.conversation.show_all()
+ vbox.add(self.conversation)
+
+ self.entry = Gtk.Entry()
+ self.entry.modify_bg(Gtk.StateType.INSENSITIVE,
+ COLOR_WHITE.get_gdk_color())
+ self.entry.modify_base(Gtk.StateType.INSENSITIVE,
+ COLOR_WHITE.get_gdk_color())
+ self.entry.set_sensitive(False)
+
+ setattr(self.entry, "nick", "???")
+ self.entry.connect('activate', self.entry_activate_cb)
+ vbox.add(self.entry)
+
+ box = Gtk.VBox(homogeneous=False)
+ box.pack_end(vbox, False, True, 0)
+ box.show_all()
+
+ return box
+
+ def add_text(self, buddy, text, status_message=False):
+ """Display text on screen, with name and colors.
+
+ buddy -- buddy object
+ text -- string, what the buddy said
+ status_message -- boolean
+ False: show what buddy said
+ True: show what buddy did
+
+ Gtk layout:
+
+ .------------- rb ---------------.
+ | +name_vbox+ +----msg_vbox----+ |
+ | | | | | |
+ | | nick: | | +----entry---+ | |
+ | | | | | text | | |
+ | +---------+ | +------------+ | |
+ | | | |
+ | | +----entry---+ | |
+ | | | text | | |
+ | | +------------+ | |
+ | +----------------+ |
+ `--------------------------------'
+
+ """
+ if buddy:
+ nick = buddy.props.nick
+ else:
+ nick = '???' # XXX: should be '' but leave for debugging
+
+ logger.debug('Nick: %s' % nick)
+
+ # Check for Right-To-Left languages:
+ if Pango.find_base_dir(nick, -1) == Pango.Direction.RTL:
+ lang_rtl = True
+ else:
+ lang_rtl = False
+
+ logger.debug('lang_rtl: %s' % str(lang_rtl))
+
+ # Check if new message box or add text to previous:
+ new_msg = True
+ if self._last_msg_sender:
+ if not status_message:
+ if buddy == self._last_msg_sender:
+ # Add text to previous message
+ new_msg = False
+
+ if not new_msg:
+ rb = self._last_msg
+ msg_vbox = rb.get_children()[1]
+
+ else: # Its a new_msg, we need to create a new rb
+ rb = Gtk.HBox()
+ logger.debug('rb: %s' % str(rb))
+ self._last_msg = rb
+ self._last_msg_sender = buddy
+ if not status_message:
+ name = Gtk.Entry()
+ name.set_text(nick+': ')
+ name_vbox = Gtk.VBox()
+ name_vbox.add(name)
+ rb.pack_start(name_vbox, False, True, 0)
+
+ msg_vbox = Gtk.VBox()
+ rb.pack_start(msg_vbox, True, True, 0)
+
+ if status_message:
+ self._last_msg_sender = None
+
+ if text:
+ msg = Gtk.TextView()
+ text_buffer = msg.get_buffer()
+ text_buffer.set_text(text)
+ msg.show()
+ msg.set_editable(False)
+ msg.set_justification(Gtk.Justification.LEFT)
+ msg.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
+ msg_vbox.pack_start(msg, True, True, 2)
+
+ # Order of boxes for RTL languages:
+ if lang_rtl:
+ msg_hbox.reverse()
+ if new_msg:
+ rb.reverse()
+
+ if new_msg:
+ self.conversation.add(rb)
+ rb.show_all()
+
+ def entry_activate_cb(self, entry):
+ text = entry.get_text()
+ logger.debug('Entry: %s' % text)
+ if text:
+ self.add_text(self.owner, text)
+ entry.set_text('')
+ if self.text_channel:
+ self.text_channel.send(text)
+ else:
+ logger.debug('Tried to send message but text channel '
+ 'not connected.')
diff --git a/MiniChat_gtk3/out b/MiniChat_gtk3/out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MiniChat_gtk3/out
diff --git a/MiniChat_gtk3/po/MiniChat.pot b/MiniChat_gtk3/po/MiniChat.pot
new file mode 100644
index 0000000..71af33d
--- /dev/null
+++ b/MiniChat_gtk3/po/MiniChat.pot
@@ -0,0 +1,50 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-12-13 03:49+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: activity/activity.info:2
+msgid "Mini Chat"
+msgstr ""
+
+#: minichat.py:77
+msgid "Off-line"
+msgstr ""
+
+#: minichat.py:77
+msgid "Share, or invite someone."
+msgstr ""
+
+#: minichat.py:98
+msgid "On-line"
+msgstr ""
+
+#: minichat.py:98
+msgid "Connected"
+msgstr ""
+
+#: minichat.py:132
+msgid "joined the chat"
+msgstr ""
+
+#: minichat.py:143
+msgid "left the chat"
+msgstr ""
+
+#: minichat.py:154
+msgid "is here"
+msgstr ""
diff --git a/MiniChat_gtk3/setup.py b/MiniChat_gtk3/setup.py
new file mode 100755
index 0000000..601d35c
--- /dev/null
+++ b/MiniChat_gtk3/setup.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+
+# setup.py
+
+# Copyright (C) 2006, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from sugar3.activity import bundlebuilder
+
+bundlebuilder.start()
diff --git a/MiniChat_gtk3/textchannel.py b/MiniChat_gtk3/textchannel.py
new file mode 100644
index 0000000..1dfe1ac
--- /dev/null
+++ b/MiniChat_gtk3/textchannel.py
@@ -0,0 +1,137 @@
+# textchannel.py
+
+# Copyright 2007-2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+from telepathy.client import Connection, Channel
+from telepathy.interfaces import (
+ CHANNEL_INTERFACE, CHANNEL_INTERFACE_GROUP, CHANNEL_TYPE_TEXT,
+ CONN_INTERFACE_ALIASING)
+from telepathy.constants import (
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES,
+ CHANNEL_TEXT_MESSAGE_TYPE_NORMAL)
+
+class TextChannelWrapper(object):
+ """Wrap a telepathy Text Channel to make usage simpler."""
+ def __init__(self, text_chan, conn):
+ """Connect to the text channel"""
+ self._activity_cb = None
+ self._activity_close_cb = None
+ self._text_chan = text_chan
+ self._conn = conn
+ self._logger = logging.getLogger(
+ 'minichat-activity.TextChannelWrapper')
+ self._signal_matches = []
+ m = self._text_chan[CHANNEL_INTERFACE].connect_to_signal(
+ 'Closed', self._closed_cb)
+ self._signal_matches.append(m)
+
+ def send(self, text):
+ """Send text over the Telepathy text channel."""
+ # XXX Implement CHANNEL_TEXT_MESSAGE_TYPE_ACTION
+ if self._text_chan is not None:
+ self._text_chan[CHANNEL_TYPE_TEXT].Send(
+ CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, text)
+
+ def close(self):
+ """Close the text channel."""
+ self._logger.debug('Closing text channel')
+ try:
+ self._text_chan[CHANNEL_INTERFACE].Close()
+ except:
+ self._logger.debug('Channel disappeared!')
+ self._closed_cb()
+
+ def _closed_cb(self):
+ """Clean up text channel."""
+ self._logger.debug('Text channel closed.')
+ for match in self._signal_matches:
+ match.remove()
+ self._signal_matches = []
+ self._text_chan = None
+ if self._activity_close_cb is not None:
+ self._activity_close_cb()
+
+ def set_received_callback(self, callback):
+ """Connect the function callback to the signal.
+
+ callback -- callback function taking buddy and text args
+ """
+ if self._text_chan is None:
+ return
+ self._activity_cb = callback
+ m = self._text_chan[CHANNEL_TYPE_TEXT].connect_to_signal('Received',
+ self._received_cb)
+ self._signal_matches.append(m)
+
+ def handle_pending_messages(self):
+ """Get pending messages and show them as received."""
+ for id, timestamp, sender, type, flags, text in \
+ self._text_chan[
+ CHANNEL_TYPE_TEXT].ListPendingMessages(False):
+ self._received_cb(id, timestamp, sender, type, flags, text)
+
+ def _received_cb(self, id, timestamp, sender, type, flags, text):
+ """Handle received text from the text channel.
+
+ Converts sender to a Buddy.
+ Calls self._activity_cb which is a callback to the activity.
+ """
+ if self._activity_cb:
+ buddy = self._get_buddy(sender)
+ self._activity_cb(buddy, text)
+ self._text_chan[
+ CHANNEL_TYPE_TEXT].AcknowledgePendingMessages([id])
+ else:
+ self._logger.debug('Throwing received message on the floor'
+ ' since there is no callback connected. See '
+ 'set_received_callback')
+
+ def set_closed_callback(self, callback):
+ """Connect a callback for when the text channel is closed.
+
+ callback -- callback function taking no args
+
+ """
+ self._activity_close_cb = callback
+
+ def _get_buddy(self, cs_handle):
+ """Get a Buddy from a (possibly channel-specific) handle."""
+ # XXX This will be made redundant once Presence Service
+ # provides buddy resolution
+ from sugar.presence import presenceservice
+ # Get the Presence Service
+ pservice = presenceservice.get_instance()
+ # Get the Telepathy Connection
+ tp_name, tp_path = pservice.get_preferred_connection()
+ conn = Connection(tp_name, tp_path)
+ group = self._text_chan[CHANNEL_INTERFACE_GROUP]
+ my_csh = group.GetSelfHandle()
+ if my_csh == cs_handle:
+ handle = conn.GetSelfHandle()
+ elif group.GetGroupFlags() & \
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ handle = group.GetHandleOwners([cs_handle])[0]
+ else:
+ handle = cs_handle
+
+ # XXX: deal with failure to get the handle owner
+ assert handle != 0
+
+ return pservice.get_buddy_by_telepathy_handle(
+ tp_name, tp_path, handle)