2007-10-28 16:56:05 +01:00
|
|
|
"""Base class for activities written in Python
|
2007-04-10 04:47:37 +02:00
|
|
|
|
2009-08-25 19:55:48 +02:00
|
|
|
This is currently the only definitive reference for what an
|
2007-04-10 04:47:37 +02:00
|
|
|
activity must do to participate in the Sugar desktop.
|
2007-10-28 16:56:05 +01:00
|
|
|
|
|
|
|
A Basic Activity
|
|
|
|
|
|
|
|
All activities must implement a class derived from 'Activity' in this class.
|
|
|
|
The convention is to call it ActivitynameActivity, but this is not required as
|
|
|
|
the activity.info file associated with your activity will tell the sugar-shell
|
|
|
|
which class to start.
|
|
|
|
|
2009-08-25 19:55:48 +02:00
|
|
|
For example the most minimal Activity:
|
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
|
|
|
|
from sugar.activity import activity
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
class ReadActivity(activity.Activity):
|
|
|
|
pass
|
|
|
|
|
|
|
|
To get a real, working activity, you will at least have to implement:
|
|
|
|
__init__(), read_file() and write_file()
|
|
|
|
|
|
|
|
Aditionally, you will probably need a at least a Toolbar so you can have some
|
|
|
|
interesting buttons for the user, like for example 'exit activity'
|
|
|
|
|
|
|
|
See the methods of the Activity class below for more information on what you
|
|
|
|
will need for a real activity.
|
2008-10-28 14:19:01 +01:00
|
|
|
|
|
|
|
STABLE.
|
2007-04-10 04:47:37 +02:00
|
|
|
"""
|
2007-06-24 14:43:48 +02:00
|
|
|
# Copyright (C) 2006-2007 Red Hat, Inc.
|
2009-01-18 16:30:53 +01:00
|
|
|
# Copyright (C) 2007-2009 One Laptop Per Child
|
2006-10-15 01:08:44 +02:00
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
|
2008-02-09 12:27:22 +01:00
|
|
|
import gettext
|
2006-08-11 17:05:06 +02:00
|
|
|
import logging
|
2007-02-22 17:27:00 +01:00
|
|
|
import os
|
2007-05-10 11:01:32 +02:00
|
|
|
import time
|
2007-08-21 12:12:13 +02:00
|
|
|
from hashlib import sha1
|
2007-11-13 15:59:24 +01:00
|
|
|
import traceback
|
2008-10-11 18:28:40 +02:00
|
|
|
import gconf
|
2006-08-09 18:29:33 +02:00
|
|
|
|
2009-01-18 16:30:53 +01:00
|
|
|
import gtk
|
|
|
|
import gobject
|
2007-06-15 18:03:17 +02:00
|
|
|
import dbus
|
2008-01-31 20:48:03 +01:00
|
|
|
import dbus.service
|
2008-10-08 17:30:08 +02:00
|
|
|
import cjson
|
2007-06-27 23:12:32 +02:00
|
|
|
|
2009-08-25 19:55:48 +02:00
|
|
|
from sugar import util
|
2007-04-09 20:40:56 +02:00
|
|
|
from sugar.presence import presenceservice
|
2007-02-21 20:15:39 +01:00
|
|
|
from sugar.activity.activityservice import ActivityService
|
2009-01-18 16:30:53 +01:00
|
|
|
from sugar.activity.namingalert import NamingAlert
|
2007-07-31 14:56:05 +02:00
|
|
|
from sugar.graphics import style
|
2007-02-27 15:05:44 +01:00
|
|
|
from sugar.graphics.window import Window
|
2007-11-13 15:59:24 +01:00
|
|
|
from sugar.graphics.alert import Alert
|
|
|
|
from sugar.graphics.icon import Icon
|
2007-05-10 11:01:32 +02:00
|
|
|
from sugar.datastore import datastore
|
2008-06-06 19:13:10 +02:00
|
|
|
from sugar.session import XSMPClient
|
2007-06-01 21:21:30 +02:00
|
|
|
from sugar import wm
|
2007-04-27 10:51:19 +02:00
|
|
|
|
2009-07-30 17:08:55 +02:00
|
|
|
# support deprecated imports
|
2009-08-25 21:12:40 +02:00
|
|
|
from sugar.activity.widgets import ActivityToolbar, EditToolbar
|
|
|
|
from sugar.activity.widgets import ActivityToolbox
|
|
|
|
|
2009-07-30 17:08:55 +02:00
|
|
|
|
2008-06-23 20:21:58 +02:00
|
|
|
_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
|
2008-02-09 12:27:22 +01:00
|
|
|
|
2007-09-01 19:07:49 +02:00
|
|
|
SCOPE_PRIVATE = "private"
|
2008-04-19 11:10:03 +02:00
|
|
|
SCOPE_INVITE_ONLY = "invite" # shouldn't be shown in UI, it's implicit
|
2007-09-01 19:07:49 +02:00
|
|
|
SCOPE_NEIGHBORHOOD = "public"
|
2007-07-24 11:29:14 +02:00
|
|
|
|
2007-12-19 13:02:16 +01:00
|
|
|
J_DBUS_SERVICE = 'org.laptop.Journal'
|
|
|
|
J_DBUS_PATH = '/org/laptop/Journal'
|
|
|
|
J_DBUS_INTERFACE = 'org.laptop.Journal'
|
|
|
|
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2008-08-06 23:04:00 +02:00
|
|
|
class _ActivitySession(gobject.GObject):
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2008-08-06 23:04:00 +02:00
|
|
|
__gsignals__ = {
|
|
|
|
'quit-requested': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
|
2009-08-25 21:12:40 +02:00
|
|
|
'quit': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
|
2008-08-06 23:04:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
gobject.GObject.__init__(self)
|
|
|
|
|
|
|
|
self._xsmp_client = XSMPClient()
|
2009-08-25 21:12:40 +02:00
|
|
|
self._xsmp_client.connect('quit-requested',
|
|
|
|
self.__sm_quit_requested_cb)
|
2008-08-06 23:04:00 +02:00
|
|
|
self._xsmp_client.connect('quit', self.__sm_quit_cb)
|
|
|
|
self._xsmp_client.startup()
|
|
|
|
|
|
|
|
self._activities = []
|
|
|
|
self._will_quit = []
|
|
|
|
|
|
|
|
def register(self, activity):
|
|
|
|
self._activities.append(activity)
|
|
|
|
|
|
|
|
def unregister(self, activity):
|
|
|
|
self._activities.remove(activity)
|
|
|
|
|
|
|
|
if len(self._activities) == 0:
|
|
|
|
logging.debug('Quitting the activity process.')
|
|
|
|
gtk.main_quit()
|
|
|
|
|
|
|
|
def will_quit(self, activity, will_quit):
|
|
|
|
if will_quit:
|
|
|
|
self._will_quit.append(activity)
|
|
|
|
|
|
|
|
# We can quit only when all the instances agreed to
|
|
|
|
for activity in self._activities:
|
|
|
|
if activity not in self._will_quit:
|
|
|
|
return
|
|
|
|
|
|
|
|
self._xsmp_client.will_quit(True)
|
|
|
|
else:
|
|
|
|
self._will_quit = []
|
|
|
|
self._xsmp_client.will_quit(False)
|
|
|
|
|
|
|
|
def __sm_quit_requested_cb(self, client):
|
|
|
|
self.emit('quit-requested')
|
|
|
|
|
|
|
|
def __sm_quit_cb(self, client):
|
|
|
|
self.emit('quit')
|
|
|
|
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2007-02-27 15:05:44 +01:00
|
|
|
class Activity(Window, gtk.Container):
|
2008-04-19 11:10:03 +02:00
|
|
|
"""This is the base Activity class that all other Activities derive from.
|
|
|
|
This is where your activity starts.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
To get a working Activity:
|
|
|
|
0. Derive your Activity from this class:
|
|
|
|
class MyActivity(activity.Activity):
|
|
|
|
...
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
1. implement an __init__() method for your Activity class.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Use your init method to create your own ActivityToolbar which will
|
2009-08-25 19:55:48 +02:00
|
|
|
contain some standard buttons:
|
2007-10-28 16:56:05 +01:00
|
|
|
toolbox = activity.ActivityToolbox(self)
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Add extra Toolbars to your toolbox.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
You should setup Activity sharing here too.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Finaly, your Activity may need some resources which you can claim
|
|
|
|
here too.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
The __init__() method is also used to make the distinction between
|
2009-08-25 19:55:48 +02:00
|
|
|
being resumed from the Journal, or starting with a blank document.
|
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
2. Implement read_file() and write_file()
|
|
|
|
Most activities revolve around creating and storing Journal entries.
|
2009-08-25 21:12:40 +02:00
|
|
|
For example, Write: You create a document, it is saved to the
|
|
|
|
Journal and then later you resume working on the document.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
read_file() and write_file() will be called by sugar to tell your
|
2009-08-25 21:12:40 +02:00
|
|
|
Activity that it should load or save the document the user is
|
|
|
|
working on.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
3. Implement our Activity Toolbars.
|
|
|
|
The Toolbars are added to your Activity in step 1 (the toolbox), but
|
|
|
|
you need to implement them somewhere. Now is a good time.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
There are a number of standard Toolbars. The most basic one, the one
|
|
|
|
your almost absolutely MUST have is the ActivityToolbar. Without
|
|
|
|
this, you're not really making a proper Sugar Activity (which may be
|
|
|
|
okay, but you should really stop and think about why not!) You do
|
2009-08-25 19:55:48 +02:00
|
|
|
this with the ActivityToolbox(self) call in step 1.
|
|
|
|
|
2009-08-25 21:12:40 +02:00
|
|
|
Usually, you will also need the standard EditToolbar. This is the
|
|
|
|
one which has the standard copy and paste buttons. You need to
|
|
|
|
derive your own EditToolbar class from sugar.EditToolbar:
|
2007-10-28 16:56:05 +01:00
|
|
|
class EditToolbar(activity.EditToolbar):
|
|
|
|
...
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
See EditToolbar for the methods you should implement in your class.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Finaly, your Activity will very likely need some activity specific
|
|
|
|
buttons and options you can create your own toolbars by deriving a
|
|
|
|
class from gtk.Toolbar:
|
|
|
|
class MySpecialToolbar(gtk.Toolbar):
|
|
|
|
...
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
4. Use your creativity. Make your Activity something special and share
|
|
|
|
it with your friends!
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Read through the methods of the Activity class below, to learn more about
|
2009-08-25 19:55:48 +02:00
|
|
|
how to make an Activity work.
|
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Hint: A good and simple Activity to learn from is the Read activity. To
|
|
|
|
create your own activity, you may want to copy it and use it as a template.
|
|
|
|
"""
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2007-02-27 15:05:44 +01:00
|
|
|
__gtype_name__ = 'SugarActivity'
|
2007-04-27 22:07:38 +02:00
|
|
|
|
|
|
|
__gsignals__ = {
|
2007-05-03 05:25:15 +02:00
|
|
|
'shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
|
2009-08-25 21:12:40 +02:00
|
|
|
'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
|
2007-04-27 22:07:38 +02:00
|
|
|
}
|
|
|
|
|
2007-05-10 11:01:32 +02:00
|
|
|
def __init__(self, handle, create_jobject=True):
|
2009-08-25 19:55:48 +02:00
|
|
|
"""Initialise the Activity
|
|
|
|
|
2007-04-10 04:47:37 +02:00
|
|
|
handle -- sugar.activity.activityhandle.ActivityHandle
|
2009-08-25 19:55:48 +02:00
|
|
|
instance providing the activity id and access to the
|
|
|
|
presence service which *may* provide sharing for this
|
2007-04-10 04:47:37 +02:00
|
|
|
application
|
2007-05-10 11:01:32 +02:00
|
|
|
|
|
|
|
create_jobject -- boolean
|
|
|
|
define if it should create a journal object if we are
|
|
|
|
not resuming
|
|
|
|
|
2009-08-25 19:55:48 +02:00
|
|
|
Side effects:
|
|
|
|
|
|
|
|
Sets the gdk screen DPI setting (resolution) to the
|
2007-04-10 04:47:37 +02:00
|
|
|
Sugar screen resolution.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-04-10 04:47:37 +02:00
|
|
|
Connects our "destroy" message to our _destroy_cb
|
|
|
|
method.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-04-10 04:47:37 +02:00
|
|
|
Creates a base gtk.Window within this window.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-04-10 04:47:37 +02:00
|
|
|
Creates an ActivityService (self._bus) servicing
|
|
|
|
this application.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
|
|
|
Usage:
|
2007-10-28 16:56:05 +01:00
|
|
|
If your Activity implements __init__(), it should call
|
|
|
|
the base class __init()__ before doing Activity specific things.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-04-10 04:47:37 +02:00
|
|
|
"""
|
2007-02-27 15:05:44 +01:00
|
|
|
Window.__init__(self)
|
2006-12-04 20:12:24 +01:00
|
|
|
|
2009-09-01 10:11:59 +02:00
|
|
|
if os.environ.has_key('SUGAR_ACTIVITY_ROOT'):
|
|
|
|
# If this activity runs inside Sugar, we want it to take all the
|
|
|
|
# screen. Would be better if it was the shell to do this, but we
|
2009-09-05 18:40:15 +02:00
|
|
|
# haven't found yet a good way to do it there. See #1263.
|
|
|
|
self.connect('window-state-event', self.__window_state_event_cb)
|
2009-09-01 10:11:59 +02:00
|
|
|
screen = gtk.gdk.screen_get_default()
|
|
|
|
screen.connect('size-changed', self.__screen_size_changed_cb)
|
|
|
|
self._adapt_window_to_screen()
|
|
|
|
|
2007-06-27 23:12:32 +02:00
|
|
|
# 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)
|
|
|
|
|
2007-10-16 15:51:48 +02:00
|
|
|
self.connect('realize', self.__realize_cb)
|
2007-10-15 23:47:02 +02:00
|
|
|
self.connect('delete-event', self.__delete_event_cb)
|
2006-12-04 20:12:24 +01:00
|
|
|
|
2007-05-16 21:30:49 +02:00
|
|
|
self._active = False
|
2007-02-22 00:57:49 +01:00
|
|
|
self._activity_id = handle.activity_id
|
2007-04-09 20:40:56 +02:00
|
|
|
self._pservice = presenceservice.get_instance()
|
2008-09-07 22:07:49 +02:00
|
|
|
self.shared_activity = None
|
2007-05-03 05:25:15 +02:00
|
|
|
self._share_id = None
|
|
|
|
self._join_id = None
|
2007-07-23 13:45:46 +02:00
|
|
|
self._updating_jobject = False
|
|
|
|
self._closing = False
|
2008-07-21 19:20:22 +02:00
|
|
|
self._quit_requested = False
|
2007-10-15 23:47:02 +02:00
|
|
|
self._deleting = False
|
2007-08-16 17:55:52 +02:00
|
|
|
self._max_participants = 0
|
2007-09-11 19:59:40 +02:00
|
|
|
self._invites_queue = []
|
2008-06-26 16:20:27 +02:00
|
|
|
self._jobject = None
|
2009-03-27 12:26:57 +01:00
|
|
|
self._read_file_called = False
|
2007-05-03 05:25:15 +02:00
|
|
|
|
2008-08-06 23:04:00 +02:00
|
|
|
self._session = _get_session()
|
|
|
|
self._session.register(self)
|
|
|
|
self._session.connect('quit-requested',
|
|
|
|
self.__session_quit_requested_cb)
|
|
|
|
self._session.connect('quit', self.__session_quit_cb)
|
2008-06-06 19:13:10 +02:00
|
|
|
|
2008-04-01 11:52:11 +02:00
|
|
|
accel_group = gtk.AccelGroup()
|
|
|
|
self.set_data('sugar-accel-group', accel_group)
|
|
|
|
self.add_accel_group(accel_group)
|
|
|
|
|
2007-02-21 20:15:39 +01:00
|
|
|
self._bus = ActivityService(self)
|
2007-07-20 19:50:49 +02:00
|
|
|
self._owns_file = False
|
2006-12-04 20:12:24 +01:00
|
|
|
|
2007-09-01 19:07:49 +02:00
|
|
|
share_scope = SCOPE_PRIVATE
|
|
|
|
|
2007-05-10 11:01:32 +02:00
|
|
|
if handle.object_id:
|
2009-08-25 19:55:48 +02:00
|
|
|
self._jobject = datastore.get(handle.object_id)
|
2007-09-28 16:32:22 +02:00
|
|
|
self.set_title(self._jobject.metadata['title'])
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-09-28 16:32:22 +02:00
|
|
|
if self._jobject.metadata.has_key('share-scope'):
|
2008-04-19 11:10:03 +02:00
|
|
|
share_scope = self._jobject.metadata['share-scope']
|
2007-08-28 23:07:57 +02:00
|
|
|
|
2007-09-01 19:07:49 +02:00
|
|
|
# handle activity share/join
|
2007-10-17 13:53:24 +02:00
|
|
|
mesh_instance = self._pservice.get_activity(self._activity_id,
|
|
|
|
warn_if_none=False)
|
|
|
|
logging.debug("*** Act %s, mesh instance %r, scope %s",
|
|
|
|
self._activity_id, mesh_instance, share_scope)
|
|
|
|
if mesh_instance is not None:
|
2007-09-01 19:07:49 +02:00
|
|
|
# There's already an instance on the mesh, join it
|
2008-04-19 11:10:03 +02:00
|
|
|
logging.debug("*** Act %s joining existing mesh instance %r",
|
|
|
|
self._activity_id, mesh_instance)
|
2008-09-07 22:07:49 +02:00
|
|
|
self.shared_activity = mesh_instance
|
|
|
|
self.shared_activity.connect('notify::private',
|
|
|
|
self.__privacy_changed_cb)
|
2008-09-07 23:57:27 +02:00
|
|
|
self._join_id = self.shared_activity.connect("joined",
|
|
|
|
self.__joined_cb)
|
2008-09-07 22:07:49 +02:00
|
|
|
if not self.shared_activity.props.joined:
|
|
|
|
self.shared_activity.join()
|
2007-09-01 19:07:49 +02:00
|
|
|
else:
|
2008-09-07 22:07:49 +02:00
|
|
|
self.__joined_cb(self.shared_activity, True, None)
|
2007-09-01 19:07:49 +02:00
|
|
|
elif share_scope != SCOPE_PRIVATE:
|
2009-08-24 12:54:02 +02:00
|
|
|
logging.debug('*** Act %s no existing mesh instance, but used to '
|
|
|
|
'be shared, will share', self._activity_id)
|
2007-09-01 19:07:49 +02:00
|
|
|
# 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:
|
2009-08-24 12:54:02 +02:00
|
|
|
logging.debug('Unknown share scope %r', share_scope)
|
2007-09-01 19:07:49 +02:00
|
|
|
|
2008-06-26 16:20:27 +02:00
|
|
|
if handle.object_id is None and create_jobject:
|
|
|
|
logging.debug('Creating a jobject.')
|
|
|
|
self._jobject = datastore.create()
|
|
|
|
title = _('%s Activity') % get_bundle_name()
|
|
|
|
self._jobject.metadata['title'] = title
|
|
|
|
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
|
2008-09-07 22:07:49 +02:00
|
|
|
if self.shared_activity is not None:
|
|
|
|
icon_color = self.shared_activity.props.color
|
2008-06-26 16:20:27 +02:00
|
|
|
else:
|
2008-10-11 18:28:40 +02:00
|
|
|
client = gconf.client_get_default()
|
|
|
|
icon_color = client.get_string('/desktop/sugar/user/color')
|
2008-06-26 16:20:27 +02:00
|
|
|
self._jobject.metadata['icon-color'] = icon_color
|
|
|
|
|
|
|
|
self._jobject.file_path = ''
|
|
|
|
# Cannot call datastore.write async for creates:
|
|
|
|
# https://dev.laptop.org/ticket/3071
|
|
|
|
datastore.write(self._jobject)
|
|
|
|
|
2008-08-11 01:10:02 +02:00
|
|
|
def get_active(self):
|
|
|
|
return self._active
|
2007-05-16 21:30:49 +02:00
|
|
|
|
2008-08-11 01:10:02 +02:00
|
|
|
def set_active(self, active):
|
|
|
|
if self._active != active:
|
|
|
|
self._active = active
|
|
|
|
if not self._active and self._jobject:
|
|
|
|
self.save()
|
|
|
|
|
|
|
|
active = gobject.property(
|
|
|
|
type=bool, default=False, getter=get_active, setter=set_active)
|
|
|
|
|
|
|
|
def get_max_participants(self):
|
|
|
|
return self._max_participants
|
|
|
|
|
|
|
|
def set_max_participants(self, participants):
|
|
|
|
self._max_participants = participants
|
|
|
|
|
|
|
|
max_participants = gobject.property(
|
|
|
|
type=int, default=0, getter=get_max_participants,
|
|
|
|
setter=set_max_participants)
|
2007-05-16 21:30:49 +02:00
|
|
|
|
2007-06-03 22:12:47 +02:00
|
|
|
def get_id(self):
|
2007-10-28 16:56:05 +01:00
|
|
|
"""Returns the activity id of the current instance of your activity.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
The activity id is sort-of-like the unix process id (PID). However,
|
|
|
|
unlike PIDs it is only different for each new instance (with
|
|
|
|
create_jobject = True set) and stays the same everytime a user
|
2009-08-25 21:12:40 +02:00
|
|
|
resumes an activity. This is also the identity of your Activity to
|
|
|
|
other XOs for use when sharing.
|
2007-10-28 16:56:05 +01:00
|
|
|
"""
|
2007-06-03 22:12:47 +02:00
|
|
|
return self._activity_id
|
|
|
|
|
2007-10-09 13:15:06 +02:00
|
|
|
def get_bundle_id(self):
|
2007-10-28 16:56:05 +01:00
|
|
|
"""Returns the bundle_id from the activity.info file"""
|
2007-10-16 14:29:38 +02:00
|
|
|
return os.environ['SUGAR_BUNDLE_ID']
|
2007-06-03 22:12:47 +02:00
|
|
|
|
2010-03-08 11:55:07 +01:00
|
|
|
def get_canvas(self):
|
|
|
|
return Window.get_canvas(self)
|
|
|
|
|
2007-05-29 15:53:58 +02:00
|
|
|
def set_canvas(self, canvas):
|
2009-08-25 21:12:40 +02:00
|
|
|
"""Sets the 'work area' of your activity with the canvas of your
|
|
|
|
choice.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
One commonly used canvas is gtk.ScrolledWindow
|
|
|
|
"""
|
2007-05-29 15:53:58 +02:00
|
|
|
Window.set_canvas(self, canvas)
|
2009-03-27 12:26:57 +01:00
|
|
|
if not self._read_file_called:
|
|
|
|
canvas.connect('map', self.__canvas_map_cb)
|
2007-05-29 15:53:58 +02:00
|
|
|
|
2010-03-08 11:55:07 +01:00
|
|
|
canvas = property(get_canvas, set_canvas)
|
|
|
|
|
2009-09-01 10:11:59 +02:00
|
|
|
def __screen_size_changed_cb(self, screen):
|
|
|
|
self._adapt_window_to_screen()
|
|
|
|
|
2009-09-05 18:40:15 +02:00
|
|
|
def __window_state_event_cb(self, window, event):
|
|
|
|
self.move(0, 0)
|
|
|
|
|
2009-09-01 10:11:59 +02:00
|
|
|
def _adapt_window_to_screen(self):
|
|
|
|
screen = gtk.gdk.screen_get_default()
|
|
|
|
self.set_geometry_hints(None,
|
|
|
|
screen.get_width(), screen.get_height(),
|
|
|
|
screen.get_width(), screen.get_height(),
|
|
|
|
screen.get_width(), screen.get_height(),
|
|
|
|
1, 1, 1, 1)
|
|
|
|
|
2008-08-06 23:04:00 +02:00
|
|
|
def __session_quit_requested_cb(self, session):
|
2008-07-21 19:20:22 +02:00
|
|
|
self._quit_requested = True
|
|
|
|
|
2009-09-29 20:33:13 +02:00
|
|
|
if self._prepare_close() and not self._updating_jobject:
|
2008-08-06 23:04:00 +02:00
|
|
|
session.will_quit(self, True)
|
2008-06-06 19:13:10 +02:00
|
|
|
|
2008-08-06 23:04:00 +02:00
|
|
|
def __session_quit_cb(self, client):
|
2008-07-21 19:20:22 +02:00
|
|
|
self._complete_close()
|
2008-06-06 19:13:10 +02:00
|
|
|
|
2009-03-27 12:14:42 +01:00
|
|
|
def __canvas_map_cb(self, canvas):
|
2009-03-27 12:26:57 +01:00
|
|
|
logging.debug('Activity.__canvas_map_cb')
|
|
|
|
if self._jobject and self._jobject.file_path and \
|
|
|
|
not self._read_file_called:
|
2007-05-29 15:53:58 +02:00
|
|
|
self.read_file(self._jobject.file_path)
|
2009-03-27 12:26:57 +01:00
|
|
|
self._read_file_called = True
|
|
|
|
canvas.disconnect_by_func(self.__canvas_map_cb)
|
2007-05-29 15:53:58 +02:00
|
|
|
|
2007-10-16 15:51:48 +02:00
|
|
|
def __jobject_create_cb(self):
|
2007-05-16 06:41:45 +02:00
|
|
|
pass
|
|
|
|
|
2007-10-16 15:51:48 +02:00
|
|
|
def __jobject_error_cb(self, err):
|
2009-08-24 12:54:02 +02:00
|
|
|
logging.debug('Error creating activity datastore object: %s', err)
|
2007-05-16 06:41:45 +02:00
|
|
|
|
2007-08-13 21:14:25 +02:00
|
|
|
def get_activity_root(self):
|
2009-08-25 19:55:48 +02:00
|
|
|
""" FIXME: Deprecated. This part of the API has been moved
|
2007-12-03 22:10:14 +01:00
|
|
|
out of this class to the module itself
|
|
|
|
|
|
|
|
Returns a path for saving Activity specific preferences, etc.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Returns a path to the location in the filesystem where the activity can
|
|
|
|
store activity related data that doesn't pertain to the current
|
|
|
|
execution of the activity and thus cannot go into the DataStore.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2008-04-19 11:10:03 +02:00
|
|
|
Currently, this will return something like
|
|
|
|
~/.sugar/default/MyActivityName/
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Activities should ONLY save settings, user preferences and other data
|
2009-08-25 21:12:40 +02:00
|
|
|
which isn't specific to a journal item here. If (meta-)data is in
|
|
|
|
anyway specific to a journal entry, it MUST be stored in the DataStore.
|
2007-08-13 21:14:25 +02:00
|
|
|
"""
|
|
|
|
if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \
|
|
|
|
os.environ['SUGAR_ACTIVITY_ROOT']:
|
|
|
|
return os.environ['SUGAR_ACTIVITY_ROOT']
|
|
|
|
else:
|
|
|
|
return '/'
|
|
|
|
|
2007-05-29 15:53:58 +02:00
|
|
|
def read_file(self, file_path):
|
2007-05-10 11:01:32 +02:00
|
|
|
"""
|
|
|
|
Subclasses implement this method if they support resuming objects from
|
2007-05-29 15:53:58 +02:00
|
|
|
the journal. 'file_path' is the file to read from.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
You should immediately open the file from the file_path, because the
|
|
|
|
file_name will be deleted immediately after returning from read_file().
|
|
|
|
Once the file has been opened, you do not have to read it immediately:
|
|
|
|
After you have opened it, the file will only be really gone when you
|
|
|
|
close it.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Although not required, this is also a good time to read all meta-data:
|
2009-08-25 21:12:40 +02:00
|
|
|
the file itself cannot be changed externally, but the title,
|
|
|
|
description and other metadata['tags'] may change. So if it is
|
|
|
|
important for you to notice changes, this is the time to record the
|
|
|
|
originals.
|
2007-05-10 11:01:32 +02:00
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2007-05-29 15:53:58 +02:00
|
|
|
def write_file(self, file_path):
|
2007-05-10 11:01:32 +02:00
|
|
|
"""
|
|
|
|
Subclasses implement this method if they support saving data to objects
|
2007-05-29 15:53:58 +02:00
|
|
|
in the journal. 'file_path' is the file to write to.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
If the user did make changes, you should create the file_path and save
|
|
|
|
all document data to it.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Additionally, you should also write any metadata needed to resume your
|
2009-08-25 21:12:40 +02:00
|
|
|
activity. For example, the Read activity saves the current page and
|
|
|
|
zoom level, so it can display the page.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Note: Currently, the file_path *WILL* be different from the one you
|
2009-08-25 21:12:40 +02:00
|
|
|
received in file_read(). Even if you kept the file_path from
|
|
|
|
file_read() open until now, you must still write the entire file to
|
|
|
|
this file_path.
|
2007-05-10 11:01:32 +02:00
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2007-10-16 15:51:48 +02:00
|
|
|
def __save_cb(self):
|
|
|
|
logging.debug('Activity.__save_cb')
|
2007-07-23 13:45:46 +02:00
|
|
|
self._updating_jobject = False
|
2008-07-21 19:20:22 +02:00
|
|
|
if self._quit_requested:
|
2008-08-06 23:04:00 +02:00
|
|
|
self._session.will_quit(self, True)
|
2008-07-21 19:20:22 +02:00
|
|
|
elif self._closing:
|
|
|
|
self._complete_close()
|
2007-05-16 06:41:45 +02:00
|
|
|
|
2007-10-16 15:51:48 +02:00
|
|
|
def __save_error_cb(self, err):
|
|
|
|
logging.debug('Activity.__save_error_cb')
|
2007-07-23 13:45:46 +02:00
|
|
|
self._updating_jobject = False
|
2008-07-21 19:20:22 +02:00
|
|
|
if self._quit_requested:
|
2008-08-06 23:04:00 +02:00
|
|
|
self._session.will_quit(self, False)
|
2007-07-23 13:45:46 +02:00
|
|
|
if self._closing:
|
2008-07-21 19:20:22 +02:00
|
|
|
self._show_keep_failed_dialog()
|
|
|
|
self._closing = False
|
2009-08-24 12:54:02 +02:00
|
|
|
logging.debug('Error saving activity object to datastore: %s', err)
|
2007-05-16 06:41:45 +02:00
|
|
|
|
2007-07-23 13:45:46 +02:00
|
|
|
def _cleanup_jobject(self):
|
|
|
|
if self._jobject:
|
|
|
|
if self._owns_file and os.path.isfile(self._jobject.file_path):
|
2009-08-24 12:54:02 +02:00
|
|
|
logging.debug('_cleanup_jobject: removing %r',
|
|
|
|
self._jobject.file_path)
|
2007-07-23 13:45:46 +02:00
|
|
|
os.remove(self._jobject.file_path)
|
|
|
|
self._owns_file = False
|
|
|
|
self._jobject.destroy()
|
|
|
|
self._jobject = None
|
|
|
|
|
2009-02-25 16:09:06 +01:00
|
|
|
def get_preview(self):
|
|
|
|
"""Returns an image representing the state of the activity. Generally
|
|
|
|
this is what the user is seeing in this moment.
|
2008-11-11 17:34:34 +01:00
|
|
|
|
2009-02-25 16:09:06 +01:00
|
|
|
Activities can override this method, which should return a str with the
|
|
|
|
binary content of a png image with a width of 300 and a height of 225
|
|
|
|
pixels.
|
|
|
|
"""
|
2008-11-11 17:34:34 +01:00
|
|
|
if self.canvas is None or not hasattr(self.canvas, 'get_snapshot'):
|
2007-07-11 11:02:43 +02:00
|
|
|
return None
|
2008-11-11 17:34:34 +01:00
|
|
|
pixmap = self.canvas.get_snapshot((-1, -1, 0, 0))
|
2007-06-15 18:03:17 +02:00
|
|
|
|
2008-11-11 17:34:34 +01:00
|
|
|
width, height = pixmap.get_size()
|
|
|
|
pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height)
|
|
|
|
pixbuf = pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(),
|
|
|
|
0, 0, 0, 0, width, height)
|
2007-11-04 17:00:48 +01:00
|
|
|
pixbuf = pixbuf.scale_simple(style.zoom(300), style.zoom(225),
|
|
|
|
gtk.gdk.INTERP_BILINEAR)
|
|
|
|
|
2008-09-17 20:25:38 +02:00
|
|
|
preview_data = []
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2008-09-17 20:25:38 +02:00
|
|
|
def save_func(buf, data):
|
|
|
|
data.append(buf)
|
2007-11-04 17:00:48 +01:00
|
|
|
|
2008-09-17 20:25:38 +02:00
|
|
|
pixbuf.save_to_callback(save_func, 'png', user_data=preview_data)
|
|
|
|
preview_data = ''.join(preview_data)
|
2007-06-15 18:03:17 +02:00
|
|
|
|
2007-10-05 22:39:45 +02:00
|
|
|
return preview_data
|
2007-06-29 20:24:22 +02:00
|
|
|
|
|
|
|
def _get_buddies(self):
|
2008-09-07 22:07:49 +02:00
|
|
|
if self.shared_activity is not None:
|
2007-08-21 12:12:13 +02:00
|
|
|
buddies = {}
|
2008-09-07 22:07:49 +02:00
|
|
|
for buddy in self.shared_activity.get_joined_buddies():
|
2007-08-21 12:12:13 +02:00
|
|
|
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 {}
|
2007-06-29 20:24:22 +02:00
|
|
|
|
|
|
|
def save(self):
|
2007-10-28 16:56:05 +01:00
|
|
|
"""Request that the activity is saved to the Journal.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
This method is called by the close() method below. In general,
|
|
|
|
activities should not override this method. This method is part of the
|
|
|
|
public API of an Acivity, and should behave in standard ways. Use your
|
2009-08-25 19:55:48 +02:00
|
|
|
own implementation of write_file() to save your Activity specific data.
|
2007-10-28 16:56:05 +01:00
|
|
|
"""
|
2007-07-23 13:45:46 +02:00
|
|
|
|
2008-07-21 19:20:22 +02:00
|
|
|
if self._jobject is None:
|
|
|
|
logging.debug('Cannot save, no journal object.')
|
|
|
|
return
|
|
|
|
|
2009-08-24 12:54:02 +02:00
|
|
|
logging.debug('Activity.save: %r', self._jobject.object_id)
|
2007-08-31 15:43:38 +02:00
|
|
|
|
2007-07-23 13:45:46 +02:00
|
|
|
if self._updating_jobject:
|
2007-09-10 17:58:01 +02:00
|
|
|
logging.info('Activity.save: still processing a previous request.')
|
2007-07-23 13:45:46 +02:00
|
|
|
return
|
|
|
|
|
2007-08-21 12:12:13 +02:00
|
|
|
buddies_dict = self._get_buddies()
|
|
|
|
if buddies_dict:
|
2008-10-08 17:30:08 +02:00
|
|
|
self.metadata['buddies_id'] = cjson.encode(buddies_dict.keys())
|
|
|
|
self.metadata['buddies'] = cjson.encode(self._get_buddies())
|
2007-08-21 12:12:13 +02:00
|
|
|
|
2009-02-25 16:09:06 +01:00
|
|
|
preview = self.get_preview()
|
2008-11-11 17:34:34 +01:00
|
|
|
if preview is not None:
|
2007-11-04 17:00:48 +01:00
|
|
|
self.metadata['preview'] = dbus.ByteArray(preview)
|
2007-08-21 12:12:13 +02:00
|
|
|
|
2009-09-19 19:02:04 +02:00
|
|
|
if not self.metadata.get('activity_id', ''):
|
|
|
|
self.metadata['activity_id'] = self.get_id()
|
|
|
|
|
2009-02-05 12:43:50 +01:00
|
|
|
file_path = os.path.join(self.get_activity_root(), 'instance',
|
|
|
|
'%i' % time.time())
|
2007-05-10 11:01:32 +02:00
|
|
|
try:
|
2007-11-09 15:43:54 +01:00
|
|
|
self.write_file(file_path)
|
2007-05-10 11:01:32 +02:00
|
|
|
except NotImplementedError:
|
2008-04-19 11:10:03 +02:00
|
|
|
logging.debug('Activity.write_file is not implemented.')
|
2009-02-05 12:43:50 +01:00
|
|
|
else:
|
|
|
|
if os.path.exists(file_path):
|
|
|
|
self._owns_file = True
|
|
|
|
self._jobject.file_path = file_path
|
2007-09-10 17:58:01 +02:00
|
|
|
|
2008-04-19 11:10:03 +02:00
|
|
|
# Cannot call datastore.write async for creates:
|
|
|
|
# https://dev.laptop.org/ticket/3071
|
2007-09-10 17:58:01 +02:00
|
|
|
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,
|
2007-10-16 15:51:48 +02:00
|
|
|
reply_handler=self.__save_cb,
|
|
|
|
error_handler=self.__save_error_cb)
|
2007-05-10 11:01:32 +02:00
|
|
|
|
2007-08-31 15:43:38 +02:00
|
|
|
def copy(self):
|
2008-04-19 11:10:03 +02:00
|
|
|
"""Request that the activity 'Keep in Journal' the current state
|
|
|
|
of the activity.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Activities should not override this method. Instead, like save() do any
|
|
|
|
copy work that needs to be done in write_file()
|
|
|
|
"""
|
2009-08-24 12:54:02 +02:00
|
|
|
logging.debug('Activity.copy: %r', self._jobject.object_id)
|
2007-08-31 15:43:38 +02:00
|
|
|
self.save()
|
|
|
|
self._jobject.object_id = None
|
|
|
|
|
2007-10-16 15:51:48 +02:00
|
|
|
def __privacy_changed_cb(self, shared_activity, param_spec):
|
2007-09-24 13:02:51 +02:00
|
|
|
if shared_activity.props.private:
|
|
|
|
self._jobject.metadata['share-scope'] = SCOPE_INVITE_ONLY
|
|
|
|
else:
|
|
|
|
self._jobject.metadata['share-scope'] = SCOPE_NEIGHBORHOOD
|
|
|
|
|
2007-10-16 15:51:48 +02:00
|
|
|
def __joined_cb(self, activity, success, err):
|
2007-05-03 05:25:15 +02:00
|
|
|
"""Callback when join has finished"""
|
2008-09-07 22:07:49 +02:00
|
|
|
self.shared_activity.disconnect(self._join_id)
|
2007-05-03 05:25:15 +02:00
|
|
|
self._join_id = None
|
|
|
|
if not success:
|
2009-08-24 12:54:02 +02:00
|
|
|
logging.debug('Failed to join activity: %s', err)
|
2007-05-03 05:25:15 +02:00
|
|
|
return
|
2007-09-12 13:35:39 +02:00
|
|
|
|
2009-09-29 20:33:13 +02:00
|
|
|
self.reveal()
|
2007-05-03 05:25:15 +02:00
|
|
|
self.emit('joined')
|
2008-09-07 22:07:49 +02:00
|
|
|
self.__privacy_changed_cb(self.shared_activity, None)
|
2007-05-03 05:25:15 +02:00
|
|
|
|
2008-07-28 16:13:59 +02:00
|
|
|
def get_shared_activity(self):
|
|
|
|
"""Returns an instance of the shared Activity or None
|
|
|
|
|
|
|
|
The shared activity is of type sugar.presence.activity.Activity
|
|
|
|
"""
|
|
|
|
return self._shared_activity
|
|
|
|
|
2006-12-04 20:12:24 +01:00
|
|
|
def get_shared(self):
|
|
|
|
"""Returns TRUE if the activity is shared on the mesh."""
|
2008-09-07 22:07:49 +02:00
|
|
|
if not self.shared_activity:
|
2007-05-03 05:25:15 +02:00
|
|
|
return False
|
2008-09-07 22:07:49 +02:00
|
|
|
return self.shared_activity.props.joined
|
2006-12-04 20:12:24 +01:00
|
|
|
|
2007-10-16 15:51:48 +02:00
|
|
|
def __share_cb(self, ps, success, activity, err):
|
2007-05-03 05:25:15 +02:00
|
|
|
self._pservice.disconnect(self._share_id)
|
|
|
|
self._share_id = None
|
|
|
|
if not success:
|
2009-08-24 12:54:02 +02:00
|
|
|
logging.debug('Share of activity %s failed: %s.',
|
|
|
|
self._activity_id, err)
|
2007-05-03 05:25:15 +02:00
|
|
|
return
|
2007-09-11 19:59:40 +02:00
|
|
|
|
2007-10-16 18:51:36 +02:00
|
|
|
logging.debug('Share of activity %s successful, PS activity is %r.',
|
|
|
|
self._activity_id, activity)
|
2007-08-31 11:37:42 +02:00
|
|
|
|
|
|
|
activity.props.name = self._jobject.metadata['title']
|
|
|
|
|
2008-09-07 22:07:49 +02:00
|
|
|
self.shared_activity = activity
|
|
|
|
self.shared_activity.connect('notify::private',
|
2007-10-16 15:51:48 +02:00
|
|
|
self.__privacy_changed_cb)
|
2007-04-27 22:07:38 +02:00
|
|
|
self.emit('shared')
|
2008-09-07 22:07:49 +02:00
|
|
|
self.__privacy_changed_cb(self.shared_activity, None)
|
2007-09-11 19:59:40 +02:00
|
|
|
|
|
|
|
self._send_invites()
|
2006-12-20 00:53:27 +01:00
|
|
|
|
2007-09-11 17:53:27 +02:00
|
|
|
def _invite_response_cb(self, error):
|
|
|
|
if error:
|
2009-08-24 12:54:02 +02:00
|
|
|
logging.error('Invite failed: %s', error)
|
2007-09-11 17:53:27 +02:00
|
|
|
|
2007-09-11 19:59:40 +02:00
|
|
|
def _send_invites(self):
|
|
|
|
while self._invites_queue:
|
2009-08-25 19:55:48 +02:00
|
|
|
buddy_key = self._invites_queue.pop()
|
2007-09-11 19:59:40 +02:00
|
|
|
buddy = self._pservice.get_buddy(buddy_key)
|
|
|
|
if buddy:
|
2008-09-07 22:07:49 +02:00
|
|
|
self.shared_activity.invite(
|
2008-04-19 11:10:03 +02:00
|
|
|
buddy, '', self._invite_response_cb)
|
2007-09-11 19:59:40 +02:00
|
|
|
else:
|
2009-08-24 12:54:02 +02:00
|
|
|
logging.error('Cannot invite %s, no such buddy.', buddy_key)
|
2007-09-11 19:59:40 +02:00
|
|
|
|
2007-09-11 17:53:27 +02:00
|
|
|
def invite(self, buddy_key):
|
2007-10-28 16:56:05 +01:00
|
|
|
"""Invite a buddy to join this Activity.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Side Effects:
|
|
|
|
Calls self.share(True) to privately share the activity if it wasn't
|
2009-08-25 19:55:48 +02:00
|
|
|
shared before.
|
2007-10-28 16:56:05 +01:00
|
|
|
"""
|
2007-09-11 19:59:40 +02:00
|
|
|
self._invites_queue.append(buddy_key)
|
2007-09-11 17:53:27 +02:00
|
|
|
|
2008-09-07 22:07:49 +02:00
|
|
|
if (self.shared_activity is None
|
|
|
|
or not self.shared_activity.props.joined):
|
2007-09-11 19:59:40 +02:00
|
|
|
self.share(True)
|
2007-09-11 17:53:27 +02:00
|
|
|
else:
|
2007-09-11 19:59:40 +02:00
|
|
|
self._send_invites()
|
2007-09-11 17:53:27 +02:00
|
|
|
|
2007-08-22 16:54:12 +02:00
|
|
|
def share(self, private=False):
|
|
|
|
"""Request that the activity be shared on the network.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-08-22 16:54:12 +02:00
|
|
|
private -- bool: True to share by invitation only,
|
|
|
|
False to advertise as shared to everyone.
|
2007-08-30 13:13:31 +02:00
|
|
|
|
|
|
|
Once the activity is shared, its privacy can be changed by setting
|
|
|
|
its 'private' property.
|
2007-08-22 16:54:12 +02:00
|
|
|
"""
|
2008-09-07 22:07:49 +02:00
|
|
|
if self.shared_activity and self.shared_activity.props.joined:
|
2007-08-22 16:54:12 +02:00
|
|
|
raise RuntimeError("Activity %s already shared." %
|
|
|
|
self._activity_id)
|
|
|
|
verb = private and 'private' or 'public'
|
2009-08-24 12:54:02 +02:00
|
|
|
logging.debug('Requesting %s share of activity %s.', verb,
|
|
|
|
self._activity_id)
|
2009-08-25 19:55:48 +02:00
|
|
|
self._share_id = self._pservice.connect("activity-shared",
|
2007-10-16 15:51:48 +02:00
|
|
|
self.__share_cb)
|
2007-08-23 14:48:16 +02:00
|
|
|
self._pservice.share_activity(self, private=private)
|
2006-12-04 20:12:24 +01:00
|
|
|
|
2008-07-21 19:20:22 +02:00
|
|
|
def _show_keep_failed_dialog(self):
|
2007-11-13 15:59:24 +01:00
|
|
|
alert = Alert()
|
|
|
|
alert.props.title = _('Keep error')
|
|
|
|
alert.props.msg = _('Keep error: all changes will be lost')
|
|
|
|
|
|
|
|
cancel_icon = Icon(icon_name='dialog-cancel')
|
|
|
|
alert.add_button(gtk.RESPONSE_CANCEL, _('Don\'t stop'), cancel_icon)
|
|
|
|
|
|
|
|
stop_icon = Icon(icon_name='dialog-ok')
|
|
|
|
alert.add_button(gtk.RESPONSE_OK, _('Stop anyway'), stop_icon)
|
|
|
|
|
|
|
|
self.add_alert(alert)
|
|
|
|
alert.connect('response', self._keep_failed_dialog_response_cb)
|
|
|
|
|
2009-09-29 20:33:13 +02:00
|
|
|
self.reveal()
|
2008-07-21 19:27:26 +02:00
|
|
|
|
2007-11-13 15:59:24 +01:00
|
|
|
def _keep_failed_dialog_response_cb(self, alert, response_id):
|
|
|
|
self.remove_alert(alert)
|
|
|
|
if response_id == gtk.RESPONSE_OK:
|
|
|
|
self.close(skip_save=True)
|
2009-09-29 20:33:13 +02:00
|
|
|
if self._quit_requested:
|
|
|
|
self._session.will_quit(self, True)
|
|
|
|
elif self._quit_requested:
|
|
|
|
self._session.will_quit(self, False)
|
2007-11-13 15:59:24 +01:00
|
|
|
|
2008-01-10 17:55:57 +01:00
|
|
|
def can_close(self):
|
|
|
|
"""Activities should override this function if they want to perform
|
|
|
|
extra checks before actually closing."""
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
2008-07-21 19:20:22 +02:00
|
|
|
def _prepare_close(self, skip_save=False):
|
|
|
|
if not skip_save:
|
|
|
|
try:
|
|
|
|
self.save()
|
2009-03-03 15:22:54 +01:00
|
|
|
except:
|
2008-07-21 19:20:22 +02:00
|
|
|
logging.info(traceback.format_exc())
|
|
|
|
self._show_keep_failed_dialog()
|
|
|
|
return False
|
|
|
|
|
|
|
|
self._closing = True
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def _complete_close(self):
|
|
|
|
self.destroy()
|
|
|
|
|
2009-09-07 13:17:57 +02:00
|
|
|
if self.shared_activity:
|
|
|
|
self.shared_activity.leave()
|
|
|
|
|
|
|
|
self._cleanup_jobject()
|
|
|
|
|
2008-07-21 19:20:22 +02:00
|
|
|
# Make the exported object inaccessible
|
|
|
|
dbus.service.Object.remove_from_connection(self._bus)
|
|
|
|
|
2008-08-06 23:04:00 +02:00
|
|
|
self._session.unregister(self)
|
|
|
|
|
2008-07-21 19:20:22 +02:00
|
|
|
def close(self, skip_save=False):
|
2007-10-28 16:56:05 +01:00
|
|
|
"""Request that the activity be stopped and saved to the Journal
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2008-04-19 11:10:03 +02:00
|
|
|
Activities should not override this method, but should implement
|
|
|
|
write_file() to do any state saving instead. If the application wants
|
|
|
|
to control wether it can close, it should override can_close().
|
2007-10-28 16:56:05 +01:00
|
|
|
"""
|
2008-07-21 19:20:22 +02:00
|
|
|
if not self.can_close():
|
2007-11-13 15:59:24 +01:00
|
|
|
return
|
2007-07-23 13:45:46 +02:00
|
|
|
|
2010-03-09 19:56:21 +01:00
|
|
|
if skip_save or self._jobject is None or \
|
|
|
|
self.metadata.get('title_set_by_user', '0') == '1':
|
2009-01-13 19:34:26 +01:00
|
|
|
if not self._closing:
|
|
|
|
if not self._prepare_close(skip_save):
|
|
|
|
return
|
2007-10-15 23:47:02 +02:00
|
|
|
|
2009-01-13 19:34:26 +01:00
|
|
|
if not self._updating_jobject:
|
|
|
|
self._complete_close()
|
|
|
|
else:
|
2009-01-18 16:30:53 +01:00
|
|
|
title_alert = NamingAlert(self, get_bundle_path())
|
2009-01-13 19:34:26 +01:00
|
|
|
title_alert.set_transient_for(self.get_toplevel())
|
|
|
|
title_alert.show()
|
2009-09-29 20:33:13 +02:00
|
|
|
self.reveal()
|
2008-01-31 20:48:03 +01:00
|
|
|
|
2007-10-16 15:51:48 +02:00
|
|
|
def __realize_cb(self, window):
|
2007-10-15 23:47:02 +02:00
|
|
|
wm.set_bundle_id(window.window, self.get_bundle_id())
|
2007-10-16 14:40:43 +02:00
|
|
|
wm.set_activity_id(window.window, str(self._activity_id))
|
2007-10-15 23:47:02 +02:00
|
|
|
|
|
|
|
def __delete_event_cb(self, widget, event):
|
|
|
|
self.close()
|
|
|
|
return True
|
2007-04-27 22:07:38 +02:00
|
|
|
|
2007-05-29 15:53:58 +02:00
|
|
|
def get_metadata(self):
|
2007-10-28 16:56:05 +01:00
|
|
|
"""Returns the jobject metadata or None if there is no jobject.
|
2009-08-25 19:55:48 +02:00
|
|
|
|
|
|
|
Activities can set metadata in write_file() using:
|
2007-10-28 16:56:05 +01:00
|
|
|
self.metadata['MyKey'] = "Something"
|
2009-08-25 19:55:48 +02:00
|
|
|
|
|
|
|
and retrieve metadata in read_file() using:
|
2007-10-28 16:56:05 +01:00
|
|
|
self.metadata.get('MyKey', 'aDefaultValue')
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2007-10-28 16:56:05 +01:00
|
|
|
Note: Make sure your activity works properly if one or more of the
|
|
|
|
metadata items is missing. Never assume they will all be present.
|
|
|
|
"""
|
2007-05-29 15:53:58 +02:00
|
|
|
if self._jobject:
|
|
|
|
return self._jobject.metadata
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
metadata = property(get_metadata, None)
|
|
|
|
|
2008-11-07 16:23:54 +01:00
|
|
|
def handle_view_source(self):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
def get_document_path(self, async_cb, async_err_cb):
|
|
|
|
async_err_cb(NotImplementedError())
|
|
|
|
|
2008-09-08 01:30:20 +02:00
|
|
|
# DEPRECATED
|
|
|
|
_shared_activity = property(lambda self: self.shared_activity, None)
|
|
|
|
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2008-08-06 23:04:00 +02:00
|
|
|
_session = None
|
|
|
|
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2008-08-06 23:04:00 +02:00
|
|
|
def _get_session():
|
|
|
|
global _session
|
|
|
|
|
|
|
|
if _session is None:
|
|
|
|
_session = _ActivitySession()
|
|
|
|
|
|
|
|
return _session
|
|
|
|
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2007-05-14 19:56:06 +02:00
|
|
|
def get_bundle_name():
|
2007-10-28 16:56:05 +01:00
|
|
|
"""Return the bundle name for the current process' bundle"""
|
2007-10-16 14:29:38 +02:00
|
|
|
return os.environ['SUGAR_BUNDLE_NAME']
|
2009-08-25 19:55:48 +02:00
|
|
|
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2007-02-22 15:55:07 +01:00
|
|
|
def get_bundle_path():
|
2007-10-28 16:56:05 +01:00
|
|
|
"""Return the bundle path for the current process' bundle"""
|
2007-02-23 17:08:37 +01:00
|
|
|
return os.environ['SUGAR_BUNDLE_PATH']
|
2007-06-27 23:12:32 +02:00
|
|
|
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2007-12-03 22:10:14 +01:00
|
|
|
def get_activity_root():
|
|
|
|
"""Returns a path for saving Activity specific preferences, etc."""
|
|
|
|
if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \
|
|
|
|
os.environ['SUGAR_ACTIVITY_ROOT']:
|
|
|
|
return os.environ['SUGAR_ACTIVITY_ROOT']
|
|
|
|
else:
|
|
|
|
raise RuntimeError("No SUGAR_ACTIVITY_ROOT set.")
|
2007-12-19 13:02:16 +01:00
|
|
|
|
2009-08-25 21:12:40 +02:00
|
|
|
|
2007-12-19 13:02:16 +01:00
|
|
|
def show_object_in_journal(object_id):
|
|
|
|
bus = dbus.SessionBus()
|
|
|
|
obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH)
|
|
|
|
journal = dbus.Interface(obj, J_DBUS_INTERFACE)
|
|
|
|
journal.ShowObject(object_id)
|