Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/groupthink/sugar_tools.py
diff options
context:
space:
mode:
Diffstat (limited to 'groupthink/sugar_tools.py')
-rw-r--r--groupthink/sugar_tools.py288
1 files changed, 288 insertions, 0 deletions
diff --git a/groupthink/sugar_tools.py b/groupthink/sugar_tools.py
new file mode 100644
index 0000000..0292a0b
--- /dev/null
+++ b/groupthink/sugar_tools.py
@@ -0,0 +1,288 @@
+# Copyright 2007 Collabora Ltd.
+#
+# 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
+import telepathy
+
+from sugar.activity.activity import Activity, ActivityToolbox
+from sugar.presence import presenceservice
+
+from sugar.presence.tubeconn import TubeConnection
+from sugar.graphics.window import Window
+
+import gtk
+import gobject
+
+import groupthink_base as groupthink
+
+def exhaust_event_loop():
+ while gtk.events_pending():
+ gtk.main_iteration()
+
+class GroupActivity(Activity):
+
+ message_preparing = "Preparing user interface"
+ message_loading = "Loading object from Journal"
+ message_joining = "Joining shared activity"
+
+ """Abstract Class for Activities using Groupthink"""
+ def __init__(self, handle):
+ # self.initiating indicates whether this instance has initiated sharing
+ # it always starts false, but will be set to true if this activity
+ # initiates sharing. In particular, if Activity.__init__ calls
+ # self.share(), self.initiating will be set to True.
+ self.initiating = False
+ # self._processed_share indicates whether when_shared() has been called
+ self._processed_share = False
+ # self.initialized tracks whether the Activity's display is up and running
+ self.initialized = False
+
+ self.early_setup()
+
+ super(GroupActivity, self).__init__(handle)
+ self.dbus_name = self.get_bundle_id()
+ self.logger = logging.getLogger(self.dbus_name)
+
+ self._handle = handle
+
+ ##gobject.threads_init()
+
+ self._sharing_completed = not self._shared_activity
+ self._readfile_completed = not handle.object_id
+ if self._shared_activity:
+ self.message = self.message_joining
+ elif handle.object_id:
+ self.message = self.message_loading
+ else:
+ self.message = self.message_preparing
+
+ # top toolbar with share and close buttons:
+ toolbox = ActivityToolbox(self)
+ self.set_toolbox(toolbox)
+ toolbox.show()
+
+ v = gtk.VBox()
+ self.startup_label = gtk.Label(self.message)
+ v.pack_start(self.startup_label)
+ Window.set_canvas(self,v)
+ self.show_all()
+
+ # The show_all method queues up draw events, but they aren't executed
+ # until the mainloop has a chance to process them. We want to process
+ # them immediately, because we need to show the waiting screen
+ # before the waiting starts, not after.
+ exhaust_event_loop()
+ # exhaust_event_loop() provides the possibility that write_file could
+ # be called at this time, so write_file is designed to trigger read_file
+ # itself if that path occurs.
+
+ self.tubebox = groupthink.TubeBox()
+ self.timer = groupthink.TimeHandler("main", self.tubebox)
+ self.cloud = groupthink.Group(self.tubebox)
+ # self.cloud is extremely important. It is the unified reference point
+ # that contains all state in the system. Everything else is stateless.
+ # self.cloud has to be defined before the call to self.set_canvas, because
+ # set_canvas can trigger almost anything, including pending calls to read_file,
+ # which relies on self.cloud.
+
+ # get the Presence Service
+ self.pservice = presenceservice.get_instance()
+ # Buddy object for you
+ owner = self.pservice.get_owner()
+ self.owner = owner
+
+ self.connect('shared', self._shared_cb)
+ self.connect('joined', self._joined_cb)
+ if self.get_shared():
+ if self.initiating:
+ self._shared_cb(self)
+ else:
+ self._joined_cb(self)
+
+ self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
+ self.connect("visibility-notify-event", self._visible_cb)
+ self.connect("notify::active", self._active_cb)
+
+ if not self._readfile_completed:
+ self.read_file(self._jobject.file_path)
+ elif not self._shared_activity:
+ gobject.idle_add(self._initialize_cleanstart)
+
+ def _initialize_cleanstart(self):
+ self.initialize_cleanstart()
+ self._initialize_display()
+ return False
+
+ def initialize_cleanstart(self):
+ """Any subclass that needs to take any extra action in the case where
+ the activity is launched locally without a sharing context or input
+ file should override this method"""
+ pass
+
+ def early_setup(self):
+ """Any subclass that needs to take an action before any external interaction
+ (e.g. read_file, write_file) occurs should place that code in early_setup"""
+ pass
+
+ def _initialize_display(self):
+ main_widget = self.initialize_display()
+ Window.set_canvas(self, main_widget)
+ self.initialized = True
+ if self._shared_activity and not self._processed_share:
+ # We are joining a shared activity, but when_shared has not yet
+ # been called
+ self.when_shared()
+ self._processed_share = True
+ self.show_all()
+
+ def initialize_display(self):
+ """All subclasses must override this method, in order to display
+ their GUI using self.set_canvas()"""
+ raise NotImplementedError
+
+ def share(self, private=False):
+ """The purpose of this function is solely to permit us to determine
+ whether share() has been called. This is necessary because share() may
+ be called during Activity.__init__, and thus emit the 'shared' signal
+ before we have a chance to connect any signal handlers."""
+ self.initiating = True
+ super(GroupActivity, self).share(private)
+ if self.initialized and not self._processed_share:
+ self.when_shared()
+ self._processed_share = True
+
+ def when_shared(self):
+ """Inheritors should override this method to perform any special
+ operations when the user shares the session"""
+ pass
+
+ def _shared_cb(self, activity):
+ self.logger.debug('My activity was shared')
+ self.initiating = True
+ self._sharing_setup()
+
+ self.logger.debug('This is my activity: making a tube...')
+ id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
+ self.dbus_name, {})
+
+ def _sharing_setup(self):
+ if self._shared_activity is None:
+ self.logger.error('Failed to share or join activity')
+ return
+
+ self.conn = self._shared_activity.telepathy_conn
+ self.tubes_chan = self._shared_activity.telepathy_tubes_chan
+ self.text_chan = self._shared_activity.telepathy_text_chan
+
+ self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube',
+ self._new_tube_cb)
+
+ def _list_tubes_reply_cb(self, tubes):
+ self.logger.debug('Got %d tubes from ListTubes' % len(tubes))
+ for tube_info in tubes:
+ self._new_tube_cb(*tube_info)
+
+ def _list_tubes_error_cb(self, e):
+ self.logger.error('ListTubes() failed: %s', e)
+
+ def _joined_cb(self, activity):
+ if not self._shared_activity:
+ return
+
+ self.logger.debug('Joined an existing shared activity')
+ self.initiating = False
+ self._sharing_setup()
+
+ self.logger.debug('This is not my activity: waiting for a tube...')
+ self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
+ reply_handler=self._list_tubes_reply_cb,
+ error_handler=self._list_tubes_error_cb)
+
+ def _new_tube_cb(self, id, initiator, type, service, params, state):
+ self.logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
+ 'params=%r state=%d', id, initiator, type, service,
+ params, state)
+ if (type == telepathy.TUBE_TYPE_DBUS and
+ service == self.dbus_name):
+ if state == telepathy.TUBE_STATE_LOCAL_PENDING:
+ self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
+ tube_conn = TubeConnection(self.conn,
+ self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
+ id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
+ self.tubebox.insert_tube(tube_conn, self.initiating)
+ self._sharing_completed = True
+ if self._readfile_completed and not self.initialized:
+ self._initialize_display()
+
+ def read_file(self, file_path):
+ self.cloud.loads(self.load_from_journal(file_path))
+ self._readfile_completed = True
+ if self._sharing_completed and not self.initialized:
+ self._initialize_display()
+ pass
+
+ def load_from_journal(self, file_path):
+ """This implementation of load_from_journal simply returns the contents
+ of the file. Any inheritor overriding this method must return the
+ string provided to save_to_journal as cloudstring."""
+ if file_path:
+ f = file(file_path,'rb')
+ s = f.read()
+ f.close()
+ return s
+
+ def write_file(self, file_path):
+ # There is a possibility that the user could trigger a write_file
+ # action before read_file has occurred. This could be dangerous,
+ # potentially overwriting the journal entry with blank state. To avoid
+ # this, we ensure that read_file has been called (if there is a file to
+ # read) before writing.
+ if not self._readfile_completed:
+ self.read_file(self._jobject.file_path)
+ self.save_to_journal(file_path, self.cloud.dumps())
+
+ def save_to_journal(self, file_path, cloudstring):
+ """This implementation of save_to_journal simply dumps the output of
+ self.cloud.dumps() to disk. Any inheritor who wishes to control file
+ output should override this method, and must
+ be sure to include cloudstring in its write_file."""
+ f = file(file_path, 'wb')
+ f.write(cloudstring)
+ f.close()
+
+ def _active_cb(self, widget, event):
+ self.logger.debug("_active_cb")
+ if self.props.active:
+ self.resume()
+ else:
+ self.pause()
+
+ def _visible_cb(self, widget, event):
+ self.logger.debug("_visible_cb")
+ if event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED:
+ self.pause()
+ else:
+ self.resume()
+
+ def pause(self):
+ """Subclasses should override this function to stop updating the display
+ since it is not visible."""
+ pass
+
+ def resume(self):
+ """Subclasses should override this function to resume updating the
+ display, since it is now visible"""
+ pass