Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar

This commit is contained in:
Dan Williams 2007-02-22 14:08:00 -05:00
commit 3b863f45a8
41 changed files with 899 additions and 980 deletions

View File

@ -17,9 +17,11 @@
* Boston, MA 02111-1307, USA.
*/
#include <math.h>
#include <stdlib.h>
#include <gdk/gdkx.h>
#include "sugar-utils.h"
gint
sugar_get_screen_dpi(void)
{
@ -27,8 +29,8 @@ sugar_get_screen_dpi(void)
if (val) {
char *e;
double d = strtod(val, &e);
if (e != val)
return round(d);
if (d > 0.0)
return (int)(d+0.5);
}
return 96;

View File

@ -29,7 +29,7 @@ class TextFileType(FileType):
return _('Text snippet')
def get_icon(self):
return 'theme:activity-xbook'
return 'theme:object-text'
def get_preview(self):
for format, data in self._formats.iteritems():
@ -57,7 +57,7 @@ class ImageFileType(FileType):
return _('Image')
def get_icon(self):
return 'theme:activity-sketch'
return 'theme:object-image'
def get_preview(self):
return ''
@ -77,7 +77,7 @@ class UriFileType(FileType):
return _('Web Page')
def get_icon(self):
return 'theme:activity-web'
return 'theme:object-link'
def get_preview(self):
for format, data in self._formats.iteritems():
@ -103,7 +103,7 @@ class PdfFileType(FileType):
return _('PDF file')
def get_icon(self):
return 'theme:activity-xbook'
return 'theme:object-text'
def get_preview(self):
return ''
@ -123,7 +123,7 @@ class MsWordFileType(FileType):
return _('MS Word file')
def get_icon(self):
return 'theme:activity-abiword'
return 'theme:object-text'
def get_preview(self):
return ''
@ -143,7 +143,7 @@ class RtfFileType(FileType):
return _('RTF file')
def get_icon(self):
return 'theme:activity-abiword'
return 'theme:object-text'
def get_preview(self):
return ''
@ -163,7 +163,7 @@ class OOTextFileType(FileType):
return _('OpenOffice text file')
def get_icon(self):
return 'theme:activity-abiword'
return 'theme:object-text'
def get_preview(self):
return ''

View File

@ -18,6 +18,7 @@ import gobject
from sugar.graphics.iconcolor import IconColor
from sugar.presence import PresenceService
from sugar.activity import bundleregistry
from model.BuddyModel import BuddyModel
class ActivityModel:
@ -53,12 +54,12 @@ class MeshModel(gobject.GObject):
gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT]))
}
def __init__(self, bundle_registry):
def __init__(self):
gobject.GObject.__init__(self)
self._activities = {}
self._buddies = {}
self._bundle_registry = bundle_registry
self._bundle_registry = bundleregistry.get_registry()
self._pservice = PresenceService.get_instance()
self._pservice.connect("service-appeared",

View File

@ -44,8 +44,6 @@ class ShellModel(gobject.GObject):
self._current_activity = None
self._state = self.STATE_RUNNING
self._bundle_registry = BundleRegistry()
PresenceService.start()
self._pservice = PresenceService.get_instance()
@ -53,16 +51,10 @@ class ShellModel(gobject.GObject):
self._owner.announce()
self._friends = Friends()
self._mesh = MeshModel(self._bundle_registry)
self._home = HomeModel(self._bundle_registry)
self._mesh = MeshModel()
self._home = HomeModel()
self._devices = DevicesModel()
for path in env.get_data_dirs():
bundles_path = os.path.join(path, 'activities')
self._bundle_registry.add_search_path(bundles_path)
self._bundle_registry.add_search_path(env.get_user_activities_dir())
def do_set_property(self, pspec, value):
if pspec.name == 'state':
self._state = value
@ -71,9 +63,6 @@ class ShellModel(gobject.GObject):
if pspec.name == 'state':
return self._state
def get_bundle_registry(self):
return self._bundle_registry
def get_mesh(self):
return self._mesh

View File

@ -15,14 +15,18 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import time
import gobject
import logging
import gobject
import dbus
from sugar.graphics.iconcolor import IconColor
from sugar.presence import PresenceService
from sugar.activity import Activity
from sugar import profile
_ACTIVITY_SERVICE_NAME = "org.laptop.Activity"
_ACTIVITY_SERVICE_PATH = "/org/laptop/Activity"
class HomeActivity(gobject.GObject):
__gsignals__ = {
'launch-timeout': (gobject.SIGNAL_RUN_FIRST,
@ -69,16 +73,22 @@ class HomeActivity(gobject.GObject):
self._window = window
self._xid = window.get_xid()
self._service = Activity.get_service(window.get_xid())
bus = dbus.SessionBus()
self._service = bus.get_object(_ACTIVITY_SERVICE_NAME + '%d' % self._xid,
_ACTIVITY_SERVICE_PATH + "/%s" % self._xid)
# verify id and type details
act_id = self._service.get_id()
if act_id != self._id:
raise RuntimeError("Activity's real ID (%s) didn't match expected (%s)." % (act_id, self._id))
act_type = self._service.get_type()
act_type = self._service.get_service_name()
if act_type != self._type:
raise RuntimeError("Activity's real type (%s) didn't match expected (%s)." % (act_type, self._type))
def get_service(self):
return self._service
def get_title(self):
if not self._launched:
raise RuntimeError("Activity is still launching.")

View File

@ -18,9 +18,13 @@ import logging
import gobject
import wnck
import dbus
from model.homeactivity import HomeActivity
from sugar.activity import Activity
from sugar.activity import bundleregistry
_ACTIVITY_SERVICE_NAME = "org.laptop.Activity"
_ACTIVITY_SERVICE_PATH = "/org/laptop/Activity"
class HomeModel(gobject.GObject):
@ -39,11 +43,11 @@ class HomeModel(gobject.GObject):
([gobject.TYPE_PYOBJECT]))
}
def __init__(self, bundle_registry):
def __init__(self):
gobject.GObject.__init__(self)
self._activities = {}
self._bundle_registry = bundle_registry
self._bundle_registry = bundleregistry.get_registry()
self._current_activity = None
screen = wnck.screen_get_default()
@ -114,7 +118,10 @@ class HomeModel(gobject.GObject):
self.emit('active-activity-changed', self._current_activity)
def _add_activity(self, window):
act_service = Activity.get_service(window.get_xid())
bus = dbus.SessionBus()
xid = window.get_xid()
act_service = bus.get_object(_ACTIVITY_SERVICE_NAME + '%d' % xid,
_ACTIVITY_SERVICE_PATH + "/%s" % xid)
act_id = act_service.get_id()
activity = None
@ -123,7 +130,7 @@ class HomeModel(gobject.GObject):
else:
# activity got lost, took longer to launch than we allow,
# or it was launched by something other than the shell
act_type = act_service.get_type()
act_type = act_service.get_service_name()
bundle = self._bundle_registry.get_bundle(act_type)
if not bundle:
raise RuntimeError("No bundle for activity type '%s'." % act_type)

View File

@ -20,7 +20,7 @@ import sys
import os
import gobject
from sugar.activity import ActivityFactory
from sugar.activity import activityfactory
from sugar import env
from sugar import util
@ -40,7 +40,7 @@ os.environ['DBUS_SESSION_BUS_ADDRESS'] = bus_name
loop = gobject.MainLoop()
handler = ActivityFactory.create(sys.argv[1])
handler = activityfactory.create(sys.argv[1])
handler.connect('success', _success_cb, loop)
handler.connect('error', _error_cb, loop)

View File

@ -18,21 +18,6 @@
import sys
import pygtk
pygtk.require('2.0')
import gobject
import gtk
from sugar.activity import activityfactoryservice
import dbus.glib
# Work around for dbus mutex locking issue
gobject.threads_init()
dbus.glib.threads_init()
from sugar.activity import ActivityFactory
sys.path.insert(0, sys.argv[2])
ActivityFactory.start_factory(sys.argv[1], sys.argv[2])
gtk.main()
activityfactoryservice.run(sys.argv)

View File

@ -17,7 +17,6 @@
import gtk
import dbus
from sugar.activity import Activity
from sugar.p2p import Stream
from sugar.p2p import network
from sugar.chat import ActivityChat
@ -42,7 +41,7 @@ class ActivityHost:
self._model = model
self._id = model.get_id()
self._window = model.get_window()
self._activity = Activity.get_service(self.get_xid())
self._activity = model.get_service()
self._gdk_window = gtk.gdk.window_foreign_new(self.get_xid())
try:

View File

@ -14,12 +14,12 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from sugar.graphics.menuicon import MenuIcon
from sugar.graphics.canvasicon import CanvasIcon
from view.BuddyMenu import BuddyMenu
class BuddyIcon(MenuIcon):
class BuddyIcon(CanvasIcon):
def __init__(self, shell, menu_shell, buddy):
MenuIcon.__init__(self, menu_shell, icon_name='theme:stock-buddy',
CanvasIcon.__init__(self, icon_name='theme:stock-buddy',
color=buddy.get_color())
self._shell = shell
@ -35,13 +35,16 @@ class BuddyIcon(MenuIcon):
def set_popup_distance(self, distance):
self._popup_distance = distance
def create_menu(self):
def get_popup(self):
menu = BuddyMenu(self._shell, self._buddy)
menu.connect('action', self._popup_action_cb)
return menu
def _popup_action_cb(self, popup, action):
self.popdown()
def get_popup_context(self):
return self._shell.get_popup_context()
def _popup_action_cb(self, popup, menu_item):
action = menu_item.props.action_id
friends = self._shell.get_model().get_friends()
if action == BuddyMenu.ACTION_REMOVE_FRIEND:

View File

@ -13,18 +13,18 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from gettext import gettext as _
import gtk
import gobject
import hippo
from sugar.graphics.menu import Menu
from sugar.graphics.menu import Menu, MenuItem
from sugar.graphics.canvasicon import CanvasIcon
from sugar.graphics import units
from sugar.presence import PresenceService
import _sugar
_ICON_SIZE = 75
class BuddyMenu(Menu):
ACTION_MAKE_FRIEND = 0
ACTION_INVITE = 1
@ -34,25 +34,26 @@ class BuddyMenu(Menu):
self._buddy = buddy
self._shell = shell
Menu.__init__(self, buddy.get_name())
pixbuf = self._get_buddy_icon_pixbuf()
if pixbuf:
icon_item = hippo.CanvasImage()
scaled_pixbuf = pixbuf.scale_simple(_ICON_SIZE, _ICON_SIZE,
scaled_pixbuf = pixbuf.scale_simple(units.grid_to_pixels(1),
units.grid_to_pixels(1),
gtk.gdk.INTERP_BILINEAR)
del pixbuf
Menu.__init__(self, buddy.get_name(), icon_item)
self.add_separator()
self.append(icon_item)
# FIXME: have to set the image _after_ adding the HippoCanvasImage
# to it's parent item, because that sets the HippoCanvasImage's context,
# which resets the object's 'image' property. Grr.
_sugar.hippo_canvas_image_set_image_from_gdk_pixbuf(icon_item, scaled_pixbuf)
else:
Menu.__init__(self, buddy.get_name(), None)
self._buddy.connect('icon-changed', self.__buddy_icon_changed_cb)
owner = shell.get_model().get_owner()
if buddy.get_name() != owner.get_name():
self._add_actions()
self._add_items()
def _get_buddy_icon_pixbuf(self):
buddy_object = self._buddy.get_buddy()
@ -76,17 +77,19 @@ class BuddyMenu(Menu):
del pbl
return pixbuf
def _add_actions(self):
def _add_items(self):
shell_model = self._shell.get_model()
pservice = PresenceService.get_instance()
friends = shell_model.get_friends()
if friends.has_buddy(self._buddy):
icon = CanvasIcon(icon_name='theme:stock-remove')
self.add_action(icon, BuddyMenu.ACTION_REMOVE_FRIEND)
self.add_item(MenuItem(BuddyMenu.ACTION_REMOVE_FRIEND,
_('Remove friend'),
'theme:stock-remove'))
else:
icon = CanvasIcon(icon_name='theme:stock-add')
self.add_action(icon, BuddyMenu.ACTION_MAKE_FRIEND)
self.add_item(MenuItem(BuddyMenu.ACTION_MAKE_FRIEND,
_('Make friend'),
'theme:stock-add'))
activity = shell_model.get_home().get_current_activity()
if activity != None:
@ -94,9 +97,9 @@ class BuddyMenu(Menu):
# FIXME check that the buddy is not in the activity already
icon = CanvasIcon(icon_name='theme:stock-invite')
self.add_action(icon, BuddyMenu.ACTION_INVITE)
self.add_item(MenuItem(BuddyMenu.ACTION_INVITE,
_('Invite'),
'theme:stock-invite'))
def __buddy_icon_changed_cb(self, buddy):
pass

View File

@ -20,10 +20,10 @@ import gobject
import wnck
from view.home.HomeWindow import HomeWindow
from sugar.presence import PresenceService
from sugar.activity.activityhandle import ActivityHandle
from sugar.graphics.popupcontext import PopupContext
from view.ActivityHost import ActivityHost
from sugar.activity import ActivityFactory
from sugar.activity import activityfactory
from view.frame.frame import Frame
from view.keyhandler import KeyHandler
from view.hardwaremanager import HardwareManager
@ -60,8 +60,6 @@ class Shell(gobject.GObject):
self._frame = Frame(self)
self._frame.show_and_hide(3)
self._pservice = PresenceService.get_instance()
self.start_activity('org.laptop.JournalActivity')
def _activity_added_cb(self, home_model, home_activity):
@ -105,13 +103,8 @@ class Shell(gobject.GObject):
def get_popup_context(self):
return self._popup_context
def _join_success_cb(self, handler, activity, activity_ps, activity_id, activity_type):
logging.debug("Joining activity %s (%s)" % (activity_id, activity_type))
activity.join(activity_ps.object_path())
def _join_error_cb(self, handler, err, home_model, activity_id, activity_type):
logging.error("Couldn't launch activity %s (%s):\n%s" % (activity_id, activity_type, err))
home_mode.notify_activity_launch_failed(activity_id)
def _join_error_cb(self, handler, err, home_model):
home_mode.notify_activity_launch_failed(handler.get_activity_id())
def join_activity(self, bundle_id, activity_id):
activity = self.get_activity(activity_id)
@ -119,11 +112,6 @@ class Shell(gobject.GObject):
activity.present()
return
activity_ps = self._pservice.get_activity(activity_id)
if not activity_ps:
logging.error("Couldn't find shared activity for %s" % activity_id)
return
# Get the service name for this activity, if
# we have a bundle on the system capable of handling
# this activity type
@ -137,62 +125,25 @@ class Shell(gobject.GObject):
home_model = self._model.get_home()
home_model.notify_activity_launch(activity_id, act_type)
handler = ActivityFactory.create(act_type)
handler.connect('success', self._join_success_cb, activity_ps, activity_id, act_type)
handler.connect('error', self._join_error_cb, home_model, activity_id, act_type)
handle = ActivityHandle(activity_id)
handle.pservice_id = activity_id
def _find_unique_activity_id(self):
# create a new unique activity ID
i = 0
act_id = None
while i < 10:
act_id = sugar.util.unique_id()
i += 1
handler = activityfactory.create(act_type, handle)
handler.connect('error', self._join_error_cb, home_model)
# check through existing activities
found = False
for xid, act_host in self._hosts.items():
if act_host.get_id() == act_id:
found = True
break
if found:
act_id = None
continue
# check through network activities
activities = self._pservice.get_activities()
for act in activities:
if act_id == act.get_id():
found = True
break
if found:
act_id = None
continue
return act_id
def _start_success_cb(self, handler, activity, activity_id, activity_type):
logging.debug("Started activity %s (%s)" % (activity_id, activity_type))
activity.start(activity_id)
def _start_error_cb(self, handler, err, home_model, activity_id, activity_type):
logging.error("Couldn't launch activity %s (%s):\n%s" % (activity_id, activity_type, err))
home_model.notify_activity_launch_failed(activity_id)
def _start_error_cb(self, handler, err, home_model):
home_model.notify_activity_launch_failed(handler.get_activity_id())
def start_activity(self, activity_type):
logging.debug('Shell.start_activity')
act_id = self._find_unique_activity_id()
if not act_id:
logging.error("Couldn't find available activity ID.")
return None
handler = activityfactory.create(activity_type)
home_model = self._model.get_home()
home_model.notify_activity_launch(act_id, activity_type)
home_model.notify_activity_launch(handler.get_activity_id(),
activity_type)
logging.debug("Shell.start_activity will start %s (%s)" % (act_id, activity_type))
handler = ActivityFactory.create(activity_type)
handler.connect('success', self._start_success_cb, act_id, activity_type)
handler.connect('error', self._start_error_cb, home_model, act_id, activity_type)
handler.connect('error', self._start_error_cb, home_model)
# Zoom to Home for launch feedback
self.set_zoom_level(sugar.ZOOM_HOME)

View File

@ -1,16 +1,17 @@
import logging
from sugar.graphics.menuicon import MenuIcon
from sugar.graphics.canvasicon import CanvasIcon
from view.clipboardmenu import ClipboardMenu
from sugar.graphics.iconcolor import IconColor
from sugar.activity import ActivityFactory
from sugar.activity import activityfactory
from sugar.clipboard import clipboardservice
from sugar import util
class ClipboardIcon(MenuIcon):
class ClipboardIcon(CanvasIcon):
def __init__(self, menu_shell, object_id, name):
MenuIcon.__init__(self, menu_shell)
def __init__(self, popup_context, object_id, name):
CanvasIcon.__init__(self)
self._popup_context = popup_context
self._object_id = object_id
self._name = name
self._percent = 0
@ -19,12 +20,15 @@ class ClipboardIcon(MenuIcon):
self.connect('activated', self._icon_activated_cb)
self._menu = None
def create_menu(self):
def get_popup(self):
self._menu = ClipboardMenu(self._name, self._percent, self._preview,
self._activity)
self._menu.connect('action', self._popup_action_cb)
return self._menu
def get_popup_context(self):
return self._popup_context
def set_state(self, name, percent, icon_name, preview, activity):
self._name = name
self._percent = percent
@ -53,15 +57,15 @@ class ClipboardIcon(MenuIcon):
logging.debug("_icon_activated_cb: " + self._object_id)
# Launch the activity to handle this item
handler = ActivityFactory.create(self._activity)
handler = activityfactory.create(self._activity)
handler.connect('success', self._activity_create_success_cb)
handler.connect('error', self._activity_create_error_cb)
def _icon_activated_cb(self, icon):
self._open_file()
def _popup_action_cb(self, popup, action):
self.popdown()
def _popup_action_cb(self, popup, menu_item):
action = menu_item.props.action_id
if action == ClipboardMenu.ACTION_STOP_DOWNLOAD:
raise "Stopping downloads still not implemented."

View File

@ -1,16 +1,23 @@
from gettext import gettext as _
import hippo
from sugar.graphics.menu import Menu
from sugar.graphics.menu import Menu, MenuItem
from sugar.graphics.canvasicon import CanvasIcon
from sugar.graphics.ClipboardBubble import ClipboardBubble
from sugar.graphics import color
from sugar.graphics import font
class ClipboardMenuItem(ClipboardBubble):
class ClipboardProgressBar(ClipboardBubble):
def __init__(self, percent = 0):
self._text_item = None
ClipboardBubble.__init__(self, percent=percent)
self._text_item = hippo.CanvasText(text=str(percent) + ' %')
self._text_item.props.color = color.LABEL_TEXT.get_int()
self._text_item.props.font_desc = font.DEFAULT.get_pango_desc()
self.append(self._text_item)
def do_set_property(self, pspec, value):
@ -30,57 +37,69 @@ class ClipboardMenu(Menu):
Menu.__init__(self, name)
if percent < 100:
self._progress_bar = ClipboardMenuItem(percent)
self._root.append(self._progress_bar)
self._progress_bar = ClipboardProgressBar(percent)
self.append(self._progress_bar)
else:
self._progress_bar = None
self._remove_icon = None
self._open_icon = None
self._stop_icon = None
self._remove_item = None
self._open_item = None
self._stop_item = None
self.add_item(preview, wrap=True)
if preview:
self._preview_text = hippo.CanvasText(text=preview,
size_mode=hippo.CANVAS_SIZE_WRAP_WORD)
self._preview_text.props.color = color.LABEL_TEXT.get_int()
self._preview_text.props.font_desc = font.DEFAULT.get_pango_desc()
self.append(self._preview_text)
self._update_icons(percent, activity)
def _update_icons(self, percent, activity):
if percent == 100 and activity:
if not self._remove_icon:
self._remove_icon = CanvasIcon(icon_name='theme:stock-remove')
self.add_action(self._remove_icon, ClipboardMenu.ACTION_DELETE)
if not self._remove_item:
self._remove_item = MenuItem(ClipboardMenu.ACTION_DELETE,
_('Remove'),
'theme:stock-remove')
self.add_item(self._remove_item)
if not self._open_icon:
self._open_icon = CanvasIcon(icon_name='theme:stock-keep')
self.add_action(self._open_icon, ClipboardMenu.ACTION_OPEN)
if not self._open_item:
self._open_item = MenuItem(ClipboardMenu.ACTION_OPEN,
_('Open'),
'theme:stock-keep')
self.add_item(self._open_item)
if self._stop_icon:
self.remove_action(self._stop_icon)
self._stop_icon = None
if self._stop_item:
self.remove_item(self._stop_item)
self._stop_item = None
elif percent == 100 and not activity:
if not self._remove_icon:
self._remove_icon = CanvasIcon(icon_name='theme:stock-remove')
self.add_action(self._remove_icon, ClipboardMenu.ACTION_DELETE)
if not self._remove_item:
self._remove_item = MenuItem(ClipboardMenu.ACTION_DELETE,
_('Remove'),
'theme:stock-remove')
self.add_item(self._remove_item)
if self._open_icon:
self.remove_action(self._open_icon)
self._open_icon = None
if self._open_item:
self.remove_item(self._open_item)
self._open_item = None
if self._stop_icon:
self.remove_action(self._stop_icon)
self._stop_icon = None
if self._stop_item:
self.remove_item(self._stop_item)
self._stop_item = None
else:
if not self._stop_icon:
self._stop_icon = CanvasIcon(icon_name='theme:stock-close')
self.add_action(self._stop_icon, ClipboardMenu.ACTION_STOP_DOWNLOAD)
if not self._stop_item:
self._stop_item = MenuItem(ClipboardMenu.ACTION_STOP_DOWNLOAD,
_('Stop download'),
'theme:stock-close')
self.add_item(self._stop_item)
if self._remove_icon:
self.remove_action(self._remove_icon)
self._remove_icon = None
if self._remove_item:
self.remove_item(self._remove_item)
self._remove_item = None
if self._open_icon:
self.remove_action(self._open_icon)
self._open_icon = None
if self._open_item:
self.remove_item(self._open_item)
self._open_item = None
def set_state(self, name, percent, preview, activity):
self.set_title(name)

View File

@ -21,6 +21,7 @@ from sugar.graphics import units
from sugar.graphics.iconcolor import IconColor
from sugar.graphics.iconbutton import IconButton
from sugar.presence import PresenceService
from sugar.activity import bundleregistry
from sugar import profile
class ActivityButton(IconButton):
@ -63,7 +64,7 @@ class ActivitiesBox(hippo.CanvasBox):
self._invite_to_item = {}
self._invites = self._shell_model.get_invites()
bundle_registry = self._shell_model.get_bundle_registry()
bundle_registry = bundleregistry.get_registry()
for bundle in bundle_registry:
if bundle.get_show_launcher():
self.add_activity(bundle)

View File

@ -10,4 +10,5 @@ sugar_PYTHON = \
ZoomBox.py \
notificationtray.py \
overlaybox.py \
PanelWindow.py
PanelWindow.py \
framepopupcontext.py

View File

@ -17,7 +17,6 @@
import gtk
import hippo
from sugar.graphics.menushell import MenuShell
from sugar.graphics import units
class PanelWindow(gtk.Window):
@ -51,13 +50,8 @@ class PanelWindow(gtk.Window):
self.add(self._canvas)
self._canvas.show()
self._menu_shell = MenuShell(self._canvas)
self.resize(width, height)
def get_menu_shell(self):
return self._menu_shell
def get_root(self):
return self._bg

View File

@ -19,31 +19,32 @@ from gettext import gettext as _
import hippo
from sugar.graphics.popup import Popup
from sugar.graphics.menuicon import MenuIcon
from sugar.graphics.menu import Menu
from sugar.graphics.menu import Menu, MenuItem
from sugar.graphics.iconcolor import IconColor
from sugar.graphics.iconbutton import IconButton
import sugar
class ActivityPopup(Popup):
class ActivityMenu(Menu):
ACTION_SHARE = 1
ACTION_CLOSE = 2
def __init__(self, activity_model):
Popup.__init__(self, activity_model.get_title())
Menu.__init__(self, activity_model.get_title())
if not activity_model.get_shared():
self.add_item(ActivityPopup.ACTION_SHARE, _('Share'),
'theme:stock-share-mesh')
self.add_item(MenuItem(ActivityMenu.ACTION_SHARE,
_('Share'),
'theme:stock-share-mesh'))
self.add_item(ActivityPopup.ACTION_CLOSE, _('Close'),
'theme:stock-close')
self.add_item(MenuItem(ActivityMenu.ACTION_CLOSE,
_('Close'),
'theme:stock-close'))
class ActivityButton(IconButton):
def __init__(self, shell, activity_model):
def __init__(self, shell, activity_model, popup_context):
self._shell = shell
self._activity_model = activity_model
self._popup_context = popup_context
icon_name = self._activity_model.get_icon_name()
icon_color = self._activity_model.get_icon_color()
@ -51,16 +52,14 @@ class ActivityButton(IconButton):
IconButton.__init__(self, icon_name=icon_name, color=icon_color)
def get_popup(self):
popup = ActivityPopup(self._activity_model)
#popup.connect('action', self._action_cb)
return popup
menu = ActivityMenu(self._activity_model)
menu.connect('action', self._action_cb)
return menu
def get_popup_context(self):
return self._shell.get_popup_context()
def _action_cb(self, menu, data):
[action_id, label] = data
return self._popup_context
def _action_cb(self, menu, menu_item):
# TODO: Wouldn't be better to share/close the activity associated with
# this button instead of asking for the current activity?
activity = self._shell.get_current_activity()
@ -68,17 +67,17 @@ class ActivityButton(IconButton):
logging.error('No active activity.')
return
if action_id == ActivityPopup.ACTION_SHARE:
if menu_item.props.action_id == ActivityMenu.ACTION_SHARE:
activity.share()
elif action_id == ActivityPopup.ACTION_CLOSE:
elif menu_item.props.action_id == ActivityMenu.ACTION_CLOSE:
activity.close()
class ZoomBox(hippo.CanvasBox):
def __init__(self, shell, menu_shell):
def __init__(self, shell, popup_context):
hippo.CanvasBox.__init__(self, orientation=hippo.ORIENTATION_HORIZONTAL)
self._shell = shell
self._menu_shell = menu_shell
self._popup_context = popup_context
self._activity_icon = None
icon = IconButton(icon_name='theme:stock-zoom-mesh')
@ -107,7 +106,7 @@ class ZoomBox(hippo.CanvasBox):
self.remove(self._activity_icon)
if home_activity:
icon = ActivityButton(self._shell, home_activity)
icon = ActivityButton(self._shell, home_activity, self._popup_context)
self.append(icon)
self._activity_icon = icon
else:

View File

@ -37,9 +37,9 @@ class _ContextMap:
class ClipboardBox(hippo.CanvasBox):
def __init__(self, menu_shell):
def __init__(self, popup_context):
hippo.CanvasBox.__init__(self)
self._menu_shell = menu_shell
self._popup_context = popup_context
self._icons = {}
self._context_map = _ContextMap()
@ -74,7 +74,7 @@ class ClipboardBox(hippo.CanvasBox):
on_disk = False)
def _object_added_cb(self, cb_service, object_id, name):
icon = ClipboardIcon(self._menu_shell, object_id, name)
icon = ClipboardIcon(self._popup_context, object_id, name)
self.append(icon)
self._icons[object_id] = icon

View File

@ -17,10 +17,9 @@ class ClipboardPanelWindow(PanelWindow):
clipboard = gtk.Clipboard()
clipboard.connect("owner-change", self._owner_change_cb)
menu_shell = self.get_menu_shell()
root = self.get_root()
box = ClipboardBox(menu_shell)
box = ClipboardBox(frame.get_popup_context())
root.append(box)
# Receiving dnd drops

View File

@ -27,9 +27,9 @@ from view.frame.FriendsBox import FriendsBox
from view.frame.PanelWindow import PanelWindow
from view.frame.clipboardpanelwindow import ClipboardPanelWindow
from view.frame.notificationtray import NotificationTray
from view.frame.framepopupcontext import FramePopupContext
from model.ShellModel import ShellModel
from sugar.graphics.timeline import Timeline
from sugar.graphics.menushell import MenuShell
from sugar.graphics import units
_ANIMATION = False
@ -62,6 +62,12 @@ class Frame:
self._event_frame.connect('leave', self._event_frame_leave_cb)
self._event_frame.show()
self._popup_context = FramePopupContext()
self._popup_context.connect('activated',
self._popup_context_activated_cb)
self._popup_context.connect('deactivated',
self._popup_context_deactivated_cb)
self._top_panel = self._create_top_panel()
self._bottom_panel = self._create_bottom_panel()
self._left_panel = self._create_left_panel()
@ -70,20 +76,11 @@ class Frame:
shell.get_model().connect('notify::state',
self._shell_state_changed_cb)
popup_context = shell.get_popup_context()
popup_context.connect('activated',
self._popup_context_activated_cb)
popup_context.connect('deactivated',
self._popup_context_deactivated_cb)
def _create_top_panel(self):
panel = self._create_panel(hippo.ORIENTATION_HORIZONTAL)
menu_shell = panel.get_menu_shell()
root = panel.get_root()
menu_shell.set_position(MenuShell.BOTTOM)
box = ZoomBox(self._shell, menu_shell)
box = ZoomBox(self._shell, self._popup_context)
root.append(box)
tray = NotificationTray()
@ -103,11 +100,8 @@ class Frame:
def _create_bottom_panel(self):
panel = self._create_panel(hippo.ORIENTATION_HORIZONTAL)
menu_shell = panel.get_menu_shell()
root = panel.get_root()
menu_shell.set_position(MenuShell.TOP)
box = ActivitiesBox(self._shell)
root.append(box)
@ -115,12 +109,9 @@ class Frame:
def _create_right_panel(self):
panel = self._create_panel(hippo.ORIENTATION_VERTICAL)
menu_shell = panel.get_menu_shell()
root = panel.get_root()
menu_shell.set_position(MenuShell.LEFT)
box = FriendsBox(self._shell, menu_shell)
box = FriendsBox(self._shell, self._popup_context)
root.append(box)
return panel
@ -158,19 +149,6 @@ class Frame:
panel.connect('enter-notify-event', self._enter_notify_cb)
panel.connect('leave-notify-event', self._leave_notify_cb)
menu_shell = panel.get_menu_shell()
menu_shell.connect('activated',
self._menu_shell_activated_cb)
menu_shell.connect('deactivated',
self._menu_shell_deactivated_cb)
def _menu_shell_activated_cb(self, menu_shell):
self._timeline.goto('slide_in', True)
def _menu_shell_deactivated_cb(self, menu_shell):
if self._mode != Frame.STICKY and not self._hover_frame:
self._timeline.play('before_slide_out', 'slide_out')
def _popup_context_activated_cb(self, popup_context):
self._timeline.goto('slide_in', True)
@ -205,8 +183,7 @@ class Frame:
def _leave_notify(self, panel):
self._hover_frame = False
if not panel.get_menu_shell().is_active() and \
not self._shell.get_popup_context().is_active() and \
if not self._popup_context.is_active() and \
(self._mode == Frame.HIDE_ON_LEAVE or \
self._mode == Frame.AUTOMATIC):
self._timeline.play('before_slide_out', 'slide_out')
@ -281,3 +258,6 @@ class Frame:
def is_visible(self):
return self._top_panel.props.visible
def get_popup_context(self):
return self._popup_context

View File

@ -0,0 +1,26 @@
# Copyright (C) 2007, One Laptop Per Child
#
# 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.
import gobject
import hippo
from sugar.graphics.popupcontext import PopupContext
class FramePopupContext(PopupContext):
__gtype_name__ = 'SugarFramePopupContext'
def __init__(self):
PopupContext.__init__(self)

View File

@ -1,211 +0,0 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import os
import logging
import dbus
import dbus.service
import gtk
import gobject
import datetime
from sugar.presence import PresenceService
from sugar.datastore import datastore
from sugar import activity
from sugar import env
import sugar.util
ACTIVITY_SERVICE_NAME = "org.laptop.Activity"
ACTIVITY_SERVICE_PATH = "/org/laptop/Activity"
ACTIVITY_INTERFACE = "org.laptop.Activity"
def get_service_name(xid):
return ACTIVITY_SERVICE_NAME + '%d' % xid
def get_object_path(xid):
return ACTIVITY_SERVICE_PATH + "/%s" % xid
def get_service(xid):
bus = dbus.SessionBus()
proxy_obj = bus.get_object(get_service_name(xid), get_object_path(xid))
return dbus.Interface(proxy_obj, ACTIVITY_INTERFACE)
class ActivityDbusService(dbus.service.Object):
"""Base dbus service object that each Activity uses to export dbus methods.
The dbus service is separate from the actual Activity object so that we can
tightly control what stuff passes through the dbus python bindings."""
def __init__(self, activity):
xid = activity.window.xid
bus = dbus.SessionBus()
bus_name = dbus.service.BusName(get_service_name(xid), bus=bus)
dbus.service.Object.__init__(self, bus_name, get_object_path(xid))
self._activity = activity
self._pservice = PresenceService.get_instance()
@dbus.service.method(ACTIVITY_INTERFACE)
def start(self, activity_id):
"""Start the activity in unshared mode."""
self._activity.start(activity_id)
@dbus.service.method(ACTIVITY_INTERFACE)
def join(self, activity_ps_path):
"""Join the activity specified by its presence service path."""
activity_ps = self._pservice.get(activity_ps_path)
return self._activity.join(activity_ps)
@dbus.service.method(ACTIVITY_INTERFACE)
def share(self):
"""Called by the shell to request the activity to share itself on the network."""
self._activity.share()
@dbus.service.method(ACTIVITY_INTERFACE)
def get_id(self):
"""Get the activity identifier"""
return self._activity.get_id()
@dbus.service.method(ACTIVITY_INTERFACE)
def get_type(self):
"""Get the activity type"""
return self._activity.get_type()
@dbus.service.method(ACTIVITY_INTERFACE)
def get_shared(self):
"""Returns True if the activity is shared on the mesh."""
return self._activity.get_shared()
@dbus.service.method(ACTIVITY_INTERFACE,
in_signature="sas", out_signature="b")
def execute(self, command, args):
return self._activity.execute(command, args)
class Activity(gtk.Window):
"""Base Activity class that all other Activities derive from."""
def __init__(self):
gtk.Window.__init__(self)
self.connect('destroy', self._destroy_cb)
#self.connect('notify::title', self._title_changed_cb)
self._shared = False
self._activity_id = None
self._service = None
#self._journal_object = None
self._pservice = PresenceService.get_instance()
self.realize()
group = gtk.Window()
group.realize()
self.window.set_group(group.window)
self._bus = ActivityDbusService(self)
def start(self, activity_id):
"""Start the activity."""
if self._activity_id != None:
logging.warning('The activity has been already started.')
return
self._activity_id = activity_id
#ds = datastore.get_instance()
#self._journal_object = ds.create('', {}, self._activity_id)
#
#date = datetime.datetime.now()
#self._journal_jobject.set_properties({'date' : date,
# 'title' : self.get_title()})
self.present()
# def get_journal_object(self):
# """Returns the journal object associated with the activity."""
# return self._journal_object
def get_type(self):
"""Gets the activity type."""
return env.get_bundle_service_name()
def get_default_type(self):
"""Gets the type of the default activity network service"""
return env.get_bundle_default_type()
def get_shared(self):
"""Returns TRUE if the activity is shared on the mesh."""
return self._shared
def get_id(self):
"""Get the unique activity identifier."""
return self._activity_id
def join(self, activity_ps):
"""Join an activity shared on the network."""
if self._activity_id != None:
logging.warning('The activity has been already started.')
return
self._activity_id = activity_ps.get_id()
self._shared = True
# Publish the default service, it's a copy of
# one of those we found on the network.
default_type = self.get_default_type()
services = activity_ps.get_services_of_type(default_type)
if len(services) > 0:
service = services[0]
addr = service.get_address()
port = service.get_port()
properties = service.get_published_values()
self._service = self._pservice.share_activity(
self, default_type, properties, addr, port)
else:
logging.error('Cannot join the activity')
#ds = datastore.get_instance()
#self._journal_object = ds.get_activity_object(self._activity_id)
self.present()
def share(self):
"""Share the activity on the network."""
logging.debug('Share activity %s on the network.' % self.get_id())
default_type = self.get_default_type()
self._service = self._pservice.share_activity(self, default_type)
self._shared = True
def execute(self, command, args):
"""Execute the given command with args"""
return False
def _destroy_cb(self, window):
if self._bus:
del self._bus
self._bus = None
if self._service:
self._pservice.unregister_service(self._service)
def _title_changed_cb(self, window, spec):
pass
# jobject = self.get_journal_object()
# if jobject:
# jobject.set_properties({'title' : self.props.title})

View File

@ -1,127 +0,0 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import os
import sys
import logging
import dbus
import dbus.service
import gobject
import gtk
from sugar.presence.PresenceService import PresenceService
from sugar.activity import Activity
from sugar.activity.bundle import Bundle
from sugar import logger
def get_path(activity_name):
"""Returns the activity path"""
return '/' + activity_name.replace('.', '/')
class ActivityFactory(dbus.service.Object):
"""Dbus service that takes care of creating new instances of an activity"""
def __init__(self, activity_type, activity_class):
self._activity_type = activity_type
self._activities = []
splitted_module = activity_class.rsplit('.', 1)
module_name = splitted_module[0]
class_name = splitted_module[1]
module = __import__(module_name)
for comp in module_name.split('.')[1:]:
module = getattr(module, comp)
if hasattr(module, 'start'):
module.start()
self._module = module
self._constructor = getattr(module, class_name)
bus = dbus.SessionBus()
factory = activity_type
bus_name = dbus.service.BusName(factory, bus = bus)
dbus.service.Object.__init__(self, bus_name, get_path(factory))
@dbus.service.method("com.redhat.Sugar.ActivityFactory")
def create(self):
activity = self._constructor()
self._activities.append(activity)
activity.connect('destroy', self._activity_destroy_cb)
return activity.window.xid
def _activity_destroy_cb(self, activity):
self._activities.remove(activity)
if hasattr(self._module, 'stop'):
self._module.stop()
if len(self._activities) == 0:
gtk.main_quit()
class ActivityCreationHandler(gobject.GObject):
__gsignals__ = {
'error': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'success': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
def __init__(self, activity_name):
gobject.GObject.__init__(self)
bus = dbus.SessionBus()
factory_name = activity_name
factory_path = get_path(factory_name)
proxy_obj = bus.get_object(factory_name, factory_path)
factory = dbus.Interface(proxy_obj, "com.redhat.Sugar.ActivityFactory")
factory.create(reply_handler=self._reply_handler, error_handler=self._error_handler)
def _reply_handler(self, xid):
bus = dbus.SessionBus()
proxy_obj = bus.get_object(Activity.get_service_name(xid),
Activity.get_object_path(xid))
activity = dbus.Interface(proxy_obj, Activity.ACTIVITY_INTERFACE)
self.emit('success', activity)
def _error_handler(self, err):
logging.debug("Couldn't create activity: %s" % err)
self.emit('error', err)
def create(activity_name):
"""Create a new activity from its name."""
return ActivityCreationHandler(activity_name)
def start_factory(activity_class, bundle_path):
"""Start the activity factory."""
bundle = Bundle(bundle_path)
logger.start(bundle.get_name())
os.environ['SUGAR_BUNDLE_PATH'] = bundle_path
os.environ['SUGAR_BUNDLE_SERVICE_NAME'] = bundle.get_service_name()
os.environ['SUGAR_BUNDLE_DEFAULT_TYPE'] = bundle.get_default_type()
factory = ActivityFactory(bundle.get_service_name(), activity_class)

View File

@ -1,8 +1,11 @@
sugardir = $(pythondir)/sugar/activity
sugar_PYTHON = \
__init__.py \
Activity.py \
ActivityFactory.py \
activity.py \
activityfactory.py \
activityfactoryservice.py \
activityhandle.py \
activityservice.py \
bundle.py \
bundlebuilder.py \
bundleregistry.py

108
sugar/activity/activity.py Normal file
View File

@ -0,0 +1,108 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import logging
import os
import gtk
from sugar.presence import PresenceService
from sugar.activity.activityservice import ActivityService
class Activity(gtk.Window):
"""Base Activity class that all other Activities derive from."""
def __init__(self, handle):
gtk.Window.__init__(self)
self.connect('destroy', self._destroy_cb)
self._shared = False
self._activity_id = handle.activity_id
self._pservice = PresenceService.get_instance()
service = handle.get_presence_service()
if service:
self._join(service)
self.realize()
group = gtk.Window()
group.realize()
self.window.set_group(group.window)
self._bus = ActivityService(self)
self.present()
def get_service_name(self):
"""Gets the activity service name."""
return os.environ['SUGAR_BUNDLE_SERVICE_NAME']
def get_default_type(self):
"""Gets the type of the default activity network service"""
return os.environ['SUGAR_BUNDLE_DEFAULT_TYPE']
def get_shared(self):
"""Returns TRUE if the activity is shared on the mesh."""
return self._shared
def get_id(self):
"""Get the unique activity identifier."""
return self._activity_id
def _join(self, service):
self._service = service
self._shared = True
# Publish the default service, it's a copy of
# one of those we found on the network.
default_type = self.get_default_type()
services = activity_ps.get_services_of_type(default_type)
if len(services) > 0:
service = services[0]
addr = service.get_address()
port = service.get_port()
properties = service.get_published_values()
self._service = self._pservice.share_activity(
self, default_type, properties, addr, port)
else:
logging.error('Cannot join the activity')
self.present()
def share(self):
"""Share the activity on the network."""
logging.debug('Share activity %s on the network.' % self.get_id())
default_type = self.get_default_type()
self._service = self._pservice.share_activity(self, default_type)
self._shared = True
def execute(self, command, args):
"""Execute the given command with args"""
return False
def _destroy_cb(self, window):
if self._bus:
del self._bus
self._bus = None
if self._service:
self._pservice.unregister_service(self._service)
def get_bundle_path():
return os.environ['SUGAR_BUNDLE_BUNDLE_PATH']

View File

@ -0,0 +1,102 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import logging
import dbus
import gobject
import gtk
from sugar.presence import PresenceService
from sugar.activity import bundleregistry
from sugar.activity.activityhandle import ActivityHandle
from sugar import util
_ACTIVITY_SERVICE_NAME = "org.laptop.Activity"
_ACTIVITY_SERVICE_PATH = "/org/laptop/Activity"
_ACTIVITY_INTERFACE = "org.laptop.Activity"
def _find_activity_id():
pservice = PresenceService.get_instance()
# create a new unique activity ID
i = 0
act_id = None
while i < 10:
act_id = util.unique_id()
i += 1
# check through network activities
found = False
activities = pservice.get_activities()
for act in activities:
if act_id == act.get_id():
found = True
break
if found:
raise RuntimeError("Cannot generate unique activity id.")
return act_id
class ActivityCreationHandler(gobject.GObject):
__gsignals__ = {
'error': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
}
def __init__(self, service_name, activity_handle):
gobject.GObject.__init__(self)
self._service_name = service_name
self._activity_handle = activity_handle
registry = bundleregistry.get_registry()
bundle = registry.get_bundle(service_name)
bus = dbus.SessionBus()
proxy_obj = bus.get_object(service_name, bundle.get_object_path())
factory = dbus.Interface(proxy_obj, "com.redhat.Sugar.ActivityFactory")
factory.create(self._activity_handle.get_dict(),
reply_handler=self._reply_handler,
error_handler=self._error_handler)
def get_activity_id(self):
return self._activity_handle.activity_id
def _reply_handler(self, xid):
logging.debug("Activity created %s (%s)." %
(self._activity_handle.activity_id, self._service_name))
def _error_handler(self, err):
logging.debug("Couldn't create activity %s (%s): %s" %
(self._activity_handle.activity_id, self._service_name, err))
self.emit('error', err)
def create(service_name, activity_handle=None):
"""Create a new activity from its name."""
if not activity_handle:
activity_handle = ActivityHandle(_find_activity_id())
return ActivityCreationHandler(service_name, activity_handle)
def create_with_uri(service_name, uri):
"""Create a new activity and pass the uri as handle."""
activity_handle = ActivityHandle(_find_activity_id())
activity_handle.uri = uri
return ActivityCreationHandler(service_name, handle)

View File

@ -0,0 +1,97 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import os
import sys
from optparse import OptionParser
import gobject
import gtk
import dbus
import dbus.service
import dbus.glib
from sugar.activity.bundle import Bundle
from sugar.activity import activityhandle
from sugar import logger
# Work around for dbus mutex locking issue
gobject.threads_init()
dbus.glib.threads_init()
class ActivityFactoryService(dbus.service.Object):
"""D-Bus service that creates new instances of an activity"""
def __init__(self, service_name, activity_class):
self._activities = []
splitted_module = activity_class.rsplit('.', 1)
module_name = splitted_module[0]
class_name = splitted_module[1]
module = __import__(module_name)
for comp in module_name.split('.')[1:]:
module = getattr(module, comp)
if hasattr(module, 'start'):
module.start()
self._module = module
self._constructor = getattr(module, class_name)
bus = dbus.SessionBus()
bus_name = dbus.service.BusName(service_name, bus = bus)
object_path = '/' + service_name.replace('.', '/')
dbus.service.Object.__init__(self, bus_name, object_path)
@dbus.service.method("com.redhat.Sugar.ActivityFactory", in_signature="a{ss}")
def create(self, handle):
activity_handle = activityhandle.create_from_dict(handle)
activity = self._constructor(activity_handle)
self._activities.append(activity)
activity.connect('destroy', self._activity_destroy_cb)
return activity.window.xid
def _activity_destroy_cb(self, activity):
self._activities.remove(activity)
if hasattr(self._module, 'stop'):
self._module.stop()
if len(self._activities) == 0:
gtk.main_quit()
def run(args):
"""Start the activity factory."""
parser = OptionParser()
parser.add_option("-p", "--bundle-path", dest="bundle_path",
help="path to the activity bundle")
(options, args) = parser.parse_args()
sys.path.insert(0, options.bundle_path)
bundle = Bundle(options.bundle_path)
logger.start(bundle.get_name())
os.environ['SUGAR_BUNDLE_PATH'] = options.bundle_path
os.environ['SUGAR_BUNDLE_SERVICE_NAME'] = bundle.get_service_name()
os.environ['SUGAR_BUNDLE_DEFAULT_TYPE'] = bundle.get_default_type()
factory = ActivityFactoryService(bundle.get_service_name(), args[0])
gtk.main()

View File

@ -0,0 +1,49 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
from sugar.presence import PresenceService
class ActivityHandle(object):
def __init__(self, activity_id):
self.activity_id = activity_id
self.pservice_id = None
self.uri = None
def get_presence_service(self):
if self.pservice_id:
pservice = PresenceService.get_instance()
return pservice.get_activity(self.pservice_id)
else:
return None
def get_dict(self):
result = { 'activity_id' : self.activity_id }
if self.pservice_id:
result['pservice_id'] = self.pservice_id
if self.uri:
result['uri'] = self.uri
return result
def create_from_dict(handle_dict):
result = ActivityHandle(handle_dict['activity_id'])
if handle_dict.has_key('pservice_id'):
result.pservice_id = handle_dict['pservice_id']
if handle_dict.has_key('uri'):
result.uri = handle_dict['uri']
return result

View File

@ -0,0 +1,68 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import dbus
import dbus.service
from sugar.presence import PresenceService
_ACTIVITY_SERVICE_NAME = "org.laptop.Activity"
_ACTIVITY_SERVICE_PATH = "/org/laptop/Activity"
_ACTIVITY_INTERFACE = "org.laptop.Activity"
class ActivityService(dbus.service.Object):
"""Base dbus service object that each Activity uses to export dbus methods.
The dbus service is separate from the actual Activity object so that we can
tightly control what stuff passes through the dbus python bindings."""
def __init__(self, activity):
xid = activity.window.xid
service_name = _ACTIVITY_SERVICE_NAME + '%d' % xid
object_path = _ACTIVITY_SERVICE_PATH + "/%s" % xid
bus = dbus.SessionBus()
bus_name = dbus.service.BusName(service_name, bus=bus)
dbus.service.Object.__init__(self, bus_name, object_path)
self._activity = activity
@dbus.service.method(_ACTIVITY_INTERFACE)
def share(self):
"""Called by the shell to request the activity to share itself on the network."""
self._activity.share()
@dbus.service.method(_ACTIVITY_INTERFACE)
def get_id(self):
"""Get the activity identifier"""
return self._activity.get_id()
@dbus.service.method(_ACTIVITY_INTERFACE)
def get_service_name(self):
"""Get the activity service name"""
return self._activity.get_service_name()
@dbus.service.method(_ACTIVITY_INTERFACE)
def get_shared(self):
"""Returns True if the activity is shared on the mesh."""
return self._activity.get_shared()
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="sas", out_signature="b")
def execute(self, command, args):
return self._activity.execute(command, args)

View File

@ -1,8 +1,11 @@
import logging
import os
from ConfigParser import ConfigParser
from sugar import env
_PYTHON_FACTORY='sugar-activity-factory'
class Bundle:
"""Info about an activity bundle. Wraps the activity.info file."""
def __init__(self, path):
@ -38,11 +41,18 @@ class Bundle:
self._valid = False
logging.error('%s must specify a name' % self._path)
if cp.has_option(section, 'exec'):
if cp.has_option(section, 'class'):
self._class = cp.get(section, 'class')
self._exec = '%s %s --bundle-path=%s' % (
os.path.join(env.get_shell_bin_dir(), _PYTHON_FACTORY),
self._class, self.get_path())
elif cp.has_option(section, 'exec'):
self._class = None
self._exec = cp.get(section, 'exec')
else:
self._exec = None
self._valid = False
logging.error('%s must specify an exec' % self._path)
logging.error('%s must specify exec or class' % self._path)
if cp.has_option(section, 'show_launcher'):
if cp.get(section, 'show_launcher') == 'no':
@ -71,6 +81,10 @@ class Bundle:
"""Get the activity service name"""
return self._service_name
def get_object_path(self):
"""Get the path to the service object"""
return '/' + self._service_name.replace('.', '/')
def get_default_type(self):
"""Get the type of the main network service which tracks presence
and provides info about the activity, for example the title."""
@ -90,6 +104,10 @@ class Bundle:
"""Get the command to execute to launch the activity factory"""
return self._exec
def get_class(self):
"""Get the main Activity class"""
return self._exec
def get_show_launcher(self):
"""Get whether there should be a visible launcher for the activity"""
return self._show_launcher

View File

@ -11,13 +11,8 @@ class _ServiceManager(object):
self._path = env.get_user_service_dir()
def add(self, bundle):
name = bundle.get_service_name()
# FIXME evil hack. Probably need to fix Exec spec
full_exec = env.get_shell_bin_dir() + '/' + bundle.get_exec()
full_exec += ' ' + bundle.get_path()
util.write_service(name, full_exec, self._path)
util.write_service(bundle.get_service_name(),
bundle.get_exec(), self._path)
class BundleRegistry(gobject.GObject):
"""Service that tracks the available activity bundles"""
@ -73,3 +68,14 @@ class BundleRegistry(gobject.GObject):
return True
else:
return False
def get_registry():
return _bundle_registry
_bundle_registry = BundleRegistry()
for path in env.get_data_dirs():
bundles_path = os.path.join(path, 'activities')
_bundle_registry.add_search_path(bundles_path)
_bundle_registry.add_search_path(env.get_user_activities_dir())

View File

@ -24,30 +24,12 @@ try:
except ImportError:
from sugar.__installed__ import *
def get_bundle_path():
if os.environ.has_key('SUGAR_BUNDLE_PATH'):
return os.environ['SUGAR_BUNDLE_PATH']
else:
return None
def is_emulator():
if os.environ.has_key('SUGAR_EMULATOR'):
if os.environ['SUGAR_EMULATOR'] == 'yes':
return True
return False
def get_bundle_service_name():
if os.environ.has_key('SUGAR_BUNDLE_SERVICE_NAME'):
return os.environ['SUGAR_BUNDLE_SERVICE_NAME']
else:
return None
def get_bundle_default_type():
if os.environ.has_key('SUGAR_BUNDLE_DEFAULT_TYPE'):
return os.environ['SUGAR_BUNDLE_DEFAULT_TYPE']
else:
return None
def get_profile_path():
if os.environ.has_key('SUGAR_PROFILE'):
profile_id = os.environ['SUGAR_PROFILE']

View File

@ -12,7 +12,6 @@ sugar_PYTHON = \
iconcolor.py \
label.py \
menu.py \
menuicon.py \
menushell.py \
optionmenu.py \
roundbox.py \

View File

@ -25,6 +25,7 @@ import cairo
import time
from sugar.graphics.iconcolor import IconColor
from sugar.graphics.timeline import Timeline
class _IconCacheIcon:
def __init__(self, name, color, now):
@ -152,9 +153,17 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
self._icon_name = None
self._cache = False
self._handle = None
self._popup = None
self._hover_popup = False
self._timeline = Timeline(self)
self._timeline.add_tag('popup', 6, 6)
self._timeline.add_tag('before_popdown', 7, 7)
self._timeline.add_tag('popdown', 8, 8)
hippo.CanvasBox.__init__(self, **kwargs)
self.connect('motion-notify-event', self._motion_notify_event_cb)
self.connect('button-press-event', self._button_press_event_cb)
def _clear_buffers(self):
@ -261,3 +270,80 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
def _button_press_event_cb(self, item, event):
item.emit_activated()
def get_popup(self):
return self._popup
def get_popup_context(self):
return None
def do_popup(self, current, n_frames):
if self._popup:
return
popup = self.get_popup()
if not popup:
return
popup_context = self.get_popup_context()
[x, y] = [None, None]
if popup_context:
try:
[x, y] = popup_context.get_position(self, popup)
except NotImplementedError:
pass
if [x, y] == [None, None]:
context = self.get_context()
[x, y] = context.translate_to_screen(self)
# TODO: Any better place to do this?
popup.props.box_width = max(popup.props.box_width,
self.get_width_request())
[width, height] = self.get_allocation()
y += height
position = [x, y]
popup.popup(x, y)
popup.connect('motion-notify-event',
self._popup_motion_notify_event_cb)
popup.connect('action-completed',
self._popup_action_completed_cb)
if popup_context:
popup_context.popped_up(popup)
self._popup = popup
def do_popdown(self, current, frame):
if self._popup:
self._popup.popdown()
popup_context = self.get_popup_context()
if popup_context:
popup_context.popped_down(self._popup)
self._popup = None
def popdown(self):
self._timeline.play('popdown', 'popdown')
def _motion_notify_event_cb(self, button, event):
if event.detail == hippo.MOTION_DETAIL_ENTER:
self._timeline.play(None, 'popup')
elif event.detail == hippo.MOTION_DETAIL_LEAVE:
if not self._hover_popup:
self._timeline.play('before_popdown', 'popdown')
def _popup_motion_notify_event_cb(self, popup, event):
if event.detail == hippo.MOTION_DETAIL_ENTER:
self._hover_popup = True
self._timeline.play('popup', 'popup')
elif event.detail == hippo.MOTION_DETAIL_LEAVE:
self._hover_popup = False
self._timeline.play('popdown', 'popdown')
def _popup_action_completed_cb(self, popup):
self.popdown()

View File

@ -23,7 +23,6 @@ import hippo
from canvasicon import CanvasIcon
from iconcolor import IconColor
from sugar.graphics import units
from sugar.graphics.timeline import Timeline
from sugar import profile
STANDARD_SIZE = 0
@ -49,100 +48,14 @@ class IconButton(CanvasIcon):
self._prelight_color = profile.get_color()
self._inactive_color = IconColor('#808080,#424242')
self._active = True
self._popup = None
self._hover_popup = False
CanvasIcon.__init__(self, icon_name=icon_name, cache=True,
color=self._normal_color)
self._set_size(STANDARD_SIZE)
self._timeline = Timeline(self)
self._timeline.add_tag('popup', 6, 6)
self._timeline.add_tag('before_popdown', 7, 7)
self._timeline.add_tag('popdown', 8, 8)
self.connect('motion-notify-event', self._motion_notify_event_cb)
self.connect('button-press-event', self._button_press_event_cb)
def get_popup(self):
return self._popup
def get_popup_context(self):
return None
def do_popup(self, current, n_frames):
if self._popup:
return
popup = self.get_popup()
if not popup:
return
popup_context = self.get_popup_context()
[x, y] = [None, None]
if popup_context:
try:
[x, y] = popup_context.get_position(self, popup)
except NotImplementedError:
pass
if [x, y] == [None, None]:
context = self.get_context()
#[x, y] = context.translate_to_screen(self)
[x, y] = context.translate_to_widget(self)
# TODO: Any better place to do this?
popup.props.box_width = max(popup.props.box_width,
self.get_width_request())
[width, height] = self.get_allocation()
y += height
position = [x, y]
popup.popup(x, y)
popup.connect('motion-notify-event',
self._popup_motion_notify_event_cb)
popup.connect('action-completed',
self._popup_action_completed_cb)
if popup_context:
popup_context.popped_up(popup)
self._popup = popup
def do_popdown(self, current, frame):
if self._popup:
self._popup.popdown()
popup_context = self.get_popup_context()
if popup_context:
popup_context.popped_down(self._popup)
self._popup = None
def popdown(self):
self._timeline.play('popdown', 'popdown')
def _motion_notify_event_cb(self, button, event):
if event.detail == hippo.MOTION_DETAIL_ENTER:
self._timeline.play(None, 'popup')
elif event.detail == hippo.MOTION_DETAIL_LEAVE:
if not self._hover_popup:
self._timeline.play('before_popdown', 'popdown')
def _popup_motion_notify_event_cb(self, popup, event):
if event.detail == hippo.MOTION_DETAIL_ENTER:
self._hover_popup = True
self._timeline.play('popup', 'popup')
elif event.detail == hippo.MOTION_DETAIL_LEAVE:
self._hover_popup = False
self._timeline.play('popdown', 'popdown')
def _popup_action_completed_cb(self, popup):
self.popdown()
def _set_size(self, size):
if size == SMALL_SIZE:
self.props.box_width = -1
@ -173,8 +86,9 @@ class IconButton(CanvasIcon):
elif pspec.name == 'active':
return self._active
else:
return CanvasIcon.get_property(self, pspec)
return CanvasIcon.do_get_property(self, pspec)
def _button_press_event_cb(self, widget, event):
if self._active:
self.emit_activated()
return True

View File

@ -14,94 +14,98 @@
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import sys
import gtk
import hippo
import gobject
from sugar.graphics.canvasicon import CanvasIcon
from sugar.graphics.popup import Popup
from sugar.graphics.roundbox import RoundBox
from sugar.graphics import color
from sugar.graphics import font
from sugar.graphics import units
class Menu(gtk.Window):
__gsignals__ = {
'action': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([int])),
class MenuItem(hippo.CanvasBox):
__gtype_name__ = 'SugarMenuItem'
__gproperties__ = {
'action-id': (int, None, None,
0, sys.maxint, 0,
gobject.PARAM_READWRITE),
'label' : (str, None, None, None,
gobject.PARAM_READWRITE)
}
def __init__(self, title=None, content_box=None):
gtk.Window.__init__(self, gtk.WINDOW_POPUP)
def __init__(self, action_id, label, icon_name=None, icon_color=None):
hippo.CanvasBox.__init__(self, orientation=hippo.ORIENTATION_HORIZONTAL)
canvas = hippo.Canvas()
self.add(canvas)
canvas.show()
self._action_id = action_id
self.props.padding = 5
self.props.spacing = 5
self._root = hippo.CanvasBox()
canvas.set_root(self._root)
if icon_name:
icon = CanvasIcon(icon_name=icon_name,
scale=units.SMALL_ICON_SCALE)
if icon_color:
icon.props.color = icon_color
self.append(icon)
self._canvas_text = hippo.CanvasText(text=label)
self._canvas_text.props.color = color.LABEL_TEXT.get_int()
self._canvas_text.props.font_desc = font.DEFAULT.get_pango_desc()
self.append(self._canvas_text)
def do_set_property(self, pspec, value):
if pspec.name == 'action-id':
self._action_id = value
elif pspec.name == 'label':
self._canvas_text.props.text = value
else:
hippo.CanvasBox.do_set_property(self, pspec, value)
def do_get_property(self, pspec):
if pspec.name == 'action-id':
return self._action_id
elif pspec.name == 'label':
return self._canvas_text.props.text
else:
return hippo.CanvasBox.do_get_property(self, pspec)
class Menu(Popup):
__gtype_name__ = 'SugarMenu'
__gsignals__ = {
'action': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([object]))
}
def __init__(self, title=None):
Popup.__init__(self)
self.props.background_color = color.MENU_BACKGROUND.get_int()
self.props.border_color = color.MENU_BORDER.get_int()
self.props.border = units.points_to_pixels(1)
if title:
self._title_item = hippo.CanvasText(text=title)
self._root.append(self._title_item)
else:
self._title_item = None
title_item = hippo.CanvasText(text=title)
title_item.props.color = color.LABEL_TEXT.get_int()
title_item.props.font_desc = font.DEFAULT.get_pango_desc()
self.append(title_item)
self.add_separator()
if content_box:
separator = self._create_separator()
self._root.append(separator)
self._root.append(content_box)
def add_item(self, item):
item.connect('button-press-event', self._item_button_press_event_cb)
self.append(item)
self._action_box = None
self._item_box = None
def remove_item(self, item):
self.remove(item)
def _create_separator(self):
separator = hippo.CanvasBox()
return separator
def add_separator(self):
box = hippo.CanvasBox()
box.props.background_color = color.MENU_SEPARATOR.get_int()
box.props.box_height = units.points_to_pixels(1)
self.append(box)
def _create_item_box(self):
if self._title_item:
separator = self._create_separator()
self._root.append(separator)
self._item_box = hippo.CanvasBox(
orientation=hippo.ORIENTATION_VERTICAL)
self._root.append(self._item_box)
def _create_action_box(self):
separator = self._create_separator()
self._root.append(separator)
self._action_box = hippo.CanvasBox(
orientation=hippo.ORIENTATION_HORIZONTAL)
self._root.append(self._action_box)
def add_item(self, label, action_id=None, wrap=False):
if not self._item_box:
self._create_item_box()
text = hippo.CanvasText(text=label)
if wrap:
text.set_property("size-mode", "wrap-word")
# FIXME need a way to make hippo items activable in python
if action_id:
text.connect('button-press-event', self._item_clicked_cb, action_id)
#text.connect('activated', self._action_clicked_cb, action_id)
self._item_box.append(text)
def add_action(self, icon, action_id):
if not self._action_box:
self._create_action_box()
icon.connect('activated', self._action_clicked_cb, action_id)
self._action_box.append(icon)
def remove_action(self, icon):
self._action_box.remove(icon)
def _item_clicked_cb(self, icon, event, action):
self.emit('action', action)
def _action_clicked_cb(self, icon, action):
self.emit('action', action)
def set_title(self, title):
self._title_item.set_property('text', title)
def _item_button_press_event_cb(self, menu_item, event):
self.emit('action', menu_item)

View File

@ -1,80 +0,0 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import hippo
import gobject
import logging
from sugar.graphics.canvasicon import CanvasIcon
from sugar.graphics.timeline import Timeline
class MenuIcon(CanvasIcon):
def __init__(self, menu_shell, **kwargs):
CanvasIcon.__init__(self, **kwargs)
self._menu_shell = menu_shell
self._menu = None
self._hover_menu = False
self._timeline = Timeline(self)
self._timeline.add_tag('popup', 6, 6)
self._timeline.add_tag('before_popdown', 7, 7)
self._timeline.add_tag('popdown', 8, 8)
self.connect('motion-notify-event', self._motion_notify_event_cb)
def do_popup(self, current, n_frames):
if self._menu:
return
self._menu = self.create_menu()
self._menu.connect('enter-notify-event',
self._menu_enter_notify_event_cb)
self._menu.connect('leave-notify-event',
self._menu_leave_notify_event_cb)
[x, y] = self._menu_shell.get_position(self._menu, self)
self._menu.move(x, y)
self._menu.show()
self._menu_shell.set_active(self)
def do_popdown(self, current, frame):
if self._menu:
self._menu.destroy()
self._menu = None
self._menu_shell.set_active(None)
def popdown(self):
self._timeline.play('popdown', 'popdown')
def _motion_notify_event_cb(self, item, event):
if event.detail == hippo.MOTION_DETAIL_ENTER:
self._timeline.play(None, 'popup')
elif event.detail == hippo.MOTION_DETAIL_LEAVE:
if not self._hover_menu:
self._timeline.play('before_popdown', 'popdown')
def _menu_enter_notify_event_cb(self, widget, event):
self._hover_menu = True
self._timeline.play('popup', 'popup')
def _menu_leave_notify_event_cb(self, widget, event):
self._hover_menu = False
self._timeline.play('popdown', 'popdown')

View File

@ -24,70 +24,27 @@ import hippo
from sugar.graphics import units
from sugar.graphics.roundbox import RoundBox
from sugar.graphics.menu import Menu, MenuItem
from sugar.graphics import iconbutton
from sugar.graphics import color
from sugar.graphics import font
from sugar.graphics.canvasicon import CanvasIcon
class Menu(hippo.CanvasBox, hippo.CanvasItem):
__gtype_name__ = 'SugarMenu'
__gsignals__ = {
'action': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([object]))
}
class _Menu(Menu):
def __init__(self):
hippo.CanvasBox.__init__(self)
self.props.background_color = color.MENU_BACKGROUND.get_int()
self.props.border_color = color.MENU_BORDER.get_int()
self.props.border = units.points_to_pixels(1)
self._window = None
def add_item(self, action_id, label, icon_name=None, icon_color=None):
box = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL)
box.props.padding = 5
box.props.spacing = 5
if icon_name:
icon = CanvasIcon(icon_name=icon_name,
scale=units.SMALL_ICON_SCALE)
if icon_color:
icon.props.color = icon_color
box.append(icon)
canvas_text = hippo.CanvasText()
canvas_text.props.text = label
canvas_text.props.color = color.LABEL_TEXT.get_int()
canvas_text.props.font_desc = font.DEFAULT.get_pango_desc()
box.append(canvas_text)
box.connect('button-press-event', self._item_button_press_event_cb,
[action_id, label])
self.append(box)
def add_separator(self):
box = hippo.CanvasBox()
box.props.background_color = color.MENU_SEPARATOR.get_int()
box.props.box_height = units.points_to_pixels(1)
self.append(box)
def show(self, x, y):
if not self._window:
self._window = hippo.CanvasWindow(gtk.WINDOW_POPUP)
self._window.move(x, y)
self._window.set_root(self)
self._window.show()
def hide(self):
if self._window:
self._window.destroy()
self._window = None
def _item_button_press_event_cb(self, item, event, data):
self.emit('action', data)
self.hide()
Menu.__init__(self)
self._is_visible = False
def is_visible(self):
return self._window != None
return self._is_visible
def popup(self, x, y):
Menu.popup(self, x, y)
self._is_visible = True
def popdown(self):
Menu.popdown(self)
self._is_visible = False
class OptionMenu(hippo.CanvasBox, hippo.CanvasItem):
__gtype_name__ = 'SugarOptionMenu'
@ -122,8 +79,9 @@ class OptionMenu(hippo.CanvasBox, hippo.CanvasItem):
arrow.props.xalign = hippo.ALIGNMENT_START
self._round_box.append(arrow)
self._menu = Menu()
self._menu = _Menu()
self._menu.connect('action', self._menu_action_cb)
self._menu.connect('action-completed', self._menu_action_completed_cb)
self.connect('button-press-event', self._button_press_event_cb)
@ -135,33 +93,37 @@ class OptionMenu(hippo.CanvasBox, hippo.CanvasItem):
if pspec.name == 'value':
return self._value
def add_option(self, action_id, label, icon_name=None, icon_color=None):
def add_item(self, menu_item):
if not self._value:
self._value = action_id
self._canvas_text.props.text = label
self._value = menu_item.props.action_id
self._canvas_text.props.text = menu_item.props.label
self._menu.add_item(action_id, label, icon_name, icon_color)
self._menu.add_item(menu_item)
def add_separator(self):
self._menu.add_separator()
def _button_press_event_cb(self, box, event):
if self._menu.is_visible():
self._menu.hide()
self._menu.popdown()
else:
context = self._round_box.get_context()
#[x, y] = context.translate_to_screen(self._round_box)
[x, y] = context.translate_to_widget(self._round_box)
[x, y] = context.translate_to_screen(self._round_box)
# TODO: Any better place to do this?
self._menu.props.box_width = self.get_width_request()
[width, height] = self._round_box.get_allocation()
self._menu.show(x, y + height)
self._menu.popup(x, y + height)
def _menu_action_cb(self, menu, menu_item):
action_id = menu_item.props.action_id
label = menu_item.props.label
def _menu_action_cb(self, menu, data):
[action_id, label] = data
if action_id != self._value:
self._value = action_id
self._canvas_text.props.text = label
self.emit('changed')
def _menu_action_completed_cb(self, menu):
self._menu.popdown()

