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

View File

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

View File

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

View File

@ -44,8 +44,6 @@ class ShellModel(gobject.GObject):
self._current_activity = None self._current_activity = None
self._state = self.STATE_RUNNING self._state = self.STATE_RUNNING
self._bundle_registry = BundleRegistry()
PresenceService.start() PresenceService.start()
self._pservice = PresenceService.get_instance() self._pservice = PresenceService.get_instance()
@ -53,16 +51,10 @@ class ShellModel(gobject.GObject):
self._owner.announce() self._owner.announce()
self._friends = Friends() self._friends = Friends()
self._mesh = MeshModel(self._bundle_registry) self._mesh = MeshModel()
self._home = HomeModel(self._bundle_registry) self._home = HomeModel()
self._devices = DevicesModel() 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): def do_set_property(self, pspec, value):
if pspec.name == 'state': if pspec.name == 'state':
self._state = value self._state = value
@ -71,9 +63,6 @@ class ShellModel(gobject.GObject):
if pspec.name == 'state': if pspec.name == 'state':
return self._state return self._state
def get_bundle_registry(self):
return self._bundle_registry
def get_mesh(self): def get_mesh(self):
return self._mesh return self._mesh

View File

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

View File

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

View File

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

View File

@ -18,21 +18,6 @@
import sys import sys
import pygtk from sugar.activity import activityfactoryservice
pygtk.require('2.0')
import gobject
import gtk
import dbus.glib activityfactoryservice.run(sys.argv)
# 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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,9 +27,9 @@ from view.frame.FriendsBox import FriendsBox
from view.frame.PanelWindow import PanelWindow from view.frame.PanelWindow import PanelWindow
from view.frame.clipboardpanelwindow import ClipboardPanelWindow from view.frame.clipboardpanelwindow import ClipboardPanelWindow
from view.frame.notificationtray import NotificationTray from view.frame.notificationtray import NotificationTray
from view.frame.framepopupcontext import FramePopupContext
from model.ShellModel import ShellModel from model.ShellModel import ShellModel
from sugar.graphics.timeline import Timeline from sugar.graphics.timeline import Timeline
from sugar.graphics.menushell import MenuShell
from sugar.graphics import units from sugar.graphics import units
_ANIMATION = False _ANIMATION = False
@ -62,6 +62,12 @@ class Frame:
self._event_frame.connect('leave', self._event_frame_leave_cb) self._event_frame.connect('leave', self._event_frame_leave_cb)
self._event_frame.show() 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._top_panel = self._create_top_panel()
self._bottom_panel = self._create_bottom_panel() self._bottom_panel = self._create_bottom_panel()
self._left_panel = self._create_left_panel() self._left_panel = self._create_left_panel()
@ -70,20 +76,11 @@ class Frame:
shell.get_model().connect('notify::state', shell.get_model().connect('notify::state',
self._shell_state_changed_cb) 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): def _create_top_panel(self):
panel = self._create_panel(hippo.ORIENTATION_HORIZONTAL) panel = self._create_panel(hippo.ORIENTATION_HORIZONTAL)
menu_shell = panel.get_menu_shell()
root = panel.get_root() root = panel.get_root()
menu_shell.set_position(MenuShell.BOTTOM) box = ZoomBox(self._shell, self._popup_context)
box = ZoomBox(self._shell, menu_shell)
root.append(box) root.append(box)
tray = NotificationTray() tray = NotificationTray()
@ -103,11 +100,8 @@ class Frame:
def _create_bottom_panel(self): def _create_bottom_panel(self):
panel = self._create_panel(hippo.ORIENTATION_HORIZONTAL) panel = self._create_panel(hippo.ORIENTATION_HORIZONTAL)
menu_shell = panel.get_menu_shell()
root = panel.get_root() root = panel.get_root()
menu_shell.set_position(MenuShell.TOP)
box = ActivitiesBox(self._shell) box = ActivitiesBox(self._shell)
root.append(box) root.append(box)
@ -115,12 +109,9 @@ class Frame:
def _create_right_panel(self): def _create_right_panel(self):
panel = self._create_panel(hippo.ORIENTATION_VERTICAL) panel = self._create_panel(hippo.ORIENTATION_VERTICAL)
menu_shell = panel.get_menu_shell()
root = panel.get_root() root = panel.get_root()
menu_shell.set_position(MenuShell.LEFT) box = FriendsBox(self._shell, self._popup_context)
box = FriendsBox(self._shell, menu_shell)
root.append(box) root.append(box)
return panel return panel
@ -158,19 +149,6 @@ class Frame:
panel.connect('enter-notify-event', self._enter_notify_cb) panel.connect('enter-notify-event', self._enter_notify_cb)
panel.connect('leave-notify-event', self._leave_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): def _popup_context_activated_cb(self, popup_context):
self._timeline.goto('slide_in', True) self._timeline.goto('slide_in', True)
@ -205,8 +183,7 @@ class Frame:
def _leave_notify(self, panel): def _leave_notify(self, panel):
self._hover_frame = False self._hover_frame = False
if not panel.get_menu_shell().is_active() and \ if not self._popup_context.is_active() and \
not self._shell.get_popup_context().is_active() and \
(self._mode == Frame.HIDE_ON_LEAVE or \ (self._mode == Frame.HIDE_ON_LEAVE or \
self._mode == Frame.AUTOMATIC): self._mode == Frame.AUTOMATIC):
self._timeline.play('before_slide_out', 'slide_out') self._timeline.play('before_slide_out', 'slide_out')
@ -281,3 +258,6 @@ class Frame:
def is_visible(self): def is_visible(self):
return self._top_panel.props.visible 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 sugardir = $(pythondir)/sugar/activity
sugar_PYTHON = \ sugar_PYTHON = \
__init__.py \ __init__.py \
Activity.py \ activity.py \
ActivityFactory.py \ activityfactory.py \
bundle.py \ activityfactoryservice.py \
bundlebuilder.py \ activityhandle.py \
activityservice.py \
bundle.py \
bundlebuilder.py \
bundleregistry.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 logging
import os import os
from ConfigParser import ConfigParser from ConfigParser import ConfigParser
from sugar import env
_PYTHON_FACTORY='sugar-activity-factory'
class Bundle: class Bundle:
"""Info about an activity bundle. Wraps the activity.info file.""" """Info about an activity bundle. Wraps the activity.info file."""
def __init__(self, path): def __init__(self, path):
@ -38,11 +41,18 @@ class Bundle:
self._valid = False self._valid = False
logging.error('%s must specify a name' % self._path) 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') self._exec = cp.get(section, 'exec')
else: else:
self._exec = None
self._valid = False 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.has_option(section, 'show_launcher'):
if cp.get(section, 'show_launcher') == 'no': if cp.get(section, 'show_launcher') == 'no':
@ -71,6 +81,10 @@ class Bundle:
"""Get the activity service name""" """Get the activity service name"""
return self._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): def get_default_type(self):
"""Get the type of the main network service which tracks presence """Get the type of the main network service which tracks presence
and provides info about the activity, for example the title.""" 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""" """Get the command to execute to launch the activity factory"""
return self._exec return self._exec
def get_class(self):
"""Get the main Activity class"""
return self._exec
def get_show_launcher(self): def get_show_launcher(self):
"""Get whether there should be a visible launcher for the activity""" """Get whether there should be a visible launcher for the activity"""
return self._show_launcher return self._show_launcher

