Use a src directory consistently with base and shell.
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
sugar-marshal.c
|
||||
sugar-marshal.h
|
||||
_sugarext.c
|
||||
_sugarext.c
|
||||
@@ -0,0 +1 @@
|
||||
LGPL
|
||||
@@ -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
|
||||
@@ -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*")
|
||||
)
|
||||
@@ -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
|
||||
_*
|
||||
%%
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
sugardir = $(pythondir)/sugar/activity
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
activity.py \
|
||||
activityfactory.py \
|
||||
activityhandle.py \
|
||||
activityservice.py \
|
||||
bundlebuilder.py \
|
||||
registry.py
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
sugardir = $(pythondir)/sugar/bundle
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
bundle.py \
|
||||
activitybundle.py \
|
||||
contentbundle.py
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -0,0 +1,5 @@
|
||||
sugardir = $(pythondir)/sugar/clipboard
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
clipboardservice.py
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
sugardir = $(pythondir)/sugar/datastore
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
dbus_helpers.py \
|
||||
datastore.py
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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 "<Control>a" or "<Shift><Alt>F1" or
|
||||
* "<Release>z" (the last one is for key release). The parser
|
||||
* is fairly liberal and allows lower or upper case, and also
|
||||
* abbreviations such as "<Ctl>" and "<Ctrl>".
|
||||
*
|
||||
* 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 "<Control>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;
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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')
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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')
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
sugardir = $(pythondir)/sugar/presence
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
activity.py \
|
||||
buddy.py \
|
||||
tubeconn.py \
|
||||
presenceservice.py
|
||||
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
[]
|
||||
|
||||
@@ -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, [])
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_ */
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -0,0 +1 @@
|
||||
BOOLEAN:UINT,UINT
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user