Documentation - rewrite activity section

- rewrite of activity section, bundle section, graphics.alert, and
  graphics.window.
This commit is contained in:
James Cameron 2017-07-19 17:31:09 +10:00
parent 4652b7ca2a
commit 5750773dda
5 changed files with 663 additions and 382 deletions

View File

@ -1,40 +1,3 @@
'''
Base class for activities written in Python
===========================================
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:
.. code-block:: python
from sugar3.activity import activity
class ReadActivity(activity.Activity):
pass
To get a real, working activity, you will at least have to implement:
__init__(), :func:`sugar3.activity.activity.Activity.read_file()` and
:func:`sugar3.activity.activity.Activity.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.
.. note:: This API is STABLE.
'''
# Copyright (C) 2006-2007 Red Hat, Inc. # Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2007-2009 One Laptop Per Child # Copyright (C) 2007-2009 One Laptop Per Child
# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> # Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
@ -54,6 +17,147 @@ will need for a real activity.
# Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA. # Boston, MA 02111-1307, USA.
'''
Activity
========
A definitive reference for what a Sugar Python activity must do to
participate in the Sugar desktop.
.. note:: This API is STABLE.
The :class:`Activity` class is used to derive all Sugar Python
activities. This is where your activity starts.
**Derive from the class**
.. code-block:: python
from sugar3.activity.activity import Activity
class MyActivity(Activity):
def __init__(self, handle):
Activity.__init__(self, handle)
An activity must implement a new class derived from
:class:`Activity`.
Name the new class `MyActivity`, where `My` is the name of your
activity. Use bundle metadata to tell Sugar to instantiate this
class. See :class:`~sugar3.bundle` for bundle metadata.
**Create a ToolbarBox**
In your :func:`__init__` method create a
:class:`~sugar3.graphics.toolbarbox.ToolbarBox`, with an
:class:`~sugar3.activity.widgets.ActivityToolbarButton`, a
:class:`~sugar3.activity.widgets.StopButton`, and then call
:func:`~sugar3.graphics.window.Window.set_toolbar_box`.
.. code-block:: python
:emphasize-lines: 2-4,10-
from sugar3.activity.activity import Activity
from sugar3.graphics.toolbarbox import ToolbarBox
from sugar3.activity.widgets import ActivityToolbarButton
from sugar3.activity.widgets import StopButton
class MyActivity(Activity):
def __init__(self, handle):
Activity.__init__(self, handle)
toolbar_box = ToolbarBox()
activity_button = ActivityToolbarButton(self)
toolbar_box.toolbar.insert(activity_button, 0)
activity_button.show()
separator = Gtk.SeparatorToolItem(draw=False)
separator.set_expand(True)
toolbar_box.toolbar.insert(separator, -1)
separator.show()
stop_button = StopButton(self)
toolbar_box.toolbar.insert(stop_button, -1)
stop_button.show()
self.set_toolbar_box(toolbar_box)
toolbar_box.show()
**Journal methods**
In your activity class, code
:func:`~sugar3.activity.activity.Activity.read_file()` and
:func:`~sugar3.activity.activity.Activity.write_file()` methods.
Most activities create and resume journal objects. For example,
the Write activity saves the document as a journal object, and
reads it from the journal object when resumed.
:func:`~sugar3.activity.activity.Activity.read_file()` and
:func:`~sugar3.activity.activity.Activity.write_file()` will be
called by the toolkit to tell your activity that it must load or
save the data the user is working on.
**Activity toolbars**
Add any activity toolbars before the last separator in the
:class:`~sugar3.graphics.toolbarbox.ToolbarBox`, so that the
:class:`~sugar3.activity.widgets.StopButton` is aligned to the
right.
There are a number of standard Toolbars.
You may need the :class:`~sugar3.activity.widgets.EditToolbar`.
This has copy and paste buttons. You may derive your own
class from
:class:`~sugar3.activity.widgets.EditToolbar`:
.. code-block:: python
from sugar3.activity.widgets import EditToolbar
class MyEditToolbar(EditToolbar):
...
See :class:`~sugar3.activity.widgets.EditToolbar` for the
methods you should implement in your class.
You may need some activity specific buttons and options which
you can create as toolbars by deriving a class from
:class:`Gtk.Toolbar`:
.. code-block:: python
class MySpecialToolbar(Gtk.Toolbar):
...
**Sharing**
An activity can be shared across the network with other users. Near
the end of your :func:`__init__`, test if the activity is shared,
and connect to signals to detect sharing.
.. code-block:: python
if self.shared_activity:
# we are joining the activity
self.connect('joined', self._joined_cb)
if self.get_shared():
# we have already joined
self._joined_cb()
else:
# we are creating the activity
self.connect('shared', self._shared_cb)
Add methods to handle the signals.
Read through the methods of the :class:`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.
You may copy it and use it as a template.
'''
import gettext import gettext
import logging import logging
import os import os
@ -117,6 +221,9 @@ N_IFACE_NAME = 'org.freedesktop.Notifications'
CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties' CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
PREVIEW_SIZE = style.zoom(300), style.zoom(225) PREVIEW_SIZE = style.zoom(300), style.zoom(225)
"""
Size of a preview image for journal object metadata.
"""
class _ActivitySession(GObject.GObject): class _ActivitySession(GObject.GObject):
@ -170,118 +277,31 @@ class _ActivitySession(GObject.GObject):
class Activity(Window, Gtk.Container): class Activity(Window, Gtk.Container):
''' """
This is the base Activity class that all other Activities derive from. Initialise an Activity.
This is where your activity starts.
To get a working Activity: Args:
0. Derive your Activity from this class: handle (:class:`~sugar3.activity.activityhandle.ActivityHandle`): instance providing the activity id and access to the presence service which *may* provide sharing for this application
create_jobject (boolean): DEPRECATED: define if it should create a journal object if we are not resuming. The parameter is ignored, and always will be created a object in the Journal.
.. code-block:: python **Signals:**
* **shared** - the activity has been shared on a network in order that other users may join,
* **joined** - the activity has joined with other instances of the activity to create a shared network activity.
class MyActivity(activity.Activity): Side effects:
...
1. implement an __init__() method for your Activity class. * sets the gdk screen DPI setting (resolution) to the Sugar screen resolution.
Use your init method to create your own ToolbarBox. * connects our "destroy" message to our _destroy_cb method.
This is the code to make a basic toolbar with the activity
toolbar and a stop button.
.. code-block:: python * creates a base Gtk.Window within this window.
from sugar3.graphics.toolbarbox import ToolbarBox * creates an ActivityService (self._bus) servicing this application.
from sugar3.activity.widgets import ActivityToolbarButton
from sugar3.activity.widgets import StopButton
def __init__(self, handle): When your activity implements :func:`__init__`, it must call the
activity.Activity.__init__(self, handle) :class:`Activity` class :func:`__init__` before any
:class:`Activity` specific code.
toolbar_box = ToolbarBox() """
activity_button = ActivityToolbarButton(self)
toolbar_box.toolbar.insert(activity_button, 0)
activity_button.show()
... Your toolbars ...
separator = Gtk.SeparatorToolItem(draw=False)
separator.set_expand(True)
toolbar_box.toolbar.insert(separator, -1)
separator.show()
stop_button = StopButton(self)
toolbar_box.toolbar.insert(stop_button, -1)
stop_button.show()
self.set_toolbar_box(toolbar_box)
toolbar_box.show()
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 :func:`sugar3.activity.activity.Activity.read_file()` and
:func:`sugar3.activity.activity.Activity.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.
:func:`sugar3.activity.activity.Activity.read_file()` and
:func:`sugar3.activity.activity.Activity.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
:class:`sugar3.activity.widgets.EditToolbar`:
.. code-block:: python
from sugar3.activity.widgets import EditToolbar
class MyEditToolbar(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 :class:`Gtk.Toolbar`:
.. code-block:: python
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' __gtype_name__ = 'SugarActivity'
@ -294,39 +314,6 @@ class Activity(Window, Gtk.Container):
} }
def __init__(self, handle, create_jobject=True): def __init__(self, handle, create_jobject=True):
'''
Initialise the Activity
Args:
handle (sugar3.activity.activityhandle.ActivityHandle)
instance providing the activity id and access to the
presence service which *may* provide sharing for this
application
create_jobject (boolean)
DEPRECATED: define if it should create a journal object if we are
not resuming. The parameter is ignored, and always will
be created a object in the Journal.
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.
Usage:
If your Activity implements __init__(), it should call
the base class __init()__ before doing Activity specific things.
'''
# Stuff that needs to be done early # Stuff that needs to be done early
icons_path = os.path.join(get_bundle_path(), 'icons') icons_path = os.path.join(get_bundle_path(), 'icons')
Gtk.IconTheme.get_default().append_search_path(icons_path) Gtk.IconTheme.get_default().append_search_path(icons_path)
@ -464,6 +451,13 @@ class Activity(Window, Gtk.Container):
self._original_title = self._jobject.metadata['title'] self._original_title = self._jobject.metadata['title']
def add_stop_button(self, button): def add_stop_button(self, button):
"""
Register an extra stop button. Normally not required. Use only
when an activity has more than the default stop button.
Args:
button (:class:`Gtk.Button`): a stop button
"""
self._stop_buttons.append(button) self._stop_buttons.append(button)
def run_main_loop(self): def run_main_loop(self):
@ -548,6 +542,14 @@ class Activity(Window, Gtk.Container):
wait_loop.quit() wait_loop.quit()
def get_active(self): def get_active(self):
'''
Get whether the activity is active. An activity may be made
inactive by the shell as a result of another activity being
active. An active activity accumulates usage metrics.
Returns:
boolean: if the activity is active.
'''
return self._active return self._active
def _update_spent_time(self): def _update_spent_time(self):
@ -562,6 +564,14 @@ class Activity(Window, Gtk.Container):
self._active_time = current self._active_time = current
def set_active(self, active): def set_active(self, active):
'''
Set whether the activity is active. An activity may declare
itself active or inactive, as can the shell. An active activity
accumulates usage metrics.
Args:
active (boolean): if the activity is active.
'''
if self._active != active: if self._active != active:
self._active = active self._active = active
self._update_spent_time() self._update_spent_time()
@ -570,12 +580,22 @@ class Activity(Window, Gtk.Container):
active = GObject.property( active = GObject.property(
type=bool, default=False, getter=get_active, setter=set_active) type=bool, default=False, getter=get_active, setter=set_active)
'''
Whether an activity is active.
'''
def get_max_participants(self): def get_max_participants(self):
''' '''
Get the maximum number of users that can share a instance
of this activity. Should be configured in the activity.info
file. When not configured, it will be zero.
Returns: Returns:
int: the max number of users than can share a instance of the int: the maximum number of participants
activity. Should be configured in the activity.info file.
See also
:func:`~sugar3.bundle.activitybundle.ActivityBundle.get_max_participants`
in :class:`~sugar3.bundle.activitybundle.ActivityBundle`.
''' '''
# If max_participants has not been set in the activity, get it # If max_participants has not been set in the activity, get it
# from the bundle. # from the bundle.
@ -585,6 +605,16 @@ class Activity(Window, Gtk.Container):
return self._max_participants return self._max_participants
def set_max_participants(self, participants): def set_max_participants(self, participants):
'''
Set the maximum number of users that can share a instance of
this activity. An activity may use this method instead of or
as well as configuring the activity.info file. When both are
used, this method takes precedence over the activity.info
file.
Args:
participants (int): the maximum number of participants
'''
self._max_participants = participants self._max_participants = participants
max_participants = GObject.property( max_participants = GObject.property(
@ -593,15 +623,18 @@ class Activity(Window, Gtk.Container):
def get_id(self): def get_id(self):
''' '''
Get the activity id, a likely-unique identifier for the
instance of an activity, randomly assigned when a new instance
is started, or read from the journal object metadata when a
saved instance is resumed.
Returns: Returns:
int: the activity id of the current instance of your activity. str: the activity id
The activity id is sort-of-like the unix process id (PID). However, See also
unlike PIDs it is only different for each new instance :meth:`~sugar3.activity.activityfactory.create_activity_id`
and stays the same everytime a user and :meth:`~sugar3.util.unique_id`.
resumes an activity. This is also the identity of your Activity to
other XOs for use when sharing.
''' '''
return self._activity_id return self._activity_id
@ -614,6 +647,8 @@ class Activity(Window, Gtk.Container):
def get_canvas(self): def get_canvas(self):
''' '''
Get the :attr:`canvas`.
Returns: Returns:
:class:`Gtk.Widget`: the widget used as canvas :class:`Gtk.Widget`: the widget used as canvas
''' '''
@ -621,9 +656,7 @@ class Activity(Window, Gtk.Container):
def set_canvas(self, canvas): def set_canvas(self, canvas):
''' '''
Sets the 'work area' of your activity with the canvas of your choice. Set the :attr:`canvas`.
One commonly used canvas is Gtk.ScrolledWindow
Args: Args:
canvas (:class:`Gtk.Widget`): the widget used as canvas canvas (:class:`Gtk.Widget`): the widget used as canvas
@ -634,6 +667,10 @@ class Activity(Window, Gtk.Container):
canvas.connect('map', self.__canvas_map_cb) canvas.connect('map', self.__canvas_map_cb)
canvas = property(get_canvas, set_canvas) canvas = property(get_canvas, set_canvas)
'''
The :class:`Gtk.Widget` used as canvas, or work area of your
activity. A common canvas is :class:`Gtk.ScrolledWindow`.
'''
def __screen_size_changed_cb(self, screen): def __screen_size_changed_cb(self, screen):
self._adapt_window_to_screen() self._adapt_window_to_screen()
@ -709,8 +746,10 @@ class Activity(Window, Gtk.Container):
Subclasses implement this method if they support resuming objects from Subclasses implement this method if they support resuming objects from
the journal. 'file_path' is the file to read from. the journal. 'file_path' is the file to read from.
You should immediately open the file from the file_path, because the You should immediately open the file from the file_path,
file_name will be deleted immediately after returning from read_file(). because the file_name will be deleted immediately after
returning from :meth:`read_file`.
Once the file has been opened, you do not have to read it immediately: 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 After you have opened it, the file will only be really gone when you
close it. close it.
@ -722,7 +761,7 @@ class Activity(Window, Gtk.Container):
originals. originals.
Args: Args:
str: the file path to read file_path (str): the file path to read
''' '''
raise NotImplementedError raise NotImplementedError
@ -738,10 +777,10 @@ class Activity(Window, Gtk.Container):
activity. For example, the Read activity saves the current page and activity. For example, the Read activity saves the current page and
zoom level, so it can display the page. zoom level, so it can display the page.
Note: Currently, the file_path *WILL* be different from the one you Note: Currently, the file_path *WILL* be different from the
received in file_read(). Even if you kept the file_path from one you received in :meth:`read_file`. Even if you kept the
file_read() open until now, you must still write the entire file to file_path from :meth:`read_file` open until now, you must
this file_path. still write the entire file to this file_path.
Args: Args:
file_path (str): complete path of the file to write file_path (str): complete path of the file to write
@ -794,17 +833,20 @@ class Activity(Window, Gtk.Container):
def get_preview(self): def get_preview(self):
''' '''
Get a preview image from the :attr:`canvas`, for use as
metadata for the journal object. This should be what the user
is seeing at the time.
Returns: Returns:
str: with data ready to save with an image representing the state str: image data in PNG format
of the activity. Generally this is what the user is seeing in
this moment.
Activities can override this method, which should return a str with the Activities may override this method, and return a string with
binary content of a png image with a width of PREVIEW_SIZE pixels. image data in PNG format with a width and height of
:attr:`~sugar3.activity.activity.PREVIEW_SIZE` pixels.
The method does create a cairo surface similar to that of the canvas' The method creates a Cairo surface similar to that of the
window and draws on that. Then we create a cairo image surface with :ref:`Gdk.Window` of the :meth:`canvas` widget, draws on it,
the desired preview size and scale the canvas surface on that. then resizes to a surface with the preview size.
''' '''
if self.canvas is None or not hasattr(self.canvas, 'get_window'): if self.canvas is None or not hasattr(self.canvas, 'get_window'):
return None return None
@ -864,12 +906,13 @@ class Activity(Window, Gtk.Container):
def save(self): def save(self):
''' '''
Request that the activity is saved to the Journal. Save to the journal.
This method is called by the close() method below. In general, This may be called by the :meth:`close` method.
activities should not override this method. This method is part of the
public API of an Activity, and should behave in standard ways. Use your Activities should not override this method. This method is part of the
own implementation of write_file() to save your Activity specific data. public API of an activity, and should behave in standard ways. Use your
own implementation of write_file() to save your activity specific data.
''' '''
if self._jobject is None: if self._jobject is None:
@ -931,11 +974,15 @@ class Activity(Window, Gtk.Container):
def copy(self): def copy(self):
''' '''
Request that the activity 'Keep in Journal' the current state Make a copy of the journal object.
of the activity.
Activities should not override this method. Instead, like save() do any Activities may use this to 'Keep in Journal' the current state
copy work that needs to be done in write_file() of the activity. A new journal object will be created for the
running activity.
Activities should not override this method. Instead, like
:meth:`save` do any copy work that needs to be done in
:meth:`write_file`.
''' '''
logging.debug('Activity.copy: %r' % self._jobject.object_id) logging.debug('Activity.copy: %r' % self._jobject.object_id)
self.save() self.save()
@ -968,17 +1015,22 @@ class Activity(Window, Gtk.Container):
def get_shared_activity(self): def get_shared_activity(self):
''' '''
Returns: Get the shared activity of type
an instance of the shared Activity or None :class:`sugar3.presence.activity.Activity`, or None if the
activity is not shared, or is shared and not yet joined.
The shared activity is of type sugar3.presence.activity.Activity Returns:
:class:`sugar3.presence.activity.Activity`: instance of
the shared activity or None
''' '''
return self.shared_activity return self.shared_activity
def get_shared(self): def get_shared(self):
''' '''
Get whether the activity is shared.
Returns: Returns:
bool: True if the activity is shared on the mesh. bool: the activity is shared.
''' '''
if not self.shared_activity: if not self.shared_activity:
return False return False
@ -1025,14 +1077,14 @@ class Activity(Window, Gtk.Container):
def invite(self, account_path, contact_id): def invite(self, account_path, contact_id):
''' '''
Invite a buddy to join this Activity. Invite a buddy to join this activity.
Args: Args:
account_path account_path
contact_id contact_id
Side Effects: **Side Effects:**
Calls self.share(True) to privately share the activity if it wasn't Calls :meth:`share` to privately share the activity if it wasn't
shared before. shared before.
''' '''
self._invites_queue.append((account_path, contact_id)) self._invites_queue.append((account_path, contact_id))
@ -1051,8 +1103,9 @@ class Activity(Window, Gtk.Container):
private (bool): True to share by invitation only, private (bool): True to share by invitation only,
False to advertise as shared to everyone. False to advertise as shared to everyone.
Once the activity is shared, its privacy can be changed by setting Once the activity is shared, its privacy can be changed by
its 'private' property. setting the :attr:`private` property of the
:attr:`sugar3.presence.activity.Activity` class.
''' '''
if self.shared_activity and self.shared_activity.props.joined: if self.shared_activity and self.shared_activity.props.joined:
raise RuntimeError('Activity %s already shared.' % raise RuntimeError('Activity %s already shared.' %
@ -1097,8 +1150,14 @@ class Activity(Window, Gtk.Container):
def can_close(self): def can_close(self):
''' '''
Activities should override this function if they want to perform Return whether :func:`close` is permitted.
extra checks before actually closing.
An activity may override this function to code extra checks
before closing.
Returns:
bool: whether :func:`close` is permitted by activity,
default True.
''' '''
return True return True
@ -1229,14 +1288,18 @@ class Activity(Window, Gtk.Container):
def close(self, skip_save=False): def close(self, skip_save=False):
''' '''
Request that the activity be stopped and saved to the Journal Save to the journal and stop the activity.
Activities should not override this method, but should implement Activities should not override this method, but should
write_file() to do any state saving instead. If the application wants implement :meth:`write_file` to do any state saving
to control wether it can close, it should override can_close(). instead. If the activity wants to control wether it can close,
it should override :meth:`can_close`.
Args: Args:
skip_save (bool) skip_save (bool): avoid last-chance save; but does not prevent
a journal object, as an object is created when the activity
starts. Use this when an activity calls :meth:`save` just
prior to :meth:`close`.
''' '''
if not self.can_close(): if not self.can_close():
return return
@ -1267,9 +1330,11 @@ class Activity(Window, Gtk.Container):
def get_metadata(self): def get_metadata(self):
''' '''
Get the journal object metadata.
Returns: Returns:
dict: the jobject metadata or None if there is no jobject. dict: the journal object metadata, or None if there is no object.
Activities can set metadata in write_file() using: Activities can set metadata in write_file() using:
@ -1283,8 +1348,9 @@ class Activity(Window, Gtk.Container):
self.metadata.get('MyKey', 'aDefaultValue') self.metadata.get('MyKey', 'aDefaultValue')
Note: Make sure your activity works properly if one or more of the Make sure your activity works properly if one or more of the
metadata items is missing. Never assume they will all be present. metadata items is missing. Never assume they will all be
present.
''' '''
if self._jobject: if self._jobject:
return self._jobject.metadata return self._jobject.metadata
@ -1295,27 +1361,32 @@ class Activity(Window, Gtk.Container):
def handle_view_source(self): def handle_view_source(self):
''' '''
A developer can impleement this method to show aditional information An activity may override this method to show aditional
in the View Source window. Example implementations are available information in the View Source window. Examples can be seen in
on activities Browse or TurtleArt. Browse and TurtleArt.
Raises:
:exc:`NotImplementedError`
''' '''
raise NotImplementedError raise NotImplementedError
def get_document_path(self, async_cb, async_err_cb): def get_document_path(self, async_cb, async_err_cb):
'''
Not implemented.
'''
async_err_cb(NotImplementedError()) async_err_cb(NotImplementedError())
def busy(self): def busy(self):
''' '''
Show that the activity is busy. If used, must be called once Show that the activity is busy. If used, must be called once
before a lengthy operation, and unbusy must be called after before a lengthy operation, and :meth:`unbusy` must be called
the operation completes. after the operation completes.
.. code-block:: python .. code-block:: python
self.busy() self.busy()
self.long_operation() self.long_operation()
self.unbusy() self.unbusy()
''' '''
if self._busy_count == 0: if self._busy_count == 0:
self._old_cursor = self.get_window().get_cursor() self._old_cursor = self.get_window().get_cursor()
@ -1324,12 +1395,12 @@ class Activity(Window, Gtk.Container):
def unbusy(self): def unbusy(self):
''' '''
Returns:
int: a count of further calls to unbusy expected
Show that the activity is not busy. An equal number of calls Show that the activity is not busy. An equal number of calls
to unbusy are required to balance the calls to busy. to :meth:`unbusy` are required to balance the calls to
:meth:`busy`.
Returns:
int: a count of further calls to :meth:`unbusy` expected
''' '''
self._busy_count -= 1 self._busy_count -= 1
if self._busy_count == 0: if self._busy_count == 0:
@ -1434,6 +1505,12 @@ def get_activity_root():
def show_object_in_journal(object_id): def show_object_in_journal(object_id):
'''
Raise the journal activity and show a journal object.
Args:
object_id (object): journal object
'''
bus = dbus.SessionBus() bus = dbus.SessionBus()
obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH) obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH)
journal = dbus.Interface(obj, J_DBUS_INTERFACE) journal = dbus.Interface(obj, J_DBUS_INTERFACE)
@ -1441,6 +1518,13 @@ def show_object_in_journal(object_id):
def launch_bundle(bundle_id='', object_id=''): def launch_bundle(bundle_id='', object_id=''):
'''
Launch an activity for a journal object, or an activity.
Args:
bundle_id (str): activity bundle id, optional
object_id (object): journal object
'''
bus = dbus.SessionBus() bus = dbus.SessionBus()
obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH) obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH)
bundle_launcher = dbus.Interface(obj, J_DBUS_INTERFACE) bundle_launcher = dbus.Interface(obj, J_DBUS_INTERFACE)
@ -1448,6 +1532,13 @@ def launch_bundle(bundle_id='', object_id=''):
def get_bundle(bundle_id='', object_id=''): def get_bundle(bundle_id='', object_id=''):
'''
Get the bundle id of an activity that can open a journal object.
Args:
bundle_id (str): activity bundle id, optional
object_id (object): journal object
'''
bus = dbus.SessionBus() bus = dbus.SessionBus()
obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH) obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH)
journal = dbus.Interface(obj, J_DBUS_INTERFACE) journal = dbus.Interface(obj, J_DBUS_INTERFACE)

View File

@ -14,3 +14,100 @@
# License along with this library; if not, write to the # License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA. # Boston, MA 02111-1307, USA.
'''
Activity Metadata
=================
Your `activity/activity.info` file must have these metadata keys after
an `[Activity]` header:
* `name` - the name of the activity, shown by Sugar in the list of
installed activities, e.g. Browse,
* `activity_version` - the version of the activity, e.g. 1, 1.2,
1.2.3, 1.2.3-country, or 1.2.3~developer,
* `bundle_id` - the activity bundle identifier, using Java package
naming conventions, usually an organisation or individual domain
name in reverse order, e.g. `org.sugarlabs.Name`,
* `license` - an identifier for the software license of the bundle,
either a `Fedora License Short Name`_, (e.g. GPLv3+) or an `SPDX
License Identifier`_, with an optional `or later version` suffix,
e.g. `GPL-3.0+`,
* `icon` - the icon file for the activity, shown by Sugar in the list
of installed activities,
* `exec` - how to execute the activity, e.g. `sugar-activity module.Class`,
Optional metadata keys are;
* `mime_types` - list of MIME types supported by the activity,
separated by semicolons. Your `read_file` method must be able to read
files of these MIME types. Used to offer your activity when opening a
downloaded file or a journal object.
* `url` - link to the home page for the activity,
* `repository` - link to repository for activity code,
.. _SPDX License Identifier: http://spdx.org/licenses/
.. _Fedora License Short Name: https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses
AppStream Metadata
==================
AppStream is a standard, distribution independent package metadata.
For Sugar activities, the AppStream metadata is automatically exported
from the activity.info file by the bundlebuilder during the install
step.
In order to be compliant with AppStream, activities must have the
following metadata fields under the [Activity] header (of the
`activity.info` file):
* `metadata_license` - license for screenshots and description. AppStream
requests only using one of the following: `CC0-1.0`, `CC-BY-3.0`,
`CC-BY-SA-3.0` or `GFDL-1.3`
* `description` - a long (multi paragraph) description of your application.
This must be written in a subset of HTML. Only the p, ol, ul and li tags
are supported.
Optional metadata key:
* `screenshots` - a space separated list of screenshot URLs. PNG or JPEG files
are supported.
Example `activity.info`
-----------------------
.. code-block:: ini
:emphasize-lines: 10-12,20-21
[Activity]
name = Browse
bundle_id = org.laptop.WebActivity
exec = sugar-activity webactivity.WebActivity
activity_version = 200
icon = activity-web
max_participants = 100
summary = Surf the world!
license = GPL-3.0+
metadata_license = CC0-1.0
description:
<p>Surf the world! Here you can do research, watch educational videos, take online courses, find books, connect with friends and more. Browse is powered by the WebKit2 rendering engine with the Faster Than Light javascript interpreter - allowing you to view the full beauty of the web.</p>
<p>To help in researching, Browse offers many features:</p>
<ul>
<li>Bookmark (save) good pages you find - never loose good resources or forget to add them to your bibliography</li>
<li>Bookmark pages with collaborators in real time - great for researching as a group or teachers showing pages to their class</li>
<li>Comment on your bookmarked pages - a great tool for making curated collections</li>
</ul>
url = https://github.com/sugarlabs/browse-activity
screenshots = https://people.sugarlabs.org/sam/activity-ss/browse-1-1.png https://people.sugarlabs.org/sam/activity-ss/browse-1-2.png
'''

View File

@ -15,18 +15,40 @@
# Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA. # Boston, MA 02111-1307, USA.
#
# Based on the implementation of PEP 386, but adapted to our """
# numeration schema. Validation and normalization of bundle versions.
#
Instances of :class:`NormalizedVersion` can be directly compared;
>>> from sugar3.bundle.bundleversion import NormalizedVersion
>>> a = NormalizedVersion('157.3')
>>> b = NormalizedVersion('201.2')
>>> a > b
False
>>> b > a
True
Invalid versions will raise :exc:`InvalidVersionError`.
Valid versions are `1`, `1.2`, `1.2.3`, `1.2.3-peru`, and
`1.2.3~dfsg`.
Invalid versions are:
* `1.2peru` (because the suffix must be preceded with a dash or tilde),
* `1.2.` (because a version can't end with a period), or
* `1.02.5` (because a version can't have a leading zero).
Based on the implementation of :pep:`386`, but adapted to our
numeration schema.
Attributes:
VERSION_RE (RegexObject): regular expression for versions, deprecated, as it is insufficient by itself.
"""
import re import re
class InvalidVersionError(Exception):
"""The passed activity version can not be normalized."""
pass
VERSION_RE = re.compile(r''' VERSION_RE = re.compile(r'''
^ ^
(?P<version>\d+) # minimum 'N' (?P<version>\d+) # minimum 'N'
@ -37,30 +59,30 @@ VERSION_RE = re.compile(r'''
$''', re.VERBOSE) $''', re.VERBOSE)
class InvalidVersionError(Exception):
"""
A version cannot be normalized, because:
* the object is not a string,
* the string does not match the regular expression, or
* the string has a leading zero in a version part.
"""
pass
class NormalizedVersion(object): class NormalizedVersion(object):
"""A normalized version.
Good:
1
1.2
1.2.3
1.2.3-peru
1.2.3~dfsg
Bad:
1.2peru # must be separated with -
1.2. # can't end with '.'
1.02.5 # can't have a leading zero
""" """
Normalize a version string.
Args:
activity_version (str): the version string
Raises:
:exc:`InvalidVersionError`
Attributes:
parts (list): the numeric parts of the version after normalization.
"""
def __init__(self, activity_version): def __init__(self, activity_version):
"""Create a NormalizedVersion instance from a version string.
Keyword arguments:
activity_version -- The version string
"""
self._activity_version = activity_version self._activity_version = activity_version
self.parts = [] self.parts = []
self._local = None self._local = None

