1026 lines
27 KiB
C
1026 lines
27 KiB
C
|
/* -*- 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 <jody@gnome.org>
|
||
|
* Carlos Garnacho Parro <carlosg@gnome.org>
|
||
|
*/
|
||
|
|
||
|
#include "gnomecc-canvas.h"
|
||
|
#include "gnomecc-event-box.h"
|
||
|
#include "gnomecc-rounded-rect.h"
|
||
|
#include <libgnomecanvas/libgnomecanvas.h>
|
||
|
#include <libgnome/gnome-desktop-item.h>
|
||
|
#include <gconf/gconf-client.h>
|
||
|
#include <gdk/gdkkeysyms.h>
|
||
|
#include <gtk/gtk.h>
|
||
|
|
||
|
#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 MAX_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;
|
||
|
};
|
||
|
|
||
|
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;
|
||
|
} 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_draw_background (GnomeCanvas *canvas, GdkDrawable *drawable,
|
||
|
int x, int y, int width, int height);
|
||
|
|
||
|
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);
|
||
|
|
||
|
|
||
|
G_DEFINE_TYPE (GnomeccCanvas, gnomecc_canvas, GNOME_TYPE_CANVAS);
|
||
|
|
||
|
static void
|
||
|
gnomecc_canvas_class_init (GnomeccCanvasClass *class)
|
||
|
{
|
||
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||
|
GnomeCanvasClass *canvas_class = GNOME_CANVAS_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;
|
||
|
canvas_class->draw_background = gnomecc_canvas_draw_background;
|
||
|
|
||
|
widget_class->style_set = gnomecc_canvas_style_set;
|
||
|
widget_class->size_allocate = gnomecc_canvas_size_allocate;
|
||
|
widget_class->realize = gnomecc_canvas_realize;
|
||
|
|
||
|
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);
|
||
|
|
||
|
gtk_widget_show_all (GTK_WIDGET (canvas));
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
single_click_activates (void)
|
||
|
{
|
||
|
static gboolean needs_init = TRUE;
|
||
|
static gboolean use_single_click = FALSE;
|
||
|
if (needs_init) {
|
||
|
GConfClient *client = gconf_client_get_default ();
|
||
|
char *policy = gconf_client_get_string (client, "/apps/nautilus/preferences/click_policy", NULL);
|
||
|
g_object_unref (G_OBJECT (client));
|
||
|
|
||
|
if (policy != NULL) {
|
||
|
use_single_click = (0 == g_ascii_strcasecmp (policy, "single"));
|
||
|
g_free (policy);
|
||
|
}
|
||
|
needs_init = FALSE;
|
||
|
}
|
||
|
|
||
|
return use_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)
|
||
|
{
|
||
|
GnomeccCanvasPrivate *priv;
|
||
|
EntryInfo *ei;
|
||
|
GtkWidget *widget;
|
||
|
GtkStateType state;
|
||
|
|
||
|
if (!entry)
|
||
|
return;
|
||
|
|
||
|
priv = GNOMECC_CANVAS_GET_PRIVATE (canvas);
|
||
|
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;
|
||
|
|
||
|
switch (event->type) {
|
||
|
case GDK_ENTER_NOTIFY:
|
||
|
ei->highlighted = TRUE;
|
||
|
setup_entry (canvas, entry); /* highlight even if it is already selected */
|
||
|
return TRUE;
|
||
|
case GDK_LEAVE_NOTIFY:
|
||
|
ei->highlighted = FALSE;
|
||
|
setup_entry (canvas, entry);
|
||
|
return TRUE;
|
||
|
case GDK_BUTTON_PRESS:
|
||
|
if (single_click_activates ()) {
|
||
|
activate_entry (entry);
|
||
|
} else {
|
||
|
select_entry (canvas, 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;
|
||
|
gint n_categories, n_entries;
|
||
|
|
||
|
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;
|
||
|
n_categories = priv->info->n_categories;
|
||
|
n_entries = priv->info->categories[ei->n_category]->n_entries;
|
||
|
|
||
|
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;
|
||
|
break;
|
||
|
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_CHAR);
|
||
|
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 > MAX_ITEM_WIDTH) {
|
||
|
pango_layout_set_width (layout, MAX_ITEM_WIDTH * PANGO_SCALE);
|
||
|
pango_layout_get_pixel_extents (layout, NULL, &rectangle);
|
||
|
rectangle.width = MAX_ITEM_WIDTH;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
priv = GNOMECC_CANVAS_GET_PRIVATE (canvas);
|
||
|
gcanvas = GNOME_CANVAS (canvas);
|
||
|
|
||
|
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 ("<span weight=\"bold\">%s</span>", 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);
|
||
|
g_object_unref (pixbuf);
|
||
|
ei->highlight_pixbuf = gnome_canvas_item_new (ei->group,
|
||
|
gnome_canvas_pixbuf_get_type (),
|
||
|
"pixbuf", highlight_pixbuf,
|
||
|
NULL);
|
||
|
g_object_unref (highlight_pixbuf);
|
||
|
} 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;
|
||
|
|
||
|
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]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
if (G_OBJECT_CLASS (gnomecc_canvas_parent_class)->finalize)
|
||
|
(* G_OBJECT_CLASS (gnomecc_canvas_parent_class)->finalize) (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gnomecc_canvas_draw_background (GnomeCanvas *canvas, GdkDrawable *drawable,
|
||
|
int x, int y, int width, int height)
|
||
|
{
|
||
|
/* By default, we use the style base color. */
|
||
|
gdk_gc_set_foreground (canvas->pixmap_gc,
|
||
|
>K_WIDGET (canvas)->style->base[GTK_STATE_NORMAL]);
|
||
|
gdk_draw_rectangle (drawable,
|
||
|
canvas->pixmap_gc,
|
||
|
TRUE,
|
||
|
0, 0,
|
||
|
width, height);
|
||
|
}
|
||
|
|
||
|
/* 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);
|
||
|
|
||
|
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;
|
||
|
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);
|
||
|
}
|