View File

@ -21,13 +21,6 @@ import gobject
import gtk
import hippo
from sugar.graphics import units
from sugar.graphics.roundbox import RoundBox
from sugar.graphics import button
from sugar.graphics import color
from sugar.graphics import font
from sugar.graphics.canvasicon import CanvasIcon
class Popup(hippo.CanvasBox, hippo.CanvasItem):
__gtype_name__ = 'SugarPopup'
@ -35,38 +28,10 @@ class Popup(hippo.CanvasBox, hippo.CanvasItem):
'action-completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
}
def __init__(self, title):
def __init__(self):
hippo.CanvasBox.__init__(self)
self.props.background_color = color.MENU_BACKGROUND.get_int()
self.props.border_color = color.MENU_BORDER.get_int()
self.props.border = units.points_to_pixels(1)
self._window = None
def add_item(self, action_id, label, icon_name=None, icon_color=None):
box = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL)
box.props.padding = 5
box.props.spacing = 5
if icon_name:
icon = CanvasIcon(icon_name=icon_name,
scale=units.SMALL_ICON_SCALE)
if icon_color:
icon.props.color = icon_color
box.append(icon)
canvas_text = hippo.CanvasText()
canvas_text.props.text = label
canvas_text.props.color = color.LABEL_TEXT.get_int()
canvas_text.props.font_desc = font.DEFAULT.get_pango_desc()
box.append(canvas_text)
box.connect('button-press-event', self._item_button_press_event_cb)
self.append(box)
def add_separator(self):
box = hippo.CanvasBox()
box.props.background_color = color.MENU_SEPARATOR.get_int()
box.props.box_height = units.points_to_pixels(1)
self.append(box)
self.connect('button-press-event', self._button_press_event_cb)
def popup(self, x, y):
if not self._window:
@ -80,5 +45,5 @@ class Popup(hippo.CanvasBox, hippo.CanvasItem):
self._window.destroy()
self._window = None
def _item_button_press_event_cb(self, item, event):
def _button_press_event_cb(self, menu, event):
self.emit('action-completed')

View File

@ -24,6 +24,7 @@ import hippo
from sugar.graphics.toolbar import Toolbar
from sugar.graphics.optionmenu import OptionMenu
from sugar.graphics.menu import MenuItem
from sugar.graphics.iconbutton import IconButton
def _option_menu_changed_cb(option_menu):
@ -56,11 +57,11 @@ OPTION_WRITE = 3
OPTION_CHAT = 4
option_menu = OptionMenu()
option_menu.add_option(OPTION_ANYTHING, _('Anything'))
option_menu.add_item(MenuItem(OPTION_ANYTHING, _('Anything')))
option_menu.add_separator()
option_menu.add_option(OPTION_DRAW, _('Draw'), 'theme:stock-close')
option_menu.add_option(OPTION_WRITE, _('Write'))
option_menu.add_option(OPTION_CHAT, _('Chat'))
option_menu.add_item(MenuItem(OPTION_DRAW, _('Draw'), 'theme:stock-close'))
option_menu.add_item(MenuItem(OPTION_WRITE, _('Write')))
option_menu.add_item(MenuItem(OPTION_CHAT, _('Chat')))
option_menu.connect('changed', _option_menu_changed_cb)
toolbar.append(option_menu)