View File

@ -11,13 +11,8 @@ class _ServiceManager(object):
self._path = env.get_user_service_dir() self._path = env.get_user_service_dir()
def add(self, bundle): def add(self, bundle):
name = bundle.get_service_name() util.write_service(bundle.get_service_name(),
bundle.get_exec(), self._path)
# 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)
class BundleRegistry(gobject.GObject): class BundleRegistry(gobject.GObject):
"""Service that tracks the available activity bundles""" """Service that tracks the available activity bundles"""
@ -73,3 +68,14 @@ class BundleRegistry(gobject.GObject):
return True return True
else: else:
return False 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: except ImportError:
from sugar.__installed__ import * 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(): def is_emulator():
if os.environ.has_key('SUGAR_EMULATOR'): if os.environ.has_key('SUGAR_EMULATOR'):
if os.environ['SUGAR_EMULATOR'] == 'yes': if os.environ['SUGAR_EMULATOR'] == 'yes':
return True return True
return False 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(): def get_profile_path():
if os.environ.has_key('SUGAR_PROFILE'): if os.environ.has_key('SUGAR_PROFILE'):
profile_id = os.environ['SUGAR_PROFILE'] profile_id = os.environ['SUGAR_PROFILE']

View File

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

View File

@ -25,6 +25,7 @@ import cairo
import time import time
from sugar.graphics.iconcolor import IconColor from sugar.graphics.iconcolor import IconColor
from sugar.graphics.timeline import Timeline
class _IconCacheIcon: class _IconCacheIcon:
def __init__(self, name, color, now): def __init__(self, name, color, now):
@ -152,9 +153,17 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
self._icon_name = None self._icon_name = None
self._cache = False self._cache = False
self._handle = None 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) 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) self.connect('button-press-event', self._button_press_event_cb)
def _clear_buffers(self): def _clear_buffers(self):
@ -261,3 +270,80 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
def _button_press_event_cb(self, item, event): def _button_press_event_cb(self, item, event):
item.emit_activated() 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 canvasicon import CanvasIcon
from iconcolor import IconColor from iconcolor import IconColor
from sugar.graphics import units from sugar.graphics import units
from sugar.graphics.timeline import Timeline
from sugar import profile from sugar import profile
STANDARD_SIZE = 0 STANDARD_SIZE = 0
@ -49,100 +48,14 @@ class IconButton(CanvasIcon):
self._prelight_color = profile.get_color() self._prelight_color = profile.get_color()
self._inactive_color = IconColor('#808080,#424242') self._inactive_color = IconColor('#808080,#424242')
self._active = True self._active = True
self._popup = None
self._hover_popup = False
CanvasIcon.__init__(self, icon_name=icon_name, cache=True, CanvasIcon.__init__(self, icon_name=icon_name, cache=True,
color=self._normal_color) color=self._normal_color)
self._set_size(STANDARD_SIZE) 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) 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): def _set_size(self, size):
if size == SMALL_SIZE: if size == SMALL_SIZE:
self.props.box_width = -1 self.props.box_width = -1
@ -173,8 +86,9 @@ class IconButton(CanvasIcon):
elif pspec.name == 'active': elif pspec.name == 'active':
return self._active return self._active
else: else:
return CanvasIcon.get_property(self, pspec) return CanvasIcon.do_get_property(self, pspec)
def _button_press_event_cb(self, widget, event): def _button_press_event_cb(self, widget, event):
if self._active: if self._active:
self.emit_activated() self.emit_activated()
return True