View File

@ -1,11 +1,16 @@
""" """
Alerts appear at the top of the body of your activity. Alerts appear in an activity below the toolbox and above the canvas.
At a high level, Alert and its different variations (TimeoutAlert, :class:`Alert` and the derived :class:`TimeoutAlert`,
ConfirmationAlert, etc.) have a title, an alert message and then several :class:`ConfirmationAlert`, :class:`ErrorAlert`, and
buttons that the user can click. The Alert class will pass "response" events :class:`NotifyAlert`, each have a title, a message and optional
to your activity when any of these buttons are clicked, along with a buttons.
response_id to help you identify what button was clicked.
:class:`Alert` will emit a `response` signal when a button is
clicked.
The :class:`TimeoutAlert` and :class:`NotifyAlert` display a countdown
and will emit a `response` signal when a timeout occurs.
Example: Example:
Create a simple alert message. Create a simple alert message.
@ -16,11 +21,12 @@ Example:
# Create a new simple alert # Create a new simple alert
alert = Alert() alert = Alert()
# Populate the title and text body of the alert.
# Set the title and text body of the alert
alert.props.title = _('Title of Alert Goes Here') alert.props.title = _('Title of Alert Goes Here')
alert.props.msg = _('Text message of alert goes here') alert.props.msg = _('Text message of alert goes here')
# Call the add_alert() method (inherited via the sugar3.graphics.Window
# superclass of Activity) to add this alert to the activity window. # Add the alert to the activity
self.add_alert(alert) self.add_alert(alert)
alert.show() alert.show()
@ -60,13 +66,17 @@ _ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg)
class Alert(Gtk.EventBox): class Alert(Gtk.EventBox):
""" """
UI interface for Alerts Alerts are inside the activity window instead of being a
separate popup window. They do not hide the canvas.
Alerts are used inside the activity window instead of being a Use :func:`~sugar3.graphics.window.Window.add_alert` and
separate popup window. They do not hide canvas content. You can :func:`~sugar3.graphics.window.Window.remove_alert` to add and
use `add_alert()` and `remove_alert()` inside your activity remove an alert. These methods are inherited by an
to add and remove the alert. The position of the alert is below the :class:`~sugar3.activity.activity.Activity` via superclass
toolbox or top in fullscreen mode. :class:`~sugar3.graphics.window.Window`.
The alert is placed between the canvas and the toolbox, or above
the canvas in fullscreen mode.
Args: Args:
title (str): the title of the alert title (str): the title of the alert
@ -159,10 +169,16 @@ class Alert(Gtk.EventBox):
def add_entry(self): def add_entry(self):
""" """
Add an entry, after the title and before the buttons. Create an entry and add it to the alert.
The entry is placed after the title and before the buttons.
Caller is responsible for capturing the entry text in the
`response` signal handler or a :class:`Gtk.Entry` signal
handler.
Returns: Returns:
Gtk.Entry: the entry added to the alert :class:`Gtk.Entry`: the entry added to the alert
""" """
entry = Gtk.Entry() entry = Gtk.Entry()
self._hbox.pack_start(entry, True, True, 0) self._hbox.pack_start(entry, True, True, 0)
@ -175,19 +191,26 @@ class Alert(Gtk.EventBox):
def add_button(self, response_id, label, icon=None, position=-1): def add_button(self, response_id, label, icon=None, position=-1):
""" """
Add a button to the alert Create a button and add it to the alert.
The button is added to the end of the alert.
When the button is clicked, the `response` signal will be
emitted, along with a response identifier.
Args: Args:
response_id (int): will be emitted with the response signal a response_id (int): the response identifier, a
response ID should one of the pre-defined GTK Response Type :class:`Gtk.ResponseType` constant or any positive
Constants or a positive number integer,
label (str): that will occure right to the buttom label (str): a label for the button
icon (:class:`sugar3.graphics.icon.Icon` or :class:`Gtk.Image`, optional): icon (:class:`~sugar3.graphics.icon.Icon` or \
icon for the button, placed before the text :class:`Gtk.Image`, optional):
postion (int, optional): the position of the button in the box an icon for the button
position (int, optional): the position of the button in
the box of buttons,
Returns: Returns:
Gtk.Button: the button added to the alert :class:`Gtk.Button`: the button added to the alert
""" """
button = Gtk.Button() button = Gtk.Button()
@ -204,10 +227,13 @@ class Alert(Gtk.EventBox):
def remove_button(self, response_id): def remove_button(self, response_id):
""" """
Remove a button from the alert by the given response id Remove a button from the alert.
The button is selected for removal using the response
identifier that was passed to :func:`add_button`.
Args: Args:
response_id (int): the same response id passed to add_button response_id (int): the response identifier
Returns: Returns:
None None
@ -233,23 +259,22 @@ if hasattr(Alert, 'set_css_name'):
class ConfirmationAlert(Alert): class ConfirmationAlert(Alert):
""" """
This is a ready-made two button (Cancel, Ok) alert. An alert with two buttons; Ok and Cancel.
A confirmation alert is a nice shortcut from a standard Alert because it When a button is clicked, the :class:`ConfirmationAlert` will emit
comes with 'OK' and 'Cancel' buttons already built-in. When clicked, the a `response` signal with a response identifier. For the Ok
'OK' button will emit a response with a response_id of button, the response identifier will be
:class:`Gtk.ResponseType.OK`, while the 'Cancel' button will emit :class:`Gtk.ResponseType.OK`. For the Cancel button,
:class:`Gtk.ResponseType.CANCEL`. :class:`Gtk.ResponseType.CANCEL`.
Args: Args:
**kwargs: options for :class:`sugar3.graphics.alert.Alert` **kwargs: parameters for :class:`~sugar3.graphics.alert.Alert`
.. code-block:: python .. code-block:: python
from sugar3.graphics.alert import ConfirmationAlert from sugar3.graphics.alert import ConfirmationAlert
# Create a Confirmation alert (with ok and cancel buttons standard) # Create a Confirmation alert and add it to the UI.
# then add it to the UI.
def _alert_confirmation(self): def _alert_confirmation(self):
alert = ConfirmationAlert() alert = ConfirmationAlert()
alert.props.title=_('Title of Alert Goes Here') alert.props.title=_('Title of Alert Goes Here')
@ -257,15 +282,14 @@ class ConfirmationAlert(Alert):
alert.connect('response', self._alert_response_cb) alert.connect('response', self._alert_response_cb)
self.add_alert(alert) self.add_alert(alert)
# Called when an alert object throws a response event. # Called when an alert object sends a response signal.
def _alert_response_cb(self, alert, response_id): def _alert_response_cb(self, alert, response_id):
# Remove the alert from the screen, since either a response button # Remove the alert
# was clicked or there was a timeout
self.remove_alert(alert) self.remove_alert(alert)
# Do any work that is specific to the type of button clicked. # Check the response identifier.
if response_id is Gtk.ResponseType.OK: if response_id is Gtk.ResponseType.OK:
print 'Ok Button was clicked. Do any work upon ok here ...' print 'Ok Button was clicked.'
elif response_id is Gtk.ResponseType.CANCEL: elif response_id is Gtk.ResponseType.CANCEL:
print 'Cancel Button was clicked.' print 'Cancel Button was clicked.'
""" """
@ -284,22 +308,20 @@ class ConfirmationAlert(Alert):
class ErrorAlert(Alert): class ErrorAlert(Alert):
""" """
This is a ready-made one button (Ok) alert. An alert with one button; Ok.
An error alert is a nice shortcut from a standard Alert because it When the button is clicked, the :class:`ErrorAlert` will
comes with the 'OK' button already built-in. When clicked, the emit a `response` signal with a response identifier
'OK' button will emit a response with a response_id of
:class:`Gtk.ResponseType.OK`. :class:`Gtk.ResponseType.OK`.
Args: Args:
**kwargs: options for :class:`sugar3.graphics.alert.Alert` **kwargs: parameters for :class:`~sugar3.graphics.alert.Alert`
.. code-block:: python .. code-block:: python
from sugar3.graphics.alert import ErrorAlert from sugar3.graphics.alert import ErrorAlert
# Create a Error alert (with ok button standard) # Create a Error alert and add it to the UI.
# and add it to the UI.
def _alert_error(self): def _alert_error(self):
alert = ErrorAlert() alert = ErrorAlert()
alert.props.title=_('Title of Alert Goes Here') alert.props.title=_('Title of Alert Goes Here')
@ -309,13 +331,12 @@ class ErrorAlert(Alert):
# called when an alert object throws a response event. # called when an alert object throws a response event.
def _alert_response_cb(self, alert, response_id): def _alert_response_cb(self, alert, response_id):
# Remove the alert from the screen, since either a response button # Remove the alert
# was clicked or there was a timeout
self.remove_alert(alert) self.remove_alert(alert)
# Do any work that is specific to the response_id. # Check the response identifier.
if response_id is Gtk.ResponseType.OK: if response_id is Gtk.ResponseType.OK:
print 'Ok Button was clicked. Do any work upon ok here' print 'Ok Button was clicked.'
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -396,40 +417,43 @@ class _TimeoutAlert(Alert):
class TimeoutAlert(_TimeoutAlert): class TimeoutAlert(_TimeoutAlert):
""" """
This is a ready-made two button (Continue, Cancel) alert. The continue A timed alert with two buttons; Continue and Cancel. The Continue
button contains a visual countdown indicating the time remaining to the button contains a countdown of seconds remaining.
user. If the user does not select a button before the timeout, the
response callback is called and the alert is usually removed. When a button is clicked, the :class:`TimeoutAlert` will emit
a `response` signal with a response identifier. For the Continue
button, the response identifier will be
:class:`Gtk.ResponseType.OK`. For the Cancel button,
:class:`Gtk.ResponseType.CANCEL`.
If the countdown reaches zero before a button is clicked, the
:class:`TimeoutAlert` will emit a `response` signal with a
response identifier of -1.
Args: Args:
timeout (int, optional): the length in seconds for the timeout to timeout (int, optional): time in seconds, default 5
last, defaults to 5 seconds **kwargs: parameters for :class:`~sugar3.graphics.alert.Alert`
**kwargs: options for :class:`sugar3.graphics.alert.Alert`
.. code-block:: python .. code-block:: python
from sugar3.graphics.alert import TimeoutAlert from sugar3.graphics.alert import TimeoutAlert
# Create a Timeout alert (with ok and cancel buttons standard) then # Create a Timeout alert and add it to the UI
# add it to the UI
def _alert_timeout(self): def _alert_timeout(self):
# Notice that for a TimeoutAlert, you pass the number of seconds alert = TimeoutAlert(timeout=10)
# in which to timeout. By default, this is 5.
alert = TimeoutAlert(10)
alert.props.title = _('Title of Alert Goes Here') alert.props.title = _('Title of Alert Goes Here')
alert.props.msg = _('Text message of timeout alert goes here') alert.props.msg = _('Text message of alert goes here')
alert.connect('response', self.__alert_response_cb) alert.connect('response', self.__alert_response_cb)
self.add_alert(alert) self.add_alert(alert)
# Called when an alert object throws a response event. # Called when an alert object throws a response event.
def __alert_response_cb(self, alert, response_id): def __alert_response_cb(self, alert, response_id):
# Remove the alert from the screen, since either a response button # Remove the alert
# was clicked or there was a timeout
self.remove_alert(alert) self.remove_alert(alert)
# Do any work that is specific to the type of button clicked. # Check the response identifier.
if response_id is Gtk.ResponseType.OK: if response_id is Gtk.ResponseType.OK:
print 'Ok Button was clicked. Do any work upon ok here ...' print 'Continue Button was clicked.'
elif response_id is Gtk.ResponseType.CANCEL: elif response_id is Gtk.ResponseType.CANCEL:
print 'Cancel Button was clicked.' print 'Cancel Button was clicked.'
elif response_id == -1: elif response_id == -1:
@ -446,32 +470,42 @@ class TimeoutAlert(_TimeoutAlert):
class NotifyAlert(_TimeoutAlert): class NotifyAlert(_TimeoutAlert):
""" """
Timeout alert with only an "OK" button. This should be used just for A timed alert with one button; Ok. The button contains a
notifications and not for user interaction. The alert will timeout after countdown of seconds remaining.
a given length, similar to a :class:`sugar3.graphics.alert.TimeoutAlert`.
When the button is clicked, the :class:`NotifyAlert` will
emit a `response` signal with a response identifier
:class:`Gtk.ResponseType.OK`.
If the countdown reaches zero before the button is clicked, the
:class:`NotifyAlert` will emit a `response` signal with a
response identifier of -1.
Args: Args:
timeout (int, optional): the length in seconds for the timeout to timeout (int, optional): time in seconds, default 5
last, defaults to 5 seconds **kwargs: parameters for :class:`~sugar3.graphics.alert.Alert`
**kwargs: options for :class:`sugar3.graphics.alert.Alert`
.. code-block:: python .. code-block:: python
from sugar3.graphics.alert import NotifyAlert from sugar3.graphics.alert import NotifyAlert
# create a Notify alert (with only an 'OK' button) then show it # create a Notify alert then show it
def _alert_notify(self): def _alert_notify(self):
alert = NotifyAlert() alert = NotifyAlert()
alert.props.title = _('Title of Alert Goes Here') alert.props.title = _('Title of Alert Goes Here')
alert.props.msg = _('Text message of notify alert goes here') alert.props.msg = _('Text message of alert goes here')
alert.connect('response', self._alert_response_cb) alert.connect('response', self._alert_response_cb)
self.add_alert(alert) self.add_alert(alert)
def __alert_response_cb(self, alert, response_id): def __alert_response_cb(self, alert, response_id):
# Hide the alert from the user # Remove the alert
self.remove_alert(alert) self.remove_alert(alert)
assert response_id == Gtk.ResponseType.OK # Check the response identifier.
if response_id is Gtk.ResponseType.OK:
print 'Ok Button was clicked.'
elif response_id == -1:
print 'Timeout occurred'
""" """
def __init__(self, timeout=5, **kwargs): def __init__(self, timeout=5, **kwargs):

