/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 1998, 1999, 2000 Free Software Foundation
* All rights reserved.
*
* This file is part of the Gnome Library.
*
* The Gnome Library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* The Gnome Library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with the Gnome Library; see the file COPYING.LIB. If not,
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
@NOTATION@
*/
/*
* GnomeIconList widget - scrollable icon list
*
* Authors:
* Federico Mena <federico@ximian.com>
* Miguel de Icaza <miguel@ximian.com>
*
* Rewrote from scratch from the code written by Federico Mena
* <federico@ximian.com> to be based on a GnomeCanvas, and
* to support banding selection and allow inline icon renaming.
*
* Redone somewhat by Elliot to support gdk-pixbuf, and to use GArray instead of
* GList for item storage.
*/
#include <config.h>
#include <string.h>
#include <stdio.h>
#include <gtk/gtk.h>
#include "e-icon-list.h"
#include <libgnomeui/gnome-icon-item.h>
#include <libgnomeui/gnome-canvas-rect-ellipse.h>
#include <gdk-pixbuf/gnome-canvas-pixbuf.h>
#include "bad-icon.xpm"
/* Aliases to minimize screen use in my laptop */
#define EIL(x) E_ICON_LIST(x)
#define EIL_CLASS(x) E_ICON_LIST_CLASS(x)
#define IS_EIL(x) E_IS_ICON_LIST(x)
typedef EIconList Eil;
typedef EIconListClass EilClass;
/* default spacings */
#define DEFAULT_ROW_SPACING 4
#define DEFAULT_COL_SPACING 2
#define DEFAULT_TEXT_SPACING 2
#define DEFAULT_ICON_BORDER 2
/* Autoscroll timeout in milliseconds */
#define SCROLL_TIMEOUT 30
/* Signals */
enum {
SELECT_ICON,
UNSELECT_ICON,
TEXT_CHANGED,
LAST_SIGNAL
};
typedef enum {
SYNC_INSERT,
SYNC_REMOVE
} SyncType;
enum {
ARG_0,
};
static guint eil_signals[LAST_SIGNAL] = { 0 };
static GnomeCanvasClass *parent_class;
/* Icon structure */
typedef struct {
/* Icon image and text items */
GnomeCanvasPixbuf *image;
GnomeIconTextItem *text;
/* Filename of the icon file. */
gchar *icon_filename;
/* User data and destroy notify function */
gpointer data;
GtkDestroyNotify destroy;
/* ID for the text item's event signal handler */
guint text_event_id;
/* Whether the icon is selected, and temporary storage for rubberband
* selections.
*/
guint selected : 1;
guint tmp_selected : 1;
} Icon;
/* A row of icons */
typedef struct {
GList *line_icons;
gint16 y;
gint16 icon_height, text_height;
} IconLine;
/* Private data of the EIconList structure */
struct _EIconListPrivate {
/* List of icons */
GArray *icon_list;
/* List of rows of icons */
GList *lines;
/* Separators used to wrap the text below icons */
char *separators;
Icon *last_selected_icon;
/* Rubberband rectangle */
GnomeCanvasItem *sel_rect;
/* Saved event for a pending selection */
GdkEvent select_pending_event;
/* Max of the height of all the icon rows and window height */
int total_height;
/* Selection mode */
GtkSelectionMode selection_mode;
/* A list of integers with the indices of the currently selected icons */
GList *selection;
/* Number of icons in the list */
int icons;
/* Freeze count */
int frozen;
/* Width allocated for icons */
int icon_width;
/* Spacing values */
int row_spacing;
int col_spacing;
int text_spacing;
int icon_border;
/* Index and pointer to last selected icon */
int last_selected_idx;
/* Timeout ID for autoscrolling */
guint timer_tag;
/* Change the adjustment value by this amount when autoscrolling */
int value_diff;
/* Mouse position for autoscrolling */
int event_last_x;
int event_last_y;
/* Selection start position */
int sel_start_x;
int sel_start_y;
/* Modifier state when the selection began */
guint sel_state;
/* Whether the icon texts are editable */
guint is_editable : 1;
/* Whether the icon texts need to be copied */
guint static_text : 1;
/* Whether the icons need to be laid out */
guint dirty : 1;
/* Whether the user is performing a rubberband selection */
guint selecting : 1;
/* Whether editing an icon is pending after a button press */
guint edit_pending : 1;
/* Whether selection is pending after a button press */
guint select_pending : 1;
/* Whether the icon that is pending selection was selected to begin with */
guint select_pending_was_selected : 1;
};
static inline int
icon_line_height (Eil *eil, IconLine *il)
{
EIconListPrivate *priv;
priv = eil->_priv;
return il->icon_height + il->text_height + priv->row_spacing + priv->text_spacing;
}
static void
icon_get_height (Icon *icon, int *icon_height, int *text_height)
{
double d_icon_height;
gtk_object_get(GTK_OBJECT(icon->image), "height", &d_icon_height, NULL);
*icon_height = d_icon_height;
*text_height = icon->text->ti->height;
}
static int
eil_get_items_per_line (Eil *eil)
{
EIconListPrivate *priv;
int items_per_line;
priv = eil->_priv;
items_per_line = GTK_WIDGET (eil)->allocation.width / (priv->icon_width + priv->col_spacing);
if (items_per_line == 0)
items_per_line = 1;
return items_per_line;
}
/**
* e_icon_list_get_items_per_line:
* @eil: An icon list.
*
* Returns the number of icons that fit in a line or row.
*/
int
e_icon_list_get_items_per_line (EIconList *eil)
{
g_return_val_if_fail (eil != NULL, 1);
g_return_val_if_fail (IS_EIL (eil), 1);
return eil_get_items_per_line (eil);
}
static void
eil_place_icon (Eil *eil, Icon *icon, int x, int y, int icon_height)
{
EIconListPrivate *priv;
int x_offset, y_offset;
double d_icon_image_height;
double d_icon_image_width;
int icon_image_height;
int icon_image_width;
priv = eil->_priv;
gtk_object_get(GTK_OBJECT(icon->image), "height", &d_icon_image_height, NULL);
icon_image_height = d_icon_image_height;
g_assert(icon_image_height != 0);
if (icon_height > icon_image_height)
y_offset = (icon_height - icon_image_height) / 2;
else
y_offset = 0;
gtk_object_get(GTK_OBJECT(icon->image), "width", &d_icon_image_width, NULL);
icon_image_width = d_icon_image_width;
g_assert(icon_image_width != 0);
if (priv->icon_width > icon_image_width)
x_offset = (priv->icon_width - icon_image_width) / 2;
else
x_offset = 0;
gnome_canvas_item_set (GNOME_CANVAS_ITEM (icon->image),
"x", (double) (x + x_offset),
"y", (double) (y + y_offset),
NULL);
gnome_icon_text_item_setxy (icon->text,
x,
y + icon_height + priv->text_spacing);
}
static void
eil_layout_line (Eil *eil, IconLine *il)
{
EIconListPrivate *priv;
GList *l;
int x;
priv = eil->_priv;
x = 0;
for (l = il->line_icons; l; l = l->next) {
Icon *icon = l->data;
eil_place_icon (eil, icon, x, il->y, il->icon_height);
x += priv->icon_width + priv->col_spacing;
}
}
static void
eil_add_and_layout_line (Eil *eil, GList *line_icons, int y,
int icon_height, int text_height)
{
EIconListPrivate *priv;
IconLine *il;
priv = eil->_priv;
il = g_new (IconLine, 1);
il->line_icons = line_icons;
il->y = y;
il->icon_height = icon_height;
il->text_height = text_height;
eil_layout_line (eil, il);
priv->lines = g_list_append (priv->lines, il);
}
static void
eil_relayout_icons_at (Eil *eil, int pos, int y)
{
EIconListPrivate *priv;
int col, row, text_height, icon_height;
int items_per_line, n;
GList *line_icons;
priv = eil->_priv;
items_per_line = eil_get_items_per_line (eil);
col = row = text_height = icon_height = 0;
line_icons = NULL;
for (n = pos; n < priv->icon_list->len; n++) {
Icon *icon = g_array_index(priv->icon_list, Icon*, n);
int ih, th;
if (!(n % items_per_line)) {
if (line_icons) {
eil_add_and_layout_line (eil, line_icons, y,
icon_height, text_height);
line_icons = NULL;
y += (icon_height + text_height
+ priv->row_spacing + priv->text_spacing);
}
icon_height = 0;
text_height = 0;
}
icon_get_height (icon, &ih, &th);
icon_height = MAX (ih, icon_height);
text_height = MAX (th, text_height);
line_icons = g_list_append (line_icons, icon);
}
if (line_icons)
eil_add_and_layout_line (eil, line_icons, y, icon_height, text_height);
}
static void
eil_free_line_info (Eil *eil)
{
EIconListPrivate *priv;
GList *l;
priv = eil->_priv;
for (l = priv->lines; l; l = l->next) {
IconLine *il = l->data;
g_list_free (il->line_icons);
g_free (il);
}
g_list_free (priv->lines);
priv->lines = NULL;
priv->total_height = 0;
}
static void
eil_free_line_info_from (Eil *eil, int first_line)
{
EIconListPrivate *priv;
GList *l, *ll;
priv = eil->_priv;
ll = g_list_nth (priv->lines, first_line);
for (l = ll; l; l = l->next) {
IconLine *il = l->data;
g_list_free (il->line_icons);
g_free (il);
}
if (priv->lines) {
if (ll->prev)
ll->prev->next = NULL;
else
priv->lines = NULL;
}
g_list_free (ll);
}
static void
eil_layout_from_line (Eil *eil, int line)
{
EIconListPrivate *priv;
GList *l;
int height;
priv = eil->_priv;
eil_free_line_info_from (eil, line);
height = 0;
for (l = priv->lines; l; l = l->next) {
IconLine *il = l->data;
height += icon_line_height (eil, il);
}
eil_relayout_icons_at (eil, line * eil_get_items_per_line (eil), height);
}
static void
eil_layout_all_icons (Eil *eil)
{
EIconListPrivate *priv;
priv = eil->_priv;
if (!GTK_WIDGET_REALIZED (eil))
return;
eil_free_line_info (eil);
eil_relayout_icons_at (eil, 0, 0);
priv->dirty = FALSE;
}
static void
eil_scrollbar_adjust (Eil *eil)
{
EIconListPrivate *priv;
GtkAdjustment *adj;
GList *l;
double wx, wy, wx1, wy1, wx2, wy2;
int height, step_increment;
priv = eil->_priv;
if (!GTK_WIDGET_REALIZED (eil))
return;
height = 0;
step_increment = 0;
for (l = priv->lines; l; l = l->next) {
IconLine *il = l->data;
height += icon_line_height (eil, il);
if (l == priv->lines)
step_increment = height;
}
if (!step_increment)
step_increment = 10;
priv->total_height = MAX (height, GTK_WIDGET (eil)->allocation.height);
gnome_canvas_c2w (GNOME_CANVAS (eil), 0, 0, &wx1, &wy1);
gnome_canvas_c2w (GNOME_CANVAS (eil),
GTK_WIDGET (eil)->allocation.width,
priv->total_height,
&wx2, &wy2);
gnome_canvas_set_scroll_region (GNOME_CANVAS (eil),
wx1, wy1, wx2, wy2);
wx = wy = 0;
gnome_canvas_window_to_world (GNOME_CANVAS (eil), 0, 0, &wx, &wy);
adj = gtk_layout_get_vadjustment (GTK_LAYOUT (eil));
adj->upper = priv->total_height;
adj->step_increment = step_increment;
adj->page_increment = GTK_WIDGET (eil)->allocation.height;
adj->page_size = GTK_WIDGET (eil)->allocation.height;
if (wy > adj->upper - adj->page_size)
wy = adj->upper - adj->page_size;
adj->value = wy;
gtk_adjustment_changed (adj);
}
/* Emits the select_icon or unselect_icon signals as appropriate */
static void
emit_select (Eil *eil, int sel, int i, GdkEvent *event)
{
gtk_signal_emit (GTK_OBJECT (eil),
eil_signals[sel ? SELECT_ICON : UNSELECT_ICON],
i,
event);
}
static int
eil_unselect_all (EIconList *eil, GdkEvent *event, gpointer keep)
{
EIconListPrivate *priv;
Icon *icon;
int i, idx = 0;
g_return_val_if_fail (eil != NULL, 0);
g_return_val_if_fail (IS_EIL (eil), 0);
priv = eil->_priv;
for (i = 0; i < priv->icon_list->len; i++) {
icon = g_array_index(priv->icon_list, Icon*, i);
if (icon == keep)
idx = i;
else if (icon->selected)
emit_select (eil, FALSE, i, event);
}
return idx;
}
/**
* e_icon_list_unselect_all:
* @eil: An icon list.
*
* Returns: the number of icons in the icon list
*/
int
e_icon_list_unselect_all (EIconList *eil)
{
return eil_unselect_all (eil, NULL, NULL);
}
static void
sync_selection (Eil *eil, int pos, SyncType type)
{
GList *list;
for (list = eil->_priv->selection; list; list = list->next) {
if (GPOINTER_TO_INT (list->data) >= pos) {
int i = GPOINTER_TO_INT (list->data);
switch (type) {
case SYNC_INSERT:
list->data = GINT_TO_POINTER (i + 1);
break;
case SYNC_REMOVE:
list->data = GINT_TO_POINTER (i - 1);
break;
default:
g_assert_not_reached ();
}
}
}
}
static int
eil_icon_to_index (Eil *eil, Icon *icon)
{
EIconListPrivate *priv;
int n;
priv = eil->_priv;
for (n = 0; n < priv->icon_list->len; n++)
if (g_array_index(priv->icon_list, Icon*, n) == icon)
return n;
g_assert_not_reached ();
return -1; /* Shut up the compiler */
}
/* Event handler for icons when we are in SINGLE or BROWSE mode */
static gint
selection_one_icon_event (Eil *eil, Icon *icon, int idx, int on_text, GdkEvent *event)
{
EIconListPrivate *priv;
GnomeIconTextItem *text;
int retval;
priv = eil->_priv;
retval = FALSE;
/* We use a separate variable and ref the object because it may be
* destroyed by one of the signal handlers.
*/
text = icon->text;
gtk_object_ref (GTK_OBJECT (text));
switch (event->type) {
case GDK_BUTTON_PRESS:
priv->edit_pending = FALSE;
priv->select_pending = FALSE;
/* Ignore wheel mouse clicks for now */
if (event->button.button > 3)
break;
if (!icon->selected) {
eil_unselect_all (eil, NULL, NULL);
emit_select (eil, TRUE, idx, event);
} else {
if (priv->selection_mode == GTK_SELECTION_SINGLE
&& (event->button.state & GDK_CONTROL_MASK))
emit_select (eil, FALSE, idx, event);
else if (on_text && priv->is_editable && event->button.button == 1)
priv->edit_pending = TRUE;
else
emit_select (eil, TRUE, idx, event);
}
retval = TRUE;
break;
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
/* Ignore wheel mouse clicks for now */
if (event->button.button > 3)
break;
emit_select (eil, TRUE, idx, event);
retval = TRUE;
break;
case GDK_BUTTON_RELEASE:
if (priv->edit_pending) {
gnome_icon_text_item_start_editing (text);
priv->edit_pending = FALSE;
}
retval = TRUE;
break;
default:
break;
}
/* If the click was on the text and we actually did something, stop the
* icon text item's own handler from executing.
*/
if (on_text && retval)
gtk_signal_emit_stop_by_name (GTK_OBJECT (text), "event");
gtk_object_unref (GTK_OBJECT (text));
return retval;
}
/* Handles range selections when clicking on an icon */
static void
select_range (Eil *eil, Icon *icon, int idx, GdkEvent *event)
{
EIconListPrivate *priv;
int a, b;
Icon *i;
priv = eil->_priv;
if (priv->last_selected_idx == -1) {
priv->last_selected_idx = idx;
priv->last_selected_icon = icon;
}
if (idx < priv->last_selected_idx) {
a = idx;
b = priv->last_selected_idx;
} else {
a = priv->last_selected_idx;
b = idx;
}
for (; a <= b; a++) {
i = g_array_index(priv->icon_list, Icon*, a);
if (!i->selected)
emit_select (eil, TRUE, a, NULL);
}
/* Actually notify the client of the event */
emit_select (eil, TRUE, idx, event);
}
/* Handles icon selection for MULTIPLE or EXTENDED selection modes */
static void
do_select_many (Eil *eil, Icon *icon, int idx, GdkEvent *event, int use_event)
{
EIconListPrivate *priv;
int range, additive;
priv = eil->_priv;
range = (event->button.state & GDK_SHIFT_MASK) != 0;
additive = (event->button.state & GDK_CONTROL_MASK) != 0;
if (!additive) {
if (icon->selected)
eil_unselect_all (eil, NULL, icon);
else
eil_unselect_all (eil, NULL, NULL);
}
if (!range) {
if (additive)
emit_select (eil, !icon->selected, idx, use_event ? event : NULL);
else
emit_select (eil, TRUE, idx, use_event ? event : NULL);
priv->last_selected_idx = idx;
priv->last_selected_icon = icon;
} else
select_range (eil, icon, idx, use_event ? event : NULL);
}
/* Event handler for icons when we are in MULTIPLE or EXTENDED mode */
static gint
selection_many_icon_event (Eil *eil, Icon *icon, int idx, int on_text, GdkEvent *event)
{
EIconListPrivate *priv;
GnomeIconTextItem *text;
int retval;
int additive, range;
int do_select;
priv = eil->_priv;
retval = FALSE;
/* We use a separate variable and ref the object because it may be
* destroyed by one of the signal handlers.
*/
text = icon->text;
gtk_object_ref (GTK_OBJECT (text));
range = (event->button.state & GDK_SHIFT_MASK) != 0;
additive = (event->button.state & GDK_CONTROL_MASK) != 0;
switch (event->type) {
case GDK_BUTTON_PRESS:
priv->edit_pending = FALSE;
priv->select_pending = FALSE;
/* Ignore wheel mouse clicks for now */
if (event->button.button > 3)
break;
do_select = TRUE;
if (additive || range) {
if (additive && !range) {
priv->select_pending = TRUE;
priv->select_pending_event = *event;
priv->select_pending_was_selected = icon->selected;
/* We have to emit this so that the client will
* know about the click.
*/
emit_select (eil, TRUE, idx, event);
do_select = FALSE;
}
} else if (icon->selected) {
priv->select_pending = TRUE;
priv->select_pending_event = *event;
priv->select_pending_was_selected = icon->selected;
if (on_text && priv->is_editable && event->button.button == 1)
priv->edit_pending = TRUE;
emit_select (eil, TRUE, idx, event);
do_select = FALSE;
}
#if 0
} else if (icon->selected && on_text && priv->is_editable
&& event->button.button == 1) {
priv->edit_pending = TRUE;
do_select = FALSE;
}
#endif
if (do_select)
do_select_many (eil, icon, idx, event, TRUE);
retval = TRUE;
break;
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
/* Ignore wheel mouse clicks for now */
if (event->button.button > 3)
break;
emit_select (eil, TRUE, idx, event);
retval = TRUE;
break;
case GDK_BUTTON_RELEASE:
if (priv->select_pending) {
icon->selected = priv->select_pending_was_selected;
do_select_many (eil, icon, idx, &priv->select_pending_event, FALSE);
priv->select_pending = FALSE;
retval = TRUE;
}
if (priv->edit_pending) {
gnome_icon_text_item_start_editing (text);
priv->edit_pending = FALSE;
retval = TRUE;
}
#if 0
if (priv->select_pending) {
icon->selected = priv->select_pending_was_selected;
do_select_many (eil, icon, idx, &priv->select_pending_event);
priv->select_pending = FALSE;
retval = TRUE;
} else if (priv->edit_pending) {
gnome_icon_text_item_start_editing (text);
priv->edit_pending = FALSE;
retval = TRUE;
}
#endif
break;
default:
break;
}
/* If the click was on the text and we actually did something, stop the
* icon text item's own handler from executing.
*/
if (on_text && retval)
gtk_signal_emit_stop_by_name (GTK_OBJECT (text), "event");
gtk_object_unref (GTK_OBJECT (text));
return retval;
}
/* Event handler for icons in the icon list */
static gint
icon_event (GnomeCanvasItem *item, GdkEvent *event, gpointer data)
{
Icon *icon;
Eil *eil;
EIconListPrivate *priv;
int idx;
int on_text;
icon = data;
eil = EIL (item->canvas);
priv = eil->_priv;
idx = eil_icon_to_index (eil, icon);
on_text = item == GNOME_CANVAS_ITEM (icon->text);
switch (priv->selection_mode) {
case GTK_SELECTION_SINGLE:
case GTK_SELECTION_BROWSE:
return selection_one_icon_event (eil, icon, idx, on_text, event);
case GTK_SELECTION_MULTIPLE:
case GTK_SELECTION_EXTENDED:
return selection_many_icon_event (eil, icon, idx, on_text, event);
default:
g_assert_not_reached ();
return FALSE; /* Shut up the compiler */
}
}
/* Handler for the editing_started signal of an icon text item. We block the
* event handler so that it will not be called while the text is being edited.
*/
static void
editing_started (GnomeIconTextItem *iti, gpointer data)
{
Icon *icon;
icon = data;
gtk_signal_handler_block (GTK_OBJECT (iti), icon->text_event_id);
eil_unselect_all (EIL (GNOME_CANVAS_ITEM (iti)->canvas), NULL, icon);
}
/* Handler for the editing_stopped signal of an icon text item. We unblock the
* event handler so that we can get events from it again.
*/
static void
editing_stopped (GnomeIconTextItem *iti, gpointer data)
{
Icon *icon;
icon = data;
gtk_signal_handler_unblock (GTK_OBJECT (iti), icon->text_event_id);
}
static gboolean
text_changed (GnomeCanvasItem *item, Icon *icon)
{
Eil *eil;
gboolean accept;
int idx;
eil = EIL (item->canvas);
accept = TRUE;
idx = eil_icon_to_index (eil, icon);
gtk_signal_emit (GTK_OBJECT (eil),
eil_signals[TEXT_CHANGED],
idx, gnome_icon_text_item_get_text (icon->text),
&accept);
return accept;
}
static void
height_changed (GnomeCanvasItem *item, Icon *icon)
{
Eil *eil;
EIconListPrivate *priv;
int n;
eil = EIL (item->canvas);
priv = eil->_priv;
if (!GTK_WIDGET_REALIZED (eil))
return;
if (priv->frozen) {
priv->dirty = TRUE;
return;
}
n = eil_icon_to_index (eil, icon);
eil_layout_from_line (eil, n / eil_get_items_per_line (eil));
eil_scrollbar_adjust (eil);
}
static Icon *
icon_new_from_pixbuf (EIconList *eil, GdkPixbuf *im,
const char *icon_filename, const char *text)
{
EIconListPrivate *priv;
GnomeCanvas *canvas;
GnomeCanvasGroup *group;
Icon *icon;
priv = eil->_priv;
canvas = GNOME_CANVAS (eil);
group = GNOME_CANVAS_GROUP (canvas->root);
icon = g_new0 (Icon, 1);
if (icon_filename)
icon->icon_filename = g_strdup (icon_filename);
else
icon->icon_filename = NULL;
if (im == NULL)
im = gdk_pixbuf_new_from_xpm_data ((const char**) bad_icon_xpm);
else
gdk_pixbuf_ref (im);
icon->image = GNOME_CANVAS_PIXBUF (gnome_canvas_item_new (
group,
gnome_canvas_pixbuf_get_type (),
"x", 0.0,
"y", 0.0,
"width", (double) gdk_pixbuf_get_width (im),
"height", (double) gdk_pixbuf_get_height (im),
"pixbuf", im,
NULL));
gdk_pixbuf_unref (im);
icon->text = GNOME_ICON_TEXT_ITEM (gnome_canvas_item_new (
group,
gnome_icon_text_item_get_type (),
NULL));
gnome_canvas_item_set (GNOME_CANVAS_ITEM (icon->text),
"use_broken_event_handling", FALSE,
NULL);
/* FIXME: should this use a selectable font?? */
#warning "Ettore: should we allow this font to be selectable??"
gnome_icon_text_item_configure (icon->text,
0, 0, priv->icon_width,
"-adobe-helvetica-medium-r-normal-*-*-120-*-*-p-*-iso8859-1",
text, priv->is_editable, priv->static_text);
gtk_signal_connect (GTK_OBJECT (icon->image), "event",
GTK_SIGNAL_FUNC (icon_event),
icon);
icon->text_event_id = gtk_signal_connect (GTK_OBJECT (icon->text), "event",
GTK_SIGNAL_FUNC (icon_event),
icon);
gtk_signal_connect (GTK_OBJECT (icon->text), "editing_started",
GTK_SIGNAL_FUNC (editing_started),
icon);
gtk_signal_connect (GTK_OBJECT (icon->text), "editing_stopped",
GTK_SIGNAL_FUNC (editing_stopped),
icon);
gtk_signal_connect (GTK_OBJECT (icon->text), "text_changed",
GTK_SIGNAL_FUNC (text_changed),
icon);
gtk_signal_connect (GTK_OBJECT (icon->text), "height_changed",
GTK_SIGNAL_FUNC (height_changed),
icon);
return icon;
}
static Icon *
icon_new (Eil *eil, const char *icon_filename, const char *text)
{
GdkPixbuf *im;
Icon *retval;
if (icon_filename) {
im = gdk_pixbuf_new_from_file (icon_filename);
/* Bad icon image
Fixme. Need a better graphic. */
if (im == NULL)
im = gdk_pixbuf_new_from_xpm_data ((const char**) bad_icon_xpm);
} else
im = NULL;
retval = icon_new_from_pixbuf (eil, im, icon_filename, text);
if(im)
gdk_pixbuf_unref(im);
return retval;
}
static int
icon_list_append (Eil *eil, Icon *icon)
{
EIconListPrivate *priv;
int pos;
priv = eil->_priv;
pos = priv->icons++;
g_array_append_val(priv->icon_list, icon);
switch (priv->selection_mode) {
case GTK_SELECTION_BROWSE:
e_icon_list_select_icon (eil, 0);
break;
default:
break;
}
if (!priv->frozen) {
/* FIXME: this should only layout the last line */
eil_layout_all_icons (eil);
eil_scrollbar_adjust (eil);
} else
priv->dirty = TRUE;
return priv->icons - 1;
}
static void
icon_list_insert (Eil *eil, int pos, Icon *icon)
{
EIconListPrivate *priv;
priv = eil->_priv;
if (pos == priv->icons) {
icon_list_append (eil, icon);
return;
}
g_array_insert_val(priv->icon_list, pos, icon);
priv->icons++;
switch (priv->selection_mode) {
case GTK_SELECTION_BROWSE:
e_icon_list_select_icon (eil, 0);
break;
default:
break;
}
if (!priv->frozen) {
/* FIXME: this should only layout the lines from then one
* containing the Icon to the end.
*/
eil_layout_all_icons (eil);
eil_scrollbar_adjust (eil);
} else
priv->dirty = TRUE;
sync_selection (eil, pos, SYNC_INSERT);
}
/**
* e_icon_list_insert_pixbuf:
* @eil: An icon list.
* @pos: Position at which the new icon should be inserted.
* @im: Pixbuf image with the icon image.
* @filename: Filename of the image file.
* @text: Text to be used for the icon's caption.
*
* Inserts an icon in the specified icon list. The icon is created from the
* specified Imlib image, and it is inserted at the @pos index.
*/
void
e_icon_list_insert_pixbuf (EIconList *eil, int pos, GdkPixbuf *im,
const char *icon_filename, const char *text)
{
Icon *icon;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
icon = icon_new_from_pixbuf (eil, im, icon_filename, text);
icon_list_insert (eil, pos, icon);
return;
}
/**
* e_icon_list_insert:
* @eil: An icon list.
* @pos: Position at which the new icon should be inserted.
* @icon_filename: Name of the file that holds the icon's image.
* @text: Text to be used for the icon's caption.
*
* Inserts an icon in the specified icon list. The icon's image is loaded
* from the specified file, and it is inserted at the @pos index.
*/
void
e_icon_list_insert (EIconList *eil, int pos, const char *icon_filename, const char *text)
{
Icon *icon;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
icon = icon_new (eil, icon_filename, text);
icon_list_insert (eil, pos, icon);
return;
}
/**
* e_icon_list_append_pixbuf:
* @eil: An icon list.
* @im: Pixbuf image with the icon image.
* @filename: Filename of the image file.
* @text: Text to be used for the icon's caption.
*
* Appends an icon to the specified icon list. The icon is created from
* the specified Imlib image.
*/
int
e_icon_list_append_pixbuf (EIconList *eil, GdkPixbuf *im,
const char *icon_filename, const char *text)
{
Icon *icon;
g_return_val_if_fail (eil != NULL, -1);
g_return_val_if_fail (IS_EIL (eil), -1);
icon = icon_new_from_pixbuf (eil, im, icon_filename, text);
return icon_list_append (eil, icon);
}
/**
* e_icon_list_append:
* @eil: An icon list.
* @icon_filename: Name of the file that holds the icon's image.
* @text: Text to be used for the icon's caption.
*
* Appends an icon to the specified icon list. The icon's image is loaded from
* the specified file, and it is inserted at the @pos index.
*/
int
e_icon_list_append (EIconList *eil, const char *icon_filename,
const char *text)
{
Icon *icon;
g_return_val_if_fail (eil != NULL, -1);
g_return_val_if_fail (IS_EIL (eil), -1);
icon = icon_new (eil, icon_filename, text);
return icon_list_append (eil, icon);
}
static void
icon_destroy (Icon *icon)
{
if (icon->destroy)
(* icon->destroy) (icon->data);
g_free (icon->icon_filename);
gtk_object_destroy (GTK_OBJECT (icon->image));
gtk_object_destroy (GTK_OBJECT (icon->text));
g_free (icon);
}
/**
* e_icon_list_remove:
* @eil: An icon list.
* @pos: Index of the icon that should be removed.
*
* Removes the icon at index position @pos. If a destroy handler was specified
* for that icon, it will be called with the appropriate data.
*/
void
e_icon_list_remove (EIconList *eil, int pos)
{
EIconListPrivate *priv;
int was_selected;
Icon *icon;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);
priv = eil->_priv;
was_selected = FALSE;
icon = g_array_index(priv->icon_list, Icon*, pos);
if (icon->selected) {
was_selected = TRUE;
switch (priv->selection_mode) {
case GTK_SELECTION_SINGLE:
case GTK_SELECTION_BROWSE:
case GTK_SELECTION_MULTIPLE:
case GTK_SELECTION_EXTENDED:
e_icon_list_unselect_icon (eil, pos);
break;
default:
break;
}
}
g_array_remove_index(priv->icon_list, pos);
priv->icons--;
sync_selection (eil, pos, SYNC_REMOVE);
if (was_selected) {
switch (priv->selection_mode) {
case GTK_SELECTION_BROWSE:
if (pos == priv->icons)
e_icon_list_select_icon (eil, pos - 1);
else
e_icon_list_select_icon (eil, pos);
break;
default:
break;
}
}
if (priv->icons >= priv->last_selected_idx)
priv->last_selected_idx = -1;
if (priv->last_selected_icon == icon)
priv->last_selected_icon = NULL;
icon_destroy (icon);
if (!priv->frozen) {
/* FIXME: Optimize, only re-layout from pos to end */
eil_layout_all_icons (eil);
eil_scrollbar_adjust (eil);
} else
priv->dirty = TRUE;
}
/**
* e_icon_list_clear:
* @eil: An icon list.
*
* Clears the contents for the icon list by removing all the icons. If destroy
* handlers were specified for any of the icons, they will be called with the
* appropriate data.
*/
void
e_icon_list_clear (EIconList *eil)
{
EIconListPrivate *priv;
int i;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
priv = eil->_priv;
for (i = 0; i < priv->icon_list->len; i++)
icon_destroy (g_array_index (priv->icon_list, Icon*, i));
eil_free_line_info (eil);
g_list_free (priv->selection);
priv->selection = NULL;
g_array_set_size(priv->icon_list, 0);
priv->icons = 0;
priv->last_selected_idx = -1;
priv->last_selected_icon = NULL;
if (!priv->frozen) {
eil_layout_all_icons (eil);
eil_scrollbar_adjust (eil);
} else
priv->dirty = TRUE;
}
static void
eil_destroy (GtkObject *object)
{
Eil *eil;
/* remember, destroy can be run multiple times! */
eil = EIL (object);
g_free (eil->_priv->separators);
eil->_priv->separators = NULL;
eil->_priv->frozen = 1;
eil->_priv->dirty = TRUE;
if(eil->_priv->icon_list) {
e_icon_list_clear (eil);
g_array_free(eil->_priv->icon_list, TRUE);
}
eil->_priv->icon_list = NULL;
if (eil->_priv->timer_tag != 0) {
gtk_timeout_remove (eil->_priv->timer_tag);
eil->_priv->timer_tag = 0;
}
if (GTK_OBJECT_CLASS (parent_class)->destroy)
(*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}
static void
eil_finalize (GtkObject *object)
{
Eil *eil;
eil = EIL (object);
g_free (eil->_priv);
eil->_priv = NULL;
if (GTK_OBJECT_CLASS (parent_class)->finalize)
(*GTK_OBJECT_CLASS (parent_class)->finalize) (object);
}
static void
select_icon (Eil *eil, int pos, GdkEvent *event)
{
EIconListPrivate *priv;
gint i;
Icon *icon;
priv = eil->_priv;
switch (priv->selection_mode) {
case GTK_SELECTION_SINGLE:
case GTK_SELECTION_BROWSE:
i = 0;
for (i = 0; i < priv->icon_list->len; i++) {
icon = g_array_index (priv->icon_list, Icon*, i);
if (i != pos && icon->selected)
emit_select (eil, FALSE, i, event);
}
emit_select (eil, TRUE, pos, event);
break;
case GTK_SELECTION_MULTIPLE:
case GTK_SELECTION_EXTENDED:
emit_select (eil, TRUE, pos, event);
break;
default:
break;
}
}
/**
* e_icon_list_select_icon:
* @eil: An icon list.
* @pos: Index of the icon to be selected.
*
* Selects the icon at the index specified by @pos.
*/
void
e_icon_list_select_icon (EIconList *eil, int pos)
{
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);
select_icon (eil, pos, NULL);
}
static void
unselect_icon (Eil *eil, int pos, GdkEvent *event)
{
EIconListPrivate *priv;
priv = eil->_priv;
switch (priv->selection_mode) {
case GTK_SELECTION_SINGLE:
case GTK_SELECTION_BROWSE:
case GTK_SELECTION_MULTIPLE:
case GTK_SELECTION_EXTENDED:
emit_select (eil, FALSE, pos, event);
break;
default:
break;
}
}
/**
* e_icon_list_unselect_icon:
* @eil: An icon list.
* @pos: Index of the icon to be unselected.
*
* Unselects the icon at the index specified by @pos.
*/
void
e_icon_list_unselect_icon (EIconList *eil, int pos)
{
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);
unselect_icon (eil, pos, NULL);
}
static void
eil_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
requisition->width = 1;
requisition->height = 1;
}
static void
eil_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
Eil *eil;
EIconListPrivate *priv;
eil = EIL (widget);
priv = eil->_priv;
if (GTK_WIDGET_CLASS (parent_class)->size_allocate)
(* GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation);
if (priv->frozen)
return;
eil_layout_all_icons (eil);
}
static void
eil_realize (GtkWidget *widget)
{
Eil *eil;
EIconListPrivate *priv;
GtkStyle *style;
eil = EIL (widget);
priv = eil->_priv;
priv->frozen++;
if (GTK_WIDGET_CLASS (parent_class)->realize)
(* GTK_WIDGET_CLASS (parent_class)->realize) (widget);
priv->frozen--;
/* Change the style to use the base color as the background */
style = gtk_style_copy (gtk_widget_get_style (widget));
style->bg[GTK_STATE_NORMAL] = style->base[GTK_STATE_NORMAL];
gtk_widget_set_style (widget, style);
gdk_window_set_background (GTK_LAYOUT (eil)->bin_window,
&widget->style->bg[GTK_STATE_NORMAL]);
if (priv->frozen)
return;
if (priv->dirty) {
eil_layout_all_icons (eil);
eil_scrollbar_adjust (eil);
}
}
static void
real_select_icon (Eil *eil, gint num, GdkEvent *event)
{
EIconListPrivate *priv;
Icon *icon;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
g_return_if_fail (num >= 0 && num < eil->_priv->icons);
priv = eil->_priv;
icon = g_array_index (priv->icon_list, Icon*, num);
if (icon->selected)
return;
icon->selected = TRUE;
gnome_icon_text_item_select (icon->text, TRUE);
priv->selection = g_list_append (priv->selection, GINT_TO_POINTER (num));
}
static void
real_unselect_icon (Eil *eil, gint num, GdkEvent *event)
{
EIconListPrivate *priv;
Icon *icon;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
g_return_if_fail (num >= 0 && num < eil->_priv->icons);
priv = eil->_priv;
icon = g_array_index (priv->icon_list, Icon*, num);
if (!icon->selected)
return;
icon->selected = FALSE;
gnome_icon_text_item_select (icon->text, FALSE);
priv->selection = g_list_remove (priv->selection, GINT_TO_POINTER (num));
}
/* Saves the selection of the icon list to temporary storage */
static void
store_temp_selection (Eil *eil)
{
EIconListPrivate *priv;
int i;
Icon *icon;
priv = eil->_priv;
for (i = 0; i < priv->icon_list->len; i++) {
icon = g_array_index(priv->icon_list, Icon*, i);
icon->tmp_selected = icon->selected;
}
}
#define gray50_width 2
#define gray50_height 2
static const char gray50_bits[] = {
0x02, 0x01, };
/* Button press handler for the icon list */
static gint
eil_button_press (GtkWidget *widget, GdkEventButton *event)
{
Eil *eil;
EIconListPrivate *priv;
int only_one;
GdkBitmap *stipple;
double tx, ty;
eil = EIL (widget);
priv = eil->_priv;
/* Invoke the canvas event handler and see if an item picks up the event */
if ((* GTK_WIDGET_CLASS (parent_class)->button_press_event) (widget, event))
return TRUE;
if (!(event->type == GDK_BUTTON_PRESS
&& event->button == 1
&& priv->selection_mode != GTK_SELECTION_BROWSE))
return FALSE;
only_one = priv->selection_mode == GTK_SELECTION_SINGLE;
if (only_one || (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == 0)
eil_unselect_all (eil, NULL, NULL);
if (only_one)
return TRUE;
if (priv->selecting)
return FALSE;
gnome_canvas_window_to_world (GNOME_CANVAS (eil), event->x, event->y, &tx, &ty);
priv->sel_start_x = tx;
priv->sel_start_y = ty;
priv->sel_state = event->state;
priv->selecting = TRUE;
store_temp_selection (eil);
stipple = gdk_bitmap_create_from_data (NULL, gray50_bits, gray50_width, gray50_height);
priv->sel_rect = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (eil)),
gnome_canvas_rect_get_type (),
"x1", tx,
"y1", ty,
"x2", tx,
"y2", ty,
"outline_color", "black",
"width_pixels", 1,
"outline_stipple", stipple,
NULL);
gdk_bitmap_unref (stipple);
gnome_canvas_item_grab (priv->sel_rect, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
NULL, event->time);
return TRUE;
}
/* Returns whether the specified icon is at least partially inside the specified
* rectangle.
*/
static int
icon_is_in_area (Icon *icon, int x1, int y1, int x2, int y2)
{
double ix1, iy1, ix2, iy2;
if (x1 == x2 && y1 == y2)
return FALSE;
gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (icon->image), &ix1, &iy1, &ix2, &iy2);
if (ix1 <= x2 && iy1 <= y2 && ix2 >= x1 && iy2 >= y1)
return TRUE;
gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (icon->text), &ix1, &iy1, &ix2, &iy2);
if (ix1 <= x2 && iy1 <= y2 && ix2 >= x1 && iy2 >= y1)
return TRUE;
return FALSE;
}
/* Updates the rubberband selection to the specified point */
static void
update_drag_selection (Eil *eil, int x, int y)
{
EIconListPrivate *priv;
int x1, x2, y1, y2;
int i;
Icon *icon;
int additive, invert;
priv = eil->_priv;
/* Update rubberband */
if (priv->sel_start_x < x) {
x1 = priv->sel_start_x;
x2 = x;
} else {
x1 = x;
x2 = priv->sel_start_x;
}
if (priv->sel_start_y < y) {
y1 = priv->sel_start_y;
y2 = y;
} else {
y1 = y;
y2 = priv->sel_start_y;
}
if (x1 < 0)
x1 = 0;
if (y1 < 0)
y1 = 0;
if (x2 >= GTK_WIDGET (eil)->allocation.width)
x2 = GTK_WIDGET (eil)->allocation.width - 1;
if (y2 >= priv->total_height)
y2 = priv->total_height - 1;
gnome_canvas_item_set (priv->sel_rect,
"x1", (double) x1,
"y1", (double) y1,
"x2", (double) x2,
"y2", (double) y2,
NULL);
/* Select or unselect icons as appropriate */
additive = priv->sel_state & GDK_SHIFT_MASK;
invert = priv->sel_state & GDK_CONTROL_MASK;
for (i = 0; i < priv->icon_list->len; i++) {
icon = g_array_index(priv->icon_list, Icon*, i);
if (icon_is_in_area (icon, x1, y1, x2, y2)) {
if (invert) {
if (icon->selected == icon->tmp_selected)
emit_select (eil, !icon->selected, i, NULL);
} else if (additive) {
if (!icon->selected)
emit_select (eil, TRUE, i, NULL);
} else {
if (!icon->selected)
emit_select (eil, TRUE, i, NULL);
}
} else if (icon->selected != icon->tmp_selected)
emit_select (eil, icon->tmp_selected, i, NULL);
}
}
/* Button release handler for the icon list */
static gint
eil_button_release (GtkWidget *widget, GdkEventButton *event)
{
Eil *eil;
EIconListPrivate *priv;
double x, y;
eil = EIL (widget);
priv = eil->_priv;
if (!priv->selecting)
return (* GTK_WIDGET_CLASS (parent_class)->button_release_event) (widget, event);
if (event->button != 1)
return FALSE;
gnome_canvas_window_to_world (GNOME_CANVAS (eil), event->x, event->y, &x, &y);
update_drag_selection (eil, x, y);
gnome_canvas_item_ungrab (priv->sel_rect, event->time);
gtk_object_destroy (GTK_OBJECT (priv->sel_rect));
priv->sel_rect = NULL;
priv->selecting = FALSE;
if (priv->timer_tag != 0) {
gtk_timeout_remove (priv->timer_tag);
priv->timer_tag = 0;
}
return TRUE;
}
/* Autoscroll timeout handler for the icon list */
static gint
scroll_timeout (gpointer data)
{
Eil *eil;
EIconListPrivate *priv;
GtkAdjustment *adj;
double x, y;
int value;
eil = data;
priv = eil->_priv;
GDK_THREADS_ENTER ();
adj = gtk_layout_get_vadjustment (GTK_LAYOUT (eil));
value = adj->value + priv->value_diff;
if (value > adj->upper - adj->page_size)
value = adj->upper - adj->page_size;
gtk_adjustment_set_value (adj, value);
gnome_canvas_window_to_world (GNOME_CANVAS (eil),
priv->event_last_x, priv->event_last_y,
&x, &y);
update_drag_selection (eil, x, y);
GDK_THREADS_LEAVE();
return TRUE;
}
/* Motion event handler for the icon list */
static gint
eil_motion_notify (GtkWidget *widget, GdkEventMotion *event)
{
Eil *eil;
EIconListPrivate *priv;
double x, y;
eil = EIL (widget);
priv = eil->_priv;
if (!priv->selecting)
return (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event) (widget, event);
gnome_canvas_window_to_world (GNOME_CANVAS (eil), event->x, event->y, &x, &y);
update_drag_selection (eil, x, y);
/* If we are out of bounds, schedule a timeout that will do the scrolling */
if (event->y < 0 || event->y > widget->allocation.height) {
if (priv->timer_tag == 0)
priv->timer_tag = gtk_timeout_add (SCROLL_TIMEOUT, scroll_timeout, eil);
if (event->y < 0)
priv->value_diff = event->y;
else
priv->value_diff = event->y - widget->allocation.height;
priv->event_last_x = event->x;
priv->event_last_y = event->y;
/* Make the steppings be relative to the mouse distance from the
* canvas. Also notice the timeout above is small to give a
* more smooth movement.
*/
priv->value_diff /= 5;
} else if (priv->timer_tag != 0) {
gtk_timeout_remove (priv->timer_tag);
priv->timer_tag = 0;
}
return TRUE;
}
typedef gboolean (*xGtkSignal_BOOL__INT_POINTER) (GtkObject * object,
gint arg1,
gpointer arg2,
gpointer user_data);
static void
xgtk_marshal_BOOL__INT_POINTER (GtkObject *object, GtkSignalFunc func, gpointer func_data,
GtkArg *args)
{
xGtkSignal_BOOL__INT_POINTER rfunc;
gboolean *return_val;
return_val = GTK_RETLOC_BOOL (args[2]);
rfunc = (xGtkSignal_BOOL__INT_POINTER) func;
*return_val = (*rfunc) (object,
GTK_VALUE_INT (args[0]),
GTK_VALUE_POINTER (args[1]),
func_data);
}
static void
eil_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
EIconList *eil;
eil = E_ICON_LIST (object);
switch (arg_id) {
default:
break;
}
}
static void
eil_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
EIconList *eil;
EIconListPrivate *priv;
eil = E_ICON_LIST (object);
priv = eil->_priv;
switch (arg_id) {
default:
arg->type = GTK_TYPE_INVALID;
break;
}
}
static void
eil_class_init (EilClass *eil_class)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
GtkLayoutClass *layout_class;
GnomeCanvasClass *canvas_class;
object_class = (GtkObjectClass *) eil_class;
widget_class = (GtkWidgetClass *) eil_class;
layout_class = (GtkLayoutClass *) eil_class;
canvas_class = (GnomeCanvasClass *) eil_class;
parent_class = gtk_type_class (gnome_canvas_get_type ());
eil_signals[SELECT_ICON] =
gtk_signal_new (
"select_icon",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (EIconListClass, select_icon),
gtk_marshal_NONE__INT_POINTER,
GTK_TYPE_NONE, 2,
GTK_TYPE_INT,
GTK_TYPE_GDK_EVENT);
eil_signals[UNSELECT_ICON] =
gtk_signal_new (
"unselect_icon",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (EIconListClass, unselect_icon),
gtk_marshal_NONE__INT_POINTER,
GTK_TYPE_NONE, 2,
GTK_TYPE_INT,
GTK_TYPE_GDK_EVENT);
eil_signals[TEXT_CHANGED] =
gtk_signal_new (
"text_changed",
GTK_RUN_LAST,
object_class->type,
GTK_SIGNAL_OFFSET (EIconListClass, text_changed),
xgtk_marshal_BOOL__INT_POINTER,
GTK_TYPE_BOOL, 2,
GTK_TYPE_INT,
GTK_TYPE_POINTER);
gtk_object_class_add_signals (object_class, eil_signals, LAST_SIGNAL);
object_class->destroy = eil_destroy;
object_class->finalize = eil_finalize;
object_class->set_arg = eil_set_arg;
object_class->get_arg = eil_get_arg;
widget_class->size_request = eil_size_request;
widget_class->size_allocate = eil_size_allocate;
widget_class->realize = eil_realize;
widget_class->button_press_event = eil_button_press;
widget_class->button_release_event = eil_button_release;
widget_class->motion_notify_event = eil_motion_notify;
eil_class->select_icon = real_select_icon;
eil_class->unselect_icon = real_unselect_icon;
}
static void
eil_init (Eil *eil)
{
eil->_priv = g_new0 (EIconListPrivate, 1);
eil->_priv->icon_list = g_array_new(FALSE, FALSE, sizeof(gpointer));
eil->_priv->row_spacing = DEFAULT_ROW_SPACING;
eil->_priv->col_spacing = DEFAULT_COL_SPACING;
eil->_priv->text_spacing = DEFAULT_TEXT_SPACING;
eil->_priv->icon_border = DEFAULT_ICON_BORDER;
eil->_priv->separators = g_strdup (" ");
eil->_priv->selection_mode = GTK_SELECTION_SINGLE;
eil->_priv->dirty = TRUE;
gnome_canvas_set_scroll_region (GNOME_CANVAS (eil), 0.0, 0.0, 1000000.0, 1000000.0);
gnome_canvas_scroll_to (GNOME_CANVAS (eil), 0, 0);
}
/**
* e_icon_list_get_type:
*
* Registers the &EIconList class if necessary, and returns the type ID
* associated to it.
*
* Returns: The type ID of the &EIconList class.
*/
guint
e_icon_list_get_type (void)
{
static guint eil_type = 0;
if (!eil_type) {
GtkTypeInfo eil_info = {
"EIconList",
sizeof (EIconList),
sizeof (EIconListClass),
(GtkClassInitFunc) eil_class_init,
(GtkObjectInitFunc) eil_init,
NULL,
NULL,
NULL
};
eil_type = gtk_type_unique (gnome_canvas_get_type (),
&eil_info);
}
return eil_type;
}
/**
* e_icon_list_set_icon_width:
* @eil: An icon list.
* @w: New width for the icon columns.
*
* Sets the amount of horizontal space allocated to the icons, i.e. the column
* width of the icon list.
*/
void
e_icon_list_set_icon_width (EIconList *eil, int w)
{
EIconListPrivate *priv;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
priv = eil->_priv;
priv->icon_width = w;
if (priv->frozen) {
priv->dirty = TRUE;
return;
}
eil_layout_all_icons (eil);
eil_scrollbar_adjust (eil);
}
/**
* e_icon_list_construct:
* @eil: An icon list.
* @icon_width: Width for the icon columns.
* @flags: A combination of %E_ICON_LIST_IS_EDITABLE and %E_ICON_LIST_STATIC_TEXT.
*
* Constructor for the icon list, to be used by derived classes.
**/
void
e_icon_list_construct (EIconList *eil, guint icon_width, int flags)
{
EIconListPrivate *priv;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
priv = eil->_priv;
e_icon_list_set_icon_width (eil, icon_width);
priv->is_editable = (flags & E_ICON_LIST_IS_EDITABLE) != 0;
priv->static_text = (flags & E_ICON_LIST_STATIC_TEXT) != 0;
}
/**
* e_icon_list_new: [constructor]
* @icon_width: Width for the icon columns.
* @flags: A combination of %E_ICON_LIST_IS_EDITABLE and %E_ICON_LIST_STATIC_TEXT.
*
* Creates a new icon list widget. The icon columns are allocated a width of
* @icon_width pixels. Icon captions will be word-wrapped to this width as
* well.
*
* If @flags has the %E_ICON_LIST_IS_EDITABLE flag set, then the user will be
* able to edit the text in the icon captions, and the "text_changed" signal
* will be emitted when an icon's text is changed.
*
* If @flags has the %E_ICON_LIST_STATIC_TEXT flags set, then the text
* for the icon captions will not be copied inside the icon list; it will only
* store the pointers to the original text strings specified by the application.
* This is intended to save memory. If this flag is not set, then the text
* strings will be copied and managed internally.
*
* Returns: a newly-created icon list widget
*/
GtkWidget *
e_icon_list_new (guint icon_width, int flags)
{
Eil *eil;
gtk_widget_push_colormap (gdk_rgb_get_cmap ());
eil = EIL (gtk_type_new (e_icon_list_get_type ()));
gtk_widget_pop_colormap ();
e_icon_list_construct (eil, icon_width, flags);
return GTK_WIDGET (eil);
}
/**
* e_icon_list_freeze:
* @eil: An icon list.
*
* Freezes an icon list so that any changes made to it will not be
* reflected on the screen until it is thawed with e_icon_list_thaw().
* It is recommended to freeze the icon list before inserting or deleting
* many icons, for example, so that the layout process will only be executed
* once, when the icon list is finally thawed.
*
* You can call this function multiple times, but it must be balanced with the
* same number of calls to e_icon_list_thaw() before the changes will take
* effect.
*/
void
e_icon_list_freeze (EIconList *eil)
{
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
eil->_priv->frozen++;
/* We hide the root so that the user will not see any changes while the
* icon list is doing stuff.
*/
if (eil->_priv->frozen == 1)
gnome_canvas_item_hide (GNOME_CANVAS (eil)->root);
}
/**
* e_icon_list_thaw:
* @eil: An icon list.
*
* Thaws the icon list and performs any pending layout operations. This
* is to be used in conjunction with e_icon_list_freeze().
*/
void
e_icon_list_thaw (EIconList *eil)
{
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
g_return_if_fail (eil->_priv->frozen > 0);
eil->_priv->frozen--;
if (eil->_priv->dirty) {
eil_layout_all_icons (eil);
eil_scrollbar_adjust (eil);
}
if (eil->_priv->frozen == 0)
gnome_canvas_item_show (GNOME_CANVAS (eil)->root);
}
/**
* e_icon_list_set_selection_mode
* @eil: An icon list.
* @mode: New selection mode.
*
* Sets the selection mode for an icon list. The %GTK_SELECTION_MULTIPLE and
* %GTK_SELECTION_EXTENDED modes are considered equivalent.
*/
void
e_icon_list_set_selection_mode (EIconList *eil, GtkSelectionMode mode)
{
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
eil->_priv->selection_mode = mode;
eil->_priv->last_selected_idx = -1;
eil->_priv->last_selected_icon = NULL;
}
/**
* e_icon_list_set_icon_data_full:
* @eil: An icon list.
* @pos: Index of an icon.
* @data: User data to set on the icon.
* @destroy: Destroy notification handler for the icon.
*
* Associates the @data pointer to the icon at the index specified by @pos. The
* @destroy argument points to a function that will be called when the icon is
* destroyed, or NULL if no function is to be called when this happens.
*/
void
e_icon_list_set_icon_data_full (EIconList *eil,
int pos, gpointer data,
GtkDestroyNotify destroy)
{
Icon *icon;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);
icon = g_array_index (eil->_priv->icon_list, Icon*, pos);
icon->data = data;
icon->destroy = destroy;
}
/**
* e_icon_list_get_icon_data:
* @eil: An icon list.
* @pos: Index of an icon.
* @data: User data to set on the icon.
*
* Associates the @data pointer to the icon at the index specified by @pos.
*/
void
e_icon_list_set_icon_data (EIconList *eil, int pos, gpointer data)
{
e_icon_list_set_icon_data_full (eil, pos, data, NULL);
}
/**
* e_icon_list_get_icon_data:
* @eil: An icon list.
* @pos: Index of an icon.
*
* Returns the user data pointer associated to the icon at the index specified
* by @pos.
*/
gpointer
e_icon_list_get_icon_data (EIconList *eil, int pos)
{
Icon *icon;
g_return_val_if_fail (eil != NULL, NULL);
g_return_val_if_fail (IS_EIL (eil), NULL);
g_return_val_if_fail (pos >= 0 && pos < eil->_priv->icons, NULL);
icon = g_array_index (eil->_priv->icon_list, Icon*, pos);
return icon->data;
}
/**
* e_icon_list_find_icon_from_data:
* @eil: An icon list.
* @data: Data pointer associated to an icon.
*
* Returns the index of the icon whose user data has been set to @data,
* or -1 if no icon has this data associated to it.
*/
int
e_icon_list_find_icon_from_data (EIconList *eil, gpointer data)
{
EIconListPrivate *priv;
int n;
Icon *icon;
g_return_val_if_fail (eil != NULL, -1);
g_return_val_if_fail (IS_EIL (eil), -1);
priv = eil->_priv;
for (n = 0; n < priv->icon_list->len; n++) {
icon = g_array_index(priv->icon_list, Icon*, n);
if (icon->data == data)
return n;
}
return -1;
}
/* Sets an integer value in the private structure of the icon list, and updates it */
static void
set_value (EIconList *eil, EIconListPrivate *priv, int *dest, int val)
{
if (val == *dest)
return;
*dest = val;
if (!priv->frozen) {
eil_layout_all_icons (eil);
eil_scrollbar_adjust (eil);
} else
priv->dirty = TRUE;
}
/**
* e_icon_list_set_row_spacing:
* @eil: An icon list.
* @pixels: Number of pixels for inter-row spacing.
*
* Sets the spacing to be used between rows of icons.
*/
void
e_icon_list_set_row_spacing (EIconList *eil, int pixels)
{
EIconListPrivate *priv;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
priv = eil->_priv;
set_value (eil, priv, &priv->row_spacing, pixels);
}
/**
* e_icon_list_set_col_spacing:
* @eil: An icon list.
* @pixels: Number of pixels for inter-column spacing.
*
* Sets the spacing to be used between columns of icons.
*/
void
e_icon_list_set_col_spacing (EIconList *eil, int pixels)
{
EIconListPrivate *priv;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
priv = eil->_priv;
set_value (eil, priv, &priv->col_spacing, pixels);
}
/**
* e_icon_list_set_text_spacing:
* @eil: An icon list.
* @pixels: Number of pixels between an icon's image and its caption.
*
* Sets the spacing to be used between an icon's image and its text caption.
*/
void
e_icon_list_set_text_spacing (EIconList *eil, int pixels)
{
EIconListPrivate *priv;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
priv = eil->_priv;
set_value (eil, priv, &priv->text_spacing, pixels);
}
/**
* e_icon_list_set_icon_border:
* @eil: An icon list.
* @pixels: Number of border pixels to be used around an icon's image.
*
* Sets the width of the border to be displayed around an icon's image. This is
* currently not implemented.
*/
void
e_icon_list_set_icon_border (EIconList *eil, int pixels)
{
EIconListPrivate *priv;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
priv = eil->_priv;
set_value (eil, priv, &priv->icon_border, pixels);
}
/**
* e_icon_list_set_separators:
* @eil: An icon list.
* @sep: String with characters to be used as word separators.
*
* Sets the characters that can be used as word separators when doing
* word-wrapping in the icon text captions.
*/
void
e_icon_list_set_separators (EIconList *eil, const char *sep)
{
EIconListPrivate *priv;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
g_return_if_fail (sep != NULL);
priv = eil->_priv;
if (priv->separators)
g_free (priv->separators);
priv->separators = g_strdup (sep);
if (priv->frozen) {
priv->dirty = TRUE;
return;
}
eil_layout_all_icons (eil);
eil_scrollbar_adjust (eil);
}
/**
* e_icon_list_moveto:
* @eil: An icon list.
* @pos: Index of an icon.
* @yalign: Vertical alignment of the icon.
*
* Makes the icon whose index is @pos be visible on the screen. The icon list
* gets scrolled so that the icon is visible. An alignment of 0.0 represents
* the top of the visible part of the icon list, and 1.0 represents the bottom.
* An icon can be centered on the icon list.
*/
void
e_icon_list_moveto (EIconList *eil, int pos, double yalign)
{
EIconListPrivate *priv;
GtkAdjustment *adj;
IconLine *il;
GList *l;
int i, y, uh, line;
g_return_if_fail (eil != NULL);
g_return_if_fail (IS_EIL (eil));
g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);
g_return_if_fail (yalign >= 0.0 && yalign <= 1.0);
priv = eil->_priv;
g_return_if_fail (priv->lines != NULL);
line = pos / eil_get_items_per_line (eil);
y = 0;
for (i = 0, l = priv->lines; l && i < line; l = l->next, i++) {
il = l->data;
y += icon_line_height (eil, il);
}
il = l->data;
uh = GTK_WIDGET (eil)->allocation.height - icon_line_height (eil,il);
adj = gtk_layout_get_vadjustment (GTK_LAYOUT (eil));
gtk_adjustment_set_value (adj, y - uh * yalign);
}
/**
* e_icon_list_is_visible:
* @eil: An icon list.
* @pos: Index of an icon.
*
* Returns whether the icon at the index specified by @pos is visible. This
* will be %GTK_VISIBILITY_NONE if the icon is not visible at all,
* %GTK_VISIBILITY_PARTIAL if the icon is at least partially shown, and
* %GTK_VISIBILITY_FULL if the icon is fully visible.
*/
GtkVisibility
e_icon_list_icon_is_visible (EIconList *eil, int pos)
{
EIconListPrivate *priv;
GtkAdjustment *adj;
IconLine *il;
GList *l;
int line, y1, y2, i;
g_return_val_if_fail (eil != NULL, GTK_VISIBILITY_NONE);
g_return_val_if_fail (IS_EIL (eil), GTK_VISIBILITY_NONE);
g_return_val_if_fail (pos >= 0 && pos < eil->_priv->icons,
GTK_VISIBILITY_NONE);
priv = eil->_priv;
if (priv->lines == NULL)
return GTK_VISIBILITY_NONE;
line = pos / eil_get_items_per_line (eil);
y1 = 0;
for (i = 0, l = priv->lines; l && i < line; l = l->next, i++) {
il = l->data;
y1 += icon_line_height (eil, il);
}
y2 = y1 + icon_line_height (eil, (IconLine *) l->data);
adj = gtk_layout_get_vadjustment (GTK_LAYOUT (eil));
if (y2 < adj->value)
return GTK_VISIBILITY_NONE;
if (y1 > adj->value + GTK_WIDGET (eil)->allocation.height)
return GTK_VISIBILITY_NONE;
if (y2 <= adj->value + GTK_WIDGET (eil)->allocation.height &&
y1 >= adj->value)
return GTK_VISIBILITY_FULL;
return GTK_VISIBILITY_PARTIAL;
}
/**
* e_icon_list_get_icon_at:
* @eil: An icon list.
* @x: X position in the icon list window.
* @y: Y position in the icon list window.
*
* Returns the index of the icon that is under the specified coordinates, which
* are relative to the icon list's window. If there is no icon in that
* position, -1 is returned.
*/
int
e_icon_list_get_icon_at (EIconList *eil, int x, int y)
{
EIconListPrivate *priv;
double wx, wy;
double dx, dy;
int cx, cy;
int n;
GnomeCanvasItem *item;
double dist;
g_return_val_if_fail (eil != NULL, -1);
g_return_val_if_fail (IS_EIL (eil), -1);
priv = eil->_priv;
dx = x;
dy = y;
gnome_canvas_window_to_world (GNOME_CANVAS (eil), dx, dy, &wx, &wy);
gnome_canvas_w2c (GNOME_CANVAS (eil), wx, wy, &cx, &cy);
for (n = 0; n < priv->icon_list->len; n++) {
Icon *icon = g_array_index(priv->icon_list, Icon*, n);
GnomeCanvasItem *image = GNOME_CANVAS_ITEM (icon->image);
GnomeCanvasItem *text = GNOME_CANVAS_ITEM (icon->text);
if (wx >= image->x1 && wx <= image->x2 && wy >= image->y1 && wy <= image->y2) {
dist = (* GNOME_CANVAS_ITEM_CLASS (GTK_OBJECT (image)->klass)->point) (
image,
wx, wy,
cx, cy,
&item);
if ((int) (dist * GNOME_CANVAS (eil)->pixels_per_unit + 0.5)
<= GNOME_CANVAS (eil)->close_enough)
return n;
}
if (wx >= text->x1 && wx <= text->x2 && wy >= text->y1 && wy <= text->y2) {
dist = (* GNOME_CANVAS_ITEM_CLASS (GTK_OBJECT (text)->klass)->point) (
text,
wx, wy,
cx, cy,
&item);
if ((int) (dist * GNOME_CANVAS (eil)->pixels_per_unit + 0.5)
<= GNOME_CANVAS (eil)->close_enough)
return n;
}
}
return -1;
}
/**
* e_icon_list_get_num_icons:
* @eil: An icon list.
*
* Returns the number of icons in the icon list.
*/
guint
e_icon_list_get_num_icons (EIconList *eil)
{
g_return_val_if_fail (E_IS_ICON_LIST (eil), 0);
return eil->_priv->icons;
}
/**
* e_icon_list_get_selection:
* @eil: An icon list.
*
* Returns a list of integers with the indices of the currently selected icons.
*/
GList *
e_icon_list_get_selection (EIconList *eil)
{
g_return_val_if_fail (E_IS_ICON_LIST (eil), NULL);
return eil->_priv->selection;
}
/**
* e_icon_list_get_selection:
* @eil: An icon list.
* @idx: Index of an @icon.
*
* Returns the filename of the icon with index @idx.
*/
gchar *
e_icon_list_get_icon_filename (EIconList *eil, int idx)
{
Icon *icon;
g_return_val_if_fail (eil != NULL, NULL);
g_return_val_if_fail (IS_EIL (eil), NULL);
g_return_val_if_fail (idx >= 0 && idx < eil->_priv->icons, NULL);
icon = g_array_index (eil->_priv->icon_list, Icon*, idx);
return icon->icon_filename;
}
/**
* e_icon_list_find_icon_from_filename:
* @eil: An icon list.
* @filename: Filename of an icon.
*
* Returns the index of the icon whose filename is @filename or -1 if
* there is no icon with this filename.
*/
int
e_icon_list_find_icon_from_filename (EIconList *eil,
const gchar *filename)
{
EIconListPrivate *priv;
int n;
Icon *icon;
g_return_val_if_fail (eil != NULL, -1);
g_return_val_if_fail (IS_EIL (eil), -1);
g_return_val_if_fail (filename != NULL, -1);
priv = eil->_priv;
for (n = 0; n < priv->icon_list->len; n++) {
icon = g_array_index(priv->icon_list, Icon*, n);
if (!strcmp (icon->icon_filename, filename))
return n;
}
return -1;
}