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

This commit is contained in:
John (J5) Palmieri 2007-08-20 14:41:03 -04:00
commit 655d7f4174
74 changed files with 2170 additions and 1895 deletions

7
NEWS
View File

@ -1,3 +1,10 @@
* #2012: Fix palette position on the left frame panel. (marco)
* #2297: Make activity name translatable. (danw)
* #2695: Recognize text files as such. (tomeu)
* #2669: Add a border to the inner of the frame. (marco)
* #2703: Update macedonian translation. (ArangelAngov)
* #2543: Offer multiple activities for opening clipboard objects. (tomeu)
Snapshot d93122bf5e
* #2751 Add keybindings for max/min brightness/volume

View File

@ -10,6 +10,8 @@ libsugarui_la_SOURCES = \
$(BUILT_SOURCES) \
eggaccelerators.c \
eggaccelerators.h \
sexy-icon-entry.h \
sexy-icon-entry.c \
sugar-address-entry.c \
sugar-address-entry.h \
sugar-key-grabber.c \

984
lib/ui/sexy-icon-entry.c Normal file
View File

@ -0,0 +1,984 @@
/*
* @file libsexy/sexy-icon-entry.c Entry widget
*
* @Copyright (C) 2004-2006 Christian Hammond.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <sexy-icon-entry.h>
#include <string.h>
#include <gtk/gtk.h>
#define ICON_MARGIN 2
#define MAX_ICONS 2
#define IS_VALID_ICON_ENTRY_POSITION(pos) \
((pos) == SEXY_ICON_ENTRY_PRIMARY || \
(pos) == SEXY_ICON_ENTRY_SECONDARY)
typedef struct
{
GtkImage *icon;
gboolean highlight;
gboolean hovered;
GdkWindow *window;
} SexyIconInfo;
struct _SexyIconEntryPriv
{
SexyIconInfo icons[MAX_ICONS];
gulong icon_released_id;
};
enum
{
ICON_PRESSED,
ICON_RELEASED,
LAST_SIGNAL
};
static void sexy_icon_entry_class_init(SexyIconEntryClass *klass);
static void sexy_icon_entry_editable_init(GtkEditableClass *iface);
static void sexy_icon_entry_init(SexyIconEntry *entry);
static void sexy_icon_entry_finalize(GObject *obj);
static void sexy_icon_entry_destroy(GtkObject *obj);
static void sexy_icon_entry_map(GtkWidget *widget);
static void sexy_icon_entry_unmap(GtkWidget *widget);
static void sexy_icon_entry_realize(GtkWidget *widget);
static void sexy_icon_entry_unrealize(GtkWidget *widget);
static void sexy_icon_entry_size_request(GtkWidget *widget,
GtkRequisition *requisition);
static void sexy_icon_entry_size_allocate(GtkWidget *widget,
GtkAllocation *allocation);
static gint sexy_icon_entry_expose(GtkWidget *widget, GdkEventExpose *event);
static gint sexy_icon_entry_enter_notify(GtkWidget *widget,
GdkEventCrossing *event);
static gint sexy_icon_entry_leave_notify(GtkWidget *widget,
GdkEventCrossing *event);
static gint sexy_icon_entry_button_press(GtkWidget *widget,
GdkEventButton *event);
static gint sexy_icon_entry_button_release(GtkWidget *widget,
GdkEventButton *event);
static GtkEntryClass *parent_class = NULL;
static guint signals[LAST_SIGNAL] = {0};
G_DEFINE_TYPE_EXTENDED(SexyIconEntry, sexy_icon_entry, GTK_TYPE_ENTRY,
0,
G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE,
sexy_icon_entry_editable_init));
static void
sexy_icon_entry_class_init(SexyIconEntryClass *klass)
{
GObjectClass *gobject_class;
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
GtkEntryClass *entry_class;
parent_class = g_type_class_peek_parent(klass);
gobject_class = G_OBJECT_CLASS(klass);
object_class = GTK_OBJECT_CLASS(klass);
widget_class = GTK_WIDGET_CLASS(klass);
entry_class = GTK_ENTRY_CLASS(klass);
gobject_class->finalize = sexy_icon_entry_finalize;
object_class->destroy = sexy_icon_entry_destroy;
widget_class->map = sexy_icon_entry_map;
widget_class->unmap = sexy_icon_entry_unmap;
widget_class->realize = sexy_icon_entry_realize;
widget_class->unrealize = sexy_icon_entry_unrealize;
widget_class->size_request = sexy_icon_entry_size_request;
widget_class->size_allocate = sexy_icon_entry_size_allocate;
widget_class->expose_event = sexy_icon_entry_expose;
widget_class->enter_notify_event = sexy_icon_entry_enter_notify;
widget_class->leave_notify_event = sexy_icon_entry_leave_notify;
widget_class->button_press_event = sexy_icon_entry_button_press;
widget_class->button_release_event = sexy_icon_entry_button_release;
/**
* SexyIconEntry::icon-pressed:
* @entry: The entry on which the signal is emitted.
* @icon_pos: The position of the clicked icon.
* @button: The mouse button clicked.
*
* The ::icon-pressed signal is emitted when an icon is clicked.
*/
signals[ICON_PRESSED] =
g_signal_new("icon_pressed",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(SexyIconEntryClass, icon_pressed),
NULL, NULL,
gtk_marshal_VOID__INT_INT,
G_TYPE_NONE, 2,
G_TYPE_INT,
G_TYPE_INT);
/**
* SexyIconEntry::icon-released:
* @entry: The entry on which the signal is emitted.
* @icon_pos: The position of the clicked icon.
* @button: The mouse button clicked.
*
* The ::icon-released signal is emitted on the button release from a
* mouse click.
*/
signals[ICON_RELEASED] =
g_signal_new("icon_released",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(SexyIconEntryClass, icon_released),
NULL, NULL,
gtk_marshal_VOID__INT_INT,
G_TYPE_NONE, 2,
G_TYPE_INT,
G_TYPE_INT);
}
static void
sexy_icon_entry_editable_init(GtkEditableClass *iface)
{
};
static void
sexy_icon_entry_init(SexyIconEntry *entry)
{
entry->priv = g_new0(SexyIconEntryPriv, 1);
}
static void
sexy_icon_entry_finalize(GObject *obj)
{
SexyIconEntry *entry;
g_return_if_fail(obj != NULL);
g_return_if_fail(SEXY_IS_ICON_ENTRY(obj));
entry = SEXY_ICON_ENTRY(obj);
g_free(entry->priv);
if (G_OBJECT_CLASS(parent_class)->finalize)
G_OBJECT_CLASS(parent_class)->finalize(obj);
}
static void
sexy_icon_entry_destroy(GtkObject *obj)
{
SexyIconEntry *entry;
entry = SEXY_ICON_ENTRY(obj);
sexy_icon_entry_set_icon(entry, SEXY_ICON_ENTRY_PRIMARY, NULL);
sexy_icon_entry_set_icon(entry, SEXY_ICON_ENTRY_SECONDARY, NULL);
if (GTK_OBJECT_CLASS(parent_class)->destroy)
GTK_OBJECT_CLASS(parent_class)->destroy(obj);
}
static void
sexy_icon_entry_map(GtkWidget *widget)
{
if (GTK_WIDGET_REALIZED(widget) && !GTK_WIDGET_MAPPED(widget))
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
GTK_WIDGET_CLASS(parent_class)->map(widget);
for (i = 0; i < MAX_ICONS; i++)
{
if (entry->priv->icons[i].icon != NULL)
gdk_window_show(entry->priv->icons[i].window);
}
}
}
static void
sexy_icon_entry_unmap(GtkWidget *widget)
{
if (GTK_WIDGET_MAPPED(widget))
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
for (i = 0; i < MAX_ICONS; i++)
{
if (entry->priv->icons[i].icon != NULL)
gdk_window_hide(entry->priv->icons[i].window);
}
GTK_WIDGET_CLASS(parent_class)->unmap(widget);
}
}
static gint
get_icon_width(SexyIconEntry *entry, SexyIconEntryPosition icon_pos)
{
GtkRequisition requisition;
gint menu_icon_width;
gint width;
SexyIconInfo *icon_info = &entry->priv->icons[icon_pos];
if (icon_info->icon == NULL)
return 0;
gtk_widget_size_request(GTK_WIDGET(icon_info->icon), &requisition);
gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &menu_icon_width, NULL);
width = MAX(requisition.width, menu_icon_width);
return width;
}
static void
get_borders(SexyIconEntry *entry, gint *xborder, gint *yborder)
{
GtkWidget *widget = GTK_WIDGET(entry);
gint focus_width;
gboolean interior_focus;
gtk_widget_style_get(widget,
"interior-focus", &interior_focus,
"focus-line-width", &focus_width,
NULL);
if (gtk_entry_get_has_frame(GTK_ENTRY(entry)))
{
*xborder = widget->style->xthickness;
*yborder = widget->style->ythickness;
}
else
{
*xborder = 0;
*yborder = 0;
}
if (!interior_focus)
{
*xborder += focus_width;
*yborder += focus_width;
}
}
static void
get_text_area_size(SexyIconEntry *entry, GtkAllocation *alloc)
{
GtkWidget *widget = GTK_WIDGET(entry);
GtkRequisition requisition;
gint xborder, yborder;
gtk_widget_get_child_requisition(widget, &requisition);
get_borders(entry, &xborder, &yborder);
alloc->x = xborder;
alloc->y = yborder;
alloc->width = widget->allocation.width - xborder * 2;
alloc->height = requisition.height - yborder * 2;
}
static void
get_icon_allocation(SexyIconEntry *icon_entry,
gboolean left,
GtkAllocation *widget_alloc,
GtkAllocation *text_area_alloc,
GtkAllocation *allocation,
SexyIconEntryPosition *icon_pos)
{
gboolean rtl;
rtl = (gtk_widget_get_direction(GTK_WIDGET(icon_entry)) ==
GTK_TEXT_DIR_RTL);
if (left)
*icon_pos = (rtl ? SEXY_ICON_ENTRY_SECONDARY : SEXY_ICON_ENTRY_PRIMARY);
else
*icon_pos = (rtl ? SEXY_ICON_ENTRY_PRIMARY : SEXY_ICON_ENTRY_SECONDARY);
allocation->y = text_area_alloc->y;
allocation->width = get_icon_width(icon_entry, *icon_pos);
allocation->height = text_area_alloc->height;
if (left)
allocation->x = text_area_alloc->x + ICON_MARGIN;
else
{
allocation->x = text_area_alloc->x + text_area_alloc->width -
allocation->width - ICON_MARGIN;
}
}
static void
sexy_icon_entry_realize(GtkWidget *widget)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
GdkWindowAttr attributes;
gint attributes_mask;
int i;
GTK_WIDGET_CLASS(parent_class)->realize(widget);
attributes.x = 0;
attributes.y = 0;
attributes.width = 1;
attributes.height = 1;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual(widget);
attributes.colormap = gtk_widget_get_colormap(widget);
attributes.event_mask = gtk_widget_get_events(widget);
attributes.event_mask |=
(GDK_EXPOSURE_MASK
| GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
| GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
for (i = 0; i < MAX_ICONS; i++)
{
SexyIconInfo *icon_info;
icon_info = &entry->priv->icons[i];
icon_info->window = gdk_window_new(widget->window, &attributes,
attributes_mask);
gdk_window_set_user_data(icon_info->window, widget);
gdk_window_set_background(icon_info->window,
&widget->style->base[GTK_WIDGET_STATE(widget)]);
}
gtk_widget_queue_resize(widget);
}
static void
sexy_icon_entry_unrealize(GtkWidget *widget)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
GTK_WIDGET_CLASS(parent_class)->unrealize(widget);
for (i = 0; i < MAX_ICONS; i++)
{
SexyIconInfo *icon_info = &entry->priv->icons[i];
gdk_window_destroy(icon_info->window);
icon_info->window = NULL;
}
}
static void
sexy_icon_entry_size_request(GtkWidget *widget, GtkRequisition *requisition)
{
GtkEntry *gtkentry;
SexyIconEntry *entry;
gint icon_widths = 0;
int i;
gtkentry = GTK_ENTRY(widget);
entry = SEXY_ICON_ENTRY(widget);
for (i = 0; i < MAX_ICONS; i++)
{
int icon_width = get_icon_width(entry, i);
if (icon_width > 0)
icon_widths += icon_width + ICON_MARGIN;
}
GTK_WIDGET_CLASS(parent_class)->size_request(widget, requisition);
if (icon_widths > requisition->width)
requisition->width += icon_widths;
}
static void
place_windows(SexyIconEntry *icon_entry, GtkAllocation *widget_alloc)
{
SexyIconEntryPosition left_icon_pos;
SexyIconEntryPosition right_icon_pos;
GtkAllocation left_icon_alloc;
GtkAllocation right_icon_alloc;
GtkAllocation text_area_alloc;
get_text_area_size(icon_entry, &text_area_alloc);
get_icon_allocation(icon_entry, TRUE, widget_alloc, &text_area_alloc,
&left_icon_alloc, &left_icon_pos);
get_icon_allocation(icon_entry, FALSE, widget_alloc, &text_area_alloc,
&right_icon_alloc, &right_icon_pos);
if (left_icon_alloc.width > 0)
{
text_area_alloc.x = left_icon_alloc.x + left_icon_alloc.width +
ICON_MARGIN;
}
if (right_icon_alloc.width > 0)
text_area_alloc.width -= right_icon_alloc.width + ICON_MARGIN;
text_area_alloc.width -= text_area_alloc.x;
gdk_window_move_resize(icon_entry->priv->icons[left_icon_pos].window,
left_icon_alloc.x, left_icon_alloc.y,
left_icon_alloc.width, left_icon_alloc.height);
gdk_window_move_resize(icon_entry->priv->icons[right_icon_pos].window,
right_icon_alloc.x, right_icon_alloc.y,
right_icon_alloc.width, right_icon_alloc.height);
gdk_window_move_resize(GTK_ENTRY(icon_entry)->text_area,
text_area_alloc.x, text_area_alloc.y,
text_area_alloc.width, text_area_alloc.height);
}
static void
sexy_icon_entry_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
{
g_return_if_fail(SEXY_IS_ICON_ENTRY(widget));
g_return_if_fail(allocation != NULL);
widget->allocation = *allocation;
GTK_WIDGET_CLASS(parent_class)->size_allocate(widget, allocation);
if (GTK_WIDGET_REALIZED(widget))
place_windows(SEXY_ICON_ENTRY(widget), allocation);
}
static GdkPixbuf *
get_pixbuf_from_icon(SexyIconEntry *entry, SexyIconEntryPosition icon_pos)
{
GdkPixbuf *pixbuf = NULL;
gchar *stock_id;
SexyIconInfo *icon_info = &entry->priv->icons[icon_pos];
GtkIconSize size;
switch (gtk_image_get_storage_type(GTK_IMAGE(icon_info->icon)))
{
case GTK_IMAGE_PIXBUF:
pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(icon_info->icon));
g_object_ref(pixbuf);
break;
case GTK_IMAGE_STOCK:
gtk_image_get_stock(GTK_IMAGE(icon_info->icon), &stock_id, &size);
pixbuf = gtk_widget_render_icon(GTK_WIDGET(entry),
stock_id, size, NULL);
break;
default:
return NULL;
}
return pixbuf;
}
/* Kudos to the gnome-panel guys. */
static void
colorshift_pixbuf(GdkPixbuf *dest, GdkPixbuf *src, int shift)
{
gint i, j;
gint width, height, has_alpha, src_rowstride, dest_rowstride;
guchar *target_pixels;
guchar *original_pixels;
guchar *pix_src;
guchar *pix_dest;
int val;
guchar r, g, b;
has_alpha = gdk_pixbuf_get_has_alpha(src);
width = gdk_pixbuf_get_width(src);
height = gdk_pixbuf_get_height(src);
src_rowstride = gdk_pixbuf_get_rowstride(src);
dest_rowstride = gdk_pixbuf_get_rowstride(dest);
original_pixels = gdk_pixbuf_get_pixels(src);
target_pixels = gdk_pixbuf_get_pixels(dest);
for (i = 0; i < height; i++)
{
pix_dest = target_pixels + i * dest_rowstride;
pix_src = original_pixels + i * src_rowstride;
for (j = 0; j < width; j++)
{
r = *(pix_src++);
g = *(pix_src++);
b = *(pix_src++);
val = r + shift;
*(pix_dest++) = CLAMP(val, 0, 255);
val = g + shift;
*(pix_dest++) = CLAMP(val, 0, 255);
val = b + shift;
*(pix_dest++) = CLAMP(val, 0, 255);
if (has_alpha)
*(pix_dest++) = *(pix_src++);
}
}
}
static void
draw_icon(GtkWidget *widget, SexyIconEntryPosition icon_pos)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
SexyIconInfo *icon_info = &entry->priv->icons[icon_pos];
GdkPixbuf *pixbuf;
gint x, y, width, height;
if (icon_info->icon == NULL || !GTK_WIDGET_REALIZED(widget))
return;
if ((pixbuf = get_pixbuf_from_icon(entry, icon_pos)) == NULL)
return;
gdk_drawable_get_size(icon_info->window, &width, &height);
if (width == 1 || height == 1)
{
/*
* size_allocate hasn't been called yet. These are the default values.
*/
return;
}
if (gdk_pixbuf_get_height(pixbuf) > height)
{
GdkPixbuf *temp_pixbuf;
int scale;
scale = height - (2 * ICON_MARGIN);
temp_pixbuf = gdk_pixbuf_scale_simple(pixbuf, scale, scale,
GDK_INTERP_BILINEAR);
g_object_unref(pixbuf);
pixbuf = temp_pixbuf;
}
x = (width - gdk_pixbuf_get_width(pixbuf)) / 2;
y = (height - gdk_pixbuf_get_height(pixbuf)) / 2;
if (icon_info->hovered)
{
GdkPixbuf *temp_pixbuf;
temp_pixbuf = gdk_pixbuf_copy(pixbuf);
colorshift_pixbuf(temp_pixbuf, pixbuf, 30);
g_object_unref(pixbuf);
pixbuf = temp_pixbuf;
}
gdk_draw_pixbuf(icon_info->window, widget->style->black_gc, pixbuf,
0, 0, x, y, -1, -1,
GDK_RGB_DITHER_NORMAL, 0, 0);
g_object_unref(pixbuf);
}
static gint
sexy_icon_entry_expose(GtkWidget *widget, GdkEventExpose *event)
{
SexyIconEntry *entry;
g_return_val_if_fail(SEXY_IS_ICON_ENTRY(widget), FALSE);
g_return_val_if_fail(event != NULL, FALSE);
entry = SEXY_ICON_ENTRY(widget);
if (GTK_WIDGET_DRAWABLE(widget))
{
gboolean found = FALSE;
int i;
for (i = 0; i < MAX_ICONS && !found; i++)
{
SexyIconInfo *icon_info = &entry->priv->icons[i];
if (event->window == icon_info->window)
{
gint width;
GtkAllocation text_area_alloc;
get_text_area_size(entry, &text_area_alloc);
gdk_drawable_get_size(icon_info->window, &width, NULL);
gtk_paint_flat_box(widget->style, icon_info->window,
GTK_WIDGET_STATE(widget), GTK_SHADOW_NONE,
NULL, widget, "entry_bg",
0, 0, width, text_area_alloc.height);
draw_icon(widget, i);
found = TRUE;
}
}
if (!found)
GTK_WIDGET_CLASS(parent_class)->expose_event(widget, event);
}
return FALSE;
}
static void
update_icon(GObject *obj, GParamSpec *param, SexyIconEntry *entry)
{
if (param != NULL)
{
const char *name = g_param_spec_get_name(param);
if (strcmp(name, "pixbuf") && strcmp(name, "stock") &&
strcmp(name, "image") && strcmp(name, "pixmap") &&
strcmp(name, "icon_set") && strcmp(name, "pixbuf_animation"))
{
return;
}
}
gtk_widget_queue_resize(GTK_WIDGET(entry));
}
static gint
sexy_icon_entry_enter_notify(GtkWidget *widget, GdkEventCrossing *event)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
for (i = 0; i < MAX_ICONS; i++)
{
if (event->window == entry->priv->icons[i].window)
{
if (sexy_icon_entry_get_icon_highlight(entry, i))
{
entry->priv->icons[i].hovered = TRUE;
update_icon(NULL, NULL, entry);
break;
}
}
}
return FALSE;
}
static gint
sexy_icon_entry_leave_notify(GtkWidget *widget, GdkEventCrossing *event)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
for (i = 0; i < MAX_ICONS; i++)
{
if (event->window == entry->priv->icons[i].window)
{
if (sexy_icon_entry_get_icon_highlight(entry, i))
{
entry->priv->icons[i].hovered = FALSE;
update_icon(NULL, NULL, entry);
break;
}
}
}
return FALSE;
}
static gint
sexy_icon_entry_button_press(GtkWidget *widget, GdkEventButton *event)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
for (i = 0; i < MAX_ICONS; i++)
{
if (event->window == entry->priv->icons[i].window)
{
if (event->button == 1 &&
sexy_icon_entry_get_icon_highlight(entry, i))
{
entry->priv->icons[i].hovered = FALSE;
update_icon(NULL, NULL, entry);
}
g_signal_emit(entry, signals[ICON_PRESSED], 0, i, event->button);
return TRUE;
}
}
if (GTK_WIDGET_CLASS(parent_class)->button_press_event)
return GTK_WIDGET_CLASS(parent_class)->button_press_event(widget,
event);
return FALSE;
}
static gint
sexy_icon_entry_button_release(GtkWidget *widget, GdkEventButton *event)
{
SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
int i;
for (i = 0; i < MAX_ICONS; i++)
{
GdkWindow *icon_window = entry->priv->icons[i].window;
if (event->window == icon_window)
{
int width, height;
gdk_drawable_get_size(icon_window, &width, &height);
if (event->button == 1 &&
sexy_icon_entry_get_icon_highlight(entry, i) &&
event->x >= 0 && event->y >= 0 &&
event->x <= width && event->y <= height)
{
entry->priv->icons[i].hovered = TRUE;
update_icon(NULL, NULL, entry);
}
g_signal_emit(entry, signals[ICON_RELEASED], 0, i, event->button);
return TRUE;
}
}
if (GTK_WIDGET_CLASS(parent_class)->button_release_event)
return GTK_WIDGET_CLASS(parent_class)->button_release_event(widget,
event);
return FALSE;
}
/**
* sexy_icon_entry_new
*
* Creates a new SexyIconEntry widget.
*
* Returns a new #SexyIconEntry.
*/
GtkWidget *
sexy_icon_entry_new(void)
{
return GTK_WIDGET(g_object_new(SEXY_TYPE_ICON_ENTRY, NULL));
}
/**
* sexy_icon_entry_set_icon
* @entry: A #SexyIconEntry.
* @position: Icon position.
* @icon: A #GtkImage to set as the icon.
*
* Sets the icon shown in the entry
*/
void
sexy_icon_entry_set_icon(SexyIconEntry *entry, SexyIconEntryPosition icon_pos,
GtkImage *icon)
{
SexyIconInfo *icon_info;
g_return_if_fail(entry != NULL);
g_return_if_fail(SEXY_IS_ICON_ENTRY(entry));
g_return_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos));
g_return_if_fail(icon == NULL || GTK_IS_IMAGE(icon));
icon_info = &entry->priv->icons[icon_pos];
if (icon == icon_info->icon)
return;
if (icon_pos == SEXY_ICON_ENTRY_SECONDARY &&
entry->priv->icon_released_id != 0)
{
g_signal_handler_disconnect(entry, entry->priv->icon_released_id);
entry->priv->icon_released_id = 0;
}
if (icon == NULL)
{
if (icon_info->icon != NULL)
{
gtk_widget_destroy(GTK_WIDGET(icon_info->icon));
icon_info->icon = NULL;
/*
* Explicitly check, as the pointer may become invalidated
* during destruction.
*/
if (icon_info->window != NULL && GDK_IS_WINDOW(icon_info->window))
gdk_window_hide(icon_info->window);
}
}
else
{
if (icon_info->window != NULL && icon_info->icon == NULL)
gdk_window_show(icon_info->window);
g_signal_connect(G_OBJECT(icon), "notify",
G_CALLBACK(update_icon), entry);
icon_info->icon = icon;
g_object_ref(icon);
}
update_icon(NULL, NULL, entry);
}
/**
* sexy_icon_entry_set_icon_highlight
* @entry: A #SexyIconEntry;
* @position: Icon position.
* @highlight: TRUE if the icon should highlight on mouse-over
*
* Determines whether the icon will highlight on mouse-over.
*/
void
sexy_icon_entry_set_icon_highlight(SexyIconEntry *entry,
SexyIconEntryPosition icon_pos,
gboolean highlight)
{
SexyIconInfo *icon_info;
g_return_if_fail(entry != NULL);
g_return_if_fail(SEXY_IS_ICON_ENTRY(entry));
g_return_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos));
icon_info = &entry->priv->icons[icon_pos];
if (icon_info->highlight == highlight)
return;
icon_info->highlight = highlight;
}
/**
* sexy_icon_entry_get_icon
* @entry: A #SexyIconEntry.
* @position: Icon position.
*
* Retrieves the image used for the icon
*
* Returns: A #GtkImage.
*/
GtkImage *
sexy_icon_entry_get_icon(const SexyIconEntry *entry,
SexyIconEntryPosition icon_pos)
{
g_return_val_if_fail(entry != NULL, NULL);
g_return_val_if_fail(SEXY_IS_ICON_ENTRY(entry), NULL);
g_return_val_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos), NULL);
return entry->priv->icons[icon_pos].icon;
}
/**
* sexy_icon_entry_get_icon_highlight
* @entry: A #SexyIconEntry.
* @position: Icon position.
*
* Retrieves whether entry will highlight the icon on mouseover.
*
* Returns: TRUE if icon highlights.
*/
gboolean
sexy_icon_entry_get_icon_highlight(const SexyIconEntry *entry,
SexyIconEntryPosition icon_pos)
{
g_return_val_if_fail(entry != NULL, FALSE);
g_return_val_if_fail(SEXY_IS_ICON_ENTRY(entry), FALSE);
g_return_val_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos), FALSE);
return entry->priv->icons[icon_pos].highlight;
}
static void
clear_button_clicked_cb(SexyIconEntry *icon_entry,
SexyIconEntryPosition icon_pos,
int button)
{
if (icon_pos != SEXY_ICON_ENTRY_SECONDARY || button != 1)
return;
gtk_entry_set_text(GTK_ENTRY(icon_entry), "");
}
/**
* sexy_icon_entry_add_clear_button
* @icon_entry: A #SexyIconEntry.
*
* A convenience function to add a clear button to the end of the entry.
* This is useful for search boxes.
*/
void
sexy_icon_entry_add_clear_button(SexyIconEntry *icon_entry)
{
GtkWidget *icon;
g_return_if_fail(icon_entry != NULL);
g_return_if_fail(SEXY_IS_ICON_ENTRY(icon_entry));
icon = gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU);
gtk_widget_show(icon);
sexy_icon_entry_set_icon(SEXY_ICON_ENTRY(icon_entry),
SEXY_ICON_ENTRY_SECONDARY,
GTK_IMAGE(icon));
sexy_icon_entry_set_icon_highlight(SEXY_ICON_ENTRY(icon_entry),
SEXY_ICON_ENTRY_SECONDARY, TRUE);
if (icon_entry->priv->icon_released_id != 0)
{
g_signal_handler_disconnect(icon_entry,
icon_entry->priv->icon_released_id);
}
icon_entry->priv->icon_released_id =
g_signal_connect(G_OBJECT(icon_entry), "icon_released",
G_CALLBACK(clear_button_clicked_cb), NULL);
}
GType
sexy_icon_entry_position_get_type (void)
{
static GType etype = 0;
if (etype == 0) {
static const GEnumValue values[] = {
{ SEXY_ICON_ENTRY_PRIMARY, "SEXY_ICON_ENTRY_PRIMARY", "primary" },
{ SEXY_ICON_ENTRY_SECONDARY, "SEXY_ICON_ENTRY_SECONDARY", "secondary" },
{ 0, NULL, NULL }
};
etype = g_enum_register_static ("SexyIconEntryPosition", values);
}
return etype;
}

