Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sugar/activity/activity.py
diff options
context:
space:
mode:
authorMarco Pesenti Gritti <mpg@redhat.com>2007-10-16 09:04:59 (GMT)
committer Marco Pesenti Gritti <mpg@redhat.com>2007-10-16 09:04:59 (GMT)
commit6240c1cf6fbd47da6743d4a66ebee21cf07fa6a5 (patch)
tree2c5276f5c820d2b5174bcc95cd15328adc98d7be /sugar/activity/activity.py
parent087856f23317537dbc6b112564c64db68601410e (diff)
Cleanup the source structure
Diffstat (limited to 'sugar/activity/activity.py')
-rw-r--r--sugar/activity/activity.py633
1 files changed, 0 insertions, 633 deletions
diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py
deleted file mode 100644
index c581c15..0000000
--- a/sugar/activity/activity.py
+++ /dev/null
@@ -1,633 +0,0 @@
-"""Base class for Python-coded activities
-
-This is currently the only reference for what an
-activity must do to participate in the Sugar desktop.
-"""
-# Copyright (C) 2006-2007 Red Hat, Inc.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-#
-# This library 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
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-# Boston, MA 02111-1307, USA.
-
-from gettext import gettext as _
-import logging
-import os
-import time
-import tempfile
-from hashlib import sha1
-
-import gtk, gobject
-import dbus
-import json
-
-from sugar import util
-from sugar.presence import presenceservice
-from sugar.activity.activityservice import ActivityService
-from sugar.graphics import style
-from sugar.graphics.window import Window
-from sugar.graphics.toolbox import Toolbox
-from sugar.graphics.toolbutton import ToolButton
-from sugar.graphics.toolcombobox import ToolComboBox
-from sugar.datastore import datastore
-from sugar import wm
-from sugar import profile
-from sugar import _sugarbaseext
-
-SCOPE_PRIVATE = "private"
-SCOPE_INVITE_ONLY = "invite" # shouldn't be shown in UI, it's implicit when you invite somebody
-SCOPE_NEIGHBORHOOD = "public"
-
-class ActivityToolbar(gtk.Toolbar):
- def __init__(self, activity):
- gtk.Toolbar.__init__(self)
-
- self._activity = activity
- self._updating_share = False
-
- activity.connect('shared', self._activity_shared_cb)
- activity.connect('joined', self._activity_shared_cb)
- activity.connect('notify::max_participants',
- self._max_participants_changed_cb)
-
- if activity.metadata:
- self.title = gtk.Entry()
- self.title.set_size_request(int(gtk.gdk.screen_width() / 6), -1)
- self.title.set_text(activity.metadata['title'])
- self.title.connect('changed', self._title_changed_cb)
- self._add_widget(self.title)
-
- activity.metadata.connect('updated', self._jobject_updated_cb)
-
- separator = gtk.SeparatorToolItem()
- separator.props.draw = False
- separator.set_expand(True);
- self.insert(separator, -1)
- separator.show()
-
- self.share = ToolComboBox(label_text=_('Share with:'))
- self.share.combo.connect('changed', self._share_changed_cb)
- self.share.combo.append_item(SCOPE_PRIVATE, _('Private'),
- 'zoom-home-mini')
- self.share.combo.append_item(SCOPE_NEIGHBORHOOD, _('My Neighborhood'),
- 'zoom-neighborhood-mini')
- self.insert(self.share, -1)
- self.share.show()
-
- self._update_share()
-
- self.keep = ToolButton('document-save')
- self.keep.set_tooltip(_('Keep'))
- self.keep.connect('clicked', self._keep_clicked_cb)
- self.insert(self.keep, -1)
- self.keep.show()
-
- self.stop = ToolButton('activity-stop')
- self.stop.set_tooltip(_('Stop'))
- self.stop.connect('clicked', self._stop_clicked_cb)
- self.insert(self.stop, -1)
- self.stop.show()
-
- self._update_title_sid = None
-
- def _update_share(self):
- self._updating_share = True
-
- if self._activity.props.max_participants == 1:
- self.share.hide()
-
- if self._activity.get_shared():
- self.share.set_sensitive(False)
- self.share.combo.set_active(1)
- else:
- self.share.set_sensitive(True)
- self.share.combo.set_active(0)
-
- self._updating_share = False
-
- def _share_changed_cb(self, combo):
- if self._updating_share:
- return
-
- model = self.share.combo.get_model()
- it = self.share.combo.get_active_iter()
- (scope, ) = model.get(it, 0)
- if scope == SCOPE_NEIGHBORHOOD:
- self._activity.share()
-
- def _keep_clicked_cb(self, button):
- self._activity.copy()
-
- def _stop_clicked_cb(self, button):
- self._activity.close()
-
- def _jobject_updated_cb(self, jobject):
- self.title.set_text(jobject['title'])
-
- def _title_changed_cb(self, entry):
- if not self._update_title_sid:
- self._update_title_sid = gobject.timeout_add(1000, self._update_title_cb)
-
- def _update_title_cb(self):
- title = self.title.get_text()
-
- self._activity.metadata['title'] = title
- self._activity.metadata['title_set_by_user'] = '1'
- self._activity.save()
-
- shared_activity = self._activity._shared_activity
- if shared_activity:
- shared_activity.props.name = title
-
- self._update_title_sid = None
- return False
-
- def _add_widget(self, widget, expand=False):
- tool_item = gtk.ToolItem()
- tool_item.set_expand(expand)
-
- tool_item.add(widget)
- widget.show()
-
- self.insert(tool_item, -1)
- tool_item.show()
-
- def _activity_shared_cb(self, activity):
- self._update_share()
-
- def _max_participants_changed_cb(self, activity, pspec):
- self._update_share()
-
-class EditToolbar(gtk.Toolbar):
- def __init__(self):
- gtk.Toolbar.__init__(self)
-
- self.undo = ToolButton('edit-undo')
- self.undo.set_tooltip(_('Undo'))
- self.insert(self.undo, -1)
- self.undo.show()
-
- self.redo = ToolButton('edit-redo')
- self.redo.set_tooltip(_('Redo'))
- self.insert(self.redo, -1)
- self.redo.show()
-
- self.separator = gtk.SeparatorToolItem()
- self.separator.set_draw(True)
- self.insert(self.separator, -1)
- self.separator.show()
-
- self.copy = ToolButton('edit-copy')
- self.copy.set_tooltip(_('Copy'))
- self.insert(self.copy, -1)
- self.copy.show()
-
- self.paste = ToolButton('edit-paste')
- self.paste.set_tooltip(_('Paste'))
- self.insert(self.paste, -1)
- self.paste.show()
-
-class ActivityToolbox(Toolbox):
- def __init__(self, activity):
- Toolbox.__init__(self)
-
- self._activity_toolbar = ActivityToolbar(activity)
- self.add_toolbar('Activity', self._activity_toolbar)
- self._activity_toolbar.show()
-
- def get_activity_toolbar(self):
- return self._activity_toolbar
-
-class Activity(Window, gtk.Container):
- """Base Activity class that all other Activities derive from."""
- __gtype_name__ = 'SugarActivity'
-
- __gsignals__ = {
- 'shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
- 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
- }
-
- __gproperties__ = {
- 'active' : (bool, None, None, False,
- gobject.PARAM_READWRITE),
- 'max-participants': (int, None, None, 0, 1000, 0,
- gobject.PARAM_READWRITE)
- }
-
- def __init__(self, handle, create_jobject=True):
- """Initialise the Activity
-
- handle -- sugar.activity.activityhandle.ActivityHandle
- instance providing the activity id and access to the
- presence service which *may* provide sharing for this
- application
-
- create_jobject -- boolean
- define if it should create a journal object if we are
- not resuming
-
- Side effects:
-
- Sets the gdk screen DPI setting (resolution) to the
- Sugar screen resolution.
-
- Connects our "destroy" message to our _destroy_cb
- method.
-
- Creates a base gtk.Window within this window.
-
- Creates an ActivityService (self._bus) servicing
- this application.
- """
- Window.__init__(self)
-
- # process titles will only show 15 characters
- # but they get truncated anyway so if more characters
- # are supported in the future we will get a better view
- # of the processes
- proc_title = "%s <%s>" % (get_bundle_name(), handle.activity_id)
- util.set_proc_title(proc_title)
-
- self.connect('realize', self._realize_cb)
- self.connect('delete-event', self.__delete_event_cb)
-
- self._active = False
- self._activity_id = handle.activity_id
- self._pservice = presenceservice.get_instance()
- self._shared_activity = None
- self._share_id = None
- self._join_id = None
- self._preview = None
- self._updating_jobject = False
- self._closing = False
- self._deleting = False
- self._max_participants = 0
- self._invites_queue = []
-
- self._bus = ActivityService(self)
- self._owns_file = False
-
- share_scope = SCOPE_PRIVATE
-
- if handle.object_id:
- self._jobject = datastore.get(handle.object_id)
- # TODO: Don't create so many objects until we have versioning
- # support in the datastore
- #self._jobject.object_id = ''
- #del self._jobject.metadata['ctime']
- del self._jobject.metadata['mtime']
-
- self.set_title(self._jobject.metadata['title'])
-
- if self._jobject.metadata.has_key('share-scope'):
- share_scope = self._jobject.metadata['share-scope']
-
- elif create_jobject:
- logging.debug('Creating a jobject.')
- self._jobject = datastore.create()
- self._jobject.metadata['title'] = _('%s Activity') % get_bundle_name()
- self.set_title(self._jobject.metadata['title'])
- self._jobject.metadata['title_set_by_user'] = '0'
- self._jobject.metadata['activity'] = self.get_bundle_id()
- self._jobject.metadata['activity_id'] = self.get_id()
- self._jobject.metadata['keep'] = '0'
- self._jobject.metadata['preview'] = ''
- self._jobject.metadata['share-scope'] = SCOPE_PRIVATE
-
- if self._shared_activity is not None:
- icon_color = self._shared_activity.props.color
- else:
- icon_color = profile.get_color().to_string()
-
- self._jobject.metadata['icon-color'] = icon_color
-
- self._jobject.file_path = ''
- datastore.write(self._jobject,
- reply_handler=self._internal_jobject_create_cb,
- error_handler=self._internal_jobject_error_cb)
- else:
- self._jobject = None
-
- # handle activity share/join
- mesh_instance = self._pservice.get_activity(self._activity_id)
- logging.debug("*** Act %s, mesh instance %r, scope %s" % (self._activity_id, mesh_instance, share_scope))
- if mesh_instance:
- # There's already an instance on the mesh, join it
- logging.debug("*** Act %s joining existing mesh instance" % self._activity_id)
- self._shared_activity = mesh_instance
- self._shared_activity.connect('notify::private',
- self._privacy_changed_cb)
- self._join_id = self._shared_activity.connect("joined", self._internal_joined_cb)
- if not self._shared_activity.props.joined:
- self._shared_activity.join()
- else:
- self._internal_joined_cb(self._shared_activity, True, None)
- elif share_scope != SCOPE_PRIVATE:
- logging.debug("*** Act %s no existing mesh instance, but used to be shared, will share" % self._activity_id)
- # no existing mesh instance, but activity used to be shared, so
- # restart the share
- if share_scope == SCOPE_INVITE_ONLY:
- self.share(private=True)
- elif share_scope == SCOPE_NEIGHBORHOOD:
- self.share(private=False)
- else:
- logging.debug("Unknown share scope %r" % share_scope)
-
- def do_set_property(self, pspec, value):
- if pspec.name == 'active':
- if self._active != value:
- self._active = value
- if not self._active and self._jobject:
- self.save()
- elif pspec.name == 'max-participants':
- self._max_participants = value
-
- def do_get_property(self, pspec):
- if pspec.name == 'active':
- return self._active
- elif pspec.name == 'max-participants':
- return self._max_participants
-
- def get_id(self):
- return self._activity_id
-
- def get_bundle_id(self):
- return _sugarbaseext.get_prgname()
-
- def set_canvas(self, canvas):
- Window.set_canvas(self, canvas)
- canvas.connect('map', self._canvas_map_cb)
-
- def _canvas_map_cb(self, canvas):
- if self._jobject and self._jobject.file_path:
- self.read_file(self._jobject.file_path)
-
- def _internal_jobject_create_cb(self):
- pass
-
- def _internal_jobject_error_cb(self, err):
- logging.debug("Error creating activity datastore object: %s" % err)
-
- def get_activity_root(self):
- """
- Return the appropriate location in the fs where to store activity related
- data that doesn't pertain to the current execution of the activity and
- thus cannot go into the DataStore.
- """
- if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \
- os.environ['SUGAR_ACTIVITY_ROOT']:
- return os.environ['SUGAR_ACTIVITY_ROOT']
- else:
- return '/'
-
- def read_file(self, file_path):
- """
- Subclasses implement this method if they support resuming objects from
- the journal. 'file_path' is the file to read from.
- """
- raise NotImplementedError
-
- def write_file(self, file_path):
- """
- Subclasses implement this method if they support saving data to objects
- in the journal. 'file_path' is the file to write to.
- """
- raise NotImplementedError
-
- def _internal_save_cb(self):
- logging.debug('Activity._internal_save_cb')
- self._updating_jobject = False
- if self._closing:
- self._cleanup_jobject()
- self.destroy()
-
- def _internal_save_error_cb(self, err):
- logging.debug('Activity._internal_save_error_cb')
- self._updating_jobject = False
- if self._closing:
- self._cleanup_jobject()
- self.destroy()
- logging.debug("Error saving activity object to datastore: %s" % err)
-
- def _cleanup_jobject(self):
- if self._jobject:
- if self._owns_file and os.path.isfile(self._jobject.file_path):
- logging.debug('_cleanup_jobject: removing %r' % self._jobject.file_path)
- os.remove(self._jobject.file_path)
- self._owns_file = False
- self._jobject.destroy()
- self._jobject = None
-
- def _get_preview(self):
- preview_pixbuf = self.get_canvas_screenshot()
- if preview_pixbuf is None:
- return None
- preview_pixbuf = preview_pixbuf.scale_simple(style.zoom(300),
- style.zoom(225),
- gtk.gdk.INTERP_BILINEAR)
-
- # TODO: Find a way of taking a png out of the pixbuf without saving to a temp file.
- # Impementing gtk.gdk.Pixbuf.save_to_buffer in pygtk would solve this.
- fd, file_path = tempfile.mkstemp('.png')
- del fd
- preview_pixbuf.save(file_path, 'png')
- f = open(file_path)
- try:
- preview_data = f.read()
- finally:
- f.close()
- os.remove(file_path)
-
- return preview_data
-
- def _get_buddies(self):
- if self._shared_activity is not None:
- buddies = {}
- for buddy in self._shared_activity.get_joined_buddies():
- if not buddy.props.owner:
- buddy_id = sha1(buddy.props.key).hexdigest()
- buddies[buddy_id] = [buddy.props.nick, buddy.props.color]
- return buddies
- else:
- return {}
-
- def save(self):
- """Request that the activity is saved to the Journal."""
-
- logging.debug('Activity.save: %r' % self._jobject.object_id)
-
- if self._updating_jobject:
- logging.info('Activity.save: still processing a previous request.')
- return
-
- buddies_dict = self._get_buddies()
- if buddies_dict:
- self.metadata['buddies_id'] = json.write(buddies_dict.keys())
- self.metadata['buddies'] = json.write(self._get_buddies())
-
- if self._preview is None:
- self.metadata['preview'] = ''
- else:
- self.metadata['preview'] = dbus.ByteArray(self._preview)
-
- try:
- if self._jobject.file_path:
- self.write_file(self._jobject.file_path)
- else:
- file_path = os.path.join(tempfile.gettempdir(), '%i' % time.time())
- self.write_file(file_path)
- self._owns_file = True
- self._jobject.file_path = file_path
- except NotImplementedError:
- pass
-
- # Cannot call datastore.write async for creates: https://dev.laptop.org/ticket/3071
- if self._jobject.object_id is None:
- datastore.write(self._jobject, transfer_ownership=True)
- else:
- self._updating_jobject = True
- datastore.write(self._jobject,
- transfer_ownership=True,
- reply_handler=self._internal_save_cb,
- error_handler=self._internal_save_error_cb)
-
- def copy(self):
- logging.debug('Activity.copy: %r' % self._jobject.object_id)
- self._preview = self._get_preview()
- self.save()
- self._jobject.object_id = None
-
- def _privacy_changed_cb(self, shared_activity, param_spec):
- if shared_activity.props.private:
- self._jobject.metadata['share-scope'] = SCOPE_INVITE_ONLY
- else:
- self._jobject.metadata['share-scope'] = SCOPE_NEIGHBORHOOD
-
- def _internal_joined_cb(self, activity, success, err):
- """Callback when join has finished"""
- self._shared_activity.disconnect(self._join_id)
- self._join_id = None
- if not success:
- logging.debug("Failed to join activity: %s" % err)
- return
-
- self.present()
- self.emit('joined')
- self._privacy_changed_cb(self._shared_activity, None)
-
- def get_shared(self):
- """Returns TRUE if the activity is shared on the mesh."""
- if not self._shared_activity:
- return False
- return self._shared_activity.props.joined
-
- def _internal_share_cb(self, ps, success, activity, err):
- self._pservice.disconnect(self._share_id)
- self._share_id = None
- if not success:
- logging.debug('Share of activity %s failed: %s.' % (self._activity_id, err))
- return
-
- logging.debug('Share of activity %s successful.' % self._activity_id)
-
- activity.props.name = self._jobject.metadata['title']
-
- self._shared_activity = activity
- self._shared_activity.connect('notify::private',
- self._privacy_changed_cb)
- self.emit('shared')
- self._privacy_changed_cb(self._shared_activity, None)
-
- self._send_invites()
-
- def _invite_response_cb(self, error):
- if error:
- logging.error('Invite failed: %s' % error)
-
- def _send_invites(self):
- while self._invites_queue:
- buddy_key = self._invites_queue.pop()
- buddy = self._pservice.get_buddy(buddy_key)
- if buddy:
- self._shared_activity.invite(buddy, '', self._invite_response_cb)
- else:
- logging.error('Cannot invite %s, no such buddy.' % buddy_key)
-
- def invite(self, buddy_key):
- self._invites_queue.append(buddy_key)
-
- if (self._shared_activity is None
- or not self._shared_activity.props.joined):
- self.share(True)
- else:
- self._send_invites()
-
- def share(self, private=False):
- """Request that the activity be shared on the network.
-
- private -- bool: True to share by invitation only,
- False to advertise as shared to everyone.
-
- Once the activity is shared, its privacy can be changed by setting
- its 'private' property.
- """
- # FIXME: Make private=True to turn on the by-invitation-only scope
- if self._shared_activity and self._shared_activity.props.joined:
- raise RuntimeError("Activity %s already shared." %
- self._activity_id)
- verb = private and 'private' or 'public'
- logging.debug('Requesting %s share of activity %s.' %
- (verb, self._activity_id))
- self._share_id = self._pservice.connect("activity-shared",
- self._internal_share_cb)
- self._pservice.share_activity(self, private=private)
-
- def close(self):
- self._preview = self._get_preview()
-
- self.save()
-
- if self._shared_activity:
- self._shared_activity.leave()
-
- if self._updating_jobject:
- self._closing = True
- else:
- self.destroy()
-
- def _realize_cb(self, window):
- wm.set_bundle_id(window.window, self.get_bundle_id())
- wm.set_activity_id(window.window, self._activity_id)
-
- def __delete_event_cb(self, widget, event):
- self.close()
- return True
-
- def get_metadata(self):
- if self._jobject:
- return self._jobject.metadata
- else:
- return None
-
- metadata = property(get_metadata, None)
-
-def get_bundle_name():
- """Return the bundle name for the current process' bundle
- """
- return _sugarbaseext.get_application_name()
-
-def get_bundle_path():
- """Return the bundle path for the current process' bundle
- """
- return os.environ['SUGAR_BUNDLE_PATH']
-