summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEttore Perazzoli <ettore@src.gnome.org>1999-10-07 20:34:14 +0000
committerEttore Perazzoli <ettore@src.gnome.org>1999-10-07 20:34:14 +0000
commit13a1ecc797c27b25ea03ca2683313b274e954211 (patch)
treeeff38e3ce1bf13fc349316bf4966f48b7360508c
parent10b8bd8a5d9ce84ac6409702129df750fe92f5e3 (diff)
downloadnautilus-13a1ecc797c27b25ea03ca2683313b274e954211.tar.gz
Initial revision
-rw-r--r--libnautilus-extensions/gnome-icon-container-dnd.c647
-rw-r--r--libnautilus-extensions/gnome-icon-container-dnd.h75
-rw-r--r--libnautilus-extensions/gnome-icon-container-layout.c161
-rw-r--r--libnautilus-extensions/gnome-icon-container-layout.h58
-rw-r--r--libnautilus-extensions/gnome-icon-container-private.h218
-rw-r--r--libnautilus-extensions/gnome-icon-container.c3020
-rw-r--r--libnautilus-extensions/gnome-icon-container.h153
-rw-r--r--libnautilus-extensions/gtkflist.c539
-rw-r--r--libnautilus-extensions/gtkflist.h70
-rw-r--r--libnautilus-extensions/gtkscrollframe.c1210
-rw-r--r--libnautilus-extensions/gtkscrollframe.h93
-rw-r--r--libnautilus-extensions/nautilus-file-operations-progress.c351
-rw-r--r--libnautilus-extensions/nautilus-file-operations-progress.h100
-rw-r--r--libnautilus-extensions/nautilus-file-operations.c252
-rw-r--r--libnautilus-extensions/nautilus-file-operations.h38
-rw-r--r--libnautilus-extensions/nautilus-scroll-frame.c1210
-rw-r--r--libnautilus-extensions/nautilus-scroll-frame.h93
-rw-r--r--libnautilus-private/gnome-icon-container-dnd.c647
-rw-r--r--libnautilus-private/gnome-icon-container-dnd.h75
-rw-r--r--libnautilus-private/gnome-icon-container-layout.c161
-rw-r--r--libnautilus-private/gnome-icon-container-layout.h58
-rw-r--r--libnautilus-private/gnome-icon-container-private.h218
-rw-r--r--libnautilus-private/gnome-icon-container.c3020
-rw-r--r--libnautilus-private/gnome-icon-container.h153
-rw-r--r--libnautilus-private/gtkflist.c539
-rw-r--r--libnautilus-private/gtkflist.h70
-rw-r--r--libnautilus-private/gtkscrollframe.c1210
-rw-r--r--libnautilus-private/gtkscrollframe.h93
-rw-r--r--libnautilus-private/nautilus-file-operations-progress.c351
-rw-r--r--libnautilus-private/nautilus-file-operations-progress.h100
-rw-r--r--libnautilus-private/nautilus-file-operations.c252
-rw-r--r--libnautilus-private/nautilus-file-operations.h38
-rw-r--r--libnautilus-private/nautilus-scroll-frame.c1210
-rw-r--r--libnautilus-private/nautilus-scroll-frame.h93
-rw-r--r--libnautilus/gnome-icon-container-dnd.c647
-rw-r--r--libnautilus/gnome-icon-container-dnd.h75
-rw-r--r--libnautilus/gnome-icon-container-layout.c161
-rw-r--r--libnautilus/gnome-icon-container-layout.h58
-rw-r--r--libnautilus/gnome-icon-container-private.h218
-rw-r--r--libnautilus/gnome-icon-container.c3020
-rw-r--r--libnautilus/gnome-icon-container.h153
-rw-r--r--libnautilus/gtkflist.c539
-rw-r--r--libnautilus/gtkflist.h70
-rw-r--r--libnautilus/gtkscrollframe.c1210
-rw-r--r--libnautilus/gtkscrollframe.h93
-rw-r--r--src/file-manager/dfos-corba.c254
-rw-r--r--src/file-manager/dfos-corba.h32
-rw-r--r--src/file-manager/dfos-xfer-progress-dialog.c351
-rw-r--r--src/file-manager/dfos-xfer-progress-dialog.h100
-rw-r--r--src/file-manager/dfos-xfer.c252
-rw-r--r--src/file-manager/dfos-xfer.h38
-rw-r--r--src/file-manager/dfos.c70
-rw-r--r--src/file-manager/dfos.h39
-rw-r--r--src/file-manager/fm-directory-view.c1052
-rw-r--r--src/file-manager/fm-directory-view.h127
55 files changed, 25135 insertions, 0 deletions
diff --git a/libnautilus-extensions/gnome-icon-container-dnd.c b/libnautilus-extensions/gnome-icon-container-dnd.c
new file mode 100644
index 000000000..02f97f181
--- /dev/null
+++ b/libnautilus-extensions/gnome-icon-container-dnd.c
@@ -0,0 +1,647 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container-dnd.c - Drag & drop handling for the icon container
+ widget.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "gnome-icon-container-private.h"
+
+#include "gnome-icon-container-dnd.h"
+
+
+struct _DndSelectionItem {
+ gchar *uri;
+ gint x, y;
+};
+typedef struct _DndSelectionItem DndSelectionItem;
+
+
+static GtkTargetEntry drag_types [] = {
+ { GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST_TYPE, 0, GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST },
+ { GNOME_ICON_CONTAINER_DND_URI_LIST_TYPE, 0, GNOME_ICON_CONTAINER_DND_URI_LIST },
+ /* { GNOME_ICON_CONTAINER_DND_URL_TYPE, 0, GNOME_ICON_CONTAINER_DND_URL } */
+};
+static const int num_drag_types = sizeof (drag_types) / sizeof (drag_types[0]);
+
+static GtkTargetEntry drop_types [] = {
+ { GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST_TYPE, 0, GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST },
+ { GNOME_ICON_CONTAINER_DND_URI_LIST_TYPE, 0, GNOME_ICON_CONTAINER_DND_URI_LIST },
+ { GNOME_ICON_CONTAINER_DND_URL_TYPE, 0, GNOME_ICON_CONTAINER_DND_URL }
+};
+static const int num_drop_types = sizeof (drop_types) / sizeof (drop_types[0]);
+
+
+static GnomeCanvasItem *
+create_selection_shadow (GnomeIconContainer *container,
+ GList *list)
+{
+ GnomeCanvasGroup *group;
+ GnomeCanvas *canvas;
+ GdkBitmap *stipple;
+ gint max_x, max_y;
+ gint min_x, min_y;
+ gint icon_width, icon_height;
+ gint cell_width, cell_height;
+ GList *p;
+
+ if (list == NULL)
+ return NULL;
+
+ stipple = container->priv->dnd_info->stipple;
+ g_return_val_if_fail (stipple != NULL, NULL);
+
+ icon_width = GNOME_ICON_CONTAINER_ICON_WIDTH (container);
+ icon_height = GNOME_ICON_CONTAINER_ICON_HEIGHT (container);
+ cell_width = GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ cell_height = GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ canvas = GNOME_CANVAS (container);
+
+ /* Creating a big set of rectangles in the canvas can be expensive, so
+ we try to be smart and only create the maximum number of rectangles
+ that we will need, in the vertical/horizontal directions. */
+
+ max_x = GTK_WIDGET (container)->allocation.width;
+ min_x = -max_x;
+
+ max_y = GTK_WIDGET (container)->allocation.height;
+ min_y = -max_y;
+
+ /* Create a group, so that it's easier to move all the items around at
+ once. */
+ group = GNOME_CANVAS_GROUP
+ (gnome_canvas_item_new (GNOME_CANVAS_GROUP (canvas->root),
+ gnome_canvas_group_get_type (),
+ NULL));
+
+ for (p = list; p != NULL; p = p->next) {
+ DndSelectionItem *item;
+ gint x1, y1;
+ gint x2, y2;
+
+ item = p->data;
+
+ x1 = item->x;
+ y1 = item->y;
+ x2 = item->x + icon_width;
+ y2 = item->y + icon_height;
+
+ if (x2 >= min_x && x1 <= max_x && y2 >= min_y && y1 <= max_y) {
+ GnomeCanvasItem *rect;
+
+ rect = gnome_canvas_item_new
+ (group,
+ gnome_canvas_rect_get_type (),
+ "x1", (double) x1, "y1", (double) y1,
+ "x2", (double) x2, "y2", (double) y2,
+ "outline_color", "black",
+ "outline_stipple", stipple,
+ "width_pixels", 1,
+ NULL);
+ }
+ }
+
+ return GNOME_CANVAS_ITEM (group);
+}
+
+/* This is a workaround for a gnome-canvas bug: with the current (1.0.18)
+ gnome-libs, setting the x/y values for an existing group fails at updating
+ the bounds of the group. So, instead of setting the x/y values to the
+ current position at initialization time, we set them to (0,0) and then use a
+ simple affine transform. */
+static void
+set_shadow_position (GnomeCanvasItem *shadow,
+ gdouble x, gdouble y)
+{
+ double affine[6];
+
+ affine[0] = 1.0;
+ affine[1] = 0.0;
+ affine[2] = 0.0;
+ affine[3] = 1.0;
+ affine[4] = x;
+ affine[5] = y;
+
+ gnome_canvas_item_affine_absolute (shadow, affine);
+}
+
+
+/* Functions to deal with DndSelectionItems. */
+
+static DndSelectionItem *
+dnd_selection_item_new (void)
+{
+ DndSelectionItem *new;
+
+ new = g_new (DndSelectionItem, 1);
+ new->uri = NULL;
+ new->x = 0;
+ new->y = 0;
+
+ return new;
+}
+
+static void
+dnd_selection_item_destroy (DndSelectionItem *item)
+{
+ g_free (item->uri);
+ g_free (item);
+}
+
+static void
+destroy_selection_list (GList *list)
+{
+ GList *p;
+
+ if (list == NULL)
+ return;
+
+ for (p = list; p != NULL; p = p->next)
+ dnd_selection_item_destroy ((DndSelectionItem *) p->data);
+
+ g_list_free (list);
+}
+
+
+/* Source-side handling of the drag. */
+
+/* Encode a "special/x-gnome-icon-list" selection. */
+static void
+set_gnome_icon_list_selection (GnomeIconContainer *container,
+ GtkSelectionData *selection_data)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+ GString *data;
+ gdouble x_offset, y_offset;
+
+ priv = container->priv;
+ if (priv->icons == NULL) {
+ /* FIXME? Actually this probably shouldn't happen. */
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, NULL, 0);
+ return;
+ }
+
+ x_offset = container->priv->dnd_info->start_x;
+ y_offset = container->priv->dnd_info->start_y;
+
+ data = g_string_new (NULL);
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+ gchar *s;
+ gint x, y;
+
+ icon = p->data;
+ if (! icon->is_selected)
+ continue;
+
+ x = (gint) (icon->x - x_offset);
+ y = (gint) (icon->y - y_offset);
+
+ x += (GNOME_ICON_CONTAINER_ICON_XOFFSET (container)
+ - GNOME_ICON_CONTAINER_ICON_WIDTH (container) / 2);
+ y += (GNOME_ICON_CONTAINER_ICON_YOFFSET (container)
+ - GNOME_ICON_CONTAINER_ICON_HEIGHT (container) / 2);
+
+ if (priv->base_uri != NULL)
+ s = g_strdup_printf ("%s%s\r%d:%d\r\n",
+ priv->base_uri, icon->text,
+ x, y);
+ else
+ s = g_strdup_printf ("%s\r%d:%d\r\n",
+ icon->text, x, y);
+
+ g_string_append (data, s);
+ g_free (s);
+ }
+
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, (guchar *) data->str, data->len);
+
+ g_string_free (data, TRUE);
+}
+
+/* Encode a "text/uri-list" selection. */
+static void
+set_uri_list_selection (GnomeIconContainer *container,
+ GtkSelectionData *selection_data)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+ GString *data;
+
+ priv = container->priv;
+ if (priv->icons == NULL) {
+ /* FIXME? Actually this probably shouldn't happen. */
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, NULL, 0);
+ return;
+ }
+
+ data = g_string_new (NULL);
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (! icon->is_selected)
+ continue;
+
+ /* This is lame code, I know. */
+
+ if (priv->base_uri != NULL)
+ g_string_append (data, priv->base_uri);
+ g_string_append (data, icon->text);
+ g_string_append (data, "\r\n");
+ }
+
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, (guchar *) data->str, data->len);
+
+ g_string_free (data, TRUE);
+}
+
+static void
+drag_data_get_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer data)
+{
+ GnomeIconContainer *container;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (widget));
+ g_return_if_fail (context != NULL);
+
+ container = GNOME_ICON_CONTAINER (widget);
+
+ switch (info) {
+ case GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST:
+ set_gnome_icon_list_selection (container, selection_data);
+ break;
+ case GNOME_ICON_CONTAINER_DND_URI_LIST:
+ set_uri_list_selection (container, selection_data);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+
+/* Target-side handling of the drag. */
+
+static void
+get_gnome_icon_list_selection (GnomeIconContainer *container,
+ GtkSelectionData *data)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+ const guchar *p, *oldp;
+ gint size;
+
+ dnd_info = container->priv->dnd_info;
+
+ oldp = data->data;
+ size = data->length;
+
+ while (1) {
+ DndSelectionItem *item;
+ guint len;
+
+ /* The list is in the form:
+
+ name\rx:y:width:height\r\n
+
+ The geometry information after the first \r is optional. */
+
+ /* 1: Decode name. */
+
+ p = memchr (oldp, '\r', size);
+ if (p == NULL)
+ break;
+
+ item = dnd_selection_item_new ();
+
+ len = p - oldp;
+
+ item->uri = g_malloc (len + 1);
+ memcpy (item->uri, oldp, len);
+ item->uri[len] = 0;
+
+ p++;
+ if (*p == '\n' || *p == '\0') {
+ dnd_info->selection_list
+ = g_list_prepend (dnd_info->selection_list,
+ item);
+ if (p == 0) {
+ g_warning ("Invalid special/x-gnome-icon-list data received: "
+ "missing newline character.");
+ break;
+ } else {
+ oldp = p + 1;
+ continue;
+ }
+ }
+
+ size -= p - oldp;
+ oldp = p;
+
+ /* 2: Decode geometry information. */
+
+ if (sscanf (p, "%d:%d", &item->x, &item->y) != 2)
+ g_warning ("Invalid special/x-gnome-icon-list data received: "
+ "invalid geometry specification.");
+
+ dnd_info->selection_list
+ = g_list_prepend (dnd_info->selection_list, item);
+
+ p = memchr (p, '\r', size);
+ if (p == NULL || p[1] != '\n') {
+ g_warning ("Invalid special/x-gnome-icon-list data received: "
+ "missing newline character.");
+ if (p == NULL)
+ break;
+ } else {
+ p += 2;
+ }
+
+ size -= p - oldp;
+ oldp = p;
+ }
+}
+
+static void
+drag_data_received_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *data,
+ guint info,
+ guint32 time,
+ gpointer user_data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerDndInfo *dnd_info;
+ GnomeCanvasItem *shadow;
+ double world_x, world_y;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ dnd_info = container->priv->dnd_info;
+ g_return_if_fail (dnd_info->selection_list == NULL);
+
+ switch (info) {
+ case GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST:
+ get_gnome_icon_list_selection (container, data);
+ break;
+ case GNOME_ICON_CONTAINER_DND_URI_LIST:
+ puts ("Bad! URI list!"); /* FIXME */
+ return;
+ }
+
+ shadow = create_selection_shadow (container, dnd_info->selection_list);
+
+ gnome_canvas_item_set (shadow, "x", (gdouble) 0, "y", (gdouble) 0,
+ NULL);
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (widget),
+ x, y, &world_x, &world_y);
+ set_shadow_position (shadow, world_x, world_y);
+
+ gnome_canvas_item_show (shadow);
+
+ if (dnd_info->shadow != NULL)
+ gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
+ dnd_info->shadow = shadow;
+}
+
+static gboolean
+drag_motion_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ dnd_info = GNOME_ICON_CONTAINER (widget)->priv->dnd_info;
+ if (dnd_info->selection_list == NULL)
+ gtk_drag_get_data (widget, context,
+ GPOINTER_TO_INT (context->targets->data),
+ time);
+
+ if (dnd_info->shadow != NULL) {
+ double world_x, world_y;
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (widget),
+ x, y, &world_x, &world_y);
+ gnome_canvas_item_show (dnd_info->shadow);
+ set_shadow_position (dnd_info->shadow, world_x, world_y);
+ }
+
+ gdk_drag_status (context, context->suggested_action, time);
+ return TRUE;
+}
+
+static void
+drag_end_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerDndInfo *dnd_info;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ dnd_info = container->priv->dnd_info;
+
+ destroy_selection_list (dnd_info->selection_list);
+ dnd_info->selection_list = NULL;
+}
+
+static gboolean
+drag_drop_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time,
+ gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerDndInfo *dnd_info;
+ GtkWidget *source_widget;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ dnd_info = container->priv->dnd_info;
+ source_widget = gtk_drag_get_source_widget (context);
+
+ if (source_widget == widget && context->action == GDK_ACTION_MOVE) {
+ double world_x, world_y;
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container),
+ x, y, &world_x, &world_y);
+
+ gnome_icon_container_xlate_selected (container,
+ world_x - dnd_info->start_x,
+ world_y - dnd_info->start_y,
+ TRUE);
+ }
+
+ return FALSE;
+}
+
+static void
+drag_leave_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ guint time,
+ gpointer data)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ dnd_info = GNOME_ICON_CONTAINER (widget)->priv->dnd_info;
+
+ if (dnd_info->shadow != NULL) {
+ gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
+ dnd_info->shadow = NULL;
+ }
+
+ if (dnd_info->selection_list != NULL) {
+ destroy_selection_list (dnd_info->selection_list);
+ dnd_info->selection_list = NULL;
+ }
+}
+
+
+void
+gnome_icon_container_dnd_init (GnomeIconContainer *container,
+ GdkBitmap *stipple)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ dnd_info = g_new (GnomeIconContainerDndInfo, 1);
+
+ dnd_info->target_list = gtk_target_list_new (drag_types,
+ num_drag_types);
+
+ dnd_info->start_x = 0;
+ dnd_info->start_y = 0;
+ dnd_info->selection_list = NULL;
+
+ dnd_info->stipple = gdk_bitmap_ref (stipple);
+
+ dnd_info->shadow = NULL;
+
+ /* Set up the widget as a drag destination. */
+ /* (But not a source, as drags starting from this widget will be
+ implemented by dealing with events manually.) */
+
+ gtk_drag_dest_set (GTK_WIDGET (container),
+ 0,
+ drop_types, num_drop_types,
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ gtk_signal_connect (GTK_OBJECT (container), "drag_data_get",
+ GTK_SIGNAL_FUNC (drag_data_get_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_motion",
+ GTK_SIGNAL_FUNC (drag_motion_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_end",
+ GTK_SIGNAL_FUNC (drag_end_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_data_received",
+ GTK_SIGNAL_FUNC (drag_data_received_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_drop",
+ GTK_SIGNAL_FUNC (drag_drop_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_leave",
+ GTK_SIGNAL_FUNC (drag_leave_cb), NULL);
+
+ container->priv->dnd_info = dnd_info;
+}
+
+void
+gnome_icon_container_dnd_fini (GnomeIconContainer *container)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ dnd_info = container->priv->dnd_info;
+ g_return_if_fail (dnd_info != NULL);
+
+ gtk_target_list_unref (dnd_info->target_list);
+ destroy_selection_list (dnd_info->selection_list);
+
+ if (dnd_info->shadow != NULL)
+ gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
+
+ gdk_bitmap_unref (dnd_info->stipple);
+
+ g_free (dnd_info);
+}
+
+
+void
+gnome_icon_container_dnd_begin_drag (GnomeIconContainer *container,
+ GdkDragAction actions,
+ gint button,
+ GdkEventMotion *event)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+ g_return_if_fail (event != NULL);
+
+ dnd_info = container->priv->dnd_info;
+ g_return_if_fail (dnd_info != NULL);
+
+ /* Notice that the event is already in world coordinates, because of
+ the way the canvas handles events! */
+ dnd_info->start_x = event->x;
+ dnd_info->start_y = event->y;
+
+ gtk_drag_begin (GTK_WIDGET (container),
+ dnd_info->target_list,
+ actions,
+ button,
+ (GdkEvent *) event);
+}
+
+void
+gnome_icon_container_dnd_end_drag (GnomeIconContainer *container)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ dnd_info = container->priv->dnd_info;
+ g_return_if_fail (dnd_info != NULL);
+}
diff --git a/libnautilus-extensions/gnome-icon-container-dnd.h b/libnautilus-extensions/gnome-icon-container-dnd.h
new file mode 100644
index 000000000..da5ab68f0
--- /dev/null
+++ b/libnautilus-extensions/gnome-icon-container-dnd.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container-dnd.h - Drag & drop handling for the icon container
+ widget.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _GNOME_ICON_CONTAINER_DND_H
+#define _GNOME_ICON_CONTAINER_DND_H
+
+typedef struct _GnomeIconContainerDndInfo GnomeIconContainerDndInfo;
+typedef enum _GnomeIconContainerDndTargetType GnomeIconContainerDndTargetType;
+
+#include "gnome-icon-container.h"
+
+/* Standard DnD types. */
+enum _GnomeIconContainerDndTargetType {
+ GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST,
+ GNOME_ICON_CONTAINER_DND_URI_LIST,
+ GNOME_ICON_CONTAINER_DND_URL,
+ GNOME_ICON_CONTAINER_DND_NTARGETS
+};
+
+/* DnD target names. */
+#define GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST_TYPE "special/x-gnome-icon-list"
+#define GNOME_ICON_CONTAINER_DND_URI_LIST_TYPE "text/uri-list"
+#define GNOME_ICON_CONTAINER_DND_URL_TYPE "_NETSCAPE_URL"
+
+
+/* DnD-related information. */
+struct _GnomeIconContainerDndInfo {
+ GtkTargetList *target_list;
+
+ /* Start of the drag, in world coordinates. */
+ gdouble start_x, start_y;
+
+ /* List of DndSelectionItems, representing items being dragged, or NULL
+ if data about them has not been received from the source yet. */
+ GList *selection_list;
+
+ /* Stipple for drawing icon shadows during DnD. */
+ GdkBitmap *stipple;
+
+ /* Shadow for the icons being dragged. */
+ GnomeCanvasItem *shadow;
+};
+
+
+void gnome_icon_container_dnd_init (GnomeIconContainer *container,
+ GdkBitmap *stipple);
+void gnome_icon_container_dnd_fini (GnomeIconContainer *container);
+void gnome_icon_container_dnd_begin_drag (GnomeIconContainer *container,
+ GdkDragAction actions,
+ gint button,
+ GdkEventMotion *event);
+void gnome_icon_container_dnd_end_drag (GnomeIconContainer *container);
+
+#endif /* _GNOME_ICON_CONTAINER_DND_H */
diff --git a/libnautilus-extensions/gnome-icon-container-layout.c b/libnautilus-extensions/gnome-icon-container-layout.c
new file mode 100644
index 000000000..3b7cfeccb
--- /dev/null
+++ b/libnautilus-extensions/gnome-icon-container-layout.c
@@ -0,0 +1,161 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-layout.c
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#include <glib.h>
+
+#include "gnome-icon-container.h"
+#include "gnome-icon-container-private.h"
+
+#include "gnome-icon-container-layout.h"
+
+
+struct _IconLayoutInfo {
+ gchar *text;
+ gint x, y;
+};
+typedef struct _IconLayoutInfo IconLayoutInfo;
+
+
+struct _GnomeIconContainerLayout {
+ GHashTable *name_to_layout;
+};
+
+
+GnomeIconContainerLayout *
+gnome_icon_container_layout_new (void)
+{
+ GnomeIconContainerLayout *new;
+
+ new = g_new (GnomeIconContainerLayout, 1);
+ new->name_to_layout = g_hash_table_new (g_str_hash, g_str_equal);
+
+ return new;
+}
+
+static void
+hash_foreach_destroy (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ IconLayoutInfo *info;
+
+ info = (IconLayoutInfo *) value;
+ g_free (info->text);
+ g_free (info);
+}
+
+void
+gnome_icon_container_layout_free (GnomeIconContainerLayout *layout)
+{
+ g_return_if_fail (layout != NULL);
+
+ g_hash_table_foreach (layout->name_to_layout,
+ hash_foreach_destroy, NULL);
+ g_hash_table_destroy (layout->name_to_layout);
+
+ g_free (layout);
+}
+
+
+void
+gnome_icon_container_layout_add (GnomeIconContainerLayout *layout,
+ const gchar *icon_text,
+ gint x,
+ gint y)
+{
+ IconLayoutInfo *info;
+
+ g_return_if_fail (layout != NULL);
+ g_return_if_fail (icon_text != NULL);
+
+ info = g_new (IconLayoutInfo, 1);
+ info->text = g_strdup (icon_text);
+ info->x = x;
+ info->y = y;
+
+ g_hash_table_insert (layout->name_to_layout, info->text, info);
+}
+
+gboolean
+gnome_icon_container_layout_get_position (const GnomeIconContainerLayout *layout,
+ const gchar *icon_text,
+ gint *x_return,
+ gint *y_return)
+{
+ IconLayoutInfo *info;
+
+ g_return_val_if_fail (layout != NULL, FALSE);
+ g_return_val_if_fail (icon_text != NULL, FALSE);
+
+ info = g_hash_table_lookup (layout->name_to_layout, icon_text);
+ if (info == NULL)
+ return FALSE;
+
+ *x_return = info->x;
+ *y_return = info->y;
+
+ return TRUE;
+}
+
+
+struct _ForeachData {
+ GnomeIconContainerLayout *layout;
+ GnomeIconContainerLayoutForeachFunc callback;
+ gpointer callback_data;
+};
+typedef struct _ForeachData ForeachData;
+
+static void
+foreach_helper (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ IconLayoutInfo *info;
+ ForeachData *data;
+
+ info = (IconLayoutInfo *) value;
+ data = (ForeachData *) user_data;
+
+ (* data->callback) (data->layout, info->text, info->x, info->y,
+ data->callback_data);
+}
+
+void
+gnome_icon_container_layout_foreach (GnomeIconContainerLayout *layout,
+ GnomeIconContainerLayoutForeachFunc callback,
+ gpointer callback_data)
+{
+ ForeachData *data;
+
+ g_return_if_fail (layout != NULL);
+ g_return_if_fail (callback != NULL);
+
+ data = g_new (ForeachData, 1);
+ data->layout = layout;
+ data->callback = callback;
+ data->callback_data = callback_data;
+
+ g_hash_table_foreach (layout->name_to_layout, foreach_helper, data);
+
+ g_free (data);
+}
diff --git a/libnautilus-extensions/gnome-icon-container-layout.h b/libnautilus-extensions/gnome-icon-container-layout.h
new file mode 100644
index 000000000..6d1ebb50c
--- /dev/null
+++ b/libnautilus-extensions/gnome-icon-container-layout.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-layout.h
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _GNOME_ICON_CONTAINER_LAYOUT_H
+#define _GNOME_ICON_CONTAINER_LAYOUT_H
+
+#include <glib.h>
+
+typedef struct _GnomeIconContainerLayout GnomeIconContainerLayout;
+
+typedef void (* GnomeIconContainerLayoutForeachFunc)
+ (const GnomeIconContainerLayout *layout,
+ const gchar *text,
+ gint x, gint y,
+ gpointer callback_data);
+
+#include "gnome-icon-container.h"
+#include "gnome-icon-container-private.h"
+
+
+GnomeIconContainerLayout *
+ gnome_icon_container_layout_new (void);
+void gnome_icon_container_layout_free (GnomeIconContainerLayout *layout);
+void gnome_icon_container_layout_add (GnomeIconContainerLayout *layout,
+ const gchar *icon,
+ gint x,
+ gint y);
+gboolean
+ gnome_icon_container_layout_get_position (const GnomeIconContainerLayout *layout,
+ const gchar *icon,
+ gint *x_return,
+ gint *y_return);
+
+void gnome_icon_container_layout_foreach (GnomeIconContainerLayout *layout,
+ GnomeIconContainerLayoutForeachFunc callback,
+ gpointer callback_data);
+
+#endif /* _GNOME_ICON_CONTAINER_LAYOUT_H */
diff --git a/libnautilus-extensions/gnome-icon-container-private.h b/libnautilus-extensions/gnome-icon-container-private.h
new file mode 100644
index 000000000..e681f43c6
--- /dev/null
+++ b/libnautilus-extensions/gnome-icon-container-private.h
@@ -0,0 +1,218 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container-private.h
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _GNOME_ICON_CONTAINER_PRIVATE_H
+#define _GNOME_ICON_CONTAINER_PRIVATE_H
+
+#include "gnome-icon-container.h"
+#include "gnome-icon-container-dnd.h"
+
+/* An Icon. */
+
+struct _GnomeIconContainerIcon {
+ /* Group containing the text and the image. */
+ GnomeCanvasGroup *item; /* FIXME wrong name. */
+
+ /* The image for the icon. Using a generic item makes it
+ possible for us to use any fancy canvas element. */
+ GnomeCanvasItem *image_item;
+
+ /* The text for the icon. */
+ GnomeIconTextItem *text_item;
+
+ /* Text for the icon. */
+ gchar *text;
+
+ /* X/Y coordinates and size. We could use the GnomeCanvasItem
+ functions, but this is a lot faster. */
+ gdouble x, y;
+ guint width, height; /* FIXME we could actually do without this if
+ we assume the size is always given by
+ GnomeIconContainer.cell_width*/
+
+ /* Whether this item is selected (i.e. highlighted) for operation. */
+ gboolean is_selected : 1;
+
+ /* Whether this item is selected for keyboard navigation. */
+ gboolean is_current : 1;
+
+ /* Whether this item has been repositioned during layout already. */
+ gboolean layout_done : 1;
+
+ /* Whether this item was selected before rubberbanding. */
+ gboolean was_selected_before_rubberband : 1;
+
+ gpointer data;
+};
+typedef struct _GnomeIconContainerIcon GnomeIconContainerIcon;
+
+
+#define INITIAL_GRID_WIDTH 64
+#define INITIAL_GRID_HEIGHT 64
+struct _GnomeIconContainerIconGrid {
+ /* Size of the grid. */
+ guint width, height;
+
+ /* This is the width that we can actually use for finding an empty
+ position. */
+ guint visible_width;
+
+ /* Array of grid elements. */
+ GList **elems;
+
+ /* Size of the allocated array. */
+ guint alloc_width, alloc_height;
+
+ /* Position of the first free cell (used to speed up progressive
+ updates). If negative, there is no free cell. */
+ gint first_free_x, first_free_y;
+};
+typedef struct _GnomeIconContainerIconGrid GnomeIconContainerIconGrid;
+
+
+/* Private GnomeIconContainer members. */
+
+struct _GnomeIconContainerRubberbandInfo {
+ gboolean active : 1;
+
+ gdouble start_x, start_y;
+
+ GnomeCanvasItem *selection_rectangle;
+ guint timer_tag;
+
+ guint prev_x, prev_y;
+ guint prev_x1, prev_y1;
+ guint prev_x2, prev_y2;
+};
+typedef struct _GnomeIconContainerRubberbandInfo GnomeIconContainerRubberbandInfo;
+
+struct _GnomeIconContainerPrivate {
+ /* Base URI for Drag & Drop. */
+ gchar *base_uri;
+
+ /* Browser mode setting. */
+ gboolean browser_mode : 1;
+
+ /* Current icon mode (index into `icon_mode_info[]' -- see
+ `gnome-icon-container.c'). */
+ GnomeIconContainerIconMode icon_mode;
+
+ /* Size of the container. */
+ guint width, height;
+
+ /* List of icons. */
+ GList *icons;
+
+ /* Total number of icons. */
+ guint num_icons;
+
+ /* The grid. */
+ GnomeIconContainerIconGrid *grid;
+
+ /* FIXME: This is *ugly*, but more efficient (both memory- and
+ speed-wise) than using gtk_object_{set,get}_data() for all the
+ icon items. */
+ GHashTable *canvas_item_to_icon;
+
+ /* Rectangle that shows that a certain icon is selected. */
+ GnomeCanvasItem *kbd_navigation_rectangle;
+
+ /* Current icon for keyboard navigation. */
+ GnomeIconContainerIcon *kbd_current;
+
+ /* Rubberbanding status. */
+ GnomeIconContainerRubberbandInfo rubberband_info;
+
+ /* Timeout used to make a selected icon fully visible after a short
+ period of time. (The timeout is needed to make sure
+ double-clicking still works.) */
+ gint kbd_icon_visibility_timer_tag;
+
+ /* Position of the pointer during the last click. */
+ gint drag_x, drag_y;
+
+ /* Button currently pressed, possibly for dragging. */
+ guint drag_button;
+
+ /* Icon on which the click happened. */
+ GnomeIconContainerIcon *drag_icon;
+
+ /* Whether we are actually performing a dragging action. */
+ gboolean doing_drag;
+
+ /* Drag offset. */
+ gint drag_x_offset, drag_y_offset;
+
+ /* Idle ID. */
+ guint idle_id;
+
+ /* Timeout for selection in browser mode. */
+ gint browser_mode_selection_timer_tag;
+
+ /* Icon to be selected at timeout in browser mode. */
+ GnomeIconContainerIcon *browser_mode_selection_icon;
+
+ /* DnD info. */
+ GnomeIconContainerDndInfo *dnd_info;
+};
+
+
+/* Definition of the available icon container modes. */
+struct _GnomeIconContainerIconModeInfo {
+ guint icon_width;
+ guint icon_height;
+
+ guint cell_width;
+ guint cell_height;
+
+ guint cell_spacing;
+
+ guint icon_xoffset;
+ guint icon_yoffset;
+};
+typedef struct _GnomeIconContainerIconModeInfo GnomeIconContainerIconModeInfo;
+
+extern GnomeIconContainerIconModeInfo gnome_icon_container_icon_mode_info[];
+
+#define GNOME_ICON_CONTAINER_ICON_WIDTH(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].icon_width
+
+#define GNOME_ICON_CONTAINER_ICON_HEIGHT(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].icon_height
+
+#define GNOME_ICON_CONTAINER_CELL_WIDTH(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].cell_width
+
+#define GNOME_ICON_CONTAINER_CELL_HEIGHT(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].cell_height
+
+#define GNOME_ICON_CONTAINER_CELL_SPACING(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].cell_spacing
+
+#define GNOME_ICON_CONTAINER_ICON_XOFFSET(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].icon_xoffset
+
+#define GNOME_ICON_CONTAINER_ICON_YOFFSET(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].icon_yoffset
+
+#endif /* _GNOME_ICON_CONTAINER_PRIVATE_H */
diff --git a/libnautilus-extensions/gnome-icon-container.c b/libnautilus-extensions/gnome-icon-container.c
new file mode 100644
index 000000000..f158fcc9b
--- /dev/null
+++ b/libnautilus-extensions/gnome-icon-container.c
@@ -0,0 +1,3020 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container.c - Icon container widget.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gnome.h>
+
+#include "gnome-icon-container-private.h"
+#include "gnome-icon-container-dnd.h"
+
+
+static GnomeCanvasClass *parent_class;
+
+/* Interval for updating the rubberband selection, in milliseconds. */
+#define RUBBERBAND_TIMEOUT_INTERVAL 10
+
+/* Timeout for making the icon currently selected for keyboard operation
+ visible. FIXME: This *must* be higher than the double-click time in GDK,
+ but there is no way to access its value from outside. */
+#define KBD_ICON_VISIBILITY_TIMEOUT 300
+
+/* Timeout for selecting an icon in "browser mode" (i.e. by just placing the
+ pointer over the icon, without pressing any button). */
+#define BROWSER_MODE_SELECTION_TIMEOUT 800
+
+
+/* WARNING: Keep this in sync with the `GnomeIconContainerIconMode' enum in
+ `gnome-icon-container.h'. */
+GnomeIconContainerIconModeInfo gnome_icon_container_icon_mode_info[] = {
+ { 48, 48, 80, 80, 4, 44, 28 }, /* GNOME_ICON_CONTAINER_NORMAL_ICONS */
+ { 24, 24, 100, 40, 4, 16, 16 } /* GNOME_ICON_CONTAINER_SMALL_ICONS */
+};
+
+#define NUM_ICON_MODES (sizeof (gnome_icon_container_icon_mode_info) \
+ / sizeof (*gnome_icon_container_icon_mode_info))
+
+
+/* The GnomeIconContainer signals. */
+enum _GnomeIconContainerSignalNumber {
+ SELECTION_CHANGED,
+ BUTTON_PRESS,
+ ACTIVATE,
+ CONTEXT_CLICK,
+ LAST_SIGNAL
+};
+typedef enum _GnomeIconContainerSignalNumber GnomeIconContainerSignalNumber;
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/* Bitmap for stippled selection rectangles. */
+static GdkBitmap *stipple;
+static char stipple_bits[] = { 0x02, 0x01 };
+
+
+/* Functions dealing with GnomeIconContainerIcons. */
+
+static void
+icon_destroy (GnomeIconContainerIcon *icon)
+{
+ gtk_object_destroy (GTK_OBJECT (icon->item));
+}
+
+static void
+icon_configure (GnomeIconContainerIcon *icon,
+ GnomeIconContainer *container)
+{
+ switch (container->priv->icon_mode) {
+ case GNOME_ICON_CONTAINER_NORMAL_ICONS:
+ gnome_icon_text_item_configure
+ (icon->text_item,
+ GNOME_ICON_CONTAINER_CELL_SPACING (container),
+ GNOME_ICON_CONTAINER_ICON_HEIGHT (container),
+ (GNOME_ICON_CONTAINER_CELL_WIDTH (container)
+ - 2 * GNOME_ICON_CONTAINER_CELL_SPACING (container)),
+ NULL,
+ icon->text,
+ TRUE,
+ TRUE);
+ break;
+ case GNOME_ICON_CONTAINER_SMALL_ICONS:
+ gnome_icon_text_item_configure
+ (icon->text_item,
+ (GNOME_ICON_CONTAINER_ICON_WIDTH (container)
+ + GNOME_ICON_CONTAINER_CELL_SPACING (container)),
+ GNOME_ICON_CONTAINER_CELL_HEIGHT (container) / 2,
+ (GNOME_ICON_CONTAINER_CELL_WIDTH (container)
+ - 2 * GNOME_ICON_CONTAINER_CELL_SPACING (container)
+ - GNOME_ICON_CONTAINER_ICON_WIDTH (container)),
+ NULL,
+ icon->text,
+ TRUE,
+ TRUE);
+ break;
+ default:
+ g_warning ("Unknown icon mode %d.", container->priv->icon_mode);
+ }
+
+ gnome_canvas_item_set
+ (GNOME_CANVAS_ITEM (icon->image_item),
+ "width", (gdouble) GNOME_ICON_CONTAINER_ICON_WIDTH (container),
+ "height", (gdouble) GNOME_ICON_CONTAINER_ICON_HEIGHT (container),
+ NULL);
+}
+
+static GnomeIconContainerIcon *
+icon_new (GnomeIconContainer *container,
+ const gchar *text,
+ gpointer data)
+{
+ GnomeCanvas *canvas;
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *new;
+
+ canvas = GNOME_CANVAS (container);
+ priv = container->priv;
+
+ new = g_new (GnomeIconContainerIcon, 1);
+
+ new->is_selected = FALSE;
+ new->is_current = FALSE;
+ new->layout_done = TRUE;
+ new->was_selected_before_rubberband = FALSE;
+
+ new->data = data;
+ new->text = g_strdup (text); /* FIXME */
+
+ new->item = GNOME_CANVAS_GROUP (gnome_canvas_item_new
+ (GNOME_CANVAS_GROUP (canvas->root),
+ gnome_canvas_group_get_type (),
+ NULL));
+
+ new->image_item = NULL;
+
+ new->text_item
+ = GNOME_ICON_TEXT_ITEM (gnome_canvas_item_new
+ (new->item,
+ gnome_icon_text_item_get_type (),
+ NULL));
+
+ new->width = GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ new->height = GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ return new;
+}
+
+static GnomeIconContainerIcon *
+icon_new_imlib (GnomeIconContainer *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *new;
+
+ priv = container->priv;
+
+ new = icon_new (container, text, data);
+
+ new->image_item
+ = gnome_canvas_item_new (new->item,
+ gnome_canvas_image_get_type (),
+ "image", image,
+ "x", (gdouble) 0,
+ "y", (gdouble) 0,
+ NULL);
+
+ icon_configure (new, container);
+
+ gnome_canvas_item_set (GNOME_CANVAS_ITEM (new->item),
+ "x", (gdouble) 0,
+ "y", (gdouble) 0,
+ NULL);
+
+ return new;
+}
+
+static void
+icon_position (GnomeIconContainerIcon *icon,
+ GnomeIconContainer *container,
+ gdouble x, gdouble y)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ icon->x = x;
+ icon->y = y;
+
+ /* ??? Canvas bug ??? It should be enough to do this once in
+ `icon-configure()', but it does not work. */
+
+ switch (container->priv->icon_mode) {
+ case GNOME_ICON_CONTAINER_NORMAL_ICONS:
+ gnome_icon_text_item_setxy
+ (icon->text_item,
+ GNOME_ICON_CONTAINER_CELL_SPACING (container),
+ (GNOME_ICON_CONTAINER_ICON_HEIGHT (container)
+ + GNOME_ICON_CONTAINER_CELL_SPACING (container) + 2));
+ break;
+ case GNOME_ICON_CONTAINER_SMALL_ICONS:
+ gnome_icon_text_item_setxy
+ (icon->text_item,
+ (GNOME_ICON_CONTAINER_ICON_WIDTH (container)
+ + GNOME_ICON_CONTAINER_CELL_SPACING (container)),
+ GNOME_ICON_CONTAINER_CELL_SPACING (container));
+ break;
+ default:
+ g_warning ("Unknown icon mode %d.", container->priv->icon_mode);
+ }
+
+ gnome_canvas_item_set
+ (icon->image_item,
+ "x", (gdouble) GNOME_ICON_CONTAINER_ICON_XOFFSET (container),
+ "y", (gdouble) GNOME_ICON_CONTAINER_ICON_YOFFSET (container),
+ NULL);
+
+ gnome_canvas_item_set (GNOME_CANVAS_ITEM (icon->item),
+ "x", (gdouble) icon->x,
+ "y", (gdouble) icon->y,
+ NULL);
+}
+
+static void
+icon_raise (GnomeIconContainerIcon *icon)
+{
+ gnome_canvas_item_raise_to_top (GNOME_CANVAS_ITEM (icon->item));
+}
+
+static void
+icon_show (GnomeIconContainerIcon *icon)
+{
+ gnome_canvas_item_show (GNOME_CANVAS_ITEM (icon->item));
+}
+
+static void
+icon_hide (GnomeIconContainerIcon *icon)
+{
+ gnome_canvas_item_hide (GNOME_CANVAS_ITEM (icon->item));
+}
+
+static void
+icon_select (GnomeIconContainerIcon *icon,
+ gboolean sel)
+{
+ gboolean was_selected;
+
+ /* FIXME: We want the icon image to appear as selected too. Maybe
+ this can be done with a new custom CanvasImage-like item providing
+ this functionality? */
+
+ was_selected = icon->is_selected;
+ icon->is_selected = sel;
+
+ gnome_icon_text_item_select (icon->text_item, sel);
+}
+
+static gboolean
+icon_toggle_selection (GnomeIconContainerIcon *icon)
+{
+ if (icon->is_selected) {
+ icon_select (icon, FALSE);
+ return TRUE;
+ } else {
+ icon_select (icon, TRUE);
+ return FALSE;
+ }
+}
+
+static gboolean
+icon_is_in_region (GnomeIconContainerIcon *icon,
+ gint x1, gint y1,
+ gint x2, gint y2)
+{
+ gint icon_x2, icon_y2;
+
+ icon_x2 = icon->x + icon->width;
+ icon_y2 = icon->y + icon->height;
+
+ if (x1 == x2 && y1 == y2)
+ return FALSE;
+
+ if (x1 < icon_x2 && x2 >= icon->x && y1 < icon_y2 && y2 >= icon->y)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void
+icon_get_text_bounding_box (GnomeIconContainerIcon *icon,
+ guint *x1_return, guint *y1_return,
+ guint *x2_return, guint *y2_return)
+{
+ double x1, y1, x2, y2;
+
+ gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (icon->text_item),
+ &x1, &y1, &x2, &y2);
+
+ *x1_return = icon->x + x1;
+ *y1_return = icon->y + y1;
+ *x2_return = icon->x + x2;
+ *y2_return = icon->y + y2;
+}
+
+static void
+icon_get_bounding_box (GnomeIconContainerIcon *icon,
+ guint *x1_return, guint *y1_return,
+ guint *x2_return, guint *y2_return)
+{
+ double x1, y1, x2, y2;
+
+ gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (icon->item),
+ &x1, &y1, &x2, &y2);
+
+ *x1_return = x1;
+ *y1_return = y1;
+ *x2_return = x2;
+ *y2_return = y2;
+}
+
+
+/* Functions for dealing with IconGrids. */
+
+static GnomeIconContainerIconGrid *
+icon_grid_new (void)
+{
+ GnomeIconContainerIconGrid *new;
+
+ new = g_new (GnomeIconContainerIconGrid, 1);
+
+ new->width = new->height = 0;
+ new->visible_width = 0;
+ new->alloc_width = new->alloc_height = 0;
+
+ new->elems = NULL;
+
+ new->first_free_x = -1;
+ new->first_free_y = -1;
+
+ return new;
+}
+
+static void
+icon_grid_clear (GnomeIconContainerIconGrid *grid)
+{
+ GList **p;
+ guint i, j;
+
+ p = grid->elems;
+ for (j = 0; j < grid->height; j++) {
+ for (i = 0; i < grid->width; i++) {
+ if (p[i] != NULL) {
+ g_list_free (p[i]);
+ p[i] = NULL;
+ }
+ }
+
+ p += grid->alloc_width;
+ }
+
+ grid->first_free_x = 0;
+ grid->first_free_y = 0;
+}
+
+static void
+icon_grid_destroy (GnomeIconContainerIconGrid *grid)
+{
+ icon_grid_clear (grid);
+ g_free (grid->elems);
+ g_free (grid);
+}
+
+inline static GList **
+icon_grid_get_element_ptr (GnomeIconContainerIconGrid *grid,
+ guint x, guint y)
+{
+ return &grid->elems[y * grid->alloc_width + x];
+}
+
+inline static GList *
+icon_grid_get_element (GnomeIconContainerIconGrid *grid,
+ guint x, guint y)
+{
+ return *icon_grid_get_element_ptr (grid, x, y);
+}
+
+/* This is admittedly a bit lame.
+
+ Instead of re-allocating the grid from scratch and copying the values, we
+ should just link grid chunks horizontally and vertically in lists;
+ i.e. use a hybrid list/array representation. */
+static void
+icon_grid_resize_allocation (GnomeIconContainerIconGrid *grid,
+ guint new_alloc_width,
+ guint new_alloc_height)
+{
+ GList **new_elems;
+ guint i, j;
+ guint new_alloc_size;
+
+ if (new_alloc_width == 0 || new_alloc_height == 0) {
+ g_free (grid->elems);
+ grid->elems = NULL;
+ grid->width = grid->height = 0;
+ grid->alloc_width = new_alloc_width;
+ grid->alloc_height = new_alloc_height;
+ return;
+ }
+
+ new_alloc_size = new_alloc_width * new_alloc_height;
+ new_elems = g_new (GList *, new_alloc_size);
+
+ if (grid->elems == NULL || grid->width == 0 || grid->height == 0) {
+ memset (new_elems, 0, sizeof (*new_elems) * new_alloc_size);
+ } else {
+ GList **sp, **dp;
+ guint copy_width, copy_height;
+
+ /* Copy existing elements into the new array. */
+
+ sp = grid->elems;
+ dp = new_elems;
+ copy_width = MIN (grid->width, new_alloc_width);
+ copy_height = MIN (grid->height, new_alloc_height);
+
+ for (i = 0; i < copy_height; i++) {
+ for (j = 0; j < copy_width; j++)
+ dp[j] = sp[j];
+
+ for (j = copy_width; j < new_alloc_width; j++)
+ dp[j] = NULL;
+
+ for (j = copy_width; j < grid->width; j++)
+ g_list_free (sp[j]);
+
+ sp += grid->alloc_width;
+ dp += new_alloc_width;
+ }
+
+ /* If there are other lines left, zero them as well. */
+
+ if (i < new_alloc_height) {
+ guint elems_left;
+
+ elems_left = new_alloc_size - (dp - new_elems);
+ memset (dp, 0, sizeof (*new_elems) * elems_left);
+ }
+ }
+
+ g_free (grid->elems);
+ grid->elems = new_elems;
+
+ grid->alloc_width = new_alloc_width;
+ grid->alloc_height = new_alloc_height;
+}
+
+static GnomeIconContainerIconGrid *
+icon_grid_new_same_alloc (GnomeIconContainerIconGrid *grid)
+{
+ GnomeIconContainerIconGrid *new_grid;
+
+ new_grid = icon_grid_new ();
+ icon_grid_resize_allocation (new_grid,
+ grid->alloc_width, grid->alloc_height);
+
+ return new_grid;
+}
+
+static void
+icon_grid_update_first_free_forward (GnomeIconContainerIconGrid *grid)
+{
+ GList **p;
+ guint start_x, start_y;
+ guint x, y;
+
+ if (grid->first_free_x == -1) {
+ start_x = start_y = 0;
+ p = grid->elems;
+ } else {
+ start_x = grid->first_free_x;
+ start_y = grid->first_free_y;
+ p = icon_grid_get_element_ptr (grid, start_x, start_y);
+ }
+
+ x = start_x;
+ y = start_y;
+ while (y < grid->height) {
+ if (*p == NULL) {
+ grid->first_free_x = x;
+ grid->first_free_y = y;
+ return;
+ }
+
+ x++, p++;
+
+ if (x >= grid->visible_width) {
+ x = 0;
+ y++;
+ p += grid->alloc_width - grid->visible_width;
+ }
+ }
+
+ /* No free cell found. */
+
+ grid->first_free_x = -1;
+ grid->first_free_y = -1;
+}
+
+static void
+icon_grid_set_visible_width (GnomeIconContainerIconGrid *grid,
+ guint visible_width)
+{
+ if (visible_width > grid->visible_width
+ && grid->height > 0
+ && grid->first_free_x == -1) {
+ grid->first_free_x = visible_width;
+ grid->first_free_y = 0;
+ } else if (grid->first_free_x >= visible_width) {
+ if (grid->first_free_y == grid->height - 1) {
+ grid->first_free_x = -1;
+ grid->first_free_y = -1;
+ } else {
+ grid->first_free_x = 0;
+ grid->first_free_y++;
+ icon_grid_update_first_free_forward (grid);
+ }
+ }
+
+ grid->visible_width = visible_width;
+}
+
+static void
+icon_grid_resize (GnomeIconContainerIconGrid *grid,
+ guint width, guint height)
+{
+ guint new_alloc_width, new_alloc_height;
+
+ if (width > grid->alloc_width || height > grid->alloc_height) {
+ if (grid->alloc_width > 0)
+ new_alloc_width = grid->alloc_width;
+ else
+ new_alloc_width = INITIAL_GRID_WIDTH;
+ while (new_alloc_width < width)
+ new_alloc_width *= 2;
+
+ if (grid->alloc_height > 0)
+ new_alloc_height = grid->alloc_height;
+ else
+ new_alloc_height = INITIAL_GRID_HEIGHT;
+ while (new_alloc_height < height)
+ new_alloc_height *= 2;
+
+ icon_grid_resize_allocation (grid, new_alloc_width,
+ new_alloc_height);
+ }
+
+ grid->width = width;
+ grid->height = height;
+
+ if (grid->visible_width > grid->width)
+ icon_grid_set_visible_width (grid, grid->width);
+}
+
+static void
+icon_grid_maybe_resize (GnomeIconContainerIconGrid *grid,
+ guint x, guint y)
+{
+ guint new_width, new_height;
+
+ if (x < grid->width && y < grid->height)
+ return;
+
+ if (x >= grid->width)
+ new_width = x + 1;
+ else
+ new_width = grid->width;
+
+ if (y >= grid->height)
+ new_height = y + 1;
+ else
+ new_height = grid->height;
+
+ icon_grid_resize (grid, new_width, new_height);
+}
+
+static void
+icon_grid_add (GnomeIconContainerIconGrid *grid,
+ GnomeIconContainerIcon *icon,
+ guint x, guint y)
+{
+ GList **elem_ptr;
+
+ icon_grid_maybe_resize (grid, x, y);
+
+ elem_ptr = icon_grid_get_element_ptr (grid, x, y);
+ *elem_ptr = g_list_prepend (*elem_ptr, icon);
+
+ if (x == grid->first_free_x && y == grid->first_free_y)
+ icon_grid_update_first_free_forward (grid);
+}
+
+static void
+icon_grid_remove (GnomeIconContainerIconGrid *grid,
+ GnomeIconContainerIcon *icon,
+ guint x, guint y)
+{
+ GList **elem_ptr;
+
+ elem_ptr = icon_grid_get_element_ptr (grid, x, y);
+
+ g_return_if_fail (*elem_ptr != NULL);
+
+ *elem_ptr = g_list_remove (*elem_ptr, icon);
+
+ if (*elem_ptr == NULL) {
+ if ((grid->first_free_x == -1 && grid->first_free_y == -1)
+ || grid->first_free_y > y
+ || (grid->first_free_y == y && grid->first_free_x > x)) {
+ grid->first_free_x = x;
+ grid->first_free_y = y;
+ }
+ }
+}
+
+static void
+icon_grid_add_auto (GnomeIconContainerIconGrid *grid,
+ GnomeIconContainerIcon *icon,
+ guint *x_return, guint *y_return)
+{
+ GList **empty_elem_ptr;
+
+ if (grid->first_free_x < 0 || grid->first_free_y < 0
+ || grid->height == 0 || grid->width == 0) {
+ /* No empty element: add a row. */
+ icon_grid_resize (grid, MAX (grid->width, 1), grid->height + 1);
+ grid->first_free_x = 0;
+ grid->first_free_y = grid->height - 1;
+ }
+
+ empty_elem_ptr = icon_grid_get_element_ptr (grid,
+ grid->first_free_x,
+ grid->first_free_y);
+
+ *empty_elem_ptr = g_list_prepend (*empty_elem_ptr, icon);
+
+ if (x_return != NULL)
+ *x_return = grid->first_free_x;
+ if (y_return != NULL)
+ *y_return = grid->first_free_y;
+
+ icon_grid_update_first_free_forward (grid);
+}
+
+static gint
+icon_grid_cell_compare_by_x (gconstpointer ap,
+ gconstpointer bp)
+{
+ GnomeIconContainerIcon *a, *b;
+
+ a = (GnomeIconContainerIcon *) ap;
+ b = (GnomeIconContainerIcon *) bp;
+
+ return (gint) a->x - b->x;
+}
+
+
+static void
+world_to_grid (GnomeIconContainer *container,
+ gint world_x, gint world_y,
+ guint *grid_x_return, guint *grid_y_return)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ if (grid_x_return != NULL) {
+ if (world_x < 0)
+ *grid_x_return = 0;
+ else
+ *grid_x_return = world_x / GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ if (grid_y_return != NULL) {
+ if (world_y < 0)
+ *grid_y_return = 0;
+ else
+ *grid_y_return = world_y / GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+}
+
+static void
+grid_to_world (GnomeIconContainer *container,
+ guint grid_x, guint grid_y,
+ gint *world_x_return, gint *world_y_return)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ if (world_x_return != NULL)
+ *world_x_return
+ = grid_x * GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+
+ if (world_y_return != NULL)
+ *world_y_return
+ = grid_y * GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+}
+
+
+/* Utility functions for GnomeIconContainer. */
+
+static void
+scroll (GnomeIconContainer *container,
+ gint delta_x, gint delta_y)
+{
+ GnomeIconContainerPrivate *priv;
+ GtkAdjustment *hadj, *vadj;
+ GtkAllocation *allocation;
+ gfloat vnew, hnew;
+ gfloat hmax, vmax;
+
+ priv = container->priv;
+
+ hadj = GTK_LAYOUT (container)->hadjustment;
+ vadj = GTK_LAYOUT (container)->vadjustment;
+
+ allocation = &GTK_WIDGET (container)->allocation;
+
+ if (container->priv->width > allocation->width)
+ hmax = (gfloat) (container->priv->width - allocation->width);
+ else
+ hmax = 0.0;
+
+ if (container->priv->height > allocation->height)
+ vmax = (gfloat) (container->priv->height - allocation->height);
+ else
+ vmax = 0.0;
+
+ hnew = CLAMP (hadj->value + (gfloat) delta_x, 0.0, hmax);
+ vnew = CLAMP (vadj->value + (gfloat) delta_y, 0.0, vmax);
+
+ if (hnew != hadj->value) {
+ hadj->value = hnew;
+ gtk_signal_emit_by_name (GTK_OBJECT (hadj), "value_changed");
+ }
+ if (vnew != vadj->value) {
+ vadj->value = vnew;
+ gtk_signal_emit_by_name (GTK_OBJECT (vadj), "value_changed");
+ }
+}
+
+static void
+make_icon_visible (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon)
+{
+ GnomeIconContainerPrivate *priv;
+ GtkAllocation *allocation;
+ GtkAdjustment *hadj, *vadj;
+ gint x1, y1, x2, y2;
+
+ priv = container->priv;
+ allocation = &GTK_WIDGET (container)->allocation;
+
+ if (priv->height < allocation->height
+ && priv->width < allocation->width)
+ return;
+
+ hadj = GTK_LAYOUT (container)->hadjustment;
+ vadj = GTK_LAYOUT (container)->vadjustment;
+
+ icon_get_bounding_box (icon, &x1, &y1, &x2, &y2);
+
+ if (y1 < vadj->value)
+ gtk_adjustment_set_value (vadj, y1);
+ else if (y2 > vadj->value + allocation->height)
+ gtk_adjustment_set_value (vadj, y2 - allocation->height);
+
+ if (x1 < hadj->value)
+ gtk_adjustment_set_value (hadj, x1);
+ else if (x2 > hadj->value + allocation->width)
+ gtk_adjustment_set_value (hadj, x2 - allocation->width);
+}
+
+static gint
+kbd_icon_visibility_timeout_cb (gpointer data)
+{
+ GnomeIconContainer *container;
+
+ GDK_THREADS_ENTER ();
+
+ container = GNOME_ICON_CONTAINER (data);
+
+ if (container->priv->kbd_current != NULL)
+ make_icon_visible (container, container->priv->kbd_current);
+ container->priv->kbd_icon_visibility_timer_tag = -1;
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static void
+unschedule_kbd_icon_visibility (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ if (priv->kbd_icon_visibility_timer_tag != -1)
+ gtk_timeout_remove (priv->kbd_icon_visibility_timer_tag);
+}
+
+static void
+schedule_kbd_icon_visibility (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ unschedule_kbd_icon_visibility (container);
+
+ priv->kbd_icon_visibility_timer_tag
+ = gtk_timeout_add (KBD_ICON_VISIBILITY_TIMEOUT,
+ kbd_icon_visibility_timeout_cb,
+ container);
+}
+
+static void
+prepare_for_layout (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+
+ priv = container->priv;
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ icon->layout_done = FALSE;
+ }
+}
+
+/* Line up icons belonging to the grid line pointed by `p'. */
+static void
+line_up (GnomeIconContainer *container,
+ GList **p)
+{
+ GnomeIconContainerIconGrid *grid;
+ GList **temp_line;
+ guint i;
+
+ grid = container->priv->grid;
+
+ temp_line = alloca (grid->width * sizeof (*temp_line));
+ for (i = 0; i < grid->width; i++)
+ temp_line[i] = p[i];
+}
+
+/* Find the "first" icon (in left-to-right, top-to-bottom order) in
+ `container'. */
+static GnomeIconContainerIcon *
+find_first (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *first;
+ GList **p;
+ guint i, j;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (grid->width == 0 || grid->height == 0)
+ return NULL;
+
+ first = NULL;
+ p = grid->elems;
+ for (i = 0; i < grid->height; i++) {
+ for (j = 0; j < grid->width; j++) {
+ GList *q;
+
+ for (q = p[j]; q != NULL; q = q->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = q->data;
+ if (first == NULL
+ || icon->y < first->y
+ || (icon->y == first->y
+ && icon->x < first->x))
+ first = icon;
+ }
+ }
+
+ p += grid->alloc_width;
+ }
+
+ return first;
+}
+
+static GnomeIconContainerIcon *
+find_last (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *last;
+ GList **p;
+ gint i, j;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ last = NULL;
+
+ if (grid->height == 0 || grid->width == 0)
+ return NULL;
+
+ p = icon_grid_get_element_ptr (grid, 0, grid->height - 1);
+
+ for (i = grid->height - 1; i >= 0; i--) {
+ for (j = grid->width - 1; j >= 0; j--) {
+ GList *q;
+
+ for (q = p[j]; q != NULL; q = q->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = q->data;
+ if (last == NULL
+ || icon->y > last->y
+ || (icon->y == last->y
+ && icon->x > last->x))
+ last = icon;
+ }
+ }
+
+ p -= grid->alloc_width;
+ }
+
+ return last;
+}
+
+/* Set `icon' as the icon currently selected for keyboard operations. */
+static void
+set_kbd_current (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ gboolean schedule_visibility)
+{
+ GnomeIconContainerPrivate *priv;
+ gint x1, y1, x2, y2;
+
+ priv = container->priv;
+
+ priv->kbd_current = icon;
+
+ icon_get_text_bounding_box (icon, &x1, &y1, &x2, &y2);
+
+ gnome_canvas_item_set (priv->kbd_navigation_rectangle,
+ "x1", (gdouble) x1 - 1,
+ "y1", (gdouble) y1 - 1,
+ "x2", (gdouble) x2,
+ "y2", (gdouble) y2,
+ NULL);
+ gnome_canvas_item_show (priv->kbd_navigation_rectangle);
+
+ icon_raise (icon);
+ gnome_canvas_item_raise_to_top (priv->kbd_navigation_rectangle);
+
+ if (schedule_visibility)
+ schedule_kbd_icon_visibility (container);
+ else
+ unschedule_kbd_icon_visibility (container);
+}
+
+static void
+unset_kbd_current (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ priv->kbd_current = NULL;
+ gnome_canvas_item_hide (priv->kbd_navigation_rectangle);
+
+ unschedule_kbd_icon_visibility (container);
+}
+
+
+/* Idle operation handler. */
+
+static void
+set_scroll_region (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GtkAllocation *allocation;
+ GtkAdjustment *vadj, *hadj;
+ gdouble x1, y1, x2, y2;
+ guint scroll_width, scroll_height;
+
+ priv = container->priv;
+ grid = priv->grid;
+ allocation = &(GTK_WIDGET (container)->allocation);
+ hadj = GTK_LAYOUT (container)->hadjustment;
+ vadj = GTK_LAYOUT (container)->vadjustment;
+
+ /* FIXME: We can do this more efficiently. */
+ gnome_canvas_item_get_bounds (GNOME_CANVAS (container)->root,
+ &x1, &y1, &x2, &y2);
+
+ priv->width = x2 + GNOME_ICON_CONTAINER_CELL_SPACING (container);
+ priv->height = y2 + GNOME_ICON_CONTAINER_CELL_SPACING (container);
+
+ scroll_width = MAX (priv->width, allocation->width);
+ scroll_height = MAX (priv->height, allocation->height);
+
+ gnome_canvas_set_scroll_region (GNOME_CANVAS (container),
+ 0.0, 0.0,
+ (gdouble) scroll_width,
+ (gdouble) scroll_height);
+
+ if (priv->width <= allocation->width)
+ gtk_adjustment_set_value (hadj, 0.0);
+ if (priv->height <= allocation->height)
+ gtk_adjustment_set_value (vadj, 0.0);
+}
+
+static gint
+idle_handler (gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+
+ GDK_THREADS_ENTER ();
+
+ container = GNOME_ICON_CONTAINER (data);
+ priv = container->priv;
+
+ set_scroll_region (container);
+
+ if (priv->icons != NULL && priv->kbd_current == NULL)
+ set_kbd_current (container, find_first (container), FALSE);
+
+ container->priv->idle_id = 0;
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static void
+add_idle (GnomeIconContainer *container)
+{
+ if (container->priv->idle_id != 0)
+ return;
+
+ container->priv->idle_id = gtk_idle_add (idle_handler, container);
+}
+
+static void
+remove_idle (GnomeIconContainer *container)
+{
+ if (container->priv->idle_id == 0)
+ return;
+
+ gtk_idle_remove (container->priv->idle_id);
+ container->priv->idle_id = 0;
+}
+
+
+/* Container-level icon handling functions. */
+
+/* Select an icon. Return TRUE if selection has changed. */
+static gboolean
+select_icon (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ gboolean sel)
+{
+ GnomeIconContainerPrivate *priv;
+ gboolean was_selected;
+
+ priv = container->priv;
+
+ was_selected = icon->is_selected;
+ icon_select (icon, sel);
+
+ if ((! was_selected && sel) || (was_selected && ! sel))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void
+toggle_icon (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon)
+{
+ icon_toggle_selection (icon);
+}
+
+static gboolean
+unselect_all_but_one (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon_to_avoid)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+ gboolean selection_changed;
+
+ priv = container->priv;
+ selection_changed = FALSE;
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon != icon_to_avoid && icon->is_selected) {
+ icon_select (icon, FALSE);
+ selection_changed = TRUE;
+ }
+ }
+
+ return selection_changed;
+}
+
+static gboolean
+unselect_all (GnomeIconContainer *container)
+{
+ return unselect_all_but_one (container, NULL);
+}
+
+/* FIXME: This could be optimized a bit. */
+static void
+move_icon (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ gint x, gint y)
+{
+ GnomeIconContainerPrivate *priv;
+ gint old_x, old_y;
+ guint old_grid_x, old_grid_y;
+ gint old_x_offset, old_y_offset;
+ guint new_grid_x, new_grid_y;
+ gint new_x_offset, new_y_offset;
+
+ priv = container->priv;
+
+ old_x = icon->x;
+ old_y = icon->y;
+
+ world_to_grid (container, old_x, old_y, &old_grid_x, &old_grid_y);
+ old_x_offset = old_x % GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ old_y_offset = old_y % GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ world_to_grid (container, x, y, &new_grid_x, &new_grid_y);
+ new_x_offset = x % GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ new_y_offset = y % GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ icon_grid_remove (priv->grid, icon, old_grid_x, old_grid_y);
+ if (old_x_offset > 0)
+ icon_grid_remove (priv->grid, icon,
+ old_grid_x + 1, old_grid_y);
+ if (old_y_offset > 0)
+ icon_grid_remove (priv->grid, icon,
+ old_grid_x, old_grid_y + 1);
+ if (old_x_offset > 0 && old_y_offset > 0)
+ icon_grid_remove (priv->grid, icon,
+ old_grid_x + 1, old_grid_y + 1);
+
+ icon_grid_add (priv->grid, icon, new_grid_x, new_grid_y);
+ if (new_x_offset > 0)
+ icon_grid_add (priv->grid, icon, new_grid_x + 1, new_grid_y);
+ if (new_y_offset > 0)
+ icon_grid_add (priv->grid, icon, new_grid_x, new_grid_y + 1);
+ if (new_x_offset > 0 && new_y_offset > 0)
+ icon_grid_add (priv->grid, icon, new_grid_x + 1, new_grid_y + 1);
+
+ icon_position (icon, container, x, y);
+}
+
+static void
+change_icon_mode (GnomeIconContainer *container,
+ GnomeIconContainerIconMode mode)
+{
+ GnomeIconContainerIconModeInfo *old_mode_info;
+ GnomeIconContainerIconModeInfo *new_mode_info;
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+ gdouble x_factor, y_factor;
+
+ priv = container->priv;
+ if (mode == priv->icon_mode)
+ return;
+
+ old_mode_info = gnome_icon_container_icon_mode_info + priv->icon_mode;
+ new_mode_info = gnome_icon_container_icon_mode_info + mode;
+
+ priv->icon_mode = mode;
+
+ x_factor = ((gdouble) new_mode_info->cell_width
+ / (gdouble) old_mode_info->cell_width);
+ y_factor = ((gdouble) new_mode_info->cell_height
+ / (gdouble) old_mode_info->cell_height);
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+
+ icon_configure (icon, container);
+ icon_position (icon, container,
+ icon->x * x_factor, icon->y * y_factor);
+ }
+
+ add_idle (container);
+
+ if (priv->kbd_current != NULL)
+ set_kbd_current (container, priv->kbd_current, TRUE);
+}
+
+
+/* Implementation of rubberband selection. */
+
+static gboolean
+rubberband_select_in_cell (GList *cell,
+ gdouble curr_x1, gdouble curr_y1,
+ gdouble curr_x2, gdouble curr_y2,
+ gdouble prev_x1, gdouble prev_y1,
+ gdouble prev_x2, gdouble prev_y2)
+{
+ GList *p;
+ gboolean selection_changed;
+
+ selection_changed = FALSE;
+
+ for (p = cell; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+ gboolean in_curr_region;
+ gboolean in_prev_region;
+
+ icon = p->data;
+
+ in_curr_region = icon_is_in_region (icon,
+ curr_x1, curr_y1,
+ curr_x2, curr_y2);
+
+ in_prev_region = icon_is_in_region (icon,
+ prev_x1, prev_y1,
+ prev_x2, prev_y2);
+
+ if (in_curr_region && ! in_prev_region) {
+ if (icon->was_selected_before_rubberband) {
+ if (icon->is_selected) {
+ icon_select (icon, FALSE);
+ selection_changed = TRUE;
+ }
+ } else {
+ if (! icon->is_selected) {
+ icon_select (icon, TRUE);
+ selection_changed = TRUE;
+ }
+ }
+ } else if (in_prev_region && ! in_curr_region) {
+ if (icon->was_selected_before_rubberband) {
+ if (! icon->is_selected) {
+ icon_select (icon, TRUE);
+ selection_changed = TRUE;
+ }
+ } else {
+ if (icon->is_selected) {
+ icon_select (icon, FALSE);
+ selection_changed = TRUE;
+ }
+ }
+ }
+ }
+
+ return selection_changed;
+}
+
+static void
+rubberband_select (GnomeIconContainer *container,
+ gdouble curr_x1, gdouble curr_y1,
+ gdouble curr_x2, gdouble curr_y2,
+ gdouble prev_x1, gdouble prev_y1,
+ gdouble prev_x2, gdouble prev_y2)
+{
+ GList **p;
+ GnomeIconContainerIconGrid *grid;
+ guint curr_grid_x1, curr_grid_y1;
+ guint curr_grid_x2, curr_grid_y2;
+ guint prev_grid_x1, prev_grid_y1;
+ guint prev_grid_x2, prev_grid_y2;
+ guint grid_x1, grid_y1;
+ guint grid_x2, grid_y2;
+ guint i, j;
+ gboolean selection_changed;
+
+ grid = container->priv->grid;
+
+ world_to_grid (container, curr_x1, curr_y1, &curr_grid_x1, &curr_grid_y1);
+ world_to_grid (container, curr_x2, curr_y2, &curr_grid_x2, &curr_grid_y2);
+ world_to_grid (container, prev_x1, prev_y1, &prev_grid_x1, &prev_grid_y1);
+ world_to_grid (container, prev_x2, prev_y2, &prev_grid_x2, &prev_grid_y2);
+
+ grid_x1 = MIN (curr_grid_x1, prev_grid_x1);
+ grid_x2 = MAX (curr_grid_x2, prev_grid_x2);
+ grid_y1 = MIN (curr_grid_y1, prev_grid_y1);
+ grid_y2 = MAX (curr_grid_y2, prev_grid_y2);
+
+ selection_changed = FALSE;
+
+ p = icon_grid_get_element_ptr (grid, grid_x1, grid_y1);
+ for (i = 0; i <= grid_y2 - grid_y1; i++) {
+ for (j = 0; j <= grid_x2 - grid_x1; j++) {
+ if (rubberband_select_in_cell (p[j],
+ curr_x1, curr_y1,
+ curr_x2, curr_y2,
+ prev_x1, prev_y1,
+ prev_x2, prev_y2))
+ selection_changed = TRUE;
+ }
+
+ p += grid->alloc_width;
+ }
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+}
+
+static gint
+rubberband_timeout_cb (gpointer data)
+{
+ GnomeIconContainer *container;
+ GtkWidget *widget;
+ GnomeIconContainerRubberbandInfo *rinfo;
+ gint x, y;
+ gdouble x1, y1, x2, y2;
+ gdouble world_x, world_y;
+ gint x_scroll, y_scroll;
+
+ GDK_THREADS_ENTER ();
+
+ widget = GTK_WIDGET (data);
+ container = GNOME_ICON_CONTAINER (data);
+ rinfo = &container->priv->rubberband_info;
+
+ gdk_window_get_pointer (widget->window, &x, &y, NULL);
+
+ if (x < 0) {
+ x_scroll = x;
+ x = 0;
+ } else if (x >= widget->allocation.width) {
+ x_scroll = x - widget->allocation.width + 1;
+ x = widget->allocation.width - 1;
+ } else {
+ x_scroll = 0;
+ }
+
+ if (y < 0) {
+ y_scroll = y;
+ y = 0;
+ } else if (y >= widget->allocation.height) {
+ y_scroll = y - widget->allocation.height + 1;
+ y = widget->allocation.height - 1;
+ } else {
+ y_scroll = 0;
+ }
+
+ if (y_scroll == 0 && x_scroll == 0
+ && rinfo->prev_x == x && rinfo->prev_y == y) {
+ GDK_THREADS_LEAVE ();
+ return TRUE;
+ }
+
+ scroll (container, x_scroll, y_scroll);
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container),
+ x, y, &world_x, &world_y);
+
+ if (world_x < rinfo->start_x) {
+ x1 = world_x;
+ x2 = rinfo->start_x;
+ } else {
+ x1 = rinfo->start_x;
+ x2 = world_x;
+ }
+
+ if (world_y < rinfo->start_y) {
+ y1 = world_y;
+ y2 = rinfo->start_y;
+ } else {
+ y1 = rinfo->start_y;
+ y2 = world_y;
+ }
+
+ gnome_canvas_item_set (rinfo->selection_rectangle,
+ "x1", (gdouble) x1,
+ "y1", (gdouble) y1,
+ "x2", (gdouble) x2,
+ "y2", (gdouble) y2,
+ NULL);
+
+ rubberband_select (container,
+ x1, y1, x2, y2,
+ rinfo->prev_x1, rinfo->prev_y1,
+ rinfo->prev_x2, rinfo->prev_y2);
+
+ rinfo->prev_x = x;
+ rinfo->prev_y = y;
+ rinfo->prev_x1 = x1;
+ rinfo->prev_y1 = y1;
+ rinfo->prev_x2 = x2;
+ rinfo->prev_y2 = y2;
+
+ GDK_THREADS_LEAVE ();
+
+ return TRUE;
+}
+
+static void
+start_rubberbanding (GnomeIconContainer *container,
+ GdkEventButton *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerRubberbandInfo *rinfo;
+ GList *p;
+
+ priv = container->priv;
+ rinfo = &priv->rubberband_info;
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ icon->was_selected_before_rubberband = icon->is_selected;
+ }
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container),
+ event->x, event->y,
+ &rinfo->start_x, &rinfo->start_y);
+
+ rinfo->selection_rectangle
+ = gnome_canvas_item_new (gnome_canvas_root
+ (GNOME_CANVAS (container)),
+ gnome_canvas_rect_get_type (),
+ "x1", rinfo->start_x,
+ "y1", rinfo->start_y,
+ "x2", rinfo->start_x,
+ "y2", rinfo->start_y,
+ "outline_color", "black",
+ "outline_stipple", stipple,
+ "width_pixels", 1,
+ NULL);
+
+ rinfo->prev_x = rinfo->prev_x1 = rinfo->prev_x2 = event->x;
+ rinfo->prev_y = rinfo->prev_y1 = rinfo->prev_y2 = event->y;
+
+ rinfo->active = TRUE;
+
+ rinfo->timer_tag = gtk_timeout_add (RUBBERBAND_TIMEOUT_INTERVAL,
+ rubberband_timeout_cb,
+ container);
+
+ gnome_canvas_item_grab (rinfo->selection_rectangle,
+ (GDK_POINTER_MOTION_MASK
+ | GDK_BUTTON_RELEASE_MASK),
+ NULL, event->time);
+}
+
+static void
+stop_rubberbanding (GnomeIconContainer *container,
+ GdkEventButton *event)
+{
+ GnomeIconContainerRubberbandInfo *rinfo;
+
+ rinfo = &container->priv->rubberband_info;
+
+ gtk_timeout_remove (rinfo->timer_tag);
+ rinfo->active = FALSE;
+
+ gnome_canvas_item_ungrab (rinfo->selection_rectangle, event->time);
+ gtk_object_destroy (GTK_OBJECT (rinfo->selection_rectangle));
+}
+
+
+/* Keyboard navigation. */
+
+static void
+kbd_move_to (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ GdkEventKey *event)
+{
+ if (! (event->state & GDK_CONTROL_MASK)) {
+ gboolean selection_changed;
+
+ selection_changed = unselect_all (container);
+ selection_changed |= select_icon (container, icon, TRUE);
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+
+ set_kbd_current (container, icon, FALSE);
+ make_icon_visible (container, icon);
+}
+
+static void
+kbd_home (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerIcon *first;
+
+ first = find_first (container);
+ if (first != NULL)
+ kbd_move_to (container, first, event);
+}
+
+static void
+kbd_end (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerIcon *last;
+
+ last = find_last (container);
+ if (last != NULL)
+ kbd_move_to (container, last, event);
+}
+
+static void
+kbd_left (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *nearmost;
+ GList **e;
+ guint grid_x, grid_y;
+ gint x, y;
+ gint max_x;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (priv->kbd_current == NULL)
+ return;
+
+ world_to_grid (container, priv->kbd_current->x, priv->kbd_current->y,
+ &grid_x, &grid_y);
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+
+ e = icon_grid_get_element_ptr (grid, 0, grid_y);
+ nearmost = NULL;
+
+ max_x = priv->kbd_current->x;
+
+ while (1) {
+ while (1) {
+ GList *p;
+
+ for (p = e[grid_x]; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon == priv->kbd_current
+ || icon->x < x
+ || icon->y < y)
+ continue;
+
+ if (icon->x <= max_x
+ && (nearmost == NULL
+ || icon->x > nearmost->x))
+ nearmost = icon;
+ }
+
+ if (nearmost != NULL) {
+ kbd_move_to (container, nearmost, event);
+ return;
+ }
+
+ if (grid_x == 0)
+ break;
+
+ grid_x--;
+ x -= GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ if (grid_y == 0)
+ break;
+
+ grid_x = grid->width - 1;
+ max_x = G_MAXINT;
+ grid_to_world (container, grid_x, 0, &x, NULL);
+
+ e -= grid->alloc_width;
+ grid_y--;
+ y -= GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+}
+
+static void
+kbd_up (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *nearmost;
+ GList **e;
+ guint grid_x, grid_y;
+ gint x, y;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (priv->kbd_current == NULL)
+ return;
+
+ world_to_grid (container, priv->kbd_current->x, priv->kbd_current->y,
+ &grid_x, &grid_y);
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+
+ e = icon_grid_get_element_ptr (grid, grid_x, grid_y);
+ nearmost = NULL;
+
+ while (1) {
+ GList *p;
+
+ p = *e;
+
+ for (; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon == priv->kbd_current
+ || icon->x < x
+ || icon->y < y)
+ continue;
+
+ if (icon->y <= priv->kbd_current->y
+ && (nearmost == NULL || icon->y > nearmost->y))
+ nearmost = icon;
+ }
+
+ if (nearmost != NULL)
+ break;
+
+ if (grid_y == 0)
+ break;
+
+ e -= grid->alloc_width;
+ grid_y--;
+ y -= GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+
+ if (nearmost != NULL)
+ kbd_move_to (container, nearmost, event);
+}
+
+static void
+kbd_right (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *nearmost;
+ GList **e;
+ guint grid_x, grid_y;
+ gint x, y;
+ gint min_x;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (priv->kbd_current == NULL)
+ return;
+
+ world_to_grid (container, priv->kbd_current->x, priv->kbd_current->y,
+ &grid_x, &grid_y);
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+
+ e = icon_grid_get_element_ptr (grid, 0, grid_y);
+ nearmost = NULL;
+
+ min_x = priv->kbd_current->x;
+
+ while (grid_y < grid->height) {
+ while (grid_x < grid->width) {
+ GList *p;
+
+ for (p = e[grid_x]; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon == priv->kbd_current
+ || icon->x < x
+ || icon->y < y)
+ continue;
+
+ if (icon->x >= min_x
+ && (nearmost == NULL
+ || icon->x < nearmost->x))
+ nearmost = icon;
+ }
+
+ if (nearmost != NULL) {
+ kbd_move_to (container, nearmost, event);
+ return;
+ }
+
+ grid_x++;
+ x += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ grid_x = 0;
+ min_x = 0;
+ x = 0;
+
+ e += grid->alloc_width;
+ grid_y++;
+ y += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+}
+
+static void
+kbd_down (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *nearmost;
+ GList **e;
+ guint grid_x, grid_y;
+ gint x, y;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (priv->kbd_current == NULL)
+ return;
+
+ world_to_grid (container, priv->kbd_current->x, priv->kbd_current->y,
+ &grid_x, &grid_y);
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+
+ e = icon_grid_get_element_ptr (grid, grid_x, grid_y);
+ nearmost = NULL;
+
+ while (grid_y < grid->height) {
+ GList *p;
+
+ p = *e;
+
+ for (; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon == priv->kbd_current
+ || icon->x < x
+ || icon->y < y)
+ continue;
+
+ if (icon->y >= priv->kbd_current->y
+ && (nearmost == NULL || icon->y < nearmost->y))
+ nearmost = icon;
+ }
+
+ if (nearmost != NULL)
+ break;
+
+ e += grid->alloc_width;
+ grid_y++;
+ y += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+
+ if (nearmost != NULL)
+ kbd_move_to (container, nearmost, event);
+}
+
+static void
+kbd_space (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ if (container->priv->kbd_current != NULL) {
+ if (select_icon (container, container->priv->kbd_current, TRUE))
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+}
+
+
+/* GtkObject methods. */
+
+static void
+destroy (GtkObject *object)
+{
+ GnomeIconContainer *container;
+
+ container = GNOME_ICON_CONTAINER (object);
+
+ icon_grid_destroy (container->priv->grid);
+
+ gnome_icon_container_dnd_fini (container);
+
+ g_free (container->priv);
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy != NULL)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+
+/* GtkWidget methods. */
+
+static void
+size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ requisition->width = 1;
+ requisition->height = 1;
+}
+
+static void
+size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerIconGrid *grid;
+ guint visible_width, visible_height;
+
+ if (GTK_WIDGET_CLASS (parent_class)->size_allocate)
+ (* GTK_WIDGET_CLASS (parent_class)->size_allocate)
+ (widget, allocation);
+
+ container = GNOME_ICON_CONTAINER (widget);
+ grid = container->priv->grid;
+
+ world_to_grid (container,
+ allocation->width, 0,
+ &visible_width, &visible_height);
+
+ if (visible_width == 0)
+ visible_width = 1;
+
+ if (visible_width > grid->width || visible_height > grid->height)
+ icon_grid_resize (grid,
+ MAX (visible_width, grid->width),
+ MAX (visible_height, grid->height));
+
+ icon_grid_set_visible_width (grid, visible_width);
+
+ set_scroll_region (container);
+}
+
+static void
+realize (GtkWidget *widget)
+{
+ GtkStyle *style;
+
+ if (GTK_WIDGET_CLASS (parent_class)->realize)
+ (* GTK_WIDGET_CLASS (parent_class)->realize) (widget);
+
+ 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 (widget)->bin_window,
+ & widget->style->bg [GTK_STATE_NORMAL]);
+}
+
+static gboolean
+button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ gboolean return_value;
+ GnomeIconContainer *container;
+
+ /* 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;
+
+ container = GNOME_ICON_CONTAINER (widget);
+
+ if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
+ if (! (event->state & GDK_CONTROL_MASK)) {
+ gboolean selection_changed;
+
+ selection_changed = unselect_all (container);
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+
+ start_rubberbanding (container, event);
+ return TRUE;
+ }
+
+ gtk_signal_emit (GTK_OBJECT (widget), signals[BUTTON_PRESS], event,
+ &return_value);
+
+ return return_value;
+}
+
+static gboolean
+button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ priv = container->priv;
+
+ if (event->button == 1 && priv->rubberband_info.active) {
+ stop_rubberbanding (container, event);
+ return TRUE;
+ }
+
+ if (event->button == priv->drag_button) {
+ priv->drag_button = 0;
+ if (! priv->doing_drag
+ && ! (event->state & GDK_CONTROL_MASK)) {
+ gboolean selection_changed;
+
+ selection_changed
+ = unselect_all_but_one (container,
+ priv->drag_icon);
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+
+ if (priv->drag_icon != NULL) {
+ set_kbd_current (container, priv->drag_icon, TRUE);
+ priv->drag_icon = NULL;
+ }
+
+ if (priv->doing_drag)
+ gnome_icon_container_dnd_end_drag (container);
+
+ priv->doing_drag = FALSE;
+ return TRUE;
+ }
+
+ if (GTK_WIDGET_CLASS (parent_class)->button_release_event != NULL)
+ return GTK_WIDGET_CLASS (parent_class)->button_release_event
+ (widget, event);
+
+ return FALSE;
+}
+
+static gint
+motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *motion)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+ double world_x, world_y;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ priv = container->priv;
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container),
+ motion->x, motion->y,
+ &world_x, &world_y);
+
+#define SNAP_RESISTANCE 2 /* GMC has this set to 3, but it's too much for
+ my taste. */
+ if (priv->drag_button != 0
+ && abs (priv->drag_x - world_x) >= SNAP_RESISTANCE
+ && abs (priv->drag_y - world_y) >= SNAP_RESISTANCE) {
+ priv->doing_drag = TRUE;
+
+ /* KLUDGE ALERT: Poke the starting values into the motion
+ structure so that dragging behaves as expected. */
+ motion->x = priv->drag_x;
+ motion->y = priv->drag_y;
+
+ gnome_icon_container_dnd_begin_drag (container,
+ GDK_ACTION_MOVE,
+ priv->drag_button,
+ motion);
+ return TRUE;
+ }
+#undef SNAP_RESISTANCE
+
+ if (GTK_WIDGET_CLASS (parent_class)->motion_notify_event != NULL)
+ return (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event)
+ (widget, motion);
+
+ return FALSE;
+}
+
+static gint
+key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ GnomeIconContainer *container;
+
+ if ((* GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event))
+ return TRUE;
+
+ container = GNOME_ICON_CONTAINER (widget);
+
+ switch (event->keyval) {
+ case GDK_Home:
+ kbd_home (container, event);
+ break;
+ case GDK_End:
+ kbd_end (container, event);
+ break;
+ case GDK_Left:
+ kbd_left (container, event);
+ break;
+ case GDK_Up:
+ kbd_up (container, event);
+ break;
+ case GDK_Right:
+ kbd_right (container, event);
+ break;
+ case GDK_Down:
+ kbd_down (container, event);
+ break;
+ case GDK_space:
+ kbd_space (container, event);
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/* Initialization. */
+
+static void
+class_init (GnomeIconContainerClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ /* Derive from GnomeCanvas. */
+
+ parent_class = gtk_type_class (gnome_canvas_get_type ());
+
+ /* GnomeIconContainer class. */
+
+ class->button_press = NULL;
+
+ /* GtkObject class. */
+
+ object_class = GTK_OBJECT_CLASS (class);
+ object_class->destroy = destroy;
+
+ /* Signals. */
+
+ signals[SELECTION_CHANGED]
+ = gtk_signal_new ("selection_changed",
+ GTK_RUN_LAST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GnomeIconContainerClass,
+ selection_changed),
+ gtk_marshal_NONE__NONE,
+ GTK_TYPE_NONE, 0);
+ signals[BUTTON_PRESS]
+ = gtk_signal_new ("button_press",
+ GTK_RUN_LAST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GnomeIconContainerClass,
+ button_press),
+ gtk_marshal_BOOL__POINTER,
+ GTK_TYPE_BOOL, 1,
+ GTK_TYPE_GDK_EVENT);
+ signals[ACTIVATE]
+ = gtk_signal_new ("activate",
+ GTK_RUN_LAST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GnomeIconContainerClass,
+ activate),
+ gtk_marshal_NONE__POINTER_POINTER,
+ GTK_TYPE_NONE, 2,
+ GTK_TYPE_STRING,
+ GTK_TYPE_POINTER);
+ signals[CONTEXT_CLICK]
+ = gtk_signal_new ("context_click",
+ GTK_RUN_LAST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GnomeIconContainerClass,
+ activate),
+ gtk_marshal_NONE__POINTER_POINTER,
+ GTK_TYPE_NONE, 2,
+ GTK_TYPE_STRING,
+ GTK_TYPE_POINTER);
+
+ gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL);
+
+ /* GtkWidget class. */
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->size_request = size_request;
+ widget_class->size_allocate = size_allocate;
+ widget_class->realize = realize;
+ widget_class->button_press_event = button_press_event;
+ widget_class->button_release_event = button_release_event;
+ widget_class->motion_notify_event = motion_notify_event;
+ widget_class->key_press_event = key_press_event;
+
+ /* Initialize the stipple bitmap. */
+
+ stipple = gdk_bitmap_create_from_data (NULL, stipple_bits, 2, 2);
+}
+
+static void
+init (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = g_new (GnomeIconContainerPrivate, 1);
+
+ priv->base_uri = NULL;
+
+ priv->width = priv->height = 0;
+
+ priv->icons = NULL;
+ priv->num_icons = 0;
+
+ priv->icon_mode = GNOME_ICON_CONTAINER_NORMAL_ICONS;
+
+ priv->grid = icon_grid_new ();
+
+ priv->canvas_item_to_icon = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ priv->kbd_navigation_rectangle
+ = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (container)),
+ gnome_canvas_rect_get_type (),
+ "outline_color", "black",
+ "outline_stipple", stipple,
+ "width_pixels", 1,
+ NULL);
+ gnome_canvas_item_hide (priv->kbd_navigation_rectangle);
+
+ priv->kbd_current = NULL;
+ priv->rubberband_info.active = FALSE;
+ priv->kbd_icon_visibility_timer_tag = -1;
+ priv->idle_id = 0;
+
+ priv->drag_button = 0;
+ priv->drag_icon = NULL;
+ priv->drag_x = priv->drag_y = 0;
+ priv->doing_drag = FALSE;
+
+ priv->browser_mode = FALSE;
+ priv->browser_mode_selection_timer_tag = -1;
+ priv->browser_mode_selection_icon = NULL;
+
+ container->priv = priv;
+
+ /* Set up DnD. */
+ gnome_icon_container_dnd_init (container, stipple);
+
+ /* Request update. */
+ add_idle (container);
+}
+
+
+/* GnomeIconContainerIcon event handling. */
+
+/* Selection in browser mode. */
+static gint
+browser_select_timeout_cb (gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *icon;
+ gboolean selection_changed;
+
+ GDK_THREADS_ENTER ();
+
+ container = GNOME_ICON_CONTAINER (data);
+ priv = container->priv;
+ icon = priv->browser_mode_selection_icon;
+
+ selection_changed = unselect_all (container);
+ selection_changed |= select_icon (container, icon, TRUE);
+
+ set_kbd_current (container, icon, FALSE);
+ make_icon_visible (container, icon);
+
+ /* FIXME: Am I allowed to do this between `GDK_THREADS_ENTER()' and
+ `GDK_THREADS_LEAVE()'? */
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+/* Conceptually, pressing button 1 together with CTRL toggles selection of a
+ single icon without affecting the other icons; without CTRL, it selects a
+ single icon and un-selects all the other icons. But in this latter case,
+ the de-selection should only happen when the button is released if the
+ icon is already selected, because the user might select multiple icons and
+ drag all of them by doing a simple click-drag. */
+static gint
+handle_icon_button_press (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ GdkEventButton *event)
+{
+ GnomeIconContainerPrivate *priv;
+ gdouble world_x, world_y;
+
+ if (event->button != 1)
+ return FALSE;
+
+ priv = container->priv;
+
+ if (event->state & GDK_CONTROL_MASK) {
+ toggle_icon (container, icon);
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ } else if (! icon->is_selected) {
+ unselect_all (container);
+ select_icon (container, icon, TRUE);
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+
+ if (event->type == GDK_2BUTTON_PRESS) {
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[ACTIVATE],
+ icon->text, icon->data);
+
+ /* Double clicking should *never* trigger a D&D action. */
+ priv->drag_button = 0;
+ priv->drag_icon = NULL;
+ return TRUE;
+ }
+
+ if (event->button == 3) {
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[CONTEXT_CLICK],
+ icon->text, icon->data);
+
+ /* FIXME this means you cannot drag with right click. Instead,
+ we should setup a timeout and emit this signal if the
+ timeout expires without movement. */
+ priv->drag_button = 0;
+ priv->drag_icon = NULL;
+ return TRUE;
+ }
+
+ priv->drag_button = event->button;
+ priv->drag_icon = icon;
+ priv->drag_x = event->x;
+ priv->drag_y = event->y;
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container), event->x, event->y,
+ &world_x, &world_y);
+ priv->drag_x_offset = (gint) world_x - icon->x;
+ priv->drag_y_offset = (gint) world_y - icon->y;
+
+ return TRUE;
+}
+
+static gint
+handle_icon_enter_notify (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ GdkEventMotion *motion)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+ if (! priv->browser_mode)
+ return FALSE;
+
+ if (priv->browser_mode_selection_timer_tag != -1)
+ gtk_timeout_remove (priv->browser_mode_selection_timer_tag);
+
+ priv->browser_mode_selection_timer_tag
+ = gtk_timeout_add (BROWSER_MODE_SELECTION_TIMEOUT,
+ browser_select_timeout_cb, container);
+
+ priv->browser_mode_selection_icon = icon;
+
+ return TRUE;
+}
+
+static gint
+handle_icon_leave_notify (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ GdkEventMotion *motion)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+ if (! priv->browser_mode)
+ return FALSE;
+
+ if (priv->browser_mode_selection_timer_tag != -1)
+ gtk_timeout_remove (priv->browser_mode_selection_timer_tag);
+
+ return TRUE;
+}
+
+static gint
+item_event_cb (GnomeCanvasItem *item,
+ GdkEvent *event,
+ gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *icon;
+
+ container = GNOME_ICON_CONTAINER (data);
+ priv = container->priv;
+
+ icon = g_hash_table_lookup (priv->canvas_item_to_icon, item);
+ g_return_val_if_fail (icon != NULL, FALSE);
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ return handle_icon_button_press (container, icon, &event->button);
+ case GDK_ENTER_NOTIFY:
+ return handle_icon_enter_notify (container, icon, &event->motion);
+ case GDK_LEAVE_NOTIFY:
+ return handle_icon_leave_notify (container, icon, &event->motion);
+ default:
+ return FALSE;
+ }
+}
+
+
+GtkWidget *
+gnome_icon_container_new (void)
+{
+ GtkWidget *new;
+
+ gtk_widget_push_visual (gdk_imlib_get_visual ());
+ gtk_widget_push_colormap (gdk_imlib_get_colormap ());
+
+ new = gtk_type_new (gnome_icon_container_get_type ());
+
+ gtk_widget_pop_visual ();
+ gtk_widget_pop_colormap ();
+
+ return new;
+}
+
+
+guint
+gnome_icon_container_get_type (void)
+{
+ static guint type = 0;
+
+ if (type == 0) {
+ GtkTypeInfo type_info = {
+ "GnomeIconContainer",
+ sizeof (GnomeIconContainer),
+ sizeof (GnomeIconContainerClass),
+ (GtkClassInitFunc) class_init,
+ (GtkObjectInitFunc) init,
+ (GtkArgSetFunc) NULL,
+ (GtkArgGetFunc) NULL
+ };
+
+ type = gtk_type_unique (gnome_canvas_get_type (), &type_info);
+ }
+
+ return type;
+}
+
+void
+gnome_icon_container_clear (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+
+ for (p = priv->icons; p != NULL; p = p->next)
+ icon_destroy (p->data);
+ g_list_free (priv->icons);
+ priv->icons = NULL;
+ priv->num_icons = 0;
+
+ icon_grid_clear (priv->grid);
+
+ unset_kbd_current (container);
+}
+
+
+void
+gnome_icon_container_set_icon_mode (GnomeIconContainer *container,
+ GnomeIconContainerIconMode mode)
+{
+ g_return_if_fail (container != NULL);
+
+ if (mode < 0 || mode >= NUM_ICON_MODES) {
+ g_warning ("Unknown icon mode %d", mode);
+ return;
+ }
+
+ change_icon_mode (container, mode);
+}
+
+GnomeIconContainerIconMode
+gnome_icon_container_get_icon_mode (GnomeIconContainer *container)
+{
+ g_return_val_if_fail (container != NULL, GNOME_ICON_CONTAINER_NORMAL_ICONS);
+
+ return container->priv->icon_mode;
+}
+
+
+static void
+setup_icon_in_container (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ priv->icons = g_list_prepend (priv->icons, icon);
+ priv->num_icons++;
+
+ g_hash_table_insert (priv->canvas_item_to_icon, icon->item, icon);
+ icon_show (icon);
+
+ gtk_signal_connect (GTK_OBJECT (icon->item), "event",
+ GTK_SIGNAL_FUNC (item_event_cb), container);
+}
+
+void
+gnome_icon_container_add_imlib (GnomeIconContainer *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gint x, gint y,
+ gpointer data)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *new_icon;
+ guint grid_x, grid_y;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (image != NULL);
+ g_return_if_fail (text != NULL);
+
+ priv = container->priv;
+
+ new_icon = icon_new_imlib (container, image, text, data);
+ icon_position (new_icon, container, x, y);
+
+ world_to_grid (container, x, y, &grid_x, &grid_y);
+ icon_grid_add (container->priv->grid, new_icon, grid_x, grid_y);
+
+ if (x % GNOME_ICON_CONTAINER_CELL_WIDTH (container) > 0)
+ icon_grid_add (priv->grid, new_icon, grid_x + 1, grid_y);
+ if (y % GNOME_ICON_CONTAINER_CELL_HEIGHT (container) > 0)
+ icon_grid_add (priv->grid, new_icon, grid_x, grid_y + 1);
+ if (x % GNOME_ICON_CONTAINER_CELL_WIDTH (container) > 0
+ && y % GNOME_ICON_CONTAINER_CELL_HEIGHT (container) > 0)
+ icon_grid_add (priv->grid, new_icon, grid_x + 1, grid_y + 1);
+
+ setup_icon_in_container (container, new_icon);
+
+ add_idle (container);
+}
+
+/**
+ * gnome_icon_container_add_imlib_auto:
+ * @container: A GnomeIconContainer
+ * @image: Image of the icon to add
+ * @text: Caption
+ * @data: Icon-specific data
+ *
+ * Add @image with caption @text and data @data to @container, in the first
+ * empty spot available.
+ **/
+void
+gnome_icon_container_add_imlib_auto (GnomeIconContainer *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data)
+{
+ GnomeIconContainerIcon *new_icon;
+ guint grid_x, grid_y;
+ gint x, y;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (image != NULL);
+ g_return_if_fail (text != NULL);
+
+ new_icon = icon_new_imlib (container, image, text, data);
+
+ icon_grid_add_auto (container->priv->grid, new_icon, &grid_x, &grid_y);
+
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+ icon_position (new_icon, container, x, y);
+
+ setup_icon_in_container (container, new_icon);
+
+ add_idle (container);
+}
+
+/**
+ * gnome_icon_container_add_imlib_with_layout:
+ * @container: A GnomeIconContainer
+ * @image: Image of the icon to add
+ * @text: Caption
+ * @data: Icon-specific data
+ * @layout: Layout information
+ *
+ * Add @image with the caption @text to @container using @layout, and attach
+ * @data to it.
+ *
+ * Return value: %FALSE if @text is not in @layout (and, consequently, the icon
+ * has not been added); %TRUE otherwise.
+ **/
+gboolean
+gnome_icon_container_add_imlib_with_layout (GnomeIconContainer *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data,
+ const GnomeIconContainerLayout *layout)
+{
+ gint x, y;
+
+ g_return_val_if_fail (container != NULL, FALSE);
+ g_return_val_if_fail (image != NULL, FALSE);
+ g_return_val_if_fail (text != NULL, FALSE);
+ g_return_val_if_fail (layout != NULL, FALSE);
+
+ if (gnome_icon_container_layout_get_position (layout, text, &x, &y)) {
+ gnome_icon_container_add_imlib (container, image,
+ text, x, y, data);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+
+/**
+ * gnome_icon_container_relayout:
+ * @container: An icon container.
+ *
+ * Relayout the icons in @container according to the allocation we are given. This
+ * is done by just collecting icons from top to bottom, from left to right, and
+ * tiling them in the same direction. The tiling is done in such a way that no
+ * horizontal scrolling is needed to see all the icons.
+ **/
+void
+gnome_icon_container_relayout (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *old_grid, *new_grid;
+ GList **sp, **dp;
+ guint i, j;
+ guint dx, dy;
+ guint sx, sy;
+ guint cols;
+ guint lines;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+ old_grid = priv->grid;
+
+ g_return_if_fail (old_grid->visible_width > 0);
+
+ prepare_for_layout (container);
+
+ new_grid = icon_grid_new ();
+
+ if (priv->num_icons % old_grid->visible_width != 0)
+ icon_grid_resize (new_grid,
+ old_grid->visible_width,
+ (priv->num_icons
+ / old_grid->visible_width) + 1);
+ else
+ icon_grid_resize (new_grid,
+ old_grid->visible_width,
+ priv->num_icons / old_grid->visible_width);
+
+ icon_grid_set_visible_width (new_grid, old_grid->visible_width);
+
+ sp = old_grid->elems;
+ dp = new_grid->elems;
+ sx = sy = 0;
+ dx = dy = 0;
+ cols = lines = 0;
+ for (i = 0; i < old_grid->height; i++) {
+ for (j = 0; j < old_grid->width; j++) {
+ GList *p;
+
+ /* Make sure the icons are sorted by increasing X
+ position. */
+ sp[j] = g_list_sort (sp[j],
+ icon_grid_cell_compare_by_x);
+
+ for (p = sp[j]; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+
+ /* Make sure icons are not moved twice, and
+ ignore icons whose upper left corner is not
+ in this cell, unless the icon is partly
+ outside the container. */
+ if (icon->layout_done
+ || (icon->x >= 0 && icon->x < sx)
+ || (icon->y >= 0 && icon->y < sy))
+ continue;
+
+ dp[cols] = g_list_alloc ();
+ dp[cols]->data = icon;
+
+ icon_position (icon, container, dx, dy);
+
+ icon->layout_done = TRUE;
+
+ if (++cols == new_grid->visible_width) {
+ cols = 0, lines++;
+ dx = 0, dy += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ dp += new_grid->alloc_width;
+ } else {
+ dx += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+ }
+
+ sx += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ sx = 0, sy += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ sp += old_grid->alloc_width;
+ }
+
+ if (cols < new_grid->visible_width && lines < new_grid->height) {
+ new_grid->first_free_x = cols;
+ new_grid->first_free_y = lines;
+ } else {
+ new_grid->first_free_x = -1;
+ new_grid->first_free_y = -1;
+ }
+
+ icon_grid_destroy (priv->grid);
+ priv->grid = new_grid;
+
+ if (priv->kbd_current != NULL)
+ set_kbd_current (container, priv->kbd_current, FALSE);
+
+ add_idle (container);
+}
+
+
+/**
+ * gnome_icon_container_line_up:
+ * @container: An icon container.
+ *
+ * Line up icons in @container.
+ **/
+void
+gnome_icon_container_line_up (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIconGrid *new_grid;
+ GList **p, **q;
+ guint new_grid_width;
+ guint i, j, k, m;
+ gint x, y, dx;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ /* Mark all icons as "not moved yet". */
+
+ prepare_for_layout (container);
+
+ /* Calculate the width for the resulting new grid. This is the maximum
+ width across all the lines. */
+
+ new_grid_width = 0;
+ p = grid->elems;
+ x = y = 0;
+ for (i = 0; i < grid->height; i++) {
+ guint line_width;
+
+ line_width = grid->width;
+ for (j = 0; j < grid->width; j++) {
+ GList *e;
+ guint count;
+
+ count = 0;
+ for (e = p[j]; e != NULL; e = e->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = e->data;
+ if (icon->x >= x && icon->y >= y)
+ count++;
+ }
+
+ if (count > 1)
+ new_grid_width += count - 1;
+
+ x += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ new_grid_width = MAX (new_grid_width, line_width);
+ p += grid->alloc_width;
+
+ y += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ x = 0;
+ }
+
+ /* Create the new grid. */
+
+ new_grid = icon_grid_new ();
+ icon_grid_resize (new_grid, new_grid_width, grid->height);
+ icon_grid_set_visible_width (new_grid, grid->visible_width);
+
+ /* Allocate the icons in the new grid, one per cell. */
+
+ p = grid->elems;
+ q = new_grid->elems;
+ k = 0;
+ x = y = dx = 0;
+ for (i = 0; i < grid->height; i++) {
+ m = 0;
+ for (j = 0; j < grid->width; j++) {
+ GList *e;
+ guint count;
+
+ /* Make sure the icons are sorted by increasing X
+ position. */
+ p[j] = g_list_sort
+ (p[j], icon_grid_cell_compare_by_x);
+
+ count = 0;
+ for (e = p[j]; e != NULL; e = e->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = e->data;
+
+ /* Make sure icons are not moved twice, and
+ ignore icons whose upper left corner is not
+ in this cell, unless the icon is partly
+ outside the container. */
+ if (icon->layout_done
+ || (icon->x >= 0 && icon->x < x)
+ || (icon->y >= 0 && icon->y < y))
+ continue;
+
+ icon_position (icon, container, dx, y);
+ icon->layout_done = TRUE;
+
+ q[k] = g_list_alloc ();
+ q[k]->data = icon;
+ dx += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+
+ k++;
+
+ if (count > 0)
+ m++;
+
+ count++;
+ }
+
+ if (count == 0) {
+ if (m > 0) {
+ m--;
+ } else {
+ k++;
+ dx += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+ }
+ }
+
+ x += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+
+ p += grid->alloc_width;
+
+ q += new_grid->alloc_width;
+ k = 0;
+
+ y += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ x = 0;
+
+ dx = 0;
+ }
+
+ /* Done: use the new grid. */
+
+ icon_grid_destroy (priv->grid);
+ priv->grid = new_grid;
+
+ /* Update the keyboard selection indicator. */
+ if (priv->kbd_current != NULL)
+ set_kbd_current (container, priv->kbd_current, FALSE);
+
+ add_idle (container);
+}
+
+
+/**
+ * gnome_icon_container_get_selection:
+ * @container: An icon container.
+ *
+ * Get a list of the icons currently selected in @container.
+ *
+ * Return value: A GList of the programmer-specified data associated to each
+ * selected icon, or NULL if no icon is selected. The caller is expected to
+ * free the list when it is not needed anymore.
+ **/
+GList *
+gnome_icon_container_get_selection (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *list, *p;
+
+ g_return_val_if_fail (container != NULL, FALSE);
+
+ priv = container->priv;
+
+ list = NULL;
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon->is_selected)
+ list = g_list_prepend (list, icon->data);
+ }
+
+ return list;
+}
+
+/**
+ * gnome_icon_container_select_all:
+ * @container: An icon container widget.
+ *
+ * Select all the icons in @container at once.
+ **/
+void
+gnome_icon_container_select_all (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GList **p, *q;
+ guint i, j;
+ gboolean selection_changed;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ selection_changed = FALSE;
+ p = grid->elems;
+ for (i = 0; i < grid->height; i++) {
+ for (j = 0; j < grid->width; j++) {
+ for (q = p[j]; q != NULL; q =q->next) {
+ if (select_icon (container, q->data, TRUE))
+ selection_changed = TRUE;
+ }
+ }
+
+ p += grid->alloc_width;
+ }
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+}
+
+/**
+ * gnome_icon_container_unselect_all:
+ * @container: An icon container widget.
+ *
+ * Deselect all the icons in @container.
+ **/
+void
+gnome_icon_container_unselect_all (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ gboolean selection_changed;
+ GList *p;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+
+ selection_changed = FALSE;
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (select_icon (container, icon, FALSE))
+ selection_changed = TRUE;
+ }
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+}
+
+/**
+ * gnome_icon_container_set_base_uri:
+ * @container: An icon container widget.
+ * @base_uri: A base URI.
+ *
+ * Set the base URI for drag & drop operations.
+ **/
+void
+gnome_icon_container_set_base_uri (GnomeIconContainer *container,
+ const gchar *base_uri)
+{
+ GnomeIconContainerPrivate *priv;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ priv = container->priv;
+
+ g_free (priv->base_uri);
+ priv->base_uri = g_strdup (base_uri);
+}
+
+/**
+ * gnome_icon_container_xlate_selected:
+ * @container: An icon container widget.
+ * @amount_x: Amount of translation on the X axis.
+ * @amount_y: Amount of translation on the Y axis.
+ * @raise: Whether icons should be raised during this operation.
+ *
+ * Translate all the currently selected items in @container by @amount_x
+ * horizontally and @amount_y vertically. Positive values move to the
+ * right/bottom, negative values to the left/top.
+ **/
+void
+gnome_icon_container_xlate_selected (GnomeIconContainer *container,
+ gint amount_x,
+ gint amount_y,
+ gboolean raise)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ if (amount_x == 0 && amount_y == 0)
+ return;
+
+ priv = container->priv;
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon->is_selected) {
+ move_icon (container, icon,
+ icon->x + amount_x, icon->y + amount_y);
+ if (raise)
+ icon_raise (icon);
+ }
+ }
+
+ set_kbd_current (container, priv->kbd_current, TRUE);
+}
+
+
+GnomeIconContainerLayout *
+gnome_icon_container_get_layout (GnomeIconContainer *container)
+{
+ GnomeIconContainerLayout *layout;
+ GList *p;
+
+ g_return_val_if_fail (container != NULL, NULL);
+ g_return_val_if_fail (GNOME_IS_ICON_CONTAINER (container), NULL);
+
+ layout = gnome_icon_container_layout_new ();
+
+ for (p = container->priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ gnome_icon_container_layout_add (layout, icon->text,
+ icon->x, icon->y);
+ }
+
+ return layout;
+}
diff --git a/libnautilus-extensions/gnome-icon-container.h b/libnautilus-extensions/gnome-icon-container.h
new file mode 100644
index 000000000..dd61eaad9
--- /dev/null
+++ b/libnautilus-extensions/gnome-icon-container.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container.h - Icon container widget.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _GNOME_ICON_CONTAINER_H
+#define _GNOME_ICON_CONTAINER_H
+
+#include <libgnomeui/libgnomeui.h>
+
+enum _GnomeIconContainerIconMode {
+ GNOME_ICON_CONTAINER_NORMAL_ICONS,
+ GNOME_ICON_CONTAINER_SMALL_ICONS
+};
+typedef enum _GnomeIconContainerIconMode GnomeIconContainerIconMode;
+
+enum _GnomeIconContainerLayoutMode {
+ GNOME_ICON_LAYOUT_MANUAL,
+ GNOME_ICON_LAYOUT_AUTO
+};
+typedef enum _GnomeIconContainerLayoutMode GnomeIconContainerLayoutMode;
+
+typedef struct _GnomeIconContainer GnomeIconContainer;
+typedef struct _GnomeIconContainerClass GnomeIconContainerClass;
+typedef struct _GnomeIconContainerPrivate GnomeIconContainerPrivate;
+
+#include "gnome-icon-container-layout.h"
+
+
+#define GNOME_ICON_CONTAINER(obj) \
+ GTK_CHECK_CAST (obj, gnome_icon_container_get_type (), GnomeIconContainer)
+#define GNOME_ICON_CONTAINER_CLASS(k) \
+ GTK_CHECK_CLASS_CAST (k, gnome_icon_container_get_type (), GnomeIconListView)
+#define GNOME_IS_ICON_CONTAINER(obj) \
+ GTK_CHECK_TYPE (obj, gnome_icon_container_get_type ())
+
+
+typedef gint (* GnomeIconContainerSortFunc) (const gchar *name_a,
+ gpointer data_a,
+ const gchar *name_b,
+ gpointer data_b,
+ gpointer user_data);
+
+struct _GnomeIconContainer {
+ GnomeCanvas canvas;
+ GnomeIconContainerPrivate *priv;
+};
+
+struct _GnomeIconContainerClass {
+ GnomeCanvasClass parent_class;
+
+ void (* selection_changed) (GnomeIconContainer *container);
+ gint (* button_press) (GnomeIconContainer *container,
+ GdkEventButton *event);
+ void (* activate) (GnomeIconContainer *container,
+ const gchar *name,
+ gpointer data);
+
+ void (* context_click) (GnomeIconContainer *container,
+ const gchar *name,
+ gpointer data);
+};
+
+
+guint gnome_icon_container_get_type (void);
+
+GtkWidget *gnome_icon_container_new (void);
+
+void gnome_icon_container_clear (GnomeIconContainer *view);
+
+void gnome_icon_container_set_icon_mode
+ (GnomeIconContainer *view,
+ GnomeIconContainerIconMode mode);
+
+GnomeIconContainerIconMode
+ gnome_icon_container_get_icon_mode
+ (GnomeIconContainer *view);
+
+void gnome_icon_container_set_editable
+ (GnomeIconContainer *view,
+ gboolean is_editable);
+gboolean gnome_icon_container_get_editable
+ (GnomeIconContainer *view);
+
+void gnome_icon_container_add_imlib (GnomeIconContainer *view,
+ GdkImlibImage *image,
+ const gchar *text,
+ gint x, gint y,
+ gpointer data);
+
+void gnome_icon_container_add_imlib_auto
+ (GnomeIconContainer *view,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data);
+gboolean gnome_icon_container_add_imlib_with_layout
+ (GnomeIconContainer
+ *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data,
+ const GnomeIconContainerLayout
+ *layout);
+
+gpointer gnome_icon_container_get_icon_data
+ (GnomeIconContainer *view,
+ const gchar *text);
+
+void gnome_icon_container_relayout (GnomeIconContainer *view);
+void gnome_icon_container_line_up (GnomeIconContainer *view);
+GList *gnome_icon_container_get_selection
+ (GnomeIconContainer *view);
+
+void gnome_icon_container_unselect_all
+ (GnomeIconContainer *view);
+void gnome_icon_container_select_all (GnomeIconContainer *view);
+
+void gnome_icon_container_enable_browser_mode
+ (GnomeIconContainer *view,
+ gboolean enable);
+
+void gnome_icon_container_set_base_uri
+ (GnomeIconContainer *container,
+ const gchar *base_uri);
+
+void gnome_icon_container_xlate_selected
+ (GnomeIconContainer *container,
+ gint amount_x,
+ gint amount_y,
+ gboolean raise);
+
+GnomeIconContainerLayout *
+ gnome_icon_container_get_layout
+ (GnomeIconContainer *container);
+#endif
diff --git a/libnautilus-extensions/gtkflist.c b/libnautilus-extensions/gtkflist.c
new file mode 100644
index 000000000..2b5abdd87
--- /dev/null
+++ b/libnautilus-extensions/gtkflist.c
@@ -0,0 +1,539 @@
+/* File list widget for the Midnight Commander
+ *
+ * Copyright (C) 1999 The Free Software Foundation
+ *
+ * Author: Federico Mena <federico@nuclecu.unam.mx>
+ * Modified by Ettore Perazzoli <ettore@gnu.org>
+ */
+
+/* FIXME this is a kludge to re-use broken CList. Instead, I'd like to have a
+ native List widget that uses a simple API similiar to the GnomeIconContainer
+ one. */
+
+#include <config.h>
+#include "gtkflist.h"
+
+
+enum {
+ ROW_POPUP_MENU,
+ EMPTY_POPUP_MENU,
+ ACTIVATE,
+ START_DRAG,
+ SELECTION_CHANGED,
+ LAST_SIGNAL
+};
+
+
+static void gtk_flist_class_init (GtkFListClass *class);
+static void gtk_flist_init (GtkFList *flist);
+
+static gint gtk_flist_button_press (GtkWidget *widget, GdkEventButton *event);
+static gint gtk_flist_button_release (GtkWidget *widget, GdkEventButton *event);
+static gint gtk_flist_motion (GtkWidget *widget, GdkEventMotion *event);
+static gint gtk_flist_key (GtkWidget *widget, GdkEventKey *event);
+static void gtk_flist_drag_begin (GtkWidget *widget, GdkDragContext *context);
+static void gtk_flist_drag_end (GtkWidget *widget, GdkDragContext *context);
+static void gtk_flist_drag_data_get (GtkWidget *widget, GdkDragContext *context,
+ GtkSelectionData *data, guint info, guint time);
+static void gtk_flist_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time);
+static gboolean gtk_flist_drag_motion (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time);
+static gboolean gtk_flist_drag_drop (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time);
+static void gtk_flist_drag_data_received (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, GtkSelectionData *data,
+ guint info, guint time);
+
+static void gtk_flist_clear (GtkCList *clist);
+
+
+static GtkCListClass *parent_class;
+
+static guint flist_signals[LAST_SIGNAL];
+
+
+/**
+ * gtk_flist_get_type:
+ * @void:
+ *
+ * Creates the GtkFList class and its type information
+ *
+ * Return value: The type ID for GtkFListClass
+ **/
+GtkType
+gtk_flist_get_type (void)
+{
+ static GtkType flist_type = 0;
+
+ if (!flist_type) {
+ GtkTypeInfo flist_info = {
+ "GtkFList",
+ sizeof (GtkFList),
+ sizeof (GtkFListClass),
+ (GtkClassInitFunc) gtk_flist_class_init,
+ (GtkObjectInitFunc) gtk_flist_init,
+ NULL, /* reserved_1 */
+ NULL, /* reserved_2 */
+ (GtkClassInitFunc) NULL
+ };
+
+ flist_type = gtk_type_unique (gtk_clist_get_type (), &flist_info);
+ }
+
+ return flist_type;
+}
+
+/* Standard class initialization function */
+static void
+gtk_flist_class_init (GtkFListClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkCListClass *clist_class;
+
+ object_class = (GtkObjectClass *) class;
+ widget_class = (GtkWidgetClass *) class;
+ clist_class = (GtkCListClass *) class;
+
+ parent_class = gtk_type_class (gtk_clist_get_type ());
+
+ flist_signals[ROW_POPUP_MENU] =
+ gtk_signal_new ("row_popup_menu",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, row_popup_menu),
+ gtk_marshal_NONE__POINTER,
+ GTK_TYPE_NONE, 1,
+ GTK_TYPE_GDK_EVENT);
+ flist_signals[EMPTY_POPUP_MENU] =
+ gtk_signal_new ("empty_popup_menu",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, empty_popup_menu),
+ gtk_marshal_NONE__POINTER,
+ GTK_TYPE_NONE, 1,
+ GTK_TYPE_GDK_EVENT);
+ flist_signals[ACTIVATE] =
+ gtk_signal_new ("activate",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, activate),
+ gtk_marshal_NONE__POINTER,
+ GTK_TYPE_NONE, 1,
+ GTK_TYPE_POINTER);
+ flist_signals[START_DRAG] =
+ gtk_signal_new ("start_drag",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, start_drag),
+ gtk_marshal_NONE__INT_POINTER,
+ GTK_TYPE_NONE, 2,
+ GTK_TYPE_INT,
+ GTK_TYPE_GDK_EVENT);
+ flist_signals[SELECTION_CHANGED] =
+ gtk_signal_new ("selection_changed",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, start_drag),
+ gtk_marshal_NONE__NONE,
+ GTK_TYPE_NONE, 0);
+
+ gtk_object_class_add_signals (object_class, flist_signals, LAST_SIGNAL);
+
+ clist_class->clear = gtk_flist_clear;
+
+ widget_class->button_press_event = gtk_flist_button_press;
+ widget_class->button_release_event = gtk_flist_button_release;
+ widget_class->motion_notify_event = gtk_flist_motion;
+ widget_class->key_press_event = gtk_flist_key;
+ widget_class->key_release_event = gtk_flist_key;
+ widget_class->drag_begin = gtk_flist_drag_begin;
+ widget_class->drag_end = gtk_flist_drag_end;
+ widget_class->drag_data_get = gtk_flist_drag_data_get;
+ widget_class->drag_leave = gtk_flist_drag_leave;
+ widget_class->drag_motion = gtk_flist_drag_motion;
+ widget_class->drag_drop = gtk_flist_drag_drop;
+ widget_class->drag_data_received = gtk_flist_drag_data_received;
+}
+
+/* Standard object initialization function */
+static void
+gtk_flist_init (GtkFList *flist)
+{
+ flist->anchor_row = -1;
+
+ /* GtkCList does not specify pointer motion by default */
+ gtk_widget_add_events (GTK_WIDGET (flist), GDK_POINTER_MOTION_MASK);
+}
+
+static gboolean
+row_selected (GtkFList *flist, gint row)
+{
+ GtkCListRow *elem;
+
+ elem = g_list_nth (GTK_CLIST (flist)->row_list, row)->data;
+
+ return elem->state == GTK_STATE_SELECTED;
+}
+
+/* Selects the rows between the anchor to the specified row, inclusive. */
+static void
+select_range (GtkFList *flist, int row)
+{
+ int min, max;
+ int i;
+
+ if (flist->anchor_row == -1)
+ flist->anchor_row = row;
+
+ if (row < flist->anchor_row) {
+ min = row;
+ max = flist->anchor_row;
+ } else {
+ min = flist->anchor_row;
+ max = row;
+ }
+
+ for (i = min; i <= max; i++)
+ gtk_clist_select_row (GTK_CLIST (flist), i, 0);
+}
+
+/* Handles row selection according to the specified modifier state */
+static void
+select_row (GtkFList *flist, int row, guint state)
+{
+ int range, additive;
+
+ range = (state & GDK_SHIFT_MASK) != 0;
+ additive = (state & GDK_CONTROL_MASK) != 0;
+
+ if (!additive)
+ gtk_clist_unselect_all (GTK_CLIST (flist));
+
+ if (!range) {
+ if (additive) {
+ if (row_selected (flist, row))
+ gtk_clist_unselect_row
+ (GTK_CLIST (flist), row, 0);
+ else
+ gtk_clist_select_row
+ (GTK_CLIST (flist), row, 0);
+ } else {
+ gtk_clist_select_row (GTK_CLIST (flist), row, 0);
+ }
+ flist->anchor_row = row;
+ } else
+ select_range (flist, row);
+
+ gtk_signal_emit (GTK_OBJECT (flist), flist_signals[SELECTION_CHANGED]);
+}
+
+/* Our handler for button_press events. We override all of GtkCList's broken
+ * behavior.
+ */
+static gint
+gtk_flist_button_press (GtkWidget *widget, GdkEventButton *event)
+{
+ GtkFList *flist;
+ GtkCList *clist;
+ int on_row;
+ gint row, col;
+ int retval;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_FLIST (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ flist = GTK_FLIST (widget);
+ clist = GTK_CLIST (widget);
+ retval = FALSE;
+
+ if (event->window != clist->clist_window)
+ return (* GTK_WIDGET_CLASS (parent_class)->button_press_event) (widget, event);
+
+ on_row = gtk_clist_get_selection_info (clist, event->x, event->y, &row, &col);
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button == 1 || event->button == 2) {
+ if (on_row) {
+ /* Save the mouse info for DnD */
+
+ flist->dnd_press_button = event->button;
+ flist->dnd_press_x = event->x;
+ flist->dnd_press_y = event->y;
+
+ /* Handle selection */
+
+ if ((row_selected (flist, row)
+ && !(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
+ || ((event->state & GDK_CONTROL_MASK)
+ && !(event->state & GDK_SHIFT_MASK))) {
+ flist->dnd_select_pending = TRUE;
+ flist->dnd_select_pending_state = event->state;
+ flist->dnd_select_pending_row = row;
+ }
+
+ select_row (flist, row, event->state);
+ } else {
+ gtk_clist_unselect_all (clist);
+ }
+
+ retval = TRUE;
+ } else if (event->button == 3) {
+ if (on_row) {
+ select_row (flist, row, event->state);
+ gtk_signal_emit (GTK_OBJECT (flist),
+ flist_signals[ROW_POPUP_MENU],
+ event);
+ } else
+ gtk_signal_emit (GTK_OBJECT (flist),
+ flist_signals[EMPTY_POPUP_MENU],
+ event);
+
+ retval = TRUE;
+ }
+
+ break;
+
+ case GDK_2BUTTON_PRESS:
+ if (event->button == 1) {
+ GtkCListRow *elem;
+
+ flist->dnd_select_pending = FALSE;
+ flist->dnd_select_pending_state = 0;
+
+ if (on_row) {
+ elem = g_list_nth (GTK_CLIST (flist)->row_list,
+ row)->data;
+ gtk_signal_emit (GTK_OBJECT (flist),
+ flist_signals[ACTIVATE],
+ elem->data);
+ }
+
+ retval = TRUE;
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+/* Our handler for button_release events. We override all of GtkCList's broken
+ * behavior.
+ */
+static gint
+gtk_flist_button_release (GtkWidget *widget, GdkEventButton *event)
+{
+ GtkFList *flist;
+ GtkCList *clist;
+ int on_row;
+ gint row, col;
+ int retval;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_FLIST (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ flist = GTK_FLIST (widget);
+ clist = GTK_CLIST (widget);
+ retval = FALSE;
+
+ if (event->window != clist->clist_window)
+ return (* GTK_WIDGET_CLASS (parent_class)->button_release_event) (widget, event);
+
+ on_row = gtk_clist_get_selection_info (clist, event->x, event->y, &row, &col);
+
+ if (!(event->button == 1 || event->button == 2))
+ return FALSE;
+
+ flist->dnd_press_button = 0;
+ flist->dnd_press_x = 0;
+ flist->dnd_press_y = 0;
+
+ if (on_row) {
+ if (flist->dnd_select_pending) {
+ /* select_row (flist, row, flist->dnd_select_pending_state); */
+ flist->dnd_select_pending = FALSE;
+ flist->dnd_select_pending_state = 0;
+ }
+
+ retval = TRUE;
+ }
+
+ return retval;
+}
+
+/* Our handler for motion_notify events. We override all of GtkCList's broken
+ * behavior.
+ */
+static gint
+gtk_flist_motion (GtkWidget *widget, GdkEventMotion *event)
+{
+ GtkFList *flist;
+ GtkCList *clist;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_FLIST (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ flist = GTK_FLIST (widget);
+ clist = GTK_CLIST (widget);
+
+ if (event->window != clist->clist_window)
+ return (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event) (widget, event);
+
+ if (!((flist->dnd_press_button == 1 && (event->state & GDK_BUTTON1_MASK))
+ || (flist->dnd_press_button == 2 && (event->state & GDK_BUTTON2_MASK))))
+ return FALSE;
+
+ /* This is the same threshold value that is used in gtkdnd.c */
+
+ if (MAX (abs (flist->dnd_press_x - event->x),
+ abs (flist->dnd_press_y - event->y)) <= 3)
+ return FALSE;
+
+ /* Handle any pending selections */
+
+ if (flist->dnd_select_pending) {
+ select_row (flist,
+ flist->dnd_select_pending_row,
+ flist->dnd_select_pending_state);
+
+ flist->dnd_select_pending = FALSE;
+ flist->dnd_select_pending_state = 0;
+ }
+
+ gtk_signal_emit (GTK_OBJECT (flist),
+ flist_signals[START_DRAG],
+ flist->dnd_press_button,
+ event);
+ return TRUE;
+}
+
+/* Our handler for key_press and key_release events. We do nothing, and we do
+ * this to avoid GtkCList's broken behavior.
+ */
+static gint
+gtk_flist_key (GtkWidget *widget, GdkEventKey *event)
+{
+ return FALSE;
+}
+
+/* We override the drag_begin signal to do nothing */
+static void
+gtk_flist_drag_begin (GtkWidget *widget, GdkDragContext *context)
+{
+ /* nothing */
+}
+
+/* We override the drag_end signal to do nothing */
+static void
+gtk_flist_drag_end (GtkWidget *widget, GdkDragContext *context)
+{
+ /* nothing */
+}
+
+/* We override the drag_data_get signal to do nothing */
+static void
+gtk_flist_drag_data_get (GtkWidget *widget, GdkDragContext *context,
+ GtkSelectionData *data, guint info, guint time)
+{
+ /* nothing */
+}
+
+/* We override the drag_leave signal to do nothing */
+static void
+gtk_flist_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time)
+{
+ /* nothing */
+}
+
+/* We override the drag_motion signal to do nothing */
+static gboolean
+gtk_flist_drag_motion (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time)
+{
+ return FALSE;
+}
+
+/* We override the drag_drop signal to do nothing */
+static gboolean
+gtk_flist_drag_drop (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time)
+{
+ return FALSE;
+}
+
+/* We override the drag_data_received signal to do nothing */
+static void
+gtk_flist_drag_data_received (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, GtkSelectionData *data,
+ guint info, guint time)
+{
+ /* nothing */
+}
+
+/* Our handler for the clear signal of the clist. We have to reset the anchor
+ * to null.
+ */
+static void
+gtk_flist_clear (GtkCList *clist)
+{
+ GtkFList *flist;
+
+ g_return_if_fail (clist != NULL);
+ g_return_if_fail (GTK_IS_FLIST (clist));
+
+ flist = GTK_FLIST (clist);
+ flist->anchor_row = -1;
+
+ if (parent_class->clear)
+ (* parent_class->clear) (clist);
+}
+
+
+/**
+ * gtk_flist_new_with_titles:
+ * @columns: The number of columns in the list
+ * @titles: The titles for the columns
+ *
+ * Return value: The newly-created file list.
+ **/
+GtkWidget *
+gtk_flist_new_with_titles (int columns, char **titles)
+{
+ GtkFList *flist;
+
+ flist = gtk_type_new (gtk_flist_get_type ());
+ gtk_clist_construct (GTK_CLIST (flist), columns, titles);
+
+ gtk_clist_set_selection_mode (GTK_CLIST (flist),
+ GTK_SELECTION_MULTIPLE);
+
+ return GTK_WIDGET (flist);
+}
+
+GList *
+gtk_flist_get_selection (GtkFList *flist)
+{
+ GList *retval;
+ GList *p;
+
+ g_return_val_if_fail (flist != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_FLIST (flist), NULL);
+
+ retval = NULL;
+ for (p = GTK_CLIST (flist)->row_list; p != NULL; p = p->next) {
+ GtkCListRow *row;
+
+ row = p->data;
+ if (row->state == GTK_STATE_SELECTED)
+ retval = g_list_prepend (retval, row->data);
+ }
+
+ return retval;
+}
diff --git a/libnautilus-extensions/gtkflist.h b/libnautilus-extensions/gtkflist.h
new file mode 100644
index 000000000..ac216deaf
--- /dev/null
+++ b/libnautilus-extensions/gtkflist.h
@@ -0,0 +1,70 @@
+/* File list widget for the Midnight Commander
+ *
+ * Copyright (C) 1999 The Free Software Foundation
+ *
+ * Author: Federico Mena <federico@nuclecu.unam.mx>
+ */
+
+#ifndef GTKFLIST_H
+#define GTKFLIST_H
+
+#include "panel.h"
+#include <gtk/gtkclist.h>
+
+
+/* It is sad that we have to do this. GtkCList's behavior is so broken that we
+ * have to override all the event handlers and implement our own selection
+ * behavior. Sigh.
+ */
+
+#define TYPE_GTK_FLIST (gtk_flist_get_type ())
+#define GTK_FLIST(obj) (GTK_CHECK_CAST ((obj), TYPE_GTK_FLIST, GtkFList))
+#define GTK_FLIST_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), TYPE_GTK_FLIST, GtkFListClass))
+#define GTK_IS_FLIST(obj) (GTK_CHECK_TYPE ((obj), TYPE_GTK_FLIST))
+#define GTK_IS_FLIST_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), TYPE_GTK_FLIST))
+
+
+typedef struct _GtkFList GtkFList;
+typedef struct _GtkFListClass GtkFListClass;
+
+struct _GtkFList {
+ GtkCList clist;
+
+ /* The anchor row for range selections */
+ int anchor_row;
+
+ /* Mouse button and position saved on button press */
+ int dnd_press_button;
+ int dnd_press_x, dnd_press_y;
+
+ /* Delayed selection information */
+ int dnd_select_pending;
+ guint dnd_select_pending_state;
+ int dnd_select_pending_row;
+};
+
+struct _GtkFListClass {
+ GtkCListClass parent_class;
+
+ /* Signal: invoke the popup menu for rows */
+ void (* row_popup_menu) (GtkFList *flist, GdkEventButton *event);
+
+ /* Signal: invoke the popup menu for empty areas */
+ void (* empty_popup_menu) (GtkFList *flist, GdkEventButton *event);
+
+ /* Signal: open the file in the selected row */
+ void (* activate) (GtkFList *flist, gpointer data);
+
+ /* Signal: initiate a drag and drop operation */
+ void (* start_drag) (GtkFList *flist, gint button, GdkEvent *event);
+
+ /* Signal: selection has changed */
+ void (* selection_changed) (GtkFList *flist);
+};
+
+
+GtkType gtk_flist_get_type (void);
+GtkWidget *gtk_flist_new_with_titles (int columns, char **titles);
+GList *gtk_flist_get_selection (GtkFList *flist);
+
+#endif
diff --git a/libnautilus-extensions/gtkscrollframe.c b/libnautilus-extensions/gtkscrollframe.c
new file mode 100644
index 000000000..6f41835ec
--- /dev/null
+++ b/libnautilus-extensions/gtkscrollframe.c
@@ -0,0 +1,1210 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This 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.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include <config.h>
+#include <gtk/gtkhscrollbar.h>
+#include <gtk/gtkvscrollbar.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkviewport.h>
+#include "gtkscrollframe.h"
+
+
+/* scrolled window policy and size requisition handling:
+ *
+ * gtk size requisition works as follows:
+ * a widget upon size-request reports the width and height that it finds
+ * to be best suited to display its contents, including children.
+ * the width and/or height reported from a widget upon size requisition
+ * may be overidden by the user by specifying a width and/or height
+ * other than 0 through gtk_widget_set_usize().
+ *
+ * a scrolled window needs (for imlementing all three policy types) to
+ * request its width and height based on two different rationales.
+ * 1) the user wants the scrolled window to just fit into the space
+ * that it gets allocated for a specifc dimension.
+ * 1.1) this does not apply if the user specified a concrete value
+ * value for that specific dimension by either specifying usize for the
+ * scrolled window or for its child.
+ * 2) the user wants the scrolled window to take as much space up as
+ * is desired by the child for a specifc dimension (i.e. POLICY_NEVER).
+ *
+ * also, kinda obvious:
+ * 3) a user would certainly not have choosen a scrolled window as a container
+ * for the child, if the resulting allocation takes up more space than the
+ * child would have allocated without the scrolled window.
+ *
+ * conclusions:
+ * A) from 1) follows: the scrolled window shouldn't request more space for a
+ * specifc dimension than is required at minimum.
+ * B) from 1.1) follows: the requisition may be overidden by usize of the scrolled
+ * window (done automatically) or by usize of the child (needs to be checked).
+ * C) from 2) follows: for POLICY_NEVER, the scrolled window simply reports the
+ * child's dimension.
+ * D) from 3) follows: the scrolled window child's minimum width and minimum height
+ * under A) at least correspond to the space taken up by its scrollbars.
+ */
+
+/* Object argument IDs */
+enum {
+ ARG_0,
+ ARG_HADJUSTMENT,
+ ARG_VADJUSTMENT,
+ ARG_HSCROLLBAR_POLICY,
+ ARG_VSCROLLBAR_POLICY,
+ ARG_FRAME_PLACEMENT,
+ ARG_SHADOW_TYPE,
+ ARG_SCROLLBAR_SPACING
+};
+
+/* Private part of the GtkScrollFrame structure */
+typedef struct {
+ /* Horizontal and vertical scrollbars */
+ GtkWidget *hsb;
+ GtkWidget *vsb;
+
+ /* Space between scrollbars and frame */
+ guint sb_spacing;
+
+ /* Allocation for frame */
+ guint frame_x;
+ guint frame_y;
+ guint frame_w;
+ guint frame_h;
+
+ /* Scrollbar policy */
+ guint hsb_policy : 2;
+ guint vsb_policy : 2;
+
+ /* Whether scrollbars are visible */
+ guint hsb_visible : 1;
+ guint vsb_visible : 1;
+
+ /* Placement of frame wrt scrollbars */
+ guint frame_placement : 2;
+
+ /* Shadow type for frame */
+ guint shadow_type : 3;
+} ScrollFramePrivate;
+
+
+static void gtk_scroll_frame_class_init (GtkScrollFrameClass *class);
+static void gtk_scroll_frame_init (GtkScrollFrame *sf);
+static void gtk_scroll_frame_set_arg (GtkObject *object, GtkArg *arg, guint arg_id);
+static void gtk_scroll_frame_get_arg (GtkObject *object, GtkArg *arg, guint arg_id);
+static void gtk_scroll_frame_destroy (GtkObject *object);
+static void gtk_scroll_frame_finalize (GtkObject *object);
+
+static void gtk_scroll_frame_map (GtkWidget *widget);
+static void gtk_scroll_frame_unmap (GtkWidget *widget);
+static void gtk_scroll_frame_draw (GtkWidget *widget, GdkRectangle *area);
+static void gtk_scroll_frame_size_request (GtkWidget *widget, GtkRequisition *requisition);
+static void gtk_scroll_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
+static gint gtk_scroll_frame_expose (GtkWidget *widget, GdkEventExpose *event);
+
+static void gtk_scroll_frame_add (GtkContainer *container, GtkWidget *widget);
+static void gtk_scroll_frame_remove (GtkContainer *container, GtkWidget *widget);
+static void gtk_scroll_frame_forall (GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data);
+
+static GtkBinClass *parent_class;
+
+
+/**
+ * gtk_scroll_frame_get_type:
+ * @void:
+ *
+ * Registers the &GtkScrollFrame class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GtkScrollFrame class.
+ **/
+GtkType
+gtk_scroll_frame_get_type (void)
+{
+ static GtkType scroll_frame_type = 0;
+
+ if (!scroll_frame_type) {
+ static const GtkTypeInfo scroll_frame_info = {
+ "GtkScrollFrame",
+ sizeof (GtkScrollFrame),
+ sizeof (GtkScrollFrameClass),
+ (GtkClassInitFunc) gtk_scroll_frame_class_init,
+ (GtkObjectInitFunc) gtk_scroll_frame_init,
+ NULL, /* reserved_1 */
+ NULL, /* reserved_2 */
+ (GtkClassInitFunc) NULL
+ };
+
+ scroll_frame_type = gtk_type_unique (GTK_TYPE_BIN, &scroll_frame_info);
+ }
+
+ return scroll_frame_type;
+}
+
+/* Class initialization function for the scroll frame widget */
+static void
+gtk_scroll_frame_class_init (GtkScrollFrameClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkContainerClass *container_class;
+
+ object_class = (GtkObjectClass *) class;
+ widget_class = (GtkWidgetClass *) class;
+ container_class = (GtkContainerClass *) class;
+
+ parent_class = gtk_type_class (GTK_TYPE_BIN);
+
+ gtk_object_add_arg_type ("GtkScrollFrame::hadjustment",
+ GTK_TYPE_ADJUSTMENT,
+ GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
+ ARG_HADJUSTMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::vadjustment",
+ GTK_TYPE_ADJUSTMENT,
+ GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
+ ARG_VADJUSTMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::hscrollbar_policy",
+ GTK_TYPE_POLICY_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_HSCROLLBAR_POLICY);
+ gtk_object_add_arg_type ("GtkScrollFrame::vscrollbar_policy",
+ GTK_TYPE_POLICY_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_VSCROLLBAR_POLICY);
+ gtk_object_add_arg_type ("GtkScrollFrame::frame_placement",
+ GTK_TYPE_CORNER_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_FRAME_PLACEMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::shadow_type",
+ GTK_TYPE_SHADOW_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_SHADOW_TYPE);
+ gtk_object_add_arg_type ("GtkScrollFrame::scrollbar_spacing",
+ GTK_TYPE_UINT,
+ GTK_ARG_READWRITE,
+ ARG_SCROLLBAR_SPACING);
+
+ object_class->set_arg = gtk_scroll_frame_set_arg;
+ object_class->get_arg = gtk_scroll_frame_get_arg;
+ object_class->destroy = gtk_scroll_frame_destroy;
+ object_class->finalize = gtk_scroll_frame_finalize;
+
+ widget_class->map = gtk_scroll_frame_map;
+ widget_class->unmap = gtk_scroll_frame_unmap;
+ widget_class->draw = gtk_scroll_frame_draw;
+ widget_class->size_request = gtk_scroll_frame_size_request;
+ widget_class->size_allocate = gtk_scroll_frame_size_allocate;
+ widget_class->expose_event = gtk_scroll_frame_expose;
+
+ container_class->add = gtk_scroll_frame_add;
+ container_class->remove = gtk_scroll_frame_remove;
+ container_class->forall = gtk_scroll_frame_forall;
+}
+
+/* Object initialization function for the scroll frame widget */
+static void
+gtk_scroll_frame_init (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ priv = g_new0 (ScrollFramePrivate, 1);
+ sf->priv = priv;
+
+ GTK_WIDGET_SET_FLAGS (sf, GTK_NO_WINDOW);
+
+ gtk_container_set_resize_mode (GTK_CONTAINER (sf), GTK_RESIZE_QUEUE);
+
+ priv->sb_spacing = 3;
+ priv->hsb_policy = GTK_POLICY_ALWAYS;
+ priv->vsb_policy = GTK_POLICY_ALWAYS;
+ priv->frame_placement = GTK_CORNER_TOP_LEFT;
+ priv->shadow_type = GTK_SHADOW_NONE;
+}
+
+/* Set_arg handler for the scroll frame widget */
+static void
+gtk_scroll_frame_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ switch (arg_id) {
+ case ARG_HADJUSTMENT:
+ gtk_scroll_frame_set_hadjustment (sf, GTK_VALUE_POINTER (*arg));
+ break;
+
+ case ARG_VADJUSTMENT:
+ gtk_scroll_frame_set_vadjustment (sf, GTK_VALUE_POINTER (*arg));
+ break;
+
+ case ARG_HSCROLLBAR_POLICY:
+ gtk_scroll_frame_set_policy (sf, GTK_VALUE_ENUM (*arg), priv->vsb_policy);
+ break;
+
+ case ARG_VSCROLLBAR_POLICY:
+ gtk_scroll_frame_set_policy (sf, priv->hsb_policy, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_FRAME_PLACEMENT:
+ gtk_scroll_frame_set_placement (sf, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_SHADOW_TYPE:
+ gtk_scroll_frame_set_shadow_type (sf, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_SCROLLBAR_SPACING:
+ gtk_scroll_frame_set_scrollbar_spacing (sf, GTK_VALUE_UINT (*arg));
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* Get_arg handler for the scroll frame widget */
+static void
+gtk_scroll_frame_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ switch (arg_id) {
+ case ARG_HADJUSTMENT:
+ GTK_VALUE_POINTER (*arg) = gtk_scroll_frame_get_hadjustment (sf);
+ break;
+
+ case ARG_VADJUSTMENT:
+ GTK_VALUE_POINTER (*arg) = gtk_scroll_frame_get_vadjustment (sf);
+ break;
+
+ case ARG_HSCROLLBAR_POLICY:
+ GTK_VALUE_ENUM (*arg) = priv->hsb_policy;
+ break;
+
+ case ARG_VSCROLLBAR_POLICY:
+ GTK_VALUE_ENUM (*arg) = priv->vsb_policy;
+ break;
+
+ case ARG_FRAME_PLACEMENT:
+ GTK_VALUE_ENUM (*arg) = priv->frame_placement;
+ break;
+
+ case ARG_SHADOW_TYPE:
+ GTK_VALUE_ENUM (*arg) = priv->shadow_type;
+ break;
+
+ case ARG_SCROLLBAR_SPACING:
+ GTK_VALUE_UINT (*arg) = priv->sb_spacing;
+ break;
+
+ default:
+ arg->type = GTK_TYPE_INVALID;
+ break;
+ }
+}
+
+/* Destroy handler for the scroll frame widget */
+static void
+gtk_scroll_frame_destroy (GtkObject *object)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (object));
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ gtk_widget_unparent (priv->hsb);
+ gtk_widget_unparent (priv->vsb);
+ gtk_widget_destroy (priv->hsb);
+ gtk_widget_destroy (priv->vsb);
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+/* Finalize handler for the scroll frame widget */
+static void
+gtk_scroll_frame_finalize (GtkObject *object)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ gtk_widget_unref (priv->hsb);
+ gtk_widget_unref (priv->vsb);
+
+ g_free (priv);
+
+ if (GTK_OBJECT_CLASS (parent_class)->finalize)
+ (* GTK_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+/* Map handler for the scroll frame widget */
+static void
+gtk_scroll_frame_map (GtkWidget *widget)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ /* chain parent class handler to map self and child */
+ if (GTK_WIDGET_CLASS (parent_class)->map)
+ (* GTK_WIDGET_CLASS (parent_class)->map) (widget);
+
+ if (GTK_WIDGET_VISIBLE (priv->hsb) && !GTK_WIDGET_MAPPED (priv->hsb))
+ gtk_widget_map (priv->hsb);
+
+ if (GTK_WIDGET_VISIBLE (priv->vsb) && !GTK_WIDGET_MAPPED (priv->vsb))
+ gtk_widget_map (priv->vsb);
+}
+
+/* Unmap handler for the scroll frame widget */
+static void
+gtk_scroll_frame_unmap (GtkWidget *widget)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ /* chain parent class handler to unmap self and child */
+ if (GTK_WIDGET_CLASS (parent_class)->unmap)
+ (* GTK_WIDGET_CLASS (parent_class)->unmap) (widget);
+
+ if (GTK_WIDGET_MAPPED (priv->hsb))
+ gtk_widget_unmap (priv->hsb);
+
+ if (GTK_WIDGET_MAPPED (priv->vsb))
+ gtk_widget_unmap (priv->vsb);
+}
+
+/* Draws the shadow of a scroll frame widget */
+static void
+draw_shadow (GtkScrollFrame *sf, GdkRectangle *area)
+{
+ ScrollFramePrivate *priv;
+
+ g_assert (area != NULL);
+
+ priv = sf->priv;
+
+ gtk_paint_shadow (GTK_WIDGET (sf)->style,
+ GTK_WIDGET (sf)->window,
+ GTK_STATE_NORMAL, priv->shadow_type,
+ area, GTK_WIDGET (sf),
+ "scroll_frame",
+ priv->frame_x, priv->frame_y,
+ priv->frame_w, priv->frame_h);
+}
+
+/* Draw handler for the scroll frame widget */
+static void
+gtk_scroll_frame_draw (GtkWidget *widget, GdkRectangle *area)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GdkRectangle child_area;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (area != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ if (GTK_WIDGET_DRAWABLE (widget))
+ draw_shadow (sf, area);
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)
+ && gtk_widget_intersect (bin->child, area, &child_area))
+ gtk_widget_draw (bin->child, &child_area);
+
+ if (GTK_WIDGET_VISIBLE (priv->hsb)
+ && gtk_widget_intersect (priv->hsb, area, &child_area))
+ gtk_widget_draw (priv->hsb, &child_area);
+
+ if (GTK_WIDGET_VISIBLE (priv->vsb)
+ && gtk_widget_intersect (priv->vsb, area, &child_area))
+ gtk_widget_draw (priv->vsb, &child_area);
+}
+
+/* Forall handler for the scroll frame widget */
+static void
+gtk_scroll_frame_forall (GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (container));
+ g_return_if_fail (callback != NULL);
+
+ sf = GTK_SCROLL_FRAME (container);
+ priv = sf->priv;
+
+ if (GTK_CONTAINER_CLASS (parent_class)->forall)
+ (* GTK_CONTAINER_CLASS (parent_class)->forall) (
+ container, include_internals,
+ callback, callback_data);
+
+ if (include_internals) {
+ if (priv->vsb)
+ (* callback) (priv->vsb, callback_data);
+
+ if (priv->hsb)
+ (* callback) (priv->hsb, callback_data);
+ }
+}
+
+/* Size_request handler for the scroll frame widget */
+static void
+gtk_scroll_frame_size_request (GtkWidget *widget, GtkRequisition *requisition)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ gint extra_width;
+ gint extra_height;
+ GtkRequisition hsb_requisition;
+ GtkRequisition vsb_requisition;
+ GtkRequisition child_requisition;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (requisition != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ extra_width = 0;
+ extra_height = 0;
+
+ requisition->width = GTK_CONTAINER (widget)->border_width * 2;
+ requisition->height = GTK_CONTAINER (widget)->border_width * 2;
+
+ if (priv->shadow_type != GTK_SHADOW_NONE) {
+ requisition->width += 2 * widget->style->klass->xthickness;
+ requisition->height += 2 * widget->style->klass->ythickness;
+ }
+
+ gtk_widget_size_request (priv->hsb, &hsb_requisition);
+ gtk_widget_size_request (priv->vsb, &vsb_requisition);
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) {
+ static guint quark_aux_info;
+
+ if (!quark_aux_info)
+ quark_aux_info = g_quark_from_static_string ("gtk-aux-info");
+
+ gtk_widget_size_request (bin->child, &child_requisition);
+
+ if (priv->hsb_policy == GTK_POLICY_NEVER)
+ requisition->width += child_requisition.width;
+ else {
+ GtkWidgetAuxInfo *aux_info;
+
+ aux_info = gtk_object_get_data_by_id (GTK_OBJECT (bin->child),
+ quark_aux_info);
+ if (aux_info && aux_info->width > 0) {
+ requisition->width += aux_info->width;
+ extra_width = -1;
+ } else
+ requisition->width += vsb_requisition.width;
+ }
+
+ if (priv->vsb_policy == GTK_POLICY_NEVER)
+ requisition->height += child_requisition.height;
+ else {
+ GtkWidgetAuxInfo *aux_info;
+
+ aux_info = gtk_object_get_data_by_id (GTK_OBJECT (bin->child),
+ quark_aux_info);
+ if (aux_info && aux_info->height > 0) {
+ requisition->height += aux_info->height;
+ extra_height = -1;
+ } else
+ requisition->height += hsb_requisition.height;
+ }
+ }
+
+ if (priv->hsb_policy == GTK_POLICY_AUTOMATIC || GTK_WIDGET_VISIBLE (priv->hsb)) {
+ requisition->width = MAX (requisition->width, hsb_requisition.width);
+ if (!extra_height || GTK_WIDGET_VISIBLE (priv->hsb))
+ extra_height = priv->sb_spacing + hsb_requisition.height;
+ }
+
+ if (priv->vsb_policy == GTK_POLICY_AUTOMATIC || GTK_WIDGET_VISIBLE (priv->vsb)) {
+ requisition->height = MAX (requisition->height, vsb_requisition.height);
+ if (!extra_width || GTK_WIDGET_VISIBLE (priv->vsb))
+ extra_width = priv->sb_spacing + vsb_requisition.width;
+ }
+
+ requisition->width += MAX (0, extra_width);
+ requisition->height += MAX (0, extra_height);
+}
+
+/* Computes the relative allocation for the scroll frame widget */
+static void
+compute_relative_allocation (GtkWidget *widget, GtkAllocation *allocation)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_assert (widget != NULL);
+ g_assert (GTK_IS_SCROLL_FRAME (widget));
+ g_assert (allocation != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ allocation->x = GTK_CONTAINER (widget)->border_width;
+ allocation->y = GTK_CONTAINER (widget)->border_width;
+ allocation->width = MAX (1, (gint) widget->allocation.width - allocation->x * 2);
+ allocation->height = MAX (1, (gint) widget->allocation.height - allocation->y * 2);
+
+ if (priv->vsb_visible) {
+ GtkRequisition vsb_requisition;
+
+ gtk_widget_get_child_requisition (priv->vsb, &vsb_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_TOP_RIGHT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_RIGHT)
+ allocation->x += vsb_requisition.width + priv->sb_spacing;
+
+ allocation->width = MAX (1, ((gint) allocation->width
+ - ((gint) vsb_requisition.width + priv->sb_spacing)));
+ }
+
+ if (priv->hsb_visible) {
+ GtkRequisition hsb_requisition;
+
+ gtk_widget_get_child_requisition (priv->hsb, &hsb_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_BOTTOM_LEFT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_RIGHT)
+ allocation->y += hsb_requisition.height + priv->sb_spacing;
+
+ allocation->height = MAX (1, ((gint) allocation->height
+ - ((gint) hsb_requisition.height + priv->sb_spacing)));
+ }
+}
+
+/* Size_allocate handler for the scroll frame widget */
+static void
+gtk_scroll_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GtkAllocation relative_allocation;
+ GtkAllocation child_allocation;
+ gint xthickness, ythickness;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (allocation != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ widget->allocation = *allocation;
+
+ if (priv->hsb_policy == GTK_POLICY_ALWAYS)
+ priv->hsb_visible = TRUE;
+ else if (priv->hsb_policy == GTK_POLICY_NEVER)
+ priv->hsb_visible = FALSE;
+
+ if (priv->vsb_policy == GTK_POLICY_ALWAYS)
+ priv->vsb_visible = TRUE;
+ else if (priv->vsb_policy == GTK_POLICY_NEVER)
+ priv->vsb_visible = FALSE;
+
+ if (priv->shadow_type == GTK_SHADOW_NONE) {
+ xthickness = 0;
+ ythickness = 0;
+ } else {
+ xthickness = widget->style->klass->xthickness;
+ ythickness = widget->style->klass->ythickness;
+ }
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) {
+ gboolean previous_hvis;
+ gboolean previous_vvis;
+ guint count = 0;
+
+ do {
+ compute_relative_allocation (widget, &relative_allocation);
+
+ priv->frame_x = relative_allocation.x + allocation->x;
+ priv->frame_y = relative_allocation.y + allocation->y;
+ priv->frame_w = relative_allocation.width;
+ priv->frame_h = relative_allocation.height;
+
+ child_allocation.x = priv->frame_x + xthickness;
+ child_allocation.y = priv->frame_y + ythickness;
+ child_allocation.width = priv->frame_w - 2 * xthickness;
+ child_allocation.height = priv->frame_h - 2 * ythickness;
+
+ previous_hvis = priv->hsb_visible;
+ previous_vvis = priv->vsb_visible;
+
+ gtk_widget_size_allocate (bin->child, &child_allocation);
+
+ /* If, after the first iteration, the hscrollbar and the
+ * vscrollbar flip visiblity, then we need both.
+ */
+ if (count
+ && previous_hvis != priv->hsb_visible
+ && previous_vvis != priv->vsb_visible) {
+ priv->hsb_visible = TRUE;
+ priv->vsb_visible = TRUE;
+
+ /* a new resize is already queued at this point,
+ * so we will immediatedly get reinvoked
+ */
+ return;
+ }
+
+ count++;
+ } while (previous_hvis != priv->hsb_visible
+ || previous_vvis != priv->vsb_visible);
+ } else
+ compute_relative_allocation (widget, &relative_allocation);
+
+ if (priv->hsb_visible) {
+ GtkRequisition hscrollbar_requisition;
+
+ gtk_widget_get_child_requisition (priv->hsb, &hscrollbar_requisition);
+
+ if (!GTK_WIDGET_VISIBLE (priv->hsb))
+ gtk_widget_show (priv->hsb);
+
+ child_allocation.x = relative_allocation.x;
+ if (priv->frame_placement == GTK_CORNER_TOP_LEFT
+ || priv->frame_placement == GTK_CORNER_TOP_RIGHT)
+ child_allocation.y = (relative_allocation.y
+ + relative_allocation.height
+ + priv->sb_spacing);
+ else
+ child_allocation.y = GTK_CONTAINER (sf)->border_width;
+
+ child_allocation.width = relative_allocation.width;
+ child_allocation.height = hscrollbar_requisition.height;
+ child_allocation.x += allocation->x;
+ child_allocation.y += allocation->y;
+
+ gtk_widget_size_allocate (priv->hsb, &child_allocation);
+ } else if (GTK_WIDGET_VISIBLE (priv->hsb))
+ gtk_widget_hide (priv->hsb);
+
+ if (priv->vsb_visible) {
+ GtkRequisition vscrollbar_requisition;
+
+ if (!GTK_WIDGET_VISIBLE (priv->vsb))
+ gtk_widget_show (priv->vsb);
+
+ gtk_widget_get_child_requisition (priv->vsb, &vscrollbar_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_TOP_LEFT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_LEFT)
+ child_allocation.x = (relative_allocation.x
+ + relative_allocation.width
+ + priv->sb_spacing);
+ else
+ child_allocation.x = GTK_CONTAINER (sf)->border_width;
+
+ child_allocation.y = relative_allocation.y;
+ child_allocation.width = vscrollbar_requisition.width;
+ child_allocation.height = relative_allocation.height;
+ child_allocation.x += allocation->x;
+ child_allocation.y += allocation->y;
+
+ gtk_widget_size_allocate (priv->vsb, &child_allocation);
+ } else if (GTK_WIDGET_VISIBLE (priv->vsb))
+ gtk_widget_hide (priv->vsb);
+}
+
+/* Expose handler for the scroll frame widget */
+static gint
+gtk_scroll_frame_expose (GtkWidget *widget, GdkEventExpose *event)
+{
+ GtkScrollFrame *sf;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ sf = GTK_SCROLL_FRAME (widget);
+
+ if (GTK_WIDGET_DRAWABLE (widget))
+ draw_shadow (sf, &event->area);
+
+ if (GTK_WIDGET_CLASS (parent_class)->expose_event)
+ (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
+
+ return FALSE;
+}
+
+/* Add handler for the scroll frame widget */
+static void
+gtk_scroll_frame_add (GtkContainer *container, GtkWidget *child)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+
+ sf = GTK_SCROLL_FRAME (container);
+ priv = sf->priv;
+ bin = GTK_BIN (container);
+ g_return_if_fail (bin->child == NULL);
+
+ bin->child = child;
+ gtk_widget_set_parent (child, GTK_WIDGET (bin));
+
+ /* this is a temporary message */
+ if (!gtk_widget_set_scroll_adjustments (child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb))))
+ g_warning ("gtk_scroll_frame_add(): cannot add non scrollable widget "
+ "use gtk_scroll_frame_add_with_viewport() instead");
+
+ if (GTK_WIDGET_REALIZED (child->parent))
+ gtk_widget_realize (child);
+
+ if (GTK_WIDGET_VISIBLE (child->parent) && GTK_WIDGET_VISIBLE (child)) {
+ if (GTK_WIDGET_MAPPED (child->parent))
+ gtk_widget_map (child);
+
+ gtk_widget_queue_resize (child);
+ }
+}
+
+/* Remove method for the scroll frame widget */
+static void
+gtk_scroll_frame_remove (GtkContainer *container, GtkWidget *child)
+{
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (container));
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_BIN (container)->child == child);
+
+ gtk_widget_set_scroll_adjustments (child, NULL, NULL);
+
+ /* chain parent class handler to remove child */
+ if (GTK_CONTAINER_CLASS (parent_class)->remove)
+ (* GTK_CONTAINER_CLASS (parent_class)->remove) (container, child);
+}
+
+/**
+ * gtk_scroll_frame_new:
+ * @hadj: If non-NULL, the adjustment to use for horizontal scrolling.
+ * @vadj: If non-NULL, the adjustment to use for vertical scrolling.
+ *
+ * Creates a new scroll frame widget.
+ *
+ * Return value: The newly-created scroll frame widget.
+ **/
+GtkWidget *
+gtk_scroll_frame_new (GtkAdjustment *hadj, GtkAdjustment *vadj)
+{
+ if (hadj)
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (hadj), NULL);
+
+ if (vadj)
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (vadj), NULL);
+
+ return gtk_widget_new (GTK_TYPE_SCROLL_FRAME,
+ "hadjustment", hadj,
+ "vadjustment", vadj,
+ NULL);
+}
+
+/* Callback used when one of the scroll frame widget's adjustments changes */
+static void
+adjustment_changed (GtkAdjustment *adj, gpointer data)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (adj != NULL);
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ g_return_if_fail (data != NULL);
+
+ sf = GTK_SCROLL_FRAME (data);
+ priv = sf->priv;
+
+ if (adj == gtk_range_get_adjustment (GTK_RANGE (priv->hsb))) {
+ if (priv->hsb_policy == GTK_POLICY_AUTOMATIC) {
+ gboolean visible;
+
+ visible = priv->hsb_visible;
+ priv->hsb_visible = (adj->upper - adj->lower > adj->page_size);
+ if (priv->hsb_visible != visible)
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+ }
+ } else if (adj == gtk_range_get_adjustment (GTK_RANGE (priv->vsb))) {
+ if (priv->vsb_policy == GTK_POLICY_AUTOMATIC) {
+ gboolean visible;
+
+ visible = priv->vsb_visible;
+ priv->vsb_visible = (adj->upper - adj->lower > adj->page_size);
+ if (priv->vsb_visible != visible)
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+ }
+ }
+}
+
+/**
+ * gtk_scroll_frame_set_hadjustment:
+ * @sf: A scroll frame widget.
+ * @adj: An adjustment.
+ *
+ * Sets the adjustment to be used for horizontal scrolling in a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_hadjustment (GtkScrollFrame *sf, GtkAdjustment *adj)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (adj)
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ else
+ adj = GTK_ADJUSTMENT (gtk_object_new (GTK_TYPE_ADJUSTMENT, NULL));
+
+ if (!priv->hsb) {
+ gtk_widget_push_composite_child ();
+ priv->hsb = gtk_hscrollbar_new (adj);
+ gtk_widget_set_composite_name (priv->hsb, "hscrollbar");
+ gtk_widget_pop_composite_child ();
+
+ gtk_widget_set_parent (priv->hsb, GTK_WIDGET (sf));
+ gtk_widget_ref (priv->hsb);
+ gtk_widget_show (priv->hsb);
+ } else {
+ GtkAdjustment *old_adj;
+
+ old_adj = gtk_range_get_adjustment (GTK_RANGE (priv->hsb));
+ if (old_adj == adj)
+ return;
+
+ gtk_signal_disconnect_by_func (GTK_OBJECT (old_adj),
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ gtk_range_set_adjustment (GTK_RANGE (priv->hsb), adj);
+ }
+
+ adj = gtk_range_get_adjustment (GTK_RANGE (priv->hsb));
+ gtk_signal_connect (GTK_OBJECT (adj),
+ "changed",
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ adjustment_changed (adj, sf);
+
+ if (GTK_BIN (sf)->child)
+ gtk_widget_set_scroll_adjustments (
+ GTK_BIN (sf)->child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb)));
+}
+
+/**
+ * gtk_scroll_frame_set_vadjustment:
+ * @sf: A scroll frame widget.
+ * @adj: An adjustment.
+ *
+ * Sets the adjustment to be used for vertical scrolling in a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_vadjustment (GtkScrollFrame *sf, GtkAdjustment *adj)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (adj)
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ else
+ adj = GTK_ADJUSTMENT (gtk_object_new (GTK_TYPE_ADJUSTMENT, NULL));
+
+ if (!priv->vsb) {
+ gtk_widget_push_composite_child ();
+ priv->vsb = gtk_vscrollbar_new (adj);
+ gtk_widget_set_composite_name (priv->vsb, "vscrollbar");
+ gtk_widget_pop_composite_child ();
+
+ gtk_widget_set_parent (priv->vsb, GTK_WIDGET (sf));
+ gtk_widget_ref (priv->vsb);
+ gtk_widget_show (priv->vsb);
+ } else {
+ GtkAdjustment *old_adj;
+
+ old_adj = gtk_range_get_adjustment (GTK_RANGE (priv->vsb));
+ if (old_adj == adj)
+ return;
+
+ gtk_signal_disconnect_by_func (GTK_OBJECT (old_adj),
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ gtk_range_set_adjustment (GTK_RANGE (priv->vsb), adj);
+ }
+
+ adj = gtk_range_get_adjustment (GTK_RANGE (priv->vsb));
+ gtk_signal_connect (GTK_OBJECT (adj),
+ "changed",
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ adjustment_changed (adj, sf);
+
+ if (GTK_BIN (sf)->child)
+ gtk_widget_set_scroll_adjustments (
+ GTK_BIN (sf)->child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb)));
+}
+
+/**
+ * gtk_scroll_frame_get_hadjustment:
+ * @sf: A scroll frame widget.
+ *
+ * Queries the horizontal adjustment of a scroll frame widget.
+ *
+ * Return value: The horizontal adjustment of the scroll frame, or NULL if none.
+ **/
+GtkAdjustment *
+gtk_scroll_frame_get_hadjustment (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_val_if_fail (sf != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (sf), NULL);
+
+ priv = sf->priv;
+
+ return priv->hsb ? gtk_range_get_adjustment (GTK_RANGE (priv->hsb)) : NULL;
+}
+
+/**
+ * gtk_scroll_frame_get_vadjustment:
+ * @sf: A scroll frame widget.
+ *
+ * Queries the vertical adjustment of a scroll frame widget.
+ *
+ * Return value: The vertical adjustment of the scroll frame, or NULL if none.
+ **/
+GtkAdjustment *
+gtk_scroll_frame_get_vadjustment (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_val_if_fail (sf != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (sf), NULL);
+
+ priv = sf->priv;
+
+ return priv->vsb ? gtk_range_get_adjustment (GTK_RANGE (priv->vsb)) : NULL;
+}
+
+/**
+ * gtk_scroll_frame_set_policy:
+ * @sf: A scroll frame widget.
+ * @hsb_policy: Policy for the horizontal scrollbar.
+ * @vsb_policy: Policy for the vertical scrollbar.
+ *
+ * Sets the scrollbar policies of a scroll frame widget. These determine when
+ * the scrollbars are to be shown or hidden.
+ **/
+void
+gtk_scroll_frame_set_policy (GtkScrollFrame *sf,
+ GtkPolicyType hsb_policy,
+ GtkPolicyType vsb_policy)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->hsb_policy == hsb_policy && priv->vsb_policy == vsb_policy)
+ return;
+
+ priv->hsb_policy = hsb_policy;
+ priv->vsb_policy = vsb_policy;
+
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_placement:
+ * @sf: A scroll frame widget.
+ * @frame_placement: Placement for the frame.
+ *
+ * Sets the placement of a scroll frame widget's frame with respect to its
+ * scrollbars.
+ **/
+void
+gtk_scroll_frame_set_placement (GtkScrollFrame *sf, GtkCornerType frame_placement)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->frame_placement == frame_placement)
+ return;
+
+ priv->frame_placement = frame_placement;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_shadow_type:
+ * @sf: A scroll frame widget.
+ * @shadow_type: A shadow type.
+ *
+ * Sets the shadow type of a scroll frame widget. You can use this when you
+ * insert a child that does not paint a frame on its own.
+ **/
+void
+gtk_scroll_frame_set_shadow_type (GtkScrollFrame *sf, GtkShadowType shadow_type)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+ g_return_if_fail (shadow_type >= GTK_SHADOW_NONE && shadow_type <= GTK_SHADOW_ETCHED_OUT);
+
+ priv = sf->priv;
+
+ if (priv->shadow_type == shadow_type)
+ return;
+
+ priv->shadow_type = shadow_type;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_scrollbar_spacing:
+ * @sf: A scroll frame widget.
+ * @spacing: Desired spacing in pixels.
+ *
+ * Sets the spacing between the frame and the scrollbars of a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_scrollbar_spacing (GtkScrollFrame *sf, guint spacing)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->sb_spacing == spacing)
+ return;
+
+ priv->sb_spacing = spacing;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_add_with_viewport:
+ * @sf: A scroll frame widget.
+ * @child: A widget.
+ *
+ * Creates a &GtkViewport and puts the specified child inside it, thus allowing
+ * the viewport to be scrolled by the scroll frame widget. This is meant to be
+ * used only when a child does not support the scrolling interface.
+ **/
+void
+gtk_scroll_frame_add_with_viewport (GtkScrollFrame *sf, GtkWidget *child)
+{
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GtkWidget *viewport;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (child));
+ g_return_if_fail (child->parent == NULL);
+
+ priv = sf->priv;
+ bin = GTK_BIN (sf);
+
+ if (bin->child != NULL) {
+ g_return_if_fail (GTK_IS_VIEWPORT (bin->child));
+ g_return_if_fail (GTK_BIN (bin->child)->child == NULL);
+
+ viewport = bin->child;
+ } else {
+ viewport = gtk_viewport_new (gtk_scroll_frame_get_hadjustment (sf),
+ gtk_scroll_frame_get_vadjustment (sf));
+ gtk_container_add (GTK_CONTAINER (sf), viewport);
+ }
+
+ gtk_widget_show (viewport);
+ gtk_container_add (GTK_CONTAINER (viewport), child);
+}
diff --git a/libnautilus-extensions/gtkscrollframe.h b/libnautilus-extensions/gtkscrollframe.h
new file mode 100644
index 000000000..facb59dac
--- /dev/null
+++ b/libnautilus-extensions/gtkscrollframe.h
@@ -0,0 +1,93 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This 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.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __GTK_SCROLL_FRAME_H__
+#define __GTK_SCROLL_FRAME_H__
+
+
+#include <gdk/gdk.h>
+#include <gtk/gtkbin.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define GTK_TYPE_SCROLL_FRAME (gtk_scroll_frame_get_type ())
+#define GTK_SCROLL_FRAME(obj) (GTK_CHECK_CAST ((obj), \
+ GTK_TYPE_SCROLL_FRAME, GtkScrollFrame))
+#define GTK_SCROLL_FRAME_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), \
+ GTK_TYPE_SCROLL_FRAME, GtkScrollFrameClass))
+#define GTK_IS_SCROLL_FRAME(obj) (GTK_CHECK_TYPE ((obj), \
+ GTK_TYPE_SCROLL_FRAME))
+#define GTK_IS_SCROLL_FRAME_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), \
+ GTK_TYPE_SCROLL_FRAME))
+
+
+typedef struct _GtkScrollFrame GtkScrollFrame;
+typedef struct _GtkScrollFrameClass GtkScrollFrameClass;
+
+struct _GtkScrollFrame
+{
+ GtkBin bin;
+
+ /* Private data */
+ gpointer priv;
+};
+
+struct _GtkScrollFrameClass
+{
+ GtkBinClass parent_class;
+};
+
+
+GtkType gtk_scroll_frame_get_type (void);
+GtkWidget *gtk_scroll_frame_new (GtkAdjustment *hadj, GtkAdjustment *vadj);
+
+void gtk_scroll_frame_set_hadjustment (GtkScrollFrame *sf, GtkAdjustment *adj);
+void gtk_scroll_frame_set_vadjustment (GtkScrollFrame *sf, GtkAdjustment *adj);
+
+GtkAdjustment *gtk_scroll_frame_get_hadjustment (GtkScrollFrame *sf);
+GtkAdjustment *gtk_scroll_frame_get_vadjustment (GtkScrollFrame *sf);
+
+void gtk_scroll_frame_set_policy (GtkScrollFrame *sf,
+ GtkPolicyType hsb_policy,
+ GtkPolicyType vsb_policy);
+
+void gtk_scroll_frame_set_placement (GtkScrollFrame *sf, GtkCornerType frame_placement);
+void gtk_scroll_frame_set_shadow_type (GtkScrollFrame *sf, GtkShadowType shadow_type);
+void gtk_scroll_frame_set_scrollbar_spacing (GtkScrollFrame *sf, guint spacing);
+
+void gtk_scroll_frame_add_with_viewport (GtkScrollFrame *sf, GtkWidget *child);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __GTK_SCROLL_FRAME_H__ */
diff --git a/libnautilus-extensions/nautilus-file-operations-progress.c b/libnautilus-extensions/nautilus-file-operations-progress.c
new file mode 100644
index 000000000..81b3b8b8b
--- /dev/null
+++ b/libnautilus-extensions/nautilus-file-operations-progress.c
@@ -0,0 +1,351 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* dfos-xfer-progress-dialog.c - Progress dialog for transfer operations in the
+ GNOME Desktop File Operation Service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Ettore Perazzoli <ettore@gnu.org> */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gnome.h>
+
+#include "dfos-xfer-progress-dialog.h"
+
+
+#define DIALOG_WIDTH 350 /* FIXME? */
+
+
+static GnomeDialogClass *parent_class;
+
+
+/* Private functions. */
+
+static void
+update (DFOSXferProgressDialog *dialog)
+{
+ gtk_progress_configure (GTK_PROGRESS (dialog->progress_bar),
+ dialog->total_bytes_copied,
+ 0.0, dialog->bytes_total);
+}
+
+/* This code by Jonathan Blandford (jrb@redhat.com) was shamelessly ripped from
+ `gnome/gdialog.c' in Midnight Commander with minor changes. */
+static gchar *
+trim_string (const gchar *string,
+ GdkFont *font,
+ guint length,
+ guint cur_length)
+{
+ static guint dotdotdot = 0;
+ gchar *string_copy = NULL;
+ gint len;
+
+ if (!dotdotdot)
+ dotdotdot = gdk_string_width (font, "...");
+
+ /* Cut the font length of string to length. */
+
+ length -= dotdotdot;
+ len = (gint) ((1.0 - (gfloat) length / (gfloat) cur_length)
+ * strlen (string));
+
+ /* we guess a starting point */
+ if (gdk_string_width (font, string + len) < length) {
+ while (gdk_string_width (font, string + len) < length)
+ len --;
+ len++;
+ } else {
+ while (gdk_string_width (font, string + len) > length)
+ len ++;
+ }
+
+ string_copy = g_strdup_printf ("...%s", string + len);
+ return string_copy;
+}
+
+static void
+set_text_trimmed (GtkLabel *label,
+ const gchar *text,
+ const gchar *trimmable_text,
+ guint max_width)
+{
+ GdkFont *font;
+ gchar *trimmed_text;
+ gchar *s;
+ guint text_width;
+ guint trimmable_text_width;
+
+ font = GTK_WIDGET (label)->style->font;
+
+ if (text != NULL)
+ text_width = gdk_string_width (font, text);
+ else
+ text_width = 0;
+
+ if (trimmable_text != NULL)
+ trimmable_text_width = gdk_string_width (font, trimmable_text);
+ else
+ trimmable_text_width = 0;
+
+ if (text_width + trimmable_text_width <= max_width) {
+ s = g_strconcat (text, trimmable_text, NULL);
+ gtk_label_set_text (GTK_LABEL (label), s);
+ g_free (s);
+ return;
+ }
+
+ trimmed_text = trim_string (trimmable_text,
+ font,
+ max_width - text_width,
+ trimmable_text_width);
+ s = g_strconcat (text, trimmed_text, NULL);
+
+ gtk_label_set_text (GTK_LABEL (label), s);
+
+ g_free (s);
+ g_free (trimmed_text);
+}
+
+
+/* GnomeDialog signals. */
+
+/* This is just to make sure the dialog is not closed without explicit
+ intervention. */
+static gboolean
+do_close (GnomeDialog *dialog)
+{
+ DFOSXferProgressDialog *progress_dialog;
+
+ progress_dialog = DFOS_XFER_PROGRESS_DIALOG (dialog);
+ return FALSE;
+}
+
+
+/* GtkObject methods. */
+
+static void
+destroy (GtkObject *object)
+{
+ DFOSXferProgressDialog *dialog;
+
+ dialog = DFOS_XFER_PROGRESS_DIALOG (object);
+
+ g_free (dialog->operation_string);
+}
+
+
+/* Initialization. */
+
+static GtkWidget *
+create_label_in_box (GtkBox *vbox)
+{
+ GtkWidget *new;
+
+ new = gtk_label_new ("");
+ gtk_label_set_justify (GTK_LABEL (new), GTK_JUSTIFY_LEFT);
+ gtk_box_pack_start (vbox, new, TRUE, TRUE, 0);
+ gtk_widget_show (new);
+
+ return new;
+}
+
+static void
+init (DFOSXferProgressDialog *dialog)
+{
+ GnomeDialog *gnome_dialog;
+ GtkBox *vbox;
+
+ gnome_dialog = GNOME_DIALOG (dialog);
+ vbox = GTK_BOX (gnome_dialog->vbox);
+
+ dialog->operation_label = create_label_in_box (vbox);
+ dialog->source_label = create_label_in_box (vbox);
+ dialog->target_label = create_label_in_box (vbox);
+
+ dialog->progress_bar = gtk_progress_bar_new ();
+ gtk_progress_bar_set_bar_style (GTK_PROGRESS_BAR (dialog->progress_bar),
+ GTK_PROGRESS_CONTINUOUS);
+ gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (dialog->progress_bar),
+ GTK_PROGRESS_LEFT_TO_RIGHT);
+ gtk_widget_set_usize (GTK_WIDGET (dialog->progress_bar), DIALOG_WIDTH,
+ -1);
+ gtk_box_pack_start (vbox, dialog->progress_bar, FALSE, TRUE, 0);
+ gtk_widget_show (dialog->progress_bar);
+
+ dialog->operation_string = NULL;
+
+ dialog->file_index = 0;
+ dialog->file_size = 0;
+ dialog->files_total = 0;
+ dialog->bytes_total = 0;
+ dialog->bytes_copied = 0;
+ dialog->total_bytes_copied = 0;
+
+ dialog->freeze_count = 0;
+}
+
+static void
+class_init (DFOSXferProgressDialogClass *class)
+{
+ GtkObjectClass *object_class;
+ GnomeDialogClass *dialog_class;
+
+ parent_class = gtk_type_class (gnome_dialog_get_type ());
+
+ object_class = GTK_OBJECT_CLASS (class);
+ dialog_class = GNOME_DIALOG_CLASS (class);
+
+ object_class->destroy = destroy;
+
+ dialog_class->close = do_close;
+}
+
+
+/* Public functions. */
+
+guint
+dfos_xfer_progress_dialog_get_type (void)
+{
+ static guint type = 0;
+
+ if (type == 0) {
+ GtkTypeInfo info = {
+ "DFOSXferProgressDialog",
+ sizeof (DFOSXferProgressDialog),
+ sizeof (DFOSXferProgressDialogClass),
+ (GtkClassInitFunc) class_init,
+ (GtkObjectInitFunc) init,
+ NULL,
+ NULL
+ };
+
+ type = gtk_type_unique (gnome_dialog_get_type (), &info);
+ }
+
+ return type;
+}
+
+GtkWidget *
+dfos_xfer_progress_dialog_new (const gchar *title,
+ const gchar *operation_string,
+ gulong total_files,
+ gulong total_bytes)
+{
+ GtkWidget *new;
+
+ new = gtk_type_new (dfos_xfer_progress_dialog_get_type ());
+
+ dfos_xfer_progress_dialog_set_operation_string (DFOS_XFER_PROGRESS_DIALOG (new),
+ operation_string);
+ dfos_xfer_progress_dialog_set_total (DFOS_XFER_PROGRESS_DIALOG (new),
+ total_files, total_bytes);
+
+ gtk_window_set_title (GTK_WINDOW (new), title);
+
+ gnome_dialog_append_button (GNOME_DIALOG (new),
+ GNOME_STOCK_BUTTON_CANCEL);
+
+ return new;
+}
+
+void
+dfos_xfer_progress_dialog_set_total (DFOSXferProgressDialog *dialog,
+ gulong files_total,
+ gulong bytes_total)
+{
+ g_return_if_fail (IS_DFOS_XFER_PROGRESS_DIALOG (dialog));
+
+ dialog->files_total = files_total;
+ dialog->bytes_total = bytes_total;
+
+ update (dialog);
+}
+
+void
+dfos_xfer_progress_dialog_set_operation_string (DFOSXferProgressDialog *dialog,
+ const gchar *operation_string)
+{
+ g_return_if_fail (IS_DFOS_XFER_PROGRESS_DIALOG (dialog));
+
+ gtk_label_set_text (GTK_LABEL (dialog->operation_label),
+ operation_string);
+ dialog->operation_string = g_strdup (operation_string);
+}
+
+void
+dfos_xfer_progress_dialog_new_file (DFOSXferProgressDialog *dialog,
+ const gchar *source_uri,
+ const gchar *target_uri,
+ gulong size)
+{
+ gchar *s;
+
+ g_return_if_fail (IS_DFOS_XFER_PROGRESS_DIALOG (dialog));
+ g_return_if_fail (GTK_WIDGET_REALIZED (dialog));
+
+ dialog->file_index++;
+ dialog->bytes_copied = 0;
+ dialog->file_size = size;
+
+ s = g_strdup_printf ("%s file %ld/%ld",
+ dialog->operation_string,
+ dialog->file_index, dialog->files_total);
+ gtk_label_set_text (GTK_LABEL (dialog->operation_label), s);
+ g_free (s);
+
+ set_text_trimmed (GTK_LABEL (dialog->source_label),
+ _("From: "), source_uri,
+ DIALOG_WIDTH);
+
+ set_text_trimmed (GTK_LABEL (dialog->target_label),
+ _("To: "), target_uri,
+ DIALOG_WIDTH);
+
+ update (dialog);
+}
+
+void
+dfos_xfer_progress_dialog_update (DFOSXferProgressDialog *dialog,
+ gulong bytes_done_in_file,
+ gulong bytes_done)
+{
+ g_return_if_fail (IS_DFOS_XFER_PROGRESS_DIALOG (dialog));
+
+ dialog->bytes_copied = bytes_done_in_file;
+ dialog->total_bytes_copied = bytes_done;
+
+ update (dialog);
+}
+
+
+void
+dfos_xfer_progress_dialog_freeze (DFOSXferProgressDialog *dialog)
+{
+ dialog->freeze_count++;
+}
+
+void
+dfos_xfer_progress_dialog_thaw (DFOSXferProgressDialog *dialog)
+{
+ if (dialog->freeze_count > 0)
+ dialog->freeze_count--;
+}
+
diff --git a/libnautilus-extensions/nautilus-file-operations-progress.h b/libnautilus-extensions/nautilus-file-operations-progress.h
new file mode 100644
index 000000000..657be9be3
--- /dev/null
+++ b/libnautilus-extensions/nautilus-file-operations-progress.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* xfer-progress-dialog.h - Progress dialog for transfer operations in the
+ GNOME Desktop File Operation Service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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 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; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Ettore Perazzoli <ettore@gnu.org> */
+
+#ifndef _DFOS_XFER_PROGRESS_DIALOG_H
+#define _DFOS_XFER_PROGRESS_DIALOG_H
+
+#include <libgnomeui/gnome-dialog.h>
+
+
+#define DFOS_XFER_PROGRESS_DIALOG(obj) \
+ GTK_CHECK_CAST (obj, dfos_xfer_progress_dialog_get_type (), DFOSXferProgressDialog)
+#define DFOS_XFER_PROGRESS_DIALOG_CLASS(klass) \
+ GTK_CHECK_CLASS_CAST (klass, dfos_xfer_progress_dialog_get_type (), DFOSXferProgressDialogClass)
+#define IS_DFOS_XFER_PROGRESS_DIALOG(obj) \
+ GTK_CHECK_TYPE (obj, dfos_xfer_progress_dialog_get_type ())
+
+
+struct _DFOSXferProgressDialog {
+ GnomeDialog dialog;
+
+ GtkWidget *operation_label;
+ GtkWidget *source_label;
+ GtkWidget *target_label;
+ GtkWidget *progress_bar;
+
+ gchar *operation_string;
+
+ guint freeze_count;
+
+ gulong file_index;
+ gulong file_size;
+
+ gulong bytes_copied;
+ gulong total_bytes_copied;
+
+ gulong files_total;
+ gulong bytes_total;
+};
+typedef struct _DFOSXferProgressDialog DFOSXferProgressDialog;
+
+struct _DFOSXferProgressDialogClass {
+ GnomeDialogClass parent_class;
+};
+typedef struct _DFOSXferProgressDialogClass DFOSXferProgressDialogClass;
+
+
+guint dfos_xfer_progress_dialog_get_type
+ (void);
+
+GtkWidget *dfos_xfer_progress_dialog_new (const gchar *title,
+ const gchar *operation_string,
+ gulong files_total,
+ gulong bytes_total);
+
+void dfos_xfer_progress_dialog_set_total
+ (DFOSXferProgressDialog *dialog,
+ gulong files_total,
+ gulong bytes_total);
+
+void dfos_xfer_progress_dialog_set_operation_string
+ (DFOSXferProgressDialog *dialog,
+ const gchar *operation_string);
+
+void dfos_xfer_progress_dialog_new_file
+ (DFOSXferProgressDialog *dialog,
+ const gchar *source_uri,
+ const gchar *target_uri,
+ gulong size);
+
+void dfos_xfer_progress_dialog_update
+ (DFOSXferProgressDialog *dialog,
+ gulong bytes_done_in_file,
+ gulong bytes_done);
+
+void dfos_xfer_progress_dialog_freeze
+ (DFOSXferProgressDialog *dialog);
+
+void dfos_xfer_progress_dialog_thaw (DFOSXferProgressDialog *dialog);
+
+#endif /* _DFOS_XFER_PROGRESS_DIALOG_H */
diff --git a/libnautilus-extensions/nautilus-file-operations.c b/libnautilus-extensions/nautilus-file-operations.c
new file mode 100644
index 000000000..4e1821eaf
--- /dev/null
+++ b/libnautilus-extensions/nautilus-file-operations.c
@@ -0,0 +1,252 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* xfer.c - GNOME::Desktop::FileOperationService transfer service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org> */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gnome.h>
+#include <libgnomevfs/gnome-vfs.h>
+
+#include "dfos.h"
+#include "error.h"
+
+#include "dfos-xfer.h"
+
+
+struct _XferInfo {
+ GnomeVFSAsyncHandle *handle;
+ GtkWidget *progress_dialog;
+ GnomeVFSXferOptions options;
+ GnomeVFSXferErrorMode error_mode;
+ GnomeVFSXferOverwriteMode overwrite_mode;
+};
+typedef struct _XferInfo XferInfo;
+
+
+static XferInfo *
+xfer_info_new (GnomeVFSAsyncHandle *handle,
+ GnomeVFSXferOptions options,
+ GnomeVFSXferErrorMode error_mode,
+ GnomeVFSXferOverwriteMode overwrite_mode)
+{
+ XferInfo *new;
+
+ new = g_new (XferInfo, 1);
+
+ new->handle = handle;
+ new->options = options;
+ new->error_mode = error_mode;
+ new->overwrite_mode = overwrite_mode;
+
+ new->progress_dialog = NULL;
+
+ return new;
+}
+
+static void
+xfer_info_destroy (XferInfo *info)
+{
+ g_free (info);
+}
+
+
+static void
+xfer_dialog_clicked_callback (DFOSXferProgressDialog *dialog,
+ gint button_number,
+ gpointer data)
+{
+ XferInfo *info;
+
+ info = (XferInfo *) data;
+ gnome_vfs_async_cancel (info->handle);
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ g_warning (_("Operation cancelled"));
+}
+
+static void
+create_xfer_dialog (const GnomeVFSXferProgressInfo *progress_info,
+ XferInfo *xfer_info)
+{
+ gchar *op_string;
+
+ g_return_if_fail (xfer_info->progress_dialog == NULL);
+
+ if (xfer_info->options & GNOME_VFS_XFER_REMOVESOURCE)
+ op_string = _("Moving");
+ else
+ op_string = _("Copying");
+
+ xfer_info->progress_dialog
+ = dfos_xfer_progress_dialog_new ("Transfer in progress",
+ op_string,
+ progress_info->files_total,
+ progress_info->bytes_total);
+
+ gtk_signal_connect (GTK_OBJECT (xfer_info->progress_dialog),
+ "clicked",
+ GTK_SIGNAL_FUNC (xfer_dialog_clicked_callback),
+ xfer_info);
+
+ gtk_widget_show (xfer_info->progress_dialog);
+}
+
+static gint
+handle_xfer_ok (const GnomeVFSXferProgressInfo *progress_info,
+ XferInfo *xfer_info)
+{
+ switch (progress_info->phase) {
+ case GNOME_VFS_XFER_PHASE_READYTOGO:
+ create_xfer_dialog (progress_info, xfer_info);
+ return TRUE;
+ case GNOME_VFS_XFER_PHASE_XFERRING:
+ if (progress_info->bytes_copied == 0) {
+ dfos_xfer_progress_dialog_new_file
+ (DFOS_XFER_PROGRESS_DIALOG
+ (xfer_info->progress_dialog),
+ progress_info->source_name,
+ progress_info->target_name,
+ progress_info->file_size);
+ } else {
+ dfos_xfer_progress_dialog_update
+ (DFOS_XFER_PROGRESS_DIALOG
+ (xfer_info->progress_dialog),
+ progress_info->bytes_copied,
+ progress_info->total_bytes_copied);
+ }
+ return TRUE;
+ case GNOME_VFS_XFER_PHASE_FILECOMPLETED:
+ /* FIXME? */
+ return TRUE;
+ case GNOME_VFS_XFER_PHASE_COMPLETED:
+ gtk_widget_destroy (xfer_info->progress_dialog);
+ g_warning ("***** RELEASING HANDLE");
+ g_free (xfer_info);
+ return TRUE;
+ default:
+ return TRUE;
+ }
+}
+
+static gint
+handle_xfer_vfs_error (const GnomeVFSXferProgressInfo *progress_info,
+ XferInfo *xfer_info)
+{
+ /* Notice that the error mode in `xfer_info' is the one we have been
+ requested, but the transfer is always performed in mode
+ `GNOME_VFS_XFER_ERROR_MODE_QUERY'. */
+
+ switch (xfer_info->error_mode) {
+ case GNOME_VFS_XFER_ERROR_MODE_QUERY: /* FIXME */
+ case GNOME_VFS_XFER_ERROR_MODE_ABORT:
+ default:
+ dfos_xfer_progress_dialog_freeze (DFOS_XFER_PROGRESS_DIALOG
+ (xfer_info->progress_dialog));
+ error (xfer_info->progress_dialog,
+ _("Copy operation failed:\n%s"),
+ gnome_vfs_result_to_string (progress_info->vfs_status));
+ dfos_xfer_progress_dialog_thaw (DFOS_XFER_PROGRESS_DIALOG
+ (xfer_info->progress_dialog));
+ gtk_widget_destroy (xfer_info->progress_dialog);
+ return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
+ }
+}
+
+static gint
+handle_xfer_overwrite (const GnomeVFSXferProgressInfo *progress_info,
+ XferInfo *xfer_info)
+{
+ return FALSE;
+}
+
+static gint
+xfer_callback (GnomeVFSAsyncHandle *handle,
+ const GnomeVFSXferProgressInfo *progress_info,
+ gpointer data)
+{
+ XferInfo *xfer_info;
+
+ xfer_info = (XferInfo *) data;
+
+ switch (progress_info->status) {
+ case GNOME_VFS_XFER_PROGRESS_STATUS_OK:
+ return handle_xfer_ok (progress_info, xfer_info);
+ case GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR:
+ return handle_xfer_vfs_error (progress_info, xfer_info);
+ case GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE:
+ return handle_xfer_overwrite (progress_info, xfer_info);
+ default:
+ g_warning (_("Unknown GnomeVFSXferProgressStatus %d"),
+ progress_info->status);
+ return FALSE;
+ }
+}
+
+
+void
+dfos_xfer (DFOS *dfos,
+ const gchar *source_directory_uri,
+ GList *source_file_name_list,
+ const gchar *target_directory_uri,
+ GList *target_file_name_list,
+ GnomeVFSXferOptions options,
+ GnomeVFSXferErrorMode error_mode,
+ GnomeVFSXferOverwriteMode overwrite_mode)
+{
+ GnomeVFSResult result;
+ XferInfo *xfer_info;
+ GnomeVFSAsyncHandle *handle;
+
+ xfer_info = xfer_info_new (handle, options, overwrite_mode, error_mode);
+
+ result = gnome_vfs_async_xfer (&handle,
+ source_directory_uri,
+ source_file_name_list,
+ target_directory_uri,
+ target_file_name_list,
+ options,
+ GNOME_VFS_XFER_ERROR_MODE_QUERY,
+ overwrite_mode,
+ xfer_callback,
+ xfer_info);
+
+ if (result != GNOME_VFS_OK) {
+ gchar *message;
+ GtkWidget *dialog;
+
+ message = g_strdup_printf (_("The transfer between\n%s\nand\n%s\ncould not be started:\n%s"),
+ source_directory_uri,
+ target_directory_uri,
+ gnome_vfs_result_to_string (result));
+
+
+ /* FIXME: signals and all that. */
+ dialog = gnome_error_dialog (message);
+
+ gtk_widget_show (dialog);
+
+ g_free (message);
+ g_free (xfer_info);
+ }
+}
diff --git a/libnautilus-extensions/nautilus-file-operations.h b/libnautilus-extensions/nautilus-file-operations.h
new file mode 100644
index 000000000..a200110e0
--- /dev/null
+++ b/libnautilus-extensions/nautilus-file-operations.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* xfer.h - GNOME::Desktop::FileOperationService transfer service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _XFER_H
+#define _XFER_H
+
+#include <libgnomevfs/gnome-vfs.h>
+
+void dfos_xfer (DFOS *dfos,
+ const gchar *source_directory_uri,
+ GList *source_file_name_list,
+ const gchar *target_directory_uri,
+ GList *target_file_name_list,
+ GnomeVFSXferOptions options,
+ GnomeVFSXferErrorMode error_mode,
+ GnomeVFSXferOverwriteMode overwrite_mode);
+
+#endif /* _XFER_H */
diff --git a/libnautilus-extensions/nautilus-scroll-frame.c b/libnautilus-extensions/nautilus-scroll-frame.c
new file mode 100644
index 000000000..6f41835ec
--- /dev/null
+++ b/libnautilus-extensions/nautilus-scroll-frame.c
@@ -0,0 +1,1210 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This 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.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include <config.h>
+#include <gtk/gtkhscrollbar.h>
+#include <gtk/gtkvscrollbar.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkviewport.h>
+#include "gtkscrollframe.h"
+
+
+/* scrolled window policy and size requisition handling:
+ *
+ * gtk size requisition works as follows:
+ * a widget upon size-request reports the width and height that it finds
+ * to be best suited to display its contents, including children.
+ * the width and/or height reported from a widget upon size requisition
+ * may be overidden by the user by specifying a width and/or height
+ * other than 0 through gtk_widget_set_usize().
+ *
+ * a scrolled window needs (for imlementing all three policy types) to
+ * request its width and height based on two different rationales.
+ * 1) the user wants the scrolled window to just fit into the space
+ * that it gets allocated for a specifc dimension.
+ * 1.1) this does not apply if the user specified a concrete value
+ * value for that specific dimension by either specifying usize for the
+ * scrolled window or for its child.
+ * 2) the user wants the scrolled window to take as much space up as
+ * is desired by the child for a specifc dimension (i.e. POLICY_NEVER).
+ *
+ * also, kinda obvious:
+ * 3) a user would certainly not have choosen a scrolled window as a container
+ * for the child, if the resulting allocation takes up more space than the
+ * child would have allocated without the scrolled window.
+ *
+ * conclusions:
+ * A) from 1) follows: the scrolled window shouldn't request more space for a
+ * specifc dimension than is required at minimum.
+ * B) from 1.1) follows: the requisition may be overidden by usize of the scrolled
+ * window (done automatically) or by usize of the child (needs to be checked).
+ * C) from 2) follows: for POLICY_NEVER, the scrolled window simply reports the
+ * child's dimension.
+ * D) from 3) follows: the scrolled window child's minimum width and minimum height
+ * under A) at least correspond to the space taken up by its scrollbars.
+ */
+
+/* Object argument IDs */
+enum {
+ ARG_0,
+ ARG_HADJUSTMENT,
+ ARG_VADJUSTMENT,
+ ARG_HSCROLLBAR_POLICY,
+ ARG_VSCROLLBAR_POLICY,
+ ARG_FRAME_PLACEMENT,
+ ARG_SHADOW_TYPE,
+ ARG_SCROLLBAR_SPACING
+};
+
+/* Private part of the GtkScrollFrame structure */
+typedef struct {
+ /* Horizontal and vertical scrollbars */
+ GtkWidget *hsb;
+ GtkWidget *vsb;
+
+ /* Space between scrollbars and frame */
+ guint sb_spacing;
+
+ /* Allocation for frame */
+ guint frame_x;
+ guint frame_y;
+ guint frame_w;
+ guint frame_h;
+
+ /* Scrollbar policy */
+ guint hsb_policy : 2;
+ guint vsb_policy : 2;
+
+ /* Whether scrollbars are visible */
+ guint hsb_visible : 1;
+ guint vsb_visible : 1;
+
+ /* Placement of frame wrt scrollbars */
+ guint frame_placement : 2;
+
+ /* Shadow type for frame */
+ guint shadow_type : 3;
+} ScrollFramePrivate;
+
+
+static void gtk_scroll_frame_class_init (GtkScrollFrameClass *class);
+static void gtk_scroll_frame_init (GtkScrollFrame *sf);
+static void gtk_scroll_frame_set_arg (GtkObject *object, GtkArg *arg, guint arg_id);
+static void gtk_scroll_frame_get_arg (GtkObject *object, GtkArg *arg, guint arg_id);
+static void gtk_scroll_frame_destroy (GtkObject *object);
+static void gtk_scroll_frame_finalize (GtkObject *object);
+
+static void gtk_scroll_frame_map (GtkWidget *widget);
+static void gtk_scroll_frame_unmap (GtkWidget *widget);
+static void gtk_scroll_frame_draw (GtkWidget *widget, GdkRectangle *area);
+static void gtk_scroll_frame_size_request (GtkWidget *widget, GtkRequisition *requisition);
+static void gtk_scroll_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
+static gint gtk_scroll_frame_expose (GtkWidget *widget, GdkEventExpose *event);
+
+static void gtk_scroll_frame_add (GtkContainer *container, GtkWidget *widget);
+static void gtk_scroll_frame_remove (GtkContainer *container, GtkWidget *widget);
+static void gtk_scroll_frame_forall (GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data);
+
+static GtkBinClass *parent_class;
+
+
+/**
+ * gtk_scroll_frame_get_type:
+ * @void:
+ *
+ * Registers the &GtkScrollFrame class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GtkScrollFrame class.
+ **/
+GtkType
+gtk_scroll_frame_get_type (void)
+{
+ static GtkType scroll_frame_type = 0;
+
+ if (!scroll_frame_type) {
+ static const GtkTypeInfo scroll_frame_info = {
+ "GtkScrollFrame",
+ sizeof (GtkScrollFrame),
+ sizeof (GtkScrollFrameClass),
+ (GtkClassInitFunc) gtk_scroll_frame_class_init,
+ (GtkObjectInitFunc) gtk_scroll_frame_init,
+ NULL, /* reserved_1 */
+ NULL, /* reserved_2 */
+ (GtkClassInitFunc) NULL
+ };
+
+ scroll_frame_type = gtk_type_unique (GTK_TYPE_BIN, &scroll_frame_info);
+ }
+
+ return scroll_frame_type;
+}
+
+/* Class initialization function for the scroll frame widget */
+static void
+gtk_scroll_frame_class_init (GtkScrollFrameClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkContainerClass *container_class;
+
+ object_class = (GtkObjectClass *) class;
+ widget_class = (GtkWidgetClass *) class;
+ container_class = (GtkContainerClass *) class;
+
+ parent_class = gtk_type_class (GTK_TYPE_BIN);
+
+ gtk_object_add_arg_type ("GtkScrollFrame::hadjustment",
+ GTK_TYPE_ADJUSTMENT,
+ GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
+ ARG_HADJUSTMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::vadjustment",
+ GTK_TYPE_ADJUSTMENT,
+ GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
+ ARG_VADJUSTMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::hscrollbar_policy",
+ GTK_TYPE_POLICY_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_HSCROLLBAR_POLICY);
+ gtk_object_add_arg_type ("GtkScrollFrame::vscrollbar_policy",
+ GTK_TYPE_POLICY_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_VSCROLLBAR_POLICY);
+ gtk_object_add_arg_type ("GtkScrollFrame::frame_placement",
+ GTK_TYPE_CORNER_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_FRAME_PLACEMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::shadow_type",
+ GTK_TYPE_SHADOW_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_SHADOW_TYPE);
+ gtk_object_add_arg_type ("GtkScrollFrame::scrollbar_spacing",
+ GTK_TYPE_UINT,
+ GTK_ARG_READWRITE,
+ ARG_SCROLLBAR_SPACING);
+
+ object_class->set_arg = gtk_scroll_frame_set_arg;
+ object_class->get_arg = gtk_scroll_frame_get_arg;
+ object_class->destroy = gtk_scroll_frame_destroy;
+ object_class->finalize = gtk_scroll_frame_finalize;
+
+ widget_class->map = gtk_scroll_frame_map;
+ widget_class->unmap = gtk_scroll_frame_unmap;
+ widget_class->draw = gtk_scroll_frame_draw;
+ widget_class->size_request = gtk_scroll_frame_size_request;
+ widget_class->size_allocate = gtk_scroll_frame_size_allocate;
+ widget_class->expose_event = gtk_scroll_frame_expose;
+
+ container_class->add = gtk_scroll_frame_add;
+ container_class->remove = gtk_scroll_frame_remove;
+ container_class->forall = gtk_scroll_frame_forall;
+}
+
+/* Object initialization function for the scroll frame widget */
+static void
+gtk_scroll_frame_init (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ priv = g_new0 (ScrollFramePrivate, 1);
+ sf->priv = priv;
+
+ GTK_WIDGET_SET_FLAGS (sf, GTK_NO_WINDOW);
+
+ gtk_container_set_resize_mode (GTK_CONTAINER (sf), GTK_RESIZE_QUEUE);
+
+ priv->sb_spacing = 3;
+ priv->hsb_policy = GTK_POLICY_ALWAYS;
+ priv->vsb_policy = GTK_POLICY_ALWAYS;
+ priv->frame_placement = GTK_CORNER_TOP_LEFT;
+ priv->shadow_type = GTK_SHADOW_NONE;
+}
+
+/* Set_arg handler for the scroll frame widget */
+static void
+gtk_scroll_frame_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ switch (arg_id) {
+ case ARG_HADJUSTMENT:
+ gtk_scroll_frame_set_hadjustment (sf, GTK_VALUE_POINTER (*arg));
+ break;
+
+ case ARG_VADJUSTMENT:
+ gtk_scroll_frame_set_vadjustment (sf, GTK_VALUE_POINTER (*arg));
+ break;
+
+ case ARG_HSCROLLBAR_POLICY:
+ gtk_scroll_frame_set_policy (sf, GTK_VALUE_ENUM (*arg), priv->vsb_policy);
+ break;
+
+ case ARG_VSCROLLBAR_POLICY:
+ gtk_scroll_frame_set_policy (sf, priv->hsb_policy, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_FRAME_PLACEMENT:
+ gtk_scroll_frame_set_placement (sf, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_SHADOW_TYPE:
+ gtk_scroll_frame_set_shadow_type (sf, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_SCROLLBAR_SPACING:
+ gtk_scroll_frame_set_scrollbar_spacing (sf, GTK_VALUE_UINT (*arg));
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* Get_arg handler for the scroll frame widget */
+static void
+gtk_scroll_frame_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ switch (arg_id) {
+ case ARG_HADJUSTMENT:
+ GTK_VALUE_POINTER (*arg) = gtk_scroll_frame_get_hadjustment (sf);
+ break;
+
+ case ARG_VADJUSTMENT:
+ GTK_VALUE_POINTER (*arg) = gtk_scroll_frame_get_vadjustment (sf);
+ break;
+
+ case ARG_HSCROLLBAR_POLICY:
+ GTK_VALUE_ENUM (*arg) = priv->hsb_policy;
+ break;
+
+ case ARG_VSCROLLBAR_POLICY:
+ GTK_VALUE_ENUM (*arg) = priv->vsb_policy;
+ break;
+
+ case ARG_FRAME_PLACEMENT:
+ GTK_VALUE_ENUM (*arg) = priv->frame_placement;
+ break;
+
+ case ARG_SHADOW_TYPE:
+ GTK_VALUE_ENUM (*arg) = priv->shadow_type;
+ break;
+
+ case ARG_SCROLLBAR_SPACING:
+ GTK_VALUE_UINT (*arg) = priv->sb_spacing;
+ break;
+
+ default:
+ arg->type = GTK_TYPE_INVALID;
+ break;
+ }
+}
+
+/* Destroy handler for the scroll frame widget */
+static void
+gtk_scroll_frame_destroy (GtkObject *object)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (object));
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ gtk_widget_unparent (priv->hsb);
+ gtk_widget_unparent (priv->vsb);
+ gtk_widget_destroy (priv->hsb);
+ gtk_widget_destroy (priv->vsb);
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+/* Finalize handler for the scroll frame widget */
+static void
+gtk_scroll_frame_finalize (GtkObject *object)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ gtk_widget_unref (priv->hsb);
+ gtk_widget_unref (priv->vsb);
+
+ g_free (priv);
+
+ if (GTK_OBJECT_CLASS (parent_class)->finalize)
+ (* GTK_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+/* Map handler for the scroll frame widget */
+static void
+gtk_scroll_frame_map (GtkWidget *widget)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ /* chain parent class handler to map self and child */
+ if (GTK_WIDGET_CLASS (parent_class)->map)
+ (* GTK_WIDGET_CLASS (parent_class)->map) (widget);
+
+ if (GTK_WIDGET_VISIBLE (priv->hsb) && !GTK_WIDGET_MAPPED (priv->hsb))
+ gtk_widget_map (priv->hsb);
+
+ if (GTK_WIDGET_VISIBLE (priv->vsb) && !GTK_WIDGET_MAPPED (priv->vsb))
+ gtk_widget_map (priv->vsb);
+}
+
+/* Unmap handler for the scroll frame widget */
+static void
+gtk_scroll_frame_unmap (GtkWidget *widget)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ /* chain parent class handler to unmap self and child */
+ if (GTK_WIDGET_CLASS (parent_class)->unmap)
+ (* GTK_WIDGET_CLASS (parent_class)->unmap) (widget);
+
+ if (GTK_WIDGET_MAPPED (priv->hsb))
+ gtk_widget_unmap (priv->hsb);
+
+ if (GTK_WIDGET_MAPPED (priv->vsb))
+ gtk_widget_unmap (priv->vsb);
+}
+
+/* Draws the shadow of a scroll frame widget */
+static void
+draw_shadow (GtkScrollFrame *sf, GdkRectangle *area)
+{
+ ScrollFramePrivate *priv;
+
+ g_assert (area != NULL);
+
+ priv = sf->priv;
+
+ gtk_paint_shadow (GTK_WIDGET (sf)->style,
+ GTK_WIDGET (sf)->window,
+ GTK_STATE_NORMAL, priv->shadow_type,
+ area, GTK_WIDGET (sf),
+ "scroll_frame",
+ priv->frame_x, priv->frame_y,
+ priv->frame_w, priv->frame_h);
+}
+
+/* Draw handler for the scroll frame widget */
+static void
+gtk_scroll_frame_draw (GtkWidget *widget, GdkRectangle *area)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GdkRectangle child_area;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (area != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ if (GTK_WIDGET_DRAWABLE (widget))
+ draw_shadow (sf, area);
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)
+ && gtk_widget_intersect (bin->child, area, &child_area))
+ gtk_widget_draw (bin->child, &child_area);
+
+ if (GTK_WIDGET_VISIBLE (priv->hsb)
+ && gtk_widget_intersect (priv->hsb, area, &child_area))
+ gtk_widget_draw (priv->hsb, &child_area);
+
+ if (GTK_WIDGET_VISIBLE (priv->vsb)
+ && gtk_widget_intersect (priv->vsb, area, &child_area))
+ gtk_widget_draw (priv->vsb, &child_area);
+}
+
+/* Forall handler for the scroll frame widget */
+static void
+gtk_scroll_frame_forall (GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (container));
+ g_return_if_fail (callback != NULL);
+
+ sf = GTK_SCROLL_FRAME (container);
+ priv = sf->priv;
+
+ if (GTK_CONTAINER_CLASS (parent_class)->forall)
+ (* GTK_CONTAINER_CLASS (parent_class)->forall) (
+ container, include_internals,
+ callback, callback_data);
+
+ if (include_internals) {
+ if (priv->vsb)
+ (* callback) (priv->vsb, callback_data);
+
+ if (priv->hsb)
+ (* callback) (priv->hsb, callback_data);
+ }
+}
+
+/* Size_request handler for the scroll frame widget */
+static void
+gtk_scroll_frame_size_request (GtkWidget *widget, GtkRequisition *requisition)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ gint extra_width;
+ gint extra_height;
+ GtkRequisition hsb_requisition;
+ GtkRequisition vsb_requisition;
+ GtkRequisition child_requisition;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (requisition != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ extra_width = 0;
+ extra_height = 0;
+
+ requisition->width = GTK_CONTAINER (widget)->border_width * 2;
+ requisition->height = GTK_CONTAINER (widget)->border_width * 2;
+
+ if (priv->shadow_type != GTK_SHADOW_NONE) {
+ requisition->width += 2 * widget->style->klass->xthickness;
+ requisition->height += 2 * widget->style->klass->ythickness;
+ }
+
+ gtk_widget_size_request (priv->hsb, &hsb_requisition);
+ gtk_widget_size_request (priv->vsb, &vsb_requisition);
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) {
+ static guint quark_aux_info;
+
+ if (!quark_aux_info)
+ quark_aux_info = g_quark_from_static_string ("gtk-aux-info");
+
+ gtk_widget_size_request (bin->child, &child_requisition);
+
+ if (priv->hsb_policy == GTK_POLICY_NEVER)
+ requisition->width += child_requisition.width;
+ else {
+ GtkWidgetAuxInfo *aux_info;
+
+ aux_info = gtk_object_get_data_by_id (GTK_OBJECT (bin->child),
+ quark_aux_info);
+ if (aux_info && aux_info->width > 0) {
+ requisition->width += aux_info->width;
+ extra_width = -1;
+ } else
+ requisition->width += vsb_requisition.width;
+ }
+
+ if (priv->vsb_policy == GTK_POLICY_NEVER)
+ requisition->height += child_requisition.height;
+ else {
+ GtkWidgetAuxInfo *aux_info;
+
+ aux_info = gtk_object_get_data_by_id (GTK_OBJECT (bin->child),
+ quark_aux_info);
+ if (aux_info && aux_info->height > 0) {
+ requisition->height += aux_info->height;
+ extra_height = -1;
+ } else
+ requisition->height += hsb_requisition.height;
+ }
+ }
+
+ if (priv->hsb_policy == GTK_POLICY_AUTOMATIC || GTK_WIDGET_VISIBLE (priv->hsb)) {
+ requisition->width = MAX (requisition->width, hsb_requisition.width);
+ if (!extra_height || GTK_WIDGET_VISIBLE (priv->hsb))
+ extra_height = priv->sb_spacing + hsb_requisition.height;
+ }
+
+ if (priv->vsb_policy == GTK_POLICY_AUTOMATIC || GTK_WIDGET_VISIBLE (priv->vsb)) {
+ requisition->height = MAX (requisition->height, vsb_requisition.height);
+ if (!extra_width || GTK_WIDGET_VISIBLE (priv->vsb))
+ extra_width = priv->sb_spacing + vsb_requisition.width;
+ }
+
+ requisition->width += MAX (0, extra_width);
+ requisition->height += MAX (0, extra_height);
+}
+
+/* Computes the relative allocation for the scroll frame widget */
+static void
+compute_relative_allocation (GtkWidget *widget, GtkAllocation *allocation)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_assert (widget != NULL);
+ g_assert (GTK_IS_SCROLL_FRAME (widget));
+ g_assert (allocation != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ allocation->x = GTK_CONTAINER (widget)->border_width;
+ allocation->y = GTK_CONTAINER (widget)->border_width;
+ allocation->width = MAX (1, (gint) widget->allocation.width - allocation->x * 2);
+ allocation->height = MAX (1, (gint) widget->allocation.height - allocation->y * 2);
+
+ if (priv->vsb_visible) {
+ GtkRequisition vsb_requisition;
+
+ gtk_widget_get_child_requisition (priv->vsb, &vsb_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_TOP_RIGHT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_RIGHT)
+ allocation->x += vsb_requisition.width + priv->sb_spacing;
+
+ allocation->width = MAX (1, ((gint) allocation->width
+ - ((gint) vsb_requisition.width + priv->sb_spacing)));
+ }
+
+ if (priv->hsb_visible) {
+ GtkRequisition hsb_requisition;
+
+ gtk_widget_get_child_requisition (priv->hsb, &hsb_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_BOTTOM_LEFT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_RIGHT)
+ allocation->y += hsb_requisition.height + priv->sb_spacing;
+
+ allocation->height = MAX (1, ((gint) allocation->height
+ - ((gint) hsb_requisition.height + priv->sb_spacing)));
+ }
+}
+
+/* Size_allocate handler for the scroll frame widget */
+static void
+gtk_scroll_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GtkAllocation relative_allocation;
+ GtkAllocation child_allocation;
+ gint xthickness, ythickness;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (allocation != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ widget->allocation = *allocation;
+
+ if (priv->hsb_policy == GTK_POLICY_ALWAYS)
+ priv->hsb_visible = TRUE;
+ else if (priv->hsb_policy == GTK_POLICY_NEVER)
+ priv->hsb_visible = FALSE;
+
+ if (priv->vsb_policy == GTK_POLICY_ALWAYS)
+ priv->vsb_visible = TRUE;
+ else if (priv->vsb_policy == GTK_POLICY_NEVER)
+ priv->vsb_visible = FALSE;
+
+ if (priv->shadow_type == GTK_SHADOW_NONE) {
+ xthickness = 0;
+ ythickness = 0;
+ } else {
+ xthickness = widget->style->klass->xthickness;
+ ythickness = widget->style->klass->ythickness;
+ }
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) {
+ gboolean previous_hvis;
+ gboolean previous_vvis;
+ guint count = 0;
+
+ do {
+ compute_relative_allocation (widget, &relative_allocation);
+
+ priv->frame_x = relative_allocation.x + allocation->x;
+ priv->frame_y = relative_allocation.y + allocation->y;
+ priv->frame_w = relative_allocation.width;
+ priv->frame_h = relative_allocation.height;
+
+ child_allocation.x = priv->frame_x + xthickness;
+ child_allocation.y = priv->frame_y + ythickness;
+ child_allocation.width = priv->frame_w - 2 * xthickness;
+ child_allocation.height = priv->frame_h - 2 * ythickness;
+
+ previous_hvis = priv->hsb_visible;
+ previous_vvis = priv->vsb_visible;
+
+ gtk_widget_size_allocate (bin->child, &child_allocation);
+
+ /* If, after the first iteration, the hscrollbar and the
+ * vscrollbar flip visiblity, then we need both.
+ */
+ if (count
+ && previous_hvis != priv->hsb_visible
+ && previous_vvis != priv->vsb_visible) {
+ priv->hsb_visible = TRUE;
+ priv->vsb_visible = TRUE;
+
+ /* a new resize is already queued at this point,
+ * so we will immediatedly get reinvoked
+ */
+ return;
+ }
+
+ count++;
+ } while (previous_hvis != priv->hsb_visible
+ || previous_vvis != priv->vsb_visible);
+ } else
+ compute_relative_allocation (widget, &relative_allocation);
+
+ if (priv->hsb_visible) {
+ GtkRequisition hscrollbar_requisition;
+
+ gtk_widget_get_child_requisition (priv->hsb, &hscrollbar_requisition);
+
+ if (!GTK_WIDGET_VISIBLE (priv->hsb))
+ gtk_widget_show (priv->hsb);
+
+ child_allocation.x = relative_allocation.x;
+ if (priv->frame_placement == GTK_CORNER_TOP_LEFT
+ || priv->frame_placement == GTK_CORNER_TOP_RIGHT)
+ child_allocation.y = (relative_allocation.y
+ + relative_allocation.height
+ + priv->sb_spacing);
+ else
+ child_allocation.y = GTK_CONTAINER (sf)->border_width;
+
+ child_allocation.width = relative_allocation.width;
+ child_allocation.height = hscrollbar_requisition.height;
+ child_allocation.x += allocation->x;
+ child_allocation.y += allocation->y;
+
+ gtk_widget_size_allocate (priv->hsb, &child_allocation);
+ } else if (GTK_WIDGET_VISIBLE (priv->hsb))
+ gtk_widget_hide (priv->hsb);
+
+ if (priv->vsb_visible) {
+ GtkRequisition vscrollbar_requisition;
+
+ if (!GTK_WIDGET_VISIBLE (priv->vsb))
+ gtk_widget_show (priv->vsb);
+
+ gtk_widget_get_child_requisition (priv->vsb, &vscrollbar_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_TOP_LEFT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_LEFT)
+ child_allocation.x = (relative_allocation.x
+ + relative_allocation.width
+ + priv->sb_spacing);
+ else
+ child_allocation.x = GTK_CONTAINER (sf)->border_width;
+
+ child_allocation.y = relative_allocation.y;
+ child_allocation.width = vscrollbar_requisition.width;
+ child_allocation.height = relative_allocation.height;
+ child_allocation.x += allocation->x;
+ child_allocation.y += allocation->y;
+
+ gtk_widget_size_allocate (priv->vsb, &child_allocation);
+ } else if (GTK_WIDGET_VISIBLE (priv->vsb))
+ gtk_widget_hide (priv->vsb);
+}
+
+/* Expose handler for the scroll frame widget */
+static gint
+gtk_scroll_frame_expose (GtkWidget *widget, GdkEventExpose *event)
+{
+ GtkScrollFrame *sf;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ sf = GTK_SCROLL_FRAME (widget);
+
+ if (GTK_WIDGET_DRAWABLE (widget))
+ draw_shadow (sf, &event->area);
+
+ if (GTK_WIDGET_CLASS (parent_class)->expose_event)
+ (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
+
+ return FALSE;
+}
+
+/* Add handler for the scroll frame widget */
+static void
+gtk_scroll_frame_add (GtkContainer *container, GtkWidget *child)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+
+ sf = GTK_SCROLL_FRAME (container);
+ priv = sf->priv;
+ bin = GTK_BIN (container);
+ g_return_if_fail (bin->child == NULL);
+
+ bin->child = child;
+ gtk_widget_set_parent (child, GTK_WIDGET (bin));
+
+ /* this is a temporary message */
+ if (!gtk_widget_set_scroll_adjustments (child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb))))
+ g_warning ("gtk_scroll_frame_add(): cannot add non scrollable widget "
+ "use gtk_scroll_frame_add_with_viewport() instead");
+
+ if (GTK_WIDGET_REALIZED (child->parent))
+ gtk_widget_realize (child);
+
+ if (GTK_WIDGET_VISIBLE (child->parent) && GTK_WIDGET_VISIBLE (child)) {
+ if (GTK_WIDGET_MAPPED (child->parent))
+ gtk_widget_map (child);
+
+ gtk_widget_queue_resize (child);
+ }
+}
+
+/* Remove method for the scroll frame widget */
+static void
+gtk_scroll_frame_remove (GtkContainer *container, GtkWidget *child)
+{
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (container));
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_BIN (container)->child == child);
+
+ gtk_widget_set_scroll_adjustments (child, NULL, NULL);
+
+ /* chain parent class handler to remove child */
+ if (GTK_CONTAINER_CLASS (parent_class)->remove)
+ (* GTK_CONTAINER_CLASS (parent_class)->remove) (container, child);
+}
+
+/**
+ * gtk_scroll_frame_new:
+ * @hadj: If non-NULL, the adjustment to use for horizontal scrolling.
+ * @vadj: If non-NULL, the adjustment to use for vertical scrolling.
+ *
+ * Creates a new scroll frame widget.
+ *
+ * Return value: The newly-created scroll frame widget.
+ **/
+GtkWidget *
+gtk_scroll_frame_new (GtkAdjustment *hadj, GtkAdjustment *vadj)
+{
+ if (hadj)
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (hadj), NULL);
+
+ if (vadj)
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (vadj), NULL);
+
+ return gtk_widget_new (GTK_TYPE_SCROLL_FRAME,
+ "hadjustment", hadj,
+ "vadjustment", vadj,
+ NULL);
+}
+
+/* Callback used when one of the scroll frame widget's adjustments changes */
+static void
+adjustment_changed (GtkAdjustment *adj, gpointer data)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (adj != NULL);
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ g_return_if_fail (data != NULL);
+
+ sf = GTK_SCROLL_FRAME (data);
+ priv = sf->priv;
+
+ if (adj == gtk_range_get_adjustment (GTK_RANGE (priv->hsb))) {
+ if (priv->hsb_policy == GTK_POLICY_AUTOMATIC) {
+ gboolean visible;
+
+ visible = priv->hsb_visible;
+ priv->hsb_visible = (adj->upper - adj->lower > adj->page_size);
+ if (priv->hsb_visible != visible)
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+ }
+ } else if (adj == gtk_range_get_adjustment (GTK_RANGE (priv->vsb))) {
+ if (priv->vsb_policy == GTK_POLICY_AUTOMATIC) {
+ gboolean visible;
+
+ visible = priv->vsb_visible;
+ priv->vsb_visible = (adj->upper - adj->lower > adj->page_size);
+ if (priv->vsb_visible != visible)
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+ }
+ }
+}
+
+/**
+ * gtk_scroll_frame_set_hadjustment:
+ * @sf: A scroll frame widget.
+ * @adj: An adjustment.
+ *
+ * Sets the adjustment to be used for horizontal scrolling in a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_hadjustment (GtkScrollFrame *sf, GtkAdjustment *adj)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (adj)
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ else
+ adj = GTK_ADJUSTMENT (gtk_object_new (GTK_TYPE_ADJUSTMENT, NULL));
+
+ if (!priv->hsb) {
+ gtk_widget_push_composite_child ();
+ priv->hsb = gtk_hscrollbar_new (adj);
+ gtk_widget_set_composite_name (priv->hsb, "hscrollbar");
+ gtk_widget_pop_composite_child ();
+
+ gtk_widget_set_parent (priv->hsb, GTK_WIDGET (sf));
+ gtk_widget_ref (priv->hsb);
+ gtk_widget_show (priv->hsb);
+ } else {
+ GtkAdjustment *old_adj;
+
+ old_adj = gtk_range_get_adjustment (GTK_RANGE (priv->hsb));
+ if (old_adj == adj)
+ return;
+
+ gtk_signal_disconnect_by_func (GTK_OBJECT (old_adj),
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ gtk_range_set_adjustment (GTK_RANGE (priv->hsb), adj);
+ }
+
+ adj = gtk_range_get_adjustment (GTK_RANGE (priv->hsb));
+ gtk_signal_connect (GTK_OBJECT (adj),
+ "changed",
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ adjustment_changed (adj, sf);
+
+ if (GTK_BIN (sf)->child)
+ gtk_widget_set_scroll_adjustments (
+ GTK_BIN (sf)->child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb)));
+}
+
+/**
+ * gtk_scroll_frame_set_vadjustment:
+ * @sf: A scroll frame widget.
+ * @adj: An adjustment.
+ *
+ * Sets the adjustment to be used for vertical scrolling in a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_vadjustment (GtkScrollFrame *sf, GtkAdjustment *adj)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (adj)
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ else
+ adj = GTK_ADJUSTMENT (gtk_object_new (GTK_TYPE_ADJUSTMENT, NULL));
+
+ if (!priv->vsb) {
+ gtk_widget_push_composite_child ();
+ priv->vsb = gtk_vscrollbar_new (adj);
+ gtk_widget_set_composite_name (priv->vsb, "vscrollbar");
+ gtk_widget_pop_composite_child ();
+
+ gtk_widget_set_parent (priv->vsb, GTK_WIDGET (sf));
+ gtk_widget_ref (priv->vsb);
+ gtk_widget_show (priv->vsb);
+ } else {
+ GtkAdjustment *old_adj;
+
+ old_adj = gtk_range_get_adjustment (GTK_RANGE (priv->vsb));
+ if (old_adj == adj)
+ return;
+
+ gtk_signal_disconnect_by_func (GTK_OBJECT (old_adj),
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ gtk_range_set_adjustment (GTK_RANGE (priv->vsb), adj);
+ }
+
+ adj = gtk_range_get_adjustment (GTK_RANGE (priv->vsb));
+ gtk_signal_connect (GTK_OBJECT (adj),
+ "changed",
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ adjustment_changed (adj, sf);
+
+ if (GTK_BIN (sf)->child)
+ gtk_widget_set_scroll_adjustments (
+ GTK_BIN (sf)->child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb)));
+}
+
+/**
+ * gtk_scroll_frame_get_hadjustment:
+ * @sf: A scroll frame widget.
+ *
+ * Queries the horizontal adjustment of a scroll frame widget.
+ *
+ * Return value: The horizontal adjustment of the scroll frame, or NULL if none.
+ **/
+GtkAdjustment *
+gtk_scroll_frame_get_hadjustment (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_val_if_fail (sf != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (sf), NULL);
+
+ priv = sf->priv;
+
+ return priv->hsb ? gtk_range_get_adjustment (GTK_RANGE (priv->hsb)) : NULL;
+}
+
+/**
+ * gtk_scroll_frame_get_vadjustment:
+ * @sf: A scroll frame widget.
+ *
+ * Queries the vertical adjustment of a scroll frame widget.
+ *
+ * Return value: The vertical adjustment of the scroll frame, or NULL if none.
+ **/
+GtkAdjustment *
+gtk_scroll_frame_get_vadjustment (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_val_if_fail (sf != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (sf), NULL);
+
+ priv = sf->priv;
+
+ return priv->vsb ? gtk_range_get_adjustment (GTK_RANGE (priv->vsb)) : NULL;
+}
+
+/**
+ * gtk_scroll_frame_set_policy:
+ * @sf: A scroll frame widget.
+ * @hsb_policy: Policy for the horizontal scrollbar.
+ * @vsb_policy: Policy for the vertical scrollbar.
+ *
+ * Sets the scrollbar policies of a scroll frame widget. These determine when
+ * the scrollbars are to be shown or hidden.
+ **/
+void
+gtk_scroll_frame_set_policy (GtkScrollFrame *sf,
+ GtkPolicyType hsb_policy,
+ GtkPolicyType vsb_policy)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->hsb_policy == hsb_policy && priv->vsb_policy == vsb_policy)
+ return;
+
+ priv->hsb_policy = hsb_policy;
+ priv->vsb_policy = vsb_policy;
+
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_placement:
+ * @sf: A scroll frame widget.
+ * @frame_placement: Placement for the frame.
+ *
+ * Sets the placement of a scroll frame widget's frame with respect to its
+ * scrollbars.
+ **/
+void
+gtk_scroll_frame_set_placement (GtkScrollFrame *sf, GtkCornerType frame_placement)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->frame_placement == frame_placement)
+ return;
+
+ priv->frame_placement = frame_placement;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_shadow_type:
+ * @sf: A scroll frame widget.
+ * @shadow_type: A shadow type.
+ *
+ * Sets the shadow type of a scroll frame widget. You can use this when you
+ * insert a child that does not paint a frame on its own.
+ **/
+void
+gtk_scroll_frame_set_shadow_type (GtkScrollFrame *sf, GtkShadowType shadow_type)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+ g_return_if_fail (shadow_type >= GTK_SHADOW_NONE && shadow_type <= GTK_SHADOW_ETCHED_OUT);
+
+ priv = sf->priv;
+
+ if (priv->shadow_type == shadow_type)
+ return;
+
+ priv->shadow_type = shadow_type;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_scrollbar_spacing:
+ * @sf: A scroll frame widget.
+ * @spacing: Desired spacing in pixels.
+ *
+ * Sets the spacing between the frame and the scrollbars of a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_scrollbar_spacing (GtkScrollFrame *sf, guint spacing)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->sb_spacing == spacing)
+ return;
+
+ priv->sb_spacing = spacing;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_add_with_viewport:
+ * @sf: A scroll frame widget.
+ * @child: A widget.
+ *
+ * Creates a &GtkViewport and puts the specified child inside it, thus allowing
+ * the viewport to be scrolled by the scroll frame widget. This is meant to be
+ * used only when a child does not support the scrolling interface.
+ **/
+void
+gtk_scroll_frame_add_with_viewport (GtkScrollFrame *sf, GtkWidget *child)
+{
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GtkWidget *viewport;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (child));
+ g_return_if_fail (child->parent == NULL);
+
+ priv = sf->priv;
+ bin = GTK_BIN (sf);
+
+ if (bin->child != NULL) {
+ g_return_if_fail (GTK_IS_VIEWPORT (bin->child));
+ g_return_if_fail (GTK_BIN (bin->child)->child == NULL);
+
+ viewport = bin->child;
+ } else {
+ viewport = gtk_viewport_new (gtk_scroll_frame_get_hadjustment (sf),
+ gtk_scroll_frame_get_vadjustment (sf));
+ gtk_container_add (GTK_CONTAINER (sf), viewport);
+ }
+
+ gtk_widget_show (viewport);
+ gtk_container_add (GTK_CONTAINER (viewport), child);
+}
diff --git a/libnautilus-extensions/nautilus-scroll-frame.h b/libnautilus-extensions/nautilus-scroll-frame.h
new file mode 100644
index 000000000..facb59dac
--- /dev/null
+++ b/libnautilus-extensions/nautilus-scroll-frame.h
@@ -0,0 +1,93 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This 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.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __GTK_SCROLL_FRAME_H__
+#define __GTK_SCROLL_FRAME_H__
+
+
+#include <gdk/gdk.h>
+#include <gtk/gtkbin.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define GTK_TYPE_SCROLL_FRAME (gtk_scroll_frame_get_type ())
+#define GTK_SCROLL_FRAME(obj) (GTK_CHECK_CAST ((obj), \
+ GTK_TYPE_SCROLL_FRAME, GtkScrollFrame))
+#define GTK_SCROLL_FRAME_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), \
+ GTK_TYPE_SCROLL_FRAME, GtkScrollFrameClass))
+#define GTK_IS_SCROLL_FRAME(obj) (GTK_CHECK_TYPE ((obj), \
+ GTK_TYPE_SCROLL_FRAME))
+#define GTK_IS_SCROLL_FRAME_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), \
+ GTK_TYPE_SCROLL_FRAME))
+
+
+typedef struct _GtkScrollFrame GtkScrollFrame;
+typedef struct _GtkScrollFrameClass GtkScrollFrameClass;
+
+struct _GtkScrollFrame
+{
+ GtkBin bin;
+
+ /* Private data */
+ gpointer priv;
+};
+
+struct _GtkScrollFrameClass
+{
+ GtkBinClass parent_class;
+};
+
+
+GtkType gtk_scroll_frame_get_type (void);
+GtkWidget *gtk_scroll_frame_new (GtkAdjustment *hadj, GtkAdjustment *vadj);
+
+void gtk_scroll_frame_set_hadjustment (GtkScrollFrame *sf, GtkAdjustment *adj);
+void gtk_scroll_frame_set_vadjustment (GtkScrollFrame *sf, GtkAdjustment *adj);
+
+GtkAdjustment *gtk_scroll_frame_get_hadjustment (GtkScrollFrame *sf);
+GtkAdjustment *gtk_scroll_frame_get_vadjustment (GtkScrollFrame *sf);
+
+void gtk_scroll_frame_set_policy (GtkScrollFrame *sf,
+ GtkPolicyType hsb_policy,
+ GtkPolicyType vsb_policy);
+
+void gtk_scroll_frame_set_placement (GtkScrollFrame *sf, GtkCornerType frame_placement);
+void gtk_scroll_frame_set_shadow_type (GtkScrollFrame *sf, GtkShadowType shadow_type);
+void gtk_scroll_frame_set_scrollbar_spacing (GtkScrollFrame *sf, guint spacing);
+
+void gtk_scroll_frame_add_with_viewport (GtkScrollFrame *sf, GtkWidget *child);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __GTK_SCROLL_FRAME_H__ */
diff --git a/libnautilus-private/gnome-icon-container-dnd.c b/libnautilus-private/gnome-icon-container-dnd.c
new file mode 100644
index 000000000..02f97f181
--- /dev/null
+++ b/libnautilus-private/gnome-icon-container-dnd.c
@@ -0,0 +1,647 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container-dnd.c - Drag & drop handling for the icon container
+ widget.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "gnome-icon-container-private.h"
+
+#include "gnome-icon-container-dnd.h"
+
+
+struct _DndSelectionItem {
+ gchar *uri;
+ gint x, y;
+};
+typedef struct _DndSelectionItem DndSelectionItem;
+
+
+static GtkTargetEntry drag_types [] = {
+ { GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST_TYPE, 0, GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST },
+ { GNOME_ICON_CONTAINER_DND_URI_LIST_TYPE, 0, GNOME_ICON_CONTAINER_DND_URI_LIST },
+ /* { GNOME_ICON_CONTAINER_DND_URL_TYPE, 0, GNOME_ICON_CONTAINER_DND_URL } */
+};
+static const int num_drag_types = sizeof (drag_types) / sizeof (drag_types[0]);
+
+static GtkTargetEntry drop_types [] = {
+ { GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST_TYPE, 0, GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST },
+ { GNOME_ICON_CONTAINER_DND_URI_LIST_TYPE, 0, GNOME_ICON_CONTAINER_DND_URI_LIST },
+ { GNOME_ICON_CONTAINER_DND_URL_TYPE, 0, GNOME_ICON_CONTAINER_DND_URL }
+};
+static const int num_drop_types = sizeof (drop_types) / sizeof (drop_types[0]);
+
+
+static GnomeCanvasItem *
+create_selection_shadow (GnomeIconContainer *container,
+ GList *list)
+{
+ GnomeCanvasGroup *group;
+ GnomeCanvas *canvas;
+ GdkBitmap *stipple;
+ gint max_x, max_y;
+ gint min_x, min_y;
+ gint icon_width, icon_height;
+ gint cell_width, cell_height;
+ GList *p;
+
+ if (list == NULL)
+ return NULL;
+
+ stipple = container->priv->dnd_info->stipple;
+ g_return_val_if_fail (stipple != NULL, NULL);
+
+ icon_width = GNOME_ICON_CONTAINER_ICON_WIDTH (container);
+ icon_height = GNOME_ICON_CONTAINER_ICON_HEIGHT (container);
+ cell_width = GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ cell_height = GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ canvas = GNOME_CANVAS (container);
+
+ /* Creating a big set of rectangles in the canvas can be expensive, so
+ we try to be smart and only create the maximum number of rectangles
+ that we will need, in the vertical/horizontal directions. */
+
+ max_x = GTK_WIDGET (container)->allocation.width;
+ min_x = -max_x;
+
+ max_y = GTK_WIDGET (container)->allocation.height;
+ min_y = -max_y;
+
+ /* Create a group, so that it's easier to move all the items around at
+ once. */
+ group = GNOME_CANVAS_GROUP
+ (gnome_canvas_item_new (GNOME_CANVAS_GROUP (canvas->root),
+ gnome_canvas_group_get_type (),
+ NULL));
+
+ for (p = list; p != NULL; p = p->next) {
+ DndSelectionItem *item;
+ gint x1, y1;
+ gint x2, y2;
+
+ item = p->data;
+
+ x1 = item->x;
+ y1 = item->y;
+ x2 = item->x + icon_width;
+ y2 = item->y + icon_height;
+
+ if (x2 >= min_x && x1 <= max_x && y2 >= min_y && y1 <= max_y) {
+ GnomeCanvasItem *rect;
+
+ rect = gnome_canvas_item_new
+ (group,
+ gnome_canvas_rect_get_type (),
+ "x1", (double) x1, "y1", (double) y1,
+ "x2", (double) x2, "y2", (double) y2,
+ "outline_color", "black",
+ "outline_stipple", stipple,
+ "width_pixels", 1,
+ NULL);
+ }
+ }
+
+ return GNOME_CANVAS_ITEM (group);
+}
+
+/* This is a workaround for a gnome-canvas bug: with the current (1.0.18)
+ gnome-libs, setting the x/y values for an existing group fails at updating
+ the bounds of the group. So, instead of setting the x/y values to the
+ current position at initialization time, we set them to (0,0) and then use a
+ simple affine transform. */
+static void
+set_shadow_position (GnomeCanvasItem *shadow,
+ gdouble x, gdouble y)
+{
+ double affine[6];
+
+ affine[0] = 1.0;
+ affine[1] = 0.0;
+ affine[2] = 0.0;
+ affine[3] = 1.0;
+ affine[4] = x;
+ affine[5] = y;
+
+ gnome_canvas_item_affine_absolute (shadow, affine);
+}
+
+
+/* Functions to deal with DndSelectionItems. */
+
+static DndSelectionItem *
+dnd_selection_item_new (void)
+{
+ DndSelectionItem *new;
+
+ new = g_new (DndSelectionItem, 1);
+ new->uri = NULL;
+ new->x = 0;
+ new->y = 0;
+
+ return new;
+}
+
+static void
+dnd_selection_item_destroy (DndSelectionItem *item)
+{
+ g_free (item->uri);
+ g_free (item);
+}
+
+static void
+destroy_selection_list (GList *list)
+{
+ GList *p;
+
+ if (list == NULL)
+ return;
+
+ for (p = list; p != NULL; p = p->next)
+ dnd_selection_item_destroy ((DndSelectionItem *) p->data);
+
+ g_list_free (list);
+}
+
+
+/* Source-side handling of the drag. */
+
+/* Encode a "special/x-gnome-icon-list" selection. */
+static void
+set_gnome_icon_list_selection (GnomeIconContainer *container,
+ GtkSelectionData *selection_data)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+ GString *data;
+ gdouble x_offset, y_offset;
+
+ priv = container->priv;
+ if (priv->icons == NULL) {
+ /* FIXME? Actually this probably shouldn't happen. */
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, NULL, 0);
+ return;
+ }
+
+ x_offset = container->priv->dnd_info->start_x;
+ y_offset = container->priv->dnd_info->start_y;
+
+ data = g_string_new (NULL);
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+ gchar *s;
+ gint x, y;
+
+ icon = p->data;
+ if (! icon->is_selected)
+ continue;
+
+ x = (gint) (icon->x - x_offset);
+ y = (gint) (icon->y - y_offset);
+
+ x += (GNOME_ICON_CONTAINER_ICON_XOFFSET (container)
+ - GNOME_ICON_CONTAINER_ICON_WIDTH (container) / 2);
+ y += (GNOME_ICON_CONTAINER_ICON_YOFFSET (container)
+ - GNOME_ICON_CONTAINER_ICON_HEIGHT (container) / 2);
+
+ if (priv->base_uri != NULL)
+ s = g_strdup_printf ("%s%s\r%d:%d\r\n",
+ priv->base_uri, icon->text,
+ x, y);
+ else
+ s = g_strdup_printf ("%s\r%d:%d\r\n",
+ icon->text, x, y);
+
+ g_string_append (data, s);
+ g_free (s);
+ }
+
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, (guchar *) data->str, data->len);
+
+ g_string_free (data, TRUE);
+}
+
+/* Encode a "text/uri-list" selection. */
+static void
+set_uri_list_selection (GnomeIconContainer *container,
+ GtkSelectionData *selection_data)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+ GString *data;
+
+ priv = container->priv;
+ if (priv->icons == NULL) {
+ /* FIXME? Actually this probably shouldn't happen. */
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, NULL, 0);
+ return;
+ }
+
+ data = g_string_new (NULL);
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (! icon->is_selected)
+ continue;
+
+ /* This is lame code, I know. */
+
+ if (priv->base_uri != NULL)
+ g_string_append (data, priv->base_uri);
+ g_string_append (data, icon->text);
+ g_string_append (data, "\r\n");
+ }
+
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, (guchar *) data->str, data->len);
+
+ g_string_free (data, TRUE);
+}
+
+static void
+drag_data_get_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer data)
+{
+ GnomeIconContainer *container;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (widget));
+ g_return_if_fail (context != NULL);
+
+ container = GNOME_ICON_CONTAINER (widget);
+
+ switch (info) {
+ case GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST:
+ set_gnome_icon_list_selection (container, selection_data);
+ break;
+ case GNOME_ICON_CONTAINER_DND_URI_LIST:
+ set_uri_list_selection (container, selection_data);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+
+/* Target-side handling of the drag. */
+
+static void
+get_gnome_icon_list_selection (GnomeIconContainer *container,
+ GtkSelectionData *data)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+ const guchar *p, *oldp;
+ gint size;
+
+ dnd_info = container->priv->dnd_info;
+
+ oldp = data->data;
+ size = data->length;
+
+ while (1) {
+ DndSelectionItem *item;
+ guint len;
+
+ /* The list is in the form:
+
+ name\rx:y:width:height\r\n
+
+ The geometry information after the first \r is optional. */
+
+ /* 1: Decode name. */
+
+ p = memchr (oldp, '\r', size);
+ if (p == NULL)
+ break;
+
+ item = dnd_selection_item_new ();
+
+ len = p - oldp;
+
+ item->uri = g_malloc (len + 1);
+ memcpy (item->uri, oldp, len);
+ item->uri[len] = 0;
+
+ p++;
+ if (*p == '\n' || *p == '\0') {
+ dnd_info->selection_list
+ = g_list_prepend (dnd_info->selection_list,
+ item);
+ if (p == 0) {
+ g_warning ("Invalid special/x-gnome-icon-list data received: "
+ "missing newline character.");
+ break;
+ } else {
+ oldp = p + 1;
+ continue;
+ }
+ }
+
+ size -= p - oldp;
+ oldp = p;
+
+ /* 2: Decode geometry information. */
+
+ if (sscanf (p, "%d:%d", &item->x, &item->y) != 2)
+ g_warning ("Invalid special/x-gnome-icon-list data received: "
+ "invalid geometry specification.");
+
+ dnd_info->selection_list
+ = g_list_prepend (dnd_info->selection_list, item);
+
+ p = memchr (p, '\r', size);
+ if (p == NULL || p[1] != '\n') {
+ g_warning ("Invalid special/x-gnome-icon-list data received: "
+ "missing newline character.");
+ if (p == NULL)
+ break;
+ } else {
+ p += 2;
+ }
+
+ size -= p - oldp;
+ oldp = p;
+ }
+}
+
+static void
+drag_data_received_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *data,
+ guint info,
+ guint32 time,
+ gpointer user_data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerDndInfo *dnd_info;
+ GnomeCanvasItem *shadow;
+ double world_x, world_y;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ dnd_info = container->priv->dnd_info;
+ g_return_if_fail (dnd_info->selection_list == NULL);
+
+ switch (info) {
+ case GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST:
+ get_gnome_icon_list_selection (container, data);
+ break;
+ case GNOME_ICON_CONTAINER_DND_URI_LIST:
+ puts ("Bad! URI list!"); /* FIXME */
+ return;
+ }
+
+ shadow = create_selection_shadow (container, dnd_info->selection_list);
+
+ gnome_canvas_item_set (shadow, "x", (gdouble) 0, "y", (gdouble) 0,
+ NULL);
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (widget),
+ x, y, &world_x, &world_y);
+ set_shadow_position (shadow, world_x, world_y);
+
+ gnome_canvas_item_show (shadow);
+
+ if (dnd_info->shadow != NULL)
+ gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
+ dnd_info->shadow = shadow;
+}
+
+static gboolean
+drag_motion_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ dnd_info = GNOME_ICON_CONTAINER (widget)->priv->dnd_info;
+ if (dnd_info->selection_list == NULL)
+ gtk_drag_get_data (widget, context,
+ GPOINTER_TO_INT (context->targets->data),
+ time);
+
+ if (dnd_info->shadow != NULL) {
+ double world_x, world_y;
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (widget),
+ x, y, &world_x, &world_y);
+ gnome_canvas_item_show (dnd_info->shadow);
+ set_shadow_position (dnd_info->shadow, world_x, world_y);
+ }
+
+ gdk_drag_status (context, context->suggested_action, time);
+ return TRUE;
+}
+
+static void
+drag_end_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerDndInfo *dnd_info;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ dnd_info = container->priv->dnd_info;
+
+ destroy_selection_list (dnd_info->selection_list);
+ dnd_info->selection_list = NULL;
+}
+
+static gboolean
+drag_drop_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time,
+ gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerDndInfo *dnd_info;
+ GtkWidget *source_widget;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ dnd_info = container->priv->dnd_info;
+ source_widget = gtk_drag_get_source_widget (context);
+
+ if (source_widget == widget && context->action == GDK_ACTION_MOVE) {
+ double world_x, world_y;
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container),
+ x, y, &world_x, &world_y);
+
+ gnome_icon_container_xlate_selected (container,
+ world_x - dnd_info->start_x,
+ world_y - dnd_info->start_y,
+ TRUE);
+ }
+
+ return FALSE;
+}
+
+static void
+drag_leave_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ guint time,
+ gpointer data)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ dnd_info = GNOME_ICON_CONTAINER (widget)->priv->dnd_info;
+
+ if (dnd_info->shadow != NULL) {
+ gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
+ dnd_info->shadow = NULL;
+ }
+
+ if (dnd_info->selection_list != NULL) {
+ destroy_selection_list (dnd_info->selection_list);
+ dnd_info->selection_list = NULL;
+ }
+}
+
+
+void
+gnome_icon_container_dnd_init (GnomeIconContainer *container,
+ GdkBitmap *stipple)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ dnd_info = g_new (GnomeIconContainerDndInfo, 1);
+
+ dnd_info->target_list = gtk_target_list_new (drag_types,
+ num_drag_types);
+
+ dnd_info->start_x = 0;
+ dnd_info->start_y = 0;
+ dnd_info->selection_list = NULL;
+
+ dnd_info->stipple = gdk_bitmap_ref (stipple);
+
+ dnd_info->shadow = NULL;
+
+ /* Set up the widget as a drag destination. */
+ /* (But not a source, as drags starting from this widget will be
+ implemented by dealing with events manually.) */
+
+ gtk_drag_dest_set (GTK_WIDGET (container),
+ 0,
+ drop_types, num_drop_types,
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ gtk_signal_connect (GTK_OBJECT (container), "drag_data_get",
+ GTK_SIGNAL_FUNC (drag_data_get_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_motion",
+ GTK_SIGNAL_FUNC (drag_motion_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_end",
+ GTK_SIGNAL_FUNC (drag_end_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_data_received",
+ GTK_SIGNAL_FUNC (drag_data_received_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_drop",
+ GTK_SIGNAL_FUNC (drag_drop_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_leave",
+ GTK_SIGNAL_FUNC (drag_leave_cb), NULL);
+
+ container->priv->dnd_info = dnd_info;
+}
+
+void
+gnome_icon_container_dnd_fini (GnomeIconContainer *container)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ dnd_info = container->priv->dnd_info;
+ g_return_if_fail (dnd_info != NULL);
+
+ gtk_target_list_unref (dnd_info->target_list);
+ destroy_selection_list (dnd_info->selection_list);
+
+ if (dnd_info->shadow != NULL)
+ gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
+
+ gdk_bitmap_unref (dnd_info->stipple);
+
+ g_free (dnd_info);
+}
+
+
+void
+gnome_icon_container_dnd_begin_drag (GnomeIconContainer *container,
+ GdkDragAction actions,
+ gint button,
+ GdkEventMotion *event)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+ g_return_if_fail (event != NULL);
+
+ dnd_info = container->priv->dnd_info;
+ g_return_if_fail (dnd_info != NULL);
+
+ /* Notice that the event is already in world coordinates, because of
+ the way the canvas handles events! */
+ dnd_info->start_x = event->x;
+ dnd_info->start_y = event->y;
+
+ gtk_drag_begin (GTK_WIDGET (container),
+ dnd_info->target_list,
+ actions,
+ button,
+ (GdkEvent *) event);
+}
+
+void
+gnome_icon_container_dnd_end_drag (GnomeIconContainer *container)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ dnd_info = container->priv->dnd_info;
+ g_return_if_fail (dnd_info != NULL);
+}
diff --git a/libnautilus-private/gnome-icon-container-dnd.h b/libnautilus-private/gnome-icon-container-dnd.h
new file mode 100644
index 000000000..da5ab68f0
--- /dev/null
+++ b/libnautilus-private/gnome-icon-container-dnd.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container-dnd.h - Drag & drop handling for the icon container
+ widget.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _GNOME_ICON_CONTAINER_DND_H
+#define _GNOME_ICON_CONTAINER_DND_H
+
+typedef struct _GnomeIconContainerDndInfo GnomeIconContainerDndInfo;
+typedef enum _GnomeIconContainerDndTargetType GnomeIconContainerDndTargetType;
+
+#include "gnome-icon-container.h"
+
+/* Standard DnD types. */
+enum _GnomeIconContainerDndTargetType {
+ GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST,
+ GNOME_ICON_CONTAINER_DND_URI_LIST,
+ GNOME_ICON_CONTAINER_DND_URL,
+ GNOME_ICON_CONTAINER_DND_NTARGETS
+};
+
+/* DnD target names. */
+#define GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST_TYPE "special/x-gnome-icon-list"
+#define GNOME_ICON_CONTAINER_DND_URI_LIST_TYPE "text/uri-list"
+#define GNOME_ICON_CONTAINER_DND_URL_TYPE "_NETSCAPE_URL"
+
+
+/* DnD-related information. */
+struct _GnomeIconContainerDndInfo {
+ GtkTargetList *target_list;
+
+ /* Start of the drag, in world coordinates. */
+ gdouble start_x, start_y;
+
+ /* List of DndSelectionItems, representing items being dragged, or NULL
+ if data about them has not been received from the source yet. */
+ GList *selection_list;
+
+ /* Stipple for drawing icon shadows during DnD. */
+ GdkBitmap *stipple;
+
+ /* Shadow for the icons being dragged. */
+ GnomeCanvasItem *shadow;
+};
+
+
+void gnome_icon_container_dnd_init (GnomeIconContainer *container,
+ GdkBitmap *stipple);
+void gnome_icon_container_dnd_fini (GnomeIconContainer *container);
+void gnome_icon_container_dnd_begin_drag (GnomeIconContainer *container,
+ GdkDragAction actions,
+ gint button,
+ GdkEventMotion *event);
+void gnome_icon_container_dnd_end_drag (GnomeIconContainer *container);
+
+#endif /* _GNOME_ICON_CONTAINER_DND_H */
diff --git a/libnautilus-private/gnome-icon-container-layout.c b/libnautilus-private/gnome-icon-container-layout.c
new file mode 100644
index 000000000..3b7cfeccb
--- /dev/null
+++ b/libnautilus-private/gnome-icon-container-layout.c
@@ -0,0 +1,161 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-layout.c
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#include <glib.h>
+
+#include "gnome-icon-container.h"
+#include "gnome-icon-container-private.h"
+
+#include "gnome-icon-container-layout.h"
+
+
+struct _IconLayoutInfo {
+ gchar *text;
+ gint x, y;
+};
+typedef struct _IconLayoutInfo IconLayoutInfo;
+
+
+struct _GnomeIconContainerLayout {
+ GHashTable *name_to_layout;
+};
+
+
+GnomeIconContainerLayout *
+gnome_icon_container_layout_new (void)
+{
+ GnomeIconContainerLayout *new;
+
+ new = g_new (GnomeIconContainerLayout, 1);
+ new->name_to_layout = g_hash_table_new (g_str_hash, g_str_equal);
+
+ return new;
+}
+
+static void
+hash_foreach_destroy (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ IconLayoutInfo *info;
+
+ info = (IconLayoutInfo *) value;
+ g_free (info->text);
+ g_free (info);
+}
+
+void
+gnome_icon_container_layout_free (GnomeIconContainerLayout *layout)
+{
+ g_return_if_fail (layout != NULL);
+
+ g_hash_table_foreach (layout->name_to_layout,
+ hash_foreach_destroy, NULL);
+ g_hash_table_destroy (layout->name_to_layout);
+
+ g_free (layout);
+}
+
+
+void
+gnome_icon_container_layout_add (GnomeIconContainerLayout *layout,
+ const gchar *icon_text,
+ gint x,
+ gint y)
+{
+ IconLayoutInfo *info;
+
+ g_return_if_fail (layout != NULL);
+ g_return_if_fail (icon_text != NULL);
+
+ info = g_new (IconLayoutInfo, 1);
+ info->text = g_strdup (icon_text);
+ info->x = x;
+ info->y = y;
+
+ g_hash_table_insert (layout->name_to_layout, info->text, info);
+}
+
+gboolean
+gnome_icon_container_layout_get_position (const GnomeIconContainerLayout *layout,
+ const gchar *icon_text,
+ gint *x_return,
+ gint *y_return)
+{
+ IconLayoutInfo *info;
+
+ g_return_val_if_fail (layout != NULL, FALSE);
+ g_return_val_if_fail (icon_text != NULL, FALSE);
+
+ info = g_hash_table_lookup (layout->name_to_layout, icon_text);
+ if (info == NULL)
+ return FALSE;
+
+ *x_return = info->x;
+ *y_return = info->y;
+
+ return TRUE;
+}
+
+
+struct _ForeachData {
+ GnomeIconContainerLayout *layout;
+ GnomeIconContainerLayoutForeachFunc callback;
+ gpointer callback_data;
+};
+typedef struct _ForeachData ForeachData;
+
+static void
+foreach_helper (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ IconLayoutInfo *info;
+ ForeachData *data;
+
+ info = (IconLayoutInfo *) value;
+ data = (ForeachData *) user_data;
+
+ (* data->callback) (data->layout, info->text, info->x, info->y,
+ data->callback_data);
+}
+
+void
+gnome_icon_container_layout_foreach (GnomeIconContainerLayout *layout,
+ GnomeIconContainerLayoutForeachFunc callback,
+ gpointer callback_data)
+{
+ ForeachData *data;
+
+ g_return_if_fail (layout != NULL);
+ g_return_if_fail (callback != NULL);
+
+ data = g_new (ForeachData, 1);
+ data->layout = layout;
+ data->callback = callback;
+ data->callback_data = callback_data;
+
+ g_hash_table_foreach (layout->name_to_layout, foreach_helper, data);
+
+ g_free (data);
+}
diff --git a/libnautilus-private/gnome-icon-container-layout.h b/libnautilus-private/gnome-icon-container-layout.h
new file mode 100644
index 000000000..6d1ebb50c
--- /dev/null
+++ b/libnautilus-private/gnome-icon-container-layout.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-layout.h
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _GNOME_ICON_CONTAINER_LAYOUT_H
+#define _GNOME_ICON_CONTAINER_LAYOUT_H
+
+#include <glib.h>
+
+typedef struct _GnomeIconContainerLayout GnomeIconContainerLayout;
+
+typedef void (* GnomeIconContainerLayoutForeachFunc)
+ (const GnomeIconContainerLayout *layout,
+ const gchar *text,
+ gint x, gint y,
+ gpointer callback_data);
+
+#include "gnome-icon-container.h"
+#include "gnome-icon-container-private.h"
+
+
+GnomeIconContainerLayout *
+ gnome_icon_container_layout_new (void);
+void gnome_icon_container_layout_free (GnomeIconContainerLayout *layout);
+void gnome_icon_container_layout_add (GnomeIconContainerLayout *layout,
+ const gchar *icon,
+ gint x,
+ gint y);
+gboolean
+ gnome_icon_container_layout_get_position (const GnomeIconContainerLayout *layout,
+ const gchar *icon,
+ gint *x_return,
+ gint *y_return);
+
+void gnome_icon_container_layout_foreach (GnomeIconContainerLayout *layout,
+ GnomeIconContainerLayoutForeachFunc callback,
+ gpointer callback_data);
+
+#endif /* _GNOME_ICON_CONTAINER_LAYOUT_H */
diff --git a/libnautilus-private/gnome-icon-container-private.h b/libnautilus-private/gnome-icon-container-private.h
new file mode 100644
index 000000000..e681f43c6
--- /dev/null
+++ b/libnautilus-private/gnome-icon-container-private.h
@@ -0,0 +1,218 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container-private.h
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _GNOME_ICON_CONTAINER_PRIVATE_H
+#define _GNOME_ICON_CONTAINER_PRIVATE_H
+
+#include "gnome-icon-container.h"
+#include "gnome-icon-container-dnd.h"
+
+/* An Icon. */
+
+struct _GnomeIconContainerIcon {
+ /* Group containing the text and the image. */
+ GnomeCanvasGroup *item; /* FIXME wrong name. */
+
+ /* The image for the icon. Using a generic item makes it
+ possible for us to use any fancy canvas element. */
+ GnomeCanvasItem *image_item;
+
+ /* The text for the icon. */
+ GnomeIconTextItem *text_item;
+
+ /* Text for the icon. */
+ gchar *text;
+
+ /* X/Y coordinates and size. We could use the GnomeCanvasItem
+ functions, but this is a lot faster. */
+ gdouble x, y;
+ guint width, height; /* FIXME we could actually do without this if
+ we assume the size is always given by
+ GnomeIconContainer.cell_width*/
+
+ /* Whether this item is selected (i.e. highlighted) for operation. */
+ gboolean is_selected : 1;
+
+ /* Whether this item is selected for keyboard navigation. */
+ gboolean is_current : 1;
+
+ /* Whether this item has been repositioned during layout already. */
+ gboolean layout_done : 1;
+
+ /* Whether this item was selected before rubberbanding. */
+ gboolean was_selected_before_rubberband : 1;
+
+ gpointer data;
+};
+typedef struct _GnomeIconContainerIcon GnomeIconContainerIcon;
+
+
+#define INITIAL_GRID_WIDTH 64
+#define INITIAL_GRID_HEIGHT 64
+struct _GnomeIconContainerIconGrid {
+ /* Size of the grid. */
+ guint width, height;
+
+ /* This is the width that we can actually use for finding an empty
+ position. */
+ guint visible_width;
+
+ /* Array of grid elements. */
+ GList **elems;
+
+ /* Size of the allocated array. */
+ guint alloc_width, alloc_height;
+
+ /* Position of the first free cell (used to speed up progressive
+ updates). If negative, there is no free cell. */
+ gint first_free_x, first_free_y;
+};
+typedef struct _GnomeIconContainerIconGrid GnomeIconContainerIconGrid;
+
+
+/* Private GnomeIconContainer members. */
+
+struct _GnomeIconContainerRubberbandInfo {
+ gboolean active : 1;
+
+ gdouble start_x, start_y;
+
+ GnomeCanvasItem *selection_rectangle;
+ guint timer_tag;
+
+ guint prev_x, prev_y;
+ guint prev_x1, prev_y1;
+ guint prev_x2, prev_y2;
+};
+typedef struct _GnomeIconContainerRubberbandInfo GnomeIconContainerRubberbandInfo;
+
+struct _GnomeIconContainerPrivate {
+ /* Base URI for Drag & Drop. */
+ gchar *base_uri;
+
+ /* Browser mode setting. */
+ gboolean browser_mode : 1;
+
+ /* Current icon mode (index into `icon_mode_info[]' -- see
+ `gnome-icon-container.c'). */
+ GnomeIconContainerIconMode icon_mode;
+
+ /* Size of the container. */
+ guint width, height;
+
+ /* List of icons. */
+ GList *icons;
+
+ /* Total number of icons. */
+ guint num_icons;
+
+ /* The grid. */
+ GnomeIconContainerIconGrid *grid;
+
+ /* FIXME: This is *ugly*, but more efficient (both memory- and
+ speed-wise) than using gtk_object_{set,get}_data() for all the
+ icon items. */
+ GHashTable *canvas_item_to_icon;
+
+ /* Rectangle that shows that a certain icon is selected. */
+ GnomeCanvasItem *kbd_navigation_rectangle;
+
+ /* Current icon for keyboard navigation. */
+ GnomeIconContainerIcon *kbd_current;
+
+ /* Rubberbanding status. */
+ GnomeIconContainerRubberbandInfo rubberband_info;
+
+ /* Timeout used to make a selected icon fully visible after a short
+ period of time. (The timeout is needed to make sure
+ double-clicking still works.) */
+ gint kbd_icon_visibility_timer_tag;
+
+ /* Position of the pointer during the last click. */
+ gint drag_x, drag_y;
+
+ /* Button currently pressed, possibly for dragging. */
+ guint drag_button;
+
+ /* Icon on which the click happened. */
+ GnomeIconContainerIcon *drag_icon;
+
+ /* Whether we are actually performing a dragging action. */
+ gboolean doing_drag;
+
+ /* Drag offset. */
+ gint drag_x_offset, drag_y_offset;
+
+ /* Idle ID. */
+ guint idle_id;
+
+ /* Timeout for selection in browser mode. */
+ gint browser_mode_selection_timer_tag;
+
+ /* Icon to be selected at timeout in browser mode. */
+ GnomeIconContainerIcon *browser_mode_selection_icon;
+
+ /* DnD info. */
+ GnomeIconContainerDndInfo *dnd_info;
+};
+
+
+/* Definition of the available icon container modes. */
+struct _GnomeIconContainerIconModeInfo {
+ guint icon_width;
+ guint icon_height;
+
+ guint cell_width;
+ guint cell_height;
+
+ guint cell_spacing;
+
+ guint icon_xoffset;
+ guint icon_yoffset;
+};
+typedef struct _GnomeIconContainerIconModeInfo GnomeIconContainerIconModeInfo;
+
+extern GnomeIconContainerIconModeInfo gnome_icon_container_icon_mode_info[];
+
+#define GNOME_ICON_CONTAINER_ICON_WIDTH(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].icon_width
+
+#define GNOME_ICON_CONTAINER_ICON_HEIGHT(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].icon_height
+
+#define GNOME_ICON_CONTAINER_CELL_WIDTH(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].cell_width
+
+#define GNOME_ICON_CONTAINER_CELL_HEIGHT(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].cell_height
+
+#define GNOME_ICON_CONTAINER_CELL_SPACING(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].cell_spacing
+
+#define GNOME_ICON_CONTAINER_ICON_XOFFSET(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].icon_xoffset
+
+#define GNOME_ICON_CONTAINER_ICON_YOFFSET(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].icon_yoffset
+
+#endif /* _GNOME_ICON_CONTAINER_PRIVATE_H */
diff --git a/libnautilus-private/gnome-icon-container.c b/libnautilus-private/gnome-icon-container.c
new file mode 100644
index 000000000..f158fcc9b
--- /dev/null
+++ b/libnautilus-private/gnome-icon-container.c
@@ -0,0 +1,3020 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container.c - Icon container widget.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gnome.h>
+
+#include "gnome-icon-container-private.h"
+#include "gnome-icon-container-dnd.h"
+
+
+static GnomeCanvasClass *parent_class;
+
+/* Interval for updating the rubberband selection, in milliseconds. */
+#define RUBBERBAND_TIMEOUT_INTERVAL 10
+
+/* Timeout for making the icon currently selected for keyboard operation
+ visible. FIXME: This *must* be higher than the double-click time in GDK,
+ but there is no way to access its value from outside. */
+#define KBD_ICON_VISIBILITY_TIMEOUT 300
+
+/* Timeout for selecting an icon in "browser mode" (i.e. by just placing the
+ pointer over the icon, without pressing any button). */
+#define BROWSER_MODE_SELECTION_TIMEOUT 800
+
+
+/* WARNING: Keep this in sync with the `GnomeIconContainerIconMode' enum in
+ `gnome-icon-container.h'. */
+GnomeIconContainerIconModeInfo gnome_icon_container_icon_mode_info[] = {
+ { 48, 48, 80, 80, 4, 44, 28 }, /* GNOME_ICON_CONTAINER_NORMAL_ICONS */
+ { 24, 24, 100, 40, 4, 16, 16 } /* GNOME_ICON_CONTAINER_SMALL_ICONS */
+};
+
+#define NUM_ICON_MODES (sizeof (gnome_icon_container_icon_mode_info) \
+ / sizeof (*gnome_icon_container_icon_mode_info))
+
+
+/* The GnomeIconContainer signals. */
+enum _GnomeIconContainerSignalNumber {
+ SELECTION_CHANGED,
+ BUTTON_PRESS,
+ ACTIVATE,
+ CONTEXT_CLICK,
+ LAST_SIGNAL
+};
+typedef enum _GnomeIconContainerSignalNumber GnomeIconContainerSignalNumber;
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/* Bitmap for stippled selection rectangles. */
+static GdkBitmap *stipple;
+static char stipple_bits[] = { 0x02, 0x01 };
+
+
+/* Functions dealing with GnomeIconContainerIcons. */
+
+static void
+icon_destroy (GnomeIconContainerIcon *icon)
+{
+ gtk_object_destroy (GTK_OBJECT (icon->item));
+}
+
+static void
+icon_configure (GnomeIconContainerIcon *icon,
+ GnomeIconContainer *container)
+{
+ switch (container->priv->icon_mode) {
+ case GNOME_ICON_CONTAINER_NORMAL_ICONS:
+ gnome_icon_text_item_configure
+ (icon->text_item,
+ GNOME_ICON_CONTAINER_CELL_SPACING (container),
+ GNOME_ICON_CONTAINER_ICON_HEIGHT (container),
+ (GNOME_ICON_CONTAINER_CELL_WIDTH (container)
+ - 2 * GNOME_ICON_CONTAINER_CELL_SPACING (container)),
+ NULL,
+ icon->text,
+ TRUE,
+ TRUE);
+ break;
+ case GNOME_ICON_CONTAINER_SMALL_ICONS:
+ gnome_icon_text_item_configure
+ (icon->text_item,
+ (GNOME_ICON_CONTAINER_ICON_WIDTH (container)
+ + GNOME_ICON_CONTAINER_CELL_SPACING (container)),
+ GNOME_ICON_CONTAINER_CELL_HEIGHT (container) / 2,
+ (GNOME_ICON_CONTAINER_CELL_WIDTH (container)
+ - 2 * GNOME_ICON_CONTAINER_CELL_SPACING (container)
+ - GNOME_ICON_CONTAINER_ICON_WIDTH (container)),
+ NULL,
+ icon->text,
+ TRUE,
+ TRUE);
+ break;
+ default:
+ g_warning ("Unknown icon mode %d.", container->priv->icon_mode);
+ }
+
+ gnome_canvas_item_set
+ (GNOME_CANVAS_ITEM (icon->image_item),
+ "width", (gdouble) GNOME_ICON_CONTAINER_ICON_WIDTH (container),
+ "height", (gdouble) GNOME_ICON_CONTAINER_ICON_HEIGHT (container),
+ NULL);
+}
+
+static GnomeIconContainerIcon *
+icon_new (GnomeIconContainer *container,
+ const gchar *text,
+ gpointer data)
+{
+ GnomeCanvas *canvas;
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *new;
+
+ canvas = GNOME_CANVAS (container);
+ priv = container->priv;
+
+ new = g_new (GnomeIconContainerIcon, 1);
+
+ new->is_selected = FALSE;
+ new->is_current = FALSE;
+ new->layout_done = TRUE;
+ new->was_selected_before_rubberband = FALSE;
+
+ new->data = data;
+ new->text = g_strdup (text); /* FIXME */
+
+ new->item = GNOME_CANVAS_GROUP (gnome_canvas_item_new
+ (GNOME_CANVAS_GROUP (canvas->root),
+ gnome_canvas_group_get_type (),
+ NULL));
+
+ new->image_item = NULL;
+
+ new->text_item
+ = GNOME_ICON_TEXT_ITEM (gnome_canvas_item_new
+ (new->item,
+ gnome_icon_text_item_get_type (),
+ NULL));
+
+ new->width = GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ new->height = GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ return new;
+}
+
+static GnomeIconContainerIcon *
+icon_new_imlib (GnomeIconContainer *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *new;
+
+ priv = container->priv;
+
+ new = icon_new (container, text, data);
+
+ new->image_item
+ = gnome_canvas_item_new (new->item,
+ gnome_canvas_image_get_type (),
+ "image", image,
+ "x", (gdouble) 0,
+ "y", (gdouble) 0,
+ NULL);
+
+ icon_configure (new, container);
+
+ gnome_canvas_item_set (GNOME_CANVAS_ITEM (new->item),
+ "x", (gdouble) 0,
+ "y", (gdouble) 0,
+ NULL);
+
+ return new;
+}
+
+static void
+icon_position (GnomeIconContainerIcon *icon,
+ GnomeIconContainer *container,
+ gdouble x, gdouble y)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ icon->x = x;
+ icon->y = y;
+
+ /* ??? Canvas bug ??? It should be enough to do this once in
+ `icon-configure()', but it does not work. */
+
+ switch (container->priv->icon_mode) {
+ case GNOME_ICON_CONTAINER_NORMAL_ICONS:
+ gnome_icon_text_item_setxy
+ (icon->text_item,
+ GNOME_ICON_CONTAINER_CELL_SPACING (container),
+ (GNOME_ICON_CONTAINER_ICON_HEIGHT (container)
+ + GNOME_ICON_CONTAINER_CELL_SPACING (container) + 2));
+ break;
+ case GNOME_ICON_CONTAINER_SMALL_ICONS:
+ gnome_icon_text_item_setxy
+ (icon->text_item,
+ (GNOME_ICON_CONTAINER_ICON_WIDTH (container)
+ + GNOME_ICON_CONTAINER_CELL_SPACING (container)),
+ GNOME_ICON_CONTAINER_CELL_SPACING (container));
+ break;
+ default:
+ g_warning ("Unknown icon mode %d.", container->priv->icon_mode);
+ }
+
+ gnome_canvas_item_set
+ (icon->image_item,
+ "x", (gdouble) GNOME_ICON_CONTAINER_ICON_XOFFSET (container),
+ "y", (gdouble) GNOME_ICON_CONTAINER_ICON_YOFFSET (container),
+ NULL);
+
+ gnome_canvas_item_set (GNOME_CANVAS_ITEM (icon->item),
+ "x", (gdouble) icon->x,
+ "y", (gdouble) icon->y,
+ NULL);
+}
+
+static void
+icon_raise (GnomeIconContainerIcon *icon)
+{
+ gnome_canvas_item_raise_to_top (GNOME_CANVAS_ITEM (icon->item));
+}
+
+static void
+icon_show (GnomeIconContainerIcon *icon)
+{
+ gnome_canvas_item_show (GNOME_CANVAS_ITEM (icon->item));
+}
+
+static void
+icon_hide (GnomeIconContainerIcon *icon)
+{
+ gnome_canvas_item_hide (GNOME_CANVAS_ITEM (icon->item));
+}
+
+static void
+icon_select (GnomeIconContainerIcon *icon,
+ gboolean sel)
+{
+ gboolean was_selected;
+
+ /* FIXME: We want the icon image to appear as selected too. Maybe
+ this can be done with a new custom CanvasImage-like item providing
+ this functionality? */
+
+ was_selected = icon->is_selected;
+ icon->is_selected = sel;
+
+ gnome_icon_text_item_select (icon->text_item, sel);
+}
+
+static gboolean
+icon_toggle_selection (GnomeIconContainerIcon *icon)
+{
+ if (icon->is_selected) {
+ icon_select (icon, FALSE);
+ return TRUE;
+ } else {
+ icon_select (icon, TRUE);
+ return FALSE;
+ }
+}
+
+static gboolean
+icon_is_in_region (GnomeIconContainerIcon *icon,
+ gint x1, gint y1,
+ gint x2, gint y2)
+{
+ gint icon_x2, icon_y2;
+
+ icon_x2 = icon->x + icon->width;
+ icon_y2 = icon->y + icon->height;
+
+ if (x1 == x2 && y1 == y2)
+ return FALSE;
+
+ if (x1 < icon_x2 && x2 >= icon->x && y1 < icon_y2 && y2 >= icon->y)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void
+icon_get_text_bounding_box (GnomeIconContainerIcon *icon,
+ guint *x1_return, guint *y1_return,
+ guint *x2_return, guint *y2_return)
+{
+ double x1, y1, x2, y2;
+
+ gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (icon->text_item),
+ &x1, &y1, &x2, &y2);
+
+ *x1_return = icon->x + x1;
+ *y1_return = icon->y + y1;
+ *x2_return = icon->x + x2;
+ *y2_return = icon->y + y2;
+}
+
+static void
+icon_get_bounding_box (GnomeIconContainerIcon *icon,
+ guint *x1_return, guint *y1_return,
+ guint *x2_return, guint *y2_return)
+{
+ double x1, y1, x2, y2;
+
+ gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (icon->item),
+ &x1, &y1, &x2, &y2);
+
+ *x1_return = x1;
+ *y1_return = y1;
+ *x2_return = x2;
+ *y2_return = y2;
+}
+
+
+/* Functions for dealing with IconGrids. */
+
+static GnomeIconContainerIconGrid *
+icon_grid_new (void)
+{
+ GnomeIconContainerIconGrid *new;
+
+ new = g_new (GnomeIconContainerIconGrid, 1);
+
+ new->width = new->height = 0;
+ new->visible_width = 0;
+ new->alloc_width = new->alloc_height = 0;
+
+ new->elems = NULL;
+
+ new->first_free_x = -1;
+ new->first_free_y = -1;
+
+ return new;
+}
+
+static void
+icon_grid_clear (GnomeIconContainerIconGrid *grid)
+{
+ GList **p;
+ guint i, j;
+
+ p = grid->elems;
+ for (j = 0; j < grid->height; j++) {
+ for (i = 0; i < grid->width; i++) {
+ if (p[i] != NULL) {
+ g_list_free (p[i]);
+ p[i] = NULL;
+ }
+ }
+
+ p += grid->alloc_width;
+ }
+
+ grid->first_free_x = 0;
+ grid->first_free_y = 0;
+}
+
+static void
+icon_grid_destroy (GnomeIconContainerIconGrid *grid)
+{
+ icon_grid_clear (grid);
+ g_free (grid->elems);
+ g_free (grid);
+}
+
+inline static GList **
+icon_grid_get_element_ptr (GnomeIconContainerIconGrid *grid,
+ guint x, guint y)
+{
+ return &grid->elems[y * grid->alloc_width + x];
+}
+
+inline static GList *
+icon_grid_get_element (GnomeIconContainerIconGrid *grid,
+ guint x, guint y)
+{
+ return *icon_grid_get_element_ptr (grid, x, y);
+}
+
+/* This is admittedly a bit lame.
+
+ Instead of re-allocating the grid from scratch and copying the values, we
+ should just link grid chunks horizontally and vertically in lists;
+ i.e. use a hybrid list/array representation. */
+static void
+icon_grid_resize_allocation (GnomeIconContainerIconGrid *grid,
+ guint new_alloc_width,
+ guint new_alloc_height)
+{
+ GList **new_elems;
+ guint i, j;
+ guint new_alloc_size;
+
+ if (new_alloc_width == 0 || new_alloc_height == 0) {
+ g_free (grid->elems);
+ grid->elems = NULL;
+ grid->width = grid->height = 0;
+ grid->alloc_width = new_alloc_width;
+ grid->alloc_height = new_alloc_height;
+ return;
+ }
+
+ new_alloc_size = new_alloc_width * new_alloc_height;
+ new_elems = g_new (GList *, new_alloc_size);
+
+ if (grid->elems == NULL || grid->width == 0 || grid->height == 0) {
+ memset (new_elems, 0, sizeof (*new_elems) * new_alloc_size);
+ } else {
+ GList **sp, **dp;
+ guint copy_width, copy_height;
+
+ /* Copy existing elements into the new array. */
+
+ sp = grid->elems;
+ dp = new_elems;
+ copy_width = MIN (grid->width, new_alloc_width);
+ copy_height = MIN (grid->height, new_alloc_height);
+
+ for (i = 0; i < copy_height; i++) {
+ for (j = 0; j < copy_width; j++)
+ dp[j] = sp[j];
+
+ for (j = copy_width; j < new_alloc_width; j++)
+ dp[j] = NULL;
+
+ for (j = copy_width; j < grid->width; j++)
+ g_list_free (sp[j]);
+
+ sp += grid->alloc_width;
+ dp += new_alloc_width;
+ }
+
+ /* If there are other lines left, zero them as well. */
+
+ if (i < new_alloc_height) {
+ guint elems_left;
+
+ elems_left = new_alloc_size - (dp - new_elems);
+ memset (dp, 0, sizeof (*new_elems) * elems_left);
+ }
+ }
+
+ g_free (grid->elems);
+ grid->elems = new_elems;
+
+ grid->alloc_width = new_alloc_width;
+ grid->alloc_height = new_alloc_height;
+}
+
+static GnomeIconContainerIconGrid *
+icon_grid_new_same_alloc (GnomeIconContainerIconGrid *grid)
+{
+ GnomeIconContainerIconGrid *new_grid;
+
+ new_grid = icon_grid_new ();
+ icon_grid_resize_allocation (new_grid,
+ grid->alloc_width, grid->alloc_height);
+
+ return new_grid;
+}
+
+static void
+icon_grid_update_first_free_forward (GnomeIconContainerIconGrid *grid)
+{
+ GList **p;
+ guint start_x, start_y;
+ guint x, y;
+
+ if (grid->first_free_x == -1) {
+ start_x = start_y = 0;
+ p = grid->elems;
+ } else {
+ start_x = grid->first_free_x;
+ start_y = grid->first_free_y;
+ p = icon_grid_get_element_ptr (grid, start_x, start_y);
+ }
+
+ x = start_x;
+ y = start_y;
+ while (y < grid->height) {
+ if (*p == NULL) {
+ grid->first_free_x = x;
+ grid->first_free_y = y;
+ return;
+ }
+
+ x++, p++;
+
+ if (x >= grid->visible_width) {
+ x = 0;
+ y++;
+ p += grid->alloc_width - grid->visible_width;
+ }
+ }
+
+ /* No free cell found. */
+
+ grid->first_free_x = -1;
+ grid->first_free_y = -1;
+}
+
+static void
+icon_grid_set_visible_width (GnomeIconContainerIconGrid *grid,
+ guint visible_width)
+{
+ if (visible_width > grid->visible_width
+ && grid->height > 0
+ && grid->first_free_x == -1) {
+ grid->first_free_x = visible_width;
+ grid->first_free_y = 0;
+ } else if (grid->first_free_x >= visible_width) {
+ if (grid->first_free_y == grid->height - 1) {
+ grid->first_free_x = -1;
+ grid->first_free_y = -1;
+ } else {
+ grid->first_free_x = 0;
+ grid->first_free_y++;
+ icon_grid_update_first_free_forward (grid);
+ }
+ }
+
+ grid->visible_width = visible_width;
+}
+
+static void
+icon_grid_resize (GnomeIconContainerIconGrid *grid,
+ guint width, guint height)
+{
+ guint new_alloc_width, new_alloc_height;
+
+ if (width > grid->alloc_width || height > grid->alloc_height) {
+ if (grid->alloc_width > 0)
+ new_alloc_width = grid->alloc_width;
+ else
+ new_alloc_width = INITIAL_GRID_WIDTH;
+ while (new_alloc_width < width)
+ new_alloc_width *= 2;
+
+ if (grid->alloc_height > 0)
+ new_alloc_height = grid->alloc_height;
+ else
+ new_alloc_height = INITIAL_GRID_HEIGHT;
+ while (new_alloc_height < height)
+ new_alloc_height *= 2;
+
+ icon_grid_resize_allocation (grid, new_alloc_width,
+ new_alloc_height);
+ }
+
+ grid->width = width;
+ grid->height = height;
+
+ if (grid->visible_width > grid->width)
+ icon_grid_set_visible_width (grid, grid->width);
+}
+
+static void
+icon_grid_maybe_resize (GnomeIconContainerIconGrid *grid,
+ guint x, guint y)
+{
+ guint new_width, new_height;
+
+ if (x < grid->width && y < grid->height)
+ return;
+
+ if (x >= grid->width)
+ new_width = x + 1;
+ else
+ new_width = grid->width;
+
+ if (y >= grid->height)
+ new_height = y + 1;
+ else
+ new_height = grid->height;
+
+ icon_grid_resize (grid, new_width, new_height);
+}
+
+static void
+icon_grid_add (GnomeIconContainerIconGrid *grid,
+ GnomeIconContainerIcon *icon,
+ guint x, guint y)
+{
+ GList **elem_ptr;
+
+ icon_grid_maybe_resize (grid, x, y);
+
+ elem_ptr = icon_grid_get_element_ptr (grid, x, y);
+ *elem_ptr = g_list_prepend (*elem_ptr, icon);
+
+ if (x == grid->first_free_x && y == grid->first_free_y)
+ icon_grid_update_first_free_forward (grid);
+}
+
+static void
+icon_grid_remove (GnomeIconContainerIconGrid *grid,
+ GnomeIconContainerIcon *icon,
+ guint x, guint y)
+{
+ GList **elem_ptr;
+
+ elem_ptr = icon_grid_get_element_ptr (grid, x, y);
+
+ g_return_if_fail (*elem_ptr != NULL);
+
+ *elem_ptr = g_list_remove (*elem_ptr, icon);
+
+ if (*elem_ptr == NULL) {
+ if ((grid->first_free_x == -1 && grid->first_free_y == -1)
+ || grid->first_free_y > y
+ || (grid->first_free_y == y && grid->first_free_x > x)) {
+ grid->first_free_x = x;
+ grid->first_free_y = y;
+ }
+ }
+}
+
+static void
+icon_grid_add_auto (GnomeIconContainerIconGrid *grid,
+ GnomeIconContainerIcon *icon,
+ guint *x_return, guint *y_return)
+{
+ GList **empty_elem_ptr;
+
+ if (grid->first_free_x < 0 || grid->first_free_y < 0
+ || grid->height == 0 || grid->width == 0) {
+ /* No empty element: add a row. */
+ icon_grid_resize (grid, MAX (grid->width, 1), grid->height + 1);
+ grid->first_free_x = 0;
+ grid->first_free_y = grid->height - 1;
+ }
+
+ empty_elem_ptr = icon_grid_get_element_ptr (grid,
+ grid->first_free_x,
+ grid->first_free_y);
+
+ *empty_elem_ptr = g_list_prepend (*empty_elem_ptr, icon);
+
+ if (x_return != NULL)
+ *x_return = grid->first_free_x;
+ if (y_return != NULL)
+ *y_return = grid->first_free_y;
+
+ icon_grid_update_first_free_forward (grid);
+}
+
+static gint
+icon_grid_cell_compare_by_x (gconstpointer ap,
+ gconstpointer bp)
+{
+ GnomeIconContainerIcon *a, *b;
+
+ a = (GnomeIconContainerIcon *) ap;
+ b = (GnomeIconContainerIcon *) bp;
+
+ return (gint) a->x - b->x;
+}
+
+
+static void
+world_to_grid (GnomeIconContainer *container,
+ gint world_x, gint world_y,
+ guint *grid_x_return, guint *grid_y_return)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ if (grid_x_return != NULL) {
+ if (world_x < 0)
+ *grid_x_return = 0;
+ else
+ *grid_x_return = world_x / GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ if (grid_y_return != NULL) {
+ if (world_y < 0)
+ *grid_y_return = 0;
+ else
+ *grid_y_return = world_y / GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+}
+
+static void
+grid_to_world (GnomeIconContainer *container,
+ guint grid_x, guint grid_y,
+ gint *world_x_return, gint *world_y_return)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ if (world_x_return != NULL)
+ *world_x_return
+ = grid_x * GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+
+ if (world_y_return != NULL)
+ *world_y_return
+ = grid_y * GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+}
+
+
+/* Utility functions for GnomeIconContainer. */
+
+static void
+scroll (GnomeIconContainer *container,
+ gint delta_x, gint delta_y)
+{
+ GnomeIconContainerPrivate *priv;
+ GtkAdjustment *hadj, *vadj;
+ GtkAllocation *allocation;
+ gfloat vnew, hnew;
+ gfloat hmax, vmax;
+
+ priv = container->priv;
+
+ hadj = GTK_LAYOUT (container)->hadjustment;
+ vadj = GTK_LAYOUT (container)->vadjustment;
+
+ allocation = &GTK_WIDGET (container)->allocation;
+
+ if (container->priv->width > allocation->width)
+ hmax = (gfloat) (container->priv->width - allocation->width);
+ else
+ hmax = 0.0;
+
+ if (container->priv->height > allocation->height)
+ vmax = (gfloat) (container->priv->height - allocation->height);
+ else
+ vmax = 0.0;
+
+ hnew = CLAMP (hadj->value + (gfloat) delta_x, 0.0, hmax);
+ vnew = CLAMP (vadj->value + (gfloat) delta_y, 0.0, vmax);
+
+ if (hnew != hadj->value) {
+ hadj->value = hnew;
+ gtk_signal_emit_by_name (GTK_OBJECT (hadj), "value_changed");
+ }
+ if (vnew != vadj->value) {
+ vadj->value = vnew;
+ gtk_signal_emit_by_name (GTK_OBJECT (vadj), "value_changed");
+ }
+}
+
+static void
+make_icon_visible (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon)
+{
+ GnomeIconContainerPrivate *priv;
+ GtkAllocation *allocation;
+ GtkAdjustment *hadj, *vadj;
+ gint x1, y1, x2, y2;
+
+ priv = container->priv;
+ allocation = &GTK_WIDGET (container)->allocation;
+
+ if (priv->height < allocation->height
+ && priv->width < allocation->width)
+ return;
+
+ hadj = GTK_LAYOUT (container)->hadjustment;
+ vadj = GTK_LAYOUT (container)->vadjustment;
+
+ icon_get_bounding_box (icon, &x1, &y1, &x2, &y2);
+
+ if (y1 < vadj->value)
+ gtk_adjustment_set_value (vadj, y1);
+ else if (y2 > vadj->value + allocation->height)
+ gtk_adjustment_set_value (vadj, y2 - allocation->height);
+
+ if (x1 < hadj->value)
+ gtk_adjustment_set_value (hadj, x1);
+ else if (x2 > hadj->value + allocation->width)
+ gtk_adjustment_set_value (hadj, x2 - allocation->width);
+}
+
+static gint
+kbd_icon_visibility_timeout_cb (gpointer data)
+{
+ GnomeIconContainer *container;
+
+ GDK_THREADS_ENTER ();
+
+ container = GNOME_ICON_CONTAINER (data);
+
+ if (container->priv->kbd_current != NULL)
+ make_icon_visible (container, container->priv->kbd_current);
+ container->priv->kbd_icon_visibility_timer_tag = -1;
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static void
+unschedule_kbd_icon_visibility (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ if (priv->kbd_icon_visibility_timer_tag != -1)
+ gtk_timeout_remove (priv->kbd_icon_visibility_timer_tag);
+}
+
+static void
+schedule_kbd_icon_visibility (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ unschedule_kbd_icon_visibility (container);
+
+ priv->kbd_icon_visibility_timer_tag
+ = gtk_timeout_add (KBD_ICON_VISIBILITY_TIMEOUT,
+ kbd_icon_visibility_timeout_cb,
+ container);
+}
+
+static void
+prepare_for_layout (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+
+ priv = container->priv;
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ icon->layout_done = FALSE;
+ }
+}
+
+/* Line up icons belonging to the grid line pointed by `p'. */
+static void
+line_up (GnomeIconContainer *container,
+ GList **p)
+{
+ GnomeIconContainerIconGrid *grid;
+ GList **temp_line;
+ guint i;
+
+ grid = container->priv->grid;
+
+ temp_line = alloca (grid->width * sizeof (*temp_line));
+ for (i = 0; i < grid->width; i++)
+ temp_line[i] = p[i];
+}
+
+/* Find the "first" icon (in left-to-right, top-to-bottom order) in
+ `container'. */
+static GnomeIconContainerIcon *
+find_first (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *first;
+ GList **p;
+ guint i, j;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (grid->width == 0 || grid->height == 0)
+ return NULL;
+
+ first = NULL;
+ p = grid->elems;
+ for (i = 0; i < grid->height; i++) {
+ for (j = 0; j < grid->width; j++) {
+ GList *q;
+
+ for (q = p[j]; q != NULL; q = q->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = q->data;
+ if (first == NULL
+ || icon->y < first->y
+ || (icon->y == first->y
+ && icon->x < first->x))
+ first = icon;
+ }
+ }
+
+ p += grid->alloc_width;
+ }
+
+ return first;
+}
+
+static GnomeIconContainerIcon *
+find_last (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *last;
+ GList **p;
+ gint i, j;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ last = NULL;
+
+ if (grid->height == 0 || grid->width == 0)
+ return NULL;
+
+ p = icon_grid_get_element_ptr (grid, 0, grid->height - 1);
+
+ for (i = grid->height - 1; i >= 0; i--) {
+ for (j = grid->width - 1; j >= 0; j--) {
+ GList *q;
+
+ for (q = p[j]; q != NULL; q = q->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = q->data;
+ if (last == NULL
+ || icon->y > last->y
+ || (icon->y == last->y
+ && icon->x > last->x))
+ last = icon;
+ }
+ }
+
+ p -= grid->alloc_width;
+ }
+
+ return last;
+}
+
+/* Set `icon' as the icon currently selected for keyboard operations. */
+static void
+set_kbd_current (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ gboolean schedule_visibility)
+{
+ GnomeIconContainerPrivate *priv;
+ gint x1, y1, x2, y2;
+
+ priv = container->priv;
+
+ priv->kbd_current = icon;
+
+ icon_get_text_bounding_box (icon, &x1, &y1, &x2, &y2);
+
+ gnome_canvas_item_set (priv->kbd_navigation_rectangle,
+ "x1", (gdouble) x1 - 1,
+ "y1", (gdouble) y1 - 1,
+ "x2", (gdouble) x2,
+ "y2", (gdouble) y2,
+ NULL);
+ gnome_canvas_item_show (priv->kbd_navigation_rectangle);
+
+ icon_raise (icon);
+ gnome_canvas_item_raise_to_top (priv->kbd_navigation_rectangle);
+
+ if (schedule_visibility)
+ schedule_kbd_icon_visibility (container);
+ else
+ unschedule_kbd_icon_visibility (container);
+}
+
+static void
+unset_kbd_current (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ priv->kbd_current = NULL;
+ gnome_canvas_item_hide (priv->kbd_navigation_rectangle);
+
+ unschedule_kbd_icon_visibility (container);
+}
+
+
+/* Idle operation handler. */
+
+static void
+set_scroll_region (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GtkAllocation *allocation;
+ GtkAdjustment *vadj, *hadj;
+ gdouble x1, y1, x2, y2;
+ guint scroll_width, scroll_height;
+
+ priv = container->priv;
+ grid = priv->grid;
+ allocation = &(GTK_WIDGET (container)->allocation);
+ hadj = GTK_LAYOUT (container)->hadjustment;
+ vadj = GTK_LAYOUT (container)->vadjustment;
+
+ /* FIXME: We can do this more efficiently. */
+ gnome_canvas_item_get_bounds (GNOME_CANVAS (container)->root,
+ &x1, &y1, &x2, &y2);
+
+ priv->width = x2 + GNOME_ICON_CONTAINER_CELL_SPACING (container);
+ priv->height = y2 + GNOME_ICON_CONTAINER_CELL_SPACING (container);
+
+ scroll_width = MAX (priv->width, allocation->width);
+ scroll_height = MAX (priv->height, allocation->height);
+
+ gnome_canvas_set_scroll_region (GNOME_CANVAS (container),
+ 0.0, 0.0,
+ (gdouble) scroll_width,
+ (gdouble) scroll_height);
+
+ if (priv->width <= allocation->width)
+ gtk_adjustment_set_value (hadj, 0.0);
+ if (priv->height <= allocation->height)
+ gtk_adjustment_set_value (vadj, 0.0);
+}
+
+static gint
+idle_handler (gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+
+ GDK_THREADS_ENTER ();
+
+ container = GNOME_ICON_CONTAINER (data);
+ priv = container->priv;
+
+ set_scroll_region (container);
+
+ if (priv->icons != NULL && priv->kbd_current == NULL)
+ set_kbd_current (container, find_first (container), FALSE);
+
+ container->priv->idle_id = 0;
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static void
+add_idle (GnomeIconContainer *container)
+{
+ if (container->priv->idle_id != 0)
+ return;
+
+ container->priv->idle_id = gtk_idle_add (idle_handler, container);
+}
+
+static void
+remove_idle (GnomeIconContainer *container)
+{
+ if (container->priv->idle_id == 0)
+ return;
+
+ gtk_idle_remove (container->priv->idle_id);
+ container->priv->idle_id = 0;
+}
+
+
+/* Container-level icon handling functions. */
+
+/* Select an icon. Return TRUE if selection has changed. */
+static gboolean
+select_icon (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ gboolean sel)
+{
+ GnomeIconContainerPrivate *priv;
+ gboolean was_selected;
+
+ priv = container->priv;
+
+ was_selected = icon->is_selected;
+ icon_select (icon, sel);
+
+ if ((! was_selected && sel) || (was_selected && ! sel))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void
+toggle_icon (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon)
+{
+ icon_toggle_selection (icon);
+}
+
+static gboolean
+unselect_all_but_one (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon_to_avoid)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+ gboolean selection_changed;
+
+ priv = container->priv;
+ selection_changed = FALSE;
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon != icon_to_avoid && icon->is_selected) {
+ icon_select (icon, FALSE);
+ selection_changed = TRUE;
+ }
+ }
+
+ return selection_changed;
+}
+
+static gboolean
+unselect_all (GnomeIconContainer *container)
+{
+ return unselect_all_but_one (container, NULL);
+}
+
+/* FIXME: This could be optimized a bit. */
+static void
+move_icon (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ gint x, gint y)
+{
+ GnomeIconContainerPrivate *priv;
+ gint old_x, old_y;
+ guint old_grid_x, old_grid_y;
+ gint old_x_offset, old_y_offset;
+ guint new_grid_x, new_grid_y;
+ gint new_x_offset, new_y_offset;
+
+ priv = container->priv;
+
+ old_x = icon->x;
+ old_y = icon->y;
+
+ world_to_grid (container, old_x, old_y, &old_grid_x, &old_grid_y);
+ old_x_offset = old_x % GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ old_y_offset = old_y % GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ world_to_grid (container, x, y, &new_grid_x, &new_grid_y);
+ new_x_offset = x % GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ new_y_offset = y % GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ icon_grid_remove (priv->grid, icon, old_grid_x, old_grid_y);
+ if (old_x_offset > 0)
+ icon_grid_remove (priv->grid, icon,
+ old_grid_x + 1, old_grid_y);
+ if (old_y_offset > 0)
+ icon_grid_remove (priv->grid, icon,
+ old_grid_x, old_grid_y + 1);
+ if (old_x_offset > 0 && old_y_offset > 0)
+ icon_grid_remove (priv->grid, icon,
+ old_grid_x + 1, old_grid_y + 1);
+
+ icon_grid_add (priv->grid, icon, new_grid_x, new_grid_y);
+ if (new_x_offset > 0)
+ icon_grid_add (priv->grid, icon, new_grid_x + 1, new_grid_y);
+ if (new_y_offset > 0)
+ icon_grid_add (priv->grid, icon, new_grid_x, new_grid_y + 1);
+ if (new_x_offset > 0 && new_y_offset > 0)
+ icon_grid_add (priv->grid, icon, new_grid_x + 1, new_grid_y + 1);
+
+ icon_position (icon, container, x, y);
+}
+
+static void
+change_icon_mode (GnomeIconContainer *container,
+ GnomeIconContainerIconMode mode)
+{
+ GnomeIconContainerIconModeInfo *old_mode_info;
+ GnomeIconContainerIconModeInfo *new_mode_info;
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+ gdouble x_factor, y_factor;
+
+ priv = container->priv;
+ if (mode == priv->icon_mode)
+ return;
+
+ old_mode_info = gnome_icon_container_icon_mode_info + priv->icon_mode;
+ new_mode_info = gnome_icon_container_icon_mode_info + mode;
+
+ priv->icon_mode = mode;
+
+ x_factor = ((gdouble) new_mode_info->cell_width
+ / (gdouble) old_mode_info->cell_width);
+ y_factor = ((gdouble) new_mode_info->cell_height
+ / (gdouble) old_mode_info->cell_height);
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+
+ icon_configure (icon, container);
+ icon_position (icon, container,
+ icon->x * x_factor, icon->y * y_factor);
+ }
+
+ add_idle (container);
+
+ if (priv->kbd_current != NULL)
+ set_kbd_current (container, priv->kbd_current, TRUE);
+}
+
+
+/* Implementation of rubberband selection. */
+
+static gboolean
+rubberband_select_in_cell (GList *cell,
+ gdouble curr_x1, gdouble curr_y1,
+ gdouble curr_x2, gdouble curr_y2,
+ gdouble prev_x1, gdouble prev_y1,
+ gdouble prev_x2, gdouble prev_y2)
+{
+ GList *p;
+ gboolean selection_changed;
+
+ selection_changed = FALSE;
+
+ for (p = cell; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+ gboolean in_curr_region;
+ gboolean in_prev_region;
+
+ icon = p->data;
+
+ in_curr_region = icon_is_in_region (icon,
+ curr_x1, curr_y1,
+ curr_x2, curr_y2);
+
+ in_prev_region = icon_is_in_region (icon,
+ prev_x1, prev_y1,
+ prev_x2, prev_y2);
+
+ if (in_curr_region && ! in_prev_region) {
+ if (icon->was_selected_before_rubberband) {
+ if (icon->is_selected) {
+ icon_select (icon, FALSE);
+ selection_changed = TRUE;
+ }
+ } else {
+ if (! icon->is_selected) {
+ icon_select (icon, TRUE);
+ selection_changed = TRUE;
+ }
+ }
+ } else if (in_prev_region && ! in_curr_region) {
+ if (icon->was_selected_before_rubberband) {
+ if (! icon->is_selected) {
+ icon_select (icon, TRUE);
+ selection_changed = TRUE;
+ }
+ } else {
+ if (icon->is_selected) {
+ icon_select (icon, FALSE);
+ selection_changed = TRUE;
+ }
+ }
+ }
+ }
+
+ return selection_changed;
+}
+
+static void
+rubberband_select (GnomeIconContainer *container,
+ gdouble curr_x1, gdouble curr_y1,
+ gdouble curr_x2, gdouble curr_y2,
+ gdouble prev_x1, gdouble prev_y1,
+ gdouble prev_x2, gdouble prev_y2)
+{
+ GList **p;
+ GnomeIconContainerIconGrid *grid;
+ guint curr_grid_x1, curr_grid_y1;
+ guint curr_grid_x2, curr_grid_y2;
+ guint prev_grid_x1, prev_grid_y1;
+ guint prev_grid_x2, prev_grid_y2;
+ guint grid_x1, grid_y1;
+ guint grid_x2, grid_y2;
+ guint i, j;
+ gboolean selection_changed;
+
+ grid = container->priv->grid;
+
+ world_to_grid (container, curr_x1, curr_y1, &curr_grid_x1, &curr_grid_y1);
+ world_to_grid (container, curr_x2, curr_y2, &curr_grid_x2, &curr_grid_y2);
+ world_to_grid (container, prev_x1, prev_y1, &prev_grid_x1, &prev_grid_y1);
+ world_to_grid (container, prev_x2, prev_y2, &prev_grid_x2, &prev_grid_y2);
+
+ grid_x1 = MIN (curr_grid_x1, prev_grid_x1);
+ grid_x2 = MAX (curr_grid_x2, prev_grid_x2);
+ grid_y1 = MIN (curr_grid_y1, prev_grid_y1);
+ grid_y2 = MAX (curr_grid_y2, prev_grid_y2);
+
+ selection_changed = FALSE;
+
+ p = icon_grid_get_element_ptr (grid, grid_x1, grid_y1);
+ for (i = 0; i <= grid_y2 - grid_y1; i++) {
+ for (j = 0; j <= grid_x2 - grid_x1; j++) {
+ if (rubberband_select_in_cell (p[j],
+ curr_x1, curr_y1,
+ curr_x2, curr_y2,
+ prev_x1, prev_y1,
+ prev_x2, prev_y2))
+ selection_changed = TRUE;
+ }
+
+ p += grid->alloc_width;
+ }
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+}
+
+static gint
+rubberband_timeout_cb (gpointer data)
+{
+ GnomeIconContainer *container;
+ GtkWidget *widget;
+ GnomeIconContainerRubberbandInfo *rinfo;
+ gint x, y;
+ gdouble x1, y1, x2, y2;
+ gdouble world_x, world_y;
+ gint x_scroll, y_scroll;
+
+ GDK_THREADS_ENTER ();
+
+ widget = GTK_WIDGET (data);
+ container = GNOME_ICON_CONTAINER (data);
+ rinfo = &container->priv->rubberband_info;
+
+ gdk_window_get_pointer (widget->window, &x, &y, NULL);
+
+ if (x < 0) {
+ x_scroll = x;
+ x = 0;
+ } else if (x >= widget->allocation.width) {
+ x_scroll = x - widget->allocation.width + 1;
+ x = widget->allocation.width - 1;
+ } else {
+ x_scroll = 0;
+ }
+
+ if (y < 0) {
+ y_scroll = y;
+ y = 0;
+ } else if (y >= widget->allocation.height) {
+ y_scroll = y - widget->allocation.height + 1;
+ y = widget->allocation.height - 1;
+ } else {
+ y_scroll = 0;
+ }
+
+ if (y_scroll == 0 && x_scroll == 0
+ && rinfo->prev_x == x && rinfo->prev_y == y) {
+ GDK_THREADS_LEAVE ();
+ return TRUE;
+ }
+
+ scroll (container, x_scroll, y_scroll);
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container),
+ x, y, &world_x, &world_y);
+
+ if (world_x < rinfo->start_x) {
+ x1 = world_x;
+ x2 = rinfo->start_x;
+ } else {
+ x1 = rinfo->start_x;
+ x2 = world_x;
+ }
+
+ if (world_y < rinfo->start_y) {
+ y1 = world_y;
+ y2 = rinfo->start_y;
+ } else {
+ y1 = rinfo->start_y;
+ y2 = world_y;
+ }
+
+ gnome_canvas_item_set (rinfo->selection_rectangle,
+ "x1", (gdouble) x1,
+ "y1", (gdouble) y1,
+ "x2", (gdouble) x2,
+ "y2", (gdouble) y2,
+ NULL);
+
+ rubberband_select (container,
+ x1, y1, x2, y2,
+ rinfo->prev_x1, rinfo->prev_y1,
+ rinfo->prev_x2, rinfo->prev_y2);
+
+ rinfo->prev_x = x;
+ rinfo->prev_y = y;
+ rinfo->prev_x1 = x1;
+ rinfo->prev_y1 = y1;
+ rinfo->prev_x2 = x2;
+ rinfo->prev_y2 = y2;
+
+ GDK_THREADS_LEAVE ();
+
+ return TRUE;
+}
+
+static void
+start_rubberbanding (GnomeIconContainer *container,
+ GdkEventButton *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerRubberbandInfo *rinfo;
+ GList *p;
+
+ priv = container->priv;
+ rinfo = &priv->rubberband_info;
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ icon->was_selected_before_rubberband = icon->is_selected;
+ }
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container),
+ event->x, event->y,
+ &rinfo->start_x, &rinfo->start_y);
+
+ rinfo->selection_rectangle
+ = gnome_canvas_item_new (gnome_canvas_root
+ (GNOME_CANVAS (container)),
+ gnome_canvas_rect_get_type (),
+ "x1", rinfo->start_x,
+ "y1", rinfo->start_y,
+ "x2", rinfo->start_x,
+ "y2", rinfo->start_y,
+ "outline_color", "black",
+ "outline_stipple", stipple,
+ "width_pixels", 1,
+ NULL);
+
+ rinfo->prev_x = rinfo->prev_x1 = rinfo->prev_x2 = event->x;
+ rinfo->prev_y = rinfo->prev_y1 = rinfo->prev_y2 = event->y;
+
+ rinfo->active = TRUE;
+
+ rinfo->timer_tag = gtk_timeout_add (RUBBERBAND_TIMEOUT_INTERVAL,
+ rubberband_timeout_cb,
+ container);
+
+ gnome_canvas_item_grab (rinfo->selection_rectangle,
+ (GDK_POINTER_MOTION_MASK
+ | GDK_BUTTON_RELEASE_MASK),
+ NULL, event->time);
+}
+
+static void
+stop_rubberbanding (GnomeIconContainer *container,
+ GdkEventButton *event)
+{
+ GnomeIconContainerRubberbandInfo *rinfo;
+
+ rinfo = &container->priv->rubberband_info;
+
+ gtk_timeout_remove (rinfo->timer_tag);
+ rinfo->active = FALSE;
+
+ gnome_canvas_item_ungrab (rinfo->selection_rectangle, event->time);
+ gtk_object_destroy (GTK_OBJECT (rinfo->selection_rectangle));
+}
+
+
+/* Keyboard navigation. */
+
+static void
+kbd_move_to (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ GdkEventKey *event)
+{
+ if (! (event->state & GDK_CONTROL_MASK)) {
+ gboolean selection_changed;
+
+ selection_changed = unselect_all (container);
+ selection_changed |= select_icon (container, icon, TRUE);
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+
+ set_kbd_current (container, icon, FALSE);
+ make_icon_visible (container, icon);
+}
+
+static void
+kbd_home (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerIcon *first;
+
+ first = find_first (container);
+ if (first != NULL)
+ kbd_move_to (container, first, event);
+}
+
+static void
+kbd_end (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerIcon *last;
+
+ last = find_last (container);
+ if (last != NULL)
+ kbd_move_to (container, last, event);
+}
+
+static void
+kbd_left (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *nearmost;
+ GList **e;
+ guint grid_x, grid_y;
+ gint x, y;
+ gint max_x;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (priv->kbd_current == NULL)
+ return;
+
+ world_to_grid (container, priv->kbd_current->x, priv->kbd_current->y,
+ &grid_x, &grid_y);
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+
+ e = icon_grid_get_element_ptr (grid, 0, grid_y);
+ nearmost = NULL;
+
+ max_x = priv->kbd_current->x;
+
+ while (1) {
+ while (1) {
+ GList *p;
+
+ for (p = e[grid_x]; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon == priv->kbd_current
+ || icon->x < x
+ || icon->y < y)
+ continue;
+
+ if (icon->x <= max_x
+ && (nearmost == NULL
+ || icon->x > nearmost->x))
+ nearmost = icon;
+ }
+
+ if (nearmost != NULL) {
+ kbd_move_to (container, nearmost, event);
+ return;
+ }
+
+ if (grid_x == 0)
+ break;
+
+ grid_x--;
+ x -= GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ if (grid_y == 0)
+ break;
+
+ grid_x = grid->width - 1;
+ max_x = G_MAXINT;
+ grid_to_world (container, grid_x, 0, &x, NULL);
+
+ e -= grid->alloc_width;
+ grid_y--;
+ y -= GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+}
+
+static void
+kbd_up (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *nearmost;
+ GList **e;
+ guint grid_x, grid_y;
+ gint x, y;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (priv->kbd_current == NULL)
+ return;
+
+ world_to_grid (container, priv->kbd_current->x, priv->kbd_current->y,
+ &grid_x, &grid_y);
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+
+ e = icon_grid_get_element_ptr (grid, grid_x, grid_y);
+ nearmost = NULL;
+
+ while (1) {
+ GList *p;
+
+ p = *e;
+
+ for (; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon == priv->kbd_current
+ || icon->x < x
+ || icon->y < y)
+ continue;
+
+ if (icon->y <= priv->kbd_current->y
+ && (nearmost == NULL || icon->y > nearmost->y))
+ nearmost = icon;
+ }
+
+ if (nearmost != NULL)
+ break;
+
+ if (grid_y == 0)
+ break;
+
+ e -= grid->alloc_width;
+ grid_y--;
+ y -= GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+
+ if (nearmost != NULL)
+ kbd_move_to (container, nearmost, event);
+}
+
+static void
+kbd_right (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *nearmost;
+ GList **e;
+ guint grid_x, grid_y;
+ gint x, y;
+ gint min_x;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (priv->kbd_current == NULL)
+ return;
+
+ world_to_grid (container, priv->kbd_current->x, priv->kbd_current->y,
+ &grid_x, &grid_y);
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+
+ e = icon_grid_get_element_ptr (grid, 0, grid_y);
+ nearmost = NULL;
+
+ min_x = priv->kbd_current->x;
+
+ while (grid_y < grid->height) {
+ while (grid_x < grid->width) {
+ GList *p;
+
+ for (p = e[grid_x]; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon == priv->kbd_current
+ || icon->x < x
+ || icon->y < y)
+ continue;
+
+ if (icon->x >= min_x
+ && (nearmost == NULL
+ || icon->x < nearmost->x))
+ nearmost = icon;
+ }
+
+ if (nearmost != NULL) {
+ kbd_move_to (container, nearmost, event);
+ return;
+ }
+
+ grid_x++;
+ x += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ grid_x = 0;
+ min_x = 0;
+ x = 0;
+
+ e += grid->alloc_width;
+ grid_y++;
+ y += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+}
+
+static void
+kbd_down (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *nearmost;
+ GList **e;
+ guint grid_x, grid_y;
+ gint x, y;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (priv->kbd_current == NULL)
+ return;
+
+ world_to_grid (container, priv->kbd_current->x, priv->kbd_current->y,
+ &grid_x, &grid_y);
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+
+ e = icon_grid_get_element_ptr (grid, grid_x, grid_y);
+ nearmost = NULL;
+
+ while (grid_y < grid->height) {
+ GList *p;
+
+ p = *e;
+
+ for (; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon == priv->kbd_current
+ || icon->x < x
+ || icon->y < y)
+ continue;
+
+ if (icon->y >= priv->kbd_current->y
+ && (nearmost == NULL || icon->y < nearmost->y))
+ nearmost = icon;
+ }
+
+ if (nearmost != NULL)
+ break;
+
+ e += grid->alloc_width;
+ grid_y++;
+ y += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+
+ if (nearmost != NULL)
+ kbd_move_to (container, nearmost, event);
+}
+
+static void
+kbd_space (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ if (container->priv->kbd_current != NULL) {
+ if (select_icon (container, container->priv->kbd_current, TRUE))
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+}
+
+
+/* GtkObject methods. */
+
+static void
+destroy (GtkObject *object)
+{
+ GnomeIconContainer *container;
+
+ container = GNOME_ICON_CONTAINER (object);
+
+ icon_grid_destroy (container->priv->grid);
+
+ gnome_icon_container_dnd_fini (container);
+
+ g_free (container->priv);
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy != NULL)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+
+/* GtkWidget methods. */
+
+static void
+size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ requisition->width = 1;
+ requisition->height = 1;
+}
+
+static void
+size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerIconGrid *grid;
+ guint visible_width, visible_height;
+
+ if (GTK_WIDGET_CLASS (parent_class)->size_allocate)
+ (* GTK_WIDGET_CLASS (parent_class)->size_allocate)
+ (widget, allocation);
+
+ container = GNOME_ICON_CONTAINER (widget);
+ grid = container->priv->grid;
+
+ world_to_grid (container,
+ allocation->width, 0,
+ &visible_width, &visible_height);
+
+ if (visible_width == 0)
+ visible_width = 1;
+
+ if (visible_width > grid->width || visible_height > grid->height)
+ icon_grid_resize (grid,
+ MAX (visible_width, grid->width),
+ MAX (visible_height, grid->height));
+
+ icon_grid_set_visible_width (grid, visible_width);
+
+ set_scroll_region (container);
+}
+
+static void
+realize (GtkWidget *widget)
+{
+ GtkStyle *style;
+
+ if (GTK_WIDGET_CLASS (parent_class)->realize)
+ (* GTK_WIDGET_CLASS (parent_class)->realize) (widget);
+
+ 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 (widget)->bin_window,
+ & widget->style->bg [GTK_STATE_NORMAL]);
+}
+
+static gboolean
+button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ gboolean return_value;
+ GnomeIconContainer *container;
+
+ /* 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;
+
+ container = GNOME_ICON_CONTAINER (widget);
+
+ if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
+ if (! (event->state & GDK_CONTROL_MASK)) {
+ gboolean selection_changed;
+
+ selection_changed = unselect_all (container);
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+
+ start_rubberbanding (container, event);
+ return TRUE;
+ }
+
+ gtk_signal_emit (GTK_OBJECT (widget), signals[BUTTON_PRESS], event,
+ &return_value);
+
+ return return_value;
+}
+
+static gboolean
+button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ priv = container->priv;
+
+ if (event->button == 1 && priv->rubberband_info.active) {
+ stop_rubberbanding (container, event);
+ return TRUE;
+ }
+
+ if (event->button == priv->drag_button) {
+ priv->drag_button = 0;
+ if (! priv->doing_drag
+ && ! (event->state & GDK_CONTROL_MASK)) {
+ gboolean selection_changed;
+
+ selection_changed
+ = unselect_all_but_one (container,
+ priv->drag_icon);
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+
+ if (priv->drag_icon != NULL) {
+ set_kbd_current (container, priv->drag_icon, TRUE);
+ priv->drag_icon = NULL;
+ }
+
+ if (priv->doing_drag)
+ gnome_icon_container_dnd_end_drag (container);
+
+ priv->doing_drag = FALSE;
+ return TRUE;
+ }
+
+ if (GTK_WIDGET_CLASS (parent_class)->button_release_event != NULL)
+ return GTK_WIDGET_CLASS (parent_class)->button_release_event
+ (widget, event);
+
+ return FALSE;
+}
+
+static gint
+motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *motion)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+ double world_x, world_y;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ priv = container->priv;
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container),
+ motion->x, motion->y,
+ &world_x, &world_y);
+
+#define SNAP_RESISTANCE 2 /* GMC has this set to 3, but it's too much for
+ my taste. */
+ if (priv->drag_button != 0
+ && abs (priv->drag_x - world_x) >= SNAP_RESISTANCE
+ && abs (priv->drag_y - world_y) >= SNAP_RESISTANCE) {
+ priv->doing_drag = TRUE;
+
+ /* KLUDGE ALERT: Poke the starting values into the motion
+ structure so that dragging behaves as expected. */
+ motion->x = priv->drag_x;
+ motion->y = priv->drag_y;
+
+ gnome_icon_container_dnd_begin_drag (container,
+ GDK_ACTION_MOVE,
+ priv->drag_button,
+ motion);
+ return TRUE;
+ }
+#undef SNAP_RESISTANCE
+
+ if (GTK_WIDGET_CLASS (parent_class)->motion_notify_event != NULL)
+ return (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event)
+ (widget, motion);
+
+ return FALSE;
+}
+
+static gint
+key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ GnomeIconContainer *container;
+
+ if ((* GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event))
+ return TRUE;
+
+ container = GNOME_ICON_CONTAINER (widget);
+
+ switch (event->keyval) {
+ case GDK_Home:
+ kbd_home (container, event);
+ break;
+ case GDK_End:
+ kbd_end (container, event);
+ break;
+ case GDK_Left:
+ kbd_left (container, event);
+ break;
+ case GDK_Up:
+ kbd_up (container, event);
+ break;
+ case GDK_Right:
+ kbd_right (container, event);
+ break;
+ case GDK_Down:
+ kbd_down (container, event);
+ break;
+ case GDK_space:
+ kbd_space (container, event);
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/* Initialization. */
+
+static void
+class_init (GnomeIconContainerClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ /* Derive from GnomeCanvas. */
+
+ parent_class = gtk_type_class (gnome_canvas_get_type ());
+
+ /* GnomeIconContainer class. */
+
+ class->button_press = NULL;
+
+ /* GtkObject class. */
+
+ object_class = GTK_OBJECT_CLASS (class);
+ object_class->destroy = destroy;
+
+ /* Signals. */
+
+ signals[SELECTION_CHANGED]
+ = gtk_signal_new ("selection_changed",
+ GTK_RUN_LAST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GnomeIconContainerClass,
+ selection_changed),
+ gtk_marshal_NONE__NONE,
+ GTK_TYPE_NONE, 0);
+ signals[BUTTON_PRESS]
+ = gtk_signal_new ("button_press",
+ GTK_RUN_LAST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GnomeIconContainerClass,
+ button_press),
+ gtk_marshal_BOOL__POINTER,
+ GTK_TYPE_BOOL, 1,
+ GTK_TYPE_GDK_EVENT);
+ signals[ACTIVATE]
+ = gtk_signal_new ("activate",
+ GTK_RUN_LAST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GnomeIconContainerClass,
+ activate),
+ gtk_marshal_NONE__POINTER_POINTER,
+ GTK_TYPE_NONE, 2,
+ GTK_TYPE_STRING,
+ GTK_TYPE_POINTER);
+ signals[CONTEXT_CLICK]
+ = gtk_signal_new ("context_click",
+ GTK_RUN_LAST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GnomeIconContainerClass,
+ activate),
+ gtk_marshal_NONE__POINTER_POINTER,
+ GTK_TYPE_NONE, 2,
+ GTK_TYPE_STRING,
+ GTK_TYPE_POINTER);
+
+ gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL);
+
+ /* GtkWidget class. */
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->size_request = size_request;
+ widget_class->size_allocate = size_allocate;
+ widget_class->realize = realize;
+ widget_class->button_press_event = button_press_event;
+ widget_class->button_release_event = button_release_event;
+ widget_class->motion_notify_event = motion_notify_event;
+ widget_class->key_press_event = key_press_event;
+
+ /* Initialize the stipple bitmap. */
+
+ stipple = gdk_bitmap_create_from_data (NULL, stipple_bits, 2, 2);
+}
+
+static void
+init (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = g_new (GnomeIconContainerPrivate, 1);
+
+ priv->base_uri = NULL;
+
+ priv->width = priv->height = 0;
+
+ priv->icons = NULL;
+ priv->num_icons = 0;
+
+ priv->icon_mode = GNOME_ICON_CONTAINER_NORMAL_ICONS;
+
+ priv->grid = icon_grid_new ();
+
+ priv->canvas_item_to_icon = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ priv->kbd_navigation_rectangle
+ = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (container)),
+ gnome_canvas_rect_get_type (),
+ "outline_color", "black",
+ "outline_stipple", stipple,
+ "width_pixels", 1,
+ NULL);
+ gnome_canvas_item_hide (priv->kbd_navigation_rectangle);
+
+ priv->kbd_current = NULL;
+ priv->rubberband_info.active = FALSE;
+ priv->kbd_icon_visibility_timer_tag = -1;
+ priv->idle_id = 0;
+
+ priv->drag_button = 0;
+ priv->drag_icon = NULL;
+ priv->drag_x = priv->drag_y = 0;
+ priv->doing_drag = FALSE;
+
+ priv->browser_mode = FALSE;
+ priv->browser_mode_selection_timer_tag = -1;
+ priv->browser_mode_selection_icon = NULL;
+
+ container->priv = priv;
+
+ /* Set up DnD. */
+ gnome_icon_container_dnd_init (container, stipple);
+
+ /* Request update. */
+ add_idle (container);
+}
+
+
+/* GnomeIconContainerIcon event handling. */
+
+/* Selection in browser mode. */
+static gint
+browser_select_timeout_cb (gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *icon;
+ gboolean selection_changed;
+
+ GDK_THREADS_ENTER ();
+
+ container = GNOME_ICON_CONTAINER (data);
+ priv = container->priv;
+ icon = priv->browser_mode_selection_icon;
+
+ selection_changed = unselect_all (container);
+ selection_changed |= select_icon (container, icon, TRUE);
+
+ set_kbd_current (container, icon, FALSE);
+ make_icon_visible (container, icon);
+
+ /* FIXME: Am I allowed to do this between `GDK_THREADS_ENTER()' and
+ `GDK_THREADS_LEAVE()'? */
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+/* Conceptually, pressing button 1 together with CTRL toggles selection of a
+ single icon without affecting the other icons; without CTRL, it selects a
+ single icon and un-selects all the other icons. But in this latter case,
+ the de-selection should only happen when the button is released if the
+ icon is already selected, because the user might select multiple icons and
+ drag all of them by doing a simple click-drag. */
+static gint
+handle_icon_button_press (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ GdkEventButton *event)
+{
+ GnomeIconContainerPrivate *priv;
+ gdouble world_x, world_y;
+
+ if (event->button != 1)
+ return FALSE;
+
+ priv = container->priv;
+
+ if (event->state & GDK_CONTROL_MASK) {
+ toggle_icon (container, icon);
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ } else if (! icon->is_selected) {
+ unselect_all (container);
+ select_icon (container, icon, TRUE);
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+
+ if (event->type == GDK_2BUTTON_PRESS) {
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[ACTIVATE],
+ icon->text, icon->data);
+
+ /* Double clicking should *never* trigger a D&D action. */
+ priv->drag_button = 0;
+ priv->drag_icon = NULL;
+ return TRUE;
+ }
+
+ if (event->button == 3) {
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[CONTEXT_CLICK],
+ icon->text, icon->data);
+
+ /* FIXME this means you cannot drag with right click. Instead,
+ we should setup a timeout and emit this signal if the
+ timeout expires without movement. */
+ priv->drag_button = 0;
+ priv->drag_icon = NULL;
+ return TRUE;
+ }
+
+ priv->drag_button = event->button;
+ priv->drag_icon = icon;
+ priv->drag_x = event->x;
+ priv->drag_y = event->y;
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container), event->x, event->y,
+ &world_x, &world_y);
+ priv->drag_x_offset = (gint) world_x - icon->x;
+ priv->drag_y_offset = (gint) world_y - icon->y;
+
+ return TRUE;
+}
+
+static gint
+handle_icon_enter_notify (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ GdkEventMotion *motion)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+ if (! priv->browser_mode)
+ return FALSE;
+
+ if (priv->browser_mode_selection_timer_tag != -1)
+ gtk_timeout_remove (priv->browser_mode_selection_timer_tag);
+
+ priv->browser_mode_selection_timer_tag
+ = gtk_timeout_add (BROWSER_MODE_SELECTION_TIMEOUT,
+ browser_select_timeout_cb, container);
+
+ priv->browser_mode_selection_icon = icon;
+
+ return TRUE;
+}
+
+static gint
+handle_icon_leave_notify (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ GdkEventMotion *motion)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+ if (! priv->browser_mode)
+ return FALSE;
+
+ if (priv->browser_mode_selection_timer_tag != -1)
+ gtk_timeout_remove (priv->browser_mode_selection_timer_tag);
+
+ return TRUE;
+}
+
+static gint
+item_event_cb (GnomeCanvasItem *item,
+ GdkEvent *event,
+ gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *icon;
+
+ container = GNOME_ICON_CONTAINER (data);
+ priv = container->priv;
+
+ icon = g_hash_table_lookup (priv->canvas_item_to_icon, item);
+ g_return_val_if_fail (icon != NULL, FALSE);
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ return handle_icon_button_press (container, icon, &event->button);
+ case GDK_ENTER_NOTIFY:
+ return handle_icon_enter_notify (container, icon, &event->motion);
+ case GDK_LEAVE_NOTIFY:
+ return handle_icon_leave_notify (container, icon, &event->motion);
+ default:
+ return FALSE;
+ }
+}
+
+
+GtkWidget *
+gnome_icon_container_new (void)
+{
+ GtkWidget *new;
+
+ gtk_widget_push_visual (gdk_imlib_get_visual ());
+ gtk_widget_push_colormap (gdk_imlib_get_colormap ());
+
+ new = gtk_type_new (gnome_icon_container_get_type ());
+
+ gtk_widget_pop_visual ();
+ gtk_widget_pop_colormap ();
+
+ return new;
+}
+
+
+guint
+gnome_icon_container_get_type (void)
+{
+ static guint type = 0;
+
+ if (type == 0) {
+ GtkTypeInfo type_info = {
+ "GnomeIconContainer",
+ sizeof (GnomeIconContainer),
+ sizeof (GnomeIconContainerClass),
+ (GtkClassInitFunc) class_init,
+ (GtkObjectInitFunc) init,
+ (GtkArgSetFunc) NULL,
+ (GtkArgGetFunc) NULL
+ };
+
+ type = gtk_type_unique (gnome_canvas_get_type (), &type_info);
+ }
+
+ return type;
+}
+
+void
+gnome_icon_container_clear (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+
+ for (p = priv->icons; p != NULL; p = p->next)
+ icon_destroy (p->data);
+ g_list_free (priv->icons);
+ priv->icons = NULL;
+ priv->num_icons = 0;
+
+ icon_grid_clear (priv->grid);
+
+ unset_kbd_current (container);
+}
+
+
+void
+gnome_icon_container_set_icon_mode (GnomeIconContainer *container,
+ GnomeIconContainerIconMode mode)
+{
+ g_return_if_fail (container != NULL);
+
+ if (mode < 0 || mode >= NUM_ICON_MODES) {
+ g_warning ("Unknown icon mode %d", mode);
+ return;
+ }
+
+ change_icon_mode (container, mode);
+}
+
+GnomeIconContainerIconMode
+gnome_icon_container_get_icon_mode (GnomeIconContainer *container)
+{
+ g_return_val_if_fail (container != NULL, GNOME_ICON_CONTAINER_NORMAL_ICONS);
+
+ return container->priv->icon_mode;
+}
+
+
+static void
+setup_icon_in_container (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ priv->icons = g_list_prepend (priv->icons, icon);
+ priv->num_icons++;
+
+ g_hash_table_insert (priv->canvas_item_to_icon, icon->item, icon);
+ icon_show (icon);
+
+ gtk_signal_connect (GTK_OBJECT (icon->item), "event",
+ GTK_SIGNAL_FUNC (item_event_cb), container);
+}
+
+void
+gnome_icon_container_add_imlib (GnomeIconContainer *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gint x, gint y,
+ gpointer data)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *new_icon;
+ guint grid_x, grid_y;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (image != NULL);
+ g_return_if_fail (text != NULL);
+
+ priv = container->priv;
+
+ new_icon = icon_new_imlib (container, image, text, data);
+ icon_position (new_icon, container, x, y);
+
+ world_to_grid (container, x, y, &grid_x, &grid_y);
+ icon_grid_add (container->priv->grid, new_icon, grid_x, grid_y);
+
+ if (x % GNOME_ICON_CONTAINER_CELL_WIDTH (container) > 0)
+ icon_grid_add (priv->grid, new_icon, grid_x + 1, grid_y);
+ if (y % GNOME_ICON_CONTAINER_CELL_HEIGHT (container) > 0)
+ icon_grid_add (priv->grid, new_icon, grid_x, grid_y + 1);
+ if (x % GNOME_ICON_CONTAINER_CELL_WIDTH (container) > 0
+ && y % GNOME_ICON_CONTAINER_CELL_HEIGHT (container) > 0)
+ icon_grid_add (priv->grid, new_icon, grid_x + 1, grid_y + 1);
+
+ setup_icon_in_container (container, new_icon);
+
+ add_idle (container);
+}
+
+/**
+ * gnome_icon_container_add_imlib_auto:
+ * @container: A GnomeIconContainer
+ * @image: Image of the icon to add
+ * @text: Caption
+ * @data: Icon-specific data
+ *
+ * Add @image with caption @text and data @data to @container, in the first
+ * empty spot available.
+ **/
+void
+gnome_icon_container_add_imlib_auto (GnomeIconContainer *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data)
+{
+ GnomeIconContainerIcon *new_icon;
+ guint grid_x, grid_y;
+ gint x, y;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (image != NULL);
+ g_return_if_fail (text != NULL);
+
+ new_icon = icon_new_imlib (container, image, text, data);
+
+ icon_grid_add_auto (container->priv->grid, new_icon, &grid_x, &grid_y);
+
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+ icon_position (new_icon, container, x, y);
+
+ setup_icon_in_container (container, new_icon);
+
+ add_idle (container);
+}
+
+/**
+ * gnome_icon_container_add_imlib_with_layout:
+ * @container: A GnomeIconContainer
+ * @image: Image of the icon to add
+ * @text: Caption
+ * @data: Icon-specific data
+ * @layout: Layout information
+ *
+ * Add @image with the caption @text to @container using @layout, and attach
+ * @data to it.
+ *
+ * Return value: %FALSE if @text is not in @layout (and, consequently, the icon
+ * has not been added); %TRUE otherwise.
+ **/
+gboolean
+gnome_icon_container_add_imlib_with_layout (GnomeIconContainer *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data,
+ const GnomeIconContainerLayout *layout)
+{
+ gint x, y;
+
+ g_return_val_if_fail (container != NULL, FALSE);
+ g_return_val_if_fail (image != NULL, FALSE);
+ g_return_val_if_fail (text != NULL, FALSE);
+ g_return_val_if_fail (layout != NULL, FALSE);
+
+ if (gnome_icon_container_layout_get_position (layout, text, &x, &y)) {
+ gnome_icon_container_add_imlib (container, image,
+ text, x, y, data);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+
+/**
+ * gnome_icon_container_relayout:
+ * @container: An icon container.
+ *
+ * Relayout the icons in @container according to the allocation we are given. This
+ * is done by just collecting icons from top to bottom, from left to right, and
+ * tiling them in the same direction. The tiling is done in such a way that no
+ * horizontal scrolling is needed to see all the icons.
+ **/
+void
+gnome_icon_container_relayout (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *old_grid, *new_grid;
+ GList **sp, **dp;
+ guint i, j;
+ guint dx, dy;
+ guint sx, sy;
+ guint cols;
+ guint lines;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+ old_grid = priv->grid;
+
+ g_return_if_fail (old_grid->visible_width > 0);
+
+ prepare_for_layout (container);
+
+ new_grid = icon_grid_new ();
+
+ if (priv->num_icons % old_grid->visible_width != 0)
+ icon_grid_resize (new_grid,
+ old_grid->visible_width,
+ (priv->num_icons
+ / old_grid->visible_width) + 1);
+ else
+ icon_grid_resize (new_grid,
+ old_grid->visible_width,
+ priv->num_icons / old_grid->visible_width);
+
+ icon_grid_set_visible_width (new_grid, old_grid->visible_width);
+
+ sp = old_grid->elems;
+ dp = new_grid->elems;
+ sx = sy = 0;
+ dx = dy = 0;
+ cols = lines = 0;
+ for (i = 0; i < old_grid->height; i++) {
+ for (j = 0; j < old_grid->width; j++) {
+ GList *p;
+
+ /* Make sure the icons are sorted by increasing X
+ position. */
+ sp[j] = g_list_sort (sp[j],
+ icon_grid_cell_compare_by_x);
+
+ for (p = sp[j]; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+
+ /* Make sure icons are not moved twice, and
+ ignore icons whose upper left corner is not
+ in this cell, unless the icon is partly
+ outside the container. */
+ if (icon->layout_done
+ || (icon->x >= 0 && icon->x < sx)
+ || (icon->y >= 0 && icon->y < sy))
+ continue;
+
+ dp[cols] = g_list_alloc ();
+ dp[cols]->data = icon;
+
+ icon_position (icon, container, dx, dy);
+
+ icon->layout_done = TRUE;
+
+ if (++cols == new_grid->visible_width) {
+ cols = 0, lines++;
+ dx = 0, dy += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ dp += new_grid->alloc_width;
+ } else {
+ dx += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+ }
+
+ sx += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ sx = 0, sy += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ sp += old_grid->alloc_width;
+ }
+
+ if (cols < new_grid->visible_width && lines < new_grid->height) {
+ new_grid->first_free_x = cols;
+ new_grid->first_free_y = lines;
+ } else {
+ new_grid->first_free_x = -1;
+ new_grid->first_free_y = -1;
+ }
+
+ icon_grid_destroy (priv->grid);
+ priv->grid = new_grid;
+
+ if (priv->kbd_current != NULL)
+ set_kbd_current (container, priv->kbd_current, FALSE);
+
+ add_idle (container);
+}
+
+
+/**
+ * gnome_icon_container_line_up:
+ * @container: An icon container.
+ *
+ * Line up icons in @container.
+ **/
+void
+gnome_icon_container_line_up (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIconGrid *new_grid;
+ GList **p, **q;
+ guint new_grid_width;
+ guint i, j, k, m;
+ gint x, y, dx;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ /* Mark all icons as "not moved yet". */
+
+ prepare_for_layout (container);
+
+ /* Calculate the width for the resulting new grid. This is the maximum
+ width across all the lines. */
+
+ new_grid_width = 0;
+ p = grid->elems;
+ x = y = 0;
+ for (i = 0; i < grid->height; i++) {
+ guint line_width;
+
+ line_width = grid->width;
+ for (j = 0; j < grid->width; j++) {
+ GList *e;
+ guint count;
+
+ count = 0;
+ for (e = p[j]; e != NULL; e = e->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = e->data;
+ if (icon->x >= x && icon->y >= y)
+ count++;
+ }
+
+ if (count > 1)
+ new_grid_width += count - 1;
+
+ x += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ new_grid_width = MAX (new_grid_width, line_width);
+ p += grid->alloc_width;
+
+ y += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ x = 0;
+ }
+
+ /* Create the new grid. */
+
+ new_grid = icon_grid_new ();
+ icon_grid_resize (new_grid, new_grid_width, grid->height);
+ icon_grid_set_visible_width (new_grid, grid->visible_width);
+
+ /* Allocate the icons in the new grid, one per cell. */
+
+ p = grid->elems;
+ q = new_grid->elems;
+ k = 0;
+ x = y = dx = 0;
+ for (i = 0; i < grid->height; i++) {
+ m = 0;
+ for (j = 0; j < grid->width; j++) {
+ GList *e;
+ guint count;
+
+ /* Make sure the icons are sorted by increasing X
+ position. */
+ p[j] = g_list_sort
+ (p[j], icon_grid_cell_compare_by_x);
+
+ count = 0;
+ for (e = p[j]; e != NULL; e = e->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = e->data;
+
+ /* Make sure icons are not moved twice, and
+ ignore icons whose upper left corner is not
+ in this cell, unless the icon is partly
+ outside the container. */
+ if (icon->layout_done
+ || (icon->x >= 0 && icon->x < x)
+ || (icon->y >= 0 && icon->y < y))
+ continue;
+
+ icon_position (icon, container, dx, y);
+ icon->layout_done = TRUE;
+
+ q[k] = g_list_alloc ();
+ q[k]->data = icon;
+ dx += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+
+ k++;
+
+ if (count > 0)
+ m++;
+
+ count++;
+ }
+
+ if (count == 0) {
+ if (m > 0) {
+ m--;
+ } else {
+ k++;
+ dx += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+ }
+ }
+
+ x += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+
+ p += grid->alloc_width;
+
+ q += new_grid->alloc_width;
+ k = 0;
+
+ y += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ x = 0;
+
+ dx = 0;
+ }
+
+ /* Done: use the new grid. */
+
+ icon_grid_destroy (priv->grid);
+ priv->grid = new_grid;
+
+ /* Update the keyboard selection indicator. */
+ if (priv->kbd_current != NULL)
+ set_kbd_current (container, priv->kbd_current, FALSE);
+
+ add_idle (container);
+}
+
+
+/**
+ * gnome_icon_container_get_selection:
+ * @container: An icon container.
+ *
+ * Get a list of the icons currently selected in @container.
+ *
+ * Return value: A GList of the programmer-specified data associated to each
+ * selected icon, or NULL if no icon is selected. The caller is expected to
+ * free the list when it is not needed anymore.
+ **/
+GList *
+gnome_icon_container_get_selection (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *list, *p;
+
+ g_return_val_if_fail (container != NULL, FALSE);
+
+ priv = container->priv;
+
+ list = NULL;
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon->is_selected)
+ list = g_list_prepend (list, icon->data);
+ }
+
+ return list;
+}
+
+/**
+ * gnome_icon_container_select_all:
+ * @container: An icon container widget.
+ *
+ * Select all the icons in @container at once.
+ **/
+void
+gnome_icon_container_select_all (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GList **p, *q;
+ guint i, j;
+ gboolean selection_changed;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ selection_changed = FALSE;
+ p = grid->elems;
+ for (i = 0; i < grid->height; i++) {
+ for (j = 0; j < grid->width; j++) {
+ for (q = p[j]; q != NULL; q =q->next) {
+ if (select_icon (container, q->data, TRUE))
+ selection_changed = TRUE;
+ }
+ }
+
+ p += grid->alloc_width;
+ }
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+}
+
+/**
+ * gnome_icon_container_unselect_all:
+ * @container: An icon container widget.
+ *
+ * Deselect all the icons in @container.
+ **/
+void
+gnome_icon_container_unselect_all (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ gboolean selection_changed;
+ GList *p;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+
+ selection_changed = FALSE;
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (select_icon (container, icon, FALSE))
+ selection_changed = TRUE;
+ }
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+}
+
+/**
+ * gnome_icon_container_set_base_uri:
+ * @container: An icon container widget.
+ * @base_uri: A base URI.
+ *
+ * Set the base URI for drag & drop operations.
+ **/
+void
+gnome_icon_container_set_base_uri (GnomeIconContainer *container,
+ const gchar *base_uri)
+{
+ GnomeIconContainerPrivate *priv;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ priv = container->priv;
+
+ g_free (priv->base_uri);
+ priv->base_uri = g_strdup (base_uri);
+}
+
+/**
+ * gnome_icon_container_xlate_selected:
+ * @container: An icon container widget.
+ * @amount_x: Amount of translation on the X axis.
+ * @amount_y: Amount of translation on the Y axis.
+ * @raise: Whether icons should be raised during this operation.
+ *
+ * Translate all the currently selected items in @container by @amount_x
+ * horizontally and @amount_y vertically. Positive values move to the
+ * right/bottom, negative values to the left/top.
+ **/
+void
+gnome_icon_container_xlate_selected (GnomeIconContainer *container,
+ gint amount_x,
+ gint amount_y,
+ gboolean raise)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ if (amount_x == 0 && amount_y == 0)
+ return;
+
+ priv = container->priv;
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon->is_selected) {
+ move_icon (container, icon,
+ icon->x + amount_x, icon->y + amount_y);
+ if (raise)
+ icon_raise (icon);
+ }
+ }
+
+ set_kbd_current (container, priv->kbd_current, TRUE);
+}
+
+
+GnomeIconContainerLayout *
+gnome_icon_container_get_layout (GnomeIconContainer *container)
+{
+ GnomeIconContainerLayout *layout;
+ GList *p;
+
+ g_return_val_if_fail (container != NULL, NULL);
+ g_return_val_if_fail (GNOME_IS_ICON_CONTAINER (container), NULL);
+
+ layout = gnome_icon_container_layout_new ();
+
+ for (p = container->priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ gnome_icon_container_layout_add (layout, icon->text,
+ icon->x, icon->y);
+ }
+
+ return layout;
+}
diff --git a/libnautilus-private/gnome-icon-container.h b/libnautilus-private/gnome-icon-container.h
new file mode 100644
index 000000000..dd61eaad9
--- /dev/null
+++ b/libnautilus-private/gnome-icon-container.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container.h - Icon container widget.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _GNOME_ICON_CONTAINER_H
+#define _GNOME_ICON_CONTAINER_H
+
+#include <libgnomeui/libgnomeui.h>
+
+enum _GnomeIconContainerIconMode {
+ GNOME_ICON_CONTAINER_NORMAL_ICONS,
+ GNOME_ICON_CONTAINER_SMALL_ICONS
+};
+typedef enum _GnomeIconContainerIconMode GnomeIconContainerIconMode;
+
+enum _GnomeIconContainerLayoutMode {
+ GNOME_ICON_LAYOUT_MANUAL,
+ GNOME_ICON_LAYOUT_AUTO
+};
+typedef enum _GnomeIconContainerLayoutMode GnomeIconContainerLayoutMode;
+
+typedef struct _GnomeIconContainer GnomeIconContainer;
+typedef struct _GnomeIconContainerClass GnomeIconContainerClass;
+typedef struct _GnomeIconContainerPrivate GnomeIconContainerPrivate;
+
+#include "gnome-icon-container-layout.h"
+
+
+#define GNOME_ICON_CONTAINER(obj) \
+ GTK_CHECK_CAST (obj, gnome_icon_container_get_type (), GnomeIconContainer)
+#define GNOME_ICON_CONTAINER_CLASS(k) \
+ GTK_CHECK_CLASS_CAST (k, gnome_icon_container_get_type (), GnomeIconListView)
+#define GNOME_IS_ICON_CONTAINER(obj) \
+ GTK_CHECK_TYPE (obj, gnome_icon_container_get_type ())
+
+
+typedef gint (* GnomeIconContainerSortFunc) (const gchar *name_a,
+ gpointer data_a,
+ const gchar *name_b,
+ gpointer data_b,
+ gpointer user_data);
+
+struct _GnomeIconContainer {
+ GnomeCanvas canvas;
+ GnomeIconContainerPrivate *priv;
+};
+
+struct _GnomeIconContainerClass {
+ GnomeCanvasClass parent_class;
+
+ void (* selection_changed) (GnomeIconContainer *container);
+ gint (* button_press) (GnomeIconContainer *container,
+ GdkEventButton *event);
+ void (* activate) (GnomeIconContainer *container,
+ const gchar *name,
+ gpointer data);
+
+ void (* context_click) (GnomeIconContainer *container,
+ const gchar *name,
+ gpointer data);
+};
+
+
+guint gnome_icon_container_get_type (void);
+
+GtkWidget *gnome_icon_container_new (void);
+
+void gnome_icon_container_clear (GnomeIconContainer *view);
+
+void gnome_icon_container_set_icon_mode
+ (GnomeIconContainer *view,
+ GnomeIconContainerIconMode mode);
+
+GnomeIconContainerIconMode
+ gnome_icon_container_get_icon_mode
+ (GnomeIconContainer *view);
+
+void gnome_icon_container_set_editable
+ (GnomeIconContainer *view,
+ gboolean is_editable);
+gboolean gnome_icon_container_get_editable
+ (GnomeIconContainer *view);
+
+void gnome_icon_container_add_imlib (GnomeIconContainer *view,
+ GdkImlibImage *image,
+ const gchar *text,
+ gint x, gint y,
+ gpointer data);
+
+void gnome_icon_container_add_imlib_auto
+ (GnomeIconContainer *view,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data);
+gboolean gnome_icon_container_add_imlib_with_layout
+ (GnomeIconContainer
+ *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data,
+ const GnomeIconContainerLayout
+ *layout);
+
+gpointer gnome_icon_container_get_icon_data
+ (GnomeIconContainer *view,
+ const gchar *text);
+
+void gnome_icon_container_relayout (GnomeIconContainer *view);
+void gnome_icon_container_line_up (GnomeIconContainer *view);
+GList *gnome_icon_container_get_selection
+ (GnomeIconContainer *view);
+
+void gnome_icon_container_unselect_all
+ (GnomeIconContainer *view);
+void gnome_icon_container_select_all (GnomeIconContainer *view);
+
+void gnome_icon_container_enable_browser_mode
+ (GnomeIconContainer *view,
+ gboolean enable);
+
+void gnome_icon_container_set_base_uri
+ (GnomeIconContainer *container,
+ const gchar *base_uri);
+
+void gnome_icon_container_xlate_selected
+ (GnomeIconContainer *container,
+ gint amount_x,
+ gint amount_y,
+ gboolean raise);
+
+GnomeIconContainerLayout *
+ gnome_icon_container_get_layout
+ (GnomeIconContainer *container);
+#endif
diff --git a/libnautilus-private/gtkflist.c b/libnautilus-private/gtkflist.c
new file mode 100644
index 000000000..2b5abdd87
--- /dev/null
+++ b/libnautilus-private/gtkflist.c
@@ -0,0 +1,539 @@
+/* File list widget for the Midnight Commander
+ *
+ * Copyright (C) 1999 The Free Software Foundation
+ *
+ * Author: Federico Mena <federico@nuclecu.unam.mx>
+ * Modified by Ettore Perazzoli <ettore@gnu.org>
+ */
+
+/* FIXME this is a kludge to re-use broken CList. Instead, I'd like to have a
+ native List widget that uses a simple API similiar to the GnomeIconContainer
+ one. */
+
+#include <config.h>
+#include "gtkflist.h"
+
+
+enum {
+ ROW_POPUP_MENU,
+ EMPTY_POPUP_MENU,
+ ACTIVATE,
+ START_DRAG,
+ SELECTION_CHANGED,
+ LAST_SIGNAL
+};
+
+
+static void gtk_flist_class_init (GtkFListClass *class);
+static void gtk_flist_init (GtkFList *flist);
+
+static gint gtk_flist_button_press (GtkWidget *widget, GdkEventButton *event);
+static gint gtk_flist_button_release (GtkWidget *widget, GdkEventButton *event);
+static gint gtk_flist_motion (GtkWidget *widget, GdkEventMotion *event);
+static gint gtk_flist_key (GtkWidget *widget, GdkEventKey *event);
+static void gtk_flist_drag_begin (GtkWidget *widget, GdkDragContext *context);
+static void gtk_flist_drag_end (GtkWidget *widget, GdkDragContext *context);
+static void gtk_flist_drag_data_get (GtkWidget *widget, GdkDragContext *context,
+ GtkSelectionData *data, guint info, guint time);
+static void gtk_flist_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time);
+static gboolean gtk_flist_drag_motion (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time);
+static gboolean gtk_flist_drag_drop (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time);
+static void gtk_flist_drag_data_received (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, GtkSelectionData *data,
+ guint info, guint time);
+
+static void gtk_flist_clear (GtkCList *clist);
+
+
+static GtkCListClass *parent_class;
+
+static guint flist_signals[LAST_SIGNAL];
+
+
+/**
+ * gtk_flist_get_type:
+ * @void:
+ *
+ * Creates the GtkFList class and its type information
+ *
+ * Return value: The type ID for GtkFListClass
+ **/
+GtkType
+gtk_flist_get_type (void)
+{
+ static GtkType flist_type = 0;
+
+ if (!flist_type) {
+ GtkTypeInfo flist_info = {
+ "GtkFList",
+ sizeof (GtkFList),
+ sizeof (GtkFListClass),
+ (GtkClassInitFunc) gtk_flist_class_init,
+ (GtkObjectInitFunc) gtk_flist_init,
+ NULL, /* reserved_1 */
+ NULL, /* reserved_2 */
+ (GtkClassInitFunc) NULL
+ };
+
+ flist_type = gtk_type_unique (gtk_clist_get_type (), &flist_info);
+ }
+
+ return flist_type;
+}
+
+/* Standard class initialization function */
+static void
+gtk_flist_class_init (GtkFListClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkCListClass *clist_class;
+
+ object_class = (GtkObjectClass *) class;
+ widget_class = (GtkWidgetClass *) class;
+ clist_class = (GtkCListClass *) class;
+
+ parent_class = gtk_type_class (gtk_clist_get_type ());
+
+ flist_signals[ROW_POPUP_MENU] =
+ gtk_signal_new ("row_popup_menu",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, row_popup_menu),
+ gtk_marshal_NONE__POINTER,
+ GTK_TYPE_NONE, 1,
+ GTK_TYPE_GDK_EVENT);
+ flist_signals[EMPTY_POPUP_MENU] =
+ gtk_signal_new ("empty_popup_menu",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, empty_popup_menu),
+ gtk_marshal_NONE__POINTER,
+ GTK_TYPE_NONE, 1,
+ GTK_TYPE_GDK_EVENT);
+ flist_signals[ACTIVATE] =
+ gtk_signal_new ("activate",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, activate),
+ gtk_marshal_NONE__POINTER,
+ GTK_TYPE_NONE, 1,
+ GTK_TYPE_POINTER);
+ flist_signals[START_DRAG] =
+ gtk_signal_new ("start_drag",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, start_drag),
+ gtk_marshal_NONE__INT_POINTER,
+ GTK_TYPE_NONE, 2,
+ GTK_TYPE_INT,
+ GTK_TYPE_GDK_EVENT);
+ flist_signals[SELECTION_CHANGED] =
+ gtk_signal_new ("selection_changed",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, start_drag),
+ gtk_marshal_NONE__NONE,
+ GTK_TYPE_NONE, 0);
+
+ gtk_object_class_add_signals (object_class, flist_signals, LAST_SIGNAL);
+
+ clist_class->clear = gtk_flist_clear;
+
+ widget_class->button_press_event = gtk_flist_button_press;
+ widget_class->button_release_event = gtk_flist_button_release;
+ widget_class->motion_notify_event = gtk_flist_motion;
+ widget_class->key_press_event = gtk_flist_key;
+ widget_class->key_release_event = gtk_flist_key;
+ widget_class->drag_begin = gtk_flist_drag_begin;
+ widget_class->drag_end = gtk_flist_drag_end;
+ widget_class->drag_data_get = gtk_flist_drag_data_get;
+ widget_class->drag_leave = gtk_flist_drag_leave;
+ widget_class->drag_motion = gtk_flist_drag_motion;
+ widget_class->drag_drop = gtk_flist_drag_drop;
+ widget_class->drag_data_received = gtk_flist_drag_data_received;
+}
+
+/* Standard object initialization function */
+static void
+gtk_flist_init (GtkFList *flist)
+{
+ flist->anchor_row = -1;
+
+ /* GtkCList does not specify pointer motion by default */
+ gtk_widget_add_events (GTK_WIDGET (flist), GDK_POINTER_MOTION_MASK);
+}
+
+static gboolean
+row_selected (GtkFList *flist, gint row)
+{
+ GtkCListRow *elem;
+
+ elem = g_list_nth (GTK_CLIST (flist)->row_list, row)->data;
+
+ return elem->state == GTK_STATE_SELECTED;
+}
+
+/* Selects the rows between the anchor to the specified row, inclusive. */
+static void
+select_range (GtkFList *flist, int row)
+{
+ int min, max;
+ int i;
+
+ if (flist->anchor_row == -1)
+ flist->anchor_row = row;
+
+ if (row < flist->anchor_row) {
+ min = row;
+ max = flist->anchor_row;
+ } else {
+ min = flist->anchor_row;
+ max = row;
+ }
+
+ for (i = min; i <= max; i++)
+ gtk_clist_select_row (GTK_CLIST (flist), i, 0);
+}
+
+/* Handles row selection according to the specified modifier state */
+static void
+select_row (GtkFList *flist, int row, guint state)
+{
+ int range, additive;
+
+ range = (state & GDK_SHIFT_MASK) != 0;
+ additive = (state & GDK_CONTROL_MASK) != 0;
+
+ if (!additive)
+ gtk_clist_unselect_all (GTK_CLIST (flist));
+
+ if (!range) {
+ if (additive) {
+ if (row_selected (flist, row))
+ gtk_clist_unselect_row
+ (GTK_CLIST (flist), row, 0);
+ else
+ gtk_clist_select_row
+ (GTK_CLIST (flist), row, 0);
+ } else {
+ gtk_clist_select_row (GTK_CLIST (flist), row, 0);
+ }
+ flist->anchor_row = row;
+ } else
+ select_range (flist, row);
+
+ gtk_signal_emit (GTK_OBJECT (flist), flist_signals[SELECTION_CHANGED]);
+}
+
+/* Our handler for button_press events. We override all of GtkCList's broken
+ * behavior.
+ */
+static gint
+gtk_flist_button_press (GtkWidget *widget, GdkEventButton *event)
+{
+ GtkFList *flist;
+ GtkCList *clist;
+ int on_row;
+ gint row, col;
+ int retval;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_FLIST (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ flist = GTK_FLIST (widget);
+ clist = GTK_CLIST (widget);
+ retval = FALSE;
+
+ if (event->window != clist->clist_window)
+ return (* GTK_WIDGET_CLASS (parent_class)->button_press_event) (widget, event);
+
+ on_row = gtk_clist_get_selection_info (clist, event->x, event->y, &row, &col);
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button == 1 || event->button == 2) {
+ if (on_row) {
+ /* Save the mouse info for DnD */
+
+ flist->dnd_press_button = event->button;
+ flist->dnd_press_x = event->x;
+ flist->dnd_press_y = event->y;
+
+ /* Handle selection */
+
+ if ((row_selected (flist, row)
+ && !(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
+ || ((event->state & GDK_CONTROL_MASK)
+ && !(event->state & GDK_SHIFT_MASK))) {
+ flist->dnd_select_pending = TRUE;
+ flist->dnd_select_pending_state = event->state;
+ flist->dnd_select_pending_row = row;
+ }
+
+ select_row (flist, row, event->state);
+ } else {
+ gtk_clist_unselect_all (clist);
+ }
+
+ retval = TRUE;
+ } else if (event->button == 3) {
+ if (on_row) {
+ select_row (flist, row, event->state);
+ gtk_signal_emit (GTK_OBJECT (flist),
+ flist_signals[ROW_POPUP_MENU],
+ event);
+ } else
+ gtk_signal_emit (GTK_OBJECT (flist),
+ flist_signals[EMPTY_POPUP_MENU],
+ event);
+
+ retval = TRUE;
+ }
+
+ break;
+
+ case GDK_2BUTTON_PRESS:
+ if (event->button == 1) {
+ GtkCListRow *elem;
+
+ flist->dnd_select_pending = FALSE;
+ flist->dnd_select_pending_state = 0;
+
+ if (on_row) {
+ elem = g_list_nth (GTK_CLIST (flist)->row_list,
+ row)->data;
+ gtk_signal_emit (GTK_OBJECT (flist),
+ flist_signals[ACTIVATE],
+ elem->data);
+ }
+
+ retval = TRUE;
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+/* Our handler for button_release events. We override all of GtkCList's broken
+ * behavior.
+ */
+static gint
+gtk_flist_button_release (GtkWidget *widget, GdkEventButton *event)
+{
+ GtkFList *flist;
+ GtkCList *clist;
+ int on_row;
+ gint row, col;
+ int retval;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_FLIST (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ flist = GTK_FLIST (widget);
+ clist = GTK_CLIST (widget);
+ retval = FALSE;
+
+ if (event->window != clist->clist_window)
+ return (* GTK_WIDGET_CLASS (parent_class)->button_release_event) (widget, event);
+
+ on_row = gtk_clist_get_selection_info (clist, event->x, event->y, &row, &col);
+
+ if (!(event->button == 1 || event->button == 2))
+ return FALSE;
+
+ flist->dnd_press_button = 0;
+ flist->dnd_press_x = 0;
+ flist->dnd_press_y = 0;
+
+ if (on_row) {
+ if (flist->dnd_select_pending) {
+ /* select_row (flist, row, flist->dnd_select_pending_state); */
+ flist->dnd_select_pending = FALSE;
+ flist->dnd_select_pending_state = 0;
+ }
+
+ retval = TRUE;
+ }
+
+ return retval;
+}
+
+/* Our handler for motion_notify events. We override all of GtkCList's broken
+ * behavior.
+ */
+static gint
+gtk_flist_motion (GtkWidget *widget, GdkEventMotion *event)
+{
+ GtkFList *flist;
+ GtkCList *clist;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_FLIST (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ flist = GTK_FLIST (widget);
+ clist = GTK_CLIST (widget);
+
+ if (event->window != clist->clist_window)
+ return (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event) (widget, event);
+
+ if (!((flist->dnd_press_button == 1 && (event->state & GDK_BUTTON1_MASK))
+ || (flist->dnd_press_button == 2 && (event->state & GDK_BUTTON2_MASK))))
+ return FALSE;
+
+ /* This is the same threshold value that is used in gtkdnd.c */
+
+ if (MAX (abs (flist->dnd_press_x - event->x),
+ abs (flist->dnd_press_y - event->y)) <= 3)
+ return FALSE;
+
+ /* Handle any pending selections */
+
+ if (flist->dnd_select_pending) {
+ select_row (flist,
+ flist->dnd_select_pending_row,
+ flist->dnd_select_pending_state);
+
+ flist->dnd_select_pending = FALSE;
+ flist->dnd_select_pending_state = 0;
+ }
+
+ gtk_signal_emit (GTK_OBJECT (flist),
+ flist_signals[START_DRAG],
+ flist->dnd_press_button,
+ event);
+ return TRUE;
+}
+
+/* Our handler for key_press and key_release events. We do nothing, and we do
+ * this to avoid GtkCList's broken behavior.
+ */
+static gint
+gtk_flist_key (GtkWidget *widget, GdkEventKey *event)
+{
+ return FALSE;
+}
+
+/* We override the drag_begin signal to do nothing */
+static void
+gtk_flist_drag_begin (GtkWidget *widget, GdkDragContext *context)
+{
+ /* nothing */
+}
+
+/* We override the drag_end signal to do nothing */
+static void
+gtk_flist_drag_end (GtkWidget *widget, GdkDragContext *context)
+{
+ /* nothing */
+}
+
+/* We override the drag_data_get signal to do nothing */
+static void
+gtk_flist_drag_data_get (GtkWidget *widget, GdkDragContext *context,
+ GtkSelectionData *data, guint info, guint time)
+{
+ /* nothing */
+}
+
+/* We override the drag_leave signal to do nothing */
+static void
+gtk_flist_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time)
+{
+ /* nothing */
+}
+
+/* We override the drag_motion signal to do nothing */
+static gboolean
+gtk_flist_drag_motion (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time)
+{
+ return FALSE;
+}
+
+/* We override the drag_drop signal to do nothing */
+static gboolean
+gtk_flist_drag_drop (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time)
+{
+ return FALSE;
+}
+
+/* We override the drag_data_received signal to do nothing */
+static void
+gtk_flist_drag_data_received (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, GtkSelectionData *data,
+ guint info, guint time)
+{
+ /* nothing */
+}
+
+/* Our handler for the clear signal of the clist. We have to reset the anchor
+ * to null.
+ */
+static void
+gtk_flist_clear (GtkCList *clist)
+{
+ GtkFList *flist;
+
+ g_return_if_fail (clist != NULL);
+ g_return_if_fail (GTK_IS_FLIST (clist));
+
+ flist = GTK_FLIST (clist);
+ flist->anchor_row = -1;
+
+ if (parent_class->clear)
+ (* parent_class->clear) (clist);
+}
+
+
+/**
+ * gtk_flist_new_with_titles:
+ * @columns: The number of columns in the list
+ * @titles: The titles for the columns
+ *
+ * Return value: The newly-created file list.
+ **/
+GtkWidget *
+gtk_flist_new_with_titles (int columns, char **titles)
+{
+ GtkFList *flist;
+
+ flist = gtk_type_new (gtk_flist_get_type ());
+ gtk_clist_construct (GTK_CLIST (flist), columns, titles);
+
+ gtk_clist_set_selection_mode (GTK_CLIST (flist),
+ GTK_SELECTION_MULTIPLE);
+
+ return GTK_WIDGET (flist);
+}
+
+GList *
+gtk_flist_get_selection (GtkFList *flist)
+{
+ GList *retval;
+ GList *p;
+
+ g_return_val_if_fail (flist != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_FLIST (flist), NULL);
+
+ retval = NULL;
+ for (p = GTK_CLIST (flist)->row_list; p != NULL; p = p->next) {
+ GtkCListRow *row;
+
+ row = p->data;
+ if (row->state == GTK_STATE_SELECTED)
+ retval = g_list_prepend (retval, row->data);
+ }
+
+ return retval;
+}
diff --git a/libnautilus-private/gtkflist.h b/libnautilus-private/gtkflist.h
new file mode 100644
index 000000000..ac216deaf
--- /dev/null
+++ b/libnautilus-private/gtkflist.h
@@ -0,0 +1,70 @@
+/* File list widget for the Midnight Commander
+ *
+ * Copyright (C) 1999 The Free Software Foundation
+ *
+ * Author: Federico Mena <federico@nuclecu.unam.mx>
+ */
+
+#ifndef GTKFLIST_H
+#define GTKFLIST_H
+
+#include "panel.h"
+#include <gtk/gtkclist.h>
+
+
+/* It is sad that we have to do this. GtkCList's behavior is so broken that we
+ * have to override all the event handlers and implement our own selection
+ * behavior. Sigh.
+ */
+
+#define TYPE_GTK_FLIST (gtk_flist_get_type ())
+#define GTK_FLIST(obj) (GTK_CHECK_CAST ((obj), TYPE_GTK_FLIST, GtkFList))
+#define GTK_FLIST_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), TYPE_GTK_FLIST, GtkFListClass))
+#define GTK_IS_FLIST(obj) (GTK_CHECK_TYPE ((obj), TYPE_GTK_FLIST))
+#define GTK_IS_FLIST_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), TYPE_GTK_FLIST))
+
+
+typedef struct _GtkFList GtkFList;
+typedef struct _GtkFListClass GtkFListClass;
+
+struct _GtkFList {
+ GtkCList clist;
+
+ /* The anchor row for range selections */
+ int anchor_row;
+
+ /* Mouse button and position saved on button press */
+ int dnd_press_button;
+ int dnd_press_x, dnd_press_y;
+
+ /* Delayed selection information */
+ int dnd_select_pending;
+ guint dnd_select_pending_state;
+ int dnd_select_pending_row;
+};
+
+struct _GtkFListClass {
+ GtkCListClass parent_class;
+
+ /* Signal: invoke the popup menu for rows */
+ void (* row_popup_menu) (GtkFList *flist, GdkEventButton *event);
+
+ /* Signal: invoke the popup menu for empty areas */
+ void (* empty_popup_menu) (GtkFList *flist, GdkEventButton *event);
+
+ /* Signal: open the file in the selected row */
+ void (* activate) (GtkFList *flist, gpointer data);
+
+ /* Signal: initiate a drag and drop operation */
+ void (* start_drag) (GtkFList *flist, gint button, GdkEvent *event);
+
+ /* Signal: selection has changed */
+ void (* selection_changed) (GtkFList *flist);
+};
+
+
+GtkType gtk_flist_get_type (void);
+GtkWidget *gtk_flist_new_with_titles (int columns, char **titles);
+GList *gtk_flist_get_selection (GtkFList *flist);
+
+#endif
diff --git a/libnautilus-private/gtkscrollframe.c b/libnautilus-private/gtkscrollframe.c
new file mode 100644
index 000000000..6f41835ec
--- /dev/null
+++ b/libnautilus-private/gtkscrollframe.c
@@ -0,0 +1,1210 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This 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.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include <config.h>
+#include <gtk/gtkhscrollbar.h>
+#include <gtk/gtkvscrollbar.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkviewport.h>
+#include "gtkscrollframe.h"
+
+
+/* scrolled window policy and size requisition handling:
+ *
+ * gtk size requisition works as follows:
+ * a widget upon size-request reports the width and height that it finds
+ * to be best suited to display its contents, including children.
+ * the width and/or height reported from a widget upon size requisition
+ * may be overidden by the user by specifying a width and/or height
+ * other than 0 through gtk_widget_set_usize().
+ *
+ * a scrolled window needs (for imlementing all three policy types) to
+ * request its width and height based on two different rationales.
+ * 1) the user wants the scrolled window to just fit into the space
+ * that it gets allocated for a specifc dimension.
+ * 1.1) this does not apply if the user specified a concrete value
+ * value for that specific dimension by either specifying usize for the
+ * scrolled window or for its child.
+ * 2) the user wants the scrolled window to take as much space up as
+ * is desired by the child for a specifc dimension (i.e. POLICY_NEVER).
+ *
+ * also, kinda obvious:
+ * 3) a user would certainly not have choosen a scrolled window as a container
+ * for the child, if the resulting allocation takes up more space than the
+ * child would have allocated without the scrolled window.
+ *
+ * conclusions:
+ * A) from 1) follows: the scrolled window shouldn't request more space for a
+ * specifc dimension than is required at minimum.
+ * B) from 1.1) follows: the requisition may be overidden by usize of the scrolled
+ * window (done automatically) or by usize of the child (needs to be checked).
+ * C) from 2) follows: for POLICY_NEVER, the scrolled window simply reports the
+ * child's dimension.
+ * D) from 3) follows: the scrolled window child's minimum width and minimum height
+ * under A) at least correspond to the space taken up by its scrollbars.
+ */
+
+/* Object argument IDs */
+enum {
+ ARG_0,
+ ARG_HADJUSTMENT,
+ ARG_VADJUSTMENT,
+ ARG_HSCROLLBAR_POLICY,
+ ARG_VSCROLLBAR_POLICY,
+ ARG_FRAME_PLACEMENT,
+ ARG_SHADOW_TYPE,
+ ARG_SCROLLBAR_SPACING
+};
+
+/* Private part of the GtkScrollFrame structure */
+typedef struct {
+ /* Horizontal and vertical scrollbars */
+ GtkWidget *hsb;
+ GtkWidget *vsb;
+
+ /* Space between scrollbars and frame */
+ guint sb_spacing;
+
+ /* Allocation for frame */
+ guint frame_x;
+ guint frame_y;
+ guint frame_w;
+ guint frame_h;
+
+ /* Scrollbar policy */
+ guint hsb_policy : 2;
+ guint vsb_policy : 2;
+
+ /* Whether scrollbars are visible */
+ guint hsb_visible : 1;
+ guint vsb_visible : 1;
+
+ /* Placement of frame wrt scrollbars */
+ guint frame_placement : 2;
+
+ /* Shadow type for frame */
+ guint shadow_type : 3;
+} ScrollFramePrivate;
+
+
+static void gtk_scroll_frame_class_init (GtkScrollFrameClass *class);
+static void gtk_scroll_frame_init (GtkScrollFrame *sf);
+static void gtk_scroll_frame_set_arg (GtkObject *object, GtkArg *arg, guint arg_id);
+static void gtk_scroll_frame_get_arg (GtkObject *object, GtkArg *arg, guint arg_id);
+static void gtk_scroll_frame_destroy (GtkObject *object);
+static void gtk_scroll_frame_finalize (GtkObject *object);
+
+static void gtk_scroll_frame_map (GtkWidget *widget);
+static void gtk_scroll_frame_unmap (GtkWidget *widget);
+static void gtk_scroll_frame_draw (GtkWidget *widget, GdkRectangle *area);
+static void gtk_scroll_frame_size_request (GtkWidget *widget, GtkRequisition *requisition);
+static void gtk_scroll_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
+static gint gtk_scroll_frame_expose (GtkWidget *widget, GdkEventExpose *event);
+
+static void gtk_scroll_frame_add (GtkContainer *container, GtkWidget *widget);
+static void gtk_scroll_frame_remove (GtkContainer *container, GtkWidget *widget);
+static void gtk_scroll_frame_forall (GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data);
+
+static GtkBinClass *parent_class;
+
+
+/**
+ * gtk_scroll_frame_get_type:
+ * @void:
+ *
+ * Registers the &GtkScrollFrame class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GtkScrollFrame class.
+ **/
+GtkType
+gtk_scroll_frame_get_type (void)
+{
+ static GtkType scroll_frame_type = 0;
+
+ if (!scroll_frame_type) {
+ static const GtkTypeInfo scroll_frame_info = {
+ "GtkScrollFrame",
+ sizeof (GtkScrollFrame),
+ sizeof (GtkScrollFrameClass),
+ (GtkClassInitFunc) gtk_scroll_frame_class_init,
+ (GtkObjectInitFunc) gtk_scroll_frame_init,
+ NULL, /* reserved_1 */
+ NULL, /* reserved_2 */
+ (GtkClassInitFunc) NULL
+ };
+
+ scroll_frame_type = gtk_type_unique (GTK_TYPE_BIN, &scroll_frame_info);
+ }
+
+ return scroll_frame_type;
+}
+
+/* Class initialization function for the scroll frame widget */
+static void
+gtk_scroll_frame_class_init (GtkScrollFrameClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkContainerClass *container_class;
+
+ object_class = (GtkObjectClass *) class;
+ widget_class = (GtkWidgetClass *) class;
+ container_class = (GtkContainerClass *) class;
+
+ parent_class = gtk_type_class (GTK_TYPE_BIN);
+
+ gtk_object_add_arg_type ("GtkScrollFrame::hadjustment",
+ GTK_TYPE_ADJUSTMENT,
+ GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
+ ARG_HADJUSTMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::vadjustment",
+ GTK_TYPE_ADJUSTMENT,
+ GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
+ ARG_VADJUSTMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::hscrollbar_policy",
+ GTK_TYPE_POLICY_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_HSCROLLBAR_POLICY);
+ gtk_object_add_arg_type ("GtkScrollFrame::vscrollbar_policy",
+ GTK_TYPE_POLICY_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_VSCROLLBAR_POLICY);
+ gtk_object_add_arg_type ("GtkScrollFrame::frame_placement",
+ GTK_TYPE_CORNER_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_FRAME_PLACEMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::shadow_type",
+ GTK_TYPE_SHADOW_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_SHADOW_TYPE);
+ gtk_object_add_arg_type ("GtkScrollFrame::scrollbar_spacing",
+ GTK_TYPE_UINT,
+ GTK_ARG_READWRITE,
+ ARG_SCROLLBAR_SPACING);
+
+ object_class->set_arg = gtk_scroll_frame_set_arg;
+ object_class->get_arg = gtk_scroll_frame_get_arg;
+ object_class->destroy = gtk_scroll_frame_destroy;
+ object_class->finalize = gtk_scroll_frame_finalize;
+
+ widget_class->map = gtk_scroll_frame_map;
+ widget_class->unmap = gtk_scroll_frame_unmap;
+ widget_class->draw = gtk_scroll_frame_draw;
+ widget_class->size_request = gtk_scroll_frame_size_request;
+ widget_class->size_allocate = gtk_scroll_frame_size_allocate;
+ widget_class->expose_event = gtk_scroll_frame_expose;
+
+ container_class->add = gtk_scroll_frame_add;
+ container_class->remove = gtk_scroll_frame_remove;
+ container_class->forall = gtk_scroll_frame_forall;
+}
+
+/* Object initialization function for the scroll frame widget */
+static void
+gtk_scroll_frame_init (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ priv = g_new0 (ScrollFramePrivate, 1);
+ sf->priv = priv;
+
+ GTK_WIDGET_SET_FLAGS (sf, GTK_NO_WINDOW);
+
+ gtk_container_set_resize_mode (GTK_CONTAINER (sf), GTK_RESIZE_QUEUE);
+
+ priv->sb_spacing = 3;
+ priv->hsb_policy = GTK_POLICY_ALWAYS;
+ priv->vsb_policy = GTK_POLICY_ALWAYS;
+ priv->frame_placement = GTK_CORNER_TOP_LEFT;
+ priv->shadow_type = GTK_SHADOW_NONE;
+}
+
+/* Set_arg handler for the scroll frame widget */
+static void
+gtk_scroll_frame_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ switch (arg_id) {
+ case ARG_HADJUSTMENT:
+ gtk_scroll_frame_set_hadjustment (sf, GTK_VALUE_POINTER (*arg));
+ break;
+
+ case ARG_VADJUSTMENT:
+ gtk_scroll_frame_set_vadjustment (sf, GTK_VALUE_POINTER (*arg));
+ break;
+
+ case ARG_HSCROLLBAR_POLICY:
+ gtk_scroll_frame_set_policy (sf, GTK_VALUE_ENUM (*arg), priv->vsb_policy);
+ break;
+
+ case ARG_VSCROLLBAR_POLICY:
+ gtk_scroll_frame_set_policy (sf, priv->hsb_policy, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_FRAME_PLACEMENT:
+ gtk_scroll_frame_set_placement (sf, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_SHADOW_TYPE:
+ gtk_scroll_frame_set_shadow_type (sf, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_SCROLLBAR_SPACING:
+ gtk_scroll_frame_set_scrollbar_spacing (sf, GTK_VALUE_UINT (*arg));
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* Get_arg handler for the scroll frame widget */
+static void
+gtk_scroll_frame_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ switch (arg_id) {
+ case ARG_HADJUSTMENT:
+ GTK_VALUE_POINTER (*arg) = gtk_scroll_frame_get_hadjustment (sf);
+ break;
+
+ case ARG_VADJUSTMENT:
+ GTK_VALUE_POINTER (*arg) = gtk_scroll_frame_get_vadjustment (sf);
+ break;
+
+ case ARG_HSCROLLBAR_POLICY:
+ GTK_VALUE_ENUM (*arg) = priv->hsb_policy;
+ break;
+
+ case ARG_VSCROLLBAR_POLICY:
+ GTK_VALUE_ENUM (*arg) = priv->vsb_policy;
+ break;
+
+ case ARG_FRAME_PLACEMENT:
+ GTK_VALUE_ENUM (*arg) = priv->frame_placement;
+ break;
+
+ case ARG_SHADOW_TYPE:
+ GTK_VALUE_ENUM (*arg) = priv->shadow_type;
+ break;
+
+ case ARG_SCROLLBAR_SPACING:
+ GTK_VALUE_UINT (*arg) = priv->sb_spacing;
+ break;
+
+ default:
+ arg->type = GTK_TYPE_INVALID;
+ break;
+ }
+}
+
+/* Destroy handler for the scroll frame widget */
+static void
+gtk_scroll_frame_destroy (GtkObject *object)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (object));
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ gtk_widget_unparent (priv->hsb);
+ gtk_widget_unparent (priv->vsb);
+ gtk_widget_destroy (priv->hsb);
+ gtk_widget_destroy (priv->vsb);
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+/* Finalize handler for the scroll frame widget */
+static void
+gtk_scroll_frame_finalize (GtkObject *object)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ gtk_widget_unref (priv->hsb);
+ gtk_widget_unref (priv->vsb);
+
+ g_free (priv);
+
+ if (GTK_OBJECT_CLASS (parent_class)->finalize)
+ (* GTK_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+/* Map handler for the scroll frame widget */
+static void
+gtk_scroll_frame_map (GtkWidget *widget)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ /* chain parent class handler to map self and child */
+ if (GTK_WIDGET_CLASS (parent_class)->map)
+ (* GTK_WIDGET_CLASS (parent_class)->map) (widget);
+
+ if (GTK_WIDGET_VISIBLE (priv->hsb) && !GTK_WIDGET_MAPPED (priv->hsb))
+ gtk_widget_map (priv->hsb);
+
+ if (GTK_WIDGET_VISIBLE (priv->vsb) && !GTK_WIDGET_MAPPED (priv->vsb))
+ gtk_widget_map (priv->vsb);
+}
+
+/* Unmap handler for the scroll frame widget */
+static void
+gtk_scroll_frame_unmap (GtkWidget *widget)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ /* chain parent class handler to unmap self and child */
+ if (GTK_WIDGET_CLASS (parent_class)->unmap)
+ (* GTK_WIDGET_CLASS (parent_class)->unmap) (widget);
+
+ if (GTK_WIDGET_MAPPED (priv->hsb))
+ gtk_widget_unmap (priv->hsb);
+
+ if (GTK_WIDGET_MAPPED (priv->vsb))
+ gtk_widget_unmap (priv->vsb);
+}
+
+/* Draws the shadow of a scroll frame widget */
+static void
+draw_shadow (GtkScrollFrame *sf, GdkRectangle *area)
+{
+ ScrollFramePrivate *priv;
+
+ g_assert (area != NULL);
+
+ priv = sf->priv;
+
+ gtk_paint_shadow (GTK_WIDGET (sf)->style,
+ GTK_WIDGET (sf)->window,
+ GTK_STATE_NORMAL, priv->shadow_type,
+ area, GTK_WIDGET (sf),
+ "scroll_frame",
+ priv->frame_x, priv->frame_y,
+ priv->frame_w, priv->frame_h);
+}
+
+/* Draw handler for the scroll frame widget */
+static void
+gtk_scroll_frame_draw (GtkWidget *widget, GdkRectangle *area)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GdkRectangle child_area;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (area != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ if (GTK_WIDGET_DRAWABLE (widget))
+ draw_shadow (sf, area);
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)
+ && gtk_widget_intersect (bin->child, area, &child_area))
+ gtk_widget_draw (bin->child, &child_area);
+
+ if (GTK_WIDGET_VISIBLE (priv->hsb)
+ && gtk_widget_intersect (priv->hsb, area, &child_area))
+ gtk_widget_draw (priv->hsb, &child_area);
+
+ if (GTK_WIDGET_VISIBLE (priv->vsb)
+ && gtk_widget_intersect (priv->vsb, area, &child_area))
+ gtk_widget_draw (priv->vsb, &child_area);
+}
+
+/* Forall handler for the scroll frame widget */
+static void
+gtk_scroll_frame_forall (GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (container));
+ g_return_if_fail (callback != NULL);
+
+ sf = GTK_SCROLL_FRAME (container);
+ priv = sf->priv;
+
+ if (GTK_CONTAINER_CLASS (parent_class)->forall)
+ (* GTK_CONTAINER_CLASS (parent_class)->forall) (
+ container, include_internals,
+ callback, callback_data);
+
+ if (include_internals) {
+ if (priv->vsb)
+ (* callback) (priv->vsb, callback_data);
+
+ if (priv->hsb)
+ (* callback) (priv->hsb, callback_data);
+ }
+}
+
+/* Size_request handler for the scroll frame widget */
+static void
+gtk_scroll_frame_size_request (GtkWidget *widget, GtkRequisition *requisition)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ gint extra_width;
+ gint extra_height;
+ GtkRequisition hsb_requisition;
+ GtkRequisition vsb_requisition;
+ GtkRequisition child_requisition;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (requisition != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ extra_width = 0;
+ extra_height = 0;
+
+ requisition->width = GTK_CONTAINER (widget)->border_width * 2;
+ requisition->height = GTK_CONTAINER (widget)->border_width * 2;
+
+ if (priv->shadow_type != GTK_SHADOW_NONE) {
+ requisition->width += 2 * widget->style->klass->xthickness;
+ requisition->height += 2 * widget->style->klass->ythickness;
+ }
+
+ gtk_widget_size_request (priv->hsb, &hsb_requisition);
+ gtk_widget_size_request (priv->vsb, &vsb_requisition);
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) {
+ static guint quark_aux_info;
+
+ if (!quark_aux_info)
+ quark_aux_info = g_quark_from_static_string ("gtk-aux-info");
+
+ gtk_widget_size_request (bin->child, &child_requisition);
+
+ if (priv->hsb_policy == GTK_POLICY_NEVER)
+ requisition->width += child_requisition.width;
+ else {
+ GtkWidgetAuxInfo *aux_info;
+
+ aux_info = gtk_object_get_data_by_id (GTK_OBJECT (bin->child),
+ quark_aux_info);
+ if (aux_info && aux_info->width > 0) {
+ requisition->width += aux_info->width;
+ extra_width = -1;
+ } else
+ requisition->width += vsb_requisition.width;
+ }
+
+ if (priv->vsb_policy == GTK_POLICY_NEVER)
+ requisition->height += child_requisition.height;
+ else {
+ GtkWidgetAuxInfo *aux_info;
+
+ aux_info = gtk_object_get_data_by_id (GTK_OBJECT (bin->child),
+ quark_aux_info);
+ if (aux_info && aux_info->height > 0) {
+ requisition->height += aux_info->height;
+ extra_height = -1;
+ } else
+ requisition->height += hsb_requisition.height;
+ }
+ }
+
+ if (priv->hsb_policy == GTK_POLICY_AUTOMATIC || GTK_WIDGET_VISIBLE (priv->hsb)) {
+ requisition->width = MAX (requisition->width, hsb_requisition.width);
+ if (!extra_height || GTK_WIDGET_VISIBLE (priv->hsb))
+ extra_height = priv->sb_spacing + hsb_requisition.height;
+ }
+
+ if (priv->vsb_policy == GTK_POLICY_AUTOMATIC || GTK_WIDGET_VISIBLE (priv->vsb)) {
+ requisition->height = MAX (requisition->height, vsb_requisition.height);
+ if (!extra_width || GTK_WIDGET_VISIBLE (priv->vsb))
+ extra_width = priv->sb_spacing + vsb_requisition.width;
+ }
+
+ requisition->width += MAX (0, extra_width);
+ requisition->height += MAX (0, extra_height);
+}
+
+/* Computes the relative allocation for the scroll frame widget */
+static void
+compute_relative_allocation (GtkWidget *widget, GtkAllocation *allocation)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_assert (widget != NULL);
+ g_assert (GTK_IS_SCROLL_FRAME (widget));
+ g_assert (allocation != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ allocation->x = GTK_CONTAINER (widget)->border_width;
+ allocation->y = GTK_CONTAINER (widget)->border_width;
+ allocation->width = MAX (1, (gint) widget->allocation.width - allocation->x * 2);
+ allocation->height = MAX (1, (gint) widget->allocation.height - allocation->y * 2);
+
+ if (priv->vsb_visible) {
+ GtkRequisition vsb_requisition;
+
+ gtk_widget_get_child_requisition (priv->vsb, &vsb_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_TOP_RIGHT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_RIGHT)
+ allocation->x += vsb_requisition.width + priv->sb_spacing;
+
+ allocation->width = MAX (1, ((gint) allocation->width
+ - ((gint) vsb_requisition.width + priv->sb_spacing)));
+ }
+
+ if (priv->hsb_visible) {
+ GtkRequisition hsb_requisition;
+
+ gtk_widget_get_child_requisition (priv->hsb, &hsb_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_BOTTOM_LEFT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_RIGHT)
+ allocation->y += hsb_requisition.height + priv->sb_spacing;
+
+ allocation->height = MAX (1, ((gint) allocation->height
+ - ((gint) hsb_requisition.height + priv->sb_spacing)));
+ }
+}
+
+/* Size_allocate handler for the scroll frame widget */
+static void
+gtk_scroll_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GtkAllocation relative_allocation;
+ GtkAllocation child_allocation;
+ gint xthickness, ythickness;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (allocation != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ widget->allocation = *allocation;
+
+ if (priv->hsb_policy == GTK_POLICY_ALWAYS)
+ priv->hsb_visible = TRUE;
+ else if (priv->hsb_policy == GTK_POLICY_NEVER)
+ priv->hsb_visible = FALSE;
+
+ if (priv->vsb_policy == GTK_POLICY_ALWAYS)
+ priv->vsb_visible = TRUE;
+ else if (priv->vsb_policy == GTK_POLICY_NEVER)
+ priv->vsb_visible = FALSE;
+
+ if (priv->shadow_type == GTK_SHADOW_NONE) {
+ xthickness = 0;
+ ythickness = 0;
+ } else {
+ xthickness = widget->style->klass->xthickness;
+ ythickness = widget->style->klass->ythickness;
+ }
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) {
+ gboolean previous_hvis;
+ gboolean previous_vvis;
+ guint count = 0;
+
+ do {
+ compute_relative_allocation (widget, &relative_allocation);
+
+ priv->frame_x = relative_allocation.x + allocation->x;
+ priv->frame_y = relative_allocation.y + allocation->y;
+ priv->frame_w = relative_allocation.width;
+ priv->frame_h = relative_allocation.height;
+
+ child_allocation.x = priv->frame_x + xthickness;
+ child_allocation.y = priv->frame_y + ythickness;
+ child_allocation.width = priv->frame_w - 2 * xthickness;
+ child_allocation.height = priv->frame_h - 2 * ythickness;
+
+ previous_hvis = priv->hsb_visible;
+ previous_vvis = priv->vsb_visible;
+
+ gtk_widget_size_allocate (bin->child, &child_allocation);
+
+ /* If, after the first iteration, the hscrollbar and the
+ * vscrollbar flip visiblity, then we need both.
+ */
+ if (count
+ && previous_hvis != priv->hsb_visible
+ && previous_vvis != priv->vsb_visible) {
+ priv->hsb_visible = TRUE;
+ priv->vsb_visible = TRUE;
+
+ /* a new resize is already queued at this point,
+ * so we will immediatedly get reinvoked
+ */
+ return;
+ }
+
+ count++;
+ } while (previous_hvis != priv->hsb_visible
+ || previous_vvis != priv->vsb_visible);
+ } else
+ compute_relative_allocation (widget, &relative_allocation);
+
+ if (priv->hsb_visible) {
+ GtkRequisition hscrollbar_requisition;
+
+ gtk_widget_get_child_requisition (priv->hsb, &hscrollbar_requisition);
+
+ if (!GTK_WIDGET_VISIBLE (priv->hsb))
+ gtk_widget_show (priv->hsb);
+
+ child_allocation.x = relative_allocation.x;
+ if (priv->frame_placement == GTK_CORNER_TOP_LEFT
+ || priv->frame_placement == GTK_CORNER_TOP_RIGHT)
+ child_allocation.y = (relative_allocation.y
+ + relative_allocation.height
+ + priv->sb_spacing);
+ else
+ child_allocation.y = GTK_CONTAINER (sf)->border_width;
+
+ child_allocation.width = relative_allocation.width;
+ child_allocation.height = hscrollbar_requisition.height;
+ child_allocation.x += allocation->x;
+ child_allocation.y += allocation->y;
+
+ gtk_widget_size_allocate (priv->hsb, &child_allocation);
+ } else if (GTK_WIDGET_VISIBLE (priv->hsb))
+ gtk_widget_hide (priv->hsb);
+
+ if (priv->vsb_visible) {
+ GtkRequisition vscrollbar_requisition;
+
+ if (!GTK_WIDGET_VISIBLE (priv->vsb))
+ gtk_widget_show (priv->vsb);
+
+ gtk_widget_get_child_requisition (priv->vsb, &vscrollbar_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_TOP_LEFT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_LEFT)
+ child_allocation.x = (relative_allocation.x
+ + relative_allocation.width
+ + priv->sb_spacing);
+ else
+ child_allocation.x = GTK_CONTAINER (sf)->border_width;
+
+ child_allocation.y = relative_allocation.y;
+ child_allocation.width = vscrollbar_requisition.width;
+ child_allocation.height = relative_allocation.height;
+ child_allocation.x += allocation->x;
+ child_allocation.y += allocation->y;
+
+ gtk_widget_size_allocate (priv->vsb, &child_allocation);
+ } else if (GTK_WIDGET_VISIBLE (priv->vsb))
+ gtk_widget_hide (priv->vsb);
+}
+
+/* Expose handler for the scroll frame widget */
+static gint
+gtk_scroll_frame_expose (GtkWidget *widget, GdkEventExpose *event)
+{
+ GtkScrollFrame *sf;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ sf = GTK_SCROLL_FRAME (widget);
+
+ if (GTK_WIDGET_DRAWABLE (widget))
+ draw_shadow (sf, &event->area);
+
+ if (GTK_WIDGET_CLASS (parent_class)->expose_event)
+ (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
+
+ return FALSE;
+}
+
+/* Add handler for the scroll frame widget */
+static void
+gtk_scroll_frame_add (GtkContainer *container, GtkWidget *child)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+
+ sf = GTK_SCROLL_FRAME (container);
+ priv = sf->priv;
+ bin = GTK_BIN (container);
+ g_return_if_fail (bin->child == NULL);
+
+ bin->child = child;
+ gtk_widget_set_parent (child, GTK_WIDGET (bin));
+
+ /* this is a temporary message */
+ if (!gtk_widget_set_scroll_adjustments (child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb))))
+ g_warning ("gtk_scroll_frame_add(): cannot add non scrollable widget "
+ "use gtk_scroll_frame_add_with_viewport() instead");
+
+ if (GTK_WIDGET_REALIZED (child->parent))
+ gtk_widget_realize (child);
+
+ if (GTK_WIDGET_VISIBLE (child->parent) && GTK_WIDGET_VISIBLE (child)) {
+ if (GTK_WIDGET_MAPPED (child->parent))
+ gtk_widget_map (child);
+
+ gtk_widget_queue_resize (child);
+ }
+}
+
+/* Remove method for the scroll frame widget */
+static void
+gtk_scroll_frame_remove (GtkContainer *container, GtkWidget *child)
+{
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (container));
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_BIN (container)->child == child);
+
+ gtk_widget_set_scroll_adjustments (child, NULL, NULL);
+
+ /* chain parent class handler to remove child */
+ if (GTK_CONTAINER_CLASS (parent_class)->remove)
+ (* GTK_CONTAINER_CLASS (parent_class)->remove) (container, child);
+}
+
+/**
+ * gtk_scroll_frame_new:
+ * @hadj: If non-NULL, the adjustment to use for horizontal scrolling.
+ * @vadj: If non-NULL, the adjustment to use for vertical scrolling.
+ *
+ * Creates a new scroll frame widget.
+ *
+ * Return value: The newly-created scroll frame widget.
+ **/
+GtkWidget *
+gtk_scroll_frame_new (GtkAdjustment *hadj, GtkAdjustment *vadj)
+{
+ if (hadj)
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (hadj), NULL);
+
+ if (vadj)
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (vadj), NULL);
+
+ return gtk_widget_new (GTK_TYPE_SCROLL_FRAME,
+ "hadjustment", hadj,
+ "vadjustment", vadj,
+ NULL);
+}
+
+/* Callback used when one of the scroll frame widget's adjustments changes */
+static void
+adjustment_changed (GtkAdjustment *adj, gpointer data)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (adj != NULL);
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ g_return_if_fail (data != NULL);
+
+ sf = GTK_SCROLL_FRAME (data);
+ priv = sf->priv;
+
+ if (adj == gtk_range_get_adjustment (GTK_RANGE (priv->hsb))) {
+ if (priv->hsb_policy == GTK_POLICY_AUTOMATIC) {
+ gboolean visible;
+
+ visible = priv->hsb_visible;
+ priv->hsb_visible = (adj->upper - adj->lower > adj->page_size);
+ if (priv->hsb_visible != visible)
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+ }
+ } else if (adj == gtk_range_get_adjustment (GTK_RANGE (priv->vsb))) {
+ if (priv->vsb_policy == GTK_POLICY_AUTOMATIC) {
+ gboolean visible;
+
+ visible = priv->vsb_visible;
+ priv->vsb_visible = (adj->upper - adj->lower > adj->page_size);
+ if (priv->vsb_visible != visible)
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+ }
+ }
+}
+
+/**
+ * gtk_scroll_frame_set_hadjustment:
+ * @sf: A scroll frame widget.
+ * @adj: An adjustment.
+ *
+ * Sets the adjustment to be used for horizontal scrolling in a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_hadjustment (GtkScrollFrame *sf, GtkAdjustment *adj)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (adj)
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ else
+ adj = GTK_ADJUSTMENT (gtk_object_new (GTK_TYPE_ADJUSTMENT, NULL));
+
+ if (!priv->hsb) {
+ gtk_widget_push_composite_child ();
+ priv->hsb = gtk_hscrollbar_new (adj);
+ gtk_widget_set_composite_name (priv->hsb, "hscrollbar");
+ gtk_widget_pop_composite_child ();
+
+ gtk_widget_set_parent (priv->hsb, GTK_WIDGET (sf));
+ gtk_widget_ref (priv->hsb);
+ gtk_widget_show (priv->hsb);
+ } else {
+ GtkAdjustment *old_adj;
+
+ old_adj = gtk_range_get_adjustment (GTK_RANGE (priv->hsb));
+ if (old_adj == adj)
+ return;
+
+ gtk_signal_disconnect_by_func (GTK_OBJECT (old_adj),
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ gtk_range_set_adjustment (GTK_RANGE (priv->hsb), adj);
+ }
+
+ adj = gtk_range_get_adjustment (GTK_RANGE (priv->hsb));
+ gtk_signal_connect (GTK_OBJECT (adj),
+ "changed",
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ adjustment_changed (adj, sf);
+
+ if (GTK_BIN (sf)->child)
+ gtk_widget_set_scroll_adjustments (
+ GTK_BIN (sf)->child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb)));
+}
+
+/**
+ * gtk_scroll_frame_set_vadjustment:
+ * @sf: A scroll frame widget.
+ * @adj: An adjustment.
+ *
+ * Sets the adjustment to be used for vertical scrolling in a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_vadjustment (GtkScrollFrame *sf, GtkAdjustment *adj)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (adj)
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ else
+ adj = GTK_ADJUSTMENT (gtk_object_new (GTK_TYPE_ADJUSTMENT, NULL));
+
+ if (!priv->vsb) {
+ gtk_widget_push_composite_child ();
+ priv->vsb = gtk_vscrollbar_new (adj);
+ gtk_widget_set_composite_name (priv->vsb, "vscrollbar");
+ gtk_widget_pop_composite_child ();
+
+ gtk_widget_set_parent (priv->vsb, GTK_WIDGET (sf));
+ gtk_widget_ref (priv->vsb);
+ gtk_widget_show (priv->vsb);
+ } else {
+ GtkAdjustment *old_adj;
+
+ old_adj = gtk_range_get_adjustment (GTK_RANGE (priv->vsb));
+ if (old_adj == adj)
+ return;
+
+ gtk_signal_disconnect_by_func (GTK_OBJECT (old_adj),
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ gtk_range_set_adjustment (GTK_RANGE (priv->vsb), adj);
+ }
+
+ adj = gtk_range_get_adjustment (GTK_RANGE (priv->vsb));
+ gtk_signal_connect (GTK_OBJECT (adj),
+ "changed",
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ adjustment_changed (adj, sf);
+
+ if (GTK_BIN (sf)->child)
+ gtk_widget_set_scroll_adjustments (
+ GTK_BIN (sf)->child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb)));
+}
+
+/**
+ * gtk_scroll_frame_get_hadjustment:
+ * @sf: A scroll frame widget.
+ *
+ * Queries the horizontal adjustment of a scroll frame widget.
+ *
+ * Return value: The horizontal adjustment of the scroll frame, or NULL if none.
+ **/
+GtkAdjustment *
+gtk_scroll_frame_get_hadjustment (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_val_if_fail (sf != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (sf), NULL);
+
+ priv = sf->priv;
+
+ return priv->hsb ? gtk_range_get_adjustment (GTK_RANGE (priv->hsb)) : NULL;
+}
+
+/**
+ * gtk_scroll_frame_get_vadjustment:
+ * @sf: A scroll frame widget.
+ *
+ * Queries the vertical adjustment of a scroll frame widget.
+ *
+ * Return value: The vertical adjustment of the scroll frame, or NULL if none.
+ **/
+GtkAdjustment *
+gtk_scroll_frame_get_vadjustment (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_val_if_fail (sf != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (sf), NULL);
+
+ priv = sf->priv;
+
+ return priv->vsb ? gtk_range_get_adjustment (GTK_RANGE (priv->vsb)) : NULL;
+}
+
+/**
+ * gtk_scroll_frame_set_policy:
+ * @sf: A scroll frame widget.
+ * @hsb_policy: Policy for the horizontal scrollbar.
+ * @vsb_policy: Policy for the vertical scrollbar.
+ *
+ * Sets the scrollbar policies of a scroll frame widget. These determine when
+ * the scrollbars are to be shown or hidden.
+ **/
+void
+gtk_scroll_frame_set_policy (GtkScrollFrame *sf,
+ GtkPolicyType hsb_policy,
+ GtkPolicyType vsb_policy)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->hsb_policy == hsb_policy && priv->vsb_policy == vsb_policy)
+ return;
+
+ priv->hsb_policy = hsb_policy;
+ priv->vsb_policy = vsb_policy;
+
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_placement:
+ * @sf: A scroll frame widget.
+ * @frame_placement: Placement for the frame.
+ *
+ * Sets the placement of a scroll frame widget's frame with respect to its
+ * scrollbars.
+ **/
+void
+gtk_scroll_frame_set_placement (GtkScrollFrame *sf, GtkCornerType frame_placement)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->frame_placement == frame_placement)
+ return;
+
+ priv->frame_placement = frame_placement;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_shadow_type:
+ * @sf: A scroll frame widget.
+ * @shadow_type: A shadow type.
+ *
+ * Sets the shadow type of a scroll frame widget. You can use this when you
+ * insert a child that does not paint a frame on its own.
+ **/
+void
+gtk_scroll_frame_set_shadow_type (GtkScrollFrame *sf, GtkShadowType shadow_type)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+ g_return_if_fail (shadow_type >= GTK_SHADOW_NONE && shadow_type <= GTK_SHADOW_ETCHED_OUT);
+
+ priv = sf->priv;
+
+ if (priv->shadow_type == shadow_type)
+ return;
+
+ priv->shadow_type = shadow_type;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_scrollbar_spacing:
+ * @sf: A scroll frame widget.
+ * @spacing: Desired spacing in pixels.
+ *
+ * Sets the spacing between the frame and the scrollbars of a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_scrollbar_spacing (GtkScrollFrame *sf, guint spacing)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->sb_spacing == spacing)
+ return;
+
+ priv->sb_spacing = spacing;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_add_with_viewport:
+ * @sf: A scroll frame widget.
+ * @child: A widget.
+ *
+ * Creates a &GtkViewport and puts the specified child inside it, thus allowing
+ * the viewport to be scrolled by the scroll frame widget. This is meant to be
+ * used only when a child does not support the scrolling interface.
+ **/
+void
+gtk_scroll_frame_add_with_viewport (GtkScrollFrame *sf, GtkWidget *child)
+{
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GtkWidget *viewport;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (child));
+ g_return_if_fail (child->parent == NULL);
+
+ priv = sf->priv;
+ bin = GTK_BIN (sf);
+
+ if (bin->child != NULL) {
+ g_return_if_fail (GTK_IS_VIEWPORT (bin->child));
+ g_return_if_fail (GTK_BIN (bin->child)->child == NULL);
+
+ viewport = bin->child;
+ } else {
+ viewport = gtk_viewport_new (gtk_scroll_frame_get_hadjustment (sf),
+ gtk_scroll_frame_get_vadjustment (sf));
+ gtk_container_add (GTK_CONTAINER (sf), viewport);
+ }
+
+ gtk_widget_show (viewport);
+ gtk_container_add (GTK_CONTAINER (viewport), child);
+}
diff --git a/libnautilus-private/gtkscrollframe.h b/libnautilus-private/gtkscrollframe.h
new file mode 100644
index 000000000..facb59dac
--- /dev/null
+++ b/libnautilus-private/gtkscrollframe.h
@@ -0,0 +1,93 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This 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.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __GTK_SCROLL_FRAME_H__
+#define __GTK_SCROLL_FRAME_H__
+
+
+#include <gdk/gdk.h>
+#include <gtk/gtkbin.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define GTK_TYPE_SCROLL_FRAME (gtk_scroll_frame_get_type ())
+#define GTK_SCROLL_FRAME(obj) (GTK_CHECK_CAST ((obj), \
+ GTK_TYPE_SCROLL_FRAME, GtkScrollFrame))
+#define GTK_SCROLL_FRAME_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), \
+ GTK_TYPE_SCROLL_FRAME, GtkScrollFrameClass))
+#define GTK_IS_SCROLL_FRAME(obj) (GTK_CHECK_TYPE ((obj), \
+ GTK_TYPE_SCROLL_FRAME))
+#define GTK_IS_SCROLL_FRAME_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), \
+ GTK_TYPE_SCROLL_FRAME))
+
+
+typedef struct _GtkScrollFrame GtkScrollFrame;
+typedef struct _GtkScrollFrameClass GtkScrollFrameClass;
+
+struct _GtkScrollFrame
+{
+ GtkBin bin;
+
+ /* Private data */
+ gpointer priv;
+};
+
+struct _GtkScrollFrameClass
+{
+ GtkBinClass parent_class;
+};
+
+
+GtkType gtk_scroll_frame_get_type (void);
+GtkWidget *gtk_scroll_frame_new (GtkAdjustment *hadj, GtkAdjustment *vadj);
+
+void gtk_scroll_frame_set_hadjustment (GtkScrollFrame *sf, GtkAdjustment *adj);
+void gtk_scroll_frame_set_vadjustment (GtkScrollFrame *sf, GtkAdjustment *adj);
+
+GtkAdjustment *gtk_scroll_frame_get_hadjustment (GtkScrollFrame *sf);
+GtkAdjustment *gtk_scroll_frame_get_vadjustment (GtkScrollFrame *sf);
+
+void gtk_scroll_frame_set_policy (GtkScrollFrame *sf,
+ GtkPolicyType hsb_policy,
+ GtkPolicyType vsb_policy);
+
+void gtk_scroll_frame_set_placement (GtkScrollFrame *sf, GtkCornerType frame_placement);
+void gtk_scroll_frame_set_shadow_type (GtkScrollFrame *sf, GtkShadowType shadow_type);
+void gtk_scroll_frame_set_scrollbar_spacing (GtkScrollFrame *sf, guint spacing);
+
+void gtk_scroll_frame_add_with_viewport (GtkScrollFrame *sf, GtkWidget *child);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __GTK_SCROLL_FRAME_H__ */
diff --git a/libnautilus-private/nautilus-file-operations-progress.c b/libnautilus-private/nautilus-file-operations-progress.c
new file mode 100644
index 000000000..81b3b8b8b
--- /dev/null
+++ b/libnautilus-private/nautilus-file-operations-progress.c
@@ -0,0 +1,351 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* dfos-xfer-progress-dialog.c - Progress dialog for transfer operations in the
+ GNOME Desktop File Operation Service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Ettore Perazzoli <ettore@gnu.org> */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gnome.h>
+
+#include "dfos-xfer-progress-dialog.h"
+
+
+#define DIALOG_WIDTH 350 /* FIXME? */
+
+
+static GnomeDialogClass *parent_class;
+
+
+/* Private functions. */
+
+static void
+update (DFOSXferProgressDialog *dialog)
+{
+ gtk_progress_configure (GTK_PROGRESS (dialog->progress_bar),
+ dialog->total_bytes_copied,
+ 0.0, dialog->bytes_total);
+}
+
+/* This code by Jonathan Blandford (jrb@redhat.com) was shamelessly ripped from
+ `gnome/gdialog.c' in Midnight Commander with minor changes. */
+static gchar *
+trim_string (const gchar *string,
+ GdkFont *font,
+ guint length,
+ guint cur_length)
+{
+ static guint dotdotdot = 0;
+ gchar *string_copy = NULL;
+ gint len;
+
+ if (!dotdotdot)
+ dotdotdot = gdk_string_width (font, "...");
+
+ /* Cut the font length of string to length. */
+
+ length -= dotdotdot;
+ len = (gint) ((1.0 - (gfloat) length / (gfloat) cur_length)
+ * strlen (string));
+
+ /* we guess a starting point */
+ if (gdk_string_width (font, string + len) < length) {
+ while (gdk_string_width (font, string + len) < length)
+ len --;
+ len++;
+ } else {
+ while (gdk_string_width (font, string + len) > length)
+ len ++;
+ }
+
+ string_copy = g_strdup_printf ("...%s", string + len);
+ return string_copy;
+}
+
+static void
+set_text_trimmed (GtkLabel *label,
+ const gchar *text,
+ const gchar *trimmable_text,
+ guint max_width)
+{
+ GdkFont *font;
+ gchar *trimmed_text;
+ gchar *s;
+ guint text_width;
+ guint trimmable_text_width;
+
+ font = GTK_WIDGET (label)->style->font;
+
+ if (text != NULL)
+ text_width = gdk_string_width (font, text);
+ else
+ text_width = 0;
+
+ if (trimmable_text != NULL)
+ trimmable_text_width = gdk_string_width (font, trimmable_text);
+ else
+ trimmable_text_width = 0;
+
+ if (text_width + trimmable_text_width <= max_width) {
+ s = g_strconcat (text, trimmable_text, NULL);
+ gtk_label_set_text (GTK_LABEL (label), s);
+ g_free (s);
+ return;
+ }
+
+ trimmed_text = trim_string (trimmable_text,
+ font,
+ max_width - text_width,
+ trimmable_text_width);
+ s = g_strconcat (text, trimmed_text, NULL);
+
+ gtk_label_set_text (GTK_LABEL (label), s);
+
+ g_free (s);
+ g_free (trimmed_text);
+}
+
+
+/* GnomeDialog signals. */
+
+/* This is just to make sure the dialog is not closed without explicit
+ intervention. */
+static gboolean
+do_close (GnomeDialog *dialog)
+{
+ DFOSXferProgressDialog *progress_dialog;
+
+ progress_dialog = DFOS_XFER_PROGRESS_DIALOG (dialog);
+ return FALSE;
+}
+
+
+/* GtkObject methods. */
+
+static void
+destroy (GtkObject *object)
+{
+ DFOSXferProgressDialog *dialog;
+
+ dialog = DFOS_XFER_PROGRESS_DIALOG (object);
+
+ g_free (dialog->operation_string);
+}
+
+
+/* Initialization. */
+
+static GtkWidget *
+create_label_in_box (GtkBox *vbox)
+{
+ GtkWidget *new;
+
+ new = gtk_label_new ("");
+ gtk_label_set_justify (GTK_LABEL (new), GTK_JUSTIFY_LEFT);
+ gtk_box_pack_start (vbox, new, TRUE, TRUE, 0);
+ gtk_widget_show (new);
+
+ return new;
+}
+
+static void
+init (DFOSXferProgressDialog *dialog)
+{
+ GnomeDialog *gnome_dialog;
+ GtkBox *vbox;
+
+ gnome_dialog = GNOME_DIALOG (dialog);
+ vbox = GTK_BOX (gnome_dialog->vbox);
+
+ dialog->operation_label = create_label_in_box (vbox);
+ dialog->source_label = create_label_in_box (vbox);
+ dialog->target_label = create_label_in_box (vbox);
+
+ dialog->progress_bar = gtk_progress_bar_new ();
+ gtk_progress_bar_set_bar_style (GTK_PROGRESS_BAR (dialog->progress_bar),
+ GTK_PROGRESS_CONTINUOUS);
+ gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (dialog->progress_bar),
+ GTK_PROGRESS_LEFT_TO_RIGHT);
+ gtk_widget_set_usize (GTK_WIDGET (dialog->progress_bar), DIALOG_WIDTH,
+ -1);
+ gtk_box_pack_start (vbox, dialog->progress_bar, FALSE, TRUE, 0);
+ gtk_widget_show (dialog->progress_bar);
+
+ dialog->operation_string = NULL;
+
+ dialog->file_index = 0;
+ dialog->file_size = 0;
+ dialog->files_total = 0;
+ dialog->bytes_total = 0;
+ dialog->bytes_copied = 0;
+ dialog->total_bytes_copied = 0;
+
+ dialog->freeze_count = 0;
+}
+
+static void
+class_init (DFOSXferProgressDialogClass *class)
+{
+ GtkObjectClass *object_class;
+ GnomeDialogClass *dialog_class;
+
+ parent_class = gtk_type_class (gnome_dialog_get_type ());
+
+ object_class = GTK_OBJECT_CLASS (class);
+ dialog_class = GNOME_DIALOG_CLASS (class);
+
+ object_class->destroy = destroy;
+
+ dialog_class->close = do_close;
+}
+
+
+/* Public functions. */
+
+guint
+dfos_xfer_progress_dialog_get_type (void)
+{
+ static guint type = 0;
+
+ if (type == 0) {
+ GtkTypeInfo info = {
+ "DFOSXferProgressDialog",
+ sizeof (DFOSXferProgressDialog),
+ sizeof (DFOSXferProgressDialogClass),
+ (GtkClassInitFunc) class_init,
+ (GtkObjectInitFunc) init,
+ NULL,
+ NULL
+ };
+
+ type = gtk_type_unique (gnome_dialog_get_type (), &info);
+ }
+
+ return type;
+}
+
+GtkWidget *
+dfos_xfer_progress_dialog_new (const gchar *title,
+ const gchar *operation_string,
+ gulong total_files,
+ gulong total_bytes)
+{
+ GtkWidget *new;
+
+ new = gtk_type_new (dfos_xfer_progress_dialog_get_type ());
+
+ dfos_xfer_progress_dialog_set_operation_string (DFOS_XFER_PROGRESS_DIALOG (new),
+ operation_string);
+ dfos_xfer_progress_dialog_set_total (DFOS_XFER_PROGRESS_DIALOG (new),
+ total_files, total_bytes);
+
+ gtk_window_set_title (GTK_WINDOW (new), title);
+
+ gnome_dialog_append_button (GNOME_DIALOG (new),
+ GNOME_STOCK_BUTTON_CANCEL);
+
+ return new;
+}
+
+void
+dfos_xfer_progress_dialog_set_total (DFOSXferProgressDialog *dialog,
+ gulong files_total,
+ gulong bytes_total)
+{
+ g_return_if_fail (IS_DFOS_XFER_PROGRESS_DIALOG (dialog));
+
+ dialog->files_total = files_total;
+ dialog->bytes_total = bytes_total;
+
+ update (dialog);
+}
+
+void
+dfos_xfer_progress_dialog_set_operation_string (DFOSXferProgressDialog *dialog,
+ const gchar *operation_string)
+{
+ g_return_if_fail (IS_DFOS_XFER_PROGRESS_DIALOG (dialog));
+
+ gtk_label_set_text (GTK_LABEL (dialog->operation_label),
+ operation_string);
+ dialog->operation_string = g_strdup (operation_string);
+}
+
+void
+dfos_xfer_progress_dialog_new_file (DFOSXferProgressDialog *dialog,
+ const gchar *source_uri,
+ const gchar *target_uri,
+ gulong size)
+{
+ gchar *s;
+
+ g_return_if_fail (IS_DFOS_XFER_PROGRESS_DIALOG (dialog));
+ g_return_if_fail (GTK_WIDGET_REALIZED (dialog));
+
+ dialog->file_index++;
+ dialog->bytes_copied = 0;
+ dialog->file_size = size;
+
+ s = g_strdup_printf ("%s file %ld/%ld",
+ dialog->operation_string,
+ dialog->file_index, dialog->files_total);
+ gtk_label_set_text (GTK_LABEL (dialog->operation_label), s);
+ g_free (s);
+
+ set_text_trimmed (GTK_LABEL (dialog->source_label),
+ _("From: "), source_uri,
+ DIALOG_WIDTH);
+
+ set_text_trimmed (GTK_LABEL (dialog->target_label),
+ _("To: "), target_uri,
+ DIALOG_WIDTH);
+
+ update (dialog);
+}
+
+void
+dfos_xfer_progress_dialog_update (DFOSXferProgressDialog *dialog,
+ gulong bytes_done_in_file,
+ gulong bytes_done)
+{
+ g_return_if_fail (IS_DFOS_XFER_PROGRESS_DIALOG (dialog));
+
+ dialog->bytes_copied = bytes_done_in_file;
+ dialog->total_bytes_copied = bytes_done;
+
+ update (dialog);
+}
+
+
+void
+dfos_xfer_progress_dialog_freeze (DFOSXferProgressDialog *dialog)
+{
+ dialog->freeze_count++;
+}
+
+void
+dfos_xfer_progress_dialog_thaw (DFOSXferProgressDialog *dialog)
+{
+ if (dialog->freeze_count > 0)
+ dialog->freeze_count--;
+}
+
diff --git a/libnautilus-private/nautilus-file-operations-progress.h b/libnautilus-private/nautilus-file-operations-progress.h
new file mode 100644
index 000000000..657be9be3
--- /dev/null
+++ b/libnautilus-private/nautilus-file-operations-progress.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* xfer-progress-dialog.h - Progress dialog for transfer operations in the
+ GNOME Desktop File Operation Service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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 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; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Ettore Perazzoli <ettore@gnu.org> */
+
+#ifndef _DFOS_XFER_PROGRESS_DIALOG_H
+#define _DFOS_XFER_PROGRESS_DIALOG_H
+
+#include <libgnomeui/gnome-dialog.h>
+
+
+#define DFOS_XFER_PROGRESS_DIALOG(obj) \
+ GTK_CHECK_CAST (obj, dfos_xfer_progress_dialog_get_type (), DFOSXferProgressDialog)
+#define DFOS_XFER_PROGRESS_DIALOG_CLASS(klass) \
+ GTK_CHECK_CLASS_CAST (klass, dfos_xfer_progress_dialog_get_type (), DFOSXferProgressDialogClass)
+#define IS_DFOS_XFER_PROGRESS_DIALOG(obj) \
+ GTK_CHECK_TYPE (obj, dfos_xfer_progress_dialog_get_type ())
+
+
+struct _DFOSXferProgressDialog {
+ GnomeDialog dialog;
+
+ GtkWidget *operation_label;
+ GtkWidget *source_label;
+ GtkWidget *target_label;
+ GtkWidget *progress_bar;
+
+ gchar *operation_string;
+
+ guint freeze_count;
+
+ gulong file_index;
+ gulong file_size;
+
+ gulong bytes_copied;
+ gulong total_bytes_copied;
+
+ gulong files_total;
+ gulong bytes_total;
+};
+typedef struct _DFOSXferProgressDialog DFOSXferProgressDialog;
+
+struct _DFOSXferProgressDialogClass {
+ GnomeDialogClass parent_class;
+};
+typedef struct _DFOSXferProgressDialogClass DFOSXferProgressDialogClass;
+
+
+guint dfos_xfer_progress_dialog_get_type
+ (void);
+
+GtkWidget *dfos_xfer_progress_dialog_new (const gchar *title,
+ const gchar *operation_string,
+ gulong files_total,
+ gulong bytes_total);
+
+void dfos_xfer_progress_dialog_set_total
+ (DFOSXferProgressDialog *dialog,
+ gulong files_total,
+ gulong bytes_total);
+
+void dfos_xfer_progress_dialog_set_operation_string
+ (DFOSXferProgressDialog *dialog,
+ const gchar *operation_string);
+
+void dfos_xfer_progress_dialog_new_file
+ (DFOSXferProgressDialog *dialog,
+ const gchar *source_uri,
+ const gchar *target_uri,
+ gulong size);
+
+void dfos_xfer_progress_dialog_update
+ (DFOSXferProgressDialog *dialog,
+ gulong bytes_done_in_file,
+ gulong bytes_done);
+
+void dfos_xfer_progress_dialog_freeze
+ (DFOSXferProgressDialog *dialog);
+
+void dfos_xfer_progress_dialog_thaw (DFOSXferProgressDialog *dialog);
+
+#endif /* _DFOS_XFER_PROGRESS_DIALOG_H */
diff --git a/libnautilus-private/nautilus-file-operations.c b/libnautilus-private/nautilus-file-operations.c
new file mode 100644
index 000000000..4e1821eaf
--- /dev/null
+++ b/libnautilus-private/nautilus-file-operations.c
@@ -0,0 +1,252 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* xfer.c - GNOME::Desktop::FileOperationService transfer service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org> */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gnome.h>
+#include <libgnomevfs/gnome-vfs.h>
+
+#include "dfos.h"
+#include "error.h"
+
+#include "dfos-xfer.h"
+
+
+struct _XferInfo {
+ GnomeVFSAsyncHandle *handle;
+ GtkWidget *progress_dialog;
+ GnomeVFSXferOptions options;
+ GnomeVFSXferErrorMode error_mode;
+ GnomeVFSXferOverwriteMode overwrite_mode;
+};
+typedef struct _XferInfo XferInfo;
+
+
+static XferInfo *
+xfer_info_new (GnomeVFSAsyncHandle *handle,
+ GnomeVFSXferOptions options,
+ GnomeVFSXferErrorMode error_mode,
+ GnomeVFSXferOverwriteMode overwrite_mode)
+{
+ XferInfo *new;
+
+ new = g_new (XferInfo, 1);
+
+ new->handle = handle;
+ new->options = options;
+ new->error_mode = error_mode;
+ new->overwrite_mode = overwrite_mode;
+
+ new->progress_dialog = NULL;
+
+ return new;
+}
+
+static void
+xfer_info_destroy (XferInfo *info)
+{
+ g_free (info);
+}
+
+
+static void
+xfer_dialog_clicked_callback (DFOSXferProgressDialog *dialog,
+ gint button_number,
+ gpointer data)
+{
+ XferInfo *info;
+
+ info = (XferInfo *) data;
+ gnome_vfs_async_cancel (info->handle);
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ g_warning (_("Operation cancelled"));
+}
+
+static void
+create_xfer_dialog (const GnomeVFSXferProgressInfo *progress_info,
+ XferInfo *xfer_info)
+{
+ gchar *op_string;
+
+ g_return_if_fail (xfer_info->progress_dialog == NULL);
+
+ if (xfer_info->options & GNOME_VFS_XFER_REMOVESOURCE)
+ op_string = _("Moving");
+ else
+ op_string = _("Copying");
+
+ xfer_info->progress_dialog
+ = dfos_xfer_progress_dialog_new ("Transfer in progress",
+ op_string,
+ progress_info->files_total,
+ progress_info->bytes_total);
+
+ gtk_signal_connect (GTK_OBJECT (xfer_info->progress_dialog),
+ "clicked",
+ GTK_SIGNAL_FUNC (xfer_dialog_clicked_callback),
+ xfer_info);
+
+ gtk_widget_show (xfer_info->progress_dialog);
+}
+
+static gint
+handle_xfer_ok (const GnomeVFSXferProgressInfo *progress_info,
+ XferInfo *xfer_info)
+{
+ switch (progress_info->phase) {
+ case GNOME_VFS_XFER_PHASE_READYTOGO:
+ create_xfer_dialog (progress_info, xfer_info);
+ return TRUE;
+ case GNOME_VFS_XFER_PHASE_XFERRING:
+ if (progress_info->bytes_copied == 0) {
+ dfos_xfer_progress_dialog_new_file
+ (DFOS_XFER_PROGRESS_DIALOG
+ (xfer_info->progress_dialog),
+ progress_info->source_name,
+ progress_info->target_name,
+ progress_info->file_size);
+ } else {
+ dfos_xfer_progress_dialog_update
+ (DFOS_XFER_PROGRESS_DIALOG
+ (xfer_info->progress_dialog),
+ progress_info->bytes_copied,
+ progress_info->total_bytes_copied);
+ }
+ return TRUE;
+ case GNOME_VFS_XFER_PHASE_FILECOMPLETED:
+ /* FIXME? */
+ return TRUE;
+ case GNOME_VFS_XFER_PHASE_COMPLETED:
+ gtk_widget_destroy (xfer_info->progress_dialog);
+ g_warning ("***** RELEASING HANDLE");
+ g_free (xfer_info);
+ return TRUE;
+ default:
+ return TRUE;
+ }
+}
+
+static gint
+handle_xfer_vfs_error (const GnomeVFSXferProgressInfo *progress_info,
+ XferInfo *xfer_info)
+{
+ /* Notice that the error mode in `xfer_info' is the one we have been
+ requested, but the transfer is always performed in mode
+ `GNOME_VFS_XFER_ERROR_MODE_QUERY'. */
+
+ switch (xfer_info->error_mode) {
+ case GNOME_VFS_XFER_ERROR_MODE_QUERY: /* FIXME */
+ case GNOME_VFS_XFER_ERROR_MODE_ABORT:
+ default:
+ dfos_xfer_progress_dialog_freeze (DFOS_XFER_PROGRESS_DIALOG
+ (xfer_info->progress_dialog));
+ error (xfer_info->progress_dialog,
+ _("Copy operation failed:\n%s"),
+ gnome_vfs_result_to_string (progress_info->vfs_status));
+ dfos_xfer_progress_dialog_thaw (DFOS_XFER_PROGRESS_DIALOG
+ (xfer_info->progress_dialog));
+ gtk_widget_destroy (xfer_info->progress_dialog);
+ return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
+ }
+}
+
+static gint
+handle_xfer_overwrite (const GnomeVFSXferProgressInfo *progress_info,
+ XferInfo *xfer_info)
+{
+ return FALSE;
+}
+
+static gint
+xfer_callback (GnomeVFSAsyncHandle *handle,
+ const GnomeVFSXferProgressInfo *progress_info,
+ gpointer data)
+{
+ XferInfo *xfer_info;
+
+ xfer_info = (XferInfo *) data;
+
+ switch (progress_info->status) {
+ case GNOME_VFS_XFER_PROGRESS_STATUS_OK:
+ return handle_xfer_ok (progress_info, xfer_info);
+ case GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR:
+ return handle_xfer_vfs_error (progress_info, xfer_info);
+ case GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE:
+ return handle_xfer_overwrite (progress_info, xfer_info);
+ default:
+ g_warning (_("Unknown GnomeVFSXferProgressStatus %d"),
+ progress_info->status);
+ return FALSE;
+ }
+}
+
+
+void
+dfos_xfer (DFOS *dfos,
+ const gchar *source_directory_uri,
+ GList *source_file_name_list,
+ const gchar *target_directory_uri,
+ GList *target_file_name_list,
+ GnomeVFSXferOptions options,
+ GnomeVFSXferErrorMode error_mode,
+ GnomeVFSXferOverwriteMode overwrite_mode)
+{
+ GnomeVFSResult result;
+ XferInfo *xfer_info;
+ GnomeVFSAsyncHandle *handle;
+
+ xfer_info = xfer_info_new (handle, options, overwrite_mode, error_mode);
+
+ result = gnome_vfs_async_xfer (&handle,
+ source_directory_uri,
+ source_file_name_list,
+ target_directory_uri,
+ target_file_name_list,
+ options,
+ GNOME_VFS_XFER_ERROR_MODE_QUERY,
+ overwrite_mode,
+ xfer_callback,
+ xfer_info);
+
+ if (result != GNOME_VFS_OK) {
+ gchar *message;
+ GtkWidget *dialog;
+
+ message = g_strdup_printf (_("The transfer between\n%s\nand\n%s\ncould not be started:\n%s"),
+ source_directory_uri,
+ target_directory_uri,
+ gnome_vfs_result_to_string (result));
+
+
+ /* FIXME: signals and all that. */
+ dialog = gnome_error_dialog (message);
+
+ gtk_widget_show (dialog);
+
+ g_free (message);
+ g_free (xfer_info);
+ }
+}
diff --git a/libnautilus-private/nautilus-file-operations.h b/libnautilus-private/nautilus-file-operations.h
new file mode 100644
index 000000000..a200110e0
--- /dev/null
+++ b/libnautilus-private/nautilus-file-operations.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* xfer.h - GNOME::Desktop::FileOperationService transfer service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _XFER_H
+#define _XFER_H
+
+#include <libgnomevfs/gnome-vfs.h>
+
+void dfos_xfer (DFOS *dfos,
+ const gchar *source_directory_uri,
+ GList *source_file_name_list,
+ const gchar *target_directory_uri,
+ GList *target_file_name_list,
+ GnomeVFSXferOptions options,
+ GnomeVFSXferErrorMode error_mode,
+ GnomeVFSXferOverwriteMode overwrite_mode);
+
+#endif /* _XFER_H */
diff --git a/libnautilus-private/nautilus-scroll-frame.c b/libnautilus-private/nautilus-scroll-frame.c
new file mode 100644
index 000000000..6f41835ec
--- /dev/null
+++ b/libnautilus-private/nautilus-scroll-frame.c
@@ -0,0 +1,1210 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This 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.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include <config.h>
+#include <gtk/gtkhscrollbar.h>
+#include <gtk/gtkvscrollbar.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkviewport.h>
+#include "gtkscrollframe.h"
+
+
+/* scrolled window policy and size requisition handling:
+ *
+ * gtk size requisition works as follows:
+ * a widget upon size-request reports the width and height that it finds
+ * to be best suited to display its contents, including children.
+ * the width and/or height reported from a widget upon size requisition
+ * may be overidden by the user by specifying a width and/or height
+ * other than 0 through gtk_widget_set_usize().
+ *
+ * a scrolled window needs (for imlementing all three policy types) to
+ * request its width and height based on two different rationales.
+ * 1) the user wants the scrolled window to just fit into the space
+ * that it gets allocated for a specifc dimension.
+ * 1.1) this does not apply if the user specified a concrete value
+ * value for that specific dimension by either specifying usize for the
+ * scrolled window or for its child.
+ * 2) the user wants the scrolled window to take as much space up as
+ * is desired by the child for a specifc dimension (i.e. POLICY_NEVER).
+ *
+ * also, kinda obvious:
+ * 3) a user would certainly not have choosen a scrolled window as a container
+ * for the child, if the resulting allocation takes up more space than the
+ * child would have allocated without the scrolled window.
+ *
+ * conclusions:
+ * A) from 1) follows: the scrolled window shouldn't request more space for a
+ * specifc dimension than is required at minimum.
+ * B) from 1.1) follows: the requisition may be overidden by usize of the scrolled
+ * window (done automatically) or by usize of the child (needs to be checked).
+ * C) from 2) follows: for POLICY_NEVER, the scrolled window simply reports the
+ * child's dimension.
+ * D) from 3) follows: the scrolled window child's minimum width and minimum height
+ * under A) at least correspond to the space taken up by its scrollbars.
+ */
+
+/* Object argument IDs */
+enum {
+ ARG_0,
+ ARG_HADJUSTMENT,
+ ARG_VADJUSTMENT,
+ ARG_HSCROLLBAR_POLICY,
+ ARG_VSCROLLBAR_POLICY,
+ ARG_FRAME_PLACEMENT,
+ ARG_SHADOW_TYPE,
+ ARG_SCROLLBAR_SPACING
+};
+
+/* Private part of the GtkScrollFrame structure */
+typedef struct {
+ /* Horizontal and vertical scrollbars */
+ GtkWidget *hsb;
+ GtkWidget *vsb;
+
+ /* Space between scrollbars and frame */
+ guint sb_spacing;
+
+ /* Allocation for frame */
+ guint frame_x;
+ guint frame_y;
+ guint frame_w;
+ guint frame_h;
+
+ /* Scrollbar policy */
+ guint hsb_policy : 2;
+ guint vsb_policy : 2;
+
+ /* Whether scrollbars are visible */
+ guint hsb_visible : 1;
+ guint vsb_visible : 1;
+
+ /* Placement of frame wrt scrollbars */
+ guint frame_placement : 2;
+
+ /* Shadow type for frame */
+ guint shadow_type : 3;
+} ScrollFramePrivate;
+
+
+static void gtk_scroll_frame_class_init (GtkScrollFrameClass *class);
+static void gtk_scroll_frame_init (GtkScrollFrame *sf);
+static void gtk_scroll_frame_set_arg (GtkObject *object, GtkArg *arg, guint arg_id);
+static void gtk_scroll_frame_get_arg (GtkObject *object, GtkArg *arg, guint arg_id);
+static void gtk_scroll_frame_destroy (GtkObject *object);
+static void gtk_scroll_frame_finalize (GtkObject *object);
+
+static void gtk_scroll_frame_map (GtkWidget *widget);
+static void gtk_scroll_frame_unmap (GtkWidget *widget);
+static void gtk_scroll_frame_draw (GtkWidget *widget, GdkRectangle *area);
+static void gtk_scroll_frame_size_request (GtkWidget *widget, GtkRequisition *requisition);
+static void gtk_scroll_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
+static gint gtk_scroll_frame_expose (GtkWidget *widget, GdkEventExpose *event);
+
+static void gtk_scroll_frame_add (GtkContainer *container, GtkWidget *widget);
+static void gtk_scroll_frame_remove (GtkContainer *container, GtkWidget *widget);
+static void gtk_scroll_frame_forall (GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data);
+
+static GtkBinClass *parent_class;
+
+
+/**
+ * gtk_scroll_frame_get_type:
+ * @void:
+ *
+ * Registers the &GtkScrollFrame class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GtkScrollFrame class.
+ **/
+GtkType
+gtk_scroll_frame_get_type (void)
+{
+ static GtkType scroll_frame_type = 0;
+
+ if (!scroll_frame_type) {
+ static const GtkTypeInfo scroll_frame_info = {
+ "GtkScrollFrame",
+ sizeof (GtkScrollFrame),
+ sizeof (GtkScrollFrameClass),
+ (GtkClassInitFunc) gtk_scroll_frame_class_init,
+ (GtkObjectInitFunc) gtk_scroll_frame_init,
+ NULL, /* reserved_1 */
+ NULL, /* reserved_2 */
+ (GtkClassInitFunc) NULL
+ };
+
+ scroll_frame_type = gtk_type_unique (GTK_TYPE_BIN, &scroll_frame_info);
+ }
+
+ return scroll_frame_type;
+}
+
+/* Class initialization function for the scroll frame widget */
+static void
+gtk_scroll_frame_class_init (GtkScrollFrameClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkContainerClass *container_class;
+
+ object_class = (GtkObjectClass *) class;
+ widget_class = (GtkWidgetClass *) class;
+ container_class = (GtkContainerClass *) class;
+
+ parent_class = gtk_type_class (GTK_TYPE_BIN);
+
+ gtk_object_add_arg_type ("GtkScrollFrame::hadjustment",
+ GTK_TYPE_ADJUSTMENT,
+ GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
+ ARG_HADJUSTMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::vadjustment",
+ GTK_TYPE_ADJUSTMENT,
+ GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
+ ARG_VADJUSTMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::hscrollbar_policy",
+ GTK_TYPE_POLICY_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_HSCROLLBAR_POLICY);
+ gtk_object_add_arg_type ("GtkScrollFrame::vscrollbar_policy",
+ GTK_TYPE_POLICY_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_VSCROLLBAR_POLICY);
+ gtk_object_add_arg_type ("GtkScrollFrame::frame_placement",
+ GTK_TYPE_CORNER_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_FRAME_PLACEMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::shadow_type",
+ GTK_TYPE_SHADOW_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_SHADOW_TYPE);
+ gtk_object_add_arg_type ("GtkScrollFrame::scrollbar_spacing",
+ GTK_TYPE_UINT,
+ GTK_ARG_READWRITE,
+ ARG_SCROLLBAR_SPACING);
+
+ object_class->set_arg = gtk_scroll_frame_set_arg;
+ object_class->get_arg = gtk_scroll_frame_get_arg;
+ object_class->destroy = gtk_scroll_frame_destroy;
+ object_class->finalize = gtk_scroll_frame_finalize;
+
+ widget_class->map = gtk_scroll_frame_map;
+ widget_class->unmap = gtk_scroll_frame_unmap;
+ widget_class->draw = gtk_scroll_frame_draw;
+ widget_class->size_request = gtk_scroll_frame_size_request;
+ widget_class->size_allocate = gtk_scroll_frame_size_allocate;
+ widget_class->expose_event = gtk_scroll_frame_expose;
+
+ container_class->add = gtk_scroll_frame_add;
+ container_class->remove = gtk_scroll_frame_remove;
+ container_class->forall = gtk_scroll_frame_forall;
+}
+
+/* Object initialization function for the scroll frame widget */
+static void
+gtk_scroll_frame_init (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ priv = g_new0 (ScrollFramePrivate, 1);
+ sf->priv = priv;
+
+ GTK_WIDGET_SET_FLAGS (sf, GTK_NO_WINDOW);
+
+ gtk_container_set_resize_mode (GTK_CONTAINER (sf), GTK_RESIZE_QUEUE);
+
+ priv->sb_spacing = 3;
+ priv->hsb_policy = GTK_POLICY_ALWAYS;
+ priv->vsb_policy = GTK_POLICY_ALWAYS;
+ priv->frame_placement = GTK_CORNER_TOP_LEFT;
+ priv->shadow_type = GTK_SHADOW_NONE;
+}
+
+/* Set_arg handler for the scroll frame widget */
+static void
+gtk_scroll_frame_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ switch (arg_id) {
+ case ARG_HADJUSTMENT:
+ gtk_scroll_frame_set_hadjustment (sf, GTK_VALUE_POINTER (*arg));
+ break;
+
+ case ARG_VADJUSTMENT:
+ gtk_scroll_frame_set_vadjustment (sf, GTK_VALUE_POINTER (*arg));
+ break;
+
+ case ARG_HSCROLLBAR_POLICY:
+ gtk_scroll_frame_set_policy (sf, GTK_VALUE_ENUM (*arg), priv->vsb_policy);
+ break;
+
+ case ARG_VSCROLLBAR_POLICY:
+ gtk_scroll_frame_set_policy (sf, priv->hsb_policy, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_FRAME_PLACEMENT:
+ gtk_scroll_frame_set_placement (sf, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_SHADOW_TYPE:
+ gtk_scroll_frame_set_shadow_type (sf, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_SCROLLBAR_SPACING:
+ gtk_scroll_frame_set_scrollbar_spacing (sf, GTK_VALUE_UINT (*arg));
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* Get_arg handler for the scroll frame widget */
+static void
+gtk_scroll_frame_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ switch (arg_id) {
+ case ARG_HADJUSTMENT:
+ GTK_VALUE_POINTER (*arg) = gtk_scroll_frame_get_hadjustment (sf);
+ break;
+
+ case ARG_VADJUSTMENT:
+ GTK_VALUE_POINTER (*arg) = gtk_scroll_frame_get_vadjustment (sf);
+ break;
+
+ case ARG_HSCROLLBAR_POLICY:
+ GTK_VALUE_ENUM (*arg) = priv->hsb_policy;
+ break;
+
+ case ARG_VSCROLLBAR_POLICY:
+ GTK_VALUE_ENUM (*arg) = priv->vsb_policy;
+ break;
+
+ case ARG_FRAME_PLACEMENT:
+ GTK_VALUE_ENUM (*arg) = priv->frame_placement;
+ break;
+
+ case ARG_SHADOW_TYPE:
+ GTK_VALUE_ENUM (*arg) = priv->shadow_type;
+ break;
+
+ case ARG_SCROLLBAR_SPACING:
+ GTK_VALUE_UINT (*arg) = priv->sb_spacing;
+ break;
+
+ default:
+ arg->type = GTK_TYPE_INVALID;
+ break;
+ }
+}
+
+/* Destroy handler for the scroll frame widget */
+static void
+gtk_scroll_frame_destroy (GtkObject *object)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (object));
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ gtk_widget_unparent (priv->hsb);
+ gtk_widget_unparent (priv->vsb);
+ gtk_widget_destroy (priv->hsb);
+ gtk_widget_destroy (priv->vsb);
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+/* Finalize handler for the scroll frame widget */
+static void
+gtk_scroll_frame_finalize (GtkObject *object)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ gtk_widget_unref (priv->hsb);
+ gtk_widget_unref (priv->vsb);
+
+ g_free (priv);
+
+ if (GTK_OBJECT_CLASS (parent_class)->finalize)
+ (* GTK_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+/* Map handler for the scroll frame widget */
+static void
+gtk_scroll_frame_map (GtkWidget *widget)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ /* chain parent class handler to map self and child */
+ if (GTK_WIDGET_CLASS (parent_class)->map)
+ (* GTK_WIDGET_CLASS (parent_class)->map) (widget);
+
+ if (GTK_WIDGET_VISIBLE (priv->hsb) && !GTK_WIDGET_MAPPED (priv->hsb))
+ gtk_widget_map (priv->hsb);
+
+ if (GTK_WIDGET_VISIBLE (priv->vsb) && !GTK_WIDGET_MAPPED (priv->vsb))
+ gtk_widget_map (priv->vsb);
+}
+
+/* Unmap handler for the scroll frame widget */
+static void
+gtk_scroll_frame_unmap (GtkWidget *widget)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ /* chain parent class handler to unmap self and child */
+ if (GTK_WIDGET_CLASS (parent_class)->unmap)
+ (* GTK_WIDGET_CLASS (parent_class)->unmap) (widget);
+
+ if (GTK_WIDGET_MAPPED (priv->hsb))
+ gtk_widget_unmap (priv->hsb);
+
+ if (GTK_WIDGET_MAPPED (priv->vsb))
+ gtk_widget_unmap (priv->vsb);
+}
+
+/* Draws the shadow of a scroll frame widget */
+static void
+draw_shadow (GtkScrollFrame *sf, GdkRectangle *area)
+{
+ ScrollFramePrivate *priv;
+
+ g_assert (area != NULL);
+
+ priv = sf->priv;
+
+ gtk_paint_shadow (GTK_WIDGET (sf)->style,
+ GTK_WIDGET (sf)->window,
+ GTK_STATE_NORMAL, priv->shadow_type,
+ area, GTK_WIDGET (sf),
+ "scroll_frame",
+ priv->frame_x, priv->frame_y,
+ priv->frame_w, priv->frame_h);
+}
+
+/* Draw handler for the scroll frame widget */
+static void
+gtk_scroll_frame_draw (GtkWidget *widget, GdkRectangle *area)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GdkRectangle child_area;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (area != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ if (GTK_WIDGET_DRAWABLE (widget))
+ draw_shadow (sf, area);
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)
+ && gtk_widget_intersect (bin->child, area, &child_area))
+ gtk_widget_draw (bin->child, &child_area);
+
+ if (GTK_WIDGET_VISIBLE (priv->hsb)
+ && gtk_widget_intersect (priv->hsb, area, &child_area))
+ gtk_widget_draw (priv->hsb, &child_area);
+
+ if (GTK_WIDGET_VISIBLE (priv->vsb)
+ && gtk_widget_intersect (priv->vsb, area, &child_area))
+ gtk_widget_draw (priv->vsb, &child_area);
+}
+
+/* Forall handler for the scroll frame widget */
+static void
+gtk_scroll_frame_forall (GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (container));
+ g_return_if_fail (callback != NULL);
+
+ sf = GTK_SCROLL_FRAME (container);
+ priv = sf->priv;
+
+ if (GTK_CONTAINER_CLASS (parent_class)->forall)
+ (* GTK_CONTAINER_CLASS (parent_class)->forall) (
+ container, include_internals,
+ callback, callback_data);
+
+ if (include_internals) {
+ if (priv->vsb)
+ (* callback) (priv->vsb, callback_data);
+
+ if (priv->hsb)
+ (* callback) (priv->hsb, callback_data);
+ }
+}
+
+/* Size_request handler for the scroll frame widget */
+static void
+gtk_scroll_frame_size_request (GtkWidget *widget, GtkRequisition *requisition)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ gint extra_width;
+ gint extra_height;
+ GtkRequisition hsb_requisition;
+ GtkRequisition vsb_requisition;
+ GtkRequisition child_requisition;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (requisition != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ extra_width = 0;
+ extra_height = 0;
+
+ requisition->width = GTK_CONTAINER (widget)->border_width * 2;
+ requisition->height = GTK_CONTAINER (widget)->border_width * 2;
+
+ if (priv->shadow_type != GTK_SHADOW_NONE) {
+ requisition->width += 2 * widget->style->klass->xthickness;
+ requisition->height += 2 * widget->style->klass->ythickness;
+ }
+
+ gtk_widget_size_request (priv->hsb, &hsb_requisition);
+ gtk_widget_size_request (priv->vsb, &vsb_requisition);
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) {
+ static guint quark_aux_info;
+
+ if (!quark_aux_info)
+ quark_aux_info = g_quark_from_static_string ("gtk-aux-info");
+
+ gtk_widget_size_request (bin->child, &child_requisition);
+
+ if (priv->hsb_policy == GTK_POLICY_NEVER)
+ requisition->width += child_requisition.width;
+ else {
+ GtkWidgetAuxInfo *aux_info;
+
+ aux_info = gtk_object_get_data_by_id (GTK_OBJECT (bin->child),
+ quark_aux_info);
+ if (aux_info && aux_info->width > 0) {
+ requisition->width += aux_info->width;
+ extra_width = -1;
+ } else
+ requisition->width += vsb_requisition.width;
+ }
+
+ if (priv->vsb_policy == GTK_POLICY_NEVER)
+ requisition->height += child_requisition.height;
+ else {
+ GtkWidgetAuxInfo *aux_info;
+
+ aux_info = gtk_object_get_data_by_id (GTK_OBJECT (bin->child),
+ quark_aux_info);
+ if (aux_info && aux_info->height > 0) {
+ requisition->height += aux_info->height;
+ extra_height = -1;
+ } else
+ requisition->height += hsb_requisition.height;
+ }
+ }
+
+ if (priv->hsb_policy == GTK_POLICY_AUTOMATIC || GTK_WIDGET_VISIBLE (priv->hsb)) {
+ requisition->width = MAX (requisition->width, hsb_requisition.width);
+ if (!extra_height || GTK_WIDGET_VISIBLE (priv->hsb))
+ extra_height = priv->sb_spacing + hsb_requisition.height;
+ }
+
+ if (priv->vsb_policy == GTK_POLICY_AUTOMATIC || GTK_WIDGET_VISIBLE (priv->vsb)) {
+ requisition->height = MAX (requisition->height, vsb_requisition.height);
+ if (!extra_width || GTK_WIDGET_VISIBLE (priv->vsb))
+ extra_width = priv->sb_spacing + vsb_requisition.width;
+ }
+
+ requisition->width += MAX (0, extra_width);
+ requisition->height += MAX (0, extra_height);
+}
+
+/* Computes the relative allocation for the scroll frame widget */
+static void
+compute_relative_allocation (GtkWidget *widget, GtkAllocation *allocation)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_assert (widget != NULL);
+ g_assert (GTK_IS_SCROLL_FRAME (widget));
+ g_assert (allocation != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ allocation->x = GTK_CONTAINER (widget)->border_width;
+ allocation->y = GTK_CONTAINER (widget)->border_width;
+ allocation->width = MAX (1, (gint) widget->allocation.width - allocation->x * 2);
+ allocation->height = MAX (1, (gint) widget->allocation.height - allocation->y * 2);
+
+ if (priv->vsb_visible) {
+ GtkRequisition vsb_requisition;
+
+ gtk_widget_get_child_requisition (priv->vsb, &vsb_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_TOP_RIGHT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_RIGHT)
+ allocation->x += vsb_requisition.width + priv->sb_spacing;
+
+ allocation->width = MAX (1, ((gint) allocation->width
+ - ((gint) vsb_requisition.width + priv->sb_spacing)));
+ }
+
+ if (priv->hsb_visible) {
+ GtkRequisition hsb_requisition;
+
+ gtk_widget_get_child_requisition (priv->hsb, &hsb_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_BOTTOM_LEFT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_RIGHT)
+ allocation->y += hsb_requisition.height + priv->sb_spacing;
+
+ allocation->height = MAX (1, ((gint) allocation->height
+ - ((gint) hsb_requisition.height + priv->sb_spacing)));
+ }
+}
+
+/* Size_allocate handler for the scroll frame widget */
+static void
+gtk_scroll_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GtkAllocation relative_allocation;
+ GtkAllocation child_allocation;
+ gint xthickness, ythickness;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (allocation != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ widget->allocation = *allocation;
+
+ if (priv->hsb_policy == GTK_POLICY_ALWAYS)
+ priv->hsb_visible = TRUE;
+ else if (priv->hsb_policy == GTK_POLICY_NEVER)
+ priv->hsb_visible = FALSE;
+
+ if (priv->vsb_policy == GTK_POLICY_ALWAYS)
+ priv->vsb_visible = TRUE;
+ else if (priv->vsb_policy == GTK_POLICY_NEVER)
+ priv->vsb_visible = FALSE;
+
+ if (priv->shadow_type == GTK_SHADOW_NONE) {
+ xthickness = 0;
+ ythickness = 0;
+ } else {
+ xthickness = widget->style->klass->xthickness;
+ ythickness = widget->style->klass->ythickness;
+ }
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) {
+ gboolean previous_hvis;
+ gboolean previous_vvis;
+ guint count = 0;
+
+ do {
+ compute_relative_allocation (widget, &relative_allocation);
+
+ priv->frame_x = relative_allocation.x + allocation->x;
+ priv->frame_y = relative_allocation.y + allocation->y;
+ priv->frame_w = relative_allocation.width;
+ priv->frame_h = relative_allocation.height;
+
+ child_allocation.x = priv->frame_x + xthickness;
+ child_allocation.y = priv->frame_y + ythickness;
+ child_allocation.width = priv->frame_w - 2 * xthickness;
+ child_allocation.height = priv->frame_h - 2 * ythickness;
+
+ previous_hvis = priv->hsb_visible;
+ previous_vvis = priv->vsb_visible;
+
+ gtk_widget_size_allocate (bin->child, &child_allocation);
+
+ /* If, after the first iteration, the hscrollbar and the
+ * vscrollbar flip visiblity, then we need both.
+ */
+ if (count
+ && previous_hvis != priv->hsb_visible
+ && previous_vvis != priv->vsb_visible) {
+ priv->hsb_visible = TRUE;
+ priv->vsb_visible = TRUE;
+
+ /* a new resize is already queued at this point,
+ * so we will immediatedly get reinvoked
+ */
+ return;
+ }
+
+ count++;
+ } while (previous_hvis != priv->hsb_visible
+ || previous_vvis != priv->vsb_visible);
+ } else
+ compute_relative_allocation (widget, &relative_allocation);
+
+ if (priv->hsb_visible) {
+ GtkRequisition hscrollbar_requisition;
+
+ gtk_widget_get_child_requisition (priv->hsb, &hscrollbar_requisition);
+
+ if (!GTK_WIDGET_VISIBLE (priv->hsb))
+ gtk_widget_show (priv->hsb);
+
+ child_allocation.x = relative_allocation.x;
+ if (priv->frame_placement == GTK_CORNER_TOP_LEFT
+ || priv->frame_placement == GTK_CORNER_TOP_RIGHT)
+ child_allocation.y = (relative_allocation.y
+ + relative_allocation.height
+ + priv->sb_spacing);
+ else
+ child_allocation.y = GTK_CONTAINER (sf)->border_width;
+
+ child_allocation.width = relative_allocation.width;
+ child_allocation.height = hscrollbar_requisition.height;
+ child_allocation.x += allocation->x;
+ child_allocation.y += allocation->y;
+
+ gtk_widget_size_allocate (priv->hsb, &child_allocation);
+ } else if (GTK_WIDGET_VISIBLE (priv->hsb))
+ gtk_widget_hide (priv->hsb);
+
+ if (priv->vsb_visible) {
+ GtkRequisition vscrollbar_requisition;
+
+ if (!GTK_WIDGET_VISIBLE (priv->vsb))
+ gtk_widget_show (priv->vsb);
+
+ gtk_widget_get_child_requisition (priv->vsb, &vscrollbar_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_TOP_LEFT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_LEFT)
+ child_allocation.x = (relative_allocation.x
+ + relative_allocation.width
+ + priv->sb_spacing);
+ else
+ child_allocation.x = GTK_CONTAINER (sf)->border_width;
+
+ child_allocation.y = relative_allocation.y;
+ child_allocation.width = vscrollbar_requisition.width;
+ child_allocation.height = relative_allocation.height;
+ child_allocation.x += allocation->x;
+ child_allocation.y += allocation->y;
+
+ gtk_widget_size_allocate (priv->vsb, &child_allocation);
+ } else if (GTK_WIDGET_VISIBLE (priv->vsb))
+ gtk_widget_hide (priv->vsb);
+}
+
+/* Expose handler for the scroll frame widget */
+static gint
+gtk_scroll_frame_expose (GtkWidget *widget, GdkEventExpose *event)
+{
+ GtkScrollFrame *sf;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ sf = GTK_SCROLL_FRAME (widget);
+
+ if (GTK_WIDGET_DRAWABLE (widget))
+ draw_shadow (sf, &event->area);
+
+ if (GTK_WIDGET_CLASS (parent_class)->expose_event)
+ (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
+
+ return FALSE;
+}
+
+/* Add handler for the scroll frame widget */
+static void
+gtk_scroll_frame_add (GtkContainer *container, GtkWidget *child)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+
+ sf = GTK_SCROLL_FRAME (container);
+ priv = sf->priv;
+ bin = GTK_BIN (container);
+ g_return_if_fail (bin->child == NULL);
+
+ bin->child = child;
+ gtk_widget_set_parent (child, GTK_WIDGET (bin));
+
+ /* this is a temporary message */
+ if (!gtk_widget_set_scroll_adjustments (child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb))))
+ g_warning ("gtk_scroll_frame_add(): cannot add non scrollable widget "
+ "use gtk_scroll_frame_add_with_viewport() instead");
+
+ if (GTK_WIDGET_REALIZED (child->parent))
+ gtk_widget_realize (child);
+
+ if (GTK_WIDGET_VISIBLE (child->parent) && GTK_WIDGET_VISIBLE (child)) {
+ if (GTK_WIDGET_MAPPED (child->parent))
+ gtk_widget_map (child);
+
+ gtk_widget_queue_resize (child);
+ }
+}
+
+/* Remove method for the scroll frame widget */
+static void
+gtk_scroll_frame_remove (GtkContainer *container, GtkWidget *child)
+{
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (container));
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_BIN (container)->child == child);
+
+ gtk_widget_set_scroll_adjustments (child, NULL, NULL);
+
+ /* chain parent class handler to remove child */
+ if (GTK_CONTAINER_CLASS (parent_class)->remove)
+ (* GTK_CONTAINER_CLASS (parent_class)->remove) (container, child);
+}
+
+/**
+ * gtk_scroll_frame_new:
+ * @hadj: If non-NULL, the adjustment to use for horizontal scrolling.
+ * @vadj: If non-NULL, the adjustment to use for vertical scrolling.
+ *
+ * Creates a new scroll frame widget.
+ *
+ * Return value: The newly-created scroll frame widget.
+ **/
+GtkWidget *
+gtk_scroll_frame_new (GtkAdjustment *hadj, GtkAdjustment *vadj)
+{
+ if (hadj)
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (hadj), NULL);
+
+ if (vadj)
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (vadj), NULL);
+
+ return gtk_widget_new (GTK_TYPE_SCROLL_FRAME,
+ "hadjustment", hadj,
+ "vadjustment", vadj,
+ NULL);
+}
+
+/* Callback used when one of the scroll frame widget's adjustments changes */
+static void
+adjustment_changed (GtkAdjustment *adj, gpointer data)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (adj != NULL);
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ g_return_if_fail (data != NULL);
+
+ sf = GTK_SCROLL_FRAME (data);
+ priv = sf->priv;
+
+ if (adj == gtk_range_get_adjustment (GTK_RANGE (priv->hsb))) {
+ if (priv->hsb_policy == GTK_POLICY_AUTOMATIC) {
+ gboolean visible;
+
+ visible = priv->hsb_visible;
+ priv->hsb_visible = (adj->upper - adj->lower > adj->page_size);
+ if (priv->hsb_visible != visible)
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+ }
+ } else if (adj == gtk_range_get_adjustment (GTK_RANGE (priv->vsb))) {
+ if (priv->vsb_policy == GTK_POLICY_AUTOMATIC) {
+ gboolean visible;
+
+ visible = priv->vsb_visible;
+ priv->vsb_visible = (adj->upper - adj->lower > adj->page_size);
+ if (priv->vsb_visible != visible)
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+ }
+ }
+}
+
+/**
+ * gtk_scroll_frame_set_hadjustment:
+ * @sf: A scroll frame widget.
+ * @adj: An adjustment.
+ *
+ * Sets the adjustment to be used for horizontal scrolling in a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_hadjustment (GtkScrollFrame *sf, GtkAdjustment *adj)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (adj)
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ else
+ adj = GTK_ADJUSTMENT (gtk_object_new (GTK_TYPE_ADJUSTMENT, NULL));
+
+ if (!priv->hsb) {
+ gtk_widget_push_composite_child ();
+ priv->hsb = gtk_hscrollbar_new (adj);
+ gtk_widget_set_composite_name (priv->hsb, "hscrollbar");
+ gtk_widget_pop_composite_child ();
+
+ gtk_widget_set_parent (priv->hsb, GTK_WIDGET (sf));
+ gtk_widget_ref (priv->hsb);
+ gtk_widget_show (priv->hsb);
+ } else {
+ GtkAdjustment *old_adj;
+
+ old_adj = gtk_range_get_adjustment (GTK_RANGE (priv->hsb));
+ if (old_adj == adj)
+ return;
+
+ gtk_signal_disconnect_by_func (GTK_OBJECT (old_adj),
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ gtk_range_set_adjustment (GTK_RANGE (priv->hsb), adj);
+ }
+
+ adj = gtk_range_get_adjustment (GTK_RANGE (priv->hsb));
+ gtk_signal_connect (GTK_OBJECT (adj),
+ "changed",
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ adjustment_changed (adj, sf);
+
+ if (GTK_BIN (sf)->child)
+ gtk_widget_set_scroll_adjustments (
+ GTK_BIN (sf)->child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb)));
+}
+
+/**
+ * gtk_scroll_frame_set_vadjustment:
+ * @sf: A scroll frame widget.
+ * @adj: An adjustment.
+ *
+ * Sets the adjustment to be used for vertical scrolling in a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_vadjustment (GtkScrollFrame *sf, GtkAdjustment *adj)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (adj)
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ else
+ adj = GTK_ADJUSTMENT (gtk_object_new (GTK_TYPE_ADJUSTMENT, NULL));
+
+ if (!priv->vsb) {
+ gtk_widget_push_composite_child ();
+ priv->vsb = gtk_vscrollbar_new (adj);
+ gtk_widget_set_composite_name (priv->vsb, "vscrollbar");
+ gtk_widget_pop_composite_child ();
+
+ gtk_widget_set_parent (priv->vsb, GTK_WIDGET (sf));
+ gtk_widget_ref (priv->vsb);
+ gtk_widget_show (priv->vsb);
+ } else {
+ GtkAdjustment *old_adj;
+
+ old_adj = gtk_range_get_adjustment (GTK_RANGE (priv->vsb));
+ if (old_adj == adj)
+ return;
+
+ gtk_signal_disconnect_by_func (GTK_OBJECT (old_adj),
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ gtk_range_set_adjustment (GTK_RANGE (priv->vsb), adj);
+ }
+
+ adj = gtk_range_get_adjustment (GTK_RANGE (priv->vsb));
+ gtk_signal_connect (GTK_OBJECT (adj),
+ "changed",
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ adjustment_changed (adj, sf);
+
+ if (GTK_BIN (sf)->child)
+ gtk_widget_set_scroll_adjustments (
+ GTK_BIN (sf)->child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb)));
+}
+
+/**
+ * gtk_scroll_frame_get_hadjustment:
+ * @sf: A scroll frame widget.
+ *
+ * Queries the horizontal adjustment of a scroll frame widget.
+ *
+ * Return value: The horizontal adjustment of the scroll frame, or NULL if none.
+ **/
+GtkAdjustment *
+gtk_scroll_frame_get_hadjustment (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_val_if_fail (sf != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (sf), NULL);
+
+ priv = sf->priv;
+
+ return priv->hsb ? gtk_range_get_adjustment (GTK_RANGE (priv->hsb)) : NULL;
+}
+
+/**
+ * gtk_scroll_frame_get_vadjustment:
+ * @sf: A scroll frame widget.
+ *
+ * Queries the vertical adjustment of a scroll frame widget.
+ *
+ * Return value: The vertical adjustment of the scroll frame, or NULL if none.
+ **/
+GtkAdjustment *
+gtk_scroll_frame_get_vadjustment (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_val_if_fail (sf != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (sf), NULL);
+
+ priv = sf->priv;
+
+ return priv->vsb ? gtk_range_get_adjustment (GTK_RANGE (priv->vsb)) : NULL;
+}
+
+/**
+ * gtk_scroll_frame_set_policy:
+ * @sf: A scroll frame widget.
+ * @hsb_policy: Policy for the horizontal scrollbar.
+ * @vsb_policy: Policy for the vertical scrollbar.
+ *
+ * Sets the scrollbar policies of a scroll frame widget. These determine when
+ * the scrollbars are to be shown or hidden.
+ **/
+void
+gtk_scroll_frame_set_policy (GtkScrollFrame *sf,
+ GtkPolicyType hsb_policy,
+ GtkPolicyType vsb_policy)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->hsb_policy == hsb_policy && priv->vsb_policy == vsb_policy)
+ return;
+
+ priv->hsb_policy = hsb_policy;
+ priv->vsb_policy = vsb_policy;
+
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_placement:
+ * @sf: A scroll frame widget.
+ * @frame_placement: Placement for the frame.
+ *
+ * Sets the placement of a scroll frame widget's frame with respect to its
+ * scrollbars.
+ **/
+void
+gtk_scroll_frame_set_placement (GtkScrollFrame *sf, GtkCornerType frame_placement)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->frame_placement == frame_placement)
+ return;
+
+ priv->frame_placement = frame_placement;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_shadow_type:
+ * @sf: A scroll frame widget.
+ * @shadow_type: A shadow type.
+ *
+ * Sets the shadow type of a scroll frame widget. You can use this when you
+ * insert a child that does not paint a frame on its own.
+ **/
+void
+gtk_scroll_frame_set_shadow_type (GtkScrollFrame *sf, GtkShadowType shadow_type)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+ g_return_if_fail (shadow_type >= GTK_SHADOW_NONE && shadow_type <= GTK_SHADOW_ETCHED_OUT);
+
+ priv = sf->priv;
+
+ if (priv->shadow_type == shadow_type)
+ return;
+
+ priv->shadow_type = shadow_type;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_scrollbar_spacing:
+ * @sf: A scroll frame widget.
+ * @spacing: Desired spacing in pixels.
+ *
+ * Sets the spacing between the frame and the scrollbars of a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_scrollbar_spacing (GtkScrollFrame *sf, guint spacing)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->sb_spacing == spacing)
+ return;
+
+ priv->sb_spacing = spacing;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_add_with_viewport:
+ * @sf: A scroll frame widget.
+ * @child: A widget.
+ *
+ * Creates a &GtkViewport and puts the specified child inside it, thus allowing
+ * the viewport to be scrolled by the scroll frame widget. This is meant to be
+ * used only when a child does not support the scrolling interface.
+ **/
+void
+gtk_scroll_frame_add_with_viewport (GtkScrollFrame *sf, GtkWidget *child)
+{
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GtkWidget *viewport;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (child));
+ g_return_if_fail (child->parent == NULL);
+
+ priv = sf->priv;
+ bin = GTK_BIN (sf);
+
+ if (bin->child != NULL) {
+ g_return_if_fail (GTK_IS_VIEWPORT (bin->child));
+ g_return_if_fail (GTK_BIN (bin->child)->child == NULL);
+
+ viewport = bin->child;
+ } else {
+ viewport = gtk_viewport_new (gtk_scroll_frame_get_hadjustment (sf),
+ gtk_scroll_frame_get_vadjustment (sf));
+ gtk_container_add (GTK_CONTAINER (sf), viewport);
+ }
+
+ gtk_widget_show (viewport);
+ gtk_container_add (GTK_CONTAINER (viewport), child);
+}
diff --git a/libnautilus-private/nautilus-scroll-frame.h b/libnautilus-private/nautilus-scroll-frame.h
new file mode 100644
index 000000000..facb59dac
--- /dev/null
+++ b/libnautilus-private/nautilus-scroll-frame.h
@@ -0,0 +1,93 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This 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.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __GTK_SCROLL_FRAME_H__
+#define __GTK_SCROLL_FRAME_H__
+
+
+#include <gdk/gdk.h>
+#include <gtk/gtkbin.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define GTK_TYPE_SCROLL_FRAME (gtk_scroll_frame_get_type ())
+#define GTK_SCROLL_FRAME(obj) (GTK_CHECK_CAST ((obj), \
+ GTK_TYPE_SCROLL_FRAME, GtkScrollFrame))
+#define GTK_SCROLL_FRAME_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), \
+ GTK_TYPE_SCROLL_FRAME, GtkScrollFrameClass))
+#define GTK_IS_SCROLL_FRAME(obj) (GTK_CHECK_TYPE ((obj), \
+ GTK_TYPE_SCROLL_FRAME))
+#define GTK_IS_SCROLL_FRAME_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), \
+ GTK_TYPE_SCROLL_FRAME))
+
+
+typedef struct _GtkScrollFrame GtkScrollFrame;
+typedef struct _GtkScrollFrameClass GtkScrollFrameClass;
+
+struct _GtkScrollFrame
+{
+ GtkBin bin;
+
+ /* Private data */
+ gpointer priv;
+};
+
+struct _GtkScrollFrameClass
+{
+ GtkBinClass parent_class;
+};
+
+
+GtkType gtk_scroll_frame_get_type (void);
+GtkWidget *gtk_scroll_frame_new (GtkAdjustment *hadj, GtkAdjustment *vadj);
+
+void gtk_scroll_frame_set_hadjustment (GtkScrollFrame *sf, GtkAdjustment *adj);
+void gtk_scroll_frame_set_vadjustment (GtkScrollFrame *sf, GtkAdjustment *adj);
+
+GtkAdjustment *gtk_scroll_frame_get_hadjustment (GtkScrollFrame *sf);
+GtkAdjustment *gtk_scroll_frame_get_vadjustment (GtkScrollFrame *sf);
+
+void gtk_scroll_frame_set_policy (GtkScrollFrame *sf,
+ GtkPolicyType hsb_policy,
+ GtkPolicyType vsb_policy);
+
+void gtk_scroll_frame_set_placement (GtkScrollFrame *sf, GtkCornerType frame_placement);
+void gtk_scroll_frame_set_shadow_type (GtkScrollFrame *sf, GtkShadowType shadow_type);
+void gtk_scroll_frame_set_scrollbar_spacing (GtkScrollFrame *sf, guint spacing);
+
+void gtk_scroll_frame_add_with_viewport (GtkScrollFrame *sf, GtkWidget *child);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __GTK_SCROLL_FRAME_H__ */
diff --git a/libnautilus/gnome-icon-container-dnd.c b/libnautilus/gnome-icon-container-dnd.c
new file mode 100644
index 000000000..02f97f181
--- /dev/null
+++ b/libnautilus/gnome-icon-container-dnd.c
@@ -0,0 +1,647 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container-dnd.c - Drag & drop handling for the icon container
+ widget.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "gnome-icon-container-private.h"
+
+#include "gnome-icon-container-dnd.h"
+
+
+struct _DndSelectionItem {
+ gchar *uri;
+ gint x, y;
+};
+typedef struct _DndSelectionItem DndSelectionItem;
+
+
+static GtkTargetEntry drag_types [] = {
+ { GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST_TYPE, 0, GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST },
+ { GNOME_ICON_CONTAINER_DND_URI_LIST_TYPE, 0, GNOME_ICON_CONTAINER_DND_URI_LIST },
+ /* { GNOME_ICON_CONTAINER_DND_URL_TYPE, 0, GNOME_ICON_CONTAINER_DND_URL } */
+};
+static const int num_drag_types = sizeof (drag_types) / sizeof (drag_types[0]);
+
+static GtkTargetEntry drop_types [] = {
+ { GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST_TYPE, 0, GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST },
+ { GNOME_ICON_CONTAINER_DND_URI_LIST_TYPE, 0, GNOME_ICON_CONTAINER_DND_URI_LIST },
+ { GNOME_ICON_CONTAINER_DND_URL_TYPE, 0, GNOME_ICON_CONTAINER_DND_URL }
+};
+static const int num_drop_types = sizeof (drop_types) / sizeof (drop_types[0]);
+
+
+static GnomeCanvasItem *
+create_selection_shadow (GnomeIconContainer *container,
+ GList *list)
+{
+ GnomeCanvasGroup *group;
+ GnomeCanvas *canvas;
+ GdkBitmap *stipple;
+ gint max_x, max_y;
+ gint min_x, min_y;
+ gint icon_width, icon_height;
+ gint cell_width, cell_height;
+ GList *p;
+
+ if (list == NULL)
+ return NULL;
+
+ stipple = container->priv->dnd_info->stipple;
+ g_return_val_if_fail (stipple != NULL, NULL);
+
+ icon_width = GNOME_ICON_CONTAINER_ICON_WIDTH (container);
+ icon_height = GNOME_ICON_CONTAINER_ICON_HEIGHT (container);
+ cell_width = GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ cell_height = GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ canvas = GNOME_CANVAS (container);
+
+ /* Creating a big set of rectangles in the canvas can be expensive, so
+ we try to be smart and only create the maximum number of rectangles
+ that we will need, in the vertical/horizontal directions. */
+
+ max_x = GTK_WIDGET (container)->allocation.width;
+ min_x = -max_x;
+
+ max_y = GTK_WIDGET (container)->allocation.height;
+ min_y = -max_y;
+
+ /* Create a group, so that it's easier to move all the items around at
+ once. */
+ group = GNOME_CANVAS_GROUP
+ (gnome_canvas_item_new (GNOME_CANVAS_GROUP (canvas->root),
+ gnome_canvas_group_get_type (),
+ NULL));
+
+ for (p = list; p != NULL; p = p->next) {
+ DndSelectionItem *item;
+ gint x1, y1;
+ gint x2, y2;
+
+ item = p->data;
+
+ x1 = item->x;
+ y1 = item->y;
+ x2 = item->x + icon_width;
+ y2 = item->y + icon_height;
+
+ if (x2 >= min_x && x1 <= max_x && y2 >= min_y && y1 <= max_y) {
+ GnomeCanvasItem *rect;
+
+ rect = gnome_canvas_item_new
+ (group,
+ gnome_canvas_rect_get_type (),
+ "x1", (double) x1, "y1", (double) y1,
+ "x2", (double) x2, "y2", (double) y2,
+ "outline_color", "black",
+ "outline_stipple", stipple,
+ "width_pixels", 1,
+ NULL);
+ }
+ }
+
+ return GNOME_CANVAS_ITEM (group);
+}
+
+/* This is a workaround for a gnome-canvas bug: with the current (1.0.18)
+ gnome-libs, setting the x/y values for an existing group fails at updating
+ the bounds of the group. So, instead of setting the x/y values to the
+ current position at initialization time, we set them to (0,0) and then use a
+ simple affine transform. */
+static void
+set_shadow_position (GnomeCanvasItem *shadow,
+ gdouble x, gdouble y)
+{
+ double affine[6];
+
+ affine[0] = 1.0;
+ affine[1] = 0.0;
+ affine[2] = 0.0;
+ affine[3] = 1.0;
+ affine[4] = x;
+ affine[5] = y;
+
+ gnome_canvas_item_affine_absolute (shadow, affine);
+}
+
+
+/* Functions to deal with DndSelectionItems. */
+
+static DndSelectionItem *
+dnd_selection_item_new (void)
+{
+ DndSelectionItem *new;
+
+ new = g_new (DndSelectionItem, 1);
+ new->uri = NULL;
+ new->x = 0;
+ new->y = 0;
+
+ return new;
+}
+
+static void
+dnd_selection_item_destroy (DndSelectionItem *item)
+{
+ g_free (item->uri);
+ g_free (item);
+}
+
+static void
+destroy_selection_list (GList *list)
+{
+ GList *p;
+
+ if (list == NULL)
+ return;
+
+ for (p = list; p != NULL; p = p->next)
+ dnd_selection_item_destroy ((DndSelectionItem *) p->data);
+
+ g_list_free (list);
+}
+
+
+/* Source-side handling of the drag. */
+
+/* Encode a "special/x-gnome-icon-list" selection. */
+static void
+set_gnome_icon_list_selection (GnomeIconContainer *container,
+ GtkSelectionData *selection_data)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+ GString *data;
+ gdouble x_offset, y_offset;
+
+ priv = container->priv;
+ if (priv->icons == NULL) {
+ /* FIXME? Actually this probably shouldn't happen. */
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, NULL, 0);
+ return;
+ }
+
+ x_offset = container->priv->dnd_info->start_x;
+ y_offset = container->priv->dnd_info->start_y;
+
+ data = g_string_new (NULL);
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+ gchar *s;
+ gint x, y;
+
+ icon = p->data;
+ if (! icon->is_selected)
+ continue;
+
+ x = (gint) (icon->x - x_offset);
+ y = (gint) (icon->y - y_offset);
+
+ x += (GNOME_ICON_CONTAINER_ICON_XOFFSET (container)
+ - GNOME_ICON_CONTAINER_ICON_WIDTH (container) / 2);
+ y += (GNOME_ICON_CONTAINER_ICON_YOFFSET (container)
+ - GNOME_ICON_CONTAINER_ICON_HEIGHT (container) / 2);
+
+ if (priv->base_uri != NULL)
+ s = g_strdup_printf ("%s%s\r%d:%d\r\n",
+ priv->base_uri, icon->text,
+ x, y);
+ else
+ s = g_strdup_printf ("%s\r%d:%d\r\n",
+ icon->text, x, y);
+
+ g_string_append (data, s);
+ g_free (s);
+ }
+
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, (guchar *) data->str, data->len);
+
+ g_string_free (data, TRUE);
+}
+
+/* Encode a "text/uri-list" selection. */
+static void
+set_uri_list_selection (GnomeIconContainer *container,
+ GtkSelectionData *selection_data)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+ GString *data;
+
+ priv = container->priv;
+ if (priv->icons == NULL) {
+ /* FIXME? Actually this probably shouldn't happen. */
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, NULL, 0);
+ return;
+ }
+
+ data = g_string_new (NULL);
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (! icon->is_selected)
+ continue;
+
+ /* This is lame code, I know. */
+
+ if (priv->base_uri != NULL)
+ g_string_append (data, priv->base_uri);
+ g_string_append (data, icon->text);
+ g_string_append (data, "\r\n");
+ }
+
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, (guchar *) data->str, data->len);
+
+ g_string_free (data, TRUE);
+}
+
+static void
+drag_data_get_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer data)
+{
+ GnomeIconContainer *container;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (widget));
+ g_return_if_fail (context != NULL);
+
+ container = GNOME_ICON_CONTAINER (widget);
+
+ switch (info) {
+ case GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST:
+ set_gnome_icon_list_selection (container, selection_data);
+ break;
+ case GNOME_ICON_CONTAINER_DND_URI_LIST:
+ set_uri_list_selection (container, selection_data);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+
+/* Target-side handling of the drag. */
+
+static void
+get_gnome_icon_list_selection (GnomeIconContainer *container,
+ GtkSelectionData *data)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+ const guchar *p, *oldp;
+ gint size;
+
+ dnd_info = container->priv->dnd_info;
+
+ oldp = data->data;
+ size = data->length;
+
+ while (1) {
+ DndSelectionItem *item;
+ guint len;
+
+ /* The list is in the form:
+
+ name\rx:y:width:height\r\n
+
+ The geometry information after the first \r is optional. */
+
+ /* 1: Decode name. */
+
+ p = memchr (oldp, '\r', size);
+ if (p == NULL)
+ break;
+
+ item = dnd_selection_item_new ();
+
+ len = p - oldp;
+
+ item->uri = g_malloc (len + 1);
+ memcpy (item->uri, oldp, len);
+ item->uri[len] = 0;
+
+ p++;
+ if (*p == '\n' || *p == '\0') {
+ dnd_info->selection_list
+ = g_list_prepend (dnd_info->selection_list,
+ item);
+ if (p == 0) {
+ g_warning ("Invalid special/x-gnome-icon-list data received: "
+ "missing newline character.");
+ break;
+ } else {
+ oldp = p + 1;
+ continue;
+ }
+ }
+
+ size -= p - oldp;
+ oldp = p;
+
+ /* 2: Decode geometry information. */
+
+ if (sscanf (p, "%d:%d", &item->x, &item->y) != 2)
+ g_warning ("Invalid special/x-gnome-icon-list data received: "
+ "invalid geometry specification.");
+
+ dnd_info->selection_list
+ = g_list_prepend (dnd_info->selection_list, item);
+
+ p = memchr (p, '\r', size);
+ if (p == NULL || p[1] != '\n') {
+ g_warning ("Invalid special/x-gnome-icon-list data received: "
+ "missing newline character.");
+ if (p == NULL)
+ break;
+ } else {
+ p += 2;
+ }
+
+ size -= p - oldp;
+ oldp = p;
+ }
+}
+
+static void
+drag_data_received_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *data,
+ guint info,
+ guint32 time,
+ gpointer user_data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerDndInfo *dnd_info;
+ GnomeCanvasItem *shadow;
+ double world_x, world_y;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ dnd_info = container->priv->dnd_info;
+ g_return_if_fail (dnd_info->selection_list == NULL);
+
+ switch (info) {
+ case GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST:
+ get_gnome_icon_list_selection (container, data);
+ break;
+ case GNOME_ICON_CONTAINER_DND_URI_LIST:
+ puts ("Bad! URI list!"); /* FIXME */
+ return;
+ }
+
+ shadow = create_selection_shadow (container, dnd_info->selection_list);
+
+ gnome_canvas_item_set (shadow, "x", (gdouble) 0, "y", (gdouble) 0,
+ NULL);
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (widget),
+ x, y, &world_x, &world_y);
+ set_shadow_position (shadow, world_x, world_y);
+
+ gnome_canvas_item_show (shadow);
+
+ if (dnd_info->shadow != NULL)
+ gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
+ dnd_info->shadow = shadow;
+}
+
+static gboolean
+drag_motion_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ dnd_info = GNOME_ICON_CONTAINER (widget)->priv->dnd_info;
+ if (dnd_info->selection_list == NULL)
+ gtk_drag_get_data (widget, context,
+ GPOINTER_TO_INT (context->targets->data),
+ time);
+
+ if (dnd_info->shadow != NULL) {
+ double world_x, world_y;
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (widget),
+ x, y, &world_x, &world_y);
+ gnome_canvas_item_show (dnd_info->shadow);
+ set_shadow_position (dnd_info->shadow, world_x, world_y);
+ }
+
+ gdk_drag_status (context, context->suggested_action, time);
+ return TRUE;
+}
+
+static void
+drag_end_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerDndInfo *dnd_info;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ dnd_info = container->priv->dnd_info;
+
+ destroy_selection_list (dnd_info->selection_list);
+ dnd_info->selection_list = NULL;
+}
+
+static gboolean
+drag_drop_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time,
+ gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerDndInfo *dnd_info;
+ GtkWidget *source_widget;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ dnd_info = container->priv->dnd_info;
+ source_widget = gtk_drag_get_source_widget (context);
+
+ if (source_widget == widget && context->action == GDK_ACTION_MOVE) {
+ double world_x, world_y;
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container),
+ x, y, &world_x, &world_y);
+
+ gnome_icon_container_xlate_selected (container,
+ world_x - dnd_info->start_x,
+ world_y - dnd_info->start_y,
+ TRUE);
+ }
+
+ return FALSE;
+}
+
+static void
+drag_leave_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ guint time,
+ gpointer data)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ dnd_info = GNOME_ICON_CONTAINER (widget)->priv->dnd_info;
+
+ if (dnd_info->shadow != NULL) {
+ gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
+ dnd_info->shadow = NULL;
+ }
+
+ if (dnd_info->selection_list != NULL) {
+ destroy_selection_list (dnd_info->selection_list);
+ dnd_info->selection_list = NULL;
+ }
+}
+
+
+void
+gnome_icon_container_dnd_init (GnomeIconContainer *container,
+ GdkBitmap *stipple)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ dnd_info = g_new (GnomeIconContainerDndInfo, 1);
+
+ dnd_info->target_list = gtk_target_list_new (drag_types,
+ num_drag_types);
+
+ dnd_info->start_x = 0;
+ dnd_info->start_y = 0;
+ dnd_info->selection_list = NULL;
+
+ dnd_info->stipple = gdk_bitmap_ref (stipple);
+
+ dnd_info->shadow = NULL;
+
+ /* Set up the widget as a drag destination. */
+ /* (But not a source, as drags starting from this widget will be
+ implemented by dealing with events manually.) */
+
+ gtk_drag_dest_set (GTK_WIDGET (container),
+ 0,
+ drop_types, num_drop_types,
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ gtk_signal_connect (GTK_OBJECT (container), "drag_data_get",
+ GTK_SIGNAL_FUNC (drag_data_get_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_motion",
+ GTK_SIGNAL_FUNC (drag_motion_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_end",
+ GTK_SIGNAL_FUNC (drag_end_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_data_received",
+ GTK_SIGNAL_FUNC (drag_data_received_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_drop",
+ GTK_SIGNAL_FUNC (drag_drop_cb), NULL);
+ gtk_signal_connect (GTK_OBJECT (container), "drag_leave",
+ GTK_SIGNAL_FUNC (drag_leave_cb), NULL);
+
+ container->priv->dnd_info = dnd_info;
+}
+
+void
+gnome_icon_container_dnd_fini (GnomeIconContainer *container)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ dnd_info = container->priv->dnd_info;
+ g_return_if_fail (dnd_info != NULL);
+
+ gtk_target_list_unref (dnd_info->target_list);
+ destroy_selection_list (dnd_info->selection_list);
+
+ if (dnd_info->shadow != NULL)
+ gtk_object_destroy (GTK_OBJECT (dnd_info->shadow));
+
+ gdk_bitmap_unref (dnd_info->stipple);
+
+ g_free (dnd_info);
+}
+
+
+void
+gnome_icon_container_dnd_begin_drag (GnomeIconContainer *container,
+ GdkDragAction actions,
+ gint button,
+ GdkEventMotion *event)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+ g_return_if_fail (event != NULL);
+
+ dnd_info = container->priv->dnd_info;
+ g_return_if_fail (dnd_info != NULL);
+
+ /* Notice that the event is already in world coordinates, because of
+ the way the canvas handles events! */
+ dnd_info->start_x = event->x;
+ dnd_info->start_y = event->y;
+
+ gtk_drag_begin (GTK_WIDGET (container),
+ dnd_info->target_list,
+ actions,
+ button,
+ (GdkEvent *) event);
+}
+
+void
+gnome_icon_container_dnd_end_drag (GnomeIconContainer *container)
+{
+ GnomeIconContainerDndInfo *dnd_info;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ dnd_info = container->priv->dnd_info;
+ g_return_if_fail (dnd_info != NULL);
+}
diff --git a/libnautilus/gnome-icon-container-dnd.h b/libnautilus/gnome-icon-container-dnd.h
new file mode 100644
index 000000000..da5ab68f0
--- /dev/null
+++ b/libnautilus/gnome-icon-container-dnd.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container-dnd.h - Drag & drop handling for the icon container
+ widget.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _GNOME_ICON_CONTAINER_DND_H
+#define _GNOME_ICON_CONTAINER_DND_H
+
+typedef struct _GnomeIconContainerDndInfo GnomeIconContainerDndInfo;
+typedef enum _GnomeIconContainerDndTargetType GnomeIconContainerDndTargetType;
+
+#include "gnome-icon-container.h"
+
+/* Standard DnD types. */
+enum _GnomeIconContainerDndTargetType {
+ GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST,
+ GNOME_ICON_CONTAINER_DND_URI_LIST,
+ GNOME_ICON_CONTAINER_DND_URL,
+ GNOME_ICON_CONTAINER_DND_NTARGETS
+};
+
+/* DnD target names. */
+#define GNOME_ICON_CONTAINER_DND_GNOME_ICON_LIST_TYPE "special/x-gnome-icon-list"
+#define GNOME_ICON_CONTAINER_DND_URI_LIST_TYPE "text/uri-list"
+#define GNOME_ICON_CONTAINER_DND_URL_TYPE "_NETSCAPE_URL"
+
+
+/* DnD-related information. */
+struct _GnomeIconContainerDndInfo {
+ GtkTargetList *target_list;
+
+ /* Start of the drag, in world coordinates. */
+ gdouble start_x, start_y;
+
+ /* List of DndSelectionItems, representing items being dragged, or NULL
+ if data about them has not been received from the source yet. */
+ GList *selection_list;
+
+ /* Stipple for drawing icon shadows during DnD. */
+ GdkBitmap *stipple;
+
+ /* Shadow for the icons being dragged. */
+ GnomeCanvasItem *shadow;
+};
+
+
+void gnome_icon_container_dnd_init (GnomeIconContainer *container,
+ GdkBitmap *stipple);
+void gnome_icon_container_dnd_fini (GnomeIconContainer *container);
+void gnome_icon_container_dnd_begin_drag (GnomeIconContainer *container,
+ GdkDragAction actions,
+ gint button,
+ GdkEventMotion *event);
+void gnome_icon_container_dnd_end_drag (GnomeIconContainer *container);
+
+#endif /* _GNOME_ICON_CONTAINER_DND_H */
diff --git a/libnautilus/gnome-icon-container-layout.c b/libnautilus/gnome-icon-container-layout.c
new file mode 100644
index 000000000..3b7cfeccb
--- /dev/null
+++ b/libnautilus/gnome-icon-container-layout.c
@@ -0,0 +1,161 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-layout.c
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#include <glib.h>
+
+#include "gnome-icon-container.h"
+#include "gnome-icon-container-private.h"
+
+#include "gnome-icon-container-layout.h"
+
+
+struct _IconLayoutInfo {
+ gchar *text;
+ gint x, y;
+};
+typedef struct _IconLayoutInfo IconLayoutInfo;
+
+
+struct _GnomeIconContainerLayout {
+ GHashTable *name_to_layout;
+};
+
+
+GnomeIconContainerLayout *
+gnome_icon_container_layout_new (void)
+{
+ GnomeIconContainerLayout *new;
+
+ new = g_new (GnomeIconContainerLayout, 1);
+ new->name_to_layout = g_hash_table_new (g_str_hash, g_str_equal);
+
+ return new;
+}
+
+static void
+hash_foreach_destroy (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ IconLayoutInfo *info;
+
+ info = (IconLayoutInfo *) value;
+ g_free (info->text);
+ g_free (info);
+}
+
+void
+gnome_icon_container_layout_free (GnomeIconContainerLayout *layout)
+{
+ g_return_if_fail (layout != NULL);
+
+ g_hash_table_foreach (layout->name_to_layout,
+ hash_foreach_destroy, NULL);
+ g_hash_table_destroy (layout->name_to_layout);
+
+ g_free (layout);
+}
+
+
+void
+gnome_icon_container_layout_add (GnomeIconContainerLayout *layout,
+ const gchar *icon_text,
+ gint x,
+ gint y)
+{
+ IconLayoutInfo *info;
+
+ g_return_if_fail (layout != NULL);
+ g_return_if_fail (icon_text != NULL);
+
+ info = g_new (IconLayoutInfo, 1);
+ info->text = g_strdup (icon_text);
+ info->x = x;
+ info->y = y;
+
+ g_hash_table_insert (layout->name_to_layout, info->text, info);
+}
+
+gboolean
+gnome_icon_container_layout_get_position (const GnomeIconContainerLayout *layout,
+ const gchar *icon_text,
+ gint *x_return,
+ gint *y_return)
+{
+ IconLayoutInfo *info;
+
+ g_return_val_if_fail (layout != NULL, FALSE);
+ g_return_val_if_fail (icon_text != NULL, FALSE);
+
+ info = g_hash_table_lookup (layout->name_to_layout, icon_text);
+ if (info == NULL)
+ return FALSE;
+
+ *x_return = info->x;
+ *y_return = info->y;
+
+ return TRUE;
+}
+
+
+struct _ForeachData {
+ GnomeIconContainerLayout *layout;
+ GnomeIconContainerLayoutForeachFunc callback;
+ gpointer callback_data;
+};
+typedef struct _ForeachData ForeachData;
+
+static void
+foreach_helper (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ IconLayoutInfo *info;
+ ForeachData *data;
+
+ info = (IconLayoutInfo *) value;
+ data = (ForeachData *) user_data;
+
+ (* data->callback) (data->layout, info->text, info->x, info->y,
+ data->callback_data);
+}
+
+void
+gnome_icon_container_layout_foreach (GnomeIconContainerLayout *layout,
+ GnomeIconContainerLayoutForeachFunc callback,
+ gpointer callback_data)
+{
+ ForeachData *data;
+
+ g_return_if_fail (layout != NULL);
+ g_return_if_fail (callback != NULL);
+
+ data = g_new (ForeachData, 1);
+ data->layout = layout;
+ data->callback = callback;
+ data->callback_data = callback_data;
+
+ g_hash_table_foreach (layout->name_to_layout, foreach_helper, data);
+
+ g_free (data);
+}
diff --git a/libnautilus/gnome-icon-container-layout.h b/libnautilus/gnome-icon-container-layout.h
new file mode 100644
index 000000000..6d1ebb50c
--- /dev/null
+++ b/libnautilus/gnome-icon-container-layout.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-layout.h
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _GNOME_ICON_CONTAINER_LAYOUT_H
+#define _GNOME_ICON_CONTAINER_LAYOUT_H
+
+#include <glib.h>
+
+typedef struct _GnomeIconContainerLayout GnomeIconContainerLayout;
+
+typedef void (* GnomeIconContainerLayoutForeachFunc)
+ (const GnomeIconContainerLayout *layout,
+ const gchar *text,
+ gint x, gint y,
+ gpointer callback_data);
+
+#include "gnome-icon-container.h"
+#include "gnome-icon-container-private.h"
+
+
+GnomeIconContainerLayout *
+ gnome_icon_container_layout_new (void);
+void gnome_icon_container_layout_free (GnomeIconContainerLayout *layout);
+void gnome_icon_container_layout_add (GnomeIconContainerLayout *layout,
+ const gchar *icon,
+ gint x,
+ gint y);
+gboolean
+ gnome_icon_container_layout_get_position (const GnomeIconContainerLayout *layout,
+ const gchar *icon,
+ gint *x_return,
+ gint *y_return);
+
+void gnome_icon_container_layout_foreach (GnomeIconContainerLayout *layout,
+ GnomeIconContainerLayoutForeachFunc callback,
+ gpointer callback_data);
+
+#endif /* _GNOME_ICON_CONTAINER_LAYOUT_H */
diff --git a/libnautilus/gnome-icon-container-private.h b/libnautilus/gnome-icon-container-private.h
new file mode 100644
index 000000000..e681f43c6
--- /dev/null
+++ b/libnautilus/gnome-icon-container-private.h
@@ -0,0 +1,218 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container-private.h
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _GNOME_ICON_CONTAINER_PRIVATE_H
+#define _GNOME_ICON_CONTAINER_PRIVATE_H
+
+#include "gnome-icon-container.h"
+#include "gnome-icon-container-dnd.h"
+
+/* An Icon. */
+
+struct _GnomeIconContainerIcon {
+ /* Group containing the text and the image. */
+ GnomeCanvasGroup *item; /* FIXME wrong name. */
+
+ /* The image for the icon. Using a generic item makes it
+ possible for us to use any fancy canvas element. */
+ GnomeCanvasItem *image_item;
+
+ /* The text for the icon. */
+ GnomeIconTextItem *text_item;
+
+ /* Text for the icon. */
+ gchar *text;
+
+ /* X/Y coordinates and size. We could use the GnomeCanvasItem
+ functions, but this is a lot faster. */
+ gdouble x, y;
+ guint width, height; /* FIXME we could actually do without this if
+ we assume the size is always given by
+ GnomeIconContainer.cell_width*/
+
+ /* Whether this item is selected (i.e. highlighted) for operation. */
+ gboolean is_selected : 1;
+
+ /* Whether this item is selected for keyboard navigation. */
+ gboolean is_current : 1;
+
+ /* Whether this item has been repositioned during layout already. */
+ gboolean layout_done : 1;
+
+ /* Whether this item was selected before rubberbanding. */
+ gboolean was_selected_before_rubberband : 1;
+
+ gpointer data;
+};
+typedef struct _GnomeIconContainerIcon GnomeIconContainerIcon;
+
+
+#define INITIAL_GRID_WIDTH 64
+#define INITIAL_GRID_HEIGHT 64
+struct _GnomeIconContainerIconGrid {
+ /* Size of the grid. */
+ guint width, height;
+
+ /* This is the width that we can actually use for finding an empty
+ position. */
+ guint visible_width;
+
+ /* Array of grid elements. */
+ GList **elems;
+
+ /* Size of the allocated array. */
+ guint alloc_width, alloc_height;
+
+ /* Position of the first free cell (used to speed up progressive
+ updates). If negative, there is no free cell. */
+ gint first_free_x, first_free_y;
+};
+typedef struct _GnomeIconContainerIconGrid GnomeIconContainerIconGrid;
+
+
+/* Private GnomeIconContainer members. */
+
+struct _GnomeIconContainerRubberbandInfo {
+ gboolean active : 1;
+
+ gdouble start_x, start_y;
+
+ GnomeCanvasItem *selection_rectangle;
+ guint timer_tag;
+
+ guint prev_x, prev_y;
+ guint prev_x1, prev_y1;
+ guint prev_x2, prev_y2;
+};
+typedef struct _GnomeIconContainerRubberbandInfo GnomeIconContainerRubberbandInfo;
+
+struct _GnomeIconContainerPrivate {
+ /* Base URI for Drag & Drop. */
+ gchar *base_uri;
+
+ /* Browser mode setting. */
+ gboolean browser_mode : 1;
+
+ /* Current icon mode (index into `icon_mode_info[]' -- see
+ `gnome-icon-container.c'). */
+ GnomeIconContainerIconMode icon_mode;
+
+ /* Size of the container. */
+ guint width, height;
+
+ /* List of icons. */
+ GList *icons;
+
+ /* Total number of icons. */
+ guint num_icons;
+
+ /* The grid. */
+ GnomeIconContainerIconGrid *grid;
+
+ /* FIXME: This is *ugly*, but more efficient (both memory- and
+ speed-wise) than using gtk_object_{set,get}_data() for all the
+ icon items. */
+ GHashTable *canvas_item_to_icon;
+
+ /* Rectangle that shows that a certain icon is selected. */
+ GnomeCanvasItem *kbd_navigation_rectangle;
+
+ /* Current icon for keyboard navigation. */
+ GnomeIconContainerIcon *kbd_current;
+
+ /* Rubberbanding status. */
+ GnomeIconContainerRubberbandInfo rubberband_info;
+
+ /* Timeout used to make a selected icon fully visible after a short
+ period of time. (The timeout is needed to make sure
+ double-clicking still works.) */
+ gint kbd_icon_visibility_timer_tag;
+
+ /* Position of the pointer during the last click. */
+ gint drag_x, drag_y;
+
+ /* Button currently pressed, possibly for dragging. */
+ guint drag_button;
+
+ /* Icon on which the click happened. */
+ GnomeIconContainerIcon *drag_icon;
+
+ /* Whether we are actually performing a dragging action. */
+ gboolean doing_drag;
+
+ /* Drag offset. */
+ gint drag_x_offset, drag_y_offset;
+
+ /* Idle ID. */
+ guint idle_id;
+
+ /* Timeout for selection in browser mode. */
+ gint browser_mode_selection_timer_tag;
+
+ /* Icon to be selected at timeout in browser mode. */
+ GnomeIconContainerIcon *browser_mode_selection_icon;
+
+ /* DnD info. */
+ GnomeIconContainerDndInfo *dnd_info;
+};
+
+
+/* Definition of the available icon container modes. */
+struct _GnomeIconContainerIconModeInfo {
+ guint icon_width;
+ guint icon_height;
+
+ guint cell_width;
+ guint cell_height;
+
+ guint cell_spacing;
+
+ guint icon_xoffset;
+ guint icon_yoffset;
+};
+typedef struct _GnomeIconContainerIconModeInfo GnomeIconContainerIconModeInfo;
+
+extern GnomeIconContainerIconModeInfo gnome_icon_container_icon_mode_info[];
+
+#define GNOME_ICON_CONTAINER_ICON_WIDTH(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].icon_width
+
+#define GNOME_ICON_CONTAINER_ICON_HEIGHT(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].icon_height
+
+#define GNOME_ICON_CONTAINER_CELL_WIDTH(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].cell_width
+
+#define GNOME_ICON_CONTAINER_CELL_HEIGHT(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].cell_height
+
+#define GNOME_ICON_CONTAINER_CELL_SPACING(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].cell_spacing
+
+#define GNOME_ICON_CONTAINER_ICON_XOFFSET(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].icon_xoffset
+
+#define GNOME_ICON_CONTAINER_ICON_YOFFSET(container) \
+ gnome_icon_container_icon_mode_info[container->priv->icon_mode].icon_yoffset
+
+#endif /* _GNOME_ICON_CONTAINER_PRIVATE_H */
diff --git a/libnautilus/gnome-icon-container.c b/libnautilus/gnome-icon-container.c
new file mode 100644
index 000000000..f158fcc9b
--- /dev/null
+++ b/libnautilus/gnome-icon-container.c
@@ -0,0 +1,3020 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container.c - Icon container widget.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gnome.h>
+
+#include "gnome-icon-container-private.h"
+#include "gnome-icon-container-dnd.h"
+
+
+static GnomeCanvasClass *parent_class;
+
+/* Interval for updating the rubberband selection, in milliseconds. */
+#define RUBBERBAND_TIMEOUT_INTERVAL 10
+
+/* Timeout for making the icon currently selected for keyboard operation
+ visible. FIXME: This *must* be higher than the double-click time in GDK,
+ but there is no way to access its value from outside. */
+#define KBD_ICON_VISIBILITY_TIMEOUT 300
+
+/* Timeout for selecting an icon in "browser mode" (i.e. by just placing the
+ pointer over the icon, without pressing any button). */
+#define BROWSER_MODE_SELECTION_TIMEOUT 800
+
+
+/* WARNING: Keep this in sync with the `GnomeIconContainerIconMode' enum in
+ `gnome-icon-container.h'. */
+GnomeIconContainerIconModeInfo gnome_icon_container_icon_mode_info[] = {
+ { 48, 48, 80, 80, 4, 44, 28 }, /* GNOME_ICON_CONTAINER_NORMAL_ICONS */
+ { 24, 24, 100, 40, 4, 16, 16 } /* GNOME_ICON_CONTAINER_SMALL_ICONS */
+};
+
+#define NUM_ICON_MODES (sizeof (gnome_icon_container_icon_mode_info) \
+ / sizeof (*gnome_icon_container_icon_mode_info))
+
+
+/* The GnomeIconContainer signals. */
+enum _GnomeIconContainerSignalNumber {
+ SELECTION_CHANGED,
+ BUTTON_PRESS,
+ ACTIVATE,
+ CONTEXT_CLICK,
+ LAST_SIGNAL
+};
+typedef enum _GnomeIconContainerSignalNumber GnomeIconContainerSignalNumber;
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/* Bitmap for stippled selection rectangles. */
+static GdkBitmap *stipple;
+static char stipple_bits[] = { 0x02, 0x01 };
+
+
+/* Functions dealing with GnomeIconContainerIcons. */
+
+static void
+icon_destroy (GnomeIconContainerIcon *icon)
+{
+ gtk_object_destroy (GTK_OBJECT (icon->item));
+}
+
+static void
+icon_configure (GnomeIconContainerIcon *icon,
+ GnomeIconContainer *container)
+{
+ switch (container->priv->icon_mode) {
+ case GNOME_ICON_CONTAINER_NORMAL_ICONS:
+ gnome_icon_text_item_configure
+ (icon->text_item,
+ GNOME_ICON_CONTAINER_CELL_SPACING (container),
+ GNOME_ICON_CONTAINER_ICON_HEIGHT (container),
+ (GNOME_ICON_CONTAINER_CELL_WIDTH (container)
+ - 2 * GNOME_ICON_CONTAINER_CELL_SPACING (container)),
+ NULL,
+ icon->text,
+ TRUE,
+ TRUE);
+ break;
+ case GNOME_ICON_CONTAINER_SMALL_ICONS:
+ gnome_icon_text_item_configure
+ (icon->text_item,
+ (GNOME_ICON_CONTAINER_ICON_WIDTH (container)
+ + GNOME_ICON_CONTAINER_CELL_SPACING (container)),
+ GNOME_ICON_CONTAINER_CELL_HEIGHT (container) / 2,
+ (GNOME_ICON_CONTAINER_CELL_WIDTH (container)
+ - 2 * GNOME_ICON_CONTAINER_CELL_SPACING (container)
+ - GNOME_ICON_CONTAINER_ICON_WIDTH (container)),
+ NULL,
+ icon->text,
+ TRUE,
+ TRUE);
+ break;
+ default:
+ g_warning ("Unknown icon mode %d.", container->priv->icon_mode);
+ }
+
+ gnome_canvas_item_set
+ (GNOME_CANVAS_ITEM (icon->image_item),
+ "width", (gdouble) GNOME_ICON_CONTAINER_ICON_WIDTH (container),
+ "height", (gdouble) GNOME_ICON_CONTAINER_ICON_HEIGHT (container),
+ NULL);
+}
+
+static GnomeIconContainerIcon *
+icon_new (GnomeIconContainer *container,
+ const gchar *text,
+ gpointer data)
+{
+ GnomeCanvas *canvas;
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *new;
+
+ canvas = GNOME_CANVAS (container);
+ priv = container->priv;
+
+ new = g_new (GnomeIconContainerIcon, 1);
+
+ new->is_selected = FALSE;
+ new->is_current = FALSE;
+ new->layout_done = TRUE;
+ new->was_selected_before_rubberband = FALSE;
+
+ new->data = data;
+ new->text = g_strdup (text); /* FIXME */
+
+ new->item = GNOME_CANVAS_GROUP (gnome_canvas_item_new
+ (GNOME_CANVAS_GROUP (canvas->root),
+ gnome_canvas_group_get_type (),
+ NULL));
+
+ new->image_item = NULL;
+
+ new->text_item
+ = GNOME_ICON_TEXT_ITEM (gnome_canvas_item_new
+ (new->item,
+ gnome_icon_text_item_get_type (),
+ NULL));
+
+ new->width = GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ new->height = GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ return new;
+}
+
+static GnomeIconContainerIcon *
+icon_new_imlib (GnomeIconContainer *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *new;
+
+ priv = container->priv;
+
+ new = icon_new (container, text, data);
+
+ new->image_item
+ = gnome_canvas_item_new (new->item,
+ gnome_canvas_image_get_type (),
+ "image", image,
+ "x", (gdouble) 0,
+ "y", (gdouble) 0,
+ NULL);
+
+ icon_configure (new, container);
+
+ gnome_canvas_item_set (GNOME_CANVAS_ITEM (new->item),
+ "x", (gdouble) 0,
+ "y", (gdouble) 0,
+ NULL);
+
+ return new;
+}
+
+static void
+icon_position (GnomeIconContainerIcon *icon,
+ GnomeIconContainer *container,
+ gdouble x, gdouble y)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ icon->x = x;
+ icon->y = y;
+
+ /* ??? Canvas bug ??? It should be enough to do this once in
+ `icon-configure()', but it does not work. */
+
+ switch (container->priv->icon_mode) {
+ case GNOME_ICON_CONTAINER_NORMAL_ICONS:
+ gnome_icon_text_item_setxy
+ (icon->text_item,
+ GNOME_ICON_CONTAINER_CELL_SPACING (container),
+ (GNOME_ICON_CONTAINER_ICON_HEIGHT (container)
+ + GNOME_ICON_CONTAINER_CELL_SPACING (container) + 2));
+ break;
+ case GNOME_ICON_CONTAINER_SMALL_ICONS:
+ gnome_icon_text_item_setxy
+ (icon->text_item,
+ (GNOME_ICON_CONTAINER_ICON_WIDTH (container)
+ + GNOME_ICON_CONTAINER_CELL_SPACING (container)),
+ GNOME_ICON_CONTAINER_CELL_SPACING (container));
+ break;
+ default:
+ g_warning ("Unknown icon mode %d.", container->priv->icon_mode);
+ }
+
+ gnome_canvas_item_set
+ (icon->image_item,
+ "x", (gdouble) GNOME_ICON_CONTAINER_ICON_XOFFSET (container),
+ "y", (gdouble) GNOME_ICON_CONTAINER_ICON_YOFFSET (container),
+ NULL);
+
+ gnome_canvas_item_set (GNOME_CANVAS_ITEM (icon->item),
+ "x", (gdouble) icon->x,
+ "y", (gdouble) icon->y,
+ NULL);
+}
+
+static void
+icon_raise (GnomeIconContainerIcon *icon)
+{
+ gnome_canvas_item_raise_to_top (GNOME_CANVAS_ITEM (icon->item));
+}
+
+static void
+icon_show (GnomeIconContainerIcon *icon)
+{
+ gnome_canvas_item_show (GNOME_CANVAS_ITEM (icon->item));
+}
+
+static void
+icon_hide (GnomeIconContainerIcon *icon)
+{
+ gnome_canvas_item_hide (GNOME_CANVAS_ITEM (icon->item));
+}
+
+static void
+icon_select (GnomeIconContainerIcon *icon,
+ gboolean sel)
+{
+ gboolean was_selected;
+
+ /* FIXME: We want the icon image to appear as selected too. Maybe
+ this can be done with a new custom CanvasImage-like item providing
+ this functionality? */
+
+ was_selected = icon->is_selected;
+ icon->is_selected = sel;
+
+ gnome_icon_text_item_select (icon->text_item, sel);
+}
+
+static gboolean
+icon_toggle_selection (GnomeIconContainerIcon *icon)
+{
+ if (icon->is_selected) {
+ icon_select (icon, FALSE);
+ return TRUE;
+ } else {
+ icon_select (icon, TRUE);
+ return FALSE;
+ }
+}
+
+static gboolean
+icon_is_in_region (GnomeIconContainerIcon *icon,
+ gint x1, gint y1,
+ gint x2, gint y2)
+{
+ gint icon_x2, icon_y2;
+
+ icon_x2 = icon->x + icon->width;
+ icon_y2 = icon->y + icon->height;
+
+ if (x1 == x2 && y1 == y2)
+ return FALSE;
+
+ if (x1 < icon_x2 && x2 >= icon->x && y1 < icon_y2 && y2 >= icon->y)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void
+icon_get_text_bounding_box (GnomeIconContainerIcon *icon,
+ guint *x1_return, guint *y1_return,
+ guint *x2_return, guint *y2_return)
+{
+ double x1, y1, x2, y2;
+
+ gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (icon->text_item),
+ &x1, &y1, &x2, &y2);
+
+ *x1_return = icon->x + x1;
+ *y1_return = icon->y + y1;
+ *x2_return = icon->x + x2;
+ *y2_return = icon->y + y2;
+}
+
+static void
+icon_get_bounding_box (GnomeIconContainerIcon *icon,
+ guint *x1_return, guint *y1_return,
+ guint *x2_return, guint *y2_return)
+{
+ double x1, y1, x2, y2;
+
+ gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (icon->item),
+ &x1, &y1, &x2, &y2);
+
+ *x1_return = x1;
+ *y1_return = y1;
+ *x2_return = x2;
+ *y2_return = y2;
+}
+
+
+/* Functions for dealing with IconGrids. */
+
+static GnomeIconContainerIconGrid *
+icon_grid_new (void)
+{
+ GnomeIconContainerIconGrid *new;
+
+ new = g_new (GnomeIconContainerIconGrid, 1);
+
+ new->width = new->height = 0;
+ new->visible_width = 0;
+ new->alloc_width = new->alloc_height = 0;
+
+ new->elems = NULL;
+
+ new->first_free_x = -1;
+ new->first_free_y = -1;
+
+ return new;
+}
+
+static void
+icon_grid_clear (GnomeIconContainerIconGrid *grid)
+{
+ GList **p;
+ guint i, j;
+
+ p = grid->elems;
+ for (j = 0; j < grid->height; j++) {
+ for (i = 0; i < grid->width; i++) {
+ if (p[i] != NULL) {
+ g_list_free (p[i]);
+ p[i] = NULL;
+ }
+ }
+
+ p += grid->alloc_width;
+ }
+
+ grid->first_free_x = 0;
+ grid->first_free_y = 0;
+}
+
+static void
+icon_grid_destroy (GnomeIconContainerIconGrid *grid)
+{
+ icon_grid_clear (grid);
+ g_free (grid->elems);
+ g_free (grid);
+}
+
+inline static GList **
+icon_grid_get_element_ptr (GnomeIconContainerIconGrid *grid,
+ guint x, guint y)
+{
+ return &grid->elems[y * grid->alloc_width + x];
+}
+
+inline static GList *
+icon_grid_get_element (GnomeIconContainerIconGrid *grid,
+ guint x, guint y)
+{
+ return *icon_grid_get_element_ptr (grid, x, y);
+}
+
+/* This is admittedly a bit lame.
+
+ Instead of re-allocating the grid from scratch and copying the values, we
+ should just link grid chunks horizontally and vertically in lists;
+ i.e. use a hybrid list/array representation. */
+static void
+icon_grid_resize_allocation (GnomeIconContainerIconGrid *grid,
+ guint new_alloc_width,
+ guint new_alloc_height)
+{
+ GList **new_elems;
+ guint i, j;
+ guint new_alloc_size;
+
+ if (new_alloc_width == 0 || new_alloc_height == 0) {
+ g_free (grid->elems);
+ grid->elems = NULL;
+ grid->width = grid->height = 0;
+ grid->alloc_width = new_alloc_width;
+ grid->alloc_height = new_alloc_height;
+ return;
+ }
+
+ new_alloc_size = new_alloc_width * new_alloc_height;
+ new_elems = g_new (GList *, new_alloc_size);
+
+ if (grid->elems == NULL || grid->width == 0 || grid->height == 0) {
+ memset (new_elems, 0, sizeof (*new_elems) * new_alloc_size);
+ } else {
+ GList **sp, **dp;
+ guint copy_width, copy_height;
+
+ /* Copy existing elements into the new array. */
+
+ sp = grid->elems;
+ dp = new_elems;
+ copy_width = MIN (grid->width, new_alloc_width);
+ copy_height = MIN (grid->height, new_alloc_height);
+
+ for (i = 0; i < copy_height; i++) {
+ for (j = 0; j < copy_width; j++)
+ dp[j] = sp[j];
+
+ for (j = copy_width; j < new_alloc_width; j++)
+ dp[j] = NULL;
+
+ for (j = copy_width; j < grid->width; j++)
+ g_list_free (sp[j]);
+
+ sp += grid->alloc_width;
+ dp += new_alloc_width;
+ }
+
+ /* If there are other lines left, zero them as well. */
+
+ if (i < new_alloc_height) {
+ guint elems_left;
+
+ elems_left = new_alloc_size - (dp - new_elems);
+ memset (dp, 0, sizeof (*new_elems) * elems_left);
+ }
+ }
+
+ g_free (grid->elems);
+ grid->elems = new_elems;
+
+ grid->alloc_width = new_alloc_width;
+ grid->alloc_height = new_alloc_height;
+}
+
+static GnomeIconContainerIconGrid *
+icon_grid_new_same_alloc (GnomeIconContainerIconGrid *grid)
+{
+ GnomeIconContainerIconGrid *new_grid;
+
+ new_grid = icon_grid_new ();
+ icon_grid_resize_allocation (new_grid,
+ grid->alloc_width, grid->alloc_height);
+
+ return new_grid;
+}
+
+static void
+icon_grid_update_first_free_forward (GnomeIconContainerIconGrid *grid)
+{
+ GList **p;
+ guint start_x, start_y;
+ guint x, y;
+
+ if (grid->first_free_x == -1) {
+ start_x = start_y = 0;
+ p = grid->elems;
+ } else {
+ start_x = grid->first_free_x;
+ start_y = grid->first_free_y;
+ p = icon_grid_get_element_ptr (grid, start_x, start_y);
+ }
+
+ x = start_x;
+ y = start_y;
+ while (y < grid->height) {
+ if (*p == NULL) {
+ grid->first_free_x = x;
+ grid->first_free_y = y;
+ return;
+ }
+
+ x++, p++;
+
+ if (x >= grid->visible_width) {
+ x = 0;
+ y++;
+ p += grid->alloc_width - grid->visible_width;
+ }
+ }
+
+ /* No free cell found. */
+
+ grid->first_free_x = -1;
+ grid->first_free_y = -1;
+}
+
+static void
+icon_grid_set_visible_width (GnomeIconContainerIconGrid *grid,
+ guint visible_width)
+{
+ if (visible_width > grid->visible_width
+ && grid->height > 0
+ && grid->first_free_x == -1) {
+ grid->first_free_x = visible_width;
+ grid->first_free_y = 0;
+ } else if (grid->first_free_x >= visible_width) {
+ if (grid->first_free_y == grid->height - 1) {
+ grid->first_free_x = -1;
+ grid->first_free_y = -1;
+ } else {
+ grid->first_free_x = 0;
+ grid->first_free_y++;
+ icon_grid_update_first_free_forward (grid);
+ }
+ }
+
+ grid->visible_width = visible_width;
+}
+
+static void
+icon_grid_resize (GnomeIconContainerIconGrid *grid,
+ guint width, guint height)
+{
+ guint new_alloc_width, new_alloc_height;
+
+ if (width > grid->alloc_width || height > grid->alloc_height) {
+ if (grid->alloc_width > 0)
+ new_alloc_width = grid->alloc_width;
+ else
+ new_alloc_width = INITIAL_GRID_WIDTH;
+ while (new_alloc_width < width)
+ new_alloc_width *= 2;
+
+ if (grid->alloc_height > 0)
+ new_alloc_height = grid->alloc_height;
+ else
+ new_alloc_height = INITIAL_GRID_HEIGHT;
+ while (new_alloc_height < height)
+ new_alloc_height *= 2;
+
+ icon_grid_resize_allocation (grid, new_alloc_width,
+ new_alloc_height);
+ }
+
+ grid->width = width;
+ grid->height = height;
+
+ if (grid->visible_width > grid->width)
+ icon_grid_set_visible_width (grid, grid->width);
+}
+
+static void
+icon_grid_maybe_resize (GnomeIconContainerIconGrid *grid,
+ guint x, guint y)
+{
+ guint new_width, new_height;
+
+ if (x < grid->width && y < grid->height)
+ return;
+
+ if (x >= grid->width)
+ new_width = x + 1;
+ else
+ new_width = grid->width;
+
+ if (y >= grid->height)
+ new_height = y + 1;
+ else
+ new_height = grid->height;
+
+ icon_grid_resize (grid, new_width, new_height);
+}
+
+static void
+icon_grid_add (GnomeIconContainerIconGrid *grid,
+ GnomeIconContainerIcon *icon,
+ guint x, guint y)
+{
+ GList **elem_ptr;
+
+ icon_grid_maybe_resize (grid, x, y);
+
+ elem_ptr = icon_grid_get_element_ptr (grid, x, y);
+ *elem_ptr = g_list_prepend (*elem_ptr, icon);
+
+ if (x == grid->first_free_x && y == grid->first_free_y)
+ icon_grid_update_first_free_forward (grid);
+}
+
+static void
+icon_grid_remove (GnomeIconContainerIconGrid *grid,
+ GnomeIconContainerIcon *icon,
+ guint x, guint y)
+{
+ GList **elem_ptr;
+
+ elem_ptr = icon_grid_get_element_ptr (grid, x, y);
+
+ g_return_if_fail (*elem_ptr != NULL);
+
+ *elem_ptr = g_list_remove (*elem_ptr, icon);
+
+ if (*elem_ptr == NULL) {
+ if ((grid->first_free_x == -1 && grid->first_free_y == -1)
+ || grid->first_free_y > y
+ || (grid->first_free_y == y && grid->first_free_x > x)) {
+ grid->first_free_x = x;
+ grid->first_free_y = y;
+ }
+ }
+}
+
+static void
+icon_grid_add_auto (GnomeIconContainerIconGrid *grid,
+ GnomeIconContainerIcon *icon,
+ guint *x_return, guint *y_return)
+{
+ GList **empty_elem_ptr;
+
+ if (grid->first_free_x < 0 || grid->first_free_y < 0
+ || grid->height == 0 || grid->width == 0) {
+ /* No empty element: add a row. */
+ icon_grid_resize (grid, MAX (grid->width, 1), grid->height + 1);
+ grid->first_free_x = 0;
+ grid->first_free_y = grid->height - 1;
+ }
+
+ empty_elem_ptr = icon_grid_get_element_ptr (grid,
+ grid->first_free_x,
+ grid->first_free_y);
+
+ *empty_elem_ptr = g_list_prepend (*empty_elem_ptr, icon);
+
+ if (x_return != NULL)
+ *x_return = grid->first_free_x;
+ if (y_return != NULL)
+ *y_return = grid->first_free_y;
+
+ icon_grid_update_first_free_forward (grid);
+}
+
+static gint
+icon_grid_cell_compare_by_x (gconstpointer ap,
+ gconstpointer bp)
+{
+ GnomeIconContainerIcon *a, *b;
+
+ a = (GnomeIconContainerIcon *) ap;
+ b = (GnomeIconContainerIcon *) bp;
+
+ return (gint) a->x - b->x;
+}
+
+
+static void
+world_to_grid (GnomeIconContainer *container,
+ gint world_x, gint world_y,
+ guint *grid_x_return, guint *grid_y_return)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ if (grid_x_return != NULL) {
+ if (world_x < 0)
+ *grid_x_return = 0;
+ else
+ *grid_x_return = world_x / GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ if (grid_y_return != NULL) {
+ if (world_y < 0)
+ *grid_y_return = 0;
+ else
+ *grid_y_return = world_y / GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+}
+
+static void
+grid_to_world (GnomeIconContainer *container,
+ guint grid_x, guint grid_y,
+ gint *world_x_return, gint *world_y_return)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ if (world_x_return != NULL)
+ *world_x_return
+ = grid_x * GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+
+ if (world_y_return != NULL)
+ *world_y_return
+ = grid_y * GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+}
+
+
+/* Utility functions for GnomeIconContainer. */
+
+static void
+scroll (GnomeIconContainer *container,
+ gint delta_x, gint delta_y)
+{
+ GnomeIconContainerPrivate *priv;
+ GtkAdjustment *hadj, *vadj;
+ GtkAllocation *allocation;
+ gfloat vnew, hnew;
+ gfloat hmax, vmax;
+
+ priv = container->priv;
+
+ hadj = GTK_LAYOUT (container)->hadjustment;
+ vadj = GTK_LAYOUT (container)->vadjustment;
+
+ allocation = &GTK_WIDGET (container)->allocation;
+
+ if (container->priv->width > allocation->width)
+ hmax = (gfloat) (container->priv->width - allocation->width);
+ else
+ hmax = 0.0;
+
+ if (container->priv->height > allocation->height)
+ vmax = (gfloat) (container->priv->height - allocation->height);
+ else
+ vmax = 0.0;
+
+ hnew = CLAMP (hadj->value + (gfloat) delta_x, 0.0, hmax);
+ vnew = CLAMP (vadj->value + (gfloat) delta_y, 0.0, vmax);
+
+ if (hnew != hadj->value) {
+ hadj->value = hnew;
+ gtk_signal_emit_by_name (GTK_OBJECT (hadj), "value_changed");
+ }
+ if (vnew != vadj->value) {
+ vadj->value = vnew;
+ gtk_signal_emit_by_name (GTK_OBJECT (vadj), "value_changed");
+ }
+}
+
+static void
+make_icon_visible (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon)
+{
+ GnomeIconContainerPrivate *priv;
+ GtkAllocation *allocation;
+ GtkAdjustment *hadj, *vadj;
+ gint x1, y1, x2, y2;
+
+ priv = container->priv;
+ allocation = &GTK_WIDGET (container)->allocation;
+
+ if (priv->height < allocation->height
+ && priv->width < allocation->width)
+ return;
+
+ hadj = GTK_LAYOUT (container)->hadjustment;
+ vadj = GTK_LAYOUT (container)->vadjustment;
+
+ icon_get_bounding_box (icon, &x1, &y1, &x2, &y2);
+
+ if (y1 < vadj->value)
+ gtk_adjustment_set_value (vadj, y1);
+ else if (y2 > vadj->value + allocation->height)
+ gtk_adjustment_set_value (vadj, y2 - allocation->height);
+
+ if (x1 < hadj->value)
+ gtk_adjustment_set_value (hadj, x1);
+ else if (x2 > hadj->value + allocation->width)
+ gtk_adjustment_set_value (hadj, x2 - allocation->width);
+}
+
+static gint
+kbd_icon_visibility_timeout_cb (gpointer data)
+{
+ GnomeIconContainer *container;
+
+ GDK_THREADS_ENTER ();
+
+ container = GNOME_ICON_CONTAINER (data);
+
+ if (container->priv->kbd_current != NULL)
+ make_icon_visible (container, container->priv->kbd_current);
+ container->priv->kbd_icon_visibility_timer_tag = -1;
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static void
+unschedule_kbd_icon_visibility (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ if (priv->kbd_icon_visibility_timer_tag != -1)
+ gtk_timeout_remove (priv->kbd_icon_visibility_timer_tag);
+}
+
+static void
+schedule_kbd_icon_visibility (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ unschedule_kbd_icon_visibility (container);
+
+ priv->kbd_icon_visibility_timer_tag
+ = gtk_timeout_add (KBD_ICON_VISIBILITY_TIMEOUT,
+ kbd_icon_visibility_timeout_cb,
+ container);
+}
+
+static void
+prepare_for_layout (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+
+ priv = container->priv;
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ icon->layout_done = FALSE;
+ }
+}
+
+/* Line up icons belonging to the grid line pointed by `p'. */
+static void
+line_up (GnomeIconContainer *container,
+ GList **p)
+{
+ GnomeIconContainerIconGrid *grid;
+ GList **temp_line;
+ guint i;
+
+ grid = container->priv->grid;
+
+ temp_line = alloca (grid->width * sizeof (*temp_line));
+ for (i = 0; i < grid->width; i++)
+ temp_line[i] = p[i];
+}
+
+/* Find the "first" icon (in left-to-right, top-to-bottom order) in
+ `container'. */
+static GnomeIconContainerIcon *
+find_first (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *first;
+ GList **p;
+ guint i, j;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (grid->width == 0 || grid->height == 0)
+ return NULL;
+
+ first = NULL;
+ p = grid->elems;
+ for (i = 0; i < grid->height; i++) {
+ for (j = 0; j < grid->width; j++) {
+ GList *q;
+
+ for (q = p[j]; q != NULL; q = q->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = q->data;
+ if (first == NULL
+ || icon->y < first->y
+ || (icon->y == first->y
+ && icon->x < first->x))
+ first = icon;
+ }
+ }
+
+ p += grid->alloc_width;
+ }
+
+ return first;
+}
+
+static GnomeIconContainerIcon *
+find_last (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *last;
+ GList **p;
+ gint i, j;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ last = NULL;
+
+ if (grid->height == 0 || grid->width == 0)
+ return NULL;
+
+ p = icon_grid_get_element_ptr (grid, 0, grid->height - 1);
+
+ for (i = grid->height - 1; i >= 0; i--) {
+ for (j = grid->width - 1; j >= 0; j--) {
+ GList *q;
+
+ for (q = p[j]; q != NULL; q = q->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = q->data;
+ if (last == NULL
+ || icon->y > last->y
+ || (icon->y == last->y
+ && icon->x > last->x))
+ last = icon;
+ }
+ }
+
+ p -= grid->alloc_width;
+ }
+
+ return last;
+}
+
+/* Set `icon' as the icon currently selected for keyboard operations. */
+static void
+set_kbd_current (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ gboolean schedule_visibility)
+{
+ GnomeIconContainerPrivate *priv;
+ gint x1, y1, x2, y2;
+
+ priv = container->priv;
+
+ priv->kbd_current = icon;
+
+ icon_get_text_bounding_box (icon, &x1, &y1, &x2, &y2);
+
+ gnome_canvas_item_set (priv->kbd_navigation_rectangle,
+ "x1", (gdouble) x1 - 1,
+ "y1", (gdouble) y1 - 1,
+ "x2", (gdouble) x2,
+ "y2", (gdouble) y2,
+ NULL);
+ gnome_canvas_item_show (priv->kbd_navigation_rectangle);
+
+ icon_raise (icon);
+ gnome_canvas_item_raise_to_top (priv->kbd_navigation_rectangle);
+
+ if (schedule_visibility)
+ schedule_kbd_icon_visibility (container);
+ else
+ unschedule_kbd_icon_visibility (container);
+}
+
+static void
+unset_kbd_current (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ priv->kbd_current = NULL;
+ gnome_canvas_item_hide (priv->kbd_navigation_rectangle);
+
+ unschedule_kbd_icon_visibility (container);
+}
+
+
+/* Idle operation handler. */
+
+static void
+set_scroll_region (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GtkAllocation *allocation;
+ GtkAdjustment *vadj, *hadj;
+ gdouble x1, y1, x2, y2;
+ guint scroll_width, scroll_height;
+
+ priv = container->priv;
+ grid = priv->grid;
+ allocation = &(GTK_WIDGET (container)->allocation);
+ hadj = GTK_LAYOUT (container)->hadjustment;
+ vadj = GTK_LAYOUT (container)->vadjustment;
+
+ /* FIXME: We can do this more efficiently. */
+ gnome_canvas_item_get_bounds (GNOME_CANVAS (container)->root,
+ &x1, &y1, &x2, &y2);
+
+ priv->width = x2 + GNOME_ICON_CONTAINER_CELL_SPACING (container);
+ priv->height = y2 + GNOME_ICON_CONTAINER_CELL_SPACING (container);
+
+ scroll_width = MAX (priv->width, allocation->width);
+ scroll_height = MAX (priv->height, allocation->height);
+
+ gnome_canvas_set_scroll_region (GNOME_CANVAS (container),
+ 0.0, 0.0,
+ (gdouble) scroll_width,
+ (gdouble) scroll_height);
+
+ if (priv->width <= allocation->width)
+ gtk_adjustment_set_value (hadj, 0.0);
+ if (priv->height <= allocation->height)
+ gtk_adjustment_set_value (vadj, 0.0);
+}
+
+static gint
+idle_handler (gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+
+ GDK_THREADS_ENTER ();
+
+ container = GNOME_ICON_CONTAINER (data);
+ priv = container->priv;
+
+ set_scroll_region (container);
+
+ if (priv->icons != NULL && priv->kbd_current == NULL)
+ set_kbd_current (container, find_first (container), FALSE);
+
+ container->priv->idle_id = 0;
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static void
+add_idle (GnomeIconContainer *container)
+{
+ if (container->priv->idle_id != 0)
+ return;
+
+ container->priv->idle_id = gtk_idle_add (idle_handler, container);
+}
+
+static void
+remove_idle (GnomeIconContainer *container)
+{
+ if (container->priv->idle_id == 0)
+ return;
+
+ gtk_idle_remove (container->priv->idle_id);
+ container->priv->idle_id = 0;
+}
+
+
+/* Container-level icon handling functions. */
+
+/* Select an icon. Return TRUE if selection has changed. */
+static gboolean
+select_icon (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ gboolean sel)
+{
+ GnomeIconContainerPrivate *priv;
+ gboolean was_selected;
+
+ priv = container->priv;
+
+ was_selected = icon->is_selected;
+ icon_select (icon, sel);
+
+ if ((! was_selected && sel) || (was_selected && ! sel))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void
+toggle_icon (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon)
+{
+ icon_toggle_selection (icon);
+}
+
+static gboolean
+unselect_all_but_one (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon_to_avoid)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+ gboolean selection_changed;
+
+ priv = container->priv;
+ selection_changed = FALSE;
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon != icon_to_avoid && icon->is_selected) {
+ icon_select (icon, FALSE);
+ selection_changed = TRUE;
+ }
+ }
+
+ return selection_changed;
+}
+
+static gboolean
+unselect_all (GnomeIconContainer *container)
+{
+ return unselect_all_but_one (container, NULL);
+}
+
+/* FIXME: This could be optimized a bit. */
+static void
+move_icon (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ gint x, gint y)
+{
+ GnomeIconContainerPrivate *priv;
+ gint old_x, old_y;
+ guint old_grid_x, old_grid_y;
+ gint old_x_offset, old_y_offset;
+ guint new_grid_x, new_grid_y;
+ gint new_x_offset, new_y_offset;
+
+ priv = container->priv;
+
+ old_x = icon->x;
+ old_y = icon->y;
+
+ world_to_grid (container, old_x, old_y, &old_grid_x, &old_grid_y);
+ old_x_offset = old_x % GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ old_y_offset = old_y % GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ world_to_grid (container, x, y, &new_grid_x, &new_grid_y);
+ new_x_offset = x % GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ new_y_offset = y % GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ icon_grid_remove (priv->grid, icon, old_grid_x, old_grid_y);
+ if (old_x_offset > 0)
+ icon_grid_remove (priv->grid, icon,
+ old_grid_x + 1, old_grid_y);
+ if (old_y_offset > 0)
+ icon_grid_remove (priv->grid, icon,
+ old_grid_x, old_grid_y + 1);
+ if (old_x_offset > 0 && old_y_offset > 0)
+ icon_grid_remove (priv->grid, icon,
+ old_grid_x + 1, old_grid_y + 1);
+
+ icon_grid_add (priv->grid, icon, new_grid_x, new_grid_y);
+ if (new_x_offset > 0)
+ icon_grid_add (priv->grid, icon, new_grid_x + 1, new_grid_y);
+ if (new_y_offset > 0)
+ icon_grid_add (priv->grid, icon, new_grid_x, new_grid_y + 1);
+ if (new_x_offset > 0 && new_y_offset > 0)
+ icon_grid_add (priv->grid, icon, new_grid_x + 1, new_grid_y + 1);
+
+ icon_position (icon, container, x, y);
+}
+
+static void
+change_icon_mode (GnomeIconContainer *container,
+ GnomeIconContainerIconMode mode)
+{
+ GnomeIconContainerIconModeInfo *old_mode_info;
+ GnomeIconContainerIconModeInfo *new_mode_info;
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+ gdouble x_factor, y_factor;
+
+ priv = container->priv;
+ if (mode == priv->icon_mode)
+ return;
+
+ old_mode_info = gnome_icon_container_icon_mode_info + priv->icon_mode;
+ new_mode_info = gnome_icon_container_icon_mode_info + mode;
+
+ priv->icon_mode = mode;
+
+ x_factor = ((gdouble) new_mode_info->cell_width
+ / (gdouble) old_mode_info->cell_width);
+ y_factor = ((gdouble) new_mode_info->cell_height
+ / (gdouble) old_mode_info->cell_height);
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+
+ icon_configure (icon, container);
+ icon_position (icon, container,
+ icon->x * x_factor, icon->y * y_factor);
+ }
+
+ add_idle (container);
+
+ if (priv->kbd_current != NULL)
+ set_kbd_current (container, priv->kbd_current, TRUE);
+}
+
+
+/* Implementation of rubberband selection. */
+
+static gboolean
+rubberband_select_in_cell (GList *cell,
+ gdouble curr_x1, gdouble curr_y1,
+ gdouble curr_x2, gdouble curr_y2,
+ gdouble prev_x1, gdouble prev_y1,
+ gdouble prev_x2, gdouble prev_y2)
+{
+ GList *p;
+ gboolean selection_changed;
+
+ selection_changed = FALSE;
+
+ for (p = cell; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+ gboolean in_curr_region;
+ gboolean in_prev_region;
+
+ icon = p->data;
+
+ in_curr_region = icon_is_in_region (icon,
+ curr_x1, curr_y1,
+ curr_x2, curr_y2);
+
+ in_prev_region = icon_is_in_region (icon,
+ prev_x1, prev_y1,
+ prev_x2, prev_y2);
+
+ if (in_curr_region && ! in_prev_region) {
+ if (icon->was_selected_before_rubberband) {
+ if (icon->is_selected) {
+ icon_select (icon, FALSE);
+ selection_changed = TRUE;
+ }
+ } else {
+ if (! icon->is_selected) {
+ icon_select (icon, TRUE);
+ selection_changed = TRUE;
+ }
+ }
+ } else if (in_prev_region && ! in_curr_region) {
+ if (icon->was_selected_before_rubberband) {
+ if (! icon->is_selected) {
+ icon_select (icon, TRUE);
+ selection_changed = TRUE;
+ }
+ } else {
+ if (icon->is_selected) {
+ icon_select (icon, FALSE);
+ selection_changed = TRUE;
+ }
+ }
+ }
+ }
+
+ return selection_changed;
+}
+
+static void
+rubberband_select (GnomeIconContainer *container,
+ gdouble curr_x1, gdouble curr_y1,
+ gdouble curr_x2, gdouble curr_y2,
+ gdouble prev_x1, gdouble prev_y1,
+ gdouble prev_x2, gdouble prev_y2)
+{
+ GList **p;
+ GnomeIconContainerIconGrid *grid;
+ guint curr_grid_x1, curr_grid_y1;
+ guint curr_grid_x2, curr_grid_y2;
+ guint prev_grid_x1, prev_grid_y1;
+ guint prev_grid_x2, prev_grid_y2;
+ guint grid_x1, grid_y1;
+ guint grid_x2, grid_y2;
+ guint i, j;
+ gboolean selection_changed;
+
+ grid = container->priv->grid;
+
+ world_to_grid (container, curr_x1, curr_y1, &curr_grid_x1, &curr_grid_y1);
+ world_to_grid (container, curr_x2, curr_y2, &curr_grid_x2, &curr_grid_y2);
+ world_to_grid (container, prev_x1, prev_y1, &prev_grid_x1, &prev_grid_y1);
+ world_to_grid (container, prev_x2, prev_y2, &prev_grid_x2, &prev_grid_y2);
+
+ grid_x1 = MIN (curr_grid_x1, prev_grid_x1);
+ grid_x2 = MAX (curr_grid_x2, prev_grid_x2);
+ grid_y1 = MIN (curr_grid_y1, prev_grid_y1);
+ grid_y2 = MAX (curr_grid_y2, prev_grid_y2);
+
+ selection_changed = FALSE;
+
+ p = icon_grid_get_element_ptr (grid, grid_x1, grid_y1);
+ for (i = 0; i <= grid_y2 - grid_y1; i++) {
+ for (j = 0; j <= grid_x2 - grid_x1; j++) {
+ if (rubberband_select_in_cell (p[j],
+ curr_x1, curr_y1,
+ curr_x2, curr_y2,
+ prev_x1, prev_y1,
+ prev_x2, prev_y2))
+ selection_changed = TRUE;
+ }
+
+ p += grid->alloc_width;
+ }
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+}
+
+static gint
+rubberband_timeout_cb (gpointer data)
+{
+ GnomeIconContainer *container;
+ GtkWidget *widget;
+ GnomeIconContainerRubberbandInfo *rinfo;
+ gint x, y;
+ gdouble x1, y1, x2, y2;
+ gdouble world_x, world_y;
+ gint x_scroll, y_scroll;
+
+ GDK_THREADS_ENTER ();
+
+ widget = GTK_WIDGET (data);
+ container = GNOME_ICON_CONTAINER (data);
+ rinfo = &container->priv->rubberband_info;
+
+ gdk_window_get_pointer (widget->window, &x, &y, NULL);
+
+ if (x < 0) {
+ x_scroll = x;
+ x = 0;
+ } else if (x >= widget->allocation.width) {
+ x_scroll = x - widget->allocation.width + 1;
+ x = widget->allocation.width - 1;
+ } else {
+ x_scroll = 0;
+ }
+
+ if (y < 0) {
+ y_scroll = y;
+ y = 0;
+ } else if (y >= widget->allocation.height) {
+ y_scroll = y - widget->allocation.height + 1;
+ y = widget->allocation.height - 1;
+ } else {
+ y_scroll = 0;
+ }
+
+ if (y_scroll == 0 && x_scroll == 0
+ && rinfo->prev_x == x && rinfo->prev_y == y) {
+ GDK_THREADS_LEAVE ();
+ return TRUE;
+ }
+
+ scroll (container, x_scroll, y_scroll);
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container),
+ x, y, &world_x, &world_y);
+
+ if (world_x < rinfo->start_x) {
+ x1 = world_x;
+ x2 = rinfo->start_x;
+ } else {
+ x1 = rinfo->start_x;
+ x2 = world_x;
+ }
+
+ if (world_y < rinfo->start_y) {
+ y1 = world_y;
+ y2 = rinfo->start_y;
+ } else {
+ y1 = rinfo->start_y;
+ y2 = world_y;
+ }
+
+ gnome_canvas_item_set (rinfo->selection_rectangle,
+ "x1", (gdouble) x1,
+ "y1", (gdouble) y1,
+ "x2", (gdouble) x2,
+ "y2", (gdouble) y2,
+ NULL);
+
+ rubberband_select (container,
+ x1, y1, x2, y2,
+ rinfo->prev_x1, rinfo->prev_y1,
+ rinfo->prev_x2, rinfo->prev_y2);
+
+ rinfo->prev_x = x;
+ rinfo->prev_y = y;
+ rinfo->prev_x1 = x1;
+ rinfo->prev_y1 = y1;
+ rinfo->prev_x2 = x2;
+ rinfo->prev_y2 = y2;
+
+ GDK_THREADS_LEAVE ();
+
+ return TRUE;
+}
+
+static void
+start_rubberbanding (GnomeIconContainer *container,
+ GdkEventButton *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerRubberbandInfo *rinfo;
+ GList *p;
+
+ priv = container->priv;
+ rinfo = &priv->rubberband_info;
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ icon->was_selected_before_rubberband = icon->is_selected;
+ }
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container),
+ event->x, event->y,
+ &rinfo->start_x, &rinfo->start_y);
+
+ rinfo->selection_rectangle
+ = gnome_canvas_item_new (gnome_canvas_root
+ (GNOME_CANVAS (container)),
+ gnome_canvas_rect_get_type (),
+ "x1", rinfo->start_x,
+ "y1", rinfo->start_y,
+ "x2", rinfo->start_x,
+ "y2", rinfo->start_y,
+ "outline_color", "black",
+ "outline_stipple", stipple,
+ "width_pixels", 1,
+ NULL);
+
+ rinfo->prev_x = rinfo->prev_x1 = rinfo->prev_x2 = event->x;
+ rinfo->prev_y = rinfo->prev_y1 = rinfo->prev_y2 = event->y;
+
+ rinfo->active = TRUE;
+
+ rinfo->timer_tag = gtk_timeout_add (RUBBERBAND_TIMEOUT_INTERVAL,
+ rubberband_timeout_cb,
+ container);
+
+ gnome_canvas_item_grab (rinfo->selection_rectangle,
+ (GDK_POINTER_MOTION_MASK
+ | GDK_BUTTON_RELEASE_MASK),
+ NULL, event->time);
+}
+
+static void
+stop_rubberbanding (GnomeIconContainer *container,
+ GdkEventButton *event)
+{
+ GnomeIconContainerRubberbandInfo *rinfo;
+
+ rinfo = &container->priv->rubberband_info;
+
+ gtk_timeout_remove (rinfo->timer_tag);
+ rinfo->active = FALSE;
+
+ gnome_canvas_item_ungrab (rinfo->selection_rectangle, event->time);
+ gtk_object_destroy (GTK_OBJECT (rinfo->selection_rectangle));
+}
+
+
+/* Keyboard navigation. */
+
+static void
+kbd_move_to (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ GdkEventKey *event)
+{
+ if (! (event->state & GDK_CONTROL_MASK)) {
+ gboolean selection_changed;
+
+ selection_changed = unselect_all (container);
+ selection_changed |= select_icon (container, icon, TRUE);
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+
+ set_kbd_current (container, icon, FALSE);
+ make_icon_visible (container, icon);
+}
+
+static void
+kbd_home (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerIcon *first;
+
+ first = find_first (container);
+ if (first != NULL)
+ kbd_move_to (container, first, event);
+}
+
+static void
+kbd_end (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerIcon *last;
+
+ last = find_last (container);
+ if (last != NULL)
+ kbd_move_to (container, last, event);
+}
+
+static void
+kbd_left (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *nearmost;
+ GList **e;
+ guint grid_x, grid_y;
+ gint x, y;
+ gint max_x;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (priv->kbd_current == NULL)
+ return;
+
+ world_to_grid (container, priv->kbd_current->x, priv->kbd_current->y,
+ &grid_x, &grid_y);
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+
+ e = icon_grid_get_element_ptr (grid, 0, grid_y);
+ nearmost = NULL;
+
+ max_x = priv->kbd_current->x;
+
+ while (1) {
+ while (1) {
+ GList *p;
+
+ for (p = e[grid_x]; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon == priv->kbd_current
+ || icon->x < x
+ || icon->y < y)
+ continue;
+
+ if (icon->x <= max_x
+ && (nearmost == NULL
+ || icon->x > nearmost->x))
+ nearmost = icon;
+ }
+
+ if (nearmost != NULL) {
+ kbd_move_to (container, nearmost, event);
+ return;
+ }
+
+ if (grid_x == 0)
+ break;
+
+ grid_x--;
+ x -= GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ if (grid_y == 0)
+ break;
+
+ grid_x = grid->width - 1;
+ max_x = G_MAXINT;
+ grid_to_world (container, grid_x, 0, &x, NULL);
+
+ e -= grid->alloc_width;
+ grid_y--;
+ y -= GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+}
+
+static void
+kbd_up (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *nearmost;
+ GList **e;
+ guint grid_x, grid_y;
+ gint x, y;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (priv->kbd_current == NULL)
+ return;
+
+ world_to_grid (container, priv->kbd_current->x, priv->kbd_current->y,
+ &grid_x, &grid_y);
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+
+ e = icon_grid_get_element_ptr (grid, grid_x, grid_y);
+ nearmost = NULL;
+
+ while (1) {
+ GList *p;
+
+ p = *e;
+
+ for (; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon == priv->kbd_current
+ || icon->x < x
+ || icon->y < y)
+ continue;
+
+ if (icon->y <= priv->kbd_current->y
+ && (nearmost == NULL || icon->y > nearmost->y))
+ nearmost = icon;
+ }
+
+ if (nearmost != NULL)
+ break;
+
+ if (grid_y == 0)
+ break;
+
+ e -= grid->alloc_width;
+ grid_y--;
+ y -= GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+
+ if (nearmost != NULL)
+ kbd_move_to (container, nearmost, event);
+}
+
+static void
+kbd_right (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *nearmost;
+ GList **e;
+ guint grid_x, grid_y;
+ gint x, y;
+ gint min_x;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (priv->kbd_current == NULL)
+ return;
+
+ world_to_grid (container, priv->kbd_current->x, priv->kbd_current->y,
+ &grid_x, &grid_y);
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+
+ e = icon_grid_get_element_ptr (grid, 0, grid_y);
+ nearmost = NULL;
+
+ min_x = priv->kbd_current->x;
+
+ while (grid_y < grid->height) {
+ while (grid_x < grid->width) {
+ GList *p;
+
+ for (p = e[grid_x]; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon == priv->kbd_current
+ || icon->x < x
+ || icon->y < y)
+ continue;
+
+ if (icon->x >= min_x
+ && (nearmost == NULL
+ || icon->x < nearmost->x))
+ nearmost = icon;
+ }
+
+ if (nearmost != NULL) {
+ kbd_move_to (container, nearmost, event);
+ return;
+ }
+
+ grid_x++;
+ x += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ grid_x = 0;
+ min_x = 0;
+ x = 0;
+
+ e += grid->alloc_width;
+ grid_y++;
+ y += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+}
+
+static void
+kbd_down (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIcon *nearmost;
+ GList **e;
+ guint grid_x, grid_y;
+ gint x, y;
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ if (priv->kbd_current == NULL)
+ return;
+
+ world_to_grid (container, priv->kbd_current->x, priv->kbd_current->y,
+ &grid_x, &grid_y);
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+
+ e = icon_grid_get_element_ptr (grid, grid_x, grid_y);
+ nearmost = NULL;
+
+ while (grid_y < grid->height) {
+ GList *p;
+
+ p = *e;
+
+ for (; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon == priv->kbd_current
+ || icon->x < x
+ || icon->y < y)
+ continue;
+
+ if (icon->y >= priv->kbd_current->y
+ && (nearmost == NULL || icon->y < nearmost->y))
+ nearmost = icon;
+ }
+
+ if (nearmost != NULL)
+ break;
+
+ e += grid->alloc_width;
+ grid_y++;
+ y += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ }
+
+ if (nearmost != NULL)
+ kbd_move_to (container, nearmost, event);
+}
+
+static void
+kbd_space (GnomeIconContainer *container,
+ GdkEventKey *event)
+{
+ if (container->priv->kbd_current != NULL) {
+ if (select_icon (container, container->priv->kbd_current, TRUE))
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+}
+
+
+/* GtkObject methods. */
+
+static void
+destroy (GtkObject *object)
+{
+ GnomeIconContainer *container;
+
+ container = GNOME_ICON_CONTAINER (object);
+
+ icon_grid_destroy (container->priv->grid);
+
+ gnome_icon_container_dnd_fini (container);
+
+ g_free (container->priv);
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy != NULL)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+
+/* GtkWidget methods. */
+
+static void
+size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ requisition->width = 1;
+ requisition->height = 1;
+}
+
+static void
+size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerIconGrid *grid;
+ guint visible_width, visible_height;
+
+ if (GTK_WIDGET_CLASS (parent_class)->size_allocate)
+ (* GTK_WIDGET_CLASS (parent_class)->size_allocate)
+ (widget, allocation);
+
+ container = GNOME_ICON_CONTAINER (widget);
+ grid = container->priv->grid;
+
+ world_to_grid (container,
+ allocation->width, 0,
+ &visible_width, &visible_height);
+
+ if (visible_width == 0)
+ visible_width = 1;
+
+ if (visible_width > grid->width || visible_height > grid->height)
+ icon_grid_resize (grid,
+ MAX (visible_width, grid->width),
+ MAX (visible_height, grid->height));
+
+ icon_grid_set_visible_width (grid, visible_width);
+
+ set_scroll_region (container);
+}
+
+static void
+realize (GtkWidget *widget)
+{
+ GtkStyle *style;
+
+ if (GTK_WIDGET_CLASS (parent_class)->realize)
+ (* GTK_WIDGET_CLASS (parent_class)->realize) (widget);
+
+ 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 (widget)->bin_window,
+ & widget->style->bg [GTK_STATE_NORMAL]);
+}
+
+static gboolean
+button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ gboolean return_value;
+ GnomeIconContainer *container;
+
+ /* 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;
+
+ container = GNOME_ICON_CONTAINER (widget);
+
+ if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
+ if (! (event->state & GDK_CONTROL_MASK)) {
+ gboolean selection_changed;
+
+ selection_changed = unselect_all (container);
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+
+ start_rubberbanding (container, event);
+ return TRUE;
+ }
+
+ gtk_signal_emit (GTK_OBJECT (widget), signals[BUTTON_PRESS], event,
+ &return_value);
+
+ return return_value;
+}
+
+static gboolean
+button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ priv = container->priv;
+
+ if (event->button == 1 && priv->rubberband_info.active) {
+ stop_rubberbanding (container, event);
+ return TRUE;
+ }
+
+ if (event->button == priv->drag_button) {
+ priv->drag_button = 0;
+ if (! priv->doing_drag
+ && ! (event->state & GDK_CONTROL_MASK)) {
+ gboolean selection_changed;
+
+ selection_changed
+ = unselect_all_but_one (container,
+ priv->drag_icon);
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+
+ if (priv->drag_icon != NULL) {
+ set_kbd_current (container, priv->drag_icon, TRUE);
+ priv->drag_icon = NULL;
+ }
+
+ if (priv->doing_drag)
+ gnome_icon_container_dnd_end_drag (container);
+
+ priv->doing_drag = FALSE;
+ return TRUE;
+ }
+
+ if (GTK_WIDGET_CLASS (parent_class)->button_release_event != NULL)
+ return GTK_WIDGET_CLASS (parent_class)->button_release_event
+ (widget, event);
+
+ return FALSE;
+}
+
+static gint
+motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *motion)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+ double world_x, world_y;
+
+ container = GNOME_ICON_CONTAINER (widget);
+ priv = container->priv;
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container),
+ motion->x, motion->y,
+ &world_x, &world_y);
+
+#define SNAP_RESISTANCE 2 /* GMC has this set to 3, but it's too much for
+ my taste. */
+ if (priv->drag_button != 0
+ && abs (priv->drag_x - world_x) >= SNAP_RESISTANCE
+ && abs (priv->drag_y - world_y) >= SNAP_RESISTANCE) {
+ priv->doing_drag = TRUE;
+
+ /* KLUDGE ALERT: Poke the starting values into the motion
+ structure so that dragging behaves as expected. */
+ motion->x = priv->drag_x;
+ motion->y = priv->drag_y;
+
+ gnome_icon_container_dnd_begin_drag (container,
+ GDK_ACTION_MOVE,
+ priv->drag_button,
+ motion);
+ return TRUE;
+ }
+#undef SNAP_RESISTANCE
+
+ if (GTK_WIDGET_CLASS (parent_class)->motion_notify_event != NULL)
+ return (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event)
+ (widget, motion);
+
+ return FALSE;
+}
+
+static gint
+key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ GnomeIconContainer *container;
+
+ if ((* GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event))
+ return TRUE;
+
+ container = GNOME_ICON_CONTAINER (widget);
+
+ switch (event->keyval) {
+ case GDK_Home:
+ kbd_home (container, event);
+ break;
+ case GDK_End:
+ kbd_end (container, event);
+ break;
+ case GDK_Left:
+ kbd_left (container, event);
+ break;
+ case GDK_Up:
+ kbd_up (container, event);
+ break;
+ case GDK_Right:
+ kbd_right (container, event);
+ break;
+ case GDK_Down:
+ kbd_down (container, event);
+ break;
+ case GDK_space:
+ kbd_space (container, event);
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/* Initialization. */
+
+static void
+class_init (GnomeIconContainerClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ /* Derive from GnomeCanvas. */
+
+ parent_class = gtk_type_class (gnome_canvas_get_type ());
+
+ /* GnomeIconContainer class. */
+
+ class->button_press = NULL;
+
+ /* GtkObject class. */
+
+ object_class = GTK_OBJECT_CLASS (class);
+ object_class->destroy = destroy;
+
+ /* Signals. */
+
+ signals[SELECTION_CHANGED]
+ = gtk_signal_new ("selection_changed",
+ GTK_RUN_LAST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GnomeIconContainerClass,
+ selection_changed),
+ gtk_marshal_NONE__NONE,
+ GTK_TYPE_NONE, 0);
+ signals[BUTTON_PRESS]
+ = gtk_signal_new ("button_press",
+ GTK_RUN_LAST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GnomeIconContainerClass,
+ button_press),
+ gtk_marshal_BOOL__POINTER,
+ GTK_TYPE_BOOL, 1,
+ GTK_TYPE_GDK_EVENT);
+ signals[ACTIVATE]
+ = gtk_signal_new ("activate",
+ GTK_RUN_LAST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GnomeIconContainerClass,
+ activate),
+ gtk_marshal_NONE__POINTER_POINTER,
+ GTK_TYPE_NONE, 2,
+ GTK_TYPE_STRING,
+ GTK_TYPE_POINTER);
+ signals[CONTEXT_CLICK]
+ = gtk_signal_new ("context_click",
+ GTK_RUN_LAST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GnomeIconContainerClass,
+ activate),
+ gtk_marshal_NONE__POINTER_POINTER,
+ GTK_TYPE_NONE, 2,
+ GTK_TYPE_STRING,
+ GTK_TYPE_POINTER);
+
+ gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL);
+
+ /* GtkWidget class. */
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->size_request = size_request;
+ widget_class->size_allocate = size_allocate;
+ widget_class->realize = realize;
+ widget_class->button_press_event = button_press_event;
+ widget_class->button_release_event = button_release_event;
+ widget_class->motion_notify_event = motion_notify_event;
+ widget_class->key_press_event = key_press_event;
+
+ /* Initialize the stipple bitmap. */
+
+ stipple = gdk_bitmap_create_from_data (NULL, stipple_bits, 2, 2);
+}
+
+static void
+init (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = g_new (GnomeIconContainerPrivate, 1);
+
+ priv->base_uri = NULL;
+
+ priv->width = priv->height = 0;
+
+ priv->icons = NULL;
+ priv->num_icons = 0;
+
+ priv->icon_mode = GNOME_ICON_CONTAINER_NORMAL_ICONS;
+
+ priv->grid = icon_grid_new ();
+
+ priv->canvas_item_to_icon = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ priv->kbd_navigation_rectangle
+ = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (container)),
+ gnome_canvas_rect_get_type (),
+ "outline_color", "black",
+ "outline_stipple", stipple,
+ "width_pixels", 1,
+ NULL);
+ gnome_canvas_item_hide (priv->kbd_navigation_rectangle);
+
+ priv->kbd_current = NULL;
+ priv->rubberband_info.active = FALSE;
+ priv->kbd_icon_visibility_timer_tag = -1;
+ priv->idle_id = 0;
+
+ priv->drag_button = 0;
+ priv->drag_icon = NULL;
+ priv->drag_x = priv->drag_y = 0;
+ priv->doing_drag = FALSE;
+
+ priv->browser_mode = FALSE;
+ priv->browser_mode_selection_timer_tag = -1;
+ priv->browser_mode_selection_icon = NULL;
+
+ container->priv = priv;
+
+ /* Set up DnD. */
+ gnome_icon_container_dnd_init (container, stipple);
+
+ /* Request update. */
+ add_idle (container);
+}
+
+
+/* GnomeIconContainerIcon event handling. */
+
+/* Selection in browser mode. */
+static gint
+browser_select_timeout_cb (gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *icon;
+ gboolean selection_changed;
+
+ GDK_THREADS_ENTER ();
+
+ container = GNOME_ICON_CONTAINER (data);
+ priv = container->priv;
+ icon = priv->browser_mode_selection_icon;
+
+ selection_changed = unselect_all (container);
+ selection_changed |= select_icon (container, icon, TRUE);
+
+ set_kbd_current (container, icon, FALSE);
+ make_icon_visible (container, icon);
+
+ /* FIXME: Am I allowed to do this between `GDK_THREADS_ENTER()' and
+ `GDK_THREADS_LEAVE()'? */
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+/* Conceptually, pressing button 1 together with CTRL toggles selection of a
+ single icon without affecting the other icons; without CTRL, it selects a
+ single icon and un-selects all the other icons. But in this latter case,
+ the de-selection should only happen when the button is released if the
+ icon is already selected, because the user might select multiple icons and
+ drag all of them by doing a simple click-drag. */
+static gint
+handle_icon_button_press (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ GdkEventButton *event)
+{
+ GnomeIconContainerPrivate *priv;
+ gdouble world_x, world_y;
+
+ if (event->button != 1)
+ return FALSE;
+
+ priv = container->priv;
+
+ if (event->state & GDK_CONTROL_MASK) {
+ toggle_icon (container, icon);
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ } else if (! icon->is_selected) {
+ unselect_all (container);
+ select_icon (container, icon, TRUE);
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+ }
+
+ if (event->type == GDK_2BUTTON_PRESS) {
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[ACTIVATE],
+ icon->text, icon->data);
+
+ /* Double clicking should *never* trigger a D&D action. */
+ priv->drag_button = 0;
+ priv->drag_icon = NULL;
+ return TRUE;
+ }
+
+ if (event->button == 3) {
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[CONTEXT_CLICK],
+ icon->text, icon->data);
+
+ /* FIXME this means you cannot drag with right click. Instead,
+ we should setup a timeout and emit this signal if the
+ timeout expires without movement. */
+ priv->drag_button = 0;
+ priv->drag_icon = NULL;
+ return TRUE;
+ }
+
+ priv->drag_button = event->button;
+ priv->drag_icon = icon;
+ priv->drag_x = event->x;
+ priv->drag_y = event->y;
+
+ gnome_canvas_window_to_world (GNOME_CANVAS (container), event->x, event->y,
+ &world_x, &world_y);
+ priv->drag_x_offset = (gint) world_x - icon->x;
+ priv->drag_y_offset = (gint) world_y - icon->y;
+
+ return TRUE;
+}
+
+static gint
+handle_icon_enter_notify (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ GdkEventMotion *motion)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+ if (! priv->browser_mode)
+ return FALSE;
+
+ if (priv->browser_mode_selection_timer_tag != -1)
+ gtk_timeout_remove (priv->browser_mode_selection_timer_tag);
+
+ priv->browser_mode_selection_timer_tag
+ = gtk_timeout_add (BROWSER_MODE_SELECTION_TIMEOUT,
+ browser_select_timeout_cb, container);
+
+ priv->browser_mode_selection_icon = icon;
+
+ return TRUE;
+}
+
+static gint
+handle_icon_leave_notify (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon,
+ GdkEventMotion *motion)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+ if (! priv->browser_mode)
+ return FALSE;
+
+ if (priv->browser_mode_selection_timer_tag != -1)
+ gtk_timeout_remove (priv->browser_mode_selection_timer_tag);
+
+ return TRUE;
+}
+
+static gint
+item_event_cb (GnomeCanvasItem *item,
+ GdkEvent *event,
+ gpointer data)
+{
+ GnomeIconContainer *container;
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *icon;
+
+ container = GNOME_ICON_CONTAINER (data);
+ priv = container->priv;
+
+ icon = g_hash_table_lookup (priv->canvas_item_to_icon, item);
+ g_return_val_if_fail (icon != NULL, FALSE);
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ return handle_icon_button_press (container, icon, &event->button);
+ case GDK_ENTER_NOTIFY:
+ return handle_icon_enter_notify (container, icon, &event->motion);
+ case GDK_LEAVE_NOTIFY:
+ return handle_icon_leave_notify (container, icon, &event->motion);
+ default:
+ return FALSE;
+ }
+}
+
+
+GtkWidget *
+gnome_icon_container_new (void)
+{
+ GtkWidget *new;
+
+ gtk_widget_push_visual (gdk_imlib_get_visual ());
+ gtk_widget_push_colormap (gdk_imlib_get_colormap ());
+
+ new = gtk_type_new (gnome_icon_container_get_type ());
+
+ gtk_widget_pop_visual ();
+ gtk_widget_pop_colormap ();
+
+ return new;
+}
+
+
+guint
+gnome_icon_container_get_type (void)
+{
+ static guint type = 0;
+
+ if (type == 0) {
+ GtkTypeInfo type_info = {
+ "GnomeIconContainer",
+ sizeof (GnomeIconContainer),
+ sizeof (GnomeIconContainerClass),
+ (GtkClassInitFunc) class_init,
+ (GtkObjectInitFunc) init,
+ (GtkArgSetFunc) NULL,
+ (GtkArgGetFunc) NULL
+ };
+
+ type = gtk_type_unique (gnome_canvas_get_type (), &type_info);
+ }
+
+ return type;
+}
+
+void
+gnome_icon_container_clear (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+
+ for (p = priv->icons; p != NULL; p = p->next)
+ icon_destroy (p->data);
+ g_list_free (priv->icons);
+ priv->icons = NULL;
+ priv->num_icons = 0;
+
+ icon_grid_clear (priv->grid);
+
+ unset_kbd_current (container);
+}
+
+
+void
+gnome_icon_container_set_icon_mode (GnomeIconContainer *container,
+ GnomeIconContainerIconMode mode)
+{
+ g_return_if_fail (container != NULL);
+
+ if (mode < 0 || mode >= NUM_ICON_MODES) {
+ g_warning ("Unknown icon mode %d", mode);
+ return;
+ }
+
+ change_icon_mode (container, mode);
+}
+
+GnomeIconContainerIconMode
+gnome_icon_container_get_icon_mode (GnomeIconContainer *container)
+{
+ g_return_val_if_fail (container != NULL, GNOME_ICON_CONTAINER_NORMAL_ICONS);
+
+ return container->priv->icon_mode;
+}
+
+
+static void
+setup_icon_in_container (GnomeIconContainer *container,
+ GnomeIconContainerIcon *icon)
+{
+ GnomeIconContainerPrivate *priv;
+
+ priv = container->priv;
+
+ priv->icons = g_list_prepend (priv->icons, icon);
+ priv->num_icons++;
+
+ g_hash_table_insert (priv->canvas_item_to_icon, icon->item, icon);
+ icon_show (icon);
+
+ gtk_signal_connect (GTK_OBJECT (icon->item), "event",
+ GTK_SIGNAL_FUNC (item_event_cb), container);
+}
+
+void
+gnome_icon_container_add_imlib (GnomeIconContainer *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gint x, gint y,
+ gpointer data)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIcon *new_icon;
+ guint grid_x, grid_y;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (image != NULL);
+ g_return_if_fail (text != NULL);
+
+ priv = container->priv;
+
+ new_icon = icon_new_imlib (container, image, text, data);
+ icon_position (new_icon, container, x, y);
+
+ world_to_grid (container, x, y, &grid_x, &grid_y);
+ icon_grid_add (container->priv->grid, new_icon, grid_x, grid_y);
+
+ if (x % GNOME_ICON_CONTAINER_CELL_WIDTH (container) > 0)
+ icon_grid_add (priv->grid, new_icon, grid_x + 1, grid_y);
+ if (y % GNOME_ICON_CONTAINER_CELL_HEIGHT (container) > 0)
+ icon_grid_add (priv->grid, new_icon, grid_x, grid_y + 1);
+ if (x % GNOME_ICON_CONTAINER_CELL_WIDTH (container) > 0
+ && y % GNOME_ICON_CONTAINER_CELL_HEIGHT (container) > 0)
+ icon_grid_add (priv->grid, new_icon, grid_x + 1, grid_y + 1);
+
+ setup_icon_in_container (container, new_icon);
+
+ add_idle (container);
+}
+
+/**
+ * gnome_icon_container_add_imlib_auto:
+ * @container: A GnomeIconContainer
+ * @image: Image of the icon to add
+ * @text: Caption
+ * @data: Icon-specific data
+ *
+ * Add @image with caption @text and data @data to @container, in the first
+ * empty spot available.
+ **/
+void
+gnome_icon_container_add_imlib_auto (GnomeIconContainer *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data)
+{
+ GnomeIconContainerIcon *new_icon;
+ guint grid_x, grid_y;
+ gint x, y;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (image != NULL);
+ g_return_if_fail (text != NULL);
+
+ new_icon = icon_new_imlib (container, image, text, data);
+
+ icon_grid_add_auto (container->priv->grid, new_icon, &grid_x, &grid_y);
+
+ grid_to_world (container, grid_x, grid_y, &x, &y);
+ icon_position (new_icon, container, x, y);
+
+ setup_icon_in_container (container, new_icon);
+
+ add_idle (container);
+}
+
+/**
+ * gnome_icon_container_add_imlib_with_layout:
+ * @container: A GnomeIconContainer
+ * @image: Image of the icon to add
+ * @text: Caption
+ * @data: Icon-specific data
+ * @layout: Layout information
+ *
+ * Add @image with the caption @text to @container using @layout, and attach
+ * @data to it.
+ *
+ * Return value: %FALSE if @text is not in @layout (and, consequently, the icon
+ * has not been added); %TRUE otherwise.
+ **/
+gboolean
+gnome_icon_container_add_imlib_with_layout (GnomeIconContainer *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data,
+ const GnomeIconContainerLayout *layout)
+{
+ gint x, y;
+
+ g_return_val_if_fail (container != NULL, FALSE);
+ g_return_val_if_fail (image != NULL, FALSE);
+ g_return_val_if_fail (text != NULL, FALSE);
+ g_return_val_if_fail (layout != NULL, FALSE);
+
+ if (gnome_icon_container_layout_get_position (layout, text, &x, &y)) {
+ gnome_icon_container_add_imlib (container, image,
+ text, x, y, data);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+
+/**
+ * gnome_icon_container_relayout:
+ * @container: An icon container.
+ *
+ * Relayout the icons in @container according to the allocation we are given. This
+ * is done by just collecting icons from top to bottom, from left to right, and
+ * tiling them in the same direction. The tiling is done in such a way that no
+ * horizontal scrolling is needed to see all the icons.
+ **/
+void
+gnome_icon_container_relayout (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *old_grid, *new_grid;
+ GList **sp, **dp;
+ guint i, j;
+ guint dx, dy;
+ guint sx, sy;
+ guint cols;
+ guint lines;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+ old_grid = priv->grid;
+
+ g_return_if_fail (old_grid->visible_width > 0);
+
+ prepare_for_layout (container);
+
+ new_grid = icon_grid_new ();
+
+ if (priv->num_icons % old_grid->visible_width != 0)
+ icon_grid_resize (new_grid,
+ old_grid->visible_width,
+ (priv->num_icons
+ / old_grid->visible_width) + 1);
+ else
+ icon_grid_resize (new_grid,
+ old_grid->visible_width,
+ priv->num_icons / old_grid->visible_width);
+
+ icon_grid_set_visible_width (new_grid, old_grid->visible_width);
+
+ sp = old_grid->elems;
+ dp = new_grid->elems;
+ sx = sy = 0;
+ dx = dy = 0;
+ cols = lines = 0;
+ for (i = 0; i < old_grid->height; i++) {
+ for (j = 0; j < old_grid->width; j++) {
+ GList *p;
+
+ /* Make sure the icons are sorted by increasing X
+ position. */
+ sp[j] = g_list_sort (sp[j],
+ icon_grid_cell_compare_by_x);
+
+ for (p = sp[j]; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+
+ /* Make sure icons are not moved twice, and
+ ignore icons whose upper left corner is not
+ in this cell, unless the icon is partly
+ outside the container. */
+ if (icon->layout_done
+ || (icon->x >= 0 && icon->x < sx)
+ || (icon->y >= 0 && icon->y < sy))
+ continue;
+
+ dp[cols] = g_list_alloc ();
+ dp[cols]->data = icon;
+
+ icon_position (icon, container, dx, dy);
+
+ icon->layout_done = TRUE;
+
+ if (++cols == new_grid->visible_width) {
+ cols = 0, lines++;
+ dx = 0, dy += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ dp += new_grid->alloc_width;
+ } else {
+ dx += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+ }
+
+ sx += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ sx = 0, sy += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+
+ sp += old_grid->alloc_width;
+ }
+
+ if (cols < new_grid->visible_width && lines < new_grid->height) {
+ new_grid->first_free_x = cols;
+ new_grid->first_free_y = lines;
+ } else {
+ new_grid->first_free_x = -1;
+ new_grid->first_free_y = -1;
+ }
+
+ icon_grid_destroy (priv->grid);
+ priv->grid = new_grid;
+
+ if (priv->kbd_current != NULL)
+ set_kbd_current (container, priv->kbd_current, FALSE);
+
+ add_idle (container);
+}
+
+
+/**
+ * gnome_icon_container_line_up:
+ * @container: An icon container.
+ *
+ * Line up icons in @container.
+ **/
+void
+gnome_icon_container_line_up (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GnomeIconContainerIconGrid *new_grid;
+ GList **p, **q;
+ guint new_grid_width;
+ guint i, j, k, m;
+ gint x, y, dx;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ /* Mark all icons as "not moved yet". */
+
+ prepare_for_layout (container);
+
+ /* Calculate the width for the resulting new grid. This is the maximum
+ width across all the lines. */
+
+ new_grid_width = 0;
+ p = grid->elems;
+ x = y = 0;
+ for (i = 0; i < grid->height; i++) {
+ guint line_width;
+
+ line_width = grid->width;
+ for (j = 0; j < grid->width; j++) {
+ GList *e;
+ guint count;
+
+ count = 0;
+ for (e = p[j]; e != NULL; e = e->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = e->data;
+ if (icon->x >= x && icon->y >= y)
+ count++;
+ }
+
+ if (count > 1)
+ new_grid_width += count - 1;
+
+ x += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+
+ new_grid_width = MAX (new_grid_width, line_width);
+ p += grid->alloc_width;
+
+ y += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ x = 0;
+ }
+
+ /* Create the new grid. */
+
+ new_grid = icon_grid_new ();
+ icon_grid_resize (new_grid, new_grid_width, grid->height);
+ icon_grid_set_visible_width (new_grid, grid->visible_width);
+
+ /* Allocate the icons in the new grid, one per cell. */
+
+ p = grid->elems;
+ q = new_grid->elems;
+ k = 0;
+ x = y = dx = 0;
+ for (i = 0; i < grid->height; i++) {
+ m = 0;
+ for (j = 0; j < grid->width; j++) {
+ GList *e;
+ guint count;
+
+ /* Make sure the icons are sorted by increasing X
+ position. */
+ p[j] = g_list_sort
+ (p[j], icon_grid_cell_compare_by_x);
+
+ count = 0;
+ for (e = p[j]; e != NULL; e = e->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = e->data;
+
+ /* Make sure icons are not moved twice, and
+ ignore icons whose upper left corner is not
+ in this cell, unless the icon is partly
+ outside the container. */
+ if (icon->layout_done
+ || (icon->x >= 0 && icon->x < x)
+ || (icon->y >= 0 && icon->y < y))
+ continue;
+
+ icon_position (icon, container, dx, y);
+ icon->layout_done = TRUE;
+
+ q[k] = g_list_alloc ();
+ q[k]->data = icon;
+ dx += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+
+ k++;
+
+ if (count > 0)
+ m++;
+
+ count++;
+ }
+
+ if (count == 0) {
+ if (m > 0) {
+ m--;
+ } else {
+ k++;
+ dx += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+ }
+ }
+ }
+
+ x += GNOME_ICON_CONTAINER_CELL_WIDTH (container);
+
+ p += grid->alloc_width;
+
+ q += new_grid->alloc_width;
+ k = 0;
+
+ y += GNOME_ICON_CONTAINER_CELL_HEIGHT (container);
+ x = 0;
+
+ dx = 0;
+ }
+
+ /* Done: use the new grid. */
+
+ icon_grid_destroy (priv->grid);
+ priv->grid = new_grid;
+
+ /* Update the keyboard selection indicator. */
+ if (priv->kbd_current != NULL)
+ set_kbd_current (container, priv->kbd_current, FALSE);
+
+ add_idle (container);
+}
+
+
+/**
+ * gnome_icon_container_get_selection:
+ * @container: An icon container.
+ *
+ * Get a list of the icons currently selected in @container.
+ *
+ * Return value: A GList of the programmer-specified data associated to each
+ * selected icon, or NULL if no icon is selected. The caller is expected to
+ * free the list when it is not needed anymore.
+ **/
+GList *
+gnome_icon_container_get_selection (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *list, *p;
+
+ g_return_val_if_fail (container != NULL, FALSE);
+
+ priv = container->priv;
+
+ list = NULL;
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon->is_selected)
+ list = g_list_prepend (list, icon->data);
+ }
+
+ return list;
+}
+
+/**
+ * gnome_icon_container_select_all:
+ * @container: An icon container widget.
+ *
+ * Select all the icons in @container at once.
+ **/
+void
+gnome_icon_container_select_all (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ GnomeIconContainerIconGrid *grid;
+ GList **p, *q;
+ guint i, j;
+ gboolean selection_changed;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+ grid = priv->grid;
+
+ selection_changed = FALSE;
+ p = grid->elems;
+ for (i = 0; i < grid->height; i++) {
+ for (j = 0; j < grid->width; j++) {
+ for (q = p[j]; q != NULL; q =q->next) {
+ if (select_icon (container, q->data, TRUE))
+ selection_changed = TRUE;
+ }
+ }
+
+ p += grid->alloc_width;
+ }
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+}
+
+/**
+ * gnome_icon_container_unselect_all:
+ * @container: An icon container widget.
+ *
+ * Deselect all the icons in @container.
+ **/
+void
+gnome_icon_container_unselect_all (GnomeIconContainer *container)
+{
+ GnomeIconContainerPrivate *priv;
+ gboolean selection_changed;
+ GList *p;
+
+ g_return_if_fail (container != NULL);
+
+ priv = container->priv;
+
+ selection_changed = FALSE;
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (select_icon (container, icon, FALSE))
+ selection_changed = TRUE;
+ }
+
+ if (selection_changed)
+ gtk_signal_emit (GTK_OBJECT (container),
+ signals[SELECTION_CHANGED]);
+}
+
+/**
+ * gnome_icon_container_set_base_uri:
+ * @container: An icon container widget.
+ * @base_uri: A base URI.
+ *
+ * Set the base URI for drag & drop operations.
+ **/
+void
+gnome_icon_container_set_base_uri (GnomeIconContainer *container,
+ const gchar *base_uri)
+{
+ GnomeIconContainerPrivate *priv;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ priv = container->priv;
+
+ g_free (priv->base_uri);
+ priv->base_uri = g_strdup (base_uri);
+}
+
+/**
+ * gnome_icon_container_xlate_selected:
+ * @container: An icon container widget.
+ * @amount_x: Amount of translation on the X axis.
+ * @amount_y: Amount of translation on the Y axis.
+ * @raise: Whether icons should be raised during this operation.
+ *
+ * Translate all the currently selected items in @container by @amount_x
+ * horizontally and @amount_y vertically. Positive values move to the
+ * right/bottom, negative values to the left/top.
+ **/
+void
+gnome_icon_container_xlate_selected (GnomeIconContainer *container,
+ gint amount_x,
+ gint amount_y,
+ gboolean raise)
+{
+ GnomeIconContainerPrivate *priv;
+ GList *p;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GNOME_IS_ICON_CONTAINER (container));
+
+ if (amount_x == 0 && amount_y == 0)
+ return;
+
+ priv = container->priv;
+
+ for (p = priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ if (icon->is_selected) {
+ move_icon (container, icon,
+ icon->x + amount_x, icon->y + amount_y);
+ if (raise)
+ icon_raise (icon);
+ }
+ }
+
+ set_kbd_current (container, priv->kbd_current, TRUE);
+}
+
+
+GnomeIconContainerLayout *
+gnome_icon_container_get_layout (GnomeIconContainer *container)
+{
+ GnomeIconContainerLayout *layout;
+ GList *p;
+
+ g_return_val_if_fail (container != NULL, NULL);
+ g_return_val_if_fail (GNOME_IS_ICON_CONTAINER (container), NULL);
+
+ layout = gnome_icon_container_layout_new ();
+
+ for (p = container->priv->icons; p != NULL; p = p->next) {
+ GnomeIconContainerIcon *icon;
+
+ icon = p->data;
+ gnome_icon_container_layout_add (layout, icon->text,
+ icon->x, icon->y);
+ }
+
+ return layout;
+}
diff --git a/libnautilus/gnome-icon-container.h b/libnautilus/gnome-icon-container.h
new file mode 100644
index 000000000..dd61eaad9
--- /dev/null
+++ b/libnautilus/gnome-icon-container.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-icon-container.h - Icon container widget.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _GNOME_ICON_CONTAINER_H
+#define _GNOME_ICON_CONTAINER_H
+
+#include <libgnomeui/libgnomeui.h>
+
+enum _GnomeIconContainerIconMode {
+ GNOME_ICON_CONTAINER_NORMAL_ICONS,
+ GNOME_ICON_CONTAINER_SMALL_ICONS
+};
+typedef enum _GnomeIconContainerIconMode GnomeIconContainerIconMode;
+
+enum _GnomeIconContainerLayoutMode {
+ GNOME_ICON_LAYOUT_MANUAL,
+ GNOME_ICON_LAYOUT_AUTO
+};
+typedef enum _GnomeIconContainerLayoutMode GnomeIconContainerLayoutMode;
+
+typedef struct _GnomeIconContainer GnomeIconContainer;
+typedef struct _GnomeIconContainerClass GnomeIconContainerClass;
+typedef struct _GnomeIconContainerPrivate GnomeIconContainerPrivate;
+
+#include "gnome-icon-container-layout.h"
+
+
+#define GNOME_ICON_CONTAINER(obj) \
+ GTK_CHECK_CAST (obj, gnome_icon_container_get_type (), GnomeIconContainer)
+#define GNOME_ICON_CONTAINER_CLASS(k) \
+ GTK_CHECK_CLASS_CAST (k, gnome_icon_container_get_type (), GnomeIconListView)
+#define GNOME_IS_ICON_CONTAINER(obj) \
+ GTK_CHECK_TYPE (obj, gnome_icon_container_get_type ())
+
+
+typedef gint (* GnomeIconContainerSortFunc) (const gchar *name_a,
+ gpointer data_a,
+ const gchar *name_b,
+ gpointer data_b,
+ gpointer user_data);
+
+struct _GnomeIconContainer {
+ GnomeCanvas canvas;
+ GnomeIconContainerPrivate *priv;
+};
+
+struct _GnomeIconContainerClass {
+ GnomeCanvasClass parent_class;
+
+ void (* selection_changed) (GnomeIconContainer *container);
+ gint (* button_press) (GnomeIconContainer *container,
+ GdkEventButton *event);
+ void (* activate) (GnomeIconContainer *container,
+ const gchar *name,
+ gpointer data);
+
+ void (* context_click) (GnomeIconContainer *container,
+ const gchar *name,
+ gpointer data);
+};
+
+
+guint gnome_icon_container_get_type (void);
+
+GtkWidget *gnome_icon_container_new (void);
+
+void gnome_icon_container_clear (GnomeIconContainer *view);
+
+void gnome_icon_container_set_icon_mode
+ (GnomeIconContainer *view,
+ GnomeIconContainerIconMode mode);
+
+GnomeIconContainerIconMode
+ gnome_icon_container_get_icon_mode
+ (GnomeIconContainer *view);
+
+void gnome_icon_container_set_editable
+ (GnomeIconContainer *view,
+ gboolean is_editable);
+gboolean gnome_icon_container_get_editable
+ (GnomeIconContainer *view);
+
+void gnome_icon_container_add_imlib (GnomeIconContainer *view,
+ GdkImlibImage *image,
+ const gchar *text,
+ gint x, gint y,
+ gpointer data);
+
+void gnome_icon_container_add_imlib_auto
+ (GnomeIconContainer *view,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data);
+gboolean gnome_icon_container_add_imlib_with_layout
+ (GnomeIconContainer
+ *container,
+ GdkImlibImage *image,
+ const gchar *text,
+ gpointer data,
+ const GnomeIconContainerLayout
+ *layout);
+
+gpointer gnome_icon_container_get_icon_data
+ (GnomeIconContainer *view,
+ const gchar *text);
+
+void gnome_icon_container_relayout (GnomeIconContainer *view);
+void gnome_icon_container_line_up (GnomeIconContainer *view);
+GList *gnome_icon_container_get_selection
+ (GnomeIconContainer *view);
+
+void gnome_icon_container_unselect_all
+ (GnomeIconContainer *view);
+void gnome_icon_container_select_all (GnomeIconContainer *view);
+
+void gnome_icon_container_enable_browser_mode
+ (GnomeIconContainer *view,
+ gboolean enable);
+
+void gnome_icon_container_set_base_uri
+ (GnomeIconContainer *container,
+ const gchar *base_uri);
+
+void gnome_icon_container_xlate_selected
+ (GnomeIconContainer *container,
+ gint amount_x,
+ gint amount_y,
+ gboolean raise);
+
+GnomeIconContainerLayout *
+ gnome_icon_container_get_layout
+ (GnomeIconContainer *container);
+#endif
diff --git a/libnautilus/gtkflist.c b/libnautilus/gtkflist.c
new file mode 100644
index 000000000..2b5abdd87
--- /dev/null
+++ b/libnautilus/gtkflist.c
@@ -0,0 +1,539 @@
+/* File list widget for the Midnight Commander
+ *
+ * Copyright (C) 1999 The Free Software Foundation
+ *
+ * Author: Federico Mena <federico@nuclecu.unam.mx>
+ * Modified by Ettore Perazzoli <ettore@gnu.org>
+ */
+
+/* FIXME this is a kludge to re-use broken CList. Instead, I'd like to have a
+ native List widget that uses a simple API similiar to the GnomeIconContainer
+ one. */
+
+#include <config.h>
+#include "gtkflist.h"
+
+
+enum {
+ ROW_POPUP_MENU,
+ EMPTY_POPUP_MENU,
+ ACTIVATE,
+ START_DRAG,
+ SELECTION_CHANGED,
+ LAST_SIGNAL
+};
+
+
+static void gtk_flist_class_init (GtkFListClass *class);
+static void gtk_flist_init (GtkFList *flist);
+
+static gint gtk_flist_button_press (GtkWidget *widget, GdkEventButton *event);
+static gint gtk_flist_button_release (GtkWidget *widget, GdkEventButton *event);
+static gint gtk_flist_motion (GtkWidget *widget, GdkEventMotion *event);
+static gint gtk_flist_key (GtkWidget *widget, GdkEventKey *event);
+static void gtk_flist_drag_begin (GtkWidget *widget, GdkDragContext *context);
+static void gtk_flist_drag_end (GtkWidget *widget, GdkDragContext *context);
+static void gtk_flist_drag_data_get (GtkWidget *widget, GdkDragContext *context,
+ GtkSelectionData *data, guint info, guint time);
+static void gtk_flist_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time);
+static gboolean gtk_flist_drag_motion (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time);
+static gboolean gtk_flist_drag_drop (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time);
+static void gtk_flist_drag_data_received (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, GtkSelectionData *data,
+ guint info, guint time);
+
+static void gtk_flist_clear (GtkCList *clist);
+
+
+static GtkCListClass *parent_class;
+
+static guint flist_signals[LAST_SIGNAL];
+
+
+/**
+ * gtk_flist_get_type:
+ * @void:
+ *
+ * Creates the GtkFList class and its type information
+ *
+ * Return value: The type ID for GtkFListClass
+ **/
+GtkType
+gtk_flist_get_type (void)
+{
+ static GtkType flist_type = 0;
+
+ if (!flist_type) {
+ GtkTypeInfo flist_info = {
+ "GtkFList",
+ sizeof (GtkFList),
+ sizeof (GtkFListClass),
+ (GtkClassInitFunc) gtk_flist_class_init,
+ (GtkObjectInitFunc) gtk_flist_init,
+ NULL, /* reserved_1 */
+ NULL, /* reserved_2 */
+ (GtkClassInitFunc) NULL
+ };
+
+ flist_type = gtk_type_unique (gtk_clist_get_type (), &flist_info);
+ }
+
+ return flist_type;
+}
+
+/* Standard class initialization function */
+static void
+gtk_flist_class_init (GtkFListClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkCListClass *clist_class;
+
+ object_class = (GtkObjectClass *) class;
+ widget_class = (GtkWidgetClass *) class;
+ clist_class = (GtkCListClass *) class;
+
+ parent_class = gtk_type_class (gtk_clist_get_type ());
+
+ flist_signals[ROW_POPUP_MENU] =
+ gtk_signal_new ("row_popup_menu",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, row_popup_menu),
+ gtk_marshal_NONE__POINTER,
+ GTK_TYPE_NONE, 1,
+ GTK_TYPE_GDK_EVENT);
+ flist_signals[EMPTY_POPUP_MENU] =
+ gtk_signal_new ("empty_popup_menu",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, empty_popup_menu),
+ gtk_marshal_NONE__POINTER,
+ GTK_TYPE_NONE, 1,
+ GTK_TYPE_GDK_EVENT);
+ flist_signals[ACTIVATE] =
+ gtk_signal_new ("activate",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, activate),
+ gtk_marshal_NONE__POINTER,
+ GTK_TYPE_NONE, 1,
+ GTK_TYPE_POINTER);
+ flist_signals[START_DRAG] =
+ gtk_signal_new ("start_drag",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, start_drag),
+ gtk_marshal_NONE__INT_POINTER,
+ GTK_TYPE_NONE, 2,
+ GTK_TYPE_INT,
+ GTK_TYPE_GDK_EVENT);
+ flist_signals[SELECTION_CHANGED] =
+ gtk_signal_new ("selection_changed",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (GtkFListClass, start_drag),
+ gtk_marshal_NONE__NONE,
+ GTK_TYPE_NONE, 0);
+
+ gtk_object_class_add_signals (object_class, flist_signals, LAST_SIGNAL);
+
+ clist_class->clear = gtk_flist_clear;
+
+ widget_class->button_press_event = gtk_flist_button_press;
+ widget_class->button_release_event = gtk_flist_button_release;
+ widget_class->motion_notify_event = gtk_flist_motion;
+ widget_class->key_press_event = gtk_flist_key;
+ widget_class->key_release_event = gtk_flist_key;
+ widget_class->drag_begin = gtk_flist_drag_begin;
+ widget_class->drag_end = gtk_flist_drag_end;
+ widget_class->drag_data_get = gtk_flist_drag_data_get;
+ widget_class->drag_leave = gtk_flist_drag_leave;
+ widget_class->drag_motion = gtk_flist_drag_motion;
+ widget_class->drag_drop = gtk_flist_drag_drop;
+ widget_class->drag_data_received = gtk_flist_drag_data_received;
+}
+
+/* Standard object initialization function */
+static void
+gtk_flist_init (GtkFList *flist)
+{
+ flist->anchor_row = -1;
+
+ /* GtkCList does not specify pointer motion by default */
+ gtk_widget_add_events (GTK_WIDGET (flist), GDK_POINTER_MOTION_MASK);
+}
+
+static gboolean
+row_selected (GtkFList *flist, gint row)
+{
+ GtkCListRow *elem;
+
+ elem = g_list_nth (GTK_CLIST (flist)->row_list, row)->data;
+
+ return elem->state == GTK_STATE_SELECTED;
+}
+
+/* Selects the rows between the anchor to the specified row, inclusive. */
+static void
+select_range (GtkFList *flist, int row)
+{
+ int min, max;
+ int i;
+
+ if (flist->anchor_row == -1)
+ flist->anchor_row = row;
+
+ if (row < flist->anchor_row) {
+ min = row;
+ max = flist->anchor_row;
+ } else {
+ min = flist->anchor_row;
+ max = row;
+ }
+
+ for (i = min; i <= max; i++)
+ gtk_clist_select_row (GTK_CLIST (flist), i, 0);
+}
+
+/* Handles row selection according to the specified modifier state */
+static void
+select_row (GtkFList *flist, int row, guint state)
+{
+ int range, additive;
+
+ range = (state & GDK_SHIFT_MASK) != 0;
+ additive = (state & GDK_CONTROL_MASK) != 0;
+
+ if (!additive)
+ gtk_clist_unselect_all (GTK_CLIST (flist));
+
+ if (!range) {
+ if (additive) {
+ if (row_selected (flist, row))
+ gtk_clist_unselect_row
+ (GTK_CLIST (flist), row, 0);
+ else
+ gtk_clist_select_row
+ (GTK_CLIST (flist), row, 0);
+ } else {
+ gtk_clist_select_row (GTK_CLIST (flist), row, 0);
+ }
+ flist->anchor_row = row;
+ } else
+ select_range (flist, row);
+
+ gtk_signal_emit (GTK_OBJECT (flist), flist_signals[SELECTION_CHANGED]);
+}
+
+/* Our handler for button_press events. We override all of GtkCList's broken
+ * behavior.
+ */
+static gint
+gtk_flist_button_press (GtkWidget *widget, GdkEventButton *event)
+{
+ GtkFList *flist;
+ GtkCList *clist;
+ int on_row;
+ gint row, col;
+ int retval;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_FLIST (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ flist = GTK_FLIST (widget);
+ clist = GTK_CLIST (widget);
+ retval = FALSE;
+
+ if (event->window != clist->clist_window)
+ return (* GTK_WIDGET_CLASS (parent_class)->button_press_event) (widget, event);
+
+ on_row = gtk_clist_get_selection_info (clist, event->x, event->y, &row, &col);
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button == 1 || event->button == 2) {
+ if (on_row) {
+ /* Save the mouse info for DnD */
+
+ flist->dnd_press_button = event->button;
+ flist->dnd_press_x = event->x;
+ flist->dnd_press_y = event->y;
+
+ /* Handle selection */
+
+ if ((row_selected (flist, row)
+ && !(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
+ || ((event->state & GDK_CONTROL_MASK)
+ && !(event->state & GDK_SHIFT_MASK))) {
+ flist->dnd_select_pending = TRUE;
+ flist->dnd_select_pending_state = event->state;
+ flist->dnd_select_pending_row = row;
+ }
+
+ select_row (flist, row, event->state);
+ } else {
+ gtk_clist_unselect_all (clist);
+ }
+
+ retval = TRUE;
+ } else if (event->button == 3) {
+ if (on_row) {
+ select_row (flist, row, event->state);
+ gtk_signal_emit (GTK_OBJECT (flist),
+ flist_signals[ROW_POPUP_MENU],
+ event);
+ } else
+ gtk_signal_emit (GTK_OBJECT (flist),
+ flist_signals[EMPTY_POPUP_MENU],
+ event);
+
+ retval = TRUE;
+ }
+
+ break;
+
+ case GDK_2BUTTON_PRESS:
+ if (event->button == 1) {
+ GtkCListRow *elem;
+
+ flist->dnd_select_pending = FALSE;
+ flist->dnd_select_pending_state = 0;
+
+ if (on_row) {
+ elem = g_list_nth (GTK_CLIST (flist)->row_list,
+ row)->data;
+ gtk_signal_emit (GTK_OBJECT (flist),
+ flist_signals[ACTIVATE],
+ elem->data);
+ }
+
+ retval = TRUE;
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+/* Our handler for button_release events. We override all of GtkCList's broken
+ * behavior.
+ */
+static gint
+gtk_flist_button_release (GtkWidget *widget, GdkEventButton *event)
+{
+ GtkFList *flist;
+ GtkCList *clist;
+ int on_row;
+ gint row, col;
+ int retval;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_FLIST (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ flist = GTK_FLIST (widget);
+ clist = GTK_CLIST (widget);
+ retval = FALSE;
+
+ if (event->window != clist->clist_window)
+ return (* GTK_WIDGET_CLASS (parent_class)->button_release_event) (widget, event);
+
+ on_row = gtk_clist_get_selection_info (clist, event->x, event->y, &row, &col);
+
+ if (!(event->button == 1 || event->button == 2))
+ return FALSE;
+
+ flist->dnd_press_button = 0;
+ flist->dnd_press_x = 0;
+ flist->dnd_press_y = 0;
+
+ if (on_row) {
+ if (flist->dnd_select_pending) {
+ /* select_row (flist, row, flist->dnd_select_pending_state); */
+ flist->dnd_select_pending = FALSE;
+ flist->dnd_select_pending_state = 0;
+ }
+
+ retval = TRUE;
+ }
+
+ return retval;
+}
+
+/* Our handler for motion_notify events. We override all of GtkCList's broken
+ * behavior.
+ */
+static gint
+gtk_flist_motion (GtkWidget *widget, GdkEventMotion *event)
+{
+ GtkFList *flist;
+ GtkCList *clist;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_FLIST (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ flist = GTK_FLIST (widget);
+ clist = GTK_CLIST (widget);
+
+ if (event->window != clist->clist_window)
+ return (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event) (widget, event);
+
+ if (!((flist->dnd_press_button == 1 && (event->state & GDK_BUTTON1_MASK))
+ || (flist->dnd_press_button == 2 && (event->state & GDK_BUTTON2_MASK))))
+ return FALSE;
+
+ /* This is the same threshold value that is used in gtkdnd.c */
+
+ if (MAX (abs (flist->dnd_press_x - event->x),
+ abs (flist->dnd_press_y - event->y)) <= 3)
+ return FALSE;
+
+ /* Handle any pending selections */
+
+ if (flist->dnd_select_pending) {
+ select_row (flist,
+ flist->dnd_select_pending_row,
+ flist->dnd_select_pending_state);
+
+ flist->dnd_select_pending = FALSE;
+ flist->dnd_select_pending_state = 0;
+ }
+
+ gtk_signal_emit (GTK_OBJECT (flist),
+ flist_signals[START_DRAG],
+ flist->dnd_press_button,
+ event);
+ return TRUE;
+}
+
+/* Our handler for key_press and key_release events. We do nothing, and we do
+ * this to avoid GtkCList's broken behavior.
+ */
+static gint
+gtk_flist_key (GtkWidget *widget, GdkEventKey *event)
+{
+ return FALSE;
+}
+
+/* We override the drag_begin signal to do nothing */
+static void
+gtk_flist_drag_begin (GtkWidget *widget, GdkDragContext *context)
+{
+ /* nothing */
+}
+
+/* We override the drag_end signal to do nothing */
+static void
+gtk_flist_drag_end (GtkWidget *widget, GdkDragContext *context)
+{
+ /* nothing */
+}
+
+/* We override the drag_data_get signal to do nothing */
+static void
+gtk_flist_drag_data_get (GtkWidget *widget, GdkDragContext *context,
+ GtkSelectionData *data, guint info, guint time)
+{
+ /* nothing */
+}
+
+/* We override the drag_leave signal to do nothing */
+static void
+gtk_flist_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time)
+{
+ /* nothing */
+}
+
+/* We override the drag_motion signal to do nothing */
+static gboolean
+gtk_flist_drag_motion (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time)
+{
+ return FALSE;
+}
+
+/* We override the drag_drop signal to do nothing */
+static gboolean
+gtk_flist_drag_drop (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time)
+{
+ return FALSE;
+}
+
+/* We override the drag_data_received signal to do nothing */
+static void
+gtk_flist_drag_data_received (GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, GtkSelectionData *data,
+ guint info, guint time)
+{
+ /* nothing */
+}
+
+/* Our handler for the clear signal of the clist. We have to reset the anchor
+ * to null.
+ */
+static void
+gtk_flist_clear (GtkCList *clist)
+{
+ GtkFList *flist;
+
+ g_return_if_fail (clist != NULL);
+ g_return_if_fail (GTK_IS_FLIST (clist));
+
+ flist = GTK_FLIST (clist);
+ flist->anchor_row = -1;
+
+ if (parent_class->clear)
+ (* parent_class->clear) (clist);
+}
+
+
+/**
+ * gtk_flist_new_with_titles:
+ * @columns: The number of columns in the list
+ * @titles: The titles for the columns
+ *
+ * Return value: The newly-created file list.
+ **/
+GtkWidget *
+gtk_flist_new_with_titles (int columns, char **titles)
+{
+ GtkFList *flist;
+
+ flist = gtk_type_new (gtk_flist_get_type ());
+ gtk_clist_construct (GTK_CLIST (flist), columns, titles);
+
+ gtk_clist_set_selection_mode (GTK_CLIST (flist),
+ GTK_SELECTION_MULTIPLE);
+
+ return GTK_WIDGET (flist);
+}
+
+GList *
+gtk_flist_get_selection (GtkFList *flist)
+{
+ GList *retval;
+ GList *p;
+
+ g_return_val_if_fail (flist != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_FLIST (flist), NULL);
+
+ retval = NULL;
+ for (p = GTK_CLIST (flist)->row_list; p != NULL; p = p->next) {
+ GtkCListRow *row;
+
+ row = p->data;
+ if (row->state == GTK_STATE_SELECTED)
+ retval = g_list_prepend (retval, row->data);
+ }
+
+ return retval;
+}
diff --git a/libnautilus/gtkflist.h b/libnautilus/gtkflist.h
new file mode 100644
index 000000000..ac216deaf
--- /dev/null
+++ b/libnautilus/gtkflist.h
@@ -0,0 +1,70 @@
+/* File list widget for the Midnight Commander
+ *
+ * Copyright (C) 1999 The Free Software Foundation
+ *
+ * Author: Federico Mena <federico@nuclecu.unam.mx>
+ */
+
+#ifndef GTKFLIST_H
+#define GTKFLIST_H
+
+#include "panel.h"
+#include <gtk/gtkclist.h>
+
+
+/* It is sad that we have to do this. GtkCList's behavior is so broken that we
+ * have to override all the event handlers and implement our own selection
+ * behavior. Sigh.
+ */
+
+#define TYPE_GTK_FLIST (gtk_flist_get_type ())
+#define GTK_FLIST(obj) (GTK_CHECK_CAST ((obj), TYPE_GTK_FLIST, GtkFList))
+#define GTK_FLIST_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), TYPE_GTK_FLIST, GtkFListClass))
+#define GTK_IS_FLIST(obj) (GTK_CHECK_TYPE ((obj), TYPE_GTK_FLIST))
+#define GTK_IS_FLIST_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), TYPE_GTK_FLIST))
+
+
+typedef struct _GtkFList GtkFList;
+typedef struct _GtkFListClass GtkFListClass;
+
+struct _GtkFList {
+ GtkCList clist;
+
+ /* The anchor row for range selections */
+ int anchor_row;
+
+ /* Mouse button and position saved on button press */
+ int dnd_press_button;
+ int dnd_press_x, dnd_press_y;
+
+ /* Delayed selection information */
+ int dnd_select_pending;
+ guint dnd_select_pending_state;
+ int dnd_select_pending_row;
+};
+
+struct _GtkFListClass {
+ GtkCListClass parent_class;
+
+ /* Signal: invoke the popup menu for rows */
+ void (* row_popup_menu) (GtkFList *flist, GdkEventButton *event);
+
+ /* Signal: invoke the popup menu for empty areas */
+ void (* empty_popup_menu) (GtkFList *flist, GdkEventButton *event);
+
+ /* Signal: open the file in the selected row */
+ void (* activate) (GtkFList *flist, gpointer data);
+
+ /* Signal: initiate a drag and drop operation */
+ void (* start_drag) (GtkFList *flist, gint button, GdkEvent *event);
+
+ /* Signal: selection has changed */
+ void (* selection_changed) (GtkFList *flist);
+};
+
+
+GtkType gtk_flist_get_type (void);
+GtkWidget *gtk_flist_new_with_titles (int columns, char **titles);
+GList *gtk_flist_get_selection (GtkFList *flist);
+
+#endif
diff --git a/libnautilus/gtkscrollframe.c b/libnautilus/gtkscrollframe.c
new file mode 100644
index 000000000..6f41835ec
--- /dev/null
+++ b/libnautilus/gtkscrollframe.c
@@ -0,0 +1,1210 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This 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.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include <config.h>
+#include <gtk/gtkhscrollbar.h>
+#include <gtk/gtkvscrollbar.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtkviewport.h>
+#include "gtkscrollframe.h"
+
+
+/* scrolled window policy and size requisition handling:
+ *
+ * gtk size requisition works as follows:
+ * a widget upon size-request reports the width and height that it finds
+ * to be best suited to display its contents, including children.
+ * the width and/or height reported from a widget upon size requisition
+ * may be overidden by the user by specifying a width and/or height
+ * other than 0 through gtk_widget_set_usize().
+ *
+ * a scrolled window needs (for imlementing all three policy types) to
+ * request its width and height based on two different rationales.
+ * 1) the user wants the scrolled window to just fit into the space
+ * that it gets allocated for a specifc dimension.
+ * 1.1) this does not apply if the user specified a concrete value
+ * value for that specific dimension by either specifying usize for the
+ * scrolled window or for its child.
+ * 2) the user wants the scrolled window to take as much space up as
+ * is desired by the child for a specifc dimension (i.e. POLICY_NEVER).
+ *
+ * also, kinda obvious:
+ * 3) a user would certainly not have choosen a scrolled window as a container
+ * for the child, if the resulting allocation takes up more space than the
+ * child would have allocated without the scrolled window.
+ *
+ * conclusions:
+ * A) from 1) follows: the scrolled window shouldn't request more space for a
+ * specifc dimension than is required at minimum.
+ * B) from 1.1) follows: the requisition may be overidden by usize of the scrolled
+ * window (done automatically) or by usize of the child (needs to be checked).
+ * C) from 2) follows: for POLICY_NEVER, the scrolled window simply reports the
+ * child's dimension.
+ * D) from 3) follows: the scrolled window child's minimum width and minimum height
+ * under A) at least correspond to the space taken up by its scrollbars.
+ */
+
+/* Object argument IDs */
+enum {
+ ARG_0,
+ ARG_HADJUSTMENT,
+ ARG_VADJUSTMENT,
+ ARG_HSCROLLBAR_POLICY,
+ ARG_VSCROLLBAR_POLICY,
+ ARG_FRAME_PLACEMENT,
+ ARG_SHADOW_TYPE,
+ ARG_SCROLLBAR_SPACING
+};
+
+/* Private part of the GtkScrollFrame structure */
+typedef struct {
+ /* Horizontal and vertical scrollbars */
+ GtkWidget *hsb;
+ GtkWidget *vsb;
+
+ /* Space between scrollbars and frame */
+ guint sb_spacing;
+
+ /* Allocation for frame */
+ guint frame_x;
+ guint frame_y;
+ guint frame_w;
+ guint frame_h;
+
+ /* Scrollbar policy */
+ guint hsb_policy : 2;
+ guint vsb_policy : 2;
+
+ /* Whether scrollbars are visible */
+ guint hsb_visible : 1;
+ guint vsb_visible : 1;
+
+ /* Placement of frame wrt scrollbars */
+ guint frame_placement : 2;
+
+ /* Shadow type for frame */
+ guint shadow_type : 3;
+} ScrollFramePrivate;
+
+
+static void gtk_scroll_frame_class_init (GtkScrollFrameClass *class);
+static void gtk_scroll_frame_init (GtkScrollFrame *sf);
+static void gtk_scroll_frame_set_arg (GtkObject *object, GtkArg *arg, guint arg_id);
+static void gtk_scroll_frame_get_arg (GtkObject *object, GtkArg *arg, guint arg_id);
+static void gtk_scroll_frame_destroy (GtkObject *object);
+static void gtk_scroll_frame_finalize (GtkObject *object);
+
+static void gtk_scroll_frame_map (GtkWidget *widget);
+static void gtk_scroll_frame_unmap (GtkWidget *widget);
+static void gtk_scroll_frame_draw (GtkWidget *widget, GdkRectangle *area);
+static void gtk_scroll_frame_size_request (GtkWidget *widget, GtkRequisition *requisition);
+static void gtk_scroll_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
+static gint gtk_scroll_frame_expose (GtkWidget *widget, GdkEventExpose *event);
+
+static void gtk_scroll_frame_add (GtkContainer *container, GtkWidget *widget);
+static void gtk_scroll_frame_remove (GtkContainer *container, GtkWidget *widget);
+static void gtk_scroll_frame_forall (GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data);
+
+static GtkBinClass *parent_class;
+
+
+/**
+ * gtk_scroll_frame_get_type:
+ * @void:
+ *
+ * Registers the &GtkScrollFrame class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GtkScrollFrame class.
+ **/
+GtkType
+gtk_scroll_frame_get_type (void)
+{
+ static GtkType scroll_frame_type = 0;
+
+ if (!scroll_frame_type) {
+ static const GtkTypeInfo scroll_frame_info = {
+ "GtkScrollFrame",
+ sizeof (GtkScrollFrame),
+ sizeof (GtkScrollFrameClass),
+ (GtkClassInitFunc) gtk_scroll_frame_class_init,
+ (GtkObjectInitFunc) gtk_scroll_frame_init,
+ NULL, /* reserved_1 */
+ NULL, /* reserved_2 */
+ (GtkClassInitFunc) NULL
+ };
+
+ scroll_frame_type = gtk_type_unique (GTK_TYPE_BIN, &scroll_frame_info);
+ }
+
+ return scroll_frame_type;
+}
+
+/* Class initialization function for the scroll frame widget */
+static void
+gtk_scroll_frame_class_init (GtkScrollFrameClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkContainerClass *container_class;
+
+ object_class = (GtkObjectClass *) class;
+ widget_class = (GtkWidgetClass *) class;
+ container_class = (GtkContainerClass *) class;
+
+ parent_class = gtk_type_class (GTK_TYPE_BIN);
+
+ gtk_object_add_arg_type ("GtkScrollFrame::hadjustment",
+ GTK_TYPE_ADJUSTMENT,
+ GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
+ ARG_HADJUSTMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::vadjustment",
+ GTK_TYPE_ADJUSTMENT,
+ GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT,
+ ARG_VADJUSTMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::hscrollbar_policy",
+ GTK_TYPE_POLICY_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_HSCROLLBAR_POLICY);
+ gtk_object_add_arg_type ("GtkScrollFrame::vscrollbar_policy",
+ GTK_TYPE_POLICY_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_VSCROLLBAR_POLICY);
+ gtk_object_add_arg_type ("GtkScrollFrame::frame_placement",
+ GTK_TYPE_CORNER_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_FRAME_PLACEMENT);
+ gtk_object_add_arg_type ("GtkScrollFrame::shadow_type",
+ GTK_TYPE_SHADOW_TYPE,
+ GTK_ARG_READWRITE,
+ ARG_SHADOW_TYPE);
+ gtk_object_add_arg_type ("GtkScrollFrame::scrollbar_spacing",
+ GTK_TYPE_UINT,
+ GTK_ARG_READWRITE,
+ ARG_SCROLLBAR_SPACING);
+
+ object_class->set_arg = gtk_scroll_frame_set_arg;
+ object_class->get_arg = gtk_scroll_frame_get_arg;
+ object_class->destroy = gtk_scroll_frame_destroy;
+ object_class->finalize = gtk_scroll_frame_finalize;
+
+ widget_class->map = gtk_scroll_frame_map;
+ widget_class->unmap = gtk_scroll_frame_unmap;
+ widget_class->draw = gtk_scroll_frame_draw;
+ widget_class->size_request = gtk_scroll_frame_size_request;
+ widget_class->size_allocate = gtk_scroll_frame_size_allocate;
+ widget_class->expose_event = gtk_scroll_frame_expose;
+
+ container_class->add = gtk_scroll_frame_add;
+ container_class->remove = gtk_scroll_frame_remove;
+ container_class->forall = gtk_scroll_frame_forall;
+}
+
+/* Object initialization function for the scroll frame widget */
+static void
+gtk_scroll_frame_init (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ priv = g_new0 (ScrollFramePrivate, 1);
+ sf->priv = priv;
+
+ GTK_WIDGET_SET_FLAGS (sf, GTK_NO_WINDOW);
+
+ gtk_container_set_resize_mode (GTK_CONTAINER (sf), GTK_RESIZE_QUEUE);
+
+ priv->sb_spacing = 3;
+ priv->hsb_policy = GTK_POLICY_ALWAYS;
+ priv->vsb_policy = GTK_POLICY_ALWAYS;
+ priv->frame_placement = GTK_CORNER_TOP_LEFT;
+ priv->shadow_type = GTK_SHADOW_NONE;
+}
+
+/* Set_arg handler for the scroll frame widget */
+static void
+gtk_scroll_frame_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ switch (arg_id) {
+ case ARG_HADJUSTMENT:
+ gtk_scroll_frame_set_hadjustment (sf, GTK_VALUE_POINTER (*arg));
+ break;
+
+ case ARG_VADJUSTMENT:
+ gtk_scroll_frame_set_vadjustment (sf, GTK_VALUE_POINTER (*arg));
+ break;
+
+ case ARG_HSCROLLBAR_POLICY:
+ gtk_scroll_frame_set_policy (sf, GTK_VALUE_ENUM (*arg), priv->vsb_policy);
+ break;
+
+ case ARG_VSCROLLBAR_POLICY:
+ gtk_scroll_frame_set_policy (sf, priv->hsb_policy, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_FRAME_PLACEMENT:
+ gtk_scroll_frame_set_placement (sf, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_SHADOW_TYPE:
+ gtk_scroll_frame_set_shadow_type (sf, GTK_VALUE_ENUM (*arg));
+ break;
+
+ case ARG_SCROLLBAR_SPACING:
+ gtk_scroll_frame_set_scrollbar_spacing (sf, GTK_VALUE_UINT (*arg));
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* Get_arg handler for the scroll frame widget */
+static void
+gtk_scroll_frame_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ switch (arg_id) {
+ case ARG_HADJUSTMENT:
+ GTK_VALUE_POINTER (*arg) = gtk_scroll_frame_get_hadjustment (sf);
+ break;
+
+ case ARG_VADJUSTMENT:
+ GTK_VALUE_POINTER (*arg) = gtk_scroll_frame_get_vadjustment (sf);
+ break;
+
+ case ARG_HSCROLLBAR_POLICY:
+ GTK_VALUE_ENUM (*arg) = priv->hsb_policy;
+ break;
+
+ case ARG_VSCROLLBAR_POLICY:
+ GTK_VALUE_ENUM (*arg) = priv->vsb_policy;
+ break;
+
+ case ARG_FRAME_PLACEMENT:
+ GTK_VALUE_ENUM (*arg) = priv->frame_placement;
+ break;
+
+ case ARG_SHADOW_TYPE:
+ GTK_VALUE_ENUM (*arg) = priv->shadow_type;
+ break;
+
+ case ARG_SCROLLBAR_SPACING:
+ GTK_VALUE_UINT (*arg) = priv->sb_spacing;
+ break;
+
+ default:
+ arg->type = GTK_TYPE_INVALID;
+ break;
+ }
+}
+
+/* Destroy handler for the scroll frame widget */
+static void
+gtk_scroll_frame_destroy (GtkObject *object)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (object));
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ gtk_widget_unparent (priv->hsb);
+ gtk_widget_unparent (priv->vsb);
+ gtk_widget_destroy (priv->hsb);
+ gtk_widget_destroy (priv->vsb);
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+/* Finalize handler for the scroll frame widget */
+static void
+gtk_scroll_frame_finalize (GtkObject *object)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ sf = GTK_SCROLL_FRAME (object);
+ priv = sf->priv;
+
+ gtk_widget_unref (priv->hsb);
+ gtk_widget_unref (priv->vsb);
+
+ g_free (priv);
+
+ if (GTK_OBJECT_CLASS (parent_class)->finalize)
+ (* GTK_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+/* Map handler for the scroll frame widget */
+static void
+gtk_scroll_frame_map (GtkWidget *widget)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ /* chain parent class handler to map self and child */
+ if (GTK_WIDGET_CLASS (parent_class)->map)
+ (* GTK_WIDGET_CLASS (parent_class)->map) (widget);
+
+ if (GTK_WIDGET_VISIBLE (priv->hsb) && !GTK_WIDGET_MAPPED (priv->hsb))
+ gtk_widget_map (priv->hsb);
+
+ if (GTK_WIDGET_VISIBLE (priv->vsb) && !GTK_WIDGET_MAPPED (priv->vsb))
+ gtk_widget_map (priv->vsb);
+}
+
+/* Unmap handler for the scroll frame widget */
+static void
+gtk_scroll_frame_unmap (GtkWidget *widget)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ /* chain parent class handler to unmap self and child */
+ if (GTK_WIDGET_CLASS (parent_class)->unmap)
+ (* GTK_WIDGET_CLASS (parent_class)->unmap) (widget);
+
+ if (GTK_WIDGET_MAPPED (priv->hsb))
+ gtk_widget_unmap (priv->hsb);
+
+ if (GTK_WIDGET_MAPPED (priv->vsb))
+ gtk_widget_unmap (priv->vsb);
+}
+
+/* Draws the shadow of a scroll frame widget */
+static void
+draw_shadow (GtkScrollFrame *sf, GdkRectangle *area)
+{
+ ScrollFramePrivate *priv;
+
+ g_assert (area != NULL);
+
+ priv = sf->priv;
+
+ gtk_paint_shadow (GTK_WIDGET (sf)->style,
+ GTK_WIDGET (sf)->window,
+ GTK_STATE_NORMAL, priv->shadow_type,
+ area, GTK_WIDGET (sf),
+ "scroll_frame",
+ priv->frame_x, priv->frame_y,
+ priv->frame_w, priv->frame_h);
+}
+
+/* Draw handler for the scroll frame widget */
+static void
+gtk_scroll_frame_draw (GtkWidget *widget, GdkRectangle *area)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GdkRectangle child_area;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (area != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ if (GTK_WIDGET_DRAWABLE (widget))
+ draw_shadow (sf, area);
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)
+ && gtk_widget_intersect (bin->child, area, &child_area))
+ gtk_widget_draw (bin->child, &child_area);
+
+ if (GTK_WIDGET_VISIBLE (priv->hsb)
+ && gtk_widget_intersect (priv->hsb, area, &child_area))
+ gtk_widget_draw (priv->hsb, &child_area);
+
+ if (GTK_WIDGET_VISIBLE (priv->vsb)
+ && gtk_widget_intersect (priv->vsb, area, &child_area))
+ gtk_widget_draw (priv->vsb, &child_area);
+}
+
+/* Forall handler for the scroll frame widget */
+static void
+gtk_scroll_frame_forall (GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (container));
+ g_return_if_fail (callback != NULL);
+
+ sf = GTK_SCROLL_FRAME (container);
+ priv = sf->priv;
+
+ if (GTK_CONTAINER_CLASS (parent_class)->forall)
+ (* GTK_CONTAINER_CLASS (parent_class)->forall) (
+ container, include_internals,
+ callback, callback_data);
+
+ if (include_internals) {
+ if (priv->vsb)
+ (* callback) (priv->vsb, callback_data);
+
+ if (priv->hsb)
+ (* callback) (priv->hsb, callback_data);
+ }
+}
+
+/* Size_request handler for the scroll frame widget */
+static void
+gtk_scroll_frame_size_request (GtkWidget *widget, GtkRequisition *requisition)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ gint extra_width;
+ gint extra_height;
+ GtkRequisition hsb_requisition;
+ GtkRequisition vsb_requisition;
+ GtkRequisition child_requisition;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (requisition != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ extra_width = 0;
+ extra_height = 0;
+
+ requisition->width = GTK_CONTAINER (widget)->border_width * 2;
+ requisition->height = GTK_CONTAINER (widget)->border_width * 2;
+
+ if (priv->shadow_type != GTK_SHADOW_NONE) {
+ requisition->width += 2 * widget->style->klass->xthickness;
+ requisition->height += 2 * widget->style->klass->ythickness;
+ }
+
+ gtk_widget_size_request (priv->hsb, &hsb_requisition);
+ gtk_widget_size_request (priv->vsb, &vsb_requisition);
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) {
+ static guint quark_aux_info;
+
+ if (!quark_aux_info)
+ quark_aux_info = g_quark_from_static_string ("gtk-aux-info");
+
+ gtk_widget_size_request (bin->child, &child_requisition);
+
+ if (priv->hsb_policy == GTK_POLICY_NEVER)
+ requisition->width += child_requisition.width;
+ else {
+ GtkWidgetAuxInfo *aux_info;
+
+ aux_info = gtk_object_get_data_by_id (GTK_OBJECT (bin->child),
+ quark_aux_info);
+ if (aux_info && aux_info->width > 0) {
+ requisition->width += aux_info->width;
+ extra_width = -1;
+ } else
+ requisition->width += vsb_requisition.width;
+ }
+
+ if (priv->vsb_policy == GTK_POLICY_NEVER)
+ requisition->height += child_requisition.height;
+ else {
+ GtkWidgetAuxInfo *aux_info;
+
+ aux_info = gtk_object_get_data_by_id (GTK_OBJECT (bin->child),
+ quark_aux_info);
+ if (aux_info && aux_info->height > 0) {
+ requisition->height += aux_info->height;
+ extra_height = -1;
+ } else
+ requisition->height += hsb_requisition.height;
+ }
+ }
+
+ if (priv->hsb_policy == GTK_POLICY_AUTOMATIC || GTK_WIDGET_VISIBLE (priv->hsb)) {
+ requisition->width = MAX (requisition->width, hsb_requisition.width);
+ if (!extra_height || GTK_WIDGET_VISIBLE (priv->hsb))
+ extra_height = priv->sb_spacing + hsb_requisition.height;
+ }
+
+ if (priv->vsb_policy == GTK_POLICY_AUTOMATIC || GTK_WIDGET_VISIBLE (priv->vsb)) {
+ requisition->height = MAX (requisition->height, vsb_requisition.height);
+ if (!extra_width || GTK_WIDGET_VISIBLE (priv->vsb))
+ extra_width = priv->sb_spacing + vsb_requisition.width;
+ }
+
+ requisition->width += MAX (0, extra_width);
+ requisition->height += MAX (0, extra_height);
+}
+
+/* Computes the relative allocation for the scroll frame widget */
+static void
+compute_relative_allocation (GtkWidget *widget, GtkAllocation *allocation)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_assert (widget != NULL);
+ g_assert (GTK_IS_SCROLL_FRAME (widget));
+ g_assert (allocation != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+
+ allocation->x = GTK_CONTAINER (widget)->border_width;
+ allocation->y = GTK_CONTAINER (widget)->border_width;
+ allocation->width = MAX (1, (gint) widget->allocation.width - allocation->x * 2);
+ allocation->height = MAX (1, (gint) widget->allocation.height - allocation->y * 2);
+
+ if (priv->vsb_visible) {
+ GtkRequisition vsb_requisition;
+
+ gtk_widget_get_child_requisition (priv->vsb, &vsb_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_TOP_RIGHT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_RIGHT)
+ allocation->x += vsb_requisition.width + priv->sb_spacing;
+
+ allocation->width = MAX (1, ((gint) allocation->width
+ - ((gint) vsb_requisition.width + priv->sb_spacing)));
+ }
+
+ if (priv->hsb_visible) {
+ GtkRequisition hsb_requisition;
+
+ gtk_widget_get_child_requisition (priv->hsb, &hsb_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_BOTTOM_LEFT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_RIGHT)
+ allocation->y += hsb_requisition.height + priv->sb_spacing;
+
+ allocation->height = MAX (1, ((gint) allocation->height
+ - ((gint) hsb_requisition.height + priv->sb_spacing)));
+ }
+}
+
+/* Size_allocate handler for the scroll frame widget */
+static void
+gtk_scroll_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GtkAllocation relative_allocation;
+ GtkAllocation child_allocation;
+ gint xthickness, ythickness;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (widget));
+ g_return_if_fail (allocation != NULL);
+
+ sf = GTK_SCROLL_FRAME (widget);
+ priv = sf->priv;
+ bin = GTK_BIN (widget);
+
+ widget->allocation = *allocation;
+
+ if (priv->hsb_policy == GTK_POLICY_ALWAYS)
+ priv->hsb_visible = TRUE;
+ else if (priv->hsb_policy == GTK_POLICY_NEVER)
+ priv->hsb_visible = FALSE;
+
+ if (priv->vsb_policy == GTK_POLICY_ALWAYS)
+ priv->vsb_visible = TRUE;
+ else if (priv->vsb_policy == GTK_POLICY_NEVER)
+ priv->vsb_visible = FALSE;
+
+ if (priv->shadow_type == GTK_SHADOW_NONE) {
+ xthickness = 0;
+ ythickness = 0;
+ } else {
+ xthickness = widget->style->klass->xthickness;
+ ythickness = widget->style->klass->ythickness;
+ }
+
+ if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) {
+ gboolean previous_hvis;
+ gboolean previous_vvis;
+ guint count = 0;
+
+ do {
+ compute_relative_allocation (widget, &relative_allocation);
+
+ priv->frame_x = relative_allocation.x + allocation->x;
+ priv->frame_y = relative_allocation.y + allocation->y;
+ priv->frame_w = relative_allocation.width;
+ priv->frame_h = relative_allocation.height;
+
+ child_allocation.x = priv->frame_x + xthickness;
+ child_allocation.y = priv->frame_y + ythickness;
+ child_allocation.width = priv->frame_w - 2 * xthickness;
+ child_allocation.height = priv->frame_h - 2 * ythickness;
+
+ previous_hvis = priv->hsb_visible;
+ previous_vvis = priv->vsb_visible;
+
+ gtk_widget_size_allocate (bin->child, &child_allocation);
+
+ /* If, after the first iteration, the hscrollbar and the
+ * vscrollbar flip visiblity, then we need both.
+ */
+ if (count
+ && previous_hvis != priv->hsb_visible
+ && previous_vvis != priv->vsb_visible) {
+ priv->hsb_visible = TRUE;
+ priv->vsb_visible = TRUE;
+
+ /* a new resize is already queued at this point,
+ * so we will immediatedly get reinvoked
+ */
+ return;
+ }
+
+ count++;
+ } while (previous_hvis != priv->hsb_visible
+ || previous_vvis != priv->vsb_visible);
+ } else
+ compute_relative_allocation (widget, &relative_allocation);
+
+ if (priv->hsb_visible) {
+ GtkRequisition hscrollbar_requisition;
+
+ gtk_widget_get_child_requisition (priv->hsb, &hscrollbar_requisition);
+
+ if (!GTK_WIDGET_VISIBLE (priv->hsb))
+ gtk_widget_show (priv->hsb);
+
+ child_allocation.x = relative_allocation.x;
+ if (priv->frame_placement == GTK_CORNER_TOP_LEFT
+ || priv->frame_placement == GTK_CORNER_TOP_RIGHT)
+ child_allocation.y = (relative_allocation.y
+ + relative_allocation.height
+ + priv->sb_spacing);
+ else
+ child_allocation.y = GTK_CONTAINER (sf)->border_width;
+
+ child_allocation.width = relative_allocation.width;
+ child_allocation.height = hscrollbar_requisition.height;
+ child_allocation.x += allocation->x;
+ child_allocation.y += allocation->y;
+
+ gtk_widget_size_allocate (priv->hsb, &child_allocation);
+ } else if (GTK_WIDGET_VISIBLE (priv->hsb))
+ gtk_widget_hide (priv->hsb);
+
+ if (priv->vsb_visible) {
+ GtkRequisition vscrollbar_requisition;
+
+ if (!GTK_WIDGET_VISIBLE (priv->vsb))
+ gtk_widget_show (priv->vsb);
+
+ gtk_widget_get_child_requisition (priv->vsb, &vscrollbar_requisition);
+
+ if (priv->frame_placement == GTK_CORNER_TOP_LEFT
+ || priv->frame_placement == GTK_CORNER_BOTTOM_LEFT)
+ child_allocation.x = (relative_allocation.x
+ + relative_allocation.width
+ + priv->sb_spacing);
+ else
+ child_allocation.x = GTK_CONTAINER (sf)->border_width;
+
+ child_allocation.y = relative_allocation.y;
+ child_allocation.width = vscrollbar_requisition.width;
+ child_allocation.height = relative_allocation.height;
+ child_allocation.x += allocation->x;
+ child_allocation.y += allocation->y;
+
+ gtk_widget_size_allocate (priv->vsb, &child_allocation);
+ } else if (GTK_WIDGET_VISIBLE (priv->vsb))
+ gtk_widget_hide (priv->vsb);
+}
+
+/* Expose handler for the scroll frame widget */
+static gint
+gtk_scroll_frame_expose (GtkWidget *widget, GdkEventExpose *event)
+{
+ GtkScrollFrame *sf;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ sf = GTK_SCROLL_FRAME (widget);
+
+ if (GTK_WIDGET_DRAWABLE (widget))
+ draw_shadow (sf, &event->area);
+
+ if (GTK_WIDGET_CLASS (parent_class)->expose_event)
+ (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
+
+ return FALSE;
+}
+
+/* Add handler for the scroll frame widget */
+static void
+gtk_scroll_frame_add (GtkContainer *container, GtkWidget *child)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+
+ sf = GTK_SCROLL_FRAME (container);
+ priv = sf->priv;
+ bin = GTK_BIN (container);
+ g_return_if_fail (bin->child == NULL);
+
+ bin->child = child;
+ gtk_widget_set_parent (child, GTK_WIDGET (bin));
+
+ /* this is a temporary message */
+ if (!gtk_widget_set_scroll_adjustments (child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb))))
+ g_warning ("gtk_scroll_frame_add(): cannot add non scrollable widget "
+ "use gtk_scroll_frame_add_with_viewport() instead");
+
+ if (GTK_WIDGET_REALIZED (child->parent))
+ gtk_widget_realize (child);
+
+ if (GTK_WIDGET_VISIBLE (child->parent) && GTK_WIDGET_VISIBLE (child)) {
+ if (GTK_WIDGET_MAPPED (child->parent))
+ gtk_widget_map (child);
+
+ gtk_widget_queue_resize (child);
+ }
+}
+
+/* Remove method for the scroll frame widget */
+static void
+gtk_scroll_frame_remove (GtkContainer *container, GtkWidget *child)
+{
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (container));
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_BIN (container)->child == child);
+
+ gtk_widget_set_scroll_adjustments (child, NULL, NULL);
+
+ /* chain parent class handler to remove child */
+ if (GTK_CONTAINER_CLASS (parent_class)->remove)
+ (* GTK_CONTAINER_CLASS (parent_class)->remove) (container, child);
+}
+
+/**
+ * gtk_scroll_frame_new:
+ * @hadj: If non-NULL, the adjustment to use for horizontal scrolling.
+ * @vadj: If non-NULL, the adjustment to use for vertical scrolling.
+ *
+ * Creates a new scroll frame widget.
+ *
+ * Return value: The newly-created scroll frame widget.
+ **/
+GtkWidget *
+gtk_scroll_frame_new (GtkAdjustment *hadj, GtkAdjustment *vadj)
+{
+ if (hadj)
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (hadj), NULL);
+
+ if (vadj)
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (vadj), NULL);
+
+ return gtk_widget_new (GTK_TYPE_SCROLL_FRAME,
+ "hadjustment", hadj,
+ "vadjustment", vadj,
+ NULL);
+}
+
+/* Callback used when one of the scroll frame widget's adjustments changes */
+static void
+adjustment_changed (GtkAdjustment *adj, gpointer data)
+{
+ GtkScrollFrame *sf;
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (adj != NULL);
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ g_return_if_fail (data != NULL);
+
+ sf = GTK_SCROLL_FRAME (data);
+ priv = sf->priv;
+
+ if (adj == gtk_range_get_adjustment (GTK_RANGE (priv->hsb))) {
+ if (priv->hsb_policy == GTK_POLICY_AUTOMATIC) {
+ gboolean visible;
+
+ visible = priv->hsb_visible;
+ priv->hsb_visible = (adj->upper - adj->lower > adj->page_size);
+ if (priv->hsb_visible != visible)
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+ }
+ } else if (adj == gtk_range_get_adjustment (GTK_RANGE (priv->vsb))) {
+ if (priv->vsb_policy == GTK_POLICY_AUTOMATIC) {
+ gboolean visible;
+
+ visible = priv->vsb_visible;
+ priv->vsb_visible = (adj->upper - adj->lower > adj->page_size);
+ if (priv->vsb_visible != visible)
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+ }
+ }
+}
+
+/**
+ * gtk_scroll_frame_set_hadjustment:
+ * @sf: A scroll frame widget.
+ * @adj: An adjustment.
+ *
+ * Sets the adjustment to be used for horizontal scrolling in a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_hadjustment (GtkScrollFrame *sf, GtkAdjustment *adj)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (adj)
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ else
+ adj = GTK_ADJUSTMENT (gtk_object_new (GTK_TYPE_ADJUSTMENT, NULL));
+
+ if (!priv->hsb) {
+ gtk_widget_push_composite_child ();
+ priv->hsb = gtk_hscrollbar_new (adj);
+ gtk_widget_set_composite_name (priv->hsb, "hscrollbar");
+ gtk_widget_pop_composite_child ();
+
+ gtk_widget_set_parent (priv->hsb, GTK_WIDGET (sf));
+ gtk_widget_ref (priv->hsb);
+ gtk_widget_show (priv->hsb);
+ } else {
+ GtkAdjustment *old_adj;
+
+ old_adj = gtk_range_get_adjustment (GTK_RANGE (priv->hsb));
+ if (old_adj == adj)
+ return;
+
+ gtk_signal_disconnect_by_func (GTK_OBJECT (old_adj),
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ gtk_range_set_adjustment (GTK_RANGE (priv->hsb), adj);
+ }
+
+ adj = gtk_range_get_adjustment (GTK_RANGE (priv->hsb));
+ gtk_signal_connect (GTK_OBJECT (adj),
+ "changed",
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ adjustment_changed (adj, sf);
+
+ if (GTK_BIN (sf)->child)
+ gtk_widget_set_scroll_adjustments (
+ GTK_BIN (sf)->child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb)));
+}
+
+/**
+ * gtk_scroll_frame_set_vadjustment:
+ * @sf: A scroll frame widget.
+ * @adj: An adjustment.
+ *
+ * Sets the adjustment to be used for vertical scrolling in a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_vadjustment (GtkScrollFrame *sf, GtkAdjustment *adj)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (adj)
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
+ else
+ adj = GTK_ADJUSTMENT (gtk_object_new (GTK_TYPE_ADJUSTMENT, NULL));
+
+ if (!priv->vsb) {
+ gtk_widget_push_composite_child ();
+ priv->vsb = gtk_vscrollbar_new (adj);
+ gtk_widget_set_composite_name (priv->vsb, "vscrollbar");
+ gtk_widget_pop_composite_child ();
+
+ gtk_widget_set_parent (priv->vsb, GTK_WIDGET (sf));
+ gtk_widget_ref (priv->vsb);
+ gtk_widget_show (priv->vsb);
+ } else {
+ GtkAdjustment *old_adj;
+
+ old_adj = gtk_range_get_adjustment (GTK_RANGE (priv->vsb));
+ if (old_adj == adj)
+ return;
+
+ gtk_signal_disconnect_by_func (GTK_OBJECT (old_adj),
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ gtk_range_set_adjustment (GTK_RANGE (priv->vsb), adj);
+ }
+
+ adj = gtk_range_get_adjustment (GTK_RANGE (priv->vsb));
+ gtk_signal_connect (GTK_OBJECT (adj),
+ "changed",
+ GTK_SIGNAL_FUNC (adjustment_changed),
+ sf);
+ adjustment_changed (adj, sf);
+
+ if (GTK_BIN (sf)->child)
+ gtk_widget_set_scroll_adjustments (
+ GTK_BIN (sf)->child,
+ gtk_range_get_adjustment (GTK_RANGE (priv->hsb)),
+ gtk_range_get_adjustment (GTK_RANGE (priv->vsb)));
+}
+
+/**
+ * gtk_scroll_frame_get_hadjustment:
+ * @sf: A scroll frame widget.
+ *
+ * Queries the horizontal adjustment of a scroll frame widget.
+ *
+ * Return value: The horizontal adjustment of the scroll frame, or NULL if none.
+ **/
+GtkAdjustment *
+gtk_scroll_frame_get_hadjustment (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_val_if_fail (sf != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (sf), NULL);
+
+ priv = sf->priv;
+
+ return priv->hsb ? gtk_range_get_adjustment (GTK_RANGE (priv->hsb)) : NULL;
+}
+
+/**
+ * gtk_scroll_frame_get_vadjustment:
+ * @sf: A scroll frame widget.
+ *
+ * Queries the vertical adjustment of a scroll frame widget.
+ *
+ * Return value: The vertical adjustment of the scroll frame, or NULL if none.
+ **/
+GtkAdjustment *
+gtk_scroll_frame_get_vadjustment (GtkScrollFrame *sf)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_val_if_fail (sf != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_SCROLL_FRAME (sf), NULL);
+
+ priv = sf->priv;
+
+ return priv->vsb ? gtk_range_get_adjustment (GTK_RANGE (priv->vsb)) : NULL;
+}
+
+/**
+ * gtk_scroll_frame_set_policy:
+ * @sf: A scroll frame widget.
+ * @hsb_policy: Policy for the horizontal scrollbar.
+ * @vsb_policy: Policy for the vertical scrollbar.
+ *
+ * Sets the scrollbar policies of a scroll frame widget. These determine when
+ * the scrollbars are to be shown or hidden.
+ **/
+void
+gtk_scroll_frame_set_policy (GtkScrollFrame *sf,
+ GtkPolicyType hsb_policy,
+ GtkPolicyType vsb_policy)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->hsb_policy == hsb_policy && priv->vsb_policy == vsb_policy)
+ return;
+
+ priv->hsb_policy = hsb_policy;
+ priv->vsb_policy = vsb_policy;
+
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_placement:
+ * @sf: A scroll frame widget.
+ * @frame_placement: Placement for the frame.
+ *
+ * Sets the placement of a scroll frame widget's frame with respect to its
+ * scrollbars.
+ **/
+void
+gtk_scroll_frame_set_placement (GtkScrollFrame *sf, GtkCornerType frame_placement)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->frame_placement == frame_placement)
+ return;
+
+ priv->frame_placement = frame_placement;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_shadow_type:
+ * @sf: A scroll frame widget.
+ * @shadow_type: A shadow type.
+ *
+ * Sets the shadow type of a scroll frame widget. You can use this when you
+ * insert a child that does not paint a frame on its own.
+ **/
+void
+gtk_scroll_frame_set_shadow_type (GtkScrollFrame *sf, GtkShadowType shadow_type)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+ g_return_if_fail (shadow_type >= GTK_SHADOW_NONE && shadow_type <= GTK_SHADOW_ETCHED_OUT);
+
+ priv = sf->priv;
+
+ if (priv->shadow_type == shadow_type)
+ return;
+
+ priv->shadow_type = shadow_type;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_set_scrollbar_spacing:
+ * @sf: A scroll frame widget.
+ * @spacing: Desired spacing in pixels.
+ *
+ * Sets the spacing between the frame and the scrollbars of a scroll frame
+ * widget.
+ **/
+void
+gtk_scroll_frame_set_scrollbar_spacing (GtkScrollFrame *sf, guint spacing)
+{
+ ScrollFramePrivate *priv;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+
+ priv = sf->priv;
+
+ if (priv->sb_spacing == spacing)
+ return;
+
+ priv->sb_spacing = spacing;
+ gtk_widget_queue_resize (GTK_WIDGET (sf));
+}
+
+/**
+ * gtk_scroll_frame_add_with_viewport:
+ * @sf: A scroll frame widget.
+ * @child: A widget.
+ *
+ * Creates a &GtkViewport and puts the specified child inside it, thus allowing
+ * the viewport to be scrolled by the scroll frame widget. This is meant to be
+ * used only when a child does not support the scrolling interface.
+ **/
+void
+gtk_scroll_frame_add_with_viewport (GtkScrollFrame *sf, GtkWidget *child)
+{
+ ScrollFramePrivate *priv;
+ GtkBin *bin;
+ GtkWidget *viewport;
+
+ g_return_if_fail (sf != NULL);
+ g_return_if_fail (GTK_IS_SCROLL_FRAME (sf));
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (child));
+ g_return_if_fail (child->parent == NULL);
+
+ priv = sf->priv;
+ bin = GTK_BIN (sf);
+
+ if (bin->child != NULL) {
+ g_return_if_fail (GTK_IS_VIEWPORT (bin->child));
+ g_return_if_fail (GTK_BIN (bin->child)->child == NULL);
+
+ viewport = bin->child;
+ } else {
+ viewport = gtk_viewport_new (gtk_scroll_frame_get_hadjustment (sf),
+ gtk_scroll_frame_get_vadjustment (sf));
+ gtk_container_add (GTK_CONTAINER (sf), viewport);
+ }
+
+ gtk_widget_show (viewport);
+ gtk_container_add (GTK_CONTAINER (viewport), child);
+}
diff --git a/libnautilus/gtkscrollframe.h b/libnautilus/gtkscrollframe.h
new file mode 100644
index 000000000..facb59dac
--- /dev/null
+++ b/libnautilus/gtkscrollframe.h
@@ -0,0 +1,93 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This 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.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __GTK_SCROLL_FRAME_H__
+#define __GTK_SCROLL_FRAME_H__
+
+
+#include <gdk/gdk.h>
+#include <gtk/gtkbin.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define GTK_TYPE_SCROLL_FRAME (gtk_scroll_frame_get_type ())
+#define GTK_SCROLL_FRAME(obj) (GTK_CHECK_CAST ((obj), \
+ GTK_TYPE_SCROLL_FRAME, GtkScrollFrame))
+#define GTK_SCROLL_FRAME_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), \
+ GTK_TYPE_SCROLL_FRAME, GtkScrollFrameClass))
+#define GTK_IS_SCROLL_FRAME(obj) (GTK_CHECK_TYPE ((obj), \
+ GTK_TYPE_SCROLL_FRAME))
+#define GTK_IS_SCROLL_FRAME_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), \
+ GTK_TYPE_SCROLL_FRAME))
+
+
+typedef struct _GtkScrollFrame GtkScrollFrame;
+typedef struct _GtkScrollFrameClass GtkScrollFrameClass;
+
+struct _GtkScrollFrame
+{
+ GtkBin bin;
+
+ /* Private data */
+ gpointer priv;
+};
+
+struct _GtkScrollFrameClass
+{
+ GtkBinClass parent_class;
+};
+
+
+GtkType gtk_scroll_frame_get_type (void);
+GtkWidget *gtk_scroll_frame_new (GtkAdjustment *hadj, GtkAdjustment *vadj);
+
+void gtk_scroll_frame_set_hadjustment (GtkScrollFrame *sf, GtkAdjustment *adj);
+void gtk_scroll_frame_set_vadjustment (GtkScrollFrame *sf, GtkAdjustment *adj);
+
+GtkAdjustment *gtk_scroll_frame_get_hadjustment (GtkScrollFrame *sf);
+GtkAdjustment *gtk_scroll_frame_get_vadjustment (GtkScrollFrame *sf);
+
+void gtk_scroll_frame_set_policy (GtkScrollFrame *sf,
+ GtkPolicyType hsb_policy,
+ GtkPolicyType vsb_policy);
+
+void gtk_scroll_frame_set_placement (GtkScrollFrame *sf, GtkCornerType frame_placement);
+void gtk_scroll_frame_set_shadow_type (GtkScrollFrame *sf, GtkShadowType shadow_type);
+void gtk_scroll_frame_set_scrollbar_spacing (GtkScrollFrame *sf, guint spacing);
+
+void gtk_scroll_frame_add_with_viewport (GtkScrollFrame *sf, GtkWidget *child);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __GTK_SCROLL_FRAME_H__ */
diff --git a/src/file-manager/dfos-corba.c b/src/file-manager/dfos-corba.c
new file mode 100644
index 000000000..517a702fe
--- /dev/null
+++ b/src/file-manager/dfos-corba.c
@@ -0,0 +1,254 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* dfos-corba.c - Implementation of the GNOME::Desktop::FileOperationService
+ CORBA server.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Ettore Perazzoli <ettore@gnu.org> */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libgnorba/gnorba.h>
+
+#include "dfos.h"
+
+
+struct _FileOperationServiceServant {
+ POA_GNOME_Desktop_FileOperationService servant;
+ PortableServer_POA poa;
+ DFOS *dfos;
+};
+typedef struct _FileOperationServiceServant FileOperationServiceServant;
+
+static PortableServer_ServantBase__epv FileOperationService_base_epv;
+static POA_GNOME_Desktop_FileOperationService__epv FileOperationService_epv;
+static POA_GNOME_Desktop_FileOperationService__vepv FileOperationService_vepv;
+
+
+/* Utility functions. */
+
+static GList *
+file_name_list_to_g_list (const GNOME_Desktop_FileOperationService_FileNameList
+ *file_list)
+{
+ GList *new;
+ guint i;
+
+ new = NULL;
+
+ i = file_list->_length;
+ while (i > 0) {
+ i--;
+ new = g_list_prepend (new, file_list->_buffer[i]);
+ }
+
+ return new;
+}
+
+
+/* CORBA -> VFS parameter conversion routines. */
+
+static GnomeVFSXferOptions
+convert_options (GNOME_Desktop_FileOperationService_XferOptions options)
+{
+ GnomeVFSXferOptions returned_options;
+
+ returned_options = 0;
+
+ if (options & GNOME_Desktop_FileOperationService_XferOptionPreserve)
+ returned_options |= GNOME_VFS_XFER_PRESERVE;
+ if (options & GNOME_Desktop_FileOperationService_XferOptionFollowLinks)
+ returned_options |= GNOME_VFS_XFER_FOLLOWLINKS;
+ if (options & GNOME_Desktop_FileOperationService_XferOptionWithParents)
+ returned_options |= GNOME_VFS_XFER_WITHPARENTS;
+ if (options & GNOME_Desktop_FileOperationService_XferOptionRecursive)
+ returned_options |= GNOME_VFS_XFER_RECURSIVE;
+ if (options & GNOME_Desktop_FileOperationService_XferOptionSameFS)
+ returned_options |= GNOME_VFS_XFER_SAMEFS;
+ if (options & GNOME_Desktop_FileOperationService_XferOptionSparseAlways)
+ returned_options |= GNOME_VFS_XFER_SPARSE_ALWAYS;
+ if (options & GNOME_Desktop_FileOperationService_XferOptionSparseNever)
+ returned_options |= GNOME_VFS_XFER_SPARSE_NEVER;
+ if (options & GNOME_Desktop_FileOperationService_XferOptionUpdateMode)
+ returned_options |= GNOME_VFS_XFER_UPDATEMODE;
+ if (options & GNOME_Desktop_FileOperationService_XferOptionRemoveSource)
+ returned_options |= GNOME_VFS_XFER_REMOVESOURCE;
+
+ return returned_options;
+}
+
+static GnomeVFSXferOverwriteMode
+convert_overwrite_mode (GNOME_Desktop_FileOperationService_XferOverwriteMode mode)
+{
+ switch (mode) {
+ case GNOME_Desktop_FileOperationService_XferOverwriteAbort:
+ return GNOME_VFS_XFER_OVERWRITE_MODE_ABORT;
+ case GNOME_Desktop_FileOperationService_XferOverwriteQuery:
+ return GNOME_VFS_XFER_OVERWRITE_MODE_QUERY;
+ case GNOME_Desktop_FileOperationService_XferOverwriteReplace:
+ return GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE;
+ case GNOME_Desktop_FileOperationService_XferOverwriteSkip:
+ return GNOME_VFS_XFER_OVERWRITE_MODE_SKIP;
+ default:
+ g_warning (_("Unknown XferOverwriteMode %d"), mode);
+ return GNOME_VFS_XFER_OVERWRITE_MODE_ABORT;
+ }
+}
+
+static GNOME_Desktop_FileOperationService_XferErrorMode
+convert_error_mode (GnomeVFSXferErrorMode mode)
+{
+ switch (mode) {
+ case GNOME_Desktop_FileOperationService_XferErrorAbort:
+ return GNOME_VFS_XFER_ERROR_MODE_ABORT;
+ case GNOME_Desktop_FileOperationService_XferErrorQuery:
+ return GNOME_VFS_XFER_ERROR_MODE_QUERY;
+ default:
+ g_warning (_("Unknown XferErrorMode %d"), mode);
+ return GNOME_VFS_XFER_ERROR_MODE_ABORT;
+ }
+}
+
+
+/* GNOME::Desktop::FileOperationService method implementations. */
+
+static void
+impl_FileOperationService_xfer (PortableServer_Servant servant,
+ const CORBA_char *source_directory_uri,
+ const GNOME_Desktop_FileOperationService_FileNameList *source_file_names,
+ const CORBA_char * target_directory_uri,
+ const GNOME_Desktop_FileOperationService_FileNameList *target_file_names,
+ const GNOME_Desktop_FileOperationService_XferOptions options,
+ const GNOME_Desktop_FileOperationService_XferErrorMode error_mode,
+ const GNOME_Desktop_FileOperationService_XferOverwriteMode overwrite_mode,
+ CORBA_Environment *ev)
+{
+ GList *source_file_name_list;
+ GList *target_file_name_list;
+ GnomeVFSXferOptions vfs_options;
+ GnomeVFSXferOverwriteMode vfs_overwrite_mode;
+ GnomeVFSXferErrorMode vfs_error_mode;
+ DFOS *dfos;
+
+ dfos = ((FileOperationServiceServant *) servant)->dfos;
+
+ source_file_name_list = file_name_list_to_g_list (source_file_names);
+ target_file_name_list = file_name_list_to_g_list (target_file_names);
+
+ vfs_options = convert_options (options);
+ vfs_error_mode = convert_error_mode (error_mode);
+ vfs_overwrite_mode = convert_overwrite_mode (overwrite_mode);
+
+ dfos_xfer (dfos,
+ source_directory_uri, source_file_name_list,
+ target_directory_uri, target_file_name_list,
+ vfs_options, vfs_error_mode, vfs_overwrite_mode);
+
+ /* Notice that we don't have to deallocate the strings, because we have
+ copied pointers from the CORBA parameters which we are not supposed
+ to free. */
+ g_list_free (source_file_name_list);
+ g_list_free (target_file_name_list);
+}
+
+
+static GNOME_Desktop_FileOperationService
+create_server (DFOS *dfos,
+ PortableServer_POA poa,
+ CORBA_Environment *ev)
+{
+ FileOperationServiceServant *servant;
+
+ /* Set up vtables. */
+
+ FileOperationService_base_epv._private = NULL;
+ FileOperationService_base_epv.finalize = NULL;
+ FileOperationService_base_epv.default_POA = NULL;
+
+ FileOperationService_epv.xfer = impl_FileOperationService_xfer;
+
+ FileOperationService_vepv._base_epv = &FileOperationService_base_epv;
+ FileOperationService_vepv.GNOME_Desktop_FileOperationService_epv =
+ &FileOperationService_epv;
+
+ servant = g_new0 (FileOperationServiceServant, 1);
+ servant->servant.vepv = &FileOperationService_vepv;
+ servant->poa = poa;
+ servant->dfos = dfos;
+
+ POA_GNOME_Desktop_FileOperationService__init
+ ((PortableServer_Servant) servant, ev);
+ if (ev->_major != CORBA_NO_EXCEPTION){
+ g_free (servant);
+ return CORBA_OBJECT_NIL;
+ }
+
+ CORBA_free (PortableServer_POA_activate_object (poa, servant, ev));
+
+ return PortableServer_POA_servant_to_reference (poa, servant, ev);
+}
+
+static GNOME_Desktop_FileOperationService
+init (DFOS *dfos,
+ CORBA_Environment *ev)
+{
+ GNOME_Desktop_FileOperationService objref;
+ PortableServer_POA poa;
+ PortableServer_POAManager poa_manager;
+
+ poa = (PortableServer_POA) CORBA_ORB_resolve_initial_references
+ (gnome_CORBA_ORB (), "RootPOA", ev);
+ if (ev->_major != CORBA_NO_EXCEPTION)
+ return FALSE;
+
+ poa_manager = PortableServer_POA__get_the_POAManager (poa, ev);
+ if (ev->_major != CORBA_NO_EXCEPTION)
+ return FALSE;
+
+ PortableServer_POAManager_activate (poa_manager, ev);
+ if (ev->_major != CORBA_NO_EXCEPTION)
+ return FALSE;
+
+ objref = create_server (dfos, poa, ev);
+ if (ev->_major != CORBA_NO_EXCEPTION)
+ return CORBA_OBJECT_NIL;
+
+ if (! goad_server_register (CORBA_OBJECT_NIL,
+ objref,
+ "IDL:GNOME:Desktop:FileOperationService:1.0",
+ "object", ev))
+ return objref;
+
+ return CORBA_OBJECT_NIL;
+}
+
+
+GNOME_Desktop_FileOperationService
+dfos_corba_init (DFOS *dfos)
+{
+ GNOME_Desktop_FileOperationService objref;
+ CORBA_Environment ev;
+
+ CORBA_exception_init (&ev);
+ objref = init (dfos, &ev);
+ CORBA_exception_free (&ev);
+
+ return objref;
+}
diff --git a/src/file-manager/dfos-corba.h b/src/file-manager/dfos-corba.h
new file mode 100644
index 000000000..892969a45
--- /dev/null
+++ b/src/file-manager/dfos-corba.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* dfos-corba.h - Implementation of the GNOME::Desktop::FileOperationService
+ CORBA server.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Ettore Perazzoli <ettore@gnu.org> */
+
+#ifndef _DFOS_CORBA_H
+#define _DFOS_CORBA_H
+
+#include "dfos.h"
+#include "GNOME_Desktop_FileOperationService.h"
+
+GNOME_Desktop_FileOperationService dfos_corba_init (DFOS *dfos);
+
+#endif
diff --git a/src/file-manager/dfos-xfer-progress-dialog.c b/src/file-manager/dfos-xfer-progress-dialog.c
new file mode 100644
index 000000000..81b3b8b8b
--- /dev/null
+++ b/src/file-manager/dfos-xfer-progress-dialog.c
@@ -0,0 +1,351 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* dfos-xfer-progress-dialog.c - Progress dialog for transfer operations in the
+ GNOME Desktop File Operation Service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Ettore Perazzoli <ettore@gnu.org> */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gnome.h>
+
+#include "dfos-xfer-progress-dialog.h"
+
+
+#define DIALOG_WIDTH 350 /* FIXME? */
+
+
+static GnomeDialogClass *parent_class;
+
+
+/* Private functions. */
+
+static void
+update (DFOSXferProgressDialog *dialog)
+{
+ gtk_progress_configure (GTK_PROGRESS (dialog->progress_bar),
+ dialog->total_bytes_copied,
+ 0.0, dialog->bytes_total);
+}
+
+/* This code by Jonathan Blandford (jrb@redhat.com) was shamelessly ripped from
+ `gnome/gdialog.c' in Midnight Commander with minor changes. */
+static gchar *
+trim_string (const gchar *string,
+ GdkFont *font,
+ guint length,
+ guint cur_length)
+{
+ static guint dotdotdot = 0;
+ gchar *string_copy = NULL;
+ gint len;
+
+ if (!dotdotdot)
+ dotdotdot = gdk_string_width (font, "...");
+
+ /* Cut the font length of string to length. */
+
+ length -= dotdotdot;
+ len = (gint) ((1.0 - (gfloat) length / (gfloat) cur_length)
+ * strlen (string));
+
+ /* we guess a starting point */
+ if (gdk_string_width (font, string + len) < length) {
+ while (gdk_string_width (font, string + len) < length)
+ len --;
+ len++;
+ } else {
+ while (gdk_string_width (font, string + len) > length)
+ len ++;
+ }
+
+ string_copy = g_strdup_printf ("...%s", string + len);
+ return string_copy;
+}
+
+static void
+set_text_trimmed (GtkLabel *label,
+ const gchar *text,
+ const gchar *trimmable_text,
+ guint max_width)
+{
+ GdkFont *font;
+ gchar *trimmed_text;
+ gchar *s;
+ guint text_width;
+ guint trimmable_text_width;
+
+ font = GTK_WIDGET (label)->style->font;
+
+ if (text != NULL)
+ text_width = gdk_string_width (font, text);
+ else
+ text_width = 0;
+
+ if (trimmable_text != NULL)
+ trimmable_text_width = gdk_string_width (font, trimmable_text);
+ else
+ trimmable_text_width = 0;
+
+ if (text_width + trimmable_text_width <= max_width) {
+ s = g_strconcat (text, trimmable_text, NULL);
+ gtk_label_set_text (GTK_LABEL (label), s);
+ g_free (s);
+ return;
+ }
+
+ trimmed_text = trim_string (trimmable_text,
+ font,
+ max_width - text_width,
+ trimmable_text_width);
+ s = g_strconcat (text, trimmed_text, NULL);
+
+ gtk_label_set_text (GTK_LABEL (label), s);
+
+ g_free (s);
+ g_free (trimmed_text);
+}
+
+
+/* GnomeDialog signals. */
+
+/* This is just to make sure the dialog is not closed without explicit
+ intervention. */
+static gboolean
+do_close (GnomeDialog *dialog)
+{
+ DFOSXferProgressDialog *progress_dialog;
+
+ progress_dialog = DFOS_XFER_PROGRESS_DIALOG (dialog);
+ return FALSE;
+}
+
+
+/* GtkObject methods. */
+
+static void
+destroy (GtkObject *object)
+{
+ DFOSXferProgressDialog *dialog;
+
+ dialog = DFOS_XFER_PROGRESS_DIALOG (object);
+
+ g_free (dialog->operation_string);
+}
+
+
+/* Initialization. */
+
+static GtkWidget *
+create_label_in_box (GtkBox *vbox)
+{
+ GtkWidget *new;
+
+ new = gtk_label_new ("");
+ gtk_label_set_justify (GTK_LABEL (new), GTK_JUSTIFY_LEFT);
+ gtk_box_pack_start (vbox, new, TRUE, TRUE, 0);
+ gtk_widget_show (new);
+
+ return new;
+}
+
+static void
+init (DFOSXferProgressDialog *dialog)
+{
+ GnomeDialog *gnome_dialog;
+ GtkBox *vbox;
+
+ gnome_dialog = GNOME_DIALOG (dialog);
+ vbox = GTK_BOX (gnome_dialog->vbox);
+
+ dialog->operation_label = create_label_in_box (vbox);
+ dialog->source_label = create_label_in_box (vbox);
+ dialog->target_label = create_label_in_box (vbox);
+
+ dialog->progress_bar = gtk_progress_bar_new ();
+ gtk_progress_bar_set_bar_style (GTK_PROGRESS_BAR (dialog->progress_bar),
+ GTK_PROGRESS_CONTINUOUS);
+ gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (dialog->progress_bar),
+ GTK_PROGRESS_LEFT_TO_RIGHT);
+ gtk_widget_set_usize (GTK_WIDGET (dialog->progress_bar), DIALOG_WIDTH,
+ -1);
+ gtk_box_pack_start (vbox, dialog->progress_bar, FALSE, TRUE, 0);
+ gtk_widget_show (dialog->progress_bar);
+
+ dialog->operation_string = NULL;
+
+ dialog->file_index = 0;
+ dialog->file_size = 0;
+ dialog->files_total = 0;
+ dialog->bytes_total = 0;
+ dialog->bytes_copied = 0;
+ dialog->total_bytes_copied = 0;
+
+ dialog->freeze_count = 0;
+}
+
+static void
+class_init (DFOSXferProgressDialogClass *class)
+{
+ GtkObjectClass *object_class;
+ GnomeDialogClass *dialog_class;
+
+ parent_class = gtk_type_class (gnome_dialog_get_type ());
+
+ object_class = GTK_OBJECT_CLASS (class);
+ dialog_class = GNOME_DIALOG_CLASS (class);
+
+ object_class->destroy = destroy;
+
+ dialog_class->close = do_close;
+}
+
+
+/* Public functions. */
+
+guint
+dfos_xfer_progress_dialog_get_type (void)
+{
+ static guint type = 0;
+
+ if (type == 0) {
+ GtkTypeInfo info = {
+ "DFOSXferProgressDialog",
+ sizeof (DFOSXferProgressDialog),
+ sizeof (DFOSXferProgressDialogClass),
+ (GtkClassInitFunc) class_init,
+ (GtkObjectInitFunc) init,
+ NULL,
+ NULL
+ };
+
+ type = gtk_type_unique (gnome_dialog_get_type (), &info);
+ }
+
+ return type;
+}
+
+GtkWidget *
+dfos_xfer_progress_dialog_new (const gchar *title,
+ const gchar *operation_string,
+ gulong total_files,
+ gulong total_bytes)
+{
+ GtkWidget *new;
+
+ new = gtk_type_new (dfos_xfer_progress_dialog_get_type ());
+
+ dfos_xfer_progress_dialog_set_operation_string (DFOS_XFER_PROGRESS_DIALOG (new),
+ operation_string);
+ dfos_xfer_progress_dialog_set_total (DFOS_XFER_PROGRESS_DIALOG (new),
+ total_files, total_bytes);
+
+ gtk_window_set_title (GTK_WINDOW (new), title);
+
+ gnome_dialog_append_button (GNOME_DIALOG (new),
+ GNOME_STOCK_BUTTON_CANCEL);
+
+ return new;
+}
+
+void
+dfos_xfer_progress_dialog_set_total (DFOSXferProgressDialog *dialog,
+ gulong files_total,
+ gulong bytes_total)
+{
+ g_return_if_fail (IS_DFOS_XFER_PROGRESS_DIALOG (dialog));
+
+ dialog->files_total = files_total;
+ dialog->bytes_total = bytes_total;
+
+ update (dialog);
+}
+
+void
+dfos_xfer_progress_dialog_set_operation_string (DFOSXferProgressDialog *dialog,
+ const gchar *operation_string)
+{
+ g_return_if_fail (IS_DFOS_XFER_PROGRESS_DIALOG (dialog));
+
+ gtk_label_set_text (GTK_LABEL (dialog->operation_label),
+ operation_string);
+ dialog->operation_string = g_strdup (operation_string);
+}
+
+void
+dfos_xfer_progress_dialog_new_file (DFOSXferProgressDialog *dialog,
+ const gchar *source_uri,
+ const gchar *target_uri,
+ gulong size)
+{
+ gchar *s;
+
+ g_return_if_fail (IS_DFOS_XFER_PROGRESS_DIALOG (dialog));
+ g_return_if_fail (GTK_WIDGET_REALIZED (dialog));
+
+ dialog->file_index++;
+ dialog->bytes_copied = 0;
+ dialog->file_size = size;
+
+ s = g_strdup_printf ("%s file %ld/%ld",
+ dialog->operation_string,
+ dialog->file_index, dialog->files_total);
+ gtk_label_set_text (GTK_LABEL (dialog->operation_label), s);
+ g_free (s);
+
+ set_text_trimmed (GTK_LABEL (dialog->source_label),
+ _("From: "), source_uri,
+ DIALOG_WIDTH);
+
+ set_text_trimmed (GTK_LABEL (dialog->target_label),
+ _("To: "), target_uri,
+ DIALOG_WIDTH);
+
+ update (dialog);
+}
+
+void
+dfos_xfer_progress_dialog_update (DFOSXferProgressDialog *dialog,
+ gulong bytes_done_in_file,
+ gulong bytes_done)
+{
+ g_return_if_fail (IS_DFOS_XFER_PROGRESS_DIALOG (dialog));
+
+ dialog->bytes_copied = bytes_done_in_file;
+ dialog->total_bytes_copied = bytes_done;
+
+ update (dialog);
+}
+
+
+void
+dfos_xfer_progress_dialog_freeze (DFOSXferProgressDialog *dialog)
+{
+ dialog->freeze_count++;
+}
+
+void
+dfos_xfer_progress_dialog_thaw (DFOSXferProgressDialog *dialog)
+{
+ if (dialog->freeze_count > 0)
+ dialog->freeze_count--;
+}
+
diff --git a/src/file-manager/dfos-xfer-progress-dialog.h b/src/file-manager/dfos-xfer-progress-dialog.h
new file mode 100644
index 000000000..657be9be3
--- /dev/null
+++ b/src/file-manager/dfos-xfer-progress-dialog.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* xfer-progress-dialog.h - Progress dialog for transfer operations in the
+ GNOME Desktop File Operation Service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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 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; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Ettore Perazzoli <ettore@gnu.org> */
+
+#ifndef _DFOS_XFER_PROGRESS_DIALOG_H
+#define _DFOS_XFER_PROGRESS_DIALOG_H
+
+#include <libgnomeui/gnome-dialog.h>
+
+
+#define DFOS_XFER_PROGRESS_DIALOG(obj) \
+ GTK_CHECK_CAST (obj, dfos_xfer_progress_dialog_get_type (), DFOSXferProgressDialog)
+#define DFOS_XFER_PROGRESS_DIALOG_CLASS(klass) \
+ GTK_CHECK_CLASS_CAST (klass, dfos_xfer_progress_dialog_get_type (), DFOSXferProgressDialogClass)
+#define IS_DFOS_XFER_PROGRESS_DIALOG(obj) \
+ GTK_CHECK_TYPE (obj, dfos_xfer_progress_dialog_get_type ())
+
+
+struct _DFOSXferProgressDialog {
+ GnomeDialog dialog;
+
+ GtkWidget *operation_label;
+ GtkWidget *source_label;
+ GtkWidget *target_label;
+ GtkWidget *progress_bar;
+
+ gchar *operation_string;
+
+ guint freeze_count;
+
+ gulong file_index;
+ gulong file_size;
+
+ gulong bytes_copied;
+ gulong total_bytes_copied;
+
+ gulong files_total;
+ gulong bytes_total;
+};
+typedef struct _DFOSXferProgressDialog DFOSXferProgressDialog;
+
+struct _DFOSXferProgressDialogClass {
+ GnomeDialogClass parent_class;
+};
+typedef struct _DFOSXferProgressDialogClass DFOSXferProgressDialogClass;
+
+
+guint dfos_xfer_progress_dialog_get_type
+ (void);
+
+GtkWidget *dfos_xfer_progress_dialog_new (const gchar *title,
+ const gchar *operation_string,
+ gulong files_total,
+ gulong bytes_total);
+
+void dfos_xfer_progress_dialog_set_total
+ (DFOSXferProgressDialog *dialog,
+ gulong files_total,
+ gulong bytes_total);
+
+void dfos_xfer_progress_dialog_set_operation_string
+ (DFOSXferProgressDialog *dialog,
+ const gchar *operation_string);
+
+void dfos_xfer_progress_dialog_new_file
+ (DFOSXferProgressDialog *dialog,
+ const gchar *source_uri,
+ const gchar *target_uri,
+ gulong size);
+
+void dfos_xfer_progress_dialog_update
+ (DFOSXferProgressDialog *dialog,
+ gulong bytes_done_in_file,
+ gulong bytes_done);
+
+void dfos_xfer_progress_dialog_freeze
+ (DFOSXferProgressDialog *dialog);
+
+void dfos_xfer_progress_dialog_thaw (DFOSXferProgressDialog *dialog);
+
+#endif /* _DFOS_XFER_PROGRESS_DIALOG_H */
diff --git a/src/file-manager/dfos-xfer.c b/src/file-manager/dfos-xfer.c
new file mode 100644
index 000000000..4e1821eaf
--- /dev/null
+++ b/src/file-manager/dfos-xfer.c
@@ -0,0 +1,252 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* xfer.c - GNOME::Desktop::FileOperationService transfer service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org> */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gnome.h>
+#include <libgnomevfs/gnome-vfs.h>
+
+#include "dfos.h"
+#include "error.h"
+
+#include "dfos-xfer.h"
+
+
+struct _XferInfo {
+ GnomeVFSAsyncHandle *handle;
+ GtkWidget *progress_dialog;
+ GnomeVFSXferOptions options;
+ GnomeVFSXferErrorMode error_mode;
+ GnomeVFSXferOverwriteMode overwrite_mode;
+};
+typedef struct _XferInfo XferInfo;
+
+
+static XferInfo *
+xfer_info_new (GnomeVFSAsyncHandle *handle,
+ GnomeVFSXferOptions options,
+ GnomeVFSXferErrorMode error_mode,
+ GnomeVFSXferOverwriteMode overwrite_mode)
+{
+ XferInfo *new;
+
+ new = g_new (XferInfo, 1);
+
+ new->handle = handle;
+ new->options = options;
+ new->error_mode = error_mode;
+ new->overwrite_mode = overwrite_mode;
+
+ new->progress_dialog = NULL;
+
+ return new;
+}
+
+static void
+xfer_info_destroy (XferInfo *info)
+{
+ g_free (info);
+}
+
+
+static void
+xfer_dialog_clicked_callback (DFOSXferProgressDialog *dialog,
+ gint button_number,
+ gpointer data)
+{
+ XferInfo *info;
+
+ info = (XferInfo *) data;
+ gnome_vfs_async_cancel (info->handle);
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ g_warning (_("Operation cancelled"));
+}
+
+static void
+create_xfer_dialog (const GnomeVFSXferProgressInfo *progress_info,
+ XferInfo *xfer_info)
+{
+ gchar *op_string;
+
+ g_return_if_fail (xfer_info->progress_dialog == NULL);
+
+ if (xfer_info->options & GNOME_VFS_XFER_REMOVESOURCE)
+ op_string = _("Moving");
+ else
+ op_string = _("Copying");
+
+ xfer_info->progress_dialog
+ = dfos_xfer_progress_dialog_new ("Transfer in progress",
+ op_string,
+ progress_info->files_total,
+ progress_info->bytes_total);
+
+ gtk_signal_connect (GTK_OBJECT (xfer_info->progress_dialog),
+ "clicked",
+ GTK_SIGNAL_FUNC (xfer_dialog_clicked_callback),
+ xfer_info);
+
+ gtk_widget_show (xfer_info->progress_dialog);
+}
+
+static gint
+handle_xfer_ok (const GnomeVFSXferProgressInfo *progress_info,
+ XferInfo *xfer_info)
+{
+ switch (progress_info->phase) {
+ case GNOME_VFS_XFER_PHASE_READYTOGO:
+ create_xfer_dialog (progress_info, xfer_info);
+ return TRUE;
+ case GNOME_VFS_XFER_PHASE_XFERRING:
+ if (progress_info->bytes_copied == 0) {
+ dfos_xfer_progress_dialog_new_file
+ (DFOS_XFER_PROGRESS_DIALOG
+ (xfer_info->progress_dialog),
+ progress_info->source_name,
+ progress_info->target_name,
+ progress_info->file_size);
+ } else {
+ dfos_xfer_progress_dialog_update
+ (DFOS_XFER_PROGRESS_DIALOG
+ (xfer_info->progress_dialog),
+ progress_info->bytes_copied,
+ progress_info->total_bytes_copied);
+ }
+ return TRUE;
+ case GNOME_VFS_XFER_PHASE_FILECOMPLETED:
+ /* FIXME? */
+ return TRUE;
+ case GNOME_VFS_XFER_PHASE_COMPLETED:
+ gtk_widget_destroy (xfer_info->progress_dialog);
+ g_warning ("***** RELEASING HANDLE");
+ g_free (xfer_info);
+ return TRUE;
+ default:
+ return TRUE;
+ }
+}
+
+static gint
+handle_xfer_vfs_error (const GnomeVFSXferProgressInfo *progress_info,
+ XferInfo *xfer_info)
+{
+ /* Notice that the error mode in `xfer_info' is the one we have been
+ requested, but the transfer is always performed in mode
+ `GNOME_VFS_XFER_ERROR_MODE_QUERY'. */
+
+ switch (xfer_info->error_mode) {
+ case GNOME_VFS_XFER_ERROR_MODE_QUERY: /* FIXME */
+ case GNOME_VFS_XFER_ERROR_MODE_ABORT:
+ default:
+ dfos_xfer_progress_dialog_freeze (DFOS_XFER_PROGRESS_DIALOG
+ (xfer_info->progress_dialog));
+ error (xfer_info->progress_dialog,
+ _("Copy operation failed:\n%s"),
+ gnome_vfs_result_to_string (progress_info->vfs_status));
+ dfos_xfer_progress_dialog_thaw (DFOS_XFER_PROGRESS_DIALOG
+ (xfer_info->progress_dialog));
+ gtk_widget_destroy (xfer_info->progress_dialog);
+ return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
+ }
+}
+
+static gint
+handle_xfer_overwrite (const GnomeVFSXferProgressInfo *progress_info,
+ XferInfo *xfer_info)
+{
+ return FALSE;
+}
+
+static gint
+xfer_callback (GnomeVFSAsyncHandle *handle,
+ const GnomeVFSXferProgressInfo *progress_info,
+ gpointer data)
+{
+ XferInfo *xfer_info;
+
+ xfer_info = (XferInfo *) data;
+
+ switch (progress_info->status) {
+ case GNOME_VFS_XFER_PROGRESS_STATUS_OK:
+ return handle_xfer_ok (progress_info, xfer_info);
+ case GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR:
+ return handle_xfer_vfs_error (progress_info, xfer_info);
+ case GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE:
+ return handle_xfer_overwrite (progress_info, xfer_info);
+ default:
+ g_warning (_("Unknown GnomeVFSXferProgressStatus %d"),
+ progress_info->status);
+ return FALSE;
+ }
+}
+
+
+void
+dfos_xfer (DFOS *dfos,
+ const gchar *source_directory_uri,
+ GList *source_file_name_list,
+ const gchar *target_directory_uri,
+ GList *target_file_name_list,
+ GnomeVFSXferOptions options,
+ GnomeVFSXferErrorMode error_mode,
+ GnomeVFSXferOverwriteMode overwrite_mode)
+{
+ GnomeVFSResult result;
+ XferInfo *xfer_info;
+ GnomeVFSAsyncHandle *handle;
+
+ xfer_info = xfer_info_new (handle, options, overwrite_mode, error_mode);
+
+ result = gnome_vfs_async_xfer (&handle,
+ source_directory_uri,
+ source_file_name_list,
+ target_directory_uri,
+ target_file_name_list,
+ options,
+ GNOME_VFS_XFER_ERROR_MODE_QUERY,
+ overwrite_mode,
+ xfer_callback,
+ xfer_info);
+
+ if (result != GNOME_VFS_OK) {
+ gchar *message;
+ GtkWidget *dialog;
+
+ message = g_strdup_printf (_("The transfer between\n%s\nand\n%s\ncould not be started:\n%s"),
+ source_directory_uri,
+ target_directory_uri,
+ gnome_vfs_result_to_string (result));
+
+
+ /* FIXME: signals and all that. */
+ dialog = gnome_error_dialog (message);
+
+ gtk_widget_show (dialog);
+
+ g_free (message);
+ g_free (xfer_info);
+ }
+}
diff --git a/src/file-manager/dfos-xfer.h b/src/file-manager/dfos-xfer.h
new file mode 100644
index 000000000..a200110e0
--- /dev/null
+++ b/src/file-manager/dfos-xfer.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* xfer.h - GNOME::Desktop::FileOperationService transfer service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _XFER_H
+#define _XFER_H
+
+#include <libgnomevfs/gnome-vfs.h>
+
+void dfos_xfer (DFOS *dfos,
+ const gchar *source_directory_uri,
+ GList *source_file_name_list,
+ const gchar *target_directory_uri,
+ GList *target_file_name_list,
+ GnomeVFSXferOptions options,
+ GnomeVFSXferErrorMode error_mode,
+ GnomeVFSXferOverwriteMode overwrite_mode);
+
+#endif /* _XFER_H */
diff --git a/src/file-manager/dfos.c b/src/file-manager/dfos.c
new file mode 100644
index 000000000..963a6d347
--- /dev/null
+++ b/src/file-manager/dfos.c
@@ -0,0 +1,70 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* dfos.h - Desktop File Operation Service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gnome.h>
+#include <libgnorba/gnorba.h>
+#include <orb/orbit.h>
+
+#include "dfos.h"
+
+
+struct _DFOS {
+ GNOME_Desktop_FileOperationService corba_objref;
+};
+
+
+DFOS *
+dfos_new (void)
+{
+ GNOME_Desktop_FileOperationService corba_objref;
+ DFOS *new;
+
+ new = g_new (DFOS, 1);
+ corba_objref = dfos_corba_init (new);
+ if (corba_objref == CORBA_OBJECT_NIL) {
+ g_free (new);
+ return NULL;
+ }
+
+ new->corba_objref = corba_objref;
+
+ return new;
+}
+
+void
+dfos_destroy (DFOS *dfos)
+{
+ CORBA_Environment ev;
+
+ g_return_if_fail (dfos != NULL);
+
+ CORBA_exception_init (&ev);
+ CORBA_Object_release (dfos->corba_objref, &ev);
+ CORBA_exception_free (&ev);
+
+ g_free (dfos);
+}
diff --git a/src/file-manager/dfos.h b/src/file-manager/dfos.h
new file mode 100644
index 000000000..7d7f4e1b5
--- /dev/null
+++ b/src/file-manager/dfos.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* dfos.h - Desktop File Operation Service.
+
+ Copyright (C) 1999 Free Software Foundation
+
+ 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; see the file COPYING. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#ifndef _DFOS_H
+#define _DFOS_H
+
+typedef struct _DFOS DFOS;
+
+#include "dfos-corba.h"
+#include "dfos-xfer-progress-dialog.h"
+#include "dfos-xfer.h"
+
+#include "GNOME_Desktop_FileOperationService.h"
+
+
+DFOS *dfos_new (void);
+void dfos_destroy (DFOS *dfos);
+
+#endif /* _DFOS_H */
diff --git a/src/file-manager/fm-directory-view.c b/src/file-manager/fm-directory-view.c
new file mode 100644
index 000000000..9b6229a96
--- /dev/null
+++ b/src/file-manager/fm-directory-view.c
@@ -0,0 +1,1052 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* explorer-directory-view.c
+ *
+ * Copyright (C) 1999 Free Software Foundaton
+ *
+ * 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.
+ *
+ * Author: Ettore Perazzoli
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gnome.h>
+
+#include "gnome-icon-container.h"
+#include "gtkflist.h"
+
+#include "explorer-debug.h"
+#include "explorer-directory-view.h"
+#include "explorer-icon-manager.h"
+
+
+enum {
+ OPEN_FAILED,
+ OPEN_DONE,
+ LOAD_FAILED,
+ LOAD_DONE,
+ ACTIVATE_URI,
+ LAST_SIGNAL
+};
+
+#define DISPLAY_TIMEOUT_INTERVAL 500
+
+
+static GtkScrollFrameClass *parent_class = NULL;
+static guint signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+display_selection_info (ExplorerDirectoryView *view,
+ GList *selection)
+{
+ GnomeVFSFileSize size;
+ guint count;
+ gchar *count_string, *size_string, *msg;
+ GList *p;
+
+ count = 0;
+ size = 0;
+ for (p = selection; p != NULL; p = p->next) {
+ GnomeVFSFileInfo *info;
+
+ info = p->data;
+ count++;
+ size += info->size;
+ }
+
+ if (count == 0) {
+ gnome_appbar_set_status (view->app_bar, "");
+ return;
+ }
+
+ /* FIXME: The following should probably go into a separate module, as
+ we might have to do the same thing in other places as well. Also,
+ I am not sure this will be OK for all the languages. */
+
+ if (size < (GnomeVFSFileSize) 1e3) {
+ if (size == 1)
+ size_string = g_strdup (_("1 byte"));
+ else
+ size_string = g_strdup_printf (_("%u bytes"),
+ (guint) size);
+ } else {
+ gdouble displayed_size;
+
+ if (size < (GnomeVFSFileSize) 1e6) {
+ displayed_size = (gdouble) size / 1.0e3;
+ size_string = g_strdup_printf (_("%.1fK"),
+ displayed_size);
+ } else if (size < (GnomeVFSFileSize) 1e9) {
+ displayed_size = (gdouble) size / 1.0e6;
+ size_string = g_strdup_printf (_("%.1fM"),
+ displayed_size);
+ } else {
+ displayed_size = (gdouble) size / 1.0e9;
+ size_string = g_strdup_printf (_("%.1fG"),
+ displayed_size);
+ }
+ }
+
+ if (count == 1)
+ count_string = g_strdup (_("1 file."));
+ else
+ count_string = g_strdup_printf (_("%d files."), count);
+
+ msg = g_strdup_printf (_("%s selected in %s"),
+ size_string, count_string);
+ gnome_appbar_set_status (view->app_bar, msg);
+
+ g_free (count_string);
+ g_free (size_string);
+ g_free (msg);
+}
+
+
+/* GnomeIconContainer handling. */
+
+static gboolean
+mode_uses_icon_container (ExplorerDirectoryViewMode mode)
+{
+ return (mode == EXPLORER_DIRECTORY_VIEW_MODE_ICONS
+ || mode == EXPLORER_DIRECTORY_VIEW_MODE_SMALLICONS);
+}
+
+static gboolean
+view_has_icon_container (ExplorerDirectoryView *view)
+{
+ return mode_uses_icon_container (view->mode);
+}
+
+static void
+icon_container_selection_changed_cb (GnomeIconContainer *container,
+ gpointer data)
+{
+ ExplorerDirectoryView *view;
+ GList *selection;
+
+ view = EXPLORER_DIRECTORY_VIEW (data);
+
+ selection = gnome_icon_container_get_selection (container);
+ display_selection_info (view, selection);
+ g_list_free (selection);
+}
+
+static void
+icon_container_activate_cb (GnomeIconContainer *icon_container,
+ const gchar *name,
+ gpointer icon_data,
+ gpointer data)
+{
+ ExplorerDirectoryView *directory_view;
+ GnomeVFSURI *new_uri;
+ GnomeVFSFileInfo *info;
+
+ info = (GnomeVFSFileInfo *) icon_data;
+ directory_view = EXPLORER_DIRECTORY_VIEW (data);
+
+ new_uri = gnome_vfs_uri_append_path (directory_view->uri, name);
+ gtk_signal_emit (GTK_OBJECT (directory_view),
+ signals[ACTIVATE_URI], new_uri, info->mime_type);
+ gnome_vfs_uri_unref (new_uri);
+}
+
+static GnomeIconContainer *
+get_icon_container (ExplorerDirectoryView *view)
+{
+ GtkBin *bin;
+
+ g_return_val_if_fail (view_has_icon_container (view), NULL);
+
+ bin = GTK_BIN (view);
+
+ if (bin->child == NULL)
+ return NULL; /* Avoid GTK+ complaints. */
+ else
+ return GNOME_ICON_CONTAINER (bin->child);
+}
+
+static void
+add_to_icon_container (ExplorerDirectoryView *view,
+ ExplorerIconManager *icon_manager,
+ GnomeIconContainer *icon_container,
+ GnomeVFSFileInfo *info,
+ gboolean with_layout)
+{
+ GdkImlibImage *image;
+
+ image = explorer_icon_manager_get_icon_for_info (icon_manager, info);
+
+ if (! with_layout || view->icon_layout == NULL) {
+ gnome_icon_container_add_imlib_auto (icon_container,
+ image,
+ info->name,
+ info);
+ } else {
+ gboolean result;
+
+ result = gnome_icon_container_add_imlib_with_layout
+ (icon_container, image, info->name, info,
+ view->icon_layout);
+ if (! result)
+ view->icons_not_in_layout = g_list_prepend
+ (view->icons_not_in_layout, info);
+ }
+}
+
+static void
+load_icon_container (ExplorerDirectoryView *view,
+ GnomeIconContainer *icon_container)
+{
+ gnome_icon_container_clear (icon_container);
+
+ if (view->directory_list != NULL) {
+ GnomeVFSDirectoryListPosition *position;
+ ExplorerIconManager *icon_manager;
+
+ icon_manager = explorer_application_get_icon_manager
+ (view->application);
+
+ position = gnome_vfs_directory_list_get_first_position
+ (view->directory_list);
+
+ while (position != view->current_position) {
+ GnomeVFSFileInfo *info;
+
+ info = gnome_vfs_directory_list_get
+ (view->directory_list, position);
+ add_to_icon_container (view, icon_manager,
+ icon_container, info, TRUE);
+
+ position = gnome_vfs_directory_list_position_next
+ (position);
+ }
+ }
+
+}
+
+static GnomeIconContainer *
+create_icon_container (ExplorerDirectoryView *view)
+{
+ GnomeIconContainer *icon_container;
+
+ icon_container = GNOME_ICON_CONTAINER (gnome_icon_container_new ());
+ GTK_WIDGET_SET_FLAGS (icon_container, GTK_CAN_FOCUS);
+ gtk_signal_connect (GTK_OBJECT (icon_container),
+ "activate",
+ GTK_SIGNAL_FUNC (icon_container_activate_cb),
+ view);
+ gtk_signal_connect (GTK_OBJECT (icon_container),
+ "selection_changed",
+ GTK_SIGNAL_FUNC (icon_container_selection_changed_cb),
+ view);
+
+ gtk_container_add (GTK_CONTAINER (view), GTK_WIDGET (icon_container));
+
+ gtk_widget_show (GTK_WIDGET (icon_container));
+ load_icon_container (view, icon_container);
+
+ return icon_container;
+}
+
+static void
+setup_icon_container (ExplorerDirectoryView *view,
+ ExplorerDirectoryViewMode mode)
+{
+ GnomeIconContainer *icon_container;
+
+ g_return_if_fail (mode_uses_icon_container (mode));
+
+ if (! view_has_icon_container (view)) {
+ GtkWidget *child;
+
+ child = GTK_BIN (view)->child;
+ if (child != NULL)
+ gtk_widget_destroy (GTK_BIN (view)->child);
+ icon_container = create_icon_container (view);
+ } else {
+ icon_container = get_icon_container (view);
+ }
+
+ if (mode == EXPLORER_DIRECTORY_VIEW_MODE_ICONS)
+ gnome_icon_container_set_icon_mode
+ (icon_container, GNOME_ICON_CONTAINER_NORMAL_ICONS);
+ else
+ gnome_icon_container_set_icon_mode
+ (icon_container, GNOME_ICON_CONTAINER_SMALL_ICONS);
+}
+
+
+/* GtkFList handling. */
+
+static gboolean
+mode_uses_flist (ExplorerDirectoryViewMode mode)
+{
+ return (mode == EXPLORER_DIRECTORY_VIEW_MODE_DETAILED
+ || mode == EXPLORER_DIRECTORY_VIEW_MODE_CUSTOM);
+}
+
+static gboolean
+view_has_flist (ExplorerDirectoryView *view)
+{
+ return mode_uses_flist (view->mode);
+}
+
+static void
+flist_selection_changed_cb (GtkFList *flist,
+ gpointer data)
+{
+ ExplorerDirectoryView *view;
+ GList *selection;
+
+ view = EXPLORER_DIRECTORY_VIEW (data);
+
+ selection = gtk_flist_get_selection (flist);
+ display_selection_info (view, selection);
+ g_list_free (selection);
+}
+
+static void
+flist_activate_cb (GtkFList *flist,
+ gpointer entry_data,
+ gpointer data)
+{
+ ExplorerDirectoryView *directory_view;
+ GnomeVFSURI *new_uri;
+ GnomeVFSFileInfo *info;
+
+ info = (GnomeVFSFileInfo *) entry_data;
+ directory_view = EXPLORER_DIRECTORY_VIEW (data);
+
+ new_uri = gnome_vfs_uri_append_path (directory_view->uri, info->name);
+ gtk_signal_emit (GTK_OBJECT (directory_view),
+ signals[ACTIVATE_URI], new_uri, info->mime_type);
+ gnome_vfs_uri_unref (new_uri);
+}
+
+static GtkFList *
+get_flist (ExplorerDirectoryView *view)
+{
+ GtkBin *bin;
+
+ g_return_val_if_fail (view_has_flist (view), NULL);
+
+ bin = GTK_BIN (view);
+
+ if (bin->child == NULL)
+ return NULL; /* Avoid GTK+ complaints. */
+ else
+ return GTK_FLIST (bin->child);
+}
+
+static void
+add_to_flist (ExplorerIconManager *icon_manager,
+ GtkFList *flist,
+ GnomeVFSFileInfo *info)
+{
+ GtkCList *clist;
+ gchar *text[2];
+
+ text[0] = info->name;
+ text[1] = NULL;
+
+ clist = GTK_CLIST (flist);
+ gtk_clist_append (clist, text);
+ gtk_clist_set_row_data (clist, clist->rows - 1, info);
+}
+
+static GtkFList *
+create_flist (ExplorerDirectoryView *view)
+{
+ GtkFList *flist;
+ gchar *titles[] = {
+ "Name",
+ NULL
+ };
+
+ flist = GTK_FLIST (gtk_flist_new_with_titles (2, titles));
+ gtk_clist_set_column_width (GTK_CLIST (flist), 0, 150); /* FIXME */
+ GTK_WIDGET_SET_FLAGS (flist, GTK_CAN_FOCUS);
+
+ gtk_signal_connect (GTK_OBJECT (flist),
+ "activate",
+ GTK_SIGNAL_FUNC (flist_activate_cb),
+ view);
+ gtk_signal_connect (GTK_OBJECT (flist),
+ "selection_changed",
+ GTK_SIGNAL_FUNC (flist_selection_changed_cb),
+ view);
+
+ gtk_container_add (GTK_CONTAINER (view), GTK_WIDGET (flist));
+
+ gtk_widget_show (GTK_WIDGET (flist));
+
+ if (view->directory_list != NULL) {
+ GnomeVFSDirectoryListPosition *position;
+ ExplorerIconManager *icon_manager;
+
+ icon_manager = explorer_application_get_icon_manager
+ (view->application);
+
+ position = gnome_vfs_directory_list_get_first_position
+ (view->directory_list);
+
+ gtk_clist_freeze (GTK_CLIST (flist));
+
+ while (position != view->current_position) {
+ GnomeVFSFileInfo *info;
+
+ info = gnome_vfs_directory_list_get
+ (view->directory_list, position);
+ add_to_flist (icon_manager, flist, info);
+
+ position = gnome_vfs_directory_list_position_next
+ (position);
+ }
+
+ gtk_clist_thaw (GTK_CLIST (flist));
+ }
+
+ return flist;
+}
+
+static void
+setup_flist (ExplorerDirectoryView *view,
+ ExplorerDirectoryViewMode mode)
+{
+ GtkFList *flist;
+
+ g_return_if_fail (mode_uses_flist (mode));
+
+ if (! view_has_flist (view)) {
+ GtkWidget *child;
+
+ child = GTK_BIN (view)->child;
+ if (child != NULL)
+ gtk_widget_destroy (GTK_BIN (view)->child);
+ flist = create_flist (view);
+ }
+}
+
+
+/* Signals. */
+
+static void
+real_open_failed (ExplorerDirectoryView *directory_view,
+ GnomeVFSResult result)
+{
+ g_return_if_fail (directory_view != NULL);
+ g_return_if_fail (EXPLORER_IS_DIRECTORY_VIEW (directory_view));
+}
+
+static void
+real_open_done (ExplorerDirectoryView *directory_view)
+{
+ g_return_if_fail (directory_view != NULL);
+ g_return_if_fail (EXPLORER_IS_DIRECTORY_VIEW (directory_view));
+}
+
+static void
+real_load_failed (ExplorerDirectoryView *directory_view,
+ GnomeVFSResult result)
+{
+ g_return_if_fail (directory_view != NULL);
+ g_return_if_fail (EXPLORER_IS_DIRECTORY_VIEW (directory_view));
+}
+
+static void
+real_load_done (ExplorerDirectoryView *directory_view)
+{
+ g_return_if_fail (directory_view != NULL);
+ g_return_if_fail (EXPLORER_IS_DIRECTORY_VIEW (directory_view));
+}
+
+static void
+real_activate_uri (ExplorerDirectoryView *directory_view,
+ const GnomeVFSURI *uri,
+ const gchar *mime_type)
+{
+ g_return_if_fail (directory_view != NULL);
+ g_return_if_fail (EXPLORER_IS_DIRECTORY_VIEW (directory_view));
+ g_return_if_fail (uri != NULL);
+}
+
+
+/* GtkObject methods. */
+
+static void
+destroy (GtkObject *object)
+{
+ ExplorerDirectoryView *view;
+
+ EXPLORER_DEBUG (("Entering function."));
+
+ view = EXPLORER_DIRECTORY_VIEW (object);
+
+ if (view->directory_list != NULL)
+ gnome_vfs_directory_list_destroy (view->directory_list);
+
+ if (view->uri != NULL)
+ gnome_vfs_uri_unref (view->uri);
+
+ if (view->vfs_async_handle != NULL) {
+ EXPLORER_DEBUG (("Cancelling VFS operation."));
+ gnome_vfs_async_cancel (view->vfs_async_handle);
+ }
+
+ if (view->display_timeout_id != 0)
+ gtk_timeout_remove (view->display_timeout_id);
+
+ if (view->icons_not_in_layout != NULL)
+ g_list_free (view->icons_not_in_layout);
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy != NULL)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+
+static void
+class_init (ExplorerDirectoryViewClass *class)
+{
+ GtkObjectClass *object_class;
+
+ object_class = GTK_OBJECT_CLASS (class);
+
+ parent_class = gtk_type_class (gtk_scroll_frame_get_type ());
+
+ signals[OPEN_FAILED] =
+ gtk_signal_new ("open_failed",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (ExplorerDirectoryViewClass,
+ open_failed),
+ gtk_marshal_NONE__INT,
+ GTK_TYPE_NONE, 0,
+ GTK_TYPE_INT);
+ signals[OPEN_DONE] =
+ gtk_signal_new ("open_done",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (ExplorerDirectoryViewClass,
+ open_done),
+ gtk_marshal_NONE__NONE,
+ GTK_TYPE_NONE, 0);
+ signals[LOAD_FAILED] =
+ gtk_signal_new ("load_failed",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (ExplorerDirectoryViewClass,
+ load_failed),
+ gtk_marshal_NONE__INT,
+ GTK_TYPE_NONE, 0,
+ GTK_TYPE_INT);
+ signals[LOAD_DONE] =
+ gtk_signal_new ("load_done",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (ExplorerDirectoryViewClass,
+ load_done),
+ gtk_marshal_NONE__NONE,
+ GTK_TYPE_NONE, 0);
+ signals[ACTIVATE_URI] =
+ gtk_signal_new ("activate_uri",
+ GTK_RUN_FIRST,
+ object_class->type,
+ GTK_SIGNAL_OFFSET (ExplorerDirectoryViewClass,
+ load_done),
+ gtk_marshal_NONE__POINTER_POINTER,
+ GTK_TYPE_NONE, 2,
+ GTK_TYPE_POINTER,
+ GTK_TYPE_STRING);
+
+ gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL);
+
+ object_class->destroy = destroy;
+
+ class->open_failed = real_open_failed;
+ class->open_done = real_open_done;
+ class->load_failed = real_load_failed;
+ class->load_done = real_load_done;
+ class->activate_uri = real_activate_uri;
+}
+
+static void
+init (ExplorerDirectoryView *directory_view)
+{
+ GtkScrollFrame *scroll_frame;
+
+ directory_view->application = NULL;
+ directory_view->app_bar = NULL;
+
+ directory_view->mode = EXPLORER_DIRECTORY_VIEW_MODE_NONE;
+
+ directory_view->uri = NULL;
+ directory_view->vfs_async_handle = NULL;
+ directory_view->directory_list = NULL;
+
+ directory_view->current_position = GNOME_VFS_DIRECTORY_LIST_POSITION_NONE;
+ directory_view->entries_to_display = 0;
+
+ directory_view->display_timeout_id = 0;
+
+ directory_view->icon_layout = NULL;
+ directory_view->icons_not_in_layout = NULL;
+
+ scroll_frame = GTK_SCROLL_FRAME (directory_view);
+
+ gtk_scroll_frame_set_hadjustment (scroll_frame, NULL);
+ gtk_scroll_frame_set_vadjustment (scroll_frame, NULL);
+ gtk_scroll_frame_set_policy (scroll_frame,
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scroll_frame_set_shadow_type (scroll_frame, GTK_SHADOW_IN);
+}
+
+
+/* Utility functions. */
+
+static void
+stop_load (ExplorerDirectoryView *view)
+{
+ if (view->vfs_async_handle != NULL) {
+ gnome_vfs_async_cancel (view->vfs_async_handle);
+ view->vfs_async_handle = NULL;
+ }
+
+ if (view->display_timeout_id != 0) {
+ gtk_timeout_remove (view->display_timeout_id);
+ view->display_timeout_id = 0;
+ }
+
+ view->current_position = GNOME_VFS_DIRECTORY_LIST_POSITION_NONE;
+ view->entries_to_display = 0;
+}
+
+
+static void
+display_pending_entries (ExplorerDirectoryView *view)
+{
+ ExplorerIconManager *icon_manager;
+ GnomeIconContainer *icon_container;
+ GtkFList *flist;
+ guint i;
+
+ EXPLORER_DEBUG (("Adding %d entries.", view->entries_to_display));
+
+ icon_manager = explorer_application_get_icon_manager (view->application);
+
+ if (view_has_icon_container (view)) {
+ icon_container = get_icon_container (view);
+ flist = NULL;
+ } else {
+ icon_container = NULL;
+ flist = get_flist (view);
+ gtk_clist_freeze (GTK_CLIST (flist));
+ }
+
+ for (i = 0; i < view->entries_to_display; i++) {
+ GnomeVFSFileInfo *info;
+
+ info = gnome_vfs_directory_list_get (view->directory_list,
+ view->current_position);
+
+ if (icon_container != NULL)
+ add_to_icon_container (view, icon_manager,
+ icon_container, info, TRUE);
+ else
+ add_to_flist (icon_manager, flist, info);
+
+ view->current_position = gnome_vfs_directory_list_position_next
+ (view->current_position);
+ }
+
+ if (flist != NULL)
+ gtk_clist_thaw (GTK_CLIST (flist));
+
+ view->entries_to_display = 0;
+
+ EXPLORER_DEBUG (("Done."));
+}
+
+static void
+display_icons_not_in_layout (ExplorerDirectoryView *view)
+{
+ ExplorerIconManager *icon_manager;
+ GnomeIconContainer *icon_container;
+ GList *p;
+
+ if (view->icons_not_in_layout == NULL)
+ return;
+
+ EXPLORER_DEBUG (("Adding entries not in layout."));
+
+ icon_manager = explorer_application_get_icon_manager (view->application);
+
+ icon_container = get_icon_container (view);
+ g_return_if_fail (icon_container != NULL);
+
+ /* FIXME: This will block if there are many files. */
+
+ for (p = view->icons_not_in_layout; p != NULL; p = p->next) {
+ GnomeVFSFileInfo *info;
+
+ info = p->data;
+ add_to_icon_container (view, icon_manager,
+ icon_container, info, FALSE);
+ EXPLORER_DEBUG (("Adding `%s'", info->name));
+ }
+
+ EXPLORER_DEBUG (("Done with entries not in layout."));
+
+ g_list_free (view->icons_not_in_layout);
+ view->icons_not_in_layout = NULL;
+}
+
+static gboolean
+display_timeout_cb (gpointer data)
+{
+ ExplorerDirectoryView *view;
+
+ EXPLORER_DEBUG (("Entering function"));
+
+ view = EXPLORER_DIRECTORY_VIEW (data);
+
+ display_pending_entries (view);
+
+ EXPLORER_DEBUG (("Done"));
+
+ return TRUE;
+}
+
+
+static void
+directory_load_cb (GnomeVFSAsyncHandle *handle,
+ GnomeVFSResult result,
+ GnomeVFSDirectoryList *list,
+ guint entries_read,
+ gpointer callback_data)
+{
+ ExplorerDirectoryView *view;
+
+ EXPLORER_DEBUG (("Entering function, %d entries read: %s",
+ entries_read, gnome_vfs_result_to_string (result)));
+
+ view = EXPLORER_DIRECTORY_VIEW (callback_data);
+
+ if (view->directory_list == NULL) {
+ if (result == GNOME_VFS_OK || result == GNOME_VFS_ERROR_EOF) {
+ gtk_signal_emit (GTK_OBJECT (view), signals[OPEN_DONE]);
+ view->directory_list = list;
+
+ /* FIXME just to make sure. But these should be
+ already set somewhere else. */
+ view->current_position
+ = GNOME_VFS_DIRECTORY_LIST_POSITION_NONE;
+ view->entries_to_display = 0;
+
+ if (result != GNOME_VFS_ERROR_EOF)
+ view->display_timeout_id
+ = gtk_timeout_add
+ (DISPLAY_TIMEOUT_INTERVAL,
+ display_timeout_cb,
+ view);
+ } else if (entries_read == 0) {
+ gtk_signal_emit (GTK_OBJECT (view),
+ signals[OPEN_FAILED]);
+ }
+ }
+
+ if (view->current_position == GNOME_VFS_DIRECTORY_LIST_POSITION_NONE)
+ view->current_position
+ = gnome_vfs_directory_list_get_position (list);
+
+ view->entries_to_display += entries_read;
+
+ if (result == GNOME_VFS_ERROR_EOF) {
+ display_pending_entries (view);
+ display_icons_not_in_layout (view);
+ stop_load (view);
+ gtk_signal_emit (GTK_OBJECT (view), signals[LOAD_DONE]);
+ } else if (result != GNOME_VFS_OK) {
+ stop_load (view);
+ gtk_signal_emit (GTK_OBJECT (view), signals[LOAD_FAILED],
+ result);
+ return;
+ }
+}
+
+
+gboolean
+explorer_directory_view_is_valid_mode (ExplorerDirectoryViewMode mode)
+{
+ switch (mode) {
+ case EXPLORER_DIRECTORY_VIEW_MODE_ICONS:
+ case EXPLORER_DIRECTORY_VIEW_MODE_SMALLICONS:
+ case EXPLORER_DIRECTORY_VIEW_MODE_DETAILED:
+ case EXPLORER_DIRECTORY_VIEW_MODE_CUSTOM:
+ return TRUE;
+ case EXPLORER_DIRECTORY_VIEW_MODE_NONE:
+ default:
+ return FALSE;
+ }
+}
+
+GtkType
+explorer_directory_view_get_type (void)
+{
+ static GtkType directory_view_type = 0;
+
+ if (directory_view_type == 0) {
+ static GtkTypeInfo directory_view_info = {
+ "ExplorerDirectoryView",
+ sizeof (ExplorerDirectoryView),
+ sizeof (ExplorerDirectoryViewClass),
+ (GtkClassInitFunc) class_init,
+ (GtkObjectInitFunc) init,
+ /* reserved_1 */ NULL,
+ /* reserved_2 */ NULL,
+ (GtkClassInitFunc) NULL
+ };
+
+ directory_view_type
+ = gtk_type_unique (gtk_scroll_frame_get_type (),
+ &directory_view_info);
+ }
+
+ return directory_view_type;
+}
+
+GtkWidget *
+explorer_directory_view_new (ExplorerApplication *application,
+ GnomeAppBar *app_bar,
+ ExplorerDirectoryViewMode mode)
+{
+ ExplorerDirectoryView *new;
+
+ g_return_val_if_fail (application != NULL, NULL);
+ g_return_val_if_fail (explorer_directory_view_is_valid_mode (mode), NULL);
+
+ new = gtk_type_new (explorer_directory_view_get_type ());
+
+ new->application = application;
+ new->app_bar = app_bar;
+
+ explorer_directory_view_set_mode (new, mode);
+
+ return GTK_WIDGET (new);
+}
+
+ExplorerDirectoryViewMode
+explorer_directory_view_get_mode (ExplorerDirectoryView *view)
+{
+ g_return_val_if_fail (view != NULL, EXPLORER_DIRECTORY_VIEW_MODE_ICONS);
+ g_return_val_if_fail (EXPLORER_IS_DIRECTORY_VIEW (view),
+ EXPLORER_DIRECTORY_VIEW_MODE_ICONS);
+
+ return view->mode;
+}
+
+void
+explorer_directory_view_set_mode (ExplorerDirectoryView *view,
+ ExplorerDirectoryViewMode mode)
+{
+ g_return_if_fail (view != NULL);
+ g_return_if_fail (EXPLORER_IS_DIRECTORY_VIEW (view));
+
+ if (view->mode == mode)
+ return;
+
+ switch (mode) {
+ case EXPLORER_DIRECTORY_VIEW_MODE_ICONS:
+ case EXPLORER_DIRECTORY_VIEW_MODE_SMALLICONS:
+ setup_icon_container (view, mode);
+ break;
+ case EXPLORER_DIRECTORY_VIEW_MODE_DETAILED:
+ case EXPLORER_DIRECTORY_VIEW_MODE_CUSTOM:
+ setup_flist (view, mode);
+ break;
+ case EXPLORER_DIRECTORY_VIEW_MODE_NONE:
+ break;
+ }
+
+ view->mode = mode;
+}
+
+
+void
+explorer_directory_view_load_uri (ExplorerDirectoryView *view,
+ const GnomeVFSURI *uri)
+{
+ static GnomeVFSDirectorySortRule sort_rules[] = {
+ GNOME_VFS_DIRECTORY_SORT_DIRECTORYFIRST,
+ GNOME_VFS_DIRECTORY_SORT_BYNAME,
+ GNOME_VFS_DIRECTORY_SORT_NONE
+ }; /* FIXME */
+ GnomeVFSResult result;
+
+ g_return_if_fail (view != NULL);
+ g_return_if_fail (EXPLORER_IS_DIRECTORY_VIEW (view));
+ g_return_if_fail (uri != NULL);
+
+ explorer_directory_view_stop (view);
+
+ if (view->uri != NULL)
+ gnome_vfs_uri_unref (view->uri);
+ view->uri = gnome_vfs_uri_dup (uri);
+
+ result = gnome_vfs_async_load_directory_uri
+ (&view->vfs_async_handle, /* handle */
+ view->uri, /* uri */
+ (GNOME_VFS_FILE_INFO_GETMIMETYPE /* options */
+ | GNOME_VFS_FILE_INFO_FASTMIMETYPE
+ | GNOME_VFS_FILE_INFO_FOLLOWLINKS),
+ NULL, /* meta_keys */
+ sort_rules, /* sort_rules */
+ FALSE, /* reverse_order */
+ GNOME_VFS_DIRECTORY_FILTER_NONE, /* filter_type */
+ (GNOME_VFS_DIRECTORY_FILTER_NOSELFDIR /* filter_options */
+ | GNOME_VFS_DIRECTORY_FILTER_NOPARENTDIR),
+ NULL, /* filter_pattern */
+ 1, /* items_per_notification */
+ directory_load_cb, /* callback */
+ view); /* callback_data */
+
+ if (result != GNOME_VFS_OK)
+ gtk_signal_emit (GTK_OBJECT (view), signals[OPEN_FAILED],
+ result);
+}
+
+void
+explorer_directory_view_stop (ExplorerDirectoryView *view)
+{
+ g_return_if_fail (view != NULL);
+ g_return_if_fail (EXPLORER_IS_DIRECTORY_VIEW (view));
+
+ if (view->vfs_async_handle == NULL)
+ return;
+
+ stop_load (view);
+}
+
+
+/* WARNING WARNING WARNING
+
+ These two functions actually do completely different things, although they
+ have similiar name. (Actually, maybe I should change these names: FIXME.)
+
+ The `get' function retrieves the current *actual* layout from the icon
+ container. The `set' function, instead, specifies the layout that will be
+ used when adding new files to the view. */
+
+GnomeIconContainerLayout *
+explorer_directory_view_get_icon_layout (ExplorerDirectoryView *view)
+{
+ g_return_val_if_fail (view != NULL, NULL);
+ g_return_val_if_fail (EXPLORER_IS_DIRECTORY_VIEW (view), NULL);
+
+ if (mode_uses_icon_container (view->mode)) {
+ GnomeIconContainer *icon_container;
+
+ icon_container = get_icon_container (view);
+ return gnome_icon_container_get_layout (icon_container);
+ }
+
+ return NULL;
+}
+
+void
+explorer_directory_view_set_icon_layout (ExplorerDirectoryView *view,
+ const GnomeIconContainerLayout *layout)
+{
+ g_return_if_fail (view != NULL);
+
+ view->icon_layout = layout;
+}
+
+void
+explorer_directory_view_line_up_icons (ExplorerDirectoryView *view)
+{
+ GnomeIconContainer *container;
+
+ g_return_if_fail (view != NULL);
+
+ container = get_icon_container (view);
+ if (container == NULL)
+ return;
+
+ gnome_icon_container_line_up (container);
+}
+
+
+void
+explorer_directory_view_sort (ExplorerDirectoryView *view,
+ ExplorerDirectoryViewSortType sort_type)
+{
+ GnomeVFSDirectorySortRule *rules;
+ GnomeIconContainer *icon_container;
+
+#define ALLOC_RULES(n) alloca ((n) * sizeof (GnomeVFSDirectorySortRule))
+
+ g_return_if_fail (view != NULL);
+ g_return_if_fail (EXPLORER_IS_DIRECTORY_VIEW (view));
+
+ if (view->directory_list == NULL)
+ return;
+
+ switch (sort_type) {
+ case EXPLORER_DIRECTORY_VIEW_SORT_BYNAME:
+ rules = ALLOC_RULES (3);
+ rules[0] = GNOME_VFS_DIRECTORY_SORT_DIRECTORYFIRST;
+ rules[1] = GNOME_VFS_DIRECTORY_SORT_BYNAME;
+ rules[2] = GNOME_VFS_DIRECTORY_SORT_NONE;
+ break;
+ case EXPLORER_DIRECTORY_VIEW_SORT_BYSIZE:
+ rules = ALLOC_RULES (4);
+ rules[0] = GNOME_VFS_DIRECTORY_SORT_DIRECTORYFIRST;
+ rules[1] = GNOME_VFS_DIRECTORY_SORT_BYSIZE;
+ rules[2] = GNOME_VFS_DIRECTORY_SORT_BYNAME;
+ rules[3] = GNOME_VFS_DIRECTORY_SORT_NONE;
+ break;
+ case EXPLORER_DIRECTORY_VIEW_SORT_BYTYPE:
+ rules = ALLOC_RULES (4);
+ rules[0] = GNOME_VFS_DIRECTORY_SORT_DIRECTORYFIRST;
+ rules[1] = GNOME_VFS_DIRECTORY_SORT_BYMIMETYPE;
+ rules[2] = GNOME_VFS_DIRECTORY_SORT_BYNAME;
+ rules[3] = GNOME_VFS_DIRECTORY_SORT_NONE;
+ break;
+ default:
+ g_warning ("explorer_directory_view_sort: Unknown sort mode %d\n",
+ sort_type);
+ return;
+ }
+
+ EXPLORER_DEBUG (("Sorting."));
+ gnome_vfs_directory_list_sort (view->directory_list, FALSE, rules);
+
+ /* This will make sure icons are re-laid out according to the new
+ order. */
+ if (view->icon_layout != NULL)
+ view->icon_layout = NULL;
+
+ /* FIXME FIXME FIXME */
+ icon_container = get_icon_container (view);
+ if (icon_container != NULL)
+ load_icon_container (view, icon_container);
+
+#undef ALLOC_RULES
+}
diff --git a/src/file-manager/fm-directory-view.h b/src/file-manager/fm-directory-view.h
new file mode 100644
index 000000000..6c5f0f8d4
--- /dev/null
+++ b/src/file-manager/fm-directory-view.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* explorer-directory-view.h
+ *
+ * Copyright (C) 1999 Free Software Foundaton
+ *
+ * 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.
+ *
+ * Author: Ettore Perazzoli
+ */
+#ifndef __EXPLORER_DIRECTORY_VIEW_H__
+#define __EXPLORER_DIRECTORY_VIEW_H__
+
+#include <libgnomevfs/gnome-vfs.h>
+
+#include "gnome-icon-container.h"
+#include "gtkscrollframe.h"
+
+
+enum _ExplorerDirectoryViewMode {
+ EXPLORER_DIRECTORY_VIEW_MODE_NONE, /* Internal */
+ EXPLORER_DIRECTORY_VIEW_MODE_ICONS,
+ EXPLORER_DIRECTORY_VIEW_MODE_SMALLICONS,
+ EXPLORER_DIRECTORY_VIEW_MODE_DETAILED,
+ EXPLORER_DIRECTORY_VIEW_MODE_CUSTOM
+};
+typedef enum _ExplorerDirectoryViewMode ExplorerDirectoryViewMode;
+
+enum _ExplorerDirectoryViewSortType {
+ EXPLORER_DIRECTORY_VIEW_SORT_BYNAME,
+ EXPLORER_DIRECTORY_VIEW_SORT_BYSIZE,
+ EXPLORER_DIRECTORY_VIEW_SORT_BYTYPE
+};
+typedef enum _ExplorerDirectoryViewSortType ExplorerDirectoryViewSortType;
+
+
+typedef struct _ExplorerDirectoryView ExplorerDirectoryView;
+typedef struct _ExplorerDirectoryViewClass ExplorerDirectoryViewClass;
+
+#include "explorer-application.h"
+
+
+#define EXPLORER_TYPE_DIRECTORY_VIEW (explorer_directory_view_get_type ())
+#define EXPLORER_DIRECTORY_VIEW(obj) (GTK_CHECK_CAST ((obj), EXPLORER_TYPE_DIRECTORY_VIEW, ExplorerDirectoryView))
+#define EXPLORER_DIRECTORY_VIEW_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), EXPLORER_TYPE_DIRECTORY_VIEW, ExplorerDirectoryViewClass))
+#define EXPLORER_IS_DIRECTORY_VIEW(obj) (GTK_CHECK_TYPE ((obj), EXPLORER_TYPE_DIRECTORY_VIEW))
+#define EXPLORER_IS_DIRECTORY_VIEW_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((obj), EXPLORER_TYPE_DIRECTORY_VIEW))
+
+struct _ExplorerDirectoryView {
+ GtkScrollFrame scroll_frame;
+
+ ExplorerApplication *application;
+ GnomeAppBar *app_bar;
+
+ ExplorerDirectoryViewMode mode;
+
+ GnomeVFSDirectoryList *directory_list;
+ GnomeVFSDirectoryListPosition current_position;
+ guint entries_to_display;
+
+ guint display_timeout_id;
+
+ GnomeVFSAsyncHandle *vfs_async_handle;
+ GnomeVFSURI *uri;
+
+ const GnomeIconContainerLayout *icon_layout;
+ GList *icons_not_in_layout;
+};
+
+struct _ExplorerDirectoryViewClass {
+ GtkScrollFrameClass parent_class;
+
+ /* Signals go here */
+ void (*open_failed) (ExplorerDirectoryView *directory_view,
+ GnomeVFSResult result);
+ void (*open_done) (ExplorerDirectoryView *directory_view);
+ void (*load_failed) (ExplorerDirectoryView *directory_view,
+ GnomeVFSResult result);
+ void (*load_done) (ExplorerDirectoryView *directory_view);
+ void (*activate_uri) (ExplorerDirectoryView *directory_view,
+ const GnomeVFSURI *uri,
+ const gchar *mime_type);
+};
+
+
+gboolean explorer_directory_view_is_valid_mode
+ (ExplorerDirectoryViewMode mode);
+
+GtkType explorer_directory_view_get_type (void);
+GtkWidget *explorer_directory_view_new (ExplorerApplication *application,
+ GnomeAppBar *app_bar,
+ ExplorerDirectoryViewMode mode);
+void explorer_directory_view_set_mode (ExplorerDirectoryView *view,
+ ExplorerDirectoryViewMode mode);
+ExplorerDirectoryViewMode
+ explorer_directory_view_get_mode (ExplorerDirectoryView *view);
+void explorer_directory_view_load_uri (ExplorerDirectoryView *view,
+ const GnomeVFSURI *uri);
+void explorer_directory_view_stop (ExplorerDirectoryView *view);
+
+GnomeIconContainerLayout *
+ explorer_directory_view_get_icon_layout
+ (ExplorerDirectoryView *view);
+void explorer_directory_view_set_icon_layout
+ (ExplorerDirectoryView *view,
+ const GnomeIconContainerLayout
+ *icon_layout);
+
+void explorer_directory_view_line_up_icons
+ (ExplorerDirectoryView *view);
+
+void explorer_directory_view_sort (ExplorerDirectoryView *view,
+ ExplorerDirectoryViewSortType sort_type);
+
+#endif /* __EXPLORER_DIRECTORY_VIEW_H__ */