104
lib/ui/sexy-icon-entry.h Normal file
View File

@ -0,0 +1,104 @@
/*
* @file libsexy/sexy-icon-entry.h Entry widget
*
* @Copyright (C) 2004-2006 Christian Hammond.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef _SEXY_ICON_ENTRY_H_
#define _SEXY_ICON_ENTRY_H_
typedef struct _SexyIconEntry SexyIconEntry;
typedef struct _SexyIconEntryClass SexyIconEntryClass;
typedef struct _SexyIconEntryPriv SexyIconEntryPriv;
#include <gtk/gtkentry.h>
#include <gtk/gtkimage.h>
#define SEXY_TYPE_ICON_ENTRY (sexy_icon_entry_get_type())
#define SEXY_ICON_ENTRY(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj), SEXY_TYPE_ICON_ENTRY, SexyIconEntry))
#define SEXY_ICON_ENTRY_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass), SEXY_TYPE_ICON_ENTRY, SexyIconEntryClass))
#define SEXY_IS_ICON_ENTRY(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj), SEXY_TYPE_ICON_ENTRY))
#define SEXY_IS_ICON_ENTRY_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass), SEXY_TYPE_ICON_ENTRY))
#define SEXY_ICON_ENTRY_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj), SEXY_TYPE_ICON_ENTRY, SexyIconEntryClass))
typedef enum
{
SEXY_ICON_ENTRY_PRIMARY,
SEXY_ICON_ENTRY_SECONDARY
} SexyIconEntryPosition;
GType sexy_icon_entry_position_get_type(void);
#define SEXY_TYPE_ICON_ENTRY_POSITION (sexy_icon_entry_position_get_type())
struct _SexyIconEntry
{
GtkEntry parent_object;
SexyIconEntryPriv *priv;
void (*gtk_reserved1)(void);
void (*gtk_reserved2)(void);
void (*gtk_reserved3)(void);
void (*gtk_reserved4)(void);
};
struct _SexyIconEntryClass
{
GtkEntryClass parent_class;
/* Signals */
void (*icon_pressed)(SexyIconEntry *entry, SexyIconEntryPosition icon_pos,
int button);
void (*icon_released)(SexyIconEntry *entry, SexyIconEntryPosition icon_pos,
int button);
void (*gtk_reserved1)(void);
void (*gtk_reserved2)(void);
void (*gtk_reserved3)(void);
void (*gtk_reserved4)(void);
};
G_BEGIN_DECLS
GType sexy_icon_entry_get_type(void);
GtkWidget *sexy_icon_entry_new(void);
void sexy_icon_entry_set_icon(SexyIconEntry *entry,
SexyIconEntryPosition position,
GtkImage *icon);
void sexy_icon_entry_set_icon_highlight(SexyIconEntry *entry,
SexyIconEntryPosition position,
gboolean highlight);
GtkImage *sexy_icon_entry_get_icon(const SexyIconEntry *entry,
SexyIconEntryPosition position);
gboolean sexy_icon_entry_get_icon_highlight(const SexyIconEntry *entry,
SexyIconEntryPosition position);
void sexy_icon_entry_add_clear_button(SexyIconEntry *icon_entry);
G_END_DECLS
#endif /* _SEXY_ICON_ENTRY_H_ */

121
po/mk.po
View File

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: olpc-sugar.master\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-07-05 07:57-0700\n"
"PO-Revision-Date: 2007-07-05 19:34+0200\n"
"POT-Creation-Date: 2007-07-24 10:09-0700\n"
"PO-Revision-Date: 2007-08-08 15:40+0200\n"
"Last-Translator: Arangel Angov <arangel@linux.net.mk>\n"
"Language-Team: Macedonian <ossm-members@hedona.on.net.mk>\n"
"MIME-Version: 1.0\n"
@ -16,72 +16,129 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: KBabel 1.11.4\n"
#: ../shell/intro/intro.py:77
msgid "Pick a buddy picture"
msgstr "Избери слика за пријател"
#: ../shell/intro/intro.py:61
msgid "Name:"
msgstr "Име:"
#: ../shell/intro/intro.py:100
msgid "My Picture:"
msgstr "Мојата слика:"
#: ../shell/intro/intro.py:84
msgid "Click to change color:"
msgstr "Кликни да смениш боја:"
#: ../shell/intro/intro.py:180
msgid "My Name:"
msgstr "Моето име:"
#: ../shell/intro/intro.py:134
msgid "Back"
msgstr "Назад"
#: ../shell/intro/intro.py:204
msgid "My Color:"
msgstr "Мојата боја:"
#: ../shell/intro/intro.py:142
msgid "Done"
msgstr "Завршено"
#: ../shell/view/BuddyMenu.py:83
#: ../shell/intro/intro.py:145
msgid "Next"
msgstr "Напред"
#: ../shell/view/BuddyMenu.py:82
msgid "Remove friend"
msgstr "Отстрани пријател"
#: ../shell/view/BuddyMenu.py:87
#: ../shell/view/BuddyMenu.py:85
msgid "Make friend"
msgstr "Додај пријател"
#: ../shell/view/BuddyMenu.py:97
#. FIXME check that the buddy is not in the activity already
#: ../shell/view/BuddyMenu.py:96
msgid "Invite"
msgstr "Покани"
#: ../shell/view/clipboardmenu.py:103
#: ../shell/view/clipboardmenu.py:66
msgid "Remove"
msgstr "Отстрани"
#: ../shell/view/clipboardmenu.py:110
#: ../shell/view/clipboardmenu.py:70
msgid "Open"
msgstr "Отвори"
#: ../shell/view/clipboardmenu.py:117
msgid "Stop download"
msgstr "Прекини преземање"
#: ../shell/view/clipboardmenu.py:124
#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
#. TODO: Implement stopping downloads
#. self._stop_item.connect('activate', self._stop_item_activate_cb)
#. self.append_menu_item(self._stop_item)
#: ../shell/view/clipboardmenu.py:79
msgid "Add to journal"
msgstr "Додај во дневникот"
#: ../shell/view/clipboardmenu.py:180
#, python-format
msgid "Clipboard object: %s."
msgstr "Објект од таблата со исечоци: %s"
#: ../shell/view/frame/zoombox.py:42
msgid "Neighborhood"
msgstr "Соседство"
#: ../shell/view/frame/zoombox.py:55
msgid "Group"
msgstr "Група"
#: ../shell/view/frame/zoombox.py:68
msgid "Home"
msgstr "Дома"
#: ../shell/view/frame/zoombox.py:81
msgid "Activity"
msgstr "Активност"
#: ../services/clipboard/objecttypeservice.py:32
msgid "Text"
msgstr "Текст"
#: ../services/clipboard/objecttypeservice.py:35
#: ../services/clipboard/objecttypeservice.py:36
msgid "Image"
msgstr "Слика"
#: ../shell/view/Shell.py:227
#: ../shell/view/Shell.py:203
msgid "Screenshot"
msgstr "Слика од екранот"
#: ../shell/view/clipboardicon.py:211
#, python-format
msgid "Clipboard object: %s."
msgstr "Објект од таблата со исечоци: %s"
#: ../shell/view/home/HomeBox.py:140
msgid "Shutdown"
msgstr "Исклучи"
#: ../shell/view/home/MeshBox.py:122
#: ../shell/view/home/MeshBox.py:126
msgid "Mesh Network"
msgstr "Соседство"
#: ../sugar/activity/activity.py:232
#: ../shell/view/devices/battery.py:34
msgid "My Battery life"
msgstr "Мојата батерија"
#: ../shell/view/devices/battery.py:87
msgid "Battery charging"
msgstr "Батеријата се полни"
#: ../shell/view/devices/battery.py:89
msgid "Battery discharging"
msgstr "Батерјате се празни"
#: ../shell/view/devices/battery.py:91
msgid "Battery fully charged"
msgstr "Батеријата е наполнета"
#: ../sugar/activity/activity.py:73
msgid "Private"
msgstr "Приватно"
#: ../sugar/activity/activity.py:74
msgid "My Neighborhood"
msgstr "Мое соседство"
#: ../sugar/activity/activity.py:81
msgid "Keep"
msgstr "Зачувај"
#: ../sugar/activity/activity.py:87
msgid "Stop"
msgstr "Стоп"
#: ../sugar/activity/activity.py:260
#, python-format
msgid "%s Activity"
msgstr "%s активност"

View File

@ -79,7 +79,7 @@ class Service(dbus.service.Object):
self._console = Console()
@dbus.service.method(CONSOLE_IFACE)
def toggle_visibility(self):
def ToggleVisibility(self):
window = self._console.window
if not window.props.visible:
window.present()

View File

