Cleanup the source structure

This commit is contained in:
Marco Pesenti Gritti
2007-10-16 11:04:59 +02:00
parent 087856f233
commit 6240c1cf6f
87 changed files with 110 additions and 139 deletions
+2
View File
@@ -0,0 +1,2 @@
_sugarext.c
_sugarext.c
+1
View File
@@ -0,0 +1 @@
LGPL
+74
View File
@@ -0,0 +1,74 @@
SUBDIRS = activity bundle clipboard graphics objects presence datastore
sugardir = $(pythondir)/sugar
sugar_PYTHON = \
env.py \
network.py \
profile.py \
util.py \
wm.py
pkgpyexecdir = $(pythondir)/sugar
pkgpyexec_LTLIBRARIES = _sugarext.la
_sugarext_la_CFLAGS = \
$(LIB_CFLAGS) \
$(PYTHON_INCLUDES)
_sugarext_la_LDFLAGS = -module -avoid-version
_sugarext_la_LIBADD = $(LIB_LIBS)
_sugarext_la_SOURCES = \
$(BUILT_SOURCES) \
_sugarextmodule.c \
eggaccelerators.c \
eggaccelerators.h \
sexy-icon-entry.h \
sexy-icon-entry.c \
sugar-address-entry.c \
sugar-address-entry.h \
sugar-key-grabber.c \
sugar-key-grabber.h \
sugar-menu.h \
sugar-menu.c \
sugar-x11-util.c \
sugar-x11-util.h
nodist__sugarext_la_SOURCES = \
_sugarext.c \
sugar-marshal.c \
sugar-marshal.h
_sugarext.c: _sugarext.defs _sugarext.override
.defs.c:
(cd $(srcdir)\
&& $(PYGTK_CODEGEN) \
--register $(PYGTK_DEFSDIR)/gdk-types.defs \
--register $(PYGTK_DEFSDIR)/gtk-types.defs \
--override $*.override \
--prefix py$* $*.defs) > gen-$*.c \
&& cp gen-$*.c $*.c \
&& rm -f gen-$*.c
stamp_files = \
stamp-sugar-marshal.c \
stamp-sugar-marshal.h
sugar-marshal.c: stamp-sugar-marshal.c
@true
stamp-sugar-marshal.c: sugar-marshal.list
$(GLIB_GENMARSHAL) --prefix=sugar_marshal \
$(srcdir)/sugar-marshal.list --header --body > \
sugar-marshal.c && echo timestamp > $(@F)
sugar-marshal.h: stamp-sugar-marshal.h
@true
stamp-sugar-marshal.h: sugar-marshal.list
$(GLIB_GENMARSHAL) --prefix=sugar_marshal \
$(srcdir)/sugar-marshal.list --header > \
sugar-marshal.h && echo timestamp > $(@F)
CLEANFILES = $(stamp_files)
EXTRA_DIST = sugar-marshal.list _sugarext.defs _sugarext.override
+171
View File
@@ -0,0 +1,171 @@
;; -*- scheme -*-
; object definitions
(define-object AddressEntry
(in-module "Sugar")
(parent "GtkEntry")
(c-name "SugarAddressEntry")
(gtype-id "SUGAR_TYPE_ADDRESS_ENTRY")
)
(define-object KeyGrabber
(in-module "Sugar")
(parent "GObject")
(c-name "SugarKeyGrabber")
(gtype-id "SUGAR_TYPE_KEY_GRABBER")
)
(define-object Menu
(in-module "Sugar")
(parent "GtkMenu")
(c-name "SugarMenu")
(gtype-id "SUGAR_TYPE_MENU")
)
(define-object IconEntry
(in-module "Sexy")
(parent "GtkEntry")
(c-name "SexyIconEntry")
(gtype-id "SEXY_TYPE_ICON_ENTRY")
)
;; Enumerations and flags ...
(define-enum IconEntryPosition
(in-module "Sexy")
(c-name "SexyIconEntryPosition")
(gtype-id "SEXY_TYPE_ICON_ENTRY_POSITION")
(values
'("primary" "SEXY_ICON_ENTRY_PRIMARY")
'("secondary" "SEXY_ICON_ENTRY_SECONDARY")
)
)
;; From sugar-menu.h
(define-method set_active
(of-object "SugarMenu")
(c-name "sugar_menu_set_active")
(return-type "none")
(parameters
'("gboolean" "active")
)
)
(define-method embed
(of-object "SugarMenu")
(c-name "sugar_menu_embed")
(return-type "none")
(parameters
'("GtkContainer" "container")
)
)
(define-method unembed
(of-object "SugarMenu")
(c-name "sugar_menu_unembed")
(return-type "none")
)
;; From sugar-key-grabber.h
(define-function sugar_key_grabber_get_type
(c-name "sugar_key_grabber_get_type")
(return-type "GType")
)
(define-method grab
(of-object "SugarKeyGrabber")
(c-name "sugar_key_grabber_grab")
(return-type "none")
(parameters
'("const-char*" "key")
)
)
(define-method get_key
(of-object "SugarKeyGrabber")
(c-name "sugar_key_grabber_get_key")
(return-type "char*")
(parameters
'("guint" "keycode")
'("guint" "state")
)
)
; functions
(define-function x11_set_string_property
(c-name "sugar_x11_util_set_string_property")
(parameters
'("GdkWindow*" "window")
'("const-char*" "property")
'("const-char*" "value")
)
)
(define-function x11_get_string_property
(c-name "sugar_x11_util_get_string_property")
(return-type "char*")
(parameters
'("GdkWindow*" "window")
'("const-char*" "property")
)
)
;; From sexy-icon-entry.h
(define-function sexy_icon_entry_get_type
(c-name "sexy_icon_entry_get_type")
(return-type "GType")
)
(define-function sexy_icon_entry_new
(c-name "sexy_icon_entry_new")
(is-constructor-of "SexyIconEntry")
(return-type "GtkWidget*")
)
(define-method set_icon
(of-object "SexyIconEntry")
(c-name "sexy_icon_entry_set_icon")
(return-type "none")
(parameters
'("SexyIconEntryPosition" "position")
'("GtkImage*" "icon")
)
)
(define-method set_icon_highlight
(of-object "SexyIconEntry")
(c-name "sexy_icon_entry_set_icon_highlight")
(return-type "none")
(parameters
'("SexyIconEntryPosition" "position")
'("gboolean" "highlight")
)
)
(define-method get_icon
(of-object "SexyIconEntry")
(c-name "sexy_icon_entry_get_icon")
(return-type "GtkImage*")
(parameters
'("SexyIconEntryPosition" "position")
)
)
(define-method get_icon_highlight
(of-object "SexyIconEntry")
(c-name "sexy_icon_entry_get_icon_highlight")
(return-type "gboolean")
(parameters
'("SexyIconEntryPosition" "position")
)
)
(define-method add_clear_button
(of-object "SexyIconEntry")
(c-name "sexy_icon_entry_add_clear_button")
(return-type "none")
)
+29
View File
@@ -0,0 +1,29 @@
/* -*- Mode: C; c-basic-offset: 4 -*- */
%%
headers
#include <Python.h>
#include "pygobject.h"
#include "sugar-address-entry.h"
#include "sugar-key-grabber.h"
#include "sugar-menu.h"
#include "sugar-x11-util.h"
#include "sexy-icon-entry.h"
#include <pygtk/pygtk.h>
#include <glib.h>
%%
modulename _sugarext
%%
import gobject.GObject as PyGObject_Type
import gtk.Entry as PyGtkEntry_Type
import gtk.Menu as PyGtkMenu_Type
import gtk.Container as PyGtkContainer_Type
import gtk.gdk.Window as PyGdkWindow_Type
import gtk.Image as PyGtkImage_Type
%%
ignore-glob
*_get_type
_*
%%
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2006-2007, 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/* include this first, before NO_IMPORT_PYGOBJECT is defined */
#include <pygobject.h>
extern PyMethodDef py_sugarext_functions[];
void py_sugarext_register_classes (PyObject *d);
void py_sugarext_add_constants (PyObject *module, const gchar *strip_prefix);
DL_EXPORT(void)
init_sugarext(void)
{
PyObject *m, *d;
init_pygobject ();
m = Py_InitModule ("_sugarext", py_sugarext_functions);
d = PyModule_GetDict (m);
py_sugarext_register_classes (d);
py_sugarext_add_constants(m, "SEXY_");
if (PyErr_Occurred ()) {
Py_FatalError ("can't initialise module _sugarext");
}
}
+9
View File
@@ -0,0 +1,9 @@
sugardir = $(pythondir)/sugar/activity
sugar_PYTHON = \
__init__.py \
activity.py \
activityfactory.py \
activityhandle.py \
activityservice.py \
bundlebuilder.py \
registry.py
+58
View File
@@ -0,0 +1,58 @@
# Copyright (C) 2006-2007, 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.
"""Activity implementation code for Sugar-based activities
Each activity within the OLPC environment must provide two
dbus services. The first, patterned after the
sugar.activity.activityfactory.ActivityFactory
class is responsible for providing a "create" method which
takes a small dictionary with values corresponding to a
sugar.activity.activityhandle.ActivityHandle
describing an individual instance of the activity.
Each activity so registered is described by a
sugar.activity.bundle.Bundle
instance, which parses a specially formatted activity.info
file (stored in the activity directory's ./activity
subdirectory). The
sugar.activity.bundlebuilder
module provides facilities for the standard setup.py module
which produces and registers bundles from activity source
directories.
Once instantiated by the ActivityFactory's create method,
each activity must provide an introspection API patterned
after the
sugar.activity.activityservice.ActivityService
class. This class allows for querying the ID of the root
window, requesting sharing across the network, and basic
"what type of application are you" queries.
"""
from sugar.activity.registry import ActivityRegistry
from sugar.activity.registry import get_registry
from sugar.activity.registry import ActivityInfo
View File
+633
View File
@@ -0,0 +1,633 @@
"""Base class for Python-coded activities
This is currently the only reference for what an
activity must do to participate in the Sugar desktop.
"""
# Copyright (C) 2006-2007 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 gettext import gettext as _
import logging
import os
import time
import tempfile
from hashlib import sha1
import gtk, gobject
import dbus
import json
from sugar import util
from sugar.presence import presenceservice
from sugar.activity.activityservice import ActivityService
from sugar.graphics import style
from sugar.graphics.window import Window
from sugar.graphics.toolbox import Toolbox
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.toolcombobox import ToolComboBox
from sugar.datastore import datastore
from sugar import wm
from sugar import profile
from sugar import _sugarbaseext
SCOPE_PRIVATE = "private"
SCOPE_INVITE_ONLY = "invite" # shouldn't be shown in UI, it's implicit when you invite somebody
SCOPE_NEIGHBORHOOD = "public"
class ActivityToolbar(gtk.Toolbar):
def __init__(self, activity):
gtk.Toolbar.__init__(self)
self._activity = activity
self._updating_share = False
activity.connect('shared', self._activity_shared_cb)
activity.connect('joined', self._activity_shared_cb)
activity.connect('notify::max_participants',
self._max_participants_changed_cb)
if activity.metadata:
self.title = gtk.Entry()
self.title.set_size_request(int(gtk.gdk.screen_width() / 6), -1)
self.title.set_text(activity.metadata['title'])
self.title.connect('changed', self._title_changed_cb)
self._add_widget(self.title)
activity.metadata.connect('updated', self._jobject_updated_cb)
separator = gtk.SeparatorToolItem()
separator.props.draw = False
separator.set_expand(True);
self.insert(separator, -1)
separator.show()
self.share = ToolComboBox(label_text=_('Share with:'))
self.share.combo.connect('changed', self._share_changed_cb)
self.share.combo.append_item(SCOPE_PRIVATE, _('Private'),
'zoom-home-mini')
self.share.combo.append_item(SCOPE_NEIGHBORHOOD, _('My Neighborhood'),
'zoom-neighborhood-mini')
self.insert(self.share, -1)
self.share.show()
self._update_share()
self.keep = ToolButton('document-save')
self.keep.set_tooltip(_('Keep'))
self.keep.connect('clicked', self._keep_clicked_cb)
self.insert(self.keep, -1)
self.keep.show()
self.stop = ToolButton('activity-stop')
self.stop.set_tooltip(_('Stop'))
self.stop.connect('clicked', self._stop_clicked_cb)
self.insert(self.stop, -1)
self.stop.show()
self._update_title_sid = None
def _update_share(self):
self._updating_share = True
if self._activity.props.max_participants == 1:
self.share.hide()
if self._activity.get_shared():
self.share.set_sensitive(False)
self.share.combo.set_active(1)
else:
self.share.set_sensitive(True)
self.share.combo.set_active(0)
self._updating_share = False
def _share_changed_cb(self, combo):
if self._updating_share:
return
model = self.share.combo.get_model()
it = self.share.combo.get_active_iter()
(scope, ) = model.get(it, 0)
if scope == SCOPE_NEIGHBORHOOD:
self._activity.share()
def _keep_clicked_cb(self, button):
self._activity.copy()
def _stop_clicked_cb(self, button):
self._activity.close()
def _jobject_updated_cb(self, jobject):
self.title.set_text(jobject['title'])
def _title_changed_cb(self, entry):
if not self._update_title_sid:
self._update_title_sid = gobject.timeout_add(1000, self._update_title_cb)
def _update_title_cb(self):
title = self.title.get_text()
self._activity.metadata['title'] = title
self._activity.metadata['title_set_by_user'] = '1'
self._activity.save()
shared_activity = self._activity._shared_activity
if shared_activity:
shared_activity.props.name = title
self._update_title_sid = None
return False
def _add_widget(self, widget, expand=False):
tool_item = gtk.ToolItem()
tool_item.set_expand(expand)
tool_item.add(widget)
widget.show()
self.insert(tool_item, -1)
tool_item.show()
def _activity_shared_cb(self, activity):
self._update_share()
def _max_participants_changed_cb(self, activity, pspec):
self._update_share()
class EditToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
self.undo = ToolButton('edit-undo')
self.undo.set_tooltip(_('Undo'))
self.insert(self.undo, -1)
self.undo.show()
self.redo = ToolButton('edit-redo')
self.redo.set_tooltip(_('Redo'))
self.insert(self.redo, -1)
self.redo.show()
self.separator = gtk.SeparatorToolItem()
self.separator.set_draw(True)
self.insert(self.separator, -1)
self.separator.show()
self.copy = ToolButton('edit-copy')
self.copy.set_tooltip(_('Copy'))
self.insert(self.copy, -1)
self.copy.show()
self.paste = ToolButton('edit-paste')
self.paste.set_tooltip(_('Paste'))
self.insert(self.paste, -1)
self.paste.show()
class ActivityToolbox(Toolbox):
def __init__(self, activity):
Toolbox.__init__(self)
self._activity_toolbar = ActivityToolbar(activity)
self.add_toolbar('Activity', self._activity_toolbar)
self._activity_toolbar.show()
def get_activity_toolbar(self):
return self._activity_toolbar
class Activity(Window, gtk.Container):
"""Base Activity class that all other Activities derive from."""
__gtype_name__ = 'SugarActivity'
__gsignals__ = {
'shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
}
__gproperties__ = {
'active' : (bool, None, None, False,
gobject.PARAM_READWRITE),
'max-participants': (int, None, None, 0, 1000, 0,
gobject.PARAM_READWRITE)
}
def __init__(self, handle, create_jobject=True):
"""Initialise the Activity
handle -- sugar.activity.activityhandle.ActivityHandle
instance providing the activity id and access to the
presence service which *may* provide sharing for this
application
create_jobject -- boolean
define if it should create a journal object if we are
not resuming
Side effects:
Sets the gdk screen DPI setting (resolution) to the
Sugar screen resolution.
Connects our "destroy" message to our _destroy_cb
method.
Creates a base gtk.Window within this window.
Creates an ActivityService (self._bus) servicing
this application.
"""
Window.__init__(self)
# process titles will only show 15 characters
# but they get truncated anyway so if more characters
# are supported in the future we will get a better view
# of the processes
proc_title = "%s <%s>" % (get_bundle_name(), handle.activity_id)
util.set_proc_title(proc_title)
self.connect('realize', self._realize_cb)
self.connect('delete-event', self.__delete_event_cb)
self._active = False
self._activity_id = handle.activity_id
self._pservice = presenceservice.get_instance()
self._shared_activity = None
self._share_id = None
self._join_id = None
self._preview = None
self._updating_jobject = False
self._closing = False
self._deleting = False
self._max_participants = 0
self._invites_queue = []
self._bus = ActivityService(self)
self._owns_file = False
share_scope = SCOPE_PRIVATE
if handle.object_id:
self._jobject = datastore.get(handle.object_id)
# TODO: Don't create so many objects until we have versioning
# support in the datastore
#self._jobject.object_id = ''
#del self._jobject.metadata['ctime']
del self._jobject.metadata['mtime']
self.set_title(self._jobject.metadata['title'])
if self._jobject.metadata.has_key('share-scope'):
share_scope = self._jobject.metadata['share-scope']
elif create_jobject:
logging.debug('Creating a jobject.')
self._jobject = datastore.create()
self._jobject.metadata['title'] = _('%s Activity') % get_bundle_name()
self.set_title(self._jobject.metadata['title'])
self._jobject.metadata['title_set_by_user'] = '0'
self._jobject.metadata['activity'] = self.get_bundle_id()
self._jobject.metadata['activity_id'] = self.get_id()
self._jobject.metadata['keep'] = '0'
self._jobject.metadata['preview'] = ''
self._jobject.metadata['share-scope'] = SCOPE_PRIVATE
if self._shared_activity is not None:
icon_color = self._shared_activity.props.color
else:
icon_color = profile.get_color().to_string()
self._jobject.metadata['icon-color'] = icon_color
self._jobject.file_path = ''
datastore.write(self._jobject,
reply_handler=self._internal_jobject_create_cb,
error_handler=self._internal_jobject_error_cb)
else:
self._jobject = None
# handle activity share/join
mesh_instance = self._pservice.get_activity(self._activity_id)
logging.debug("*** Act %s, mesh instance %r, scope %s" % (self._activity_id, mesh_instance, share_scope))
if mesh_instance:
# There's already an instance on the mesh, join it
logging.debug("*** Act %s joining existing mesh instance" % self._activity_id)
self._shared_activity = mesh_instance
self._shared_activity.connect('notify::private',
self._privacy_changed_cb)
self._join_id = self._shared_activity.connect("joined", self._internal_joined_cb)
if not self._shared_activity.props.joined:
self._shared_activity.join()
else:
self._internal_joined_cb(self._shared_activity, True, None)
elif share_scope != SCOPE_PRIVATE:
logging.debug("*** Act %s no existing mesh instance, but used to be shared, will share" % self._activity_id)
# no existing mesh instance, but activity used to be shared, so
# restart the share
if share_scope == SCOPE_INVITE_ONLY:
self.share(private=True)
elif share_scope == SCOPE_NEIGHBORHOOD:
self.share(private=False)
else:
logging.debug("Unknown share scope %r" % share_scope)
def do_set_property(self, pspec, value):
if pspec.name == 'active':
if self._active != value:
self._active = value
if not self._active and self._jobject:
self.save()
elif pspec.name == 'max-participants':
self._max_participants = value
def do_get_property(self, pspec):
if pspec.name == 'active':
return self._active
elif pspec.name == 'max-participants':
return self._max_participants
def get_id(self):
return self._activity_id
def get_bundle_id(self):
return _sugarbaseext.get_prgname()
def set_canvas(self, canvas):
Window.set_canvas(self, canvas)
canvas.connect('map', self._canvas_map_cb)
def _canvas_map_cb(self, canvas):
if self._jobject and self._jobject.file_path:
self.read_file(self._jobject.file_path)
def _internal_jobject_create_cb(self):
pass
def _internal_jobject_error_cb(self, err):
logging.debug("Error creating activity datastore object: %s" % err)
def get_activity_root(self):
"""
Return the appropriate location in the fs where to store activity related
data that doesn't pertain to the current execution of the activity and
thus cannot go into the DataStore.
"""
if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \
os.environ['SUGAR_ACTIVITY_ROOT']:
return os.environ['SUGAR_ACTIVITY_ROOT']
else:
return '/'
def read_file(self, file_path):
"""
Subclasses implement this method if they support resuming objects from
the journal. 'file_path' is the file to read from.
"""
raise NotImplementedError
def write_file(self, file_path):
"""
Subclasses implement this method if they support saving data to objects
in the journal. 'file_path' is the file to write to.
"""
raise NotImplementedError
def _internal_save_cb(self):
logging.debug('Activity._internal_save_cb')
self._updating_jobject = False
if self._closing:
self._cleanup_jobject()
self.destroy()
def _internal_save_error_cb(self, err):
logging.debug('Activity._internal_save_error_cb')
self._updating_jobject = False
if self._closing:
self._cleanup_jobject()
self.destroy()
logging.debug("Error saving activity object to datastore: %s" % err)
def _cleanup_jobject(self):
if self._jobject:
if self._owns_file and os.path.isfile(self._jobject.file_path):
logging.debug('_cleanup_jobject: removing %r' % self._jobject.file_path)
os.remove(self._jobject.file_path)
self._owns_file = False
self._jobject.destroy()
self._jobject = None
def _get_preview(self):
preview_pixbuf = self.get_canvas_screenshot()
if preview_pixbuf is None:
return None
preview_pixbuf = preview_pixbuf.scale_simple(style.zoom(300),
style.zoom(225),
gtk.gdk.INTERP_BILINEAR)
# TODO: Find a way of taking a png out of the pixbuf without saving to a temp file.
# Impementing gtk.gdk.Pixbuf.save_to_buffer in pygtk would solve this.
fd, file_path = tempfile.mkstemp('.png')
del fd
preview_pixbuf.save(file_path, 'png')
f = open(file_path)
try:
preview_data = f.read()
finally:
f.close()
os.remove(file_path)
return preview_data
def _get_buddies(self):
if self._shared_activity is not None:
buddies = {}
for buddy in self._shared_activity.get_joined_buddies():
if not buddy.props.owner:
buddy_id = sha1(buddy.props.key).hexdigest()
buddies[buddy_id] = [buddy.props.nick, buddy.props.color]
return buddies
else:
return {}
def save(self):
"""Request that the activity is saved to the Journal."""
logging.debug('Activity.save: %r' % self._jobject.object_id)
if self._updating_jobject:
logging.info('Activity.save: still processing a previous request.')
return
buddies_dict = self._get_buddies()
if buddies_dict:
self.metadata['buddies_id'] = json.write(buddies_dict.keys())
self.metadata['buddies'] = json.write(self._get_buddies())
if self._preview is None:
self.metadata['preview'] = ''
else:
self.metadata['preview'] = dbus.ByteArray(self._preview)
try:
if self._jobject.file_path:
self.write_file(self._jobject.file_path)
else:
file_path = os.path.join(tempfile.gettempdir(), '%i' % time.time())
self.write_file(file_path)
self._owns_file = True
self._jobject.file_path = file_path
except NotImplementedError:
pass
# Cannot call datastore.write async for creates: https://dev.laptop.org/ticket/3071
if self._jobject.object_id is None:
datastore.write(self._jobject, transfer_ownership=True)
else:
self._updating_jobject = True
datastore.write(self._jobject,
transfer_ownership=True,
reply_handler=self._internal_save_cb,
error_handler=self._internal_save_error_cb)
def copy(self):
logging.debug('Activity.copy: %r' % self._jobject.object_id)
self._preview = self._get_preview()
self.save()
self._jobject.object_id = None
def _privacy_changed_cb(self, shared_activity, param_spec):
if shared_activity.props.private:
self._jobject.metadata['share-scope'] = SCOPE_INVITE_ONLY
else:
self._jobject.metadata['share-scope'] = SCOPE_NEIGHBORHOOD
def _internal_joined_cb(self, activity, success, err):
"""Callback when join has finished"""
self._shared_activity.disconnect(self._join_id)
self._join_id = None
if not success:
logging.debug("Failed to join activity: %s" % err)
return
self.present()
self.emit('joined')
self._privacy_changed_cb(self._shared_activity, None)
def get_shared(self):
"""Returns TRUE if the activity is shared on the mesh."""
if not self._shared_activity:
return False
return self._shared_activity.props.joined
def _internal_share_cb(self, ps, success, activity, err):
self._pservice.disconnect(self._share_id)
self._share_id = None
if not success:
logging.debug('Share of activity %s failed: %s.' % (self._activity_id, err))
return
logging.debug('Share of activity %s successful.' % self._activity_id)
activity.props.name = self._jobject.metadata['title']
self._shared_activity = activity
self._shared_activity.connect('notify::private',
self._privacy_changed_cb)
self.emit('shared')
self._privacy_changed_cb(self._shared_activity, None)
self._send_invites()
def _invite_response_cb(self, error):
if error:
logging.error('Invite failed: %s' % error)
def _send_invites(self):
while self._invites_queue:
buddy_key = self._invites_queue.pop()
buddy = self._pservice.get_buddy(buddy_key)
if buddy:
self._shared_activity.invite(buddy, '', self._invite_response_cb)
else:
logging.error('Cannot invite %s, no such buddy.' % buddy_key)
def invite(self, buddy_key):
self._invites_queue.append(buddy_key)
if (self._shared_activity is None
or not self._shared_activity.props.joined):
self.share(True)
else:
self._send_invites()
def share(self, private=False):
"""Request that the activity be shared on the network.
private -- bool: True to share by invitation only,
False to advertise as shared to everyone.
Once the activity is shared, its privacy can be changed by setting
its 'private' property.
"""
# FIXME: Make private=True to turn on the by-invitation-only scope
if self._shared_activity and self._shared_activity.props.joined:
raise RuntimeError("Activity %s already shared." %
self._activity_id)
verb = private and 'private' or 'public'
logging.debug('Requesting %s share of activity %s.' %
(verb, self._activity_id))
self._share_id = self._pservice.connect("activity-shared",
self._internal_share_cb)
self._pservice.share_activity(self, private=private)
def close(self):
self._preview = self._get_preview()
self.save()
if self._shared_activity:
self._shared_activity.leave()
if self._updating_jobject:
self._closing = True
else:
self.destroy()
def _realize_cb(self, window):
wm.set_bundle_id(window.window, self.get_bundle_id())
wm.set_activity_id(window.window, self._activity_id)
def __delete_event_cb(self, widget, event):
self.close()
return True
def get_metadata(self):
if self._jobject:
return self._jobject.metadata
else:
return None
metadata = property(get_metadata, None)
def get_bundle_name():
"""Return the bundle name for the current process' bundle
"""
return _sugarbaseext.get_application_name()
def get_bundle_path():
"""Return the bundle path for the current process' bundle
"""
return os.environ['SUGAR_BUNDLE_PATH']
+256
View File
@@ -0,0 +1,256 @@
"""Shell side object which manages request to start activity"""
# Copyright (C) 2006-2007 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 subprocess
import dbus
import gobject
import gtk
from sugar.presence import presenceservice
from sugar.activity.activityhandle import ActivityHandle
from sugar.activity import registry
from sugar.datastore import datastore
from sugar import util
from sugar import env
import os
# #3903 - this constant can be removed and assumed to be 1 when dbus-python
# 0.82.3 is the only version used
if dbus.version >= (0, 82, 3):
DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND = 1
else:
DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND = 1000
_SHELL_SERVICE = "org.laptop.Shell"
_SHELL_PATH = "/org/laptop/Shell"
_SHELL_IFACE = "org.laptop.Shell"
_DS_SERVICE = "org.laptop.sugar.DataStore"
_DS_INTERFACE = "org.laptop.sugar.DataStore"
_DS_PATH = "/org/laptop/sugar/DataStore"
_ACTIVITY_FACTORY_INTERFACE = "org.laptop.ActivityFactory"
_RAINBOW_SERVICE_NAME = "org.laptop.security.Rainbow"
_RAINBOW_ACTIVITY_FACTORY_PATH = "/"
_RAINBOW_ACTIVITY_FACTORY_INTERFACE = "org.laptop.security.Rainbow"
def create_activity_id():
"""Generate a new, unique ID for this activity"""
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.props.id:
found = True
break
if not found:
return act_id
raise RuntimeError("Cannot generate unique activity id.")
def get_environment(activity):
environ = os.environ.copy()
bin_path = os.path.join(activity.path, 'bin')
environ['SUGAR_BUNDLE_PATH'] = activity.path
environ['PATH'] = bin_path + ':' + environ['PATH']
return environ
def get_command(activity, activity_id=None, object_id=None, uri=None):
if not activity_id:
activity_id = create_activity_id()
command = activity.command
command += ' -b %s' % activity.bundle_id
command += ' -a %s' % activity_id
if object_id is not None:
command += ' -o %s' % object_id
if uri is not None:
command += ' -u %s' % uri
return command
def open_log_file(activity, activity_id):
for i in range(1, 100):
path = env.get_logs_path('%s-%s.log' % (activity.bundle_id, i))
if not os.path.exists(path):
return open(path, 'w')
class ActivityCreationHandler(gobject.GObject):
"""Sugar-side activity creation interface
This object uses a dbus method on the ActivityFactory
service to create the new activity. It generates
GObject events in response to the success/failure of
activity startup using callbacks to the service's
create call.
"""
def __init__(self, service_name, handle):
"""Initialise the handler
service_name -- the service name of the bundle factory
activity_handle -- stores the values which are to
be passed to the service to uniquely identify
the activity to be created and the sharing
service that may or may not be connected with it
sugar.activity.activityhandle.ActivityHandle instance
calls the "create" method on the service for this
particular activity type and registers the
_reply_handler and _error_handler methods on that
call's results.
The specific service which creates new instances of this
particular type of activity is created during the activity
registration process in shell bundle registry which creates
service definition files for each registered bundle type.
If the file '/etc/olpc-security' exists, then activity launching
will be delegated to the prototype 'Rainbow' security service.
"""
gobject.GObject.__init__(self)
self._service_name = service_name
self._handle = handle
bus = dbus.SessionBus()
bus_object = bus.get_object(_SHELL_SERVICE, _SHELL_PATH)
self._shell = dbus.Interface(bus_object, _SHELL_IFACE)
if handle.activity_id is not None and \
handle.object_id is None:
datastore = dbus.Interface(
bus.get_object(_DS_SERVICE, _DS_PATH), _DS_INTERFACE)
datastore.find({ 'activity_id': self._handle.activity_id }, [],
reply_handler=self._find_object_reply_handler,
error_handler=self._find_object_error_handler)
else:
self._launch_activity()
def _launch_activity(self):
if self._handle.activity_id != None:
self._shell.ActivateActivity(self._handle.activity_id,
reply_handler=self._activate_reply_handler,
error_handler=self._activate_error_handler)
else:
self._create_activity()
def _create_activity(self):
if self._handle.activity_id is None:
self._handle.activity_id = create_activity_id()
self._shell.NotifyLaunch(
self._service_name, self._handle.activity_id,
reply_handler=self._no_reply_handler,
error_handler=self._notify_launch_error_handler)
if not os.path.exists('/etc/olpc-security'):
activity_registry = registry.get_registry()
activity = activity_registry.get_activity(self._service_name)
if activity:
env = get_environment(activity)
log_file = open_log_file(activity, self._handle.activity_id)
command = get_command(activity, self._handle.activity_id,
self._handle.object_id,
self._handle.uri)
process = subprocess.Popen(command, env=env, shell=True,
cwd=activity.path, stdout=log_file,
stderr=log_file)
else:
system_bus = dbus.SystemBus()
factory = system_bus.get_object(_RAINBOW_SERVICE_NAME,
_RAINBOW_ACTIVITY_FACTORY_PATH)
stdio_paths = {'stdout': '/logs/stdout', 'stderr': '/logs/stderr'}
factory.CreateActivity(
self._service_name,
self._handle.get_dict(),
stdio_paths,
timeout=120 * DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND,
reply_handler=self._create_reply_handler,
error_handler=self._create_error_handler,
dbus_interface=_RAINBOW_ACTIVITY_FACTORY_INTERFACE)
def _no_reply_handler(self, *args):
pass
def _notify_launch_failure_error_handler(self, err):
logging.error('Notify launch failure failed %s' % err)
def _notify_launch_error_handler(self, err):
logging.debug('Notify launch failed %s' % err)
def _activate_reply_handler(self, activated):
if not activated:
self._create_activity()
def _activate_error_handler(self, err):
logging.error("Activity activation request failed %s" % err)
def _create_reply_handler(self, xid):
logging.debug("Activity created %s (%s)." %
(self._handle.activity_id, self._service_name))
def _create_error_handler(self, err):
logging.error("Couldn't create activity %s (%s): %s" %
(self._handle.activity_id, self._service_name, err))
self._shell.NotifyLaunchFailure(
self._handle.activity_id, reply_handler=self._no_reply_handler,
error_handler=self._notify_launch_failure_error_handler)
def _find_object_reply_handler(self, jobjects, count):
if count > 0:
if count > 1:
logging.debug("Multiple objects has the same activity_id.")
self._handle.object_id = jobjects[0]['uid']
self._create_activity()
def _find_object_error_handler(self, err):
logging.error("Datastore find failed %s" % err)
self._create_activity()
def create(service_name, activity_handle=None):
"""Create a new activity from its name."""
if not activity_handle:
activity_handle = ActivityHandle()
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(uri=uri)
return ActivityCreationHandler(service_name, activity_handle)
def create_with_object_id(service_name, object_id):
"""Create a new activity and pass the object id as handle."""
activity_handle = ActivityHandle(object_id=object_id)
return ActivityCreationHandler(service_name, activity_handle)
+68
View File
@@ -0,0 +1,68 @@
# Copyright (C) 2006-2007 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):
"""Data structure storing simple activity metadata"""
def __init__(
self, activity_id=None, object_id=None, uri=None
):
"""Initialise the handle from activity_id
activity_id -- unique id for the activity to be
created
object_id -- identity of the journal object
associated with the activity. It was used by
the journal prototype implementation, might
change when we do the real one.
When you resume an activity from the journal
the object_id will be passed in. It's optional
since new activities does not have an
associated object (yet).
XXX Not clear how this relates to the activity
id yet, i.e. not sure we really need both. TBF
uri -- URI associated with the activity. Used when
opening an external file or resource in the
activity, rather than a journal object
(downloads stored on the file system for
example or web pages)
"""
self.activity_id = activity_id
self.object_id = object_id
self.uri = uri
def get_dict(self):
"""Retrieve our settings as a dictionary"""
result = { 'activity_id' : self.activity_id }
if self.object_id:
result['object_id'] = self.object_id
if self.uri:
result['uri'] = self.uri
return result
def create_from_dict(handle_dict):
"""Create a handle from a dictionary of parameters"""
result = ActivityHandle(
handle_dict['activity_id'],
object_id = handle_dict.get('object_id'),
uri = handle_dict.get('uri'),
)
return result
+66
View File
@@ -0,0 +1,66 @@
# Copyright (C) 2006-2007 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 dbus.service
_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):
"""Initialise the service for the given activity
activity -- sugar.activity.activity.Activity instance
Creates dbus services that use the instance's activity_id
as discriminants among all active services
of this type. That is, the services are all available
as names/paths derived from the instance's activity_id.
The various methods exposed on dbus are just forwarded
to the client Activity object's equally-named methods.
"""
activity.realize()
activity_id = activity.get_id()
service_name = _ACTIVITY_SERVICE_NAME + activity_id
object_path = _ACTIVITY_SERVICE_PATH + "/" + activity_id
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 SetActive(self, active):
logging.debug('ActivityService.set_active: %s.' % active)
self._activity.props.active = active
@dbus.service.method(_ACTIVITY_INTERFACE)
def Invite(self, buddy_key):
self._activity.invite(buddy_key)
+403
View File
@@ -0,0 +1,403 @@
# Copyright (C) 2006-2007 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 sys
import os
import zipfile
import shutil
import subprocess
import re
import gettext
from sugar import env
from sugar.bundle.activitybundle import ActivityBundle
class _SvnFileList(list):
def __init__(self):
f = os.popen('svn list -R')
for line in f.readlines():
filename = line.strip()
if os.path.isfile(filename):
self.append(filename)
f.close()
class _GitFileList(list):
def __init__(self):
f = os.popen('git-ls-files')
for line in f.readlines():
filename = line.strip()
if not filename.startswith('.'):
self.append(filename)
f.close()
class _DefaultFileList(list):
def __init__(self):
for name in os.listdir('activity'):
if name.endswith('.svg'):
self.append(os.path.join('activity', name))
self.append('activity/activity.info')
if os.path.isfile(_get_source_path('NEWS')):
self.append('NEWS')
class _ManifestFileList(_DefaultFileList):
def __init__(self, manifest):
_DefaultFileList.__init__(self)
self.append(manifest)
f = open(manifest,'r')
for line in f.readlines():
stripped_line = line.strip()
if stripped_line and not stripped_line in self:
self.append(stripped_line)
f.close()
def _extract_bundle(source_file, dest_dir):
if not os.path.exists(dest_dir):
os.mkdir(dest_dir)
zf = zipfile.ZipFile(source_file)
for i, name in enumerate(zf.namelist()):
path = os.path.join(dest_dir, name)
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
outfile = open(path, 'wb')
outfile.write(zf.read(name))
outfile.flush()
outfile.close()
def _get_source_path(path=None):
if path:
return os.path.join(os.getcwd(), path)
else:
return os.getcwd()
def _get_bundle_dir():
bundle_name = os.path.basename(_get_source_path())
return bundle_name + '.activity'
def _get_install_dir(prefix):
return os.path.join(prefix, 'share/activities')
def _get_package_name(bundle_name):
bundle = ActivityBundle(_get_source_path())
zipname = '%s-%d.xo' % (bundle_name, bundle.get_activity_version())
return zipname
def _delete_backups(arg, dirname, names):
for name in names:
if name.endswith('~') or name.endswith('pyc'):
os.remove(os.path.join(dirname, name))
def _get_bundle_id():
bundle = ActivityBundle(_get_source_path())
return bundle.get_bundle_id()
def cmd_help():
print 'Usage: \n\
setup.py dev - setup for development \n\
setup.py dist - create a bundle package \n\
setup.py install [dirname] - install the bundle \n\
setup.py uninstall [dirname] - uninstall the bundle \n\
setup.py genpot - generate the gettext pot file \n\
setup.py genl10n - generate localization files \n\
setup.py clean - clean the directory \n\
setup.py release - do a new release of the bundle \n\
setup.py help - print this message \n\
'
def cmd_dev():
bundle_path = env.get_user_activities_path()
if not os.path.isdir(bundle_path):
os.mkdir(bundle_path)
bundle_path = os.path.join(bundle_path, _get_bundle_dir())
try:
os.symlink(_get_source_path(), bundle_path)
except OSError:
if os.path.islink(bundle_path):
print 'ERROR - The bundle has been already setup for development.'
else:
print 'ERROR - A bundle with the same name is already installed.'
def _get_file_list(manifest):
if os.path.isfile(manifest):
return _ManifestFileList(manifest)
elif os.path.isdir('.git'):
return _GitFileList()
elif os.path.isdir('.svn'):
return _SvnFileList()
else:
return _DefaultFileList()
def _get_po_list(manifest):
file_list = {}
po_regex = re.compile("po/(.*)\.po$")
for file_name in _get_file_list(manifest):
match = po_regex.match(file_name)
if match:
file_list[match.group(1)] = file_name
return file_list
def _get_l10n_list(manifest):
l10n_list = []
for lang in _get_po_list(manifest).keys():
filename = _get_bundle_id() + '.mo'
l10n_list.append(os.path.join('locale', lang, 'LC_MESSAGES', filename))
l10n_list.append(os.path.join('locale', lang, 'activity.linfo'))
return l10n_list
def _get_activity_name():
info_path = os.path.join(_get_source_path(), 'activity', 'activity.info')
f = open(info_path,'r')
info = f.read()
f.close()
match = re.search('^name\s*=\s*(.*)$', info, flags = re.MULTILINE)
return match.group(1)
def cmd_dist(bundle_name, manifest):
cmd_genl10n(bundle_name, manifest)
file_list = _get_file_list(manifest)
zipname = _get_package_name(bundle_name)
bundle_zip = zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED)
base_dir = bundle_name + '.activity'
for filename in file_list:
bundle_zip.write(filename, os.path.join(base_dir, filename))
for filename in _get_l10n_list(manifest):
bundle_zip.write(filename, os.path.join(base_dir, filename))
bundle_zip.close()
def cmd_install(bundle_name, manifest, prefix):
cmd_dist(bundle_name, manifest)
cmd_uninstall(prefix)
_extract_bundle(_get_package_name(bundle_name),
_get_install_dir(prefix))
def cmd_uninstall(prefix):
path = os.path.join(_get_install_dir(prefix), _get_bundle_dir())
if os.path.isdir(path):
shutil.rmtree(path)
def cmd_genpot(bundle_name, manifest):
po_path = os.path.join(_get_source_path(), 'po')
if not os.path.isdir(po_path):
os.mkdir(po_path)
python_files = []
file_list = _get_file_list(manifest)
for file_name in file_list:
if file_name.endswith('.py'):
python_files.append(file_name)
# First write out a stub .pot file containing just the translated
# activity name, then have xgettext merge the rest of the
# translations into that. (We can't just append the activity name
# to the end of the .pot file afterwards, because that might
# create a duplicate msgid.)
pot_file = os.path.join('po', '%s.pot' % bundle_name)
activity_name = _get_activity_name()
escaped_name = re.sub('([\\\\"])', '\\\\\\1', activity_name)
f = open(pot_file, 'w')
f.write('#: activity/activity.info:2\n')
f.write('msgid "%s"\n' % escaped_name)
f.write('msgstr ""\n')
f.close()
args = [ 'xgettext', '--join-existing', '--language=Python',
'--keyword=_', '--add-comments=TRANS:', '--output=%s' % pot_file ]
args += python_files
retcode = subprocess.call(args)
if retcode:
print 'ERROR - xgettext failed with return code %i.' % retcode
for file_name in _get_po_list(manifest).values():
args = [ 'msgmerge', '-U', file_name, pot_file ]
retcode = subprocess.call(args)
if retcode:
print 'ERROR - msgmerge failed with return code %i.' % retcode
def cmd_genl10n(bundle_name, manifest):
source_path = _get_source_path()
activity_name = _get_activity_name()
po_list = _get_po_list(manifest)
for lang in po_list.keys():
file_name = po_list[lang]
localedir = os.path.join(source_path, 'locale', lang)
mo_path = os.path.join(localedir, 'LC_MESSAGES')
if not os.path.isdir(mo_path):
os.makedirs(mo_path)
mo_file = os.path.join(mo_path, "%s.mo" % _get_bundle_id())
args = ["msgfmt", "--output-file=%s" % mo_file, file_name]
retcode = subprocess.call(args)
if retcode:
print 'ERROR - msgfmt failed with return code %i.' % retcode
cat = gettext.GNUTranslations(open(mo_file, 'r'))
translated_name = cat.gettext(activity_name)
linfo_file = os.path.join(localedir, 'activity.linfo')
f = open(linfo_file, 'w')
f.write('[Activity]\nname = %s\n' % translated_name)
f.close()
def cmd_release(bundle_name, manifest):
if not os.path.isdir('.git'):
print 'ERROR - this command works only for git repositories'
retcode = subprocess.call(['git', 'pull'])
if retcode:
print 'ERROR - cannot pull from git'
print 'Bumping activity version...'
info_path = os.path.join(_get_source_path(), 'activity', 'activity.info')
f = open(info_path,'r')
info = f.read()
f.close()
exp = re.compile('activity_version\s?=\s?([0-9]*)')
match = re.search(exp, info)
version = int(match.group(1)) + 1
info = re.sub(exp, 'activity_version = %d' % version, info)
f = open(info_path, 'w')
f.write(info)
f.close()
news_path = os.path.join(_get_source_path(), 'NEWS')
if os.environ.has_key('SUGAR_NEWS'):
print 'Update NEWS.sugar...'
sugar_news_path = os.environ['SUGAR_NEWS']
if os.path.isfile(sugar_news_path):
f = open(sugar_news_path,'r')
sugar_news = f.read()
f.close()
else:
sugar_news = ''
sugar_news += '%s - %d\n\n' % (bundle_name, version)
f = open(news_path,'r')
for line in f.readlines():
if len(line.strip()) > 0:
sugar_news += line
else:
break
f.close()
sugar_news += '\n'
f = open(sugar_news_path, 'w')
f.write(sugar_news)
f.close()
print 'Update NEWS...'
f = open(news_path,'r')
news = f.read()
f.close()
news = '%d\n\n' % version + news
f = open(news_path, 'w')
f.write(news)
f.close()
print 'Committing to git...'
changelog = 'Release version %d.' % version
retcode = subprocess.call(['git', 'commit', '-a', '-m % s' % changelog])
if retcode:
print 'ERROR - cannot commit to git'
retcode = subprocess.call(['git', 'push'])
if retcode:
print 'ERROR - cannot push to git'
print 'Creating the bundle...'
cmd_dist(bundle_name, manifest)
if os.environ.has_key('ACTIVITIES_REPOSITORY'):
print 'Uploading to the activities repository...'
repo = os.environ['ACTIVITIES_REPOSITORY']
server, path = repo.split(':')
retcode = subprocess.call(['ssh', server, 'rm',
'%s/%s*' % (path, bundle_name)])
if retcode:
print 'ERROR - cannot remove old bundles from the repository.'
bundle_path = os.path.join(_get_source_path(),
_get_package_name(bundle_name))
retcode = subprocess.call(['scp', bundle_path, repo])
if retcode:
print 'ERROR - cannot upload the bundle to the repository.'
print 'Done.'
def cmd_clean():
os.path.walk('.', _delete_backups, None)
def sanity_check():
if not os.path.isfile(_get_source_path('NEWS')):
print 'WARNING: NEWS file is missing.'
def start(bundle_name, manifest='MANIFEST'):
sanity_check()
if len(sys.argv) < 2:
cmd_help()
elif sys.argv[1] == 'build':
pass
elif sys.argv[1] == 'dev':
cmd_dev()
elif sys.argv[1] == 'dist':
cmd_dist(bundle_name, manifest)
elif sys.argv[1] == 'install' and len(sys.argv) == 3:
cmd_install(bundle_name, manifest, sys.argv[2])
elif sys.argv[1] == 'uninstall' and len(sys.argv) == 3:
cmd_uninstall(sys.argv[2])
elif sys.argv[1] == 'genpot':
cmd_genpot(bundle_name, manifest)
elif sys.argv[1] == 'genl10n':
cmd_genl10n(bundle_name, manifest)
elif sys.argv[1] == 'clean':
cmd_clean()
elif sys.argv[1] == 'release':
cmd_release(bundle_name, manifest)
else:
cmd_help()
if __name__ == '__main__':
start()
+156
View File
@@ -0,0 +1,156 @@
# Copyright (C) 2006-2007 Red Hat, Inc.
# 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 logging
import dbus
import gobject
_ACTIVITY_REGISTRY_SERVICE_NAME = 'org.laptop.ActivityRegistry'
_ACTIVITY_REGISTRY_IFACE = 'org.laptop.ActivityRegistry'
_ACTIVITY_REGISTRY_PATH = '/org/laptop/ActivityRegistry'
def _activity_info_from_dict(info_dict):
if not info_dict:
return None
return ActivityInfo(info_dict['name'], info_dict['icon'],
info_dict['bundle_id'], info_dict['path'],
info_dict['show_launcher'], info_dict['command'])
class ActivityInfo(object):
def __init__(self, name, icon, bundle_id,
path, show_launcher, command):
self.name = name
self.icon = icon
self.bundle_id = bundle_id
self.path = path
self.command = command
self.show_launcher = show_launcher
class ActivityRegistry(gobject.GObject):
__gsignals__ = {
'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
def __init__(self):
gobject.GObject.__init__(self)
bus = dbus.SessionBus()
# NOTE: We need to follow_name_owner_changes here
# because we can not connect to a signal unless
# we follow the changes or we start the service
# before we connect. Starting the service here
# causes a major bottleneck during startup
bus_object = bus.get_object(_ACTIVITY_REGISTRY_SERVICE_NAME,
_ACTIVITY_REGISTRY_PATH,
follow_name_owner_changes = True)
self._registry = dbus.Interface(bus_object, _ACTIVITY_REGISTRY_IFACE)
self._registry.connect_to_signal('ActivityAdded', self._activity_added_cb)
self._registry.connect_to_signal('ActivityRemoved', self._activity_removed_cb)
# Two caches fo saving some travel across dbus.
self._service_name_to_activity_info = {}
self._mime_type_to_activities = {}
def _convert_info_list(self, info_list):
result = []
for info_dict in info_list:
result.append(_activity_info_from_dict(info_dict))
return result
def get_activities(self):
info_list = self._registry.GetActivities()
return self._convert_info_list(info_list)
def _get_activities_cb(self, reply_handler, info_list):
result = []
i = 0
for info_dict in info_list:
result.append(_activity_info_from_dict(info_dict))
reply_handler(result)
def _get_activities_error_cb(self, error_handler, e):
if error_handler:
error_handler(e)
else:
logging.error('Error getting activities async: %s' % str(e))
def get_activities_async(self, reply_handler=None, error_handler=None):
if not reply_handler:
logging.error('Function get_activities_async called without a reply handler. Can not run.')
return
self._registry.GetActivities(
reply_handler=lambda info_list:self._get_activities_cb(reply_handler, info_list),
error_handler=lambda e:self._get_activities_error_cb(error_handler, e))
def get_activity(self, service_name):
if self._service_name_to_activity_info.has_key(service_name):
return self._service_name_to_activity_info[service_name]
info_dict = self._registry.GetActivity(service_name)
activity_info = _activity_info_from_dict(info_dict)
self._service_name_to_activity_info[service_name] = activity_info
return activity_info
def find_activity(self, name):
info_list = self._registry.FindActivity(name)
return self._convert_info_list(info_list)
def get_activities_for_type(self, mime_type):
if self._mime_type_to_activities.has_key(mime_type):
return self._mime_type_to_activities[mime_type]
info_list = self._registry.GetActivitiesForType(mime_type)
activities = self._convert_info_list(info_list)
self._mime_type_to_activities[mime_type] = activities
return activities
def add_bundle(self, bundle_path):
return self._registry.AddBundle(bundle_path)
def _activity_added_cb(self, info_dict):
logging.debug('ActivityRegistry._activity_added_cb: flushing caches')
self._service_name_to_activity_info.clear()
self._mime_type_to_activities.clear()
self.emit('activity-added', _activity_info_from_dict(info_dict))
def remove_bundle(self, bundle_path):
return self._registry.RemoveBundle(bundle_path)
def _activity_removed_cb(self, info_dict):
logging.debug('ActivityRegistry._activity_removed_cb: flushing caches')
self._service_name_to_activity_info.clear()
self._mime_type_to_activities.clear()
self.emit('activity-removed', _activity_info_from_dict(info_dict))
_registry = None
def get_registry():
global _registry
if not _registry:
_registry = ActivityRegistry()
return _registry
+6
View File
@@ -0,0 +1,6 @@
sugardir = $(pythondir)/sugar/bundle
sugar_PYTHON = \
__init__.py \
bundle.py \
activitybundle.py \
contentbundle.py
+16
View File
@@ -0,0 +1,16 @@
# Copyright (C) 2006-2007, 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.
+285
View File
@@ -0,0 +1,285 @@
# Copyright (C) 2007, 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.
"""Sugar activity bundles"""
from ConfigParser import ConfigParser
import locale
import os
import tempfile
from sugar.bundle.bundle import Bundle, MalformedBundleException
from sugar import activity
from sugar import env
class ActivityBundle(Bundle):
"""A Sugar activity bundle
See http://wiki.laptop.org/go/Activity_bundles for details
"""
MIME_TYPE = 'application/vnd.olpc-sugar'
DEPRECATED_MIME_TYPE = 'application/vnd.olpc-x-sugar'
_zipped_extension = '.xo'
_unzipped_extension = '.activity'
_infodir = 'activity'
def __init__(self, path):
Bundle.__init__(self, path)
self.activity_class = None
self.bundle_exec = None
self._name = None
self._icon = None
self._bundle_id = None
self._mime_types = None
self._show_launcher = True
self._activity_version = 0
info_file = self._get_file('activity/activity.info')
if info_file is None:
raise MalformedBundleException('No activity.info file')
self._parse_info(info_file)
linfo_file = self._get_linfo_file()
if linfo_file:
self._parse_linfo(linfo_file)
def _parse_info(self, info_file):
cp = ConfigParser()
cp.readfp(info_file)
section = 'Activity'
if cp.has_option(section, 'bundle_id'):
self._bundle_id = cp.get(section, 'bundle_id')
# FIXME deprecated
elif cp.has_option(section, 'service_name'):
self._bundle_id = cp.get(section, 'service_name')
else:
raise MalformedBundleException(
'Activity bundle %s does not specify a bundle id' %
self._path)
if cp.has_option(section, 'name'):
self._name = cp.get(section, 'name')
else:
raise MalformedBundleException(
'Activity bundle %s does not specify a name' % self._path)
# FIXME class is deprecated
if cp.has_option(section, 'class'):
self.activity_class = cp.get(section, 'class')
elif cp.has_option(section, 'exec'):
self.bundle_exec = cp.get(section, 'exec')
else:
raise MalformedBundleException(
'Activity bundle %s must specify either class or exec' %
self._path)
if cp.has_option(section, 'mime_types'):
mime_list = cp.get(section, 'mime_types')
self._mime_types = mime_list.strip(';').split(';')
if cp.has_option(section, 'show_launcher'):
if cp.get(section, 'show_launcher') == 'no':
self._show_launcher = False
if cp.has_option(section, 'icon'):
self._icon = cp.get(section, 'icon')
if cp.has_option(section, 'activity_version'):
version = cp.get(section, 'activity_version')
try:
self._activity_version = int(version)
except ValueError:
raise MalformedBundleException(
'Activity bundle %s has invalid version number %s' %
(self._path, version))
def _get_linfo_file(self):
lang = locale.getdefaultlocale()[0]
if not lang:
return None
linfo_path = os.path.join('locale', lang, 'activity.linfo')
linfo_file = self._get_file(linfo_path)
if linfo_file is not None:
return linfo_file
linfo_path = os.path.join('locale', lang[:2], 'activity.linfo')
linfo_file = self._get_file(linfo_path)
if linfo_file is not None:
return linfo_file
return None
def _parse_linfo(self, linfo_file):
cp = ConfigParser()
cp.readfp(linfo_file)
section = 'Activity'
if cp.has_option(section, 'name'):
self._name = cp.get(section, 'name')
def get_locale_path(self):
"""Get the locale path inside the (installed) activity bundle."""
if not self._unpacked:
raise NotInstalledException
return os.path.join(self._path, 'locale')
def get_icons_path(self):
"""Get the icons path inside the (installed) activity bundle."""
if not self._unpacked:
raise NotInstalledException
return os.path.join(self._path, 'icons')
def get_path(self):
"""Get the activity bundle path."""
return self._path
def get_name(self):
"""Get the activity user visible name."""
return self._name
def get_bundle_id(self):
"""Get the activity bundle id"""
return self._bundle_id
# FIXME: this should return the icon data, not a filename, so that
# we don't need to create a temp file in the zip case
def get_icon(self):
"""Get the activity icon name"""
icon_path = os.path.join('activity', self._icon + '.svg')
if self._unpacked:
return os.path.join(self._path, icon_path)
else:
icon_data = self._get_file(icon_path).read()
temp_file, temp_file_path = tempfile.mkstemp(self._icon)
os.write(temp_file, icon_data)
os.close(temp_file)
return temp_file_path
def get_activity_version(self):
"""Get the activity version"""
return self._activity_version
def get_command(self):
"""Get the command to execute to launch the activity factory"""
if self.bundle_exec:
command = os.path.expandvars(self.bundle_exec)
else:
command = 'sugar-activity ' + self.activity_class
return command
def get_mime_types(self):
"""Get the MIME types supported by the activity"""
return self._mime_types
def get_show_launcher(self):
"""Get whether there should be a visible launcher for the activity"""
return self._show_launcher
def is_installed(self):
if activity.get_registry().get_activity(self._bundle_id):
return True
else:
return False
def install(self):
if self.is_installed():
raise AlreadyInstalledException
install_dir = env.get_user_activities_path()
self._unzip(install_dir)
install_path = os.path.join(install_dir, self._zip_root_dir)
xdg_data_home = os.getenv('XDG_DATA_HOME', os.path.expanduser('~/.local/share'))
mime_path = os.path.join(install_path, 'activity', 'mimetypes.xml')
if os.path.isfile(mime_path):
mime_dir = os.path.join(xdg_data_home, 'mime')
mime_pkg_dir = os.path.join(mime_dir, 'packages')
if not os.path.isdir(mime_pkg_dir):
os.makedirs(mime_pkg_dir)
installed_mime_path = os.path.join(mime_pkg_dir, '%s.xml' % self._bundle_id)
os.symlink(mime_path, installed_mime_path)
os.spawnlp(os.P_WAIT, 'update-mime-database',
'update-mime-database', mime_dir)
mime_types = self.get_mime_types()
if mime_types is not None:
installed_icons_dir = os.path.join(xdg_data_home,
'icons/sugar/scalable/mimetypes')
if not os.path.isdir(installed_icons_dir):
os.makedirs(installed_icons_dir)
for mime_type in mime_types:
mime_icon_base = os.path.join(install_path, 'activity',
mime_type.replace('/', '-'))
svg_file = mime_icon_base + '.svg'
info_file = mime_icon_base + '.icon'
if os.path.isfile(svg_file):
os.symlink(svg_file,
os.path.join(installed_icons_dir,
os.path.basename(svg_file)))
if os.path.isfile(info_file):
os.symlink(info_file,
os.path.join(installed_icons_dir,
os.path.basename(info_file)))
if not activity.get_registry().add_bundle(install_path):
raise RegistrationException
def uninstall(self):
if self._unpacked:
install_path = self._path
else:
if not self.is_installed():
raise NotInstalledException
install_path = os.path.join(env.get_user_activities_path(),
self._zip_root_dir)
xdg_data_home = os.getenv('XDG_DATA_HOME', os.path.expanduser('~/.local/share'))
mime_dir = os.path.join(xdg_data_home, 'mime')
installed_mime_path = os.path.join(mime_dir, 'packages', '%s.xml' % self._bundle_id)
if os.path.exists(installed_mime_path):
os.remove(installed_mime_path)
os.spawnlp(os.P_WAIT, 'update-mime-database',
'update-mime-database', mime_dir)
mime_types = self.get_mime_types()
if mime_types is not None:
installed_icons_dir = os.path.join(xdg_data_home,
'icons/sugar/scalable/mimetypes')
for file in os.listdir(installed_icons_dir):
path = os.path.join(installed_icons_dir, file)
if os.path.islink(path) and \
os.readlink(path).startswith(install_path):
os.remove(path)
self._uninstall(install_path)
if not activity.get_registry().remove_bundle(install_path):
raise RegistrationException
+146
View File
@@ -0,0 +1,146 @@
# Copyright (C) 2007, 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.
"""Sugar bundle file handler"""
import os
import StringIO
import zipfile
class AlreadyInstalledException(Exception): pass
class NotInstalledException(Exception): pass
class InvalidPathException(Exception): pass
class ZipExtractException(Exception): pass
class RegistrationException(Exception): pass
class MalformedBundleException(Exception): pass
class Bundle:
"""A Sugar activity, content module, etc.
The bundle itself may be either a zip file or a directory
hierarchy, with metadata about the bundle stored various files
inside it.
This is an abstract base class. See ActivityBundle and
ContentBundle for more details on those bundle types.
"""
def __init__(self, path):
self._path = path
if os.path.isdir(self._path):
self._unpacked = True
else:
self._unpacked = False
self._check_zip_bundle()
# manifest = self._get_file(self._infodir + '/contents')
# if manifest is None:
# raise MalformedBundleException('No manifest file')
#
# signature = self._get_file(self._infodir + '/contents.sig')
# if signature is None:
# raise MalformedBundleException('No signature file')
def _check_zip_bundle(self):
zip_file = zipfile.ZipFile(self._path)
file_names = zip_file.namelist()
if len(file_names) == 0:
raise MalformedBundleException('Empty zip file')
if file_names[0] == 'mimetype':
del file_names[0]
self._zip_root_dir = file_names[0].split('/')[0]
if self._unzipped_extension is not None:
(name, ext) = os.path.splitext(self._zip_root_dir)
if ext != self._unzipped_extension:
raise MalformedBundleException(
'All files in the bundle must be inside a single ' +
'directory whose name ends with "%s"' %
self._unzipped_extension)
for file_name in file_names:
if not file_name.startswith(self._zip_root_dir):
raise MalformedBundleException(
'All files in the bundle must be inside a single ' +
'top-level directory')
def _get_file(self, filename):
file = None
if self._unpacked:
path = os.path.join(self._path, filename)
if os.path.isfile(path):
file = open(path)
else:
zip_file = zipfile.ZipFile(self._path)
path = os.path.join(self._zip_root_dir, filename)
try:
data = zip_file.read(path)
file = StringIO.StringIO(data)
except KeyError:
# == "file not found"
pass
zip_file.close()
return file
def get_path(self):
"""Get the bundle path."""
return self._path
def _unzip(self, install_dir):
if self._unpacked:
raise AlreadyInstalledException
if not os.path.isdir(install_dir):
os.mkdir(install_dir)
# zipfile provides API that in theory would let us do this
# correctly by hand, but handling all the oddities of
# Windows/UNIX mappings, extension attributes, deprecated
# features, etc makes it impractical.
# FIXME: use manifest
if os.spawnlp(os.P_WAIT, 'unzip', 'unzip', self._path,
'-x', 'mimetype', '-d', install_dir):
raise ZipExtractException
def _zip(self, bundle_path):
if not self._unpacked:
raise NotInstalledException
# FIXME: use manifest
zip = zipfile.ZipFile(bundle_path, 'w', zipfile.ZIP_DEFLATED)
for root, dirs, files in os.walk(self._path):
for name in files:
zip.write(filename, os.path.join(base_dir, filename))
zip.close()
def _uninstall(self, install_path):
if not os.path.isdir(install_path):
raise InvalidPathException
if self._unzipped_extension is not None:
ext = os.path.splitext(install_path)[1]
if ext != self._unzipped_extension:
raise InvalidPathException
for root, dirs, files in os.walk(install_path, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
os.rmdir(install_path)
+189
View File
@@ -0,0 +1,189 @@
# Copyright (C) 2007, 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.
"""Sugar content bundles"""
from ConfigParser import ConfigParser
import os
from sugar import env
from sugar.bundle.bundle import Bundle, NotInstalledException
class ContentBundle(Bundle):
"""A Sugar content bundle
See http://wiki.laptop.org/go/Content_bundles for details
"""
MIME_TYPE = 'application/vnd.olpc-content'
_zipped_extension = '.xol'
_unzipped_extension = None
_infodir = 'library'
def __init__(self, path):
Bundle.__init__(self, path)
info_file = self._get_file('library/library.info')
if info_file is None:
raise MalformedBundleException('No library.info file')
self._parse_info(info_file)
if (self._get_file('index.html') is None and
self._get_file('library/library.xml') is None):
raise MalformedBundleException(
'Content bundle %s has neither index.html nor library.xml' %
self._path)
def _parse_info(self, info_file):
cp = ConfigParser()
cp.readfp(info_file)
section = 'Library'
if cp.has_option(section, 'host_version'):
version = cp.get(section, 'host_version')
try:
if int(version) != 1:
raise MalformedBundleException(
'Content bundle %s has unknown host_version number %s' %
(self._path, version))
except ValueError:
raise MalformedBundleException(
'Content bundle %s has invalid host_version number %s' %
(self._path, version))
if cp.has_option(section, 'name'):
self._name = cp.get(section, 'name')
else:
raise MalformedBundleException(
'Content bundle %s does not specify a name' % self._path)
if cp.has_option(section, 'library_version'):
version = cp.get(section, 'library_version')
try:
self._library_version = int(version)
except ValueError:
raise MalformedBundleException(
'Content bundle %s has invalid version number %s' %
(self._path, version))
if cp.has_option(section, 'l10n'):
l10n = cp.get(section, 'l10n')
if l10n == 'true':
self._l10n = True
elif l10n == 'false':
self._l10n = False
else:
raise MalformedBundleException(
'Content bundle %s has invalid l10n key "%s"' %
(self._path, l10n))
else:
raise MalformedBundleException(
'Content bundle %s does not specify if it is localized' %
self._path)
if cp.has_option(section, 'locale'):
self._locale = cp.get(section, 'locale')
else:
raise MalformedBundleException(
'Content bundle %s does not specify a locale' % self._path)
if cp.has_option(section, 'category'):
self._category = cp.get(section, 'category')
else:
raise MalformedBundleException(
'Content bundle %s does not specify a category' % self._path)
if cp.has_option(section, 'category_icon'):
self._category_icon = cp.get(section, 'category_icon')
else:
self._category_icon = None
if cp.has_option(section, 'category_class'):
self._category_class = cp.get(section, 'category_class')
else:
self._category_class = None
if cp.has_option(section, 'subcategory'):
self._subcategory = cp.get(section, 'subcategory')
else:
self._subcategory = None
if cp.has_option(section, 'bundle_class'):
self._bundle_class = cp.get(section, 'bundle_class')
else:
self._bundle_class = None
def get_name(self):
return self._name
def get_library_version(self):
return self._library_version
def get_l10n(self):
return self._l10n
def get_locale(self):
return self._locale
def get_category(self):
return self._category
def get_category(self):
return self._category
def get_category_icon(self):
return self._category_icon
def get_category_class(self):
return self._category_class
def get_subcategory(self):
return self._subcategory
def get_bundle_class(self):
return self._bundle_class
def _run_indexer(self):
os.spawnlp(os.P_WAIT, 'python',
'python',
os.path.join(env.get_user_library_path(), 'makeIndex.py'))
def is_installed(self):
if self._unpacked:
return True
elif os.path.isdir(os.path.join(env.get_user_library_path(),
self._zip_root_dir)):
return True
else:
return False
def install(self):
self._unzip(env.get_user_library_path())
self._run_indexer()
def uninstall(self):
if self._unpacked:
if not self.is_installed():
raise NotInstalledException
install_dir = self._path
else:
install_dir = os.path.join(env.get_user_library_path(),
self._zip_root_dir)
self._uninstall(install_dir)
self._run_indexer()
+5
View File
@@ -0,0 +1,5 @@
sugardir = $(pythondir)/sugar/clipboard
sugar_PYTHON = \
__init__.py \
clipboardservice.py
+22
View File
@@ -0,0 +1,22 @@
"""Client-code's interface to the ClipboardService
Provides a simplified API for accessing the dbus service
which coordinates clipboard operations within Sugar.
"""
# 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.
+229
View File
@@ -0,0 +1,229 @@
"""UI class to access system-level clipboard object"""
# 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 logging
import dbus
import gobject
NAME_KEY = 'NAME'
PERCENT_KEY = 'PERCENT'
ICON_KEY = 'ICON'
PREVIEW_KEY = 'PREVIEW'
ACTIVITIES_KEY = 'ACTIVITIES'
FORMATS_KEY = 'FORMATS'
TYPE_KEY = 'TYPE'
DATA_KEY = 'DATA'
ON_DISK_KEY = 'ON_DISK'
DBUS_SERVICE = "org.laptop.Clipboard"
DBUS_INTERFACE = "org.laptop.Clipboard"
DBUS_PATH = "/org/laptop/Clipboard"
class ClipboardService(gobject.GObject):
"""GUI interfaces for the system clipboard dbus service
This object is used to provide convenient access to the clipboard
service (see source/services/clipboard/clipboardservice.py). It
provides utility methods for adding/getting/removing objects from
the clipboard as well as generating events when such events occur.
Meaning is source/services/clipboard/clipboardobject.py
objects when describing "objects" on the clipboard.
"""
__gsignals__ = {
'object-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([str, str])),
'object-deleted': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([str])),
'object-state-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([str, str, int, str, str, object])),
}
def __init__(self):
"""Initialise the ClipboardService instance
If the service is not yet active in the background uses
a signal watcher to connect when the service appears.
"""
gobject.GObject.__init__(self)
self._dbus_service = None
bus = dbus.SessionBus()
self._nameOwnerChangedHandler = bus.add_signal_receiver(
self._name_owner_changed_cb,
signal_name="NameOwnerChanged",
dbus_interface="org.freedesktop.DBus",
arg0=DBUS_SERVICE)
self._connected = False
# Try to register to ClipboardService, if we fail, we'll try later.
try:
self._connect_clipboard_signals()
except dbus.DBusException, exception:
logging.debug(exception)
def _connect_clipboard_signals(self):
"""Connect dbus signals to our GObject signal generating callbacks"""
bus = dbus.SessionBus()
if not self._connected:
# NOTE: We need to follow_name_owner_changes here
# because we can not connect to a signal unless
# we follow the changes or we start the service
# before we connect. Starting the service here
# causes a major bottleneck during startup
proxy_obj = bus.get_object(DBUS_SERVICE,
DBUS_PATH,
follow_name_owner_changes=True)
self._dbus_service = dbus.Interface(proxy_obj, DBUS_SERVICE)
self._dbus_service.connect_to_signal('object_added',
self._object_added_cb)
self._dbus_service.connect_to_signal('object_deleted',
self._object_deleted_cb)
self._dbus_service.connect_to_signal('object_state_changed',
self._object_state_changed_cb)
self._connected = True
bus.remove_signal_receiver(self._nameOwnerChangedHandler)
def _name_owner_changed_cb(self, name, old, new):
"""On backend service creation, connect to the server"""
if not old and new:
# ClipboardService started up
self._connect_clipboard_signals()
def _object_added_cb(self, object_id, name):
"""Emit an object-added GObject event when dbus event arrives"""
self.emit('object-added', str(object_id), name)
def _object_deleted_cb(self, object_id):
"""Emit an object-deleted GObject event when dbus event arrives"""
self.emit('object-deleted', str(object_id))
def _object_state_changed_cb(self, object_id, values):
"""Emit an object-state-changed GObject event when dbus event arrives
GObject event has:
object_id
name
percent
icon
preview
activities
From the ClipboardObject instance which is being described.
"""
self.emit('object-state-changed', str(object_id), values[NAME_KEY],
values[PERCENT_KEY], values[ICON_KEY], values[PREVIEW_KEY],
values[ACTIVITIES_KEY])
def add_object(self, name):
"""Add a new object to the path
returns dbus path-name for the new object's cliboard service,
this is used for all future references to the cliboard object.
Note:
That service is actually provided by the clipboard
service object, not the ClipboardObject
"""
return str(self._dbus_service.add_object(name))
def add_object_format(self, object_id, formatType, data, on_disk):
"""Annotate given object on the clipboard with new information
object_id -- dbus path as returned from add_object
formatType -- XXX what should this be? mime type?
data -- storage format for the clipped object?
on_disk -- whether the data is on-disk (non-volatile) or in
memory (volatile)
Last three arguments are just passed directly to the
clipboardobject.Format instance on the server side.
returns None
"""
self._dbus_service.add_object_format(dbus.ObjectPath(object_id),
formatType,
data,
on_disk)
def delete_object(self, object_id):
"""Remove the given object from the clipboard
object_id -- dbus path as returned from add_object
"""
self._dbus_service.delete_object(dbus.ObjectPath(object_id))
def set_object_percent(self, object_id, percent):
"""Set the "percentage" for the given clipboard object
object_id -- dbus path as returned from add_object
percentage -- numeric value from 0 to 100 inclusive
Object percentages which are set to 100% trigger "file-completed"
operations, see the backend ClipboardService's
_handle_file_completed method for details.
returns None
"""
self._dbus_service.set_object_percent(dbus.ObjectPath(object_id), percent)
def get_object(self, object_id):
"""Retrieve the clipboard object structure for given object
object_id -- dbus path as returned from add_object
Retrieves the metadata description of a given object, but
*not* the data for the object. Use get_object_data passing
one of the values in the FORMATS_KEY value in order to
retrieve the data.
returns dictionary with
NAME_KEY: str,
PERCENT_KEY: number,
ICON_KEY: str,
PREVIEW_KEY: XXX what is it?,
ACTIVITIES_KEY: activities that can open this object,
FORMATS_KEY: list of XXX what is it?
"""
return self._dbus_service.get_object(dbus.ObjectPath(object_id),)
def get_object_data(self, object_id, formatType):
"""Retrieve object's data in the given formatType
object_id -- dbus path as returned from add_object
formatType -- format specifier XXX of what description
returns dictionary with
TYPE_KEY: str,
DATA_KEY: str,
ON_DISK_KEY: bool
"""
return self._dbus_service.get_object_data(dbus.ObjectPath(object_id),
formatType,
byte_arrays=True)
_clipboard_service = None
def get_instance():
"""Retrieve this process's interface to the clipboard service"""
global _clipboard_service
if not _clipboard_service:
_clipboard_service = ClipboardService()
return _clipboard_service
+5
View File
@@ -0,0 +1,5 @@
sugardir = $(pythondir)/sugar/datastore
sugar_PYTHON = \
__init__.py \
dbus_helpers.py \
datastore.py
+16
View File
@@ -0,0 +1,16 @@
# 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.
+307
View File
@@ -0,0 +1,307 @@
# 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 logging
import time
from datetime import datetime
import os
import gobject
from sugar.datastore import dbus_helpers
from sugar import activity
from sugar.activity.activityhandle import ActivityHandle
from sugar.bundle.contentbundle import ContentBundle
from sugar.bundle.activitybundle import ActivityBundle
from sugar.bundle.contentbundle import ContentBundle
from sugar import mime
class DSMetadata(gobject.GObject):
__gsignals__ = {
'updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([]))
}
def __init__(self, props=None):
gobject.GObject.__init__(self)
if not props:
self._props = {}
else:
self._props = props
default_keys = ['activity', 'activity_id',
'mime_type', 'title_set_by_user']
for key in default_keys:
if not self._props.has_key(key):
self._props[key] = ''
def __getitem__(self, key):
return self._props[key]
def __setitem__(self, key, value):
if not self._props.has_key(key) or self._props[key] != value:
self._props[key] = value
self.emit('updated')
def __delitem__(self, key):
del self._props[key]
def __contains__(self, key):
return self._props.__contains__(key)
def has_key(self, key):
return self._props.has_key(key)
def keys(self):
return self._props.keys()
def get_dictionary(self):
return self._props
def copy(self):
return DSMetadata(self._props.copy())
def get(self, key, default=None):
if self._props.has_key(key):
return self._props[key]
else:
return default
class DSObject(object):
def __init__(self, object_id, metadata=None, file_path=None):
self.object_id = object_id
self._metadata = metadata
self._file_path = file_path
self._destroyed = False
self._owns_file = False
def get_metadata(self):
if self._metadata is None and not self.object_id is None:
metadata = DSMetadata(dbus_helpers.get_properties(self.object_id))
self._metadata = metadata
return self._metadata
def set_metadata(self, metadata):
if self._metadata != metadata:
self._metadata = metadata
metadata = property(get_metadata, set_metadata)
def get_file_path(self):
if self._file_path is None and not self.object_id is None:
self.set_file_path(dbus_helpers.get_filename(self.object_id))
self._owns_file = True
return self._file_path
def set_file_path(self, file_path):
if self._file_path != file_path:
if self._file_path and self._owns_file:
if os.path.isfile(self._file_path):
os.remove(self._file_path)
self._owns_file = False
self._file_path = file_path
file_path = property(get_file_path, set_file_path)
def get_activities(self):
activities = []
if self.metadata['activity']:
activity_info = activity.get_registry().get_activity(self.metadata['activity'])
if activity_info:
activities.append(activity_info)
mime_type = self.metadata['mime_type']
if mime_type:
activities_info = activity.get_registry().get_activities_for_type(mime_type)
for activity_info in activities_info:
if activity_info.bundle_id != self.metadata['activity']:
activities.append(activity_info)
return activities
def is_activity_bundle(self):
return self.metadata['mime_type'] in \
[ActivityBundle.MIME_TYPE, ActivityBundle.DEPRECATED_MIME_TYPE]
def is_content_bundle(self):
return self.metadata['mime_type'] == ContentBundle.MIME_TYPE
def is_bundle(self):
return self.is_activity_bundle() or self.is_content_bundle()
def resume(self, bundle_id=None):
from sugar.activity import activityfactory
if self.is_activity_bundle():
if bundle_id is not None:
raise ValueError('Object is a bundle, cannot be resumed as an activity.')
bundle = ActivityBundle(self.file_path)
if not bundle.is_installed():
bundle.install()
activityfactory.create(bundle.get_bundle_id())
else:
if not self.get_activities() and bundle_id is None:
logging.warning('No activity can open this object.')
return
if bundle_id is None:
bundle_id = self.get_activities()[0].bundle_id
activity_id = self.metadata['activity_id']
object_id = self.object_id
if activity_id:
handle = ActivityHandle(object_id=object_id,
activity_id=activity_id)
activityfactory.create(bundle_id, handle)
else:
activityfactory.create_with_object_id(bundle_id, object_id)
def destroy(self):
if self._destroyed:
logging.warning('This DSObject has already been destroyed!.')
import traceback;traceback.print_stack()
return
self._destroyed = True
if self._file_path and self._owns_file:
if os.path.isfile(self._file_path):
os.remove(self._file_path)
self._owns_file = False
self._file_path = None
def __del__(self):
if not self._destroyed:
logging.warning('DSObject was deleted without cleaning up first. ' \
'Please call DSObject.destroy() before disposing it.')
self.destroy()
def copy(self):
return DSObject(None, self._metadata.copy(), self._file_path)
def get(object_id):
logging.debug('datastore.get')
metadata = dbus_helpers.get_properties(object_id)
ds_object = DSObject(object_id, DSMetadata(metadata), None)
# TODO: register the object for updates
return ds_object
def create():
metadata = DSMetadata()
metadata['mtime'] = datetime.now().isoformat()
metadata['timestamp'] = int(time.time())
return DSObject(object_id=None, metadata=metadata, file_path=None)
def write(ds_object, update_mtime=True, transfer_ownership=False, reply_handler=None, error_handler=None, timeout=-1):
logging.debug('datastore.write')
properties = ds_object.metadata.get_dictionary().copy()
if update_mtime:
properties['mtime'] = datetime.now().isoformat()
properties['timestamp'] = int(time.time())
if ds_object._file_path is None:
file_path = ''
else:
file_path = ds_object._file_path
# FIXME: this func will be sync for creates regardless of the handlers
# supplied. This is very bad API, need to decide what to do here.
if ds_object.object_id:
dbus_helpers.update(ds_object.object_id,
properties,
file_path,
transfer_ownership,
reply_handler=reply_handler,
error_handler=error_handler,
timeout=timeout)
else:
if reply_handler or error_handler:
logging.warning('datastore.write() cannot currently be called async' \
' for creates, see https://dev.laptop.org/ticket/3071')
ds_object.object_id = dbus_helpers.create(properties,
file_path,
transfer_ownership)
# TODO: register the object for updates
logging.debug('Written object %s to the datastore.' % ds_object.object_id)
def delete(object_id):
logging.debug('datastore.delete')
dbus_helpers.delete(object_id)
def find(query, sorting=None, limit=None, offset=None, properties=[],
reply_handler=None, error_handler=None):
query = query.copy()
if sorting:
query['order_by'] = sorting
if limit:
query['limit'] = limit
if offset:
query['offset'] = offset
props_list, total_count = dbus_helpers.find(query, properties, reply_handler, error_handler)
objects = []
for props in props_list:
object_id = props['uid']
del props['uid']
ds_object = DSObject(object_id, DSMetadata(props), None)
objects.append(ds_object)
return objects, total_count
def copy(jobject, mount_point):
new_jobject = jobject.copy()
new_jobject.metadata['mountpoint'] = mount_point
if jobject.metadata.has_key('title'):
filename = jobject.metadata['title']
if jobject.metadata.has_key('mime_type'):
mime_type = jobject.metadata['mime_type']
extension = mime.get_primary_extension(mime_type)
if extension:
filename += '.' + extension
new_jobject.metadata['suggested_filename'] = filename
# this will cause the file be retrieved from the DS
new_jobject.file_path = jobject.file_path
write(new_jobject)
def mount(uri, options, timeout=-1):
return dbus_helpers.mount(uri, options, timeout=timeout)
def unmount(mount_point_id):
dbus_helpers.unmount(mount_point_id)
def mounts():
return dbus_helpers.mounts()
def complete_indexing():
return dbus_helpers.complete_indexing()
def get_unique_values(key):
return dbus_helpers.get_unique_values(key)
+99
View File
@@ -0,0 +1,99 @@
# Copyright (C) 2006-2007 Red Hat, Inc.
# 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 logging
import dbus
import dbus.glib
import gobject
from sugar import util
DS_DBUS_SERVICE = "org.laptop.sugar.DataStore"
DS_DBUS_INTERFACE = "org.laptop.sugar.DataStore"
DS_DBUS_PATH = "/org/laptop/sugar/DataStore"
_data_store = None
def _get_data_store():
global _data_store
if not _data_store:
_bus = dbus.SessionBus()
_data_store = dbus.Interface(_bus.get_object(DS_DBUS_SERVICE,
DS_DBUS_PATH),
DS_DBUS_INTERFACE)
return _data_store
def create(properties, filename, transfer_ownership=False):
object_id = _get_data_store().create(dbus.Dictionary(properties), filename,
transfer_ownership)
logging.debug('dbus_helpers.create: ' + object_id)
return object_id
def update(uid, properties, filename, transfer_ownership=False,
reply_handler=None, error_handler=None, timeout=-1):
debug_props = properties.copy()
if debug_props.has_key("preview"):
debug_props["preview"] = "<omitted>"
logging.debug('dbus_helpers.update: %s, %s, %s, %s' % (uid, filename, debug_props, transfer_ownership))
if reply_handler and error_handler:
_get_data_store().update(uid, dbus.Dictionary(properties), filename,
transfer_ownership,
reply_handler=reply_handler,
error_handler=error_handler,
timeout=timeout)
else:
_get_data_store().update(uid, dbus.Dictionary(properties), filename, transfer_ownership)
def delete(uid):
logging.debug('dbus_helpers.delete: %r' % uid)
_get_data_store().delete(uid)
def get_properties(uid):
logging.debug('dbus_helpers.get_properties: %s' % uid)
return _get_data_store().get_properties(uid, byte_arrays=True)
def get_filename(uid):
filename = _get_data_store().get_filename(uid)
logging.debug('dbus_helpers.get_filename: %s, %s' % (uid, filename))
return filename
def find(query, properties, reply_handler, error_handler):
logging.debug('dbus_helpers.find: %r %r' % (query, properties))
if reply_handler and error_handler:
return _get_data_store().find(query, properties,
reply_handler=reply_handler, error_handler=error_handler)
else:
return _get_data_store().find(query, properties)
def mount(uri, options, timeout=-1):
return _get_data_store().mount(uri, options, timeout=timeout)
def unmount(mount_point_id):
_get_data_store().unmount(mount_point_id)
def mounts():
return _get_data_store().mounts()
def get_unique_values(key):
return _get_data_store().get_uniquevaluesfor(key, dbus.Dictionary({}, signature='ss'))
def complete_indexing():
return _get_data_store().complete_indexing()
+702
View File
@@ -0,0 +1,702 @@
/* eggaccelerators.c
* Copyright (C) 2002 Red Hat, Inc.; Copyright 1998, 2001 Tim Janik
* Developed by Havoc Pennington, Tim Janik
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library 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.
*/
#include "eggaccelerators.h"
#include <stdlib.h>
#include <string.h>
#include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h>
enum
{
EGG_MODMAP_ENTRY_SHIFT = 0,
EGG_MODMAP_ENTRY_LOCK = 1,
EGG_MODMAP_ENTRY_CONTROL = 2,
EGG_MODMAP_ENTRY_MOD1 = 3,
EGG_MODMAP_ENTRY_MOD2 = 4,
EGG_MODMAP_ENTRY_MOD3 = 5,
EGG_MODMAP_ENTRY_MOD4 = 6,
EGG_MODMAP_ENTRY_MOD5 = 7,
EGG_MODMAP_ENTRY_LAST = 8
};
#define MODMAP_ENTRY_TO_MODIFIER(x) (1 << (x))
typedef struct
{
EggVirtualModifierType mapping[EGG_MODMAP_ENTRY_LAST];
} EggModmap;
const EggModmap* egg_keymap_get_modmap (GdkKeymap *keymap);
static inline gboolean
is_alt (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'a' || string[1] == 'A') &&
(string[2] == 'l' || string[2] == 'L') &&
(string[3] == 't' || string[3] == 'T') &&
(string[4] == '>'));
}
static inline gboolean
is_ctl (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'c' || string[1] == 'C') &&
(string[2] == 't' || string[2] == 'T') &&
(string[3] == 'l' || string[3] == 'L') &&
(string[4] == '>'));
}
static inline gboolean
is_modx (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'm' || string[1] == 'M') &&
(string[2] == 'o' || string[2] == 'O') &&
(string[3] == 'd' || string[3] == 'D') &&
(string[4] >= '1' && string[4] <= '5') &&
(string[5] == '>'));
}
static inline gboolean
is_ctrl (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'c' || string[1] == 'C') &&
(string[2] == 't' || string[2] == 'T') &&
(string[3] == 'r' || string[3] == 'R') &&
(string[4] == 'l' || string[4] == 'L') &&
(string[5] == '>'));
}
static inline gboolean
is_shft (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 's' || string[1] == 'S') &&
(string[2] == 'h' || string[2] == 'H') &&
(string[3] == 'f' || string[3] == 'F') &&
(string[4] == 't' || string[4] == 'T') &&
(string[5] == '>'));
}
static inline gboolean
is_shift (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 's' || string[1] == 'S') &&
(string[2] == 'h' || string[2] == 'H') &&
(string[3] == 'i' || string[3] == 'I') &&
(string[4] == 'f' || string[4] == 'F') &&
(string[5] == 't' || string[5] == 'T') &&
(string[6] == '>'));
}
static inline gboolean
is_control (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'c' || string[1] == 'C') &&
(string[2] == 'o' || string[2] == 'O') &&
(string[3] == 'n' || string[3] == 'N') &&
(string[4] == 't' || string[4] == 'T') &&
(string[5] == 'r' || string[5] == 'R') &&
(string[6] == 'o' || string[6] == 'O') &&
(string[7] == 'l' || string[7] == 'L') &&
(string[8] == '>'));
}
static inline gboolean
is_release (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'r' || string[1] == 'R') &&
(string[2] == 'e' || string[2] == 'E') &&
(string[3] == 'l' || string[3] == 'L') &&
(string[4] == 'e' || string[4] == 'E') &&
(string[5] == 'a' || string[5] == 'A') &&
(string[6] == 's' || string[6] == 'S') &&
(string[7] == 'e' || string[7] == 'E') &&
(string[8] == '>'));
}
static inline gboolean
is_meta (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'm' || string[1] == 'M') &&
(string[2] == 'e' || string[2] == 'E') &&
(string[3] == 't' || string[3] == 'T') &&
(string[4] == 'a' || string[4] == 'A') &&
(string[5] == '>'));
}
static inline gboolean
is_super (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 's' || string[1] == 'S') &&
(string[2] == 'u' || string[2] == 'U') &&
(string[3] == 'p' || string[3] == 'P') &&
(string[4] == 'e' || string[4] == 'E') &&
(string[5] == 'r' || string[5] == 'R') &&
(string[6] == '>'));
}
static inline gboolean
is_hyper (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'h' || string[1] == 'H') &&
(string[2] == 'y' || string[2] == 'Y') &&
(string[3] == 'p' || string[3] == 'P') &&
(string[4] == 'e' || string[4] == 'E') &&
(string[5] == 'r' || string[5] == 'R') &&
(string[6] == '>'));
}
static inline gboolean
is_keycode (const gchar *string)
{
return ((string[0] == '0') &&
(string[1] == 'x'));
}
/**
* egg_accelerator_parse_virtual:
* @accelerator: string representing an accelerator
* @accelerator_key: return location for accelerator keyval
* @accelerator_mods: return location for accelerator modifier mask
*
* Parses a string representing a virtual accelerator. The format
* looks like "&lt;Control&gt;a" or "&lt;Shift&gt;&lt;Alt&gt;F1" or
* "&lt;Release&gt;z" (the last one is for key release). The parser
* is fairly liberal and allows lower or upper case, and also
* abbreviations such as "&lt;Ctl&gt;" and "&lt;Ctrl&gt;".
*
* If the parse fails, @accelerator_key and @accelerator_mods will
* be set to 0 (zero) and %FALSE will be returned. If the string contains
* only modifiers, @accelerator_key will be set to 0 but %TRUE will be
* returned.
*
* The virtual vs. concrete accelerator distinction is a relic of
* how the X Window System works; there are modifiers Mod2-Mod5 that
* can represent various keyboard keys (numlock, meta, hyper, etc.),
* the virtual modifier represents the keyboard key, the concrete
* modifier the actual Mod2-Mod5 bits in the key press event.
*
* Returns: %TRUE on success.
*/
gboolean
egg_accelerator_parse_virtual (const gchar *accelerator,
guint *accelerator_key,
guint *keycode,
EggVirtualModifierType *accelerator_mods)
{
guint keyval;
GdkModifierType mods;
gint len;
gboolean bad_keyval;
if (accelerator_key)
*accelerator_key = 0;
if (accelerator_mods)
*accelerator_mods = 0;
if (keycode)
*keycode = 0;
g_return_val_if_fail (accelerator != NULL, FALSE);
bad_keyval = FALSE;
keyval = 0;
mods = 0;
len = strlen (accelerator);
while (len)
{
if (*accelerator == '<')
{
if (len >= 9 && is_release (accelerator))
{
accelerator += 9;
len -= 9;
mods |= EGG_VIRTUAL_RELEASE_MASK;
}
else if (len >= 9 && is_control (accelerator))
{
accelerator += 9;
len -= 9;
mods |= EGG_VIRTUAL_CONTROL_MASK;
}
else if (len >= 7 && is_shift (accelerator))
{
accelerator += 7;
len -= 7;
mods |= EGG_VIRTUAL_SHIFT_MASK;
}
else if (len >= 6 && is_shft (accelerator))
{
accelerator += 6;
len -= 6;
mods |= EGG_VIRTUAL_SHIFT_MASK;
}
else if (len >= 6 && is_ctrl (accelerator))
{
accelerator += 6;
len -= 6;
mods |= EGG_VIRTUAL_CONTROL_MASK;
}
else if (len >= 6 && is_modx (accelerator))
{
static const guint mod_vals[] = {
EGG_VIRTUAL_ALT_MASK, EGG_VIRTUAL_MOD2_MASK, EGG_VIRTUAL_MOD3_MASK,
EGG_VIRTUAL_MOD4_MASK, EGG_VIRTUAL_MOD5_MASK
};
len -= 6;
accelerator += 4;
mods |= mod_vals[*accelerator - '1'];
accelerator += 2;
}
else if (len >= 5 && is_ctl (accelerator))
{
accelerator += 5;
len -= 5;
mods |= EGG_VIRTUAL_CONTROL_MASK;
}
else if (len >= 5 && is_alt (accelerator))
{
accelerator += 5;
len -= 5;
mods |= EGG_VIRTUAL_ALT_MASK;
}
else if (len >= 6 && is_meta (accelerator))
{
accelerator += 6;
len -= 6;
mods |= EGG_VIRTUAL_META_MASK;
}
else if (len >= 7 && is_hyper (accelerator))
{
accelerator += 7;
len -= 7;
mods |= EGG_VIRTUAL_HYPER_MASK;
}
else if (len >= 7 && is_super (accelerator))
{
accelerator += 7;
len -= 7;
mods |= EGG_VIRTUAL_SUPER_MASK;
}
else
{
gchar last_ch;
last_ch = *accelerator;
while (last_ch && last_ch != '>')
{
last_ch = *accelerator;
accelerator += 1;
len -= 1;
}
}
}
else
{
keyval = gdk_keyval_from_name (accelerator);
if (keyval == 0)
{
/* If keyval is 0, than maybe it's a keycode. Check for 0x## */
if (len >= 4 && is_keycode (accelerator))
{
char keystring[5];
gchar *endptr;
gint tmp_keycode;
memcpy (keystring, accelerator, 4);
keystring [4] = '\000';
tmp_keycode = strtol (keystring, &endptr, 16);
if (endptr == NULL || *endptr != '\000')
{
bad_keyval = TRUE;
}
else if (keycode != NULL)
{
*keycode = tmp_keycode;
/* 0x00 is an invalid keycode too. */
if (*keycode == 0)
bad_keyval = TRUE;
}
}
} else if (keycode != NULL)
*keycode = XKeysymToKeycode (GDK_DISPLAY(), keyval);
accelerator += len;
len -= len;
}
}
if (accelerator_key)
*accelerator_key = gdk_keyval_to_lower (keyval);
if (accelerator_mods)
*accelerator_mods = mods;
return !bad_keyval;
}
/**
* egg_virtual_accelerator_name:
* @accelerator_key: accelerator keyval
* @accelerator_mods: accelerator modifier mask
* @returns: a newly-allocated accelerator name
*
* Converts an accelerator keyval and modifier mask
* into a string parseable by egg_accelerator_parse_virtual().
* For example, if you pass in #GDK_q and #EGG_VIRTUAL_CONTROL_MASK,
* this function returns "&lt;Control&gt;q".
*
* The caller of this function must free the returned string.
*/
gchar*
egg_virtual_accelerator_name (guint accelerator_key,
guint keycode,
EggVirtualModifierType accelerator_mods)
{
static const gchar text_release[] = "<Release>";
static const gchar text_shift[] = "<Shift>";
static const gchar text_control[] = "<Control>";
static const gchar text_mod1[] = "<Alt>";
static const gchar text_mod2[] = "<Mod2>";
static const gchar text_mod3[] = "<Mod3>";
static const gchar text_mod4[] = "<Mod4>";
static const gchar text_mod5[] = "<Mod5>";
static const gchar text_meta[] = "<Meta>";
static const gchar text_super[] = "<Super>";
static const gchar text_hyper[] = "<Hyper>";
guint l;
gchar *keyval_name;
gchar *accelerator;
accelerator_mods &= EGG_VIRTUAL_MODIFIER_MASK;
if (!accelerator_key)
{
keyval_name = g_strdup_printf ("0x%02x", keycode);
}
else
{
keyval_name = gdk_keyval_name (gdk_keyval_to_lower (accelerator_key));
if (!keyval_name)
keyval_name = "";
}
l = 0;
if (accelerator_mods & EGG_VIRTUAL_RELEASE_MASK)
l += sizeof (text_release) - 1;
if (accelerator_mods & EGG_VIRTUAL_SHIFT_MASK)
l += sizeof (text_shift) - 1;
if (accelerator_mods & EGG_VIRTUAL_CONTROL_MASK)
l += sizeof (text_control) - 1;
if (accelerator_mods & EGG_VIRTUAL_ALT_MASK)
l += sizeof (text_mod1) - 1;
if (accelerator_mods & EGG_VIRTUAL_MOD2_MASK)
l += sizeof (text_mod2) - 1;
if (accelerator_mods & EGG_VIRTUAL_MOD3_MASK)
l += sizeof (text_mod3) - 1;
if (accelerator_mods & EGG_VIRTUAL_MOD4_MASK)
l += sizeof (text_mod4) - 1;
if (accelerator_mods & EGG_VIRTUAL_MOD5_MASK)
l += sizeof (text_mod5) - 1;
if (accelerator_mods & EGG_VIRTUAL_META_MASK)
l += sizeof (text_meta) - 1;
if (accelerator_mods & EGG_VIRTUAL_HYPER_MASK)
l += sizeof (text_hyper) - 1;
if (accelerator_mods & EGG_VIRTUAL_SUPER_MASK)
l += sizeof (text_super) - 1;
l += strlen (keyval_name);
accelerator = g_new (gchar, l + 1);
l = 0;
accelerator[l] = 0;
if (accelerator_mods & EGG_VIRTUAL_RELEASE_MASK)
{
strcpy (accelerator + l, text_release);
l += sizeof (text_release) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_SHIFT_MASK)
{
strcpy (accelerator + l, text_shift);
l += sizeof (text_shift) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_CONTROL_MASK)
{
strcpy (accelerator + l, text_control);
l += sizeof (text_control) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_ALT_MASK)
{
strcpy (accelerator + l, text_mod1);
l += sizeof (text_mod1) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_MOD2_MASK)
{
strcpy (accelerator + l, text_mod2);
l += sizeof (text_mod2) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_MOD3_MASK)
{
strcpy (accelerator + l, text_mod3);
l += sizeof (text_mod3) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_MOD4_MASK)
{
strcpy (accelerator + l, text_mod4);
l += sizeof (text_mod4) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_MOD5_MASK)
{
strcpy (accelerator + l, text_mod5);
l += sizeof (text_mod5) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_META_MASK)
{
strcpy (accelerator + l, text_meta);
l += sizeof (text_meta) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_HYPER_MASK)
{
strcpy (accelerator + l, text_hyper);
l += sizeof (text_hyper) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_SUPER_MASK)
{
strcpy (accelerator + l, text_super);
l += sizeof (text_super) - 1;
}
strcpy (accelerator + l, keyval_name);
return accelerator;
}
void
egg_keymap_resolve_virtual_modifiers (GdkKeymap *keymap,
EggVirtualModifierType virtual_mods,
GdkModifierType *concrete_mods)
{
GdkModifierType concrete;
int i;
const EggModmap *modmap;
g_return_if_fail (GDK_IS_KEYMAP (keymap));
g_return_if_fail (concrete_mods != NULL);
modmap = egg_keymap_get_modmap (keymap);
/* Not so sure about this algorithm. */
concrete = 0;
i = 0;
while (i < EGG_MODMAP_ENTRY_LAST)
{
if (modmap->mapping[i] & virtual_mods)
concrete |= (1 << i);
++i;
}
*concrete_mods = concrete;
}
void
egg_keymap_virtualize_modifiers (GdkKeymap *keymap,
GdkModifierType concrete_mods,
EggVirtualModifierType *virtual_mods)
{
GdkModifierType virtual;
int i;
const EggModmap *modmap;
g_return_if_fail (GDK_IS_KEYMAP (keymap));
g_return_if_fail (virtual_mods != NULL);
modmap = egg_keymap_get_modmap (keymap);
/* Not so sure about this algorithm. */
virtual = 0;
i = 0;
while (i < EGG_MODMAP_ENTRY_LAST)
{
if ((1 << i) & concrete_mods)
{
EggVirtualModifierType cleaned;
cleaned = modmap->mapping[i] & ~(EGG_VIRTUAL_MOD2_MASK |
EGG_VIRTUAL_MOD3_MASK |
EGG_VIRTUAL_MOD4_MASK |
EGG_VIRTUAL_MOD5_MASK);
if (cleaned != 0)
{
virtual |= cleaned;
}
else
{
/* Rather than dropping mod2->mod5 if not bound,
* go ahead and use the concrete names
*/
virtual |= modmap->mapping[i];
}
}
++i;
}
*virtual_mods = virtual;
}
static void
reload_modmap (GdkKeymap *keymap,
EggModmap *modmap)
{
XModifierKeymap *xmodmap;
int map_size;
int i;
/* FIXME multihead */
xmodmap = XGetModifierMapping (gdk_x11_get_default_xdisplay ());
memset (modmap->mapping, 0, sizeof (modmap->mapping));
/* there are 8 modifiers, and the first 3 are shift, shift lock,
* and control
*/
map_size = 8 * xmodmap->max_keypermod;
i = 3 * xmodmap->max_keypermod;
while (i < map_size)
{
/* get the key code at this point in the map,
* see if its keysym is one we're interested in
*/
int keycode = xmodmap->modifiermap[i];
GdkKeymapKey *keys;
guint *keyvals;
int n_entries;
int j;
EggVirtualModifierType mask;
keys = NULL;
keyvals = NULL;
n_entries = 0;
gdk_keymap_get_entries_for_keycode (keymap,
keycode,
&keys, &keyvals, &n_entries);
mask = 0;
j = 0;
while (j < n_entries)
{
if (keyvals[j] == GDK_Num_Lock)
mask |= EGG_VIRTUAL_NUM_LOCK_MASK;
else if (keyvals[j] == GDK_Scroll_Lock)
mask |= EGG_VIRTUAL_SCROLL_LOCK_MASK;
else if (keyvals[j] == GDK_Meta_L ||
keyvals[j] == GDK_Meta_R)
mask |= EGG_VIRTUAL_META_MASK;
else if (keyvals[j] == GDK_Hyper_L ||
keyvals[j] == GDK_Hyper_R)
mask |= EGG_VIRTUAL_HYPER_MASK;
else if (keyvals[j] == GDK_Super_L ||
keyvals[j] == GDK_Super_R)
mask |= EGG_VIRTUAL_SUPER_MASK;
else if (keyvals[j] == GDK_Mode_switch)
mask |= EGG_VIRTUAL_MODE_SWITCH_MASK;
++j;
}
/* Mod1Mask is 1 << 3 for example, i.e. the
* fourth modifier, i / keyspermod is the modifier
* index
*/
modmap->mapping[i/xmodmap->max_keypermod] |= mask;
g_free (keyvals);
g_free (keys);
++i;
}
/* Add in the not-really-virtual fixed entries */
modmap->mapping[EGG_MODMAP_ENTRY_SHIFT] |= EGG_VIRTUAL_SHIFT_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_CONTROL] |= EGG_VIRTUAL_CONTROL_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_LOCK] |= EGG_VIRTUAL_LOCK_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_MOD1] |= EGG_VIRTUAL_ALT_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_MOD2] |= EGG_VIRTUAL_MOD2_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_MOD3] |= EGG_VIRTUAL_MOD3_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_MOD4] |= EGG_VIRTUAL_MOD4_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_MOD5] |= EGG_VIRTUAL_MOD5_MASK;
XFreeModifiermap (xmodmap);
}
const EggModmap*
egg_keymap_get_modmap (GdkKeymap *keymap)
{
EggModmap *modmap;
/* This is all a hack, much simpler when we can just
* modify GDK directly.
*/
modmap = g_object_get_data (G_OBJECT (keymap),
"egg-modmap");
if (modmap == NULL)
{
modmap = g_new0 (EggModmap, 1);
/* FIXME modify keymap change events with an event filter
* and force a reload if we get one
*/
reload_modmap (keymap, modmap);
g_object_set_data_full (G_OBJECT (keymap),
"egg-modmap",
modmap,
g_free);
}
g_assert (modmap != NULL);
return modmap;
}
+89
View File
@@ -0,0 +1,89 @@
/* eggaccelerators.h
* Copyright (C) 2002 Red Hat, Inc.
* Developed by Havoc Pennington
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library 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.
*/
#ifndef __EGG_ACCELERATORS_H__
#define __EGG_ACCELERATORS_H__
#include <gtk/gtkaccelgroup.h>
#include <gdk/gdk.h>
G_BEGIN_DECLS
/* Where a value is also in GdkModifierType we coincide,
* otherwise we don't overlap.
*/
typedef enum
{
EGG_VIRTUAL_SHIFT_MASK = 1 << 0,
EGG_VIRTUAL_LOCK_MASK = 1 << 1,
EGG_VIRTUAL_CONTROL_MASK = 1 << 2,
EGG_VIRTUAL_ALT_MASK = 1 << 3, /* fixed as Mod1 */
EGG_VIRTUAL_MOD2_MASK = 1 << 4,
EGG_VIRTUAL_MOD3_MASK = 1 << 5,
EGG_VIRTUAL_MOD4_MASK = 1 << 6,
EGG_VIRTUAL_MOD5_MASK = 1 << 7,
#if 0
GDK_BUTTON1_MASK = 1 << 8,
GDK_BUTTON2_MASK = 1 << 9,
GDK_BUTTON3_MASK = 1 << 10,
GDK_BUTTON4_MASK = 1 << 11,
GDK_BUTTON5_MASK = 1 << 12,
/* 13, 14 are used by Xkb for the keyboard group */
#endif
EGG_VIRTUAL_META_MASK = 1 << 24,
EGG_VIRTUAL_SUPER_MASK = 1 << 25,
EGG_VIRTUAL_HYPER_MASK = 1 << 26,
EGG_VIRTUAL_MODE_SWITCH_MASK = 1 << 27,
EGG_VIRTUAL_NUM_LOCK_MASK = 1 << 28,
EGG_VIRTUAL_SCROLL_LOCK_MASK = 1 << 29,
/* Also in GdkModifierType */
EGG_VIRTUAL_RELEASE_MASK = 1 << 30,
/* 28-31 24-27 20-23 16-19 12-15 8-11 4-7 0-3
* 7 f 0 0 0 0 f f
*/
EGG_VIRTUAL_MODIFIER_MASK = 0x7f0000ff
} EggVirtualModifierType;
gboolean egg_accelerator_parse_virtual (const gchar *accelerator,
guint *accelerator_key,
guint *keycode,
EggVirtualModifierType *accelerator_mods);
void egg_keymap_resolve_virtual_modifiers (GdkKeymap *keymap,
EggVirtualModifierType virtual_mods,
GdkModifierType *concrete_mods);
void egg_keymap_virtualize_modifiers (GdkKeymap *keymap,
GdkModifierType concrete_mods,
EggVirtualModifierType *virtual_mods);
gchar* egg_virtual_accelerator_name (guint accelerator_key,
guint keycode,
EggVirtualModifierType accelerator_mods);
G_END_DECLS
#endif /* __EGG_ACCELERATORS_H__ */
+93
View File
@@ -0,0 +1,93 @@
"""Calculates file-paths for the Sugar working environment"""
# Copyright (C) 2006-2007 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
def _get_prefix_path(base, path=None):
if os.environ.has_key('SUGAR_PREFIX'):
prefix = os.environ['SUGAR_PREFIX']
else:
raise RuntimeError("The SUGAR_PREFIX environment variable is not set.")
if path:
return os.path.join(prefix, base, path)
else:
return os.path.join(prefix, base)
def _get_sugar_path(base, path=None):
if os.environ.has_key('SUGAR_PATH'):
sugar_path = os.environ['SUGAR_PATH']
else:
raise RuntimeError("The SUGAR_PATH environment variable is not set.")
if path:
return os.path.join(sugar_path, base, path)
else:
return os.path.join(sugar_path, base)
def is_emulator():
if os.environ.has_key('SUGAR_EMULATOR'):
if os.environ['SUGAR_EMULATOR'] == 'yes':
return True
return False
def get_profile_path(path=None):
if os.environ.has_key('SUGAR_PROFILE'):
profile_id = os.environ['SUGAR_PROFILE']
else:
profile_id = 'default'
base = os.path.join(os.path.expanduser('~/.sugar'), profile_id)
if not os.path.isdir(base):
try:
os.makedirs(base)
except OSError, exc:
print "Could not create user directory."
if path != None:
return os.path.join(base, path)
else:
return base
def get_logs_path(path=None):
base = get_profile_path('logs')
if path != None:
return os.path.join(base, path)
else:
return base
def get_user_activities_path():
return os.path.expanduser('~/Activities')
def get_user_library_path():
return os.path.expanduser('~/Library')
def get_locale_path(path=None):
return _get_prefix_path('share/locale', path)
def get_bin_path(path=None):
return _get_sugar_path('bin', path)
def get_service_path(name):
return _get_sugar_path('services', name)
def get_shell_path(path=None):
return _get_sugar_path('shell', path)
def get_data_path(path=None):
return _get_sugar_path('data', path)
+26
View File
@@ -0,0 +1,26 @@
sugardir = $(pythondir)/sugar/graphics
sugar_PYTHON = \
__init__.py \
alert.py \
animator.py \
combobox.py \
entry.py \
icon.py \
iconentry.py \
menuitem.py \
notebook.py \
objectchooser.py \
radiotoolbutton.py \
palette.py \
palettegroup.py \
panel.py \
roundbox.py \
spreadlayout.py \
style.py \
toggletoolbutton.py \
toolbox.py \
toolbutton.py \
toolcombobox.py \
tray.py \
window.py \
xocolor.py
+18
View File
@@ -0,0 +1,18 @@
"""Graphics/controls for use in Sugar"""
# Copyright (C) 2006-2007, 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.
+230
View File
@@ -0,0 +1,230 @@
# 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.
from gettext import gettext as _
import gtk
import gobject
import hippo
import math
from sugar.graphics import style
from sugar.graphics.icon import Icon
class Alert(gtk.EventBox, gobject.GObject):
"""UI interface for Alerts
Alerts are used inside the activity window instead of being a
separate popup window. They do not hide canvas content. You can
use add_alert(widget) and remove_alert(widget) inside your activity
to add and remove the alert. You can set the position (bottom=-1,
top=0,1) for alerts global for the window by changing alert_position,
default is bottom.
Properties:
'title': the title of the alert,
'message': the message of the alert,
'icon': the icon that appears at the far left
See __gproperties__
"""
__gtype_name__ = 'SugarAlert'
__gsignals__ = {
'response': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([object]))
}
__gproperties__ = {
'title' : (str, None, None, None,
gobject.PARAM_READWRITE),
'msg' : (str, None, None, None,
gobject.PARAM_READWRITE),
'icon' : (object, None, None,
gobject.PARAM_WRITABLE)
}
def __init__(self, **kwargs):
gobject.GObject.__init__(self)
self.set_visible_window(True)
self._hbox = gtk.HBox()
self.add(self._hbox)
self._title = None
self._msg = None
self._icon = None
self._timeout = 0
self._buttons = {}
self._msg_box = gtk.VBox()
self._title_label = gtk.Label()
size = style.zoom(style.GRID_CELL_SIZE * 0.5)
self._title_label.set_alignment(0, 0.5)
self._title_label.set_padding(style.DEFAULT_SPACING, 0)
self._msg_box.pack_start(self._title_label, False)
self._title_label.show()
self._msg_label = gtk.Label()
self._msg_label.set_alignment(0, 0.5)
self._msg_label.set_padding(style.DEFAULT_SPACING, 0)
self._msg_box.pack_start(self._msg_label, False)
self._msg_label.show()
self._hbox.pack_start(self._msg_box)
self._buttons_box = gtk.HButtonBox()
self._buttons_box.set_layout(gtk.BUTTONBOX_END)
self._buttons_box.set_spacing(style.DEFAULT_SPACING)
self._hbox.pack_start(self._buttons_box)
self._buttons_box.show()
self._msg_box.show()
self._hbox.show()
self.show()
def do_set_property(self, pspec, value):
if pspec.name == 'title':
if self._title != value:
self._title = value
self._title_label.set_markup("<b>" + self._title + "</b>")
elif pspec.name == 'msg':
if self._msg != value:
self._msg = value
self._msg_label.set_markup(self._msg)
elif pspec.name == 'icon':
if self._icon != value:
self._icon = value
self._hbox.pack_start(self._icon)
self._hbox.reorder_child(self._icon, 0)
def do_get_property(self, pspec):
if pspec.name == 'title':
return self._title
elif pspec.name == 'msg':
return self._msg
def add_button(self, response_id, label, icon=None, position=-1):
"""Add a button to the alert
response_id: will be emitted with the response signal
a response ID should one of the pre-defined
GTK Response Type Constants or a positive number
label: that will occure right to the buttom
icon: this can be a SugarIcon or a gtk.Image
position: the position of the button in the box (optional)
"""
button = gtk.Button()
self._buttons[response_id] = button
if icon is not None:
button.set_image(icon)
button.set_label(label)
self._buttons_box.pack_start(button)
button.show()
button.connect('clicked', self.__button_clicked_cb, response_id)
if position != -1:
self._buttons_box.reorder_child(button, position)
return button
def remove_button(self, response_id):
"""Remove a button from the alert by the given button id"""
self._buttons_box.remove(self._buttons[id])
def _response(self, id):
"""Emitting response when we have a result
A result can be that a user has clicked a button or
a timeout has occured, the id identifies the button
that has been clicked and -1 for a timeout
"""
self.emit('response', id)
def __button_clicked_cb(self, button, response_id):
self._response(response_id)
class ConfirmationAlert(Alert):
"""This is a ready-made two button (Cancel,Ok) alert"""
def __init__(self, **kwargs):
Alert.__init__(self, **kwargs)
icon = Icon(icon_name='dialog-cancel')
cancel_button = self.add_button(gtk.RESPONSE_CANCEL, _('Cancel'), icon)
icon.show()
icon = Icon(icon_name='dialog-ok')
ok_button = self.add_button(gtk.RESPONSE_OK, _('Ok'), icon)
icon.show()
class _TimeoutIcon(hippo.CanvasText, hippo.CanvasItem):
__gtype_name__ = 'AlertTimeoutIcon'
def __init__(self, **kwargs):
hippo.CanvasText.__init__(self, **kwargs)
self.props.orientation = hippo.ORIENTATION_HORIZONTAL
self.props.border_left = style.DEFAULT_SPACING
self.props.border_right = style.DEFAULT_SPACING
def do_paint_background(self, cr, damaged_box):
[width, height] = self.get_allocation()
x = width * 0.5
y = height * 0.5
radius = min(width * 0.5, height * 0.5)
hippo.cairo_set_source_rgba32(cr, self.props.background_color)
cr.arc(x, y, radius, 0, 2*math.pi)
cr.fill_preserve()
class TimeoutAlert(Alert):
"""This is a ready-made two button (Cancel,Continue) alert
It times out with a positive reponse after the given amount of seconds.
"""
def __init__(self, timeout=5, **kwargs):
Alert.__init__(self, **kwargs)
self._timeout = timeout
icon = Icon(icon_name='dialog-cancel')
cancel_button = self.add_button(gtk.RESPONSE_CANCEL, _('Cancel'), icon)
icon.show()
self._timeout_text = _TimeoutIcon(
text=self._timeout,
color=style.COLOR_BUTTON_GREY.get_int(),
background_color=style.COLOR_WHITE.get_int())
canvas = hippo.Canvas()
canvas.set_root(self._timeout_text)
canvas.show()
self.add_button(gtk.RESPONSE_OK, _('Continue'), canvas)
gobject.timeout_add(1000, self.__timeout)
def __timeout(self):
self._timeout -= 1
self._timeout_text.props.text = self._timeout
if self._timeout == 0:
self._response(gtk.RESPONSE_OK)
return False
return True
+94
View File
@@ -0,0 +1,94 @@
# Copyright (C) 2007, 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 time
import gobject
EASE_OUT_EXPO = 0
EASE_IN_EXPO = 1
class Animator(gobject.GObject):
__gsignals__ = {
'completed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([])),
}
def __init__(self, time, fps=20, easing=EASE_OUT_EXPO):
gobject.GObject.__init__(self)
self._animations = []
self._time = time
self._interval = 1.0 / fps
self._easing = easing
self._timeout_sid = 0
def add(self, animation):
self._animations.append(animation)
def remove_all(self):
self.stop()
self._animations = []
def start(self):
if self._timeout_sid:
self.stop()
self._start_time = time.time()
self._timeout_sid = gobject.timeout_add(
int(self._interval * 1000), self._next_frame_cb)
def stop(self):
if self._timeout_sid:
gobject.source_remove(self._timeout_sid)
self._timeout_sid = 0
self.emit('completed')
def _next_frame_cb(self):
current_time = min(self._time, time.time() - self._start_time)
current_time = max(current_time, 0.0)
for animation in self._animations:
animation.do_frame(current_time, self._time, self._easing)
if current_time == self._time:
self.stop()
return False
else:
return True
class Animation(object):
def __init__(self, start, end):
self.start = start
self.end = end
def do_frame(self, time, duration, easing):
start = self.start
change = self.end - self.start
if time == duration:
# last frame
frame = self.end
else:
if easing == EASE_OUT_EXPO:
frame = change * (-pow(2, -10 * time/duration) + 1) + start;
elif easing == EASE_IN_EXPO:
frame = change * pow(2, 10 * (time / duration - 1)) + start;
self.next_frame(frame)
def next_frame(self, frame):
pass
+114
View File
@@ -0,0 +1,114 @@
# 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 sys
import os
import logging
import gobject
import gtk
class ComboBox(gtk.ComboBox):
__gtype_name__ = 'SugarComboBox'
__gproperties__ = {
'value' : (object, None, None,
gobject.PARAM_READABLE)
}
def __init__(self):
gtk.ComboBox.__init__(self)
self._text_renderer = None
self._icon_renderer = None
self._model = gtk.ListStore(gobject.TYPE_PYOBJECT,
gobject.TYPE_STRING,
gtk.gdk.Pixbuf,
gobject.TYPE_BOOLEAN)
self.set_model(self._model)
self.set_row_separator_func(self._is_separator)
def do_get_property(self, pspec):
if pspec.name == 'value':
row = self.get_active_item()
if not row:
return None
return row[0]
else:
return gtk.ComboBox.do_get_property(self, pspec)
def _get_real_name_from_theme(self, name, size):
icon_theme = gtk.icon_theme_get_default()
width, height = gtk.icon_size_lookup(size)
info = icon_theme.lookup_icon(name, width, 0)
if not info:
raise ValueError("Icon '" + name + "' not found.")
fname = info.get_filename()
del info
return fname
def append_item(self, action_id, text, icon_name=None, file_name=None):
if not self._icon_renderer and (icon_name or file_name):
self._icon_renderer = gtk.CellRendererPixbuf()
settings = self.get_settings()
w, h = gtk.icon_size_lookup_for_settings(settings, gtk.ICON_SIZE_MENU)
self._icon_renderer.props.stock_size = w
self.pack_start(self._icon_renderer, False)
self.add_attribute(self._icon_renderer, 'pixbuf', 2)
if not self._text_renderer and text:
self._text_renderer = gtk.CellRendererText()
self.pack_end(self._text_renderer, True)
self.add_attribute(self._text_renderer, 'text', 1)
if icon_name or file_name:
if text:
size = gtk.ICON_SIZE_MENU
else:
size = gtk.ICON_SIZE_LARGE_TOOLBAR
width, height = gtk.icon_size_lookup(size)
if icon_name:
file_name = self._get_real_name_from_theme(icon_name, size)
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(file_name, width, height)
else:
pixbuf = None
self._model.append([action_id, text, pixbuf, False])
def append_separator(self):
self._model.append([0, None, None, True])
def get_active_item(self):
index = self.get_active()
if index == -1:
index = 0
row = self._model.iter_nth_child(None, index)
if not row:
return None
return self._model[row]
def remove_all(self):
self._model.clear()
def _is_separator(self, model, row):
action_id, text, icon_name, is_separator = model[row]
return is_separator
+25
View File
@@ -0,0 +1,25 @@
# Copyright (C) 2007, 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 gtk
import hippo
class CanvasEntry(hippo.CanvasEntry):
def set_background(self, color_spec):
color = gtk.gdk.color_parse(color_spec)
self.props.widget.modify_bg(gtk.STATE_INSENSITIVE, color)
self.props.widget.modify_base(gtk.STATE_INSENSITIVE, color)
+504
View File
@@ -0,0 +1,504 @@
# Copyright (C) 2006-2007 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 re
import math
import time
import logging
import gobject
import gtk
import hippo
import rsvg
import cairo
from sugar.graphics.style import Color
from sugar.graphics.xocolor import XoColor
from sugar.graphics import style
from sugar.graphics.palette import Palette, CanvasInvoker
from sugar.util import LRU
_BADGE_SIZE = 0.45
class _SVGLoader(object):
def __init__(self):
self._cache = LRU(50)
def load(self, file_name, entities, cache):
if file_name in self._cache:
icon = self._cache[file_name]
else:
icon_file = open(file_name, 'r')
icon = icon_file.read()
icon_file.close()
if cache:
self._cache[file_name] = icon
for entity, value in entities.items():
if isinstance(value, basestring):
xml = '<!ENTITY %s "%s">' % (entity, value)
icon = re.sub('<!ENTITY %s .*>' % entity, xml, icon)
else:
logging.error(
'Icon %s, entity %s is invalid.', file_name, entity)
return rsvg.Handle(data=icon)
class _IconInfo(object):
def __init__(self):
self.file_name = None
self.attach_x = 0
self.attach_y = 0
class _BadgeInfo(object):
def __init__(self):
self.attach_x = 0
self.attach_y = 0
self.size = 0
self.icon_padding = 0
class _IconBuffer(object):
_surface_cache = LRU(50)
_loader = _SVGLoader()
def __init__(self):
self.icon_name = None
self.file_name = None
self.fill_color = None
self.stroke_color = None
self.badge_name = None
self.width = None
self.height = None
self.cache = False
def _get_cache_key(self):
return (self.icon_name, self.file_name, self.fill_color,
self.stroke_color, self.badge_name, self.width, self.height)
def _load_svg(self, file_name):
entities = {}
if self.fill_color:
entities['fill_color'] = self.fill_color
if self.stroke_color:
entities['stroke_color'] = self.stroke_color
return self._loader.load(file_name, entities, self.cache)
def _get_attach_points(self, info, size_request):
attach_points = info.get_attach_points()
if attach_points:
attach_x = float(attach_points[0][0]) / size_request
attach_y = float(attach_points[0][1]) / size_request
else:
attach_x = attach_y = 0
return attach_x, attach_y
def _get_icon_info(self):
icon_info = _IconInfo()
if self.file_name:
icon_info.file_name = self.file_name
elif self.icon_name:
theme = gtk.icon_theme_get_default()
size = 50
if self.width != None:
size = self.width
info = theme.lookup_icon(self.icon_name, size, 0)
if info:
attach_x, attach_y = self._get_attach_points(info, size)
icon_info.file_name = info.get_filename()
icon_info.attach_x = attach_x
icon_info.attach_y = attach_y
else:
logging.warning('No icon with the name %s '
'was found in the theme.' % self.icon_name)
return icon_info
def _draw_badge(self, context, size):
theme = gtk.icon_theme_get_default()
badge_info = theme.lookup_icon(self.badge_name, size, 0)
if badge_info:
badge_file_name = badge_info.get_filename()
if badge_file_name.endswith('.svg'):
handle = self._loader.load(badge_file_name, {}, self.cache)
handle.render_cairo(context)
else:
pixbuf = gtk.gdk.pixbuf_new_from_file(badge_file_name)
surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf)
context.set_source_surface(surface, 0, 0)
context.paint()
def _get_size(self, icon_width, icon_height, padding):
if self.width is not None and self.height is not None:
width = self.width + padding
height = self.height + padding
else:
width = icon_width + padding
height = icon_height + padding
return width, height
def _get_badge_info(self, icon_info, icon_width, icon_height):
info = _BadgeInfo()
if self.badge_name is None:
return info
info.size = int(_BADGE_SIZE * icon_width)
info.attach_x = int(icon_info.attach_x * icon_width - info.size / 2)
info.attach_y = int(icon_info.attach_y * icon_height - info.size / 2)
if info.attach_x < 0 or info.attach_y < 0:
info.icon_padding = max(-info.attach_x, -info.attach_y)
elif info.attach_x + info.size > icon_width or \
info.attach_y + info.size > icon_height:
x_padding = info.attach_x + info.size - icon_width
y_padding = info.attach_y + info.size - icon_height
info.icon_padding = max(x_padding, y_padding)
return info
def _get_xo_color(self):
if self.stroke_color and self.fill_color:
return XoColor('%s,%s' % (self.stroke_color, self.fill_color))
else:
return None
def _set_xo_color(self, xo_color):
if xo_color:
self.stroke_color = xo_color.get_stroke_color()
self.fill_color = xo_color.get_fill_color()
else:
self.stroke_color = None
self.fill_color = None
def get_surface(self):
cache_key = self._get_cache_key()
if cache_key in self._surface_cache:
return self._surface_cache[cache_key]
icon_info = self._get_icon_info()
if icon_info.file_name is None:
return None
is_svg = icon_info.file_name.endswith('.svg')
if is_svg:
handle = self._load_svg(icon_info.file_name)
dimensions = handle.get_dimension_data()
icon_width = int(dimensions[0])
icon_height = int(dimensions[1])
else:
pixbuf = gtk.gdk.pixbuf_new_from_file(icon_info.file_name)
icon_width = pixbuf.get_width()
icon_height = pixbuf.get_height()
badge_info = self._get_badge_info(icon_info, icon_width, icon_height)
padding = badge_info.icon_padding
width, height = self._get_size(icon_width, icon_height, padding)
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
context = cairo.Context(surface)
context.scale(float(width) / (icon_width + padding * 2),
float(height) / (icon_height + padding * 2))
context.save()
context.translate(padding, padding)
if is_svg:
handle.render_cairo(context)
else:
pixbuf_surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf)
context.set_source_surface(pixbuf_surface, 0, 0)
context.paint()
if self.badge_name:
context.restore()
context.translate(badge_info.attach_x, badge_info.attach_y)
self._draw_badge(context, badge_info.size)
self._surface_cache[cache_key] = surface
return surface
xo_color = property(_get_xo_color, _set_xo_color)
class Icon(gtk.Image):
__gtype_name__ = 'SugarIcon'
__gproperties__ = {
'xo-color' : (object, None, None,
gobject.PARAM_WRITABLE),
'fill-color' : (object, None, None,
gobject.PARAM_READWRITE),
'stroke-color' : (object, None, None,
gobject.PARAM_READWRITE),
'badge-name' : (str, None, None, None,
gobject.PARAM_READWRITE)
}
def __init__(self, **kwargs):
self._buffer = _IconBuffer()
gobject.GObject.__init__(self, **kwargs)
def _sync_image_properties(self):
if self._buffer.icon_name != self.props.icon_name:
self._buffer.icon_name = self.props.icon_name
if self._buffer.file_name != self.props.file:
self._buffer.file_name = self.props.file
width, height = gtk.icon_size_lookup(self.props.icon_size)
if self._buffer.width != width and self._buffer.height != height:
self._buffer.width = width
self._buffer.height = height
def _icon_size_changed_cb(self, image, pspec):
self._buffer.icon_size = self.props.icon_size
def _icon_name_changed_cb(self, image, pspec):
self._buffer.icon_name = self.props.icon_name
def _file_changed_cb(self, image, pspec):
self._buffer.file_name = self.props.file
def _update_buffer_size(self):
width, height = gtk.icon_size_lookup(self.props.icon_size)
self._buffer.width = width
self._buffer.height = height
def do_size_request(self, requisition):
self._sync_image_properties()
surface = self._buffer.get_surface()
if surface:
requisition[0] = surface.get_width()
requisition[1] = surface.get_height()
elif self._buffer.width and self._buffer.height:
requisition[0] = self._buffer.width
requisition[1] = self._buffer.width
else:
requisition[0] = requisition[1] = 0
def do_expose_event(self, event):
self._sync_image_properties()
surface = self._buffer.get_surface()
if surface is None:
return
xpad, ypad = self.get_padding()
xalign, yalign = self.get_alignment()
requisition = self.get_child_requisition()
if self.get_direction != gtk.TEXT_DIR_LTR:
xalign = 1.0 - xalign
x = math.floor(self.allocation.x + xpad +
(self.allocation.width - requisition[0]) * xalign)
y = math.floor(self.allocation.y + ypad +
(self.allocation.height - requisition[1]) * yalign)
cr = self.window.cairo_create()
cr.set_source_surface(surface, x, y)
cr.paint()
def do_set_property(self, pspec, value):
if pspec.name == 'xo-color':
if self._buffer.xo_color != value:
self._buffer.xo_color = value
self.queue_draw()
elif pspec.name == 'fill-color':
if self._buffer.fill_color != value:
self._buffer.fill_color = value
self.queue_draw()
elif pspec.name == 'stroke-color':
if self._buffer.stroke_color != value:
self._buffer.stroke_color = value
self.queue_draw()
elif pspec.name == 'badge-name':
if self._buffer.badge_name != value:
self._buffer.badge_name = value
self.queue_resize()
else:
gtk.Image.do_set_property(self, pspec, value)
def do_get_property(self, pspec):
if pspec.name == 'fill-color':
return self._buffer.fill_color
elif pspec.name == 'stroke-color':
return self._buffer.stroke_color
elif pspec.name == 'badge-name':
return self._buffer.badge_name
else:
return gtk.Image.do_get_property(self, pspec)
class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
__gtype_name__ = 'CanvasIcon'
__gproperties__ = {
'file-name' : (str, None, None, None,
gobject.PARAM_READWRITE),
'icon-name' : (str, None, None, None,
gobject.PARAM_READWRITE),
'xo-color' : (object, None, None,
gobject.PARAM_WRITABLE),
'fill-color' : (object, None, None,
gobject.PARAM_READWRITE),
'stroke-color' : (object, None, None,
gobject.PARAM_READWRITE),
'size' : (int, None, None, 0, 1024, 0,
gobject.PARAM_READWRITE),
'scale' : (float, None, None, -1024.0, 1024.0, 1.0,
gobject.PARAM_READWRITE),
'cache' : (bool, None, None, False,
gobject.PARAM_READWRITE),
'badge-name' : (str, None, None, None,
gobject.PARAM_READWRITE)
}
def __init__(self, **kwargs):
self._buffer = _IconBuffer()
hippo.CanvasBox.__init__(self, **kwargs)
self._palette = None
def do_set_property(self, pspec, value):
if pspec.name == 'file-name':
if self._buffer.file_name != value:
self._buffer.file_name = value
self.emit_paint_needed(0, 0, -1, -1)
elif pspec.name == 'icon-name':
if self._buffer.icon_name != value:
self._buffer.icon_name = value
self.emit_paint_needed(0, 0, -1, -1)
elif pspec.name == 'xo-color':
if self._buffer.xo_color != value:
self._buffer.xo_color = value
self.emit_paint_needed(0, 0, -1, -1)
elif pspec.name == 'fill-color':
if self._buffer.fill_color != value:
self._buffer.fill_color = value
self.emit_paint_needed(0, 0, -1, -1)
elif pspec.name == 'stroke-color':
if self._buffer.stroke_color != value:
self._buffer.stroke_color = value
self.emit_paint_needed(0, 0, -1, -1)
elif pspec.name == 'size':
if self._buffer.width != value:
self._buffer.width = value
self._buffer.height = value
self.emit_request_changed()
elif pspec.name == 'scale':
if self._buffer.scale != value:
self._buffer.scale = value
self.emit_request_changed()
elif pspec.name == 'cache':
self._buffer.cache = value
elif pspec.name == 'badge-name':
if self._buffer.badge_name != value:
self._buffer.badge_name = value
self.emit_paint_needed(0, 0, -1, -1)
def do_get_property(self, pspec):
if pspec.name == 'size':
return self._buffer.width
elif pspec.name == 'file-name':
return self._buffer.file_name
elif pspec.name == 'icon-name':
return self._buffer.icon_name
elif pspec.name == 'fill-color':
return self._buffer.fill_color
elif pspec.name == 'stroke-color':
return self._buffer.stroke_color
elif pspec.name == 'cache':
return self._buffer.cache
elif pspec.name == 'badge-name':
return self._buffer.badge_name
elif pspec.name == 'scale':
return self._buffer.scale
def do_paint_below_children(self, cr, damaged_box):
surface = self._buffer.get_surface()
if surface:
width, height = self.get_allocation()
x = (width - surface.get_width()) / 2
y = (height - surface.get_height()) / 2
cr.set_source_surface(surface, x, y)
cr.paint()
def do_get_content_width_request(self):
surface = self._buffer.get_surface()
if surface:
size = surface.get_width()
elif self._buffer.width:
size = self._buffer.width
else:
size = 0
return size, size
def do_get_content_height_request(self, for_width):
surface = self._buffer.get_surface()
if surface:
size = surface.get_height()
elif self._buffer.height:
size = self._buffer.height
else:
size = 0
return size, size
def do_button_press_event(self, event):
self.emit_activated()
return True
def get_palette(self):
return self._palette
def set_palette(self, palette):
if self._palette is not None:
self._palette.props.invoker = None
self._palette = palette
if not self._palette.props.invoker:
self._palette.props.invoker = CanvasInvoker(self)
def set_tooltip(self, text):
self.set_palette(Palette(text))
palette = property(get_palette, set_palette)
def get_icon_state(base_name, perc):
step = 5
strength = round(perc / step) * step
icon_theme = gtk.icon_theme_get_default()
while strength <= 100:
icon_name = '%s-%03d' % (base_name, strength)
if icon_theme.has_icon(icon_name):
return icon_name
strength = strength + step
+45
View File
@@ -0,0 +1,45 @@
# 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 gtk
from sugar import _sugarext
ICON_ENTRY_PRIMARY = _sugarext.ICON_ENTRY_PRIMARY
ICON_ENTRY_SECONDARY = _sugarext.ICON_ENTRY_SECONDARY
class IconEntry(_sugarext.IconEntry):
def set_icon_from_name(self, position, name):
icon_theme = gtk.icon_theme_get_default()
icon_info = icon_theme.lookup_icon(name,
gtk.ICON_SIZE_SMALL_TOOLBAR,
0)
pixbuf = gtk.gdk.pixbuf_new_from_file(icon_info.get_filename())
image = gtk.Image()
image.set_from_pixbuf(pixbuf)
image.show()
self.set_icon(position, image)
def set_icon(self, position, image):
if image.get_storage_type() not in [gtk.IMAGE_PIXBUF, gtk.IMAGE_STOCK]:
raise ValueError('Image must have a storage type of pixbuf or ' +
'stock, not %r.' % image.get_storage_type())
_sugarext.IconEntry.set_icon(self, position, image)
+28
View File
@@ -0,0 +1,28 @@
# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
#
# 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 gtk
from sugar.graphics.icon import Icon
class MenuItem(gtk.ImageMenuItem):
def __init__(self, text_label, icon_name=None):
gtk.ImageMenuItem.__init__(self, text_label)
if icon_name:
icon = Icon(icon_name=icon_name, icon_size=gtk.ICON_SIZE_MENU)
self.set_image(icon)
icon.show()
+115
View File
@@ -0,0 +1,115 @@
"""Notebook class
This class create a gtk.Notebook() widget supporting
a close button in every tab when the 'can-close-tabs' gproperty
is enabled (True)
"""
# Copyright (C) 2007, Eduardo Silva (edsiper@gmail.com)
#
# 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 gtk
import gobject
class Notebook(gtk.Notebook):
__gtype_name__ = 'SugarNotebook'
__gproperties__ = {
'can-close-tabs': (bool, None, None, False,
gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY)
}
def __init__(self, **kwargs):
# Initialise the Widget
#
# Side effects:
# Set the 'can-close-tabs' property using **kwargs
# Set True the scrollable notebook property
gobject.GObject.__init__(self, **kwargs)
gtk.Notebook.__init__(self)
self.set_scrollable(True)
self.show()
def do_set_property(self, pspec, value):
if pspec.name == 'can-close-tabs':
self._can_close_tabs = value
else:
raise AssertionError
def _add_icon_to_button(self, button):
icon_box = gtk.HBox()
image = gtk.Image()
image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
gtk.Button.set_relief(button, gtk.RELIEF_NONE)
settings = gtk.Widget.get_settings(button)
(w,h) = gtk.icon_size_lookup_for_settings(settings, gtk.ICON_SIZE_MENU)
gtk.Widget.set_size_request(button, w + 4, h + 4)
image.show()
icon_box.pack_start(image, True, False, 0)
button.add(icon_box)
icon_box.show()
def _create_custom_tab(self, text, child):
event_box = gtk.EventBox()
tab_box = gtk.HBox(False, 2)
tab_label = gtk.Label(text)
tab_button = gtk.Button()
tab_button.connect('clicked', self._close_page, child)
# Add a picture on a button
self._add_icon_to_button(tab_button)
icon_box = gtk.HBox(False, 0)
event_box.show()
tab_button.show()
tab_label.show()
tab_box.pack_start(tab_label, True)
tab_box.pack_start(tab_button, True)
tab_box.show_all()
event_box.add(tab_box)
return event_box
def add_page(self, text_label, widget):
# Add a new page to the notebook
if self._can_close_tabs:
eventbox = self._create_custom_tab(text_label, widget)
self.append_page(widget, eventbox)
else:
self.append_page(widget, gtk.Label(text_label))
pages = self.get_n_pages()
# Set the new page
self.set_current_page(pages - 1)
self.show_all()
return True
def _close_page(self, button, child):
# Remove a page from the notebook
page = self.page_num(child)
if page != -1:
self.remove_page(page)
+209
View File
@@ -0,0 +1,209 @@
# 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 logging
import time
from gettext import gettext as _
import gtk
import hippo
from sugar.bundle.activitybundle import ActivityBundle
from sugar.graphics import style
from sugar.graphics.icon import CanvasIcon
from sugar.graphics.xocolor import XoColor
from sugar.graphics.roundbox import CanvasRoundBox
from sugar.datastore import datastore
from sugar import activity
from sugar.objects import objecttype
# TODO: Activities should request the Journal to open objectchooser dialogs. In
# that way, we'll be able to reuse most of this code inside the Journal.
class ObjectChooser(gtk.Dialog):
def __init__(self, title=None, parent=None, flags=0):
gtk.Dialog.__init__(self, title, parent, flags, (gtk.STOCK_CANCEL,
gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
self._jobjects = None
self._query = {}
self._selected_entry = False
self._box = hippo.CanvasBox()
self._box.props.background_color = style.COLOR_PANEL_GREY.get_int()
self._box.props.spacing = style.DEFAULT_SPACING
self._box.props.padding = style.DEFAULT_SPACING
canvas = hippo.Canvas()
canvas.set_root(self._box)
scrolled_window = gtk.ScrolledWindow()
scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
scrolled_window.add_with_viewport(canvas)
canvas.show()
self.vbox.add(scrolled_window)
scrolled_window.show()
scrolled_window.props.shadow_type = gtk.SHADOW_NONE
scrolled_window.get_child().props.shadow_type = gtk.SHADOW_NONE
self.refresh()
height = self.get_screen().get_height() * 3 / 4
width = self.get_screen().get_width() * 3 / 4
self.set_default_size(width, height)
def update_with_query(self, query):
self._query = query
self.refresh()
def refresh(self):
logging.debug('ListView.refresh: %r' % self._query)
self._jobjects, total_count = datastore.find(self._query, sorting=['-mtime'])
self._update()
def _update(self):
self._box.remove_all()
for jobject in self._jobjects:
entry_view = CollapsedEntry(jobject)
entry_view.connect('button-release-event',
self._entry_view_button_release_event_cb)
self._box.append(entry_view)
def _entry_view_button_release_event_cb(self, entry_view, event):
if self._selected_entry:
self._selected_entry.set_selected(False)
entry_view.set_selected(True)
self._selected_entry = entry_view
def get_selected_object(self):
if self._selected_entry:
return self._selected_entry.jobject
else:
return None
class CollapsedEntry(CanvasRoundBox):
_DATE_COL_WIDTH = style.zoom(100)
_BUDDIES_COL_WIDTH = style.zoom(50)
def __init__(self, jobject):
CanvasRoundBox.__init__(self)
self.props.box_height = style.zoom(75)
self.props.spacing = style.DEFAULT_SPACING
self.props.border_color = style.COLOR_BLACK.get_int()
self.props.background_color = style.COLOR_PANEL_GREY.get_int()
self.jobject = jobject
self._icon_name = None
date = hippo.CanvasText(text=self._format_date(),
xalign=hippo.ALIGNMENT_START,
font_desc=style.FONT_NORMAL.get_pango_desc(),
box_width=self._DATE_COL_WIDTH)
self.append(date)
icon = CanvasIcon(icon_name=self._get_icon_name(),
box_width=style.zoom(75))
if self.jobject.metadata.has_key('icon-color'):
icon.props.xo_color = XoColor(self.jobject.metadata['icon-color'])
self.append(icon)
title = hippo.CanvasText(text=self._format_title(),
xalign=hippo.ALIGNMENT_START,
font_desc=style.FONT_BOLD.get_pango_desc(),
size_mode=hippo.CANVAS_SIZE_WRAP_WORD)
self.append(title)
def _get_icon_name(self):
if self._icon_name:
return self._icon_name
if self.jobject.is_activity_bundle():
bundle = ActivityBundle(self.jobject.file_path)
self._icon_name = bundle.get_icon()
if self.jobject.metadata['activity']:
service_name = self.jobject.metadata['activity']
activity_info = activity.get_registry().get_activity(service_name)
if activity_info:
self._icon_name = activity_info.icon
mime_type = self.jobject.metadata['mime_type']
if not self._icon_name and mime_type:
type = objecttype.get_registry().get_type_for_mime(mime_type)
if type:
self._icon_name = type.icon
if not self._icon_name:
self._icon_name = 'image-missing'
return self._icon_name
def _format_date(self):
""" Convert from a string in iso format to a more human-like format. """
return _get_elapsed_string(self.jobject.metadata['mtime'])
def _format_title(self):
return '"%s"' % self.jobject.metadata['title']
def set_selected(self, selected):
if selected:
self.props.border_color = style.COLOR_WHITE.get_int()
self.props.background_color = style.COLOR_WHITE.get_int()
else:
self.props.border_color = style.COLOR_BLACK.get_int()
self.props.background_color = style.COLOR_PANEL_GREY.get_int()
def _get_elapsed_string(date_string, max_levels=2):
ti = time.strptime(date_string, "%Y-%m-%dT%H:%M:%S")
units = [[_('%d year'), _('%d years'), 356 * 24 * 60 * 60],
[_('%d month'), _('%d months'), 30 * 24 * 60 * 60],
[_('%d week'), _('%d weeks'), 7 * 24 * 60 * 60],
[_('%d day'), _('%d days'), 24 * 60 * 60],
[_('%d hour'), _('%d hours'), 60 * 60],
[_('%d minute'), _('%d minutes'), 60],
[_('%d second'), _('%d seconds'), 1]]
levels = 0
result = ''
elapsed_seconds = int(time.time() - time.mktime(ti))
for name_singular, name_plural, factor in units:
elapsed_units = elapsed_seconds / factor
if elapsed_units > 0:
if levels > 0:
if max_levels - levels == 1:
result += _(' and ')
else:
result += _(', ')
if elapsed_units == 1:
result += name_singular % elapsed_units
else:
result += name_plural % elapsed_units
elapsed_seconds -= elapsed_units * factor
levels += 1
if levels == max_levels:
break
return result
+722
View File
@@ -0,0 +1,722 @@
# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
#
# 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 gtk
import gobject
import time
import hippo
from sugar.graphics import palettegroup
from sugar.graphics import animator
from sugar.graphics import style
from sugar import _sugarext
# Helper function to find the gap position and size of widget a
def _calculate_gap(a, b):
# Test for each side if the palette and invoker are
# adjacent to each other.
gap = True
if a.y + a.height == b.y:
gap_side = gtk.POS_BOTTOM
elif a.x + a.width == b.x:
gap_side = gtk.POS_RIGHT
elif a.x == b.x + b.width:
gap_side = gtk.POS_LEFT
elif a.y == b.y + b.height:
gap_side = gtk.POS_TOP
else:
gap = False
if gap:
if gap_side == gtk.POS_BOTTOM or gap_side == gtk.POS_TOP:
gap_start = min(a.width, max(0, b.x - a.x))
gap_size = max(0, min(a.width,
(b.x + b.width) - a.x) - gap_start)
elif gap_side == gtk.POS_RIGHT or gap_side == gtk.POS_LEFT:
gap_start = min(a.height, max(0, b.y - a.y))
gap_size = max(0, min(a.height,
(b.y + b.height) - a.y) - gap_start)
if gap and gap_size > 0:
return (gap_side, gap_start, gap_size)
else:
return False
class Palette(gtk.Window):
PRIMARY = 0
SECONDARY = 1
__gtype_name__ = 'SugarPalette'
__gproperties__ = {
'invoker' : (object, None, None,
gobject.PARAM_READWRITE)
}
__gsignals__ = {
'popup' : (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([])),
'popdown' : (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([]))
}
def __init__(self, label, accel_path=None, menu_after_content=False):
gtk.Window.__init__(self)
self.set_decorated(False)
self.set_resizable(False)
# Just assume xthickness and ythickness are the same
self.set_border_width(self.style.xthickness)
self.connect('realize', self._realize_cb)
self.palette_state = self.PRIMARY
self._alignment = None
self._old_alloc = None
self._full_request = [0, 0]
self._cursor_x = 0
self._cursor_y = 0
self._invoker = None
self._group_id = None
self._up = False
self._palette_popup_sid = None
self._popup_anim = animator.Animator(0.3, 10)
self._popup_anim.add(_PopupAnimation(self))
self._secondary_anim = animator.Animator(1.0, 10)
self._secondary_anim.add(_SecondaryAnimation(self))
self._popdown_anim = animator.Animator(0.6, 10)
self._popdown_anim.add(_PopdownAnimation(self))
vbox = gtk.VBox()
self._label = gtk.Label()
self._label.set_size_request(-1, style.zoom(style.GRID_CELL_SIZE))
self._label.set_alignment(0, 0.5)
self._label.set_padding(style.zoom(15), 0)
vbox.pack_start(self._label, False)
self._secondary_box = gtk.VBox()
vbox.pack_start(self._secondary_box)
self._separator = gtk.HSeparator()
self._secondary_box.pack_start(self._separator)
self._menu_content_separator = gtk.HSeparator()
if menu_after_content:
self._add_content()
self._secondary_box.pack_start(self._menu_content_separator)
self._add_menu()
else:
self._add_menu()
self._secondary_box.pack_start(self._menu_content_separator)
self._add_content()
self.action_bar = PaletteActionBar()
self._secondary_box.pack_start(self.action_bar)
self.action_bar.show()
self.add(vbox)
vbox.show()
# The menu is not shown here until an item is added
self.menu = _Menu(self)
self.connect('enter-notify-event',
self._enter_notify_event_cb)
self.connect('leave-notify-event',
self._leave_notify_event_cb)
self.set_primary_text(label, accel_path)
self.set_group_id('default')
def _add_menu(self):
self._menu_box = gtk.VBox()
self._secondary_box.pack_start(self._menu_box)
self._menu_box.show()
def _add_content(self):
# The content is not shown until a widget is added
self._content = gtk.VBox()
self._content.set_border_width(style.zoom(15))
self._secondary_box.pack_start(self._content)
def do_style_set(self, previous_style):
# Prevent a warning from pygtk
if previous_style is not None:
gtk.Window.do_style_set(self, previous_style)
self.set_border_width(self.style.xthickness)
def is_up(self):
return self._up
def get_rect(self):
win_x, win_y = self.window.get_origin()
rectangle = self.get_allocation()
x = win_x + rectangle.x
y = win_y + rectangle.y
width = rectangle.width
height = rectangle.height
return gtk.gdk.Rectangle(x, y, width, height)
def set_primary_text(self, label, accel_path=None):
if label is not None:
self._label.set_markup("<b>"+label+"</b>")
self._label.show()
def set_content(self, widget):
if len(self._content.get_children()) > 0:
self._content.remove(self._content.get_children()[0])
if widget is not None:
self._content.add(widget)
self._content.show()
else:
self._content.hide()
self._update_accept_focus()
self._update_separators()
def set_group_id(self, group_id):
if self._group_id:
group = palettegroup.get_group(self._group_id)
group.remove(self)
if group_id:
self._group_id = group_id
group = palettegroup.get_group(group_id)
group.add(self)
def do_set_property(self, pspec, value):
if pspec.name == 'invoker':
if self._invoker is not None:
self._invoker.disconnect(self._enter_invoker_hid)
self._invoker.disconnect(self._leave_invoker_hid)
self._invoker = value
if value is not None:
self._enter_invoker_hid = self._invoker.connect(
'mouse-enter', self._invoker_mouse_enter_cb)
self._leave_invoker_hid = self._invoker.connect(
'mouse-leave', self._invoker_mouse_leave_cb)
else:
raise AssertionError
def do_get_property(self, pspec):
if pspec.name == 'invoker':
return self._invoker
else:
raise AssertionError
def do_size_request(self, requisition):
gtk.Window.do_size_request(self, requisition)
requisition.width = max(requisition.width, self._full_request[0])
# Minimum width
requisition.width = max(requisition.width,
style.zoom(style.GRID_CELL_SIZE*2))
def do_size_allocate(self, allocation):
gtk.Window.do_size_allocate(self, allocation)
if self._old_alloc is None or \
self._old_alloc.x != allocation.x or \
self._old_alloc.y != allocation.y or \
self._old_alloc.width != allocation.width or \
self._old_alloc.height != allocation.height:
self.queue_draw()
# We need to store old allocation because when size_allocate
# is called widget.allocation is already updated.
# gtk.Window resizing is different from normal containers:
# the X window is resized, widget.allocation is updated from
# the configure request handler and finally size_allocate is called.
self._old_alloc = allocation
def do_expose_event(self, event):
# We want to draw a border with a beautiful gap
if self._invoker is not None and self._invoker.has_rectangle_gap():
invoker = self._invoker.get_rect()
palette = self.get_rect()
gap = _calculate_gap(palette, invoker)
else:
gap = False
if gap:
self.style.paint_box_gap(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_IN, event.area, self, "palette",
0, 0,
self.allocation.width,
self.allocation.height,
gap[0], gap[1], gap[2])
else:
self.style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_IN, event.area, self, "palette",
0, 0,
self.allocation.width,
self.allocation.height)
# Fall trough to the container expose handler.
# (Leaving out the window expose handler which redraws everything)
gtk.Bin.do_expose_event(self, event)
def _update_separators(self):
visible = len(self.menu.get_children()) > 0 or \
len(self._content.get_children()) > 0
self._separator.props.visible = visible
visible = len(self.menu.get_children()) > 0 and \
len(self._content.get_children()) > 0
self._menu_content_separator.props.visible = visible
def _update_accept_focus(self):
accept_focus = len(self._content.get_children())
if self.window:
self.window.set_accept_focus(accept_focus)
def _realize_cb(self, widget):
self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
self._update_accept_focus()
def _update_full_request(self):
state = self.palette_state
self._set_state(self.SECONDARY)
self._full_request = self.size_request()
self._set_state(state)
def _update_position(self):
invoker = self._invoker
if invoker is None or self._alignment is None:
logging.error('Cannot update the palette position.')
return
rect = self.size_request()
position = invoker.get_position_for_alignment(self._alignment, rect)
if position is None:
position = invoker.get_position(rect)
self.move(position.x, position.y)
def _show(self):
if self._up:
return
self._palette_popup_sid = _palette_observer.connect(
'popup', self._palette_observer_popup_cb)
if self._invoker is not None:
self._update_full_request()
self._alignment = self._invoker.get_alignment(self._full_request)
self._update_position()
self.menu.set_active(True)
self.show()
self._invoker.notify_popup()
self._up = True
_palette_observer.emit('popup', self)
self.emit('popup')
def _hide(self):
self._secondary_anim.stop()
if not self._palette_popup_sid is None:
_palette_observer.disconnect(self._palette_popup_sid)
self._palette_popup_sid = None
self.menu.set_active(False)
self.hide()
if self._invoker:
self._invoker.notify_popdown()
self._up = False
self.emit('popdown')
def popup(self, immediate=False):
self._popdown_anim.stop()
if not immediate:
self._popup_anim.start()
else:
self._show()
self._secondary_anim.start()
def popdown(self, immediate=False):
self._popup_anim.stop()
if not immediate:
self._popdown_anim.start()
else:
self._hide()
def _set_state(self, state):
if self.palette_state == state:
return
if state == self.PRIMARY:
self.menu.unembed()
self._secondary_box.hide()
elif state == self.SECONDARY:
self.menu.embed(self._menu_box)
self._secondary_box.show()
self.palette_state = state
def _invoker_mouse_enter_cb(self, invoker):
immediate = False
if self.is_up():
self._popdown_anim.stop()
return
if self._group_id:
group = palettegroup.get_group(self._group_id)
if group and group.is_up():
self._set_state(group.get_state())
immediate = True
group.popdown()
self.popup(immediate=immediate)
def _invoker_mouse_leave_cb(self, invoker):
self.popdown()
def _enter_notify_event_cb(self, widget, event):
if event.detail != gtk.gdk.NOTIFY_INFERIOR:
self._popdown_anim.stop()
self._secondary_anim.start()
def _leave_notify_event_cb(self, widget, event):
if event.detail != gtk.gdk.NOTIFY_INFERIOR:
self.popdown()
def _palette_observer_popup_cb(self, observer, palette):
if self != palette:
self._hide()
class PaletteActionBar(gtk.HButtonBox):
def add_action(label, icon_name=None):
button = Button(label)
if icon_name:
icon = Icon(icon_name)
button.set_image(icon)
icon.show()
self.pack_start(button)
button.show()
class _Menu(_sugarext.Menu):
__gtype_name__ = 'SugarPaletteMenu'
def __init__(self, palette):
_sugarext.Menu.__init__(self)
self._palette = palette
def do_insert(self, item, position):
_sugarext.Menu.do_insert(self, item, position)
self._palette._update_separators()
self.show()
def do_expose_event(self, event):
# Ignore the Menu expose, just do the MenuShell expose to prevent any
# border from being drawn here. A border is drawn by the palette object
# around everything.
gtk.MenuShell.do_expose_event(self, event)
def do_grab_notify(self, was_grabbed):
# Ignore grab_notify as the menu would close otherwise
pass
def do_deactivate(self):
self._palette._hide()
class _PopupAnimation(animator.Animation):
def __init__(self, palette):
animator.Animation.__init__(self, 0.0, 1.0)
self._palette = palette
def next_frame(self, current):
if current == 1.0:
self._palette._set_state(Palette.PRIMARY)
self._palette._show()
class _SecondaryAnimation(animator.Animation):
def __init__(self, palette):
animator.Animation.__init__(self, 0.0, 1.0)
self._palette = palette
def next_frame(self, current):
if current == 1.0:
self._palette._set_state(Palette.SECONDARY)
self._palette._update_position()
class _PopdownAnimation(animator.Animation):
def __init__(self, palette):
animator.Animation.__init__(self, 0.0, 1.0)
self._palette = palette
def next_frame(self, current):
if current == 1.0:
self._palette._hide()
class Invoker(gobject.GObject):
__gtype_name__ = 'SugarPaletteInvoker'
__gsignals__ = {
'mouse-enter': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
'mouse-leave': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
'focus-out': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
}
ANCHORED = 0
AT_CURSOR = 1
BOTTOM = [(0.0, 0.0, 0.0, 1.0),
(-1.0, 0.0, 1.0, 1.0)]
RIGHT = [(0.0, 0.0, 1.0, 0.0),
(0.0, -1.0, 1.0, 1.0)]
TOP = [(0.0, -1.0, 0.0, 0.0),
(-1.0, -1.0, 1.0, 0.0)]
LEFT = [(-1.0, 0.0, 0.0, 0.0),
(-1.0, -1.0, 0.0, 1.0)]
def __init__(self):
gobject.GObject.__init__(self)
self._screen_area = gtk.gdk.Rectangle(0, 0, gtk.gdk.screen_width(),
gtk.gdk.screen_height())
self._position_hint = self.ANCHORED
self._cursor_x = -1
self._cursor_y = -1
def _get_position_for_alignment(self, alignment, palette_dim):
palette_halign = alignment[0]
palette_valign = alignment[1]
invoker_halign = alignment[2]
invoker_valign = alignment[3]
if self._cursor_x == -1 or self._cursor_y == -1:
display = gtk.gdk.display_get_default()
screen, x, y, mask = display.get_pointer()
self._cursor_x = x
self._cursor_y = y
if self._position_hint is self.ANCHORED:
rect = self.get_rect()
else:
dist = style.PALETTE_CURSOR_DISTANCE
rect = gtk.gdk.Rectangle(self._cursor_x - dist,
self._cursor_y - dist,
dist * 2, dist * 2)
palette_width, palette_height = palette_dim
x = rect.x + rect.width * invoker_halign + \
palette_width * palette_halign
y = rect.y + rect.height * invoker_valign + \
palette_height * palette_valign
return gtk.gdk.Rectangle(int(x), int(y),
palette_width, palette_height)
def _in_screen(self, rect):
return rect.x >= self._screen_area.x and \
rect.y >= self._screen_area.y and \
rect.x + rect.width <= self._screen_area.width and \
rect.y + rect.height <= self._screen_area.height
def _get_alignments(self):
if self._position_hint is self.AT_CURSOR:
return [(0.0, 0.0, 1.0, 1.0),
(0.0, -1.0, 1.0, 0.0),
(-1.0, -1.0, 0.0, 0.0),
(-1.0, 0.0, 0.0, 1.0)]
else:
return self.BOTTOM + self.RIGHT + self.TOP + self.LEFT
def get_position_for_alignment(self, alignment, palette_dim):
rect = self._get_position_for_alignment(alignment, palette_dim)
if self._in_screen(rect):
return rect
else:
return None
def get_position(self, palette_dim):
for alignment in self._get_alignments():
rect = self._get_position_for_alignment(alignment, palette_dim)
if self._in_screen(rect):
break
return rect
def get_alignment(self, palette_dim):
for alignment in self._get_alignments():
rect = self._get_position_for_alignment(alignment, palette_dim)
if self._in_screen(rect):
break
return alignment
def has_rectangle_gap(self):
return False
def draw_rectangle(self, event, palette):
pass
def notify_popup(self):
pass
def notify_popdown(self):
self._cursor_x = -1
self._cursor_y = -1
class WidgetInvoker(Invoker):
def __init__(self, widget):
Invoker.__init__(self)
self._widget = widget
widget.connect('enter-notify-event', self._enter_notify_event_cb)
widget.connect('leave-notify-event', self._leave_notify_event_cb)
def get_rect(self):
win_x, win_y = self._widget.window.get_origin()
rectangle = self._widget.get_allocation()
x = win_x + rectangle.x
y = win_y + rectangle.y
width = rectangle.width
height = rectangle.height
return gtk.gdk.Rectangle(x, y, width, height)
def has_rectangle_gap(self):
return True
def draw_rectangle(self, event, palette):
style = self._widget.style
gap = _calculate_gap(self.get_rect(), palette.get_rect())
if gap:
style.paint_box_gap(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_IN, event.area, self._widget,
"palette-invoker",
self._widget.allocation.x,
self._widget.allocation.y,
self._widget.allocation.width,
self._widget.allocation.height,
gap[0], gap[1], gap[2])
else:
style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_IN, event.area, self._widget,
"palette-invoker",
self._widget.allocation.x,
self._widget.allocation.y,
self._widget.allocation.width,
self._widget.allocation.height)
def _enter_notify_event_cb(self, widget, event):
self.emit('mouse-enter')
def _leave_notify_event_cb(self, widget, event):
self.emit('mouse-leave')
def get_toplevel(self):
return self._widget.get_toplevel()
def notify_popup(self):
Invoker.notify_popup(self)
self._widget.queue_draw()
def notify_popdown(self):
Invoker.notify_popdown(self)
self._widget.queue_draw()
class CanvasInvoker(Invoker):
def __init__(self, item):
Invoker.__init__(self)
self._item = item
self._position_hint = self.AT_CURSOR
item.connect('motion-notify-event',
self._motion_notify_event_cb)
def get_default_position(self):
return self.AT_CURSOR
def get_rect(self):
context = self._item.get_context()
if context:
x, y = context.translate_to_screen(self._item)
width, height = self._item.get_allocation()
return gtk.gdk.Rectangle(x, y, width, height)
else:
return gtk.gdk.Rectangle()
def _motion_notify_event_cb(self, button, event):
if event.detail == hippo.MOTION_DETAIL_ENTER:
context = self._item.get_context()
self.emit('mouse-enter')
elif event.detail == hippo.MOTION_DETAIL_LEAVE:
self.emit('mouse-leave')
return False
def get_toplevel(self):
return hippo.get_canvas_for_item(self._item).get_toplevel()
class ToolInvoker(WidgetInvoker):
def __init__(self, widget):
WidgetInvoker.__init__(self, widget.child)
def _get_alignments(self):
parent = self._widget.get_parent()
if parent is None:
return WidgetInvoker.get_alignments()
if parent.get_orientation() is gtk.ORIENTATION_HORIZONTAL:
return self.BOTTOM + self.TOP
else:
return self.LEFT + self.RIGHT
class _PaletteObserver(gobject.GObject):
__gtype_name__ = 'SugarPaletteObserver'
__gsignals__ = {
'popup': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([object]))
}
def __init__(self):
gobject.GObject.__init__(self)
_palette_observer = _PaletteObserver()
+90
View File
@@ -0,0 +1,90 @@
# Copyright (C) 2007 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 gobject
_groups = {}
def get_group(group_id):
if _groups.has_key(group_id):
group = _groups[group_id]
else:
group = Group()
_groups[group_id] = group
return group
class Group(gobject.GObject):
__gsignals__ = {
'popup' : (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([])),
'popdown' : (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([]))
}
def __init__(self):
gobject.GObject.__init__(self)
self._up = False
self._palettes = []
self._sig_ids = {}
def is_up(self):
return self._up
def get_state(self):
for palette in self._palettes:
if palette.is_up():
return palette.palette_state
return None
def add(self, palette):
self._palettes.append(palette)
self._sig_ids[palette] = []
sid = palette.connect('popup', self._palette_popup_cb)
self._sig_ids[palette].append(sid)
sid = palette.connect('popdown', self._palette_popdown_cb)
self._sig_ids[palette].append(sid)
def remove(self, palette):
sig_ids = self._sig_ids[palette]
for sid in sig_ids:
palette.disconnect(sid)
self._palettes.remove(palette)
def popdown(self):
for palette in self._palettes:
if palette.is_up():
palette.popdown(immediate=True)
def _palette_popup_cb(self, palette):
if not self._up:
self.emit('popup')
self._up = True
def _palette_popdown_cb(self, palette):
down = True
for palette in self._palettes:
if palette.is_up():
down = False
if down:
self._up = False
self.emit('popdown')
+23
View File
@@ -0,0 +1,23 @@
# Copyright (C) 2007, 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 gtk
class Panel(gtk.VBox):
__gtype_name__ = 'SugarPanel'
def __init__(self):
gtk.VBox.__init__(self)
+67
View File
@@ -0,0 +1,67 @@
# Copyright (C) 2007, Red Hat, Inc.
# 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 gtk
from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, ToolInvoker
class RadioToolButton(gtk.RadioToolButton):
__gtype_name__ = "SugarRadioToolButton"
def __init__(self, named_icon=None, group=None, xo_color=None):
gtk.RadioToolButton.__init__(self, group=group)
self._palette = None
self._xo_color = xo_color
self.set_named_icon(named_icon)
def set_named_icon(self, named_icon):
icon = Icon(icon_name=named_icon,
xo_color=self._xo_color,
icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
self.set_icon_widget(icon)
icon.show()
def get_palette(self):
return self._palette
def set_palette(self, palette):
if self._palette is not None:
self._palette.props.invoker = None
self._palette = palette
self._palette.props.invoker = ToolInvoker(self)
def set_tooltip(self, text):
self.set_palette(Palette(text))
def do_expose_event(self, event):
if self._palette and self._palette.is_up():
invoker = self._palette.props.invoker
invoker.draw_rectangle(event, self._palette)
elif self.child.state == gtk.STATE_PRELIGHT:
self.child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_NONE, event.area,
self.child, "toolbutton-prelight",
self.allocation.x,
self.allocation.y,
self.allocation.width,
self.allocation.height)
gtk.RadioToolButton.do_expose_event(self, event)
palette = property(get_palette, set_palette)
+66
View File
@@ -0,0 +1,66 @@
# Copyright (C) 2006-2007 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 math
import hippo
from sugar.graphics import style
class CanvasRoundBox(hippo.CanvasBox, hippo.CanvasItem):
__gtype_name__ = 'SugarRoundBox'
_BORDER_DEFAULT = style.LINE_WIDTH
def __init__(self, **kwargs):
hippo.CanvasBox.__init__(self, **kwargs)
# TODO: we should calculate this value depending on the height of the box.
self._radius = style.zoom(10)
self.props.orientation = hippo.ORIENTATION_HORIZONTAL
self.props.border = self._BORDER_DEFAULT
self.props.border_left = self._radius
self.props.border_right = self._radius
self.props.border_color = style.COLOR_BLACK.get_int()
def do_paint_background(self, cr, damaged_box):
[width, height] = self.get_allocation()
x = self._BORDER_DEFAULT / 2
y = self._BORDER_DEFAULT / 2
width -= self._BORDER_DEFAULT
height -= self._BORDER_DEFAULT
cr.move_to(x + self._radius, y);
cr.arc(x + width - self._radius, y + self._radius,
self._radius, math.pi * 1.5, math.pi * 2);
cr.arc(x + width - self._radius, x + height - self._radius,
self._radius, 0, math.pi * 0.5);
cr.arc(x + self._radius, y + height - self._radius,
self._radius, math.pi * 0.5, math.pi);
cr.arc(x + self._radius, y + self._radius, self._radius,
math.pi, math.pi * 1.5);
hippo.cairo_set_source_rgba32(cr, self.props.background_color)
cr.fill_preserve();
# TODO: we should be more consistent here with the border properties.
if self.props.border_color:
hippo.cairo_set_source_rgba32(cr, self.props.border_color)
cr.set_line_width(self.props.border_top)
cr.stroke()
+239
View File
@@ -0,0 +1,239 @@
# Copyright (C) 2007 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 numpy import array
from random import random
import hippo
import gobject
import gtk
_PLACE_TRIALS = 20
_MAX_WEIGHT = 255
_CELL_SIZE = 4
class _Grid(gobject.GObject):
__gsignals__ = {
'child-changed' : (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
def __init__(self, width, height):
gobject.GObject.__init__(self)
self.width = width
self.height = height
self._children = []
self._collisions = []
self._collisions_sid = 0
self._array = array([0], dtype='b')
self._array.resize(width * height)
def add(self, child, width, height):
trials = _PLACE_TRIALS
weight = _MAX_WEIGHT
while trials > 0 and weight:
x = int(random() * (self.width - width))
y = int(random() * (self.height - height))
rect = gtk.gdk.Rectangle(x, y, width, height)
new_weight = self._compute_weight(rect)
if weight > new_weight:
weight = new_weight
trials -= 1
child.grid_rect = rect
child.locked = False
self._add_child(child)
if weight > 0:
self._detect_collisions(child)
def remove(self, child):
self._children.remove(child)
self._remove_weight(child.grid_rect)
child.grid_rect = None
def _add_child(self, child):
self._children.append(child)
self.add_weight(child.grid_rect)
def _move_child(self, child, new_rect):
self._remove_weight(child.grid_rect)
self.add_weight(new_rect)
child.grid_rect = new_rect
self.emit('child-changed', child)
def _shift_child(self, child):
rect = child.grid_rect
weight = self._compute_weight(rect)
new_rects = []
if (rect.x + rect.width < self.width - 1):
new_rects.append(gtk.gdk.Rectangle(rect.x + 1, rect.y,
rect.width, rect.height))
if (rect.x - 1 > 0):
new_rects.append(gtk.gdk.Rectangle(rect.x - 1, rect.y,
rect.width, rect.height))
if (rect.y + rect.height < self.height - 1):
new_rects.append(gtk.gdk.Rectangle(rect.x, rect.y + 1,
rect.width, rect.height))
if (rect.y - 1 > 0):
new_rects.append(gtk.gdk.Rectangle(rect.x, rect.y - 1,
rect.width, rect.height))
best_rect = None
for new_rect in new_rects:
new_weight = self._compute_weight(new_rect)
if new_weight < weight:
best_rect = new_rect
weight = new_weight
if best_rect:
self._move_child(child, best_rect)
return weight
def _solve_collisions(self):
for collision in self._collisions[:]:
weight = self._shift_child(collision)
if not weight:
self._collisions.remove(collision)
return (len(self._collisions) > 0)
def _detect_collisions(self, child):
collision_found = False
for c in self._children:
intersection = child.grid_rect.intersect(c.grid_rect)
if c != child and intersection.width > 0:
if c not in self._collisions:
collision_found = True
self._collisions.append(c)
if collision_found:
if child not in self._collisions:
self._collisions.append(child)
# if len(self._collisions) and not self._collisions_sid:
# self._collisions_sid = gobject.idle_add(self._solve_collisions)
def add_weight(self, rect):
for i in range(rect.x, rect.x + rect.width):
for j in range(rect.y, rect.y + rect.height):
self[j, i] += 1
def _remove_weight(self, rect):
for i in range(rect.x, rect.x + rect.width):
for j in range(rect.y, rect.y + rect.height):
self[j, i] -= 1
def _compute_weight(self, rect):
weight = 0
for i in range(rect.x, rect.x + rect.width):
for j in range(rect.y, rect.y + rect.height):
weight += self[j, i]
return weight
def __getitem__(self, (row, col)):
return self._array[col + row * self.width]
def __setitem__(self, (row, col), value):
self._array[col + row * self.width] = value
class SpreadLayout(gobject.GObject,hippo.CanvasLayout):
__gtype_name__ = 'SugarSpreadLayout'
def __init__(self):
gobject.GObject.__init__(self)
min_width, width = self.do_get_width_request()
min_height, height = self.do_get_height_request(width)
self._grid = _Grid(width / _CELL_SIZE, height / _CELL_SIZE)
self._grid.connect('child-changed', self._grid_child_changed_cb)
def add_center(self, child):
self._box.append(child)
width, height = self._get_child_grid_size(child)
rect = gtk.gdk.Rectangle(int((self._grid.width - width) / 2),
int((self._grid.height - height) / 2),
width + 1, height + 1)
self._grid.add_weight(rect)
box_child = self._box.find_box_child(child)
box_child.grid_rect = None
def add(self, child):
self._box.append(child)
width, height = self._get_child_grid_size(child)
box_child = self._box.find_box_child(child)
self._grid.add(box_child, width, height)
def remove(self, child):
box_child = self._box.find_box_child(child)
self._grid.remove(box_child)
self._box.remove(child)
def do_set_box(self, box):
self._box = box
def do_get_height_request(self, for_width):
return 0, gtk.gdk.screen_height()
def do_get_width_request(self):
return 0, gtk.gdk.screen_width()
def do_allocate(self, x, y, width, height,
req_width, req_height, origin_changed):
for child in self._box.get_layout_children():
rect = child.grid_rect
if child.grid_rect:
child.allocate(rect.x * _CELL_SIZE,
rect.y * _CELL_SIZE,
rect.width * _CELL_SIZE,
rect.height * _CELL_SIZE,
origin_changed)
else:
min_w, child_width = child.get_width_request()
min_h, child_height = child.get_height_request(child_width)
child.allocate(x + (width - child_width) / 2,
y + (height - child_height) / 2,
child_width, child_height, origin_changed)
def _get_child_grid_size(self, child):
min_width, width = child.get_width_request()
min_height, height = child.get_height_request(width)
return int(width / _CELL_SIZE), int(height / _CELL_SIZE)
def _grid_child_changed_cb(self, grid, box_child):
box_child.item.emit_request_changed()
+147
View File
@@ -0,0 +1,147 @@
# Copyright (C) 2007, 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.
"""
All the constants are expressed in pixels. They are defined for the XO screen
and are usually adapted to different resolution by applying a zoom factor. The
factor for traditional 96 dpi screen is currently 0.72 which is the inverse
of the one we are using to adapt web pages to the XO screen. It should be
considered a reference value rather then a scale constant which has to be
automatically applied and always respected.
"""
import os
import gtk
import pango
_XO_DPI = 200.0
_FOCUS_LINE_WIDTH = 2
_TAB_CURVATURE = 1
def _get_screen_dpi():
xft_dpi = gtk.settings_get_default().get_property('gtk-xft-dpi')
return float(xft_dpi / 1024)
def _compute_zoom_factor():
if _get_screen_dpi() == 96.0:
if not os.environ.has_key('SUGAR_XO_STYLE') or \
not os.environ['SUGAR_XO_STYLE'] == 'yes':
return 0.72
return 1.0
def _compute_font_height(font):
widget = gtk.Label('')
context = widget.get_pango_context()
pango_font = context.load_font(font.get_pango_desc())
metrics = pango_font.get_metrics()
return pango.PIXELS(metrics.get_ascent() + metrics.get_descent())
class Font(object):
def __init__(self, desc):
self._desc = desc
def __str__(self):
return self._desc
def get_pango_desc(self):
return pango.FontDescription(self._desc)
class Color(object):
def __init__(self, color, alpha=1.0):
self._r, self._g, self._b = self._html_to_rgb(color)
self._a = alpha
def get_rgba(self):
return (self._r, self._g, self._b, self._a)
def get_int(self):
return int(self._a * 255) + (int(self._b * 255) << 8) + \
(int(self._g * 255) << 16) + (int(self._r * 255) << 24)
def get_gdk_color(self):
return gtk.gdk.Color(int(self._r * 65535), int(self._g * 65535),
int(self._b * 65535))
def get_html(self):
return '#%02x%02x%02x' % (self._r * 255, self._g * 255, self._b * 255)
def _html_to_rgb(self, html_color):
""" #RRGGBB -> (r, g, b) tuple (in float format) """
html_color = html_color.strip()
if html_color[0] == '#':
html_color = html_color[1:]
if len(html_color) != 6:
raise ValueError, "input #%s is not in #RRGGBB format" % html_color
r, g, b = html_color[:2], html_color[2:4], html_color[4:]
r, g, b = [int(n, 16) for n in (r, g, b)]
r, g, b = (r / 255.0, g / 255.0, b / 255.0)
return (r, g, b)
def get_svg(self):
if self._a == 0.0:
return 'none'
else:
return self.get_html()
def zoom(units):
return int(ZOOM_FACTOR * units)
ZOOM_FACTOR = _compute_zoom_factor()
DEFAULT_SPACING = zoom(8)
DEFAULT_PADDING = zoom(6)
GRID_CELL_SIZE = zoom(75)
LINE_WIDTH = zoom(2)
STANDARD_ICON_SIZE = zoom(55)
SMALL_ICON_SIZE = zoom(55 * 0.5)
MEDIUM_ICON_SIZE = zoom(55 * 1.5)
LARGE_ICON_SIZE = zoom(55 * 2.0)
XLARGE_ICON_SIZE = zoom(55 * 2.75)
FONT_SIZE = zoom(7 * _XO_DPI / _get_screen_dpi())
FONT_NORMAL = Font('Bitstream Vera Sans %d' % FONT_SIZE)
FONT_BOLD = Font('Bitstream Vera Sans bold %d' % FONT_SIZE)
FONT_NORMAL_H = _compute_font_height(FONT_NORMAL)
FONT_BOLD_H = _compute_font_height(FONT_BOLD)
TOOLBOX_SEPARATOR_HEIGHT = zoom(9)
TOOLBOX_HORIZONTAL_PADDING = zoom(75)
TOOLBOX_TAB_VBORDER = int((zoom(36) - FONT_NORMAL_H - _FOCUS_LINE_WIDTH) / 2)
TOOLBOX_TAB_HBORDER = zoom(15) - _FOCUS_LINE_WIDTH - _TAB_CURVATURE
TOOLBOX_TAB_LABEL_WIDTH = zoom(150 - 15 * 2)
COLOR_BLACK = Color('#000000')
COLOR_WHITE = Color('#FFFFFF')
COLOR_TRANSPARENT = Color('#FFFFFF', alpha=0.0)
COLOR_PANEL_GREY = Color('#C0C0C0')
COLOR_SELECTION_GREY = Color('#A6A6A6')
COLOR_TOOLBAR_GREY = Color('#404040')
COLOR_BUTTON_GREY = Color('#808080')
COLOR_INACTIVE_FILL = Color('#9D9FA1')
COLOR_INACTIVE_STROKE = Color('#757575')
COLOR_TEXT_FIELD_GREY = Color('#E5E5E5')
PALETTE_CURSOR_DISTANCE = zoom(10)
+63
View File
@@ -0,0 +1,63 @@
# Copyright (C) 2007, 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 gtk
from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, ToolInvoker
class ToggleToolButton(gtk.ToggleToolButton):
__gtype_name__ = "SugarToggleToolButton"
def __init__(self, named_icon=None):
gtk.ToggleToolButton.__init__(self)
self._palette = None
self.set_named_icon(named_icon)
def set_named_icon(self, named_icon):
icon = Icon(icon_name=named_icon)
self.set_icon_widget(icon)
icon.show()
def get_palette(self):
return self._palette
def set_palette(self, palette):
if self._palette is not None:
self._palette.props.invoker = None
self._palette = palette
self._palette.props.invoker = ToolInvoker(self)
def set_tooltip(self, text):
self.set_palette(Palette(text))
def do_expose_event(self, event):
if self._palette and self._palette.is_up():
invoker = self._palette.props.invoker
invoker.draw_rectangle(event, self._palette)
elif self.child.state == gtk.STATE_PRELIGHT:
self.child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_NONE, event.area,
self.child, "toolbutton-prelight",
self.allocation.x,
self.allocation.y,
self.allocation.width,
self.allocation.height)
gtk.ToggleToolButton.do_expose_event(self, event)
palette = property(get_palette, set_palette)
+91
View File
@@ -0,0 +1,91 @@
# Copyright (C) 2007, 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 gtk
import gobject
import hippo
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics import style
class Toolbox(gtk.VBox):
__gtype_name__ = 'SugarToolbox'
__gsignals__ = {
'current-toolbar-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
([int]))
}
def __init__(self):
gtk.VBox.__init__(self)
self._notebook = gtk.Notebook()
self._notebook.set_tab_pos(gtk.POS_BOTTOM)
self._notebook.set_show_border(False)
self._notebook.set_show_tabs(False)
self._notebook.props.tab_vborder = style.TOOLBOX_TAB_VBORDER
self._notebook.props.tab_hborder = style.TOOLBOX_TAB_HBORDER
self.pack_start(self._notebook)
self._notebook.show()
# FIXME improve gtk.Notebook and do this in the theme
self._separator = hippo.Canvas()
box = hippo.CanvasBox(
border_color=style.COLOR_BUTTON_GREY.get_int(),
background_color=style.COLOR_PANEL_GREY.get_int(),
box_height=style.TOOLBOX_SEPARATOR_HEIGHT,
border_bottom=style.LINE_WIDTH)
self._separator.set_root(box)
self.pack_start(self._separator, False)
self._notebook.connect('notify::page', self._notify_page_cb)
def _notify_page_cb(self, notebook, pspec):
self.emit('current-toolbar-changed', notebook.props.page)
def add_toolbar(self, name, toolbar):
label = gtk.Label(name)
label.set_size_request(style.TOOLBOX_TAB_LABEL_WIDTH, -1)
label.set_alignment(0.0, 0.5)
event_box = gtk.EventBox()
alignment = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
alignment.set_padding(0, 0, style.TOOLBOX_HORIZONTAL_PADDING,
style.TOOLBOX_HORIZONTAL_PADDING)
alignment.add(toolbar)
event_box.add(alignment)
alignment.show()
event_box.show()
self._notebook.append_page(event_box, label)
if self._notebook.get_n_pages() > 1:
self._notebook.set_show_tabs(True)
self._separator.show()
def remove_toolbar(self, index):
self._notebook.remove_page(index)
if self._notebook.get_n_pages() < 2:
self._notebook.set_show_tabs(False)
self._separator.hide()
def set_current_toolbar(self, index):
self._notebook.set_current_page(index)
+71
View File
@@ -0,0 +1,71 @@
# Copyright (C) 2007, 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 gtk
import gobject
import time
from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, ToolInvoker
class ToolButton(gtk.ToolButton):
__gtype_name__ = "SugarToolButton"
def __init__(self, icon_name=None):
gtk.ToolButton.__init__(self)
self._palette = None
if icon_name:
self.set_icon(icon_name)
self.connect('clicked', self._button_clicked_cb)
def set_icon(self, icon_name):
icon = Icon(icon_name=icon_name)
self.set_icon_widget(icon)
icon.show()
def get_palette(self):
return self._palette
def set_palette(self, palette):
if self._palette is not None:
self._palette.props.invoker = None
self._palette = palette
self._palette.props.invoker = ToolInvoker(self)
def set_tooltip(self, text):
self.set_palette(Palette(text))
def do_expose_event(self, event):
if self._palette and self._palette.is_up():
invoker = self._palette.props.invoker
invoker.draw_rectangle(event, self._palette)
elif self.child.state == gtk.STATE_PRELIGHT:
self.child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_NONE, event.area,
self.child, "toolbutton-prelight",
self.allocation.x,
self.allocation.y,
self.allocation.width,
self.allocation.height)
gtk.ToolButton.do_expose_event(self, event)
def _button_clicked_cb(self, widget):
if self._palette:
self._palette.popdown(True)
palette = property(get_palette, set_palette)
+59
View File
@@ -0,0 +1,59 @@
# Copyright (C) 2007, 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 gtk
import gobject
from sugar.graphics.combobox import ComboBox
from sugar.graphics import style
class ToolComboBox(gtk.ToolItem):
__gproperties__ = {
'label-text' : (str, None, None, None,
gobject.PARAM_WRITABLE),
}
def __init__(self, combo=None, **kwargs):
self.label = None
self._label_text = ''
gobject.GObject.__init__(self, **kwargs)
self.set_border_width(style.DEFAULT_PADDING)
hbox = gtk.HBox(False, style.DEFAULT_SPACING)
self.label = gtk.Label(self._label_text)
hbox.pack_start(self.label, False)
self.label.show()
if combo:
self.combo = combo
else:
self.combo = ComboBox()
hbox.pack_start(self.combo)
self.combo.show()
self.add(hbox)
hbox.show()
def do_set_property(self, pspec, value):
if pspec.name == 'label-text':
self._label_text = value
if self.label:
self.label.set_text(self._label_text)
+241
View File
@@ -0,0 +1,241 @@
# 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 gtk
from sugar.graphics import style
from sugar.graphics.palette import Palette, ToolInvoker
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.icon import Icon
_PREVIOUS_PAGE = 0
_NEXT_PAGE = 1
class _TrayViewport(gtk.Viewport):
__gproperties__ = {
'can-scroll' : (bool, None, None, False,
gobject.PARAM_READABLE),
}
def __init__(self, orientation):
self.orientation = orientation
self._can_scroll = False
gobject.GObject.__init__(self)
self.set_shadow_type(gtk.SHADOW_NONE)
self.traybar = gtk.Toolbar()
self.traybar.set_orientation(orientation)
self.traybar.set_show_arrow(False)
self.add(self.traybar)
self.traybar.show()
self.connect('size_allocate', self._size_allocate_cb)
def scroll(self, direction):
if direction == _PREVIOUS_PAGE:
self._scroll_previous()
elif direction == _NEXT_PAGE:
self._scroll_next()
def _scroll_next(self):
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
adj = self.get_hadjustment()
new_value = adj.value + self.allocation.width
adj.value = min(new_value, adj.upper - self.allocation.width)
else:
adj = self.get_vadjustment()
new_value = adj.value + self.allocation.height
adj.value = min(new_value, adj.upper - self.allocation.height)
def _scroll_previous(self):
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
adj = self.get_hadjustment()
new_value = adj.value - self.allocation.width
adj.value = max(adj.lower, new_value)
else:
adj = self.get_vadjustment()
new_value = adj.value - self.allocation.height
adj.value = max(adj.lower, new_value)
def do_size_request(self, requisition):
child_requisition = self.child.size_request()
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
requisition[0] = 0
requisition[1] = child_requisition[1]
else:
requisition[0] = child_requisition[0]
requisition[1] = 0
def do_get_property(self, pspec):
if pspec.name == 'can-scroll':
return self._can_scroll
def _size_allocate_cb(self, viewport, allocation):
bar_requisition = self.traybar.get_child_requisition()
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
can_scroll = bar_requisition[0] > allocation.width
else:
can_scroll = bar_requisition[1] > allocation.height
if can_scroll != self._can_scroll:
self._can_scroll = can_scroll
self.notify('can-scroll')
class _TrayScrollButton(gtk.Button):
def __init__(self, icon_name, scroll_direction):
gobject.GObject.__init__(self)
self._viewport = None
self._scroll_direction = scroll_direction
self.set_relief(gtk.RELIEF_NONE)
self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
icon = Icon(icon_name = icon_name,
icon_size=gtk.ICON_SIZE_SMALL_TOOLBAR)
self.set_image(icon)
icon.show()
self.connect('clicked', self._clicked_cb)
def set_viewport(self, viewport):
self._viewport = viewport
self._viewport.connect('notify::can-scroll',
self._viewport_can_scroll_changed_cb)
def _viewport_can_scroll_changed_cb(self, viewport, pspec):
self.props.visible = self._viewport.props.can_scroll
def _clicked_cb(self, button):
self._viewport.scroll(self._scroll_direction)
viewport = property(fset=set_viewport)
class HTray(gtk.HBox):
def __init__(self, **kwargs):
gobject.GObject.__init__(self, **kwargs)
scroll_left = _TrayScrollButton('go-left', _PREVIOUS_PAGE)
self.pack_start(scroll_left, False)
self._viewport = _TrayViewport(gtk.ORIENTATION_HORIZONTAL)
self.pack_start(self._viewport)
self._viewport.show()
scroll_right = _TrayScrollButton('go-right', _NEXT_PAGE)
self.pack_start(scroll_right, False)
scroll_left.viewport = self._viewport
scroll_right.viewport = self._viewport
def get_children(self):
return self._viewport.traybar.get_children()
def add_item(self, item, index=-1):
self._viewport.traybar.insert(item, index)
def remove_item(self, item):
self._viewport.traybar.remove(item)
def get_item_index(self, item):
return self._viewport.traybar.get_item_index(item)
class VTray(gtk.VBox):
def __init__(self, **kwargs):
gobject.GObject.__init__(self, **kwargs)
# FIXME we need a go-up icon
scroll_left = _TrayScrollButton('go-left', _PREVIOUS_PAGE)
self.pack_start(scroll_left, False)
self._viewport = _TrayViewport(gtk.ORIENTATION_VERTICAL)
self.pack_start(self._viewport)
self._viewport.show()
# FIXME we need a go-down icon
scroll_right = _TrayScrollButton('go-right', _NEXT_PAGE)
self.pack_start(scroll_right, False)
scroll_left.viewport = self._viewport
scroll_right.viewport = self._viewport
def get_children(self):
return self._viewport.traybar.get_children()
def add_item(self, item, index=-1):
self._viewport.traybar.insert(item, index)
def remove_item(self, item):
self._viewport.traybar.remove(item)
def get_item_index(self, item):
return self._viewport.traybar.get_item_index(item)
class TrayButton(ToolButton):
def __init__(self, **kwargs):
ToolButton.__init__(self, **kwargs)
class _IconWidget(gtk.EventBox):
__gtype_name__ = "SugarTrayIconWidget"
def __init__(self, icon_name=None, xo_color=None):
gtk.EventBox.__init__(self)
self._palette = None
self.set_app_paintable(True)
icon = Icon(icon_name=icon_name, xo_color=xo_color,
icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
self.add(icon)
icon.show()
def do_expose_event(self, event):
if self._palette and self._palette.is_up():
invoker = self._palette.props.invoker
invoker.draw_rectangle(event, self._palette)
gtk.EventBox.do_expose_event(self, event)
def set_palette(self, palette):
if self._palette is not None:
self._palette.props.invoker = None
self._palette = palette
self._palette.props.invoker = ToolInvoker(self)
class TrayIcon(gtk.ToolItem):
__gtype_name__ = "SugarTrayIcon"
def __init__(self, icon_name=None, xo_color=None):
gtk.ToolItem.__init__(self)
self._icon_widget = _IconWidget(icon_name, xo_color)
self.add(self._icon_widget)
self._icon_widget.show()
self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
def set_palette(self, palette):
self._icon_widget.set_palette(palette)
def set_tooltip(self, text):
self.set_palette(Palette(text))
+84
View File
@@ -0,0 +1,84 @@
# Copyright (C) 2007, 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 gtk
import hippo
class Window(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
self.connect('realize', self._window_realize_cb)
self.toolbox = None
self._alerts = []
self.alert_position = -1
self.canvas = None
self._vbox = gtk.VBox()
self.add(self._vbox)
self._vbox.show()
def set_canvas(self, canvas):
if self.canvas:
self._vbox.remove(self.canvas)
self._vbox.pack_start(canvas)
self._vbox.reorder_child(canvas, -1)
self.canvas = canvas
def set_toolbox(self, toolbox):
if self.toolbox:
self._vbox.remove(self.toolbox)
self._vbox.pack_start(toolbox, False)
self._vbox.reorder_child(toolbox, 0)
self.toolbox = toolbox
def add_alert(self, alert):
self._alerts.append(alert)
if len(self._alerts) == 1:
self._vbox.pack_start(alert, False)
self._vbox.reorder_child(alert, self.alert_position)
def remove_alert(self, alert):
if alert in self._alerts:
self._alerts.remove(alert)
self._vbox.remove(alert)
if len(self._alerts) >= 1:
self._vbox.pack_start(self._alerts[0], False)
self._vbox.reorder_child(self._alerts[0], self.alert_position)
def _window_realize_cb(self, window):
group = gtk.Window()
group.realize()
window.window.set_group(group.window)
def get_canvas_screenshot(self):
if not self.canvas:
return None
window = self.canvas.window
width, height = window.get_size()
screenshot = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, has_alpha=False,
bits_per_sample=8, width=width, height=height)
screenshot.get_from_drawable(window, window.get_colormap(), 0, 0, 0, 0,
width, height)
return screenshot
+255
View File
@@ -0,0 +1,255 @@
# Copyright (C) 2006-2007 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 random
_colors = [
['#B20008', '#FF2B34'], \
['#FF2B34', '#B20008'], \
['#E6000A', '#FF2B34'], \
['#FF2B34', '#E6000A'], \
['#FFADCE', '#FF2B34'], \
['#9A5200', '#FF2B34'], \
['#FF2B34', '#9A5200'], \
['#FF8F00', '#FF2B34'], \
['#FF2B34', '#FF8F00'], \
['#FFC169', '#FF2B34'], \
['#807500', '#FF2B34'], \
['#FF2B34', '#807500'], \
['#BE9E00', '#FF2B34'], \
['#FF2B34', '#BE9E00'], \
['#F8E800', '#FF2B34'], \
['#008009', '#FF2B34'], \
['#FF2B34', '#008009'], \
['#00B20D', '#FF2B34'], \
['#FF2B34', '#00B20D'], \
['#8BFF7A', '#FF2B34'], \
['#00588C', '#FF2B34'], \
['#FF2B34', '#00588C'], \
['#005FE4', '#FF2B34'], \
['#FF2B34', '#005FE4'], \
['#BCCDFF', '#FF2B34'], \
['#5E008C', '#FF2B34'], \
['#FF2B34', '#5E008C'], \
['#7F00BF', '#FF2B34'], \
['#FF2B34', '#7F00BF'], \
['#D1A3FF', '#FF2B34'], \
['#9A5200', '#FF8F00'], \
['#FF8F00', '#9A5200'], \
['#C97E00', '#FF8F00'], \
['#FF8F00', '#C97E00'], \
['#FFC169', '#FF8F00'], \
['#807500', '#FF8F00'], \
['#FF8F00', '#807500'], \
['#BE9E00', '#FF8F00'], \
['#FF8F00', '#BE9E00'], \
['#F8E800', '#FF8F00'], \
['#008009', '#FF8F00'], \
['#FF8F00', '#008009'], \
['#00B20D', '#FF8F00'], \
['#FF8F00', '#00B20D'], \
['#8BFF7A', '#FF8F00'], \
['#00588C', '#FF8F00'], \
['#FF8F00', '#00588C'], \
['#005FE4', '#FF8F00'], \
['#FF8F00', '#005FE4'], \
['#BCCDFF', '#FF8F00'], \
['#5E008C', '#FF8F00'], \
['#FF8F00', '#5E008C'], \
['#A700FF', '#FF8F00'], \
['#FF8F00', '#A700FF'], \
['#D1A3FF', '#FF8F00'], \
['#B20008', '#FF8F00'], \
['#FF8F00', '#B20008'], \
['#FF2B34', '#FF8F00'], \
['#FF8F00', '#FF2B34'], \
['#FFADCE', '#FF8F00'], \
['#807500', '#F8E800'], \
['#F8E800', '#807500'], \
['#BE9E00', '#F8E800'], \
['#F8E800', '#BE9E00'], \
['#FFFA00', '#EDDE00'], \
['#008009', '#F8E800'], \
['#F8E800', '#008009'], \
['#00EA11', '#F8E800'], \
['#F8E800', '#00EA11'], \
['#8BFF7A', '#F8E800'], \
['#00588C', '#F8E800'], \
['#F8E800', '#00588C'], \
['#00A0FF', '#F8E800'], \
['#F8E800', '#00A0FF'], \
['#BCCEFF', '#F8E800'], \
['#5E008C', '#F8E800'], \
['#F8E800', '#5E008C'], \
['#AC32FF', '#F8E800'], \
['#F8E800', '#AC32FF'], \
['#D1A3FF', '#F8E800'], \
['#B20008', '#F8E800'], \
['#F8E800', '#B20008'], \
['#FF2B34', '#F8E800'], \
['#F8E800', '#FF2B34'], \
['#FFADCE', '#F8E800'], \
['#9A5200', '#F8E800'], \
['#F8E800', '#9A5200'], \
['#FF8F00', '#F8E800'], \
['#F8E800', '#FF8F00'], \
['#FFC169', '#F8E800'], \
['#008009', '#00EA11'], \
['#00EA11', '#008009'], \
['#00B20D', '#00EA11'], \
['#00EA11', '#00B20D'], \
['#8BFF7A', '#00EA11'], \
['#00588C', '#00EA11'], \
['#00EA11', '#00588C'], \
['#005FE4', '#00EA11'], \
['#00EA11', '#005FE4'], \
['#BCCDFF', '#00EA11'], \
['#5E008C', '#00EA11'], \
['#00EA11', '#5E008C'], \
['#7F00BF', '#00EA11'], \
['#00EA11', '#7F00BF'], \
['#D1A3FF', '#00EA11'], \
['#B20008', '#00EA11'], \
['#00EA11', '#B20008'], \
['#FF2B34', '#00EA11'], \
['#00EA11', '#FF2B34'], \
['#FFADCE', '#00EA11'], \
['#9A5200', '#00EA11'], \
['#00EA11', '#9A5200'], \
['#FF8F00', '#00EA11'], \
['#00EA11', '#FF8F00'], \
['#FFC169', '#00EA11'], \
['#807500', '#00EA11'], \
['#00EA11', '#807500'], \
['#BE9E00', '#00EA11'], \
['#00EA11', '#BE9E00'], \
['#F8E800', '#00EA11'], \
['#00588C', '#00A0FF'], \
['#00A0FF', '#00588C'], \
['#005FE4', '#00A0FF'], \
['#00A0FF', '#005FE4'], \
['#BCCDFF', '#00A0FF'], \
['#5E008C', '#00A0FF'], \
['#00A0FF', '#5E008C'], \
['#9900E6', '#00A0FF'], \
['#00A0FF', '#9900E6'], \
['#D1A3FF', '#00A0FF'], \
['#B20008', '#00A0FF'], \
['#00A0FF', '#B20008'], \
['#FF2B34', '#00A0FF'], \
['#00A0FF', '#FF2B34'], \
['#FFADCE', '#00A0FF'], \
['#9A5200', '#00A0FF'], \
['#00A0FF', '#9A5200'], \
['#FF8F00', '#00A0FF'], \
['#00A0FF', '#FF8F00'], \
['#FFC169', '#00A0FF'], \
['#807500', '#00A0FF'], \
['#00A0FF', '#807500'], \
['#BE9E00', '#00A0FF'], \
['#00A0FF', '#BE9E00'], \
['#F8E800', '#00A0FF'], \
['#008009', '#00A0FF'], \
['#00A0FF', '#008009'], \
['#00B20D', '#00A0FF'], \
['#00A0FF', '#00B20D'], \
['#8BFF7A', '#00A0FF'], \
['#5E008C', '#AC32FF'], \
['#AC32FF', '#5E008C'], \
['#7F00BF', '#AC32FF'], \
['#AC32FF', '#7F00BF'], \
['#D1A3FF', '#AC32FF'], \
['#B20008', '#AC32FF'], \
['#AC32FF', '#B20008'], \
['#FF2B34', '#AC32FF'], \
['#AC32FF', '#FF2B34'], \
['#FFADCE', '#AC32FF'], \
['#9A5200', '#AC32FF'], \
['#AC32FF', '#9A5200'], \
['#FF8F00', '#AC32FF'], \
['#AC32FF', '#FF8F00'], \
['#FFC169', '#AC32FF'], \
['#807500', '#AC32FF'], \
['#AC32FF', '#807500'], \
['#BE9E00', '#AC32FF'], \
['#AC32FF', '#BE9E00'], \
['#F8E800', '#AC32FF'], \
['#008009', '#AC32FF'], \
['#AC32FF', '#008009'], \
['#00B20D', '#AC32FF'], \
['#AC32FF', '#00B20D'], \
['#8BFF7A', '#AC32FF'], \
['#00588C', '#AC32FF'], \
['#AC32FF', '#00588C'], \
['#005FE4', '#AC32FF'], \
['#AC32FF', '#005FE4'], \
['#BCCDFF', '#AC32FF'], \
]
def _parse_string(color_string):
if color_string == 'white':
return ['#ffffff', '#414141']
elif color_string == 'insensitive':
return ['#ffffff', '#e2e2e2']
splitted = color_string.split(',')
if len(splitted) == 2:
return [splitted[0], splitted[1]]
else:
return None
def is_valid(color_string):
return (_parse_string(color_string) != None)
class XoColor:
def __init__(self, color_string=None):
if color_string == None or not is_valid(color_string):
n = int(random.random() * (len(_colors) - 1))
[self._stroke, self._fill] = _colors[n]
else:
[self._stroke, self._fill] = _parse_string(color_string)
def __cmp__(self, other):
if isinstance(other, XoColor):
if self._stroke == other._stroke and self._fill == other._fill:
return 0
return -1
def get_stroke_color(self):
return self._stroke
def get_fill_color(self):
return self._fill
def to_string(self):
return '%s,%s' % (self._stroke, self._fill)
if __name__ == "__main__":
import sys
import re
f = open(sys.argv[1], 'r')
print '_colors = ['
for line in f.readlines():
match = re.match(r'fill: ([A-Z0-9]*) stroke: ([A-Z0-9]*)', line)
print "['#%s', '#%s'], \\" % (match.group(2), match.group(1))
print ']'
f.close()
+575
View File
@@ -0,0 +1,575 @@
# Copyright (C) 2006-2007 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.
# pylint: disable-msg = W0221
import socket
import os
import threading
import traceback
import xmlrpclib
import sys
import httplib
import urllib
import fcntl
import tempfile
import gobject
import SimpleXMLRPCServer
import SimpleHTTPServer
import SocketServer
__authinfos = {}
def _add_authinfo(authinfo):
__authinfos[threading.currentThread()] = authinfo
def get_authinfo():
return __authinfos.get(threading.currentThread())
def _del_authinfo():
del __authinfos[threading.currentThread()]
class GlibTCPServer(SocketServer.TCPServer):
"""GlibTCPServer
Integrate socket accept into glib mainloop.
"""
allow_reuse_address = True
request_queue_size = 20
def __init__(self, server_address, RequestHandlerClass):
SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass)
self.socket.setblocking(0) # Set nonblocking
# Watch the listener socket for data
gobject.io_add_watch(self.socket, gobject.IO_IN, self._handle_accept)
def _handle_accept(self, source, condition):
"""Process incoming data on the server's socket by doing an accept()
via handle_request()."""
if not (condition & gobject.IO_IN):
return True
self.handle_request()
return True
def close_request(self, request):
"""Called to clean up an individual request."""
# let the request be closed by the request handler when its done
pass
class ChunkedGlibHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
"""RequestHandler class that integrates with Glib mainloop. It writes
the specified file to the client in chunks, returning control to the
mainloop between chunks.
"""
CHUNK_SIZE = 4096
def __init__(self, request, client_address, server):
self._file = None
self._srcid = 0
SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, request, client_address, server)
def log_request(self, code='-', size='-'):
pass
def do_GET(self):
"""Serve a GET request."""
self._file = self.send_head()
if self._file:
self._srcid = gobject.io_add_watch(self.wfile, gobject.IO_OUT | gobject.IO_ERR, self._send_next_chunk)
else:
self._file.close()
self._cleanup()
def _send_next_chunk(self, source, condition):
if condition & gobject.IO_ERR:
self._cleanup()
return False
if not (condition & gobject.IO_OUT):
self._cleanup()
return False
data = self._file.read(self.CHUNK_SIZE)
count = os.write(self.wfile.fileno(), data)
if count != len(data) or len(data) != self.CHUNK_SIZE:
self._cleanup()
return False
return True
def _cleanup(self):
if self._file:
self._file.close()
self._file = None
if self._srcid > 0:
gobject.source_remove(self._srcid)
self._srcid = 0
if not self.wfile.closed:
self.wfile.flush()
self.wfile.close()
self.rfile.close()
def finish(self):
"""Close the sockets when we're done, not before"""
pass
def send_head(self):
"""Common code for GET and HEAD commands.
This sends the response code and MIME headers.
Return value is either a file object (which has to be copied
to the outputfile by the caller unless the command was HEAD,
and must be closed by the caller under all circumstances), or
None, in which case the caller has nothing further to do.
** [dcbw] modified to send Content-disposition filename too
"""
path = self.translate_path(self.path)
if not path or not os.path.exists(path):
self.send_error(404, "File not found")
return None
f = None
if os.path.isdir(path):
for index in "index.html", "index.htm":
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
else:
return self.list_directory(path)
ctype = self.guess_type(path)
try:
# Always read in binary mode. Opening files in text mode may cause
# newline translations, making the actual size of the content
# transmitted *less* than the content-length!
f = open(path, 'rb')
except IOError:
self.send_error(404, "File not found")
return None
self.send_response(200)
self.send_header("Content-type", ctype)
self.send_header("Content-Length", str(os.fstat(f.fileno())[6]))
self.send_header("Content-Disposition", 'attachment; filename="%s"' % os.path.basename(path))
self.end_headers()
return f
class GlibURLDownloader(gobject.GObject):
"""Grabs a URL in chunks, returning to the mainloop after each chunk"""
__gsignals__ = {
'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
'error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'progress': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
CHUNK_SIZE = 4096
def __init__(self, url, destdir=None):
self._url = url
if not destdir:
destdir = tempfile.gettempdir()
self._destdir = destdir
self._srcid = 0
self._fname = None
self._outf = None
self._written = 0
gobject.GObject.__init__(self)
def start(self, destfile=None, destfd=None):
self._info = urllib.urlopen(self._url)
self._outf = None
self._fname = None
if destfd and not destfile:
raise ValueError("Must provide destination file too when specifying file descriptor")
if destfile:
self._suggested_fname = os.path.basename(destfile)
self._fname = os.path.abspath(os.path.expanduser(destfile))
if destfd:
# Use the user-supplied destination file descriptor
self._outf = destfd
else:
self._outf = os.open(self._fname, os.O_RDWR | os.O_TRUNC | os.O_CREAT, 0644)
else:
self._suggested_fname = self._get_filename_from_headers(self._info.headers)
garbage, path = urllib.splittype(self._url)
garbage, path = urllib.splithost(path or "")
path, garbage = urllib.splitquery(path or "")
path, garbage = urllib.splitattr(path or "")
suffix = os.path.splitext(path)[1]
(self._outf, self._fname) = tempfile.mkstemp(suffix=suffix, dir=self._destdir)
fcntl.fcntl(self._info.fp.fileno(), fcntl.F_SETFD, os.O_NDELAY)
self._srcid = gobject.io_add_watch(self._info.fp.fileno(),
gobject.IO_IN | gobject.IO_ERR,
self._read_next_chunk)
def cancel(self):
if self._srcid == 0:
raise RuntimeError("Download already canceled or stopped")
self.cleanup(remove=True)
def _get_filename_from_headers(self, headers):
if not headers.has_key("Content-Disposition"):
return None
ftag = "filename="
data = headers["Content-Disposition"]
fidx = data.find(ftag)
if fidx < 0:
return None
fname = data[fidx+len(ftag):]
if fname[0] == '"' or fname[0] == "'":
fname = fname[1:]
if fname[len(fname)-1] == '"' or fname[len(fname)-1] == "'":
fname = fname[:len(fname)-1]
return fname
def _read_next_chunk(self, source, condition):
if condition & gobject.IO_ERR:
self.cleanup(remove=True)
self.emit("error", "Error downloading file.")
return False
elif not (condition & gobject.IO_IN):
# shouldn't get here, but...
return True
try:
data = self._info.fp.read(self.CHUNK_SIZE)
count = os.write(self._outf, data)
self._written += len(data)
# error writing data to file?
if count < len(data):
self.cleanup(remove=True)
self.emit("error", "Error writing to download file.")
return False
self.emit("progress", self._written)
# done?
if len(data) < self.CHUNK_SIZE:
self.cleanup()
self.emit("finished", self._fname, self._suggested_fname)
return False
except Exception, err:
self.cleanup(remove=True)
self.emit("error", "Error downloading file: %s" % err)
return False
return True
def cleanup(self, remove=False):
if self._srcid > 0:
gobject.source_remove(self._srcid)
self._srcid = 0
del self._info
self._info = None
os.close(self._outf)
if remove:
os.remove(self._fname)
self._outf = None
class GlibXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
""" GlibXMLRPCRequestHandler
The stock SimpleXMLRPCRequestHandler and server don't allow any way to pass
the client's address and/or SSL certificate into the function that actually
_processes_ the request. So we have to store it in a thread-indexed dict.
"""
def do_POST(self):
_add_authinfo(self.client_address)
try:
SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.do_POST(self)
except socket.timeout:
pass
except socket.error, e:
print "Error (%s): socket error - '%s'" % (self.client_address, e)
except:
print "Error while processing POST:"
traceback.print_exc()
_del_authinfo()
class GlibXMLRPCServer(GlibTCPServer, SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
"""GlibXMLRPCServer
Use nonblocking sockets and handle the accept via glib rather than
blocking on accept().
"""
def __init__(self, addr, requestHandler=GlibXMLRPCRequestHandler,
logRequests=0, allow_none=False):
self.logRequests = logRequests
if sys.version_info[:3] >= (2, 5, 0):
SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding="utf-8")
else:
SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
GlibTCPServer.__init__(self, addr, requestHandler)
def _marshaled_dispatch(self, data, dispatch_method = None):
"""Dispatches an XML-RPC method from marshalled (XML) data.
XML-RPC methods are dispatched from the marshalled (XML) data
using the _dispatch method and the result is returned as
marshalled data. For backwards compatibility, a dispatch
function can be provided as an argument (see comment in
SimpleXMLRPCRequestHandler.do_POST) but overriding the
existing method through subclassing is the prefered means
of changing method dispatch behavior.
"""
params, method = xmlrpclib.loads(data)
# generate response
try:
if dispatch_method is not None:
response = dispatch_method(method, params)
else:
response = self._dispatch(method, params)
# wrap response in a singleton tuple
response = (response,)
response = xmlrpclib.dumps(response, methodresponse=1)
except xmlrpclib.Fault, fault:
response = xmlrpclib.dumps(fault)
except:
print "Exception while processing request:"
traceback.print_exc()
# report exception back to server
response = xmlrpclib.dumps(
xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value))
)
return response
class GlibHTTP(httplib.HTTP):
"""Subclass HTTP so we can return it's connection class' socket."""
def connect(self, host=None, port=None):
httplib.HTTP.connect(self, host, port)
self._conn.sock.setblocking(0)
class GlibXMLRPCTransport(xmlrpclib.Transport):
"""Integrate the request with the glib mainloop rather than blocking."""
##
# Connect to server.
#
# @param host Target host.
# @return A connection handle.
def __init__(self, use_datetime=0):
if sys.version_info[:3] >= (2, 5, 0):
xmlrpclib.Transport.__init__(self, use_datetime)
def make_connection(self, host):
"""Use our own connection object so we can get its socket."""
# create a HTTP connection object from a host descriptor
host, extra_headers, x509 = self.get_host_info(host)
return GlibHTTP(host)
##
# Send a complete request, and parse the response.
#
# @param host Target host.
# @param handler Target PRC handler.
# @param request_body XML-RPC request body.
# @param verbose Debugging flag.
# @return Parsed response.
def start_request(self, host, handler, request_body, verbose=0, reply_handler=None, error_handler=None, user_data=None):
"""Do the first half of the request by sending data to the remote
server. The bottom half bits get run when the remote server's response
actually comes back."""
# issue XML-RPC request
h = self.make_connection(host)
if verbose:
h.set_debuglevel(1)
self.send_request(h, handler, request_body)
self.send_host(h, host)
self.send_user_agent(h)
self.send_content(h, request_body)
# Schedule a GIOWatch so we don't block waiting for the response
gobject.io_add_watch(h._conn.sock, gobject.IO_IN, self._finish_request,
h, host, handler, verbose, reply_handler, error_handler, user_data)
def _finish_request(self, source, condition, h, host, handler, verbose, reply_handler=None, error_handler=None, user_data=None):
"""Parse and return response when the remote server actually returns it."""
if not (condition & gobject.IO_IN):
return True
try:
errcode, errmsg, headers = h.getreply()
except socket.error, err:
if err[0] != 104:
raise socket.error(err)
else:
if error_handler:
gobject.idle_add(error_handler, err, user_data)
return False
if errcode != 200:
raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
self.verbose = verbose
response = self._parse_response(h.getfile(), h._conn.sock)
if reply_handler:
# Coerce to a list so we can append user data
response = response[0]
if not isinstance(response, list):
response = [response]
response.append(user_data)
gobject.idle_add(reply_handler, *response)
return False
class _Method:
"""Right, so python people thought it would be funny to make this
class private to xmlrpclib.py..."""
# some magic to bind an XML-RPC method to an RPC server.
# supports "nested" methods (e.g. examples.getStateName)
def __init__(self, send, name):
self.__send = send
self.__name = name
def __getattr__(self, name):
return _Method(self.__send, "%s.%s" % (self.__name, name))
def __call__(self, *args, **kwargs):
return self.__send(self.__name, *args, **kwargs)
class GlibServerProxy(xmlrpclib.ServerProxy):
"""Subclass xmlrpclib.ServerProxy so we can run the XML-RPC request
in two parts, integrated with the glib mainloop, such that we don't
block anywhere.
Using this object is somewhat special; it requires more arguments to each
XML-RPC request call than the normal xmlrpclib.ServerProxy object:
client = GlibServerProxy("http://127.0.0.1:8888")
user_data = "bar"
xmlrpc_arg1 = "test"
xmlrpc_arg2 = "foo"
client.test(xmlrpc_test_cb, user_data, xmlrpc_arg1, xmlrpc_arg2)
Here, 'xmlrpc_test_cb' is the callback function, which has the following
signature:
def xmlrpc_test_cb(result_status, response, user_data=None):
...
"""
def __init__(self, uri, encoding=None, verbose=0, allow_none=0):
self._transport = GlibXMLRPCTransport()
self._encoding = encoding
self._verbose = verbose
self._allow_none = allow_none
xmlrpclib.ServerProxy.__init__(self, uri, self._transport, encoding, verbose, allow_none)
# get the url
import urllib
urltype, uri = urllib.splittype(uri)
if urltype not in ("http", "https"):
raise IOError, "unsupported XML-RPC protocol"
self._host, self._handler = urllib.splithost(uri)
if not self._handler:
self._handler = "/RPC2"
def __request(self, methodname, *args, **kwargs):
"""Call the method on the remote server. We just start the request here
and the transport itself takes care of scheduling the response callback
when the remote server returns the response. We don't want to block anywhere."""
request = xmlrpclib.dumps(args, methodname, encoding=self._encoding,
allow_none=self._allow_none)
reply_hdl = kwargs.get("reply_handler")
err_hdl = kwargs.get("error_handler")
udata = kwargs.get("user_data")
try:
response = self._transport.start_request(
self._host,
self._handler,
request,
verbose=self._verbose,
reply_handler=reply_hdl,
error_handler=err_hdl,
user_data=udata
)
except socket.error, exc:
if err_hdl:
gobject.idle_add(err_hdl, exc, udata)
def __getattr__(self, name):
# magic method dispatcher
return _Method(self.__request, name)
class Test(object):
def test(self, arg1, arg2):
print "Request got %s, %s" % (arg1, arg2)
return "success", "bork"
def xmlrpc_success_cb(response, resp2, loop):
print "Response was %s %s" % (response, resp2)
loop.quit()
def xmlrpc_error_cb(err, loop):
print "Error: %s" % err
loop.quit()
def xmlrpc_test(loop):
client = GlibServerProxy("http://127.0.0.1:8888")
client.test("bar", "baz",
reply_handler=xmlrpc_success_cb,
error_handler=xmlrpc_error_cb,
user_data=loop)
def start_xmlrpc():
server = GlibXMLRPCServer(("", 8888))
inst = Test()
server.register_instance(inst)
gobject.idle_add(xmlrpc_test, loop)
class TestReqHandler(ChunkedGlibHTTPRequestHandler):
def translate_path(self, path):
return "/tmp/foo"
def start_http():
server = GlibTCPServer(("", 8890), TestReqHandler)
def main():
loop = gobject.MainLoop()
# start_xmlrpc()
start_http()
try:
loop.run()
except KeyboardInterrupt:
print 'Ctrl+C pressed, exiting...'
print "Done."
if __name__ == "__main__":
main()
+5
View File
@@ -0,0 +1,5 @@
sugardir = $(pythondir)/sugar/objects
sugar_PYTHON = \
__init__.py \
objecttype.py
+16
View File
@@ -0,0 +1,16 @@
# Copyright (C) 2006-2007, 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.
+79
View File
@@ -0,0 +1,79 @@
# Copyright (C) 2006-2007, 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
_SERVICE = "org.laptop.ObjectTypeRegistry"
_PATH = "/org/laptop/ObjectTypeRegistry"
_IFACE = "org.laptop.ObjectTypeRegistry"
def _object_type_from_dict(info_dict):
if info_dict:
return ObjectType(info_dict['type_id'],
info_dict['name'],
info_dict['icon'],
info_dict['mime_types'])
else:
return None
class ObjectType(object):
def __init__(self, type_id, name, icon, mime_types):
self.type_id = type_id
self.name = name
self.icon = icon
self.mime_types = mime_types
self._type_id_to_type = {}
self._mime_type_to_type = {}
class ObjectTypeRegistry(object):
def __init__(self):
bus = dbus.SessionBus()
bus_object = bus.get_object(_SERVICE, _PATH)
self._registry = dbus.Interface(bus_object, _IFACE)
# Two caches fo saving some travel across dbus.
self._type_id_to_type = {}
self._mime_type_to_type = {}
def get_type(self, type_id):
if self._type_id_to_type.has_key(type_id):
return self._type_id_to_type[type_id]
type_dict = self._registry.GetType(type_id)
object_type = _object_type_from_dict(type_dict)
self._type_id_to_type[type_id] = object_type
return object_type
def get_type_for_mime(self, mime_type):
if self._mime_type_to_type.has_key(mime_type):
return self._mime_type_to_type[mime_type]
type_dict = self._registry.GetTypeForMIME(mime_type)
object_type = _object_type_from_dict(type_dict)
self._mime_type_to_type[mime_type] = object_type
return object_type
_registry = None
def get_registry():
global _registry
if not _registry:
_registry = ObjectTypeRegistry()
return _registry
+8
View File
@@ -0,0 +1,8 @@
sugardir = $(pythondir)/sugar/presence
sugar_PYTHON = \
__init__.py \
activity.py \
buddy.py \
tubeconn.py \
presenceservice.py
+24
View File
@@ -0,0 +1,24 @@
"""Client-code's interface to the PresenceService
Provides a simplified API for accessing the dbus service
which coordinates native network presence and sharing
information. This includes both "buddies" and "shared
activities".
"""
# Copyright (C) 2006-2007, 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.
+290
View File
@@ -0,0 +1,290 @@
"""UI interface to an activity in the presence service"""
# Copyright (C) 2007, 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 gobject
import dbus
_logger = logging.getLogger('sugar.presence.activity')
class Activity(gobject.GObject):
"""UI interface for an Activity in the presence service
Activities in the presence service represent your and other user's
shared activities.
Properties:
id
color
name
type
joined
"""
__gsignals__ = {
'buddy-joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'buddy-left': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'new-channel': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
}
__gproperties__ = {
'id' : (str, None, None, None, gobject.PARAM_READABLE),
'name' : (str, None, None, None, gobject.PARAM_READWRITE),
'tags' : (str, None, None, None, gobject.PARAM_READWRITE),
'color' : (str, None, None, None, gobject.PARAM_READWRITE),
'type' : (str, None, None, None, gobject.PARAM_READABLE),
'private' : (bool, None, None, True, gobject.PARAM_READWRITE),
'joined' : (bool, None, None, False, gobject.PARAM_READABLE),
}
_PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
_ACTIVITY_DBUS_INTERFACE = "org.laptop.Sugar.Presence.Activity"
def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
"""Initialse the activity interface, connecting to service"""
gobject.GObject.__init__(self)
self._object_path = object_path
self._ps_new_object = new_obj_cb
self._ps_del_object = del_obj_cb
bobj = bus.get_object(self._PRESENCE_SERVICE, object_path)
self._activity = dbus.Interface(bobj, self._ACTIVITY_DBUS_INTERFACE)
self._activity.connect_to_signal('BuddyHandleJoined',
self._buddy_handle_joined_cb)
self._activity.connect_to_signal('BuddyLeft',
self._buddy_left_cb)
self._activity.connect_to_signal('NewChannel', self._new_channel_cb)
self._activity.connect_to_signal('PropertiesChanged',
self._properties_changed_cb,
utf8_strings=True)
# FIXME: this *would* just use a normal proxy call, but I want the
# pending call object so I can block on it, and normal proxy methods
# don't return those as of dbus-python 0.82.1; so do it the hard way
self._get_properties_call = bus.call_async(self._PRESENCE_SERVICE,
object_path, self._ACTIVITY_DBUS_INTERFACE, 'GetProperties',
'', (), self._get_properties_reply_cb,
self._get_properties_error_cb, utf8_strings=True)
self._id = None
self._color = None
self._name = None
self._type = None
self._tags = None
self._private = True
self._joined = False
# Cache for get_buddy_by_handle, maps handles to buddy object paths
self._handle_to_buddy_path = {}
self._buddy_path_to_handle = {}
def _get_properties_reply_cb(self, new_props):
self._properties_changed_cb(new_props)
self._get_properties_call = None
def _get_properties_error_cb(self, e):
self._get_properties_call = None
# FIXME: do something with the error
_logger.warning('Error doing initial GetProperties: %s', e)
def _properties_changed_cb(self, new_props):
_logger.debug('Activity properties changed to %r', new_props)
val = new_props.get('name', self._name)
if isinstance(val, str) and val != self._name:
self._name = val
self.notify('name')
val = new_props.get('tags', self._tags)
if isinstance(val, str) and val != self._tags:
self._tags = val
self.notify('tags')
val = new_props.get('color', self._color)
if isinstance(val, str) and val != self._color:
self._color = val
self.notify('color')
val = bool(new_props.get('private', self._private))
if val != self._private:
self._private = val
self.notify('private')
val = new_props.get('id', self._id)
if isinstance(val, str) and self._id is None:
self._id = val
self.notify('id')
val = new_props.get('type', self._type)
if isinstance(val, str) and self._type is None:
self._type = val
self.notify('type')
def object_path(self):
"""Get our dbus object path"""
return self._object_path
def do_get_property(self, pspec):
"""Retrieve a particular property from our property dictionary"""
_logger.debug('Looking up property %s', pspec.name)
if pspec.name == "joined":
return self._joined
if self._get_properties_call is not None:
_logger.debug('Blocking on GetProperties() because someone wants '
'property %s', pspec.name)
self._get_properties_call.block()
if pspec.name == "id":
return self._id
elif pspec.name == "name":
return self._name
elif pspec.name == "color":
return self._color
elif pspec.name == "type":
return self._type
elif pspec.name == "tags":
return self._tags
elif pspec.name == "private":
return self._private
# FIXME: need an asynchronous API to set these properties, particularly
# 'private'
def do_set_property(self, pspec, val):
"""Set a particular property in our property dictionary"""
if pspec.name == "name":
self._activity.SetProperties({'name': val})
self._name = val
elif pspec.name == "color":
self._activity.SetProperties({'color': val})
self._color = val
elif pspec.name == "tags":
self._activity.SetProperties({'tags': val})
self._tags = val
elif pspec.name == "private":
self._activity.SetProperties({'private': val})
self._private = val
def _emit_buddy_joined_signal(self, object_path):
"""Generate buddy-joined GObject signal with presence Buddy object"""
self.emit('buddy-joined', self._ps_new_object(object_path))
return False
def _buddy_handle_joined_cb(self, object_path, handle):
gobject.idle_add(self._emit_buddy_joined_signal, object_path)
self._handle_to_buddy_path[handle] = object_path
self._buddy_path_to_handle[object_path] = handle
def _emit_buddy_left_signal(self, object_path):
"""Generate buddy-left GObject signal with presence Buddy object
XXX note use of _ps_new_object instead of _ps_del_object here
"""
self.emit('buddy-left', self._ps_new_object(object_path))
return False
def _buddy_left_cb(self, object_path):
gobject.idle_add(self._emit_buddy_left_signal, object_path)
handle = self._buddy_path_to_handle.pop(object_path)
self._handle_to_buddy_path.pop(handle, None)
def _emit_new_channel_signal(self, object_path):
"""Generate new-channel GObject signal with channel object path
New telepathy-python communications channel has been opened
"""
self.emit('new-channel', object_path)
return False
def _new_channel_cb(self, object_path):
gobject.idle_add(self._emit_new_channel_signal, object_path)
def get_joined_buddies(self):
"""Retrieve the set of Buddy objects attached to this activity
returns list of presence Buddy objects
"""
resp = self._activity.GetJoinedBuddies()
buddies = []
for item in resp:
buddies.append(self._ps_new_object(item))
return buddies
def get_buddy_by_handle(self, handle):
"""Retrieve the Buddy object given a telepathy handle.
buddy object paths are cached in self._handle_to_buddy_path,
so we can get the buddy without calling PS.
"""
object_path = self._handle_to_buddy_path.get(handle, None)
if object_path:
buddy = self._ps_new_object(object_path)
return buddy
return None
def invite(self, buddy, message, response_cb):
"""Invite the given buddy to join this activity.
The callback will be called with one parameter: None on success,
or an exception on failure.
"""
self._activity.Invite(buddy.object_path(), message,
reply_handler=lambda: response_cb(None),
error_handler=response_cb)
def _join_cb(self):
self._joined = True
self.emit("joined", True, None)
def _join_error_cb(self, err):
self.emit("joined", False, str(err))
def join(self):
"""Join this activity
XXX if these are all activities, can I join my own activity?
"""
if self._joined:
self.emit("joined", True, None)
return
self._activity.Join(reply_handler=self._join_cb, error_handler=self._join_error_cb)
def get_channels(self):
"""Retrieve communications channel descriptions for the activity
Returns a tuple containing:
- the D-Bus well-known service name of the connection
(FIXME: this is redundant; in Telepathy it can be derived
from that of the connection)
- the D-Bus object path of the connection
- a list of D-Bus object paths representing the channels
associated with this activity
"""
(bus_name, connection, channels) = self._activity.GetChannels()
return bus_name, connection, channels
def _leave_cb(self):
"""Callback for async action of leaving shared activity."""
self.emit("joined", False, "left activity")
def _leave_error_cb(self, err):
"""Callback for error in async leaving of shared activity."""
_logger.debug('Failed to leave activity: %s', err)
def leave(self):
"""Leave this shared activity"""
self._joined = False
self._activity.Leave(reply_handler=self._leave_cb,
error_handler=self._leave_error_cb)
+225
View File
@@ -0,0 +1,225 @@
"""UI interface to a buddy in the presence service"""
# Copyright (C) 2007, 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 gobject
import gtk
import dbus
class Buddy(gobject.GObject):
"""UI interface for a Buddy in the presence service
Each buddy interface tracks a set of activities and properties
that can be queried to provide UI controls for manipulating
the presence interface.
Properties Dictionary:
'key': public key,
'nick': nickname ,
'color': color (XXX what format),
'current-activity': (XXX dbus path?),
'owner': (XXX dbus path?),
'icon': (XXX pixel data for an icon?)
See __gproperties__
"""
__gsignals__ = {
'icon-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([])),
'joined-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'left-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'property-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
}
__gproperties__ = {
'key' : (str, None, None, None, gobject.PARAM_READABLE),
'icon' : (str, None, None, None, gobject.PARAM_READABLE),
'nick' : (str, None, None, None, gobject.PARAM_READABLE),
'color' : (str, None, None, None, gobject.PARAM_READABLE),
'current-activity' : (object, None, None, gobject.PARAM_READABLE),
'owner' : (bool, None, None, False, gobject.PARAM_READABLE),
'ip4-address' : (str, None, None, None, gobject.PARAM_READABLE)
}
_PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
_BUDDY_DBUS_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
"""Initialise the reference to the buddy
bus -- dbus bus object
new_obj_cb -- callback to call when this buddy joins an activity
del_obj_cb -- callback to call when this buddy leaves an activity
object_path -- path to the buddy object
"""
gobject.GObject.__init__(self)
self._object_path = object_path
self._ps_new_object = new_obj_cb
self._ps_del_object = del_obj_cb
self._properties = {}
self._activities = {}
bobj = bus.get_object(self._PRESENCE_SERVICE, object_path)
self._buddy = dbus.Interface(bobj, self._BUDDY_DBUS_INTERFACE)
self._buddy.connect_to_signal('IconChanged', self._icon_changed_cb,
byte_arrays=True)
self._buddy.connect_to_signal('JoinedActivity', self._joined_activity_cb)
self._buddy.connect_to_signal('LeftActivity', self._left_activity_cb)
self._buddy.connect_to_signal('PropertyChanged', self._property_changed_cb)
self._properties = self._get_properties_helper()
activities = self._buddy.GetJoinedActivities()
for op in activities:
self._activities[op] = self._ps_new_object(op)
self._icon = None
def _get_properties_helper(self):
"""Retrieve the Buddy's property dictionary from the service object
"""
props = self._buddy.GetProperties(byte_arrays=True)
if not props:
return {}
return props
def do_get_property(self, pspec):
"""Retrieve a particular property from our property dictionary
pspec -- XXX some sort of GTK specifier object with attributes
including 'name', 'active' and 'icon-name'
"""
if pspec.name == "key":
return self._properties["key"]
elif pspec.name == "nick":
return self._properties["nick"]
elif pspec.name == "color":
return self._properties["color"]
elif pspec.name == "current-activity":
if not self._properties.has_key("current-activity"):
return None
curact = self._properties["current-activity"]
if not len(curact):
return None
for activity in self._activities.values():
if activity.props.id == curact:
return activity
return None
elif pspec.name == "owner":
return self._properties["owner"]
elif pspec.name == "icon":
if not self._icon:
self._icon = str(self._buddy.GetIcon(byte_arrays=True))
return self._icon
elif pspec.name == "ip4-address":
# IPv4 address will go away quite soon
if not self._properties.has_key("ip4-address"):
return None
return self._properties["ip4-address"]
def object_path(self):
"""Retrieve our dbus object path"""
return self._object_path
def _emit_icon_changed_signal(self, bytes):
"""Emit GObject signal when icon has changed"""
self._icon = str(bytes)
self.emit('icon-changed')
return False
def _icon_changed_cb(self, icon_data):
"""Handle dbus signal by emitting a GObject signal"""
gobject.idle_add(self._emit_icon_changed_signal, icon_data)
def _emit_joined_activity_signal(self, object_path):
"""Emit activity joined signal with Activity object"""
self.emit('joined-activity', self._ps_new_object(object_path))
return False
def _joined_activity_cb(self, object_path):
"""Handle dbus signal by emitting a GObject signal
Stores the activity in activities dictionary as well
"""
if not self._activities.has_key(object_path):
self._activities[object_path] = self._ps_new_object(object_path)
gobject.idle_add(self._emit_joined_activity_signal, object_path)
def _emit_left_activity_signal(self, object_path):
"""Emit activity left signal with Activity object
XXX this calls self._ps_new_object instead of self._ps_del_object,
which would seem to be the incorrect callback?
"""
self.emit('left-activity', self._ps_new_object(object_path))
return False
def _left_activity_cb(self, object_path):
"""Handle dbus signal by emitting a GObject signal
Also removes from the activities dictionary
"""
if self._activities.has_key(object_path):
del self._activities[object_path]
gobject.idle_add(self._emit_left_activity_signal, object_path)
def _handle_property_changed_signal(self, prop_list):
"""Emit property-changed signal with property dictionary
Generates a property-changed signal with the results of
_get_properties_helper()
"""
self._properties = self._get_properties_helper()
# FIXME: don't leak unexposed property names
self.emit('property-changed', prop_list)
return False
def _property_changed_cb(self, prop_list):
"""Handle dbus signal by emitting a GObject signal"""
gobject.idle_add(self._handle_property_changed_signal, prop_list)
def get_icon_pixbuf(self):
"""Retrieve Buddy's icon as a GTK pixel buffer
XXX Why aren't the icons coming in as SVG?
"""
if self.props.icon and len(self.props.icon):
pbl = gtk.gdk.PixbufLoader()
pbl.write(self.props.icon)
pbl.close()
return pbl.get_pixbuf()
else:
return None
def get_joined_activities(self):
"""Retrieve the set of all activities which this buddy has joined
Uses the GetJoinedActivities method on the service
object to produce object paths, wraps each in an
Activity object.
returns list of presence Activity objects
"""
try:
resp = self._buddy.GetJoinedActivities()
except dbus.exceptions.DBusException:
return []
acts = []
for item in resp:
acts.append(self._ps_new_object(item))
return acts
+570
View File
@@ -0,0 +1,570 @@
"""UI class to access system-level presence object"""
# Copyright (C) 2007, 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 dbus.exceptions
import dbus.glib
import gobject
from sugar.presence.buddy import Buddy
from sugar.presence.activity import Activity
DBUS_SERVICE = "org.laptop.Sugar.Presence"
DBUS_INTERFACE = "org.laptop.Sugar.Presence"
DBUS_PATH = "/org/laptop/Sugar/Presence"
_logger = logging.getLogger('sugar.presence.presenceservice')
class PresenceService(gobject.GObject):
"""UI-side interface to the dbus presence service
This class provides UI programmers with simplified access
to the dbus service of the same name. It allows for observing
various events from the presence service as GObject events,
as well as some basic introspection queries.
"""
__gsignals__ = {
'buddy-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
# parameters: (activity: Activity, inviter: Buddy, message: unicode)
'activity-invitation': (gobject.SIGNAL_RUN_FIRST, None, ([object]*3)),
'private-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
gobject.TYPE_PYOBJECT])),
'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'activity-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'activity-shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
gobject.TYPE_PYOBJECT]))
}
_PS_BUDDY_OP = DBUS_PATH + "/Buddies/"
_PS_ACTIVITY_OP = DBUS_PATH + "/Activities/"
def __init__(self, allow_offline_iface=True):
"""Initialise the service and attempt to connect to events
"""
gobject.GObject.__init__(self)
self._objcache = {}
# Get a connection to the session bus
self._bus = dbus.SessionBus()
self._bus.add_signal_receiver(self._name_owner_changed_cb,
signal_name="NameOwnerChanged",
dbus_interface="org.freedesktop.DBus")
# attempt to load the interface to the service...
self._allow_offline_iface = allow_offline_iface
self._get_ps()
def _name_owner_changed_cb(self, name, old, new):
if name != DBUS_SERVICE:
return
if (old and len(old)) and (not new and not len(new)):
# PS went away, clear out PS dbus service wrapper
self._ps_ = None
elif (not old and not len(old)) and (new and len(new)):
# PS started up
self._get_ps()
_ps_ = None
def _get_ps(self):
"""Retrieve dbus interface to PresenceService
Also registers for updates from various dbus events on the
interface.
If unable to retrieve the interface, we will temporarily
return an _OfflineInterface object to allow the calling
code to continue functioning as though it had accessed a
real presence service.
If successful, caches the presence service interface
for use by other methods and returns that interface
"""
if not self._ps_:
try:
# NOTE: We need to follow_name_owner_changes here
# because we can not connect to a signal unless
# we follow the changes or we start the service
# before we connect. Starting the service here
# causes a major bottleneck during startup
ps = dbus.Interface(
self._bus.get_object(DBUS_SERVICE,
DBUS_PATH,
follow_name_owner_changes=True),
DBUS_INTERFACE
)
except dbus.exceptions.DBusException, err:
_logger.error(
"""Failure retrieving %r interface from the D-BUS service %r %r: %s""",
DBUS_INTERFACE, DBUS_SERVICE, DBUS_PATH, err
)
if self._allow_offline_iface:
return _OfflineInterface()
raise RuntimeError("Failed to connect to the presence service.")
else:
self._ps_ = ps
ps.connect_to_signal('BuddyAppeared', self._buddy_appeared_cb)
ps.connect_to_signal('BuddyDisappeared', self._buddy_disappeared_cb)
ps.connect_to_signal('ActivityAppeared', self._activity_appeared_cb)
ps.connect_to_signal('ActivityDisappeared', self._activity_disappeared_cb)
ps.connect_to_signal('ActivityInvitation', self._activity_invitation_cb)
ps.connect_to_signal('PrivateInvitation', self._private_invitation_cb)
return self._ps_
_ps = property(
_get_ps, None, None,
"""DBUS interface to the PresenceService (services/presence/presenceservice)"""
)
def _new_object(self, object_path):
"""Turn new object path into (cached) Buddy/Activity instance
object_path -- full dbus path of the new object, must be
prefixed with either of _PS_BUDDY_OP or _PS_ACTIVITY_OP
Note that this method is called throughout the class whenever
the representation of the object is required, it is not only
called when the object is first discovered. The point is to only have
_one_ Python object for any D-Bus object represented by an object path,
effectively wrapping the D-Bus object in a single Python GObject.
returns presence Buddy or Activity representation
"""
obj = None
try:
obj = self._objcache[object_path]
except KeyError:
if object_path.startswith(self._PS_BUDDY_OP):
obj = Buddy(self._bus, self._new_object,
self._del_object, object_path)
elif object_path.startswith(self._PS_ACTIVITY_OP):
obj = Activity(self._bus, self._new_object,
self._del_object, object_path)
try:
# Pre-fill the activity's ID
foo = obj.props.id
except dbus.exceptions.DBusException, err:
pass
else:
raise RuntimeError("Unknown object type")
self._objcache[object_path] = obj
return obj
def _have_object(self, object_path):
return object_path in self._objcache.keys()
def _del_object(self, object_path):
"""Fully remove an object from the object cache when it's no longer needed.
"""
del self._objcache[object_path]
def _emit_buddy_appeared_signal(self, object_path):
"""Emit GObject event with presence.buddy.Buddy object"""
self.emit('buddy-appeared', self._new_object(object_path))
return False
def _buddy_appeared_cb(self, op):
"""Callback for dbus event (forwards to method to emit GObject event)"""
gobject.idle_add(self._emit_buddy_appeared_signal, op)
def _emit_buddy_disappeared_signal(self, object_path):
"""Emit GObject event with presence.buddy.Buddy object"""
# Don't try to create a new object here if needed; it will probably
# fail anyway because the object has already been destroyed in the PS
if self._have_object(object_path):
self.emit('buddy-disappeared', self._new_object(object_path))
return False
def _buddy_disappeared_cb(self, object_path):
"""Callback for dbus event (forwards to method to emit GObject event)"""
gobject.idle_add(self._emit_buddy_disappeared_signal, object_path)
def _emit_activity_invitation_signal(self, activity_path, buddy_path,
message):
"""Emit GObject event with presence.activity.Activity object"""
self.emit('activity-invitation', self._new_object(activity_path),
self._new_object(buddy_path), unicode(message))
return False
def _activity_invitation_cb(self, activity_path, buddy_path, message):
"""Callback for dbus event (forwards to method to emit GObject event)"""
gobject.idle_add(self._emit_activity_invitation_signal, activity_path,
buddy_path, message)
def _emit_private_invitation_signal(self, bus_name, connection, channel):
"""Emit GObject event with bus_name, connection and channel"""
self.emit('private-invitation', bus_name, connection, channel)
return False
def _private_invitation_cb(self, bus_name, connection, channel):
"""Callback for dbus event (forwards to method to emit GObject event)"""
gobject.idle_add(self._emit_service_disappeared_signal, bus_name,
connection, channel)
def _emit_activity_appeared_signal(self, object_path):
"""Emit GObject event with presence.activity.Activity object"""
self.emit('activity-appeared', self._new_object(object_path))
return False
def _activity_appeared_cb(self, object_path):
"""Callback for dbus event (forwards to method to emit GObject event)"""
gobject.idle_add(self._emit_activity_appeared_signal, object_path)
def _emit_activity_disappeared_signal(self, object_path):
"""Emit GObject event with presence.activity.Activity object"""
self.emit('activity-disappeared', self._new_object(object_path))
return False
def _activity_disappeared_cb(self, object_path):
"""Callback for dbus event (forwards to method to emit GObject event)"""
gobject.idle_add(self._emit_activity_disappeared_signal, object_path)
def get(self, object_path):
"""Return the Buddy or Activity object corresponding to the given
D-Bus object path.
"""
return self._new_object(object_path)
def get_activities(self):
"""Retrieve set of all activities from service
returns list of Activity objects for all object paths
the service reports exist (using GetActivities)
"""
try:
resp = self._ps.GetActivities()
except dbus.exceptions.DBusException, err:
_logger.warn(
"""Unable to retrieve activity list from presence service: %s"""
% err
)
return []
else:
acts = []
for item in resp:
acts.append(self._new_object(item))
return acts
def _get_activities_cb(self, reply_handler, resp):
acts = []
for item in resp:
acts.append(self._new_object(item))
reply_handler(acts)
def _get_activities_error_cb(self, error_handler, e):
if error_handler:
error_handler(e)
else:
_logger.warn(
"""Unable to retrieve activity-list from presence service: %s"""
% e
)
def get_activities_async(self, reply_handler=None, error_handler=None):
"""Retrieve set of all activities from service asyncronously
"""
if not reply_handler:
logging.error('Function get_activities_async called without a reply handler. Can not run.')
return
self._ps.GetActivities(
reply_handler=lambda resp:self._get_activities_cb(reply_handler, resp),
error_handler=lambda e:self._get_activities_error_cb(error_handler, e))
def get_activity(self, activity_id):
"""Retrieve single Activity object for the given unique id
activity_id -- unique ID for the activity
returns single Activity object or None if the activity
is not found using GetActivityById on the service
"""
try:
act_op = self._ps.GetActivityById(activity_id)
except dbus.exceptions.DBusException, err:
_logger.warn(
"""Unable to retrieve activity handle for %r from presence service: %s"""
% (activity_id, err)
)
return None
return self._new_object(act_op)
def get_buddies(self):
"""Retrieve set of all buddies from service
returns list of Buddy objects for all object paths
the service reports exist (using GetBuddies)
"""
try:
resp = self._ps.GetBuddies()
except dbus.exceptions.DBusException, err:
_logger.warn(
"""Unable to retrieve buddy-list from presence service: %s"""
% err
)
return []
else:
buddies = []
for item in resp:
buddies.append(self._new_object(item))
return buddies
def _get_buddies_cb(self, reply_handler, resp):
buddies = []
for item in resp:
buddies.append(self._new_object(item))
reply_handler(buddies)
def _get_buddies_error_cb(self, error_handler, e):
if error_handler:
error_handler(e)
else:
_logger.warn(
"""Unable to retrieve buddy-list from presence service: %s"""
% e
)
def get_buddies_async(self, reply_handler=None, error_handler=None):
"""Retrieve set of all buddies from service asyncronously
"""
if not reply_handler:
logging.error('Function get_buddies_async called without a reply handler. Can not run.')
return
self._ps.GetBuddies(
reply_handler=lambda resp:self._get_buddies_cb(reply_handler, resp),
error_handler=lambda e:self._get_buddies_error_cb(error_handler, e))
def get_buddy(self, key):
"""Retrieve single Buddy object for the given public key
key -- buddy's public encryption key
returns single Buddy object or None if the activity
is not found using GetBuddyByPublicKey on the
service
"""
try:
buddy_op = self._ps.GetBuddyByPublicKey(dbus.ByteArray(key))
except dbus.exceptions.DBusException, err:
_logger.warn(
"""Unable to retrieve buddy handle for %r from presence service: %s"""
% key, err
)
return None
return self._new_object(buddy_op)
def get_buddy_by_telepathy_handle(self, tp_conn_name, tp_conn_path,
handle):
"""Retrieve single Buddy object for the given public key
:Parameters:
`tp_conn_name` : str
The well-known bus name of a Telepathy connection
`tp_conn_path` : dbus.ObjectPath
The object path of the Telepathy connection
`handle` : int or long
The handle of a Telepathy contact on that connection,
of type HANDLE_TYPE_CONTACT. This may not be a
channel-specific handle.
:Returns: the Buddy object, or None if the buddy is not found
"""
try:
buddy_op = self._ps.GetBuddyByTelepathyHandle(tp_conn_name,
tp_conn_path,
handle)
except dbus.exceptions.DBusException, err:
_logger.warn('Unable to retrieve buddy handle for handle %u at '
'conn %s:%s from presence service: %s',
handle, tp_conn_name, tp_conn_path, err)
return None
return self._new_object(buddy_op)
def get_owner(self):
"""Retrieves the laptop "owner" Buddy object."""
try:
owner_op = self._ps.GetOwner()
except dbus.exceptions.DBusException, err:
_logger.warn(
"""Unable to retrieve local user/owner from presence service: %s"""
% err
)
raise RuntimeError("Could not get owner object from presence service.")
return self._new_object(owner_op)
def _share_activity_cb(self, activity, psact):
"""Notify with GObject event of successful sharing of activity
"""
psact._joined = True
self.emit("activity-shared", True, psact, None)
def _share_activity_privacy_cb(self, activity, private, op):
psact = self._new_object(op)
# FIXME: this should be done asynchronously (more API needed)
try:
psact.props.private = private
except Exception, e:
self._share_activity_error_cb(activity, e)
else:
self._share_activity_cb(activity, psact)
def _share_activity_error_cb(self, activity, err):
"""Notify with GObject event of unsuccessful sharing of activity"""
_logger.debug("Error sharing activity %s: %s" % (activity.get_id(), err))
self.emit("activity-shared", False, None, err)
def share_activity(self, activity, properties={}, private=True):
"""Ask presence service to ask the activity to share itself publicly.
Uses the AdvertiseActivity method on the service to ask for the
sharing of the given activity. Arranges to emit activity-shared
event with:
(success, Activity, err)
on success/failure.
returns None
"""
actid = activity.get_id()
# Ensure the activity is not already shared/joined
for obj in self._objcache.values():
if not isinstance(object, Activity):
continue
if obj.props.id == actid or obj.props.joined:
raise RuntimeError("Activity %s is already shared." %
actid)
atype = activity.get_bundle_id()
name = activity.props.title
self._ps.ShareActivity(actid, atype, name, properties,
reply_handler=lambda op: \
self._share_activity_privacy_cb(activity, private, op),
error_handler=lambda e: \
self._share_activity_error_cb(activity, e))
def get_preferred_connection(self):
"""Gets the preferred telepathy connection object that an activity
should use when talking directly to telepathy
returns the bus name and the object path of the Telepathy connection"""
try:
bus_name, object_path = self._ps.GetPreferredConnection()
except dbus.exceptions.DBusException:
return None
return bus_name, object_path
class _OfflineInterface( object ):
"""Offline-presence-service interface
Used to mimic the behaviour of a real PresenceService sufficiently
to avoid crashing client code that expects the given interface.
XXX we could likely return a "MockOwner" object reasonably
easily, but would it be worth it?
"""
def raiseException( self, *args, **named ):
"""Raise dbus.exceptions.DBusException"""
raise dbus.exceptions.DBusException(
"""PresenceService Interface not available"""
)
GetActivities = raiseException
GetActivityById = raiseException
GetBuddies = raiseException
GetBuddyByPublicKey = raiseException
GetOwner = raiseException
GetPreferredConnection = raiseException
def ShareActivity(
self, actid, atype, name, properties,
reply_handler, error_handler,
):
"""Pretend to share and fail..."""
exc = IOError(
"""Unable to share activity as PresenceService is not currenly available"""
)
return error_handler( exc )
class _MockPresenceService(gobject.GObject):
"""Test fixture allowing testing of items that use PresenceService
See PresenceService for usage and purpose
"""
__gsignals__ = {
'buddy-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'activity-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'private-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
gobject.TYPE_PYOBJECT])),
'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'activity-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
def __init__(self):
gobject.GObject.__init__(self)
def get_activities(self):
return []
def get_activity(self, activity_id):
return None
def get_buddies(self):
return []
def get_buddy(self, key):
return None
def get_owner(self):
return None
def share_activity(self, activity, properties={}):
return None
_ps = None
def get_instance(allow_offline_iface=False):
"""Retrieve this process' view of the PresenceService"""
global _ps
if not _ps:
_ps = PresenceService(allow_offline_iface)
return _ps
+26
View File
@@ -0,0 +1,26 @@
This is a test of presence.
To test this service we will start up a mock dbus library:
>>> from sugar.testing import mockdbus
>>> import dbus
>>> pres_service = mockdbus.MockService(
... 'org.laptop.Presence', '/org/laptop/Presence', name='pres')
>>> pres_service.install()
>>> pres_interface = dbus.Interface(pres_service, 'org.laptop.Presence')
Then we import the library (second, to make sure it connects to our
mocked system, though the lazy instantiation in get_instance() should
handle it):
>>> from sugar.presence import PresenceService
>>> ps = PresenceService.get_instance()
>>> pres_interface.make_response('getServices', [])
>>> ps.get_services()
Called pres.org.laptop.Presence:getServices()
[]
>>> pres_interface.make_response('getBuddies', [])
>>> ps.get_buddies()
Called pres.org.laptop.Presence:getBuddies()
[]
+107
View File
@@ -0,0 +1,107 @@
# This should eventually land in telepathy-python, so has the same license:
# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
#
# This program 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.1 of the License, or
# (at your option) any later version.
#
# This program 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 program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
__all__ = ('TubeConnection',)
__docformat__ = 'reStructuredText'
import logging
from dbus.connection import Connection
logger = logging.getLogger('telepathy.tubeconn')
class TubeConnection(Connection):
def __new__(cls, conn, tubes_iface, tube_id, address=None,
group_iface=None, mainloop=None):
if address is None:
address = tubes_iface.GetDBusTubeAddress(tube_id)
self = super(TubeConnection, cls).__new__(cls, address,
mainloop=mainloop)
self._tubes_iface = tubes_iface
self.tube_id = tube_id
self.participants = {}
self.bus_name_to_handle = {}
self._mapping_watches = []
if group_iface is None:
method = conn.GetSelfHandle
else:
method = group_iface.GetSelfHandle
method(reply_handler=self._on_get_self_handle_reply,
error_handler=self._on_get_self_handle_error)
return self
def _on_get_self_handle_reply(self, handle):
self.self_handle = handle
match = self._tubes_iface.connect_to_signal('DBusNamesChanged',
self._on_dbus_names_changed)
self._tubes_iface.GetDBusNames(self.tube_id,
reply_handler=self._on_get_dbus_names_reply,
error_handler=self._on_get_dbus_names_error)
self._dbus_names_changed_match = match
def _on_get_self_handle_error(self, e):
logging.basicConfig()
logger.error('GetSelfHandle failed: %s', e)
def close(self):
self._dbus_names_changed_match.remove()
self._on_dbus_names_changed(self.tube_id, (), self.participants.keys())
super(TubeConnection, self).close()
def _on_get_dbus_names_reply(self, names):
self._on_dbus_names_changed(self.tube_id, names, ())
def _on_get_dbus_names_error(self, e):
logging.basicConfig()
logger.error('GetDBusNames failed: %s', e)
def _on_dbus_names_changed(self, tube_id, added, removed):
if tube_id == self.tube_id:
for handle, bus_name in added:
if handle == self.self_handle:
# I've just joined - set my unique name
self.set_unique_name(bus_name)
self.participants[handle] = bus_name
self.bus_name_to_handle[bus_name] = handle
# call the callback while the removed people are still in
# participants, so their bus names are available
for callback in self._mapping_watches:
callback(added, removed)
for handle in removed:
bus_name = self.participants.pop(handle, None)
self.bus_name_to_handle.pop(bus_name, None)
def watch_participants(self, callback):
self._mapping_watches.append(callback)
if self.participants:
# GetDBusNames already returned: fake a participant add event
# immediately
added = []
for k, v in self.participants.iteritems():
added.append((k, v))
callback(added, [])
+199
View File
@@ -0,0 +1,199 @@
"""User settings/configuration loading"""
# Copyright (C) 2006-2007, 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
from ConfigParser import ConfigParser
from sugar import env
from sugar import util
from sugar.graphics.xocolor import XoColor
DEFAULT_JABBER_SERVER = 'olpc.collabora.co.uk'
DEFAULT_VOLUME = 81
_profile = None
def _set_key(cp, section, key, value):
if not cp.has_section(section):
cp.add_section(section)
cp.set(section, key, value)
class Profile(object):
"""Local user's current options/profile information
User settings are stored in an INI-style configuration
file. This object uses the ConfigParser module to load
the settings. (We only very rarely set keys, so we don't
keep the ConfigParser around between calls.)
The profile is also responsible for loading the user's
public and private ssh keys from disk.
Attributes:
name -- child's name
color -- XoColor for the child's icon
server -- school server with which the child is
associated
server_registered -- whether the child has registered
with the school server or not
backup1 -- temporary backup info key for Trial-2
pubkey -- public ssh key
privkey_hash -- SHA has of the child's public key
"""
def __init__(self, path):
self.nick_name = None
self.color = None
self.pubkey = None
self.privkey_hash = None
self.jabber_server = DEFAULT_JABBER_SERVER
self.jabber_registered = False
self.backup1 = None
self._config_path = path
self._load_config()
self._load_pubkey()
self._hash_private_key()
def is_valid(self):
return self.nick_name is not None and \
self.color is not None and \
self.pubkey is not None and \
self.privkey_hash is not None
def is_registered(self):
return self.backup1 is not None
def save(self):
cp = ConfigParser()
parsed = cp.read([self._config_path])
if self.nick_name:
_set_key(cp, 'Buddy', 'NickName', self.nick_name)
if self.color:
_set_key(cp, 'Buddy', 'Color', self.color.to_string())
if self.backup1:
_set_key(cp, 'Server', 'Backup1', self.backup1)
if self.jabber_server:
_set_key(cp, 'Jabber', 'Server', self.jabber_server)
_set_key(cp, 'Jabber', 'Registered', self.jabber_registered)
_set_key(cp, 'Sound', 'Volume', self.sound_volume)
f = open(self._config_path, 'w')
cp.write(f)
f.close()
def _load_config(self):
cp = ConfigParser()
parsed = cp.read([self._config_path])
if cp.has_option('Buddy', 'NickName'):
name = cp.get('Buddy', 'NickName')
# decode nickname from ascii-safe chars to unicode
self.nick_name = name.decode("utf-8")
if cp.has_option('Buddy', 'Color'):
self.color = XoColor(cp.get('Buddy', 'Color'))
if cp.has_option('Jabber', 'Server'):
self.jabber_server = cp.get('Jabber', 'Server')
if cp.has_option('Jabber', 'Registered'):
registered = cp.get('Jabber', 'Registered')
if registered.lower() == "true":
self.jabber_registered = True
if cp.has_option('Server', 'Backup1'):
self.backup1 = cp.get('Server', 'Backup1')
if cp.has_option('Sound', 'Volume'):
self.sound_volume = float(cp.get('Sound', 'Volume'))
else:
self.sound_volume = DEFAULT_VOLUME
del cp
def _load_pubkey(self):
self.pubkey = None
key_path = os.path.join(env.get_profile_path(), 'owner.key.pub')
try:
f = open(key_path, "r")
lines = f.readlines()
f.close()
except IOError, e:
self.valid = False
logging.error("Error reading public key: %s" % e)
return
magic = "ssh-dss "
for l in lines:
l = l.strip()
if not l.startswith(magic):
continue
self.pubkey = l[len(magic):]
break
if not self.pubkey:
logging.error("Error parsing public key.")
def _hash_private_key(self):
self.privkey_hash = None
key_path = os.path.join(env.get_profile_path(), 'owner.key')
try:
f = open(key_path, "r")
lines = f.readlines()
f.close()
except IOError, e:
logging.error("Error reading private key: %s" % e)
return
key = ""
for l in lines:
l = l.strip()
if l.startswith("-----BEGIN DSA PRIVATE KEY-----"):
continue
if l.startswith("-----END DSA PRIVATE KEY-----"):
continue
key += l
if not len(key):
logging.error("Error parsing public key.")
# hash it
key_hash = util._sha_data(key)
self.privkey_hash = util.printable_hash(key_hash)
def get_profile():
global _profile
if not _profile:
path = os.path.join(env.get_profile_path(), 'config')
_profile = Profile(path)
return _profile
# Convenience methods for frequently used properties
def get_nick_name():
return get_profile().nick_name
def get_color():
return get_profile().color
def get_pubkey():
return get_profile().pubkey
+984
View File
@@ -0,0 +1,984 @@
/*
* @file libsexy/sexy-icon-entry.c Entry widget
*
* @Copyright (C) 2004-2006 Christian Hammond.
*
* 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.
*/
#include <sexy-icon-entry.h>
#include <string.h>
#include <gtk/gtk.h>
#define ICON_MARGIN 2
#define MAX_ICONS 2
#define IS_VALID_ICON_ENTRY_POSITION(pos) \
((pos) == SEXY_ICON_ENTRY_PRIMARY || \
(pos) == SEXY_ICON_ENTRY_SECONDARY)
typedef struct
{
GtkImage *icon;
gboolean highlight;
gboolean hovered;
GdkWindow *window;
} SexyIconInfo;
struct _SexyIconEntryPriv
{
SexyIconInfo icons[MAX_ICONS];
gulong icon_released_id;
};
enum
{
ICON_PRESSED,
ICON_RELEASED,
LAST_SIGNAL
};
static void sexy_icon_entry_class_init(SexyIconEntryClass *klass);
static void sexy_icon_entry_editable_init(GtkEditableClass *iface);
static void sexy_icon_entry_init(SexyIconEntry *entry);
static void sexy_icon_entry_finalize(GObject *obj);
static void sexy_icon_entry_destroy(GtkObject *obj);
static void sexy_icon_entry_map(GtkWidget *widget);
static void sexy_icon_entry_unmap(GtkWidget *widget);
static void sexy_icon_entry_realize(GtkWidget *widget);
static void sexy_icon_entry_unrealize(GtkWidget *widget);
static void sexy_icon_entry_size_request(GtkWidget *widget,
GtkRequisition *requisition);
static void sexy_icon_entry_size_allocate(GtkWidget *widget,
GtkAllocation *allocation);
static gint sexy_icon_entry_expose(GtkWidget *widget, GdkEventExpose *event);
static gint sexy_icon_entry_enter_notify(GtkWidget *widget,
GdkEventCrossing *event);
static gint sexy_icon_entry_leave_notify(GtkWidget *widget,
GdkEventCrossing *event);
static gint sexy_icon_entry_button_press(GtkWidget *widget,
GdkEventButton *event);
static gint sexy_icon_entry_button_release(GtkWidget *widget,
GdkEventButton *event);
static GtkEntryClass *parent_class = NULL;
static guint signals[LAST_SIGNAL] = {0};
G_DEFINE_TYPE_EXTENDED(SexyIconEntry, sexy_icon_entry, GTK_TYPE_ENTRY,
0,
G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE,
sexy_icon_entry_editable_init));
static void
sexy_icon_entry_class_init(SexyIconEntryClass *klass)
{
GObjectClass *gobject_class;
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
GtkEntryClass *entry_class;
parent_class = g_type_class_peek_parent(klass);
gobject_class = G_OBJECT_CLASS(klass);
object_class = GTK_OBJECT_CLASS(klass);
widget_class = GTK_WIDGET_CLASS(klass);
entry_class = GTK_ENTRY_CLASS(klass);
gobject_class->finalize = sexy_icon_entry_finalize;
object_class->destroy = sexy_icon_entry_destroy;
widget_class->map = sexy_icon_entry_map;
widget_class->unmap = sexy_icon_entry_unmap;
widget_class->realize = sexy_icon_entry_realize;
widget_class->unrealize = sexy_icon_entry_unrealize;
widget_class->size_request = sexy_icon_entry_size_request;
widget_class->size_allocate = sexy_icon_entry_size_allocate;
widget_class->expose_event = sexy_icon_entry_expose;
widget_class->enter_notify_event = sexy_icon_entry_enter_notify;
widget_class->leave_notify_event = sexy_icon_entry_leave_notify;
widget_class->button_press_event = sexy_icon_entry_button_press;
widget_class->button_release_event = sexy_icon_entry_button_release;
/**
* SexyIconEntry::icon-pressed:
* @entry: The entry on which the signal is emitted.
* @icon_pos: The position of the clicked icon.
* @button: The mouse button clicked.
*
* The ::icon-pressed signal is emitted when an icon is clicked.
*/
signals[ICON_PRESSED] =
g_signal_new("icon_pressed",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(SexyIconEntryClass, icon_pressed),
NULL, NULL,
gtk_marshal_VOID__INT_INT,
G_TYPE_NONE, 2,
G_TYPE_INT,
G_TYPE_INT);
/**
* SexyIconEntry::icon-released:
* @entry: The entry on which the signal is emitted.
* @icon_pos: The position of the clicked icon.
* @button: The mouse button clicked.
*
* The ::icon-released signal is emitted on the button release from a
* mouse click.
*/
signals[ICON_RELEASED] =
g_signal_new("icon_released",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(SexyIconEntryClass, icon_released),
NULL, NULL,
gtk_marshal_VOID__INT_INT,
G_TYPE_NONE, 2,
G_TYPE_INT,
G_TYPE_INT);
}
static void
sexy_icon_entry_editable_init(GtkEditableClass *iface)
{
};
static void
sexy_icon_entry_init(SexyIconEntry *entry)
{
entry->priv = g_new0(SexyIconEntryPriv, 1);
}
static void
sexy_icon_entry_finalize(GObject *obj)
{
SexyIconEntry *entry;
g_return_if_fail(obj != NULL);
g_return_if_fail(SEXY_IS_ICON_ENTRY(obj));
entry = SEXY_ICON_ENTRY(obj);
g_free(entry->priv);
if (G_OBJECT_CLASS(parent_class)->finalize)
G_OBJECT_CLASS(parent_class)->finalize(obj);
}
static void
sexy_icon_entry_destroy(GtkObject *obj)
{
SexyIconEntry *entry;
entry = SEXY_ICON_ENTRY(obj);
sexy_icon_entry_set_icon(entry, SEXY_ICON_ENTRY_PRIMARY, NULL);
sexy_icon_entry_set_icon(entry, SEXY_ICON_ENTRY_SECONDARY, NULL);
if (GTK_OBJECT_CLASS(parent_class)->destroy)
GTK_OBJECT_CLASS(parent_class)->destroy(obj);
}
static void
sexy_icon_entry_map(GtkWidget *widget)
{
if (GTK_WIDGET_REALIZED(widget) && !GTK_WIDGET_MAPPED(widget))
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
GTK_WIDGET_CLASS(parent_class)->map(widget);
for (i = 0; i < MAX_ICONS; i++)
{
if (entry->priv->icons[i].icon != NULL)
gdk_window_show(entry->priv->icons[i].window);
}
}
}
static void
sexy_icon_entry_unmap(GtkWidget *widget)
{
if (GTK_WIDGET_MAPPED(widget))
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
for (i = 0; i < MAX_ICONS; i++)
{
if (entry->priv->icons[i].icon != NULL)
gdk_window_hide(entry->priv->icons[i].window);
}
GTK_WIDGET_CLASS(parent_class)->unmap(widget);
}
}
static gint
get_icon_width(SexyIconEntry *entry, SexyIconEntryPosition icon_pos)
{
GtkRequisition requisition;
gint menu_icon_width;
gint width;
SexyIconInfo *icon_info = &entry->priv->icons[icon_pos];
if (icon_info->icon == NULL)
return 0;
gtk_widget_size_request(GTK_WIDGET(icon_info->icon), &requisition);
gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &menu_icon_width, NULL);
width = MAX(requisition.width, menu_icon_width);
return width;
}
static void
get_borders(SexyIconEntry *entry, gint *xborder, gint *yborder)
{
GtkWidget *widget = GTK_WIDGET(entry);
gint focus_width;
gboolean interior_focus;
gtk_widget_style_get(widget,
"interior-focus", &interior_focus,
"focus-line-width", &focus_width,
NULL);
if (gtk_entry_get_has_frame(GTK_ENTRY(entry)))
{
*xborder = widget->style->xthickness;
*yborder = widget->style->ythickness;
}
else
{
*xborder = 0;
*yborder = 0;
}
if (!interior_focus)
{
*xborder += focus_width;
*yborder += focus_width;
}
}
static void
get_text_area_size(SexyIconEntry *entry, GtkAllocation *alloc)
{
GtkWidget *widget = GTK_WIDGET(entry);
GtkRequisition requisition;
gint xborder, yborder;
gtk_widget_get_child_requisition(widget, &requisition);
get_borders(entry, &xborder, &yborder);
alloc->x = xborder;
alloc->y = yborder;
alloc->width = widget->allocation.width - xborder * 2;
alloc->height = requisition.height - yborder * 2;
}
static void
get_icon_allocation(SexyIconEntry *icon_entry,
gboolean left,
GtkAllocation *widget_alloc,
GtkAllocation *text_area_alloc,
GtkAllocation *allocation,
SexyIconEntryPosition *icon_pos)
{
gboolean rtl;
rtl = (gtk_widget_get_direction(GTK_WIDGET(icon_entry)) ==
GTK_TEXT_DIR_RTL);
if (left)
*icon_pos = (rtl ? SEXY_ICON_ENTRY_SECONDARY : SEXY_ICON_ENTRY_PRIMARY);
else
*icon_pos = (rtl ? SEXY_ICON_ENTRY_PRIMARY : SEXY_ICON_ENTRY_SECONDARY);
allocation->y = text_area_alloc->y;
allocation->width = get_icon_width(icon_entry, *icon_pos);
allocation->height = text_area_alloc->height;
if (left)
allocation->x = text_area_alloc->x + ICON_MARGIN;
else
{
allocation->x = text_area_alloc->x + text_area_alloc->width -
allocation->width - ICON_MARGIN;
}
}
static void
sexy_icon_entry_realize(GtkWidget *widget)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
GdkWindowAttr attributes;
gint attributes_mask;
int i;
GTK_WIDGET_CLASS(parent_class)->realize(widget);
attributes.x = 0;
attributes.y = 0;
attributes.width = 1;
attributes.height = 1;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual(widget);
attributes.colormap = gtk_widget_get_colormap(widget);
attributes.event_mask = gtk_widget_get_events(widget);
attributes.event_mask |=
(GDK_EXPOSURE_MASK
| GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
| GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
for (i = 0; i < MAX_ICONS; i++)
{
SexyIconInfo *icon_info;
icon_info = &entry->priv->icons[i];
icon_info->window = gdk_window_new(widget->window, &attributes,
attributes_mask);
gdk_window_set_user_data(icon_info->window, widget);
gdk_window_set_background(icon_info->window,
&widget->style->base[GTK_WIDGET_STATE(widget)]);
}
gtk_widget_queue_resize(widget);
}
static void
sexy_icon_entry_unrealize(GtkWidget *widget)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
GTK_WIDGET_CLASS(parent_class)->unrealize(widget);
for (i = 0; i < MAX_ICONS; i++)
{
SexyIconInfo *icon_info = &entry->priv->icons[i];
gdk_window_destroy(icon_info->window);
icon_info->window = NULL;
}
}
static void
sexy_icon_entry_size_request(GtkWidget *widget, GtkRequisition *requisition)
{
GtkEntry *gtkentry;
SexyIconEntry *entry;
gint icon_widths = 0;
int i;
gtkentry = GTK_ENTRY(widget);
entry = SEXY_ICON_ENTRY(widget);
for (i = 0; i < MAX_ICONS; i++)
{
int icon_width = get_icon_width(entry, i);
if (icon_width > 0)
icon_widths += icon_width + ICON_MARGIN;
}
GTK_WIDGET_CLASS(parent_class)->size_request(widget, requisition);
if (icon_widths > requisition->width)
requisition->width += icon_widths;
}
static void
place_windows(SexyIconEntry *icon_entry, GtkAllocation *widget_alloc)
{
SexyIconEntryPosition left_icon_pos;
SexyIconEntryPosition right_icon_pos;
GtkAllocation left_icon_alloc;
GtkAllocation right_icon_alloc;
GtkAllocation text_area_alloc;
get_text_area_size(icon_entry, &text_area_alloc);
get_icon_allocation(icon_entry, TRUE, widget_alloc, &text_area_alloc,
&left_icon_alloc, &left_icon_pos);
get_icon_allocation(icon_entry, FALSE, widget_alloc, &text_area_alloc,
&right_icon_alloc, &right_icon_pos);
if (left_icon_alloc.width > 0)
{
text_area_alloc.x = left_icon_alloc.x + left_icon_alloc.width +
ICON_MARGIN;
}
if (right_icon_alloc.width > 0)
text_area_alloc.width -= right_icon_alloc.width + ICON_MARGIN;
text_area_alloc.width -= text_area_alloc.x;
gdk_window_move_resize(icon_entry->priv->icons[left_icon_pos].window,
left_icon_alloc.x, left_icon_alloc.y,
left_icon_alloc.width, left_icon_alloc.height);
gdk_window_move_resize(icon_entry->priv->icons[right_icon_pos].window,
right_icon_alloc.x, right_icon_alloc.y,
right_icon_alloc.width, right_icon_alloc.height);
gdk_window_move_resize(GTK_ENTRY(icon_entry)->text_area,
text_area_alloc.x, text_area_alloc.y,
text_area_alloc.width, text_area_alloc.height);
}
static void
sexy_icon_entry_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
{
g_return_if_fail(SEXY_IS_ICON_ENTRY(widget));
g_return_if_fail(allocation != NULL);
widget->allocation = *allocation;
GTK_WIDGET_CLASS(parent_class)->size_allocate(widget, allocation);
if (GTK_WIDGET_REALIZED(widget))
place_windows(SEXY_ICON_ENTRY(widget), allocation);
}
static GdkPixbuf *
get_pixbuf_from_icon(SexyIconEntry *entry, SexyIconEntryPosition icon_pos)
{
GdkPixbuf *pixbuf = NULL;
gchar *stock_id;
SexyIconInfo *icon_info = &entry->priv->icons[icon_pos];
GtkIconSize size;
switch (gtk_image_get_storage_type(GTK_IMAGE(icon_info->icon)))
{
case GTK_IMAGE_PIXBUF:
pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(icon_info->icon));
g_object_ref(pixbuf);
break;
case GTK_IMAGE_STOCK:
gtk_image_get_stock(GTK_IMAGE(icon_info->icon), &stock_id, &size);
pixbuf = gtk_widget_render_icon(GTK_WIDGET(entry),
stock_id, size, NULL);
break;
default:
return NULL;
}
return pixbuf;
}
/* Kudos to the gnome-panel guys. */
static void
colorshift_pixbuf(GdkPixbuf *dest, GdkPixbuf *src, int shift)
{
gint i, j;
gint width, height, has_alpha, src_rowstride, dest_rowstride;
guchar *target_pixels;
guchar *original_pixels;
guchar *pix_src;
guchar *pix_dest;
int val;
guchar r, g, b;
has_alpha = gdk_pixbuf_get_has_alpha(src);
width = gdk_pixbuf_get_width(src);
height = gdk_pixbuf_get_height(src);
src_rowstride = gdk_pixbuf_get_rowstride(src);
dest_rowstride = gdk_pixbuf_get_rowstride(dest);
original_pixels = gdk_pixbuf_get_pixels(src);
target_pixels = gdk_pixbuf_get_pixels(dest);
for (i = 0; i < height; i++)
{
pix_dest = target_pixels + i * dest_rowstride;
pix_src = original_pixels + i * src_rowstride;
for (j = 0; j < width; j++)
{
r = *(pix_src++);
g = *(pix_src++);
b = *(pix_src++);
val = r + shift;
*(pix_dest++) = CLAMP(val, 0, 255);
val = g + shift;
*(pix_dest++) = CLAMP(val, 0, 255);
val = b + shift;
*(pix_dest++) = CLAMP(val, 0, 255);
if (has_alpha)
*(pix_dest++) = *(pix_src++);
}
}
}
static void
draw_icon(GtkWidget *widget, SexyIconEntryPosition icon_pos)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
SexyIconInfo *icon_info = &entry->priv->icons[icon_pos];
GdkPixbuf *pixbuf;
gint x, y, width, height;
if (icon_info->icon == NULL || !GTK_WIDGET_REALIZED(widget))
return;
if ((pixbuf = get_pixbuf_from_icon(entry, icon_pos)) == NULL)
return;
gdk_drawable_get_size(icon_info->window, &width, &height);
if (width == 1 || height == 1)
{
/*
* size_allocate hasn't been called yet. These are the default values.
*/
return;
}
if (gdk_pixbuf_get_height(pixbuf) > height)
{
GdkPixbuf *temp_pixbuf;
int scale;
scale = height - (2 * ICON_MARGIN);
temp_pixbuf = gdk_pixbuf_scale_simple(pixbuf, scale, scale,
GDK_INTERP_BILINEAR);
g_object_unref(pixbuf);
pixbuf = temp_pixbuf;
}
x = (width - gdk_pixbuf_get_width(pixbuf)) / 2;
y = (height - gdk_pixbuf_get_height(pixbuf)) / 2;
if (icon_info->hovered)
{
GdkPixbuf *temp_pixbuf;
temp_pixbuf = gdk_pixbuf_copy(pixbuf);
colorshift_pixbuf(temp_pixbuf, pixbuf, 30);
g_object_unref(pixbuf);
pixbuf = temp_pixbuf;
}
gdk_draw_pixbuf(icon_info->window, widget->style->black_gc, pixbuf,
0, 0, x, y, -1, -1,
GDK_RGB_DITHER_NORMAL, 0, 0);
g_object_unref(pixbuf);
}
static gint
sexy_icon_entry_expose(GtkWidget *widget, GdkEventExpose *event)
{
SexyIconEntry *entry;
g_return_val_if_fail(SEXY_IS_ICON_ENTRY(widget), FALSE);
g_return_val_if_fail(event != NULL, FALSE);
entry = SEXY_ICON_ENTRY(widget);
if (GTK_WIDGET_DRAWABLE(widget))
{
gboolean found = FALSE;
int i;
for (i = 0; i < MAX_ICONS && !found; i++)
{
SexyIconInfo *icon_info = &entry->priv->icons[i];
if (event->window == icon_info->window)
{
gint width;
GtkAllocation text_area_alloc;
get_text_area_size(entry, &text_area_alloc);
gdk_drawable_get_size(icon_info->window, &width, NULL);
gtk_paint_flat_box(widget->style, icon_info->window,
GTK_WIDGET_STATE(widget), GTK_SHADOW_NONE,
NULL, widget, "entry_bg",
0, 0, width, text_area_alloc.height);
draw_icon(widget, i);
found = TRUE;
}
}
if (!found)
GTK_WIDGET_CLASS(parent_class)->expose_event(widget, event);
}
return FALSE;
}
static void
update_icon(GObject *obj, GParamSpec *param, SexyIconEntry *entry)
{
if (param != NULL)
{
const char *name = g_param_spec_get_name(param);
if (strcmp(name, "pixbuf") && strcmp(name, "stock") &&
strcmp(name, "image") && strcmp(name, "pixmap") &&
strcmp(name, "icon_set") && strcmp(name, "pixbuf_animation"))
{
return;
}
}
gtk_widget_queue_resize(GTK_WIDGET(entry));
}
static gint
sexy_icon_entry_enter_notify(GtkWidget *widget, GdkEventCrossing *event)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
for (i = 0; i < MAX_ICONS; i++)
{
if (event->window == entry->priv->icons[i].window)
{
if (sexy_icon_entry_get_icon_highlight(entry, i))
{
entry->priv->icons[i].hovered = TRUE;
update_icon(NULL, NULL, entry);
break;
}
}
}
return FALSE;
}
static gint
sexy_icon_entry_leave_notify(GtkWidget *widget, GdkEventCrossing *event)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
for (i = 0; i < MAX_ICONS; i++)
{
if (event->window == entry->priv->icons[i].window)
{
if (sexy_icon_entry_get_icon_highlight(entry, i))
{
entry->priv->icons[i].hovered = FALSE;
update_icon(NULL, NULL, entry);
break;
}
}
}
return FALSE;
}
static gint
sexy_icon_entry_button_press(GtkWidget *widget, GdkEventButton *event)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
for (i = 0; i < MAX_ICONS; i++)
{
if (event->window == entry->priv->icons[i].window)
{
if (event->button == 1 &&
sexy_icon_entry_get_icon_highlight(entry, i))
{
entry->priv->icons[i].hovered = FALSE;
update_icon(NULL, NULL, entry);
}
g_signal_emit(entry, signals[ICON_PRESSED], 0, i, event->button);
return TRUE;
}
}
if (GTK_WIDGET_CLASS(parent_class)->button_press_event)
return GTK_WIDGET_CLASS(parent_class)->button_press_event(widget,
event);
return FALSE;
}
static gint
sexy_icon_entry_button_release(GtkWidget *widget, GdkEventButton *event)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
for (i = 0; i < MAX_ICONS; i++)
{
GdkWindow *icon_window = entry->priv->icons[i].window;
if (event->window == icon_window)
{
int width, height;
gdk_drawable_get_size(icon_window, &width, &height);
if (event->button == 1 &&
sexy_icon_entry_get_icon_highlight(entry, i) &&
event->x >= 0 && event->y >= 0 &&
event->x <= width && event->y <= height)
{
entry->priv->icons[i].hovered = TRUE;
update_icon(NULL, NULL, entry);
}
g_signal_emit(entry, signals[ICON_RELEASED], 0, i, event->button);
return TRUE;
}
}
if (GTK_WIDGET_CLASS(parent_class)->button_release_event)
return GTK_WIDGET_CLASS(parent_class)->button_release_event(widget,
event);
return FALSE;
}
/**
* sexy_icon_entry_new
*
* Creates a new SexyIconEntry widget.
*
* Returns a new #SexyIconEntry.
*/
GtkWidget *
sexy_icon_entry_new(void)
{
return GTK_WIDGET(g_object_new(SEXY_TYPE_ICON_ENTRY, NULL));
}
/**
* sexy_icon_entry_set_icon
* @entry: A #SexyIconEntry.
* @position: Icon position.
* @icon: A #GtkImage to set as the icon.
*
* Sets the icon shown in the entry
*/
void
sexy_icon_entry_set_icon(SexyIconEntry *entry, SexyIconEntryPosition icon_pos,
GtkImage *icon)
{
SexyIconInfo *icon_info;
g_return_if_fail(entry != NULL);
g_return_if_fail(SEXY_IS_ICON_ENTRY(entry));
g_return_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos));
g_return_if_fail(icon == NULL || GTK_IS_IMAGE(icon));
icon_info = &entry->priv->icons[icon_pos];
if (icon == icon_info->icon)
return;
if (icon_pos == SEXY_ICON_ENTRY_SECONDARY &&
entry->priv->icon_released_id != 0)
{
g_signal_handler_disconnect(entry, entry->priv->icon_released_id);
entry->priv->icon_released_id = 0;
}
if (icon == NULL)
{
if (icon_info->icon != NULL)
{
gtk_widget_destroy(GTK_WIDGET(icon_info->icon));
icon_info->icon = NULL;
/*
* Explicitly check, as the pointer may become invalidated
* during destruction.
*/
if (icon_info->window != NULL && GDK_IS_WINDOW(icon_info->window))
gdk_window_hide(icon_info->window);
}
}
else
{
if (icon_info->window != NULL && icon_info->icon == NULL)
gdk_window_show(icon_info->window);
g_signal_connect(G_OBJECT(icon), "notify",
G_CALLBACK(update_icon), entry);
icon_info->icon = icon;
g_object_ref(icon);
}
update_icon(NULL, NULL, entry);
}
/**
* sexy_icon_entry_set_icon_highlight
* @entry: A #SexyIconEntry;
* @position: Icon position.
* @highlight: TRUE if the icon should highlight on mouse-over
*
* Determines whether the icon will highlight on mouse-over.
*/
void
sexy_icon_entry_set_icon_highlight(SexyIconEntry *entry,
SexyIconEntryPosition icon_pos,
gboolean highlight)
{
SexyIconInfo *icon_info;
g_return_if_fail(entry != NULL);
g_return_if_fail(SEXY_IS_ICON_ENTRY(entry));
g_return_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos));
icon_info = &entry->priv->icons[icon_pos];
if (icon_info->highlight == highlight)
return;
icon_info->highlight = highlight;
}
/**
* sexy_icon_entry_get_icon
* @entry: A #SexyIconEntry.
* @position: Icon position.
*
* Retrieves the image used for the icon
*
* Returns: A #GtkImage.
*/
GtkImage *
sexy_icon_entry_get_icon(const SexyIconEntry *entry,
SexyIconEntryPosition icon_pos)
{
g_return_val_if_fail(entry != NULL, NULL);
g_return_val_if_fail(SEXY_IS_ICON_ENTRY(entry), NULL);
g_return_val_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos), NULL);
return entry->priv->icons[icon_pos].icon;
}
/**
* sexy_icon_entry_get_icon_highlight
* @entry: A #SexyIconEntry.
* @position: Icon position.
*
* Retrieves whether entry will highlight the icon on mouseover.
*
* Returns: TRUE if icon highlights.
*/
gboolean
sexy_icon_entry_get_icon_highlight(const SexyIconEntry *entry,
SexyIconEntryPosition icon_pos)
{
g_return_val_if_fail(entry != NULL, FALSE);
g_return_val_if_fail(SEXY_IS_ICON_ENTRY(entry), FALSE);
g_return_val_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos), FALSE);
return entry->priv->icons[icon_pos].highlight;
}
static void
clear_button_clicked_cb(SexyIconEntry *icon_entry,
SexyIconEntryPosition icon_pos,
int button)
{
if (icon_pos != SEXY_ICON_ENTRY_SECONDARY || button != 1)
return;
gtk_entry_set_text(GTK_ENTRY(icon_entry), "");
}
/**
* sexy_icon_entry_add_clear_button
* @icon_entry: A #SexyIconEntry.
*
* A convenience function to add a clear button to the end of the entry.
* This is useful for search boxes.
*/
void
sexy_icon_entry_add_clear_button(SexyIconEntry *icon_entry)
{
GtkWidget *icon;
g_return_if_fail(icon_entry != NULL);
g_return_if_fail(SEXY_IS_ICON_ENTRY(icon_entry));
icon = gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU);
gtk_widget_show(icon);
sexy_icon_entry_set_icon(SEXY_ICON_ENTRY(icon_entry),
SEXY_ICON_ENTRY_SECONDARY,
GTK_IMAGE(icon));
sexy_icon_entry_set_icon_highlight(SEXY_ICON_ENTRY(icon_entry),
SEXY_ICON_ENTRY_SECONDARY, TRUE);
if (icon_entry->priv->icon_released_id != 0)
{
g_signal_handler_disconnect(icon_entry,
icon_entry->priv->icon_released_id);
}
icon_entry->priv->icon_released_id =
g_signal_connect(G_OBJECT(icon_entry), "icon_released",
G_CALLBACK(clear_button_clicked_cb), NULL);
}
GType
sexy_icon_entry_position_get_type (void)
{
static GType etype = 0;
if (etype == 0) {
static const GEnumValue values[] = {
{ SEXY_ICON_ENTRY_PRIMARY, "SEXY_ICON_ENTRY_PRIMARY", "primary" },
{ SEXY_ICON_ENTRY_SECONDARY, "SEXY_ICON_ENTRY_SECONDARY", "secondary" },
{ 0, NULL, NULL }
};
etype = g_enum_register_static ("SexyIconEntryPosition", values);
}
return etype;
}
+104
View File
@@ -0,0 +1,104 @@
/*
* @file libsexy/sexy-icon-entry.h Entry widget
*
* @Copyright (C) 2004-2006 Christian Hammond.
*
* 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.
*/
#ifndef _SEXY_ICON_ENTRY_H_
#define _SEXY_ICON_ENTRY_H_
typedef struct _SexyIconEntry SexyIconEntry;
typedef struct _SexyIconEntryClass SexyIconEntryClass;
typedef struct _SexyIconEntryPriv SexyIconEntryPriv;
#include <gtk/gtkentry.h>
#include <gtk/gtkimage.h>
#define SEXY_TYPE_ICON_ENTRY (sexy_icon_entry_get_type())
#define SEXY_ICON_ENTRY(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj), SEXY_TYPE_ICON_ENTRY, SexyIconEntry))
#define SEXY_ICON_ENTRY_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass), SEXY_TYPE_ICON_ENTRY, SexyIconEntryClass))
#define SEXY_IS_ICON_ENTRY(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj), SEXY_TYPE_ICON_ENTRY))
#define SEXY_IS_ICON_ENTRY_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass), SEXY_TYPE_ICON_ENTRY))
#define SEXY_ICON_ENTRY_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj), SEXY_TYPE_ICON_ENTRY, SexyIconEntryClass))
typedef enum
{
SEXY_ICON_ENTRY_PRIMARY,
SEXY_ICON_ENTRY_SECONDARY
} SexyIconEntryPosition;
GType sexy_icon_entry_position_get_type(void);
#define SEXY_TYPE_ICON_ENTRY_POSITION (sexy_icon_entry_position_get_type())
struct _SexyIconEntry
{
GtkEntry parent_object;
SexyIconEntryPriv *priv;
void (*gtk_reserved1)(void);
void (*gtk_reserved2)(void);
void (*gtk_reserved3)(void);
void (*gtk_reserved4)(void);
};
struct _SexyIconEntryClass
{
GtkEntryClass parent_class;
/* Signals */
void (*icon_pressed)(SexyIconEntry *entry, SexyIconEntryPosition icon_pos,
int button);
void (*icon_released)(SexyIconEntry *entry, SexyIconEntryPosition icon_pos,
int button);
void (*gtk_reserved1)(void);
void (*gtk_reserved2)(void);
void (*gtk_reserved3)(void);
void (*gtk_reserved4)(void);
};
G_BEGIN_DECLS
GType sexy_icon_entry_get_type(void);
GtkWidget *sexy_icon_entry_new(void);
void sexy_icon_entry_set_icon(SexyIconEntry *entry,
SexyIconEntryPosition position,
GtkImage *icon);
void sexy_icon_entry_set_icon_highlight(SexyIconEntry *entry,
SexyIconEntryPosition position,
gboolean highlight);
GtkImage *sexy_icon_entry_get_icon(const SexyIconEntry *entry,
SexyIconEntryPosition position);
gboolean sexy_icon_entry_get_icon_highlight(const SexyIconEntry *entry,
SexyIconEntryPosition position);
void sexy_icon_entry_add_clear_button(SexyIconEntry *icon_entry);
G_END_DECLS
#endif /* _SEXY_ICON_ENTRY_H_ */
+694
View File
@@ -0,0 +1,694 @@
/*
* Copyright (C) 2006-2007 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.
*/
#include <math.h>
#include <gtk/gtkentry.h>
#include "sugar-address-entry.h"
enum {
PROP_0,
PROP_PROGRESS,
PROP_ADDRESS,
PROP_TITLE
};
typedef enum {
CURSOR_STANDARD,
CURSOR_DND
} CursorType;
static void _gtk_entry_effective_inner_border (GtkEntry *entry,
GtkBorder *border);
static void get_text_area_size (GtkEntry *entry,
gint *x,
gint *y,
gint *width,
gint *height);
G_DEFINE_TYPE(SugarAddressEntry, sugar_address_entry, GTK_TYPE_ENTRY)
static GQuark quark_inner_border = 0;
static const GtkBorder default_inner_border = { 2, 2, 2, 2 };
static void
draw_insertion_cursor (GtkEntry *entry,
GdkRectangle *cursor_location,
gboolean is_primary,
PangoDirection direction,
gboolean draw_arrow)
{
GtkWidget *widget = GTK_WIDGET (entry);
GtkTextDirection text_dir;
if (direction == PANGO_DIRECTION_LTR)
text_dir = GTK_TEXT_DIR_LTR;
else
text_dir = GTK_TEXT_DIR_RTL;
gtk_draw_insertion_cursor (widget, entry->text_area, NULL,
cursor_location,
is_primary, text_dir, draw_arrow);
}
static void
gtk_entry_get_pixel_ranges (GtkEntry *entry,
gint **ranges,
gint *n_ranges)
{
gint start_char, end_char;
if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start_char, &end_char))
{
//PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE);
PangoLayout *layout = gtk_entry_get_layout (entry);
PangoLayoutLine *line = pango_layout_get_lines (layout)->data;
const char *text = pango_layout_get_text (layout);
gint start_index = g_utf8_offset_to_pointer (text, start_char) - text;
gint end_index = g_utf8_offset_to_pointer (text, end_char) - text;
gint real_n_ranges, i;
pango_layout_line_get_x_ranges (line, start_index, end_index, ranges, &real_n_ranges);
if (ranges)
{
gint *r = *ranges;
for (i = 0; i < real_n_ranges; ++i)
{
r[2 * i + 1] = (r[2 * i + 1] - r[2 * i]) / PANGO_SCALE;
r[2 * i] = r[2 * i] / PANGO_SCALE;
}
}
if (n_ranges)
*n_ranges = real_n_ranges;
}
else
{
if (n_ranges)
*n_ranges = 0;
if (ranges)
*ranges = NULL;
}
}
static void
gtk_entry_get_cursor_locations (GtkEntry *entry,
CursorType type,
gint *strong_x,
gint *weak_x)
{
if (!entry->visible && !entry->invisible_char)
{
if (strong_x)
*strong_x = 0;
if (weak_x)
*weak_x = 0;
}
else
{
//PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE);
PangoLayout *layout = gtk_entry_get_layout (entry);
const gchar *text = pango_layout_get_text (layout);
PangoRectangle strong_pos, weak_pos;
gint index;
if (type == CURSOR_STANDARD)
{
index = g_utf8_offset_to_pointer (text, entry->current_pos + entry->preedit_cursor) - text;
}
else /* type == CURSOR_DND */
{
index = g_utf8_offset_to_pointer (text, entry->dnd_position) - text;
if (entry->dnd_position > entry->current_pos)
{
if (entry->visible)
index += entry->preedit_length;
else
{
gint preedit_len_chars = g_utf8_strlen (text, -1) - entry->text_length;
index += preedit_len_chars * g_unichar_to_utf8 (entry->invisible_char, NULL);
}
}
}
pango_layout_get_cursor_pos (layout, index, &strong_pos, &weak_pos);
if (strong_x)
*strong_x = strong_pos.x / PANGO_SCALE;
if (weak_x)
*weak_x = weak_pos.x / PANGO_SCALE;
}
}
static void
gtk_entry_draw_cursor (GtkEntry *entry,
CursorType type)
{
GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (entry)));
PangoDirection keymap_direction = gdk_keymap_get_direction (keymap);
if (GTK_WIDGET_DRAWABLE (entry))
{
GtkWidget *widget = GTK_WIDGET (entry);
GdkRectangle cursor_location;
gboolean split_cursor;
GtkBorder inner_border;
gint xoffset;
gint strong_x, weak_x;
gint text_area_height;
PangoDirection dir1 = PANGO_DIRECTION_NEUTRAL;
PangoDirection dir2 = PANGO_DIRECTION_NEUTRAL;
gint x1 = 0;
gint x2 = 0;
_gtk_entry_effective_inner_border (entry, &inner_border);
xoffset = inner_border.left - entry->scroll_offset;
gdk_drawable_get_size (entry->text_area, NULL, &text_area_height);
gtk_entry_get_cursor_locations (entry, type, &strong_x, &weak_x);
g_object_get (gtk_widget_get_settings (widget),
"gtk-split-cursor", &split_cursor,
NULL);
dir1 = entry->resolved_dir;
if (split_cursor)
{
x1 = strong_x;
if (weak_x != strong_x)
{
dir2 = (entry->resolved_dir == PANGO_DIRECTION_LTR) ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
x2 = weak_x;
}
}
else
{
if (keymap_direction == entry->resolved_dir)
x1 = strong_x;
else
x1 = weak_x;
}
cursor_location.x = xoffset + x1;
cursor_location.y = inner_border.top;
cursor_location.width = 0;
cursor_location.height = text_area_height - inner_border.top - inner_border.bottom;
draw_insertion_cursor (entry,
&cursor_location, TRUE, dir1,
dir2 != PANGO_DIRECTION_NEUTRAL);
if (dir2 != PANGO_DIRECTION_NEUTRAL)
{
cursor_location.x = xoffset + x2;
draw_insertion_cursor (entry,
&cursor_location, FALSE, dir2,
TRUE);
}
}
}
static void
get_layout_position (GtkEntry *entry,
gint *x,
gint *y)
{
PangoLayout *layout;
PangoRectangle logical_rect;
gint area_width, area_height;
GtkBorder inner_border;
gint y_pos;
PangoLayoutLine *line;
// layout = gtk_entry_ensure_layout (entry, TRUE);
layout = gtk_entry_get_layout(entry);
get_text_area_size (entry, NULL, NULL, &area_width, &area_height);
_gtk_entry_effective_inner_border (entry, &inner_border);
area_height = PANGO_SCALE * (area_height - inner_border.top - inner_border.bottom);
line = pango_layout_get_lines (layout)->data;
pango_layout_line_get_extents (line, NULL, &logical_rect);
/* Align primarily for locale's ascent/descent */
y_pos = ((area_height - entry->ascent - entry->descent) / 2 +
entry->ascent + logical_rect.y);
/* Now see if we need to adjust to fit in actual drawn string */
if (logical_rect.height > area_height)
y_pos = (area_height - logical_rect.height) / 2;
else if (y_pos < 0)
y_pos = 0;
else if (y_pos + logical_rect.height > area_height)
y_pos = area_height - logical_rect.height;
y_pos = inner_border.top + y_pos / PANGO_SCALE;
if (x)
*x = inner_border.left - entry->scroll_offset;
if (y)
*y = y_pos;
}
static void
_gtk_entry_effective_inner_border (GtkEntry *entry,
GtkBorder *border)
{
GtkBorder *tmp_border;
tmp_border = g_object_get_qdata (G_OBJECT (entry), quark_inner_border);
if (tmp_border)
{
*border = *tmp_border;
return;
}
gtk_widget_style_get (GTK_WIDGET (entry), "inner-border", &tmp_border, NULL);
if (tmp_border)
{
*border = *tmp_border;
g_free (tmp_border);
return;
}
*border = default_inner_border;
}
static void
gtk_entry_draw_text (GtkEntry *entry)
{
GtkWidget *widget;
if (!entry->visible && entry->invisible_char == 0)
return;
if (GTK_WIDGET_DRAWABLE (entry))
{
//PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE);
PangoLayout *layout = gtk_entry_get_layout (entry);
cairo_t *cr;
gint x, y;
gint start_pos, end_pos;
widget = GTK_WIDGET (entry);
get_layout_position (entry, &x, &y);
cr = gdk_cairo_create (entry->text_area);
cairo_move_to (cr, x, y);
gdk_cairo_set_source_color (cr, &widget->style->text [widget->state]);
pango_cairo_show_layout (cr, layout);
if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start_pos, &end_pos))
{
gint *ranges;
gint n_ranges, i;
PangoRectangle logical_rect;
GdkColor *selection_color, *text_color;
GtkBorder inner_border;
pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
gtk_entry_get_pixel_ranges (entry, &ranges, &n_ranges);
if (GTK_WIDGET_HAS_FOCUS (entry))
{
selection_color = &widget->style->base [GTK_STATE_SELECTED];
text_color = &widget->style->text [GTK_STATE_SELECTED];
}
else
{
selection_color = &widget->style->base [GTK_STATE_ACTIVE];
text_color = &widget->style->text [GTK_STATE_ACTIVE];
}
_gtk_entry_effective_inner_border (entry, &inner_border);
for (i = 0; i < n_ranges; ++i)
cairo_rectangle (cr,
inner_border.left - entry->scroll_offset + ranges[2 * i],
y,
ranges[2 * i + 1],
logical_rect.height);
cairo_clip (cr);
gdk_cairo_set_source_color (cr, selection_color);
cairo_paint (cr);
cairo_move_to (cr, x, y);
gdk_cairo_set_source_color (cr, text_color);
pango_cairo_show_layout (cr, layout);
g_free (ranges);
}
cairo_destroy (cr);
}
}
static void
sugar_address_entry_get_borders (GtkEntry *entry,
gint *xborder,
gint *yborder)
{
GtkWidget *widget = GTK_WIDGET (entry);
gint focus_width;
gboolean interior_focus;
gtk_widget_style_get (widget,
"interior-focus", &interior_focus,
"focus-line-width", &focus_width,
NULL);
if (entry->has_frame)
{
*xborder = widget->style->xthickness;
*yborder = widget->style->ythickness;
}
else
{
*xborder = 0;
*yborder = 0;
}
if (!interior_focus)
{
*xborder += focus_width;
*yborder += focus_width;
}
}
static void
get_text_area_size (GtkEntry *entry,
gint *x,
gint *y,
gint *width,
gint *height)
{
gint xborder, yborder;
GtkRequisition requisition;
GtkWidget *widget = GTK_WIDGET (entry);
gtk_widget_get_child_requisition (widget, &requisition);
sugar_address_entry_get_borders (entry, &xborder, &yborder);
if (x)
*x = xborder;
if (y)
*y = yborder;
if (width)
*width = GTK_WIDGET (entry)->allocation.width - xborder * 2;
if (height)
*height = requisition.height - yborder * 2;
}
static gint
sugar_address_entry_expose(GtkWidget *widget,
GdkEventExpose *event)
{
GtkEntry *entry = GTK_ENTRY (widget);
SugarAddressEntry *address_entry = SUGAR_ADDRESS_ENTRY(widget);
cairo_t *cr;
if (entry->text_area == event->window) {
gint area_width, area_height;
get_text_area_size (entry, NULL, NULL, &area_width, &area_height);
/* gtk_paint_flat_box (widget->style, entry->text_area,
GTK_WIDGET_STATE(widget), GTK_SHADOW_NONE,
NULL, widget, "entry_bg",
0, 0, area_width, area_height);
*/
if (address_entry->progress != 0.0 && address_entry->progress != 1.0 &&
!GTK_WIDGET_HAS_FOCUS(entry)) {
int bar_width = area_width * address_entry->progress;
float radius = area_height / 2;
cr = gdk_cairo_create(entry->text_area);
cairo_set_source_rgb(cr, 0xA6 / 255.0, 0xA6 / 255.0, 0xA6 / 255.0);
cairo_move_to (cr, radius, 0);
cairo_arc (cr, bar_width - radius, radius, radius, M_PI * 1.5, M_PI * 2);
cairo_arc (cr, bar_width - radius, area_height - radius, radius, 0, M_PI * 0.5);
cairo_arc (cr, radius, area_height - radius, radius, M_PI * 0.5, M_PI);
cairo_arc (cr, radius, radius, radius, M_PI, M_PI * 1.5);
cairo_fill(cr);
cairo_destroy (cr);
}
if ((entry->visible || entry->invisible_char != 0) &&
GTK_WIDGET_HAS_FOCUS (widget) &&
entry->selection_bound == entry->current_pos && entry->cursor_visible)
gtk_entry_draw_cursor (GTK_ENTRY (widget), CURSOR_STANDARD);
if (entry->dnd_position != -1)
gtk_entry_draw_cursor (GTK_ENTRY (widget), CURSOR_DND);
gtk_entry_draw_text (GTK_ENTRY (widget));
} else {
GtkWidgetClass *parent_class;
parent_class = GTK_WIDGET_CLASS(sugar_address_entry_parent_class);
parent_class->expose_event(widget, event);
}
return FALSE;
}
static void
entry_changed_cb(SugarAddressEntry *entry)
{
if (entry->address) {
g_free (entry->address);
}
entry->address = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
}
static void
update_entry_text(SugarAddressEntry *address_entry,
gboolean has_focus)
{
g_signal_handlers_block_by_func(address_entry, entry_changed_cb, NULL);
if (has_focus || address_entry->title == NULL) {
gtk_entry_set_text(GTK_ENTRY(address_entry),
address_entry->address);
} else {
gtk_entry_set_text(GTK_ENTRY(address_entry),
address_entry->title);
}
g_signal_handlers_unblock_by_func(address_entry, entry_changed_cb, NULL);
}
static void
sugar_address_entry_set_address(SugarAddressEntry *address_entry,
const char *address)
{
g_free(address_entry->address);
address_entry->address = g_strdup(address);
update_entry_text(address_entry,
gtk_widget_is_focus(GTK_WIDGET(address_entry)));
}
static void
sugar_address_entry_set_title(SugarAddressEntry *address_entry,
const char *title)
{
g_free(address_entry->title);
address_entry->title = g_strdup(title);
update_entry_text(address_entry,
gtk_widget_is_focus(GTK_WIDGET(address_entry)));
}
static void
sugar_address_entry_set_property(GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SugarAddressEntry *address_entry = SUGAR_ADDRESS_ENTRY(object);
GtkEntry *entry = GTK_ENTRY(object);
switch (prop_id) {
case PROP_PROGRESS:
address_entry->progress = g_value_get_double(value);
if (GTK_WIDGET_REALIZED(entry))
gdk_window_invalidate_rect(entry->text_area, NULL, FALSE);
break;
case PROP_ADDRESS:
sugar_address_entry_set_address(address_entry,
g_value_get_string(value));
break;
case PROP_TITLE:
sugar_address_entry_set_title(address_entry,
g_value_get_string(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
sugar_address_entry_get_property(GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SugarAddressEntry *entry = SUGAR_ADDRESS_ENTRY(object);
switch (prop_id) {
case PROP_PROGRESS:
g_value_set_double(value, entry->progress);
break;
case PROP_TITLE:
g_value_set_string(value, entry->title);
break;
case PROP_ADDRESS:
g_value_set_string(value, entry->address);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
sugar_address_entry_class_init(SugarAddressEntryClass *klass)
{
GtkWidgetClass *widget_class = (GtkWidgetClass*)klass;
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
widget_class->expose_event = sugar_address_entry_expose;
gobject_class->set_property = sugar_address_entry_set_property;
gobject_class->get_property = sugar_address_entry_get_property;
quark_inner_border = g_quark_from_static_string ("gtk-entry-inner-border");
g_object_class_install_property (gobject_class, PROP_PROGRESS,
g_param_spec_double("progress",
"Progress",
"Progress",
0.0, 1.0, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_TITLE,
g_param_spec_string("title",
"Title",
"Title",
"",
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_ADDRESS,
g_param_spec_string("address",
"Address",
"Address",
"",
G_PARAM_READWRITE));
}
static gboolean
button_press_event_cb (GtkWidget *widget, GdkEventButton *event)
{
if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) {
gtk_editable_select_region(GTK_EDITABLE(widget), 0, -1);
gtk_widget_grab_focus(widget);
return TRUE;
}
return FALSE;
}
static gboolean
focus_in_event_cb(GtkWidget *widget, GdkEventFocus *event)
{
update_entry_text(SUGAR_ADDRESS_ENTRY(widget), TRUE);
return FALSE;
}
static gboolean
focus_out_event_cb(GtkWidget *widget, GdkEventFocus *event)
{
update_entry_text(SUGAR_ADDRESS_ENTRY(widget), FALSE);
return FALSE;
}
static void
popup_unmap_cb(GtkWidget *popup, SugarAddressEntry *entry)
{
g_signal_handlers_unblock_by_func(entry, focus_out_event_cb, NULL);
}
static void
populate_popup_cb(SugarAddressEntry *entry, GtkWidget *menu)
{
g_signal_handlers_block_by_func(entry, focus_out_event_cb, NULL);
g_signal_connect(menu, "unmap",
G_CALLBACK(popup_unmap_cb), entry);
}
static void
sugar_address_entry_init(SugarAddressEntry *entry)
{
entry->progress = 0.0;
entry->address = NULL;
entry->title = g_strdup("");
g_signal_connect(entry, "focus-in-event",
G_CALLBACK(focus_in_event_cb), NULL);
g_signal_connect(entry, "focus-out-event",
G_CALLBACK(focus_out_event_cb), NULL);
g_signal_connect(entry, "changed",
G_CALLBACK(entry_changed_cb), NULL);
g_signal_connect(entry, "button-press-event",
G_CALLBACK(button_press_event_cb), NULL);
g_signal_connect(entry, "populate-popup",
G_CALLBACK(populate_popup_cb), NULL);
}
+54
View File
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2006-2007 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.
*/
#ifndef __SUGAR_ADDRESS_ENTRY_H__
#define __SUGAR_ADDRESS_ENTRY_H__
#include <gtk/gtkentry.h>
G_BEGIN_DECLS
typedef struct _SugarAddressEntry SugarAddressEntry;
typedef struct _SugarAddressEntryClass SugarAddressEntryClass;
typedef struct _SugarAddressEntryPrivate SugarAddressEntryPrivate;
#define SUGAR_TYPE_ADDRESS_ENTRY (sugar_address_entry_get_type())
#define SUGAR_ADDRESS_ENTRY(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_ADDRESS_ENTRY, SugarAddressEntry))
#define SUGAR_ADDRESS_ENTRY_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_ADDRESS_ENTRY, SugarAddressEntryClass))
#define SUGAR_IS_ADDRESS_ENTRY(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_ADDRESS_ENTRY))
#define SUGAR_IS_ADDRESS_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_ADDRESS_ENTRY))
#define SUGAR_ADDRESS_ENTRY_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_ADDRESS_ENTRY, SugarAddressEntryClass))
struct _SugarAddressEntry {
GtkEntry base_instance;
float progress;
char *title;
char *address;
};
struct _SugarAddressEntryClass {
GtkEntryClass base_class;
};
GType sugar_address_entry_get_type (void);
G_END_DECLS
#endif /* __SUGAR_ADDRESS_ENTRY_H__ */
+219
View File
@@ -0,0 +1,219 @@
/*
* Copyright (C) 2006-2007, 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.
*/
#include <X11/X.h>
#include <gdk/gdkscreen.h>
#include <gdk/gdkx.h>
#include <gdk/gdk.h>
#include "sugar-key-grabber.h"
#include "eggaccelerators.h"
#include "sugar-marshal.h"
/* we exclude shift, GDK_CONTROL_MASK and GDK_MOD1_MASK since we know what
these modifiers mean
these are the mods whose combinations are bound by the keygrabbing code */
#define IGNORED_MODS (0x2000 /*Xkb modifier*/ | GDK_LOCK_MASK | \
GDK_MOD2_MASK | GDK_MOD3_MASK | GDK_MOD4_MASK | GDK_MOD5_MASK)
/* these are the ones we actually use for global keys, we always only check
* for these set */
#define USED_MODS (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)
enum {
KEY_PRESSED,
KEY_RELEASED,
N_SIGNALS
};
typedef struct {
char *key;
guint keysym;
guint state;
guint keycode;
} Key;
G_DEFINE_TYPE(SugarKeyGrabber, sugar_key_grabber, G_TYPE_OBJECT)
static guint signals[N_SIGNALS];
static void
free_key_info(Key *key_info)
{
g_free(key_info->key);
g_free(key_info);
}
static void
sugar_key_grabber_dispose (GObject *object)
{
SugarKeyGrabber *grabber = SUGAR_KEY_GRABBER(object);
if (grabber->keys) {
g_list_foreach(grabber->keys, (GFunc)free_key_info, NULL);
g_list_free(grabber->keys);
grabber->keys = NULL;
}
}
static void
sugar_key_grabber_class_init(SugarKeyGrabberClass *grabber_class)
{
GObjectClass *g_object_class = G_OBJECT_CLASS (grabber_class);
g_object_class->dispose = sugar_key_grabber_dispose;
signals[KEY_PRESSED] = g_signal_new ("key-pressed",
G_TYPE_FROM_CLASS (grabber_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (SugarKeyGrabberClass, key_pressed),
NULL, NULL,
sugar_marshal_BOOLEAN__UINT_UINT,
G_TYPE_BOOLEAN, 2,
G_TYPE_UINT,
G_TYPE_UINT);
signals[KEY_RELEASED] = g_signal_new ("key-released",
G_TYPE_FROM_CLASS (grabber_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (SugarKeyGrabberClass, key_released),
NULL, NULL,
sugar_marshal_BOOLEAN__UINT_UINT,
G_TYPE_BOOLEAN, 2,
G_TYPE_UINT,
G_TYPE_UINT);
}
char *
sugar_key_grabber_get_key(SugarKeyGrabber *grabber, guint keycode, guint state)
{
GList *l;
for (l = grabber->keys; l != NULL; l = l->next) {
Key *keyinfo = (Key *)l->data;
if ((keyinfo->keycode == keycode) &&
((state & USED_MODS) == keyinfo->state)) {
return g_strdup(keyinfo->key);
}
}
return NULL;
}
static GdkFilterReturn
filter_events(GdkXEvent *xevent, GdkEvent *event, gpointer data)
{
SugarKeyGrabber *grabber = (SugarKeyGrabber *)data;
XEvent *xev = (XEvent *)xevent;
if (xev->type == KeyRelease) {
int return_value;
g_signal_emit (grabber, signals[KEY_RELEASED], 0, xev->xkey.keycode,
xev->xkey.state, &return_value);
if(return_value)
return GDK_FILTER_REMOVE;
}
if (xev->type == KeyPress) {
int return_value;
g_signal_emit (grabber, signals[KEY_PRESSED], 0, xev->xkey.keycode,
xev->xkey.state, &return_value);
if(return_value)
return GDK_FILTER_REMOVE;
}
return GDK_FILTER_CONTINUE;
}
static void
sugar_key_grabber_init(SugarKeyGrabber *grabber)
{
GdkScreen *screen;
screen = gdk_screen_get_default();
grabber->root = gdk_screen_get_root_window(screen);
grabber->keys = NULL;
gdk_window_add_filter(grabber->root, filter_events, grabber);
}
/* grab_key and grab_key_real are from
* gnome-control-center/gnome-settings-daemon/gnome-settings-multimedia-keys.c
*/
static gboolean
grab_key_real (Key *key, GdkWindow *root, gboolean grab, int result)
{
gdk_error_trap_push ();
if (grab)
XGrabKey (GDK_DISPLAY(), key->keycode, (result | key->state),
GDK_WINDOW_XID (root), True, GrabModeAsync, GrabModeAsync);
else
XUngrabKey(GDK_DISPLAY(), key->keycode, (result | key->state),
GDK_WINDOW_XID (root));
gdk_flush ();
gdk_error_trap_pop ();
return TRUE;
}
#define N_BITS 32
static void
grab_key (SugarKeyGrabber *grabber, Key *key, gboolean grab)
{
int indexes[N_BITS];/*indexes of bits we need to flip*/
int i, bit, bits_set_cnt;
int uppervalue;
guint mask_to_traverse = IGNORED_MODS & ~ key->state;
bit = 0;
for (i = 0; i < N_BITS; i++) {
if (mask_to_traverse & (1<<i))
indexes[bit++]=i;
}
bits_set_cnt = bit;
uppervalue = 1<<bits_set_cnt;
for (i = 0; i < uppervalue; i++) {
int j, result = 0;
for (j = 0; j < bits_set_cnt; j++) {
if (i & (1<<j))
result |= (1<<indexes[j]);
}
if (grab_key_real (key, grabber->root, grab, result) == FALSE)
return;
}
}
void
sugar_key_grabber_grab(SugarKeyGrabber *grabber, const char *key)
{
Key *keyinfo;
keyinfo = g_new0 (Key, 1);
keyinfo->key = g_strdup(key);
egg_accelerator_parse_virtual (key, &keyinfo->keysym,
&keyinfo->keycode, &keyinfo->state);
grab_key(grabber, keyinfo, TRUE);
grabber->keys = g_list_append(grabber->keys, keyinfo);
}
+66
View File
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2006-2007, 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.
*/
#ifndef __SUGAR_KEY_GRABBER_H__
#define __SUGAR_KEY_GRABBER_H__
#include <glib-object.h>
#include <gdk/gdkwindow.h>
G_BEGIN_DECLS
typedef struct _SugarKeyGrabber SugarKeyGrabber;
typedef struct _SugarKeyGrabberClass SugarKeyGrabberClass;
typedef struct _SugarKeyGrabberPrivate SugarKeyGrabberPrivate;
#define SUGAR_TYPE_KEY_GRABBER (sugar_key_grabber_get_type())
#define SUGAR_KEY_GRABBER(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabber))
#define SUGAR_KEY_GRABBER_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabberClass))
#define SUGAR_IS_KEY_GRABBER(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_KEY_GRABBER))
#define SUGAR_IS_KEYGRABBER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_KEY_GRABBER))
#define SUGAR_KEY_GRABBER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabberClass))
struct _SugarKeyGrabber {
GObject base_instance;
GdkWindow *root;
GList *keys;
};
struct _SugarKeyGrabberClass {
GObjectClass base_class;
gboolean (* key_pressed) (SugarKeyGrabber *grabber,
guint keycode,
guint state);
gboolean (* key_released) (SugarKeyGrabber *grabber,
guint keycode,
guint state);
};
GType sugar_key_grabber_get_type (void);
void sugar_key_grabber_grab (SugarKeyGrabber *grabber,
const char *key);
char *sugar_key_grabber_get_key (SugarKeyGrabber *grabber,
guint keycode,
guint state);
G_END_DECLS
#endif /* __SUGAR_KEY_GRABBER_H__ */
+1
View File
@@ -0,0 +1 @@
BOOLEAN:UINT,UINT
+63
View File
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2006-2007, 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.
*/
#include <gtk/gtkwindow.h>
#include "sugar-menu.h"
static void sugar_menu_class_init (SugarMenuClass *menu_class);
static void sugar_menu_init (SugarMenu *menu);
G_DEFINE_TYPE(SugarMenu, sugar_menu, GTK_TYPE_MENU)
void
sugar_menu_set_active(SugarMenu *menu, gboolean active)
{
GTK_MENU_SHELL(menu)->active = active;
}
void
sugar_menu_embed(SugarMenu *menu, GtkContainer *parent)
{
menu->orig_toplevel = GTK_MENU(menu)->toplevel;
GTK_MENU(menu)->toplevel = gtk_widget_get_toplevel(GTK_WIDGET(parent));
gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(parent));
}
void
sugar_menu_unembed(SugarMenu *menu)
{
if (menu->orig_toplevel) {
GTK_MENU(menu)->toplevel = menu->orig_toplevel;
gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(menu->orig_toplevel));
}
}
static void
sugar_menu_class_init(SugarMenuClass *menu_class)
{
}
static void
sugar_menu_init(SugarMenu *menu)
{
menu->orig_toplevel = NULL;
}
+56
View File
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2006-2007, 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.
*/
#ifndef __SUGAR_MENU_H__
#define __SUGAR_MENU_H__
#include <gtk/gtkmenu.h>
G_BEGIN_DECLS
typedef struct _SugarMenu SugarMenu;
typedef struct _SugarMenuClass SugarMenuClass;
#define SUGAR_TYPE_MENU (sugar_menu_get_type())
#define SUGAR_MENU(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_MENU, SugarMenu))
#define SUGAR_MENU_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_MENU, SugarMenuClass))
#define SUGAR_IS_MENU(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_MENU))
#define SUGAR_IS_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_MENU))
#define SUGAR_MENU_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_MENU, SugarMenuClass))
struct _SugarMenu {
GtkMenu base_instance;
GtkWidget *orig_toplevel;
int min_width;
};
struct _SugarMenuClass {
GtkMenuClass base_class;
};
GType sugar_menu_get_type (void);
void sugar_menu_set_active (SugarMenu *menu,
gboolean active);
void sugar_menu_embed (SugarMenu *menu,
GtkContainer *parent);
G_END_DECLS
#endif /* __SUGAR_MENU_H__ */
+88
View File
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2006-2007 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.
*/
#include <gdk/gdkproperty.h>
#include <X11/Xatom.h>
#include <glib/gstrfuncs.h>
#include <string.h>
#include "sugar-x11-util.h"
void
sugar_x11_util_set_string_property(GdkWindow *window,
const char *property,
const char *value)
{
Atom prop_atom;
Atom string_atom;
GdkDisplay *display;
char *prop_text;
display = gdk_drawable_get_display(window);
prop_atom = gdk_x11_get_xatom_by_name_for_display(display, property);
string_atom = gdk_x11_get_xatom_by_name_for_display(display, "STRING");
prop_text = gdk_utf8_to_string_target(value);
XChangeProperty(GDK_DISPLAY_XDISPLAY(display),
GDK_WINDOW_XID(window),
prop_atom,
string_atom, 8,
PropModeReplace, prop_text,
strlen(prop_text));
g_free(prop_text);
}
char *
sugar_x11_util_get_string_property(GdkWindow *window,
const char *property)
{
Atom type;
Atom prop_atom;
Atom string_atom;
int format;
int result;
unsigned long bytes_after, n_items;
unsigned char *str = NULL;
char *value = NULL;
GdkDisplay *display;
display = gdk_drawable_get_display(window);
prop_atom = gdk_x11_get_xatom_by_name_for_display(display, property);
string_atom = gdk_x11_get_xatom_by_name_for_display(display, "STRING");
result = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(display),
GDK_WINDOW_XID(window),
prop_atom,
0, 1024L,
False, string_atom,
&type, &format, &n_items,
&bytes_after, (unsigned char **)&str);
if (result == Success && str != NULL && type == string_atom &&
format == 8 && n_items > 0) {
value = g_strdup(str);
}
if (str) {
XFree(str);
}
return value;
}
+36
View File
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2006-2007 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.
*/
#ifndef __SUGAR_X11_UTIL_H__
#define __SUGAR_X11_UTIL_H__
#include <gdk/gdkx.h>
G_BEGIN_DECLS
void sugar_x11_util_set_string_property(GdkWindow *window,
const char *property,
const char *value);
char *sugar_x11_util_get_string_property(GdkWindow *window,
const char *property);
G_END_DECLS
#endif /* __SUGAR_ADDRESS_ENTRY_H__ */
+175
View File
@@ -0,0 +1,175 @@
"""Various utility functions"""
# Copyright (C) 2006-2007 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 time
import sha
import random
import binascii
import string
import os
import logging
from ConfigParser import ConfigParser
from ConfigParser import NoOptionError
def printable_hash(in_hash):
"""Convert binary hash data into printable characters."""
printable = ""
for char in in_hash:
printable = printable + binascii.b2a_hex(char)
return printable
def _sha_data(data):
"""sha1 hash some bytes."""
sha_hash = sha.new()
sha_hash.update(data)
return sha_hash.digest()
def unique_id(data = ''):
"""Generate a likely-unique ID for whatever purpose
data -- suffix appended to working data before hashing
Returns a 40-character string with hexidecimal digits
representing an SHA hash of the time, a random digit
within a constrained range and the data passed.
Note: these are *not* crypotographically secure or
globally unique identifiers. While they are likely
to be unique-enough, no attempt is made to make
perfectly unique values.
"""
data_string = "%s%s%s" % (time.time(), random.randint(10000, 100000), data)
return printable_hash(_sha_data(data_string))
ACTIVITY_ID_LEN = 40
def is_hex(s):
return s.strip(string.hexdigits) == ''
def validate_activity_id(actid):
"""Validate an activity ID."""
if not isinstance(actid, (str,unicode)):
return False
if len(actid) != ACTIVITY_ID_LEN:
return False
if not is_hex(actid):
return False
return True
def set_proc_title(title):
"""Sets the process title so ps and top show more
descriptive names. This does not modify argv[0]
and only the first 15 characters will be shown.
title -- the title you wish to change the process
title to
Returns True on success. We don't raise exceptions
because if something goes wrong here it is not a big
deal as this is intended as a nice thing to have for
debugging
"""
try:
import ctypes
libc = ctypes.CDLL('libc.so.6')
libc.prctl(15, str(title), 0, 0, 0)
return True
except:
return False
class Node(object):
__slots__ = ['prev', 'next', 'me']
def __init__(self, prev, me):
self.prev = prev
self.me = me
self.next = None
class LRU:
"""
Implementation of a length-limited O(1) LRU queue.
Built for and used by PyPE:
http://pype.sourceforge.net
Copyright 2003 Josiah Carlson.
"""
def __init__(self, count, pairs=[]):
self.count = max(count, 1)
self.d = {}
self.first = None
self.last = None
for key, value in pairs:
self[key] = value
def __contains__(self, obj):
return obj in self.d
def __getitem__(self, obj):
a = self.d[obj].me
self[a[0]] = a[1]
return a[1]
def __setitem__(self, obj, val):
if obj in self.d:
del self[obj]
nobj = Node(self.last, (obj, val))
if self.first is None:
self.first = nobj
if self.last:
self.last.next = nobj
self.last = nobj
self.d[obj] = nobj
if len(self.d) > self.count:
if self.first == self.last:
self.first = None
self.last = None
return
a = self.first
a.next.prev = None
self.first = a.next
a.next = None
del self.d[a.me[0]]
del a
def __delitem__(self, obj):
nobj = self.d[obj]
if nobj.prev:
nobj.prev.next = nobj.next
else:
self.first = nobj.next
if nobj.next:
nobj.next.prev = nobj.prev
else:
self.last = nobj.prev
del self.d[obj]
def __iter__(self):
cur = self.first
while cur != None:
cur2 = cur.next
yield cur.me[1]
cur = cur2
def iteritems(self):
cur = self.first
while cur != None:
cur2 = cur.next
yield cur.me
cur = cur2
def iterkeys(self):
return iter(self.d)
def itervalues(self):
for i,j in self.iteritems():
yield j
def keys(self):
return self.d.keys()
+38
View File
@@ -0,0 +1,38 @@
# Copyright (C) 2007, 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 gtk
import _sugarext
def get_activity_id(wnck_window):
window = gtk.gdk.window_foreign_new(wnck_window.get_xid())
return _sugarext.x11_get_string_property(
window, '_SUGAR_ACTIVITY_ID')
def get_bundle_id(wnck_window):
window = gtk.gdk.window_foreign_new(wnck_window.get_xid())
return _sugarext.x11_get_string_property(
window, '_SUGAR_BUNDLE_ID')
def set_activity_id(window, activity_id):
_sugarext.x11_set_string_property(
window, '_SUGAR_ACTIVITY_ID', activity_id)
def set_bundle_id(window, bundle_id):
_sugarext.x11_set_string_property(
window, '_SUGAR_BUNDLE_ID', bundle_id)