/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* Copyright (C) 2005 Carlos Garnacho * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * Authors: Jody Goldberg * Carlos Garnacho Parro */ #include "gnomecc-canvas.h" #include "gnomecc-event-box.h" #include "gnomecc-rounded-rect.h" #include #include #include #include #include #include #define GNOMECC_CANVAS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNOMECC_TYPE_CANVAS, GnomeccCanvasPrivate)) #define PAD 5 /*when scrolling keep a few pixels above or below if possible */ #define ABOVE_LINE_SPACING 4 #define UNDER_LINE_SPACING 0 #define UNDER_TITLE_SPACING 0 /* manually insert 1 blank line of text */ #define LINE_HEIGHT 1 #define BORDERS 7 #define PREFERRED_ITEM_WIDTH 120 #define ITEMS_SEPARATION 12 typedef struct _GnomeccCanvasPrivate GnomeccCanvasPrivate; struct _GnomeccCanvasPrivate { GnomeCanvasItem *under_cover; double height; double width; double min_height; double max_width; ControlCenterInformation *info; ControlCenterEntry *selected; gboolean rtl; gint items_per_row; /* calculated sizes for the elements */ gdouble max_item_width; gdouble max_item_height; gdouble max_icon_height; /* accessibility stuff */ GHashTable *accessible_children; gboolean single_click; }; typedef struct { GnomeccCanvas *canvas; GnomeCanvasGroup *group; GnomeCanvasItem *text; GnomeCanvasItem *pixbuf; GnomeCanvasItem *highlight_pixbuf; GnomeCanvasItem *cover; GnomeCanvasItem *selection; double icon_height; double icon_width; double text_height; guint launching : 1; guint selected : 1; guint highlighted : 1; gint n_category; gint n_entry; gint index; } EntryInfo; typedef struct { GnomeCanvasGroup *group; GnomeCanvasItem *title; GnomeCanvasItem *line; } CategoryInfo; enum { SELECTION_CHANGED, LAST_SIGNAL }; enum { PROP_0, PROP_INFO }; static guint gnomecc_canvas_signals [LAST_SIGNAL] = { 0 }; static void gnomecc_canvas_class_init (GnomeccCanvasClass *class); static void gnomecc_canvas_init (GnomeccCanvas *canvas); static void gnomecc_canvas_finalize (GObject *object); static void gnomecc_canvas_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gnomecc_canvas_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gnomecc_canvas_size_allocate (GtkWidget *canvas, GtkAllocation *allocation); static void gnomecc_canvas_style_set (GtkWidget *canvas, GtkStyle *previous_style); static void gnomecc_canvas_realize (GtkWidget *canvas); static AtkObject* gnomecc_canvas_get_accessible (GtkWidget *widget); G_DEFINE_TYPE (GnomeccCanvas, gnomecc_canvas, GNOME_TYPE_CANVAS); static void gnomecc_canvas_class_init (GnomeccCanvasClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); object_class->set_property = gnomecc_canvas_set_property; object_class->get_property = gnomecc_canvas_get_property; object_class->finalize = gnomecc_canvas_finalize; widget_class->style_set = gnomecc_canvas_style_set; widget_class->size_allocate = gnomecc_canvas_size_allocate; widget_class->realize = gnomecc_canvas_realize; widget_class->get_accessible = gnomecc_canvas_get_accessible; class->changed = NULL; g_object_class_install_property (object_class, PROP_INFO, g_param_spec_pointer ("info", "information for the canvas", "information for the canvas", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); gnomecc_canvas_signals [SELECTION_CHANGED] = g_signal_new ("selection-changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GnomeccCanvasClass, changed), NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); g_type_class_add_private (object_class, sizeof (GnomeccCanvasPrivate)); } static void gnomecc_canvas_init (GnomeccCanvas *canvas) { GnomeccCanvasPrivate *priv; g_return_if_fail (GNOMECC_IS_CANVAS (canvas)); priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); priv->max_width = 300; priv->min_height = 0; priv->info = NULL; priv->selected = NULL; priv->max_item_width = 0; priv->max_item_height = 0; priv->items_per_row = 0; priv->rtl = (gtk_widget_get_direction (GTK_WIDGET (canvas)) == GTK_TEXT_DIR_RTL); priv->accessible_children = g_hash_table_new (g_int_hash, g_int_equal); priv->single_click = FALSE; gtk_widget_show_all (GTK_WIDGET (canvas)); } static gboolean single_click_activates (GnomeccCanvas *canvas) { GnomeccCanvasPrivate *priv; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); return priv->single_click; } static void gnome_canvas_item_show_hide (GnomeCanvasItem *item, gboolean show) { if (show) gnome_canvas_item_show (item); else gnome_canvas_item_hide (item); } static void setup_entry (GnomeccCanvas *canvas, ControlCenterEntry *entry) { EntryInfo *ei; GtkWidget *widget; GtkStateType state; if (!entry) return; widget = GTK_WIDGET (canvas); ei = entry->user_data; if (ei->pixbuf) { gnome_canvas_item_show_hide (ei->highlight_pixbuf, ei->highlighted); gnome_canvas_item_show_hide (ei->pixbuf, !ei->highlighted); } if (!ei->selected) state = GTK_STATE_NORMAL; else if (gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (widget)))) state = GTK_STATE_SELECTED; else state = GTK_STATE_ACTIVE; gnome_canvas_item_show_hide (ei->selection, ei->selected); g_object_set (ei->selection, "fill_color_gdk", &widget->style->base [state], NULL); g_object_set (ei->text, "fill_color_gdk", &widget->style->text [state], NULL); } static gboolean cb_entry_info_reset (gpointer data) { EntryInfo *ei = data; ei->launching = FALSE; return FALSE; } static void activate_entry (ControlCenterEntry *entry) { EntryInfo *ei = entry->user_data; if (!ei->launching) { GnomeDesktopItem *desktop_item; ei->launching = TRUE; gtk_timeout_add (1000, cb_entry_info_reset, ei); desktop_item = gnome_desktop_item_new_from_file (entry->desktop_entry, GNOME_DESKTOP_ITEM_LOAD_ONLY_IF_EXISTS, NULL); if (desktop_item != NULL) { gnome_desktop_item_launch (desktop_item, NULL, 0, NULL); gnome_desktop_item_unref (desktop_item); } } } static void select_entry (GnomeccCanvas *canvas, ControlCenterEntry *entry) { GnomeccCanvasPrivate *priv; EntryInfo *ei = NULL; GtkAdjustment *pos; double affine[6]; ControlCenterEntry *selected; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); selected = priv->selected; if (selected == entry) return; if (selected && selected->user_data) ((EntryInfo *) selected->user_data)->selected = FALSE; setup_entry (canvas, selected); priv->selected = selected = entry; if (selected && selected->user_data) ((EntryInfo *) selected->user_data)->selected = TRUE; setup_entry (canvas, selected); g_signal_emit (canvas, gnomecc_canvas_signals [SELECTION_CHANGED], 0, (entry) ? entry->comment : NULL); if (!entry) return; ei = entry->user_data; gnome_canvas_item_i2c_affine (GNOME_CANVAS_ITEM (ei->group), affine); pos = gtk_layout_get_vadjustment (GTK_LAYOUT (ei->cover->canvas)); if (affine[5] < pos->value) gtk_adjustment_set_value (pos, MAX (affine[5] - PAD, 0)); else if ((affine[5] + priv->max_item_height) > (pos->value+pos->page_size)) gtk_adjustment_set_value (pos, MAX (MIN (affine[5] + priv->max_item_height + PAD, pos->upper) - pos->page_size, 0)); } static gboolean cover_event (GnomeCanvasItem *item, GdkEvent *event, ControlCenterEntry *entry) { EntryInfo *ei = entry->user_data; GnomeccCanvas *canvas = ei->canvas; GdkCursor *cursor; switch (event->type) { case GDK_ENTER_NOTIFY: ei->highlighted = TRUE; setup_entry (canvas, entry); /* highlight even if it is already selected */ if (single_click_activates (canvas)) { cursor = gdk_cursor_new (GDK_HAND1); gdk_window_set_cursor (GTK_WIDGET (canvas)->window, cursor); gdk_cursor_unref (cursor); } return TRUE; case GDK_LEAVE_NOTIFY: ei->highlighted = FALSE; setup_entry (canvas, entry); gdk_window_set_cursor (GTK_WIDGET (canvas)->window, NULL); return TRUE; case GDK_BUTTON_PRESS: select_entry (canvas, entry); if (single_click_activates (canvas)) activate_entry (entry); return TRUE; case GDK_2BUTTON_PRESS: activate_entry (entry); return TRUE; default: return FALSE; } } static gboolean cb_canvas_event (GnomeCanvasItem *item, GdkEvent *event, GnomeccCanvas *canvas) { GnomeccCanvasPrivate *priv; EntryInfo *ei = NULL; gint n_category, n_entry; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); if (event->type == GDK_BUTTON_PRESS) { select_entry (canvas, NULL); return TRUE; } if (event->type != GDK_KEY_PRESS) return FALSE; if (priv->selected) ei = priv->selected->user_data; n_entry = 0; n_category = 0; switch (event->key.keyval) { case GDK_KP_Right: case GDK_Right: if (ei) { n_entry = (priv->rtl) ? ei->n_entry - 1 : ei->n_entry + 1; n_category = ei->n_category; } break; case GDK_KP_Left: case GDK_Left: if (ei) { n_entry = (priv->rtl) ? ei->n_entry + 1 : ei->n_entry - 1; n_category = ei->n_category; } break; case GDK_KP_Down: case GDK_Down: if (ei) { n_category = ei->n_category; n_entry = ei->n_entry; if (ei->n_entry + priv->items_per_row < priv->info->categories[n_category]->n_entries) n_entry += priv->items_per_row; } break; case GDK_KP_Up: case GDK_Up: if (ei) { n_category = ei->n_category; n_entry = ei->n_entry; if (ei->n_entry - priv->items_per_row >= 0) n_entry -= priv->items_per_row; } break; case GDK_Tab: case GDK_KP_Tab: if (ei) { n_entry = 0; n_category = ei->n_category + 1; if (n_category > priv->info->n_categories - 1) n_category = 0; } break; case GDK_ISO_Left_Tab: if (ei) { n_entry = 0; n_category = ei->n_category - 1; if (n_category < 0) n_category = priv->info->n_categories; } break; case GDK_Return: case GDK_KP_Enter: if (priv->selected) activate_entry (priv->selected); return TRUE; case GDK_Escape: gtk_main_quit (); return TRUE; case 'q': case 'Q': if (event->key.state == GDK_CONTROL_MASK) { gtk_main_quit (); } return TRUE; default: return FALSE; } n_category = CLAMP (n_category, 0, priv->info->n_categories - 1); n_entry = CLAMP (n_entry, 0, priv->info->categories[n_category]->n_entries - 1); select_entry (canvas, priv->info->categories[n_category]->entries[n_entry]); return TRUE; } static void calculate_item_width (GnomeccCanvas *canvas, EntryInfo *ei) { GnomeccCanvasPrivate *priv; PangoLayout *layout; PangoRectangle rectangle; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); layout = GNOME_CANVAS_TEXT (ei->text)->layout; pango_layout_set_wrap (layout, PANGO_WRAP_WORD); pango_layout_set_width (layout, -1); pango_layout_get_pixel_extents (layout, NULL, &rectangle); /* If its too big wrap at the max and regen to find the layout */ if (rectangle.width > PREFERRED_ITEM_WIDTH) { pango_layout_set_width (layout, PREFERRED_ITEM_WIDTH * PANGO_SCALE); pango_layout_get_pixel_extents (layout, NULL, &rectangle); } ei->text_height = rectangle.height; priv->max_item_width = MAX (priv->max_item_width, rectangle.width); } static void calculate_item_height (GnomeccCanvas *canvas, EntryInfo *ei) { GnomeccCanvasPrivate *priv; gint item_height; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); if (ei->pixbuf) priv->max_icon_height = MAX (priv->max_icon_height, ei->icon_height); item_height = ei->icon_height + ei->text_height; priv->max_item_height = MAX (priv->max_item_height, item_height); } static void calculate_sizes (GnomeccCanvas *canvas) { GnomeccCanvasPrivate *priv; int i, j; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); priv->max_item_height = 0; priv->max_icon_height = 0; priv->max_item_width = 0; for (i = 0; i < priv->info->n_categories; i++) { for (j = 0; j < priv->info->categories[i]->n_entries; j++) { EntryInfo *ei = priv->info->categories[i]->entries[j]->user_data; calculate_item_width (canvas, ei); calculate_item_height (canvas, ei); } } } static void gnome_canvas_item_move_absolute (GnomeCanvasItem *item, double dx, double dy) { double translate[6]; g_return_if_fail (item != NULL); g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); art_affine_translate (translate, dx, dy); gnome_canvas_item_affine_absolute (item, translate); } static guchar lighten_component (guchar cur_value) { int new_value = cur_value; new_value += 24 + (new_value >> 3); if (new_value > 255) { new_value = 255; } return (guchar) new_value; } static GdkPixbuf * create_spotlight_pixbuf (GdkPixbuf* src) { GdkPixbuf *dest; int i, j; int width, height, has_alpha, src_row_stride, dst_row_stride; guchar *target_pixels, *original_pixels; guchar *pixsrc, *pixdest; g_return_val_if_fail (gdk_pixbuf_get_colorspace (src) == GDK_COLORSPACE_RGB, NULL); g_return_val_if_fail ((!gdk_pixbuf_get_has_alpha (src) && gdk_pixbuf_get_n_channels (src) == 3) || (gdk_pixbuf_get_has_alpha (src) && gdk_pixbuf_get_n_channels (src) == 4), NULL); g_return_val_if_fail (gdk_pixbuf_get_bits_per_sample (src) == 8, NULL); dest = gdk_pixbuf_copy (src); has_alpha = gdk_pixbuf_get_has_alpha (src); width = gdk_pixbuf_get_width (src); height = gdk_pixbuf_get_height (src); dst_row_stride = gdk_pixbuf_get_rowstride (dest); src_row_stride = gdk_pixbuf_get_rowstride (src); target_pixels = gdk_pixbuf_get_pixels (dest); original_pixels = gdk_pixbuf_get_pixels (src); for (i = 0; i < height; i++) { pixdest = target_pixels + i * dst_row_stride; pixsrc = original_pixels + i * src_row_stride; for (j = 0; j < width; j++) { *pixdest++ = lighten_component (*pixsrc++); *pixdest++ = lighten_component (*pixsrc++); *pixdest++ = lighten_component (*pixsrc++); if (has_alpha) { *pixdest++ = *pixsrc++; } } } return dest; } static void build_canvas (GnomeccCanvas *canvas) { GnomeccCanvasPrivate *priv; GnomeCanvas *gcanvas; int i, j, index; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); gcanvas = GNOME_CANVAS (canvas); index = 0; priv->under_cover = gnome_canvas_item_new (gnome_canvas_root (gcanvas), gnomecc_event_box_get_type(), NULL); gnome_canvas_item_grab_focus (GNOME_CANVAS_ITEM (gnome_canvas_root (gcanvas))); g_signal_connect (gnome_canvas_root (gcanvas), "event", G_CALLBACK (cb_canvas_event), canvas); for (i = 0; i < priv->info->n_categories; i++) { CategoryInfo *catinfo; if (priv->info->categories[i]->user_data == NULL) priv->info->categories[i]->user_data = g_new (CategoryInfo, 1); catinfo = priv->info->categories[i]->user_data; catinfo->group = NULL; catinfo->title = NULL; catinfo->line = NULL; catinfo->group = GNOME_CANVAS_GROUP (gnome_canvas_item_new (gnome_canvas_root (gcanvas), gnome_canvas_group_get_type (), NULL)); gnome_canvas_item_move_absolute (GNOME_CANVAS_ITEM (catinfo->group), 0, BORDERS); if (i > 0) { catinfo->line = gnome_canvas_item_new (catinfo->group, gnome_canvas_rect_get_type (), "x2", (double) priv->max_width - 2 * BORDERS, "y2", (double) LINE_HEIGHT, NULL); } catinfo->title = NULL; if (priv->info->categories[i] && (priv->info->n_categories != 1 || priv->info->categories[0]->real_category)) { char *label = g_strdup_printf ("%s", priv->info->categories[i]->title); catinfo->title = gnome_canvas_item_new (catinfo->group, gnome_canvas_text_get_type (), "text", priv->info->categories[i]->title, "markup", label, "anchor", GTK_ANCHOR_NW, NULL); g_free (label); } for (j = 0; j < priv->info->categories[i]->n_entries; j++) { EntryInfo *ei; if (priv->info->categories[i]->entries[j]->user_data == NULL) priv->info->categories[i]->entries[j]->user_data = g_new0 (EntryInfo, 1); ei = priv->info->categories[i]->entries[j]->user_data; ei->canvas = canvas; ei->group = GNOME_CANVAS_GROUP ( gnome_canvas_item_new (catinfo->group, gnome_canvas_group_get_type (), NULL)); ei->selection = gnome_canvas_item_new ( ei->group, GNOMECC_TYPE_ROUNDED_RECT, NULL); if (priv->info->categories[i]->entries[j]->title) { ei->text = gnome_canvas_item_new (ei->group, gnome_canvas_text_get_type (), "anchor", GTK_ANCHOR_NW, "justification", GTK_JUSTIFY_CENTER, "clip", TRUE, NULL); pango_layout_set_alignment (GNOME_CANVAS_TEXT (ei->text)->layout, PANGO_ALIGN_CENTER); pango_layout_set_justify (GNOME_CANVAS_TEXT (ei->text)->layout, FALSE); g_object_set (ei->text, "text", priv->info->categories[i]->entries[j]->title, NULL); } else ei->text = NULL; if (priv->info->categories[i]->entries[j]->icon_pixbuf) { GdkPixbuf *pixbuf = priv->info->categories[i]->entries[j]->icon_pixbuf; GdkPixbuf *highlight_pixbuf = create_spotlight_pixbuf (pixbuf); ei->icon_height = gdk_pixbuf_get_height (pixbuf); ei->icon_width = gdk_pixbuf_get_width (pixbuf); ei->pixbuf = gnome_canvas_item_new (ei->group, gnome_canvas_pixbuf_get_type (), "pixbuf", pixbuf, NULL); ei->highlight_pixbuf = gnome_canvas_item_new (ei->group, gnome_canvas_pixbuf_get_type (), "pixbuf", highlight_pixbuf, NULL); } else { ei->pixbuf = NULL; ei->highlight_pixbuf = NULL; } ei->cover = gnome_canvas_item_new (ei->group, gnomecc_event_box_get_type(), NULL); calculate_item_width (canvas, ei); calculate_item_height (canvas, ei); ei->n_category = i; ei->n_entry = j; ei->index = index; g_signal_connect (ei->cover, "event", G_CALLBACK (cover_event), priv->info->categories[i]->entries[j]); setup_entry (canvas, priv->info->categories[i]->entries[j]); index++; } } } static void gnomecc_canvas_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GnomeccCanvasPrivate *priv; priv = GNOMECC_CANVAS_GET_PRIVATE (object); switch (prop_id) { case PROP_INFO: priv->info = g_value_get_pointer (value); build_canvas (GNOMECC_CANVAS (object)); break; } } static void gnomecc_canvas_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GnomeccCanvasPrivate *priv; priv = GNOMECC_CANVAS_GET_PRIVATE (object); switch (prop_id) { case PROP_INFO: g_value_set_pointer (value, priv->info); break; } } static void gnomecc_canvas_finalize (GObject *object) { GnomeccCanvasPrivate *priv; priv = GNOMECC_CANVAS_GET_PRIVATE (object); g_hash_table_destroy (priv->accessible_children); if (G_OBJECT_CLASS (gnomecc_canvas_parent_class)->finalize) (* G_OBJECT_CLASS (gnomecc_canvas_parent_class)->finalize) (object); } /* A category canvas item contains all the elements * for the category, as well as the title and a separator */ static void relayout_category (GnomeccCanvas *canvas, CategoryInfo *catinfo, gint vert_pos, gint *category_vert_pos) { GnomeccCanvasPrivate *priv; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); gnome_canvas_item_move_absolute (GNOME_CANVAS_ITEM (catinfo->group), 0, vert_pos); if (catinfo->line) { gnome_canvas_item_move_absolute (catinfo->line, BORDERS, ABOVE_LINE_SPACING); gnome_canvas_item_set (catinfo->line, "x2", (double) priv->max_width - 2 * BORDERS, "y2", (double) LINE_HEIGHT, NULL); } if (catinfo->title) { double text_height, text_width; g_object_get (catinfo->title, "text_height", &text_height, "text_width", &text_width, NULL); *category_vert_pos += text_height; /* move it down 1 line */ gnome_canvas_item_move_absolute (catinfo->title, (priv->rtl) ? priv->max_width - BORDERS - text_width : BORDERS, *category_vert_pos); *category_vert_pos += text_height + text_height/2 + UNDER_TITLE_SPACING; } } static void relayout_item (GnomeccCanvas *canvas, EntryInfo *ei, gint category_horiz_pos, gint category_vert_pos) { GnomeccCanvasPrivate *priv; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); gnome_canvas_item_move_absolute (GNOME_CANVAS_ITEM (ei->group), category_horiz_pos, category_vert_pos); gnome_canvas_item_set (ei->selection, "x2", (double) priv->max_item_width + 2 * PAD, "y2", (double) ei->text_height + 1, /* expand it down slightly */ NULL); gnome_canvas_item_move_absolute (ei->selection, -PAD, priv->max_icon_height); if (ei->text) { /* canvas asks layout for its extent, layout gives real * size, not fixed width and drawing gets confused. */ gnome_canvas_item_set (ei->text, "clip_width", (double) priv->max_item_width, "clip_height", (double) priv->max_item_height, NULL); /* text is centered by pango */ pango_layout_set_width (GNOME_CANVAS_TEXT (ei->text)->layout, (gint) priv->max_item_width * PANGO_SCALE); gnome_canvas_item_move_absolute (ei->text, 0, priv->max_icon_height); } if (ei->pixbuf) { /* manually center the icon */ gnome_canvas_item_move_absolute (ei->pixbuf, priv->max_item_width / 2 - ei->icon_width / 2, 0); gnome_canvas_item_move_absolute (ei->highlight_pixbuf, priv->max_item_width / 2 - ei->icon_width / 2, 0); } /* cover the item */ gnome_canvas_item_set (ei->cover, "x2", (double) priv->max_item_width, "y2", (double) priv->max_item_height, NULL); } static void relayout_canvas (GnomeccCanvas *canvas) { gint cat, cat_count, entry, cat_entries; gint vert_pos, category_vert_pos, category_horiz_pos; gint real_width; GnomeccCanvasPrivate *priv; ControlCenterCategory *category; CategoryInfo *catinfo; EntryInfo *ei; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); real_width = priv->max_width - 2 * BORDERS + ITEMS_SEPARATION; priv->items_per_row = real_width / ((gint) priv->max_item_width + ITEMS_SEPARATION); cat_count = priv->info->n_categories; vert_pos = BORDERS; for (cat = 0; cat < cat_count; cat++) { category_vert_pos = 0; category_horiz_pos = (priv->rtl) ? priv->max_width - (gint) priv->max_item_width - BORDERS : BORDERS; category = priv->info->categories [cat]; catinfo = category->user_data; cat_entries = category->n_entries; relayout_category (canvas, catinfo, vert_pos, &category_vert_pos); category_vert_pos += UNDER_LINE_SPACING; for (entry = 0; entry < cat_entries; entry++) { /* we don't want the first item to wrap, it would be too separated from the section title */ if ((entry > 0) && ((priv->items_per_row == 0) || (priv->items_per_row > 0 && (entry % priv->items_per_row == 0)))) { category_horiz_pos = (priv->rtl) ? priv->max_width - (gint) priv->max_item_width - BORDERS : BORDERS; category_vert_pos += (gint) priv->max_item_height + ITEMS_SEPARATION; } ei = category->entries[entry]->user_data; relayout_item (canvas, ei, category_horiz_pos, category_vert_pos); if (priv->rtl) category_horiz_pos -= (gint) priv->max_item_width + ITEMS_SEPARATION; else category_horiz_pos += (gint) priv->max_item_width + ITEMS_SEPARATION; } category_vert_pos += (gint) priv->max_item_height; vert_pos += category_vert_pos + ITEMS_SEPARATION; } /* substract the last ITEMS_SEPARATION to adjust the canvas size a bit more */ vert_pos -= ITEMS_SEPARATION; priv->height = MAX (vert_pos, priv->min_height); priv->width = priv->max_width; } static void gnomecc_canvas_size_allocate(GtkWidget *canvas, GtkAllocation *allocation) { GnomeccCanvasPrivate *priv; if (GTK_WIDGET_CLASS (gnomecc_canvas_parent_class)->size_allocate) (* GTK_WIDGET_CLASS (gnomecc_canvas_parent_class)->size_allocate) (canvas, allocation); if (allocation->height == 1 || allocation->width == 1) return; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); priv->max_width = allocation->width; priv->min_height = allocation->height; relayout_canvas (GNOMECC_CANVAS (canvas)); gnome_canvas_set_scroll_region (GNOME_CANVAS (canvas), 0, 0, priv->width - 1, priv->height - 1); g_object_set (priv->under_cover, "x2", priv->width, "y2", priv->height, NULL); } static void set_style (GnomeccCanvas *canvas, gboolean font_changed) { int i, j; GnomeccCanvasPrivate *priv; GtkWidget *widget = GTK_WIDGET (canvas); if (!GTK_WIDGET_REALIZED (widget)) return; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); widget->style->bg[GTK_STATE_NORMAL] = widget->style->base[GTK_STATE_NORMAL]; control_center_reload_icons (priv->info); for (i = 0; i < priv->info->n_categories; i++) { CategoryInfo *catinfo = priv->info->categories[i]->user_data; if (catinfo->line) { g_object_set (catinfo->line, "fill_color_gdk", &widget->style->text_aa[GTK_STATE_NORMAL], NULL); } if (catinfo->title) { g_object_set (catinfo->title, "fill_color_gdk", &widget->style->text[GTK_STATE_NORMAL], NULL); if (font_changed) g_object_set (catinfo->title, "font", NULL, NULL); } for (j = 0; j < priv->info->categories[i]->n_entries; j++) { ControlCenterEntry *entry = priv->info->categories[i]->entries[j]; EntryInfo *entryinfo = entry->user_data; g_object_set (entryinfo->pixbuf, "pixbuf", entry->icon_pixbuf, NULL); g_object_set (entryinfo->highlight_pixbuf, "pixbuf", create_spotlight_pixbuf (entry->icon_pixbuf), NULL); if (font_changed && entryinfo->text) g_object_set (entryinfo->text, "font", NULL, NULL); setup_entry (canvas, entry); } } if (font_changed) { calculate_sizes (canvas); relayout_canvas (canvas); } } static void gnomecc_canvas_style_set (GtkWidget *canvas, GtkStyle *previous_style) { if (!GTK_WIDGET_REALIZED (canvas)) return; set_style (GNOMECC_CANVAS (canvas), (previous_style && canvas->style && !pango_font_description_equal (canvas->style->font_desc, previous_style->font_desc))); } static void gnomecc_canvas_realize (GtkWidget *canvas) { if (GTK_WIDGET_CLASS (gnomecc_canvas_parent_class)->realize) (* GTK_WIDGET_CLASS (gnomecc_canvas_parent_class)->realize) (canvas); set_style (GNOMECC_CANVAS (canvas), FALSE); } GtkWidget* gnomecc_canvas_new (ControlCenterInformation *info) { return g_object_new (GNOMECC_TYPE_CANVAS, "info", info, NULL); } /* Accessibility support */ static gpointer accessible_parent_class; static gpointer accessible_item_parent_class; enum { ACTION_ACTIVATE, LAST_ACTION }; typedef struct { AtkObject parent; ControlCenterEntry *entry; AtkStateSet *state_set; guint action_idle_handler; } GnomeccCanvasItemAccessible; typedef struct { AtkObjectClass parent_class; } GnomeccCanvasItemAccessibleClass; static const gchar *const action_names[] = { "activate", NULL }; static const gchar *const action_descriptions[] = { "Activate item", NULL }; static void gnomecc_canvas_item_accessible_get_extents (AtkComponent *component, gint *x, gint *y, gint *width, gint *height, AtkCoordType coord_type) { GnomeccCanvasItemAccessible *item; GnomeccCanvas *canvas; GnomeCanvasItem *cover; AtkObject *parent_object; gint p_x, p_y; item = (GnomeccCanvasItemAccessible *) component; canvas = ((EntryInfo *) item->entry->user_data)->canvas; parent_object = gtk_widget_get_accessible (GTK_WIDGET (canvas)); atk_component_get_position (ATK_COMPONENT (parent_object), &p_x, &p_y, coord_type); /* the cover pretty much represents the item size */ cover = GNOME_CANVAS_ITEM (((EntryInfo *)item->entry->user_data)->cover); *x = p_x + cover->x1; *y = p_y + cover->y1; *width = cover->x2 - cover->x1; *height = cover->y2 - cover->y1; } static void atk_component_item_interface_init (AtkComponentIface *iface) { iface->get_extents = gnomecc_canvas_item_accessible_get_extents; } static gboolean idle_do_action (gpointer data) { GnomeccCanvasItemAccessible *item; item = (GnomeccCanvasItemAccessible *) data; item->action_idle_handler = 0; activate_entry (item->entry); return FALSE; } static gboolean gnomecc_canvas_item_accessible_action_do_action (AtkAction *action, gint index) { GnomeccCanvasItemAccessible *item; if (index < 0 || index >= LAST_ACTION) return FALSE; item = (GnomeccCanvasItemAccessible *) action; switch (index) { case ACTION_ACTIVATE: if (!item->action_idle_handler) item->action_idle_handler = g_idle_add (idle_do_action, item); break; default: g_assert_not_reached (); return FALSE; } return TRUE; } static gint gnomecc_canvas_item_accessible_action_get_n_actions (AtkAction *action) { return LAST_ACTION; } static const gchar * gnomecc_canvas_item_accessible_action_get_description (AtkAction *action, gint index) { if (index < 0 || index >= LAST_ACTION) return NULL; return action_descriptions[index]; } static const gchar * gnomecc_canvas_item_accessible_action_get_name (AtkAction *action, gint index) { if (index < 0 || index >= LAST_ACTION) return NULL; return action_names[index]; } static void atk_action_item_interface_init (AtkActionIface *iface) { iface->do_action = gnomecc_canvas_item_accessible_action_do_action; iface->get_n_actions = gnomecc_canvas_item_accessible_action_get_n_actions; iface->get_description = gnomecc_canvas_item_accessible_action_get_description; iface->get_name = gnomecc_canvas_item_accessible_action_get_name; } static gint gnomecc_canvas_item_accessible_get_index_in_parent (AtkObject *object) { GnomeccCanvasItemAccessible *item; item = (GnomeccCanvasItemAccessible *) object; return ((EntryInfo *) item->entry->user_data)->index; } static G_CONST_RETURN gchar* gnomecc_canvas_item_accessible_get_name (AtkObject *object) { GnomeccCanvasItemAccessible *item; if (object->name) return object->name; else { item = (GnomeccCanvasItemAccessible *) object; if (item->entry && item->entry->title) return item->entry->title; else return "Item with no description"; } } static AtkObject* gnomecc_canvas_item_accessible_get_parent (AtkObject *object) { GnomeccCanvasItemAccessible *item; GnomeccCanvas *canvas; item = (GnomeccCanvasItemAccessible *) object; canvas = ((EntryInfo *) item->entry->user_data)->canvas; return gtk_widget_get_accessible (GTK_WIDGET (canvas)); } static AtkStateSet* gnomecc_canvas_item_accessible_ref_state_set (AtkObject *object) { GnomeccCanvasItemAccessible *item; GnomeccCanvasPrivate *priv; GnomeccCanvas *canvas; item = (GnomeccCanvasItemAccessible *) object; canvas = ((EntryInfo *) item->entry->user_data)->canvas; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); if (item->entry == priv->selected) atk_state_set_add_state (item->state_set, ATK_STATE_FOCUSED); else atk_state_set_remove_state (item->state_set, ATK_STATE_FOCUSED); return g_object_ref (item->state_set); } static void gnomecc_canvas_item_accessible_class_init (AtkObjectClass *class) { accessible_item_parent_class = g_type_class_peek_parent (class); class->get_index_in_parent = gnomecc_canvas_item_accessible_get_index_in_parent; class->get_name = gnomecc_canvas_item_accessible_get_name; class->get_parent = gnomecc_canvas_item_accessible_get_parent; class->ref_state_set = gnomecc_canvas_item_accessible_ref_state_set; } static void gnomecc_canvas_item_accessible_object_init (GnomeccCanvasItemAccessible *item) { item->state_set = atk_state_set_new (); atk_state_set_add_state (item->state_set, ATK_STATE_ENABLED); atk_state_set_add_state (item->state_set, ATK_STATE_FOCUSABLE); atk_state_set_add_state (item->state_set, ATK_STATE_SENSITIVE); atk_state_set_add_state (item->state_set, ATK_STATE_SELECTABLE); atk_state_set_add_state (item->state_set, ATK_STATE_VISIBLE); item->action_idle_handler = 0; } static GType gnomecc_canvas_item_accessible_get_type (void) { static GType type = 0; if (type == 0) { static const GTypeInfo info = { sizeof (GnomeccCanvasItemAccessibleClass), (GBaseInitFunc) NULL, /* base init */ (GBaseFinalizeFunc) NULL, /* base finalize */ (GClassInitFunc) gnomecc_canvas_item_accessible_class_init, /* class init */ (GClassFinalizeFunc) NULL, /* class finalize */ NULL, /* class data */ sizeof (GnomeccCanvasItemAccessible), /* instance size */ 0, /* nb preallocs */ (GInstanceInitFunc) gnomecc_canvas_item_accessible_object_init, /* instance init */ NULL /* value table */ }; static const GInterfaceInfo atk_component_info = { (GInterfaceInitFunc) atk_component_item_interface_init, (GInterfaceFinalizeFunc) NULL, NULL }; static const GInterfaceInfo atk_action_info = { (GInterfaceInitFunc) atk_action_item_interface_init, (GInterfaceFinalizeFunc) NULL, NULL }; type = g_type_register_static (ATK_TYPE_OBJECT, "GnomeccCanvasItemAccessible", &info, 0); g_type_add_interface_static (type, ATK_TYPE_COMPONENT, &atk_component_info); g_type_add_interface_static (type, ATK_TYPE_ACTION, &atk_action_info); } return type; } static gint gnomecc_canvas_accessible_get_n_children (AtkObject *accessible) { GnomeccCanvasPrivate *priv; GnomeccCanvas *canvas; GtkWidget *widget; gint i, count; widget = GTK_ACCESSIBLE (accessible)->widget; if (!widget) return 0; canvas = GNOMECC_CANVAS (widget); priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); count = 0; for (i = 0; i < priv->info->n_categories; i++) count += priv->info->categories[i]->n_entries; return count; } static ControlCenterEntry* gnomecc_canvas_accessible_get_entry (GnomeccCanvas *canvas, gint index) { GnomeccCanvasPrivate *priv; gint i, count; priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); count = 0; for (i = 0; i < priv->info->n_categories; i++) { if (index >= count && index < count + priv->info->categories[i]->n_entries) { return priv->info->categories[i]->entries[index - count]; } else count += priv->info->categories [i]->n_entries; } return NULL; } static AtkObject* gnomecc_canvas_accessible_ref_child (AtkObject *accessible, gint index) { GnomeccCanvasItemAccessible *item; ControlCenterEntry *entry; GnomeccCanvasPrivate *priv; GnomeccCanvas *canvas; GtkWidget *widget; AtkObject *object; widget = GTK_ACCESSIBLE (accessible)->widget; if (!widget) return NULL; canvas = GNOMECC_CANVAS (widget); priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); entry = gnomecc_canvas_accessible_get_entry (canvas, index); if (!entry) return NULL; object = g_hash_table_lookup (priv->accessible_children, &index); if (!object) { object = g_object_new (gnomecc_canvas_item_accessible_get_type (), NULL); item = (GnomeccCanvasItemAccessible *) object; object->role = ATK_ROLE_ICON; item->entry = entry; g_hash_table_insert (priv->accessible_children, &((EntryInfo *) entry->user_data)->index, object); } return g_object_ref (object); } static void gnomecc_canvas_accessible_initialize (AtkObject *accessible, gpointer data) { if (ATK_OBJECT_CLASS (accessible_parent_class)->initialize) ATK_OBJECT_CLASS (accessible_parent_class)->initialize (accessible, data); accessible->role = ATK_ROLE_LAYERED_PANE; } static void gnomecc_canvas_accessible_class_init (AtkObjectClass *class) { accessible_parent_class = g_type_class_peek_parent (class); class->get_n_children = gnomecc_canvas_accessible_get_n_children; class->ref_child = gnomecc_canvas_accessible_ref_child; class->initialize = gnomecc_canvas_accessible_initialize; } static gboolean gnomecc_canvas_accessible_add_selection (AtkSelection *selection, gint i) { GnomeccCanvas *canvas; GtkWidget *widget; ControlCenterEntry *entry; widget = GTK_ACCESSIBLE (selection)->widget; if (!widget) return FALSE; canvas = GNOMECC_CANVAS (widget); entry = gnomecc_canvas_accessible_get_entry (canvas, i); select_entry (canvas, entry); return TRUE; } static gboolean gnomecc_canvas_accessible_clear_selection (AtkSelection *selection) { GnomeccCanvas *canvas; GtkWidget *widget; widget = GTK_ACCESSIBLE (selection)->widget; if (!widget) return FALSE; canvas = GNOMECC_CANVAS (widget); select_entry (canvas, NULL); return TRUE; } static AtkObject* gnomecc_canvas_accessible_ref_selection (AtkSelection *selection, gint i) { GnomeccCanvasPrivate *priv; GnomeccCanvas *canvas; GtkWidget *widget; widget = GTK_ACCESSIBLE (selection)->widget; if (!widget) return NULL; canvas = GNOMECC_CANVAS (widget); priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); if (priv->selected) return atk_object_ref_accessible_child (gtk_widget_get_accessible (widget), ((EntryInfo *)priv->selected->user_data)->index); return NULL; } static gint gnomecc_canvas_accessible_get_selection_count (AtkSelection *selection) { GnomeccCanvasPrivate *priv; GnomeccCanvas *canvas; GtkWidget *widget; widget = GTK_ACCESSIBLE (selection)->widget; if (!widget) return 0; canvas = GNOMECC_CANVAS (widget); priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); return (priv->selected) ? 1 : 0; } static gboolean gnomecc_canvas_accessible_is_child_selected (AtkSelection *selection, gint i) { GnomeccCanvasPrivate *priv; GnomeccCanvas *canvas; GtkWidget *widget; widget = GTK_ACCESSIBLE (selection)->widget; if (!widget) return FALSE; canvas = GNOMECC_CANVAS (widget); priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); return (priv->selected == gnomecc_canvas_accessible_get_entry (canvas, i)); } static gboolean gnomecc_canvas_accessible_remove_selection (AtkSelection *selection, gint i) { /* There can be only one item selected */ return gnomecc_canvas_accessible_clear_selection (selection); } static gboolean gnomecc_canvas_accessible_select_all_selection (AtkSelection *selection) { /* This can't happen */ return FALSE; } static void gnomecc_canvas_accessible_selection_interface_init (AtkSelectionIface *iface) { iface->add_selection = gnomecc_canvas_accessible_add_selection; iface->clear_selection = gnomecc_canvas_accessible_clear_selection; iface->ref_selection = gnomecc_canvas_accessible_ref_selection; iface->get_selection_count = gnomecc_canvas_accessible_get_selection_count; iface->is_child_selected = gnomecc_canvas_accessible_is_child_selected; iface->remove_selection = gnomecc_canvas_accessible_remove_selection; iface->select_all_selection = gnomecc_canvas_accessible_select_all_selection; } static AtkObject* gnomecc_canvas_accessible_ref_accessible_at_point (AtkComponent *component, gint x, gint y, AtkCoordType coord_type) { GtkWidget *widget; GnomeccCanvasPrivate *priv; GnomeccCanvas *canvas; EntryInfo *entry; gint x_pos, y_pos, x_w, y_w; gint i, j; widget = GTK_ACCESSIBLE (component)->widget; if (widget == NULL) return NULL; canvas = GNOMECC_CANVAS (widget); priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); atk_component_get_extents (component, &x_pos, &y_pos, NULL, NULL, coord_type); x_w = x - x_pos; y_w = y - y_pos; for (i = 0; i < priv->info->n_categories; i++) { for (j = 0; j < priv->info->categories[i]->n_entries; j++) { entry = (EntryInfo *) priv->info->categories[i]->entries[j]; if (x_w > entry->cover->x1 && x_w < entry->cover->x2 && y_w > entry->cover->y1 && y_w < entry->cover->y2) return gnomecc_canvas_accessible_ref_child (ATK_OBJECT (component), entry->index); } } return NULL; } static void atk_component_interface_init (AtkComponentIface *iface) { iface->ref_accessible_at_point = gnomecc_canvas_accessible_ref_accessible_at_point; } static GType gnomecc_canvas_accessible_get_type (void) { static GType type = 0; AtkObjectFactory *factory; GType derived_type; GTypeQuery query; GType derived_atk_type; if (type == 0) { static GTypeInfo info = { 0, /* class size */ (GBaseInitFunc) NULL, /* base init */ (GBaseFinalizeFunc) NULL, /* base finalize */ (GClassInitFunc) gnomecc_canvas_accessible_class_init, (GClassFinalizeFunc) NULL, /* class finalize */ NULL, /* class data */ 0, /* instance size */ 0, /* nb preallocs */ (GInstanceInitFunc) NULL, /* instance init */ NULL /* value table */ }; static const GInterfaceInfo atk_component_info = { (GInterfaceInitFunc) atk_component_interface_init, (GInterfaceFinalizeFunc) NULL, NULL }; static const GInterfaceInfo atk_selection_info = { (GInterfaceInitFunc) gnomecc_canvas_accessible_selection_interface_init, (GInterfaceFinalizeFunc) NULL, NULL }; derived_type = g_type_parent (GNOMECC_TYPE_CANVAS); factory = atk_registry_get_factory (atk_get_default_registry (), derived_type); derived_atk_type = atk_object_factory_get_accessible_type (factory); g_type_query (derived_atk_type, &query); info.class_size = query.class_size; info.instance_size = query.instance_size; type = g_type_register_static (derived_atk_type, "GnomeccCanvasAccessible", &info, 0); g_type_add_interface_static (type, ATK_TYPE_COMPONENT, &atk_component_info); g_type_add_interface_static (type, ATK_TYPE_SELECTION, &atk_selection_info); } return type; } static AtkObject* gnomecc_canvas_accessible_new (GObject *object) { AtkObject *accessible; accessible = g_object_new (gnomecc_canvas_accessible_get_type (), NULL); atk_object_initialize (accessible, object); return accessible; } static AtkObject* gnomecc_canvas_accessible_factory_create_accessible (GObject *object) { return gnomecc_canvas_accessible_new (object); } static void gnomecc_canvas_accessible_factory_class_init (AtkObjectFactoryClass *class) { class->create_accessible = gnomecc_canvas_accessible_factory_create_accessible; class->get_accessible_type = gnomecc_canvas_accessible_get_type; } static GType gnomecc_canvas_accessible_factory_get_type (void) { static GType type = 0; if (type == 0) { static const GTypeInfo info = { sizeof (AtkObjectFactoryClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) gnomecc_canvas_accessible_factory_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (AtkObjectFactory), 0, /* n_preallocs */ NULL, NULL }; type = g_type_register_static (ATK_TYPE_OBJECT_FACTORY, "GnomeccCanvasAccessibleFactory", &info, 0); } return type; } static AtkObject* gnomecc_canvas_get_accessible (GtkWidget *widget) { static gboolean already_here = FALSE; if (!already_here) { AtkObjectFactory *factory; AtkRegistry *registry; GType derived_type; GType derived_atk_type; already_here = TRUE; derived_type = g_type_parent (GNOMECC_TYPE_CANVAS); registry = atk_get_default_registry (); factory = atk_registry_get_factory (registry, derived_type); derived_atk_type = atk_object_factory_get_accessible_type (factory); if (g_type_is_a (derived_atk_type, GTK_TYPE_ACCESSIBLE)) { atk_registry_set_factory_type (registry, GNOMECC_TYPE_CANVAS, gnomecc_canvas_accessible_factory_get_type ()); } } return (* GTK_WIDGET_CLASS (gnomecc_canvas_parent_class)->get_accessible) (widget); } void gnomecc_canvas_set_single_click_mode (GnomeccCanvas *canvas, gboolean single_click) { GnomeccCanvasPrivate *priv; g_return_if_fail (GNOMECC_IS_CANVAS (canvas)); priv = GNOMECC_CANVAS_GET_PRIVATE (canvas); priv->single_click = (single_click == TRUE); }