@ -69,16 +69,16 @@ class BundleRegistry(gobject.GObject):
def __init__(self):
gobject.GObject.__init__(self)
self._bundles = {}
self._bundles = []
self._search_path = []
self._service_manager = _ServiceManager()
def get_bundle(self, service_name):
"""Returns an bundle given his service name"""
if self._bundles.has_key(service_name):
return self._bundles[service_name]
else:
return None
for bundle in self._bundles:
if bundle.get_service_name() == service_name:
return bundle
return None
def add_search_path(self, path):
"""Add a directory to the bundles search path"""
@ -86,20 +86,30 @@ class BundleRegistry(gobject.GObject):
self._scan_directory(path)
def __iter__(self):
return self._bundles.values().__iter__()
return self._bundles.__iter__()
def _scan_directory(self, path):
if os.path.isdir(path):
for f in os.listdir(path):
bundle_dir = os.path.join(path, f)
if os.path.isdir(bundle_dir) and \
bundle_dir.endswith('.activity'):
self.add_bundle(bundle_dir)
if not os.path.isdir(path):
return
# Sort by mtime to ensure a stable activity order
bundles = {}
for f in os.listdir(path):
if not f.endswith('.activity'):
continue
bundle_dir = os.path.join(path, f)
if os.path.isdir(bundle_dir):
bundles[bundle_dir] = os.stat(bundle_dir).st_mtime
bundle_dirs = bundles.keys()
bundle_dirs.sort(lambda d1,d2: cmp(bundles[d1], bundles[d2]))
for dir in bundle_dirs:
self.add_bundle(dir)
def add_bundle(self, bundle_path):
bundle = Bundle(bundle_path)
if bundle.is_valid():
self._bundles[bundle.get_service_name()] = bundle
self._bundles.append(bundle)
self._service_manager.add(bundle)
self.emit('bundle-added', bundle)
return True
@ -108,7 +118,7 @@ class BundleRegistry(gobject.GObject):
def get_activities_for_type(self, mime_type):
result = []
for bundle in self._bundles.values():
for bundle in self._bundles:
if bundle.get_mime_types() and mime_type in bundle.get_mime_types():
result.append(bundle)
return result

View File

@ -65,16 +65,15 @@ class ClipboardObject:
#return self._get_type_info().get_preview()
return ''
def get_activity(self):
def get_activities(self):
mime = self.get_mime_type()
if not mime:
return ''
registry = bundleregistry.get_registry()
activities = registry.get_activities_for_type(self.get_mime_type())
# TODO: should we return several activities?
if activities:
return activities[0].get_service_name()
return [activity.get_service_name() for activity in activities]
else:
return ''
@ -102,8 +101,11 @@ class ClipboardObject:
if len(uris) == 1 or not uris[1]:
uri = urlparse.urlparse(uris[0], 'file')
if uri.scheme == 'file':
logging.debug('Choosed %r!' % mime.get_for_file(uri.path))
format = mime.get_for_file(uri.path)
if os.path.exists(uri.path):
format = mime.get_for_file(uri.path)
else:
format = mime.get_from_file_name(uri.path)
logging.debug('Choosed %r!' % format)
return format

View File

@ -33,7 +33,7 @@ NAME_KEY = 'NAME'
PERCENT_KEY = 'PERCENT'
ICON_KEY = 'ICON'
PREVIEW_KEY = 'PREVIEW'
ACTIVITY_KEY = 'ACTIVITY'
ACTIVITIES_KEY = 'ACTIVITIES'
FORMATS_KEY = 'FORMATS'
TYPE_KEY = 'TYPE'
@ -87,7 +87,7 @@ class ClipboardService(dbus.service.Object):
PERCENT_KEY: cb_object.get_percent(),
ICON_KEY: cb_object.get_icon(),
PREVIEW_KEY: cb_object.get_preview(),
ACTIVITY_KEY: cb_object.get_activity()})
ACTIVITIES_KEY: cb_object.get_activities()})
@dbus.service.method(_CLIPBOARD_DBUS_INTERFACE,
in_signature="o", out_signature="")
@ -121,7 +121,7 @@ class ClipboardService(dbus.service.Object):
PERCENT_KEY: percent,
ICON_KEY: cb_object.get_icon(),
PREVIEW_KEY: cb_object.get_preview(),
ACTIVITY_KEY: cb_object.get_activity()})
ACTIVITIES_KEY: cb_object.get_activities()})
@dbus.service.method(_CLIPBOARD_DBUS_INTERFACE,
in_signature="o", out_signature="a{sv}")
@ -142,7 +142,7 @@ class ClipboardService(dbus.service.Object):
PERCENT_KEY: cb_object.get_percent(),
ICON_KEY: cb_object.get_icon(),
PREVIEW_KEY: cb_object.get_preview(),
ACTIVITY_KEY: cb_object.get_activity(),
ACTIVITIES_KEY: cb_object.get_activities(),
FORMATS_KEY: format_types}
return dbus.Dictionary(result_dict)

View File

@ -17,6 +17,8 @@
import dbus
import dbus.service
from gettext import gettext as _
_REGISTRY_IFACE = "org.laptop.ObjectTypeRegistry"
_REGISTRY_PATH = "/org/laptop/ObjectTypeRegistry"
@ -28,13 +30,15 @@ class ObjectTypeRegistry(dbus.service.Object):
self._types = {}
from gettext import gettext as _
self._add_primitive('Text', _('Text'), 'theme:object-text',
self._add_primitive('Text', _('Text'), 'theme:text',
[ 'text/plain', 'text/rtf', 'application/pdf',
'application/x-pdf', 'text/html',
'application/vnd.oasis.opendocument.text' ])
self._add_primitive('Image', _('Image'), 'theme:object-image',
'application/vnd.oasis.opendocument.text',
'application/rtf', 'text/rtf' ])
self._add_primitive('Image', _('Image'), 'theme:image',
[ 'image/png', 'image/gif', 'image/jpeg' ])
self._add_primitive('Audio', _('Audio'), 'theme:audio', [ 'audio/ogg' ])
self._add_primitive('Video', _('Video'), 'theme:video', [ 'video/ogg' ])
def _add_primitive(self, type_id, name, icon, mime_types):
object_type = {'type_id': type_id,

View File

@ -26,7 +26,7 @@ class ColorPicker(hippo.CanvasBox, hippo.CanvasItem):
self.props.orientation = hippo.ORIENTATION_HORIZONTAL
self._xo = CanvasIcon(size=style.XLARGE_ICON_SIZE,
icon_name='theme:stock-buddy')
icon_name='theme:xo')
self._set_random_colors()
self._xo.connect('activated', self._xo_activated_cb)
self.append(self._xo)

View File

@ -103,7 +103,7 @@ class HomeActivity(gobject.GObject):
if self._activity_info:
return self._activity_info.icon
else:
return 'theme:stock-missing'
return 'theme:image-missing'
def get_icon_color(self):
"""Retrieve the appropriate icon colour for this activity

View File

@ -107,15 +107,15 @@ class HomeModel(gobject.GObject):
if self._active_activity:
service = self._active_activity.get_service()
if service:
service.set_active(False,
reply_handler=self._set_active_success,
error_handler=self._set_active_error)
service.SetActive(False,
reply_handler=self._set_active_success,
error_handler=self._set_active_error)
if home_activity:
service = home_activity.get_service()
if service:
service.set_active(True,
reply_handler=self._set_active_success,
error_handler=self._set_active_error)
service.SetActive(True,
reply_handler=self._set_active_success,
error_handler=self._set_active_error)
self._active_activity = home_activity
self.emit('active-activity-changed', self._active_activity)

View File

@ -20,7 +20,7 @@ from view.BuddyMenu import BuddyMenu
class BuddyIcon(CanvasIcon):
def __init__(self, shell, buddy):
CanvasIcon.__init__(self, icon_name='theme:stock-buddy',
CanvasIcon.__init__(self, icon_name='theme:xo',
xo_color=buddy.get_color())
self._shell = shell

View File

@ -80,10 +80,10 @@ class BuddyMenu(Palette):
friends = shell_model.get_friends()
if friends.has_buddy(self._buddy):
menu_item = MenuItem(_('Remove friend'), 'stock-remove')
menu_item = MenuItem(_('Remove friend'), 'list-remove')
menu_item.connect('activate', self._remove_friend_cb)
else:
menu_item = MenuItem(_('Make friend'), 'stock-add')
menu_item = MenuItem(_('Make friend'), 'list-add')
menu_item.connect('activate', self._make_friend_cb)
self.menu.append(menu_item)

View File

@ -88,7 +88,7 @@ class ClipboardIcon(CanvasIcon):
if icon_name:
self.props.icon_name = icon_name
else:
self.props.icon_name = 'theme:object-generic'
self.props.icon_name = 'theme:unknown-object'
self._name = name
self._percent = percent

View File

@ -30,16 +30,17 @@ from sugar.clipboard import clipboardservice
from sugar.datastore import datastore
from sugar.objects import mime
from sugar import profile
from sugar import activity
class ClipboardMenu(Palette):
def __init__(self, object_id, name, percent, preview, activity, installable):
def __init__(self, object_id, name, percent, preview, activities, installable):
Palette.__init__(self, name)
self.props.position = Palette.RIGHT
self._object_id = object_id
self._percent = percent
self._activity = activity
self._activities = activities
self.set_group_id('frame')
@ -62,13 +63,14 @@ class ClipboardMenu(Palette):
self.append(self._preview_text)
"""
self._remove_item = MenuItem(_('Remove'), 'stock-remove')
self._remove_item = MenuItem(_('Remove'), 'list-remove')
self._remove_item.connect('activate', self._remove_item_activate_cb)
self.menu.append(self._remove_item)
self._remove_item.show()
self._open_item = MenuItem(_('Open'), 'stock-keep')
self._open_item.connect('activate', self._open_item_activate_cb)
self._open_item = MenuItem(_('Open'))
self._open_item_activate_sid = self._open_item.connect('activate',
self._open_item_activate_cb)
self.menu.append(self._open_item)
self._open_item.show()
@ -83,14 +85,49 @@ class ClipboardMenu(Palette):
self._journal_item.show()
self._update_items_visibility(installable)
self._update_open_submenu()
def _update_open_submenu(self):
submenu = self._open_item.get_submenu()
if submenu:
for item in submenu.get_children():
submenu.remove(item)
if self._activities is None or len(self._activities) <= 1:
if self._open_item_activate_sid is None:
self._open_item_activate_sid = self._open_item.connect(
'activate',
self._open_item_activate_cb)
return
else:
if self._open_item_activate_sid is not None:
self._open_item.disconnect(self._open_item_activate_sid)
self._open_item_activate_sid = None
if not submenu:
submenu = gtk.Menu()
self._open_item.set_submenu(submenu)
submenu.show()
for service_name in self._activities:
registry = activity.get_registry()
activity_info = registry.get_activity(service_name)
if not activity_info:
logging.warning('Activity %s is unknown.' % service_name)
item = gtk.MenuItem(activity_info.name)
item.connect('activate', self._open_submenu_item_activate_cb, service_name)
submenu.append(item)
item.show()
def _update_items_visibility(self, installable):
if self._percent == 100 and (self._activity or installable):
if self._percent == 100 and (self._activities or installable):
self._remove_item.props.sensitive = True
self._open_item.props.sensitive = True
#self._stop_item.props.sensitive = False
self._journal_item.props.sensitive = True
elif self._percent == 100 and (not self._activity and not installable):
elif self._percent == 100 and (not self._activities and not installable):
self._remove_item.props.sensitive = True
self._open_item.props.sensitive = False
#self._stop_item.props.sensitive = False
@ -112,26 +149,36 @@ class ClipboardMenu(Palette):
self._progress_bar.props.fraction = self._percent / 100.0
self._progress_bar.props.text = '%.2f %%' % self._percent
def set_state(self, name, percent, preview, activity, installable):
def set_state(self, name, percent, preview, activities, installable):
self.set_primary_text(name)
self._percent = percent
self._activity = activity
self._activities = activities
if self._progress_bar:
self._update_progress_bar()
self._update_items_visibility(installable)
self._update_open_submenu()
def _open_item_activate_cb(self, menu_item):
if self._percent < 100:
return
jobject = self._copy_to_journal()
jobject.resume()
jobject.resume(self._activities[0])
jobject.destroy()
def _open_submenu_item_activate_cb(self, menu_item, service_name):
if self._percent < 100:
return
jobject = self._copy_to_journal()
jobject.resume(service_name)
jobject.destroy()
def _remove_item_activate_cb(self, menu_item):
cb_service = clipboardservice.get_instance()
cb_service.delete_object(self._object_id)
def _journal_item_activate_cb(self, menu_item):
self._copy_to_journal()
jobject = self._copy_to_journal()
jobject.destroy()
def _copy_to_journal(self):
cb_service = clipboardservice.get_instance()

View File

@ -21,7 +21,7 @@ from sugar.graphics import canvasicon
from sugar.graphics import style
from sugar.graphics.palette import Palette
_ICON_NAME = 'device-battery'
_ICON_NAME = 'battery'
_STATUS_CHARGING = 0
_STATUS_DISCHARGING = 1

View File

@ -22,7 +22,7 @@ from model.devices import device
class DeviceView(canvasicon.CanvasIcon):
def __init__(self, model):
canvasicon.CanvasIcon.__init__(self, size=style.MEDIUM_ICON_SIZE,
icon_name='theme:device-network-mesh')
icon_name='theme:network-mesh')
self._model = model
model.connect('notify::state', self._state_changed_cb)

View File

@ -19,4 +19,4 @@ from view.devices import deviceview
class DeviceView(deviceview.DeviceView):
def __init__(self, model):
deviceview.DeviceView.__init__(self, model)
self.props.icon_name = 'theme:stock-net-wired'
self.props.icon_name = 'theme:network-wired'

View File

@ -22,7 +22,7 @@ from model.devices.network import wireless
from sugar.graphics.canvasicon import CanvasIcon
from model.devices import device
_ICON_NAME = 'device-network-wireless'
_ICON_NAME = 'network-wireless'
class DeviceView(CanvasIcon):
def __init__(self, model):

View File

@ -16,22 +16,29 @@
import hippo
from sugar.graphics.palette import Palette
from sugar.graphics.canvasicon import CanvasIcon
from sugar.graphics import style
from sugar.presence import presenceservice
from view.BuddyIcon import BuddyIcon
from model.BuddyModel import BuddyModel
from view.frame.frameinvoker import FrameCanvasInvoker
class FriendIcon(BuddyIcon):
def __init__(self, shell, buddy):
BuddyIcon.__init__(self, shell, buddy)
self.get_palette().set_group_id('frame')
palette = self.get_palette()
palette.set_group_id('frame')
palette.props.position = Palette.AROUND
palette.props.invoker = FrameCanvasInvoker(self)
def prelight(self, enter):
if enter:
self.props.background_color = style.COLOR_BLACK.get_int()
else:
self.props.background_color = style.COLOR_TOOLBAR.GREY.get_int()
self.props.background_color = style.COLOR_TOOLBAR_GREY.get_int()
class FriendsBox(hippo.CanvasBox):
def __init__(self, shell):

View File

@ -75,7 +75,7 @@ class ClipboardBox(hippo.CanvasBox):
return self._owns_clipboard
def _get_icon_at_coords(self, x, y):
box_x, box_y = self.get_context().get_position(self)
box_x, box_y = self.get_context().translate_to_widget(self)
x -= box_x
y -= box_y
for object_id, icon in self._icons.iteritems():
@ -92,7 +92,7 @@ class ClipboardBox(hippo.CanvasBox):
if not selection.data:
return
logging.debug('ClipboardBox: adding type ' + selection.type + ' ' + selection.data)
logging.debug('ClipboardBox: adding type ' + selection.type)
cb_service = clipboardservice.get_instance()
if selection.type == 'text/uri-list':

View File

@ -36,10 +36,8 @@ class ClipboardPanelWindow(FrameWindow):
self._clipboard = gtk.Clipboard()
self._clipboard.connect("owner-change", self._owner_change_cb)
root = self.get_root()
self._clipboard_box = ClipboardBox()
root.append(self._clipboard_box)
self.append(self._clipboard_box)
# Receiving dnd drops
self.drag_dest_set(0, [], 0)

View File

@ -208,11 +208,10 @@ class Frame(object):
self._right_panel.hover)
def _create_top_panel(self):
panel = self._create_panel(hippo.ORIENTATION_HORIZONTAL)
root = panel.get_root()
panel = self._create_panel(gtk.POS_TOP)
box = ZoomBox(self._shell)
root.append(box)
panel.append(box)
#box = OverlayBox(self._shell)
#root.append(box, hippo.PACK_END)
@ -220,25 +219,23 @@ class Frame(object):
return panel
def _create_bottom_panel(self):
panel = self._create_panel(hippo.ORIENTATION_HORIZONTAL)
root = panel.get_root()
panel = self._create_panel(gtk.POS_BOTTOM)
box = ActivitiesBox(self._shell)
root.append(box)
panel.append(box)
return panel
def _create_right_panel(self):
panel = self._create_panel(hippo.ORIENTATION_VERTICAL)
root = panel.get_root()
panel = self._create_panel(gtk.POS_RIGHT)
box = FriendsBox(self._shell)
root.append(box)
panel.append(box)
return panel
def _create_left_panel(self):
panel = ClipboardPanelWindow(self, hippo.ORIENTATION_VERTICAL)
panel = ClipboardPanelWindow(self, gtk.POS_LEFT)
self._connect_to_panel(panel)
panel.connect('drag-motion', self._drag_motion_cb)
@ -271,16 +268,16 @@ class Frame(object):
screen_w = gtk.gdk.screen_width()
self._move_panel(self._top_panel, self._current_position,
0, - style.GRID_CELL_SIZE, 0, 0)
0, - self._top_panel.size, 0, 0)
self._move_panel(self._bottom_panel, self._current_position,
0, screen_h, 0, screen_h - style.GRID_CELL_SIZE)
0, screen_h, 0, screen_h - self._bottom_panel.size)
self._move_panel(self._left_panel, self._current_position,
- style.GRID_CELL_SIZE, 0, 0, 0)
- self._left_panel.size, 0, 0, 0)
self._move_panel(self._right_panel, self._current_position,
screen_w, 0, screen_w - style.GRID_CELL_SIZE, 0)
screen_w, 0, screen_w - self._right_panel.size, 0)
def _hide_completed_cb(self, animator):
self.mode = MODE_NONE

View File

@ -29,7 +29,7 @@ class FrameCanvasInvoker(CanvasInvoker):
return Palette.AROUND
def get_screen_area(self):
frame_thickness = style.zoom(75)
frame_thickness = style.GRID_CELL_SIZE
x = y = frame_thickness
width = gtk.gdk.screen_width() - frame_thickness

View File

