Use a src directory consistently with base and shell.

This commit is contained in:
Marco Pesenti Gritti
2008-04-29 14:58:34 +02:00
parent 6efdf259b6
commit 72c2a1d770
78 changed files with 9 additions and 8 deletions
+4
View File
@@ -0,0 +1,4 @@
sugar-marshal.c
sugar-marshal.h
_sugarext.c
_sugarext.c
+1
View File
@@ -0,0 +1 @@
LGPL
+64
View File
@@ -0,0 +1,64 @@
SUBDIRS = activity bundle clipboard graphics 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 = \
$(EXT_CFLAGS) \
$(PYTHON_INCLUDES)
_sugarext_la_LDFLAGS = -module -avoid-version
_sugarext_la_LIBADD = $(EXT_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-preview.h \
sugar-preview.c
BUILT_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
sugar-marshal.c: sugar-marshal.list
$(GLIB_GENMARSHAL) --prefix=sugar_marshal \
$(srcdir)/sugar-marshal.list --header --body > sugar-marshal.c
sugar-marshal.h: sugar-marshal.list
$(GLIB_GENMARSHAL) --prefix=sugar_marshal \
$(srcdir)/sugar-marshal.list --header > sugar-marshal.h
CLEANFILES = $(BUILT_SOURCES)
EXTRA_DIST = sugar-marshal.list _sugarext.defs _sugarext.override
+196
View File
@@ -0,0 +1,196 @@
;; -*- 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 Preview
(in-module "Sugar")
(parent "GObject")
(c-name "SugarPreview")
(gtype-id "SUGAR_TYPE_PREVIEW")
)
(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")
)
)
;; 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" (null-ok))
)
)
(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")
)
;; From sugar-preview.h
(define-function sugar_preview_get_type
(c-name "sugar_preview_get_type")
(return-type "GType")
)
(define-method take_screenshot
(of-object "SugarPreview")
(c-name "sugar_preview_take_screenshot")
(return-type "none")
(parameters
'("GdkDrawable" "drawable")
)
)
(define-method set_size
(of-object "SugarPreview")
(c-name "sugar_preview_set_size")
(return-type "none")
(parameters
'("int" "width")
'("int" "height")
)
)
(define-method clear
(of-object "SugarPreview")
(c-name "sugar_preview_clear")
(return-type "none")
)
(define-method get_pixbuf
(of-object "SugarPreview")
(c-name "sugar_preview_get_pixbuf")
(return-type "GdkPixbuf*")
)
+30
View File
@@ -0,0 +1,30 @@
/* -*- 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-preview.h"
#include "sexy-icon-entry.h"
#include <pygtk/pygtk.h>
#include <glib.h>
%%
modulename sugar._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.gdk.Drawable as PyGdkDrawable_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
+945
View File
@@ -0,0 +1,945 @@
"""Base class for activities written in Python
This is currently the only definitive reference for what an
activity must do to participate in the Sugar desktop.
A Basic Activity
All activities must implement a class derived from 'Activity' in this class.
The convention is to call it ActivitynameActivity, but this is not required as
the activity.info file associated with your activity will tell the sugar-shell
which class to start.
For example the most minimal Activity:
from sugar.activity import activity
class ReadActivity(activity.Activity):
pass
To get a real, working activity, you will at least have to implement:
__init__(), read_file() and write_file()
Aditionally, you will probably need a at least a Toolbar so you can have some
interesting buttons for the user, like for example 'exit activity'
See the methods of the Activity class below for more information on what you
will need for a real activity.
"""
# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2007-2008 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 gettext
import logging
import os
import time
import tempfile
from hashlib import sha1
import traceback
import gtk, gobject
import dbus
import dbus.service
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.graphics.alert import Alert
from sugar.graphics.icon import Icon
from sugar.datastore import datastore
from sugar import wm
from sugar import profile
from sugar import _sugarext
_ = lambda msg: gettext.dgettext('sugar', msg)
SCOPE_PRIVATE = "private"
SCOPE_INVITE_ONLY = "invite" # shouldn't be shown in UI, it's implicit
SCOPE_NEIGHBORHOOD = "public"
J_DBUS_SERVICE = 'org.laptop.Journal'
J_DBUS_PATH = '/org/laptop/Journal'
J_DBUS_INTERFACE = 'org.laptop.Journal'
class ActivityToolbar(gtk.Toolbar):
"""The Activity toolbar with the Journal entry title, sharing,
Keep and Stop buttons
All activities should have this toolbar. It is easiest to add it to your
Activity by using the ActivityToolbox.
"""
def __init__(self, activity):
gtk.Toolbar.__init__(self)
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')
self.share.combo.append_item(SCOPE_NEIGHBORHOOD, _('My Neighborhood'),
'zoom-neighborhood')
self.insert(self.share, -1)
self.share.show()
self._update_share()
self.keep = ToolButton('document-save', tooltip=_('Keep'))
self.keep.props.accelerator = '<Ctrl>S'
self.keep.connect('clicked', self.__keep_clicked_cb)
self.insert(self.keep, -1)
self.keep.show()
self.stop = ToolButton('activity-stop', tooltip=_('Stop'))
self.stop.props.accelerator = '<Ctrl>Q'
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.take_screenshot()
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):
"""Provides the standard edit toolbar for Activities.
Members:
undo -- the undo button
redo -- the redo button
copy -- the copy button
paste -- the paste button
separator -- A separator between undo/redo and copy/paste
This class only provides the 'edit' buttons in a standard layout,
your activity will need to either hide buttons which make no sense for your
Activity, or you need to connect the button events to your own callbacks:
## Example from Read.activity:
# Create the edit toolbar:
self._edit_toolbar = EditToolbar(self._view)
# Hide undo and redo, they're not needed
self._edit_toolbar.undo.props.visible = False
self._edit_toolbar.redo.props.visible = False
# Hide the separator too:
self._edit_toolbar.separator.props.visible = False
# As long as nothing is selected, copy needs to be insensitive:
self._edit_toolbar.copy.set_sensitive(False)
# When the user clicks the button, call _edit_toolbar_copy_cb()
self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb)
# Add the edit toolbar:
toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
# And make it visible:
self._edit_toolbar.show()
"""
def __init__(self):
gtk.Toolbar.__init__(self)
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):
"""Creates the Toolbox for the Activity
By default, the toolbox contains only the ActivityToolbar. After creating
the toolbox, you can add your activity specific toolbars, for example the
EditToolbar.
To add the ActivityToolbox to your Activity in MyActivity.__init__() do:
# Create the Toolbar with the ActivityToolbar:
toolbox = activity.ActivityToolbox(self)
... your code, inserting all other toolbars you need, like EditToolbar
# Add the toolbox to the activity frame:
self.set_toolbox(toolbox)
# And make it visible:
toolbox.show()
"""
def __init__(self, activity):
Toolbox.__init__(self)
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):
"""This is the base Activity class that all other Activities derive from.
This is where your activity starts.
To get a working Activity:
0. Derive your Activity from this class:
class MyActivity(activity.Activity):
...
1. implement an __init__() method for your Activity class.
Use your init method to create your own ActivityToolbar which will
contain some standard buttons:
toolbox = activity.ActivityToolbox(self)
Add extra Toolbars to your toolbox.
You should setup Activity sharing here too.
Finaly, your Activity may need some resources which you can claim
here too.
The __init__() method is also used to make the distinction between
being resumed from the Journal, or starting with a blank document.
2. Implement read_file() and write_file()
Most activities revolve around creating and storing Journal entries.
For example, Write: You create a document, it is saved to the Journal
and then later you resume working on the document.
read_file() and write_file() will be called by sugar to tell your
Activity that it should load or save the document the user is working
on.
3. Implement our Activity Toolbars.
The Toolbars are added to your Activity in step 1 (the toolbox), but
you need to implement them somewhere. Now is a good time.
There are a number of standard Toolbars. The most basic one, the one
your almost absolutely MUST have is the ActivityToolbar. Without
this, you're not really making a proper Sugar Activity (which may be
okay, but you should really stop and think about why not!) You do
this with the ActivityToolbox(self) call in step 1.
Usually, you will also need the standard EditToolbar. This is the one
which has the standard copy and paste buttons. You need to derive
your own EditToolbar class from sugar.EditToolbar:
class EditToolbar(activity.EditToolbar):
...
See EditToolbar for the methods you should implement in your class.
Finaly, your Activity will very likely need some activity specific
buttons and options you can create your own toolbars by deriving a
class from gtk.Toolbar:
class MySpecialToolbar(gtk.Toolbar):
...
4. Use your creativity. Make your Activity something special and share
it with your friends!
Read through the methods of the Activity class below, to learn more about
how to make an Activity work.
Hint: A good and simple Activity to learn from is the Read activity. To
create your own activity, you may want to copy it and use it as a template.
"""
__gtype_name__ = 'SugarActivity'
__gsignals__ = {
'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.
Usage:
If your Activity implements __init__(), it should call
the base class __init()__ before doing Activity specific things.
"""
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 = _sugarext.Preview()
self._updating_jobject = False
self._closing = False
self._deleting = False
self._max_participants = 0
self._invites_queue = []
accel_group = gtk.AccelGroup()
self.set_data('sugar-accel-group', accel_group)
self.add_accel_group(accel_group)
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()
title = _('%s Activity') % get_bundle_name()
self._jobject.metadata['title'] = title
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 = ''
# Cannot call datastore.write async for creates:
# https://dev.laptop.org/ticket/3071
datastore.write(self._jobject)
else:
self._jobject = None
# handle activity share/join
mesh_instance = self._pservice.get_activity(self._activity_id,
warn_if_none=False)
logging.debug("*** Act %s, mesh instance %r, scope %s",
self._activity_id, mesh_instance, share_scope)
if mesh_instance is not None:
# There's already an instance on the mesh, join it
logging.debug("*** Act %s joining existing mesh instance %r",
self._activity_id, mesh_instance)
self._shared_activity = mesh_instance
self._shared_activity.connect('notify::private',
self.__privacy_changed_cb)
self._join_id = self._shared_activity.connect(
"joined", self.__joined_cb)
if not self._shared_activity.props.joined:
self._shared_activity.join()
else:
self.__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
else:
Window.do_set_property(self, pspec, value)
def do_get_property(self, pspec):
if pspec.name == 'active':
return self._active
elif pspec.name == 'max-participants':
return self._max_participants
else:
return Window.do_get_property(self, pspec)
def get_id(self):
"""Returns the activity id of the current instance of your activity.
The activity id is sort-of-like the unix process id (PID). However,
unlike PIDs it is only different for each new instance (with
create_jobject = True set) and stays the same everytime a user
resumes an activity. This is also the identity of your Activity to other
XOs for use when sharing.
"""
return self._activity_id
def get_bundle_id(self):
"""Returns the bundle_id from the activity.info file"""
return os.environ['SUGAR_BUNDLE_ID']
def set_canvas(self, canvas):
"""Sets the 'work area' of your activity with the canvas of your choice.
One commonly used canvas is gtk.ScrolledWindow
"""
Window.set_canvas(self, canvas)
canvas.connect('map', self.__canvas_map_cb)
def __canvas_map_cb(self, canvas):
if self._jobject and self._jobject.file_path:
self.read_file(self._jobject.file_path)
def __jobject_create_cb(self):
pass
def __jobject_error_cb(self, err):
logging.debug("Error creating activity datastore object: %s" % err)
def get_activity_root(self):
""" FIXME: Deprecated. This part of the API has been moved
out of this class to the module itself
Returns a path for saving Activity specific preferences, etc.
Returns a path to the location in the filesystem where the activity can
store activity related data that doesn't pertain to the current
execution of the activity and thus cannot go into the DataStore.
Currently, this will return something like
~/.sugar/default/MyActivityName/
Activities should ONLY save settings, user preferences and other data
which isn't specific to a journal item here. If (meta-)data is in anyway
specific to a journal entry, it MUST be stored in the DataStore.
"""
if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \
os.environ['SUGAR_ACTIVITY_ROOT']:
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.
You should immediately open the file from the file_path, because the
file_name will be deleted immediately after returning from read_file().
Once the file has been opened, you do not have to read it immediately:
After you have opened it, the file will only be really gone when you
close it.
Although not required, this is also a good time to read all meta-data:
the file itself cannot be changed externally, but the title, description
and other metadata['tags'] may change. So if it is important for you to
notice changes, this is the time to record the originals.
"""
raise NotImplementedError
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.
If the user did make changes, you should create the file_path and save
all document data to it.
Additionally, you should also write any metadata needed to resume your
activity. For example, the Read activity saves the current page and zoom
level, so it can display the page.
Note: Currently, the file_path *WILL* be different from the one you
received in file_read(). Even if you kept the file_path from file_read()
open until now, you must still write the entire file to this file_path.
"""
raise NotImplementedError
def __save_cb(self):
logging.debug('Activity.__save_cb')
self._updating_jobject = False
if self._closing:
self._cleanup_jobject()
self.destroy()
def __save_error_cb(self, err):
logging.debug('Activity.__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):
pixbuf = self._preview.get_pixbuf()
if pixbuf is None:
return None
pixbuf = 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')
os.close(fd)
pixbuf.save(file_path, 'png')
f = open(file_path)
try:
preview_data = f.read()
finally:
f.close()
os.remove(file_path)
self._preview.clear()
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 take_screenshot(self):
if self.canvas and self.canvas.window:
self._preview.take_screenshot(self.canvas.window)
def save(self):
"""Request that the activity is saved to the Journal.
This method is called by the close() method below. In general,
activities should not override this method. This method is part of the
public API of an Acivity, and should behave in standard ways. Use your
own implementation of write_file() to save your Activity specific data.
"""
logging.debug('Activity.save: %r' % self._jobject.object_id)
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())
preview = self._get_preview()
if self._preview:
self.metadata['preview'] = dbus.ByteArray(preview)
try:
file_path = os.path.join(self.get_activity_root(), 'instance',
'%i' % time.time())
self.write_file(file_path)
self._owns_file = True
self._jobject.file_path = file_path
except NotImplementedError:
logging.debug('Activity.write_file is not implemented.')
# 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.__save_cb,
error_handler=self.__save_error_cb)
def copy(self):
"""Request that the activity 'Keep in Journal' the current state
of the activity.
Activities should not override this method. Instead, like save() do any
copy work that needs to be done in write_file()
"""
logging.debug('Activity.copy: %r' % self._jobject.object_id)
self.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 __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 __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, PS activity is %r.',
self._activity_id, activity)
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):
"""Invite a buddy to join this Activity.
Side Effects:
Calls self.share(True) to privately share the activity if it wasn't
shared before.
"""
self._invites_queue.append(buddy_key)
if (self._shared_activity is None
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.
"""
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.__share_cb)
self._pservice.share_activity(self, private=private)
def _display_keep_failed_dialog(self):
alert = Alert()
alert.props.title = _('Keep error')
alert.props.msg = _('Keep error: all changes will be lost')
cancel_icon = Icon(icon_name='dialog-cancel')
alert.add_button(gtk.RESPONSE_CANCEL, _('Don\'t stop'), cancel_icon)
stop_icon = Icon(icon_name='dialog-ok')
alert.add_button(gtk.RESPONSE_OK, _('Stop anyway'), stop_icon)
self.add_alert(alert)
alert.connect('response', self._keep_failed_dialog_response_cb)
def _keep_failed_dialog_response_cb(self, alert, response_id):
self.remove_alert(alert)
if response_id == gtk.RESPONSE_OK:
self.close(skip_save=True)
def can_close(self):
"""Activities should override this function if they want to perform
extra checks before actually closing."""
return True
def close(self, force=False, skip_save=False):
"""Request that the activity be stopped and saved to the Journal
Activities should not override this method, but should implement
write_file() to do any state saving instead. If the application wants
to control wether it can close, it should override can_close().
"""
if not force:
if not self.can_close():
return
try:
if not skip_save:
self.save()
except Exception:
logging.info(traceback.format_exc())
self._display_keep_failed_dialog()
return
if self._shared_activity:
self._shared_activity.leave()
if self._updating_jobject:
self._closing = True
else:
self.destroy()
# Make the exported object inaccessible
dbus.service.Object.remove_from_connection(self._bus)
def __realize_cb(self, window):
wm.set_bundle_id(window.window, self.get_bundle_id())
wm.set_activity_id(window.window, str(self._activity_id))
def __delete_event_cb(self, widget, event):
self.close()
return True
def get_metadata(self):
"""Returns the jobject metadata or None if there is no jobject.
Activities can set metadata in write_file() using:
self.metadata['MyKey'] = "Something"
and retrieve metadata in read_file() using:
self.metadata.get('MyKey', 'aDefaultValue')
Note: Make sure your activity works properly if one or more of the
metadata items is missing. Never assume they will all be present.
"""
if self._jobject:
return self._jobject.metadata
else:
return None
metadata = property(get_metadata, None)
def get_bundle_name():
"""Return the bundle name for the current process' bundle"""
return os.environ['SUGAR_BUNDLE_NAME']
def get_bundle_path():
"""Return the bundle path for the current process' bundle"""
return os.environ['SUGAR_BUNDLE_PATH']
def get_activity_root():
"""Returns a path for saving Activity specific preferences, etc."""
if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \
os.environ['SUGAR_ACTIVITY_ROOT']:
return os.environ['SUGAR_ACTIVITY_ROOT']
else:
raise RuntimeError("No SUGAR_ACTIVITY_ROOT set.")
def show_object_in_journal(object_id):
bus = dbus.SessionBus()
obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH)
journal = dbus.Interface(obj, J_DBUS_INTERFACE)
journal.ShowObject(object_id)
+316
View File
@@ -0,0 +1,316 @@
"""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 signal
import dbus
import gobject
from sugar.presence import presenceservice
from sugar.activity.activityhandle import ActivityHandle
from sugar.activity import registry
from sugar import util
from sugar import env
from errno import EEXIST
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"
_children_pid = []
def _sigchild_handler(signum, frame):
for child_pid in _children_pid:
pid, status_ = os.waitpid(child_pid, os.WNOHANG)
if pid > 0:
_children_pid.remove(pid)
signal.signal(signal.SIGCHLD, _sigchild_handler)
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')
activity_root = env.get_profile_path(activity.bundle_id)
if not os.path.exists(activity_root):
os.mkdir(activity_root)
data_dir = os.path.join(activity_root, 'instance')
if not os.path.exists(data_dir):
os.mkdir(data_dir)
data_dir = os.path.join(activity_root, 'data')
if not os.path.exists(data_dir):
os.mkdir(data_dir)
tmp_dir = os.path.join(activity_root, 'tmp')
if not os.path.exists(tmp_dir):
os.mkdir(tmp_dir)
environ['SUGAR_BUNDLE_PATH'] = activity.path
environ['SUGAR_BUNDLE_ID'] = activity.bundle_id
environ['SUGAR_ACTIVITY_ROOT'] = activity_root
environ['PATH'] = bin_path + ':' + environ['PATH']
#environ['RAINBOW_STRACE_LOG'] = '1'
if activity.bundle_id in [ 'org.laptop.WebActivity',
'org.laptop.GmailActivity',
'org.laptop.WikiBrowseActivity'
]:
environ['RAINBOW_CONSTANT_UID'] = 'yes'
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.split(' ')
command.extend(['-b', activity.bundle_id])
command.extend(['-a', activity_id])
if object_id is not None:
command.extend(['-o', object_id])
if uri is not None:
command.extend(['-u', uri])
print command
return command
def open_log_file(activity):
i = 1
while True:
path = env.get_logs_path('%s-%s.log' % (activity.bundle_id, i))
try:
fd = os.open(path, os.O_EXCL | os.O_CREAT \
| os.O_SYNC | os.O_WRONLY, 0644)
f = os.fdopen(fd, 'w', 0)
return (path, f)
except OSError, e:
if e.errno == EEXIST:
i += 1
else:
raise e
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
self._use_rainbow = os.path.exists('/etc/olpc-security')
if service_name in [ 'org.laptop.JournalActivity',
'org.laptop.Terminal',
'org.laptop.LogViewer',
'org.laptop.Analyze'
]:
self._use_rainbow = False
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)
activity_registry = registry.get_registry()
activity = activity_registry.get_activity(self._service_name)
if activity:
environ = get_environment(activity)
(log_path, log_file) = open_log_file(activity)
command = get_command(activity, self._handle.activity_id,
self._handle.object_id,
self._handle.uri)
if not self._use_rainbow:
p = subprocess.Popen(command, env=environ, cwd=activity.path,
stdout=log_file, stderr=log_file)
_children_pid.append(p.pid)
else:
log_file.close()
system_bus = dbus.SystemBus()
factory = system_bus.get_object(_RAINBOW_SERVICE_NAME,
_RAINBOW_ACTIVITY_FACTORY_PATH)
factory.CreateActivity(
log_path,
environ,
command,
environ['SUGAR_BUNDLE_PATH'],
environ['SUGAR_BUNDLE_ID'],
timeout=30 * 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):
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)
+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.
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
+70
View File
@@ -0,0 +1,70 @@
# 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)
@dbus.service.method(_ACTIVITY_INTERFACE)
def TakeScreenshot(self):
self._activity.take_screenshot()
+377
View File
@@ -0,0 +1,377 @@
# 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 name in 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_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, path):
cmd_dist(bundle_name, manifest)
cmd_uninstall(path)
_extract_bundle(_get_package_name(bundle_name), path)
def cmd_uninstall(path):
path = os.path.join(path, _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
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)
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()
+182
View File
@@ -0,0 +1,182 @@
# 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['version'],
info_dict['path'], info_dict['show_launcher'],
info_dict['command'], info_dict['favorite'])
class ActivityInfo(object):
def __init__(self, name, icon, bundle_id, version,
path, show_launcher, command, favorite):
self.name = name
self.icon = icon
self.bundle_id = bundle_id
self.version = version
self.path = path
self.command = command
self.show_launcher = show_launcher
self.favorite = favorite
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])),
'activity-changed': (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)
self._registry.connect_to_signal('ActivityChanged',
self._activity_changed_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 = []
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):
result = self._registry.AddBundle(bundle_path)
# Need to invalidate here because get_activity could be called after
# add_bundle and before we receive activity-added, causing a race.
self._invalidate_cache()
return result
def _activity_added_cb(self, info_dict):
logging.debug('ActivityRegistry._activity_added_cb: invalidating cache')
self._invalidate_cache()
self.emit('activity-added', _activity_info_from_dict(info_dict))
def _invalidate_cache(self):
self._service_name_to_activity_info.clear()
self._mime_type_to_activities.clear()
def remove_bundle(self, bundle_path):
self._invalidate_cache()
return self._registry.RemoveBundle(bundle_path)
def _activity_removed_cb(self, info_dict):
logging.debug('ActivityRegistry._activity_removed_cb: flushing caches')
self._invalidate_cache()
self.emit('activity-removed', _activity_info_from_dict(info_dict))
def _activity_changed_cb(self, info_dict):
logging.debug('ActivityRegistry._activity_changed_cb: flushing caches')
self._invalidate_cache()
self.emit('activity-changed', _activity_info_from_dict(info_dict))
def set_activity_favorite(self, bundle_id, version, favorite):
self._registry.SetActivityFavorite(bundle_id, version, favorite)
_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.
+328
View File
@@ -0,0 +1,328 @@
# 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, \
AlreadyInstalledException, RegistrationException, \
NotInstalledException
from sugar import activity
from sugar import env
import logging
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').strip(';')
self._mime_types = [ mime.strip() for mime in mime_list.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 need_upgrade(self):
act = activity.get_registry().get_activity(self._bundle_id)
if act is None or act.version != self._activity_version:
return True
else:
return False
def install(self):
activities_path = env.get_user_activities_path()
act = activity.get_registry().get_activity(self._bundle_id)
if act is not None and act.path.startswith(activities_path):
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, force=False):
if self._unpacked:
install_path = self._path
else:
if not self.is_installed():
raise NotInstalledException
act = activity.get_registry().get_activity(self._bundle_id)
if not force and act.version != self._activity_version:
logging.warning('Not uninstalling, different bundle present')
return
elif not act.path.startswith(env.get_user_activities_path()):
logging.warning('Not uninstalling system activity')
return
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 f in os.listdir(installed_icons_dir):
path = os.path.join(installed_icons_dir, f)
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
def upgrade(self):
act = activity.get_registry().get_activity(self._bundle_id)
if act is None:
logging.warning('Activity not installed')
elif act.path.startswith(env.get_user_activities_path()):
try:
self.uninstall(force=True)
except Exception, e:
logging.warning('Uninstall failed (%s), still trying ' \
'to install newer bundle', e)
else:
logging.warning('Unable to uninstall system activity, ' \
'installing upgraded version in user activities')
self.install()
+157
View File
@@ -0,0 +1,157 @@
# 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 logging
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.
"""
_zipped_extension = None
_unzipped_extension = None
def __init__(self, path):
self._path = path
self._zip_root_dir = None
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):
f = None
if self._unpacked:
path = os.path.join(self._path, filename)
if os.path.isfile(path):
f = open(path)
else:
zip_file = zipfile.ZipFile(self._path)
path = os.path.join(self._zip_root_dir, filename)
try:
data = zip_file.read(path)
f = StringIO.StringIO(data)
except KeyError:
logging.debug('%s not found.' % filename)
zip_file.close()
return f
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, 0775)
# 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', '-o', self._path,
'-x', 'mimetype', '-d', install_dir):
raise ZipExtractException
def _zip(self, bundle_path):
if not self._unpacked:
raise NotInstalledException
raise NotImplementedError
def _uninstall(self, install_path):
if not os.path.isdir(install_path):
raise InvalidPathException
if self._unzipped_extension is not None:
(name_, ext) = os.path.splitext(install_path)
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)
+199
View File
@@ -0,0 +1,199 @@
# 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, \
MalformedBundleException
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)
self._locale = None
self._l10n = None
self._category = None
self._name = None
self._subcategory = None
self._category_class = None
self._category_icon = None
self._library_version = None
self._bundle_class = None
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_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):
if os.environ.has_key('XDG_DATA_DIRS'):
for path in os.environ['XDG_DATA_DIRS'].split(':'):
indexer = os.path.join(path, 'library-common', 'make_index.py')
if os.path.exists(indexer):
os.spawnlp(os.P_WAIT, 'python', 'python', indexer)
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.
+230
View File
@@ -0,0 +1,230 @@
"""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.
+324
View File
@@ -0,0 +1,324 @@
# 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 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_for_mime(self, mime_type):
registry = activity.get_registry()
result = registry.get_activities_for_type(mime_type)
if not result:
for parent_mime in mime.get_mime_parents(mime_type):
result.extend(registry.get_activities_for_type(parent_mime))
return result
def get_activities(self):
activities = []
bundle_id = self.metadata.get('activity', '')
if bundle_id:
activity_info = activity.get_registry().get_activity(bundle_id)
if activity_info:
activities.append(activity_info)
mime_type = self.metadata.get('mime_type', '')
if mime_type:
activities_info = self._get_activities_for_mime(mime_type)
for activity_info in activities_info:
if activity_info.bundle_id != bundle_id:
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('Bundle cannot be resumed as an activity.')
logging.debug('Creating activity bundle')
bundle = ActivityBundle(self.file_path)
if not bundle.is_installed():
logging.debug('Installing activity bundle')
bundle.install()
elif bundle.need_upgrade():
logging.debug('Upgrading activity bundle')
bundle.upgrade()
logging.debug('activityfactory.creating bundle with id %r',
bundle.get_bundle_id())
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, %s.' %
self.metadata.get('mime_type', None))
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!.')
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. ' \
'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 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
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__ */
+56
View File
@@ -0,0 +1,56 @@
"""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 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, 0770)
except OSError:
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')
+25
View File
@@ -0,0 +1,25 @@
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 \
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.
+257
View File
@@ -0,0 +1,257 @@
# 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 gettext
import gtk
import gobject
import hippo
import math
from sugar.graphics import style
from sugar.graphics.icon import Icon
_ = lambda msg: gettext.dgettext('sugar', msg)
class Alert(gtk.EventBox):
"""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. The position of the alert is below the
toolbox or top in fullscreen mode.
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):
self._title = None
self._msg = None
self._icon = None
self._buttons = {}
self._hbox = gtk.HBox()
self._hbox.set_border_width(style.DEFAULT_SPACING)
self._hbox.set_spacing(style.DEFAULT_SPACING)
self._msg_box = gtk.VBox()
self._title_label = gtk.Label()
self._title_label.set_alignment(0, 0.5)
self._msg_box.pack_start(self._title_label, False)
self._msg_label = gtk.Label()
self._msg_label.set_alignment(0, 0.5)
self._msg_box.pack_start(self._msg_label, False)
self._hbox.pack_start(self._msg_box, False)
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)
gtk.EventBox.__init__(self, **kwargs)
self.set_visible_window(True)
self.add(self._hbox)
self._title_label.show()
self._msg_label.show()
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, False)
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 response id"""
self._buttons_box.remove(self._buttons[response_id])
def _response(self, response_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', 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')
self.add_button(gtk.RESPONSE_CANCEL, _('Cancel'), icon)
icon.show()
icon = Icon(icon_name='dialog-ok')
self.add_button(gtk.RESPONSE_OK, _('Ok'), icon)
icon.show()
class _TimeoutIcon(hippo.CanvasText, hippo.CanvasItem):
"""An icon with a round border"""
__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()
xval = width * 0.5
yval = height * 0.5
radius = min(width * 0.5, height * 0.5)
hippo.cairo_set_source_rgba32(cr, self.props.background_color)
cr.arc(xval, yval, 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')
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
class NotifyAlert(Alert):
"""Timeout alert with only an "OK" button - just for notifications"""
def __init__(self, timeout=5, **kwargs):
Alert.__init__(self, **kwargs)
self._timeout = timeout
self._timeout_text = _TimeoutIcon(
text=self._timeout,
color=style.COLOR_BUTTON_GREY.get_int(),
background_color=style.COLOR_WHITE.get_int())
canvas = hippo.Canvas()
canvas.set_root(self._timeout_text)
canvas.show()
self.add_button(gtk.RESPONSE_OK, _('Ok'), canvas)
gobject.timeout_add(1000, self.__timeout)
def __timeout(self):
self._timeout -= 1
self._timeout_text.props.text = self._timeout
if self._timeout == 0:
self._response(gtk.RESPONSE_OK)
return False
return True
+95
View File
@@ -0,0 +1,95 @@
# 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, duration, fps=20, easing=EASE_OUT_EXPO):
gobject.GObject.__init__(self)
self._animations = []
self._duration = duration
self._interval = 1.0 / fps
self._easing = easing
self._timeout_sid = 0
self._start_time = None
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._duration, time.time() - self._start_time)
current_time = max(current_time, 0.0)
for animation in self._animations:
animation.do_frame(current_time, self._duration, self._easing)
if current_time == self._duration:
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, t, duration, easing):
start = self.start
change = self.end - self.start
if t == duration:
# last frame
frame = self.end
else:
if easing == EASE_OUT_EXPO:
frame = change * (-pow(2, -10 * t / duration) + 1) + start
elif easing == EASE_IN_EXPO:
frame = change * pow(2, 10 * (t / duration - 1)) + start
self.next_frame(frame)
def next_frame(self, frame):
pass
+112
View File
@@ -0,0 +1,112 @@
# 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
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, max(width, height), 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 = max(w, h)
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):
return model[row][3]
+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)
+560
View File
@@ -0,0 +1,560 @@
# 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 re
import math
import logging
import gobject
import gtk
import hippo
import cairo
from sugar.graphics.xocolor import XoColor
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)
import rsvg # XXX this is very slow! why?
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.icon_size = 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
self.scale = 1.0
def _get_cache_key(self, sensitive):
return (self.icon_name, self.file_name, self.fill_color,
self.stroke_color, self.badge_name, self.width, self.height,
sensitive)
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
del info
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, sensitive, widget):
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)
dimensions = handle.get_dimension_data()
icon_width = int(dimensions[0])
icon_height = int(dimensions[1])
pixbuf = handle.get_pixbuf()
else:
pixbuf = gtk.gdk.pixbuf_new_from_file(badge_file_name)
icon_width = pixbuf.get_width()
icon_height = pixbuf.get_height()
context.scale(float(size) / icon_width,
float(size) / icon_height)
if not sensitive:
pixbuf = self._get_insensitive_pixbuf(pixbuf, widget)
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_insensitive_pixbuf (self, pixbuf, widget):
if not (widget and widget.style):
return pixbuf
icon_source = gtk.IconSource()
# Special size meaning "don't touch"
icon_source.set_size(-1)
icon_source.set_pixbuf(pixbuf)
icon_source.set_state(gtk.STATE_INSENSITIVE)
icon_source.set_direction_wildcarded(False)
icon_source.set_size_wildcarded(False)
# Please note that the pixbuf returned by this function is leaked
# with current stable versions of pygtk. The relevant bug is
# http://bugzilla.gnome.org/show_bug.cgi?id=502871
# -- 2007-12-14 Benjamin Berg
pixbuf = widget.style.render_icon(icon_source, widget.get_direction(),
gtk.STATE_INSENSITIVE, -1, widget,
"sugar-icon")
return pixbuf
def get_surface(self, sensitive=True, widget=None):
cache_key = self._get_cache_key(sensitive)
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:
if sensitive:
handle.render_cairo(context)
else:
pixbuf = handle.get_pixbuf()
pixbuf = self._get_insensitive_pixbuf(pixbuf, widget)
pixbuf_surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf)
context.set_source_surface(pixbuf_surface, 0, 0)
context.paint()
else:
if not sensitive:
pixbuf = self._get_insensitive_pixbuf(pixbuf, widget)
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, sensitive, widget)
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
if self.props.pixel_size == -1:
width, height = gtk.icon_size_lookup(self.props.icon_size)
else:
width = height = self.props.pixel_size
if self._buffer.width != width or 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 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()
sensitive = (self.state != gtk.STATE_INSENSITIVE)
surface = self._buffer.get_surface(sensitive, self)
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
allocation = self.get_allocation()
x = math.floor(allocation.x + xpad +
(allocation.width - requisition[0]) * xalign)
y = math.floor(allocation.y + ypad +
(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
self.connect('destroy', self.__destroy_cb)
def __destroy_cb(self, icon):
if self._palette is not None:
self._palette.destroy()
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':
logging.warning(
'CanvasIcon: the scale parameter is currently unsupported')
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):
from sugar.graphics.palette import CanvasInvoker
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):
from sugar.graphics.palette import Palette
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
+106
View File
@@ -0,0 +1,106 @@
# 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
from sugar.graphics import style
from sugar.graphics.icon import _SVGLoader
ICON_ENTRY_PRIMARY = _sugarext.ICON_ENTRY_PRIMARY
ICON_ENTRY_SECONDARY = _sugarext.ICON_ENTRY_SECONDARY
class IconEntry(_sugarext.IconEntry):
def __init__(self):
_sugarext.IconEntry.__init__(self)
self._clear_icon = None
self._clear_shown = False
self.connect('key_press_event', self._keypress_event_cb)
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)
if icon_info.get_filename().endswith('.svg'):
loader = _SVGLoader()
entities = {'fill_color': style.COLOR_TOOLBAR_GREY.get_svg(),
'stroke_color': style.COLOR_TOOLBAR_GREY.get_svg()}
handle = loader.load(icon_info.get_filename(), entities, None)
pixbuf = handle.get_pixbuf()
else:
pixbuf = gtk.gdk.pixbuf_new_from_file(icon_info.get_filename())
del icon_info
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)
def remove_icon(self, position):
_sugarext.IconEntry.set_icon(self, position, None)
def add_clear_button(self):
if self.props.text != "":
self.show_clear_button()
else:
self.hide_clear_button()
self.connect('icon-pressed', self._icon_pressed_cb)
self.connect('changed', self._changed_cb)
def show_clear_button(self):
if not self._clear_shown:
self.set_icon_from_name(ICON_ENTRY_SECONDARY,
'dialog-cancel')
self._clear_shown = True
def hide_clear_button(self):
if self._clear_shown:
self.remove_icon(ICON_ENTRY_SECONDARY)
self._clear_shown = False
def _keypress_event_cb(self, widget, event):
keyval = gtk.gdk.keyval_name(event.keyval)
if keyval == 'Escape':
self.props.text = ''
return True
return False
def _icon_pressed_cb(self, entru, icon_pos, button):
if icon_pos == ICON_ENTRY_SECONDARY:
self.set_text('')
self.hide_clear_button()
def _changed_cb(self, icon_entry):
if not self.props.text:
self.hide_clear_button()
else:
self.show_clear_button()
+34
View File
@@ -0,0 +1,34 @@
# 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
import pango
class MenuItem(gtk.ImageMenuItem):
def __init__(self, text_label=None, icon_name=None, text_maxlen=0):
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()
if text_maxlen > 0:
child = self.get_child()
child.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
child.set_max_width_chars(text_maxlen)
+116
View File
@@ -0,0 +1,116 @@
"""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)
self._can_close_tabs = None
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)
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)
+118
View File
@@ -0,0 +1,118 @@
# 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 gobject
import gtk
import dbus
from sugar.datastore import datastore
J_DBUS_SERVICE = 'org.laptop.Journal'
J_DBUS_INTERFACE = 'org.laptop.Journal'
J_DBUS_PATH = '/org/laptop/Journal'
class ObjectChooser(object):
def __init__(self, title=None, parent=None, flags=None, buttons=None):
# For backwards compatibility:
# - We ignore title, flags and buttons.
# - 'parent' can be a xid or a gtk.gdk.Window
if title is not None or flags is not None or buttons is not None:
logging.warning('Invocation of ObjectChooser() has deprecated '
'parameters.')
if parent is None:
parent_xid = 0
elif hasattr(parent, 'window') and hasattr(parent.window, 'xid'):
parent_xid = parent.window.xid
else:
parent_xid = parent
self._parent_xid = parent_xid
self._main_loop = None
self._object_id = None
self._bus = None
self._chooser_id = None
self._response_code = gtk.RESPONSE_NONE
def run(self):
self._object_id = None
self._main_loop = gobject.MainLoop()
self._bus = dbus.SessionBus(mainloop=self._main_loop)
self._bus.add_signal_receiver(
self.__name_owner_changed_cb,
signal_name="NameOwnerChanged",
dbus_interface="org.freedesktop.DBus",
arg0=J_DBUS_SERVICE)
obj = self._bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH)
journal = dbus.Interface(obj, J_DBUS_INTERFACE)
journal.connect_to_signal('ObjectChooserResponse',
self.__chooser_response_cb)
journal.connect_to_signal('ObjectChooserCancelled',
self.__chooser_cancelled_cb)
self._chooser_id = journal.ChooseObject(self._parent_xid)
gtk.gdk.threads_leave()
try:
self._main_loop.run()
finally:
gtk.gdk.threads_enter()
self._main_loop = None
return self._response_code
def get_selected_object(self):
if self._object_id is None:
return None
else:
return datastore.get(self._object_id)
def destroy(self):
self._cleanup()
def _cleanup(self):
if self._main_loop is not None:
self._main_loop.quit()
self._main_loop = None
self._bus = None
def __chooser_response_cb(self, chooser_id, object_id):
if chooser_id != self._chooser_id:
return
logging.debug('ObjectChooser.__chooser_response_cb: %r' % object_id)
self._response_code = gtk.RESPONSE_ACCEPT
self._object_id = object_id
self._cleanup()
def __chooser_cancelled_cb(self, chooser_id):
if chooser_id != self._chooser_id:
return
logging.debug('ObjectChooser.__chooser_cancelled_cb: %r' % chooser_id)
self._response_code = gtk.RESPONSE_CANCEL
self._cleanup()
def __name_owner_changed_cb(self, name, old, new):
logging.debug('ObjectChooser.__name_owner_changed_cb')
# Journal service disappeared from the bus
self._response_code = gtk.RESPONSE_CANCEL
self._cleanup()
File diff suppressed because it is too large Load Diff
+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 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)
del self._sig_ids[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)
+104
View File
@@ -0,0 +1,104 @@
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007-2008, 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
import gobject
from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, ToolInvoker
from sugar.graphics import toolbutton
class RadioToolButton(gtk.RadioToolButton):
__gtype_name__ = "SugarRadioToolButton"
def __init__(self, named_icon=None, group=None, xo_color=None, **kwargs):
self._accelerator = None
self._tooltip = None
self._palette = None
self._xo_color = xo_color
gobject.GObject.__init__(self, **kwargs)
if named_icon:
self.set_named_icon(named_icon)
if group:
self.props.group = group
def set_tooltip(self, tooltip):
""" Set a simple palette with just a single label.
"""
if self.palette is None or self._tooltip is None:
self.palette = Palette(tooltip)
elif self.palette is not None:
self.palette.set_primary_text(tooltip)
self._tooltip = tooltip
# Set label, shows up when toolbar overflows
gtk.RadioToolButton.set_label(self, tooltip)
def get_tooltip(self):
return self._tooltip
tooltip = gobject.property(type=str, setter=set_tooltip, getter=get_tooltip)
def set_accelerator(self, accelerator):
self._accelerator = accelerator
toolbutton.setup_accelerator(self)
def get_accelerator(self):
return self._accelerator
accelerator = gobject.property(type=str, setter=set_accelerator,
getter=get_accelerator)
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)
palette = gobject.property(
type=object, setter=set_palette, getter=get_palette)
def do_expose_event(self, event):
child = self.get_child()
allocation = self.get_allocation()
if self._palette and self._palette.is_up():
invoker = self._palette.props.invoker
invoker.draw_rectangle(event, self._palette)
elif child.state == gtk.STATE_PRELIGHT:
child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_NONE, event.area,
child, "toolbutton-prelight",
allocation.x, allocation.y,
allocation.width, allocation.height)
gtk.RadioToolButton.do_expose_event(self, event)
+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 radius 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()
+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(15)
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('#282828')
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)
+64
View File
@@ -0,0 +1,64 @@
# 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):
allocation = self.get_allocation()
child = self.get_child()
if self._palette and self._palette.is_up():
invoker = self._palette.props.invoker
invoker.draw_rectangle(event, self._palette)
elif child.state == gtk.STATE_PRELIGHT:
child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_NONE, event.area,
child, "toolbutton-prelight",
allocation.x, allocation.y,
allocation.width, allocation.height)
gtk.ToggleToolButton.do_expose_event(self, event)
palette = property(get_palette, set_palette)
+97
View File
@@ -0,0 +1,97 @@
# 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 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)
width, height_ = label.size_request()
label.set_size_request(max(width, 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)
def get_current_toolbar(self):
return self._notebook.get_current_page()
current_toolbar = property(get_current_toolbar, set_current_toolbar)
+130
View File
@@ -0,0 +1,130 @@
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2008, 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 gtk
import gobject
from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, ToolInvoker
def _add_accelerator(tool_button):
if not tool_button.props.accelerator or not tool_button.get_toplevel() or \
not tool_button.child:
return
# TODO: should we remove the accelerator from the prev top level?
accel_group = tool_button.get_toplevel().get_data('sugar-accel-group')
if not accel_group:
logging.warning('No gtk.AccelGroup in the top level window.')
return
keyval, mask = gtk.accelerator_parse(tool_button.props.accelerator)
# the accelerator needs to be set at the child, so the gtk.AccelLabel
# in the palette can pick it up.
tool_button.child.add_accelerator('clicked', accel_group, keyval, mask,
gtk.ACCEL_LOCKED | gtk.ACCEL_VISIBLE)
def _hierarchy_changed_cb(tool_button, previous_toplevel):
_add_accelerator(tool_button)
def setup_accelerator(tool_button):
_add_accelerator(tool_button)
tool_button.connect('hierarchy-changed', _hierarchy_changed_cb)
class ToolButton(gtk.ToolButton):
__gtype_name__ = "SugarToolButton"
def __init__(self, icon_name=None, **kwargs):
self._accelerator = None
self._tooltip = None
self._palette = None
gobject.GObject.__init__(self, **kwargs)
if icon_name:
self.set_icon(icon_name)
self.connect('clicked', self.__button_clicked_cb)
def set_tooltip(self, tooltip):
""" Set a simple palette with just a single label.
"""
if self.palette is None or self._tooltip is None:
self.palette = Palette(tooltip)
elif self.palette is not None:
self.palette.set_primary_text(tooltip)
self._tooltip = tooltip
# Set label, shows up when toolbar overflows
gtk.ToolButton.set_label(self, tooltip)
def get_tooltip(self):
return self._tooltip
tooltip = gobject.property(type=str, setter=set_tooltip, getter=get_tooltip)
def set_accelerator(self, accelerator):
self._accelerator = accelerator
setup_accelerator(self)
def get_accelerator(self):
return self._accelerator
accelerator = gobject.property(type=str, setter=set_accelerator,
getter=get_accelerator)
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)
palette = gobject.property(
type=object, setter=set_palette, getter=get_palette)
def do_expose_event(self, event):
child = self.get_child()
allocation = self.get_allocation()
if self._palette and self._palette.is_up():
invoker = self._palette.props.invoker
invoker.draw_rectangle(event, self._palette)
elif child.state == gtk.STATE_PRELIGHT:
child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_NONE, event.area,
child, "toolbutton-prelight",
allocation.x, allocation.y,
allocation.width, allocation.height)
gtk.ToolButton.do_expose_event(self, event)
def __button_clicked_cb(self, widget):
if self._palette:
self._palette.popdown(True)
+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)
+308
View File
@@ -0,0 +1,308 @@
# 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__ = {
'scrollable' : (bool, None, None, False,
gobject.PARAM_READABLE),
'can-scroll-prev' : (bool, None, None, False,
gobject.PARAM_READABLE),
'can-scroll-next' : (bool, None, None, False,
gobject.PARAM_READABLE),
}
def __init__(self, orientation):
self.orientation = orientation
self._scrollable = False
self._can_scroll_next = False
self._can_scroll_prev = 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)
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
adj = self.get_hadjustment()
else:
adj = self.get_vadjustment()
adj.connect('changed', self._adjustment_changed_cb)
adj.connect('value-changed', self._adjustment_changed_cb)
def scroll(self, direction):
if direction == _PREVIOUS_PAGE:
self._scroll_previous()
elif direction == _NEXT_PAGE:
self._scroll_next()
def _scroll_next(self):
allocation = self.get_allocation()
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
adj = self.get_hadjustment()
new_value = adj.value + allocation.width
adj.value = min(new_value, adj.upper - allocation.width)
else:
adj = self.get_vadjustment()
new_value = adj.value + allocation.height
adj.value = min(new_value, adj.upper - allocation.height)
def _scroll_previous(self):
allocation = self.get_allocation()
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
adj = self.get_hadjustment()
new_value = adj.value - allocation.width
adj.value = max(adj.lower, new_value)
else:
adj = self.get_vadjustment()
new_value = adj.value - allocation.height
adj.value = max(adj.lower, new_value)
def do_size_request(self, requisition):
child_requisition = self.get_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 == 'scrollable':
return self._scrollable
elif pspec.name == 'can-scroll-next':
return self._can_scroll_next
elif pspec.name == 'can-scroll-prev':
return self._can_scroll_prev
def _size_allocate_cb(self, viewport, allocation):
bar_requisition = self.traybar.get_child_requisition()
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
scrollable = bar_requisition[0] > allocation.width
else:
scrollable = bar_requisition[1] > allocation.height
if scrollable != self._scrollable:
self._scrollable = scrollable
self.notify('scrollable')
def _adjustment_changed_cb(self, adjustment):
if adjustment.value <= adjustment.lower:
can_scroll_prev = False
else:
can_scroll_prev = True
if adjustment.value + adjustment.page_size >= adjustment.upper:
can_scroll_next = False
else:
can_scroll_next = True
if can_scroll_prev != self._can_scroll_prev:
self._can_scroll_prev = can_scroll_prev
self.notify('can-scroll-prev')
if can_scroll_next != self._can_scroll_next:
self._can_scroll_next = can_scroll_next
self.notify('can-scroll-next')
class _TrayScrollButton(ToolButton):
def __init__(self, icon_name, scroll_direction):
ToolButton.__init__(self)
self._viewport = None
self._scroll_direction = scroll_direction
self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
self.icon = Icon(icon_name = icon_name,
icon_size=gtk.ICON_SIZE_SMALL_TOOLBAR)
# The alignment is a hack to work around gtk.ToolButton code
# that sets the icon_size when the icon_widget is a gtk.Image
alignment = gtk.Alignment(0.5, 0.5)
alignment.add(self.icon)
self.set_icon_widget(alignment)
alignment.show_all()
self.connect('clicked', self._clicked_cb)
def set_viewport(self, viewport):
self._viewport = viewport
self._viewport.connect('notify::scrollable',
self._viewport_scrollable_changed_cb)
if self._scroll_direction == _PREVIOUS_PAGE:
self._viewport.connect('notify::can-scroll-prev',
self._viewport_can_scroll_dir_changed_cb)
self.set_sensitive(self._viewport.props.can_scroll_prev)
else:
self._viewport.connect('notify::can-scroll-next',
self._viewport_can_scroll_dir_changed_cb)
self.set_sensitive(self._viewport.props.can_scroll_next)
def _viewport_scrollable_changed_cb(self, viewport, pspec):
self.props.visible = self._viewport.props.scrollable
def _viewport_can_scroll_dir_changed_cb(self, viewport, pspec):
if self._scroll_direction == _PREVIOUS_PAGE:
sensitive = self._viewport.props.can_scroll_prev
else:
sensitive = self._viewport.props.can_scroll_next
self.set_sensitive(sensitive)
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)
self.set_direction(gtk.TEXT_DIR_LTR)
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)
self._icon = Icon(icon_name=icon_name, xo_color=xo_color,
icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
self.add(self._icon)
self._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)
def get_icon(self):
return self._icon
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))
def get_icon(self):
return self._icon_widget.get_icon()
icon = property(get_icon, None)
+212
View File
@@ -0,0 +1,212 @@
# 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
from sugar.graphics.icon import Icon
class UnfullscreenButton(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
self.set_decorated(False)
self.set_resizable(False)
self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
self.set_border_width(0)
self.props.accept_focus = False
#Setup estimate of width, height
w, h = gtk.icon_size_lookup(gtk.ICON_SIZE_LARGE_TOOLBAR)
self._width = w
self._height = h
self.connect('size-request', self._size_request_cb)
screen = self.get_screen()
screen.connect('size-changed', self._screen_size_changed_cb)
self._button = gtk.Button()
self._button.set_relief(gtk.RELIEF_NONE)
self._icon = Icon(icon_name='view-return',
icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
self._icon.show()
self._button.add(self._icon)
self._button.show()
self.add(self._button)
def connect_button_press(self, cb):
self._button.connect('button-press-event', cb)
def _reposition(self):
x = gtk.gdk.screen_width() - self._width
self.move(x, 0)
def _size_request_cb(self, widget, req):
self._width = req.width
self._height = req.height
self._reposition()
def _screen_size_changed_cb(self, screen):
self._reposition()
class Window(gtk.Window):
def __init__(self, **args):
self._enable_fullscreen_mode = True
gtk.Window.__init__(self, **args)
self.connect('realize', self.__window_realize_cb)
self.connect('window-state-event', self.__window_state_event_cb)
self.connect('key-press-event', self.__key_press_cb)
self.toolbox = None
self._alerts = []
self.canvas = None
self.tray = None
self._vbox = gtk.VBox()
self._hbox = gtk.HBox()
self._vbox.pack_start(self._hbox)
self._hbox.show()
self._event_box = gtk.EventBox()
self._hbox.pack_start(self._event_box)
self._event_box.show()
self.add(self._vbox)
self._vbox.show()
self._is_fullscreen = False
self._unfullscreen_button = UnfullscreenButton()
self._unfullscreen_button.set_transient_for(self)
self._unfullscreen_button.connect_button_press(
self.__unfullscreen_button_pressed)
def set_canvas(self, canvas):
if self.canvas:
self._event_box.remove(self.canvas)
if canvas:
self._event_box.add(canvas)
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 set_tray(self, tray, position):
if self.tray:
box = self.tray.get_parent()
box.remove(self.tray)
if position == gtk.POS_LEFT:
self._hbox.pack_start(tray, False)
elif position == gtk.POS_RIGHT:
self._hbox.pack_end(tray, False)
elif position == gtk.POS_BOTTOM:
self._vbox.pack_end(tray, False)
self.tray = tray
def add_alert(self, alert):
self._alerts.append(alert)
if len(self._alerts) == 1:
self._vbox.pack_start(alert, False)
if self.toolbox is not None:
self._vbox.reorder_child(alert, 1)
else:
self._vbox.reorder_child(alert, 0)
def remove_alert(self, alert):
if alert in self._alerts:
self._alerts.remove(alert)
# if the alert is the visible one on top of the queue
if alert.get_parent() is not None:
self._vbox.remove(alert)
if len(self._alerts) >= 1:
self._vbox.pack_start(self._alerts[0], False)
if self.toolbox is not None:
self._vbox.reorder_child(self._alerts[0], 1)
else:
self._vbox.reorder_child(self._alert[0], 0)
def __window_realize_cb(self, window):
group = gtk.Window()
group.realize()
window.window.set_group(group.window)
def __window_state_event_cb(self, window, event):
if not (event.changed_mask & gtk.gdk.WINDOW_STATE_FULLSCREEN):
return False
if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
if self.toolbox is not None:
self.toolbox.hide()
if self.tray is not None:
self.tray.hide()
self._is_fullscreen = True
if self.props.enable_fullscreen_mode:
self._unfullscreen_button.show()
else:
if self.toolbox is not None:
self.toolbox.show()
if self.tray is not None:
self.tray.show()
self._is_fullscreen = False
if self.props.enable_fullscreen_mode:
self._unfullscreen_button.hide()
def __key_press_cb(self, widget, event):
key = gtk.gdk.keyval_name(event.keyval)
if event.state & gtk.gdk.MOD1_MASK:
if key == 'space':
self.tray.props.visible = not self.tray.props.visible
return True
elif key == 'Escape' and self._is_fullscreen and \
self.props.enable_fullscreen_mode:
self.unfullscreen()
return True
return False
def __unfullscreen_button_pressed(self, widget, event):
self.unfullscreen()
def set_enable_fullscreen_mode(self, enable_fullscreen_mode):
self._enable_fullscreen_mode = enable_fullscreen_mode
def get_enable_fullscreen_mode(self):
return self._enable_fullscreen_mode
enable_fullscreen_mode = gobject.property(type=object,
setter=set_enable_fullscreen_mode,
getter=get_enable_fullscreen_mode)
+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()
+592
View File
@@ -0,0 +1,592 @@
# 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 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._suggested_fname = None
self._info = 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:
fname = self._get_filename_from_headers(self._info.headers)
self._suggested_fname = fname
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):
self.verbose = None
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
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()
+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.
+408
View File
@@ -0,0 +1,408 @@
"""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 dbus
import gobject
import telepathy
_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._telepathy_room_handle = None
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 = {}
# Set up by set_up_tubes()
self.telepathy_conn = None
self.telepathy_tubes_chan = None
self.telepathy_text_chan = None
self._telepathy_room = None
def __repr__(self):
return ('<proxy for %s at %x>' % (self._object_path, id(self)))
def _get_properties_reply_cb(self, new_props):
self._get_properties_call = None
_logger.debug('%r: initial GetProperties returned', self)
self._properties_changed_cb(new_props)
def _get_properties_error_cb(self, e):
self._get_properties_call = None
# FIXME: do something with the error
_logger.warning('%r: Error doing initial GetProperties: %s', self, e)
def _properties_changed_cb(self, new_props):
_logger.debug('%r: Activity properties changed to %r', self, 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"""
if pspec.name == "joined":
return self._joined
if self._get_properties_call is not None:
_logger.debug('%r: Blocking on GetProperties() because someone '
'wants property %s', self, 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 set_private(self, val, reply_handler, error_handler):
self._activity.SetProperties({'private': bool(val)},
reply_handler=reply_handler,
error_handler=error_handler)
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):
_logger.debug('%r: buddy %s joined with handle %u', 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):
_logger.debug('%r: buddy %s left', self, object_path)
gobject.idle_add(self._emit_buddy_left_signal, object_path)
handle = self._buddy_path_to_handle.pop(object_path, None)
if handle:
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):
_logger.debug('%r: new channel created at %s', 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.
"""
op = buddy.object_path()
_logger.debug('%r: inviting %s', self, op)
self._activity.Invite(op, message,
reply_handler=lambda: response_cb(None),
error_handler=response_cb)
# Joining and sharing (FIXME: sharing is actually done elsewhere)
def set_up_tubes(self, reply_handler, error_handler):
cpaths = []
def tubes_chan_ready(chan):
_logger.debug('%r: Tubes channel %r is ready', self, chan)
self.telepathy_tubes_chan = chan
_logger.debug('%r: finished setting up tubes', self)
reply_handler()
def got_tubes_chan(path):
_logger.debug('%r: got Tubes channel at %s', self, path)
telepathy.client.Channel(self.telepathy_conn.service_name,
path, ready_handler=tubes_chan_ready,
error_handler=error_handler)
def text_chan_ready(chan):
_logger.debug('%r: Text channel %r is ready', self, chan)
self.telepathy_text_chan = chan
self.telepathy_conn.RequestChannel(telepathy.CHANNEL_TYPE_TUBES,
telepathy.HANDLE_TYPE_ROOM,
self._telepathy_room_handle,
True,
reply_handler=got_tubes_chan,
error_handler=error_handler)
def got_text_chan(path):
_logger.debug('%r: got Text channel at %s', self, path)
telepathy.client.Channel(self.telepathy_conn.service_name,
path, ready_handler=text_chan_ready,
error_handler=error_handler)
def conn_ready(conn):
_logger.debug('%r: Connection %r is ready', self, conn)
self.telepathy_conn = conn
# For the moment we'll do this synchronously.
# If the PS gained a GetRoom method, we could
# do this async too
for channel_path in cpaths:
channel = telepathy.client.Channel(conn.service_name,
channel_path)
handle_type, handle = channel.GetHandle()
if handle_type == telepathy.HANDLE_TYPE_ROOM:
room = handle
break
if room is None:
error_handler(AssertionError("Presence Service didn't create "
"a chatroom"))
else:
self._telepathy_room_handle = room
conn.RequestChannel(telepathy.CHANNEL_TYPE_TEXT,
telepathy.HANDLE_TYPE_ROOM,
room, True,
reply_handler=got_text_chan,
error_handler=error_handler)
def got_channels(bus_name, conn_path, channel_paths):
_logger.debug('%r: Connection on %s at %s, channel paths: %r',
self, bus_name, conn_path, channel_paths)
# can't use assignment for this due to Python scoping
cpaths.extend(channel_paths)
telepathy.client.Connection(bus_name, conn_path,
ready_handler=conn_ready,
error_handler=error_handler)
self._activity.GetChannels(reply_handler=got_channels,
error_handler=error_handler)
def _join_cb(self):
_logger.debug('%r: Join finished', self)
self._joined = True
self.emit("joined", True, None)
def _join_error_cb(self, err):
_logger.debug('%r: Join failed because: %s', self, err)
self.emit("joined", False, str(err))
def join(self):
"""Join this activity.
Emits 'joined' and otherwise does nothing if we're already joined.
"""
if self._joined:
self.emit("joined", True, None)
return
_logger.debug('%r: joining', self)
def joined():
self.set_up_tubes(reply_handler=self._join_cb,
error_handler=self._join_error_cb)
self._activity.Join(reply_handler=joined,
error_handler=self._join_error_cb)
# GetChannels() wrapper
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()
_logger.debug('%r: bus name is %s, connection is %s, channels are %r',
self, bus_name, connection, channels)
return bus_name, connection, channels
# Leaving
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"""
_logger.debug('%r: leaving', self)
self._joined = False
self._activity.Leave(reply_handler=self._leave_cb,
error_handler=self._leave_error_cb)
+236
View File
@@ -0,0 +1,236 @@
"""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._icon_changed_signal = self._buddy.connect_to_signal(
'IconChanged', self._icon_changed_cb, byte_arrays=True)
self._joined_activity_signal = self._buddy.connect_to_signal(
'JoinedActivity', self._joined_activity_cb)
self._left_activity_signal = self._buddy.connect_to_signal(
'LeftActivity', self._left_activity_cb)
self._property_changed_signal = 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 destroy(self):
self._icon_changed_signal.remove()
self._joined_activity_signal.remove()
self._left_activity_signal.remove()
self._property_changed_signal.remove()
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
+597
View File
@@ -0,0 +1,597 @@
"""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 = {}
self._joined = None
# 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]
_logger.debug('Reused proxy %r', obj)
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:
logging.debug('Cannot get the activity ID')
else:
raise RuntimeError("Unknown object type")
self._objcache[object_path] = obj
_logger.debug('Created proxy %r', 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):
obj = self._objcache[object_path]
self.emit('buddy-disappeared', obj)
# We cannot maintain the object in the cache because that would keep
# a lot of objects from being collected. That includes UI objects
# due to signals using strong references.
# If we want to cache some despite the memory usage increase,
# we could use a LRU cache limited to some value.
del self._objcache[object_path]
obj.destroy()
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_private_invitation_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, warn_if_none=True):
"""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:
if warn_if_none:
_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.")
return self._new_object(owner_op)
def _share_activity_cb(self, activity, op):
"""Finish sharing the activity
"""
psact = self._new_object(op)
psact._joined = True
_logger.debug('%r: Just shared, setting up tubes', activity)
psact.set_up_tubes(reply_handler=lambda:
self.emit("activity-shared", True, psact, None),
error_handler=lambda e:
self._share_activity_error_cb(activity, e))
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
properties['private'] = bool(private)
self._ps.ShareActivity(actid, atype, name, properties,
reply_handler=lambda op: \
self._share_activity_cb(activity, 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, [])
+207
View File
@@ -0,0 +1,207 @@
"""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.jabber_server = DEFAULT_JABBER_SERVER
self.jabber_registered = False
self.backup1 = None
self.sound_volume = DEFAULT_VOLUME
self._pubkey = None
self._privkey_hash = None
self._config_path = path
self._load_config()
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()
cp.read([self._config_path])
if self.nick_name:
_set_key(cp, 'Buddy', 'NickName', self.nick_name.encode('utf8'))
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()
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'))
del cp
def _load_pubkey(self):
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:
logging.error("Error reading public key: %s" % e)
return None
magic = "ssh-dss "
for l in lines:
l = l.strip()
if not l.startswith(magic):
continue
return l[len(magic):]
else:
logging.error("Error parsing public key.")
return None
def _get_pubkey(self):
# load on-demand.
if not self._pubkey:
self._pubkey = self._load_pubkey()
return self._pubkey
def _hash_private_key(self):
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 None
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.")
return None
# hash it
key_hash = util._sha_data(key)
return util.printable_hash(key_hash)
def _get_privkey_hash(self):
# load on-demand.
if not self._privkey_hash:
self._privkey_hash = self._hash_private_key()
return self._privkey_hash
privkey_hash = property(_get_privkey_hash)
pubkey = property(_get_pubkey)
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;
gtk_border_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__ */
+118
View File
@@ -0,0 +1,118 @@
/*
* 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.
*/
#include <gdk/gdkx.h>
#include <gtk/gtkwindow.h>
#include "sugar-preview.h"
static void sugar_preview_class_init (SugarPreviewClass *menu_class);
static void sugar_preview_init (SugarPreview *menu);
G_DEFINE_TYPE(SugarPreview, sugar_preview, G_TYPE_OBJECT)
void
sugar_preview_set_size(SugarPreview *preview, int width, int height)
{
preview->width = width;
preview->height = height;
}
GdkPixbuf *
sugar_preview_get_pixbuf(SugarPreview *preview)
{
GdkPixbuf *pixbuf;
if (preview->pixbuf != NULL) {
return preview->pixbuf;
}
if (preview->image == NULL) {
return NULL;
}
preview->pixbuf = gdk_pixbuf_get_from_image(NULL, preview->image, NULL,
0, 0, 0, 0,
preview->image->width,
preview->image->height);
g_object_unref(G_OBJECT(preview->image));
preview->image = NULL;
return preview->pixbuf;
}
void
sugar_preview_clear(SugarPreview *preview)
{
if (preview->image != NULL) {
g_object_unref(G_OBJECT(preview->image));
preview->image = NULL;
}
if (preview->pixbuf != NULL) {
g_object_unref(G_OBJECT(preview->pixbuf));
preview->pixbuf = NULL;
}
}
void
sugar_preview_take_screenshot(SugarPreview *preview, GdkDrawable *drawable)
{
GdkScreen *screen;
GdkVisual *visual;
GdkColormap *colormap;
gint width, height;
sugar_preview_clear(preview);
gdk_drawable_get_size(drawable, &width, &height);
screen = gdk_drawable_get_screen(drawable);
visual = gdk_drawable_get_visual(drawable);
colormap = gdk_drawable_get_colormap(drawable);
preview->image = gdk_image_new(GDK_IMAGE_SHARED, visual, width, height);
gdk_image_set_colormap(preview->image, colormap);
XShmGetImage(GDK_SCREEN_XDISPLAY(screen),
GDK_DRAWABLE_XID(drawable),
gdk_x11_image_get_ximage(preview->image),
0, 0, AllPlanes, ZPixmap);
}
static void
sugar_preview_dispose(GObject *object)
{
SugarPreview *preview = SUGAR_PREVIEW(object);
sugar_preview_clear(preview);
}
static void
sugar_preview_class_init(SugarPreviewClass *preview_class)
{
GObjectClass *g_object_class = G_OBJECT_CLASS (preview_class);
g_object_class->dispose = sugar_preview_dispose;
}
static void
sugar_preview_init(SugarPreview *preview)
{
preview->image = NULL;
preview->pixbuf = NULL;
}
+62
View File
@@ -0,0 +1,62 @@
/*
* 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.
*/
#ifndef __SUGAR_PREVIEW_H__
#define __SUGAR_PREVIEW_H__
#include <gdk/gdkdrawable.h>
G_BEGIN_DECLS
typedef struct _SugarPreview SugarPreview;
typedef struct _SugarPreviewClass SugarPreviewClass;
#define SUGAR_TYPE_PREVIEW (sugar_preview_get_type())
#define SUGAR_PREVIEW(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_PREVIEW, SugarPreview))
#define SUGAR_PREVIEW_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_PREVIEW, SugarPreviewClass))
#define SUGAR_IS_PREVIEW(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_PREVIEW))
#define SUGAR_IS_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_PREVIEW))
#define SUGAR_PREVIEW_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_PREVIEW, SugarPreviewClass))
struct _SugarPreview {
GObject base_instance;
GdkImage *image;
GdkPixbuf *pixbuf;
int width;
int height;
};
struct _SugarPreviewClass {
GObjectClass base_class;
};
GType sugar_preview_get_type (void);
void sugar_preview_take_screenshot (SugarPreview *preview,
GdkDrawable *drawable);
void sugar_preview_set_size (SugarPreview *preview,
int width,
int height);
GdkPixbuf *sugar_preview_get_pixbuf (SugarPreview *preview);
void sugar_preview_clear (SugarPreview *preview);
G_END_DECLS
#endif /* __SUGAR_PREVIEW_H__ */
+170
View File
@@ -0,0 +1,170 @@
"""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
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 Exception:
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()
+42
View File
@@ -0,0 +1,42 @@
# 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
def get_activity_id(wnck_window):
window = gtk.gdk.window_foreign_new(wnck_window.get_xid())
prop_info = window.property_get('_SUGAR_ACTIVITY_ID', 'STRING')
if prop_info is None:
return None
else:
return prop_info[2]
def get_bundle_id(wnck_window):
window = gtk.gdk.window_foreign_new(wnck_window.get_xid())
prop_info = window.property_get('_SUGAR_BUNDLE_ID', 'STRING')
if prop_info is None:
return None
else:
return prop_info[2]
def set_activity_id(window, activity_id):
window.property_change('_SUGAR_ACTIVITY_ID', 'STRING', 8,
gtk.gdk.PROP_MODE_REPLACE, activity_id)
def set_bundle_id(window, bundle_id):
window.property_change('_SUGAR_BUNDLE_ID', 'STRING', 8,
gtk.gdk.PROP_MODE_REPLACE, bundle_id)