Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorSimon McVittie <simon.mcvittie@collabora.co.uk>2007-10-30 12:38:32 (GMT)
committer Simon McVittie <simon.mcvittie@collabora.co.uk>2007-10-30 12:38:32 (GMT)
commit4391d4777ad9d42d0cd6c193699ab7a1ec585a37 (patch)
treefbf913532bc494b610b49be23c7cb879675fdaa6 /lib
parent249521253dfc61248a22133c6624225234515eb5 (diff)
parent6768a4216fad97711faeeffcfa9c43e8adc8491b (diff)
Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar
Conflicts: NEWS
Diffstat (limited to 'lib')
-rw-r--r--lib/sugar/activity/activity.py244
-rw-r--r--lib/sugar/activity/bundlebuilder.py16
-rw-r--r--lib/sugar/graphics/alert.py33
3 files changed, 262 insertions, 31 deletions
diff --git a/lib/sugar/activity/activity.py b/lib/sugar/activity/activity.py
index 731a88a..14cadd7 100644
--- a/lib/sugar/activity/activity.py
+++ b/lib/sugar/activity/activity.py
@@ -1,7 +1,31 @@
-"""Base class for Python-coded activities
+"""Base class for activities written in Python
-This is currently the only reference for what an
+This is currently the only definitive reference for what an
activity must do to participate in the Sugar desktop.
+
+ 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.
+
+For example the most minimal Activity:
+
+
+ from sugar.activity import activity
+
+ 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.
"""
# Copyright (C) 2006-2007 Red Hat, Inc.
#
@@ -49,6 +73,11 @@ SCOPE_INVITE_ONLY = "invite" # shouldn't be shown in UI, it's implicit when you
SCOPE_NEIGHBORHOOD = "public"
class ActivityToolbar(gtk.Toolbar):
+ """The Activity toolbar with the Journal entry title, sharing, Keep and Stop buttons
+
+ All activities should have this toolbar. It is easiest to add it to your
+ Activity by using the ActivityToolbox.
+ """
def __init__(self, activity):
gtk.Toolbar.__init__(self)
@@ -169,6 +198,38 @@ class ActivityToolbar(gtk.Toolbar):
self._update_share()
class EditToolbar(gtk.Toolbar):
+ """Provides the standard edit toolbar for Activities.
+
+ Members:
+ undo -- the undo button
+ redo -- the redo button
+ copy -- the copy button
+ paste -- the paste button
+ separator -- A separator between undo/redo and copy/paste
+
+ This class only provides the 'edit' buttons in a standard layout, your activity
+ will need to either hide buttons which make no sense for your Activity, or you
+ need to connect the button events to your own callbacks:
+
+ ## Example from Read.activity:
+ # Create the edit toolbar:
+ self._edit_toolbar = EditToolbar(self._view)
+ # Hide undo and redo, they're not needed
+ self._edit_toolbar.undo.props.visible = False
+ self._edit_toolbar.redo.props.visible = False
+ # Hide the separator too:
+ self._edit_toolbar.separator.props.visible = False
+
+ # As long as nothing is selected, copy needs to be insensitive:
+ self._edit_toolbar.copy.set_sensitive(False)
+ # When the user clicks the button, call _edit_toolbar_copy_cb()
+ self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb)
+
+ # Add the edit toolbar:
+ toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
+ # And make it visible:
+ self._edit_toolbar.show()
+ """
def __init__(self):
gtk.Toolbar.__init__(self)
@@ -198,6 +259,23 @@ class EditToolbar(gtk.Toolbar):
self.paste.show()
class ActivityToolbox(Toolbox):
+ """Creates the Toolbox for the Activity
+
+ By default, the toolbox contains only the ActivityToolbar. After creating the
+ toolbox, you can add your activity specific toolbars, for example the
+ EditToolbar.
+
+ To add the ActivityToolbox to your Activity in MyActivity.__init__() do:
+
+ # Create the Toolbar with the ActivityToolbar:
+ toolbox = activity.ActivityToolbox(self)
+ ... your code, inserting all other toolbars you need, like EditToolbar ...
+
+ # Add the toolbox to the activity frame:
+ self.set_toolbox(toolbox)
+ # And make it visible:
+ toolbox.show()
+ """
def __init__(self, activity):
Toolbox.__init__(self)
@@ -209,7 +287,71 @@ class ActivityToolbox(Toolbox):
return self._activity_toolbar
class Activity(Window, gtk.Container):
- """Base Activity class that all other Activities derive from."""
+ """This is the base Activity class that all other Activities derive from. This is where your activity starts.
+
+ To get a working Activity:
+ 0. Derive your Activity from this class:
+ class MyActivity(activity.Activity):
+ ...
+
+ 1. implement an __init__() method for your Activity class.
+
+ Use your init method to create your own ActivityToolbar which will
+ contain some standard buttons:
+ toolbox = activity.ActivityToolbox(self)
+
+ Add extra Toolbars to your toolbox.
+
+ You should setup Activity sharing here too.
+
+ Finaly, your Activity may need some resources which you can claim
+ here too.
+
+ The __init__() method is also used to make the distinction between
+ being resumed from the Journal, or starting with a blank document.
+
+ 2. Implement read_file() and write_file()
+ Most activities revolve around creating and storing Journal entries.
+ For example, Write: You create a document, it is saved to the Journal
+ and then later you resume working on the document.
+
+ read_file() and write_file() will be called by sugar to tell your
+ Activity that it should load or save the document the user is working
+ on.
+
+ 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.
+
+ 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
+ this with the ActivityToolbox(self) call in step 1.
+
+ 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:
+ class EditToolbar(activity.EditToolbar):
+ ...
+
+ See EditToolbar for the methods you should implement in your class.
+
+ 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):
+ ...
+
+ 4. Use your creativity. Make your Activity something special and share
+ it with your friends!
+
+ Read through the methods of the Activity class below, to learn more about
+ how to make an Activity work.
+
+ 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.
+ """
__gtype_name__ = 'SugarActivity'
__gsignals__ = {
@@ -248,6 +390,11 @@ class Activity(Window, gtk.Container):
Creates an ActivityService (self._bus) servicing
this application.
+
+ Usage:
+ If your Activity implements __init__(), it should call
+ the base class __init()__ before doing Activity specific things.
+
"""
Window.__init__(self)
@@ -360,12 +507,25 @@ class Activity(Window, gtk.Container):
return self._max_participants
def get_id(self):
+ """Returns the activity id of the current instance of your activity.
+
+ 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
+ resumes an activity. This is also the identity of your Activity to other
+ XOs for use when sharing.
+ """
return self._activity_id
def get_bundle_id(self):
+ """Returns the bundle_id from the activity.info file"""
return os.environ['SUGAR_BUNDLE_ID']
def set_canvas(self, canvas):
+ """Sets the 'work area' of your activity with the canvas of your choice.
+
+ One commonly used canvas is gtk.ScrolledWindow
+ """
Window.set_canvas(self, canvas)
canvas.connect('map', self.__canvas_map_cb)
@@ -380,10 +540,17 @@ class Activity(Window, gtk.Container):
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.
+ """Returns a path for saving Activity specific preferences, etc.
+
+ 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.
+
+ Currently, this will return something like ~/.sugar/default/MyActivityName/
+
+ Activities should ONLY save settings, user preferences and other data
+ 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.
"""
if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \
os.environ['SUGAR_ACTIVITY_ROOT']:
@@ -395,6 +562,17 @@ class Activity(Window, gtk.Container):
"""
Subclasses implement this method if they support resuming objects from
the journal. 'file_path' is the file to read from.
+
+ 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.
+
+ Although not required, this is also a good time to read all meta-data:
+ 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.
"""
raise NotImplementedError
@@ -402,6 +580,17 @@ class Activity(Window, gtk.Container):
"""
Subclasses implement this method if they support saving data to objects
in the journal. 'file_path' is the file to write to.
+
+ If the user did make changes, you should create the file_path and save
+ all document data to it.
+
+ Additionally, you should also write any metadata needed to resume your
+ activity. For example, the Read activity saves the current page and zoom
+ level, so it can display the page.
+
+ Note: Currently, the file_path *WILL* be different from the one you
+ 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.
"""
raise NotImplementedError
@@ -466,7 +655,13 @@ class Activity(Window, gtk.Container):
self._preview = self._get_preview()
def save(self):
- """Request that the activity is saved to the Journal."""
+ """Request that the activity is saved to the Journal.
+
+ 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
+ own implementation of write_file() to save your Activity specific data.
+ """
logging.debug('Activity.save: %r' % self._jobject.object_id)
@@ -507,6 +702,11 @@ class Activity(Window, gtk.Container):
error_handler=self.__save_error_cb)
def copy(self):
+ """Request that the activity 'Keep in Journal' the current state of the activity.
+
+ Activities should not override this method. Instead, like save() do any
+ copy work that needs to be done in write_file()
+ """
logging.debug('Activity.copy: %r' % self._jobject.object_id)
self._preview = self._get_preview()
self.save()
@@ -570,6 +770,12 @@ class Activity(Window, gtk.Container):
logging.error('Cannot invite %s, no such buddy.' % buddy_key)
def invite(self, buddy_key):
+ """Invite a buddy to join this Activity.
+
+ Side Effects:
+ Calls self.share(True) to privately share the activity if it wasn't
+ shared before.
+ """
self._invites_queue.append(buddy_key)
if (self._shared_activity is None
@@ -598,6 +804,11 @@ class Activity(Window, gtk.Container):
self._pservice.share_activity(self, private=private)
def close(self):
+ """Request that the activity be stopped and saved to the Journal
+
+ Activities should not override this method, but should implement write_file() to
+ do any state saving instead.
+ """
self.save()
if self._shared_activity:
@@ -617,6 +828,17 @@ class Activity(Window, gtk.Container):
return True
def get_metadata(self):
+ """Returns the jobject metadata or None if there is no jobject.
+
+ Activities can set metadata in write_file() using:
+ self.metadata['MyKey'] = "Something"
+
+ and retrieve metadata in read_file() using:
+ self.metadata.get('MyKey', 'aDefaultValue')
+
+ Note: Make sure your activity works properly if one or more of the
+ metadata items is missing. Never assume they will all be present.
+ """
if self._jobject:
return self._jobject.metadata
else:
@@ -625,12 +847,10 @@ class Activity(Window, gtk.Container):
metadata = property(get_metadata, None)
def get_bundle_name():
- """Return the bundle name for the current process' bundle
- """
+ """Return the bundle name for the current process' bundle"""
return os.environ['SUGAR_BUNDLE_NAME']
def get_bundle_path():
- """Return the bundle path for the current process' bundle
- """
+ """Return the bundle path for the current process' bundle"""
return os.environ['SUGAR_BUNDLE_PATH']
diff --git a/lib/sugar/activity/bundlebuilder.py b/lib/sugar/activity/bundlebuilder.py
index c2e3278..927b695 100644
--- a/lib/sugar/activity/bundlebuilder.py
+++ b/lib/sugar/activity/bundlebuilder.py
@@ -348,22 +348,6 @@ def cmd_release(bundle_name, manifest):
print 'Creating the bundle...'
cmd_dist(bundle_name, manifest)
- if os.environ.has_key('ACTIVITIES_REPOSITORY'):
- print 'Uploading to the activities repository...'
- repo = os.environ['ACTIVITIES_REPOSITORY']
-
- server, path = repo.split(':')
- retcode = subprocess.call(['ssh', server, 'rm',
- '%s/%s*' % (path, bundle_name)])
- if retcode:
- print 'ERROR - cannot remove old bundles from the repository.'
-
- bundle_path = os.path.join(_get_source_path(),
- _get_package_name(bundle_name))
- retcode = subprocess.call(['scp', bundle_path, repo])
- if retcode:
- print 'ERROR - cannot upload the bundle to the repository.'
-
print 'Done.'
def cmd_clean():
diff --git a/lib/sugar/graphics/alert.py b/lib/sugar/graphics/alert.py
index dd1bcec..ef649b2 100644
--- a/lib/sugar/graphics/alert.py
+++ b/lib/sugar/graphics/alert.py
@@ -32,9 +32,8 @@ class Alert(gtk.EventBox, gobject.GObject):
Alerts are used inside the activity window instead of being a
separate popup window. They do not hide canvas content. You can
use add_alert(widget) and remove_alert(widget) inside your activity
- to add and remove the alert. You can set the position (bottom=-1,
- top=0,1) for alerts global for the window by changing alert_position,
- default is bottom.
+ to add and remove the alert. The position of the alert is below the
+ toolbox or top in fullscreen mode.
Properties:
'title': the title of the alert,
@@ -225,3 +224,31 @@ class TimeoutAlert(Alert):
self._response(gtk.RESPONSE_OK)
return False
return True
+
+
+class NotifyAlert(Alert):
+ """Timeout alert with only an "OK" button - just for notifications"""
+
+ def __init__(self, timeout=5, **kwargs):
+ Alert.__init__(self, **kwargs)
+
+ self._timeout = timeout
+
+ self._timeout_text = _TimeoutIcon(
+ text=self._timeout,
+ color=style.COLOR_BUTTON_GREY.get_int(),
+ background_color=style.COLOR_WHITE.get_int())
+ canvas = hippo.Canvas()
+ canvas.set_root(self._timeout_text)
+ canvas.show()
+ self.add_button(gtk.RESPONSE_OK, _('OK'), canvas)
+
+ gobject.timeout_add(1000, self.__timeout)
+
+ def __timeout(self):
+ self._timeout -= 1
+ self._timeout_text.props.text = self._timeout
+ if self._timeout == 0:
+ self._response(gtk.RESPONSE_OK)
+ return False
+ return True