@ -21,11 +21,13 @@ from sugar.graphics import style
class FrameWindow(gtk.Window):
__gtype_name__ = 'SugarFrameWindow'
def __init__(self, orientation):
def __init__(self, position):
gtk.Window.__init__(self)
self.hover = False
self.size = style.GRID_CELL_SIZE + style.LINE_WIDTH
self._orientation = orientation
self._position = position
self.set_decorated(False)
self.connect('realize', self._realize_cb)
@ -36,36 +38,55 @@ class FrameWindow(gtk.Window):
self.add(self._canvas)
self._canvas.show()
self._bg = hippo.CanvasBox(orientation=self._orientation)
self._canvas.set_root(self._bg)
box = hippo.CanvasBox()
self._canvas.set_root(box)
padding = style.GRID_CELL_SIZE
if self._position == gtk.POS_TOP or self._position == gtk.POS_BOTTOM:
box.props.orientation = hippo.ORIENTATION_HORIZONTAL
box.props.padding_left = padding
box.props.padding_right = padding
box.props.padding_top = 0
box.props.padding_bottom = 0
else:
box.props.orientation = hippo.ORIENTATION_VERTICAL
box.props.padding_left = 0
box.props.padding_right = 0
box.props.padding_top = padding
box.props.padding_bottom = padding
self._bg = hippo.CanvasBox(
border_color=style.COLOR_BUTTON_GREY.get_int())
border = style.LINE_WIDTH
if position == gtk.POS_TOP:
self._bg.props.orientation = hippo.ORIENTATION_HORIZONTAL
self._bg.props.border_bottom = border
elif position == gtk.POS_BOTTOM:
self._bg.props.orientation = hippo.ORIENTATION_HORIZONTAL
self._bg.props.border_top = border
elif position == gtk.POS_LEFT:
self._bg.props.orientation = hippo.ORIENTATION_VERTICAL
self._bg.props.border_right = border
elif position == gtk.POS_RIGHT:
self._bg.props.orientation = hippo.ORIENTATION_VERTICAL
self._bg.props.border_left = border
box.append(self._bg, hippo.PACK_EXPAND)
self._update_size()
screen = gtk.gdk.screen_get_default()
screen.connect('size-changed', self._size_changed_cb)
def get_root(self):
return self._bg
def append(self, child, flags=0):
self._bg.append(child, flags)
def _update_size(self):
padding = style.GRID_CELL_SIZE
if self._orientation == hippo.ORIENTATION_HORIZONTAL:
self._bg.props.padding_left = padding
self._bg.props.padding_right = padding
self._bg.props.padding_top = 0
self._bg.props.padding_bottom = 0
width = gtk.gdk.screen_width()
height = style.GRID_CELL_SIZE
if self._position == gtk.POS_TOP or self._position == gtk.POS_BOTTOM:
self.resize(gtk.gdk.screen_width(), self.size)
else:
self._bg.props.padding_left = 0
self._bg.props.padding_right = 0
self._bg.props.padding_top = padding
self._bg.props.padding_bottom = padding
width = style.GRID_CELL_SIZE
height = gtk.gdk.screen_height()
self.resize(width, height)
self.resize(self.size, gtk.gdk.screen_height())
def _realize_cb(self, widget):
self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)

View File

@ -63,6 +63,11 @@ class HomeBox(hippo.CanvasBox, hippo.CanvasItem):
devices_model.connect('device-disappeared',
self._device_disappeared_cb)
self._redraw_id = None
def __del__(self):
self.suspend()
def _add_device(self, device):
view = deviceview.create(device)
self.append(view, hippo.PACK_FIXED)
@ -103,6 +108,23 @@ class HomeBox(hippo.CanvasBox, hippo.CanvasItem):
i += 1
_REDRAW_TIMEOUT = 5 * 60 * 1000 # 5 minutes
def resume(self):
self._redraw_activity_ring()
if self._redraw_id is None:
self._redraw_id = gobject.timeout_add(self._REDRAW_TIMEOUT,
self._redraw_activity_ring)
def suspend(self):
if self._redraw_id is not None:
gobject.source_remove(self._redraw_id)
self._redraw_id = None
def _redraw_activity_ring(self):
self._donut.emit_request_changed()
return True
def has_activities(self):
return self._donut.has_activities()

View File

@ -64,6 +64,7 @@ class HomeWindow(gtk.Window):
self._mesh_box = MeshBox(shell)
self._transition_box = TransitionBox()
self._activate_view()
self._canvas.set_root(self._home_box)
self._transition_box.connect('completed',
@ -93,22 +94,28 @@ class HomeWindow(gtk.Window):
if keyname == "Alt_L":
self._home_box.release()
def _update_mesh_state(self):
if self._active and self._level == ShellModel.ZOOM_MESH:
self._mesh_box.resume()
else:
def _deactivate_view(self):
if self._level == ShellModel.ZOOM_HOME:
self._home_box.suspend()
elif self._level == ShellModel.ZOOM_MESH:
self._mesh_box.suspend()
def _activate_view(self):
if self._level == ShellModel.ZOOM_HOME:
self._home_box.resume()
elif self._level == ShellModel.ZOOM_MESH:
self._mesh_box.resume()
def _focus_in_cb(self, widget, event):
self._active = True
self._update_mesh_state()
self._activate_view()
def _focus_out_cb(self, widget, event):
self._active = False
self._update_mesh_state()
self._deactivate_view()
def set_zoom_level(self, level):
self._deactivate_view()
self._level = level
self._activate_view()
self._canvas.set_root(self._transition_box)
@ -129,7 +136,5 @@ class HomeWindow(gtk.Window):
elif self._level == ShellModel.ZOOM_MESH:
self._canvas.set_root(self._mesh_box)
self._update_mesh_state()
def get_home_box(self):
return self._home_box

View File

@ -36,7 +36,7 @@ from view.BuddyIcon import BuddyIcon
from view.pulsingicon import PulsingIcon
from view.home.snowflakelayout import SnowflakeLayout
_ICON_NAME = 'device-network-wireless'
_ICON_NAME = 'network-wireless'
class AccessPointView(PulsingIcon):
def __init__(self, model):
@ -116,7 +116,7 @@ class AccessPointView(PulsingIcon):
]
_MESH_ICON_NAME = 'theme:device-network-mesh'
_MESH_ICON_NAME = 'theme:network-mesh'
class MeshDeviceView(PulsingIcon):
def __init__(self, nm_device):

View File

@ -20,5 +20,5 @@ from sugar import profile
class MyIcon(CanvasIcon):
def __init__(self, size):
CanvasIcon.__init__(self, size=size,
icon_name='theme:stock-buddy',
icon_name='theme:xo',
xo_color=profile.get_color())

View File

@ -16,6 +16,7 @@
import colorsys
from gettext import gettext as _
import logging
import math
import hippo
@ -317,10 +318,12 @@ class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem):
'expected format' % pid)
# Next, see how much free memory is left.
free_memory = 0
try:
meminfo = open('/proc/meminfo')
meminfo.readline()
free_memory = int(meminfo.readline()[9:-3])
for line in meminfo.readlines():
if line.startswith('MemFree:') or line.startswith('SwapFree:'):
free_memory += int(line[9:-3])
meminfo.close()
except IOError:
logging.warn('ActivitiesDonut: could not read /proc/meminfo')

View File

@ -28,7 +28,7 @@ from sugar._sugaruiext import KeyGrabber
_BRIGHTNESS_STEP = 2
_VOLUME_STEP = 10
_BRIGTHNESS_MAX = 15
_BRIGHTNESS_MAX = 15
_VOLUME_MAX = 100
_actions_table = {
@ -224,4 +224,4 @@ class KeyHandler(object):
proxy = bus.get_object('org.laptop.sugar.Console',
'/org/laptop/sugar/Console')
console = dbus.Interface(proxy, 'org.laptop.sugar.Console')
console.toggle_visibility()
console.ToggleVisibility()

View File

@ -96,7 +96,7 @@ def _start_matchbox():
cmd = ['matchbox-window-manager']
cmd.extend(['-use_titlebar', 'no'])
cmd.extend(['-theme', 'olpc'])
cmd.extend(['-theme', 'sugar'])
log.debug( 'Matchbox command: %s', " ".join( cmd) )
gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
@ -148,16 +148,20 @@ def main():
os.environ['SUGAR_XO_STYLE'] = 'no'
os.environ['GTK2_RC_FILES'] = env.get_data_path(gtkrc_filename)
print os.environ['GTK2_RC_FILES']
command = ['dbus-launch', 'dbus-launch', '--exit-with-session']
if not args:
program = 'sugar-shell'
command.append('sugar-shell')
else:
_start_matchbox()
program = args[0]
if args[0].endswith('.py'):
command.append('python')
command.append(args[0])
command = ['dbus-launch', 'dbus-launch', '--exit-with-session', program]
log.info( "Attempting to launch sugar to replace this process: %s", " ".join(command) )
log.info( "Attempting to launch sugar to replace this process: %s", " ".join(command))
os.execlp( *command )
if __name__ == "__main__":

View File

@ -22,8 +22,25 @@
(gtype-id "SUGAR_TYPE_MENU")
)
(define-object IconEntry
(in-module "Sexy")
(parent "GtkEntry")
(c-name "SexyIconEntry")
(gtype-id "SEXY_TYPE_ICON_ENTRY")
)
;; Enumerations and flags ...
(define-enum IconEntryPosition
(in-module "Sexy")
(c-name "SexyIconEntryPosition")
(gtype-id "SEXY_TYPE_ICON_ENTRY_POSITION")
(values
'("primary" "SEXY_ICON_ENTRY_PRIMARY")
'("secondary" "SEXY_ICON_ENTRY_SECONDARY")
)
)
;; From sugar-menu.h
(define-method set_active
@ -94,3 +111,61 @@
'("const-char*" "property")
)
)
;; From sexy-icon-entry.h
(define-function sexy_icon_entry_get_type
(c-name "sexy_icon_entry_get_type")
(return-type "GType")
)
(define-function sexy_icon_entry_new
(c-name "sexy_icon_entry_new")
(is-constructor-of "SexyIconEntry")
(return-type "GtkWidget*")
)
(define-method set_icon
(of-object "SexyIconEntry")
(c-name "sexy_icon_entry_set_icon")
(return-type "none")
(parameters
'("SexyIconEntryPosition" "position")
'("GtkImage*" "icon")
)
)
(define-method set_icon_highlight
(of-object "SexyIconEntry")
(c-name "sexy_icon_entry_set_icon_highlight")
(return-type "none")
(parameters
'("SexyIconEntryPosition" "position")
'("gboolean" "highlight")
)
)
(define-method get_icon
(of-object "SexyIconEntry")
(c-name "sexy_icon_entry_get_icon")
(return-type "GtkImage*")
(parameters
'("SexyIconEntryPosition" "position")
)
)
(define-method get_icon_highlight
(of-object "SexyIconEntry")
(c-name "sexy_icon_entry_get_icon_highlight")
(return-type "gboolean")
(parameters
'("SexyIconEntryPosition" "position")
)
)
(define-method add_clear_button
(of-object "SexyIconEntry")
(c-name "sexy_icon_entry_add_clear_button")
(return-type "none")
)

View File

@ -8,6 +8,7 @@ headers
#include "sugar-key-grabber.h"
#include "sugar-menu.h"
#include "sugar-x11-util.h"
#include "sexy-icon-entry.h"
#include <pygtk/pygtk.h>
#include <glib.h>
@ -20,6 +21,7 @@ import gtk.Entry as PyGtkEntry_Type
import gtk.Menu as PyGtkMenu_Type
import gtk.Container as PyGtkContainer_Type
import gtk.gdk.Window as PyGdkWindow_Type
import gtk.Image as PyGtkImage_Type
%%
ignore-glob
*_get_type

View File

@ -27,6 +27,7 @@
extern PyMethodDef py_sugaruiext_functions[];
void py_sugaruiext_register_classes (PyObject *d);
void py_sugaruiext_add_constants (PyObject *module, const gchar *strip_prefix);
DL_EXPORT(void)
init_sugaruiext(void)
@ -39,6 +40,7 @@ init_sugaruiext(void)
d = PyModule_GetDict (m);
py_sugaruiext_register_classes (d);
py_sugaruiext_add_constants(m, "SEXY_");
if (PyErr_Occurred ()) {
Py_FatalError ("can't initialise module _sugaruiext");

View File

@ -52,6 +52,8 @@ class ActivityToolbar(gtk.Toolbar):
self._activity = activity
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()
@ -74,11 +76,11 @@ class ActivityToolbar(gtk.Toolbar):
'theme:zoom-home-mini')
self.share.combo.append_item(None, _('My Neighborhood'),
'theme:zoom-neighborhood-mini')
self._update_share()
self.insert(self.share, -1)
self.share.show()
self._update_share()
self.keep = ToolButton('document-save')
self.keep.set_tooltip(_('Keep'))
self.keep.connect('clicked', self._keep_clicked_cb)
@ -94,6 +96,9 @@ class ActivityToolbar(gtk.Toolbar):
self._update_title_sid = None
def _update_share(self):
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(self.SHARE_NEIGHBORHOOD)
@ -139,6 +144,9 @@ class ActivityToolbar(gtk.Toolbar):
def _activity_shared_cb(self, activity):
self._update_share()
def _max_participants_changed_cb(self, activity, pspec):
self._update_share()
class EditToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
@ -185,7 +193,10 @@ class Activity(Window, gtk.Container):
}
__gproperties__ = {
'active': (bool, None, None, False, gobject.PARAM_READWRITE)
'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):
@ -235,6 +246,7 @@ class Activity(Window, gtk.Container):
self._preview = None
self._updating_jobject = False
self._closing = False
self._max_participants = 0
shared_activity = handle.get_shared_activity()
if shared_activity:
@ -280,10 +292,14 @@ class Activity(Window, gtk.Container):
self._active = value
if not self._active and self._jobject:
self.save()
elif pspec.name == 'max-participants':
self._max_participants = value
def do_get_property(self, pspec):
if pspec.name == 'active':
return self._active
elif pspec.name == 'max-participants':
return self._max_participants
def get_id(self):
return self._activity_id

View File

@ -56,6 +56,6 @@ class ActivityService(dbus.service.Object):
self._activity = activity
@dbus.service.method(_ACTIVITY_INTERFACE)
def set_active(self, active):
def SetActive(self, active):
logging.debug('ActivityService.set_active: %s.' % active)
self._activity.props.active = active

View File

@ -21,6 +21,7 @@ import zipfile
import shutil
import subprocess
import re
import gettext
from sugar import env
from sugar.activity.bundle import Bundle
@ -117,7 +118,7 @@ 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 genmo - compile gettext po files in mo \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\
@ -157,17 +158,26 @@ def _get_po_list(manifest):
return file_list
def _get_mo_list(manifest):
mo_list = []
def _get_l10n_list(manifest):
l10n_list = []
for lang in _get_po_list(manifest).keys():
filename = _get_service_name() + '.mo'
mo_list.append(os.path.join('locale', lang, 'LC_MESSAGES', filename))
l10n_list.append(os.path.join('locale', lang, 'LC_MESSAGES', filename))
l10n_list.append(os.path.join('locale', lang, 'activity.linfo'))
return mo_list
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_genmo(bundle_name, manifest)
cmd_genl10n(bundle_name, manifest)
file_list = _get_file_list(manifest)
zipname = _get_package_name(bundle_name)
@ -177,7 +187,7 @@ def cmd_dist(bundle_name, manifest):
for filename in file_list:
bundle_zip.write(filename, os.path.join(base_dir, filename))
for filename in _get_mo_list(manifest):
for filename in _get_l10n_list(manifest):
bundle_zip.write(filename, os.path.join(base_dir, filename))
bundle_zip.close()
@ -205,8 +215,21 @@ def cmd_genpot(bundle_name, manifest):
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)
args = [ 'xgettext', '--language=Python',
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=_', '--output=%s' % pot_file ]
args += python_files
@ -220,14 +243,16 @@ def cmd_genpot(bundle_name, manifest):
if retcode:
print 'ERROR - msgmerge failed with return code %i.' % retcode
def cmd_genmo(bundle_name, manifest):
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]
mo_path = os.path.join(source_path, 'locale', lang, 'LC_MESSAGES')
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)
@ -237,6 +262,13 @@ def cmd_genmo(bundle_name, manifest):
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'
@ -358,8 +390,8 @@ def start(bundle_name, manifest='MANIFEST'):
cmd_uninstall(sys.argv[2])
elif sys.argv[1] == 'genpot':
cmd_genpot(bundle_name, manifest)
elif sys.argv[1] == 'genmo':
cmd_genmo(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':

View File

@ -23,7 +23,7 @@ NAME_KEY = 'NAME'
PERCENT_KEY = 'PERCENT'
ICON_KEY = 'ICON'
PREVIEW_KEY = 'PREVIEW'
ACTIVITY_KEY = 'ACTIVITY'
ACTIVITIES_KEY = 'ACTIVITIES'
FORMATS_KEY = 'FORMATS'
TYPE_KEY = 'TYPE'
@ -51,7 +51,7 @@ class ClipboardService(gobject.GObject):
'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, str])),
([str, str, int, str, str, object])),
}
def __init__(self):
@ -118,13 +118,13 @@ class ClipboardService(gobject.GObject):
percent
icon
preview
activity
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[ACTIVITY_KEY])
values[ACTIVITIES_KEY])
def add_object(self, name):
"""Add a new object to the path
@ -193,7 +193,7 @@ class ClipboardService(gobject.GObject):
PERCENT_KEY: number,
ICON_KEY: str,
PREVIEW_KEY: XXX what is it?,
ACTIVITY_KEY: source activity id,
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),)

View File

@ -120,6 +120,9 @@ class DSObject(object):
def resume(self, service_name=None):
if self.is_bundle():
if service_name is not None:
raise ValueError('Object is a bundle, cannot be resumed as an activity.')
bundle = Bundle(self.file_path)
if not bundle.is_installed():
bundle.install()

View File

@ -28,55 +28,63 @@ DS_DBUS_SERVICE = "org.laptop.sugar.DataStore"
DS_DBUS_INTERFACE = "org.laptop.sugar.DataStore"
DS_DBUS_PATH = "/org/laptop/sugar/DataStore"
_bus = dbus.SessionBus()
_data_store = dbus.Interface(_bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH),
DS_DBUS_INTERFACE)
_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):
object_id = _data_store.create(dbus.Dictionary(properties), filename)
object_id = _get_data_store().create(dbus.Dictionary(properties), filename)
logging.debug('dbus_helpers.create: ' + object_id)
return object_id
def update(uid, properties, filename, reply_handler=None, error_handler=None, timeout=-1):
logging.debug('dbus_helpers.update: %s, %s, %s' % (uid, filename, properties))
if reply_handler and error_handler:
_data_store.update(uid, dbus.Dictionary(properties), filename,
_get_data_store().update(uid, dbus.Dictionary(properties), filename,
reply_handler=reply_handler,
error_handler=error_handler,
timeout=timeout)
else:
_data_store.update(uid, dbus.Dictionary(properties), filename)
_get_data_store().update(uid, dbus.Dictionary(properties), filename)
def delete(uid):
logging.debug('dbus_helpers.delete: %r' % uid)
_data_store.delete(uid)
_get_data_store().delete(uid)
def get_properties(uid):
logging.debug('dbus_helpers.get_properties: %s' % uid)
return _data_store.get_properties(uid)
return _get_data_store().get_properties(uid)
def get_filename(uid):
filename = _data_store.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, reply_handler, error_handler):
logging.debug('dbus_helpers.find: %r' % query)
if reply_handler and error_handler:
return _data_store.find(query, reply_handler=reply_handler,
return _get_data_store().find(query, reply_handler=reply_handler,
error_handler=error_handler)
else:
return _data_store.find(query)
return _get_data_store().find(query)
def mount(uri, options):
return _data_store.mount(uri, options)
return _get_data_store().mount(uri, options)
def unmount(mount_point_id):
_data_store.unmount(mount_point_id)
_get_data_store().unmount(mount_point_id)
def mounts():
return _data_store.mounts()
return _get_data_store().mounts()
def get_unique_values(key):
return _data_store.get_uniquevaluesfor(key, dbus.Dictionary({}, signature='ss'))
return _get_data_store().get_uniquevaluesfor(key, dbus.Dictionary({}, signature='ss'))

View File

@ -21,12 +21,13 @@ import datetime
class Date(object):
"""Date-object storing a simple time.time() float
XXX not sure about the rationale for this class,
possibly it makes transfer over dbus easier?
Useful to display dates in the UI in an
abbreviated and easy to read format.
"""
def __init__(self, timestamp):
"""Initialise via a timestamp (floating point value)"""
self._today = datetime.date.today()
self._timestamp = timestamp
def __str__(self):
@ -39,14 +40,13 @@ class Date(object):
the year in the date.
"""
date = datetime.date.fromtimestamp(self._timestamp)
today = datetime.date.today()
# FIXME localization
if date == today:
if date == self._today:
result = 'Today'
elif date == today - datetime.timedelta(1):
elif date == self._today - datetime.timedelta(1):
result = 'Yesterday'
elif date.year == today.year:
elif date.year == self._today.year:
result = date.strftime('%B %d')
else:
result = date.strftime('%B %d, %Y')