View File

@ -41,8 +41,8 @@ class UnfullscreenButton(Gtk.Window):
""" """
A ready-made "Unfullscreen" button. A ready-made "Unfullscreen" button.
The type of button used by :class:`sugar3.graphics.window.Window` to exit Used by :class:`~sugar3.graphics.window.Window` to exit fullscreen
fullscreen mode when in fullscreen mode. mode.
""" """
def __init__(self): def __init__(self):
@ -94,13 +94,28 @@ class UnfullscreenButton(Gtk.Window):
class Window(Gtk.Window): class Window(Gtk.Window):
""" """
UI interface for activity Windows An activity window.
Used as a container to display things that happen in an activity.
A window must contain a canvas widget, and a toolbar box widget.
A window may also contain alert message widgets and a tray widget.
Widgets are kept in a vertical box in this order;
* toolbar box,
* alerts,
* canvas,
* tray.
A window may be in fullscreen or non-fullscreen mode. In fullscreen
mode, the toolbar and tray are hidden.
Motion events are tracked, and an unfullscreen button is shown when
the mouse is moved into the top right corner of the canvas.
Key press events are tracked;
* :kbd:`escape` will cancel fullscreen mode,
* :kbd:`Alt+space` will toggle tray visibility.
Windows are used as a container to display things that happen in an
activity. They contain canvas content, alerts messages, a tray and a
toolbar. Windows can either be in fullscreen or non-fullscreen mode.
Note that the toolbar is hidden in fullscreen mode, while it is visible in
non-fullscreen mode.
""" """
def __init__(self, **args): def __init__(self, **args):
@ -152,9 +167,9 @@ class Window(Gtk.Window):
""" """
Make window active. Make window active.
Brings the window to the top and makes it acive, even after invoking on Brings the window to the top and makes it active, even after
response to non-gtk events (in contrast to present()). invoking on response to non-GTK events (in contrast to
See bug #1423 present()). See bug #1423
""" """
window = self.get_window() window = self.get_window()
if window is None: if window is None:
@ -167,16 +182,18 @@ class Window(Gtk.Window):
def is_fullscreen(self): def is_fullscreen(self):
""" """
Check whether the window is fullscreen or not. Check if the window is fullscreen.
Returns: Returns:
bool: true if window is fullscreen, false otherwise bool: window is fullscreen
""" """
return self._is_fullscreen return self._is_fullscreen
def fullscreen(self): def fullscreen(self):
""" """
Make the window fullscreen and hide the toolbar. Make the window fullscreen. The toolbar and tray will be
hidden, and the :class:`UnfullscreenButton` will be shown for
a short time.
""" """
palettegroup.popdown_all() palettegroup.popdown_all()
if self._toolbar_box is not None: if self._toolbar_box is not None:
@ -200,7 +217,9 @@ class Window(Gtk.Window):
def unfullscreen(self): def unfullscreen(self):
""" """
Put the window in non-fullscreen mode and make the toolbar visible. Restore the window to non-fullscreen mode. The
:class:`UnfullscreenButton` will be hidden, and the toolbar
and tray will be shown.
""" """
if self._toolbar_box is not None: if self._toolbar_box is not None:
self._toolbar_box.show() self._toolbar_box.show()
@ -218,10 +237,10 @@ class Window(Gtk.Window):
def set_canvas(self, canvas): def set_canvas(self, canvas):
""" """
Set current canvas of the window. Set canvas widget.
Args: Args:
canvas (:class:`Gtk.Widget`): the canvas to set as current canvas (:class:`Gtk.Widget`): the canvas to set
""" """
if self._canvas: if self._canvas:
self.__hbox.remove(self._canvas) self.__hbox.remove(self._canvas)
@ -234,30 +253,36 @@ class Window(Gtk.Window):
def get_canvas(self): def get_canvas(self):
""" """
Get current canvas content of the window. Get canvas widget.
Returns: Returns:
Gtk.Widget: the current canvas of the window :class:`Gtk.Widget`: the canvas
""" """
return self._canvas return self._canvas
canvas = property(get_canvas, set_canvas) canvas = property(get_canvas, set_canvas)
"""
Property: the :class:`Gtk.Widget` to be shown as the canvas, below
the toolbar and alerts, and above the tray.
"""
def get_toolbar_box(self): def get_toolbar_box(self):
""" """
Return window's current toolbar. Get :class:`~sugar3.graphics.toolbarbox.ToolbarBox` widget.
Returns: Returns:
sugar3.graphics.toolbar_box.ToolbarBox: the current toolbar box of the window :class:`~sugar3.graphics.toolbarbox.ToolbarBox`: the
current toolbar box of the window
""" """
return self._toolbar_box return self._toolbar_box
def set_toolbar_box(self, toolbar_box): def set_toolbar_box(self, toolbar_box):
""" """
Set window's current toolbar. Set :class:`~sugar3.graphics.toolbarbox.ToolbarBox` widget.
Args: Args:
toolbar_box (:class:`sugar3.graphics.toolbarbox.ToolbarBox`): the toolbar box to set as current toolbar_box (:class:`~sugar3.graphics.toolbarbox.ToolbarBox`):
the toolbar box to set as current
""" """
if self._toolbar_box: if self._toolbar_box:
self.__vbox.remove(self._toolbar_box) self.__vbox.remove(self._toolbar_box)
@ -269,14 +294,19 @@ class Window(Gtk.Window):
self._toolbar_box = toolbar_box self._toolbar_box = toolbar_box
toolbar_box = property(get_toolbar_box, set_toolbar_box) toolbar_box = property(get_toolbar_box, set_toolbar_box)
"""
Property: the :class:`~sugar3.graphics.toolbarbox.ToolbarBox` to
be shown above the alerts and canvas.
"""
def set_tray(self, tray, position): def set_tray(self, tray, position):
""" """
Set the window's current tray. Set the tray.
Args: Args:
tray (:class:`sugar3.graphics.tray.HTray` or :class:`sugar3.graphics.tray.VTray`): the tray to set tray (:class:`~sugar3.graphics.tray.HTray` \
position (Gtk.PositionType constant): the edge to set the tray at or :class:`~sugar3.graphics.tray.VTray`): the tray to set
position (:class:`Gtk.PositionType`): the edge to set the tray at
""" """
if self.tray: if self.tray:
box = self.tray.get_parent() box = self.tray.get_parent()
@ -293,12 +323,14 @@ class Window(Gtk.Window):
def add_alert(self, alert): def add_alert(self, alert):
""" """
Add an alert message to the window. Add an alert to the window.
Add an alert to the window. Note that you do need to .show the alert
You must call :class:`Gtk.Widget`. :func:`show` on the alert
to make it visible. to make it visible.
Args: Args:
alert (:class:`sugar3.graphics.alert.Alert`): the alert to add alert (:class:`~sugar3.graphics.alert.Alert`): the alert
to add
""" """
self._alerts.append(alert) self._alerts.append(alert)
if len(self._alerts) == 1: if len(self._alerts) == 1:
@ -313,7 +345,8 @@ class Window(Gtk.Window):
Remove an alert message from the window. Remove an alert message from the window.
Args: Args:
alert (:class:`sugar3.graphics.alert.Alert`): the alert to remove alert (:class:`~sugar3.graphics.alert.Alert`): the alert
to remove
""" """
if alert in self._alerts: if alert in self._alerts:
self._alerts.remove(alert) self._alerts.remove(alert)
@ -397,19 +430,19 @@ class Window(Gtk.Window):
def set_enable_fullscreen_mode(self, enable_fullscreen_mode): def set_enable_fullscreen_mode(self, enable_fullscreen_mode):
""" """
Set whether the window is allowed to enter fullscreen mode. Set enable fullscreen mode.
Args: Args:
enable_fullscreen_mode (bool): the boolean to set `_enable_fullscreen_mode` to enable_fullscreen_mode (bool): enable fullscreen mode
""" """
self._enable_fullscreen_mode = enable_fullscreen_mode self._enable_fullscreen_mode = enable_fullscreen_mode
def get_enable_fullscreen_mode(self): def get_enable_fullscreen_mode(self):
""" """
Return whether the window is allowed to enter fullscreen mode. Get enable fullscreen mode.
Returns: Returns:
bool: true if window is allowed to be fullscreen, false otherwise bool: enable fullscreen mode
""" """
return self._enable_fullscreen_mode return self._enable_fullscreen_mode
@ -417,3 +450,7 @@ class Window(Gtk.Window):
type=object, type=object,
setter=set_enable_fullscreen_mode, setter=set_enable_fullscreen_mode,
getter=get_enable_fullscreen_mode) getter=get_enable_fullscreen_mode)
"""
Property: (bool) whether the window is allowed to enter fullscreen
mode, default True.
"""