View File

@ -14,94 +14,98 @@
# 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.
import sys
import gtk import gtk
import hippo import hippo
import gobject import gobject
from sugar.graphics.canvasicon import CanvasIcon 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): class MenuItem(hippo.CanvasBox):
__gsignals__ = { __gtype_name__ = 'SugarMenuItem'
'action': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([int])), __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): def __init__(self, action_id, label, icon_name=None, icon_color=None):
gtk.Window.__init__(self, gtk.WINDOW_POPUP) hippo.CanvasBox.__init__(self, orientation=hippo.ORIENTATION_HORIZONTAL)
canvas = hippo.Canvas() self._action_id = action_id
self.add(canvas) self.props.padding = 5
canvas.show() self.props.spacing = 5
self._root = hippo.CanvasBox() if icon_name:
canvas.set_root(self._root) 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: if title:
self._title_item = hippo.CanvasText(text=title) title_item = hippo.CanvasText(text=title)
self._root.append(self._title_item) title_item.props.color = color.LABEL_TEXT.get_int()
else: title_item.props.font_desc = font.DEFAULT.get_pango_desc()
self._title_item = None self.append(title_item)
self.add_separator()
if content_box: def add_item(self, item):
separator = self._create_separator() item.connect('button-press-event', self._item_button_press_event_cb)
self._root.append(separator) self.append(item)
self._root.append(content_box)
self._action_box = None def remove_item(self, item):
self._item_box = None self.remove(item)
def _create_separator(self): def add_separator(self):
separator = hippo.CanvasBox() box = hippo.CanvasBox()
return separator 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): def _item_button_press_event_cb(self, menu_item, event):
if self._title_item: self.emit('action', menu_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)

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 import units
from sugar.graphics.roundbox import RoundBox from sugar.graphics.roundbox import RoundBox
from sugar.graphics.menu import Menu, MenuItem
from sugar.graphics import iconbutton from sugar.graphics import iconbutton
from sugar.graphics import color from sugar.graphics import color
from sugar.graphics import font from sugar.graphics import font
from sugar.graphics.canvasicon import CanvasIcon from sugar.graphics.canvasicon import CanvasIcon
class Menu(hippo.CanvasBox, hippo.CanvasItem): class _Menu(Menu):
__gtype_name__ = 'SugarMenu'
__gsignals__ = {
'action': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([object]))
}
def __init__(self): def __init__(self):
hippo.CanvasBox.__init__(self) Menu.__init__(self)
self.props.background_color = color.MENU_BACKGROUND.get_int() self._is_visible = False
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()
def is_visible(self): 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): class OptionMenu(hippo.CanvasBox, hippo.CanvasItem):
__gtype_name__ = 'SugarOptionMenu' __gtype_name__ = 'SugarOptionMenu'
@ -122,8 +79,9 @@ class OptionMenu(hippo.CanvasBox, hippo.CanvasItem):
arrow.props.xalign = hippo.ALIGNMENT_START arrow.props.xalign = hippo.ALIGNMENT_START
self._round_box.append(arrow) self._round_box.append(arrow)
self._menu = Menu() self._menu = _Menu()
self._menu.connect('action', self._menu_action_cb) 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) self.connect('button-press-event', self._button_press_event_cb)
@ -135,33 +93,37 @@ class OptionMenu(hippo.CanvasBox, hippo.CanvasItem):
if pspec.name == 'value': if pspec.name == 'value':
return self._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: if not self._value:
self._value = action_id self._value = menu_item.props.action_id
self._canvas_text.props.text = label 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): def add_separator(self):
self._menu.add_separator() self._menu.add_separator()
def _button_press_event_cb(self, box, event): def _button_press_event_cb(self, box, event):
if self._menu.is_visible(): if self._menu.is_visible():
self._menu.hide() self._menu.popdown()
else: else:
context = self._round_box.get_context() context = self._round_box.get_context()
#[x, y] = context.translate_to_screen(self._round_box) [x, y] = context.translate_to_screen(self._round_box)
[x, y] = context.translate_to_widget(self._round_box)
# TODO: Any better place to do this? # TODO: Any better place to do this?
self._menu.props.box_width = self.get_width_request() self._menu.props.box_width = self.get_width_request()
[width, height] = self._round_box.get_allocation() [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: if action_id != self._value:
self._value = action_id self._value = action_id
self._canvas_text.props.text = label self._canvas_text.props.text = label
self.emit('changed') self.emit('changed')
def _menu_action_completed_cb(self, menu):
self._menu.popdown()

View File

@ -21,13 +21,6 @@ import gobject
import gtk import gtk
import hippo 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): class Popup(hippo.CanvasBox, hippo.CanvasItem):
__gtype_name__ = 'SugarPopup' __gtype_name__ = 'SugarPopup'
@ -35,38 +28,10 @@ class Popup(hippo.CanvasBox, hippo.CanvasItem):
'action-completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])) 'action-completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
} }
def __init__(self, title): def __init__(self):
hippo.CanvasBox.__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 self._window = None
self.connect('button-press-event', self._button_press_event_cb)
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)
def popup(self, x, y): def popup(self, x, y):
if not self._window: if not self._window:
@ -80,5 +45,5 @@ class Popup(hippo.CanvasBox, hippo.CanvasItem):
self._window.destroy() self._window.destroy()
self._window = None self._window = None
def _item_button_press_event_cb(self, item, event): def _button_press_event_cb(self, menu, event):
self.emit('action-completed') self.emit('action-completed')

View File

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