View File

@ -9,7 +9,7 @@ sugar_PYTHON = \
combobox.py \
icon.py \
iconbutton.py \
menuitem.py \
iconentry.py \
notebook.py \
objectchooser.py \
radiotoolbutton.py \

View File

@ -25,7 +25,7 @@ class CanvasButton(hippo.CanvasButton):
hippo.CanvasButton.__init__(self, text=label)
if icon_name:
icon = Icon(icon_name, gtk.ICON_SIZE_BUTTON)
icon = Icon(icon_name,icon_size=gtk.ICON_SIZE_BUTTON)
self.props.widget.set_image(icon)
icon.show()

View File

@ -142,6 +142,8 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
gobject.PARAM_READWRITE),
'size' : (int, None, None, 0, 1024, 0,
gobject.PARAM_READWRITE),
'scale' : (int, None, None, 0, 1024, 0,
gobject.PARAM_READWRITE),
'cache' : (bool, None, None, False,
gobject.PARAM_READWRITE),
'active' : (bool, None, None, True,
@ -156,6 +158,7 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
self._buffers = {}
self._cur_buffer = None
self._size = 0
self._scale = 0
self._fill_color = None
self._stroke_color = None
self._icon_name = None
@ -210,6 +213,11 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
self._clear_buffers()
self._size = value
self.emit_request_changed()
elif pspec.name == 'scale':
if self._scale != value and not self._cache:
self._clear_buffers()
self._scale = value
self.emit_request_changed()
elif pspec.name == 'cache':
self._cache = value
elif pspec.name == 'active':
@ -277,6 +285,8 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
return self._active
elif pspec.name == 'badge-name':
return self._badge_name
elif pspec.name == 'scale':
return self._scale
def _get_icon_size(self, handle):
if handle:
@ -286,9 +296,11 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
return [0, 0]
def _get_size(self, handle):
if self._size == 0:
width, height = self._get_icon_size(handle)
else:
width, height = self._get_icon_size(handle)
if self._scale != 0:
width = int(width * self._scale)
height = int(height * self._scale)
elif self._size != 0:
width = height = self._size
return [width, height]

View File

@ -34,17 +34,18 @@ class Icon(gtk.Image):
gobject.PARAM_READWRITE)
}
def __init__(self, name, size=gtk.ICON_SIZE_LARGE_TOOLBAR, **kwargs):
def __init__(self, name, **kwargs):
self._constructed = False
self._fill_color = None
self._stroke_color = None
self._icon_name = name
self._size = size
self._theme = gtk.icon_theme_get_default()
self._data = None
gobject.GObject.__init__(self, **kwargs)
# If we have a non-styled-icon
if not self._fill_color and not self._stroke_color:
self._update_normal_icon()
self._constructed = True
self._update_icon()
def _get_pixbuf(self, data, width, height):
loader = gtk.gdk.PixbufLoader('svg')
@ -77,9 +78,12 @@ class Icon(gtk.Image):
source.set_state(gtk.STATE_INSENSITIVE)
icon_set.add_source(source)
self.set_from_icon_set(icon_set, self._size)
self.props.icon_set = icon_set
def _update_icon(self):
if not self._constructed:
return
if not self._fill_color and not self._stroke_color:
self._update_normal_icon()
return
@ -100,12 +104,12 @@ class Icon(gtk.Image):
self._data = data
# Redraw pixbuf
[w, h] = gtk.icon_size_lookup(self._size)
[w, h] = gtk.icon_size_lookup(self.props.icon_size)
pixbuf = self._get_pixbuf(self._data, w, h)
self.set_from_pixbuf(pixbuf)
def _get_real_name(self, name):
info = self._theme.lookup_icon(name, self._size, 0)
info = self._theme.lookup_icon(name, self.props.icon_size, 0)
if not info:
raise ValueError("Icon '" + name + "' not found.")
fname = info.get_filename()
@ -122,9 +126,16 @@ class Icon(gtk.Image):
elif pspec.name == 'stroke-color':
self._stroke_color = value
self._update_icon()
else:
gtk.Image.do_set_property(self, pspec, value)
if pspec.name == 'icon-size':
self._update_icon()
def do_get_property(self, pspec):
if pspec.name == 'fill-color':
return self._fill_color
elif pspec.name == 'stroke-color':
return self._stroke_color
else:
return gtk.Image.do_get_property(self, pspec)

View File

@ -0,0 +1,45 @@
# Copyright (C) 2007, One Laptop Per Child
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import gtk
from sugar import _sugaruiext
ICON_ENTRY_PRIMARY = _sugaruiext.ICON_ENTRY_PRIMARY
ICON_ENTRY_SECONDARY = _sugaruiext.ICON_ENTRY_SECONDARY
class IconEntry(_sugaruiext.IconEntry):
def set_icon_from_name(self, position, name):
icon_theme = gtk.icon_theme_get_default()
icon_info = icon_theme.lookup_icon(name,
gtk.ICON_SIZE_SMALL_TOOLBAR,
0)
pixbuf = gtk.gdk.pixbuf_new_from_file(icon_info.get_filename())
image = gtk.Image()
image.set_from_pixbuf(pixbuf)
image.show()
self.set_icon(position, image)
def set_icon(self, position, image):
if image.get_storage_type() not in [gtk.IMAGE_PIXBUF, gtk.IMAGE_STOCK]:
raise ValueError('Image must have a storage type of pixbuf or ' +
'stock, not %r.' % image.get_storage_type())
_sugaruiext.IconEntry.set_icon(self, position, image)

View File

@ -22,7 +22,7 @@ class MenuItem(gtk.ImageMenuItem):
def __init__(self, text_label, icon_name=None):
gtk.ImageMenuItem.__init__(self, text_label)
if icon_name:
icon = Icon(icon_name, gtk.ICON_SIZE_MENU)
icon = Icon(icon_name, icon_size=gtk.ICON_SIZE_MENU)
self.set_image(icon)
icon.show()

View File

@ -150,7 +150,7 @@ class CollapsedEntry(CanvasRoundBox):
self._icon_name = type.icon
if not self._icon_name:
self._icon_name = 'theme:stock-missing'
self._icon_name = 'theme:image-missing'
return self._icon_name

View File

@ -87,9 +87,7 @@ class Palette(gtk.Window):
'invoker' : (object, None, None,
gobject.PARAM_READWRITE),
'position' : (gobject.TYPE_INT, None, None, 0, 6,
0, gobject.PARAM_READWRITE),
'draw-gap' : (bool, None, None, False,
gobject.PARAM_READWRITE)
0, gobject.PARAM_READWRITE)
}
__gsignals__ = {
@ -114,7 +112,6 @@ class Palette(gtk.Window):
self._group_id = None
self._up = False
self._position = self.DEFAULT
self._draw_gap = False
self._palette_popup_sid = None
self._popup_anim = animator.Animator(0.3, 10)
@ -162,6 +159,7 @@ class Palette(gtk.Window):
self._leave_notify_event_cb)
self.set_primary_text(label, accel_path)
self.set_group_id('default')
def is_up(self):
return self._up
@ -178,8 +176,9 @@ class Palette(gtk.Window):
return gtk.gdk.Rectangle(x, y, width, height)
def set_primary_text(self, label, accel_path=None):
self._label.set_text(label)
self._label.show()
if label is not None:
self._label.set_text(label)
self._label.show()
def set_content(self, widget):
if len(self._content.get_children()) > 0:
@ -196,6 +195,7 @@ class Palette(gtk.Window):
group = palettegroup.get_group(self._group_id)
group.remove(self)
if group_id:
self._group_id = group_id
group = palettegroup.get_group(group_id)
group.add(self)
@ -206,9 +206,6 @@ class Palette(gtk.Window):
self._invoker.connect('mouse-leave', self._invoker_mouse_leave_cb)
elif pspec.name == 'position':
self._position = value
elif pspec.name == 'draw-gap':
self._draw_gap = value
self.queue_draw()
else:
raise AssertionError
@ -217,8 +214,6 @@ class Palette(gtk.Window):
return self._invoker
elif pspec.name == 'position':
return self._position
elif pspec.name == 'draw-gap':
return self._draw_gap
else:
raise AssertionError
@ -228,7 +223,7 @@ class Palette(gtk.Window):
def do_expose_event(self, event):
# We want to draw a border with a beautiful gap
if self._draw_gap:
if self._invoker.has_rectangle_gap():
invoker = self._invoker.get_rect()
palette = self.get_rect()
@ -398,6 +393,9 @@ class Palette(gtk.Window):
self.menu.set_active(True)
self.show()
if self._invoker:
self._invoker.notify_popup()
self._up = True
_palette_observer.emit('popup', self)
self.emit('popup')
@ -406,22 +404,31 @@ class Palette(gtk.Window):
if not self._palette_popup_sid is None:
_palette_observer.disconnect(self._palette_popup_sid)
self._palette_popup_sid = None
self.menu.set_active(False)
self.hide()
if self._invoker:
self._invoker.notify_popdown()
self._up = False
self.emit('popdown')
def popup(self):
def popup(self, immediate=False):
self._popdown_anim.stop()
self._popup_anim.start()
if not immediate:
self._popup_anim.start()
else:
self._show()
self._secondary_anim.start()
def popdown(self, inmediate=False):
def popdown(self, immediate=False):
self._secondary_anim.stop()
self._popup_anim.stop()
if not inmediate:
if not immediate:
self._popdown_anim.start()
else:
self._hide()
@ -440,7 +447,15 @@ class Palette(gtk.Window):
self._state = state
def _invoker_mouse_enter_cb(self, invoker):
self.popup()
immediate = False
if self._group_id:
group = palettegroup.get_group(self._group_id)
if group and group.is_up():
immediate = True
group.popdown()
print immediate
self.popup(immediate=immediate)
def _invoker_mouse_leave_cb(self, invoker):
self.popdown()
@ -535,6 +550,12 @@ class Invoker(gobject.GObject):
def __init__(self):
gobject.GObject.__init__(self)
def has_rectangle_gap(self):
return False
def draw_rectangle(self, event, palette):
pass
def get_default_position(self):
return Palette.AROUND
@ -543,6 +564,12 @@ class Invoker(gobject.GObject):
height = gtk.gdk.screen_height()
return gtk.gdk.Rectangle(0, 0, width, height)
def notify_popup(self):
pass
def notify_popdown(self):
pass
class WidgetInvoker(Invoker):
def __init__(self, widget):
Invoker.__init__(self)
@ -562,31 +589,24 @@ class WidgetInvoker(Invoker):
return gtk.gdk.Rectangle(x, y, width, height)
def draw_invoker_rect(self, event, palette):
style = self._widget.style
if palette.is_up():
gap = _calculate_gap(self.get_rect(), palette.get_rect())
def has_rectangle_gap(self):
return True
if gap:
style.paint_box_gap(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_IN, event.area, self._widget,
"palette-invoker",
self._widget.allocation.x,
self._widget.allocation.y,
self._widget.allocation.width,
self._widget.allocation.height,
gap[0], gap[1], gap[2])
else:
style.paint_box(event.window, gtk.STATE_PRELIGHT,
def draw_rectangle(self, event, palette):
style = self._widget.style
gap = _calculate_gap(self.get_rect(), palette.get_rect())
if gap:
style.paint_box_gap(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_IN, event.area, self._widget,
"palette-invoker",
self._widget.allocation.x,
self._widget.allocation.y,
self._widget.allocation.width,
self._widget.allocation.height)
self._widget.allocation.height,
gap[0], gap[1], gap[2])
else:
style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_NONE, event.area, self._widget,
gtk.SHADOW_IN, event.area, self._widget,
"palette-invoker",
self._widget.allocation.x,
self._widget.allocation.y,
@ -602,6 +622,12 @@ class WidgetInvoker(Invoker):
def get_toplevel(self):
return self._widget.get_toplevel()
def notify_popup(self):
self._widget.queue_draw()
def notify_popdown(self):
self._widget.queue_draw()
class CanvasInvoker(Invoker):
def __init__(self, item):
Invoker.__init__(self)

View File

@ -39,6 +39,7 @@ class Group(gobject.GObject):
gobject.GObject.__init__(self)
self._up = False
self._palettes = []
self._sig_ids = {}
def is_up(self):
return self._up
@ -46,18 +47,26 @@ class Group(gobject.GObject):
def add(self, palette):
self._palettes.append(palette)
self._sig_ids[palette] = []
sid = palette.connect('popup', self._palette_popup_cb)
palette.popup_sid = sid
self._sig_ids[palette].append(sid)
sid = palette.connect('popdown', self._palette_popdown_cb)
palette.podown_sid = sid
self._sig_ids[palette].append(sid)
def remove(self, palette):
self.disconnect(palette.popup_sid)
self.disconnect(palette.popdown_sid)
sig_ids = self._sig_ids[palette]
for sid in sig_ids:
palette.disconnect(sid)
self._palettes.remove(palette)
def popdown(self):
for palette in self._palettes:
if palette.is_up():
palette.popdown(immediate=True)
def _palette_popup_cb(self, palette):
if not self._up:
self.emit('popup')

View File

@ -40,25 +40,15 @@ class RadioToolButton(gtk.RadioToolButton):
def set_palette(self, palette):
self._palette = palette
self._palette.props.invoker = WidgetInvoker(self.child)
self._palette.props.draw_gap = True
self._palette.connect("popup", self._palette_changed)
self._palette.connect("popdown", self._palette_changed)
def set_tooltip(self, text):
self._palette = Palette(text)
self._palette.props.invoker = WidgetInvoker(self.child)
self._set_palette(Palette(text))
def do_expose_event(self, event):
if self._palette and self._palette.props.draw_gap:
if self._palette.is_up() or self.child.state == gtk.STATE_PRELIGHT:
invoker = self._palette.props.invoker
invoker.draw_invoker_rect(event, self._palette)
if self._palette and self._palette.is_up():
invoker = self._palette.props.invoker
invoker.draw_rectangle(event, self._palette)
gtk.RadioToolButton.do_expose_event(self, event)
def _palette_changed(self, palette):
# Force a redraw to update the invoker rectangle
self.queue_draw()
palette = property(get_palette, set_palette)

View File

@ -128,6 +128,8 @@ COLOR_WHITE = Color('#FFFFFF')
COLOR_TRANSPARENT = Color('#FFFFFF', alpha=0.0)
COLOR_PANEL_GREY = Color('#C0C0C0')
COLOR_SELECTION_GREY = Color('#A6A6A6')
COLOR_TOOLBAR_GREY = Color('#404040')
COLOR_BUTTON_GREY = Color('#808080')
COLOR_INACTIVE_FILL = Color('#9D9FA1')
COLOR_INACTIVE_STROKE = Color('#757575')

View File

@ -39,25 +39,15 @@ class ToggleToolButton(gtk.ToggleToolButton):
def set_palette(self, palette):
self._palette = palette
self._palette.props.invoker = WidgetInvoker(self.child)
self._palette.props.draw_gap = True
self._palette.connect("popup", self._palette_changed)
self._palette.connect("popdown", self._palette_changed)
def set_tooltip(self, text):
self._palette = Palette(text)
self._palette.props.invoker = WidgetInvoker(self.child)
self._set_palette(Palette(text))
def do_expose_event(self, event):
if self._palette and self._palette.props.draw_gap:
if self._palette.is_up() or self.child.state == gtk.STATE_PRELIGHT:
invoker = self._palette.props.invoker
invoker.draw_invoker_rect(event, self._palette)
if self._palette and self._palette.is_up():
invoker = self._palette.props.invoker
invoker.draw_rectangle(event, self._palette)
gtk.ToggleToolButton.do_expose_event(self, event)
def _palette_changed(self, palette):
# Force a redraw to update the invoker rectangle
self.queue_draw()
palette = property(get_palette, set_palette)

View File

