diff options
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 = >K_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 = >K_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 = >K_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 = >K_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 = >K_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 = >K_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__ */ |