From 655d7f41741d90617aa341db7542153261c9f6e7 Mon Sep 17 00:00:00 2001 From: John (J5) Palmieri Date: Mon, 20 Aug 2007 18:41:03 +0000 Subject: Merge branch 'master' of git+ssh://j5@dev.laptop.org/git/sugar --- diff --git a/NEWS b/NEWS index 0068c95..ecfc924 100644 --- a/NEWS +++ b/NEWS @@ -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 diff --git a/lib/ui/Makefile.am b/lib/ui/Makefile.am index bae36c6..5c432be 100644 --- a/lib/ui/Makefile.am +++ b/lib/ui/Makefile.am @@ -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 \ diff --git a/lib/ui/sexy-icon-entry.c b/lib/ui/sexy-icon-entry.c new file mode 100644 index 0000000..ca35209 --- /dev/null +++ b/lib/ui/sexy-icon-entry.c @@ -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 +#include +#include + +#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; +} + diff --git a/lib/ui/sexy-icon-entry.h b/lib/ui/sexy-icon-entry.h new file mode 100644 index 0000000..eb83fed --- /dev/null +++ b/lib/ui/sexy-icon-entry.h @@ -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 +#include + +#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_ */ diff --git a/po/mk.po b/po/mk.po index 7dbefec..99173f6 100644 --- a/po/mk.po +++ b/po/mk.po @@ -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 \n" "Language-Team: Macedonian \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 активност" diff --git a/services/console/console.py b/services/console/console.py index dae34c3..4fb3609 100755 --- a/services/console/console.py +++ b/services/console/console.py @@ -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() diff --git a/services/shell/bundleregistry.py b/services/shell/bundleregistry.py index 65a2348..19d0032 100644 --- a/services/shell/bundleregistry.py +++ b/services/shell/bundleregistry.py @@ -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 diff --git a/services/shell/clipboardobject.py b/services/shell/clipboardobject.py index bc51f47..c3cdab3 100644 --- a/services/shell/clipboardobject.py +++ b/services/shell/clipboardobject.py @@ -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 diff --git a/services/shell/clipboardservice.py b/services/shell/clipboardservice.py index 19958a7..90e1b8e 100644 --- a/services/shell/clipboardservice.py +++ b/services/shell/clipboardservice.py @@ -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) diff --git a/services/shell/objecttypeservice.py b/services/shell/objecttypeservice.py index e12398e..f949375 100644 --- a/services/shell/objecttypeservice.py +++ b/services/shell/objecttypeservice.py @@ -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, diff --git a/shell/intro/colorpicker.py b/shell/intro/colorpicker.py index f7ab59c..d70d84c 100644 --- a/shell/intro/colorpicker.py +++ b/shell/intro/colorpicker.py @@ -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) diff --git a/shell/model/homeactivity.py b/shell/model/homeactivity.py index e95fe9a..434a3a6 100644 --- a/shell/model/homeactivity.py +++ b/shell/model/homeactivity.py @@ -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 diff --git a/shell/model/homemodel.py b/shell/model/homemodel.py index dac434a..759c2b8 100644 --- a/shell/model/homemodel.py +++ b/shell/model/homemodel.py @@ -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) diff --git a/shell/view/BuddyIcon.py b/shell/view/BuddyIcon.py index ebd12f0..f16b71c 100644 --- a/shell/view/BuddyIcon.py +++ b/shell/view/BuddyIcon.py @@ -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 diff --git a/shell/view/BuddyMenu.py b/shell/view/BuddyMenu.py index e3efb5c..e3d1a20 100644 --- a/shell/view/BuddyMenu.py +++ b/shell/view/BuddyMenu.py @@ -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) diff --git a/shell/view/clipboardicon.py b/shell/view/clipboardicon.py index 2e60e36..c9d910a 100644 --- a/shell/view/clipboardicon.py +++ b/shell/view/clipboardicon.py @@ -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 diff --git a/shell/view/clipboardmenu.py b/shell/view/clipboardmenu.py index 28ea0bb..0875f70 100644 --- a/shell/view/clipboardmenu.py +++ b/shell/view/clipboardmenu.py @@ -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() diff --git a/shell/view/devices/battery.py b/shell/view/devices/battery.py index 16863d3..cdd2e78 100644 --- a/shell/view/devices/battery.py +++ b/shell/view/devices/battery.py @@ -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 diff --git a/shell/view/devices/network/mesh.py b/shell/view/devices/network/mesh.py index 1f108cb..10830f2 100644 --- a/shell/view/devices/network/mesh.py +++ b/shell/view/devices/network/mesh.py @@ -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) diff --git a/shell/view/devices/network/wired.py b/shell/view/devices/network/wired.py index e2b9ce3..662c672 100644 --- a/shell/view/devices/network/wired.py +++ b/shell/view/devices/network/wired.py @@ -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' diff --git a/shell/view/devices/network/wireless.py b/shell/view/devices/network/wireless.py index 0be096d..9f0abd8 100644 --- a/shell/view/devices/network/wireless.py +++ b/shell/view/devices/network/wireless.py @@ -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): diff --git a/shell/view/frame/FriendsBox.py b/shell/view/frame/FriendsBox.py index 562c042..30f5963 100644 --- a/shell/view/frame/FriendsBox.py +++ b/shell/view/frame/FriendsBox.py @@ -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): diff --git a/shell/view/frame/clipboardbox.py b/shell/view/frame/clipboardbox.py index 60c154b..33d583c 100644 --- a/shell/view/frame/clipboardbox.py +++ b/shell/view/frame/clipboardbox.py @@ -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': diff --git a/shell/view/frame/clipboardpanelwindow.py b/shell/view/frame/clipboardpanelwindow.py index 70b87e4..217096a 100644 --- a/shell/view/frame/clipboardpanelwindow.py +++ b/shell/view/frame/clipboardpanelwindow.py @@ -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) diff --git a/shell/view/frame/frame.py b/shell/view/frame/frame.py index 1c4680e..2582fe6 100644 --- a/shell/view/frame/frame.py +++ b/shell/view/frame/frame.py @@ -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 diff --git a/shell/view/frame/frameinvoker.py b/shell/view/frame/frameinvoker.py index 1b99d50..8be0eaf 100644 --- a/shell/view/frame/frameinvoker.py +++ b/shell/view/frame/frameinvoker.py @@ -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 diff --git a/shell/view/frame/framewindow.py b/shell/view/frame/framewindow.py index 469ea65..6738bf2 100644 --- a/shell/view/frame/framewindow.py +++ b/shell/view/frame/framewindow.py @@ -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) diff --git a/shell/view/home/HomeBox.py b/shell/view/home/HomeBox.py index 2fa2183..f340c1a 100644 --- a/shell/view/home/HomeBox.py +++ b/shell/view/home/HomeBox.py @@ -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() diff --git a/shell/view/home/HomeWindow.py b/shell/view/home/HomeWindow.py index ce8fe74..2f7040e 100644 --- a/shell/view/home/HomeWindow.py +++ b/shell/view/home/HomeWindow.py @@ -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 diff --git a/shell/view/home/MeshBox.py b/shell/view/home/MeshBox.py index e632770..ff6d290 100644 --- a/shell/view/home/MeshBox.py +++ b/shell/view/home/MeshBox.py @@ -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): diff --git a/shell/view/home/MyIcon.py b/shell/view/home/MyIcon.py index 2168b0b..f4013b9 100644 --- a/shell/view/home/MyIcon.py +++ b/shell/view/home/MyIcon.py @@ -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()) diff --git a/shell/view/home/activitiesdonut.py b/shell/view/home/activitiesdonut.py index 3b12a09..306cdf5 100644 --- a/shell/view/home/activitiesdonut.py +++ b/shell/view/home/activitiesdonut.py @@ -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') diff --git a/shell/view/keyhandler.py b/shell/view/keyhandler.py index 1d5c481..23075d4 100644 --- a/shell/view/keyhandler.py +++ b/shell/view/keyhandler.py @@ -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() diff --git a/sugar-emulator b/sugar-emulator index 6ab9292..3a96a8a 100755 --- a/sugar-emulator +++ b/sugar-emulator @@ -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__": diff --git a/sugar/_sugaruiext.defs b/sugar/_sugaruiext.defs index 3c011e1..6a9129d 100644 --- a/sugar/_sugaruiext.defs +++ b/sugar/_sugaruiext.defs @@ -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") +) + diff --git a/sugar/_sugaruiext.override b/sugar/_sugaruiext.override index 6daafc3..beeaad0 100644 --- a/sugar/_sugaruiext.override +++ b/sugar/_sugaruiext.override @@ -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 #include @@ -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 diff --git a/sugar/_sugaruiextmodule.c b/sugar/_sugaruiextmodule.c index 8c74010..719b153 100644 --- a/sugar/_sugaruiextmodule.c +++ b/sugar/_sugaruiextmodule.c @@ -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"); diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py index 262d89b..553b1a1 100644 --- a/sugar/activity/activity.py +++ b/sugar/activity/activity.py @@ -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 diff --git a/sugar/activity/activityservice.py b/sugar/activity/activityservice.py index c9ee482..f2b3394 100644 --- a/sugar/activity/activityservice.py +++ b/sugar/activity/activityservice.py @@ -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 diff --git a/sugar/activity/bundlebuilder.py b/sugar/activity/bundlebuilder.py index 3bbe454..b0e46ab 100644 --- a/sugar/activity/bundlebuilder.py +++ b/sugar/activity/bundlebuilder.py @@ -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': diff --git a/sugar/clipboard/clipboardservice.py b/sugar/clipboard/clipboardservice.py index dbdf41d..0e357fe 100644 --- a/sugar/clipboard/clipboardservice.py +++ b/sugar/clipboard/clipboardservice.py @@ -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),) diff --git a/sugar/datastore/datastore.py b/sugar/datastore/datastore.py index 5ba994d..0dbe35b 100644 --- a/sugar/datastore/datastore.py +++ b/sugar/datastore/datastore.py @@ -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() diff --git a/sugar/datastore/dbus_helpers.py b/sugar/datastore/dbus_helpers.py index f0cfa3b..442a35d 100644 --- a/sugar/datastore/dbus_helpers.py +++ b/sugar/datastore/dbus_helpers.py @@ -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')) diff --git a/sugar/date.py b/sugar/date.py index 3b68818..3f4dcc2 100644 --- a/sugar/date.py +++ b/sugar/date.py @@ -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') diff --git a/sugar/graphics/Makefile.am b/sugar/graphics/Makefile.am index af66cfb..2d0dc17 100644 --- a/sugar/graphics/Makefile.am +++ b/sugar/graphics/Makefile.am @@ -9,7 +9,7 @@ sugar_PYTHON = \ combobox.py \ icon.py \ iconbutton.py \ - menuitem.py \ + iconentry.py \ notebook.py \ objectchooser.py \ radiotoolbutton.py \ diff --git a/sugar/graphics/canvasbutton.py b/sugar/graphics/canvasbutton.py index 31bc833..fc869f9 100644 --- a/sugar/graphics/canvasbutton.py +++ b/sugar/graphics/canvasbutton.py @@ -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() diff --git a/sugar/graphics/canvasicon.py b/sugar/graphics/canvasicon.py index 39f1358..a77eecd 100644 --- a/sugar/graphics/canvasicon.py +++ b/sugar/graphics/canvasicon.py @@ -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] diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index 731412f..e324d45 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -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) diff --git a/sugar/graphics/iconentry.py b/sugar/graphics/iconentry.py new file mode 100644 index 0000000..2f7584f --- /dev/null +++ b/sugar/graphics/iconentry.py @@ -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) + diff --git a/sugar/graphics/menuitem.py b/sugar/graphics/menuitem.py index 492f5f1..db4a293 100644 --- a/sugar/graphics/menuitem.py +++ b/sugar/graphics/menuitem.py @@ -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() diff --git a/sugar/graphics/objectchooser.py b/sugar/graphics/objectchooser.py index 5b09e13..46739f6 100644 --- a/sugar/graphics/objectchooser.py +++ b/sugar/graphics/objectchooser.py @@ -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 diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py index 368a0f6..6b1209a 100644 --- a/sugar/graphics/palette.py +++ b/sugar/graphics/palette.py @@ -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): + def has_rectangle_gap(self): + return True + + def draw_rectangle(self, event, palette): style = self._widget.style - if palette.is_up(): - gap = _calculate_gap(self.get_rect(), palette.get_rect()) - - if gap: - style.paint_box_gap(event.window, gtk.STATE_PRELIGHT, - gtk.SHADOW_IN, event.area, self._widget, - "palette-invoker", - self._widget.allocation.x, - self._widget.allocation.y, - self._widget.allocation.width, - self._widget.allocation.height, - gap[0], gap[1], gap[2]) - else: - style.paint_box(event.window, gtk.STATE_PRELIGHT, + 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) diff --git a/sugar/graphics/palettegroup.py b/sugar/graphics/palettegroup.py index d5e1125..d6ae122 100644 --- a/sugar/graphics/palettegroup.py +++ b/sugar/graphics/palettegroup.py @@ -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') diff --git a/sugar/graphics/radiotoolbutton.py b/sugar/graphics/radiotoolbutton.py index fb584ee..4ffc7d1 100644 --- a/sugar/graphics/radiotoolbutton.py +++ b/sugar/graphics/radiotoolbutton.py @@ -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) diff --git a/sugar/graphics/style.py b/sugar/graphics/style.py index c0094b6..9d561eb 100644 --- a/sugar/graphics/style.py +++ b/sugar/graphics/style.py @@ -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') diff --git a/sugar/graphics/toggletoolbutton.py b/sugar/graphics/toggletoolbutton.py index 41050e2..74e78a6 100644 --- a/sugar/graphics/toggletoolbutton.py +++ b/sugar/graphics/toggletoolbutton.py @@ -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) diff --git a/sugar/graphics/toolbutton.py b/sugar/graphics/toolbutton.py index 52a5d62..b42f63e 100644 --- a/sugar/graphics/toolbutton.py +++ b/sugar/graphics/toolbutton.py @@ -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) diff --git a/sugar/logger.py b/sugar/logger.py index e857044..72c4a99 100644 --- a/sugar/logger.py +++ b/sugar/logger.py @@ -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) diff --git a/sugar/objects/mime.py b/sugar/objects/mime.py index 80eac9b..b933143 100644 --- a/sugar/objects/mime.py +++ b/sugar/objects/mime.py @@ -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 diff --git a/sugar/presence/Makefile.am b/sugar/presence/Makefile.am index 6314c1a..cb52a41 100644 --- a/sugar/presence/Makefile.am +++ b/sugar/presence/Makefile.am @@ -3,5 +3,6 @@ sugar_PYTHON = \ __init__.py \ activity.py \ buddy.py \ + tubeconn.py \ presenceservice.py diff --git a/sugar/presence/tubeconn.py b/sugar/presence/tubeconn.py new file mode 100644 index 0000000..d1c1403 --- /dev/null +++ b/sugar/presence/tubeconn.py @@ -0,0 +1,107 @@ +# This should eventually land in telepathy-python, so has the same license: + +# Copyright (C) 2007 Collabora Ltd. +# +# 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, []) diff --git a/tests/graphics/common.py b/tests/graphics/common.py new file mode 100644 index 0000000..2f00099 --- /dev/null +++ b/tests/graphics/common.py @@ -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() diff --git a/tests/graphics/ticket2855.py b/tests/graphics/ticket2855.py new file mode 100644 index 0000000..f680e95 --- /dev/null +++ b/tests/graphics/ticket2855.py @@ -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) diff --git a/tests/lib/runall.py b/tests/lib/runall.py new file mode 100644 index 0000000..6ee9442 --- /dev/null +++ b/tests/lib/runall.py @@ -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) diff --git a/tests/lib/test_date.py b/tests/lib/test_date.py new file mode 100644 index 0000000..c6da871 --- /dev/null +++ b/tests/lib/test_date.py @@ -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() diff --git a/tests/lib/test_mime.py b/tests/lib/test_mime.py new file mode 100644 index 0000000..88598fe --- /dev/null +++ b/tests/lib/test_mime.py @@ -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() + diff --git a/tests/presence/mock-share-read.py b/tests/presence/mock-share-read.py deleted file mode 100755 index 80be169..0000000 --- a/tests/presence/mock-share-read.py +++ /dev/null @@ -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() diff --git a/tests/presence/mockps.py b/tests/presence/mockps.py deleted file mode 100755 index 3468515..0000000 --- a/tests/presence/mockps.py +++ /dev/null @@ -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() diff --git a/tests/presence/test-ps-bindings.py b/tests/presence/test-ps-bindings.py deleted file mode 100755 index ccf6617..0000000 --- a/tests/presence/test-ps-bindings.py +++ /dev/null @@ -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() diff --git a/tests/test-mime.py b/tests/test-mime.py deleted file mode 100755 index a7f185e..0000000 --- a/tests/test-mime.py +++ /dev/null @@ -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]) diff --git a/tests/test-notebook.py b/tests/test-notebook.py deleted file mode 100755 index 262b38e..0000000 --- a/tests/test-notebook.py +++ /dev/null @@ -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() diff --git a/tests/test-snowflake-layout.py b/tests/test-snowflake-layout.py deleted file mode 100755 index 279f638..0000000 --- a/tests/test-snowflake-layout.py +++ /dev/null @@ -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() diff --git a/tests/test-spread-layout.py b/tests/test-spread-layout.py deleted file mode 100755 index 412d7fc..0000000 --- a/tests/test-spread-layout.py +++ /dev/null @@ -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() diff --git a/tests/test-ui.py b/tests/test-ui.py deleted file mode 100755 index bb80201..0000000 --- a/tests/test-ui.py +++ /dev/null @@ -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() -- cgit v0.9.1