@ -28,7 +28,8 @@ class ToolButton(gtk.ToolButton):
def __init__(self, icon_name=None):
gtk.ToolButton.__init__(self)
self._palette = None
self.set_icon(icon_name)
if icon_name:
self.set_icon(icon_name)
self.connect('clicked', self._button_clicked_cb)
def set_icon(self, icon_name):
@ -42,19 +43,14 @@ class ToolButton(gtk.ToolButton):
def set_palette(self, palette):
self._palette = palette
self._palette.props.invoker = WidgetInvoker(self.child)
self._palette.props.draw_gap = True
self._palette.connect("popup", self._palette_changed)
self._palette.connect("popdown", self._palette_changed)
def set_tooltip(self, text):
self.set_palette(Palette(text))
def do_expose_event(self, event):
if self._palette and self._palette.props.draw_gap:
if self._palette.is_up() or self.child.state == gtk.STATE_PRELIGHT:
invoker = self._palette.props.invoker
invoker.draw_invoker_rect(event, self._palette)
if self._palette and self._palette.is_up():
invoker = self._palette.props.invoker
invoker.draw_rectangle(event, self._palette)
gtk.ToolButton.do_expose_event(self, event)
@ -62,8 +58,4 @@ class ToolButton(gtk.ToolButton):
if self._palette:
self._palette.popdown(True)
def _palette_changed(self, palette):
# Force a redraw to update the invoker rectangle
self.queue_draw()
palette = property(get_palette, set_palette)

View File

@ -105,11 +105,7 @@ def _get_logs_dir():
def start(module_id):
# Only log if logging is set up for the activity
module_key = module_id.upper() + "_DEBUG"
emulator = False
if os.environ.has_key("SUGAR_EMULATOR"):
if os.environ["SUGAR_EMULATOR"] == "yes":
emulator = True
if not os.environ.has_key(module_key) and not emulator:
if not os.environ.has_key(module_key) and not env.is_emulator():
return
log_writer = LogWriter(module_id)

View File

@ -1,4 +1,5 @@
# 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
@ -20,7 +21,13 @@ import logging
from sugar import _sugarext
def get_for_file(file_name):
return _sugarext.get_mime_type_for_file(file_name)
mime_type = _sugarext.get_mime_type_for_file(file_name)
if mime_type == 'application/octet-stream':
if _file_looks_like_text(file_name):
return 'text/plain'
else:
return 'application/octet-stream'
return mime_type
def get_from_file_name(file_name):
return _sugarext.get_mime_type_from_file_name(file_name)
@ -51,13 +58,9 @@ def choose_most_significant(mime_types):
if 'text/uri-list' in mime_types:
return 'text/uri-list'
for mime_category in ['image/', 'text/', 'application/']:
for mime_category in ['image/', 'application/']:
for mime_type in mime_types:
# skip text/plain and text/html, these have lower priority.
if mime_type in ['text/plain', 'text/html']:
continue
if mime_type.startswith(mime_category):
# skip mozilla private types (second component starts with '_'
# or ends with '-priv')
@ -70,6 +73,10 @@ def choose_most_significant(mime_types):
logging.debug('Choosed %r!' % mime_type)
return mime_type
if 'text/x-moz-url' in mime_types:
logging.debug('Choosed text/x-moz-url!')
return 'text/x-moz-url'
if 'text/html' in mime_types:
logging.debug('Choosed text/html!')
return 'text/html'
@ -80,3 +87,22 @@ def choose_most_significant(mime_types):
logging.debug('Returning first: %r.' % mime_types[0])
return mime_types[0]
def _file_looks_like_text(file_name):
f = open(file_name, 'r')
try:
sample = f.read(256)
finally:
f.close()
if '\000' in sample:
return False
for encoding in ('ascii', 'latin_1', 'utf_8', 'utf_16'):
try:
string = unicode(sample, encoding)
return True
except Exception, e:
pass
return False

View File

@ -3,5 +3,6 @@ sugar_PYTHON = \
__init__.py \
activity.py \
buddy.py \
tubeconn.py \
presenceservice.py

107
sugar/presence/tubeconn.py Normal file
View File

@ -0,0 +1,107 @@
# This should eventually land in telepathy-python, so has the same license:
# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
__all__ = ('TubeConnection',)
__docformat__ = 'reStructuredText'
import logging
from dbus.connection import Connection
logger = logging.getLogger('telepathy.tubeconn')
class TubeConnection(Connection):
def __new__(cls, conn, tubes_iface, tube_id, address=None,
group_iface=None, mainloop=None):
if address is None:
address = tubes_iface.GetDBusServerAddress(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, [])

55
tests/graphics/common.py Normal file
View File

@ -0,0 +1,55 @@
# 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.toolbutton import ToolButton
class Test(gtk.VBox):
def __init__(self):
gtk.VBox.__init__(self)
class TestPalette(Test):
def __init__(self):
Test.__init__(self)
toolbar = gtk.Toolbar()
self._invoker = ToolButton('go-previous')
toolbar.insert(self._invoker, -1)
self._invoker.show()
self.pack_start(toolbar, False)
toolbar.show()
def set_palette(self, palette):
self._invoker.set_palette(palette)
class TestRunner(object):
def run(self, test):
window = gtk.Window()
window.connect("destroy", lambda w: gtk.main_quit())
window.add(test)
test.show()
window.show()
def main(test):
runner = TestRunner()
runner.run(test)
gtk.main()

View File

