From 6240c1cf6fbd47da6743d4a66ebee21cf07fa6a5 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Tue, 16 Oct 2007 09:04:59 +0000 Subject: Cleanup the source structure --- (limited to 'sugar/activity/activity.py') 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'] - -- cgit v0.9.1