@ -0,0 +1,59 @@
# Copyright (C) 2007, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
"""
Test the style of toggle and radio buttons inside a palette. The buttons
contains only an icon and should be rendered similarly to the toolbar
controls. Ticket #2855.
"""
import gtk
from sugar.graphics.palette import Palette
from sugar.graphics.icon import Icon
import common
test = common.TestPalette()
palette = Palette('Test radio and toggle')
test.set_palette(palette)
box = gtk.HBox()
toggle = gtk.ToggleButton()
icon = Icon('go-previous', icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
toggle.set_image(icon)
box.pack_start(toggle, False)
toggle.show()
radio = gtk.RadioButton()
icon = Icon('go-next', icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
radio.set_image(icon)
radio.set_mode(False)
box.pack_start(radio, False)
radio.show()
palette.set_content(box)
box.show()
if __name__ == "__main__":
common.main(test)

30
tests/lib/runall.py Normal file
View File

@ -0,0 +1,30 @@
# 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 unittest
import test_date
import test_mime
runner = unittest.TextTestRunner()
loader = unittest.TestLoader()
suite = unittest.TestSuite()
suite.addTest(loader.loadTestsFromModule(test_date))
suite.addTest(loader.loadTestsFromModule(test_mime))
runner.run(suite)

30
tests/lib/test_date.py Normal file
View File

@ -0,0 +1,30 @@
# 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 datetime
import unittest
from sugar.date import Date
class TestDate(unittest.TestCase):
def test_today(self):
date = Date(datetime.date(2000, 1, 1))
date._today = datetime.date(2000, 1, 1)
self.assertEqual(str(date), 'Today')
if __name__ == "__main__":
unittest.main()

72
tests/lib/test_mime.py Normal file
View File

@ -0,0 +1,72 @@
#!/usr/bin/env python
# Copyright (C) 2006, Red Hat, Inc.
# Copyright (C) 2007, One Laptop Per Child
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import sys
import unittest
from sugar import objects
class TestMime(unittest.TestCase):
def test_from_file_name(self):
self.assertEqual(objects.mime.get_from_file_name('test.pdf'),
'application/pdf')
def test_choose_most_significant(self):
# Mozilla's text in dnd
mime_type = objects.mime.choose_most_significant(
['text/plain', 'text/_moz_htmlcontext', 'text/unicode',
'text/html', 'text/_moz_htmlinfo'])
self.assertEqual(mime_type, 'text/html')
# Mozilla's text in c&v
mime_type = objects.mime.choose_most_significant(
['text/_moz_htmlcontext', 'STRING', 'text/html', 'text/_moz_htmlinfo',
'text/x-moz-url-priv', 'UTF8_STRING', 'COMPOUND_TEXT'])
self.assertEqual(mime_type, 'text/html')
# Mozilla gif in dnd
mime_type = objects.mime.choose_most_significant(
['application/x-moz-file-promise-url',
'application/x-moz-file-promise-dest-filename', 'text/_moz_htmlinfo',
'text/x-moz-url-desc', 'text/_moz_htmlcontext', 'text/x-moz-url-data',
'text/uri-list'])
self.assertEqual(mime_type, 'text/uri-list')
# Mozilla url in dnd
mime_type = objects.mime.choose_most_significant(
['text/_moz_htmlcontext', 'text/html', 'text/_moz_htmlinfo',
'_NETSCAPE_URL', 'text/x-moz-url', 'text/x-moz-url-desc',
'text/x-moz-url-data', 'text/plain', 'text/unicode'])
self.assertEqual(mime_type, 'text/x-moz-url')
# Abiword text in dnd
mime_type = objects.mime.choose_most_significant(
['text/rtf', 'text/uri-list'])
self.assertEqual(mime_type, 'text/uri-list')
# Abiword text in c&v
mime_type = objects.mime.choose_most_significant(
['UTF8_STRING', 'STRING', 'text/html', 'TEXT', 'text/rtf',
'COMPOUND_TEXT', 'application/rtf', 'text/plain',
'application/xhtml+xml'])
self.assertEqual(mime_type, 'application/rtf')
if __name__ == "__main__":
unittest.main()

View File

@ -1,136 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2007, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os, time, sys
import dbus, dbus.glib
import gobject
from sugar.presence import presenceservice
from sugar.p2p import network
class ReadHTTPRequestHandler(network.ChunkedGlibHTTPRequestHandler):
def translate_path(self, path):
return self.server._filepath
class ReadHTTPServer(network.GlibTCPServer):
def __init__(self, server_address, request_handler, filepath):
self._filepath = filepath
network.GlibTCPServer.__init__(self, server_address, request_handler);
class XMLRPCResponder(object):
def __init__(self, have_file=False):
self._have_file = have_file
def _set_have_file(self):
self._have_file = True
def have_file(self):
return self._have_file
class MockReadActivity(gobject.GObject):
__gproperties__ = {
'title' : (str, None, None, None, gobject.PARAM_READABLE)
}
def __init__(self, filepath):
self._actid = "ef60b3af42f7b5aa558ef9269e2ed7998798afc0"
self._name = "Test Read Activity"
self._type = "org.laptop.sugar.ReadActivity"
gobject.GObject.__init__(self)
self._ps_act = None
self._filepath = os.path.abspath(filepath)
self._file_server = ReadHTTPServer(("", 8867), ReadHTTPRequestHandler, self._filepath)
self._xmlrpc_server = network.GlibXMLRPCServer(("", 8868))
responder = XMLRPCResponder(have_file=True)
self._xmlrpc_server.register_instance(responder)
def _activity_appeared_cb(self, ps, activity):
if activity.props.id != self._actid:
return
self._ps_act = activity
def share(self):
ps = presenceservice.get_instance()
ps.connect("activity-appeared", self._activity_appeared_cb)
ps.share_activity(self)
return False
def do_get_property(self, pspec):
if pspec.name == "title":
return self._name
def get_id(self):
return self._actid
def get_service_name(self):
return self._type
def start_ps():
import commands
(s, o) = commands.getstatusoutput("which sugar-presence-service")
if s != 0:
raise RuntimeError("Failed to find sugar presence service: %s" % o)
argv = [o, "1"]
(pid, stdin, stdout, stderr) = gobject.spawn_async(argv, flags=gobject.SPAWN_LEAVE_DESCRIPTORS_OPEN)
# Wait until it shows up on the bus
tries = 0
bus = dbus.SessionBus()
while tries < 10:
time.sleep(0.5)
bus_object = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
try:
if bus_object.GetNameOwner(presenceservice.DBUS_SERVICE, dbus_interface='org.freedesktop.DBus'):
break
except dbus.exceptions.DBusException, err:
pass
tries += 1
if tries >= 5:
stop_ps(pid)
raise RuntimeError("Couldn't start the mock presence service")
print "Started presence service PID %d" % pid
return pid
def stop_ps(pid):
if pid >= 0:
os.kill(pid, 15)
print "Stopped presence service PID %d" % pid
def main():
if len(sys.argv) != 2:
raise RuntimeError("Must specify a PDF to share.")
path = os.path.abspath(sys.argv[1])
if not os.path.exists(path):
raise RuntimeError("File %s doesn't exist." % path)
mact = MockReadActivity(path)
pid = start_ps()
loop = gobject.MainLoop()
gobject.timeout_add(2000, mact.share)
try:
loop.run()
except KeyboardInterrupt:
pass
stop_ps(pid)
if __name__ == "__main__":
main()

View File

@ -1,422 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2007, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import gobject
import dbus, dbus.service, dbus.glib
_PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
_PRESENCE_INTERFACE = "org.laptop.Sugar.Presence"
_PRESENCE_TEST_INTERFACE = "org.laptop.Sugar.Presence._Test"
_PRESENCE_PATH = "/org/laptop/Sugar/Presence"
class NotFoundError(dbus.DBusException):
def __init__(self, msg=None):
dbus.DBusException.__init__(self, msg)
self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound'
_ACTIVITY_PATH = "/org/laptop/Sugar/Presence/Activities/"
_ACTIVITY_INTERFACE = "org.laptop.Sugar.Presence.Activity"
class TestActivity(dbus.service.Object):
def __init__(self, bus_name, object_id, parent, actid, name, color, atype, properties):
self._parent = parent
self._actid = actid
self._aname = name
self._color = color
self._type = atype
self._properties = {}
for (key, value) in properties.items():
self._properties[str(key)] = str(value)
self._buddies = {}
self._object_id = object_id
self._object_path = _ACTIVITY_PATH + str(self._object_id)
dbus.service.Object.__init__(self, bus_name, self._object_path)
def add_buddy(self, buddy):
if self._buddies.has_key(buddy._key):
raise NotFoundError("Buddy already in activity")
self._buddies[buddy._key] = buddy
self.BuddyJoined(buddy._object_path)
def remove_buddy(self, buddy):
if not self._buddies.has_key(buddy._key):
raise NotFoundError("Buddy not in activity")
self.BuddyLeft(buddy._object_path)
del self._buddies[buddy._key]
def disappear(self):
# remove all buddies from activity
for buddy in self.get_buddies():
self.BuddyLeft(buddy._object_path)
self._buddies = {}
def get_buddies(self):
return self._buddies.values()
@dbus.service.signal(_ACTIVITY_INTERFACE, signature="o")
def BuddyJoined(self, buddy_path):
pass
@dbus.service.signal(_ACTIVITY_INTERFACE, signature="o")
def BuddyLeft(self, buddy_path):
pass
@dbus.service.signal(_ACTIVITY_INTERFACE, signature="o")
def NewChannel(self, channel_path):
pass
@dbus.service.method(_ACTIVITY_INTERFACE, out_signature="s")
def GetId(self):
return self._actid
@dbus.service.method(_ACTIVITY_INTERFACE, out_signature="s")
def GetName(self):
return self._aname
@dbus.service.method(_ACTIVITY_INTERFACE, out_signature="s")
def GetColor(self):
return self._color
@dbus.service.method(_ACTIVITY_INTERFACE, out_signature="s")
def GetType(self):
return self._type
@dbus.service.method(_ACTIVITY_INTERFACE)
def Join(self):
owner = self._parent._owner
self.add_buddy(owner)
owner.add_activity(self)
@dbus.service.method(_ACTIVITY_INTERFACE, out_signature="ao")
def GetJoinedBuddies(self):
ret = []
for buddy in self._buddies.values():
ret.append(dbus.ObjectPath(buddy._object_path))
return ret
@dbus.service.method(_ACTIVITY_INTERFACE, out_signature="soao")
def GetChannels(self):
return None
_BUDDY_PATH = "/org/laptop/Sugar/Presence/Buddies/"
_BUDDY_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
_OWNER_INTERFACE = "org.laptop.Sugar.Presence.Buddy.Owner"
_PROP_NICK = "nick"
_PROP_KEY = "key"
_PROP_ICON = "icon"
_PROP_CURACT = "current-activity"
_PROP_COLOR = "color"
_PROP_OWNER = "owner"
class TestBuddy(dbus.service.Object):
def __init__(self, bus_name, object_id, pubkey, nick, color):
self._key = pubkey
self._nick = nick
self._color = color
self._owner = False
self._curact = None
self._icon = ""
self._activities = {}
self._object_id = object_id
self._object_path = _BUDDY_PATH + str(self._object_id)
dbus.service.Object.__init__(self, bus_name, self._object_path)
def add_activity(self, activity):
if self._activities.has_key(activity._actid):
raise NotFoundError("Buddy already in activity")
self._activities[activity._actid] = activity
self.JoinedActivity(activity._object_path)
def remove_activity(self, activity):
if not self._activities.has_key(activity._actid):
raise NotFoundError("Buddy not in activity")
self.LeftActivity(activity._object_path)
del self._activities[activity._actid]
def leave_activities(self):
for activity in self.get_activities():
self.LeftActivity(activity._object_path)
self._activities = {}
def get_activities(self):
return self._activities.values()
def set_current_activity(self, actid):
self._curact = actid
self.PropertyChanged({_PROP_CURACT: actid})
@dbus.service.signal(_BUDDY_INTERFACE, signature="ay")
def IconChanged(self, icon_data):
pass
@dbus.service.signal(_BUDDY_INTERFACE, signature="o")
def JoinedActivity(self, activity_path):
pass
@dbus.service.signal(_BUDDY_INTERFACE, signature="o")
def LeftActivity(self, activity_path):
pass
@dbus.service.signal(_BUDDY_INTERFACE, signature="a{sv}")
def PropertyChanged(self, updated):
pass
# dbus methods
@dbus.service.method(_BUDDY_INTERFACE, in_signature="", out_signature="ay")
def GetIcon(self):
return dbus.ByteArray(self._icon)
@dbus.service.method(_BUDDY_INTERFACE, in_signature="", out_signature="ao")
def GetJoinedActivities(self):
acts = []
for activity in self._activities.values():
acts.append(dbus.ObjectPath(activity._object_path))
return acts
@dbus.service.method(_BUDDY_INTERFACE, in_signature="", out_signature="a{sv}")
def GetProperties(self):
props = {}
props[_PROP_NICK] = self._nick
props[_PROP_OWNER] = self._owner
props[_PROP_KEY] = self._key
props[_PROP_COLOR] = self._color
if self._curact:
props[_PROP_CURACT] = self._curact
else:
props[_PROP_CURACT] = ""
return props
_OWNER_PUBKEY = "AAAAB3NzaC1kc3MAAACBAKEVDFJW9D9GK20QFYRKbhV7kpjnhKkkzudn34ij" \
"Ixje+x1ZXTIU6J1GFmJYrHq9uBRi72lOVAosGUop+HHZFRyTeYLxItmKfIoD" \
"S2rwyL9cGRoDsD4yjECMqa2I+pGxriw4OmHeu5vmBkk+5bXBdkLf0EfseuPC" \
"lT7FE+Fj4C6FAAAAFQCygOIpXXybKlVTcEfprOQp3Uud0QAAAIBjyjQhOWHq" \
"FdJlALmnriQR+Zi1i4N/UMjWihF245RXJuUU6DyYbx4QxznxRnYKx/ZvsD0O" \
"9+ihzmQd6eFwU/jQ6sxiL7DSlCJ3axgG9Yvbf7ELeXGo4/Z9keOVdei0sXz4" \
"VBvJC0c0laELsnU0spFC62qQKxNemTbXDGksauj19gAAAIEAmcvY8VX47pRP" \
"k7MjrDzZlPvvNQgHMNZSwHGIsF7EMGVDCYpbQTyR+cmtJBBFVyxtNbK7TWTZ" \
"K8uH1tm9GyMcViUdIT4xCirA0JanE597KdlBz39l/623wF4jvbnnHOZ/pIT9" \
"tPd1pCYJf+L7OEKCBUAyQhcq159X8A1toM48Soc="
_OWNER_PRIVKEY = "MIIBuwIBAAKBgQChFQxSVvQ/RittEBWESm4Ve5KY54SpJM7nZ9+IoyMY3vs" \
"dWV0yFOidRhZiWKx6vbgUYu9pTlQKLBlKKfhx2RUck3mC8SLZinyKA0tq8M" \
"i/XBkaA7A+MoxAjKmtiPqRsa4sODph3rub5gZJPuW1wXZC39BH7HrjwpU+x" \
"RPhY+AuhQIVALKA4ildfJsqVVNwR+ms5CndS53RAoGAY8o0ITlh6hXSZQC5" \
"p64kEfmYtYuDf1DI1ooRduOUVyblFOg8mG8eEMc58UZ2Csf2b7A9Dvfooc5" \
"kHenhcFP40OrMYi+w0pQid2sYBvWL23+xC3lxqOP2fZHjlXXotLF8+FQbyQ" \
"tHNJWhC7J1NLKRQutqkCsTXpk21wxpLGro9fYCgYEAmcvY8VX47pRPk7Mjr" \
"DzZlPvvNQgHMNZSwHGIsF7EMGVDCYpbQTyR+cmtJBBFVyxtNbK7TWTZK8uH" \
"1tm9GyMcViUdIT4xCirA0JanE597KdlBz39l/623wF4jvbnnHOZ/pIT9tPd" \
"1pCYJf+L7OEKCBUAyQhcq159X8A1toM48SocCFAvkZYCYtLhSDEPrlf0jLD" \
"jrMz+i"
_OWNER_NICK = "TestOwner"
_OWNER_COLOR = "#75C228,#308C30"
class TestOwner(TestBuddy):
def __init__(self, bus_name, object_id):
TestBuddy.__init__(self, bus_name, object_id, _OWNER_PUBKEY,
_OWNER_NICK, _OWNER_COLOR)
self._owner = True
class TestPresenceService(dbus.service.Object):
"""A test D-Bus PresenceService used to exercise the Sugar PS bindings."""
def __init__(self):
self._next_object_id = 0
self._activities = {}
self._buddies = {}
self._bus_name = dbus.service.BusName(_PRESENCE_SERVICE,
bus=dbus.SessionBus())
objid = self._get_next_object_id()
self._owner = TestOwner(self._bus_name, objid)
dbus.service.Object.__init__(self, self._bus_name, _PRESENCE_PATH)
def _get_next_object_id(self):
"""Increment and return the object ID counter."""
self._next_object_id = self._next_object_id + 1
return self._next_object_id
@dbus.service.signal(_PRESENCE_INTERFACE, signature="o")
def ActivityAppeared(self, activity):
pass
@dbus.service.signal(_PRESENCE_INTERFACE, signature="o")
def ActivityDisappeared(self, activity):
pass
@dbus.service.signal(_PRESENCE_INTERFACE, signature="o")
def BuddyAppeared(self, buddy):
pass
@dbus.service.signal(_PRESENCE_INTERFACE, signature="o")
def BuddyDisappeared(self, buddy):
pass
@dbus.service.signal(_PRESENCE_INTERFACE, signature="o")
def ActivityInvitation(self, activity):
pass
@dbus.service.signal(_PRESENCE_INTERFACE, signature="soo")
def PrivateInvitation(self, bus_name, connection, channel):
pass
@dbus.service.method(_PRESENCE_INTERFACE, out_signature="ao")
def GetActivities(self):
ret = []
for act in self._activities.values():
ret.append(dbus.ObjectPath(act._object_path))
return ret
@dbus.service.method(_PRESENCE_INTERFACE, in_signature="s", out_signature="o")
def GetActivityById(self, actid):
if self._activities.has_key(actid):
return dbus.ObjectPath(self._activities[actid]._object_path)
raise NotFoundError("The activity was not found.")
@dbus.service.method(_PRESENCE_INTERFACE, out_signature="ao")
def GetBuddies(self):
ret = []
for buddy in self._buddies.values():
ret.append(buddy._object_path)
return ret
@dbus.service.method(_PRESENCE_INTERFACE, in_signature="ay", out_signature="o")
def GetBuddyByPublicKey(self, key):
key = ''.join([chr(item) for item in key])
if self._buddies.has_key(key):
return self._buddies[key]._object_path
raise NotFoundError("The buddy was not found.")
@dbus.service.method(_PRESENCE_INTERFACE, out_signature="o")
def GetOwner(self):
if not self._owner:
raise NotFoundError("The owner was not found.")
return dbus.ObjectPath(self._owner._object_path)
def _internal_share_activity(self, actid, atype, name, properties, color=None):
objid = self._get_next_object_id()
if not color:
color = self._owner._color
act = TestActivity(self._bus_name, objid, self, actid, name, color, atype, properties)
self._activities[actid] = act
self.ActivityAppeared(act._object_path)
return act
@dbus.service.method(_PRESENCE_INTERFACE, in_signature="sssa{sv}",
out_signature="o")
def ShareActivity(self, actid, atype, name, properties):
act = self._internal_share_activity(actid, atype, name, properties)
act.add_buddy(self._owner)
self._owner.add_activity(act)
return act._object_path
@dbus.service.method(_PRESENCE_INTERFACE, out_signature="so")
def GetPreferredConnection(self):
return "bar.baz.foo", "/bar/baz/foo"
# Private methods used for testing
@dbus.service.method(_PRESENCE_TEST_INTERFACE, in_signature="ayss")
def AddBuddy(self, pubkey, nick, color):
pubkey = ''.join([chr(item) for item in pubkey])
objid = self._get_next_object_id()
buddy = TestBuddy(self._bus_name, objid, pubkey, nick, color)
self._buddies[pubkey] = buddy
self.BuddyAppeared(buddy._object_path)
@dbus.service.method(_PRESENCE_TEST_INTERFACE, in_signature="ay")
def RemoveBuddy(self, pubkey):
pubkey = ''.join([chr(item) for item in pubkey])
if not self._buddies.has_key(pubkey):
raise NotFoundError("Buddy not found")
buddy = self._buddies[pubkey]
activities = buddy.get_activities()
# remove activity from the buddy
buddy.leave_activities()
# remove the buddy from all activities
for act in activities:
act.remove_buddy(buddy)
self.BuddyDisappeared(buddy._object_path)
del self._buddies[pubkey]
@dbus.service.method(_PRESENCE_TEST_INTERFACE, in_signature="ays")
def AddBuddyToActivity(self, pubkey, actid):
pubkey = ''.join([chr(item) for item in pubkey])
if not self._buddies.has_key(pubkey):
raise NotFoundError("Buddy unknown")
if not self._activities.has_key(actid):
raise NotFoundError("Activity unknown")
buddy = self._buddies[pubkey]
activity = self._activities[actid]
activity.add_buddy(buddy)
buddy.add_activity(activity)
@dbus.service.method(_PRESENCE_TEST_INTERFACE, in_signature="ays")
def RemoveBuddyFromActivity(self, pubkey, actid):
pubkey = ''.join([chr(item) for item in pubkey])
if not self._buddies.has_key(pubkey):
raise NotFoundError("Buddy unknown")
if not self._activities.has_key(actid):
raise NotFoundError("Activity unknown")
buddy = self._buddies[pubkey]
activity = self._activities[actid]
buddy.remove_activity(activity)
activity.remove_buddy(buddy)
@dbus.service.method(_PRESENCE_TEST_INTERFACE, in_signature="ssssa{sv}")
def AddActivity(self, actid, name, color, atype, properties):
self._internal_share_activity(actid, atype, name, properties, color=color)
@dbus.service.method(_PRESENCE_TEST_INTERFACE, in_signature="s")
def RemoveActivity(self, actid):
if not self._activities.has_key(actid):
raise NotFoundError("Activity not found")
act = self._activities[actid]
# remove activity from all buddies
for buddy in act.get_buddies():
buddy.remove_activity(act)
act.disappear()
self.ActivityDisappeared(act._object_path)
del self._activities[actid]
@dbus.service.method(_PRESENCE_TEST_INTERFACE, in_signature="ays")
def SetBuddyCurrentActivity(self, pubkey, actid):
pubkey = ''.join([chr(item) for item in pubkey])
if not self._buddies.has_key(pubkey):
raise NotFoundError("Buddy unknown")
buddy = self._buddies[pubkey]
buddy.set_current_activity(actid)
def main():
import logging
logging.basicConfig(level=logging.DEBUG)
loop = gobject.MainLoop()
ps = TestPresenceService()
loop.run()
if __name__ == "__main__":
main()

View File

@ -1,723 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2007, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os, time
import dbus
import gobject, gtk
import unittest
from sugar.presence import presenceservice
import mockps
def start_ps():
argv = ["mockps.py"]
(pid, stdin, stdout, stderr) = gobject.spawn_async(argv, flags=gobject.SPAWN_LEAVE_DESCRIPTORS_OPEN)
# Wait until it shows up on the bus
tries = 0
bus = dbus.SessionBus()
while tries < 10:
time.sleep(0.5)
bus_object = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
try:
if bus_object.GetNameOwner(presenceservice.DBUS_SERVICE, dbus_interface='org.freedesktop.DBus'):
break
except dbus.exceptions.DBusException, err:
pass
tries += 1
if tries >= 5:
stop_ps(pid)
raise RuntimeError("Couldn't start the mock presence service")
return pid
def stop_ps(pid):
# EVIL HACK: get a new presence service object every time; close the
# connection to completely clear all signal matches too
presenceservice._ps._bus.close()
del presenceservice._ps
presenceservice._ps = None
if pid >= 0:
os.kill(pid, 15)
def get_ps():
ps = presenceservice.get_instance(False)
# HACK
# Set exit on disconnect to False so we don't get aborted when
# explicitly closing the bus connection in stop_ps()
ps._bus.set_exit_on_disconnect(False)
return ps
class GenericTestCase(unittest.TestCase):
def setUp(self):
self._pspid = start_ps()
self._success = False
self._err = ""
self._signals = []
self._sources = []
def tearDown(self):
# Remove all signal handlers
for (obj, sid) in self._signals:
obj.disconnect(sid)
for source in self._sources:
gobject.source_remove(source)
if self._pspid > 0:
stop_ps(self._pspid)
self._pspid = -1
def _handle_success(self):
self._success = True
gtk.main_quit()
def _handle_error(self, err):
self._success = False
self._err = str(err)
gtk.main_quit()
class BuddyTests(GenericTestCase):
def _testOwner_helper(self):
try:
ps = get_ps()
except RuntimeError, err:
self._handle_error(err)
return False
try:
owner = ps.get_owner()
except RuntimeError, err:
self._handle_error(err)
return False
self._owner = owner
self._handle_success()
return False
def testOwner(self):
gobject.idle_add(self._testOwner_helper)
gtk.main()
assert self._success == True, "Test unsuccessful."
assert self._owner, "Owner could not be found."
assert self._owner.props.key == mockps._OWNER_PUBKEY, "Owner public key doesn't match expected"
assert self._owner.props.nick == mockps._OWNER_NICK, "Owner nickname doesn't match expected"
assert self._owner.props.color == mockps._OWNER_COLOR, "Owner color doesn't match expected"
_BA_PUBKEY = "akjadskjjfahfdahfdsahjfhfewaew3253232832832q098qewa98fdsafa98fa"
_BA_NICK = "BuddyAppearedTestBuddy"
_BA_COLOR = "#23adfb,#56bb11"
def _testBuddyAppeared_helper_timeout(self):
self._handle_error("Timeout waiting for buddy-appeared signal")
return False
def _testBuddyAppeared_helper_cb(self, ps, buddy):
self._buddy = buddy
self._handle_success()
def _testBuddyAppeared_helper(self):
ps = get_ps()
sid = ps.connect('buddy-appeared', self._testBuddyAppeared_helper_cb)
self._signals.append((ps, sid))
# Wait 5 seconds max for signal to be emitted
sid = gobject.timeout_add(5000, self._testBuddyAppeared_helper_timeout)
self._sources.append(sid)
busobj = dbus.SessionBus().get_object(mockps._PRESENCE_SERVICE,
mockps._PRESENCE_PATH)
try:
testps = dbus.Interface(busobj, mockps._PRESENCE_TEST_INTERFACE)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
try:
testps.AddBuddy(self._BA_PUBKEY, self._BA_NICK, self._BA_COLOR)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
return False
def testBuddyAppeared(self):
ps = get_ps()
assert ps, "Couldn't get presence service"
self._buddy = None
gobject.idle_add(self._testBuddyAppeared_helper)
gtk.main()
assert self._success == True, "Test unsuccessful."
assert self._buddy, "Buddy was not received"
assert self._buddy.props.key == self._BA_PUBKEY, "Public key doesn't match expected"
assert self._buddy.props.nick == self._BA_NICK, "Nickname doesn't match expected"
assert self._buddy.props.color == self._BA_COLOR, "Color doesn't match expected"
# Try to get buddy by public key
buddy2 = ps.get_buddy(self._BA_PUBKEY)
assert buddy2, "Couldn't get buddy by public key"
assert buddy2.props.key == self._BA_PUBKEY, "Public key doesn't match expected"
assert buddy2.props.nick == self._BA_NICK, "Nickname doesn't match expected"
assert buddy2.props.color == self._BA_COLOR, "Color doesn't match expected"
def _testBuddyDisappeared_helper_timeout(self):
self._handle_error("Timeout waiting for buddy-disappeared signal")
return False
def _testBuddyDisappeared_helper_cb(self, ps, buddy):
self._buddy = buddy
self._handle_success()
def _testBuddyDisappeared_helper(self):
busobj = dbus.SessionBus().get_object(mockps._PRESENCE_SERVICE,
mockps._PRESENCE_PATH)
try:
testps = dbus.Interface(busobj, mockps._PRESENCE_TEST_INTERFACE)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
# Add a fake buddy
try:
testps.AddBuddy(self._BA_PUBKEY, self._BA_NICK, self._BA_COLOR)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
ps = get_ps()
sid = ps.connect('buddy-disappeared', self._testBuddyDisappeared_helper_cb)
self._signals.append((ps, sid))
# Wait 5 seconds max for signal to be emitted
sid = gobject.timeout_add(5000, self._testBuddyDisappeared_helper_timeout)
self._sources.append(sid)
# Delete the fake buddy
try:
testps.RemoveBuddy(self._BA_PUBKEY)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
return False
def testBuddyDisappeared(self):
ps = get_ps()
assert ps, "Couldn't get presence service"
self._buddy = None
gobject.idle_add(self._testBuddyDisappeared_helper)
gtk.main()
assert self._success == True, "Test unsuccessful."
assert self._buddy, "Buddy was not received"
assert self._buddy.props.key == self._BA_PUBKEY, "Public key doesn't match expected"
assert self._buddy.props.nick == self._BA_NICK, "Nickname doesn't match expected"
assert self._buddy.props.color == self._BA_COLOR, "Color doesn't match expected"
def addToSuite(suite):
suite.addTest(BuddyTests("testOwner"))
suite.addTest(BuddyTests("testBuddyAppeared"))
suite.addTest(BuddyTests("testBuddyDisappeared"))
addToSuite = staticmethod(addToSuite)
class MockSugarActivity(gobject.GObject):
__gproperties__ = {
'title' : (str, None, None, None, gobject.PARAM_READABLE)
}
def __init__(self, actid, name, atype):
self._actid = actid
self._name = name
self._type = atype
gobject.GObject.__init__(self)
def do_get_property(self, pspec):
if pspec.name == "title":
return self._name
def get_id(self):
return self._actid
def get_service_name(self):
return self._type
class ActivityTests(GenericTestCase):
_AA_ID = "d622b99b9f365d712296094b9f6110521e6c9cba"
_AA_NAME = "Test Activity"
_AA_TYPE = "org.laptop.Sugar.Foobar"
_AA_COLOR = "#adfe20,#bf781a"
_AA_PROPS = {"foo": "asdfadf", "bar":"5323aggdas"}
def _testActivityAppeared_helper_timeout(self):
self._handle_error("Timeout waiting for activity-appeared signal")
return False
def _testActivityAppeared_helper_cb(self, ps, activity):
self._activity = activity
self._handle_success()
def _testActivityAppeared_helper(self):
ps = get_ps()
sid = ps.connect('activity-appeared', self._testActivityAppeared_helper_cb)
self._signals.append((ps, sid))
# Wait 5 seconds max for signal to be emitted
sid = gobject.timeout_add(5000, self._testActivityAppeared_helper_timeout)
self._sources.append(sid)
busobj = dbus.SessionBus().get_object(mockps._PRESENCE_SERVICE,
mockps._PRESENCE_PATH)
try:
testps = dbus.Interface(busobj, mockps._PRESENCE_TEST_INTERFACE)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
try:
testps.AddActivity(self._AA_ID, self._AA_NAME, self._AA_COLOR, self._AA_TYPE, {})
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
return False
def testActivityAppeared(self):
ps = get_ps()
assert ps, "Couldn't get presence service"
self._activity = None
gobject.idle_add(self._testActivityAppeared_helper)
gtk.main()
assert self._success == True, "Test unsuccessful"
assert self._activity, "Activity was not received"
assert self._activity.props.id == self._AA_ID, "ID doesn't match expected"
assert self._activity.props.name == self._AA_NAME, "Name doesn't match expected"
assert self._activity.props.color == self._AA_COLOR, "Color doesn't match expected"
assert self._activity.props.type == self._AA_TYPE, "Type doesn't match expected"
assert self._activity.props.joined == False, "Joined doesn't match expected"
# Try to get activity by activity ID
act2 = ps.get_activity(self._AA_ID)
assert act2.props.id == self._AA_ID, "ID doesn't match expected"
assert act2.props.name == self._AA_NAME, "Name doesn't match expected"
assert act2.props.color == self._AA_COLOR, "Color doesn't match expected"
assert act2.props.type == self._AA_TYPE, "Type doesn't match expected"
assert act2.props.joined == False, "Joined doesn't match expected"
def _testActivityDisappeared_helper_timeout(self):
self._handle_error("Timeout waiting for activity-disappeared signal")
return False
def _testActivityDisappeared_helper_cb(self, ps, activity):
self._activity = activity
self._handle_success()
def _testActivityDisappeared_helper(self):
busobj = dbus.SessionBus().get_object(mockps._PRESENCE_SERVICE,
mockps._PRESENCE_PATH)
try:
testps = dbus.Interface(busobj, mockps._PRESENCE_TEST_INTERFACE)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
# Add a fake activity
try:
testps.AddActivity(self._AA_ID, self._AA_NAME, self._AA_COLOR, self._AA_TYPE, {})
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
ps = get_ps()
sid = ps.connect('activity-disappeared', self._testActivityDisappeared_helper_cb)
self._signals.append((ps, sid))
# Wait 5 seconds max for signal to be emitted
sid = gobject.timeout_add(5000, self._testActivityDisappeared_helper_timeout)
self._sources.append(sid)
# Delete the fake activity
try:
testps.RemoveActivity(self._AA_ID)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
return False
def testActivityDisappeared(self):
ps = get_ps()
assert ps, "Couldn't get presence service"
self._activity = None
gobject.idle_add(self._testActivityDisappeared_helper)
gtk.main()
assert self._success == True, "Test unsuccessful"
assert self._activity, "Activity was not received"
assert self._activity.props.id == self._AA_ID, "ID doesn't match expected"
assert self._activity.props.name == self._AA_NAME, "Name doesn't match expected"
assert self._activity.props.color == self._AA_COLOR, "Color doesn't match expected"
assert self._activity.props.type == self._AA_TYPE, "Type doesn't match expected"
assert self._activity.props.joined == False, "Joined doesn't match expected"
def _testActivityShare_helper_is_done(self):
if self._got_act_appeared and self._got_joined_activity:
self._handle_success()
def _testActivityShare_helper_timeout(self):
self._handle_error("Timeout waiting for activity share")
return False
def _testActivityShare_helper_joined_activity_cb(self, buddy, activity):
self._joined_activity_buddy = buddy
self._joined_activity_activity = activity
self._got_joined_activity = True
self._testActivityShare_helper_is_done()
def _testActivityShare_helper_cb(self, ps, activity):
self._activity = activity
self._got_act_appeared = True
self._testActivityShare_helper_is_done()
def _testActivityShare_helper(self):
ps = get_ps()
mockact = MockSugarActivity(self._AA_ID, self._AA_NAME, self._AA_TYPE)
sid = ps.connect('activity-appeared', self._testActivityShare_helper_cb)
self._signals.append((ps, sid))
try:
# Hook up to the owner's joined-activity signal
owner = ps.get_owner()
sid = owner.connect("joined-activity", self._testActivityShare_helper_joined_activity_cb)
self._signals.append((owner, sid))
except RuntimeError, err:
self._handle_error(err)
return False
# Wait 5 seconds max for signal to be emitted
sid = gobject.timeout_add(5000, self._testActivityShare_helper_timeout)
self._sources.append(sid)
ps.share_activity(mockact, self._AA_PROPS)
return False
def testActivityShare(self):
ps = get_ps()
assert ps, "Couldn't get presence service"
self._activity = None
self._got_act_appeared = False
self._joined_activity_buddy = None
self._joined_activity_activity = None
self._got_joined_activity = False
gobject.idle_add(self._testActivityShare_helper)
gtk.main()
assert self._success == True, "Test unsuccessful."
assert self._activity, "Shared activity was not received"
assert self._activity.props.id == self._AA_ID, "ID doesn't match expected"
assert self._activity.props.name == self._AA_NAME, "Name doesn't match expected"
# Shared activities from local machine take the owner's color
assert self._activity.props.color == mockps._OWNER_COLOR, "Color doesn't match expected"
assert self._activity.props.type == self._AA_TYPE, "Type doesn't match expected"
assert self._activity.props.joined == False, "Joined doesn't match expected"
buddies = self._activity.get_joined_buddies()
assert len(buddies) == 1, "No buddies in activity"
owner = buddies[0]
assert owner.props.key == mockps._OWNER_PUBKEY, "Buddy key doesn't match expected"
assert owner.props.nick == mockps._OWNER_NICK, "Buddy nick doesn't match expected"
assert owner.props.color == mockps._OWNER_COLOR, "Buddy color doesn't match expected"
real_owner = ps.get_owner()
assert real_owner == owner, "Owner mismatch"
assert self._joined_activity_activity == self._activity, "Activity mismatch"
assert self._joined_activity_buddy == owner, "Owner mismatch"
def _testActivityJoin_helper_is_done(self):
if self._got_act_appeared and self._got_joined_activity and \
self._got_buddy_joined:
self._handle_success()
def _testActivityJoin_helper_timeout(self):
self._handle_error("Timeout waiting for activity share")
return False
def _testActivityJoin_helper_buddy_joined_cb(self, activity, buddy):
self._buddy_joined_buddy = buddy
self._buddy_joined_activity = activity
self._got_buddy_joined = True
self._testActivityJoin_helper_is_done()
def _testActivityJoin_helper_joined_activity_cb(self, buddy, activity):
self._joined_activity_buddy = buddy
self._joined_activity_activity = activity
self._got_joined_activity = True
self._testActivityJoin_helper_is_done()
def _testActivityJoin_helper_cb(self, ps, activity):
self._activity = activity
self._got_act_appeared = True
# Hook up to the join signals
sid = activity.connect("buddy-joined", self._testActivityJoin_helper_buddy_joined_cb)
self._signals.append((activity, sid))
ps = get_ps()
owner = ps.get_owner()
sid = owner.connect("joined-activity", self._testActivityJoin_helper_joined_activity_cb)
self._signals.append((owner, sid))
# Join the activity
activity.join()
def _testActivityJoin_helper(self):
busobj = dbus.SessionBus().get_object(mockps._PRESENCE_SERVICE,
mockps._PRESENCE_PATH)
try:
testps = dbus.Interface(busobj, mockps._PRESENCE_TEST_INTERFACE)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
ps = get_ps()
sid = ps.connect('activity-appeared', self._testActivityJoin_helper_cb)
self._signals.append((ps, sid))
# Add a fake activity
try:
testps.AddActivity(self._AA_ID, self._AA_NAME, self._AA_COLOR, self._AA_TYPE, {})
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
# Wait 5 seconds max for signal to be emitted
sid = gobject.timeout_add(5000, self._testActivityJoin_helper_timeout)
self._sources.append(sid)
return False
def testActivityJoin(self):
ps = get_ps()
assert ps, "Couldn't get presence service"
self._activity = None
self._got_act_appeared = False
self._joined_activity_buddy = None
self._joined_activity_activity = None
self._got_joined_activity = False
self._buddy_joined_buddy = None
self._buddy_joined_activity = None
self._got_buddy_joined = False
gobject.idle_add(self._testActivityJoin_helper)
gtk.main()
assert self._success == True, "Test unsuccessful"
assert self._activity, "Shared activity was not received"
assert self._activity.props.id == self._AA_ID, "ID doesn't match expected"
assert self._activity.props.name == self._AA_NAME, "Name doesn't match expected"
buddies = self._activity.get_joined_buddies()
assert len(buddies) == 1, "No buddies in activity"
owner = buddies[0]
assert owner.props.key == mockps._OWNER_PUBKEY, "Buddy key doesn't match expected"
assert owner.props.nick == mockps._OWNER_NICK, "Buddy nick doesn't match expected"
assert owner.props.color == mockps._OWNER_COLOR, "Buddy color doesn't match expected"
real_owner = ps.get_owner()
assert real_owner == owner, "Owner mismatch"
assert self._joined_activity_activity == self._activity, "Activity mismatch"
assert self._joined_activity_buddy == owner, "Owner mismatch"
assert self._buddy_joined_activity == self._activity, "Activity mismatch"
assert self._buddy_joined_buddy == owner, "Owner mismatch"
def _testCurrentActivity_helper_timeout(self):
self._handle_error("Timeout waiting for current activity")
return False
def _testCurrentActivity_set_current_activity(self, actid):
busobj = dbus.SessionBus().get_object(mockps._PRESENCE_SERVICE,
mockps._PRESENCE_PATH)
try:
testps = dbus.Interface(busobj, mockps._PRESENCE_TEST_INTERFACE)
testps.SetBuddyCurrentActivity(self._buddy.props.key, actid)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return
def _testCurrentActivity_buddy_property_changed_cb(self, buddy, proplist):
if not self._start_monitor:
return
if not 'current-activity' in proplist:
return
buddy_curact = buddy.props.current_activity
if buddy_curact.props.id == self._AA_ID:
self._got_first_curact = True
# set next current activity
self._testCurrentActivity_set_current_activity(self._other_actid)
elif buddy_curact.props.id == self._other_actid:
self._got_other_curact = True
if self._got_first_curact and self._got_other_curact:
self._handle_success()
def _testCurrentActivity_start_monitor_helper(self):
if len(self._activities) != 2 or not self._buddy:
return
self._start_monitor = True
# Set first current activity
self._testCurrentActivity_set_current_activity(self._AA_ID)
def _testCurrentActivity_activity_helper_cb(self, ps, activity):
if activity in self._activities:
self._handle_error("Activity %s already known." % activity.props.id)
self._activities.append(activity)
self._testCurrentActivity_start_monitor_helper()
def _testCurrentActivity_buddy_helper_cb(self, ps, buddy):
self._buddy = buddy
sid = buddy.connect("property-changed", self._testCurrentActivity_buddy_property_changed_cb)
self._signals.append((buddy, sid))
self._testCurrentActivity_start_monitor_helper()
def _testCurrentActivity_helper(self):
busobj = dbus.SessionBus().get_object(mockps._PRESENCE_SERVICE,
mockps._PRESENCE_PATH)
try:
testps = dbus.Interface(busobj, mockps._PRESENCE_TEST_INTERFACE)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
ps = get_ps()
sid = ps.connect('activity-appeared', self._testCurrentActivity_activity_helper_cb)
self._signals.append((ps, sid))
sid = ps.connect('buddy-appeared', self._testCurrentActivity_buddy_helper_cb)
self._signals.append((ps, sid))
# Add a fake buddy
try:
testps.AddBuddy(BuddyTests._BA_PUBKEY, BuddyTests._BA_NICK, BuddyTests._BA_COLOR)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
# Add first fake activity
try:
testps.AddActivity(self._AA_ID, self._AA_NAME, self._AA_COLOR, self._AA_TYPE, {})
testps.AddBuddyToActivity(BuddyTests._BA_PUBKEY, self._AA_ID)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
# Add second fake activity
try:
testps.AddActivity(self._other_actid, self._other_actname,
self._other_actcolor, self._AA_TYPE, {})
testps.AddBuddyToActivity(BuddyTests._BA_PUBKEY, self._other_actid)
except dbus.exceptions.DBusException, err:
self._handle_error(err)
return False
# Wait 10 seconds max for everything to complete
sid = gobject.timeout_add(10000, self._testCurrentActivity_helper_timeout)
self._sources.append(sid)
return False
def testCurrentActivity(self):
ps = get_ps()
assert ps, "Couldn't get presence service"
self._other_actid = "ea8a94522c53a6741e141adece1711e4d9884678"
self._other_actname = "Some random activity"
self._other_actcolor = "#073838,#3A6E3A"
self._activities = []
self._got_first_curact = False
self._got_other_curact = False
self._start_monitor = False
gobject.idle_add(self._testCurrentActivity_helper)
gtk.main()
assert self._success == True, "Test unsuccessful"
assert len(self._activities) == 2, "Shared activities were not received"
assert self._got_first_curact == True, "Couldn't discover first activity"
assert self._got_other_curact == True, "Couldn't discover second activity"
assert self._start_monitor == True, "Couldn't discover both activities"
# check the buddy
assert self._buddy.props.key == BuddyTests._BA_PUBKEY, "Buddy key doesn't match expected"
assert self._buddy.props.nick == BuddyTests._BA_NICK, "Buddy nick doesn't match expected"
assert self._buddy.props.color == BuddyTests._BA_COLOR, "Buddy color doesn't match expected"
assert self._buddy.props.current_activity.props.id == self._other_actid, "Buddy current activity didn't match expected"
# check both activities
found = 0
for act in self._activities:
if act.props.id == self._AA_ID:
assert act.props.name == self._AA_NAME, "Name doesn't match expected"
assert act.props.color == self._AA_COLOR, "Color doesn't match expected"
buddies = act.get_joined_buddies()
assert len(buddies) == 1, "Unexpected number of buddies in first activity"
assert buddies[0] == self._buddy, "Unexpected buddy in first activity"
found += 1
elif act.props.id == self._other_actid:
assert act.props.name == self._other_actname, "Name doesn't match expected"
assert act.props.color == self._other_actcolor, "Color doesn't match expected"
buddies = act.get_joined_buddies()
assert len(buddies) == 1, "Unexpected number of buddies in first activity"
assert buddies[0] == self._buddy, "Unexpected buddy in first activity"
found += 1
assert found == 2, "Couldn't discover both activities"
def addToSuite(suite):
suite.addTest(ActivityTests("testActivityAppeared"))
suite.addTest(ActivityTests("testActivityDisappeared"))
suite.addTest(ActivityTests("testActivityShare"))
suite.addTest(ActivityTests("testActivityJoin"))
suite.addTest(ActivityTests("testCurrentActivity"))
addToSuite = staticmethod(addToSuite)
def main():
import logging
logging.basicConfig(level=logging.DEBUG)
suite = unittest.TestSuite()
BuddyTests.addToSuite(suite)
ActivityTests.addToSuite(suite)
runner = unittest.TextTestRunner()
runner.run(suite)
if __name__ == "__main__":
main()

View File

@ -1,30 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import sys
from sugar import objects
print 'MIME type for test.pdf (from extension):'
print objects.mime.get_from_file_name('test.pdf')
print ''
if len(sys.argv) > 1:
print 'MIME type for file %s:' % sys.argv[1]
print objects.mime.get_for_file(sys.argv[1])

View File

@ -1,42 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2007, Eduardo Silva (edsiper@gmail.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import pygtk
pygtk.require('2.0')
import gtk
from sugar.graphics.notebook import Notebook
window = gtk.Window()
window.connect("destroy", lambda w: gtk.main_quit())
window.set_size_request(800, 600)
window.show_all()
nb = Notebook(can_close_tabs=True)
window.add(nb)
button1 = gtk.Button('Example 1')
button2 = gtk.Button('Example 2')
button3 = gtk.Button('Example 3')
nb.add_page('Testing label 1', button1)
nb.add_page('Testing label 2', button2)
nb.add_page('Testing label 3', button3)
gtk.main()

View File

@ -1,66 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import sys
import gtk
import hippo
from sugar.graphics.xocolor import XoColor
from sugar.graphics.canvasicon import CanvasIcon
from sugar import env
sys.path.append(env.get_shell_path())
from view.home.snowflakelayout import SnowflakeLayout
def add_snowflake(parent, size):
box = hippo.CanvasBox()
parent.append(box)
layout = SnowflakeLayout()
box.set_layout(layout)
icon = CanvasIcon(scale=0.8, xo_color=XoColor(),
icon_name='theme:object-link')
layout.add_center(icon)
for k in range(0, size):
icon = CanvasIcon(scale=0.4, xo_color=XoColor(),
icon_name='theme:stock-buddy')
layout.add(icon)
window = gtk.Window()
window.set_default_size(gtk.gdk.screen_width(), gtk.gdk.screen_height())
window.connect("destroy", lambda w: gtk.main_quit())
window.show()
canvas = hippo.Canvas()
root = hippo.CanvasBox(background_color=0xe2e2e2ff)
canvas.set_root(root)
add_snowflake(root, 10)
add_snowflake(root, 20)
add_snowflake(root, 15)
add_snowflake(root, 5)
canvas.show()
window.add(canvas)
gtk.main()

View File

@ -1,62 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import sys
import random
import pygtk
pygtk.require('2.0')
import gobject
import gtk
import hippo
from sugar.graphics.spreadlayout import SpreadLayout
from sugar.graphics.xocolor import XoColor
from sugar.graphics.canvasicon import CanvasIcon
def _create_icon():
color = XoColor()
scale = 1.0 + random.random() * 1.5
icon = CanvasIcon(scale=scale, xo_color=color,
icon_name='theme:stock-buddy')
icon.set_tooltip('Test')
layout.add(icon)
return (len(box.get_children()) < 50)
window = gtk.Window()
window.connect("destroy", lambda w: gtk.main_quit())
window.show()
canvas = hippo.Canvas()
box = hippo.CanvasBox(background_color=0xe2e2e2ff)
layout = SpreadLayout()
box.set_layout(layout)
canvas.set_root(box)
window.add(canvas)
canvas.show()
gobject.timeout_add(200, _create_icon)
gtk.main()

View File

@ -1,109 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import gtk
from sugar.graphics.window import Window
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.toolbox import Toolbox
from sugar.graphics.palette import Palette
class EditToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
class TextToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
button = ToolButton('text-format-bold')
self.insert(button, -1)
button.show()
palette = Palette()
button.set_palette(palette)
palette.set_primary_state('This is a palette')
menu_item = gtk.MenuItem('First menu item')
palette.append_menu_item(menu_item)
menu_item = gtk.MenuItem('Second menu item')
palette.append_menu_item(menu_item)
menu_item = gtk.MenuItem('Third menu item')
palette.append_menu_item(menu_item)
class ImageToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
class TableToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
class FormatToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
class ViewToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
window = Window()
window.connect("destroy", lambda w: gtk.main_quit())
toolbox = Toolbox()
window.set_toolbox(toolbox)
toolbox.show()
edit_toolbar = EditToolbar()
toolbox.add_toolbar('Edit', edit_toolbar)
edit_toolbar.show()
text_toolbar = TextToolbar()
toolbox.add_toolbar('Text', text_toolbar)
text_toolbar.show()
image_toolbar = ImageToolbar()
toolbox.add_toolbar('Image', image_toolbar)
image_toolbar.show()
table_toolbar = TableToolbar()
toolbox.add_toolbar('Table', table_toolbar)
table_toolbar.show()
format_toolbar = FormatToolbar()
toolbox.add_toolbar('Format', format_toolbar)
format_toolbar.show()
view_toolbar = ViewToolbar()
toolbox.add_toolbar('View', view_toolbar)
view_toolbar.show()
toolbox.set_current_toolbar(1)
scrolled_window = gtk.ScrolledWindow()
scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
window.set_canvas(scrolled_window)
scrolled_window.show()
text_view = gtk.TextView()
scrolled_window.add(text_view)
text_view.show()
window.show()
gtk.main()