summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOndrej Holy <oholy@redhat.com>2022-02-11 14:31:50 +0100
committerAntónio Fernandes <antoniof@gnome.org>2022-02-11 19:24:09 +0000
commitf0ed057f7a27cacdf9cfa80432e64046685f113a (patch)
tree7dcfb03a1ef96950c4e35f1bccd5ce7d3cef6539
parent7cd29ca006e7438491d528224250d8d7b22c511e (diff)
downloadnautilus-f0ed057f7a27cacdf9cfa80432e64046685f113a.tar.gz
Revert "general: Remove canvas view"
This reverts commit 2d1deaac2dd12b0ba16446bfbf3498b266e60338.
-rw-r--r--eel/eel-canvas.c4153
-rw-r--r--eel/eel-canvas.h497
-rw-r--r--eel/meson.build2
-rw-r--r--po/POTFILES.in6
-rw-r--r--src/meson.build13
-rw-r--r--src/nautilus-canvas-container.c6360
-rw-r--r--src/nautilus-canvas-container.h295
-rw-r--r--src/nautilus-canvas-dnd.c1833
-rw-r--r--src/nautilus-canvas-dnd.h56
-rw-r--r--src/nautilus-canvas-item.c2760
-rw-r--r--src/nautilus-canvas-item.h90
-rw-r--r--src/nautilus-canvas-private.h223
-rw-r--r--src/nautilus-canvas-view-container.c377
-rw-r--r--src/nautilus-canvas-view-container.h35
-rw-r--r--src/nautilus-canvas-view.c1646
-rw-r--r--src/nautilus-canvas-view.h45
-rw-r--r--src/nautilus-debug.c1
-rw-r--r--src/nautilus-debug.h25
-rw-r--r--src/nautilus-dnd.c1
-rw-r--r--src/nautilus-files-view.c1
-rw-r--r--src/nautilus-selection-canvas-item.c554
-rw-r--r--src/nautilus-selection-canvas-item.h62
-rw-r--r--src/resources/css/Adwaita.css12
23 files changed, 19035 insertions, 12 deletions
diff --git a/eel/eel-canvas.c b/eel/eel-canvas.c
new file mode 100644
index 000000000..559114f8f
--- /dev/null
+++ b/eel/eel-canvas.c
@@ -0,0 +1,4153 @@
+/*
+ * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
+ * All rights reserved.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * @NOTATION@
+ */
+/*
+ * EelCanvas widget - Tk-like canvas widget for Gnome
+ *
+ * EelCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is
+ * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties.
+ *
+ *
+ * Authors: Federico Mena <federico@nuclecu.unam.mx>
+ * Raph Levien <raph@gimp.org>
+ */
+
+/*
+ * TO-DO list for the canvas:
+ *
+ * - Allow to specify whether EelCanvasImage sizes are in units or pixels (scale or don't scale).
+ *
+ * - GC put functions for items.
+ *
+ * - Widget item (finish it).
+ *
+ * - GList *eel_canvas_gimme_all_items_contained_in_this_area (EelCanvas *canvas, Rectangle area);
+ *
+ * - Retrofit all the primitive items with microtile support.
+ *
+ * - Curve support for line item.
+ *
+ * - Arc item (Havoc has it; to be integrated in EelCanvasEllipse).
+ *
+ * - Sane font handling API.
+ *
+ * - Get_arg methods for items:
+ * - How to fetch the outline width and know whether it is in pixels or units?
+ */
+
+#include <config.h>
+
+#include <math.h>
+#include <string.h>
+#include <stdio.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+#include <cairo-gobject.h>
+#include "eel-canvas.h"
+
+static void eel_canvas_request_update (EelCanvas *canvas);
+static void group_add (EelCanvasGroup *group,
+ EelCanvasItem *item);
+static void group_remove (EelCanvasGroup *group,
+ EelCanvasItem *item);
+static void redraw_and_repick_if_mapped (EelCanvasItem *item);
+
+/*** EelCanvasItem ***/
+
+/* Some convenience stuff */
+#define GCI_UPDATE_MASK (EEL_CANVAS_UPDATE_REQUESTED | EEL_CANVAS_UPDATE_DEEP)
+
+enum
+{
+ ITEM_PROP_0,
+ ITEM_PROP_VISIBLE
+};
+
+enum
+{
+ ITEM_DESTROY,
+ ITEM_EVENT,
+ ITEM_LAST_SIGNAL
+};
+
+static void eel_canvas_item_class_init (EelCanvasItemClass *klass);
+static void eel_canvas_item_init (EelCanvasItem *item);
+static int emit_event (EelCanvas *canvas,
+ GdkEvent *event);
+
+static guint item_signals[ITEM_LAST_SIGNAL];
+
+static GObjectClass *item_parent_class;
+
+static gpointer accessible_item_parent_class;
+static gpointer accessible_parent_class;
+
+
+/**
+ * eel_canvas_item_get_type:
+ *
+ * Registers the &EelCanvasItem class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &EelCanvasItem class.
+ **/
+GType
+eel_canvas_item_get_type (void)
+{
+ static GType canvas_item_type = 0;
+
+ if (!canvas_item_type)
+ {
+ static const GTypeInfo canvas_item_info =
+ {
+ sizeof (EelCanvasItemClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) eel_canvas_item_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (EelCanvasItem),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) eel_canvas_item_init
+ };
+
+ canvas_item_type = g_type_register_static (G_TYPE_INITIALLY_UNOWNED,
+ "EelCanvasItem",
+ &canvas_item_info,
+ 0);
+ }
+
+ return canvas_item_type;
+}
+
+/* Object initialization function for EelCanvasItem */
+static void
+eel_canvas_item_init (EelCanvasItem *item)
+{
+ item->flags |= EEL_CANVAS_ITEM_VISIBLE;
+}
+
+/* Performs post-creation operations on a canvas item (adding it to its parent
+ * group, etc.)
+ */
+static void
+item_post_create_setup (EelCanvasItem *item)
+{
+ group_add (EEL_CANVAS_GROUP (item->parent), item);
+
+ redraw_and_repick_if_mapped (item);
+}
+
+/**
+ * eel_canvas_item_new:
+ * @parent: The parent group for the new item.
+ * @type: The object type of the item.
+ * @first_arg_name: A list of object argument name/value pairs, NULL-terminated,
+ * used to configure the item. For example, "fill_color", "black",
+ * "width_units", 5.0, NULL.
+ * @Varargs:
+ *
+ * Creates a new canvas item with @parent as its parent group. The item is
+ * created at the top of its parent's stack, and starts up as visible. The item
+ * is of the specified @type, for example, it can be
+ * eel_canvas_rect_get_type(). The list of object arguments/value pairs is
+ * used to configure the item.
+ *
+ * Return value: The newly-created item.
+ **/
+EelCanvasItem *
+eel_canvas_item_new (EelCanvasGroup *parent,
+ GType type,
+ const gchar *first_arg_name,
+ ...)
+{
+ EelCanvasItem *item;
+ va_list args;
+
+ g_return_val_if_fail (EEL_IS_CANVAS_GROUP (parent), NULL);
+ g_return_val_if_fail (g_type_is_a (type, eel_canvas_item_get_type ()), NULL);
+
+ item = EEL_CANVAS_ITEM (g_object_new (type, NULL));
+
+ item->parent = EEL_CANVAS_ITEM (parent);
+ item->canvas = item->parent->canvas;
+
+ va_start (args, first_arg_name);
+ g_object_set_valist (G_OBJECT (item), first_arg_name, args);
+ va_end (args);
+
+ item_post_create_setup (item);
+
+ return item;
+}
+
+/* Set_property handler for canvas items */
+static void
+eel_canvas_item_set_property (GObject *gobject,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EelCanvasItem *item;
+
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (gobject));
+
+ item = EEL_CANVAS_ITEM (gobject);
+
+ switch (param_id)
+ {
+ case ITEM_PROP_VISIBLE:
+ {
+ if (g_value_get_boolean (value))
+ {
+ eel_canvas_item_show (item);
+ }
+ else
+ {
+ eel_canvas_item_hide (item);
+ }
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
+ }
+ break;
+ }
+}
+
+/* Get_property handler for canvas items */
+static void
+eel_canvas_item_get_property (GObject *gobject,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EelCanvasItem *item;
+
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (gobject));
+
+ item = EEL_CANVAS_ITEM (gobject);
+
+ switch (param_id)
+ {
+ case ITEM_PROP_VISIBLE:
+ {
+ g_value_set_boolean (value, item->flags & EEL_CANVAS_ITEM_VISIBLE);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+redraw_and_repick_if_mapped (EelCanvasItem *item)
+{
+ if (item->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ eel_canvas_item_request_redraw (item);
+ item->canvas->need_repick = TRUE;
+ }
+}
+
+/* Dispose handler for canvas items */
+static void
+eel_canvas_item_dispose (GObject *object)
+{
+ EelCanvasItem *item;
+
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (object));
+
+ item = EEL_CANVAS_ITEM (object);
+
+ if (item->canvas)
+ {
+ eel_canvas_item_request_redraw (item);
+
+ /* Make the canvas forget about us */
+
+ if (item == item->canvas->current_item)
+ {
+ item->canvas->current_item = NULL;
+ item->canvas->need_repick = TRUE;
+ }
+
+ if (item == item->canvas->new_current_item)
+ {
+ item->canvas->new_current_item = NULL;
+ item->canvas->need_repick = TRUE;
+ }
+
+ eel_canvas_item_ungrab (item);
+
+ if (item == item->canvas->focused_item)
+ {
+ item->canvas->focused_item = NULL;
+ }
+
+ /* Normal destroy stuff */
+
+ if (item->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item);
+ }
+
+ if (item->flags & EEL_CANVAS_ITEM_REALIZED)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->unrealize)(item);
+ }
+
+ if (item->parent)
+ {
+ group_remove (EEL_CANVAS_GROUP (item->parent), item);
+ }
+
+ item->canvas = NULL;
+ }
+
+ g_object_set_data (object, "in-destruction", GINT_TO_POINTER (1));
+ g_signal_emit (object, item_signals[ITEM_DESTROY], 0);
+
+ g_object_set_data (object, "in-destruction", NULL);
+
+ G_OBJECT_CLASS (item_parent_class)->dispose (object);
+}
+
+void
+eel_canvas_item_destroy (EelCanvasItem *item)
+{
+ if (g_object_get_data (G_OBJECT (item), "in-destruction") == NULL)
+ {
+ g_object_run_dispose (G_OBJECT (item));
+ }
+}
+
+/* Realize handler for canvas items */
+static void
+eel_canvas_item_realize (EelCanvasItem *item)
+{
+ if (item->parent && !(item->parent->flags & EEL_CANVAS_ITEM_REALIZED))
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item->parent)->realize)(item->parent);
+ }
+
+ if (item->parent == NULL && !gtk_widget_get_realized (GTK_WIDGET (item->canvas)))
+ {
+ gtk_widget_realize (GTK_WIDGET (item->canvas));
+ }
+
+ item->flags |= EEL_CANVAS_ITEM_REALIZED;
+
+ eel_canvas_item_request_update (item);
+}
+
+/* Unrealize handler for canvas items */
+static void
+eel_canvas_item_unrealize (EelCanvasItem *item)
+{
+ if (item->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item);
+ }
+
+ item->flags &= ~(EEL_CANVAS_ITEM_REALIZED);
+}
+
+/* Map handler for canvas items */
+static void
+eel_canvas_item_map (EelCanvasItem *item)
+{
+ item->flags |= EEL_CANVAS_ITEM_MAPPED;
+}
+
+/* Unmap handler for canvas items */
+static void
+eel_canvas_item_unmap (EelCanvasItem *item)
+{
+ item->flags &= ~(EEL_CANVAS_ITEM_MAPPED);
+}
+
+/* Update handler for canvas items */
+static void
+eel_canvas_item_update (EelCanvasItem *item,
+ double i2w_dx,
+ double i2w_dy,
+ int flags)
+{
+ item->flags &= ~(EEL_CANVAS_ITEM_NEED_UPDATE);
+ item->flags &= ~(EEL_CANVAS_ITEM_NEED_DEEP_UPDATE);
+}
+
+/*
+ * This routine invokes the update method of the item
+ * Please notice, that we take parent to canvas pixel matrix as argument
+ * unlike virtual method ::update, whose argument is item 2 canvas pixel
+ * matrix
+ *
+ * I will try to force somewhat meaningful naming for affines (Lauris)
+ * General naming rule is FROM2TO, where FROM and TO are abbreviations
+ * So p2cpx is Parent2CanvasPixel and i2cpx is Item2CanvasPixel
+ * I hope that this helps to keep track of what really happens
+ *
+ */
+
+static void
+eel_canvas_item_invoke_update (EelCanvasItem *item,
+ double i2w_dx,
+ double i2w_dy,
+ int flags)
+{
+ int child_flags;
+
+ child_flags = flags;
+
+ /* apply object flags to child flags */
+ child_flags &= ~EEL_CANVAS_UPDATE_REQUESTED;
+
+ if (item->flags & EEL_CANVAS_ITEM_NEED_UPDATE)
+ {
+ child_flags |= EEL_CANVAS_UPDATE_REQUESTED;
+ }
+
+ if (item->flags & EEL_CANVAS_ITEM_NEED_DEEP_UPDATE)
+ {
+ child_flags |= EEL_CANVAS_UPDATE_DEEP;
+ }
+
+ if (child_flags & GCI_UPDATE_MASK)
+ {
+ if (EEL_CANVAS_ITEM_GET_CLASS (item)->update)
+ {
+ EEL_CANVAS_ITEM_GET_CLASS (item)->update (item, i2w_dx, i2w_dy, child_flags);
+ }
+ }
+
+ /* If this fail you probably forgot to chain up to
+ * EelCanvasItem::update from a derived class */
+ g_return_if_fail (!(item->flags & EEL_CANVAS_ITEM_NEED_UPDATE));
+}
+
+/*
+ * This routine invokes the point method of the item.
+ * The arguments x, y should be in the parent item local coordinates.
+ */
+
+static double
+eel_canvas_item_invoke_point (EelCanvasItem *item,
+ double x,
+ double y,
+ int cx,
+ int cy,
+ EelCanvasItem **actual_item)
+{
+ /* Calculate x & y in item local coordinates */
+
+ if (EEL_CANVAS_ITEM_GET_CLASS (item)->point)
+ {
+ return EEL_CANVAS_ITEM_GET_CLASS (item)->point (item, x, y, cx, cy, actual_item);
+ }
+
+ return 1e18;
+}
+
+/**
+ * eel_canvas_item_set:
+ * @item: A canvas item.
+ * @first_arg_name: The list of object argument name/value pairs used to configure the item.
+ * @Varargs:
+ *
+ * Configures a canvas item. The arguments in the item are set to the specified
+ * values, and the item is repainted as appropriate.
+ **/
+void
+eel_canvas_item_set (EelCanvasItem *item,
+ const gchar *first_arg_name,
+ ...)
+{
+ va_list args;
+
+ va_start (args, first_arg_name);
+ g_object_set_valist (G_OBJECT (item), first_arg_name, args);
+ va_end (args);
+
+ item->canvas->need_repick = TRUE;
+}
+
+/**
+ * eel_canvas_item_move:
+ * @item: A canvas item.
+ * @dx: Horizontal offset.
+ * @dy: Vertical offset.
+ *
+ * Moves a canvas item by creating an affine transformation matrix for
+ * translation by using the specified values. This happens in item
+ * local coordinate system, so if you have nontrivial transform, it
+ * most probably does not do, what you want.
+ **/
+void
+eel_canvas_item_move (EelCanvasItem *item,
+ double dx,
+ double dy)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+
+ if (!EEL_CANVAS_ITEM_GET_CLASS (item)->translate)
+ {
+ g_warning ("Item type %s does not implement translate method.\n",
+ g_type_name (G_OBJECT_TYPE (item)));
+ return;
+ }
+
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->translate)(item, dx, dy);
+
+ if (item->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ item->canvas->need_repick = TRUE;
+ }
+
+ if (!(item->flags & EEL_CANVAS_ITEM_NEED_DEEP_UPDATE))
+ {
+ item->flags |= EEL_CANVAS_ITEM_NEED_DEEP_UPDATE;
+ if (item->parent != NULL)
+ {
+ eel_canvas_item_request_update (item->parent);
+ }
+ else
+ {
+ eel_canvas_request_update (item->canvas);
+ }
+ }
+}
+
+static void
+eel_canvas_queue_resize (EelCanvas *canvas)
+{
+ if (gtk_widget_is_drawable (GTK_WIDGET (canvas)))
+ {
+ gtk_widget_queue_resize (GTK_WIDGET (canvas));
+ }
+}
+
+/* Convenience function to reorder items in a group's child list. This puts the
+ * specified link after the "before" link. Returns TRUE if the list was changed.
+ */
+static gboolean
+put_item_after (GList *link,
+ GList *before)
+{
+ EelCanvasGroup *parent;
+
+ if (link == before)
+ {
+ return FALSE;
+ }
+
+ parent = EEL_CANVAS_GROUP (EEL_CANVAS_ITEM (link->data)->parent);
+
+ if (before == NULL)
+ {
+ if (link == parent->item_list)
+ {
+ return FALSE;
+ }
+
+ link->prev->next = link->next;
+
+ if (link->next)
+ {
+ link->next->prev = link->prev;
+ }
+ else
+ {
+ parent->item_list_end = link->prev;
+ }
+
+ link->prev = before;
+ link->next = parent->item_list;
+ link->next->prev = link;
+ parent->item_list = link;
+ }
+ else
+ {
+ if ((link == parent->item_list_end) && (before == parent->item_list_end->prev))
+ {
+ return FALSE;
+ }
+
+ if (link->next)
+ {
+ link->next->prev = link->prev;
+ }
+
+ if (link->prev)
+ {
+ link->prev->next = link->next;
+ }
+ else
+ {
+ parent->item_list = link->next;
+ parent->item_list->prev = NULL;
+ }
+
+ link->prev = before;
+ link->next = before->next;
+
+ link->prev->next = link;
+
+ if (link->next)
+ {
+ link->next->prev = link;
+ }
+ else
+ {
+ parent->item_list_end = link;
+ }
+ }
+ return TRUE;
+}
+
+
+/**
+ * eel_canvas_item_raise:
+ * @item: A canvas item.
+ * @positions: Number of steps to raise the item.
+ *
+ * Raises the item in its parent's stack by the specified number of positions.
+ * If the number of positions is greater than the distance to the top of the
+ * stack, then the item is put at the top.
+ **/
+void
+eel_canvas_item_raise (EelCanvasItem *item,
+ int positions)
+{
+ GList *link, *before;
+ EelCanvasGroup *parent;
+
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+ g_return_if_fail (positions >= 0);
+
+ if (!item->parent || positions == 0)
+ {
+ return;
+ }
+
+ parent = EEL_CANVAS_GROUP (item->parent);
+ link = g_list_find (parent->item_list, item);
+ g_assert (link != NULL);
+
+ for (before = link; positions && before; positions--)
+ {
+ before = before->next;
+ }
+
+ if (!before)
+ {
+ before = parent->item_list_end;
+ }
+
+ if (put_item_after (link, before))
+ {
+ redraw_and_repick_if_mapped (item);
+ }
+}
+
+
+/**
+ * eel_canvas_item_lower:
+ * @item: A canvas item.
+ * @positions: Number of steps to lower the item.
+ *
+ * Lowers the item in its parent's stack by the specified number of positions.
+ * If the number of positions is greater than the distance to the bottom of the
+ * stack, then the item is put at the bottom.
+ **/
+void
+eel_canvas_item_lower (EelCanvasItem *item,
+ int positions)
+{
+ GList *link, *before;
+ EelCanvasGroup *parent;
+
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+ g_return_if_fail (positions >= 1);
+
+ if (!item->parent)
+ {
+ return;
+ }
+
+ parent = EEL_CANVAS_GROUP (item->parent);
+ link = g_list_find (parent->item_list, item);
+ g_assert (link != NULL);
+
+ if (link->prev)
+ {
+ for (before = link->prev; positions && before; positions--)
+ {
+ before = before->prev;
+ }
+ }
+ else
+ {
+ before = NULL;
+ }
+
+ if (put_item_after (link, before))
+ {
+ redraw_and_repick_if_mapped (item);
+ }
+}
+
+
+/**
+ * eel_canvas_item_raise_to_top:
+ * @item: A canvas item.
+ *
+ * Raises an item to the top of its parent's stack.
+ **/
+void
+eel_canvas_item_raise_to_top (EelCanvasItem *item)
+{
+ GList *link;
+ EelCanvasGroup *parent;
+
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+
+ if (!item->parent)
+ {
+ return;
+ }
+
+ parent = EEL_CANVAS_GROUP (item->parent);
+ link = g_list_find (parent->item_list, item);
+ g_assert (link != NULL);
+
+ if (put_item_after (link, parent->item_list_end))
+ {
+ redraw_and_repick_if_mapped (item);
+ }
+}
+
+
+/**
+ * eel_canvas_item_lower_to_bottom:
+ * @item: A canvas item.
+ *
+ * Lowers an item to the bottom of its parent's stack.
+ **/
+void
+eel_canvas_item_lower_to_bottom (EelCanvasItem *item)
+{
+ GList *link;
+ EelCanvasGroup *parent;
+
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+
+ if (!item->parent)
+ {
+ return;
+ }
+
+ parent = EEL_CANVAS_GROUP (item->parent);
+ link = g_list_find (parent->item_list, item);
+ g_assert (link != NULL);
+
+ if (put_item_after (link, NULL))
+ {
+ redraw_and_repick_if_mapped (item);
+ }
+}
+
+/**
+ * eel_canvas_item_send_behind:
+ * @item: A canvas item.
+ * @behind_item: The canvas item to put item behind, or NULL
+ *
+ * Moves item to a in the position in the stacking order so that
+ * it is placed immediately below behind_item, or at the top if
+ * behind_item is NULL.
+ **/
+void
+eel_canvas_item_send_behind (EelCanvasItem *item,
+ EelCanvasItem *behind_item)
+{
+ GList *item_list;
+ int item_position, behind_position;
+
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+
+ if (behind_item == NULL)
+ {
+ eel_canvas_item_raise_to_top (item);
+ return;
+ }
+
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (behind_item));
+ g_return_if_fail (item->parent == behind_item->parent);
+
+ item_list = EEL_CANVAS_GROUP (item->parent)->item_list;
+
+ item_position = g_list_index (item_list, item);
+ g_assert (item_position != -1);
+ behind_position = g_list_index (item_list, behind_item);
+ g_assert (behind_position != -1);
+ g_assert (item_position != behind_position);
+
+ if (item_position == behind_position - 1)
+ {
+ return;
+ }
+
+ if (item_position < behind_position)
+ {
+ eel_canvas_item_raise (item, (behind_position - 1) - item_position);
+ }
+ else
+ {
+ eel_canvas_item_lower (item, item_position - behind_position);
+ }
+}
+
+/**
+ * eel_canvas_item_show:
+ * @item: A canvas item.
+ *
+ * Shows a canvas item. If the item was already shown, then no action is taken.
+ **/
+void
+eel_canvas_item_show (EelCanvasItem *item)
+{
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+
+ if (!(item->flags & EEL_CANVAS_ITEM_VISIBLE))
+ {
+ item->flags |= EEL_CANVAS_ITEM_VISIBLE;
+
+ if (!(item->flags & EEL_CANVAS_ITEM_REALIZED))
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->realize)(item);
+ }
+
+ if (item->parent != NULL)
+ {
+ if (!(item->flags & EEL_CANVAS_ITEM_MAPPED) &&
+ item->parent->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->map)(item);
+ }
+ }
+ else
+ {
+ if (!(item->flags & EEL_CANVAS_ITEM_MAPPED) &&
+ gtk_widget_get_mapped (GTK_WIDGET (item->canvas)))
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->map)(item);
+ }
+ }
+
+ redraw_and_repick_if_mapped (item);
+ eel_canvas_queue_resize (item->canvas);
+ }
+}
+
+
+/**
+ * eel_canvas_item_hide:
+ * @item: A canvas item.
+ *
+ * Hides a canvas item. If the item was already hidden, then no action is
+ * taken.
+ **/
+void
+eel_canvas_item_hide (EelCanvasItem *item)
+{
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+
+ if (item->flags & EEL_CANVAS_ITEM_VISIBLE)
+ {
+ item->flags &= ~EEL_CANVAS_ITEM_VISIBLE;
+
+ redraw_and_repick_if_mapped (item);
+
+ if (item->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item);
+ }
+
+ eel_canvas_queue_resize (item->canvas);
+
+ /* No need to unrealize when we just want to hide */
+ }
+}
+
+
+/*
+ * Prepare the window for grabbing, i.e. show it.
+ */
+static void
+seat_grab_prepare_window (GdkSeat *seat,
+ GdkWindow *window,
+ gpointer user_data)
+{
+ gdk_window_show (window);
+}
+
+/**
+ * eel_canvas_item_grab:
+ * @item: A canvas item.
+ * @event_mask: Mask of events that will be sent to this item.
+ * @cursor: If non-NULL, the cursor that will be used while the grab is active.
+ * @event: The event, triggering the grab, if any.
+ *
+ * Specifies that all events that match the specified event mask should be sent
+ * to the specified item, and also grabs the seat by calling gdk_seat_grab().
+ * If @cursor is not NULL, then that cursor is used while the grab is active.
+ *
+ * Return value: If an item was already grabbed, it returns %GDK_GRAB_ALREADY_GRABBED. If
+ * the specified item was hidden by calling eel_canvas_item_hide(), then it
+ * returns %GDK_GRAB_NOT_VIEWABLE. Else, it returns the result of calling
+ * gdk_seat_grab().
+ **/
+GdkGrabStatus
+eel_canvas_item_grab (EelCanvasItem *item,
+ GdkEventMask event_mask,
+ GdkCursor *cursor,
+ const GdkEvent *event)
+{
+ GdkGrabStatus retval;
+ GdkDisplay *display;
+ GdkSeat *seat;
+
+ g_return_val_if_fail (EEL_IS_CANVAS_ITEM (item), GDK_GRAB_NOT_VIEWABLE);
+ g_return_val_if_fail (gtk_widget_get_mapped (GTK_WIDGET (item->canvas)),
+ GDK_GRAB_NOT_VIEWABLE);
+
+ if (item->canvas->grabbed_item)
+ {
+ return GDK_GRAB_ALREADY_GRABBED;
+ }
+
+ if (!(item->flags & EEL_CANVAS_ITEM_MAPPED))
+ {
+ return GDK_GRAB_NOT_VIEWABLE;
+ }
+
+ display = gtk_widget_get_display (GTK_WIDGET (item->canvas));
+ seat = gdk_display_get_default_seat (display);
+
+ retval = gdk_seat_grab (seat,
+ gtk_layout_get_bin_window (GTK_LAYOUT (item->canvas)),
+ GDK_SEAT_CAPABILITY_ALL_POINTING,
+ FALSE,
+ cursor,
+ event,
+ seat_grab_prepare_window,
+ NULL);
+
+ if (retval != GDK_GRAB_SUCCESS)
+ {
+ return retval;
+ }
+
+ item->canvas->grabbed_item = item;
+ item->canvas->grabbed_event_mask = event_mask;
+ item->canvas->current_item = item; /* So that events go to the grabbed item */
+
+ return retval;
+}
+
+
+/**
+ * eel_canvas_item_ungrab:
+ * @item: A canvas item that holds a grab.
+ *
+ * Ungrabs the item, which must have been grabbed in the canvas, and ungrabs the
+ * seat.
+ **/
+void
+eel_canvas_item_ungrab (EelCanvasItem *item)
+{
+ GdkDisplay *display;
+ GdkSeat *seat;
+
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+
+ if (item->canvas->grabbed_item != item)
+ {
+ return;
+ }
+
+ display = gtk_widget_get_display (GTK_WIDGET (item->canvas));
+ seat = gdk_display_get_default_seat (display);
+
+ item->canvas->grabbed_item = NULL;
+ gdk_seat_ungrab (seat);
+}
+
+/**
+ * eel_canvas_item_i2w:
+ * @item: A canvas item.
+ * @x: X coordinate to convert (input/output value).
+ * @y: Y coordinate to convert (input/output value).
+ *
+ * Converts a coordinate pair from item-relative coordinates to world
+ * coordinates.
+ **/
+void
+eel_canvas_item_i2w (EelCanvasItem *item,
+ double *x,
+ double *y)
+{
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+ g_return_if_fail (x != NULL);
+ g_return_if_fail (y != NULL);
+
+ item = item->parent;
+ while (item)
+ {
+ if (EEL_IS_CANVAS_GROUP (item))
+ {
+ *x += EEL_CANVAS_GROUP (item)->xpos;
+ *y += EEL_CANVAS_GROUP (item)->ypos;
+ }
+
+ item = item->parent;
+ }
+}
+
+/* Returns whether the item is an inferior of or is equal to the parent. */
+static int
+is_descendant (EelCanvasItem *item,
+ EelCanvasItem *parent)
+{
+ for (; item; item = item->parent)
+ {
+ if (item == parent)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * eel_canvas_item_grab_focus:
+ * @item: A canvas item.
+ *
+ * Makes the specified item take the keyboard focus, so all keyboard events will
+ * be sent to it. If the canvas widget itself did not have the focus, it grabs
+ * it as well.
+ **/
+static void
+eel_canvas_item_grab_focus (EelCanvasItem *item)
+{
+ EelCanvasItem *focused_item;
+ GdkEvent ev;
+
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+ g_return_if_fail (gtk_widget_get_can_focus (GTK_WIDGET (item->canvas)));
+
+ focused_item = item->canvas->focused_item;
+
+ if (focused_item)
+ {
+ ev.focus_change.type = GDK_FOCUS_CHANGE;
+ ev.focus_change.window = gtk_layout_get_bin_window (GTK_LAYOUT (item->canvas));
+ ev.focus_change.send_event = FALSE;
+ ev.focus_change.in = FALSE;
+
+ emit_event (item->canvas, &ev);
+ }
+
+ item->canvas->focused_item = item;
+ gtk_widget_grab_focus (GTK_WIDGET (item->canvas));
+
+ if (focused_item)
+ {
+ ev.focus_change.type = GDK_FOCUS_CHANGE;
+ ev.focus_change.window = gtk_layout_get_bin_window (GTK_LAYOUT (item->canvas));
+ ev.focus_change.send_event = FALSE;
+ ev.focus_change.in = TRUE;
+
+ emit_event (item->canvas, &ev);
+ }
+}
+
+
+/**
+ * eel_canvas_item_get_bounds:
+ * @item: A canvas item.
+ * @x1: Leftmost edge of the bounding box (return value).
+ * @y1: Upper edge of the bounding box (return value).
+ * @x2: Rightmost edge of the bounding box (return value).
+ * @y2: Lower edge of the bounding box (return value).
+ *
+ * Queries the bounding box of a canvas item. The bounds are returned in the
+ * coordinate system of the item's parent.
+ **/
+void
+eel_canvas_item_get_bounds (EelCanvasItem *item,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+{
+ double tx1, ty1, tx2, ty2;
+
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+
+ tx1 = ty1 = tx2 = ty2 = 0.0;
+
+ /* Get the item's bounds in its coordinate system */
+
+ if (EEL_CANVAS_ITEM_GET_CLASS (item)->bounds)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->bounds)(item, &tx1, &ty1, &tx2, &ty2);
+ }
+
+ /* Return the values */
+
+ if (x1)
+ {
+ *x1 = tx1;
+ }
+
+ if (y1)
+ {
+ *y1 = ty1;
+ }
+
+ if (x2)
+ {
+ *x2 = tx2;
+ }
+
+ if (y2)
+ {
+ *y2 = ty2;
+ }
+}
+
+
+/**
+ * eel_canvas_item_request_update
+ * @item: A canvas item.
+ *
+ * To be used only by item implementations. Requests that the canvas queue an
+ * update for the specified item.
+ **/
+void
+eel_canvas_item_request_update (EelCanvasItem *item)
+{
+ if (NULL == item->canvas)
+ {
+ return;
+ }
+
+ g_return_if_fail (!item->canvas->doing_update);
+
+ if (item->flags & EEL_CANVAS_ITEM_NEED_UPDATE)
+ {
+ return;
+ }
+
+ item->flags |= EEL_CANVAS_ITEM_NEED_UPDATE;
+
+ if (item->parent != NULL)
+ {
+ /* Recurse up the tree */
+ eel_canvas_item_request_update (item->parent);
+ }
+ else
+ {
+ /* Have reached the top of the tree, make sure the update call gets scheduled. */
+ eel_canvas_request_update (item->canvas);
+ }
+}
+
+/**
+ * eel_canvas_item_request_update
+ * @item: A canvas item.
+ *
+ * Convenience function that informs a canvas that the specified item needs
+ * to be repainted. To be used by item implementations
+ **/
+void
+eel_canvas_item_request_redraw (EelCanvasItem *item)
+{
+ if (item->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ eel_canvas_request_redraw (item->canvas,
+ item->x1, item->y1,
+ item->x2 + 1, item->y2 + 1);
+ }
+}
+
+
+
+/*** EelCanvasGroup ***/
+
+
+enum
+{
+ GROUP_PROP_0,
+ GROUP_PROP_X,
+ GROUP_PROP_Y
+};
+
+
+static void eel_canvas_group_class_init (EelCanvasGroupClass *klass);
+static void eel_canvas_group_init (EelCanvasGroup *group);
+static void eel_canvas_group_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void eel_canvas_group_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void eel_canvas_group_destroy (EelCanvasItem *object);
+
+static void eel_canvas_group_update (EelCanvasItem *item,
+ double i2w_dx,
+ double i2w_dy,
+ int flags);
+static void eel_canvas_group_unrealize (EelCanvasItem *item);
+static void eel_canvas_group_map (EelCanvasItem *item);
+static void eel_canvas_group_unmap (EelCanvasItem *item);
+static void eel_canvas_group_draw (EelCanvasItem *item,
+ cairo_t *cr,
+ cairo_region_t *region);
+static double eel_canvas_group_point (EelCanvasItem *item,
+ double x,
+ double y,
+ int cx,
+ int cy,
+ EelCanvasItem **actual_item);
+static void eel_canvas_group_translate (EelCanvasItem *item,
+ double dx,
+ double dy);
+static void eel_canvas_group_bounds (EelCanvasItem *item,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2);
+
+
+static EelCanvasItemClass *group_parent_class;
+
+
+/**
+ * eel_canvas_group_get_type:
+ *
+ * Registers the &EelCanvasGroup class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &EelCanvasGroup class.
+ **/
+GType
+eel_canvas_group_get_type (void)
+{
+ static GType group_type = 0;
+
+ if (!group_type)
+ {
+ static const GTypeInfo group_info =
+ {
+ sizeof (EelCanvasGroupClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) eel_canvas_group_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (EelCanvasGroup),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) eel_canvas_group_init
+ };
+
+ group_type = g_type_register_static (eel_canvas_item_get_type (),
+ "EelCanvasGroup",
+ &group_info,
+ 0);
+ }
+
+ return group_type;
+}
+
+/* Class initialization function for EelCanvasGroupClass */
+static void
+eel_canvas_group_class_init (EelCanvasGroupClass *klass)
+{
+ GObjectClass *gobject_class;
+ EelCanvasItemClass *item_class;
+
+ gobject_class = (GObjectClass *) klass;
+ item_class = (EelCanvasItemClass *) klass;
+
+ group_parent_class = g_type_class_peek_parent (klass);
+
+ gobject_class->set_property = eel_canvas_group_set_property;
+ gobject_class->get_property = eel_canvas_group_get_property;
+
+ g_object_class_install_property
+ (gobject_class, GROUP_PROP_X,
+ g_param_spec_double ("x",
+ _("X"),
+ _("X"),
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ G_PARAM_READWRITE));
+ g_object_class_install_property
+ (gobject_class, GROUP_PROP_Y,
+ g_param_spec_double ("y",
+ _("Y"),
+ _("Y"),
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ G_PARAM_READWRITE));
+
+ item_class->destroy = eel_canvas_group_destroy;
+ item_class->update = eel_canvas_group_update;
+ item_class->unrealize = eel_canvas_group_unrealize;
+ item_class->map = eel_canvas_group_map;
+ item_class->unmap = eel_canvas_group_unmap;
+ item_class->draw = eel_canvas_group_draw;
+ item_class->point = eel_canvas_group_point;
+ item_class->translate = eel_canvas_group_translate;
+ item_class->bounds = eel_canvas_group_bounds;
+}
+
+/* Object initialization function for EelCanvasGroup */
+static void
+eel_canvas_group_init (EelCanvasGroup *group)
+{
+ group->xpos = 0.0;
+ group->ypos = 0.0;
+}
+
+/* Set_property handler for canvas groups */
+static void
+eel_canvas_group_set_property (GObject *gobject,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EelCanvasItem *item;
+ EelCanvasGroup *group;
+ double old;
+ gboolean moved;
+
+ g_return_if_fail (EEL_IS_CANVAS_GROUP (gobject));
+
+ item = EEL_CANVAS_ITEM (gobject);
+ group = EEL_CANVAS_GROUP (gobject);
+
+ moved = FALSE;
+ switch (param_id)
+ {
+ case GROUP_PROP_X:
+ {
+ old = group->xpos;
+ group->xpos = g_value_get_double (value);
+ if (old != group->xpos)
+ {
+ moved = TRUE;
+ }
+ }
+ break;
+
+ case GROUP_PROP_Y:
+ {
+ old = group->ypos;
+ group->ypos = g_value_get_double (value);
+ if (old != group->ypos)
+ {
+ moved = TRUE;
+ }
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
+ }
+ break;
+ }
+
+ if (moved)
+ {
+ item->flags |= EEL_CANVAS_ITEM_NEED_DEEP_UPDATE;
+ if (item->parent != NULL)
+ {
+ eel_canvas_item_request_update (item->parent);
+ }
+ else
+ {
+ eel_canvas_request_update (item->canvas);
+ }
+ }
+}
+
+/* Get_property handler for canvas groups */
+static void
+eel_canvas_group_get_property (GObject *gobject,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EelCanvasGroup *group;
+
+ g_return_if_fail (EEL_IS_CANVAS_GROUP (gobject));
+
+ group = EEL_CANVAS_GROUP (gobject);
+
+ switch (param_id)
+ {
+ case GROUP_PROP_X:
+ {
+ g_value_set_double (value, group->xpos);
+ }
+ break;
+
+ case GROUP_PROP_Y:
+ {
+ g_value_set_double (value, group->ypos);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
+ }
+ break;
+ }
+}
+
+/* Destroy handler for canvas groups */
+static void
+eel_canvas_group_destroy (EelCanvasItem *object)
+{
+ EelCanvasGroup *group;
+ EelCanvasItem *child;
+ GList *list;
+
+ g_return_if_fail (EEL_IS_CANVAS_GROUP (object));
+
+ group = EEL_CANVAS_GROUP (object);
+
+ list = group->item_list;
+ while (list)
+ {
+ child = list->data;
+ list = list->next;
+
+ eel_canvas_item_destroy (child);
+ }
+
+ if (EEL_CANVAS_ITEM_CLASS (group_parent_class)->destroy)
+ {
+ (*EEL_CANVAS_ITEM_CLASS (group_parent_class)->destroy)(object);
+ }
+}
+
+/* Update handler for canvas groups */
+static void
+eel_canvas_group_update (EelCanvasItem *item,
+ double i2w_dx,
+ double i2w_dy,
+ int flags)
+{
+ EelCanvasGroup *group;
+ GList *list;
+ EelCanvasItem *i;
+ double bbox_x0, bbox_y0, bbox_x1, bbox_y1;
+ gboolean first = TRUE;
+
+ group = EEL_CANVAS_GROUP (item);
+
+ (*group_parent_class->update)(item, i2w_dx, i2w_dy, flags);
+
+ bbox_x0 = 0;
+ bbox_y0 = 0;
+ bbox_x1 = 0;
+ bbox_y1 = 0;
+
+ for (list = group->item_list; list; list = list->next)
+ {
+ i = list->data;
+
+ eel_canvas_item_invoke_update (i, i2w_dx + group->xpos, i2w_dy + group->ypos, flags);
+
+ if (first)
+ {
+ first = FALSE;
+ bbox_x0 = i->x1;
+ bbox_y0 = i->y1;
+ bbox_x1 = i->x2;
+ bbox_y1 = i->y2;
+ }
+ else
+ {
+ bbox_x0 = MIN (bbox_x0, i->x1);
+ bbox_y0 = MIN (bbox_y0, i->y1);
+ bbox_x1 = MAX (bbox_x1, i->x2);
+ bbox_y1 = MAX (bbox_y1, i->y2);
+ }
+ }
+ item->x1 = bbox_x0;
+ item->y1 = bbox_y0;
+ item->x2 = bbox_x1;
+ item->y2 = bbox_y1;
+}
+
+/* Unrealize handler for canvas groups */
+static void
+eel_canvas_group_unrealize (EelCanvasItem *item)
+{
+ EelCanvasGroup *group;
+ GList *list;
+ EelCanvasItem *i;
+
+ group = EEL_CANVAS_GROUP (item);
+
+ /* Unmap group before children to avoid flash */
+ if (item->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item);
+ }
+
+ for (list = group->item_list; list; list = list->next)
+ {
+ i = list->data;
+
+ if (i->flags & EEL_CANVAS_ITEM_REALIZED)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (i)->unrealize)(i);
+ }
+ }
+
+ (*group_parent_class->unrealize)(item);
+}
+
+/* Map handler for canvas groups */
+static void
+eel_canvas_group_map (EelCanvasItem *item)
+{
+ EelCanvasGroup *group;
+ GList *list;
+ EelCanvasItem *i;
+
+ group = EEL_CANVAS_GROUP (item);
+
+ for (list = group->item_list; list; list = list->next)
+ {
+ i = list->data;
+
+ if (i->flags & EEL_CANVAS_ITEM_VISIBLE &&
+ !(i->flags & EEL_CANVAS_ITEM_MAPPED))
+ {
+ if (!(i->flags & EEL_CANVAS_ITEM_REALIZED))
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (i)->realize)(i);
+ }
+
+ (*EEL_CANVAS_ITEM_GET_CLASS (i)->map)(i);
+ }
+ }
+
+ (*group_parent_class->map)(item);
+}
+
+/* Unmap handler for canvas groups */
+static void
+eel_canvas_group_unmap (EelCanvasItem *item)
+{
+ EelCanvasGroup *group;
+ GList *list;
+ EelCanvasItem *i;
+
+ group = EEL_CANVAS_GROUP (item);
+
+ for (list = group->item_list; list; list = list->next)
+ {
+ i = list->data;
+
+ if (i->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (i)->unmap)(i);
+ }
+ }
+
+ (*group_parent_class->unmap)(item);
+}
+
+/* Draw handler for canvas groups */
+static void
+eel_canvas_group_draw (EelCanvasItem *item,
+ cairo_t *cr,
+ cairo_region_t *region)
+{
+ EelCanvasGroup *group;
+ GList *list;
+ EelCanvasItem *child = NULL;
+
+ group = EEL_CANVAS_GROUP (item);
+
+ for (list = group->item_list; list; list = list->next)
+ {
+ child = list->data;
+
+ if ((child->flags & EEL_CANVAS_ITEM_MAPPED) &&
+ (EEL_CANVAS_ITEM_GET_CLASS (child)->draw))
+ {
+ GdkRectangle child_rect;
+
+ child_rect.x = child->x1;
+ child_rect.y = child->y1;
+ child_rect.width = child->x2 - child->x1 + 1;
+ child_rect.height = child->y2 - child->y1 + 1;
+
+ if (cairo_region_contains_rectangle (region, &child_rect) != CAIRO_REGION_OVERLAP_OUT)
+ {
+ EEL_CANVAS_ITEM_GET_CLASS (child)->draw (child, cr, region);
+ }
+ }
+ }
+}
+
+/* Point handler for canvas groups */
+static double
+eel_canvas_group_point (EelCanvasItem *item,
+ double x,
+ double y,
+ int cx,
+ int cy,
+ EelCanvasItem **actual_item)
+{
+ EelCanvasGroup *group;
+ GList *list;
+ EelCanvasItem *child, *point_item;
+ int x1, y1, x2, y2;
+ double gx, gy;
+ double dist, best;
+ int has_point;
+
+ group = EEL_CANVAS_GROUP (item);
+
+ x1 = cx - item->canvas->close_enough;
+ y1 = cy - item->canvas->close_enough;
+ x2 = cx + item->canvas->close_enough;
+ y2 = cy + item->canvas->close_enough;
+
+ best = 0.0;
+ *actual_item = NULL;
+
+ gx = x - group->xpos;
+ gy = y - group->ypos;
+
+ dist = 0.0; /* keep gcc happy */
+
+ for (list = group->item_list; list; list = list->next)
+ {
+ child = list->data;
+
+ if ((child->x1 > x2) || (child->y1 > y2) || (child->x2 < x1) || (child->y2 < y1))
+ {
+ continue;
+ }
+
+ point_item = NULL; /* cater for incomplete item implementations */
+
+ if ((child->flags & EEL_CANVAS_ITEM_MAPPED)
+ && EEL_CANVAS_ITEM_GET_CLASS (child)->point)
+ {
+ dist = eel_canvas_item_invoke_point (child, gx, gy, cx, cy, &point_item);
+ has_point = TRUE;
+ }
+ else
+ {
+ has_point = FALSE;
+ }
+
+ if (has_point
+ && point_item
+ && ((int) (dist * item->canvas->pixels_per_unit + 0.5)
+ <= item->canvas->close_enough))
+ {
+ best = dist;
+ *actual_item = point_item;
+ }
+ }
+
+ return best;
+}
+
+static void
+eel_canvas_group_translate (EelCanvasItem *item,
+ double dx,
+ double dy)
+{
+ EelCanvasGroup *group;
+
+ group = EEL_CANVAS_GROUP (item);
+
+ group->xpos += dx;
+ group->ypos += dy;
+}
+
+/* Bounds handler for canvas groups */
+static void
+eel_canvas_group_bounds (EelCanvasItem *item,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+{
+ EelCanvasGroup *group;
+ EelCanvasItem *child;
+ GList *list;
+ double tx1, ty1, tx2, ty2;
+ double minx, miny, maxx, maxy;
+ int set;
+
+ group = EEL_CANVAS_GROUP (item);
+
+ /* Get the bounds of the first visible item */
+
+ child = NULL; /* Unnecessary but eliminates a warning. */
+
+ set = FALSE;
+
+ for (list = group->item_list; list; list = list->next)
+ {
+ child = list->data;
+
+ if (child->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ set = TRUE;
+ eel_canvas_item_get_bounds (child, &minx, &miny, &maxx, &maxy);
+ break;
+ }
+ }
+
+ /* If there were no visible items, return an empty bounding box */
+
+ if (!set)
+ {
+ *x1 = *y1 = *x2 = *y2 = 0.0;
+ return;
+ }
+
+ /* Now we can grow the bounds using the rest of the items */
+
+ list = list->next;
+
+ for (; list; list = list->next)
+ {
+ child = list->data;
+
+ if (!(child->flags & EEL_CANVAS_ITEM_MAPPED))
+ {
+ continue;
+ }
+
+ eel_canvas_item_get_bounds (child, &tx1, &ty1, &tx2, &ty2);
+
+ if (tx1 < minx)
+ {
+ minx = tx1;
+ }
+
+ if (ty1 < miny)
+ {
+ miny = ty1;
+ }
+
+ if (tx2 > maxx)
+ {
+ maxx = tx2;
+ }
+
+ if (ty2 > maxy)
+ {
+ maxy = ty2;
+ }
+ }
+
+ /* Make the bounds be relative to our parent's coordinate system */
+
+ if (item->parent)
+ {
+ minx += group->xpos;
+ miny += group->ypos;
+ maxx += group->xpos;
+ maxy += group->ypos;
+ }
+
+ *x1 = minx;
+ *y1 = miny;
+ *x2 = maxx;
+ *y2 = maxy;
+}
+
+/* Adds an item to a group */
+static void
+group_add (EelCanvasGroup *group,
+ EelCanvasItem *item)
+{
+ g_object_ref_sink (item);
+
+ if (!group->item_list)
+ {
+ group->item_list = g_list_append (group->item_list, item);
+ group->item_list_end = group->item_list;
+ }
+ else
+ {
+ group->item_list_end = g_list_append (group->item_list_end, item)->next;
+ }
+
+ if (item->flags & EEL_CANVAS_ITEM_VISIBLE &&
+ group->item.flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ if (!(item->flags & EEL_CANVAS_ITEM_REALIZED))
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->realize)(item);
+ }
+
+ if (!(item->flags & EEL_CANVAS_ITEM_MAPPED))
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->map)(item);
+ }
+ }
+
+ if (item->flags & EEL_CANVAS_ITEM_VISIBLE)
+ {
+ eel_canvas_queue_resize (EEL_CANVAS_ITEM (group)->canvas);
+ }
+}
+
+/* Removes an item from a group */
+static void
+group_remove (EelCanvasGroup *group,
+ EelCanvasItem *item)
+{
+ GList *children;
+
+ g_return_if_fail (EEL_IS_CANVAS_GROUP (group));
+ g_return_if_fail (EEL_IS_CANVAS_ITEM (item));
+
+ for (children = group->item_list; children; children = children->next)
+ {
+ if (children->data == item)
+ {
+ if (item->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->unmap)(item);
+ }
+
+ if (item->flags & EEL_CANVAS_ITEM_REALIZED)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (item)->unrealize)(item);
+ }
+
+ if (item->flags & EEL_CANVAS_ITEM_VISIBLE)
+ {
+ eel_canvas_queue_resize (item->canvas);
+ }
+
+ /* Unparent the child */
+
+ item->parent = NULL;
+ /* item->canvas = NULL; */
+ g_object_unref (G_OBJECT (item));
+
+ /* Remove it from the list */
+
+ if (children == group->item_list_end)
+ {
+ group->item_list_end = children->prev;
+ }
+
+ group->item_list = g_list_remove_link (group->item_list, children);
+ g_list_free (children);
+ break;
+ }
+ }
+}
+
+
+/*** EelCanvas ***/
+
+
+static void eel_canvas_class_init (EelCanvasClass *klass);
+static void eel_canvas_init (EelCanvas *canvas);
+static void eel_canvas_destroy (GtkWidget *object);
+static void eel_canvas_map (GtkWidget *widget);
+static void eel_canvas_unmap (GtkWidget *widget);
+static void eel_canvas_realize (GtkWidget *widget);
+static void eel_canvas_unrealize (GtkWidget *widget);
+static void eel_canvas_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static gint eel_canvas_button (GtkWidget *widget,
+ GdkEventButton *event);
+static gint eel_canvas_motion (GtkWidget *widget,
+ GdkEventMotion *event);
+static gint eel_canvas_draw (GtkWidget *widget,
+ cairo_t *cr);
+static gint eel_canvas_key (GtkWidget *widget,
+ GdkEventKey *event);
+static gint eel_canvas_crossing (GtkWidget *widget,
+ GdkEventCrossing *event);
+static gint eel_canvas_focus_in (GtkWidget *widget,
+ GdkEventFocus *event);
+static gint eel_canvas_focus_out (GtkWidget *widget,
+ GdkEventFocus *event);
+static void eel_canvas_request_update_real (EelCanvas *canvas);
+static GtkLayoutClass *canvas_parent_class;
+
+/**
+ * eel_canvas_get_type:
+ *
+ * Registers the &EelCanvas class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &EelCanvas class.
+ **/
+GType
+eel_canvas_get_type (void)
+{
+ static GType canvas_type = 0;
+
+ if (!canvas_type)
+ {
+ static const GTypeInfo canvas_info =
+ {
+ sizeof (EelCanvasClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) eel_canvas_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (EelCanvas),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) eel_canvas_init
+ };
+
+ canvas_type = g_type_register_static (gtk_layout_get_type (),
+ "EelCanvas",
+ &canvas_info,
+ 0);
+ }
+
+ return canvas_type;
+}
+
+static void
+eel_canvas_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+eel_canvas_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+eel_canvas_accessible_adjustment_changed (GtkAdjustment *adjustment,
+ gpointer data)
+{
+ AtkObject *atk_obj;
+
+ /* The scrollbars have changed */
+ atk_obj = ATK_OBJECT (data);
+
+ g_signal_emit_by_name (atk_obj, "visible-data-changed");
+}
+
+static void
+eel_canvas_accessible_initialize (AtkObject *obj,
+ gpointer data)
+{
+ EelCanvas *canvas = data;
+
+ if (ATK_OBJECT_CLASS (accessible_parent_class)->initialize != NULL)
+ {
+ ATK_OBJECT_CLASS (accessible_parent_class)->initialize (obj, data);
+ }
+
+ gtk_accessible_set_widget (GTK_ACCESSIBLE (obj), GTK_WIDGET (data));
+ g_signal_connect (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas)),
+ "value-changed",
+ G_CALLBACK (eel_canvas_accessible_adjustment_changed),
+ obj);
+ g_signal_connect (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas)),
+ "value-changed",
+ G_CALLBACK (eel_canvas_accessible_adjustment_changed),
+ obj);
+
+ obj->role = ATK_ROLE_LAYERED_PANE;
+}
+
+static gint
+eel_canvas_accessible_get_n_children (AtkObject *obj)
+{
+ GtkAccessible *accessible;
+ GtkWidget *widget;
+ EelCanvas *canvas;
+ EelCanvasGroup *root_group;
+
+ accessible = GTK_ACCESSIBLE (obj);
+ widget = gtk_accessible_get_widget (accessible);
+
+ if (widget == NULL)
+ {
+ return 0;
+ }
+
+ g_return_val_if_fail (EEL_IS_CANVAS (widget), 0);
+
+ canvas = EEL_CANVAS (widget);
+ root_group = eel_canvas_root (canvas);
+ g_return_val_if_fail (root_group, 0);
+
+ return 1;
+}
+
+static AtkObject *
+eel_canvas_accessible_ref_child (AtkObject *obj,
+ gint i)
+{
+ GtkAccessible *accessible;
+ GtkWidget *widget;
+ EelCanvas *canvas;
+ EelCanvasGroup *root_group;
+ AtkObject *atk_object;
+
+ /* Canvas only has one child, so return NULL if index is non zero */
+ if (i != 0)
+ {
+ return NULL;
+ }
+
+ accessible = GTK_ACCESSIBLE (obj);
+ widget = gtk_accessible_get_widget (accessible);
+
+ if (widget == NULL)
+ {
+ return NULL;
+ }
+
+ canvas = EEL_CANVAS (widget);
+ root_group = eel_canvas_root (canvas);
+ g_return_val_if_fail (root_group, NULL);
+
+ atk_object = atk_gobject_accessible_for_object (G_OBJECT (root_group));
+
+ return g_object_ref (atk_object);
+}
+
+static void
+eel_canvas_accessible_class_init (EelCanvasAccessibleClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+
+ accessible_parent_class = g_type_class_peek_parent (klass);
+
+ atk_class->initialize = eel_canvas_accessible_initialize;
+ atk_class->get_n_children = eel_canvas_accessible_get_n_children;
+ atk_class->ref_child = eel_canvas_accessible_ref_child;
+}
+
+static void
+eel_canvas_accessible_init (EelCanvasAccessible *accessible)
+{
+}
+
+G_DEFINE_TYPE (EelCanvasAccessible, eel_canvas_accessible, GTK_TYPE_CONTAINER_ACCESSIBLE)
+
+/* Class initialization function for EelCanvasClass */
+static void
+eel_canvas_class_init (EelCanvasClass *klass)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+
+ gobject_class = (GObjectClass *) klass;
+ widget_class = (GtkWidgetClass *) klass;
+
+ canvas_parent_class = g_type_class_peek_parent (klass);
+
+ gobject_class->set_property = eel_canvas_set_property;
+ gobject_class->get_property = eel_canvas_get_property;
+
+ widget_class->destroy = eel_canvas_destroy;
+ widget_class->map = eel_canvas_map;
+ widget_class->unmap = eel_canvas_unmap;
+ widget_class->realize = eel_canvas_realize;
+ widget_class->unrealize = eel_canvas_unrealize;
+ widget_class->size_allocate = eel_canvas_size_allocate;
+ widget_class->button_press_event = eel_canvas_button;
+ widget_class->button_release_event = eel_canvas_button;
+ widget_class->motion_notify_event = eel_canvas_motion;
+ widget_class->draw = eel_canvas_draw;
+ widget_class->key_press_event = eel_canvas_key;
+ widget_class->key_release_event = eel_canvas_key;
+ widget_class->enter_notify_event = eel_canvas_crossing;
+ widget_class->leave_notify_event = eel_canvas_crossing;
+ widget_class->focus_in_event = eel_canvas_focus_in;
+ widget_class->focus_out_event = eel_canvas_focus_out;
+
+ klass->request_update = eel_canvas_request_update_real;
+
+ gtk_widget_class_set_accessible_type (widget_class, eel_canvas_accessible_get_type ());
+}
+
+/* Callback used when the root item of a canvas is destroyed. The user should
+ * never ever do this, so we panic if this happens.
+ */
+static void
+panic_root_destroyed (GtkWidget *object,
+ gpointer data)
+{
+ g_error ("Eeeek, root item %p of canvas %p was destroyed!", object, data);
+}
+
+/* Object initialization function for EelCanvas */
+static void
+eel_canvas_init (EelCanvas *canvas)
+{
+ guint width, height;
+ gtk_widget_set_can_focus (GTK_WIDGET (canvas), TRUE);
+
+ gtk_widget_set_redraw_on_allocate (GTK_WIDGET (canvas), FALSE);
+
+ canvas->scroll_x1 = 0.0;
+ canvas->scroll_y1 = 0.0;
+ gtk_layout_get_size (GTK_LAYOUT (canvas),
+ &width, &height);
+ canvas->scroll_x2 = width;
+ canvas->scroll_y2 = height;
+
+ canvas->pixels_per_unit = 1.0;
+
+ canvas->pick_event.type = GDK_LEAVE_NOTIFY;
+ canvas->pick_event.crossing.x = 0;
+ canvas->pick_event.crossing.y = 0;
+
+ gtk_scrollable_set_hadjustment (GTK_SCROLLABLE (canvas), NULL);
+ gtk_scrollable_set_vadjustment (GTK_SCROLLABLE (canvas), NULL);
+
+ /* Create the root item as a special case */
+
+ canvas->root = EEL_CANVAS_ITEM (g_object_new (eel_canvas_group_get_type (), NULL));
+ canvas->root->canvas = canvas;
+
+ g_object_ref_sink (canvas->root);
+
+ canvas->root_destroy_id = g_signal_connect (G_OBJECT (canvas->root),
+ "destroy", G_CALLBACK (panic_root_destroyed), canvas);
+
+ canvas->need_repick = TRUE;
+ canvas->doing_update = FALSE;
+}
+
+/* Convenience function to remove the idle handler of a canvas */
+static void
+remove_idle (EelCanvas *canvas)
+{
+ if (canvas->idle_id == 0)
+ {
+ return;
+ }
+
+ g_source_remove (canvas->idle_id);
+ canvas->idle_id = 0;
+}
+
+/* Removes the transient state of the canvas (idle handler, grabs). */
+static void
+shutdown_transients (EelCanvas *canvas)
+{
+ /* We turn off the need_redraw flag, since if the canvas is mapped again
+ * it will request a redraw anyways. We do not turn off the need_update
+ * flag, though, because updates are not queued when the canvas remaps
+ * itself.
+ */
+ if (canvas->need_redraw)
+ {
+ canvas->need_redraw = FALSE;
+ }
+
+ if (canvas->grabbed_item)
+ {
+ eel_canvas_item_ungrab (canvas->grabbed_item);
+ }
+
+ remove_idle (canvas);
+}
+
+/* Destroy handler for EelCanvas */
+static void
+eel_canvas_destroy (GtkWidget *object)
+{
+ EelCanvas *canvas;
+
+ g_return_if_fail (EEL_IS_CANVAS (object));
+
+ /* remember, destroy can be run multiple times! */
+
+ canvas = EEL_CANVAS (object);
+
+ g_clear_signal_handler (&canvas->root_destroy_id, G_OBJECT (canvas->root));
+ if (canvas->root)
+ {
+ EelCanvasItem *root = canvas->root;
+ canvas->root = NULL;
+ eel_canvas_item_destroy (root);
+ g_object_unref (root);
+ }
+
+ shutdown_transients (canvas);
+
+ if (GTK_WIDGET_CLASS (canvas_parent_class)->destroy)
+ {
+ (*GTK_WIDGET_CLASS (canvas_parent_class)->destroy)(object);
+ }
+}
+
+/**
+ * eel_canvas_new:
+ * @void:
+ *
+ * Creates a new empty canvas. If you wish to use the
+ * &EelCanvasImage item inside this canvas, then you must push the gdk_imlib
+ * visual and colormap before calling this function, and they can be popped
+ * afterwards.
+ *
+ * Return value: A newly-created canvas.
+ **/
+GtkWidget *
+eel_canvas_new (void)
+{
+ return GTK_WIDGET (g_object_new (eel_canvas_get_type (), NULL));
+}
+
+/* Map handler for the canvas */
+static void
+eel_canvas_map (GtkWidget *widget)
+{
+ EelCanvas *canvas;
+
+ g_return_if_fail (EEL_IS_CANVAS (widget));
+
+ /* Normal widget mapping stuff */
+
+ if (GTK_WIDGET_CLASS (canvas_parent_class)->map)
+ {
+ (*GTK_WIDGET_CLASS (canvas_parent_class)->map)(widget);
+ }
+
+ canvas = EEL_CANVAS (widget);
+
+ /* Map items */
+
+ if (canvas->root->flags & EEL_CANVAS_ITEM_VISIBLE &&
+ !(canvas->root->flags & EEL_CANVAS_ITEM_MAPPED) &&
+ EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->map)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->map)(canvas->root);
+ }
+}
+
+/* Unmap handler for the canvas */
+static void
+eel_canvas_unmap (GtkWidget *widget)
+{
+ EelCanvas *canvas;
+
+ g_return_if_fail (EEL_IS_CANVAS (widget));
+
+ canvas = EEL_CANVAS (widget);
+
+ shutdown_transients (canvas);
+
+ /* Unmap items */
+
+ if (EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->unmap)
+ {
+ (*EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->unmap)(canvas->root);
+ }
+
+ /* Normal widget unmapping stuff */
+
+ if (GTK_WIDGET_CLASS (canvas_parent_class)->unmap)
+ {
+ (*GTK_WIDGET_CLASS (canvas_parent_class)->unmap)(widget);
+ }
+}
+
+/* Realize handler for the canvas */
+static void
+eel_canvas_realize (GtkWidget *widget)
+{
+ EelCanvas *canvas;
+
+ g_return_if_fail (EEL_IS_CANVAS (widget));
+
+ /* Normal widget realization stuff */
+
+ if (GTK_WIDGET_CLASS (canvas_parent_class)->realize)
+ {
+ (*GTK_WIDGET_CLASS (canvas_parent_class)->realize)(widget);
+ }
+
+ canvas = EEL_CANVAS (widget);
+
+ gdk_window_set_events (gtk_layout_get_bin_window (GTK_LAYOUT (canvas)),
+ (gdk_window_get_events (gtk_layout_get_bin_window (GTK_LAYOUT (canvas)))
+ | GDK_EXPOSURE_MASK
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_KEY_PRESS_MASK
+ | GDK_KEY_RELEASE_MASK
+ | GDK_ENTER_NOTIFY_MASK
+ | GDK_LEAVE_NOTIFY_MASK
+ | GDK_FOCUS_CHANGE_MASK));
+
+ /* Create our own temporary pixmap gc and realize all the items */
+
+ (*EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->realize)(canvas->root);
+}
+
+/* Unrealize handler for the canvas */
+static void
+eel_canvas_unrealize (GtkWidget *widget)
+{
+ EelCanvas *canvas;
+
+ g_return_if_fail (EEL_IS_CANVAS (widget));
+
+ canvas = EEL_CANVAS (widget);
+
+ shutdown_transients (canvas);
+
+ /* Unrealize items and parent widget */
+
+ (*EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->unrealize)(canvas->root);
+
+ if (GTK_WIDGET_CLASS (canvas_parent_class)->unrealize)
+ {
+ (*GTK_WIDGET_CLASS (canvas_parent_class)->unrealize)(widget);
+ }
+}
+
+/* Handles scrolling of the canvas. Adjusts the scrolling and zooming offset to
+ * keep as much as possible of the canvas scrolling region in view.
+ */
+static void
+scroll_to (EelCanvas *canvas,
+ int cx,
+ int cy)
+{
+ int scroll_width, scroll_height;
+ int right_limit, bottom_limit;
+ int old_zoom_xofs, old_zoom_yofs;
+ int changed_x = FALSE, changed_y = FALSE;
+ int canvas_width, canvas_height;
+ GtkAllocation allocation;
+ GtkAdjustment *vadjustment, *hadjustment;
+ guint width, height;
+
+ gtk_widget_get_allocation (GTK_WIDGET (canvas), &allocation);
+ canvas_width = allocation.width;
+ canvas_height = allocation.height;
+
+ scroll_width = floor ((canvas->scroll_x2 - canvas->scroll_x1) * canvas->pixels_per_unit + 0.5);
+ scroll_height = floor ((canvas->scroll_y2 - canvas->scroll_y1) * canvas->pixels_per_unit + 0.5);
+
+ right_limit = scroll_width - canvas_width;
+ bottom_limit = scroll_height - canvas_height;
+
+ old_zoom_xofs = canvas->zoom_xofs;
+ old_zoom_yofs = canvas->zoom_yofs;
+
+ if (right_limit < 0)
+ {
+ cx = 0;
+ if (canvas->center_scroll_region)
+ {
+ canvas->zoom_xofs = (canvas_width - scroll_width) / 2;
+ scroll_width = canvas_width;
+ }
+ else
+ {
+ canvas->zoom_xofs = 0;
+ }
+ }
+ else if (cx < 0)
+ {
+ cx = 0;
+ canvas->zoom_xofs = 0;
+ }
+ else if (cx > right_limit)
+ {
+ cx = right_limit;
+ canvas->zoom_xofs = 0;
+ }
+ else
+ {
+ canvas->zoom_xofs = 0;
+ }
+
+ if (bottom_limit < 0)
+ {
+ cy = 0;
+ if (canvas->center_scroll_region)
+ {
+ canvas->zoom_yofs = (canvas_height - scroll_height) / 2;
+ scroll_height = canvas_height;
+ }
+ else
+ {
+ canvas->zoom_yofs = 0;
+ }
+ }
+ else if (cy < 0)
+ {
+ cy = 0;
+ canvas->zoom_yofs = 0;
+ }
+ else if (cy > bottom_limit)
+ {
+ cy = bottom_limit;
+ canvas->zoom_yofs = 0;
+ }
+ else
+ {
+ canvas->zoom_yofs = 0;
+ }
+
+ if ((canvas->zoom_xofs != old_zoom_xofs) || (canvas->zoom_yofs != old_zoom_yofs))
+ {
+ /* This can only occur, if either canvas size or widget size changes
+ * So I think we can request full redraw here
+ * More stuff - we have to mark root as needing fresh affine (Lauris)
+ */
+ if (!(canvas->root->flags & EEL_CANVAS_ITEM_NEED_DEEP_UPDATE))
+ {
+ canvas->root->flags |= EEL_CANVAS_ITEM_NEED_DEEP_UPDATE;
+ eel_canvas_request_update (canvas);
+ }
+ gtk_widget_queue_draw (GTK_WIDGET (canvas));
+ }
+
+ hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas));
+ vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas));
+
+ if (((int) gtk_adjustment_get_value (hadjustment)) != cx)
+ {
+ gtk_adjustment_set_value (hadjustment, cx);
+ changed_x = TRUE;
+ }
+
+ if (((int) gtk_adjustment_get_value (vadjustment)) != cy)
+ {
+ gtk_adjustment_set_value (vadjustment, cy);
+ changed_y = TRUE;
+ }
+
+ gtk_layout_get_size (&canvas->layout, &width, &height);
+ if ((scroll_width != (int) width) || (scroll_height != (int) height))
+ {
+ gtk_layout_set_size (GTK_LAYOUT (canvas), scroll_width, scroll_height);
+ }
+
+ /* Signal GtkLayout that it should do a redraw. */
+ if (changed_x)
+ {
+ g_signal_emit_by_name (hadjustment, "value-changed");
+ }
+ if (changed_y)
+ {
+ g_signal_emit_by_name (vadjustment, "value-changed");
+ }
+}
+
+/* Size allocation handler for the canvas */
+static void
+eel_canvas_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ EelCanvas *canvas;
+ GtkAdjustment *vadjustment, *hadjustment;
+
+ g_return_if_fail (EEL_IS_CANVAS (widget));
+ g_return_if_fail (allocation != NULL);
+
+ if (GTK_WIDGET_CLASS (canvas_parent_class)->size_allocate)
+ {
+ (*GTK_WIDGET_CLASS (canvas_parent_class)->size_allocate)(widget, allocation);
+ }
+
+ canvas = EEL_CANVAS (widget);
+
+ /* Recenter the view, if appropriate */
+
+ hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas));
+ vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas));
+
+ gtk_adjustment_set_page_size (hadjustment, allocation->width);
+ gtk_adjustment_set_page_increment (hadjustment, allocation->width / 2);
+
+ gtk_adjustment_set_page_size (vadjustment, allocation->height);
+ gtk_adjustment_set_page_increment (vadjustment, allocation->height / 2);
+
+ scroll_to (canvas,
+ gtk_adjustment_get_value (hadjustment),
+ gtk_adjustment_get_value (vadjustment));
+
+ g_signal_emit_by_name (hadjustment, "changed");
+ g_signal_emit_by_name (vadjustment, "changed");
+}
+
+/* Emits an event for an item in the canvas, be it the current item, grabbed
+ * item, or focused item, as appropriate.
+ */
+
+static int
+emit_event (EelCanvas *canvas,
+ GdkEvent *event)
+{
+ GdkEvent ev;
+ gint finished;
+ EelCanvasItem *item;
+ EelCanvasItem *parent;
+ guint mask;
+
+ /* Could be an old pick event */
+ if (!gtk_widget_get_realized (GTK_WIDGET (canvas)))
+ {
+ return FALSE;
+ }
+
+ /* Perform checks for grabbed items */
+
+ if (canvas->grabbed_item &&
+ !is_descendant (canvas->current_item, canvas->grabbed_item))
+ {
+ return FALSE;
+ }
+
+ if (canvas->grabbed_item)
+ {
+ switch (event->type)
+ {
+ case GDK_ENTER_NOTIFY:
+ {
+ mask = GDK_ENTER_NOTIFY_MASK;
+ }
+ break;
+
+ case GDK_LEAVE_NOTIFY:
+ {
+ mask = GDK_LEAVE_NOTIFY_MASK;
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ {
+ mask = GDK_POINTER_MOTION_MASK;
+ }
+ break;
+
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ {
+ mask = GDK_BUTTON_PRESS_MASK;
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ {
+ mask = GDK_BUTTON_RELEASE_MASK;
+ }
+ break;
+
+ case GDK_KEY_PRESS:
+ {
+ mask = GDK_KEY_PRESS_MASK;
+ }
+ break;
+
+ case GDK_KEY_RELEASE:
+ {
+ mask = GDK_KEY_RELEASE_MASK;
+ }
+ break;
+
+ default:
+ {
+ mask = 0;
+ }
+ break;
+ }
+
+ if (!(mask & canvas->grabbed_event_mask))
+ {
+ return FALSE;
+ }
+ }
+
+ /* Convert to world coordinates -- we have two cases because of diferent
+ * offsets of the fields in the event structures.
+ */
+
+ ev = *event;
+
+ switch (ev.type)
+ {
+ case GDK_ENTER_NOTIFY:
+ case GDK_LEAVE_NOTIFY:
+ {
+ eel_canvas_window_to_world (canvas,
+ ev.crossing.x, ev.crossing.y,
+ &ev.crossing.x, &ev.crossing.y);
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ case GDK_BUTTON_RELEASE:
+ {
+ eel_canvas_window_to_world (canvas,
+ ev.motion.x, ev.motion.y,
+ &ev.motion.x, &ev.motion.y);
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+
+ /* Choose where we send the event */
+
+ item = canvas->current_item;
+
+ if (canvas->focused_item
+ && ((event->type == GDK_KEY_PRESS) ||
+ (event->type == GDK_KEY_RELEASE) ||
+ (event->type == GDK_FOCUS_CHANGE)))
+ {
+ item = canvas->focused_item;
+ }
+
+ /* The event is propagated up the hierarchy (for if someone connected to
+ * a group instead of a leaf event), and emission is stopped if a
+ * handler returns TRUE, just like for GtkWidget events.
+ */
+
+ finished = FALSE;
+
+ while (item && !finished)
+ {
+ g_object_ref (item);
+
+ g_signal_emit (
+ G_OBJECT (item), item_signals[ITEM_EVENT], 0,
+ &ev, &finished);
+
+ parent = item->parent;
+ g_object_unref (item);
+
+ item = parent;
+ }
+
+ return finished;
+}
+
+/* Re-picks the current item in the canvas, based on the event's coordinates.
+ * Also emits enter/leave events for items as appropriate.
+ */
+static int
+pick_current_item (EelCanvas *canvas,
+ GdkEvent *event)
+{
+ int button_down;
+ double x, y;
+ int cx, cy;
+ int retval;
+
+ retval = FALSE;
+
+ /* If a button is down, we'll perform enter and leave events on the
+ * current item, but not enter on any other item. This is more or less
+ * like X pointer grabbing for canvas items.
+ */
+ button_down = canvas->state & (GDK_BUTTON1_MASK
+ | GDK_BUTTON2_MASK
+ | GDK_BUTTON3_MASK
+ | GDK_BUTTON4_MASK
+ | GDK_BUTTON5_MASK);
+ if (!button_down)
+ {
+ canvas->left_grabbed_item = FALSE;
+ }
+
+ /* Save the event in the canvas. This is used to synthesize enter and
+ * leave events in case the current item changes. It is also used to
+ * re-pick the current item if the current one gets deleted. Also,
+ * synthesize an enter event.
+ */
+ if (event != &canvas->pick_event)
+ {
+ if ((event->type == GDK_MOTION_NOTIFY) || (event->type == GDK_BUTTON_RELEASE))
+ {
+ /* these fields have the same offsets in both types of events */
+
+ canvas->pick_event.crossing.type = GDK_ENTER_NOTIFY;
+ canvas->pick_event.crossing.window = event->motion.window;
+ canvas->pick_event.crossing.send_event = event->motion.send_event;
+ canvas->pick_event.crossing.subwindow = NULL;
+ canvas->pick_event.crossing.x = event->motion.x;
+ canvas->pick_event.crossing.y = event->motion.y;
+ canvas->pick_event.crossing.mode = GDK_CROSSING_NORMAL;
+ canvas->pick_event.crossing.detail = GDK_NOTIFY_NONLINEAR;
+ canvas->pick_event.crossing.focus = FALSE;
+ canvas->pick_event.crossing.state = event->motion.state;
+
+ /* these fields don't have the same offsets in both types of events */
+
+ if (event->type == GDK_MOTION_NOTIFY)
+ {
+ canvas->pick_event.crossing.x_root = event->motion.x_root;
+ canvas->pick_event.crossing.y_root = event->motion.y_root;
+ }
+ else
+ {
+ canvas->pick_event.crossing.x_root = event->button.x_root;
+ canvas->pick_event.crossing.y_root = event->button.y_root;
+ }
+ }
+ else
+ {
+ canvas->pick_event = *event;
+ }
+ }
+
+ /* Don't do anything else if this is a recursive call */
+
+ if (canvas->in_repick)
+ {
+ return retval;
+ }
+
+ /* LeaveNotify means that there is no current item, so we don't look for one */
+
+ if (canvas->pick_event.type != GDK_LEAVE_NOTIFY)
+ {
+ /* these fields don't have the same offsets in both types of events */
+
+ if (canvas->pick_event.type == GDK_ENTER_NOTIFY)
+ {
+ x = canvas->pick_event.crossing.x;
+ y = canvas->pick_event.crossing.y;
+ }
+ else
+ {
+ x = canvas->pick_event.motion.x;
+ y = canvas->pick_event.motion.y;
+ }
+
+ /* canvas pixel coords */
+
+ cx = (int) (x + 0.5);
+ cy = (int) (y + 0.5);
+
+ /* world coords */
+ eel_canvas_c2w (canvas, cx, cy, &x, &y);
+
+ /* find the closest item */
+ if (canvas->root->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ eel_canvas_item_invoke_point (canvas->root, x, y, cx, cy,
+ &canvas->new_current_item);
+ }
+ else
+ {
+ canvas->new_current_item = NULL;
+ }
+ }
+ else
+ {
+ canvas->new_current_item = NULL;
+ }
+
+ if ((canvas->new_current_item == canvas->current_item) && !canvas->left_grabbed_item)
+ {
+ return retval; /* current item did not change */
+ }
+ /* Synthesize events for old and new current items */
+
+ if ((canvas->new_current_item != canvas->current_item)
+ && (canvas->current_item != NULL)
+ && !canvas->left_grabbed_item)
+ {
+ GdkEvent new_event;
+
+ new_event = canvas->pick_event;
+ new_event.type = GDK_LEAVE_NOTIFY;
+
+ new_event.crossing.detail = GDK_NOTIFY_ANCESTOR;
+ new_event.crossing.subwindow = NULL;
+ canvas->in_repick = TRUE;
+ retval = emit_event (canvas, &new_event);
+ canvas->in_repick = FALSE;
+ }
+
+ /* new_current_item may have been set to NULL during the call to emit_event() above */
+
+ if ((canvas->new_current_item != canvas->current_item) && button_down)
+ {
+ canvas->current_item = canvas->new_current_item;
+ canvas->left_grabbed_item = TRUE;
+ return retval;
+ }
+
+ /* Handle the rest of cases */
+
+ canvas->left_grabbed_item = FALSE;
+ canvas->current_item = canvas->new_current_item;
+
+ if (canvas->current_item != NULL)
+ {
+ GdkEvent new_event;
+
+ new_event = canvas->pick_event;
+ new_event.type = GDK_ENTER_NOTIFY;
+ new_event.crossing.detail = GDK_NOTIFY_ANCESTOR;
+ new_event.crossing.subwindow = NULL;
+ retval = emit_event (canvas, &new_event);
+ }
+
+ return retval;
+}
+
+/* Button event handler for the canvas */
+static gint
+eel_canvas_button (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EelCanvas *canvas;
+ int mask;
+ int retval;
+
+ g_return_val_if_fail (EEL_IS_CANVAS (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ retval = FALSE;
+
+ canvas = EEL_CANVAS (widget);
+
+ /* Don't handle extra mouse button events */
+ if (event->button > 5)
+ {
+ return FALSE;
+ }
+
+ /*
+ * dispatch normally regardless of the event's window if an item has
+ * has a pointer grab in effect
+ */
+ if (!canvas->grabbed_item && event->window != gtk_layout_get_bin_window (GTK_LAYOUT (canvas)))
+ {
+ return retval;
+ }
+
+ switch (event->button)
+ {
+ case 1:
+ {
+ mask = GDK_BUTTON1_MASK;
+ }
+ break;
+
+ case 2:
+ {
+ mask = GDK_BUTTON2_MASK;
+ }
+ break;
+
+ case 3:
+ {
+ mask = GDK_BUTTON3_MASK;
+ }
+ break;
+
+ case 4:
+ {
+ mask = GDK_BUTTON4_MASK;
+ }
+ break;
+
+ case 5:
+ {
+ mask = GDK_BUTTON5_MASK;
+ }
+ break;
+
+ default:
+ {
+ mask = 0;
+ }
+ }
+
+ switch (event->type)
+ {
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ {
+ /* Pick the current item as if the button were not pressed, and
+ * then process the event.
+ */
+ event->state ^= mask;
+ canvas->state = event->state;
+ pick_current_item (canvas, (GdkEvent *) event);
+ event->state ^= mask;
+ canvas->state = event->state;
+ retval = emit_event (canvas, (GdkEvent *) event);
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ {
+ /* Process the event as if the button were pressed, then repick
+ * after the button has been released
+ */
+ canvas->state = event->state;
+ retval = emit_event (canvas, (GdkEvent *) event);
+ event->state ^= mask;
+ canvas->state = event->state;
+ pick_current_item (canvas, (GdkEvent *) event);
+ event->state ^= mask;
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ }
+
+ return retval;
+}
+
+/* Motion event handler for the canvas */
+static gint
+eel_canvas_motion (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ EelCanvas *canvas;
+
+ g_return_val_if_fail (EEL_IS_CANVAS (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ canvas = EEL_CANVAS (widget);
+
+ if (event->window != gtk_layout_get_bin_window (GTK_LAYOUT (canvas)))
+ {
+ return FALSE;
+ }
+
+ canvas->state = event->state;
+ pick_current_item (canvas, (GdkEvent *) event);
+ return emit_event (canvas, (GdkEvent *) event);
+}
+
+/* Key event handler for the canvas */
+static gint
+eel_canvas_key (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ EelCanvas *canvas;
+
+ g_return_val_if_fail (EEL_IS_CANVAS (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ canvas = EEL_CANVAS (widget);
+
+ if (emit_event (canvas, (GdkEvent *) event))
+ {
+ return TRUE;
+ }
+ if (event->type == GDK_KEY_RELEASE)
+ {
+ return GTK_WIDGET_CLASS (canvas_parent_class)->key_release_event (widget, event);
+ }
+ else
+ {
+ return GTK_WIDGET_CLASS (canvas_parent_class)->key_press_event (widget, event);
+ }
+}
+
+
+/* Crossing event handler for the canvas */
+static gint
+eel_canvas_crossing (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ EelCanvas *canvas;
+
+ g_return_val_if_fail (EEL_IS_CANVAS (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ canvas = EEL_CANVAS (widget);
+
+ if (event->window != gtk_layout_get_bin_window (GTK_LAYOUT (canvas)))
+ {
+ return FALSE;
+ }
+
+ canvas->state = event->state;
+ return pick_current_item (canvas, (GdkEvent *) event);
+}
+
+/* Focus in handler for the canvas */
+static gint
+eel_canvas_focus_in (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ EelCanvas *canvas;
+
+ canvas = EEL_CANVAS (widget);
+
+ if (canvas->focused_item)
+ {
+ return emit_event (canvas, (GdkEvent *) event);
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+/* Focus out handler for the canvas */
+static gint
+eel_canvas_focus_out (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ EelCanvas *canvas;
+
+ canvas = EEL_CANVAS (widget);
+
+ if (canvas->focused_item)
+ {
+ return emit_event (canvas, (GdkEvent *) event);
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+
+static cairo_region_t *
+eel_cairo_get_clip_region (cairo_t *cr)
+{
+ cairo_rectangle_list_t *list;
+ cairo_region_t *region;
+ int i;
+
+ list = cairo_copy_clip_rectangle_list (cr);
+ if (list->status == CAIRO_STATUS_CLIP_NOT_REPRESENTABLE)
+ {
+ cairo_rectangle_int_t clip_rect;
+
+ cairo_rectangle_list_destroy (list);
+
+ if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect))
+ {
+ return NULL;
+ }
+ return cairo_region_create_rectangle (&clip_rect);
+ }
+
+
+ region = cairo_region_create ();
+ for (i = list->num_rectangles - 1; i >= 0; --i)
+ {
+ cairo_rectangle_t *rect = &list->rectangles[i];
+ cairo_rectangle_int_t clip_rect;
+
+ clip_rect.x = floor (rect->x);
+ clip_rect.y = floor (rect->y);
+ clip_rect.width = ceil (rect->x + rect->width) - clip_rect.x;
+ clip_rect.height = ceil (rect->y + rect->height) - clip_rect.y;
+
+ if (cairo_region_union_rectangle (region, &clip_rect) != CAIRO_STATUS_SUCCESS)
+ {
+ cairo_region_destroy (region);
+ region = NULL;
+ break;
+ }
+ }
+
+ cairo_rectangle_list_destroy (list);
+ return region;
+}
+
+/* Expose handler for the canvas */
+static gboolean
+eel_canvas_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ EelCanvas *canvas = EEL_CANVAS (widget);
+ GdkWindow *bin_window;
+ cairo_region_t *region;
+
+ if (!gdk_cairo_get_clip_rectangle (cr, NULL))
+ {
+ return FALSE;
+ }
+
+ bin_window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
+
+ if (!gtk_cairo_should_draw_window (cr, bin_window))
+ {
+ return FALSE;
+ }
+
+ cairo_save (cr);
+
+ gtk_cairo_transform_to_window (cr, widget, bin_window);
+
+ region = eel_cairo_get_clip_region (cr);
+ if (region == NULL)
+ {
+ cairo_restore (cr);
+ return FALSE;
+ }
+
+#ifdef VERBOSE
+ g_print ("Draw\n");
+#endif
+ /* If there are any outstanding items that need updating, do them now */
+ if (canvas->idle_id)
+ {
+ g_source_remove (canvas->idle_id);
+ canvas->idle_id = 0;
+ }
+ if (canvas->need_update)
+ {
+ g_return_val_if_fail (!canvas->doing_update, FALSE);
+
+ canvas->doing_update = TRUE;
+ eel_canvas_item_invoke_update (canvas->root, 0, 0, 0);
+
+ g_return_val_if_fail (canvas->doing_update, FALSE);
+
+ canvas->doing_update = FALSE;
+
+ canvas->need_update = FALSE;
+ }
+
+ if (canvas->root->flags & EEL_CANVAS_ITEM_MAPPED)
+ {
+ EEL_CANVAS_ITEM_GET_CLASS (canvas->root)->draw (canvas->root, cr, region);
+ }
+
+ cairo_restore (cr);
+
+ /* Chain up to get exposes on child widgets */
+ if (GTK_WIDGET_CLASS (canvas_parent_class)->draw)
+ {
+ GTK_WIDGET_CLASS (canvas_parent_class)->draw (widget, cr);
+ }
+
+ cairo_region_destroy (region);
+ return FALSE;
+}
+
+static void
+do_update (EelCanvas *canvas)
+{
+ /* Cause the update if necessary */
+
+update_again:
+ if (canvas->need_update)
+ {
+ g_return_if_fail (!canvas->doing_update);
+
+ canvas->doing_update = TRUE;
+ eel_canvas_item_invoke_update (canvas->root, 0, 0, 0);
+
+ g_return_if_fail (canvas->doing_update);
+
+ canvas->doing_update = FALSE;
+
+ canvas->need_update = FALSE;
+ }
+
+ /* Pick new current item */
+
+ while (canvas->need_repick)
+ {
+ canvas->need_repick = FALSE;
+ pick_current_item (canvas, &canvas->pick_event);
+ }
+
+ /* it is possible that during picking we emitted an event in which
+ * the user then called some function which then requested update
+ * of something. Without this we'd be left in a state where
+ * need_update would have been left TRUE and the canvas would have
+ * been left unpainted. */
+ if (canvas->need_update)
+ {
+ goto update_again;
+ }
+}
+
+/* Idle handler for the canvas. It deals with pending updates and redraws. */
+static gint
+idle_handler (gpointer data)
+{
+ EelCanvas *canvas;
+
+ canvas = EEL_CANVAS (data);
+ do_update (canvas);
+
+ /* Reset idle id */
+ canvas->idle_id = 0;
+
+ return FALSE;
+}
+
+/* Convenience function to add an idle handler to a canvas */
+static void
+add_idle (EelCanvas *canvas)
+{
+ if (!canvas->idle_id)
+ {
+ /* We let the update idle handler have higher priority
+ * than the redraw idle handler so the canvas state
+ * will be updated during the expose event. canvas in
+ * expose_event.
+ */
+ canvas->idle_id = g_idle_add_full (GDK_PRIORITY_REDRAW - 20,
+ idle_handler, canvas, NULL);
+ }
+}
+
+/**
+ * eel_canvas_root:
+ * @canvas: A canvas.
+ *
+ * Queries the root group of a canvas.
+ *
+ * Return value: The root group of the specified canvas.
+ **/
+EelCanvasGroup *
+eel_canvas_root (EelCanvas *canvas)
+{
+ g_return_val_if_fail (EEL_IS_CANVAS (canvas), NULL);
+
+ return EEL_CANVAS_GROUP (canvas->root);
+}
+
+
+/**
+ * eel_canvas_set_scroll_region:
+ * @canvas: A canvas.
+ * @x1: Leftmost limit of the scrolling region.
+ * @y1: Upper limit of the scrolling region.
+ * @x2: Rightmost limit of the scrolling region.
+ * @y2: Lower limit of the scrolling region.
+ *
+ * Sets the scrolling region of a canvas to the specified rectangle. The canvas
+ * will then be able to scroll only within this region. The view of the canvas
+ * is adjusted as appropriate to display as much of the new region as possible.
+ **/
+void
+eel_canvas_set_scroll_region (EelCanvas *canvas,
+ double x1,
+ double y1,
+ double x2,
+ double y2)
+{
+ double wxofs, wyofs;
+ int xofs, yofs;
+ GtkAdjustment *vadjustment, *hadjustment;
+
+ g_return_if_fail (EEL_IS_CANVAS (canvas));
+
+ if ((canvas->scroll_x1 == x1) && (canvas->scroll_y1 == y1) &&
+ (canvas->scroll_x2 == x2) && (canvas->scroll_y2 == y2))
+ {
+ return;
+ }
+
+ /*
+ * Set the new scrolling region. If possible, do not move the visible contents of the
+ * canvas.
+ */
+ hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas));
+ vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas));
+
+ eel_canvas_c2w (canvas,
+ gtk_adjustment_get_value (hadjustment) + canvas->zoom_xofs,
+ gtk_adjustment_get_value (vadjustment) + canvas->zoom_yofs,
+ /*canvas->zoom_xofs,
+ * canvas->zoom_yofs,*/
+ &wxofs, &wyofs);
+
+ canvas->scroll_x1 = x1;
+ canvas->scroll_y1 = y1;
+ canvas->scroll_x2 = x2;
+ canvas->scroll_y2 = y2;
+
+ eel_canvas_w2c (canvas, wxofs, wyofs, &xofs, &yofs);
+
+ scroll_to (canvas, xofs, yofs);
+
+ canvas->need_repick = TRUE;
+
+ if (!(canvas->root->flags & EEL_CANVAS_ITEM_NEED_DEEP_UPDATE))
+ {
+ canvas->root->flags |= EEL_CANVAS_ITEM_NEED_DEEP_UPDATE;
+ eel_canvas_request_update (canvas);
+ }
+}
+
+
+/**
+ * eel_canvas_get_scroll_region:
+ * @canvas: A canvas.
+ * @x1: Leftmost limit of the scrolling region (return value).
+ * @y1: Upper limit of the scrolling region (return value).
+ * @x2: Rightmost limit of the scrolling region (return value).
+ * @y2: Lower limit of the scrolling region (return value).
+ *
+ * Queries the scrolling region of a canvas.
+ **/
+void
+eel_canvas_get_scroll_region (EelCanvas *canvas,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+{
+ g_return_if_fail (EEL_IS_CANVAS (canvas));
+
+ if (x1)
+ {
+ *x1 = canvas->scroll_x1;
+ }
+
+ if (y1)
+ {
+ *y1 = canvas->scroll_y1;
+ }
+
+ if (x2)
+ {
+ *x2 = canvas->scroll_x2;
+ }
+
+ if (y2)
+ {
+ *y2 = canvas->scroll_y2;
+ }
+}
+
+/**
+ * eel_canvas_set_pixels_per_unit:
+ * @canvas: A canvas.
+ * @n: The number of pixels that correspond to one canvas unit.
+ *
+ * Sets the zooming factor of a canvas by specifying the number of pixels that
+ * correspond to one canvas unit.
+ **/
+void
+eel_canvas_set_pixels_per_unit (EelCanvas *canvas,
+ double n)
+{
+ GtkWidget *widget;
+ double cx, cy;
+ int x1, y1;
+ int center_x, center_y;
+ GdkWindow *window;
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+ GtkAllocation allocation;
+ GtkAdjustment *vadjustment, *hadjustment;
+
+ g_return_if_fail (EEL_IS_CANVAS (canvas));
+ g_return_if_fail (n > EEL_CANVAS_EPSILON);
+
+ widget = GTK_WIDGET (canvas);
+
+ gtk_widget_get_allocation (widget, &allocation);
+ center_x = allocation.width / 2;
+ center_y = allocation.height / 2;
+
+ /* Find the coordinates of the screen center in units. */
+ hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas));
+ vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas));
+ cx = (gtk_adjustment_get_value (hadjustment) + center_x) / canvas->pixels_per_unit + canvas->scroll_x1 + canvas->zoom_xofs;
+ cy = (gtk_adjustment_get_value (vadjustment) + center_y) / canvas->pixels_per_unit + canvas->scroll_y1 + canvas->zoom_yofs;
+
+ /* Now calculate the new offset of the upper left corner. (round not truncate) */
+ x1 = ((cx - canvas->scroll_x1) * n) - center_x + .5;
+ y1 = ((cy - canvas->scroll_y1) * n) - center_y + .5;
+
+ canvas->pixels_per_unit = n;
+
+ if (!(canvas->root->flags & EEL_CANVAS_ITEM_NEED_DEEP_UPDATE))
+ {
+ canvas->root->flags |= EEL_CANVAS_ITEM_NEED_DEEP_UPDATE;
+ eel_canvas_request_update (canvas);
+ }
+
+ /* Map a background None window over the bin_window to avoid
+ * scrolling the window scroll causing exposes.
+ */
+ window = NULL;
+ if (gtk_widget_get_mapped (widget))
+ {
+ attributes.window_type = GDK_WINDOW_CHILD;
+ gtk_widget_get_allocation (widget, &allocation);
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK;
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+
+ window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes, attributes_mask);
+ gdk_window_set_user_data (window, widget);
+
+ gdk_window_show (window);
+ }
+
+ scroll_to (canvas, x1, y1);
+
+ /* If we created a an overlapping background None window, remove it how.
+ *
+ * TODO: We would like to temporarily set the bin_window background to
+ * None to avoid clearing the bin_window to the background, but gdk doesn't
+ * expose enought to let us do this, so we get a flash-effect here. At least
+ * it looks better than scroll + expose.
+ */
+ if (window != NULL)
+ {
+ gdk_window_hide (window);
+ gdk_window_set_user_data (window, NULL);
+ gdk_window_destroy (window);
+ }
+
+ canvas->need_repick = TRUE;
+}
+
+/**
+ * eel_canvas_get_scroll_offsets:
+ * @canvas: A canvas.
+ * @cx: Horizontal scrolling offset (return value).
+ * @cy: Vertical scrolling offset (return value).
+ *
+ * Queries the scrolling offsets of a canvas. The values are returned in canvas
+ * pixel units.
+ **/
+void
+eel_canvas_get_scroll_offsets (EelCanvas *canvas,
+ int *cx,
+ int *cy)
+{
+ GtkAdjustment *vadjustment, *hadjustment;
+
+ g_return_if_fail (EEL_IS_CANVAS (canvas));
+
+ hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas));
+ vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas));
+
+ if (cx)
+ {
+ *cx = gtk_adjustment_get_value (hadjustment);
+ }
+
+ if (cy)
+ {
+ *cy = gtk_adjustment_get_value (vadjustment);
+ }
+}
+
+/* Queues an update of the canvas */
+static void
+eel_canvas_request_update (EelCanvas *canvas)
+{
+ EEL_CANVAS_GET_CLASS (canvas)->request_update (canvas);
+}
+
+static void
+eel_canvas_request_update_real (EelCanvas *canvas)
+{
+ canvas->need_update = TRUE;
+ add_idle (canvas);
+}
+
+/**
+ * eel_canvas_request_redraw:
+ * @canvas: A canvas.
+ * @x1: Leftmost coordinate of the rectangle to be redrawn.
+ * @y1: Upper coordinate of the rectangle to be redrawn.
+ * @x2: Rightmost coordinate of the rectangle to be redrawn, plus 1.
+ * @y2: Lower coordinate of the rectangle to be redrawn, plus 1.
+ *
+ * Convenience function that informs a canvas that the specified rectangle needs
+ * to be repainted. The rectangle includes @x1 and @y1, but not @x2 and @y2.
+ * To be used only by item implementations.
+ **/
+void
+eel_canvas_request_redraw (EelCanvas *canvas,
+ int x1,
+ int y1,
+ int x2,
+ int y2)
+{
+ GdkRectangle bbox;
+
+ g_return_if_fail (EEL_IS_CANVAS (canvas));
+
+ if (!gtk_widget_is_drawable (GTK_WIDGET (canvas))
+ || (x1 >= x2) || (y1 >= y2))
+ {
+ return;
+ }
+
+ bbox.x = x1;
+ bbox.y = y1;
+ bbox.width = x2 - x1;
+ bbox.height = y2 - y1;
+
+ gdk_window_invalidate_rect (gtk_layout_get_bin_window (GTK_LAYOUT (canvas)),
+ &bbox, FALSE);
+}
+
+/**
+ * eel_canvas_w2c:
+ * @canvas: A canvas.
+ * @wx: World X coordinate.
+ * @wy: World Y coordinate.
+ * @cx: X pixel coordinate (return value).
+ * @cy: Y pixel coordinate (return value).
+ *
+ * Converts world coordinates into canvas pixel coordinates.
+ **/
+void
+eel_canvas_w2c (EelCanvas *canvas,
+ double wx,
+ double wy,
+ int *cx,
+ int *cy)
+{
+ double zoom;
+
+ g_return_if_fail (EEL_IS_CANVAS (canvas));
+
+ zoom = canvas->pixels_per_unit;
+
+ if (cx)
+ {
+ *cx = floor ((wx - canvas->scroll_x1) * zoom + canvas->zoom_xofs + 0.5);
+ }
+ if (cy)
+ {
+ *cy = floor ((wy - canvas->scroll_y1) * zoom + canvas->zoom_yofs + 0.5);
+ }
+}
+
+/**
+ * eel_canvas_w2c:
+ * @canvas: A canvas.
+ * @world: rectangle in world coordinates.
+ * @canvas: rectangle in canvase coordinates.
+ *
+ * Converts rectangles in world coordinates into canvas pixel coordinates.
+ **/
+void
+eel_canvas_w2c_rect_d (EelCanvas *canvas,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+{
+ eel_canvas_w2c_d (canvas,
+ *x1, *y1,
+ x1, y1);
+ eel_canvas_w2c_d (canvas,
+ *x2, *y2,
+ x2, y2);
+}
+
+
+
+/**
+ * eel_canvas_w2c_d:
+ * @canvas: A canvas.
+ * @wx: World X coordinate.
+ * @wy: World Y coordinate.
+ * @cx: X pixel coordinate (return value).
+ * @cy: Y pixel coordinate (return value).
+ *
+ * Converts world coordinates into canvas pixel coordinates. This version
+ * produces coordinates in floating point coordinates, for greater precision.
+ **/
+void
+eel_canvas_w2c_d (EelCanvas *canvas,
+ double wx,
+ double wy,
+ double *cx,
+ double *cy)
+{
+ double zoom;
+
+ g_return_if_fail (EEL_IS_CANVAS (canvas));
+
+ zoom = canvas->pixels_per_unit;
+
+ if (cx)
+ {
+ *cx = (wx - canvas->scroll_x1) * zoom + canvas->zoom_xofs;
+ }
+ if (cy)
+ {
+ *cy = (wy - canvas->scroll_y1) * zoom + canvas->zoom_yofs;
+ }
+}
+
+
+/**
+ * eel_canvas_c2w:
+ * @canvas: A canvas.
+ * @cx: Canvas pixel X coordinate.
+ * @cy: Canvas pixel Y coordinate.
+ * @wx: X world coordinate (return value).
+ * @wy: Y world coordinate (return value).
+ *
+ * Converts canvas pixel coordinates to world coordinates.
+ **/
+void
+eel_canvas_c2w (EelCanvas *canvas,
+ int cx,
+ int cy,
+ double *wx,
+ double *wy)
+{
+ double zoom;
+
+ g_return_if_fail (EEL_IS_CANVAS (canvas));
+
+ zoom = canvas->pixels_per_unit;
+
+ if (wx)
+ {
+ *wx = (cx - canvas->zoom_xofs) / zoom + canvas->scroll_x1;
+ }
+ if (wy)
+ {
+ *wy = (cy - canvas->zoom_yofs) / zoom + canvas->scroll_y1;
+ }
+}
+
+
+/**
+ * eel_canvas_window_to_world:
+ * @canvas: A canvas.
+ * @winx: Window-relative X coordinate.
+ * @winy: Window-relative Y coordinate.
+ * @worldx: X world coordinate (return value).
+ * @worldy: Y world coordinate (return value).
+ *
+ * Converts window-relative coordinates into world coordinates. You can use
+ * this when you need to convert mouse coordinates into world coordinates, for
+ * example.
+ * Window coordinates are really the same as canvas coordinates now, but this
+ * function is here for backwards compatibility reasons.
+ **/
+void
+eel_canvas_window_to_world (EelCanvas *canvas,
+ double winx,
+ double winy,
+ double *worldx,
+ double *worldy)
+{
+ g_return_if_fail (EEL_IS_CANVAS (canvas));
+
+ if (worldx)
+ {
+ *worldx = canvas->scroll_x1 + ((winx - canvas->zoom_xofs)
+ / canvas->pixels_per_unit);
+ }
+
+ if (worldy)
+ {
+ *worldy = canvas->scroll_y1 + ((winy - canvas->zoom_yofs)
+ / canvas->pixels_per_unit);
+ }
+}
+
+
+/**
+ * eel_canvas_world_to_window:
+ * @canvas: A canvas.
+ * @worldx: World X coordinate.
+ * @worldy: World Y coordinate.
+ * @winx: X window-relative coordinate.
+ * @winy: Y window-relative coordinate.
+ *
+ * Converts world coordinates into window-relative coordinates.
+ * Window coordinates are really the same as canvas coordinates now, but this
+ * function is here for backwards compatibility reasons.
+ **/
+void
+eel_canvas_world_to_window (EelCanvas *canvas,
+ double worldx,
+ double worldy,
+ double *winx,
+ double *winy)
+{
+ g_return_if_fail (EEL_IS_CANVAS (canvas));
+
+ if (winx)
+ {
+ *winx = (canvas->pixels_per_unit) * (worldx - canvas->scroll_x1) + canvas->zoom_xofs;
+ }
+
+ if (winy)
+ {
+ *winy = (canvas->pixels_per_unit) * (worldy - canvas->scroll_y1) + canvas->zoom_yofs;
+ }
+}
+
+static gboolean
+boolean_handled_accumulator (GSignalInvocationHint *ihint,
+ GValue *return_accu,
+ const GValue *handler_return,
+ gpointer dummy)
+{
+ gboolean continue_emission;
+ gboolean signal_handled;
+
+ signal_handled = g_value_get_boolean (handler_return);
+ g_value_set_boolean (return_accu, signal_handled);
+ continue_emission = !signal_handled;
+
+ return continue_emission;
+}
+
+static void
+eel_canvas_item_accessible_get_item_extents (EelCanvasItem *item,
+ GdkRectangle *rect)
+{
+ double bx1, bx2, by1, by2;
+ gint scroll_x, scroll_y;
+ gint x1, x2, y1, y2;
+
+ eel_canvas_item_get_bounds (item, &bx1, &by1, &bx2, &by2);
+ eel_canvas_w2c_rect_d (item->canvas, &bx1, &by1, &bx2, &by2);
+ eel_canvas_get_scroll_offsets (item->canvas, &scroll_x, &scroll_y);
+ x1 = floor (bx1 + .5);
+ y1 = floor (by1 + .5);
+ x2 = floor (bx2 + .5);
+ y2 = floor (by2 + .5);
+ rect->x = x1 - scroll_x;
+ rect->y = y1 - scroll_y;
+ rect->width = x2 - x1;
+ rect->height = y2 - y1;
+}
+
+static gboolean
+eel_canvas_item_accessible_is_item_in_window (EelCanvasItem *item,
+ GdkRectangle *rect)
+{
+ GtkWidget *widget;
+ gboolean retval;
+
+ widget = GTK_WIDGET (item->canvas);
+ if (gtk_widget_get_window (widget))
+ {
+ int window_width, window_height;
+
+ gdk_window_get_geometry (gtk_widget_get_window (widget), NULL, NULL,
+ &window_width, &window_height);
+ /*
+ * Check whether rectangles intersect
+ */
+ if (rect->x + rect->width < 0 ||
+ rect->y + rect->height < 0 ||
+ rect->x > window_width ||
+ rect->y > window_height)
+ {
+ retval = FALSE;
+ }
+ else
+ {
+ retval = TRUE;
+ }
+ }
+ else
+ {
+ retval = FALSE;
+ }
+ return retval;
+}
+
+
+static void
+eel_canvas_item_accessible_get_extents (AtkComponent *component,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height,
+ AtkCoordType coord_type)
+{
+ AtkGObjectAccessible *atk_gobj;
+ GObject *obj;
+ EelCanvasItem *item;
+ gint window_x, window_y;
+ gint toplevel_x, toplevel_y;
+ GdkRectangle rect;
+ GdkWindow *window;
+ GtkWidget *canvas;
+
+ atk_gobj = ATK_GOBJECT_ACCESSIBLE (component);
+ obj = atk_gobject_accessible_get_object (atk_gobj);
+
+ if (obj == NULL)
+ {
+ /* item is defunct */
+ return;
+ }
+
+ /* Get the CanvasItem */
+ item = EEL_CANVAS_ITEM (obj);
+
+ /* If this item has no parent canvas, something's broken */
+ g_return_if_fail (GTK_IS_WIDGET (item->canvas));
+
+ eel_canvas_item_accessible_get_item_extents (item, &rect);
+ *width = rect.width;
+ *height = rect.height;
+ if (!eel_canvas_item_accessible_is_item_in_window (item, &rect))
+ {
+ *x = G_MININT;
+ *y = G_MININT;
+ return;
+ }
+
+ canvas = GTK_WIDGET (item->canvas);
+ window = gtk_widget_get_parent_window (canvas);
+ gdk_window_get_origin (window, &window_x, &window_y);
+ *x = rect.x + window_x;
+ *y = rect.y + window_y;
+ if (coord_type == ATK_XY_WINDOW)
+ {
+ window = gdk_window_get_toplevel (gtk_widget_get_window (canvas));
+ gdk_window_get_origin (window, &toplevel_x, &toplevel_y);
+ *x -= toplevel_x;
+ *y -= toplevel_y;
+ }
+ return;
+}
+
+static gint
+eel_canvas_item_accessible_get_mdi_zorder (AtkComponent *component)
+{
+ AtkGObjectAccessible *atk_gobj;
+ GObject *g_obj;
+ EelCanvasItem *item;
+
+ atk_gobj = ATK_GOBJECT_ACCESSIBLE (component);
+ g_obj = atk_gobject_accessible_get_object (atk_gobj);
+ if (g_obj == NULL)
+ {
+ /* Object is defunct */
+ return -1;
+ }
+
+ item = EEL_CANVAS_ITEM (g_obj);
+ if (item->parent)
+ {
+ return g_list_index (EEL_CANVAS_GROUP (item->parent)->item_list, item);
+ }
+ else
+ {
+ g_return_val_if_fail (item->canvas->root == item, -1);
+ return 0;
+ }
+}
+
+static gboolean
+eel_canvas_item_accessible_grab_focus (AtkComponent *component)
+{
+ AtkGObjectAccessible *atk_gobj;
+ GObject *obj;
+ EelCanvasItem *item;
+ GtkWidget *toplevel;
+
+ atk_gobj = ATK_GOBJECT_ACCESSIBLE (component);
+ obj = atk_gobject_accessible_get_object (atk_gobj);
+
+ item = EEL_CANVAS_ITEM (obj);
+ if (item == NULL)
+ {
+ /* item is defunct */
+ return FALSE;
+ }
+
+ eel_canvas_item_grab_focus (item);
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (item->canvas));
+ if (gtk_widget_is_toplevel (toplevel))
+ {
+ gtk_window_present (GTK_WINDOW (toplevel));
+ }
+
+ return TRUE;
+}
+
+static void
+eel_canvas_item_accessible_component_interface_init (AtkComponentIface *iface)
+{
+ g_return_if_fail (iface != NULL);
+
+ iface->get_extents = eel_canvas_item_accessible_get_extents;
+ iface->get_mdi_zorder = eel_canvas_item_accessible_get_mdi_zorder;
+ iface->grab_focus = eel_canvas_item_accessible_grab_focus;
+}
+
+static gboolean
+eel_canvas_item_accessible_is_item_on_screen (EelCanvasItem *item)
+{
+ GdkRectangle rect;
+
+ eel_canvas_item_accessible_get_item_extents (item, &rect);
+ return eel_canvas_item_accessible_is_item_in_window (item, &rect);
+}
+
+static void
+eel_canvas_item_accessible_initialize (AtkObject *obj,
+ gpointer data)
+{
+ if (ATK_OBJECT_CLASS (accessible_item_parent_class)->initialize != NULL)
+ {
+ ATK_OBJECT_CLASS (accessible_item_parent_class)->initialize (obj, data);
+ }
+ g_object_set_data (G_OBJECT (obj), "atk-component-layer",
+ GINT_TO_POINTER (ATK_LAYER_MDI));
+}
+
+static AtkStateSet *
+eel_canvas_item_accessible_ref_state_set (AtkObject *accessible)
+{
+ AtkGObjectAccessible *atk_gobj;
+ GObject *obj;
+ EelCanvasItem *item;
+ AtkStateSet *state_set;
+
+ state_set = ATK_OBJECT_CLASS (accessible_item_parent_class)->ref_state_set (accessible);
+ atk_gobj = ATK_GOBJECT_ACCESSIBLE (accessible);
+ obj = atk_gobject_accessible_get_object (atk_gobj);
+
+ item = EEL_CANVAS_ITEM (obj);
+ if (item == NULL)
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
+ }
+ else
+ {
+ if (item->flags & EEL_CANVAS_ITEM_VISIBLE)
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_VISIBLE);
+
+ if (eel_canvas_item_accessible_is_item_on_screen (item))
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_SHOWING);
+ }
+ }
+ if (gtk_widget_get_can_focus (GTK_WIDGET (item->canvas)))
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE);
+
+ if (item->canvas->focused_item == item)
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
+ }
+ }
+ }
+
+ return state_set;
+}
+
+static void
+eel_canvas_item_accessible_class_init (EelCanvasItemAccessibleClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+
+ accessible_item_parent_class = g_type_class_peek_parent (klass);
+
+ atk_class->initialize = eel_canvas_item_accessible_initialize;
+ atk_class->ref_state_set = eel_canvas_item_accessible_ref_state_set;
+}
+
+static void
+eel_canvas_item_accessible_init (EelCanvasItemAccessible *self)
+{
+}
+
+G_DEFINE_TYPE_WITH_CODE (EelCanvasItemAccessible,
+ eel_canvas_item_accessible,
+ ATK_TYPE_GOBJECT_ACCESSIBLE,
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT,
+ eel_canvas_item_accessible_component_interface_init));
+
+static GType eel_canvas_item_accessible_factory_get_type (void);
+
+typedef AtkObjectFactory EelCanvasItemAccessibleFactory;
+typedef AtkObjectFactoryClass EelCanvasItemAccessibleFactoryClass;
+G_DEFINE_TYPE (EelCanvasItemAccessibleFactory, eel_canvas_item_accessible_factory,
+ ATK_TYPE_OBJECT_FACTORY)
+
+static GType
+eel_canvas_item_accessible_factory_get_accessible_type (void)
+{
+ return eel_canvas_item_accessible_get_type ();
+}
+
+static AtkObject *
+eel_canvas_item_accessible_factory_create_accessible (GObject *for_object)
+{
+ AtkObject *accessible;
+
+ accessible = g_object_new (eel_canvas_item_accessible_get_type (), NULL);
+ atk_object_initialize (accessible, for_object);
+ return accessible;
+}
+
+static void
+eel_canvas_item_accessible_factory_init (EelCanvasItemAccessibleFactory *self)
+{
+}
+
+static void
+eel_canvas_item_accessible_factory_class_init (AtkObjectFactoryClass *klass)
+{
+ klass->create_accessible = eel_canvas_item_accessible_factory_create_accessible;
+ klass->get_accessible_type = eel_canvas_item_accessible_factory_get_accessible_type;
+}
+
+/* Class initialization function for EelCanvasItemClass */
+static void
+eel_canvas_item_class_init (EelCanvasItemClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+
+ item_parent_class = g_type_class_peek_parent (klass);
+
+ gobject_class->set_property = eel_canvas_item_set_property;
+ gobject_class->get_property = eel_canvas_item_get_property;
+ gobject_class->dispose = eel_canvas_item_dispose;
+
+ g_object_class_install_property
+ (gobject_class, ITEM_PROP_VISIBLE,
+ g_param_spec_boolean ("visible", NULL, NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+
+ item_signals[ITEM_EVENT] =
+ g_signal_new ("event",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EelCanvasItemClass, event),
+ boolean_handled_accumulator, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 1,
+ GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ item_signals[ITEM_DESTROY] =
+ g_signal_new ("destroy",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_CLEANUP | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
+ G_STRUCT_OFFSET (EelCanvasItemClass, destroy),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ klass->realize = eel_canvas_item_realize;
+ klass->unrealize = eel_canvas_item_unrealize;
+ klass->map = eel_canvas_item_map;
+ klass->unmap = eel_canvas_item_unmap;
+ klass->update = eel_canvas_item_update;
+
+ atk_registry_set_factory_type (atk_get_default_registry (),
+ EEL_TYPE_CANVAS_ITEM,
+ eel_canvas_item_accessible_factory_get_type ());
+}
diff --git a/eel/eel-canvas.h b/eel/eel-canvas.h
new file mode 100644
index 000000000..f406ca0a4
--- /dev/null
+++ b/eel/eel-canvas.h
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
+ * All rights reserved.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * see <http://www.gnu.org/licenses/>.
+ */
+/*
+ @NOTATION@
+ */
+/* EelCanvas widget - Tk-like canvas widget for Gnome
+ *
+ * EelCanvas is basically a port of the Tk toolkit's most excellent canvas
+ * widget. Tk is copyrighted by the Regents of the University of California,
+ * Sun Microsystems, and other parties.
+ *
+ *
+ * Authors: Federico Mena <federico@nuclecu.unam.mx>
+ * Raph Levien <raph@gimp.org>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <gtk/gtk-a11y.h>
+#include <gdk/gdk.h>
+#include <stdarg.h>
+
+G_BEGIN_DECLS
+
+
+/* "Small" value used by canvas stuff */
+#define EEL_CANVAS_EPSILON 1e-10
+
+
+/* Macros for building colors that fit in a 32-bit integer. The values are in
+ * [0, 255].
+ */
+
+#define EEL_CANVAS_COLOR(r, g, b) ((((int) (r) & 0xff) << 24) \
+ | (((int) (g) & 0xff) << 16) \
+ | (((int) (b) & 0xff) << 8) \
+ | 0xff)
+
+#define EEL_CANVAS_COLOR_A(r, g, b, a) ((((int) (r) & 0xff) << 24) \
+ | (((int) (g) & 0xff) << 16) \
+ | (((int) (b) & 0xff) << 8) \
+ | ((int) (a) & 0xff))
+
+
+typedef struct _EelCanvas EelCanvas;
+typedef struct _EelCanvasClass EelCanvasClass;
+typedef struct _EelCanvasItem EelCanvasItem;
+typedef struct _EelCanvasItemClass EelCanvasItemClass;
+typedef struct _EelCanvasGroup EelCanvasGroup;
+typedef struct _EelCanvasGroupClass EelCanvasGroupClass;
+
+
+/* EelCanvasItem - base item class for canvas items
+ *
+ * All canvas items are derived from EelCanvasItem. The only information a
+ * EelCanvasItem contains is its parent canvas, its parent canvas item group,
+ * and its bounding box in world coordinates.
+ *
+ * Items inside a canvas are organized in a tree of EelCanvasItemGroup nodes
+ * and EelCanvasItem leaves. Each canvas has a single root group, which can
+ * be obtained with the eel_canvas_get_root() function.
+ *
+ * The abstract EelCanvasItem class does not have any configurable or
+ * queryable attributes.
+ */
+
+/* Object flags for items */
+enum {
+ EEL_CANVAS_ITEM_REALIZED = 1 << 4,
+ EEL_CANVAS_ITEM_MAPPED = 1 << 5,
+ EEL_CANVAS_ITEM_ALWAYS_REDRAW = 1 << 6,
+ EEL_CANVAS_ITEM_VISIBLE = 1 << 7,
+ EEL_CANVAS_ITEM_NEED_UPDATE = 1 << 8,
+ EEL_CANVAS_ITEM_NEED_DEEP_UPDATE = 1 << 9
+};
+
+/* Update flags for items */
+enum {
+ EEL_CANVAS_UPDATE_REQUESTED = 1 << 0,
+ EEL_CANVAS_UPDATE_DEEP = 1 << 1
+};
+
+#define EEL_TYPE_CANVAS_ITEM (eel_canvas_item_get_type ())
+#define EEL_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEL_TYPE_CANVAS_ITEM, EelCanvasItem))
+#define EEL_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEL_TYPE_CANVAS_ITEM, EelCanvasItemClass))
+#define EEL_IS_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEL_TYPE_CANVAS_ITEM))
+#define EEL_IS_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEL_TYPE_CANVAS_ITEM))
+#define EEL_CANVAS_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEL_TYPE_CANVAS_ITEM, EelCanvasItemClass))
+
+
+struct _EelCanvasItem {
+ GInitiallyUnowned object;
+
+ /* Parent canvas for this item */
+ EelCanvas *canvas;
+
+ /* Parent canvas group for this item (a EelCanvasGroup) */
+ EelCanvasItem *parent;
+
+ /* Bounding box for this item (in canvas coordinates) */
+ double x1, y1, x2, y2;
+
+ /* Object flags */
+ guint flags;
+};
+
+struct _EelCanvasItemClass {
+ GInitiallyUnownedClass parent_class;
+
+ void (* destroy) (EelCanvasItem *item);
+
+ /* Tell the item to update itself. The flags are from the update flags
+ * defined above. The item should update its internal state from its
+ * queued state, and recompute and request its repaint area. The
+ * update method also recomputes the bounding box of the item.
+ */
+ void (* update) (EelCanvasItem *item, double i2w_dx, double i2w_dy, int flags);
+
+ /* Realize an item -- create GCs, etc. */
+ void (* realize) (EelCanvasItem *item);
+
+ /* Unrealize an item */
+ void (* unrealize) (EelCanvasItem *item);
+
+ /* Map an item - normally only need by items with their own GdkWindows */
+ void (* map) (EelCanvasItem *item);
+
+ /* Unmap an item */
+ void (* unmap) (EelCanvasItem *item);
+
+ /* Draw an item of this type. (x, y) are the upper-left canvas pixel
+ * coordinates of the drawable, a temporary pixmap, where things get
+ * drawn. (width, height) are the dimensions of the drawable.
+ */
+ void (* draw) (EelCanvasItem *item, cairo_t *cr, cairo_region_t *region);
+
+ /* Calculate the distance from an item to the specified point. It also
+ * returns a canvas item which is the item itself in the case of the
+ * object being an actual leaf item, or a child in case of the object
+ * being a canvas group. (cx, cy) are the canvas pixel coordinates that
+ * correspond to the item-relative coordinates (x, y).
+ */
+ double (* point) (EelCanvasItem *item, double x, double y, int cx, int cy,
+ EelCanvasItem **actual_item);
+
+ void (* translate) (EelCanvasItem *item, double dx, double dy);
+
+ /* Fetch the item's bounding box (need not be exactly tight). This
+ * should be in item-relative coordinates.
+ */
+ void (* bounds) (EelCanvasItem *item, double *x1, double *y1, double *x2, double *y2);
+
+ /* Signal: an event ocurred for an item of this type. The (x, y)
+ * coordinates are in the canvas world coordinate system.
+ */
+ gboolean (* event) (EelCanvasItem *item, GdkEvent *event);
+
+ /* Reserved for future expansion */
+ gpointer spare_vmethods [4];
+};
+
+
+/* Standard Gtk function */
+GType eel_canvas_item_get_type (void) G_GNUC_CONST;
+
+/* Create a canvas item using the standard Gtk argument mechanism. The item is
+ * automatically inserted at the top of the specified canvas group. The last
+ * argument must be a NULL pointer.
+ */
+EelCanvasItem *eel_canvas_item_new (EelCanvasGroup *parent, GType type,
+ const gchar *first_arg_name, ...);
+
+void eel_canvas_item_destroy (EelCanvasItem *item);
+
+/* Configure an item using the standard Gtk argument mechanism. The last
+ * argument must be a NULL pointer.
+ */
+void eel_canvas_item_set (EelCanvasItem *item, const gchar *first_arg_name, ...);
+
+/* Move an item by the specified amount */
+void eel_canvas_item_move (EelCanvasItem *item, double dx, double dy);
+
+/* Raise an item in the z-order of its parent group by the specified number of
+ * positions.
+ */
+void eel_canvas_item_raise (EelCanvasItem *item, int positions);
+
+/* Lower an item in the z-order of its parent group by the specified number of
+ * positions.
+ */
+void eel_canvas_item_lower (EelCanvasItem *item, int positions);
+
+/* Raise an item to the top of its parent group's z-order. */
+void eel_canvas_item_raise_to_top (EelCanvasItem *item);
+
+/* Lower an item to the bottom of its parent group's z-order */
+void eel_canvas_item_lower_to_bottom (EelCanvasItem *item);
+
+/* Send an item behind another item */
+void eel_canvas_item_send_behind (EelCanvasItem *item,
+ EelCanvasItem *behind_item);
+
+
+/* Show an item (make it visible). If the item is already shown, it has no
+ * effect.
+ */
+void eel_canvas_item_show (EelCanvasItem *item);
+
+/* Hide an item (make it invisible). If the item is already invisible, it has
+ * no effect.
+ */
+void eel_canvas_item_hide (EelCanvasItem *item);
+
+/* Grab the seat for the specified item. Only the events in event_mask will be
+ * reported. If cursor is non-NULL, it will be used during the duration of the
+ * grab. event is the event, triggering the grab. Returns the same values as gdk_seat_grab().
+ */
+GdkGrabStatus eel_canvas_item_grab (EelCanvasItem *item,
+ GdkEventMask event_mask,
+ GdkCursor *cursor,
+ const GdkEvent* event);
+
+/* Ungrabs the seat -- the specified item must be the same that was passed to
+ * eel_canvas_item_grab().
+ */
+void eel_canvas_item_ungrab (EelCanvasItem *item);
+
+/* These functions convert from a coordinate system to another. "w" is world
+ * coordinates and "i" is item coordinates.
+ */
+void eel_canvas_item_i2w (EelCanvasItem *item, double *x, double *y);
+
+/* Fetch the bounding box of the item. The bounding box may not be exactly
+ * tight, but the canvas items will do the best they can. The returned bounding
+ * box is in the coordinate system of the item's parent.
+ */
+void eel_canvas_item_get_bounds (EelCanvasItem *item,
+ double *x1, double *y1, double *x2, double *y2);
+
+/* Request that the update method eventually get called. This should be used
+ * only by item implementations.
+ */
+void eel_canvas_item_request_update (EelCanvasItem *item);
+
+/* Request a redraw of the bounding box of the canvas item */
+void eel_canvas_item_request_redraw (EelCanvasItem *item);
+
+/* EelCanvasGroup - a group of canvas items
+ *
+ * A group is a node in the hierarchical tree of groups/items inside a canvas.
+ * Groups serve to give a logical structure to the items.
+ *
+ * Consider a circuit editor application that uses the canvas for its schematic
+ * display. Hierarchically, there would be canvas groups that contain all the
+ * components needed for an "adder", for example -- this includes some logic
+ * gates as well as wires. You can move stuff around in a convenient way by
+ * doing a eel_canvas_item_move() of the hierarchical groups -- to move an
+ * adder, simply move the group that represents the adder.
+ *
+ * The following arguments are available:
+ *
+ * name type read/write description
+ * --------------------------------------------------------------------------------
+ * x double RW X coordinate of group's origin
+ * y double RW Y coordinate of group's origin
+ */
+
+
+#define EEL_TYPE_CANVAS_GROUP (eel_canvas_group_get_type ())
+#define EEL_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEL_TYPE_CANVAS_GROUP, EelCanvasGroup))
+#define EEL_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEL_TYPE_CANVAS_GROUP, EelCanvasGroupClass))
+#define EEL_IS_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEL_TYPE_CANVAS_GROUP))
+#define EEL_IS_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEL_TYPE_CANVAS_GROUP))
+#define EEL_CANVAS_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEL_TYPE_CANVAS_GROUP, EelCanvasGroupClass))
+
+
+struct _EelCanvasGroup {
+ EelCanvasItem item;
+
+ double xpos, ypos;
+
+ /* Children of the group */
+ GList *item_list;
+ GList *item_list_end;
+};
+
+struct _EelCanvasGroupClass {
+ EelCanvasItemClass parent_class;
+};
+
+
+/* Standard Gtk function */
+GType eel_canvas_group_get_type (void) G_GNUC_CONST;
+
+
+/*** EelCanvas ***/
+
+
+#define EEL_TYPE_CANVAS (eel_canvas_get_type ())
+#define EEL_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEL_TYPE_CANVAS, EelCanvas))
+#define EEL_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEL_TYPE_CANVAS, EelCanvasClass))
+#define EEL_IS_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEL_TYPE_CANVAS))
+#define EEL_IS_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEL_TYPE_CANVAS))
+#define EEL_CANVAS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEL_TYPE_CANVAS, EelCanvasClass))
+
+
+struct _EelCanvas {
+ GtkLayout layout;
+
+ /* Root canvas group */
+ EelCanvasItem *root;
+
+ /* The item containing the mouse pointer, or NULL if none */
+ EelCanvasItem *current_item;
+
+ /* Item that is about to become current (used to track deletions and such) */
+ EelCanvasItem *new_current_item;
+
+ /* Item that holds a pointer grab, or NULL if none */
+ EelCanvasItem *grabbed_item;
+
+ /* If non-NULL, the currently focused item */
+ EelCanvasItem *focused_item;
+
+ /* Event on which selection of current item is based */
+ GdkEvent pick_event;
+
+ /* Scrolling region */
+ double scroll_x1, scroll_y1;
+ double scroll_x2, scroll_y2;
+
+ /* Scaling factor to be used for display */
+ double pixels_per_unit;
+
+ /* Idle handler ID */
+ guint idle_id;
+
+ /* Signal handler ID for destruction of the root item */
+ gulong root_destroy_id;
+
+ /* Internal pixel offsets when zoomed out */
+ int zoom_xofs, zoom_yofs;
+
+ /* Last known modifier state, for deferred repick when a button is down */
+ int state;
+
+ /* Event mask specified when grabbing an item */
+ guint grabbed_event_mask;
+
+ /* Tolerance distance for picking items */
+ int close_enough;
+
+ /* Whether the canvas should center the canvas in the middle of
+ * the window if the scroll region is smaller than the window */
+ unsigned int center_scroll_region : 1;
+
+ /* Whether items need update at next idle loop iteration */
+ unsigned int need_update : 1;
+
+ /* Are we in the midst of an update */
+ unsigned int doing_update : 1;
+
+ /* Whether the canvas needs redrawing at the next idle loop iteration */
+ unsigned int need_redraw : 1;
+
+ /* Whether current item will be repicked at next idle loop iteration */
+ unsigned int need_repick : 1;
+
+ /* For use by internal pick_current_item() function */
+ unsigned int left_grabbed_item : 1;
+
+ /* For use by internal pick_current_item() function */
+ unsigned int in_repick : 1;
+};
+
+struct _EelCanvasClass {
+ GtkLayoutClass parent_class;
+
+ /* Private Virtual methods for groping the canvas inside bonobo */
+ void (* request_update) (EelCanvas *canvas);
+
+ /* Reserved for future expansion */
+ gpointer spare_vmethods [4];
+};
+
+
+/* Standard Gtk function */
+GType eel_canvas_get_type (void) G_GNUC_CONST;
+
+/* Creates a new canvas. You should check that the canvas is created with the
+ * proper visual and colormap. Any visual will do unless you intend to insert
+ * gdk_imlib images into it, in which case you should use the gdk_imlib visual.
+ *
+ * You should call eel_canvas_set_scroll_region() soon after calling this
+ * function to set the desired scrolling limits for the canvas.
+ */
+GtkWidget *eel_canvas_new (void);
+
+/* Returns the root canvas item group of the canvas */
+EelCanvasGroup *eel_canvas_root (EelCanvas *canvas);
+
+/* Sets the limits of the scrolling region, in world coordinates */
+void eel_canvas_set_scroll_region (EelCanvas *canvas,
+ double x1, double y1, double x2, double y2);
+
+/* Gets the limits of the scrolling region, in world coordinates */
+void eel_canvas_get_scroll_region (EelCanvas *canvas,
+ double *x1, double *y1, double *x2, double *y2);
+
+/* Sets the number of pixels that correspond to one unit in world coordinates */
+void eel_canvas_set_pixels_per_unit (EelCanvas *canvas, double n);
+
+/* Returns the scroll offsets of the canvas in canvas pixel coordinates. You
+ * can specify NULL for any of the values, in which case that value will not be
+ * queried.
+ */
+void eel_canvas_get_scroll_offsets (EelCanvas *canvas, int *cx, int *cy);
+
+/* For use only by item type implementations. Request that the canvas
+ * eventually redraw the specified region, specified in canvas pixel
+ * coordinates. The region contains (x1, y1) but not (x2, y2).
+ */
+void eel_canvas_request_redraw (EelCanvas *canvas, int x1, int y1, int x2, int y2);
+
+/* These functions convert from a coordinate system to another. "w" is world
+ * coordinates, "c" is canvas pixel coordinates (pixel coordinates that are
+ * (0,0) for the upper-left scrolling limit and something else for the
+ * lower-left scrolling limit).
+ */
+void eel_canvas_w2c_rect_d (EelCanvas *canvas,
+ double *x1, double *y1,
+ double *x2, double *y2);
+void eel_canvas_w2c (EelCanvas *canvas, double wx, double wy, int *cx, int *cy);
+void eel_canvas_w2c_d (EelCanvas *canvas, double wx, double wy, double *cx, double *cy);
+void eel_canvas_c2w (EelCanvas *canvas, int cx, int cy, double *wx, double *wy);
+
+/* This function takes in coordinates relative to the GTK_LAYOUT
+ * (canvas)->bin_window and converts them to world coordinates.
+ * These days canvas coordinates and window coordinates are the same, but
+ * these are left for backwards compat reasons.
+ */
+void eel_canvas_window_to_world (EelCanvas *canvas,
+ double winx, double winy, double *worldx, double *worldy);
+
+/* This is the inverse of eel_canvas_window_to_world() */
+void eel_canvas_world_to_window (EelCanvas *canvas,
+ double worldx, double worldy, double *winx, double *winy);
+
+/* Accessible implementation */
+GType eel_canvas_accessible_get_type (void);
+
+typedef struct _EelCanvasAccessible EelCanvasAccessible;
+struct _EelCanvasAccessible
+{
+ GtkContainerAccessible parent;
+};
+
+typedef struct _EelCanvasAccessibleClass EelCanvasAccessibleClass;
+struct _EelCanvasAccessibleClass
+{
+ GtkContainerAccessibleClass parent_class;
+};
+
+GType eel_canvas_item_accessible_get_type (void);
+
+typedef struct _EelCanvasItemAccessible EelCanvasItemAccessible;
+struct _EelCanvasItemAccessible
+{
+ GtkAccessible parent;
+};
+
+typedef struct _EelCanvasItemAccessibleClass EelCanvasItemAccessibleClass;
+struct _EelCanvasItemAccessibleClass
+{
+ GtkAccessibleClass parent_class;
+};
+
+G_END_DECLS \ No newline at end of file
diff --git a/eel/meson.build b/eel/meson.build
index 8f88a9bad..e5fa7465d 100644
--- a/eel/meson.build
+++ b/eel/meson.build
@@ -1,6 +1,8 @@
libeel_2_sources = [
'eel-art-extensions.h',
'eel-art-extensions.c',
+ 'eel-canvas.h',
+ 'eel-canvas.c',
'eel-debug.h',
'eel-debug.c',
'eel-glib-extensions.h',
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7c1b6a31b..6b7fdf593 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -4,6 +4,7 @@ data/nautilus-autorun-software.desktop.in
data/org.gnome.Nautilus.appdata.xml.in.in
data/org.gnome.Nautilus.desktop.in.in
data/org.gnome.nautilus.gschema.xml
+eel/eel-canvas.c
eel/eel-gtk-extensions.c
eel/eel-stock-dialogs.c
eel/eel-vfs-extensions.c
@@ -22,6 +23,11 @@ src/nautilus-autorun-software.c
src/nautilus-batch-rename-dialog.c
src/nautilus-batch-rename-dialog.h
src/nautilus-bookmark.c
+src/nautilus-canvas-container.c
+src/nautilus-canvas-dnd.c
+src/nautilus-canvas-item.c
+src/nautilus-canvas-view.c
+src/nautilus-canvas-view-container.c
src/nautilus-clipboard.c
src/nautilus-column-chooser.c
src/nautilus-column-utilities.c
diff --git a/src/meson.build b/src/meson.build
index 20865fff4..dac467d54 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -72,6 +72,10 @@ libnautilus_sources = [
'nautilus-application.h',
'nautilus-bookmark-list.c',
'nautilus-bookmark-list.h',
+ 'nautilus-canvas-view.c',
+ 'nautilus-canvas-view.h',
+ 'nautilus-canvas-view-container.c',
+ 'nautilus-canvas-view-container.h',
'nautilus-dbus-manager.c',
'nautilus-dbus-manager.h',
'nautilus-error-reporting.c',
@@ -143,6 +147,13 @@ libnautilus_sources = [
'nautilus-x-content-bar.h',
'nautilus-bookmark.c',
'nautilus-bookmark.h',
+ 'nautilus-canvas-container.c',
+ 'nautilus-canvas-container.h',
+ 'nautilus-canvas-dnd.c',
+ 'nautilus-canvas-dnd.h',
+ 'nautilus-canvas-item.c',
+ 'nautilus-canvas-item.h',
+ 'nautilus-canvas-private.h',
'nautilus-clipboard.c',
'nautilus-clipboard.h',
'nautilus-column-chooser.c',
@@ -223,6 +234,8 @@ libnautilus_sources = [
'nautilus-search-engine-simple.h',
'nautilus-search-hit.c',
'nautilus-search-hit.h',
+ 'nautilus-selection-canvas-item.c',
+ 'nautilus-selection-canvas-item.h',
'nautilus-signaller.h',
'nautilus-signaller.c',
'nautilus-query.c',
diff --git a/src/nautilus-canvas-container.c b/src/nautilus-canvas-container.c
new file mode 100644
index 000000000..44b637166
--- /dev/null
+++ b/src/nautilus-canvas-container.c
@@ -0,0 +1,6360 @@
+/* nautilus-canvas-container.c - Canvas container widget.
+ *
+ * Copyright (C) 1999, 2000 Free Software Foundation
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2002, 2003 Red Hat, Inc.
+ *
+ * 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,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ettore Perazzoli <ettore@gnu.org>,
+ * Darin Adler <darin@bentspoon.com>
+ */
+
+#include "nautilus-canvas-container.h"
+
+#include <atk/atkaction.h>
+#include <eel/eel-art-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-vfs-extensions.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdk.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_CANVAS_CONTAINER
+#include "nautilus-debug.h"
+
+#include "nautilus-canvas-private.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-icon-info.h"
+#include "nautilus-lib-self-check-functions.h"
+#include "nautilus-selection-canvas-item.h"
+
+/* Interval for updating the rubberband selection, in milliseconds. */
+#define RUBBERBAND_TIMEOUT_INTERVAL 10
+
+#define RUBBERBAND_SCROLL_THRESHOLD 5
+
+/* Initial unpositioned icon value */
+#define ICON_UNPOSITIONED_VALUE -1
+
+/* Timeout for making the icon currently selected for keyboard operation visible.
+ * If this is 0, you can get into trouble with extra scrolling after holding
+ * down the arrow key for awhile when there are many items.
+ */
+#define KEYBOARD_ICON_REVEAL_TIMEOUT 10
+
+#define CONTEXT_MENU_TIMEOUT_INTERVAL 500
+
+/* Maximum amount of milliseconds the mouse button is allowed to stay down
+ * and still be considered a click.
+ */
+#define MAX_CLICK_TIME 1500
+
+/* Button assignments. */
+#define DRAG_BUTTON 1
+#define RUBBERBAND_BUTTON 1
+#define MIDDLE_BUTTON 2
+#define CONTEXTUAL_MENU_BUTTON 3
+#define DRAG_MENU_BUTTON 2
+
+/* Maximum size (pixels) allowed for icons at the standard zoom level. */
+#define MINIMUM_IMAGE_SIZE 24
+#define MAXIMUM_IMAGE_SIZE 96
+
+#define ICON_PAD_LEFT 4
+#define ICON_PAD_RIGHT 4
+#define ICON_PAD_TOP 4
+#define ICON_PAD_BOTTOM 4
+
+#define CONTAINER_PAD_LEFT 4
+#define CONTAINER_PAD_RIGHT 4
+#define CONTAINER_PAD_TOP 4
+#define CONTAINER_PAD_BOTTOM 4
+
+/* Width of a "grid unit". Canvas items will always take up one or more
+ * grid units, rounding up their size relative to the unit width.
+ * So with an 80px grid unit, a 100px canvas item would take two grid units,
+ * where a 76px canvas item would only take one.
+ * Canvas items are then centered in the extra available space.
+ * Keep in sync with MAX_TEXT_WIDTH at nautilus-canvas-item.
+ */
+#define SMALL_ICON_GRID_WIDTH 124
+#define STANDARD_ICON_GRID_WIDTH 112
+#define LARGE_ICON_GRID_WIDTH 106
+#define LARGER_ICON_GRID_WIDTH 128
+
+/* Copied from NautilusCanvasContainer */
+#define NAUTILUS_CANVAS_CONTAINER_SEARCH_DIALOG_TIMEOUT 5
+
+/* Copied from NautilusFile */
+#define UNDEFINED_TIME ((time_t) (-1))
+
+enum
+{
+ ACTION_ACTIVATE,
+ ACTION_MENU,
+ LAST_ACTION
+};
+
+typedef struct
+{
+ GList *selection;
+ char *action_descriptions[LAST_ACTION];
+} NautilusCanvasContainerAccessiblePrivate;
+
+static GType nautilus_canvas_container_accessible_get_type (void);
+static void preview_selected_items (NautilusCanvasContainer *container);
+static void activate_selected_items (NautilusCanvasContainer *container);
+static void activate_selected_items_alternate (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon);
+static NautilusCanvasIcon *get_first_selected_icon (NautilusCanvasContainer *container);
+static NautilusCanvasIcon *get_nth_selected_icon (NautilusCanvasContainer *container,
+ int index);
+static gboolean has_multiple_selection (NautilusCanvasContainer *container);
+static gboolean all_selected (NautilusCanvasContainer *container);
+static gboolean has_selection (NautilusCanvasContainer *container);
+static void icon_destroy (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon);
+static gboolean finish_adding_new_icons (NautilusCanvasContainer *container);
+static inline void icon_get_bounding_box (NautilusCanvasIcon *icon,
+ int *x1_return,
+ int *y1_return,
+ int *x2_return,
+ int *y2_return,
+ NautilusCanvasItemBoundsUsage usage);
+static void handle_hadjustment_changed (GtkAdjustment *adjustment,
+ NautilusCanvasContainer *container);
+static void handle_vadjustment_changed (GtkAdjustment *adjustment,
+ NautilusCanvasContainer *container);
+static GList *nautilus_canvas_container_get_selected_icons (NautilusCanvasContainer *container);
+static void nautilus_canvas_container_update_visible_icons (NautilusCanvasContainer *container);
+static void reveal_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon);
+
+static void nautilus_canvas_container_set_rtl_positions (NautilusCanvasContainer *container);
+static double get_mirror_x_position (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ double x);
+static void text_ellipsis_limit_changed_container_callback (gpointer callback_data);
+
+static int compare_icons_horizontal (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b);
+
+static int compare_icons_vertical (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b);
+
+static void schedule_redo_layout (NautilusCanvasContainer *container);
+
+static const char *nautilus_canvas_container_accessible_action_names[] =
+{
+ "activate",
+ "menu",
+ NULL
+};
+
+static const char *nautilus_canvas_container_accessible_action_descriptions[] =
+{
+ "Activate selected items",
+ "Popup context menu",
+ NULL
+};
+
+G_DEFINE_TYPE (NautilusCanvasContainer, nautilus_canvas_container, EEL_TYPE_CANVAS);
+
+/* The NautilusCanvasContainer signals. */
+enum
+{
+ ACTIVATE,
+ ACTIVATE_ALTERNATE,
+ ACTIVATE_PREVIEWER,
+ BAND_SELECT_STARTED,
+ BAND_SELECT_ENDED,
+ BUTTON_PRESS,
+ CONTEXT_CLICK_BACKGROUND,
+ CONTEXT_CLICK_SELECTION,
+ MIDDLE_CLICK,
+ GET_CONTAINER_URI,
+ GET_ICON_URI,
+ GET_ICON_ACTIVATION_URI,
+ GET_ICON_DROP_TARGET_URI,
+ ICON_RENAME_STARTED,
+ ICON_RENAME_ENDED,
+ ICON_STRETCH_STARTED,
+ ICON_STRETCH_ENDED,
+ MOVE_COPY_ITEMS,
+ HANDLE_NETSCAPE_URL,
+ HANDLE_URI_LIST,
+ HANDLE_TEXT,
+ HANDLE_RAW,
+ HANDLE_HOVER,
+ SELECTION_CHANGED,
+ ICON_ADDED,
+ ICON_REMOVED,
+ CLEARED,
+ LAST_SIGNAL
+};
+
+typedef struct
+{
+ int **icon_grid;
+ int *grid_memory;
+ int num_rows;
+ int num_columns;
+ gboolean tight;
+} PlacementGrid;
+
+static guint signals[LAST_SIGNAL];
+
+/* Functions dealing with NautilusIcons. */
+
+static void
+icon_free (NautilusCanvasIcon *icon)
+{
+ /* Destroy this icon item; the parent will unref it. */
+ eel_canvas_item_destroy (EEL_CANVAS_ITEM (icon->item));
+ g_free (icon);
+}
+
+static gboolean
+icon_is_positioned (const NautilusCanvasIcon *icon)
+{
+ return icon->x != ICON_UNPOSITIONED_VALUE && icon->y != ICON_UNPOSITIONED_VALUE;
+}
+
+
+/* x, y are the top-left coordinates of the icon. */
+static void
+icon_set_position (NautilusCanvasIcon *icon,
+ double x,
+ double y)
+{
+ if (icon->x == x && icon->y == y)
+ {
+ return;
+ }
+
+ if (icon->x == ICON_UNPOSITIONED_VALUE)
+ {
+ icon->x = 0;
+ }
+ if (icon->y == ICON_UNPOSITIONED_VALUE)
+ {
+ icon->y = 0;
+ }
+
+ eel_canvas_item_move (EEL_CANVAS_ITEM (icon->item),
+ x - icon->x,
+ y - icon->y);
+
+ icon->x = x;
+ icon->y = y;
+}
+
+static guint
+nautilus_canvas_container_get_grid_size_for_zoom_level (NautilusCanvasZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL:
+ {
+ return SMALL_ICON_GRID_WIDTH;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD:
+ {
+ return STANDARD_ICON_GRID_WIDTH;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE:
+ {
+ return LARGE_ICON_GRID_WIDTH;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER:
+ {
+ return LARGER_ICON_GRID_WIDTH;
+ }
+ break;
+
+ default:
+ {
+ g_return_val_if_reached (STANDARD_ICON_GRID_WIDTH);
+ }
+ break;
+ }
+}
+
+guint
+nautilus_canvas_container_get_icon_size_for_zoom_level (NautilusCanvasZoomLevel zoom_level)
+{
+ switch (zoom_level)
+ {
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_SMALL;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_STANDARD;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_LARGE;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER:
+ {
+ return NAUTILUS_CANVAS_ICON_SIZE_LARGER;
+ }
+ break;
+
+ default:
+ {
+ g_return_val_if_reached (NAUTILUS_CANVAS_ICON_SIZE_STANDARD);
+ }
+ break;
+ }
+}
+
+static void
+icon_get_size (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ guint *size)
+{
+ if (size != NULL)
+ {
+ *size = MAX (nautilus_canvas_container_get_icon_size_for_zoom_level (container->details->zoom_level),
+ NAUTILUS_CANVAS_ICON_SIZE_SMALL);
+ }
+}
+
+static void
+icon_raise (NautilusCanvasIcon *icon)
+{
+ EelCanvasItem *item, *band;
+
+ item = EEL_CANVAS_ITEM (icon->item);
+ band = NAUTILUS_CANVAS_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
+
+ eel_canvas_item_send_behind (item, band);
+}
+
+static void
+icon_toggle_selected (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ icon->is_selected = !icon->is_selected;
+ if (icon->is_selected)
+ {
+ container->details->selection = g_list_prepend (container->details->selection, icon->data);
+ container->details->selection_needs_resort = TRUE;
+ }
+ else
+ {
+ container->details->selection = g_list_remove (container->details->selection, icon->data);
+ }
+
+ eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
+ "highlighted_for_selection", (gboolean) icon->is_selected,
+ NULL);
+
+ /* Raise each newly-selected icon to the front as it is selected. */
+ if (icon->is_selected)
+ {
+ icon_raise (icon);
+ }
+}
+
+/* Select an icon. Return TRUE if selection has changed. */
+static gboolean
+icon_set_selected (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ gboolean select)
+{
+ if (select == icon->is_selected)
+ {
+ return FALSE;
+ }
+
+ icon_toggle_selected (container, icon);
+ g_assert (select == icon->is_selected);
+ return TRUE;
+}
+
+static inline void
+icon_get_bounding_box (NautilusCanvasIcon *icon,
+ int *x1_return,
+ int *y1_return,
+ int *x2_return,
+ int *y2_return,
+ NautilusCanvasItemBoundsUsage usage)
+{
+ double x1, y1, x2, y2;
+
+ if (usage == BOUNDS_USAGE_FOR_DISPLAY)
+ {
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
+ &x1, &y1, &x2, &y2);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_LAYOUT)
+ {
+ nautilus_canvas_item_get_bounds_for_layout (icon->item,
+ &x1, &y1, &x2, &y2);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ nautilus_canvas_item_get_bounds_for_entire_item (icon->item,
+ &x1, &y1, &x2, &y2);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ if (x1_return != NULL)
+ {
+ *x1_return = x1;
+ }
+
+ if (y1_return != NULL)
+ {
+ *y1_return = y1;
+ }
+
+ if (x2_return != NULL)
+ {
+ *x2_return = x2;
+ }
+
+ if (y2_return != NULL)
+ {
+ *y2_return = y2;
+ }
+}
+
+/* Utility functions for NautilusCanvasContainer. */
+
+gboolean
+nautilus_canvas_container_scroll (NautilusCanvasContainer *container,
+ int delta_x,
+ int delta_y)
+{
+ GtkAdjustment *hadj, *vadj;
+ int old_h_value, old_v_value;
+
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
+
+ /* Store the old ajustment values so we can tell if we
+ * ended up actually scrolling. We may not have in a case
+ * where the resulting value got pinned to the adjustment
+ * min or max.
+ */
+ old_h_value = gtk_adjustment_get_value (hadj);
+ old_v_value = gtk_adjustment_get_value (vadj);
+
+ gtk_adjustment_set_value (hadj, gtk_adjustment_get_value (hadj) + delta_x);
+ gtk_adjustment_set_value (vadj, gtk_adjustment_get_value (vadj) + delta_y);
+
+ /* return TRUE if we did scroll */
+ return gtk_adjustment_get_value (hadj) != old_h_value || gtk_adjustment_get_value (vadj) != old_v_value;
+}
+
+static void
+pending_icon_to_reveal_destroy_callback (NautilusCanvasItem *item,
+ NautilusCanvasContainer *container)
+{
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_assert (container->details->pending_icon_to_reveal != NULL);
+ g_assert (container->details->pending_icon_to_reveal->item == item);
+
+ container->details->pending_icon_to_reveal = NULL;
+}
+
+static NautilusCanvasIcon *
+get_pending_icon_to_reveal (NautilusCanvasContainer *container)
+{
+ return container->details->pending_icon_to_reveal;
+}
+
+static void
+set_pending_icon_to_reveal (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ NautilusCanvasIcon *old_icon;
+
+ old_icon = container->details->pending_icon_to_reveal;
+
+ if (icon == old_icon)
+ {
+ return;
+ }
+
+ if (old_icon != NULL)
+ {
+ g_signal_handlers_disconnect_by_func
+ (old_icon->item,
+ G_CALLBACK (pending_icon_to_reveal_destroy_callback),
+ container);
+ }
+
+ if (icon != NULL)
+ {
+ g_signal_connect (icon->item, "destroy",
+ G_CALLBACK (pending_icon_to_reveal_destroy_callback),
+ container);
+ }
+
+ container->details->pending_icon_to_reveal = icon;
+}
+
+static void
+item_get_canvas_bounds (EelCanvasItem *item,
+ EelIRect *bounds)
+{
+ EelDRect world_rect;
+
+ eel_canvas_item_get_bounds (item,
+ &world_rect.x0,
+ &world_rect.y0,
+ &world_rect.x1,
+ &world_rect.y1);
+ eel_canvas_item_i2w (item->parent,
+ &world_rect.x0,
+ &world_rect.y0);
+ eel_canvas_item_i2w (item->parent,
+ &world_rect.x1,
+ &world_rect.y1);
+
+ world_rect.x0 -= ICON_PAD_LEFT + ICON_PAD_RIGHT;
+ world_rect.x1 += ICON_PAD_LEFT + ICON_PAD_RIGHT;
+
+ world_rect.y0 -= ICON_PAD_TOP + ICON_PAD_BOTTOM;
+ world_rect.y1 += ICON_PAD_TOP + ICON_PAD_BOTTOM;
+
+ eel_canvas_w2c (item->canvas,
+ world_rect.x0,
+ world_rect.y0,
+ &bounds->x0,
+ &bounds->y0);
+ eel_canvas_w2c (item->canvas,
+ world_rect.x1,
+ world_rect.y1,
+ &bounds->x1,
+ &bounds->y1);
+}
+
+static void
+icon_get_row_and_column_bounds (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ EelIRect *bounds)
+{
+ GList *p;
+ NautilusCanvasIcon *one_icon;
+ EelIRect one_bounds;
+
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), bounds);
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ one_icon = p->data;
+
+ if (icon == one_icon)
+ {
+ continue;
+ }
+
+ if (compare_icons_horizontal (container, icon, one_icon) == 0)
+ {
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (one_icon->item), &one_bounds);
+ bounds->x0 = MIN (bounds->x0, one_bounds.x0);
+ bounds->x1 = MAX (bounds->x1, one_bounds.x1);
+ }
+
+ if (compare_icons_vertical (container, icon, one_icon) == 0)
+ {
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (one_icon->item), &one_bounds);
+ bounds->y0 = MIN (bounds->y0, one_bounds.y0);
+ bounds->y1 = MAX (bounds->y1, one_bounds.y1);
+ }
+ }
+}
+
+static void
+reveal_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ GtkAllocation allocation;
+ GtkAdjustment *hadj, *vadj;
+ EelIRect bounds;
+
+ if (!icon_is_positioned (icon))
+ {
+ set_pending_icon_to_reveal (container, icon);
+ return;
+ }
+
+ set_pending_icon_to_reveal (container, NULL);
+
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
+
+ /* ensure that we reveal the entire row/column */
+ icon_get_row_and_column_bounds (container, icon, &bounds);
+
+ if (bounds.y0 < gtk_adjustment_get_value (vadj))
+ {
+ gtk_adjustment_set_value (vadj, bounds.y0);
+ }
+ else if (bounds.y1 > gtk_adjustment_get_value (vadj) + allocation.height)
+ {
+ gtk_adjustment_set_value
+ (vadj, bounds.y1 - allocation.height);
+ }
+
+ if (bounds.x0 < gtk_adjustment_get_value (hadj))
+ {
+ gtk_adjustment_set_value (hadj, bounds.x0);
+ }
+ else if (bounds.x1 > gtk_adjustment_get_value (hadj) + allocation.width)
+ {
+ gtk_adjustment_set_value
+ (hadj, bounds.x1 - allocation.width);
+ }
+}
+
+static void
+process_pending_icon_to_reveal (NautilusCanvasContainer *container)
+{
+ NautilusCanvasIcon *pending_icon_to_reveal;
+
+ pending_icon_to_reveal = get_pending_icon_to_reveal (container);
+
+ if (pending_icon_to_reveal != NULL)
+ {
+ reveal_icon (container, pending_icon_to_reveal);
+ }
+}
+
+static gboolean
+keyboard_icon_reveal_timeout_callback (gpointer data)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasIcon *icon;
+
+ container = NAUTILUS_CANVAS_CONTAINER (data);
+ icon = container->details->keyboard_icon_to_reveal;
+
+ g_assert (icon != NULL);
+
+ /* Only reveal the icon if it's still the keyboard focus or if
+ * it's still selected. Someone originally thought we should
+ * cancel this reveal if the user manages to sneak a direct
+ * scroll in before the timeout fires, but we later realized
+ * this wouldn't actually be an improvement
+ * (see bugzilla.gnome.org 40612).
+ */
+ if (icon == container->details->focus
+ || icon->is_selected)
+ {
+ reveal_icon (container, icon);
+ }
+ container->details->keyboard_icon_reveal_timer_id = 0;
+
+ return FALSE;
+}
+
+static void
+unschedule_keyboard_icon_reveal (NautilusCanvasContainer *container)
+{
+ NautilusCanvasContainerDetails *details;
+
+ details = container->details;
+
+ if (details->keyboard_icon_reveal_timer_id != 0)
+ {
+ g_source_remove (details->keyboard_icon_reveal_timer_id);
+ }
+}
+
+static void
+schedule_keyboard_icon_reveal (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ NautilusCanvasContainerDetails *details;
+
+ details = container->details;
+
+ unschedule_keyboard_icon_reveal (container);
+
+ details->keyboard_icon_to_reveal = icon;
+ details->keyboard_icon_reveal_timer_id
+ = g_timeout_add (KEYBOARD_ICON_REVEAL_TIMEOUT,
+ keyboard_icon_reveal_timeout_callback,
+ container);
+}
+
+static void inline
+emit_atk_object_notify_focused (NautilusCanvasIcon *icon,
+ gboolean focused)
+{
+ AtkObject *atk_object = atk_gobject_accessible_for_object (G_OBJECT (icon->item));
+ atk_object_notify_state_change (atk_object, ATK_STATE_FOCUSED, focused);
+}
+
+static void
+clear_focus (NautilusCanvasContainer *container)
+{
+ if (container->details->focus != NULL)
+ {
+ if (container->details->keyboard_focus)
+ {
+ eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->focus->item),
+ "highlighted_as_keyboard_focus", 0,
+ NULL);
+ }
+ else
+ {
+ emit_atk_object_notify_focused (container->details->focus, FALSE);
+ }
+ }
+
+ container->details->focus = NULL;
+}
+
+/* Set @icon as the icon currently focused for accessibility. */
+static void
+set_focus (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ gboolean keyboard_focus)
+{
+ g_assert (icon != NULL);
+
+ if (icon == container->details->focus)
+ {
+ return;
+ }
+
+ clear_focus (container);
+
+ container->details->focus = icon;
+ container->details->keyboard_focus = keyboard_focus;
+
+ if (keyboard_focus)
+ {
+ eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->focus->item),
+ "highlighted_as_keyboard_focus", 1,
+ NULL);
+ }
+ else
+ {
+ emit_atk_object_notify_focused (container->details->focus, TRUE);
+ }
+}
+
+static void
+set_keyboard_rubberband_start (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ container->details->keyboard_rubberband_start = icon;
+}
+
+static void
+clear_keyboard_rubberband_start (NautilusCanvasContainer *container)
+{
+ container->details->keyboard_rubberband_start = NULL;
+}
+
+/* carbon-copy of eel_canvas_group_bounds(), but
+ * for NautilusCanvasContainerItems it returns the
+ * bounds for the “entire item”.
+ */
+static void
+get_icon_bounds_for_canvas_bounds (EelCanvasGroup *group,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2,
+ NautilusCanvasItemBoundsUsage usage)
+{
+ EelCanvasItem *child;
+ GList *list;
+ double tx1, ty1, tx2, ty2;
+ double minx, miny, maxx, maxy;
+ int set;
+
+ /* Get the bounds of the first visible item */
+
+ child = NULL; /* Unnecessary but eliminates a warning. */
+
+ set = FALSE;
+
+ for (list = group->item_list; list; list = list->next)
+ {
+ child = list->data;
+
+ if (!NAUTILUS_IS_CANVAS_ITEM (child))
+ {
+ continue;
+ }
+
+ if (child->flags & EEL_CANVAS_ITEM_VISIBLE)
+ {
+ set = TRUE;
+ if (!NAUTILUS_IS_CANVAS_ITEM (child) ||
+ usage == BOUNDS_USAGE_FOR_DISPLAY)
+ {
+ eel_canvas_item_get_bounds (child, &minx, &miny, &maxx, &maxy);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_LAYOUT)
+ {
+ nautilus_canvas_item_get_bounds_for_layout (NAUTILUS_CANVAS_ITEM (child),
+ &minx, &miny, &maxx, &maxy);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ nautilus_canvas_item_get_bounds_for_entire_item (NAUTILUS_CANVAS_ITEM (child),
+ &minx, &miny, &maxx, &maxy);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+ }
+
+ /* If there were no visible items, return an empty bounding box */
+
+ if (!set)
+ {
+ *x1 = *y1 = *x2 = *y2 = 0.0;
+ return;
+ }
+
+ /* Now we can grow the bounds using the rest of the items */
+
+ list = list->next;
+
+ for (; list; list = list->next)
+ {
+ child = list->data;
+
+ if (!NAUTILUS_IS_CANVAS_ITEM (child))
+ {
+ continue;
+ }
+
+ if (!(child->flags & EEL_CANVAS_ITEM_VISIBLE))
+ {
+ continue;
+ }
+
+ if (!NAUTILUS_IS_CANVAS_ITEM (child) ||
+ usage == BOUNDS_USAGE_FOR_DISPLAY)
+ {
+ eel_canvas_item_get_bounds (child, &tx1, &ty1, &tx2, &ty2);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_LAYOUT)
+ {
+ nautilus_canvas_item_get_bounds_for_layout (NAUTILUS_CANVAS_ITEM (child),
+ &tx1, &ty1, &tx2, &ty2);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ nautilus_canvas_item_get_bounds_for_entire_item (NAUTILUS_CANVAS_ITEM (child),
+ &tx1, &ty1, &tx2, &ty2);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ if (tx1 < minx)
+ {
+ minx = tx1;
+ }
+
+ if (ty1 < miny)
+ {
+ miny = ty1;
+ }
+
+ if (tx2 > maxx)
+ {
+ maxx = tx2;
+ }
+
+ if (ty2 > maxy)
+ {
+ maxy = ty2;
+ }
+ }
+
+ /* Make the bounds be relative to our parent's coordinate system */
+
+ if (EEL_CANVAS_ITEM (group)->parent)
+ {
+ minx += group->xpos;
+ miny += group->ypos;
+ maxx += group->xpos;
+ maxy += group->ypos;
+ }
+
+ if (x1 != NULL)
+ {
+ *x1 = minx;
+ }
+
+ if (y1 != NULL)
+ {
+ *y1 = miny;
+ }
+
+ if (x2 != NULL)
+ {
+ *x2 = maxx;
+ }
+
+ if (y2 != NULL)
+ {
+ *y2 = maxy;
+ }
+}
+
+static void
+get_all_icon_bounds (NautilusCanvasContainer *container,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2,
+ NautilusCanvasItemBoundsUsage usage)
+{
+ /* FIXME bugzilla.gnome.org 42477: Do we have to do something about the rubberband
+ * here? Any other non-icon items?
+ */
+ get_icon_bounds_for_canvas_bounds (EEL_CANVAS_GROUP (EEL_CANVAS (container)->root),
+ x1, y1, x2, y2, usage);
+}
+
+void
+nautilus_canvas_container_update_scroll_region (NautilusCanvasContainer *container)
+{
+ double x1, y1, x2, y2;
+ double pixels_per_unit;
+ GtkAdjustment *hadj, *vadj;
+ float step_increment;
+ GtkAllocation allocation;
+
+ pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
+
+ get_all_icon_bounds (container, &x1, &y1, &x2, &y2, BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+
+ /* Add border at the "end"of the layout (i.e. after the icons), to
+ * ensure we get some space when scrolled to the end.
+ */
+ y2 += ICON_PAD_BOTTOM + CONTAINER_PAD_BOTTOM;
+
+ /* Auto-layout assumes a 0, 0 scroll origin and at least allocation->width.
+ * Then we lay out to the right or to the left, so
+ * x can be < 0 and > allocation */
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ x1 = MIN (x1, 0);
+ x2 = MAX (x2, allocation.width / pixels_per_unit);
+ y1 = 0;
+
+ x2 -= 1;
+ x2 = MAX (x1, x2);
+
+ y2 -= 1;
+ y2 = MAX (y1, y2);
+
+ eel_canvas_set_scroll_region (EEL_CANVAS (container), x1, y1, x2, y2);
+
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
+
+ /* Scroll by 1/4 icon each time you click. */
+ step_increment = nautilus_canvas_container_get_icon_size_for_zoom_level
+ (container->details->zoom_level) / 4;
+ if (gtk_adjustment_get_step_increment (hadj) != step_increment)
+ {
+ gtk_adjustment_set_step_increment (hadj, step_increment);
+ }
+ if (gtk_adjustment_get_step_increment (vadj) != step_increment)
+ {
+ gtk_adjustment_set_step_increment (vadj, step_increment);
+ }
+}
+
+static void
+cache_icon_positions (NautilusCanvasContainer *container)
+{
+ GList *l;
+ gint idx;
+ NautilusCanvasIcon *icon;
+
+ for (l = container->details->icons, idx = 0; l != NULL; l = l->next)
+ {
+ icon = l->data;
+ icon->position = idx++;
+ }
+}
+
+static int
+compare_icons_data (gconstpointer a,
+ gconstpointer b,
+ gpointer canvas_container)
+{
+ NautilusCanvasContainerClass *klass;
+ NautilusCanvasIconData *data_a, *data_b;
+
+ data_a = (NautilusCanvasIconData *) a;
+ data_b = (NautilusCanvasIconData *) b;
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (canvas_container);
+
+ return klass->compare_icons (canvas_container, data_a, data_b);
+}
+
+static int
+compare_icons (gconstpointer a,
+ gconstpointer b,
+ gpointer canvas_container)
+{
+ NautilusCanvasContainerClass *klass;
+ const NautilusCanvasIcon *icon_a, *icon_b;
+
+ icon_a = a;
+ icon_b = b;
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (canvas_container);
+
+ return klass->compare_icons (canvas_container, icon_a->data, icon_b->data);
+}
+
+static void
+sort_selection (NautilusCanvasContainer *container)
+{
+ container->details->selection = g_list_sort_with_data (container->details->selection,
+ compare_icons_data,
+ container);
+ container->details->selection_needs_resort = FALSE;
+}
+
+static void
+sort_icons (NautilusCanvasContainer *container,
+ GList **icons)
+{
+ NautilusCanvasContainerClass *klass;
+
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (container);
+ g_assert (klass->compare_icons != NULL);
+
+ *icons = g_list_sort_with_data (*icons, compare_icons, container);
+}
+
+static void
+resort (NautilusCanvasContainer *container)
+{
+ sort_icons (container, &container->details->icons);
+ sort_selection (container);
+ cache_icon_positions (container);
+}
+
+typedef struct
+{
+ double width;
+ double height;
+ double x_offset;
+ double y_offset;
+} IconPositions;
+
+static void
+lay_down_one_line (NautilusCanvasContainer *container,
+ GList *line_start,
+ GList *line_end,
+ double y,
+ double max_height,
+ GArray *positions,
+ gboolean whole_text)
+{
+ GList *p;
+ NautilusCanvasIcon *icon;
+ double x, ltr_icon_x, icon_x, y_offset;
+ IconPositions *position;
+ int i;
+ gboolean is_rtl;
+
+ is_rtl = nautilus_canvas_container_is_layout_rtl (container);
+
+ /* Lay out the icons along the baseline. */
+ x = ICON_PAD_LEFT;
+ i = 0;
+ for (p = line_start; p != line_end; p = p->next)
+ {
+ icon = p->data;
+
+ position = &g_array_index (positions, IconPositions, i++);
+ ltr_icon_x = x + position->x_offset;
+ icon_x = is_rtl ? get_mirror_x_position (container, icon, ltr_icon_x) : ltr_icon_x;
+ y_offset = position->y_offset;
+
+ icon_set_position (icon, icon_x, y + y_offset);
+ nautilus_canvas_item_set_entire_text (icon->item, whole_text);
+
+ icon->saved_ltr_x = is_rtl ? ltr_icon_x : icon->x;
+
+ x += position->width;
+ }
+}
+
+static void
+lay_down_icons_horizontal (NautilusCanvasContainer *container,
+ GList *icons,
+ double start_y)
+{
+ GList *p, *line_start;
+ NautilusCanvasIcon *icon;
+ double canvas_width, y;
+ double available_width;
+ GArray *positions;
+ IconPositions *position;
+ EelDRect bounds;
+ EelDRect icon_bounds;
+ double max_height_above, max_height_below;
+ double height_above, height_below;
+ double line_width;
+ double min_grid_width;
+ double grid_width;
+ double num_columns;
+ double icon_width, icon_size;
+ int i;
+ GtkAllocation allocation;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ /* We can't get the right allocation if the size hasn't been allocated yet */
+ g_return_if_fail (container->details->has_been_allocated);
+
+ if (icons == NULL)
+ {
+ return;
+ }
+
+ positions = g_array_new (FALSE, FALSE, sizeof (IconPositions));
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ /* Lay out icons a line at a time. */
+ canvas_width = CANVAS_WIDTH (container, allocation);
+ min_grid_width = nautilus_canvas_container_get_grid_size_for_zoom_level (container->details->zoom_level);
+ icon_size = nautilus_canvas_container_get_icon_size_for_zoom_level (container->details->zoom_level);
+
+ /* Subtracting 1.0 adds some room for error to prevent the jitter due to
+ * the code not being able to decide how many columns should be there, as
+ * "double" is not perfectly precise and increasing the size of the the
+ * window by one pixel could well make it so that the space taken by the
+ * icons and the padding is actually greater than the canvas with by like
+ * 0.01, causing an entire column to be dropped unnecessarily. This fix is
+ * adapted from Nemo.
+ */
+ available_width = MAX (1.0, canvas_width - ICON_PAD_LEFT - ICON_PAD_RIGHT - 1.0);
+ num_columns = MAX (1.0, floor (available_width / min_grid_width));
+
+ if (g_list_nth (icons, num_columns) != NULL)
+ {
+ grid_width = available_width / num_columns;
+ }
+ else
+ {
+ /* It does not look good when the icons jump around when new columns are
+ * added or removed to the grid while there is only one line. It does
+ * not look good either when the icons do not move at all when the
+ * window is resized.
+ *
+ * To do this, we first compute the maximum extra fraction we can add to
+ * the grid width. Adding this much, however, would simply distribute
+ * the icons evenly, which looks bad when there's a wide window with
+ * only a few icons.
+ *
+ * To fix this, we need to apply a function to the fraction which never
+ * makes it larger and instead makes its growth slow down quickly but
+ * smoothly as the window gets wider and wider. Here's the function used
+ * by this code:
+ *
+ * f(x) = ∜(x + 1) - 1
+ *
+ * The +1 and -1 are there to skip the 0 to 1 part of ∜ where it makes
+ * the number larger.
+ */
+
+ double num_icons = MAX (1.0, g_list_length (icons));
+
+ double used_width = num_icons * min_grid_width;
+ double unused_width = available_width - used_width;
+
+ double max_extra_fraction = (unused_width / num_icons) / min_grid_width;
+ double extra_fraction = pow (max_extra_fraction + 1.0, 1.0 / 4.0) - 1.0;
+
+ grid_width = min_grid_width * (1 + extra_fraction);
+ }
+
+ grid_width = MAX (min_grid_width, grid_width);
+
+ line_width = 0;
+ line_start = icons;
+ y = start_y + CONTAINER_PAD_TOP;
+ i = 0;
+
+ max_height_above = 0;
+ max_height_below = 0;
+ for (p = icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ /* Assume it's only one level hierarchy to avoid costly affine calculations */
+ nautilus_canvas_item_get_bounds_for_layout (icon->item,
+ &bounds.x0, &bounds.y0,
+ &bounds.x1, &bounds.y1);
+
+ /* Normalize the icon width to the grid unit.
+ * Use the icon size for this zoom level too in the calculation, since
+ * the actual bounds might be smaller - e.g. because we have a very
+ * narrow thumbnail.
+ */
+ icon_width = ceil (MAX ((bounds.x1 - bounds.x0), icon_size) / grid_width) * grid_width;
+
+ /* Calculate size above/below baseline */
+ icon_bounds = nautilus_canvas_item_get_icon_rectangle (icon->item);
+ height_above = icon_bounds.y1 - bounds.y0;
+ height_below = bounds.y1 - icon_bounds.y1;
+
+ /* If this icon doesn't fit, it's time to lay out the line that's queued up. */
+ if (line_start != p && line_width + icon_width >= canvas_width)
+ {
+ /* Advance to the baseline. */
+ y += ICON_PAD_TOP + max_height_above;
+
+ lay_down_one_line (container, line_start, p, y, max_height_above, positions, FALSE);
+
+ /* Advance to next line. */
+ y += max_height_below + ICON_PAD_BOTTOM;
+
+ line_width = 0;
+ line_start = p;
+ i = 0;
+
+ max_height_above = height_above;
+ max_height_below = height_below;
+ }
+ else
+ {
+ if (height_above > max_height_above)
+ {
+ max_height_above = height_above;
+ }
+ if (height_below > max_height_below)
+ {
+ max_height_below = height_below;
+ }
+ }
+
+ g_array_set_size (positions, i + 1);
+ position = &g_array_index (positions, IconPositions, i++);
+ position->width = icon_width;
+ position->height = icon_bounds.y1 - icon_bounds.y0;
+
+ position->x_offset = (icon_width - (icon_bounds.x1 - icon_bounds.x0)) / 2;
+ position->y_offset = icon_bounds.y0 - icon_bounds.y1;
+
+ /* Add this icon. */
+ line_width += icon_width;
+ }
+
+ /* Lay down that last line of icons. */
+ if (line_start != NULL)
+ {
+ /* Advance to the baseline. */
+ y += ICON_PAD_TOP + max_height_above;
+
+ lay_down_one_line (container, line_start, NULL, y, max_height_above, positions, FALSE);
+ }
+
+ g_array_free (positions, TRUE);
+}
+
+static double
+get_mirror_x_position (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ double x)
+{
+ EelDRect icon_bounds;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ icon_bounds = nautilus_canvas_item_get_icon_rectangle (icon->item);
+
+ return CANVAS_WIDTH (container, allocation) - x - (icon_bounds.x1 - icon_bounds.x0);
+}
+
+static void
+nautilus_canvas_container_set_rtl_positions (NautilusCanvasContainer *container)
+{
+ GList *l;
+ NautilusCanvasIcon *icon;
+ double x;
+
+ if (!container->details->icons)
+ {
+ return;
+ }
+
+ for (l = container->details->icons; l != NULL; l = l->next)
+ {
+ icon = l->data;
+ x = get_mirror_x_position (container, icon, icon->saved_ltr_x);
+ icon_set_position (icon, x, icon->y);
+ }
+}
+
+static void
+lay_down_icons (NautilusCanvasContainer *container,
+ GList *icons,
+ double start_y)
+{
+ lay_down_icons_horizontal (container, icons, start_y);
+}
+
+static void
+redo_layout_internal (NautilusCanvasContainer *container)
+{
+ gboolean layout_possible;
+
+ layout_possible = finish_adding_new_icons (container);
+ if (!layout_possible)
+ {
+ schedule_redo_layout (container);
+ return;
+ }
+
+ if (container->details->needs_resort)
+ {
+ resort (container);
+ container->details->needs_resort = FALSE;
+ }
+ lay_down_icons (container, container->details->icons, 0);
+
+ if (nautilus_canvas_container_is_layout_rtl (container))
+ {
+ nautilus_canvas_container_set_rtl_positions (container);
+ }
+
+ nautilus_canvas_container_update_scroll_region (container);
+
+ process_pending_icon_to_reveal (container);
+ nautilus_canvas_container_update_visible_icons (container);
+}
+
+static gboolean
+redo_layout_callback (gpointer callback_data)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (callback_data);
+ redo_layout_internal (container);
+ container->details->idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+unschedule_redo_layout (NautilusCanvasContainer *container)
+{
+ if (container->details->idle_id != 0)
+ {
+ g_source_remove (container->details->idle_id);
+ container->details->idle_id = 0;
+ }
+}
+
+static void
+schedule_redo_layout (NautilusCanvasContainer *container)
+{
+ if (container->details->idle_id == 0
+ && container->details->has_been_allocated)
+ {
+ container->details->idle_id = g_idle_add
+ (redo_layout_callback, container);
+ }
+}
+
+static void
+redo_layout (NautilusCanvasContainer *container)
+{
+ unschedule_redo_layout (container);
+ /* We can't lay out if the size hasn't been allocated yet; wait for it to
+ * be and then we will be called again from size_allocate ()
+ */
+ if (container->details->has_been_allocated)
+ {
+ redo_layout_internal (container);
+ }
+}
+
+/* Container-level icon handling functions. */
+
+static gboolean
+button_event_modifies_selection (GdkEventButton *event)
+{
+ return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
+}
+
+/* invalidate the cached label sizes for all the icons */
+static void
+invalidate_label_sizes (NautilusCanvasContainer *container)
+{
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ nautilus_canvas_item_invalidate_label_size (icon->item);
+ }
+}
+
+static gboolean
+select_range (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon1,
+ NautilusCanvasIcon *icon2,
+ gboolean unselect_outside_range)
+{
+ gboolean selection_changed;
+ GList *p;
+ NautilusCanvasIcon *icon;
+ NautilusCanvasIcon *unmatched_icon;
+ gboolean select;
+
+ selection_changed = FALSE;
+
+ unmatched_icon = NULL;
+ select = FALSE;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ if (unmatched_icon == NULL)
+ {
+ if (icon == icon1)
+ {
+ unmatched_icon = icon2;
+ select = TRUE;
+ }
+ else if (icon == icon2)
+ {
+ unmatched_icon = icon1;
+ select = TRUE;
+ }
+ }
+
+ if (select || unselect_outside_range)
+ {
+ selection_changed |= icon_set_selected
+ (container, icon, select);
+ }
+
+ if (unmatched_icon != NULL && icon == unmatched_icon)
+ {
+ select = FALSE;
+ }
+ }
+ return selection_changed;
+}
+
+
+static gboolean
+select_one_unselect_others (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_to_select)
+{
+ gboolean selection_changed;
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ selection_changed = FALSE;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ selection_changed |= icon_set_selected
+ (container, icon, icon == icon_to_select);
+ }
+
+ if (selection_changed && icon_to_select != NULL)
+ {
+ reveal_icon (container, icon_to_select);
+ }
+ return selection_changed;
+}
+
+static gboolean
+unselect_all (NautilusCanvasContainer *container)
+{
+ return select_one_unselect_others (container, NULL);
+}
+
+/* Implementation of rubberband selection. */
+static void
+rubberband_select (NautilusCanvasContainer *container,
+ const EelDRect *current_rect)
+{
+ GList *p;
+ gboolean selection_changed, is_in, canvas_rect_calculated;
+ NautilusCanvasIcon *icon;
+ EelIRect canvas_rect;
+ EelCanvas *canvas;
+
+ selection_changed = FALSE;
+ canvas_rect_calculated = FALSE;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ if (!canvas_rect_calculated)
+ {
+ /* Only do this calculation once, since all the canvas items
+ * we are interating are in the same coordinate space
+ */
+ canvas = EEL_CANVAS_ITEM (icon->item)->canvas;
+ eel_canvas_w2c (canvas,
+ current_rect->x0,
+ current_rect->y0,
+ &canvas_rect.x0,
+ &canvas_rect.y0);
+ eel_canvas_w2c (canvas,
+ current_rect->x1,
+ current_rect->y1,
+ &canvas_rect.x1,
+ &canvas_rect.y1);
+ canvas_rect_calculated = TRUE;
+ }
+
+ is_in = nautilus_canvas_item_hit_test_rectangle (icon->item, canvas_rect);
+
+ selection_changed |= icon_set_selected
+ (container, icon,
+ is_in ^ icon->was_selected_before_rubberband);
+ }
+
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+static int
+rubberband_timeout_callback (gpointer data)
+{
+ NautilusCanvasContainer *container;
+ GtkWidget *widget;
+ NautilusCanvasRubberbandInfo *band_info;
+ int x, y;
+ double x1, y1, x2, y2;
+ double world_x, world_y;
+ int x_scroll, y_scroll;
+ int adj_x, adj_y;
+ gboolean adj_changed;
+ GtkAllocation allocation;
+
+ EelDRect selection_rect;
+
+ widget = GTK_WIDGET (data);
+ container = NAUTILUS_CANVAS_CONTAINER (data);
+ band_info = &container->details->rubberband_info;
+
+ g_assert (band_info->timer_id != 0);
+
+ adj_changed = FALSE;
+ gtk_widget_get_allocation (widget, &allocation);
+
+ adj_x = gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
+ if (adj_x != band_info->last_adj_x)
+ {
+ band_info->last_adj_x = adj_x;
+ adj_changed = TRUE;
+ }
+
+ adj_y = gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
+ if (adj_y != band_info->last_adj_y)
+ {
+ band_info->last_adj_y = adj_y;
+ adj_changed = TRUE;
+ }
+
+ gdk_window_get_device_position (gtk_widget_get_window (widget),
+ band_info->device,
+ &x, &y, NULL);
+
+ if (x < RUBBERBAND_SCROLL_THRESHOLD)
+ {
+ x_scroll = x - RUBBERBAND_SCROLL_THRESHOLD;
+ x = 0;
+ }
+ else if (x >= allocation.width - RUBBERBAND_SCROLL_THRESHOLD)
+ {
+ x_scroll = x - allocation.width + RUBBERBAND_SCROLL_THRESHOLD + 1;
+ x = allocation.width - 1;
+ }
+ else
+ {
+ x_scroll = 0;
+ }
+
+ if (y < RUBBERBAND_SCROLL_THRESHOLD)
+ {
+ y_scroll = y - RUBBERBAND_SCROLL_THRESHOLD;
+ y = 0;
+ }
+ else if (y >= allocation.height - RUBBERBAND_SCROLL_THRESHOLD)
+ {
+ y_scroll = y - allocation.height + RUBBERBAND_SCROLL_THRESHOLD + 1;
+ y = allocation.height - 1;
+ }
+ else
+ {
+ y_scroll = 0;
+ }
+
+ if (y_scroll == 0 && x_scroll == 0
+ && (int) band_info->prev_x == x && (int) band_info->prev_y == y && !adj_changed)
+ {
+ return TRUE;
+ }
+
+ nautilus_canvas_container_scroll (container, x_scroll, y_scroll);
+
+ /* Remember to convert from widget to scrolled window coords */
+ eel_canvas_window_to_world (EEL_CANVAS (container),
+ x + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container))),
+ y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))),
+ &world_x, &world_y);
+
+ if (world_x < band_info->start_x)
+ {
+ x1 = world_x;
+ x2 = band_info->start_x;
+ }
+ else
+ {
+ x1 = band_info->start_x;
+ x2 = world_x;
+ }
+
+ if (world_y < band_info->start_y)
+ {
+ y1 = world_y;
+ y2 = band_info->start_y;
+ }
+ else
+ {
+ y1 = band_info->start_y;
+ y2 = world_y;
+ }
+
+ /* Don't let the area of the selection rectangle be empty.
+ * Aside from the fact that it would be funny when the rectangle disappears,
+ * this also works around a crash in libart that happens sometimes when a
+ * zero height rectangle is passed.
+ */
+ x2 = MAX (x1 + 1, x2);
+ y2 = MAX (y1 + 1, y2);
+
+ eel_canvas_item_set
+ (band_info->selection_rectangle,
+ "x1", x1, "y1", y1,
+ "x2", x2, "y2", y2,
+ NULL);
+
+ selection_rect.x0 = x1;
+ selection_rect.y0 = y1;
+ selection_rect.x1 = x2;
+ selection_rect.y1 = y2;
+
+ rubberband_select (container,
+ &selection_rect);
+
+ band_info->prev_x = x;
+ band_info->prev_y = y;
+
+ return TRUE;
+}
+
+static void
+stop_rubberbanding (NautilusCanvasContainer *container,
+ GdkEventButton *event);
+
+static void
+start_rubberbanding (NautilusCanvasContainer *container,
+ GdkEventButton *event)
+{
+ AtkObject *accessible;
+ NautilusCanvasContainerDetails *details;
+ NautilusCanvasRubberbandInfo *band_info;
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ details = container->details;
+ band_info = &details->rubberband_info;
+
+ if (band_info->active)
+ {
+ g_debug ("Canceling active rubberband by device %s", gdk_device_get_name (band_info->device));
+ stop_rubberbanding (container, NULL);
+ }
+
+ g_signal_emit (container,
+ signals[BAND_SELECT_STARTED], 0);
+
+ band_info->device = event->device;
+
+ for (p = details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ icon->was_selected_before_rubberband = icon->is_selected;
+ }
+
+ eel_canvas_window_to_world
+ (EEL_CANVAS (container), event->x, event->y,
+ &band_info->start_x, &band_info->start_y);
+
+ band_info->selection_rectangle = eel_canvas_item_new
+ (eel_canvas_root
+ (EEL_CANVAS (container)),
+ NAUTILUS_TYPE_SELECTION_CANVAS_ITEM,
+ "x1", band_info->start_x,
+ "y1", band_info->start_y,
+ "x2", band_info->start_x,
+ "y2", band_info->start_y,
+ NULL);
+
+ accessible = atk_gobject_accessible_for_object
+ (G_OBJECT (band_info->selection_rectangle));
+ atk_object_set_name (accessible, "selection");
+ atk_object_set_description (accessible, _("The selection rectangle"));
+
+ band_info->prev_x = event->x - gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
+ band_info->prev_y = event->y - gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
+
+ band_info->active = TRUE;
+
+ if (band_info->timer_id == 0)
+ {
+ band_info->timer_id = g_timeout_add
+ (RUBBERBAND_TIMEOUT_INTERVAL,
+ rubberband_timeout_callback,
+ container);
+ }
+
+ eel_canvas_item_grab (band_info->selection_rectangle,
+ (GDK_POINTER_MOTION_MASK
+ | GDK_BUTTON_RELEASE_MASK
+ | GDK_SCROLL_MASK),
+ NULL,
+ (GdkEvent *) event);
+}
+
+static void
+stop_rubberbanding (NautilusCanvasContainer *container,
+ GdkEventButton *event)
+{
+ NautilusCanvasRubberbandInfo *band_info;
+ GList *icons;
+ gboolean enable_animation;
+
+ band_info = &container->details->rubberband_info;
+
+ if (event != NULL && event->device != band_info->device)
+ {
+ return;
+ }
+
+ g_assert (band_info->timer_id != 0);
+ g_source_remove (band_info->timer_id);
+ band_info->timer_id = 0;
+
+ band_info->active = FALSE;
+
+ band_info->device = NULL;
+
+ g_object_get (gtk_settings_get_default (), "gtk-enable-animations", &enable_animation, NULL);
+
+ /* Destroy this canvas item; the parent will unref it. */
+ eel_canvas_item_ungrab (band_info->selection_rectangle);
+ eel_canvas_item_lower_to_bottom (band_info->selection_rectangle);
+ eel_canvas_item_destroy (band_info->selection_rectangle);
+ band_info->selection_rectangle = NULL;
+
+ /* if only one item has been selected, use it as range
+ * selection base (cf. handle_icon_button_press) */
+ icons = nautilus_canvas_container_get_selected_icons (container);
+ if (g_list_length (icons) == 1)
+ {
+ container->details->range_selection_base_icon = icons->data;
+ }
+ g_list_free (icons);
+
+ g_signal_emit (container,
+ signals[BAND_SELECT_ENDED], 0);
+}
+
+/* Keyboard navigation. */
+
+typedef gboolean (*IsBetterCanvasFunction) (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data);
+
+static NautilusCanvasIcon *
+find_best_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ IsBetterCanvasFunction function,
+ void *data)
+{
+ GList *p;
+ NautilusCanvasIcon *best, *candidate;
+
+ best = NULL;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ candidate = p->data;
+
+ if (candidate != start_icon)
+ {
+ if ((*function)(container, start_icon, best, candidate, data))
+ {
+ best = candidate;
+ }
+ }
+ }
+ return best;
+}
+
+static NautilusCanvasIcon *
+find_best_selected_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ IsBetterCanvasFunction function,
+ void *data)
+{
+ GList *p;
+ NautilusCanvasIcon *best, *candidate;
+
+ best = NULL;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ candidate = p->data;
+
+ if (candidate != start_icon && candidate->is_selected)
+ {
+ if ((*function)(container, start_icon, best, candidate, data))
+ {
+ best = candidate;
+ }
+ }
+ }
+ return best;
+}
+
+static int
+compare_icons_by_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b)
+{
+ char *uri_a, *uri_b;
+ int result;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_assert (icon_a != NULL);
+ g_assert (icon_b != NULL);
+ g_assert (icon_a != icon_b);
+
+ uri_a = nautilus_canvas_container_get_icon_uri (container, icon_a);
+ uri_b = nautilus_canvas_container_get_icon_uri (container, icon_b);
+ result = strcmp (uri_a, uri_b);
+ g_assert (result != 0);
+ g_free (uri_a);
+ g_free (uri_b);
+
+ return result;
+}
+
+static int
+get_cmp_point_x (NautilusCanvasContainer *container,
+ EelDRect icon_rect)
+{
+ return (icon_rect.x0 + icon_rect.x1) / 2;
+}
+
+static int
+get_cmp_point_y (NautilusCanvasContainer *container,
+ EelDRect icon_rect)
+{
+ return icon_rect.y1;
+}
+
+
+static int
+compare_icons_horizontal (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ax, bx;
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_a->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &ax,
+ NULL);
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_b->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &bx,
+ NULL);
+
+ if (ax < bx)
+ {
+ return -1;
+ }
+ if (ax > bx)
+ {
+ return +1;
+ }
+ return 0;
+}
+
+static int
+compare_icons_vertical (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ay, by;
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_a->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ NULL,
+ &ay);
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_b->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ NULL,
+ &by);
+
+ if (ay < by)
+ {
+ return -1;
+ }
+ if (ay > by)
+ {
+ return +1;
+ }
+ return 0;
+}
+
+static int
+compare_icons_horizontal_first (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ax, ay, bx, by;
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_a->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &ax,
+ &ay);
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_b->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &bx,
+ &by);
+
+ if (ax < bx)
+ {
+ return -1;
+ }
+ if (ax > bx)
+ {
+ return +1;
+ }
+ if (ay < by)
+ {
+ return -1;
+ }
+ if (ay > by)
+ {
+ return +1;
+ }
+ return compare_icons_by_uri (container, icon_a, icon_b);
+}
+
+static int
+compare_icons_vertical_first (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon_a,
+ NautilusCanvasIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ax, ay, bx, by;
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_a->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &ax,
+ &ay);
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon_b->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &bx,
+ &by);
+
+ if (ay < by)
+ {
+ return -1;
+ }
+ if (ay > by)
+ {
+ return +1;
+ }
+ if (ax < bx)
+ {
+ return -1;
+ }
+ if (ax > bx)
+ {
+ return +1;
+ }
+ return compare_icons_by_uri (container, icon_a, icon_b);
+}
+
+static gboolean
+leftmost_in_top_row (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ if (best_so_far == NULL)
+ {
+ return TRUE;
+ }
+ return compare_icons_vertical_first (container, best_so_far, candidate) > 0;
+}
+
+static gboolean
+rightmost_in_top_row (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ if (best_so_far == NULL)
+ {
+ return TRUE;
+ }
+ return compare_icons_vertical (container, best_so_far, candidate) > 0;
+ return compare_icons_horizontal (container, best_so_far, candidate) < 0;
+}
+
+static gboolean
+rightmost_in_bottom_row (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ if (best_so_far == NULL)
+ {
+ return TRUE;
+ }
+ return compare_icons_vertical_first (container, best_so_far, candidate) < 0;
+}
+
+static int
+compare_with_start_row (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ EelCanvasItem *item;
+
+ item = EEL_CANVAS_ITEM (icon->item);
+
+ if (container->details->arrow_key_start_y < item->y1)
+ {
+ return -1;
+ }
+ if (container->details->arrow_key_start_y > item->y2)
+ {
+ return +1;
+ }
+ return 0;
+}
+
+static int
+compare_with_start_column (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ EelCanvasItem *item;
+
+ item = EEL_CANVAS_ITEM (icon->item);
+
+ if (container->details->arrow_key_start_x < item->x1)
+ {
+ return -1;
+ }
+ if (container->details->arrow_key_start_x > item->x2)
+ {
+ return +1;
+ }
+ return 0;
+}
+
+static gboolean
+same_row_right_side_leftmost (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* Candidates not on the start row do not qualify. */
+ if (compare_with_start_row (container, candidate) != 0)
+ {
+ return FALSE;
+ }
+
+ /* Candidates that are farther right lose out. */
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ return FALSE;
+ }
+ }
+
+ /* Candidate to the left of the start do not qualify. */
+ if (compare_icons_horizontal_first (container,
+ candidate,
+ start_icon) <= 0)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+same_row_left_side_rightmost (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* Candidates not on the start row do not qualify. */
+ if (compare_with_start_row (container, candidate) != 0)
+ {
+ return FALSE;
+ }
+
+ /* Candidates that are farther left lose out. */
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ return FALSE;
+ }
+ }
+
+ /* Candidate to the right of the start do not qualify. */
+ if (compare_icons_horizontal_first (container,
+ candidate,
+ start_icon) >= 0)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+next_row_leftmost (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not below the current row */
+ if (compare_with_start_row (container, candidate) >= 0)
+ {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ /* candidate is above best choice, but below the current row */
+ return TRUE;
+ }
+
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+static gboolean
+next_row_rightmost (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not below the current row */
+ if (compare_with_start_row (container, candidate) >= 0)
+ {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ /* candidate is above best choice, but below the current row */
+ return TRUE;
+ }
+
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+static gboolean
+previous_row_rightmost (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not above the current row */
+ if (compare_with_start_row (container, candidate) <= 0)
+ {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ /* candidate is below the best choice, but above the current row */
+ return TRUE;
+ }
+
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+static gboolean
+same_column_above_lowest (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* Candidates not on the start column do not qualify. */
+ if (compare_with_start_column (container, candidate) != 0)
+ {
+ return FALSE;
+ }
+
+ /* Candidates that are higher lose out. */
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ return FALSE;
+ }
+ }
+
+ /* Candidates below the start do not qualify. */
+ if (compare_icons_vertical_first (container,
+ candidate,
+ start_icon) >= 0)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+same_column_below_highest (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ /* Candidates not on the start column do not qualify. */
+ if (compare_with_start_column (container, candidate) != 0)
+ {
+ return FALSE;
+ }
+
+ /* Candidates that are lower lose out. */
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ return FALSE;
+ }
+ }
+
+ /* Candidates above the start do not qualify. */
+ if (compare_icons_vertical_first (container,
+ candidate,
+ start_icon) <= 0)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+closest_in_90_degrees (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *start_icon,
+ NautilusCanvasIcon *best_so_far,
+ NautilusCanvasIcon *candidate,
+ void *data)
+{
+ EelDRect world_rect;
+ int x, y;
+ int dx, dy;
+ int dist;
+ int *best_dist;
+
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (candidate->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &x,
+ &y);
+
+ dx = x - container->details->arrow_key_start_x;
+ dy = y - container->details->arrow_key_start_y;
+
+ switch (container->details->arrow_key_direction)
+ {
+ case GTK_DIR_UP:
+ {
+ if (dy > 0 ||
+ ABS (dx) > ABS (dy))
+ {
+ return FALSE;
+ }
+ }
+ break;
+
+ case GTK_DIR_DOWN:
+ {
+ if (dy < 0 ||
+ ABS (dx) > ABS (dy))
+ {
+ return FALSE;
+ }
+ }
+ break;
+
+ case GTK_DIR_LEFT:
+ {
+ if (dx > 0 ||
+ ABS (dy) > ABS (dx))
+ {
+ return FALSE;
+ }
+ }
+ break;
+
+ case GTK_DIR_RIGHT:
+ {
+ if (dx < 0 ||
+ ABS (dy) > ABS (dx))
+ {
+ return FALSE;
+ }
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+
+ dist = dx * dx + dy * dy;
+ best_dist = data;
+
+ if (best_so_far == NULL)
+ {
+ *best_dist = dist;
+ return TRUE;
+ }
+
+ if (dist < *best_dist)
+ {
+ *best_dist = dist;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static EelDRect
+get_rubberband (NautilusCanvasIcon *icon1,
+ NautilusCanvasIcon *icon2)
+{
+ EelDRect rect1;
+ EelDRect rect2;
+ EelDRect ret;
+
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon1->item),
+ &rect1.x0, &rect1.y0,
+ &rect1.x1, &rect1.y1);
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon2->item),
+ &rect2.x0, &rect2.y0,
+ &rect2.x1, &rect2.y1);
+
+ eel_drect_union (&ret, &rect1, &rect2);
+
+ return ret;
+}
+
+static void
+keyboard_move_to (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ NautilusCanvasIcon *from,
+ GdkEventKey *event)
+{
+ if (icon == NULL)
+ {
+ return;
+ }
+
+ set_focus (container, icon, TRUE);
+
+ if (event != NULL &&
+ (event->state & GDK_CONTROL_MASK) != 0 &&
+ (event->state & GDK_SHIFT_MASK) == 0)
+ {
+ clear_keyboard_rubberband_start (container);
+ }
+ else if (event != NULL &&
+ (event->state & GDK_CONTROL_MASK) != 0 &&
+ (event->state & GDK_SHIFT_MASK) != 0)
+ {
+ /* Do rubberband selection */
+ EelDRect rect;
+
+ if (from && !container->details->keyboard_rubberband_start)
+ {
+ set_keyboard_rubberband_start (container, from);
+ }
+
+ if (icon && container->details->keyboard_rubberband_start)
+ {
+ rect = get_rubberband (container->details->keyboard_rubberband_start,
+ icon);
+ rubberband_select (container, &rect);
+ }
+ }
+ else if (event != NULL &&
+ (event->state & GDK_CONTROL_MASK) == 0 &&
+ (event->state & GDK_SHIFT_MASK) != 0)
+ {
+ /* Select range */
+ NautilusCanvasIcon *start_icon;
+
+ start_icon = container->details->range_selection_base_icon;
+ if (start_icon == NULL || !start_icon->is_selected)
+ {
+ start_icon = icon;
+ container->details->range_selection_base_icon = icon;
+ }
+
+ if (select_range (container, start_icon, icon, TRUE))
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+ else
+ {
+ /* Select icon. */
+ clear_keyboard_rubberband_start (container);
+
+ container->details->range_selection_base_icon = icon;
+ if (select_one_unselect_others (container, icon))
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+ schedule_keyboard_icon_reveal (container, icon);
+}
+
+static void
+keyboard_home (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ NautilusCanvasIcon *from;
+ NautilusCanvasIcon *to;
+
+ /* Home selects the first canvas.
+ * Control-Home sets the keyboard focus to the first canvas.
+ */
+
+ from = find_best_selected_icon (container, NULL,
+ rightmost_in_bottom_row,
+ NULL);
+ to = find_best_icon (container, NULL, leftmost_in_top_row, NULL);
+
+ keyboard_move_to (container, to, from, event);
+}
+
+static void
+keyboard_end (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ NautilusCanvasIcon *to;
+ NautilusCanvasIcon *from;
+
+ /* End selects the last canvas.
+ * Control-End sets the keyboard focus to the last canvas.
+ */
+ from = find_best_selected_icon (container, NULL,
+ leftmost_in_top_row,
+ NULL);
+ to = find_best_icon (container, NULL, rightmost_in_bottom_row, NULL);
+
+ keyboard_move_to (container, to, from, event);
+}
+
+static void
+record_arrow_key_start (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ GtkDirectionType direction)
+{
+ EelDRect world_rect;
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon->item);
+ eel_canvas_w2c
+ (EEL_CANVAS (container),
+ get_cmp_point_x (container, world_rect),
+ get_cmp_point_y (container, world_rect),
+ &container->details->arrow_key_start_x,
+ &container->details->arrow_key_start_y);
+ container->details->arrow_key_direction = direction;
+}
+
+static void
+keyboard_arrow_key (NautilusCanvasContainer *container,
+ GdkEventKey *event,
+ GtkDirectionType direction,
+ IsBetterCanvasFunction better_start,
+ IsBetterCanvasFunction empty_start,
+ IsBetterCanvasFunction better_destination,
+ IsBetterCanvasFunction better_destination_fallback,
+ IsBetterCanvasFunction better_destination_fallback_fallback,
+ IsBetterCanvasFunction better_destination_manual)
+{
+ NautilusCanvasIcon *from;
+ NautilusCanvasIcon *to;
+ int data;
+
+ /* Chose the icon to start with.
+ * If we have a keyboard focus, start with it.
+ * Otherwise, use the single selected icon.
+ * If there's multiple selection, use the icon farthest toward the end.
+ */
+
+ from = container->details->focus;
+
+ if (from == NULL)
+ {
+ if (has_multiple_selection (container))
+ {
+ if (all_selected (container))
+ {
+ from = find_best_selected_icon
+ (container, NULL,
+ empty_start, NULL);
+ }
+ else
+ {
+ from = find_best_selected_icon
+ (container, NULL,
+ better_start, NULL);
+ }
+ }
+ else
+ {
+ from = get_first_selected_icon (container);
+ }
+ }
+
+ /* If there's no icon, select the icon farthest toward the end.
+ * If there is an icon, select the next icon based on the arrow direction.
+ */
+ if (from == NULL)
+ {
+ to = from = find_best_icon
+ (container, NULL,
+ empty_start, NULL);
+ }
+ else
+ {
+ record_arrow_key_start (container, from, direction);
+
+ to = find_best_icon
+ (container, from,
+ better_destination,
+ &data);
+
+ /* Wrap around to next/previous row/column */
+ if (to == NULL &&
+ better_destination_fallback != NULL)
+ {
+ to = find_best_icon
+ (container, from,
+ better_destination_fallback,
+ &data);
+ }
+
+ /* With a layout like
+ * 1 2 3
+ * 4
+ * (horizontal layout)
+ *
+ * or
+ *
+ * 1 4
+ * 2
+ * 3
+ * (vertical layout)
+ *
+ * * pressing down for any of 1,2,3 (horizontal layout)
+ * * pressing right for any of 1,2,3 (vertical layout)
+ *
+ * Should select 4.
+ */
+ if (to == NULL &&
+ better_destination_fallback_fallback != NULL)
+ {
+ to = find_best_icon
+ (container, from,
+ better_destination_fallback_fallback,
+ &data);
+ }
+
+ if (to == NULL)
+ {
+ to = from;
+ }
+ }
+
+ keyboard_move_to (container, to, from, event);
+}
+
+static gboolean
+is_rectangle_selection_event (GdkEventKey *event)
+{
+ return event != NULL &&
+ (event->state & GDK_CONTROL_MASK) != 0 &&
+ (event->state & GDK_SHIFT_MASK) != 0;
+}
+
+static void
+keyboard_right (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterCanvasFunction fallback;
+
+ fallback = NULL;
+ if (!is_rectangle_selection_event (event))
+ {
+ fallback = next_row_leftmost;
+ }
+
+ /* Right selects the next icon in the same row.
+ * Control-Right sets the keyboard focus to the next icon in the same row.
+ */
+ keyboard_arrow_key (container,
+ event,
+ GTK_DIR_RIGHT,
+ rightmost_in_bottom_row,
+ nautilus_canvas_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_row_right_side_leftmost,
+ fallback,
+ NULL,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_left (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterCanvasFunction fallback;
+
+ fallback = NULL;
+ if (!is_rectangle_selection_event (event))
+ {
+ fallback = previous_row_rightmost;
+ }
+
+ /* Left selects the next icon in the same row.
+ * Control-Left sets the keyboard focus to the next icon in the same row.
+ */
+ keyboard_arrow_key (container,
+ event,
+ GTK_DIR_LEFT,
+ rightmost_in_bottom_row,
+ nautilus_canvas_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_row_left_side_rightmost,
+ fallback,
+ NULL,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_down (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterCanvasFunction next_row_fallback;
+
+ next_row_fallback = NULL;
+ if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
+ {
+ next_row_fallback = next_row_leftmost;
+ }
+ else
+ {
+ next_row_fallback = next_row_rightmost;
+ }
+
+ /* Down selects the next icon in the same column.
+ * Control-Down sets the keyboard focus to the next icon in the same column.
+ */
+ keyboard_arrow_key (container,
+ event,
+ GTK_DIR_DOWN,
+ rightmost_in_bottom_row,
+ nautilus_canvas_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_column_below_highest,
+ NULL,
+ next_row_fallback,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_up (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ /* Up selects the next icon in the same column.
+ * Control-Up sets the keyboard focus to the next icon in the same column.
+ */
+ keyboard_arrow_key (container,
+ event,
+ GTK_DIR_UP,
+ rightmost_in_bottom_row,
+ nautilus_canvas_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_column_above_lowest,
+ NULL,
+ NULL,
+ closest_in_90_degrees);
+}
+
+void
+nautilus_canvas_container_preview_selection_event (NautilusCanvasContainer *container,
+ GtkDirectionType direction)
+{
+ if (direction == GTK_DIR_UP)
+ {
+ keyboard_up (container, NULL);
+ }
+ else if (direction == GTK_DIR_DOWN)
+ {
+ keyboard_down (container, NULL);
+ }
+ else if (direction == GTK_DIR_LEFT)
+ {
+ keyboard_left (container, NULL);
+ }
+ else if (direction == GTK_DIR_RIGHT)
+ {
+ keyboard_right (container, NULL);
+ }
+}
+
+static void
+keyboard_space (NautilusCanvasContainer *container,
+ GdkEventKey *event)
+{
+ NautilusCanvasIcon *icon;
+
+ if (!has_selection (container) &&
+ container->details->focus != NULL)
+ {
+ keyboard_move_to (container,
+ container->details->focus,
+ NULL, NULL);
+ }
+ else if ((event->state & GDK_CONTROL_MASK) != 0 &&
+ (event->state & GDK_SHIFT_MASK) == 0)
+ {
+ /* Control-space toggles the selection state of the current icon. */
+ if (container->details->focus != NULL)
+ {
+ icon_toggle_selected (container, container->details->focus);
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+ if (container->details->focus->is_selected)
+ {
+ container->details->range_selection_base_icon = container->details->focus;
+ }
+ }
+ else
+ {
+ icon = find_best_selected_icon (container,
+ NULL,
+ leftmost_in_top_row,
+ NULL);
+ if (icon == NULL)
+ {
+ icon = find_best_icon (container,
+ NULL,
+ leftmost_in_top_row,
+ NULL);
+ }
+ if (icon != NULL)
+ {
+ set_focus (container, icon, TRUE);
+ }
+ }
+ }
+ else if ((event->state & GDK_SHIFT_MASK) != 0)
+ {
+ activate_selected_items_alternate (container, NULL);
+ }
+ else
+ {
+ preview_selected_items (container);
+ }
+}
+
+static void
+destroy (GtkWidget *object)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (object);
+
+ nautilus_canvas_container_clear (container);
+
+ if (container->details->rubberband_info.timer_id != 0)
+ {
+ g_source_remove (container->details->rubberband_info.timer_id);
+ container->details->rubberband_info.timer_id = 0;
+ }
+
+ if (container->details->idle_id != 0)
+ {
+ g_source_remove (container->details->idle_id);
+ container->details->idle_id = 0;
+ }
+
+ if (container->details->align_idle_id != 0)
+ {
+ g_source_remove (container->details->align_idle_id);
+ container->details->align_idle_id = 0;
+ }
+
+ if (container->details->selection_changed_id != 0)
+ {
+ g_source_remove (container->details->selection_changed_id);
+ container->details->selection_changed_id = 0;
+ }
+
+ if (container->details->size_allocation_count_id != 0)
+ {
+ g_source_remove (container->details->size_allocation_count_id);
+ container->details->size_allocation_count_id = 0;
+ }
+
+ GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->destroy (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ NautilusCanvasContainerDetails *details;
+
+ details = NAUTILUS_CANVAS_CONTAINER (object)->details;
+
+ g_signal_handlers_disconnect_by_func (nautilus_icon_view_preferences,
+ text_ellipsis_limit_changed_container_callback,
+ object);
+
+ g_hash_table_destroy (details->icon_set);
+ details->icon_set = NULL;
+
+ g_free (details->font);
+
+ if (details->a11y_item_action_queue != NULL)
+ {
+ while (!g_queue_is_empty (details->a11y_item_action_queue))
+ {
+ g_free (g_queue_pop_head (details->a11y_item_action_queue));
+ }
+ g_queue_free (details->a11y_item_action_queue);
+ }
+ if (details->a11y_item_action_idle_handler != 0)
+ {
+ g_source_remove (details->a11y_item_action_idle_handler);
+ }
+
+ g_free (details);
+
+ G_OBJECT_CLASS (nautilus_canvas_container_parent_class)->finalize (object);
+}
+
+/* GtkWidget methods. */
+
+static gboolean
+clear_size_allocation_count (gpointer data)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (data);
+
+ container->details->size_allocation_count_id = 0;
+ container->details->size_allocation_count = 0;
+
+ return FALSE;
+}
+
+static void
+size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ NautilusCanvasContainer *container;
+ gboolean need_layout_redone;
+ GtkAllocation wid_allocation;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ need_layout_redone = !container->details->has_been_allocated;
+ gtk_widget_get_allocation (widget, &wid_allocation);
+
+ if (allocation->width != wid_allocation.width)
+ {
+ need_layout_redone = TRUE;
+ }
+
+ if (allocation->height != wid_allocation.height)
+ {
+ need_layout_redone = TRUE;
+ }
+
+ /* Under some conditions we can end up in a loop when size allocating.
+ * This happens when the icons don't fit without a scrollbar, but fits
+ * when a scrollbar is added (bug #129963 for details).
+ * We keep track of this looping by increasing a counter in size_allocate
+ * and clearing it in a high-prio idle (the only way to detect the loop is
+ * done).
+ * When we've done at more than two iterations (with/without scrollbar)
+ * we terminate this looping by not redoing the layout when the width
+ * is wider than the current one (i.e when removing the scrollbar).
+ */
+ if (container->details->size_allocation_count_id == 0)
+ {
+ container->details->size_allocation_count_id =
+ g_idle_add_full (G_PRIORITY_HIGH,
+ clear_size_allocation_count,
+ container, NULL);
+ }
+ container->details->size_allocation_count++;
+ if (container->details->size_allocation_count > 2 &&
+ allocation->width >= wid_allocation.width)
+ {
+ need_layout_redone = FALSE;
+ }
+
+ GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->size_allocate (widget, allocation);
+
+ container->details->has_been_allocated = TRUE;
+
+ if (need_layout_redone)
+ {
+ redo_layout (container);
+ }
+}
+
+static GtkSizeRequestMode
+get_request_mode (GtkWidget *widget)
+{
+ /* Don't trade size at all, since we get whatever we get anyway. */
+ return GTK_SIZE_REQUEST_CONSTANT_SIZE;
+}
+
+/* We need to implement these since the GtkScrolledWindow uses them
+ * to guess whether to show scrollbars or not, and if we don't report
+ * anything it'll tend to get it wrong causing double calls
+ * to size_allocate (at different sizes) during its size allocation. */
+static void
+get_prefered_width (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ EelCanvasGroup *root;
+ double x1, x2;
+ int cx1, cx2;
+ int width;
+
+ root = eel_canvas_root (EEL_CANVAS (widget));
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (root),
+ &x1, NULL, &x2, NULL);
+ eel_canvas_w2c (EEL_CANVAS (widget), x1, 0, &cx1, NULL);
+ eel_canvas_w2c (EEL_CANVAS (widget), x2, 0, &cx2, NULL);
+
+ width = cx2 - cx1;
+ if (natural_size)
+ {
+ *natural_size = width;
+ }
+ if (minimum_size)
+ {
+ *minimum_size = width;
+ }
+}
+
+static void
+get_prefered_height (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ EelCanvasGroup *root;
+ double y1, y2;
+ int cy1, cy2;
+ int height;
+
+ root = eel_canvas_root (EEL_CANVAS (widget));
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (root),
+ NULL, &y1, NULL, &y2);
+ eel_canvas_w2c (EEL_CANVAS (widget), 0, y1, NULL, &cy1);
+ eel_canvas_w2c (EEL_CANVAS (widget), 0, y2, NULL, &cy2);
+
+ height = cy2 - cy1;
+ if (natural_size)
+ {
+ *natural_size = height;
+ }
+ if (minimum_size)
+ {
+ *minimum_size = height;
+ }
+}
+
+static void
+realize (GtkWidget *widget)
+{
+ GtkAdjustment *vadj, *hadj;
+ NautilusCanvasContainer *container;
+
+ GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->realize (widget);
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ /* Set up DnD. */
+ nautilus_canvas_dnd_init (container);
+
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
+ g_signal_connect (hadj, "value-changed",
+ G_CALLBACK (handle_hadjustment_changed), widget);
+
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
+ g_signal_connect (vadj, "value-changed",
+ G_CALLBACK (handle_vadjustment_changed), widget);
+}
+
+static void
+unrealize (GtkWidget *widget)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ nautilus_canvas_dnd_fini (container);
+
+ GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->unrealize (widget);
+}
+
+static void
+nautilus_canvas_container_request_update_all_internal (NautilusCanvasContainer *container,
+ gboolean invalidate_labels)
+{
+ GList *node;
+ NautilusCanvasIcon *icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ for (node = container->details->icons; node != NULL; node = node->next)
+ {
+ icon = node->data;
+
+ if (invalidate_labels)
+ {
+ nautilus_canvas_item_invalidate_label (icon->item);
+ }
+
+ nautilus_canvas_container_update_icon (container, icon);
+ }
+
+ container->details->needs_resort = TRUE;
+ redo_layout (container);
+}
+
+static void
+style_updated (GtkWidget *widget)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->style_updated (widget);
+
+ if (gtk_widget_get_realized (widget))
+ {
+ nautilus_canvas_container_request_update_all_internal (container, TRUE);
+ }
+}
+
+static gboolean
+button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ NautilusCanvasContainer *container;
+ gboolean selection_changed;
+ gboolean return_value;
+ gboolean clicked_on_icon;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ container->details->button_down_time = event->time;
+
+ /* Forget about the old keyboard selection now that we've started mousing. */
+ clear_keyboard_rubberband_start (container);
+
+ if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)
+ {
+ /* We use our own double-click detection. */
+ return TRUE;
+ }
+
+ /* Invoke the canvas event handler and see if an item picks up the event. */
+ clicked_on_icon = GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->button_press_event (widget, event);
+
+ if (!gtk_widget_has_focus (widget))
+ {
+ gtk_widget_grab_focus (widget);
+ }
+
+ if (clicked_on_icon)
+ {
+ return TRUE;
+ }
+
+ clear_focus (container);
+
+ if (event->button == DRAG_BUTTON &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ /* Clear the last click icon for double click */
+ container->details->double_click_icon[1] = container->details->double_click_icon[0];
+ container->details->double_click_icon[0] = NULL;
+ }
+
+ /* Button 1 does rubber banding. */
+ if (event->button == RUBBERBAND_BUTTON)
+ {
+ if (!button_event_modifies_selection (event))
+ {
+ selection_changed = unselect_all (container);
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+
+ start_rubberbanding (container, event);
+ return TRUE;
+ }
+
+ /* Prevent multi-button weirdness such as bug 6181 */
+ if (container->details->rubberband_info.active)
+ {
+ return TRUE;
+ }
+
+ /* Button 2 may be passed to the window manager. */
+ if (event->button == MIDDLE_BUTTON)
+ {
+ selection_changed = unselect_all (container);
+ if (selection_changed)
+ {
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+ }
+ g_signal_emit (widget, signals[MIDDLE_CLICK], 0, event);
+ return TRUE;
+ }
+
+ /* Button 3 does a contextual menu. */
+ if (event->button == CONTEXTUAL_MENU_BUTTON)
+ {
+ selection_changed = unselect_all (container);
+ if (selection_changed)
+ {
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+ }
+ g_signal_emit (widget, signals[CONTEXT_CLICK_BACKGROUND], 0, event);
+ return TRUE;
+ }
+
+ /* Otherwise, we emit a button_press message. */
+ g_signal_emit (widget,
+ signals[BUTTON_PRESS], 0, event,
+ &return_value);
+ return return_value;
+}
+
+static void
+nautilus_canvas_container_did_not_drag (NautilusCanvasContainer *container,
+ GdkEventButton *event)
+{
+ NautilusCanvasContainerDetails *details;
+ gboolean selection_changed;
+ static gint64 last_click_time = 0;
+ static gint click_count = 0;
+ gint double_click_time;
+ gint64 current_time;
+
+ details = container->details;
+
+ if (details->icon_selected_on_button_down &&
+ ((event->state & GDK_CONTROL_MASK) != 0 ||
+ (event->state & GDK_SHIFT_MASK) == 0))
+ {
+ if (button_event_modifies_selection (event))
+ {
+ details->range_selection_base_icon = NULL;
+ icon_toggle_selected (container, details->drag_icon);
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ else
+ {
+ details->range_selection_base_icon = details->drag_icon;
+ selection_changed = select_one_unselect_others
+ (container, details->drag_icon);
+
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+ }
+
+ if (details->drag_icon != NULL &&
+ (details->single_click_mode ||
+ event->button == MIDDLE_BUTTON))
+ {
+ /* Determine click count */
+ g_object_get (G_OBJECT (gtk_widget_get_settings (GTK_WIDGET (container))),
+ "gtk-double-click-time", &double_click_time,
+ NULL);
+ current_time = g_get_monotonic_time ();
+ if (current_time - last_click_time < double_click_time * 1000)
+ {
+ click_count++;
+ }
+ else
+ {
+ click_count = 0;
+ }
+
+ /* Stash time for next compare */
+ last_click_time = current_time;
+
+ /* If single-click mode, activate the selected icons, unless modifying
+ * the selection or pressing for a very long time, or double clicking.
+ */
+
+
+ if (click_count == 0 &&
+ event->time - details->button_down_time < MAX_CLICK_TIME &&
+ !button_event_modifies_selection (event))
+ {
+ /* It's a tricky UI issue whether this should activate
+ * just the clicked item (as if it were a link), or all
+ * the selected items (as if you were issuing an "activate
+ * selection" command). For now, we're trying the activate
+ * entire selection version to see how it feels. Note that
+ * NautilusList goes the other way because its "links" seem
+ * much more link-like.
+ */
+ if (event->button == MIDDLE_BUTTON)
+ {
+ activate_selected_items_alternate (container, NULL);
+ }
+ else
+ {
+ activate_selected_items (container);
+ }
+ }
+ }
+}
+
+static gboolean
+clicked_within_double_click_interval (NautilusCanvasContainer *container)
+{
+ static gint64 last_click_time = 0;
+ static gint click_count = 0;
+ gint double_click_time;
+ gint64 current_time;
+
+ /* Determine click count */
+ g_object_get (G_OBJECT (gtk_widget_get_settings (GTK_WIDGET (container))),
+ "gtk-double-click-time", &double_click_time,
+ NULL);
+ current_time = g_get_monotonic_time ();
+ if (current_time - last_click_time < double_click_time * 1000)
+ {
+ click_count++;
+ }
+ else
+ {
+ click_count = 0;
+ }
+
+ /* Stash time for next compare */
+ last_click_time = current_time;
+
+ /* Only allow double click */
+ if (click_count == 1)
+ {
+ click_count = 0;
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static void
+clear_drag_state (NautilusCanvasContainer *container)
+{
+ container->details->drag_icon = NULL;
+ container->details->drag_state = DRAG_STATE_INITIAL;
+}
+
+static gboolean
+button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasContainerDetails *details;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ details = container->details;
+
+ if (event->button == RUBBERBAND_BUTTON && details->rubberband_info.active)
+ {
+ stop_rubberbanding (container, event);
+ return TRUE;
+ }
+
+ if (event->button == details->drag_button)
+ {
+ details->drag_button = 0;
+
+ switch (details->drag_state)
+ {
+ case DRAG_STATE_MOVE_OR_COPY:
+ {
+ if (!details->drag_started)
+ {
+ nautilus_canvas_container_did_not_drag (container, event);
+ }
+ else
+ {
+ nautilus_canvas_dnd_end_drag (container);
+ DEBUG ("Ending drag from canvas container");
+ }
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+
+ clear_drag_state (container);
+ return TRUE;
+ }
+
+ return GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->button_release_event (widget, event);
+}
+
+static int
+motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasContainerDetails *details;
+ double world_x, world_y;
+ int canvas_x, canvas_y;
+ GdkDragAction actions;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ details = container->details;
+
+ if (details->drag_button != 0)
+ {
+ switch (details->drag_state)
+ {
+ case DRAG_STATE_MOVE_OR_COPY:
+ {
+ if (details->drag_started)
+ {
+ break;
+ }
+
+ eel_canvas_window_to_world
+ (EEL_CANVAS (container), event->x, event->y, &world_x, &world_y);
+
+ if (gtk_drag_check_threshold (widget,
+ details->drag_x,
+ details->drag_y,
+ world_x,
+ world_y))
+ {
+ details->drag_started = TRUE;
+ details->drag_state = DRAG_STATE_MOVE_OR_COPY;
+
+ eel_canvas_w2c (EEL_CANVAS (container),
+ details->drag_x,
+ details->drag_y,
+ &canvas_x,
+ &canvas_y);
+
+ actions = GDK_ACTION_COPY
+ | GDK_ACTION_MOVE
+ | GDK_ACTION_LINK
+ | GDK_ACTION_ASK;
+
+ nautilus_canvas_dnd_begin_drag (container,
+ actions,
+ details->drag_button,
+ event,
+ canvas_x,
+ canvas_y);
+ DEBUG ("Beginning drag from canvas container");
+ }
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+ }
+
+ return GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->motion_notify_event (widget, event);
+}
+
+static void
+nautilus_canvas_container_get_icon_text (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ char **editable_text,
+ char **additional_text,
+ gboolean include_invisible)
+{
+ NautilusCanvasContainerClass *klass;
+
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (container);
+ g_assert (klass->get_icon_text != NULL);
+
+ klass->get_icon_text (container, data, editable_text, additional_text, include_invisible);
+}
+
+static gboolean
+handle_popups (NautilusCanvasContainer *container,
+ GdkEvent *event,
+ const char *signal)
+{
+ /* ensure we clear the drag state before showing the menu */
+ clear_drag_state (container);
+
+ g_signal_emit_by_name (container, signal, event);
+
+ return TRUE;
+}
+
+static int
+key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ NautilusCanvasContainer *container;
+ gboolean handled;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ handled = FALSE;
+
+ switch (event->keyval)
+ {
+ case GDK_KEY_Home:
+ case GDK_KEY_KP_Home:
+ {
+ keyboard_home (container, event);
+ handled = TRUE;
+ }
+ break;
+
+ case GDK_KEY_End:
+ case GDK_KEY_KP_End:
+ {
+ keyboard_end (container, event);
+ handled = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Left:
+ case GDK_KEY_KP_Left:
+ {
+ /* Don't eat Alt-Left, as that is used for history browsing */
+ if ((event->state & GDK_MOD1_MASK) == 0)
+ {
+ keyboard_left (container, event);
+ handled = TRUE;
+ }
+ }
+ break;
+
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up:
+ {
+ /* Don't eat Alt-Up, as that is used for alt-shift-Up */
+ if ((event->state & GDK_MOD1_MASK) == 0)
+ {
+ keyboard_up (container, event);
+ handled = TRUE;
+ }
+ }
+ break;
+
+ case GDK_KEY_Right:
+ case GDK_KEY_KP_Right:
+ {
+ /* Don't eat Alt-Right, as that is used for history browsing */
+ if ((event->state & GDK_MOD1_MASK) == 0)
+ {
+ keyboard_right (container, event);
+ handled = TRUE;
+ }
+ }
+ break;
+
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down:
+ {
+ /* Don't eat Alt-Down, as that is used for Open */
+ if ((event->state & GDK_MOD1_MASK) == 0)
+ {
+ keyboard_down (container, event);
+ handled = TRUE;
+ }
+ }
+ break;
+
+ case GDK_KEY_space:
+ {
+ keyboard_space (container, event);
+ handled = TRUE;
+ }
+ break;
+
+ case GDK_KEY_F10:
+ {
+ /* handle Ctrl+F10 because we want to display the
+ * background popup even if something is selected.
+ * The other cases are handled by the "popup-menu" GtkWidget signal.
+ */
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ handled = handle_popups (container, (GdkEvent *) event,
+ "context_click_background");
+ }
+ }
+ break;
+
+ case GDK_KEY_v:
+ {
+ /* Eat Control + v to not enable type ahead */
+ if ((event->state & GDK_CONTROL_MASK) != 0)
+ {
+ handled = TRUE;
+ }
+ }
+ break;
+
+ default:
+ {
+ }
+ break;
+ }
+
+ if (!handled)
+ {
+ handled = GTK_WIDGET_CLASS (nautilus_canvas_container_parent_class)->key_press_event (widget, event);
+ }
+
+ return handled;
+}
+
+static void
+grab_notify_cb (GtkWidget *widget,
+ gboolean was_grabbed)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ if (container->details->rubberband_info.active &&
+ !was_grabbed)
+ {
+ /* we got a (un)grab-notify during rubberband.
+ * This happens when a new modal dialog shows
+ * up (e.g. authentication or an error). Stop
+ * the rubberbanding so that we can handle the
+ * dialog. */
+ stop_rubberbanding (container, NULL);
+ }
+}
+
+static void
+text_ellipsis_limit_changed_container_callback (gpointer callback_data)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (callback_data);
+ invalidate_label_sizes (container);
+ schedule_redo_layout (container);
+}
+
+static GObject *
+nautilus_canvas_container_constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ NautilusCanvasContainer *container;
+ GObject *object;
+
+ object = G_OBJECT_CLASS (nautilus_canvas_container_parent_class)->constructor
+ (type,
+ n_construct_params,
+ construct_params);
+
+ container = NAUTILUS_CANVAS_CONTAINER (object);
+ g_signal_connect_swapped (nautilus_icon_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT,
+ G_CALLBACK (text_ellipsis_limit_changed_container_callback),
+ container);
+
+ return object;
+}
+
+/* Initialization. */
+
+static void
+nautilus_canvas_container_class_init (NautilusCanvasContainerClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ G_OBJECT_CLASS (class)->constructor = nautilus_canvas_container_constructor;
+ G_OBJECT_CLASS (class)->finalize = finalize;
+
+ /* Signals. */
+
+ signals[SELECTION_CHANGED]
+ = g_signal_new ("selection-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ selection_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[BUTTON_PRESS]
+ = g_signal_new ("button-press",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ button_press),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 1,
+ GDK_TYPE_EVENT);
+ signals[ACTIVATE]
+ = g_signal_new ("activate",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ activate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[ACTIVATE_ALTERNATE]
+ = g_signal_new ("activate-alternate",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ activate_alternate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[ACTIVATE_PREVIEWER]
+ = g_signal_new ("activate-previewer",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ activate_previewer),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 2,
+ G_TYPE_POINTER, G_TYPE_POINTER);
+ signals[CONTEXT_CLICK_SELECTION]
+ = g_signal_new ("context-click-selection",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ context_click_selection),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[CONTEXT_CLICK_BACKGROUND]
+ = g_signal_new ("context-click-background",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ context_click_background),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[MIDDLE_CLICK]
+ = g_signal_new ("middle-click",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ middle_click),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[GET_ICON_URI]
+ = g_signal_new ("get-icon-uri",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ get_icon_uri),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_STRING, 1,
+ G_TYPE_POINTER);
+ signals[GET_ICON_ACTIVATION_URI]
+ = g_signal_new ("get-icon-activation-uri",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ get_icon_activation_uri),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_STRING, 1,
+ G_TYPE_POINTER);
+ signals[GET_ICON_DROP_TARGET_URI]
+ = g_signal_new ("get-icon-drop-target-uri",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ get_icon_drop_target_uri),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_STRING, 1,
+ G_TYPE_POINTER);
+ signals[MOVE_COPY_ITEMS]
+ = g_signal_new ("move-copy-items",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ move_copy_items),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_NETSCAPE_URL]
+ = g_signal_new ("handle-netscape-url",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ handle_netscape_url),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_URI_LIST]
+ = g_signal_new ("handle-uri-list",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ handle_uri_list),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_TEXT]
+ = g_signal_new ("handle-text",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ handle_text),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_RAW]
+ = g_signal_new ("handle-raw",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ handle_raw),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 5,
+ G_TYPE_POINTER,
+ G_TYPE_INT,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION);
+ signals[HANDLE_HOVER] =
+ g_signal_new ("handle-hover",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ handle_hover),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+ signals[GET_CONTAINER_URI]
+ = g_signal_new ("get-container-uri",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ get_container_uri),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_STRING, 0);
+ signals[BAND_SELECT_STARTED]
+ = g_signal_new ("band-select-started",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ band_select_started),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[BAND_SELECT_ENDED]
+ = g_signal_new ("band-select-ended",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ band_select_ended),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[ICON_ADDED]
+ = g_signal_new ("icon-added",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ icon_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+ signals[ICON_REMOVED]
+ = g_signal_new ("icon-removed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ icon_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ signals[CLEARED]
+ = g_signal_new ("cleared",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NautilusCanvasContainerClass,
+ cleared),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /* GtkWidget class. */
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->destroy = destroy;
+ widget_class->size_allocate = size_allocate;
+ widget_class->get_request_mode = get_request_mode;
+ widget_class->get_preferred_width = get_prefered_width;
+ widget_class->get_preferred_height = get_prefered_height;
+ widget_class->realize = realize;
+ widget_class->unrealize = unrealize;
+ 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;
+ widget_class->style_updated = style_updated;
+ widget_class->grab_notify = grab_notify_cb;
+
+ gtk_widget_class_set_accessible_type (widget_class, nautilus_canvas_container_accessible_get_type ());
+}
+
+static void
+update_selected (NautilusCanvasContainer *container)
+{
+ GList *node;
+ NautilusCanvasIcon *icon;
+
+ for (node = container->details->icons; node != NULL; node = node->next)
+ {
+ icon = node->data;
+ if (icon->is_selected)
+ {
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (icon->item));
+ }
+ }
+}
+
+static void
+handle_has_focus_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ update_selected (NAUTILUS_CANVAS_CONTAINER (object));
+}
+
+static void
+handle_scale_factor_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ nautilus_canvas_container_request_update_all_internal (NAUTILUS_CANVAS_CONTAINER (object),
+ TRUE);
+}
+
+
+
+static int text_ellipsis_limits[NAUTILUS_CANVAS_ZOOM_LEVEL_N_ENTRIES];
+
+static gboolean
+get_text_ellipsis_limit_for_zoom (char **strs,
+ const char *zoom_level,
+ int *limit)
+{
+ char **p;
+ char *str;
+ gboolean success;
+
+ success = FALSE;
+
+ /* default */
+ *limit = 3;
+
+ if (zoom_level != NULL)
+ {
+ str = g_strdup_printf ("%s:%%d", zoom_level);
+ }
+ else
+ {
+ str = g_strdup ("%d");
+ }
+
+ if (strs != NULL)
+ {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ for (p = strs; *p != NULL; p++)
+ {
+ if (sscanf (*p, str, limit))
+ {
+ success = TRUE;
+ }
+ }
+#pragma GCC diagnostic pop
+ }
+
+ g_free (str);
+
+ return success;
+}
+
+static const char *zoom_level_names[] =
+{
+ "small",
+ "standard",
+ "large",
+};
+
+static void
+text_ellipsis_limit_changed_callback (gpointer callback_data)
+{
+ char **pref;
+ unsigned int i;
+ int one_limit;
+
+ pref = g_settings_get_strv (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT);
+
+ /* set default */
+ get_text_ellipsis_limit_for_zoom (pref, NULL, &one_limit);
+ for (i = 0; i < NAUTILUS_CANVAS_ZOOM_LEVEL_N_ENTRIES; i++)
+ {
+ text_ellipsis_limits[i] = one_limit;
+ }
+
+ /* override for each zoom level */
+ for (i = 0; i < G_N_ELEMENTS (zoom_level_names); i++)
+ {
+ if (get_text_ellipsis_limit_for_zoom (pref,
+ zoom_level_names[i],
+ &one_limit))
+ {
+ text_ellipsis_limits[i] = one_limit;
+ }
+ }
+
+ g_strfreev (pref);
+}
+
+static void
+nautilus_canvas_container_init (NautilusCanvasContainer *container)
+{
+ NautilusCanvasContainerDetails *details;
+ static gboolean setup_prefs = FALSE;
+
+ details = g_new0 (NautilusCanvasContainerDetails, 1);
+
+ details->icon_set = g_hash_table_new (g_direct_hash, g_direct_equal);
+ details->zoom_level = NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD;
+
+ container->details = details;
+
+ g_signal_connect (container, "notify::has-focus",
+ G_CALLBACK (handle_has_focus_changed), NULL);
+ g_signal_connect (container, "notify::scale-factor",
+ G_CALLBACK (handle_scale_factor_changed), NULL);
+
+ if (!setup_prefs)
+ {
+ g_signal_connect_swapped (nautilus_icon_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT,
+ G_CALLBACK (text_ellipsis_limit_changed_callback),
+ NULL);
+ text_ellipsis_limit_changed_callback (NULL);
+
+ setup_prefs = TRUE;
+ }
+}
+
+typedef struct
+{
+ NautilusCanvasContainer *container;
+ GdkEventButton *event;
+} ContextMenuParameters;
+
+static gboolean
+handle_canvas_double_click (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ GdkEventButton *event)
+{
+ NautilusCanvasContainerDetails *details;
+
+ if (event->button != DRAG_BUTTON)
+ {
+ return FALSE;
+ }
+
+ details = container->details;
+
+ if (!details->single_click_mode &&
+ clicked_within_double_click_interval (container) &&
+ details->double_click_icon[0] == details->double_click_icon[1] &&
+ details->double_click_button[0] == details->double_click_button[1])
+ {
+ details->double_clicked = TRUE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* NautilusCanvasIcon event handling. */
+
+/* Conceptually, pressing button 1 together with CTRL or SHIFT toggles
+ * selection of a single icon without affecting the other icons;
+ * without CTRL or SHIFT, 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 gboolean
+handle_canvas_button_press (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon,
+ GdkEventButton *event)
+{
+ NautilusCanvasContainerDetails *details;
+
+ details = container->details;
+
+ if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)
+ {
+ return TRUE;
+ }
+
+ if (event->button != DRAG_BUTTON
+ && event->button != CONTEXTUAL_MENU_BUTTON
+ && event->button != DRAG_MENU_BUTTON)
+ {
+ return TRUE;
+ }
+
+ if ((event->button == DRAG_BUTTON) &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ /* The next double click has to be on this icon */
+ details->double_click_icon[1] = details->double_click_icon[0];
+ details->double_click_icon[0] = icon;
+
+ details->double_click_button[1] = details->double_click_button[0];
+ details->double_click_button[0] = event->button;
+ }
+
+ if (handle_canvas_double_click (container, icon, event))
+ {
+ /* Double clicking does not trigger a D&D action. */
+ details->drag_button = 0;
+ details->drag_icon = NULL;
+ return TRUE;
+ }
+
+ if (event->button == DRAG_BUTTON
+ || event->button == DRAG_MENU_BUTTON)
+ {
+ details->drag_button = event->button;
+ details->drag_icon = icon;
+ details->drag_x = event->x;
+ details->drag_y = event->y;
+ details->drag_state = DRAG_STATE_MOVE_OR_COPY;
+ details->drag_started = FALSE;
+ }
+
+ /* Modify the selection as appropriate. Selection is modified
+ * the same way for contextual menu as it would be without.
+ */
+ details->icon_selected_on_button_down = icon->is_selected;
+
+ if ((event->button == DRAG_BUTTON || event->button == MIDDLE_BUTTON) &&
+ (event->state & GDK_SHIFT_MASK) != 0)
+ {
+ NautilusCanvasIcon *start_icon;
+
+ set_focus (container, icon, FALSE);
+
+ start_icon = details->range_selection_base_icon;
+ if (start_icon == NULL || !start_icon->is_selected)
+ {
+ start_icon = icon;
+ details->range_selection_base_icon = icon;
+ }
+ if (select_range (container, start_icon, icon,
+ (event->state & GDK_CONTROL_MASK) == 0))
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+ else if (!details->icon_selected_on_button_down)
+ {
+ set_focus (container, icon, FALSE);
+
+ details->range_selection_base_icon = icon;
+ if (button_event_modifies_selection (event))
+ {
+ icon_toggle_selected (container, icon);
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ else
+ {
+ select_one_unselect_others (container, icon);
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+
+ if (event->button == CONTEXTUAL_MENU_BUTTON)
+ {
+ clear_drag_state (container);
+
+ g_signal_emit (container,
+ signals[CONTEXT_CLICK_SELECTION], 0,
+ event);
+ }
+
+
+ return TRUE;
+}
+
+static int
+item_event_callback (EelCanvasItem *item,
+ GdkEvent *event,
+ gpointer data)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasIcon *icon;
+ GdkEventButton *event_button;
+
+ container = NAUTILUS_CANVAS_CONTAINER (data);
+
+ icon = NAUTILUS_CANVAS_ITEM (item)->user_data;
+ g_assert (icon != NULL);
+
+ event_button = &event->button;
+
+ switch (event->type)
+ {
+ case GDK_MOTION_NOTIFY:
+ {
+ return FALSE;
+ }
+
+ case GDK_BUTTON_PRESS:
+ {
+ container->details->double_clicked = FALSE;
+ if (handle_canvas_button_press (container, icon, event_button))
+ {
+ /* Stop the event from being passed along further. Returning
+ * TRUE ain't enough.
+ */
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ case GDK_BUTTON_RELEASE:
+ {
+ if (event_button->button == DRAG_BUTTON
+ && container->details->double_clicked)
+ {
+ if (!button_event_modifies_selection (event_button))
+ {
+ activate_selected_items (container);
+ }
+ else if ((event_button->state & GDK_CONTROL_MASK) == 0 &&
+ (event_button->state & GDK_SHIFT_MASK) != 0)
+ {
+ activate_selected_items_alternate (container, icon);
+ }
+ }
+ /* fall through */
+ }
+
+ default:
+ {
+ container->details->double_clicked = FALSE;
+ return FALSE;
+ }
+ break;
+ }
+}
+
+GtkWidget *
+nautilus_canvas_container_new (void)
+{
+ return gtk_widget_new (NAUTILUS_TYPE_CANVAS_CONTAINER, NULL);
+}
+
+/* Clear all of the icons in the container. */
+void
+nautilus_canvas_container_clear (NautilusCanvasContainer *container)
+{
+ NautilusCanvasContainerDetails *details;
+ GList *p;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ details = container->details;
+
+ if (details->icons == NULL)
+ {
+ return;
+ }
+
+ clear_focus (container);
+ clear_keyboard_rubberband_start (container);
+ unschedule_keyboard_icon_reveal (container);
+ set_pending_icon_to_reveal (container, NULL);
+ details->drop_target = NULL;
+
+ for (p = details->icons; p != NULL; p = p->next)
+ {
+ icon_free (p->data);
+ }
+ g_list_free (details->icons);
+ details->icons = NULL;
+ g_list_free (details->new_icons);
+ details->new_icons = NULL;
+ g_list_free (details->selection);
+ details->selection = NULL;
+
+ g_hash_table_destroy (details->icon_set);
+ details->icon_set = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ nautilus_canvas_container_update_scroll_region (container);
+}
+
+gboolean
+nautilus_canvas_container_is_empty (NautilusCanvasContainer *container)
+{
+ return container->details->icons == NULL;
+}
+
+NautilusCanvasIconData *
+nautilus_canvas_container_get_first_visible_icon (NautilusCanvasContainer *container)
+{
+ GList *l;
+ NautilusCanvasIcon *icon, *best_icon;
+ double x, y;
+ double x1, y1, x2, y2;
+ double *pos, best_pos;
+ double hadj_v, vadj_v, h_page_size;
+ gboolean better_icon;
+ gboolean compare_lt;
+
+ hadj_v = gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
+ vadj_v = gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
+ h_page_size = gtk_adjustment_get_page_size (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
+
+ if (nautilus_canvas_container_is_layout_rtl (container))
+ {
+ x = hadj_v + h_page_size - ICON_PAD_LEFT - 1;
+ y = vadj_v;
+ }
+ else
+ {
+ x = hadj_v;
+ y = vadj_v;
+ }
+
+ eel_canvas_c2w (EEL_CANVAS (container),
+ x, y,
+ &x, &y);
+
+ l = container->details->icons;
+ best_icon = NULL;
+ best_pos = 0;
+ while (l != NULL)
+ {
+ icon = l->data;
+
+ if (icon_is_positioned (icon))
+ {
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
+ &x1, &y1, &x2, &y2);
+
+ compare_lt = FALSE;
+ pos = &y1;
+ better_icon = y2 > y + ICON_PAD_TOP;
+ if (better_icon)
+ {
+ if (best_icon == NULL)
+ {
+ better_icon = TRUE;
+ }
+ else if (compare_lt)
+ {
+ better_icon = best_pos < *pos;
+ }
+ else
+ {
+ better_icon = best_pos > *pos;
+ }
+
+ if (better_icon)
+ {
+ best_icon = icon;
+ best_pos = *pos;
+ }
+ }
+ }
+
+ l = l->next;
+ }
+
+ return best_icon ? best_icon->data : NULL;
+}
+
+NautilusCanvasIconData *
+nautilus_canvas_container_get_focused_icon (NautilusCanvasContainer *container)
+{
+ NautilusCanvasIcon *icon;
+
+ icon = container->details->focus;
+
+ if (icon != NULL)
+ {
+ return icon->data;
+ }
+
+ return NULL;
+}
+
+/* puts the icon at the top of the screen */
+void
+nautilus_canvas_container_scroll_to_canvas (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ GList *l;
+ NautilusCanvasIcon *icon;
+ GtkAdjustment *vadj;
+ EelIRect bounds;
+ GtkAllocation allocation;
+
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ /* We need to force a relayout now if there are updates queued
+ * since we need the final positions */
+ nautilus_canvas_container_layout_now (container);
+
+ l = container->details->icons;
+ while (l != NULL)
+ {
+ icon = l->data;
+
+ if (icon->data == data &&
+ icon_is_positioned (icon))
+ {
+ /* ensure that we reveal the entire row/column */
+ icon_get_row_and_column_bounds (container, icon, &bounds);
+
+ gtk_adjustment_set_value (vadj, bounds.y0);
+ }
+
+ l = l->next;
+ }
+}
+
+/* Call a function for all the icons. */
+typedef struct
+{
+ NautilusCanvasCallback callback;
+ gpointer callback_data;
+} CallbackAndData;
+
+static void
+call_canvas_callback (gpointer data,
+ gpointer callback_data)
+{
+ NautilusCanvasIcon *icon;
+ CallbackAndData *callback_and_data;
+
+ icon = data;
+ callback_and_data = callback_data;
+ (*callback_and_data->callback)(icon->data, callback_and_data->callback_data);
+}
+
+void
+nautilus_canvas_container_for_each (NautilusCanvasContainer *container,
+ NautilusCanvasCallback callback,
+ gpointer callback_data)
+{
+ CallbackAndData callback_and_data;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ callback_and_data.callback = callback;
+ callback_and_data.callback_data = callback_data;
+
+ g_list_foreach (container->details->icons,
+ call_canvas_callback, &callback_and_data);
+}
+
+static int
+selection_changed_at_idle_callback (gpointer data)
+{
+ NautilusCanvasContainer *container;
+
+ container = NAUTILUS_CANVAS_CONTAINER (data);
+
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+
+ container->details->selection_changed_id = 0;
+ return FALSE;
+}
+
+/* utility routine to remove a single icon from the container */
+
+static void
+icon_destroy (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ NautilusCanvasContainerDetails *details;
+ gboolean was_selected;
+ NautilusCanvasIcon *icon_to_focus;
+ GList *item;
+
+ details = container->details;
+
+ item = g_list_find (details->icons, icon);
+ item = item->next ? item->next : item->prev;
+ icon_to_focus = (item != NULL) ? item->data : NULL;
+
+ details->icons = g_list_remove (details->icons, icon);
+ details->new_icons = g_list_remove (details->new_icons, icon);
+ details->selection = g_list_remove (details->selection, icon->data);
+ g_hash_table_remove (details->icon_set, icon->data);
+
+ was_selected = icon->is_selected;
+
+ if (details->focus == icon ||
+ details->focus == NULL)
+ {
+ if (icon_to_focus != NULL)
+ {
+ set_focus (container, icon_to_focus, TRUE);
+ }
+ else
+ {
+ clear_focus (container);
+ }
+ }
+
+ if (details->keyboard_rubberband_start == icon)
+ {
+ clear_keyboard_rubberband_start (container);
+ }
+
+ if (details->keyboard_icon_to_reveal == icon)
+ {
+ unschedule_keyboard_icon_reveal (container);
+ }
+ if (details->drag_icon == icon)
+ {
+ clear_drag_state (container);
+ }
+ if (details->drop_target == icon)
+ {
+ details->drop_target = NULL;
+ }
+ if (details->range_selection_base_icon == icon)
+ {
+ details->range_selection_base_icon = NULL;
+ }
+ if (details->pending_icon_to_reveal == icon)
+ {
+ set_pending_icon_to_reveal (container, NULL);
+ }
+
+ icon_free (icon);
+
+ if (was_selected)
+ {
+ /* Coalesce multiple removals causing multiple selection_changed events */
+ details->selection_changed_id = g_idle_add (selection_changed_at_idle_callback, container);
+ }
+}
+
+/* activate any selected items in the container */
+static void
+activate_selected_items (NautilusCanvasContainer *container)
+{
+ GList *selection;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ selection = nautilus_canvas_container_get_selection (container);
+ if (selection != NULL)
+ {
+ g_signal_emit (container,
+ signals[ACTIVATE], 0,
+ selection);
+ }
+ g_list_free (selection);
+}
+
+static void
+preview_selected_items (NautilusCanvasContainer *container)
+{
+ GList *selection;
+ GArray *locations;
+ gint idx;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ selection = nautilus_canvas_container_get_selection (container);
+ locations = nautilus_canvas_container_get_selected_icon_locations (container);
+
+ for (idx = 0; idx < locations->len; idx++)
+ {
+ GdkPoint *point = &(g_array_index (locations, GdkPoint, idx));
+ gint scroll_x, scroll_y;
+
+ eel_canvas_get_scroll_offsets (EEL_CANVAS (container),
+ &scroll_x, &scroll_y);
+
+ point->x -= scroll_x;
+ point->y -= scroll_y;
+ }
+
+ if (selection != NULL)
+ {
+ g_signal_emit (container,
+ signals[ACTIVATE_PREVIEWER], 0,
+ selection, locations);
+ }
+ g_list_free (selection);
+ g_array_unref (locations);
+}
+
+static void
+activate_selected_items_alternate (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ GList *selection;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ if (icon != NULL)
+ {
+ selection = g_list_prepend (NULL, icon->data);
+ }
+ else
+ {
+ selection = nautilus_canvas_container_get_selection (container);
+ }
+ if (selection != NULL)
+ {
+ g_signal_emit (container,
+ signals[ACTIVATE_ALTERNATE], 0,
+ selection);
+ }
+ g_list_free (selection);
+}
+
+static NautilusIconInfo *
+nautilus_canvas_container_get_icon_images (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ int size,
+ gboolean for_drag_accept)
+{
+ NautilusCanvasContainerClass *klass;
+
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (container);
+ g_assert (klass->get_icon_images != NULL);
+
+ return klass->get_icon_images (container, data, size, for_drag_accept);
+}
+
+static void
+nautilus_canvas_container_prioritize_thumbnailing (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ NautilusCanvasContainerClass *klass;
+
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (container);
+ g_assert (klass->prioritize_thumbnailing != NULL);
+
+ klass->prioritize_thumbnailing (container, icon->data);
+}
+
+static void
+nautilus_canvas_container_update_visible_icons (NautilusCanvasContainer *container)
+{
+ GtkAdjustment *vadj, *hadj;
+ double min_y, max_y;
+ double min_x, max_x;
+ double x0, y0, x1, y1;
+ GList *node;
+ NautilusCanvasIcon *icon;
+ gboolean visible;
+ GtkAllocation allocation;
+
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ min_x = gtk_adjustment_get_value (hadj);
+ max_x = min_x + allocation.width;
+
+ min_y = gtk_adjustment_get_value (vadj);
+ max_y = min_y + allocation.height;
+
+ eel_canvas_c2w (EEL_CANVAS (container),
+ min_x, min_y, &min_x, &min_y);
+ eel_canvas_c2w (EEL_CANVAS (container),
+ max_x, max_y, &max_x, &max_y);
+
+ /* Do the iteration in reverse to get the render-order from top to
+ * bottom for the prioritized thumbnails.
+ */
+ for (node = g_list_last (container->details->icons); node != NULL; node = node->prev)
+ {
+ icon = node->data;
+
+ if (icon_is_positioned (icon))
+ {
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
+ &x0,
+ &y0,
+ &x1,
+ &y1);
+ eel_canvas_item_i2w (EEL_CANVAS_ITEM (icon->item)->parent,
+ &x0,
+ &y0);
+ eel_canvas_item_i2w (EEL_CANVAS_ITEM (icon->item)->parent,
+ &x1,
+ &y1);
+
+ visible = y1 >= min_y && y0 <= max_y;
+
+ if (visible)
+ {
+ nautilus_canvas_item_set_is_visible (icon->item, TRUE);
+ nautilus_canvas_container_prioritize_thumbnailing (container,
+ icon);
+ }
+ else
+ {
+ nautilus_canvas_item_set_is_visible (icon->item, FALSE);
+ }
+ }
+ }
+}
+
+static void
+handle_vadjustment_changed (GtkAdjustment *adjustment,
+ NautilusCanvasContainer *container)
+{
+ nautilus_canvas_container_update_visible_icons (container);
+}
+
+static void
+handle_hadjustment_changed (GtkAdjustment *adjustment,
+ NautilusCanvasContainer *container)
+{
+ nautilus_canvas_container_update_visible_icons (container);
+}
+
+
+void
+nautilus_canvas_container_update_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ NautilusCanvasContainerDetails *details;
+ guint icon_size;
+ guint min_image_size, max_image_size;
+ NautilusIconInfo *icon_info;
+ GdkPixbuf *pixbuf;
+ char *editable_text, *additional_text;
+
+ if (icon == NULL)
+ {
+ return;
+ }
+
+ details = container->details;
+
+ /* compute the maximum size based on the scale factor */
+ min_image_size = MINIMUM_IMAGE_SIZE * EEL_CANVAS (container)->pixels_per_unit;
+ max_image_size = MAX (MAXIMUM_IMAGE_SIZE * EEL_CANVAS (container)->pixels_per_unit, NAUTILUS_ICON_MAXIMUM_SIZE);
+
+ /* Get the appropriate images for the file. */
+ icon_get_size (container, icon, &icon_size);
+
+ icon_size = MAX (icon_size, min_image_size);
+ icon_size = MIN (icon_size, max_image_size);
+
+ DEBUG ("Icon size, getting for size %d", icon_size);
+
+ /* Get the icons. */
+ icon_info = nautilus_canvas_container_get_icon_images (container, icon->data, icon_size,
+ icon == details->drop_target);
+
+ pixbuf = nautilus_icon_info_get_pixbuf (icon_info);
+ g_object_unref (icon_info);
+
+ nautilus_canvas_container_get_icon_text (container,
+ icon->data,
+ &editable_text,
+ &additional_text,
+ FALSE);
+
+ eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
+ "editable_text", editable_text,
+ "additional_text", additional_text,
+ "highlighted_for_drop", icon == details->drop_target,
+ NULL);
+
+ nautilus_canvas_item_set_image (icon->item, pixbuf);
+
+ /* Let the pixbufs go. */
+ g_object_unref (pixbuf);
+
+ g_free (editable_text);
+ g_free (additional_text);
+}
+
+static void
+finish_adding_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ nautilus_canvas_container_update_icon (container, icon);
+ eel_canvas_item_show (EEL_CANVAS_ITEM (icon->item));
+
+ g_signal_connect_object (icon->item, "event",
+ G_CALLBACK (item_event_callback), container, 0);
+
+ g_signal_emit (container, signals[ICON_ADDED], 0, icon->data);
+}
+
+static gboolean
+finish_adding_new_icons (NautilusCanvasContainer *container)
+{
+ GList *p, *new_icons;
+
+ new_icons = container->details->new_icons;
+ container->details->new_icons = NULL;
+ container->details->is_populating_container = g_list_length (new_icons) ==
+ g_hash_table_size (container->details->icon_set);
+
+ /* Position most icons (not unpositioned manual-layout icons). */
+ new_icons = g_list_reverse (new_icons);
+ for (p = new_icons; p != NULL; p = p->next)
+ {
+ finish_adding_icon (container, p->data);
+ }
+ g_list_free (new_icons);
+
+ return TRUE;
+}
+
+/**
+ * nautilus_canvas_container_add:
+ * @container: A NautilusCanvasContainer
+ * @data: Icon data.
+ *
+ * Add icon to represent @data to container.
+ * Returns FALSE if there was already such an icon.
+ **/
+gboolean
+nautilus_canvas_container_add (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasContainerDetails *details;
+ NautilusCanvasIcon *icon;
+ EelCanvasItem *band, *item;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), FALSE);
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ details = container->details;
+
+ if (g_hash_table_lookup (details->icon_set, data) != NULL)
+ {
+ return FALSE;
+ }
+
+ /* Create the new icon, including the canvas item. */
+ icon = g_new0 (NautilusCanvasIcon, 1);
+ icon->data = data;
+ icon->x = ICON_UNPOSITIONED_VALUE;
+ icon->y = ICON_UNPOSITIONED_VALUE;
+
+ /* Whether the saved icon position should only be used
+ * if the previous icon position is free. If the position
+ * is occupied, another position near the last one will
+ */
+ icon->item = NAUTILUS_CANVAS_ITEM
+ (eel_canvas_item_new (EEL_CANVAS_GROUP (EEL_CANVAS (container)->root),
+ nautilus_canvas_item_get_type (),
+ "visible", FALSE,
+ NULL));
+ icon->item->user_data = icon;
+
+ /* Make sure the icon is under the selection_rectangle */
+ item = EEL_CANVAS_ITEM (icon->item);
+ band = NAUTILUS_CANVAS_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
+ if (band)
+ {
+ eel_canvas_item_send_behind (item, band);
+ }
+
+ /* Put it on both lists. */
+ details->icons = g_list_prepend (details->icons, icon);
+ details->new_icons = g_list_prepend (details->new_icons, icon);
+
+ g_hash_table_insert (details->icon_set, data, icon);
+
+ details->needs_resort = TRUE;
+
+ /* Run an idle function to add the icons. */
+ schedule_redo_layout (container);
+
+ return TRUE;
+}
+
+void
+nautilus_canvas_container_layout_now (NautilusCanvasContainer *container)
+{
+ container->details->in_layout_now = TRUE;
+ if (container->details->idle_id != 0)
+ {
+ unschedule_redo_layout (container);
+ redo_layout_internal (container);
+ }
+
+ /* Also need to make sure we're properly resized, for instance
+ * newly added files may trigger a change in the size allocation and
+ * thus toggle scrollbars on */
+ gtk_container_check_resize (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (container))));
+ container->details->in_layout_now = FALSE;
+}
+
+/**
+ * nautilus_canvas_container_remove:
+ * @container: A NautilusCanvasContainer.
+ * @data: Icon data.
+ *
+ * Remove the icon with this data.
+ **/
+gboolean
+nautilus_canvas_container_remove (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasIcon *icon;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), FALSE);
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ icon = g_hash_table_lookup (container->details->icon_set, data);
+
+ if (icon == NULL)
+ {
+ return FALSE;
+ }
+
+ icon_destroy (container, icon);
+ schedule_redo_layout (container);
+
+ g_signal_emit (container, signals[ICON_REMOVED], 0, icon);
+
+ return TRUE;
+}
+
+/**
+ * nautilus_canvas_container_request_update:
+ * @container: A NautilusCanvasContainer.
+ * @data: Icon data.
+ *
+ * Update the icon with this data.
+ **/
+void
+nautilus_canvas_container_request_update (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasIcon *icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_return_if_fail (data != NULL);
+
+ icon = g_hash_table_lookup (container->details->icon_set, data);
+
+ if (icon != NULL)
+ {
+ nautilus_canvas_container_update_icon (container, icon);
+ container->details->needs_resort = TRUE;
+ schedule_redo_layout (container);
+ }
+}
+
+/* zooming */
+
+NautilusCanvasZoomLevel
+nautilus_canvas_container_get_zoom_level (NautilusCanvasContainer *container)
+{
+ return container->details->zoom_level;
+}
+
+void
+nautilus_canvas_container_set_zoom_level (NautilusCanvasContainer *container,
+ int new_level)
+{
+ NautilusCanvasContainerDetails *details;
+ int pinned_level;
+ double pixels_per_unit;
+
+ details = container->details;
+
+ pinned_level = new_level;
+ if (pinned_level < NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL)
+ {
+ pinned_level = NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL;
+ }
+ else if (pinned_level > NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER)
+ {
+ pinned_level = NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER;
+ }
+
+ if (pinned_level == details->zoom_level)
+ {
+ return;
+ }
+
+ details->zoom_level = pinned_level;
+
+ pixels_per_unit = (double) nautilus_canvas_container_get_icon_size_for_zoom_level (pinned_level)
+ / NAUTILUS_CANVAS_ICON_SIZE_STANDARD;
+ eel_canvas_set_pixels_per_unit (EEL_CANVAS (container), pixels_per_unit);
+
+ nautilus_canvas_container_request_update_all_internal (container, TRUE);
+}
+
+/**
+ * nautilus_canvas_container_request_update_all:
+ * For each icon, synchronizes the displayed information (image, text) with the
+ * information from the model.
+ *
+ * @container: An canvas container.
+ **/
+void
+nautilus_canvas_container_request_update_all (NautilusCanvasContainer *container)
+{
+ nautilus_canvas_container_request_update_all_internal (container, FALSE);
+}
+
+/**
+ * nautilus_canvas_container_reveal:
+ * Change scroll position as necessary to reveal the specified item.
+ */
+void
+nautilus_canvas_container_reveal (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasIcon *icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_return_if_fail (data != NULL);
+
+ icon = g_hash_table_lookup (container->details->icon_set, data);
+
+ if (icon != NULL)
+ {
+ reveal_icon (container, icon);
+ }
+}
+
+/**
+ * nautilus_canvas_container_get_selection:
+ * @container: An canvas 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 canvas is selected. The caller is expected to
+ * free the list when it is not needed anymore.
+ **/
+GList *
+nautilus_canvas_container_get_selection (NautilusCanvasContainer *container)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), NULL);
+
+ if (container->details->selection_needs_resort)
+ {
+ sort_selection (container);
+ }
+
+ return g_list_copy (container->details->selection);
+}
+
+static GList *
+nautilus_canvas_container_get_selected_icons (NautilusCanvasContainer *container)
+{
+ GList *list, *p;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), NULL);
+
+ list = NULL;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ NautilusCanvasIcon *icon;
+
+ icon = p->data;
+ if (icon->is_selected)
+ {
+ list = g_list_prepend (list, icon);
+ }
+ }
+
+ return g_list_reverse (list);
+}
+
+/**
+ * nautilus_canvas_container_invert_selection:
+ * @container: An canvas container.
+ *
+ * Inverts the selection in @container.
+ *
+ **/
+void
+nautilus_canvas_container_invert_selection (NautilusCanvasContainer *container)
+{
+ GList *p;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ NautilusCanvasIcon *icon;
+
+ icon = p->data;
+ icon_toggle_selected (container, icon);
+ }
+
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+}
+
+
+/* Returns an array of GdkPoints of locations of the icons. */
+static GArray *
+nautilus_canvas_container_get_icon_locations (NautilusCanvasContainer *container,
+ GList *icons)
+{
+ GArray *result;
+ GList *node;
+ int index;
+
+ result = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
+ result = g_array_set_size (result, g_list_length (icons));
+
+ for (index = 0, node = icons; node != NULL; index++, node = node->next)
+ {
+ g_array_index (result, GdkPoint, index).x =
+ ((NautilusCanvasIcon *) node->data)->x;
+ g_array_index (result, GdkPoint, index).y =
+ ((NautilusCanvasIcon *) node->data)->y;
+ }
+
+ return result;
+}
+
+/* Returns a GdkRectangle of the icon. The bounding box is adjusted with the
+ * pixels_per_unit already, so they are the final positions on the canvas */
+GdkRectangle *
+nautilus_canvas_container_get_icon_bounding_box (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasIcon *icon;
+ int x1, x2, y1, y2;
+ GdkRectangle *bounding_box;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), NULL);
+ g_return_val_if_fail (data != NULL, NULL);
+
+ icon = g_hash_table_lookup (container->details->icon_set, data);
+ icon_get_bounding_box (icon,
+ &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_DISPLAY);
+ bounding_box = g_malloc0 (sizeof (GdkRectangle));
+ bounding_box->x = x1 * EEL_CANVAS (container)->pixels_per_unit;
+ bounding_box->width = (x2 - x1) * EEL_CANVAS (container)->pixels_per_unit;
+ bounding_box->y = y1 * EEL_CANVAS (container)->pixels_per_unit;
+ bounding_box->height = (y2 - y1) * EEL_CANVAS (container)->pixels_per_unit;
+
+ return bounding_box;
+}
+
+/**
+ * nautilus_canvas_container_get_selected_icon_locations:
+ * @container: An canvas container widget.
+ *
+ * Returns an array of GdkPoints of locations of the selected icons.
+ **/
+GArray *
+nautilus_canvas_container_get_selected_icon_locations (NautilusCanvasContainer *container)
+{
+ GArray *result;
+ GList *icons;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), NULL);
+
+ icons = nautilus_canvas_container_get_selected_icons (container);
+ result = nautilus_canvas_container_get_icon_locations (container, icons);
+ g_list_free (icons);
+
+ return result;
+}
+
+/**
+ * nautilus_canvas_container_select_all:
+ * @container: An canvas container widget.
+ *
+ * Select all the icons in @container at once.
+ **/
+void
+nautilus_canvas_container_select_all (NautilusCanvasContainer *container)
+{
+ gboolean selection_changed;
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ selection_changed = FALSE;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ selection_changed |= icon_set_selected (container, icon, TRUE);
+ }
+
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * nautilus_canvas_container_select_first:
+ * @container: An canvas container widget.
+ *
+ * Select the first icon in @container.
+ **/
+void
+nautilus_canvas_container_select_first (NautilusCanvasContainer *container)
+{
+ gboolean selection_changed;
+ NautilusCanvasIcon *icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ selection_changed = FALSE;
+
+ if (container->details->needs_resort)
+ {
+ resort (container);
+ container->details->needs_resort = FALSE;
+ }
+
+ icon = g_list_nth_data (container->details->icons, 0);
+ if (icon)
+ {
+ selection_changed |= icon_set_selected (container, icon, TRUE);
+ }
+
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * nautilus_canvas_container_set_selection:
+ * @container: An canvas container widget.
+ * @selection: A list of NautilusCanvasIconData *.
+ *
+ * Set the selection to exactly the icons in @container which have
+ * programmer data matching one of the items in @selection.
+ **/
+void
+nautilus_canvas_container_set_selection (NautilusCanvasContainer *container,
+ GList *selection)
+{
+ gboolean selection_changed;
+ GHashTable *hash;
+ GList *p;
+ gboolean res;
+ NautilusCanvasIcon *icon, *selected_icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ selection_changed = FALSE;
+ selected_icon = NULL;
+
+ hash = g_hash_table_new (NULL, NULL);
+ for (p = selection; p != NULL; p = p->next)
+ {
+ g_hash_table_insert (hash, p->data, p->data);
+ }
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ res = icon_set_selected
+ (container, icon,
+ g_hash_table_lookup (hash, icon->data) != NULL);
+ selection_changed |= res;
+
+ if (res)
+ {
+ selected_icon = icon;
+ }
+ }
+ g_hash_table_destroy (hash);
+
+ if (selection_changed)
+ {
+ /* if only one item has been selected, use it as range
+ * selection base (cf. handle_canvas_button_press) */
+ if (g_list_length (selection) == 1)
+ {
+ container->details->range_selection_base_icon = selected_icon;
+ }
+
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * nautilus_canvas_container_select_list_unselect_others.
+ * @container: An canvas container widget.
+ * @selection: A list of NautilusCanvasIcon *.
+ *
+ * Set the selection to exactly the icons in @selection.
+ **/
+void
+nautilus_canvas_container_select_list_unselect_others (NautilusCanvasContainer *container,
+ GList *selection)
+{
+ gboolean selection_changed;
+ GHashTable *hash;
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ selection_changed = FALSE;
+
+ hash = g_hash_table_new (NULL, NULL);
+ for (p = selection; p != NULL; p = p->next)
+ {
+ g_hash_table_insert (hash, p->data, p->data);
+ }
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ selection_changed |= icon_set_selected
+ (container, icon,
+ g_hash_table_lookup (hash, icon) != NULL);
+ }
+ g_hash_table_destroy (hash);
+
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * nautilus_canvas_container_unselect_all:
+ * @container: An canvas container widget.
+ *
+ * Deselect all the icons in @container.
+ **/
+void
+nautilus_canvas_container_unselect_all (NautilusCanvasContainer *container)
+{
+ if (unselect_all (container))
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * nautilus_canvas_container_get_icon_by_uri:
+ * @container: An canvas container widget.
+ * @uri: The uri of an canvas to find.
+ *
+ * Locate an icon, given the URI. The URI must match exactly.
+ * Later we may have to have some way of figuring out if the
+ * URI specifies the same object that does not require an exact match.
+ **/
+NautilusCanvasIcon *
+nautilus_canvas_container_get_icon_by_uri (NautilusCanvasContainer *container,
+ const char *uri)
+{
+ NautilusCanvasContainerDetails *details;
+ GList *p;
+
+ /* Eventually, we must avoid searching the entire canvas list,
+ * but it's OK for now.
+ * A hash table mapping uri to canvas is one possibility.
+ */
+
+ details = container->details;
+
+ for (p = details->icons; p != NULL; p = p->next)
+ {
+ NautilusCanvasIcon *icon;
+ char *icon_uri;
+ gboolean is_match;
+
+ icon = p->data;
+
+ icon_uri = nautilus_canvas_container_get_icon_uri
+ (container, icon);
+ is_match = strcmp (uri, icon_uri) == 0;
+ g_free (icon_uri);
+
+ if (is_match)
+ {
+ return icon;
+ }
+ }
+
+ return NULL;
+}
+
+static NautilusCanvasIcon *
+get_nth_selected_icon (NautilusCanvasContainer *container,
+ int index)
+{
+ GList *p;
+ NautilusCanvasIcon *icon;
+ int selection_count;
+
+ g_assert (index > 0);
+
+ /* Find the nth selected icon. */
+ selection_count = 0;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ if (icon->is_selected)
+ {
+ if (++selection_count == index)
+ {
+ return icon;
+ }
+ }
+ }
+ return NULL;
+}
+
+static NautilusCanvasIcon *
+get_first_selected_icon (NautilusCanvasContainer *container)
+{
+ return get_nth_selected_icon (container, 1);
+}
+
+static gboolean
+has_multiple_selection (NautilusCanvasContainer *container)
+{
+ return get_nth_selected_icon (container, 2) != NULL;
+}
+
+static gboolean
+all_selected (NautilusCanvasContainer *container)
+{
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ if (!icon->is_selected)
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static gboolean
+has_selection (NautilusCanvasContainer *container)
+{
+ return get_nth_selected_icon (container, 1) != NULL;
+}
+
+char *
+nautilus_canvas_container_get_icon_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ char *uri;
+
+ uri = NULL;
+ g_signal_emit (container,
+ signals[GET_ICON_URI], 0,
+ icon->data,
+ &uri);
+ return uri;
+}
+
+char *
+nautilus_canvas_container_get_icon_activation_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ char *uri;
+
+ uri = NULL;
+ g_signal_emit (container,
+ signals[GET_ICON_ACTIVATION_URI], 0,
+ icon->data,
+ &uri);
+ return uri;
+}
+
+char *
+nautilus_canvas_container_get_icon_drop_target_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ char *uri;
+
+ uri = NULL;
+ g_signal_emit (container,
+ signals[GET_ICON_DROP_TARGET_URI], 0,
+ icon->data,
+ &uri);
+ return uri;
+}
+
+/* Re-sort, switching to automatic layout if it was in manual layout. */
+void
+nautilus_canvas_container_sort (NautilusCanvasContainer *container)
+{
+ container->details->needs_resort = TRUE;
+ redo_layout (container);
+}
+
+void
+nautilus_canvas_container_set_single_click_mode (NautilusCanvasContainer *container,
+ gboolean single_click_mode)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ container->details->single_click_mode = single_click_mode;
+}
+
+/* handle theme changes */
+
+void
+nautilus_canvas_container_set_font (NautilusCanvasContainer *container,
+ const char *font)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ if (g_strcmp0 (container->details->font, font) == 0)
+ {
+ return;
+ }
+
+ g_free (container->details->font);
+ container->details->font = g_strdup (font);
+
+ nautilus_canvas_container_request_update_all_internal (container, TRUE);
+ gtk_widget_queue_draw (GTK_WIDGET (container));
+}
+
+/**
+ * nautilus_canvas_container_get_icon_description
+ * @container: An canvas container widget.
+ * @data: Icon data
+ *
+ * Gets the description for the icon. This function may return NULL.
+ **/
+char *
+nautilus_canvas_container_get_icon_description (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasContainerClass *klass;
+
+ klass = NAUTILUS_CANVAS_CONTAINER_GET_CLASS (container);
+
+ if (klass->get_icon_description)
+ {
+ return klass->get_icon_description (container, data);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+/**
+ * nautilus_canvas_container_set_highlighted_for_clipboard
+ * @container: An canvas container widget.
+ * @data: Canvas Data associated with all icons that should be highlighted.
+ * Others will be unhighlighted.
+ **/
+void
+nautilus_canvas_container_set_highlighted_for_clipboard (NautilusCanvasContainer *container,
+ GList *clipboard_canvas_data)
+{
+ GList *l;
+ NautilusCanvasIcon *icon;
+ gboolean highlighted_for_clipboard;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ for (l = container->details->icons; l != NULL; l = l->next)
+ {
+ icon = l->data;
+ highlighted_for_clipboard = (g_list_find (clipboard_canvas_data, icon->data) != NULL);
+
+ eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
+ "highlighted-for-clipboard", highlighted_for_clipboard,
+ NULL);
+ }
+}
+
+/* NautilusCanvasContainerAccessible */
+typedef struct
+{
+ EelCanvasAccessible parent;
+ NautilusCanvasContainerAccessiblePrivate *priv;
+} NautilusCanvasContainerAccessible;
+
+typedef EelCanvasAccessibleClass NautilusCanvasContainerAccessibleClass;
+
+#define GET_ACCESSIBLE_PRIV(o) ((NautilusCanvasContainerAccessible *) o)->priv
+
+/* AtkAction interface */
+static gboolean
+nautilus_canvas_container_accessible_do_action (AtkAction *accessible,
+ int i)
+{
+ GtkWidget *widget;
+ NautilusCanvasContainer *container;
+ GList *selection;
+
+ g_return_val_if_fail (i < LAST_ACTION, FALSE);
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ switch (i)
+ {
+ case ACTION_ACTIVATE:
+ {
+ selection = nautilus_canvas_container_get_selection (container);
+
+ if (selection)
+ {
+ g_signal_emit_by_name (container, "activate", selection);
+ g_list_free (selection);
+ }
+ }
+ break;
+
+ case ACTION_MENU:
+ {
+ handle_popups (container, NULL, "context_click_background");
+ }
+ break;
+
+ default:
+ {
+ g_warning ("Invalid action passed to NautilusCanvasContainerAccessible::do_action");
+ return FALSE;
+ }
+ break;
+ }
+ return TRUE;
+}
+
+static int
+nautilus_canvas_container_accessible_get_n_actions (AtkAction *accessible)
+{
+ return LAST_ACTION;
+}
+
+static const char *
+nautilus_canvas_container_accessible_action_get_description (AtkAction *accessible,
+ int i)
+{
+ NautilusCanvasContainerAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+
+ if (priv->action_descriptions[i])
+ {
+ return priv->action_descriptions[i];
+ }
+ else
+ {
+ return nautilus_canvas_container_accessible_action_descriptions[i];
+ }
+}
+
+static const char *
+nautilus_canvas_container_accessible_action_get_name (AtkAction *accessible,
+ int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return nautilus_canvas_container_accessible_action_names[i];
+}
+
+static const char *
+nautilus_canvas_container_accessible_action_get_keybinding (AtkAction *accessible,
+ int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return NULL;
+}
+
+static gboolean
+nautilus_canvas_container_accessible_action_set_description (AtkAction *accessible,
+ int i,
+ const char *description)
+{
+ NautilusCanvasContainerAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+
+ if (priv->action_descriptions[i])
+ {
+ g_free (priv->action_descriptions[i]);
+ }
+ priv->action_descriptions[i] = g_strdup (description);
+
+ return FALSE;
+}
+
+static void
+nautilus_canvas_container_accessible_action_interface_init (AtkActionIface *iface)
+{
+ iface->do_action = nautilus_canvas_container_accessible_do_action;
+ iface->get_n_actions = nautilus_canvas_container_accessible_get_n_actions;
+ iface->get_description = nautilus_canvas_container_accessible_action_get_description;
+ iface->get_name = nautilus_canvas_container_accessible_action_get_name;
+ iface->get_keybinding = nautilus_canvas_container_accessible_action_get_keybinding;
+ iface->set_description = nautilus_canvas_container_accessible_action_set_description;
+}
+
+/* AtkSelection interface */
+
+static void
+nautilus_canvas_container_accessible_update_selection (AtkObject *accessible)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasContainerAccessiblePrivate *priv;
+
+ container = NAUTILUS_CANVAS_CONTAINER (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+
+ if (priv->selection)
+ {
+ g_list_free (priv->selection);
+ priv->selection = NULL;
+ }
+
+ priv->selection = nautilus_canvas_container_get_selected_icons (container);
+}
+
+static void
+nautilus_canvas_container_accessible_selection_changed_cb (NautilusCanvasContainer *container,
+ gpointer data)
+{
+ g_signal_emit_by_name (data, "selection-changed");
+}
+
+static void
+nautilus_canvas_container_accessible_icon_added_cb (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *icon_data,
+ gpointer data)
+{
+ NautilusCanvasIcon *icon;
+ AtkObject *atk_parent;
+ AtkObject *atk_child;
+
+ /* We don't want to emit children_changed signals during any type of load. */
+ if (!container->details->in_layout_now || container->details->is_populating_container)
+ {
+ return;
+ }
+
+ icon = g_hash_table_lookup (container->details->icon_set, icon_data);
+ if (icon)
+ {
+ atk_parent = ATK_OBJECT (data);
+ atk_child = atk_gobject_accessible_for_object
+ (G_OBJECT (icon->item));
+
+ g_signal_emit_by_name (atk_parent, "children-changed::add",
+ icon->position, atk_child, NULL);
+ }
+}
+
+static void
+nautilus_canvas_container_accessible_icon_removed_cb (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *icon_data,
+ gpointer data)
+{
+ NautilusCanvasIcon *icon;
+ AtkObject *atk_parent;
+ AtkObject *atk_child;
+
+ icon = g_hash_table_lookup (container->details->icon_set, icon_data);
+ if (icon)
+ {
+ atk_parent = ATK_OBJECT (data);
+ atk_child = atk_gobject_accessible_for_object
+ (G_OBJECT (icon->item));
+
+ g_signal_emit_by_name (atk_parent, "children-changed::remove",
+ icon->position, atk_child, NULL);
+ }
+}
+
+static void
+nautilus_canvas_container_accessible_cleared_cb (NautilusCanvasContainer *container,
+ gpointer data)
+{
+ g_signal_emit_by_name (data, "children-changed", 0, NULL, NULL);
+}
+
+static gboolean
+nautilus_canvas_container_accessible_add_selection (AtkSelection *accessible,
+ int i)
+{
+ GtkWidget *widget;
+ NautilusCanvasContainer *container;
+ GList *l;
+ GList *selection;
+ NautilusCanvasIcon *icon;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ l = g_list_nth (container->details->icons, i);
+ if (l)
+ {
+ icon = l->data;
+
+ selection = nautilus_canvas_container_get_selection (container);
+ selection = g_list_prepend (selection,
+ icon->data);
+ nautilus_canvas_container_set_selection (container, selection);
+
+ g_list_free (selection);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+nautilus_canvas_container_accessible_clear_selection (AtkSelection *accessible)
+{
+ GtkWidget *widget;
+ NautilusCanvasContainer *container;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ nautilus_canvas_container_unselect_all (container);
+
+ return TRUE;
+}
+
+static AtkObject *
+nautilus_canvas_container_accessible_ref_selection (AtkSelection *accessible,
+ int i)
+{
+ NautilusCanvasContainerAccessiblePrivate *priv;
+ AtkObject *atk_object;
+ GList *item;
+ NautilusCanvasIcon *icon;
+
+ nautilus_canvas_container_accessible_update_selection (ATK_OBJECT (accessible));
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+
+ item = (g_list_nth (priv->selection, i));
+
+ if (item)
+ {
+ icon = item->data;
+ atk_object = atk_gobject_accessible_for_object (G_OBJECT (icon->item));
+ if (atk_object)
+ {
+ g_object_ref (atk_object);
+ }
+
+ return atk_object;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+static int
+nautilus_canvas_container_accessible_get_selection_count (AtkSelection *accessible)
+{
+ NautilusCanvasContainerAccessiblePrivate *priv;
+ int count;
+
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+ nautilus_canvas_container_accessible_update_selection (ATK_OBJECT (accessible));
+ count = g_list_length (priv->selection);
+
+ return count;
+}
+
+static gboolean
+nautilus_canvas_container_accessible_is_child_selected (AtkSelection *accessible,
+ int i)
+{
+ NautilusCanvasContainer *container;
+ GList *l;
+ NautilusCanvasIcon *icon;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ l = g_list_nth (container->details->icons, i);
+ if (l)
+ {
+ icon = l->data;
+ return icon->is_selected;
+ }
+ return FALSE;
+}
+
+static gboolean
+nautilus_canvas_container_accessible_remove_selection (AtkSelection *accessible,
+ int i)
+{
+ NautilusCanvasContainerAccessiblePrivate *priv;
+ NautilusCanvasContainer *container;
+ GList *l;
+ GList *selection;
+ NautilusCanvasIcon *icon;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ nautilus_canvas_container_accessible_update_selection (ATK_OBJECT (accessible));
+
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+ l = g_list_nth (priv->selection, i);
+ if (l)
+ {
+ icon = l->data;
+
+ selection = nautilus_canvas_container_get_selection (container);
+ selection = g_list_remove (selection, icon->data);
+ nautilus_canvas_container_set_selection (container, selection);
+
+ g_list_free (selection);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+nautilus_canvas_container_accessible_select_all_selection (AtkSelection *accessible)
+{
+ NautilusCanvasContainer *container;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ nautilus_canvas_container_select_all (container);
+
+ return TRUE;
+}
+
+void
+nautilus_canvas_container_widget_to_file_operation_position (NautilusCanvasContainer *container,
+ GdkPoint *position)
+{
+ double x, y;
+
+ g_return_if_fail (position != NULL);
+
+ x = position->x;
+ y = position->y;
+
+ eel_canvas_window_to_world (EEL_CANVAS (container), x, y, &x, &y);
+
+ position->x = (int) x;
+ position->y = (int) y;
+
+ /* ensure that we end up in the middle of the icon */
+ position->x -= nautilus_canvas_container_get_icon_size_for_zoom_level (container->details->zoom_level) / 2;
+ position->y -= nautilus_canvas_container_get_icon_size_for_zoom_level (container->details->zoom_level) / 2;
+}
+
+static void
+nautilus_canvas_container_accessible_selection_interface_init (AtkSelectionIface *iface)
+{
+ iface->add_selection = nautilus_canvas_container_accessible_add_selection;
+ iface->clear_selection = nautilus_canvas_container_accessible_clear_selection;
+ iface->ref_selection = nautilus_canvas_container_accessible_ref_selection;
+ iface->get_selection_count = nautilus_canvas_container_accessible_get_selection_count;
+ iface->is_child_selected = nautilus_canvas_container_accessible_is_child_selected;
+ iface->remove_selection = nautilus_canvas_container_accessible_remove_selection;
+ iface->select_all_selection = nautilus_canvas_container_accessible_select_all_selection;
+}
+
+
+static gint
+nautilus_canvas_container_accessible_get_n_children (AtkObject *accessible)
+{
+ NautilusCanvasContainer *container;
+ GtkWidget *widget;
+ gint i;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ i = g_hash_table_size (container->details->icon_set);
+
+ return i;
+}
+
+static AtkObject *
+nautilus_canvas_container_accessible_ref_child (AtkObject *accessible,
+ int i)
+{
+ AtkObject *atk_object;
+ NautilusCanvasContainer *container;
+ GList *item;
+ NautilusCanvasIcon *icon;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return NULL;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ item = (g_list_nth (container->details->icons, i));
+
+ if (item)
+ {
+ icon = item->data;
+
+ atk_object = atk_gobject_accessible_for_object (G_OBJECT (icon->item));
+ g_object_ref (atk_object);
+
+ return atk_object;
+ }
+ return NULL;
+}
+
+G_DEFINE_TYPE_WITH_CODE (NautilusCanvasContainerAccessible, nautilus_canvas_container_accessible,
+ eel_canvas_accessible_get_type (),
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, nautilus_canvas_container_accessible_action_interface_init)
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_SELECTION, nautilus_canvas_container_accessible_selection_interface_init))
+
+static void
+nautilus_canvas_container_accessible_initialize (AtkObject *accessible,
+ gpointer data)
+{
+ NautilusCanvasContainer *container;
+
+ if (ATK_OBJECT_CLASS (nautilus_canvas_container_accessible_parent_class)->initialize)
+ {
+ ATK_OBJECT_CLASS (nautilus_canvas_container_accessible_parent_class)->initialize (accessible, data);
+ }
+
+ if (GTK_IS_ACCESSIBLE (accessible))
+ {
+ nautilus_canvas_container_accessible_update_selection
+ (ATK_OBJECT (accessible));
+
+ container = NAUTILUS_CANVAS_CONTAINER (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
+ g_signal_connect (container, "selection-changed",
+ G_CALLBACK (nautilus_canvas_container_accessible_selection_changed_cb),
+ accessible);
+ g_signal_connect (container, "icon-added",
+ G_CALLBACK (nautilus_canvas_container_accessible_icon_added_cb),
+ accessible);
+ g_signal_connect (container, "icon-removed",
+ G_CALLBACK (nautilus_canvas_container_accessible_icon_removed_cb),
+ accessible);
+ g_signal_connect (container, "cleared",
+ G_CALLBACK (nautilus_canvas_container_accessible_cleared_cb),
+ accessible);
+ }
+}
+
+static void
+nautilus_canvas_container_accessible_finalize (GObject *object)
+{
+ NautilusCanvasContainerAccessiblePrivate *priv;
+ int i;
+
+ priv = GET_ACCESSIBLE_PRIV (object);
+
+ if (priv->selection)
+ {
+ g_list_free (priv->selection);
+ }
+
+ for (i = 0; i < LAST_ACTION; i++)
+ {
+ if (priv->action_descriptions[i])
+ {
+ g_free (priv->action_descriptions[i]);
+ }
+ }
+
+ G_OBJECT_CLASS (nautilus_canvas_container_accessible_parent_class)->finalize (object);
+}
+
+static void
+nautilus_canvas_container_accessible_init (NautilusCanvasContainerAccessible *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_canvas_container_accessible_get_type (),
+ NautilusCanvasContainerAccessiblePrivate);
+}
+
+static void
+nautilus_canvas_container_accessible_class_init (NautilusCanvasContainerAccessibleClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = nautilus_canvas_container_accessible_finalize;
+
+ atk_class->get_n_children = nautilus_canvas_container_accessible_get_n_children;
+ atk_class->ref_child = nautilus_canvas_container_accessible_ref_child;
+ atk_class->initialize = nautilus_canvas_container_accessible_initialize;
+
+ g_type_class_add_private (klass, sizeof (NautilusCanvasContainerAccessiblePrivate));
+}
+
+gboolean
+nautilus_canvas_container_is_layout_rtl (NautilusCanvasContainer *container)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), 0);
+
+ return (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL);
+}
+
+int
+nautilus_canvas_container_get_max_layout_lines_for_pango (NautilusCanvasContainer *container)
+{
+ int limit;
+
+ limit = text_ellipsis_limits[container->details->zoom_level];
+
+ if (limit <= 0)
+ {
+ return G_MININT;
+ }
+
+ return -limit;
+}
+
+int
+nautilus_canvas_container_get_max_layout_lines (NautilusCanvasContainer *container)
+{
+ int limit;
+
+ limit = text_ellipsis_limits[container->details->zoom_level];
+
+ if (limit <= 0)
+ {
+ return G_MAXINT;
+ }
+
+ return limit;
+}
diff --git a/src/nautilus-canvas-container.h b/src/nautilus-canvas-container.h
new file mode 100644
index 000000000..7955cf34d
--- /dev/null
+++ b/src/nautilus-canvas-container.h
@@ -0,0 +1,295 @@
+
+/* gnome-canvas-container.h - Canvas container widget.
+
+ Copyright (C) 1999, 2000 Free Software Foundation
+ Copyright (C) 2000 Eazel, Inc.
+
+ 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,
+ see <http://www.gnu.org/licenses/>.
+
+ Authors: Ettore Perazzoli <ettore@gnu.org>, Darin Adler <darin@bentspoon.com>
+*/
+
+#pragma once
+
+#include <eel/eel-canvas.h>
+
+#include "nautilus-types.h"
+
+#define NAUTILUS_TYPE_CANVAS_CONTAINER nautilus_canvas_container_get_type()
+#define NAUTILUS_CANVAS_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_CANVAS_CONTAINER, NautilusCanvasContainer))
+#define NAUTILUS_CANVAS_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_CANVAS_CONTAINER, NautilusCanvasContainerClass))
+#define NAUTILUS_IS_CANVAS_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_CANVAS_CONTAINER))
+#define NAUTILUS_IS_CANVAS_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_CANVAS_CONTAINER))
+#define NAUTILUS_CANVAS_CONTAINER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_CANVAS_CONTAINER, NautilusCanvasContainerClass))
+
+
+#define NAUTILUS_CANVAS_ICON_DATA(pointer) \
+ ((NautilusCanvasIconData *) (pointer))
+
+typedef struct NautilusCanvasIconData NautilusCanvasIconData;
+
+typedef void (* NautilusCanvasCallback) (NautilusCanvasIconData *icon_data,
+ gpointer callback_data);
+
+typedef struct {
+ int x;
+ int y;
+ double scale;
+} NautilusCanvasPosition;
+
+#define NAUTILUS_CANVAS_CONTAINER_TYPESELECT_FLUSH_DELAY 1000000
+
+typedef struct _NautilusCanvasContainer NautilusCanvasContainer;
+typedef struct NautilusCanvasContainerDetails NautilusCanvasContainerDetails;
+
+struct _NautilusCanvasContainer {
+ EelCanvas canvas;
+ NautilusCanvasContainerDetails *details;
+};
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (NautilusCanvasContainer, g_object_unref)
+
+typedef struct {
+ EelCanvasClass parent_slot;
+
+ /* Operations on the container. */
+ int (* button_press) (NautilusCanvasContainer *container,
+ GdkEventButton *event);
+ void (* context_click_background) (NautilusCanvasContainer *container,
+ GdkEventButton *event);
+ void (* middle_click) (NautilusCanvasContainer *container,
+ GdkEventButton *event);
+
+ /* Operations on icons. */
+ void (* activate) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ void (* activate_alternate) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ void (* activate_previewer) (NautilusCanvasContainer *container,
+ GList *files,
+ GArray *locations);
+ void (* context_click_selection) (NautilusCanvasContainer *container,
+ GdkEventButton *event);
+ void (* move_copy_items) (NautilusCanvasContainer *container,
+ const GList *item_uris,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_netscape_url) (NautilusCanvasContainer *container,
+ const char *url,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_uri_list) (NautilusCanvasContainer *container,
+ const char *uri_list,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_text) (NautilusCanvasContainer *container,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_raw) (NautilusCanvasContainer *container,
+ char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action,
+ int x,
+ int y);
+ void (* handle_hover) (NautilusCanvasContainer *container,
+ const char *target_uri);
+
+ /* Queries on the container for subclass/client.
+ * These must be implemented. The default "do nothing" is not good enough.
+ */
+ char * (* get_container_uri) (NautilusCanvasContainer *container);
+
+ /* Queries on icons for subclass/client.
+ * These must be implemented. The default "do nothing" is not
+ * good enough, these are _not_ signals.
+ */
+ NautilusIconInfo *(* get_icon_images) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ int canvas_size,
+ gboolean for_drag_accept);
+ void (* get_icon_text) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ char **editable_text,
+ char **additional_text,
+ gboolean include_invisible);
+ char * (* get_icon_description) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ int (* compare_icons) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *canvas_a,
+ NautilusCanvasIconData *canvas_b);
+ int (* compare_icons_by_name) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *canvas_a,
+ NautilusCanvasIconData *canvas_b);
+ void (* prioritize_thumbnailing) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+
+ /* Queries on icons for subclass/client.
+ * These must be implemented => These are signals !
+ * The default "do nothing" is not good enough.
+ */
+ gboolean (* get_stored_icon_position) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ NautilusCanvasPosition *position);
+ char * (* get_icon_uri) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ char * (* get_icon_activation_uri) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ char * (* get_icon_drop_target_uri) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+
+ /* If canvas data is NULL, the layout timestamp of the container should be retrieved.
+ * That is the time when the container displayed a fully loaded directory with
+ * all canvas positions assigned.
+ *
+ * If canvas data is not NULL, the position timestamp of the canvas should be retrieved.
+ * That is the time when the file (i.e. canvas data payload) was last displayed in a
+ * fully loaded directory with all canvas positions assigned.
+ */
+ gboolean (* get_stored_layout_timestamp) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ time_t *time);
+ /* If canvas data is NULL, the layout timestamp of the container should be stored.
+ * If canvas data is not NULL, the position timestamp of the container should be stored.
+ */
+ gboolean (* store_layout_timestamp) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ const time_t *time);
+
+ /* Notifications for the whole container. */
+ void (* band_select_started) (NautilusCanvasContainer *container);
+ void (* band_select_ended) (NautilusCanvasContainer *container);
+ void (* selection_changed) (NautilusCanvasContainer *container);
+ void (* layout_changed) (NautilusCanvasContainer *container);
+
+ /* Notifications for icons. */
+ void (* icon_position_changed) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ const NautilusCanvasPosition *position);
+ int (* preview) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ gboolean start_flag);
+ void (* icon_added) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ void (* icon_removed) (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+ void (* cleared) (NautilusCanvasContainer *container);
+ gboolean (* start_interactive_search) (NautilusCanvasContainer *container);
+} NautilusCanvasContainerClass;
+
+/* GtkObject */
+GType nautilus_canvas_container_get_type (void);
+GtkWidget * nautilus_canvas_container_new (void);
+
+
+/* adding, removing, and managing icons */
+void nautilus_canvas_container_clear (NautilusCanvasContainer *view);
+gboolean nautilus_canvas_container_add (NautilusCanvasContainer *view,
+ NautilusCanvasIconData *data);
+void nautilus_canvas_container_layout_now (NautilusCanvasContainer *container);
+gboolean nautilus_canvas_container_remove (NautilusCanvasContainer *view,
+ NautilusCanvasIconData *data);
+void nautilus_canvas_container_for_each (NautilusCanvasContainer *view,
+ NautilusCanvasCallback callback,
+ gpointer callback_data);
+void nautilus_canvas_container_request_update (NautilusCanvasContainer *view,
+ NautilusCanvasIconData *data);
+void nautilus_canvas_container_request_update_all (NautilusCanvasContainer *container);
+void nautilus_canvas_container_reveal (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+gboolean nautilus_canvas_container_is_empty (NautilusCanvasContainer *container);
+NautilusCanvasIconData *nautilus_canvas_container_get_first_visible_icon (NautilusCanvasContainer *container);
+NautilusCanvasIconData *nautilus_canvas_container_get_focused_icon (NautilusCanvasContainer *container);
+GdkRectangle *nautilus_canvas_container_get_icon_bounding_box (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+void nautilus_canvas_container_scroll_to_canvas (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+
+void nautilus_canvas_container_begin_loading (NautilusCanvasContainer *container);
+void nautilus_canvas_container_end_loading (NautilusCanvasContainer *container,
+ gboolean all_icons_added);
+
+void nautilus_canvas_container_sort (NautilusCanvasContainer *container);
+void nautilus_canvas_container_freeze_icon_positions (NautilusCanvasContainer *container);
+
+int nautilus_canvas_container_get_max_layout_lines (NautilusCanvasContainer *container);
+int nautilus_canvas_container_get_max_layout_lines_for_pango (NautilusCanvasContainer *container);
+
+void nautilus_canvas_container_set_highlighted_for_clipboard (NautilusCanvasContainer *container,
+ GList *clipboard_canvas_data);
+
+/* operations on all icons */
+void nautilus_canvas_container_unselect_all (NautilusCanvasContainer *view);
+void nautilus_canvas_container_select_all (NautilusCanvasContainer *view);
+
+
+void nautilus_canvas_container_select_first (NautilusCanvasContainer *view);
+
+void nautilus_canvas_container_preview_selection_event (NautilusCanvasContainer *view,
+ GtkDirectionType direction);
+
+/* operations on the selection */
+GList * nautilus_canvas_container_get_selection (NautilusCanvasContainer *view);
+void nautilus_canvas_container_invert_selection (NautilusCanvasContainer *view);
+void nautilus_canvas_container_set_selection (NautilusCanvasContainer *view,
+ GList *selection);
+GArray * nautilus_canvas_container_get_selected_icon_locations (NautilusCanvasContainer *view);
+
+/* options */
+NautilusCanvasZoomLevel nautilus_canvas_container_get_zoom_level (NautilusCanvasContainer *view);
+void nautilus_canvas_container_set_zoom_level (NautilusCanvasContainer *view,
+ int new_zoom_level);
+void nautilus_canvas_container_set_single_click_mode (NautilusCanvasContainer *container,
+ gboolean single_click_mode);
+void nautilus_canvas_container_enable_linger_selection (NautilusCanvasContainer *view,
+ gboolean enable);
+void nautilus_canvas_container_set_font (NautilusCanvasContainer *container,
+ const char *font);
+void nautilus_canvas_container_set_margins (NautilusCanvasContainer *container,
+ int left_margin,
+ int right_margin,
+ int top_margin,
+ int bottom_margin);
+char* nautilus_canvas_container_get_icon_description (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data);
+
+gboolean nautilus_canvas_container_is_layout_rtl (NautilusCanvasContainer *container);
+
+gboolean nautilus_canvas_container_get_store_layout_timestamps (NautilusCanvasContainer *container);
+
+void nautilus_canvas_container_widget_to_file_operation_position (NautilusCanvasContainer *container,
+ GdkPoint *position);
+guint nautilus_canvas_container_get_icon_size_for_zoom_level (NautilusCanvasZoomLevel zoom_level);
+
+#define CANVAS_WIDTH(container,allocation) (allocation.width \
+ / EEL_CANVAS (container)->pixels_per_unit)
+
+#define CANVAS_HEIGHT(container,allocation) (allocation.height \
+ / EEL_CANVAS (container)->pixels_per_unit)
diff --git a/src/nautilus-canvas-dnd.c b/src/nautilus-canvas-dnd.c
new file mode 100644
index 000000000..9522f321c
--- /dev/null
+++ b/src/nautilus-canvas-dnd.c
@@ -0,0 +1,1833 @@
+/* nautilus-canvas-dnd.c - Drag & drop handling for the canvas container widget.
+ *
+ * Copyright (C) 1999, 2000 Free Software Foundation
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * 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,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ettore Perazzoli <ettore@gnu.org>,
+ * Darin Adler <darin@bentspoon.com>,
+ * Andy Hertzfeld <andy@eazel.com>
+ * Pavel Cisler <pavel@eazel.com>
+ *
+ *
+ * XDS support: Benedikt Meurer <benny@xfce.org> (adapted by Amos Brocco <amos.brocco@unifr.ch>)
+ *
+ */
+
+
+#include <config.h>
+#include <math.h>
+#include <src/nautilus-window.h>
+
+#include "nautilus-canvas-dnd.h"
+
+#include "nautilus-canvas-private.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-metadata.h"
+#include "nautilus-selection-canvas-item.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-graphic-effects.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "nautilus-file-utilities.h"
+#include "nautilus-file-changes-queue.h"
+#include <stdio.h>
+#include <string.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_CANVAS_CONTAINER
+#include "nautilus-debug.h"
+
+static const GtkTargetEntry drag_types [] =
+{
+ { NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
+ { NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
+};
+
+static const GtkTargetEntry drop_types [] =
+{
+ { NAUTILUS_ICON_DND_GNOME_ICON_LIST_TYPE, 0, NAUTILUS_ICON_DND_GNOME_ICON_LIST },
+ /* prefer "_NETSCAPE_URL" over "text/uri-list" to satisfy web browsers. */
+ { NAUTILUS_ICON_DND_NETSCAPE_URL_TYPE, 0, NAUTILUS_ICON_DND_NETSCAPE_URL },
+ /* prefer XDS over "text/uri-list" */
+ { NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, 0, NAUTILUS_ICON_DND_XDNDDIRECTSAVE }, /* XDS Protocol Type */
+ { NAUTILUS_ICON_DND_URI_LIST_TYPE, 0, NAUTILUS_ICON_DND_URI_LIST },
+ { NAUTILUS_ICON_DND_RAW_TYPE, 0, NAUTILUS_ICON_DND_RAW },
+ /* Must be last: */
+ { NAUTILUS_ICON_DND_ROOTWINDOW_DROP_TYPE, 0, NAUTILUS_ICON_DND_ROOTWINDOW_DROP }
+};
+static void stop_dnd_highlight (GtkWidget *widget);
+static void dnd_highlight_queue_redraw (GtkWidget *widget);
+
+static GtkTargetList *drop_types_list = NULL;
+static GtkTargetList *drop_types_list_root = NULL;
+
+static char *nautilus_canvas_container_find_drop_target (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ int x,
+ int y,
+ gboolean *icon_hit);
+
+static EelCanvasItem *
+create_selection_shadow (NautilusCanvasContainer *container,
+ GList *list)
+{
+ EelCanvasGroup *group;
+ EelCanvas *canvas;
+ int max_x, max_y;
+ int min_x, min_y;
+ GList *p;
+ GtkAllocation allocation;
+
+ if (list == NULL)
+ {
+ return NULL;
+ }
+
+ /* if we're only dragging a single item, don't worry about the shadow */
+ if (list->next == NULL)
+ {
+ return NULL;
+ }
+
+ canvas = EEL_CANVAS (container);
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ /* 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 = allocation.width;
+ min_x = -max_x;
+
+ max_y = allocation.height;
+ min_y = -max_y;
+
+ /* Create a group, so that it's easier to move all the items around at
+ * once. */
+ group = EEL_CANVAS_GROUP
+ (eel_canvas_item_new (EEL_CANVAS_GROUP (canvas->root),
+ eel_canvas_group_get_type (),
+ NULL));
+
+ for (p = list; p != NULL; p = p->next)
+ {
+ NautilusDragSelectionItem *item;
+ int x1, y1, x2, y2;
+
+ item = p->data;
+
+ if (!item->got_icon_position)
+ {
+ continue;
+ }
+
+ x1 = item->icon_x;
+ y1 = item->icon_y;
+ x2 = x1 + item->icon_width;
+ y2 = y1 + item->icon_height;
+
+ if (x2 >= min_x && x1 <= max_x && y2 >= min_y && y1 <= max_y)
+ {
+ eel_canvas_item_new
+ (group,
+ NAUTILUS_TYPE_SELECTION_CANVAS_ITEM,
+ "x1", (double) x1,
+ "y1", (double) y1,
+ "x2", (double) x2,
+ "y2", (double) y2,
+ NULL);
+ }
+ }
+
+ return EEL_CANVAS_ITEM (group);
+}
+
+/* Set the affine instead of the x and y position.
+ * Simple, and setting x and y was broken at one point.
+ */
+static void
+set_shadow_position (EelCanvasItem *shadow,
+ double x,
+ double y)
+{
+ eel_canvas_item_set (shadow,
+ "x", x, "y", y,
+ NULL);
+}
+
+
+/* Source-side handling of the drag. */
+
+/* iteration glue struct */
+typedef struct
+{
+ gpointer iterator_context;
+ NautilusDragEachSelectedItemDataGet iteratee;
+ gpointer iteratee_data;
+} CanvasGetDataBinderContext;
+
+static void
+canvas_rect_world_to_widget (EelCanvas *canvas,
+ EelDRect *world_rect,
+ EelIRect *widget_rect)
+{
+ EelDRect window_rect;
+ GtkAdjustment *hadj, *vadj;
+
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas));
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas));
+
+ eel_canvas_world_to_window (canvas,
+ world_rect->x0, world_rect->y0,
+ &window_rect.x0, &window_rect.y0);
+ eel_canvas_world_to_window (canvas,
+ world_rect->x1, world_rect->y1,
+ &window_rect.x1, &window_rect.y1);
+ widget_rect->x0 = (int) window_rect.x0 - gtk_adjustment_get_value (hadj);
+ widget_rect->y0 = (int) window_rect.y0 - gtk_adjustment_get_value (vadj);
+ widget_rect->x1 = (int) window_rect.x1 - gtk_adjustment_get_value (hadj);
+ widget_rect->y1 = (int) window_rect.y1 - gtk_adjustment_get_value (vadj);
+}
+
+static void
+canvas_widget_to_world (EelCanvas *canvas,
+ double widget_x,
+ double widget_y,
+ double *world_x,
+ double *world_y)
+{
+ eel_canvas_window_to_world (canvas,
+ widget_x + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas))),
+ widget_y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas))),
+ world_x, world_y);
+}
+
+static gboolean
+icon_get_data_binder (NautilusCanvasIcon *icon,
+ gpointer data)
+{
+ CanvasGetDataBinderContext *context;
+ EelDRect world_rect;
+ EelIRect widget_rect;
+ char *uri;
+ NautilusCanvasContainer *container;
+ NautilusFile *file;
+
+ context = (CanvasGetDataBinderContext *) data;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (context->iterator_context));
+
+ container = NAUTILUS_CANVAS_CONTAINER (context->iterator_context);
+
+ world_rect = nautilus_canvas_item_get_icon_rectangle (icon->item);
+
+ canvas_rect_world_to_widget (EEL_CANVAS (container), &world_rect, &widget_rect);
+
+ uri = nautilus_canvas_container_get_icon_uri (container, icon);
+ file = nautilus_file_get_by_uri (uri);
+ g_free (uri);
+ uri = nautilus_canvas_container_get_icon_activation_uri (container, icon);
+
+ if (uri == NULL)
+ {
+ g_warning ("no URI for one of the iterated icons");
+ nautilus_file_unref (file);
+ return TRUE;
+ }
+
+ widget_rect = eel_irect_offset_by (widget_rect,
+ -container->details->dnd_info->drag_info.start_x,
+ -container->details->dnd_info->drag_info.start_y);
+
+ widget_rect = eel_irect_scale_by (widget_rect,
+ 1 / EEL_CANVAS (container)->pixels_per_unit);
+
+ /* pass the uri, mouse-relative x/y and icon width/height */
+ context->iteratee (uri,
+ (int) widget_rect.x0,
+ (int) widget_rect.y0,
+ widget_rect.x1 - widget_rect.x0,
+ widget_rect.y1 - widget_rect.y0,
+ context->iteratee_data);
+
+ g_free (uri);
+ nautilus_file_unref (file);
+
+ return TRUE;
+}
+
+typedef gboolean (*CanvasContainerEachFunc)(NautilusCanvasIcon *,
+ gpointer);
+
+/* Iterate over each selected icon in a NautilusCanvasContainer,
+ * calling each_function on each.
+ */
+static void
+nautilus_canvas_container_each_selected_icon (NautilusCanvasContainer *container,
+ CanvasContainerEachFunc each_function,
+ gpointer data)
+{
+ GList *p;
+ NautilusCanvasIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ if (!icon->is_selected)
+ {
+ continue;
+ }
+ if (!each_function (icon, data))
+ {
+ return;
+ }
+ }
+}
+
+/* Adaptor function used with nautilus_canvas_container_each_selected_icon
+ * to help iterate over all selected items, passing uris, x, y, w and h
+ * values to the iteratee
+ */
+static void
+each_icon_get_data_binder (NautilusDragEachSelectedItemDataGet iteratee,
+ gpointer iterator_context,
+ gpointer data)
+{
+ CanvasGetDataBinderContext context;
+ NautilusCanvasContainer *container;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (iterator_context));
+ container = NAUTILUS_CANVAS_CONTAINER (iterator_context);
+
+ context.iterator_context = iterator_context;
+ context.iteratee = iteratee;
+ context.iteratee_data = data;
+ nautilus_canvas_container_each_selected_icon (container, icon_get_data_binder, &context);
+}
+
+/* Called when the data for drag&drop is needed */
+static void
+drag_data_get_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint32 time,
+ gpointer data)
+{
+ NautilusDragInfo *drag_info;
+
+ g_assert (widget != NULL);
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (widget));
+ g_return_if_fail (context != NULL);
+
+ /* Call common function from nautilus-drag that set's up
+ * the selection data in the right format. Pass it means to
+ * iterate all the selected icons.
+ */
+ drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info);
+ nautilus_drag_drag_data_get_from_cache (drag_info->selection_cache, context, selection_data, info, time);
+}
+
+
+/* Target-side handling of the drag. */
+
+static void
+nautilus_canvas_container_position_shadow (NautilusCanvasContainer *container,
+ int x,
+ int y)
+{
+ EelCanvasItem *shadow;
+ double world_x, world_y;
+
+ shadow = container->details->dnd_info->shadow;
+ if (shadow == NULL)
+ {
+ return;
+ }
+
+ canvas_widget_to_world (EEL_CANVAS (container), x, y,
+ &world_x, &world_y);
+
+ set_shadow_position (shadow, world_x, world_y);
+ eel_canvas_item_show (shadow);
+}
+
+static void
+stop_cache_selection_list (NautilusDragInfo *drag_info)
+{
+ if (drag_info->file_list_info_handler)
+ {
+ nautilus_file_list_cancel_call_when_ready (drag_info->file_list_info_handler);
+ drag_info->file_list_info_handler = NULL;
+ }
+}
+
+static void
+cache_selection_list (NautilusDragInfo *drag_info)
+{
+ GList *files;
+
+ files = nautilus_drag_file_list_from_selection_list (drag_info->selection_list);
+ nautilus_file_list_call_when_ready (files,
+ NAUTILUS_FILE_ATTRIBUTE_INFO,
+ drag_info->file_list_info_handler,
+ NULL, NULL);
+
+ g_list_free_full (files, g_object_unref);
+}
+
+static void
+nautilus_canvas_container_dropped_canvas_feedback (GtkWidget *widget,
+ GtkSelectionData *data,
+ int x,
+ int y)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasDndInfo *dnd_info;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ dnd_info = container->details->dnd_info;
+
+ /* Delete old selection list. */
+ stop_cache_selection_list (&dnd_info->drag_info);
+ nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
+ dnd_info->drag_info.selection_list = NULL;
+
+ /* Delete old shadow if any. */
+ if (dnd_info->shadow != NULL)
+ {
+ /* FIXME bugzilla.gnome.org 42484:
+ * Is a destroy really sufficient here? Who does the unref? */
+ eel_canvas_item_destroy (dnd_info->shadow);
+ }
+
+ /* Build the selection list and the shadow. */
+ dnd_info->drag_info.selection_list = nautilus_drag_build_selection_list (data);
+ cache_selection_list (&dnd_info->drag_info);
+ dnd_info->shadow = create_selection_shadow (container, dnd_info->drag_info.selection_list);
+ nautilus_canvas_container_position_shadow (container, x, y);
+}
+
+static char *
+get_direct_save_filename (GdkDragContext *context)
+{
+ guchar *prop_text;
+ gint prop_len;
+
+ if (!gdk_property_get (gdk_drag_context_get_source_window (context), gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
+ gdk_atom_intern ("text/plain", FALSE), 0, 1024, FALSE, NULL, NULL,
+ &prop_len, &prop_text))
+ {
+ return NULL;
+ }
+
+ /* Zero-terminate the string */
+ prop_text = g_realloc (prop_text, prop_len + 1);
+ prop_text[prop_len] = '\0';
+
+ /* Verify that the file name provided by the source is valid */
+ if (*prop_text == '\0' ||
+ strchr ((const gchar *) prop_text, G_DIR_SEPARATOR) != NULL)
+ {
+ DEBUG ("Invalid filename provided by XDS drag site");
+ g_free (prop_text);
+ return NULL;
+ }
+
+ return (gchar *) prop_text;
+}
+
+static void
+set_direct_save_uri (GtkWidget *widget,
+ GdkDragContext *context,
+ NautilusDragInfo *drag_info,
+ int x,
+ int y)
+{
+ GFile *base, *child;
+ char *filename, *drop_target;
+ gchar *uri;
+
+ drag_info->got_drop_data_type = TRUE;
+ drag_info->data_type = NAUTILUS_ICON_DND_XDNDDIRECTSAVE;
+
+ uri = NULL;
+
+ filename = get_direct_save_filename (context);
+ drop_target = nautilus_canvas_container_find_drop_target (NAUTILUS_CANVAS_CONTAINER (widget),
+ context, x, y, NULL);
+
+ if (drop_target && eel_uri_is_trash (drop_target))
+ {
+ g_free (drop_target);
+ drop_target = NULL; /* Cannot save to trash ...*/
+ }
+
+ if (filename != NULL && drop_target != NULL)
+ {
+ /* Resolve relative path */
+ base = g_file_new_for_uri (drop_target);
+ child = g_file_get_child (base, filename);
+ uri = g_file_get_uri (child);
+ g_object_unref (base);
+ g_object_unref (child);
+
+ /* Change the uri property */
+ gdk_property_change (gdk_drag_context_get_source_window (context),
+ gdk_atom_intern (NAUTILUS_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
+ gdk_atom_intern ("text/plain", FALSE), 8,
+ GDK_PROP_MODE_REPLACE, (const guchar *) uri,
+ strlen (uri));
+
+ drag_info->direct_save_uri = uri;
+ }
+
+ g_free (filename);
+ g_free (drop_target);
+}
+
+/* FIXME bugzilla.gnome.org 47445: Needs to become a shared function */
+static void
+get_data_on_first_target_we_support (GtkWidget *widget,
+ GdkDragContext *context,
+ guint32 time,
+ int x,
+ int y)
+{
+ GtkTargetList *list;
+ GdkAtom target;
+
+ if (drop_types_list == NULL)
+ {
+ drop_types_list = gtk_target_list_new (drop_types,
+ G_N_ELEMENTS (drop_types) - 1);
+ gtk_target_list_add_text_targets (drop_types_list, NAUTILUS_ICON_DND_TEXT);
+ }
+ if (drop_types_list_root == NULL)
+ {
+ drop_types_list_root = gtk_target_list_new (drop_types,
+ G_N_ELEMENTS (drop_types));
+ gtk_target_list_add_text_targets (drop_types_list_root, NAUTILUS_ICON_DND_TEXT);
+ }
+
+ list = drop_types_list;
+
+ target = gtk_drag_dest_find_target (widget, context, list);
+ if (target != GDK_NONE)
+ {
+ guint info;
+ NautilusDragInfo *drag_info;
+ gboolean found;
+
+ drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info);
+
+ found = gtk_target_list_find (list, target, &info);
+ g_assert (found);
+
+ /* Don't get_data for destructive ops */
+ if ((info == NAUTILUS_ICON_DND_ROOTWINDOW_DROP ||
+ info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE) &&
+ !drag_info->drop_occurred)
+ {
+ /* We can't call get_data here, because that would
+ * make the source execute the rootwin action or the direct save */
+ drag_info->got_drop_data_type = TRUE;
+ drag_info->data_type = info;
+ }
+ else
+ {
+ if (info == NAUTILUS_ICON_DND_XDNDDIRECTSAVE)
+ {
+ set_direct_save_uri (widget, context, drag_info, x, y);
+ }
+ gtk_drag_get_data (GTK_WIDGET (widget), context,
+ target, time);
+ }
+ }
+}
+
+static void
+nautilus_canvas_container_ensure_drag_data (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ guint32 time)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = container->details->dnd_info;
+
+ if (!dnd_info->drag_info.got_drop_data_type)
+ {
+ get_data_on_first_target_we_support (GTK_WIDGET (container), context, time, 0, 0);
+ }
+}
+
+static void
+drag_end_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer data)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasDndInfo *dnd_info;
+ NautilusWindow *window;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ window = NAUTILUS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (container)));
+ dnd_info = container->details->dnd_info;
+
+ stop_cache_selection_list (&dnd_info->drag_info);
+ nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_list);
+ nautilus_drag_destroy_selection_list (dnd_info->drag_info.selection_cache);
+ nautilus_drag_destroy_selection_list (container->details->dnd_source_info->selection_cache);
+ dnd_info->drag_info.selection_list = NULL;
+ dnd_info->drag_info.selection_cache = NULL;
+ container->details->dnd_source_info->selection_cache = NULL;
+
+ nautilus_window_end_dnd (window, context);
+}
+
+static NautilusCanvasIcon *
+nautilus_canvas_container_item_at (NautilusCanvasContainer *container,
+ int x,
+ int y)
+{
+ GList *p;
+ int size;
+ EelDRect point;
+ EelIRect canvas_point;
+
+ /* build the hit-test rectangle. Base the size on the scale factor to ensure that it is
+ * non-empty even at the smallest scale factor
+ */
+
+ size = MAX (1, 1 + (1 / EEL_CANVAS (container)->pixels_per_unit));
+ point.x0 = x;
+ point.y0 = y;
+ point.x1 = x + size;
+ point.y1 = y + size;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ NautilusCanvasIcon *icon;
+ icon = p->data;
+
+ eel_canvas_w2c (EEL_CANVAS (container),
+ point.x0,
+ point.y0,
+ &canvas_point.x0,
+ &canvas_point.y0);
+ eel_canvas_w2c (EEL_CANVAS (container),
+ point.x1,
+ point.y1,
+ &canvas_point.x1,
+ &canvas_point.y1);
+ if (nautilus_canvas_item_hit_test_rectangle (icon->item, canvas_point))
+ {
+ return icon;
+ }
+ }
+
+ return NULL;
+}
+
+static char *
+get_container_uri (NautilusCanvasContainer *container)
+{
+ char *uri;
+
+ /* get the URI associated with the container */
+ uri = NULL;
+ g_signal_emit_by_name (container, "get-container-uri", &uri);
+ return uri;
+}
+
+static gboolean
+nautilus_canvas_container_selection_items_local (NautilusCanvasContainer *container,
+ GList *items)
+{
+ char *container_uri_string;
+ gboolean result;
+
+ /* must have at least one item */
+ g_assert (items);
+
+ /* get the URI associated with the container */
+ container_uri_string = get_container_uri (container);
+
+ result = nautilus_drag_items_local (container_uri_string, items);
+
+ g_free (container_uri_string);
+
+ return result;
+}
+
+/* handle dropped url */
+static void
+receive_dropped_netscape_url (NautilusCanvasContainer *container,
+ const char *encoded_url,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+
+ if (encoded_url == NULL)
+ {
+ return;
+ }
+
+ drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL);
+
+ g_signal_emit_by_name (container, "handle-netscape-url",
+ encoded_url,
+ drop_target,
+ gdk_drag_context_get_selected_action (context));
+
+ g_free (drop_target);
+}
+
+/* handle dropped uri list */
+static void
+receive_dropped_uri_list (NautilusCanvasContainer *container,
+ const char *uri_list,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+
+ if (uri_list == NULL)
+ {
+ return;
+ }
+
+ drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL);
+
+ g_signal_emit_by_name (container, "handle-uri-list",
+ uri_list,
+ drop_target,
+ gdk_drag_context_get_selected_action (context));
+
+ g_free (drop_target);
+}
+
+/* handle dropped text */
+static void
+receive_dropped_text (NautilusCanvasContainer *container,
+ const char *text,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+
+ if (text == NULL)
+ {
+ return;
+ }
+
+ drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL);
+
+ g_signal_emit_by_name (container, "handle-text",
+ text,
+ drop_target,
+ gdk_drag_context_get_selected_action (context));
+
+ g_free (drop_target);
+}
+
+/* handle dropped raw data */
+static void
+receive_dropped_raw (NautilusCanvasContainer *container,
+ const char *raw_data,
+ int length,
+ const char *direct_save_uri,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+
+ if (raw_data == NULL)
+ {
+ return;
+ }
+
+ drop_target = nautilus_canvas_container_find_drop_target (container, context, x, y, NULL);
+
+ g_signal_emit_by_name (container, "handle-raw",
+ raw_data,
+ length,
+ drop_target,
+ direct_save_uri,
+ gdk_drag_context_get_selected_action (context));
+
+ g_free (drop_target);
+}
+
+static int
+auto_scroll_timeout_callback (gpointer data)
+{
+ NautilusCanvasContainer *container;
+ GtkWidget *widget;
+ float x_scroll_delta, y_scroll_delta;
+ GdkRectangle exposed_area;
+ GtkAllocation allocation;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (data));
+ widget = GTK_WIDGET (data);
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+
+ if (container->details->dnd_info->drag_info.waiting_to_autoscroll
+ && container->details->dnd_info->drag_info.start_auto_scroll_in > g_get_monotonic_time ())
+ {
+ /* not yet */
+ return TRUE;
+ }
+
+ container->details->dnd_info->drag_info.waiting_to_autoscroll = FALSE;
+
+ nautilus_drag_autoscroll_calculate_delta (widget, &x_scroll_delta, &y_scroll_delta);
+ if (x_scroll_delta == 0 && y_scroll_delta == 0)
+ {
+ /* no work */
+ return TRUE;
+ }
+
+ /* Clear the old dnd highlight frame */
+ dnd_highlight_queue_redraw (widget);
+
+ if (!nautilus_canvas_container_scroll (container, (int) x_scroll_delta, (int) y_scroll_delta))
+ {
+ /* the scroll value got pinned to a min or max adjustment value,
+ * we ended up not scrolling
+ */
+ return TRUE;
+ }
+
+ /* Make sure the dnd highlight frame is redrawn */
+ dnd_highlight_queue_redraw (widget);
+
+ /* update cached drag start offsets */
+ container->details->dnd_info->drag_info.start_x -= x_scroll_delta;
+ container->details->dnd_info->drag_info.start_y -= y_scroll_delta;
+
+ /* Due to a glitch in GtkLayout, whe need to do an explicit draw of the exposed
+ * area.
+ * Calculate the size of the area we need to draw
+ */
+ gtk_widget_get_allocation (widget, &allocation);
+ exposed_area.x = allocation.x;
+ exposed_area.y = allocation.y;
+ exposed_area.width = allocation.width;
+ exposed_area.height = allocation.height;
+
+ if (x_scroll_delta > 0)
+ {
+ exposed_area.x = exposed_area.width - x_scroll_delta;
+ }
+ else if (x_scroll_delta < 0)
+ {
+ exposed_area.width = -x_scroll_delta;
+ }
+
+ if (y_scroll_delta > 0)
+ {
+ exposed_area.y = exposed_area.height - y_scroll_delta;
+ }
+ else if (y_scroll_delta < 0)
+ {
+ exposed_area.height = -y_scroll_delta;
+ }
+
+ /* offset it to 0, 0 */
+ exposed_area.x -= allocation.x;
+ exposed_area.y -= allocation.y;
+
+ gtk_widget_queue_draw_area (widget,
+ exposed_area.x,
+ exposed_area.y,
+ exposed_area.width,
+ exposed_area.height);
+
+ return TRUE;
+}
+
+static void
+set_up_auto_scroll_if_needed (NautilusCanvasContainer *container)
+{
+ nautilus_drag_autoscroll_start (&container->details->dnd_info->drag_info,
+ GTK_WIDGET (container),
+ auto_scroll_timeout_callback,
+ container);
+}
+
+static void
+stop_auto_scroll (NautilusCanvasContainer *container)
+{
+ nautilus_drag_autoscroll_stop (&container->details->dnd_info->drag_info);
+}
+
+static void
+handle_nonlocal_move (NautilusCanvasContainer *container,
+ GdkDragAction action,
+ const char *target_uri,
+ gboolean icon_hit)
+{
+ GList *source_uris, *p;
+ gboolean free_target_uri;
+
+ if (container->details->dnd_info->drag_info.selection_list == NULL)
+ {
+ return;
+ }
+
+ source_uris = NULL;
+ for (p = container->details->dnd_info->drag_info.selection_list; p != NULL; p = p->next)
+ {
+ /* do a shallow copy of all the uri strings of the copied files */
+ source_uris = g_list_prepend (source_uris, ((NautilusDragSelectionItem *) p->data)->uri);
+ }
+ source_uris = g_list_reverse (source_uris);
+
+ free_target_uri = FALSE;
+
+ /* start the copy */
+ g_signal_emit_by_name (container, "move-copy-items",
+ source_uris,
+ target_uri,
+ action);
+
+ if (free_target_uri)
+ {
+ g_free ((char *) target_uri);
+ }
+
+ g_list_free (source_uris);
+}
+
+static char *
+nautilus_canvas_container_find_drop_target (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ int x,
+ int y,
+ gboolean *icon_hit)
+{
+ NautilusCanvasIcon *drop_target_icon;
+ double world_x, world_y;
+ NautilusFile *file;
+ char *icon_uri;
+ char *container_uri;
+
+ if (icon_hit)
+ {
+ *icon_hit = FALSE;
+ }
+
+ if (!container->details->dnd_info->drag_info.got_drop_data_type)
+ {
+ return NULL;
+ }
+
+ canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y);
+
+ /* FIXME bugzilla.gnome.org 42485:
+ * These "can_accept_items" tests need to be done by
+ * the canvas view, not here. This file is not supposed to know
+ * that the target is a file.
+ */
+
+ /* Find the item we hit with our drop, if any */
+ drop_target_icon = nautilus_canvas_container_item_at (container, world_x, world_y);
+ if (drop_target_icon != NULL)
+ {
+ icon_uri = nautilus_canvas_container_get_icon_uri (container, drop_target_icon);
+ if (icon_uri != NULL)
+ {
+ file = nautilus_file_get_by_uri (icon_uri);
+
+ if (!nautilus_drag_can_accept_info (file,
+ container->details->dnd_info->drag_info.data_type,
+ container->details->dnd_info->drag_info.selection_list))
+ {
+ /* the item we dropped our selection on cannot accept the items,
+ * do the same thing as if we just dropped the items on the canvas
+ */
+ drop_target_icon = NULL;
+ }
+
+ g_free (icon_uri);
+ nautilus_file_unref (file);
+ }
+ }
+
+ if (drop_target_icon == NULL)
+ {
+ if (icon_hit)
+ {
+ *icon_hit = FALSE;
+ }
+
+ container_uri = get_container_uri (container);
+
+ if (container_uri != NULL)
+ {
+ gboolean can;
+ file = nautilus_file_get_by_uri (container_uri);
+ can = nautilus_drag_can_accept_info (file,
+ container->details->dnd_info->drag_info.data_type,
+ container->details->dnd_info->drag_info.selection_list);
+ g_object_unref (file);
+ if (!can)
+ {
+ g_free (container_uri);
+ container_uri = NULL;
+ }
+ }
+
+ return container_uri;
+ }
+
+ if (icon_hit)
+ {
+ *icon_hit = TRUE;
+ }
+ return nautilus_canvas_container_get_icon_drop_target_uri (container, drop_target_icon);
+}
+
+static void
+nautilus_canvas_container_receive_dropped_icons (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ char *drop_target;
+ gboolean local_move_only;
+ double world_x, world_y;
+ gboolean icon_hit;
+ GdkDragAction action, real_action;
+
+ drop_target = NULL;
+
+ if (container->details->dnd_info->drag_info.selection_list == NULL)
+ {
+ return;
+ }
+
+ real_action = gdk_drag_context_get_selected_action (context);
+
+ if (real_action == GDK_ACTION_ASK)
+ {
+ action = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK;
+ real_action = nautilus_drag_drop_action_ask (GTK_WIDGET (container), action);
+ }
+
+ if (real_action > 0)
+ {
+ eel_canvas_window_to_world (EEL_CANVAS (container),
+ x + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container))),
+ y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))),
+ &world_x, &world_y);
+
+ drop_target = nautilus_canvas_container_find_drop_target (container,
+ context, x, y, &icon_hit);
+
+ local_move_only = FALSE;
+ if (!icon_hit && real_action == GDK_ACTION_MOVE)
+ {
+ local_move_only = nautilus_canvas_container_selection_items_local
+ (container, container->details->dnd_info->drag_info.selection_list);
+ }
+
+ /* If the move is local, there is nothing to do. */
+ if (!local_move_only)
+ {
+ handle_nonlocal_move (container, real_action, drop_target, icon_hit);
+ }
+ }
+
+ g_free (drop_target);
+ stop_cache_selection_list (&container->details->dnd_info->drag_info);
+ nautilus_drag_destroy_selection_list (container->details->dnd_info->drag_info.selection_list);
+ container->details->dnd_info->drag_info.selection_list = NULL;
+}
+
+NautilusDragInfo *
+nautilus_canvas_dnd_get_drag_source_data (NautilusCanvasContainer *container,
+ GdkDragContext *context)
+{
+ return container->details->dnd_source_info;
+}
+
+static void
+nautilus_canvas_container_get_drop_action (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ int x,
+ int y,
+ int *action)
+{
+ char *drop_target;
+ gboolean icon_hit;
+ double world_x, world_y;
+
+ icon_hit = FALSE;
+ if (!container->details->dnd_info->drag_info.got_drop_data_type)
+ {
+ /* drag_data_received_callback didn't get called yet */
+ return;
+ }
+
+ /* find out if we're over an canvas */
+ canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y);
+ *action = 0;
+
+ drop_target = nautilus_canvas_container_find_drop_target (container,
+ context, x, y, &icon_hit);
+ if (drop_target == NULL)
+ {
+ return;
+ }
+
+ /* case out on the type of object being dragged */
+ switch (container->details->dnd_info->drag_info.data_type)
+ {
+ case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
+ {
+ if (container->details->dnd_info->drag_info.selection_list != NULL)
+ {
+ nautilus_drag_default_drop_action_for_icons (context, drop_target,
+ container->details->dnd_info->drag_info.selection_list,
+ 0,
+ action);
+ }
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_URI_LIST:
+ {
+ *action = nautilus_drag_default_drop_action_for_uri_list (context, drop_target);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_NETSCAPE_URL:
+ {
+ *action = nautilus_drag_default_drop_action_for_netscape_url (context);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_ROOTWINDOW_DROP:
+ {
+ *action = gdk_drag_context_get_suggested_action (context);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_TEXT:
+ case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
+ case NAUTILUS_ICON_DND_RAW:
+ {
+ *action = GDK_ACTION_COPY;
+ }
+ break;
+ }
+
+ g_free (drop_target);
+}
+
+static void
+set_drop_target (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *icon)
+{
+ NautilusCanvasIcon *old_icon;
+
+ /* Check if current drop target changed, update icon drop
+ * higlight if needed.
+ */
+ old_icon = container->details->drop_target;
+ if (icon == old_icon)
+ {
+ return;
+ }
+
+ /* Remember the new drop target for the next round. */
+ container->details->drop_target = icon;
+ nautilus_canvas_container_update_icon (container, old_icon);
+ nautilus_canvas_container_update_icon (container, icon);
+}
+
+static void
+nautilus_canvas_dnd_update_drop_target (NautilusCanvasContainer *container,
+ GdkDragContext *context,
+ int x,
+ int y)
+{
+ NautilusCanvasIcon *icon;
+ NautilusFile *file;
+ double world_x, world_y;
+ char *uri;
+
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ canvas_widget_to_world (EEL_CANVAS (container), x, y, &world_x, &world_y);
+
+ /* Find the item we hit with our drop, if any. */
+ icon = nautilus_canvas_container_item_at (container, world_x, world_y);
+
+ /* FIXME bugzilla.gnome.org 42485:
+ * These "can_accept_items" tests need to be done by
+ * the canvas view, not here. This file is not supposed to know
+ * that the target is a file.
+ */
+
+ /* Find if target canvas accepts our drop. */
+ if (icon != NULL)
+ {
+ uri = nautilus_canvas_container_get_icon_uri (container, icon);
+ file = nautilus_file_get_by_uri (uri);
+ g_free (uri);
+
+ if (!nautilus_drag_can_accept_info (file,
+ container->details->dnd_info->drag_info.data_type,
+ container->details->dnd_info->drag_info.selection_list))
+ {
+ icon = NULL;
+ }
+
+ nautilus_file_unref (file);
+ }
+
+ set_drop_target (container, icon);
+}
+
+static void
+remove_hover_timer (NautilusCanvasDndInfo *dnd_info)
+{
+ if (dnd_info->hover_id != 0)
+ {
+ g_source_remove (dnd_info->hover_id);
+ dnd_info->hover_id = 0;
+ }
+}
+
+static void
+nautilus_canvas_container_free_drag_data (NautilusCanvasContainer *container)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = container->details->dnd_info;
+
+ dnd_info->drag_info.got_drop_data_type = FALSE;
+
+ if (dnd_info->shadow != NULL)
+ {
+ eel_canvas_item_destroy (dnd_info->shadow);
+ dnd_info->shadow = NULL;
+ }
+
+ if (dnd_info->drag_info.selection_data != NULL)
+ {
+ gtk_selection_data_free (dnd_info->drag_info.selection_data);
+ dnd_info->drag_info.selection_data = NULL;
+ }
+
+ if (dnd_info->drag_info.direct_save_uri != NULL)
+ {
+ g_free (dnd_info->drag_info.direct_save_uri);
+ dnd_info->drag_info.direct_save_uri = NULL;
+ }
+
+ g_free (dnd_info->target_uri);
+ dnd_info->target_uri = NULL;
+
+ remove_hover_timer (dnd_info);
+}
+
+static void
+drag_leave_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ guint32 time,
+ gpointer data)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
+
+ if (dnd_info->shadow != NULL)
+ {
+ eel_canvas_item_hide (dnd_info->shadow);
+ }
+
+ stop_dnd_highlight (widget);
+
+ set_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), NULL);
+ stop_auto_scroll (NAUTILUS_CANVAS_CONTAINER (widget));
+ nautilus_canvas_container_free_drag_data (NAUTILUS_CANVAS_CONTAINER (widget));
+}
+
+static void
+drag_begin_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer data)
+{
+ NautilusCanvasContainer *container;
+ NautilusDragInfo *drag_info;
+ NautilusWindow *window;
+ cairo_surface_t *surface;
+ double x1, y1, x2, y2, winx, winy;
+ int x_offset, y_offset;
+ int start_x, start_y;
+ GList *dragged_files;
+ double sx, sy;
+
+ container = NAUTILUS_CANVAS_CONTAINER (widget);
+ window = NAUTILUS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (container)));
+
+ start_x = container->details->dnd_info->drag_info.start_x +
+ gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
+ start_y = container->details->dnd_info->drag_info.start_y +
+ gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
+
+ /* create a pixmap and mask to drag with */
+ surface = nautilus_canvas_item_get_drag_surface (container->details->drag_icon->item);
+
+ /* compute the image's offset */
+ eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (container->details->drag_icon->item),
+ &x1, &y1, &x2, &y2);
+ eel_canvas_world_to_window (EEL_CANVAS (container),
+ x1, y1, &winx, &winy);
+ x_offset = start_x - winx;
+ y_offset = start_y - winy;
+
+ cairo_surface_get_device_scale (surface, &sx, &sy);
+ cairo_surface_set_device_offset (surface,
+ -x_offset * sx,
+ -y_offset * sy);
+ gtk_drag_set_icon_surface (context, surface);
+ cairo_surface_destroy (surface);
+
+ /* cache the data at the beginning since the view may change */
+ drag_info = &(container->details->dnd_info->drag_info);
+ drag_info->selection_cache = nautilus_drag_create_selection_cache (widget,
+ each_icon_get_data_binder);
+
+ container->details->dnd_source_info->selection_cache = nautilus_drag_create_selection_cache (widget,
+ each_icon_get_data_binder);
+
+ dragged_files = nautilus_drag_file_list_from_selection_list (drag_info->selection_cache);
+ if (nautilus_file_list_are_all_folders (dragged_files))
+ {
+ nautilus_window_start_dnd (window, context);
+ }
+ g_list_free_full (dragged_files, g_object_unref);
+}
+
+void
+nautilus_canvas_dnd_begin_drag (NautilusCanvasContainer *container,
+ GdkDragAction actions,
+ int button,
+ GdkEventMotion *event,
+ int start_x,
+ int start_y)
+{
+ NautilusCanvasDndInfo *dnd_info;
+ NautilusDragInfo *dnd_source_info;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_return_if_fail (event != NULL);
+
+ dnd_info = container->details->dnd_info;
+ container->details->dnd_source_info = g_new0 (NautilusDragInfo, 1);
+ dnd_source_info = container->details->dnd_source_info;
+ g_return_if_fail (dnd_info != NULL);
+
+ /* Notice that the event is in bin_window coordinates, because of
+ * the way the canvas handles events.
+ */
+ dnd_info->drag_info.start_x = start_x -
+ gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
+ dnd_info->drag_info.start_y = start_y -
+ gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
+
+ dnd_source_info->source_actions = actions;
+ /* start the drag */
+ gtk_drag_begin_with_coordinates (GTK_WIDGET (container),
+ dnd_info->drag_info.target_list,
+ actions,
+ button,
+ (GdkEvent *) event,
+ dnd_info->drag_info.start_x,
+ dnd_info->drag_info.start_y);
+}
+
+static gboolean
+drag_highlight_draw (GtkWidget *widget,
+ cairo_t *cr,
+ gpointer user_data)
+{
+ gint width, height;
+ GdkWindow *window;
+ GtkStyleContext *style;
+
+ window = gtk_widget_get_window (widget);
+ width = gdk_window_get_width (window);
+ height = gdk_window_get_height (window);
+
+ style = gtk_widget_get_style_context (widget);
+
+ gtk_style_context_save (style);
+ gtk_style_context_add_class (style, GTK_STYLE_CLASS_DND);
+ gtk_style_context_set_state (style, GTK_STATE_FLAG_FOCUSED);
+
+ gtk_render_frame (style,
+ cr,
+ 0, 0, width, height);
+
+ gtk_style_context_restore (style);
+
+ return FALSE;
+}
+
+/* Queue a redraw of the dnd highlight rect */
+static void
+dnd_highlight_queue_redraw (GtkWidget *widget)
+{
+ NautilusCanvasDndInfo *dnd_info;
+ int width, height;
+ GtkAllocation allocation;
+
+ dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
+
+ if (!dnd_info->highlighted)
+ {
+ return;
+ }
+
+ gtk_widget_get_allocation (widget, &allocation);
+ width = allocation.width;
+ height = allocation.height;
+
+ /* we don't know how wide the shadow is exactly,
+ * so we expose a 10-pixel wide border
+ */
+ gtk_widget_queue_draw_area (widget,
+ 0, 0,
+ width, 10);
+ gtk_widget_queue_draw_area (widget,
+ 0, 0,
+ 10, height);
+ gtk_widget_queue_draw_area (widget,
+ 0, height - 10,
+ width, 10);
+ gtk_widget_queue_draw_area (widget,
+ width - 10, 0,
+ 10, height);
+}
+
+static void
+start_dnd_highlight (GtkWidget *widget)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
+
+ if (!dnd_info->highlighted)
+ {
+ dnd_info->highlighted = TRUE;
+ g_signal_connect_after (widget, "draw",
+ G_CALLBACK (drag_highlight_draw),
+ NULL);
+ dnd_highlight_queue_redraw (widget);
+ }
+}
+
+static void
+stop_dnd_highlight (GtkWidget *widget)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
+
+ if (dnd_info->highlighted)
+ {
+ g_signal_handlers_disconnect_by_func (widget,
+ drag_highlight_draw,
+ NULL);
+ dnd_highlight_queue_redraw (widget);
+ dnd_info->highlighted = FALSE;
+ }
+}
+
+static gboolean
+hover_timer (gpointer user_data)
+{
+ NautilusCanvasContainer *container = user_data;
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = container->details->dnd_info;
+
+ dnd_info->hover_id = 0;
+
+ g_signal_emit_by_name (container, "handle-hover", dnd_info->target_uri);
+
+ return FALSE;
+}
+
+static void
+check_hover_timer (NautilusCanvasContainer *container,
+ const char *uri)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = container->details->dnd_info;
+
+ if (g_strcmp0 (uri, dnd_info->target_uri) == 0)
+ {
+ return;
+ }
+
+ remove_hover_timer (dnd_info);
+
+ g_clear_pointer (&dnd_info->target_uri, g_free);
+
+ if (uri != NULL)
+ {
+ dnd_info->target_uri = g_strdup (uri);
+ dnd_info->hover_id = g_timeout_add (HOVER_TIMEOUT, hover_timer, container);
+ }
+}
+
+static gboolean
+drag_motion_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ guint32 time)
+{
+ int action;
+
+ nautilus_canvas_container_ensure_drag_data (NAUTILUS_CANVAS_CONTAINER (widget), context, time);
+ nautilus_canvas_container_position_shadow (NAUTILUS_CANVAS_CONTAINER (widget), x, y);
+ nautilus_canvas_dnd_update_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), context, x, y);
+ set_up_auto_scroll_if_needed (NAUTILUS_CANVAS_CONTAINER (widget));
+ /* Find out what the drop actions are based on our drag selection and
+ * the drop target.
+ */
+ action = 0;
+ nautilus_canvas_container_get_drop_action (NAUTILUS_CANVAS_CONTAINER (widget), context, x, y,
+ &action);
+ if (action != 0)
+ {
+ char *uri;
+ uri = nautilus_canvas_container_find_drop_target (NAUTILUS_CANVAS_CONTAINER (widget),
+ context, x, y, NULL);
+ check_hover_timer (NAUTILUS_CANVAS_CONTAINER (widget), uri);
+ g_free (uri);
+ start_dnd_highlight (widget);
+ }
+ else
+ {
+ remove_hover_timer (NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info);
+ }
+
+ gdk_drag_status (context, action, time);
+
+ return TRUE;
+}
+
+static gboolean
+drag_drop_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ guint32 time,
+ gpointer data)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ dnd_info = NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info;
+
+ /* tell the drag_data_received callback that
+ * the drop occurred and that it can actually
+ * process the actions.
+ * make sure it is going to be called at least once.
+ */
+ dnd_info->drag_info.drop_occurred = TRUE;
+
+ get_data_on_first_target_we_support (widget, context, time, x, y);
+
+ return TRUE;
+}
+
+void
+nautilus_canvas_dnd_end_drag (NautilusCanvasContainer *container)
+{
+ NautilusCanvasDndInfo *dnd_info;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ dnd_info = container->details->dnd_info;
+ g_return_if_fail (dnd_info != NULL);
+ stop_auto_scroll (container);
+ /* Do nothing.
+ * Can that possibly be right?
+ */
+}
+
+/** this callback is called in 2 cases.
+ * It is called upon drag_motion events to get the actual data
+ * In that case, it just makes sure it gets the data.
+ * It is called upon drop_drop events to execute the actual
+ * actions on the received action. In that case, it actually first makes sure
+ * that we have got the data then processes it.
+ */
+
+static void
+drag_data_received_callback (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *data,
+ guint info,
+ guint32 time,
+ gpointer user_data)
+{
+ NautilusDragInfo *drag_info;
+ guchar *tmp;
+ const guchar *tmp_raw;
+ int length;
+ gboolean success;
+
+ drag_info = &(NAUTILUS_CANVAS_CONTAINER (widget)->details->dnd_info->drag_info);
+
+ drag_info->got_drop_data_type = TRUE;
+ drag_info->data_type = info;
+
+ switch (info)
+ {
+ case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
+ {
+ nautilus_canvas_container_dropped_canvas_feedback (widget, data, x, y);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_URI_LIST:
+ case NAUTILUS_ICON_DND_TEXT:
+ case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
+ case NAUTILUS_ICON_DND_RAW:
+ {
+ /* Save the data so we can do the actual work on drop. */
+ if (drag_info->selection_data != NULL)
+ {
+ gtk_selection_data_free (drag_info->selection_data);
+ }
+ drag_info->selection_data = gtk_selection_data_copy (data);
+ }
+ break;
+
+ /* Netscape keeps sending us the data, even though we accept the first drag */
+ case NAUTILUS_ICON_DND_NETSCAPE_URL:
+ {
+ if (drag_info->selection_data != NULL)
+ {
+ gtk_selection_data_free (drag_info->selection_data);
+ drag_info->selection_data = gtk_selection_data_copy (data);
+ }
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_ROOTWINDOW_DROP:
+ {
+ /* Do nothing, this won't even happen, since we don't want to call get_data twice */
+ }
+ break;
+ }
+
+ /* this is the second use case of this callback.
+ * we have to do the actual work for the drop.
+ */
+ if (drag_info->drop_occurred)
+ {
+ success = FALSE;
+ switch (info)
+ {
+ case NAUTILUS_ICON_DND_GNOME_ICON_LIST:
+ {
+ nautilus_canvas_container_receive_dropped_icons
+ (NAUTILUS_CANVAS_CONTAINER (widget),
+ context, x, y);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_NETSCAPE_URL:
+ {
+ receive_dropped_netscape_url
+ (NAUTILUS_CANVAS_CONTAINER (widget),
+ (char *) gtk_selection_data_get_data (data), context, x, y);
+ success = TRUE;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_URI_LIST:
+ {
+ receive_dropped_uri_list
+ (NAUTILUS_CANVAS_CONTAINER (widget),
+ (char *) gtk_selection_data_get_data (data), context, x, y);
+ success = TRUE;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_TEXT:
+ {
+ tmp = gtk_selection_data_get_text (data);
+ receive_dropped_text
+ (NAUTILUS_CANVAS_CONTAINER (widget),
+ (char *) tmp, context, x, y);
+ success = TRUE;
+ g_free (tmp);
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_RAW:
+ {
+ length = gtk_selection_data_get_length (data);
+ tmp_raw = gtk_selection_data_get_data (data);
+ receive_dropped_raw
+ (NAUTILUS_CANVAS_CONTAINER (widget),
+ (const gchar *) tmp_raw, length, drag_info->direct_save_uri,
+ context, x, y);
+ success = TRUE;
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_ROOTWINDOW_DROP:
+ {
+ /* Do nothing, everything is done by the sender */
+ }
+ break;
+
+ case NAUTILUS_ICON_DND_XDNDDIRECTSAVE:
+ {
+ const guchar *selection_data;
+ gint selection_length;
+ gint selection_format;
+
+ selection_data = gtk_selection_data_get_data (drag_info->selection_data);
+ selection_length = gtk_selection_data_get_length (drag_info->selection_data);
+ selection_format = gtk_selection_data_get_format (drag_info->selection_data);
+
+ if (selection_format == 8 &&
+ selection_length == 1 &&
+ selection_data[0] == 'F')
+ {
+ gtk_drag_get_data (widget, context,
+ gdk_atom_intern (NAUTILUS_ICON_DND_RAW_TYPE,
+ FALSE),
+ time);
+ return;
+ }
+ else if (selection_format == 8 &&
+ selection_length == 1 &&
+ selection_data[0] == 'F' &&
+ drag_info->direct_save_uri != NULL)
+ {
+ GFile *location;
+
+ location = g_file_new_for_uri (drag_info->direct_save_uri);
+
+ nautilus_file_changes_queue_file_added (location);
+ g_object_unref (location);
+ nautilus_file_changes_consume_changes (TRUE);
+ success = TRUE;
+ }
+ } /* NAUTILUS_ICON_DND_XDNDDIRECTSAVE */
+ break;
+ }
+ gtk_drag_finish (context, success, FALSE, time);
+
+ nautilus_canvas_container_free_drag_data (NAUTILUS_CANVAS_CONTAINER (widget));
+
+ set_drop_target (NAUTILUS_CANVAS_CONTAINER (widget), NULL);
+
+ /* reinitialise it for the next dnd */
+ drag_info->drop_occurred = FALSE;
+ }
+}
+
+void
+nautilus_canvas_dnd_init (NautilusCanvasContainer *container)
+{
+ GtkTargetList *targets;
+ int n_elements;
+
+ g_return_if_fail (container != NULL);
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+
+ container->details->dnd_info = g_new0 (NautilusCanvasDndInfo, 1);
+ nautilus_drag_init (&container->details->dnd_info->drag_info,
+ drag_types, G_N_ELEMENTS (drag_types), TRUE);
+
+ /* 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.)
+ */
+ n_elements = G_N_ELEMENTS (drop_types) - 1;
+ gtk_drag_dest_set (GTK_WIDGET (container),
+ 0,
+ drop_types, n_elements,
+ GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
+
+ targets = gtk_drag_dest_get_target_list (GTK_WIDGET (container));
+ gtk_target_list_add_text_targets (targets, NAUTILUS_ICON_DND_TEXT);
+
+
+ /* Messages for outgoing drag. */
+ g_signal_connect (container, "drag-begin",
+ G_CALLBACK (drag_begin_callback), NULL);
+ g_signal_connect (container, "drag-data-get",
+ G_CALLBACK (drag_data_get_callback), NULL);
+ g_signal_connect (container, "drag-end",
+ G_CALLBACK (drag_end_callback), NULL);
+
+ /* Messages for incoming drag. */
+ g_signal_connect (container, "drag-data-received",
+ G_CALLBACK (drag_data_received_callback), NULL);
+ g_signal_connect (container, "drag-motion",
+ G_CALLBACK (drag_motion_callback), NULL);
+ g_signal_connect (container, "drag-drop",
+ G_CALLBACK (drag_drop_callback), NULL);
+ g_signal_connect (container, "drag-leave",
+ G_CALLBACK (drag_leave_callback), NULL);
+}
+
+void
+nautilus_canvas_dnd_fini (NautilusCanvasContainer *container)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container));
+
+ if (container->details->dnd_info != NULL)
+ {
+ stop_auto_scroll (container);
+
+ nautilus_drag_finalize (&container->details->dnd_info->drag_info);
+ container->details->dnd_info = NULL;
+ }
+}
diff --git a/src/nautilus-canvas-dnd.h b/src/nautilus-canvas-dnd.h
new file mode 100644
index 000000000..0e8b980e3
--- /dev/null
+++ b/src/nautilus-canvas-dnd.h
@@ -0,0 +1,56 @@
+
+/* nautilus-canvas-dnd.h - Drag & drop handling for the canvas container widget.
+
+ Copyright (C) 1999, 2000 Free Software Foundation
+ Copyright (C) 2000 Eazel, Inc.
+
+ 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,
+ see <http://www.gnu.org/licenses/>.
+
+ Authors: Ettore Perazzoli <ettore@gnu.org>,
+ Darin Adler <darin@bentspoon.com>,
+ Andy Hertzfeld <andy@eazel.com>
+*/
+
+#pragma once
+
+#include "nautilus-canvas-container.h"
+#include "nautilus-dnd.h"
+
+/* DnD-related information. */
+typedef struct {
+ /* inherited drag info context */
+ NautilusDragInfo drag_info;
+
+ gboolean highlighted;
+ char *target_uri;
+
+ /* Shadow for the icons being dragged. */
+ EelCanvasItem *shadow;
+ guint hover_id;
+} NautilusCanvasDndInfo;
+
+
+void nautilus_canvas_dnd_init (NautilusCanvasContainer *container);
+void nautilus_canvas_dnd_fini (NautilusCanvasContainer *container);
+void nautilus_canvas_dnd_begin_drag (NautilusCanvasContainer *container,
+ GdkDragAction actions,
+ gint button,
+ GdkEventMotion *event,
+ int start_x,
+ int start_y);
+void nautilus_canvas_dnd_end_drag (NautilusCanvasContainer *container);
+
+NautilusDragInfo* nautilus_canvas_dnd_get_drag_source_data (NautilusCanvasContainer *container,
+ GdkDragContext *context);
diff --git a/src/nautilus-canvas-item.c b/src/nautilus-canvas-item.c
new file mode 100644
index 000000000..762c8832a
--- /dev/null
+++ b/src/nautilus-canvas-item.c
@@ -0,0 +1,2760 @@
+/* Nautilus - Canvas item class for canvas container.
+ *
+ * Copyright (C) 2000 Eazel, Inc
+ *
+ * Author: Andy Hertzfeld <andy@eazel.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <math.h>
+#include "nautilus-canvas-item.h"
+
+#include <glib/gi18n.h>
+
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-canvas-private.h"
+#include <eel/eel-art-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-graphic-effects.h>
+#include <eel/eel-string.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <glib/gi18n.h>
+#include <atk/atkimage.h>
+#include <atk/atkcomponent.h>
+#include <atk/atknoopobject.h>
+#include <stdio.h>
+#include <string.h>
+
+/* gap between bottom of icon and start of text box */
+#define LABEL_OFFSET 1
+#define LABEL_LINE_SPACING 0
+
+/* Text padding */
+#define TEXT_BACK_PADDING_X 4
+#define TEXT_BACK_PADDING_Y 1
+
+/* Width of the label, keep in sync with ICON_GRID_WIDTH at nautilus-canvas-container.c */
+#define MAX_TEXT_WIDTH_SMALL 116
+#define MAX_TEXT_WIDTH_STANDARD 104
+#define MAX_TEXT_WIDTH_LARGE 98
+#define MAX_TEXT_WIDTH_LARGER 100
+
+/* special text height handling
+ * each item has three text height variables:
+ * + text_height: actual height of the displayed (i.e. on-screen) PangoLayout.
+ * + text_height_for_layout: height used in canvas grid layout algorithms.
+ * “sane amount” of text.
+ * “sane amount“ as of
+ * + hard-coded to three lines in text-below-icon mode.
+ *
+ * This layout height is used by grid layout algorithms, even
+ * though the actually displayed and/or requested text size may be larger
+ * and overlap adjacent icons, if an icon is selected.
+ *
+ * + text_height_for_entire_text: height needed to display the entire PangoLayout,
+ * if it wasn't ellipsized.
+ */
+
+/* Private part of the NautilusCanvasItem structure. */
+struct NautilusCanvasItemDetails
+{
+ /* The image, text, font. */
+ double x, y;
+ GdkPixbuf *pixbuf;
+ cairo_surface_t *rendered_surface;
+ char *editable_text; /* Text that can be modified by a renaming function */
+ char *additional_text; /* Text that cannot be modifed, such as file size, etc. */
+
+ /* Size of the text at current font. */
+ int text_dx;
+ int text_width;
+
+ /* actual size required for rendering the text to display */
+ int text_height;
+ /* actual size that would be required for rendering the entire text if it wasn't ellipsized */
+ int text_height_for_entire_text;
+ /* actual size needed for rendering a “sane amount” of text */
+ int text_height_for_layout;
+
+ int editable_text_height;
+
+ /* whether the entire text must always be visible. In that case,
+ * text_height_for_layout will always be equal to text_height.
+ * Used for the last line of a line-wise icon layout. */
+ guint entire_text : 1;
+
+ /* Highlight state. */
+ guint is_highlighted_for_selection : 1;
+ guint is_highlighted_as_keyboard_focus : 1;
+ guint is_highlighted_for_drop : 1;
+ guint is_highlighted_for_clipboard : 1;
+ guint is_prelit : 1;
+
+ guint rendered_is_highlighted_for_selection : 1;
+ guint rendered_is_highlighted_for_drop : 1;
+ guint rendered_is_highlighted_for_clipboard : 1;
+ guint rendered_is_prelit : 1;
+ guint rendered_is_focused : 1;
+
+ guint bounds_cached : 1;
+
+ guint is_visible : 1;
+
+ /* Cached PangoLayouts. Only used if the icon is visible */
+ PangoLayout *editable_text_layout;
+ PangoLayout *additional_text_layout;
+
+ /* Cached rectangle in canvas coordinates */
+ EelIRect icon_rect;
+ EelIRect text_rect;
+
+ EelIRect bounds_cache;
+ EelIRect bounds_cache_for_layout;
+ EelIRect bounds_cache_for_entire_item;
+
+ GdkWindow *cursor_window;
+
+ GString *text;
+};
+
+/* Object argument IDs. */
+enum
+{
+ PROP_0,
+ PROP_EDITABLE_TEXT,
+ PROP_ADDITIONAL_TEXT,
+ PROP_HIGHLIGHTED_FOR_SELECTION,
+ PROP_HIGHLIGHTED_AS_KEYBOARD_FOCUS,
+ PROP_HIGHLIGHTED_FOR_DROP,
+ PROP_HIGHLIGHTED_FOR_CLIPBOARD
+};
+
+typedef enum
+{
+ RIGHT_SIDE,
+ BOTTOM_SIDE,
+ LEFT_SIDE,
+ TOP_SIDE
+} RectangleSide;
+
+static GType nautilus_canvas_item_accessible_factory_get_type (void);
+
+G_DEFINE_TYPE (NautilusCanvasItem, nautilus_canvas_item, EEL_TYPE_CANVAS_ITEM)
+
+/* private */
+static void get_icon_rectangle (NautilusCanvasItem *item,
+ EelIRect *rect);
+static PangoLayout *get_label_layout (PangoLayout **layout,
+ NautilusCanvasItem *item,
+ const char *text);
+
+static void nautilus_canvas_item_ensure_bounds_up_to_date (NautilusCanvasItem *canvas_item);
+
+/* Object initialization function for the canvas item. */
+static void
+nautilus_canvas_item_init (NautilusCanvasItem *canvas_item)
+{
+ canvas_item->details = G_TYPE_INSTANCE_GET_PRIVATE ((canvas_item), NAUTILUS_TYPE_CANVAS_ITEM, NautilusCanvasItemDetails);
+ nautilus_canvas_item_invalidate_label_size (canvas_item);
+}
+
+static void
+nautilus_canvas_item_finalize (GObject *object)
+{
+ NautilusCanvasItemDetails *details;
+
+ g_assert (NAUTILUS_IS_CANVAS_ITEM (object));
+
+ details = NAUTILUS_CANVAS_ITEM (object)->details;
+
+ if (details->cursor_window != NULL)
+ {
+ gdk_window_set_cursor (details->cursor_window, NULL);
+ g_object_unref (details->cursor_window);
+ }
+
+ if (details->pixbuf != NULL)
+ {
+ g_object_unref (details->pixbuf);
+ }
+
+ if (details->text != NULL)
+ {
+ g_string_free (details->text, TRUE);
+ details->text = NULL;
+ }
+
+ g_free (details->editable_text);
+ g_free (details->additional_text);
+
+ if (details->rendered_surface != NULL)
+ {
+ cairo_surface_destroy (details->rendered_surface);
+ }
+
+ if (details->editable_text_layout != NULL)
+ {
+ g_object_unref (details->editable_text_layout);
+ }
+
+ if (details->additional_text_layout != NULL)
+ {
+ g_object_unref (details->additional_text_layout);
+ }
+
+ G_OBJECT_CLASS (nautilus_canvas_item_parent_class)->finalize (object);
+}
+
+/* Currently we require pixbufs in this format (for hit testing).
+ * Perhaps gdk-pixbuf will be changed so it can do the hit testing
+ * and we won't have this requirement any more.
+ */
+static gboolean
+pixbuf_is_acceptable (GdkPixbuf *pixbuf)
+{
+ return gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB
+ && ((!gdk_pixbuf_get_has_alpha (pixbuf)
+ && gdk_pixbuf_get_n_channels (pixbuf) == 3)
+ || (gdk_pixbuf_get_has_alpha (pixbuf)
+ && gdk_pixbuf_get_n_channels (pixbuf) == 4))
+ && gdk_pixbuf_get_bits_per_sample (pixbuf) == 8;
+}
+
+static void
+nautilus_canvas_item_invalidate_bounds_cache (NautilusCanvasItem *item)
+{
+ item->details->bounds_cached = FALSE;
+}
+
+/* invalidate the text width and height cached in the item details. */
+void
+nautilus_canvas_item_invalidate_label_size (NautilusCanvasItem *item)
+{
+ if (item->details->editable_text_layout != NULL)
+ {
+ pango_layout_context_changed (item->details->editable_text_layout);
+ }
+ if (item->details->additional_text_layout != NULL)
+ {
+ pango_layout_context_changed (item->details->additional_text_layout);
+ }
+ nautilus_canvas_item_invalidate_bounds_cache (item);
+ item->details->text_width = -1;
+ item->details->text_height = -1;
+ item->details->text_height_for_layout = -1;
+ item->details->text_height_for_entire_text = -1;
+ item->details->editable_text_height = -1;
+}
+
+/* Set property handler for the canvas item. */
+static void
+nautilus_canvas_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusCanvasItem *item;
+ NautilusCanvasItemDetails *details;
+ AtkObject *accessible;
+ gboolean is_rename;
+
+ item = NAUTILUS_CANVAS_ITEM (object);
+ accessible = atk_gobject_accessible_for_object (G_OBJECT (item));
+ details = item->details;
+
+ switch (property_id)
+ {
+ case PROP_EDITABLE_TEXT:
+ {
+ if (g_strcmp0 (details->editable_text,
+ g_value_get_string (value)) == 0)
+ {
+ return;
+ }
+
+ is_rename = details->editable_text != NULL;
+ g_free (details->editable_text);
+ details->editable_text = g_strdup (g_value_get_string (value));
+ if (details->text)
+ {
+ details->text = g_string_assign (details->text, details->editable_text);
+
+ if (is_rename)
+ {
+ g_object_notify (G_OBJECT (accessible), "accessible-name");
+ }
+ }
+
+ nautilus_canvas_item_invalidate_label_size (item);
+ if (details->editable_text_layout)
+ {
+ g_object_unref (details->editable_text_layout);
+ details->editable_text_layout = NULL;
+ }
+ }
+ break;
+
+ case PROP_ADDITIONAL_TEXT:
+ {
+ if (g_strcmp0 (details->additional_text,
+ g_value_get_string (value)) == 0)
+ {
+ return;
+ }
+
+ g_free (details->additional_text);
+ details->additional_text = g_strdup (g_value_get_string (value));
+
+ nautilus_canvas_item_invalidate_label_size (item);
+ if (details->additional_text_layout)
+ {
+ g_object_unref (details->additional_text_layout);
+ details->additional_text_layout = NULL;
+ }
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_FOR_SELECTION:
+ {
+ if (!details->is_highlighted_for_selection == !g_value_get_boolean (value))
+ {
+ return;
+ }
+ details->is_highlighted_for_selection = g_value_get_boolean (value);
+ nautilus_canvas_item_invalidate_label_size (item);
+
+ atk_object_notify_state_change (accessible, ATK_STATE_SELECTED,
+ details->is_highlighted_for_selection);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_AS_KEYBOARD_FOCUS:
+ {
+ if (!details->is_highlighted_as_keyboard_focus == !g_value_get_boolean (value))
+ {
+ return;
+ }
+ details->is_highlighted_as_keyboard_focus = g_value_get_boolean (value);
+
+ atk_object_notify_state_change (accessible, ATK_STATE_FOCUSED,
+ details->is_highlighted_as_keyboard_focus);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_FOR_DROP:
+ {
+ if (!details->is_highlighted_for_drop == !g_value_get_boolean (value))
+ {
+ return;
+ }
+ details->is_highlighted_for_drop = g_value_get_boolean (value);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_FOR_CLIPBOARD:
+ {
+ if (!details->is_highlighted_for_clipboard == !g_value_get_boolean (value))
+ {
+ return;
+ }
+ details->is_highlighted_for_clipboard = g_value_get_boolean (value);
+ }
+ break;
+
+ default:
+ {
+ g_warning ("nautilus_canvas_item_set_property on unknown argument");
+ return;
+ }
+ }
+
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (object));
+}
+
+/* Get property handler for the canvas item */
+static void
+nautilus_canvas_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusCanvasItemDetails *details;
+
+ details = NAUTILUS_CANVAS_ITEM (object)->details;
+
+ switch (property_id)
+ {
+ case PROP_EDITABLE_TEXT:
+ {
+ g_value_set_string (value, details->editable_text);
+ }
+ break;
+
+ case PROP_ADDITIONAL_TEXT:
+ {
+ g_value_set_string (value, details->additional_text);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_FOR_SELECTION:
+ {
+ g_value_set_boolean (value, details->is_highlighted_for_selection);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_AS_KEYBOARD_FOCUS:
+ {
+ g_value_set_boolean (value, details->is_highlighted_as_keyboard_focus);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_FOR_DROP:
+ {
+ g_value_set_boolean (value, details->is_highlighted_for_drop);
+ }
+ break;
+
+ case PROP_HIGHLIGHTED_FOR_CLIPBOARD:
+ {
+ g_value_set_boolean (value, details->is_highlighted_for_clipboard);
+ }
+ break;
+
+ default:
+ {
+ g_warning ("invalid property %d", property_id);
+ }
+ break;
+ }
+}
+
+static void
+get_scaled_icon_size (NautilusCanvasItem *item,
+ gint *width,
+ gint *height)
+{
+ EelCanvas *canvas;
+ GdkPixbuf *pixbuf = NULL;
+ gint scale;
+
+ if (item != NULL)
+ {
+ canvas = EEL_CANVAS_ITEM (item)->canvas;
+ scale = gtk_widget_get_scale_factor (GTK_WIDGET (canvas));
+ pixbuf = item->details->pixbuf;
+ }
+
+ if (width)
+ {
+ *width = (pixbuf == NULL) ? 0 : (gdk_pixbuf_get_width (pixbuf) / scale);
+ }
+ if (height)
+ {
+ *height = (pixbuf == NULL) ? 0 : (gdk_pixbuf_get_height (pixbuf) / scale);
+ }
+}
+
+void
+nautilus_canvas_item_set_image (NautilusCanvasItem *item,
+ GdkPixbuf *image)
+{
+ NautilusCanvasItemDetails *details;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_ITEM (item));
+ g_return_if_fail (image == NULL || pixbuf_is_acceptable (image));
+
+ details = item->details;
+ if (details->pixbuf == image)
+ {
+ return;
+ }
+
+ if (image != NULL)
+ {
+ g_object_ref (image);
+ }
+ if (details->pixbuf != NULL)
+ {
+ g_object_unref (details->pixbuf);
+ }
+ if (details->rendered_surface != NULL)
+ {
+ cairo_surface_destroy (details->rendered_surface);
+ details->rendered_surface = NULL;
+ }
+
+ details->pixbuf = image;
+
+ nautilus_canvas_item_invalidate_bounds_cache (item);
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
+}
+
+/* Recomputes the bounding box of a canvas item.
+ * This is a generic implementation that could be used for any canvas item
+ * class, it has no assumptions about how the item is used.
+ */
+static void
+recompute_bounding_box (NautilusCanvasItem *canvas_item,
+ double i2w_dx,
+ double i2w_dy)
+{
+ /* The bounds stored in the item is the same as what get_bounds
+ * returns, except it's in canvas coordinates instead of the item's
+ * parent's coordinates.
+ */
+
+ EelCanvasItem *item;
+ EelDRect bounds_rect;
+
+ item = EEL_CANVAS_ITEM (canvas_item);
+
+ eel_canvas_item_get_bounds (item,
+ &bounds_rect.x0, &bounds_rect.y0,
+ &bounds_rect.x1, &bounds_rect.y1);
+
+ bounds_rect.x0 += i2w_dx;
+ bounds_rect.y0 += i2w_dy;
+ bounds_rect.x1 += i2w_dx;
+ bounds_rect.y1 += i2w_dy;
+ eel_canvas_w2c_d (item->canvas,
+ bounds_rect.x0, bounds_rect.y0,
+ &item->x1, &item->y1);
+ eel_canvas_w2c_d (item->canvas,
+ bounds_rect.x1, bounds_rect.y1,
+ &item->x2, &item->y2);
+}
+
+static EelIRect
+compute_text_rectangle (const NautilusCanvasItem *item,
+ EelIRect icon_rectangle,
+ gboolean canvas_coords,
+ NautilusCanvasItemBoundsUsage usage)
+{
+ EelIRect text_rectangle;
+ double pixels_per_unit;
+ double text_width, text_height, text_height_for_layout, text_height_for_entire_text, real_text_height;
+
+ pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
+ if (canvas_coords)
+ {
+ text_width = item->details->text_width;
+ text_height = item->details->text_height;
+ text_height_for_layout = item->details->text_height_for_layout;
+ text_height_for_entire_text = item->details->text_height_for_entire_text;
+ }
+ else
+ {
+ text_width = item->details->text_width / pixels_per_unit;
+ text_height = item->details->text_height / pixels_per_unit;
+ text_height_for_layout = item->details->text_height_for_layout / pixels_per_unit;
+ text_height_for_entire_text = item->details->text_height_for_entire_text / pixels_per_unit;
+ }
+
+ text_rectangle.x0 = (icon_rectangle.x0 + icon_rectangle.x1) / 2 - (int) text_width / 2;
+ text_rectangle.y0 = icon_rectangle.y1;
+ text_rectangle.x1 = text_rectangle.x0 + text_width;
+
+ if (usage == BOUNDS_USAGE_FOR_LAYOUT)
+ {
+ real_text_height = text_height_for_layout;
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ real_text_height = text_height_for_entire_text;
+ }
+ else if (usage == BOUNDS_USAGE_FOR_DISPLAY)
+ {
+ real_text_height = text_height;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ text_rectangle.y1 = text_rectangle.y0 + real_text_height + LABEL_OFFSET / pixels_per_unit;
+
+ return text_rectangle;
+}
+
+static EelIRect
+get_current_canvas_bounds (EelCanvasItem *item)
+{
+ EelIRect bounds;
+
+ g_assert (EEL_IS_CANVAS_ITEM (item));
+
+ bounds.x0 = item->x1;
+ bounds.y0 = item->y1;
+ bounds.x1 = item->x2;
+ bounds.y1 = item->y2;
+
+ return bounds;
+}
+
+void
+nautilus_canvas_item_update_bounds (NautilusCanvasItem *item,
+ double i2w_dx,
+ double i2w_dy)
+{
+ EelIRect before, after;
+ EelCanvasItem *canvas_item;
+
+ canvas_item = EEL_CANVAS_ITEM (item);
+
+ /* Compute new bounds. */
+ before = get_current_canvas_bounds (canvas_item);
+ recompute_bounding_box (item, i2w_dx, i2w_dy);
+ after = get_current_canvas_bounds (canvas_item);
+
+ /* If the bounds didn't change, we are done. */
+ if (eel_irect_equal (before, after))
+ {
+ return;
+ }
+
+ /* Update canvas and text rect cache */
+ get_icon_rectangle (item, &item->details->icon_rect);
+ item->details->text_rect = compute_text_rectangle (item, item->details->icon_rect,
+ TRUE, BOUNDS_USAGE_FOR_DISPLAY);
+
+ /* queue a redraw. */
+ eel_canvas_request_redraw (canvas_item->canvas,
+ before.x0, before.y0,
+ before.x1 + 1, before.y1 + 1);
+}
+
+/* Update handler for the canvas canvas item. */
+static void
+nautilus_canvas_item_update (EelCanvasItem *item,
+ double i2w_dx,
+ double i2w_dy,
+ gint flags)
+{
+ nautilus_canvas_item_update_bounds (NAUTILUS_CANVAS_ITEM (item), i2w_dx, i2w_dy);
+
+ eel_canvas_item_request_redraw (EEL_CANVAS_ITEM (item));
+
+ EEL_CANVAS_ITEM_CLASS (nautilus_canvas_item_parent_class)->update (item, i2w_dx, i2w_dy, flags);
+}
+
+/* Rendering */
+static gboolean
+in_single_click_mode (void)
+{
+ int click_policy;
+
+ click_policy = g_settings_get_enum (nautilus_preferences,
+ NAUTILUS_PREFERENCES_CLICK_POLICY);
+
+ return click_policy == NAUTILUS_CLICK_POLICY_SINGLE;
+}
+
+
+/* Keep these for a bit while we work on performance of draw_or_measure_label_text. */
+/*
+ #define PERFORMANCE_TEST_DRAW_DISABLE
+ #define PERFORMANCE_TEST_MEASURE_DISABLE
+ */
+
+/* This gets the size of the layout from the position of the layout.
+ * This means that if the layout is right aligned we get the full width
+ * of the layout, not just the width of the text snippet on the right side
+ */
+static void
+layout_get_full_size (PangoLayout *layout,
+ int *width,
+ int *height,
+ int *dx)
+{
+ PangoRectangle logical_rect;
+ int the_width, total_width;
+
+ pango_layout_get_extents (layout, NULL, &logical_rect);
+ the_width = (logical_rect.width + PANGO_SCALE / 2) / PANGO_SCALE;
+ total_width = (logical_rect.x + logical_rect.width + PANGO_SCALE / 2) / PANGO_SCALE;
+
+ if (width != NULL)
+ {
+ *width = the_width;
+ }
+
+ if (height != NULL)
+ {
+ *height = (logical_rect.height + PANGO_SCALE / 2) / PANGO_SCALE;
+ }
+
+ if (dx != NULL)
+ {
+ *dx = total_width - the_width;
+ }
+}
+
+static void
+layout_get_size_for_layout (PangoLayout *layout,
+ int max_layout_line_count,
+ int height_for_entire_text,
+ int *height_for_layout)
+{
+ PangoLayoutIter *iter;
+ PangoRectangle logical_rect;
+ int i;
+
+ /* only use the first max_layout_line_count lines for the gridded auto layout */
+ if (pango_layout_get_line_count (layout) <= max_layout_line_count)
+ {
+ *height_for_layout = height_for_entire_text;
+ }
+ else
+ {
+ *height_for_layout = 0;
+ iter = pango_layout_get_iter (layout);
+ for (i = 0; i < max_layout_line_count; i++)
+ {
+ pango_layout_iter_get_line_extents (iter, NULL, &logical_rect);
+ *height_for_layout += (logical_rect.height + PANGO_SCALE / 2) / PANGO_SCALE;
+
+ if (!pango_layout_iter_next_line (iter))
+ {
+ break;
+ }
+
+ *height_for_layout += pango_layout_get_spacing (layout);
+ }
+ pango_layout_iter_free (iter);
+ }
+}
+
+static double
+nautilus_canvas_item_get_max_text_width (NautilusCanvasItem *item)
+{
+ EelCanvasItem *canvas_item;
+ NautilusCanvasContainer *container;
+ guint max_text_width;
+
+
+ canvas_item = EEL_CANVAS_ITEM (item);
+ container = NAUTILUS_CANVAS_CONTAINER (canvas_item->canvas);
+
+ switch (nautilus_canvas_container_get_zoom_level (container))
+ {
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL:
+ {
+ max_text_width = MAX_TEXT_WIDTH_SMALL;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_STANDARD:
+ {
+ max_text_width = MAX_TEXT_WIDTH_STANDARD;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE:
+ {
+ max_text_width = MAX_TEXT_WIDTH_LARGE;
+ }
+ break;
+
+ case NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER:
+ {
+ max_text_width = MAX_TEXT_WIDTH_LARGER;
+ }
+ break;
+
+ default:
+ {
+ g_warning ("Zoom level not valid. This may incur in missaligned grid");
+ max_text_width = MAX_TEXT_WIDTH_STANDARD;
+ }
+ }
+
+ return max_text_width * canvas_item->canvas->pixels_per_unit - 2 * TEXT_BACK_PADDING_X;
+}
+
+static void
+prepare_pango_layout_width (NautilusCanvasItem *item,
+ PangoLayout *layout)
+{
+ pango_layout_set_width (layout, floor (nautilus_canvas_item_get_max_text_width (item)) * PANGO_SCALE);
+ pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
+}
+
+static void
+prepare_pango_layout_for_measure_entire_text (NautilusCanvasItem *item,
+ PangoLayout *layout)
+{
+ prepare_pango_layout_width (item, layout);
+ pango_layout_set_height (layout, G_MININT);
+}
+
+static void
+prepare_pango_layout_for_draw (NautilusCanvasItem *item,
+ PangoLayout *layout)
+{
+ NautilusCanvasItemDetails *details;
+ NautilusCanvasContainer *container;
+ gboolean needs_highlight;
+
+ prepare_pango_layout_width (item, layout);
+
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ details = item->details;
+
+ needs_highlight = details->is_highlighted_for_selection || details->is_highlighted_for_drop;
+
+ if (needs_highlight ||
+ details->is_highlighted_as_keyboard_focus ||
+ details->entire_text)
+ {
+ /* VOODOO-TODO, cf. compute_text_rectangle() */
+ pango_layout_set_height (layout, G_MININT);
+ }
+ else
+ {
+ /* TODO? we might save some resources, when the re-layout is not neccessary in case
+ * the layout height already fits into max. layout lines. But pango should figure this
+ * out itself (which it doesn't ATM).
+ */
+ pango_layout_set_height (layout,
+ nautilus_canvas_container_get_max_layout_lines_for_pango (container));
+ }
+}
+
+static void
+measure_label_text (NautilusCanvasItem *item)
+{
+ NautilusCanvasItemDetails *details;
+ NautilusCanvasContainer *container;
+ gint editable_height, editable_height_for_layout, editable_height_for_entire_text, editable_width, editable_dx;
+ gint additional_height, additional_width, additional_dx;
+ PangoLayout *editable_layout;
+ PangoLayout *additional_layout;
+ gboolean have_editable, have_additional;
+
+ /* check to see if the cached values are still valid; if so, there's
+ * no work necessary
+ */
+
+ if (item->details->text_width >= 0 && item->details->text_height >= 0)
+ {
+ return;
+ }
+
+ details = item->details;
+
+ have_editable = details->editable_text != NULL && details->editable_text[0] != '\0';
+ have_additional = details->additional_text != NULL && details->additional_text[0] != '\0';
+
+ /* No font or no text, then do no work. */
+ if (!have_editable && !have_additional)
+ {
+ details->text_height = 0;
+ details->text_height_for_layout = 0;
+ details->text_height_for_entire_text = 0;
+ details->text_width = 0;
+ return;
+ }
+
+#ifdef PERFORMANCE_TEST_MEASURE_DISABLE
+ /* fake out the width */
+ details->text_width = 80;
+ details->text_height = 20;
+ details->text_height_for_layout = 20;
+ details->text_height_for_entire_text = 20;
+ return;
+#endif
+
+ editable_width = 0;
+ editable_height = 0;
+ editable_height_for_layout = 0;
+ editable_height_for_entire_text = 0;
+ editable_dx = 0;
+ additional_width = 0;
+ additional_height = 0;
+ additional_dx = 0;
+
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ editable_layout = NULL;
+ additional_layout = NULL;
+
+ if (have_editable)
+ {
+ /* first, measure required text height: editable_height_for_entire_text
+ * then, measure text height applicable for layout: editable_height_for_layout
+ * next, measure actually displayed height: editable_height
+ */
+ editable_layout = get_label_layout (&details->editable_text_layout, item, details->editable_text);
+
+ prepare_pango_layout_for_measure_entire_text (item, editable_layout);
+ layout_get_full_size (editable_layout,
+ NULL,
+ &editable_height_for_entire_text,
+ NULL);
+ layout_get_size_for_layout (editable_layout,
+ nautilus_canvas_container_get_max_layout_lines (container),
+ editable_height_for_entire_text,
+ &editable_height_for_layout);
+
+ prepare_pango_layout_for_draw (item, editable_layout);
+ layout_get_full_size (editable_layout,
+ &editable_width,
+ &editable_height,
+ &editable_dx);
+ }
+
+ if (have_additional)
+ {
+ additional_layout = get_label_layout (&details->additional_text_layout, item, details->additional_text);
+ prepare_pango_layout_for_draw (item, additional_layout);
+ layout_get_full_size (additional_layout,
+ &additional_width, &additional_height, &additional_dx);
+ }
+
+ details->editable_text_height = editable_height;
+
+ if (editable_width > additional_width)
+ {
+ details->text_width = editable_width;
+ details->text_dx = editable_dx;
+ }
+ else
+ {
+ details->text_width = additional_width;
+ details->text_dx = additional_dx;
+ }
+
+ if (have_additional)
+ {
+ details->text_height = editable_height + LABEL_LINE_SPACING + additional_height;
+ details->text_height_for_layout = editable_height_for_layout + LABEL_LINE_SPACING + additional_height;
+ details->text_height_for_entire_text = editable_height_for_entire_text + LABEL_LINE_SPACING + additional_height;
+ }
+ else
+ {
+ details->text_height = editable_height;
+ details->text_height_for_layout = editable_height_for_layout;
+ details->text_height_for_entire_text = editable_height_for_entire_text;
+ }
+
+ /* add some extra space for highlighting even when we don't highlight so things won't move */
+
+ /* extra slop for nicer highlighting */
+ details->text_height += TEXT_BACK_PADDING_Y * 2;
+ details->text_height_for_layout += TEXT_BACK_PADDING_Y * 2;
+ details->text_height_for_entire_text += TEXT_BACK_PADDING_Y * 2;
+ details->editable_text_height += TEXT_BACK_PADDING_Y * 2;
+
+ /* extra to make it look nicer */
+ details->text_width += TEXT_BACK_PADDING_X * 2;
+
+ if (editable_layout)
+ {
+ g_object_unref (editable_layout);
+ }
+
+ if (additional_layout)
+ {
+ g_object_unref (additional_layout);
+ }
+}
+
+static void
+draw_label_text (NautilusCanvasItem *item,
+ cairo_t *cr,
+ EelIRect icon_rect)
+{
+ NautilusCanvasItemDetails *details;
+ NautilusCanvasContainer *container;
+ PangoLayout *editable_layout;
+ PangoLayout *additional_layout;
+ GtkStyleContext *context;
+ GtkStateFlags state, base_state;
+ gboolean have_editable, have_additional;
+ gboolean needs_highlight;
+ EelIRect text_rect;
+ int x;
+ int max_text_width;
+ gdouble frame_w, frame_h, frame_x, frame_y;
+ gboolean draw_frame = TRUE;
+
+#ifdef PERFORMANCE_TEST_DRAW_DISABLE
+ return;
+#endif
+
+ details = item->details;
+
+ measure_label_text (item);
+ if (details->text_height == 0 ||
+ details->text_width == 0)
+ {
+ return;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ context = gtk_widget_get_style_context (GTK_WIDGET (container));
+
+ text_rect = compute_text_rectangle (item, icon_rect, TRUE, BOUNDS_USAGE_FOR_DISPLAY);
+
+ needs_highlight = details->is_highlighted_for_selection || details->is_highlighted_for_drop;
+
+ editable_layout = NULL;
+ additional_layout = NULL;
+
+ have_editable = details->editable_text != NULL && details->editable_text[0] != '\0';
+ have_additional = details->additional_text != NULL && details->additional_text[0] != '\0';
+ g_assert (have_editable || have_additional);
+
+ max_text_width = floor (nautilus_canvas_item_get_max_text_width (item));
+
+ base_state = gtk_widget_get_state_flags (GTK_WIDGET (container));
+ base_state &= ~(GTK_STATE_FLAG_SELECTED |
+ GTK_STATE_FLAG_PRELIGHT);
+ state = base_state;
+
+ /* if the canvas is highlighted, do some set-up */
+ if (needs_highlight)
+ {
+ state |= GTK_STATE_FLAG_SELECTED;
+
+ frame_x = text_rect.x0;
+ frame_y = text_rect.y0;
+ frame_w = text_rect.x1 - text_rect.x0;
+ frame_h = text_rect.y1 - text_rect.y0;
+ }
+ else
+ {
+ draw_frame = FALSE;
+ }
+
+ if (draw_frame)
+ {
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+
+ gtk_render_frame (context, cr,
+ frame_x, frame_y,
+ frame_w, frame_h);
+ gtk_render_background (context, cr,
+ frame_x, frame_y,
+ frame_w, frame_h);
+
+ gtk_style_context_restore (context);
+ }
+
+ x = text_rect.x0 + ((text_rect.x1 - text_rect.x0) - max_text_width) / 2;
+
+ if (have_editable)
+ {
+ state = base_state;
+
+ if (needs_highlight)
+ {
+ state |= GTK_STATE_FLAG_SELECTED;
+ }
+
+ editable_layout = get_label_layout (&item->details->editable_text_layout, item, item->details->editable_text);
+ prepare_pango_layout_for_draw (item, editable_layout);
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+
+ gtk_render_layout (context, cr,
+ x, text_rect.y0 + TEXT_BACK_PADDING_Y,
+ editable_layout);
+
+ gtk_style_context_restore (context);
+ }
+
+ if (have_additional)
+ {
+ state = base_state;
+
+ if (needs_highlight)
+ {
+ state |= GTK_STATE_FLAG_SELECTED;
+ }
+
+ additional_layout = get_label_layout (&item->details->additional_text_layout, item, item->details->additional_text);
+ prepare_pango_layout_for_draw (item, additional_layout);
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+ gtk_style_context_add_class (context, "dim-label");
+
+ gtk_render_layout (context, cr,
+ x, text_rect.y0 + details->editable_text_height + LABEL_LINE_SPACING + TEXT_BACK_PADDING_Y,
+ additional_layout);
+
+ gtk_style_context_restore (context);
+ }
+
+ if (item->details->is_highlighted_as_keyboard_focus)
+ {
+ if (needs_highlight)
+ {
+ state = GTK_STATE_FLAG_SELECTED;
+ }
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+
+ gtk_render_focus (context,
+ cr,
+ text_rect.x0,
+ text_rect.y0,
+ text_rect.x1 - text_rect.x0,
+ text_rect.y1 - text_rect.y0);
+
+ gtk_style_context_restore (context);
+ }
+
+ if (editable_layout != NULL)
+ {
+ g_object_unref (editable_layout);
+ }
+
+ if (additional_layout != NULL)
+ {
+ g_object_unref (additional_layout);
+ }
+}
+
+void
+nautilus_canvas_item_set_is_visible (NautilusCanvasItem *item,
+ gboolean visible)
+{
+ if (item->details->is_visible == visible)
+ {
+ return;
+ }
+
+ item->details->is_visible = visible;
+
+ if (!visible)
+ {
+ nautilus_canvas_item_invalidate_label (item);
+ }
+}
+
+void
+nautilus_canvas_item_invalidate_label (NautilusCanvasItem *item)
+{
+ nautilus_canvas_item_invalidate_label_size (item);
+
+ if (item->details->editable_text_layout)
+ {
+ g_object_unref (item->details->editable_text_layout);
+ item->details->editable_text_layout = NULL;
+ }
+
+ if (item->details->additional_text_layout)
+ {
+ g_object_unref (item->details->additional_text_layout);
+ item->details->additional_text_layout = NULL;
+ }
+}
+
+/* shared code to highlight or dim the passed-in pixbuf */
+static cairo_surface_t *
+real_map_surface (NautilusCanvasItem *canvas_item)
+{
+ EelCanvas *canvas;
+ g_autoptr (GdkPixbuf) temp_pixbuf = NULL;
+ gint scale_factor;
+ GdkWindow *window;
+
+ canvas = EEL_CANVAS_ITEM (canvas_item)->canvas;
+ temp_pixbuf = g_object_ref (canvas_item->details->pixbuf);
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (canvas));
+ window = gtk_widget_get_window (GTK_WIDGET (canvas));
+
+ if (canvas_item->details->is_prelit ||
+ canvas_item->details->is_highlighted_for_clipboard)
+ {
+ g_autoptr (GdkPixbuf) old_pixbuf = NULL;
+
+ old_pixbuf = temp_pixbuf;
+ temp_pixbuf = eel_create_spotlight_pixbuf (temp_pixbuf);
+ }
+
+ if (canvas_item->details->is_highlighted_for_selection
+ || canvas_item->details->is_highlighted_for_drop)
+ {
+ GtkWidget *widget;
+ GtkStyleContext *style;
+ gboolean has_focus;
+ GtkStateFlags state;
+ gint width;
+ gint height;
+ gboolean has_alpha;
+ cairo_format_t format;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+ g_autoptr (GdkPixbuf) old_pixbuf = NULL;
+
+ widget = GTK_WIDGET (canvas);
+ style = gtk_widget_get_style_context (widget);
+ has_focus = gtk_widget_has_focus (widget);
+ state = has_focus ? GTK_STATE_FLAG_SELECTED : GTK_STATE_FLAG_ACTIVE;
+ width = gdk_pixbuf_get_width (temp_pixbuf);
+ height = gdk_pixbuf_get_height (temp_pixbuf);
+ has_alpha = gdk_pixbuf_get_has_alpha (temp_pixbuf);
+ format = has_alpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24;
+ surface = cairo_image_surface_create (format, width, height);
+ cr = cairo_create (surface);
+
+ gtk_style_context_save (style);
+ gtk_style_context_set_state (style, state);
+
+ gtk_render_background (style, cr,
+ 0, 0,
+ width, height);
+
+ gtk_style_context_restore (style);
+
+ cairo_surface_flush (surface);
+
+ pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, width, height);
+ old_pixbuf = temp_pixbuf;
+
+ temp_pixbuf = eel_create_colorized_pixbuf (temp_pixbuf, g_steal_pointer (&pixbuf));
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+ }
+
+ return gdk_cairo_surface_create_from_pixbuf (temp_pixbuf, scale_factor, window);
+}
+
+static cairo_surface_t *
+map_surface (NautilusCanvasItem *canvas_item)
+{
+ if (!(canvas_item->details->rendered_surface != NULL
+ && canvas_item->details->rendered_is_prelit == canvas_item->details->is_prelit
+ && canvas_item->details->rendered_is_highlighted_for_selection == canvas_item->details->is_highlighted_for_selection
+ && canvas_item->details->rendered_is_highlighted_for_drop == canvas_item->details->is_highlighted_for_drop
+ && canvas_item->details->rendered_is_highlighted_for_clipboard == canvas_item->details->is_highlighted_for_clipboard
+ && (canvas_item->details->is_highlighted_for_selection && canvas_item->details->rendered_is_focused == gtk_widget_has_focus (GTK_WIDGET (EEL_CANVAS_ITEM (canvas_item)->canvas)))))
+ {
+ if (canvas_item->details->rendered_surface != NULL)
+ {
+ cairo_surface_destroy (canvas_item->details->rendered_surface);
+ }
+ canvas_item->details->rendered_surface = real_map_surface (canvas_item);
+ canvas_item->details->rendered_is_prelit = canvas_item->details->is_prelit;
+ canvas_item->details->rendered_is_highlighted_for_selection = canvas_item->details->is_highlighted_for_selection;
+ canvas_item->details->rendered_is_highlighted_for_drop = canvas_item->details->is_highlighted_for_drop;
+ canvas_item->details->rendered_is_highlighted_for_clipboard = canvas_item->details->is_highlighted_for_clipboard;
+ canvas_item->details->rendered_is_focused = gtk_widget_has_focus (GTK_WIDGET (EEL_CANVAS_ITEM (canvas_item)->canvas));
+ }
+
+ cairo_surface_reference (canvas_item->details->rendered_surface);
+
+ return canvas_item->details->rendered_surface;
+}
+
+cairo_surface_t *
+nautilus_canvas_item_get_drag_surface (NautilusCanvasItem *item)
+{
+ cairo_surface_t *surface;
+ EelCanvas *canvas;
+ int width, height;
+ int pix_width, pix_height;
+ int item_offset_x, item_offset_y;
+ EelIRect icon_rect;
+ double item_x, item_y;
+ cairo_t *cr;
+ GtkStyleContext *context;
+ cairo_surface_t *drag_surface;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_ITEM (item), NULL);
+
+ canvas = EEL_CANVAS_ITEM (item)->canvas;
+ context = gtk_widget_get_style_context (GTK_WIDGET (canvas));
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, "nautilus-canvas-item");
+
+ /* Assume we're updated so canvas item data is right */
+
+ /* Calculate the offset from the top-left corner of the
+ * new image to the item position (where the pixmap is placed) */
+ eel_canvas_world_to_window (canvas,
+ item->details->x, item->details->y,
+ &item_x, &item_y);
+
+ item_offset_x = item_x - EEL_CANVAS_ITEM (item)->x1;
+ item_offset_y = item_y - EEL_CANVAS_ITEM (item)->y1;
+
+ /* Calculate the width of the item */
+ width = EEL_CANVAS_ITEM (item)->x2 - EEL_CANVAS_ITEM (item)->x1;
+ height = EEL_CANVAS_ITEM (item)->y2 - EEL_CANVAS_ITEM (item)->y1;
+
+ surface = gdk_window_create_similar_surface (gtk_widget_get_window (GTK_WIDGET (canvas)),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ width, height);
+ cr = cairo_create (surface);
+
+ drag_surface = map_surface (item);
+ gtk_render_icon_surface (context, cr, drag_surface,
+ item_offset_x, item_offset_y);
+ cairo_surface_destroy (drag_surface);
+
+ get_scaled_icon_size (item, &pix_width, &pix_height);
+
+ icon_rect.x0 = item_offset_x;
+ icon_rect.y0 = item_offset_y;
+ icon_rect.x1 = item_offset_x + pix_width;
+ icon_rect.y1 = item_offset_y + pix_height;
+
+ draw_label_text (item, cr, icon_rect);
+ cairo_destroy (cr);
+
+ gtk_style_context_restore (context);
+
+ return surface;
+}
+
+/* Draw the canvas item for non-anti-aliased mode. */
+static void
+nautilus_canvas_item_draw (EelCanvasItem *item,
+ cairo_t *cr,
+ cairo_region_t *region)
+{
+ NautilusCanvasContainer *container;
+ NautilusCanvasItem *canvas_item;
+ NautilusCanvasItemDetails *details;
+ EelIRect icon_rect;
+ cairo_surface_t *temp_surface;
+ GtkStyleContext *context;
+
+ container = NAUTILUS_CANVAS_CONTAINER (item->canvas);
+ canvas_item = NAUTILUS_CANVAS_ITEM (item);
+ details = canvas_item->details;
+
+ /* Draw the pixbuf. */
+ if (details->pixbuf == NULL)
+ {
+ return;
+ }
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (container));
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, "nautilus-canvas-item");
+
+ icon_rect = canvas_item->details->icon_rect;
+ temp_surface = map_surface (canvas_item);
+
+ gtk_render_icon_surface (context, cr,
+ temp_surface,
+ icon_rect.x0, icon_rect.y0);
+ cairo_surface_destroy (temp_surface);
+
+ /* Draw the label text. */
+ draw_label_text (canvas_item, cr, icon_rect);
+
+ gtk_style_context_restore (context);
+}
+
+#define ZERO_WIDTH_SPACE "\xE2\x80\x8B"
+
+static PangoLayout *
+create_label_layout (NautilusCanvasItem *item,
+ const char *text)
+{
+ PangoLayout *layout;
+ PangoContext *context;
+ PangoFontDescription *desc;
+ NautilusCanvasContainer *container;
+ EelCanvasItem *canvas_item;
+ GString *str;
+ char *zeroified_text;
+ const char *p;
+
+ canvas_item = EEL_CANVAS_ITEM (item);
+
+ container = NAUTILUS_CANVAS_CONTAINER (canvas_item->canvas);
+ context = gtk_widget_get_pango_context (GTK_WIDGET (canvas_item->canvas));
+ layout = pango_layout_new (context);
+
+ zeroified_text = NULL;
+
+ if (text != NULL)
+ {
+ str = g_string_new (NULL);
+
+ for (p = text; *p != '\0'; p++)
+ {
+ str = g_string_append_c (str, *p);
+
+ if (*p == '_' || *p == '-' || (*p == '.' && !g_ascii_isdigit (*(p + 1))))
+ {
+ /* Ensure that we allow to break after '_' or '.' characters,
+ * if they are not followed by a number */
+ str = g_string_append (str, ZERO_WIDTH_SPACE);
+ }
+ }
+
+ zeroified_text = g_string_free (str, FALSE);
+ }
+
+ pango_layout_set_text (layout, zeroified_text, -1);
+ pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
+
+ pango_layout_set_spacing (layout, LABEL_LINE_SPACING);
+ pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
+
+#if PANGO_VERSION_CHECK (1, 44, 4)
+ {
+ PangoAttrList *attr_list = pango_attr_list_new ();
+
+ pango_attr_list_insert (attr_list, pango_attr_insert_hyphens_new (FALSE));
+ pango_layout_set_attributes (layout, attr_list);
+ pango_attr_list_unref (attr_list);
+ }
+#endif
+
+ /* Create a font description */
+ if (container->details->font)
+ {
+ desc = pango_font_description_from_string (container->details->font);
+ }
+ else
+ {
+ desc = pango_font_description_copy (pango_context_get_font_description (context));
+ }
+ pango_layout_set_font_description (layout, desc);
+ pango_font_description_free (desc);
+ g_free (zeroified_text);
+
+ return layout;
+}
+
+static PangoLayout *
+get_label_layout (PangoLayout **layout_cache,
+ NautilusCanvasItem *item,
+ const char *text)
+{
+ PangoLayout *layout;
+
+ if (*layout_cache != NULL)
+ {
+ return g_object_ref (*layout_cache);
+ }
+
+ layout = create_label_layout (item, text);
+
+ if (item->details->is_visible)
+ {
+ *layout_cache = g_object_ref (layout);
+ }
+
+ return layout;
+}
+
+/* handle events */
+
+static int
+nautilus_canvas_item_event (EelCanvasItem *item,
+ GdkEvent *event)
+{
+ NautilusCanvasItem *canvas_item;
+ GdkCursor *cursor;
+ GdkWindow *cursor_window;
+
+ canvas_item = NAUTILUS_CANVAS_ITEM (item);
+ cursor_window = ((GdkEventAny *) event)->window;
+
+ switch (event->type)
+ {
+ case GDK_ENTER_NOTIFY:
+ {
+ if (!canvas_item->details->is_prelit)
+ {
+ canvas_item->details->is_prelit = TRUE;
+ nautilus_canvas_item_invalidate_label_size (canvas_item);
+ eel_canvas_item_request_update (item);
+ eel_canvas_item_send_behind (item,
+ NAUTILUS_CANVAS_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle);
+
+ /* show a hand cursor */
+ if (in_single_click_mode ())
+ {
+ cursor = gdk_cursor_new_for_display (gdk_display_get_default (),
+ GDK_HAND2);
+ gdk_window_set_cursor (cursor_window, cursor);
+ g_object_unref (cursor);
+
+ canvas_item->details->cursor_window = g_object_ref (cursor_window);
+ }
+ }
+ return TRUE;
+ }
+
+ case GDK_LEAVE_NOTIFY:
+ {
+ if (canvas_item->details->is_prelit
+ || canvas_item->details->is_highlighted_for_drop)
+ {
+ /* When leaving, turn of the prelight state and the
+ * higlighted for drop. The latter gets turned on
+ * by the drag&drop motion callback.
+ */
+ canvas_item->details->is_prelit = FALSE;
+ canvas_item->details->is_highlighted_for_drop = FALSE;
+ nautilus_canvas_item_invalidate_label_size (canvas_item);
+ eel_canvas_item_request_update (item);
+
+ /* show default cursor */
+ gdk_window_set_cursor (cursor_window, NULL);
+ g_clear_object (&canvas_item->details->cursor_window);
+ }
+ return TRUE;
+ }
+
+ default:
+ {
+ /* Don't eat up other events; canvas container might use them. */
+ return FALSE;
+ }
+ }
+}
+
+static gboolean
+hit_test (NautilusCanvasItem *canvas_item,
+ EelIRect icon_rect)
+{
+ NautilusCanvasItemDetails *details;
+
+ details = canvas_item->details;
+
+ /* Quick check to see if the rect hits the canvas or text at all. */
+ if (!eel_irect_hits_irect (canvas_item->details->icon_rect, icon_rect)
+ && (!eel_irect_hits_irect (details->text_rect, icon_rect)))
+ {
+ return FALSE;
+ }
+
+ /* Check for hit in the canvas. */
+ if (eel_irect_hits_irect (canvas_item->details->icon_rect, icon_rect))
+ {
+ return TRUE;
+ }
+
+ /* Check for hit in the text. */
+ if (eel_irect_hits_irect (details->text_rect, icon_rect))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Point handler for the canvas canvas item. */
+static double
+nautilus_canvas_item_point (EelCanvasItem *item,
+ double x,
+ double y,
+ int cx,
+ int cy,
+ EelCanvasItem **actual_item)
+{
+ EelIRect icon_rect;
+
+ *actual_item = item;
+ icon_rect.x0 = cx;
+ icon_rect.y0 = cy;
+ icon_rect.x1 = cx + 1;
+ icon_rect.y1 = cy + 1;
+ if (hit_test (NAUTILUS_CANVAS_ITEM (item), icon_rect))
+ {
+ return 0.0;
+ }
+ else
+ {
+ /* This value means not hit.
+ * It's kind of arbitrary. Can we do better?
+ */
+ return item->canvas->pixels_per_unit * 2 + 10;
+ }
+}
+
+static void
+nautilus_canvas_item_translate (EelCanvasItem *item,
+ double dx,
+ double dy)
+{
+ NautilusCanvasItem *canvas_item;
+ NautilusCanvasItemDetails *details;
+
+ canvas_item = NAUTILUS_CANVAS_ITEM (item);
+ details = canvas_item->details;
+
+ details->x += dx;
+ details->y += dy;
+}
+
+void
+nautilus_canvas_item_get_bounds_for_layout (NautilusCanvasItem *canvas_item,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+{
+ NautilusCanvasItemDetails *details;
+ EelIRect *total_rect;
+
+ details = canvas_item->details;
+
+ nautilus_canvas_item_ensure_bounds_up_to_date (canvas_item);
+ g_assert (details->bounds_cached);
+
+ total_rect = &details->bounds_cache_for_layout;
+
+ /* Return the result. */
+ if (x1 != NULL)
+ {
+ *x1 = (int) details->x + total_rect->x0;
+ }
+ if (y1 != NULL)
+ {
+ *y1 = (int) details->y + total_rect->y0;
+ }
+ if (x2 != NULL)
+ {
+ *x2 = (int) details->x + total_rect->x1 + 1;
+ }
+ if (y2 != NULL)
+ {
+ *y2 = (int) details->y + total_rect->y1 + 1;
+ }
+}
+
+void
+nautilus_canvas_item_get_bounds_for_entire_item (NautilusCanvasItem *canvas_item,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+{
+ NautilusCanvasItemDetails *details;
+ EelIRect *total_rect;
+
+ details = canvas_item->details;
+
+ nautilus_canvas_item_ensure_bounds_up_to_date (canvas_item);
+ g_assert (details->bounds_cached);
+
+ total_rect = &details->bounds_cache_for_entire_item;
+
+ /* Return the result. */
+ if (x1 != NULL)
+ {
+ *x1 = (int) details->x + total_rect->x0;
+ }
+ if (y1 != NULL)
+ {
+ *y1 = (int) details->y + total_rect->y0;
+ }
+ if (x2 != NULL)
+ {
+ *x2 = (int) details->x + total_rect->x1 + 1;
+ }
+ if (y2 != NULL)
+ {
+ *y2 = (int) details->y + total_rect->y1 + 1;
+ }
+}
+
+/* Bounds handler for the canvas canvas item. */
+static void
+nautilus_canvas_item_bounds (EelCanvasItem *item,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+{
+ NautilusCanvasItem *canvas_item;
+ NautilusCanvasItemDetails *details;
+ EelIRect *total_rect;
+
+ canvas_item = NAUTILUS_CANVAS_ITEM (item);
+ details = canvas_item->details;
+
+ g_assert (x1 != NULL);
+ g_assert (y1 != NULL);
+ g_assert (x2 != NULL);
+ g_assert (y2 != NULL);
+
+ nautilus_canvas_item_ensure_bounds_up_to_date (canvas_item);
+ g_assert (details->bounds_cached);
+
+ total_rect = &details->bounds_cache;
+
+ /* Return the result. */
+ *x1 = (int) details->x + total_rect->x0;
+ *y1 = (int) details->y + total_rect->y0;
+ *x2 = (int) details->x + total_rect->x1 + 1;
+ *y2 = (int) details->y + total_rect->y1 + 1;
+}
+
+static void
+nautilus_canvas_item_ensure_bounds_up_to_date (NautilusCanvasItem *canvas_item)
+{
+ NautilusCanvasItemDetails *details;
+ EelIRect icon_rect;
+ EelIRect text_rect, text_rect_for_layout, text_rect_for_entire_text;
+ EelIRect total_rect, total_rect_for_layout, total_rect_for_entire_text;
+ EelCanvasItem *item;
+ double pixels_per_unit;
+ gint width, height;
+
+ details = canvas_item->details;
+ item = EEL_CANVAS_ITEM (canvas_item);
+
+ if (!details->bounds_cached)
+ {
+ measure_label_text (canvas_item);
+
+ pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
+
+ /* Compute scaled canvas rectangle. */
+ icon_rect.x0 = 0;
+ icon_rect.y0 = 0;
+
+ get_scaled_icon_size (canvas_item, &width, &height);
+
+ icon_rect.x1 = width / pixels_per_unit;
+ icon_rect.y1 = height / pixels_per_unit;
+
+ /* Compute text rectangle. */
+ text_rect = compute_text_rectangle (canvas_item, icon_rect, FALSE, BOUNDS_USAGE_FOR_DISPLAY);
+ text_rect_for_layout = compute_text_rectangle (canvas_item, icon_rect, FALSE, BOUNDS_USAGE_FOR_LAYOUT);
+ text_rect_for_entire_text = compute_text_rectangle (canvas_item, icon_rect, FALSE, BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+
+ /* Compute total rectangle */
+ eel_irect_union (&total_rect, &icon_rect, &text_rect);
+ eel_irect_union (&total_rect_for_layout, &icon_rect, &text_rect_for_layout);
+ eel_irect_union (&total_rect_for_entire_text, &icon_rect, &text_rect_for_entire_text);
+
+ details->bounds_cache = total_rect;
+ details->bounds_cache_for_layout = total_rect_for_layout;
+ details->bounds_cache_for_entire_item = total_rect_for_entire_text;
+ details->bounds_cached = TRUE;
+ }
+}
+
+/* Get the rectangle of the canvas only, in world coordinates. */
+EelDRect
+nautilus_canvas_item_get_icon_rectangle (const NautilusCanvasItem *item)
+{
+ EelDRect rectangle;
+ double pixels_per_unit;
+ gint width, height;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_ITEM (item), eel_drect_empty);
+
+ rectangle.x0 = item->details->x;
+ rectangle.y0 = item->details->y;
+
+ pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
+ get_scaled_icon_size (NAUTILUS_CANVAS_ITEM (item), &width, &height);
+ rectangle.x1 = rectangle.x0 + width / pixels_per_unit;
+ rectangle.y1 = rectangle.y0 + height / pixels_per_unit;
+
+ eel_canvas_item_i2w (EEL_CANVAS_ITEM (item),
+ &rectangle.x0,
+ &rectangle.y0);
+ eel_canvas_item_i2w (EEL_CANVAS_ITEM (item),
+ &rectangle.x1,
+ &rectangle.y1);
+
+ return rectangle;
+}
+
+/* Get the rectangle of the icon only, in canvas coordinates. */
+static void
+get_icon_rectangle (NautilusCanvasItem *item,
+ EelIRect *rect)
+{
+ gint width, height;
+
+ g_assert (NAUTILUS_IS_CANVAS_ITEM (item));
+ g_assert (rect != NULL);
+
+
+ eel_canvas_w2c (EEL_CANVAS_ITEM (item)->canvas,
+ item->details->x,
+ item->details->y,
+ &rect->x0,
+ &rect->y0);
+
+ get_scaled_icon_size (item, &width, &height);
+
+ rect->x1 = rect->x0 + width;
+ rect->y1 = rect->y0 + height;
+}
+
+/* nautilus_canvas_item_hit_test_rectangle
+ *
+ * Check and see if there is an intersection between the item and the
+ * canvas rect.
+ */
+gboolean
+nautilus_canvas_item_hit_test_rectangle (NautilusCanvasItem *item,
+ EelIRect icon_rect)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_ITEM (item), FALSE);
+
+ return hit_test (item, icon_rect);
+}
+
+void
+nautilus_canvas_item_set_entire_text (NautilusCanvasItem *item,
+ gboolean entire_text)
+{
+ if (item->details->entire_text != entire_text)
+ {
+ item->details->entire_text = entire_text;
+
+ nautilus_canvas_item_invalidate_label_size (item);
+ eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
+ }
+}
+
+/* Class initialization function for the canvas canvas item. */
+static void
+nautilus_canvas_item_class_init (NautilusCanvasItemClass *class)
+{
+ GObjectClass *object_class;
+ EelCanvasItemClass *item_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ item_class = EEL_CANVAS_ITEM_CLASS (class);
+
+ object_class->finalize = nautilus_canvas_item_finalize;
+ object_class->set_property = nautilus_canvas_item_set_property;
+ object_class->get_property = nautilus_canvas_item_get_property;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_EDITABLE_TEXT,
+ g_param_spec_string ("editable_text",
+ "editable text",
+ "the editable label",
+ "", G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ADDITIONAL_TEXT,
+ g_param_spec_string ("additional_text",
+ "additional text",
+ "some more text",
+ "", G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_HIGHLIGHTED_FOR_SELECTION,
+ g_param_spec_boolean ("highlighted_for_selection",
+ "highlighted for selection",
+ "whether we are highlighted for a selection",
+ FALSE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_HIGHLIGHTED_AS_KEYBOARD_FOCUS,
+ g_param_spec_boolean ("highlighted_as_keyboard_focus",
+ "highlighted as keyboard focus",
+ "whether we are highlighted to render keyboard focus",
+ FALSE, G_PARAM_READWRITE));
+
+
+ g_object_class_install_property (
+ object_class,
+ PROP_HIGHLIGHTED_FOR_DROP,
+ g_param_spec_boolean ("highlighted_for_drop",
+ "highlighted for drop",
+ "whether we are highlighted for a D&D drop",
+ FALSE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_HIGHLIGHTED_FOR_CLIPBOARD,
+ g_param_spec_boolean ("highlighted_for_clipboard",
+ "highlighted for clipboard",
+ "whether we are highlighted for a clipboard paste (after we have been cut)",
+ FALSE, G_PARAM_READWRITE));
+
+ item_class->update = nautilus_canvas_item_update;
+ item_class->draw = nautilus_canvas_item_draw;
+ item_class->point = nautilus_canvas_item_point;
+ item_class->translate = nautilus_canvas_item_translate;
+ item_class->bounds = nautilus_canvas_item_bounds;
+ item_class->event = nautilus_canvas_item_event;
+
+ atk_registry_set_factory_type (atk_get_default_registry (),
+ NAUTILUS_TYPE_CANVAS_ITEM,
+ nautilus_canvas_item_accessible_factory_get_type ());
+
+ g_type_class_add_private (class, sizeof (NautilusCanvasItemDetails));
+}
+
+/* ============================= a11y interfaces =========================== */
+
+static const char *nautilus_canvas_item_accessible_action_names[] =
+{
+ "open",
+ "menu",
+ NULL
+};
+
+static const char *nautilus_canvas_item_accessible_action_descriptions[] =
+{
+ "Open item",
+ "Popup context menu",
+ NULL
+};
+
+enum
+{
+ ACTION_OPEN,
+ ACTION_MENU,
+ LAST_ACTION
+};
+
+typedef struct
+{
+ char *action_descriptions[LAST_ACTION];
+ char *image_description;
+ char *description;
+} NautilusCanvasItemAccessiblePrivate;
+
+typedef struct
+{
+ NautilusCanvasItem *item;
+ gint action_number;
+} NautilusCanvasItemAccessibleActionContext;
+
+typedef struct
+{
+ EelCanvasItemAccessible parent;
+ NautilusCanvasItemAccessiblePrivate *priv;
+} NautilusCanvasItemAccessible;
+
+typedef struct
+{
+ EelCanvasItemAccessibleClass parent_class;
+} NautilusCanvasItemAccessibleClass;
+
+#define GET_ACCESSIBLE_PRIV(o) ((NautilusCanvasItemAccessible *) o)->priv;
+
+/* accessible AtkAction interface */
+static gboolean
+nautilus_canvas_item_accessible_idle_do_action (gpointer data)
+{
+ NautilusCanvasItem *item;
+ NautilusCanvasItemAccessibleActionContext *ctx;
+ NautilusCanvasIcon *icon;
+ NautilusCanvasContainer *container;
+ GList *selection;
+ GList file_list;
+ GdkEventButton button_event = { 0 };
+ gint action_number;
+
+ container = NAUTILUS_CANVAS_CONTAINER (data);
+ container->details->a11y_item_action_idle_handler = 0;
+ while (!g_queue_is_empty (container->details->a11y_item_action_queue))
+ {
+ ctx = g_queue_pop_head (container->details->a11y_item_action_queue);
+ action_number = ctx->action_number;
+ item = ctx->item;
+ g_free (ctx);
+ icon = item->user_data;
+
+ switch (action_number)
+ {
+ case ACTION_OPEN:
+ {
+ file_list.data = icon->data;
+ file_list.next = NULL;
+ file_list.prev = NULL;
+ g_signal_emit_by_name (container, "activate", &file_list);
+ }
+ break;
+
+ case ACTION_MENU:
+ {
+ selection = nautilus_canvas_container_get_selection (container);
+ if (selection == NULL ||
+ g_list_length (selection) != 1 ||
+ selection->data != icon->data)
+ {
+ g_list_free (selection);
+ return FALSE;
+ }
+ g_list_free (selection);
+ g_signal_emit_by_name (container, "context-click-selection", &button_event);
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ break;
+ }
+ }
+ return FALSE;
+}
+
+static gboolean
+nautilus_canvas_item_accessible_do_action (AtkAction *accessible,
+ int i)
+{
+ NautilusCanvasItem *item;
+ NautilusCanvasItemAccessibleActionContext *ctx;
+ NautilusCanvasContainer *container;
+
+ g_assert (i < LAST_ACTION);
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
+ if (!item)
+ {
+ return FALSE;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ switch (i)
+ {
+ case ACTION_OPEN:
+ case ACTION_MENU:
+ {
+ if (container->details->a11y_item_action_queue == NULL)
+ {
+ container->details->a11y_item_action_queue = g_queue_new ();
+ }
+ ctx = g_new (NautilusCanvasItemAccessibleActionContext, 1);
+ ctx->action_number = i;
+ ctx->item = item;
+ g_queue_push_head (container->details->a11y_item_action_queue, ctx);
+ if (container->details->a11y_item_action_idle_handler == 0)
+ {
+ container->details->a11y_item_action_idle_handler = g_idle_add (nautilus_canvas_item_accessible_idle_do_action, container);
+ }
+ }
+ break;
+
+ default:
+ {
+ g_warning ("Invalid action passed to NautilusCanvasItemAccessible::do_action");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static int
+nautilus_canvas_item_accessible_get_n_actions (AtkAction *accessible)
+{
+ return LAST_ACTION;
+}
+
+static const char *
+nautilus_canvas_item_accessible_action_get_description (AtkAction *accessible,
+ int i)
+{
+ NautilusCanvasItemAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+
+ if (priv->action_descriptions[i])
+ {
+ return priv->action_descriptions[i];
+ }
+ else
+ {
+ return nautilus_canvas_item_accessible_action_descriptions[i];
+ }
+}
+
+static const char *
+nautilus_canvas_item_accessible_action_get_name (AtkAction *accessible,
+ int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return nautilus_canvas_item_accessible_action_names[i];
+}
+
+static const char *
+nautilus_canvas_item_accessible_action_get_keybinding (AtkAction *accessible,
+ int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return NULL;
+}
+
+static gboolean
+nautilus_canvas_item_accessible_action_set_description (AtkAction *accessible,
+ int i,
+ const char *description)
+{
+ NautilusCanvasItemAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = GET_ACCESSIBLE_PRIV (accessible);
+
+ if (priv->action_descriptions[i])
+ {
+ g_free (priv->action_descriptions[i]);
+ }
+ priv->action_descriptions[i] = g_strdup (description);
+
+ return TRUE;
+}
+
+static void
+nautilus_canvas_item_accessible_action_interface_init (AtkActionIface *iface)
+{
+ iface->do_action = nautilus_canvas_item_accessible_do_action;
+ iface->get_n_actions = nautilus_canvas_item_accessible_get_n_actions;
+ iface->get_description = nautilus_canvas_item_accessible_action_get_description;
+ iface->get_keybinding = nautilus_canvas_item_accessible_action_get_keybinding;
+ iface->get_name = nautilus_canvas_item_accessible_action_get_name;
+ iface->set_description = nautilus_canvas_item_accessible_action_set_description;
+}
+
+static const gchar *
+nautilus_canvas_item_accessible_get_name (AtkObject *accessible)
+{
+ NautilusCanvasItem *item;
+
+ if (accessible->name)
+ {
+ return accessible->name;
+ }
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
+ if (!item)
+ {
+ return NULL;
+ }
+ return item->details->editable_text;
+}
+
+static const gchar *
+nautilus_canvas_item_accessible_get_description (AtkObject *accessible)
+{
+ NautilusCanvasItem *item;
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
+ if (!item)
+ {
+ return NULL;
+ }
+
+ return item->details->additional_text;
+}
+
+static AtkObject *
+nautilus_canvas_item_accessible_get_parent (AtkObject *accessible)
+{
+ NautilusCanvasItem *item;
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
+ if (!item)
+ {
+ return NULL;
+ }
+
+ return gtk_widget_get_accessible (GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas));
+}
+
+static int
+nautilus_canvas_item_accessible_get_index_in_parent (AtkObject *accessible)
+{
+ NautilusCanvasItem *item;
+ NautilusCanvasContainer *container;
+ GList *l;
+ NautilusCanvasIcon *icon;
+ int i;
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
+ if (!item)
+ {
+ return -1;
+ }
+
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+
+ l = container->details->icons;
+ i = 0;
+ while (l)
+ {
+ icon = l->data;
+
+ if (icon->item == item)
+ {
+ return i;
+ }
+
+ i++;
+ l = l->next;
+ }
+
+ return -1;
+}
+
+static const gchar *
+nautilus_canvas_item_accessible_get_image_description (AtkImage *image)
+{
+ NautilusCanvasItemAccessiblePrivate *priv;
+ NautilusCanvasItem *item;
+ NautilusCanvasIcon *icon;
+ NautilusCanvasContainer *container;
+ char *description;
+
+ priv = GET_ACCESSIBLE_PRIV (image);
+
+ if (priv->image_description)
+ {
+ return priv->image_description;
+ }
+ else
+ {
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (image)));
+ if (item == NULL)
+ {
+ return NULL;
+ }
+ icon = item->user_data;
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ description = nautilus_canvas_container_get_icon_description (container, icon->data);
+ g_free (priv->description);
+ priv->description = description;
+ return priv->description;
+ }
+}
+
+static void
+nautilus_canvas_item_accessible_get_image_size (AtkImage *image,
+ gint *width,
+ gint *height)
+{
+ NautilusCanvasItem *item;
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (image)));
+ get_scaled_icon_size (item, width, height);
+}
+
+static void
+nautilus_canvas_item_accessible_get_image_position (AtkImage *image,
+ gint *x,
+ gint *y,
+ AtkCoordType coord_type)
+{
+ NautilusCanvasItem *item;
+ gint x_offset, y_offset, itmp;
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (image)));
+ if (!item)
+ {
+ return;
+ }
+ if (!item->details->icon_rect.x0 && !item->details->icon_rect.x1)
+ {
+ return;
+ }
+ else
+ {
+ x_offset = 0;
+ y_offset = 0;
+ if (item->details->text_width)
+ {
+ itmp = item->details->icon_rect.x0 -
+ item->details->text_rect.x0;
+ if (itmp > x_offset)
+ {
+ x_offset = itmp;
+ }
+ itmp = item->details->icon_rect.y0 -
+ item->details->text_rect.y0;
+ if (itmp > y_offset)
+ {
+ y_offset = itmp;
+ }
+ }
+ }
+ atk_component_get_extents (ATK_COMPONENT (image), x, y, NULL, NULL, coord_type);
+ *x += x_offset;
+ *y += y_offset;
+}
+
+static gboolean
+nautilus_canvas_item_accessible_set_image_description (AtkImage *image,
+ const gchar *description)
+{
+ NautilusCanvasItemAccessiblePrivate *priv;
+
+ priv = GET_ACCESSIBLE_PRIV (image);
+
+ g_free (priv->image_description);
+ priv->image_description = g_strdup (description);
+
+ return TRUE;
+}
+
+static void
+nautilus_canvas_item_accessible_image_interface_init (AtkImageIface *iface)
+{
+ iface->get_image_description = nautilus_canvas_item_accessible_get_image_description;
+ iface->set_image_description = nautilus_canvas_item_accessible_set_image_description;
+ iface->get_image_size = nautilus_canvas_item_accessible_get_image_size;
+ iface->get_image_position = nautilus_canvas_item_accessible_get_image_position;
+}
+
+/* accessible text interface */
+static gint
+nautilus_canvas_item_accessible_get_offset_at_point (AtkText *text,
+ gint x,
+ gint y,
+ AtkCoordType coords)
+{
+ gint real_x, real_y, real_width, real_height;
+ NautilusCanvasItem *item;
+ gint editable_height;
+ gint offset = 0;
+ gint index;
+ PangoLayout *layout, *editable_layout, *additional_layout;
+ PangoRectangle rect0;
+ char *canvas_text;
+ gboolean have_editable;
+ gboolean have_additional;
+ gint text_offset, height;
+
+ atk_component_get_extents (ATK_COMPONENT (text), &real_x, &real_y,
+ &real_width, &real_height, coords);
+
+ x -= real_x;
+ y -= real_y;
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
+
+ if (item->details->pixbuf)
+ {
+ get_scaled_icon_size (item, NULL, &height);
+ y -= height;
+ }
+ have_editable = item->details->editable_text != NULL &&
+ item->details->editable_text[0] != '\0';
+ have_additional = item->details->additional_text != NULL && item->details->additional_text[0] != '\0';
+
+ editable_layout = NULL;
+ additional_layout = NULL;
+ if (have_editable)
+ {
+ editable_layout = get_label_layout (&item->details->editable_text_layout, item, item->details->editable_text);
+ prepare_pango_layout_for_draw (item, editable_layout);
+ pango_layout_get_pixel_size (editable_layout, NULL, &editable_height);
+ if (y >= editable_height &&
+ have_additional)
+ {
+ prepare_pango_layout_for_draw (item, editable_layout);
+ additional_layout = get_label_layout (&item->details->additional_text_layout, item, item->details->additional_text);
+ layout = additional_layout;
+ canvas_text = item->details->additional_text;
+ y -= editable_height + LABEL_LINE_SPACING;
+ }
+ else
+ {
+ layout = editable_layout;
+ canvas_text = item->details->editable_text;
+ }
+ }
+ else if (have_additional)
+ {
+ additional_layout = get_label_layout (&item->details->additional_text_layout, item, item->details->additional_text);
+ prepare_pango_layout_for_draw (item, additional_layout);
+ layout = additional_layout;
+ canvas_text = item->details->additional_text;
+ }
+ else
+ {
+ return 0;
+ }
+
+ text_offset = 0;
+ if (have_editable)
+ {
+ pango_layout_index_to_pos (editable_layout, 0, &rect0);
+ text_offset = PANGO_PIXELS (rect0.x);
+ }
+ if (have_additional)
+ {
+ gint itmp;
+
+ pango_layout_index_to_pos (additional_layout, 0, &rect0);
+ itmp = PANGO_PIXELS (rect0.x);
+ if (itmp < text_offset)
+ {
+ text_offset = itmp;
+ }
+ }
+ pango_layout_index_to_pos (layout, 0, &rect0);
+ x += text_offset;
+ if (!pango_layout_xy_to_index (layout,
+ x * PANGO_SCALE,
+ y * PANGO_SCALE,
+ &index, NULL))
+ {
+ if (x < 0 || y < 0)
+ {
+ index = 0;
+ }
+ else
+ {
+ index = -1;
+ }
+ }
+ if (index == -1)
+ {
+ offset = g_utf8_strlen (canvas_text, -1);
+ }
+ else
+ {
+ offset = g_utf8_pointer_to_offset (canvas_text, canvas_text + index);
+ }
+ if (layout == additional_layout)
+ {
+ offset += g_utf8_strlen (item->details->editable_text, -1);
+ }
+
+ if (editable_layout != NULL)
+ {
+ g_object_unref (editable_layout);
+ }
+
+ if (additional_layout != NULL)
+ {
+ g_object_unref (additional_layout);
+ }
+
+ return offset;
+}
+
+static void
+nautilus_canvas_item_accessible_get_character_extents (AtkText *text,
+ gint offset,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height,
+ AtkCoordType coords)
+{
+ gint pos_x, pos_y;
+ gint len, byte_offset;
+ gint editable_height;
+ gchar *canvas_text;
+ NautilusCanvasItem *item;
+ PangoLayout *layout, *editable_layout, *additional_layout;
+ PangoRectangle rect;
+ PangoRectangle rect0;
+ gboolean have_editable;
+ gint text_offset, pix_height;
+
+ atk_component_get_extents (ATK_COMPONENT (text), &pos_x, &pos_y, NULL, NULL, coords);
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
+
+ if (item->details->pixbuf)
+ {
+ get_scaled_icon_size (item, NULL, &pix_height);
+ pos_y += pix_height;
+ }
+
+ have_editable = item->details->editable_text != NULL &&
+ item->details->editable_text[0] != '\0';
+ if (have_editable)
+ {
+ len = g_utf8_strlen (item->details->editable_text, -1);
+ }
+ else
+ {
+ len = 0;
+ }
+
+ editable_layout = get_label_layout (&item->details->editable_text_layout, item, item->details->editable_text);
+ additional_layout = get_label_layout (&item->details->additional_text_layout, item, item->details->additional_text);
+
+ if (offset < len)
+ {
+ canvas_text = item->details->editable_text;
+ layout = editable_layout;
+ }
+ else
+ {
+ offset -= len;
+ canvas_text = item->details->additional_text;
+ layout = additional_layout;
+ pos_y += LABEL_LINE_SPACING;
+ if (have_editable)
+ {
+ pango_layout_get_pixel_size (editable_layout, NULL, &editable_height);
+ pos_y += editable_height;
+ }
+ }
+ byte_offset = g_utf8_offset_to_pointer (canvas_text, offset) - canvas_text;
+ pango_layout_index_to_pos (layout, byte_offset, &rect);
+ text_offset = 0;
+ if (have_editable)
+ {
+ pango_layout_index_to_pos (editable_layout, 0, &rect0);
+ text_offset = PANGO_PIXELS (rect0.x);
+ }
+ if (item->details->additional_text != NULL &&
+ item->details->additional_text[0] != '\0')
+ {
+ gint itmp;
+
+ pango_layout_index_to_pos (additional_layout, 0, &rect0);
+ itmp = PANGO_PIXELS (rect0.x);
+ if (itmp < text_offset)
+ {
+ text_offset = itmp;
+ }
+ }
+
+ g_object_unref (editable_layout);
+ g_object_unref (additional_layout);
+
+ *x = pos_x + PANGO_PIXELS (rect.x) - text_offset;
+ *y = pos_y + PANGO_PIXELS (rect.y);
+ *width = PANGO_PIXELS (rect.width);
+ *height = PANGO_PIXELS (rect.height);
+}
+
+static char *
+nautilus_canvas_item_accessible_text_get_text (AtkText *text,
+ gint start_pos,
+ gint end_pos)
+{
+ GObject *object;
+ NautilusCanvasItem *item;
+
+ object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+ item = NAUTILUS_CANVAS_ITEM (object);
+
+ return g_utf8_substring (item->details->text->str, start_pos, end_pos);
+}
+
+static gunichar
+nautilus_canvas_item_accessible_text_get_character_at_offset (AtkText *text,
+ gint offset)
+{
+ GObject *object;
+ NautilusCanvasItem *item;
+ gchar *pointer;
+
+ object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+ item = NAUTILUS_CANVAS_ITEM (object);
+ pointer = g_utf8_offset_to_pointer (item->details->text->str, offset);
+
+ return g_utf8_get_char (pointer);
+}
+
+static gint
+nautilus_canvas_item_accessible_text_get_character_count (AtkText *text)
+{
+ GObject *object;
+ NautilusCanvasItem *item;
+
+ object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+ item = NAUTILUS_CANVAS_ITEM (object);
+
+ return g_utf8_strlen (item->details->text->str, -1);
+}
+
+static void
+nautilus_canvas_item_accessible_text_interface_init (AtkTextIface *iface)
+{
+ iface->get_text = nautilus_canvas_item_accessible_text_get_text;
+ iface->get_character_at_offset = nautilus_canvas_item_accessible_text_get_character_at_offset;
+ iface->get_character_count = nautilus_canvas_item_accessible_text_get_character_count;
+ iface->get_character_extents = nautilus_canvas_item_accessible_get_character_extents;
+ iface->get_offset_at_point = nautilus_canvas_item_accessible_get_offset_at_point;
+}
+
+static GType nautilus_canvas_item_accessible_get_type (void);
+
+G_DEFINE_TYPE_WITH_CODE (NautilusCanvasItemAccessible,
+ nautilus_canvas_item_accessible,
+ eel_canvas_item_accessible_get_type (),
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_IMAGE,
+ nautilus_canvas_item_accessible_image_interface_init)
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT,
+ nautilus_canvas_item_accessible_text_interface_init)
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION,
+ nautilus_canvas_item_accessible_action_interface_init));
+
+static AtkStateSet *
+nautilus_canvas_item_accessible_ref_state_set (AtkObject *accessible)
+{
+ AtkStateSet *state_set;
+ NautilusCanvasItem *item;
+ NautilusCanvasContainer *container;
+ GList *selection;
+ gboolean one_item_selected;
+
+ state_set = ATK_OBJECT_CLASS (nautilus_canvas_item_accessible_parent_class)->ref_state_set (accessible);
+
+ item = NAUTILUS_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
+ if (!item)
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
+ return state_set;
+ }
+ container = NAUTILUS_CANVAS_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
+ if (item->details->is_highlighted_as_keyboard_focus)
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
+ }
+ else if (!container->details->keyboard_focus)
+ {
+ selection = nautilus_canvas_container_get_selection (container);
+ one_item_selected = (g_list_length (selection) == 1) &&
+ item->details->is_highlighted_for_selection;
+
+ if (one_item_selected)
+ {
+ atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
+ }
+
+ g_list_free (selection);
+ }
+
+ return state_set;
+}
+
+static void
+nautilus_canvas_item_accessible_finalize (GObject *object)
+{
+ NautilusCanvasItemAccessiblePrivate *priv;
+ int i;
+
+ priv = GET_ACCESSIBLE_PRIV (object);
+
+ for (i = 0; i < LAST_ACTION; i++)
+ {
+ g_free (priv->action_descriptions[i]);
+ }
+ g_free (priv->image_description);
+ g_free (priv->description);
+
+ G_OBJECT_CLASS (nautilus_canvas_item_accessible_parent_class)->finalize (object);
+}
+
+static void
+nautilus_canvas_item_accessible_initialize (AtkObject *accessible,
+ gpointer widget)
+{
+ ATK_OBJECT_CLASS (nautilus_canvas_item_accessible_parent_class)->initialize (accessible, widget);
+
+ atk_object_set_role (accessible, ATK_ROLE_CANVAS);
+}
+
+static void
+nautilus_canvas_item_accessible_class_init (NautilusCanvasItemAccessibleClass *klass)
+{
+ AtkObjectClass *aclass = ATK_OBJECT_CLASS (klass);
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_canvas_item_accessible_finalize;
+
+ aclass->initialize = nautilus_canvas_item_accessible_initialize;
+
+ aclass->get_name = nautilus_canvas_item_accessible_get_name;
+ aclass->get_description = nautilus_canvas_item_accessible_get_description;
+ aclass->get_parent = nautilus_canvas_item_accessible_get_parent;
+ aclass->get_index_in_parent = nautilus_canvas_item_accessible_get_index_in_parent;
+ aclass->ref_state_set = nautilus_canvas_item_accessible_ref_state_set;
+
+ g_type_class_add_private (klass, sizeof (NautilusCanvasItemAccessiblePrivate));
+}
+
+static void
+nautilus_canvas_item_accessible_init (NautilusCanvasItemAccessible *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_canvas_item_accessible_get_type (),
+ NautilusCanvasItemAccessiblePrivate);
+}
+
+/* dummy typedef */
+typedef AtkObjectFactory NautilusCanvasItemAccessibleFactory;
+typedef AtkObjectFactoryClass NautilusCanvasItemAccessibleFactoryClass;
+
+G_DEFINE_TYPE (NautilusCanvasItemAccessibleFactory, nautilus_canvas_item_accessible_factory,
+ ATK_TYPE_OBJECT_FACTORY);
+
+static AtkObject *
+nautilus_canvas_item_accessible_factory_create_accessible (GObject *for_object)
+{
+ AtkObject *accessible;
+ NautilusCanvasItem *item;
+
+ item = NAUTILUS_CANVAS_ITEM (for_object);
+ g_assert (item != NULL);
+
+ item->details->text = g_string_new (NULL);
+ if (item->details->editable_text)
+ {
+ g_string_append (item->details->text, item->details->editable_text);
+ }
+ if (item->details->additional_text)
+ {
+ g_string_append (item->details->text, item->details->additional_text);
+ }
+
+ accessible = g_object_new (nautilus_canvas_item_accessible_get_type (), NULL);
+ atk_object_initialize (accessible, for_object);
+
+ return accessible;
+}
+
+static GType
+nautilus_canvas_item_accessible_factory_get_accessible_type (void)
+{
+ return nautilus_canvas_item_accessible_get_type ();
+}
+
+static void
+nautilus_canvas_item_accessible_factory_init (NautilusCanvasItemAccessibleFactory *self)
+{
+}
+
+static void
+nautilus_canvas_item_accessible_factory_class_init (NautilusCanvasItemAccessibleFactoryClass *klass)
+{
+ klass->create_accessible = nautilus_canvas_item_accessible_factory_create_accessible;
+ klass->get_accessible_type = nautilus_canvas_item_accessible_factory_get_accessible_type;
+}
diff --git a/src/nautilus-canvas-item.h b/src/nautilus-canvas-item.h
new file mode 100644
index 000000000..436fb6b46
--- /dev/null
+++ b/src/nautilus-canvas-item.h
@@ -0,0 +1,90 @@
+
+/* Nautilus - Canvas item class for canvas container.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Author: Andy Hertzfeld <andy@eazel.com>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <eel/eel-canvas.h>
+#include <eel/eel-art-extensions.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_CANVAS_ITEM nautilus_canvas_item_get_type()
+#define NAUTILUS_CANVAS_ITEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_CANVAS_ITEM, NautilusCanvasItem))
+#define NAUTILUS_CANVAS_ITEM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_CANVAS_ITEM, NautilusCanvasItemClass))
+#define NAUTILUS_IS_CANVAS_ITEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_CANVAS_ITEM))
+#define NAUTILUS_IS_CANVAS_ITEM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_CANVAS_ITEM))
+#define NAUTILUS_CANVAS_ITEM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_CANVAS_ITEM, NautilusCanvasItemClass))
+
+typedef struct NautilusCanvasItem NautilusCanvasItem;
+typedef struct NautilusCanvasItemClass NautilusCanvasItemClass;
+typedef struct NautilusCanvasItemDetails NautilusCanvasItemDetails;
+
+struct NautilusCanvasItem {
+ EelCanvasItem item;
+ NautilusCanvasItemDetails *details;
+ gpointer user_data;
+};
+
+struct NautilusCanvasItemClass {
+ EelCanvasItemClass parent_class;
+};
+
+/* not namespaced due to their length */
+typedef enum {
+ BOUNDS_USAGE_FOR_LAYOUT,
+ BOUNDS_USAGE_FOR_ENTIRE_ITEM,
+ BOUNDS_USAGE_FOR_DISPLAY
+} NautilusCanvasItemBoundsUsage;
+
+/* GObject */
+GType nautilus_canvas_item_get_type (void);
+
+/* attributes */
+void nautilus_canvas_item_set_image (NautilusCanvasItem *item,
+ GdkPixbuf *image);
+cairo_surface_t* nautilus_canvas_item_get_drag_surface (NautilusCanvasItem *item);
+void nautilus_canvas_item_set_emblems (NautilusCanvasItem *item,
+ GList *emblem_pixbufs);
+
+/* geometry and hit testing */
+gboolean nautilus_canvas_item_hit_test_rectangle (NautilusCanvasItem *item,
+ EelIRect canvas_rect);
+void nautilus_canvas_item_invalidate_label (NautilusCanvasItem *item);
+void nautilus_canvas_item_invalidate_label_size (NautilusCanvasItem *item);
+EelDRect nautilus_canvas_item_get_icon_rectangle (const NautilusCanvasItem *item);
+void nautilus_canvas_item_get_bounds_for_layout (NautilusCanvasItem *item,
+ double *x1, double *y1, double *x2, double *y2);
+void nautilus_canvas_item_get_bounds_for_entire_item (NautilusCanvasItem *item,
+ double *x1, double *y1, double *x2, double *y2);
+void nautilus_canvas_item_update_bounds (NautilusCanvasItem *item,
+ double i2w_dx, double i2w_dy);
+void nautilus_canvas_item_set_is_visible (NautilusCanvasItem *item,
+ gboolean visible);
+/* whether the entire label text must be visible at all times */
+void nautilus_canvas_item_set_entire_text (NautilusCanvasItem *canvas_item,
+ gboolean entire_text);
+
+G_END_DECLS
diff --git a/src/nautilus-canvas-private.h b/src/nautilus-canvas-private.h
new file mode 100644
index 000000000..e60e86299
--- /dev/null
+++ b/src/nautilus-canvas-private.h
@@ -0,0 +1,223 @@
+/* gnome-canvas-container-private.h
+
+ Copyright (C) 1999, 2000 Free Software Foundation
+ Copyright (C) 2000 Eazel, Inc.
+
+ 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,
+ see <http://www.gnu.org/licenses/>.
+
+ Author: Ettore Perazzoli <ettore@gnu.org>
+*/
+
+#pragma once
+
+#include <eel/eel-glib-extensions.h>
+#include "nautilus-canvas-item.h"
+#include "nautilus-canvas-container.h"
+#include "nautilus-canvas-dnd.h"
+
+/* An Icon. */
+
+typedef struct {
+ /* Object represented by this icon. */
+ NautilusCanvasIconData *data;
+
+ /* Canvas item for the icon. */
+ NautilusCanvasItem *item;
+
+ /* X/Y coordinates. */
+ double x, y;
+
+ /*
+ * In RTL mode x is RTL x position, we use saved_ltr_x for
+ * keeping track of x value before it gets converted into
+ * RTL value, this is used for saving the icon position
+ * to the nautilus metafile.
+ */
+ double saved_ltr_x;
+
+ /* Position in the view */
+ int position;
+
+ /* Whether this item is selected. */
+ eel_boolean_bit is_selected : 1;
+
+ /* Whether this item was selected before rubberbanding. */
+ eel_boolean_bit was_selected_before_rubberband : 1;
+
+ /* Whether this item is visible in the view. */
+ eel_boolean_bit is_visible : 1;
+} NautilusCanvasIcon;
+
+
+/* Private NautilusCanvasContainer members. */
+
+typedef struct {
+ gboolean active;
+
+ double start_x, start_y;
+
+ EelCanvasItem *selection_rectangle;
+ GdkDevice *device;
+
+ guint timer_id;
+
+ guint prev_x, prev_y;
+ int last_adj_x;
+ int last_adj_y;
+} NautilusCanvasRubberbandInfo;
+
+typedef enum {
+ DRAG_STATE_INITIAL,
+ DRAG_STATE_MOVE_OR_COPY,
+ DRAG_STATE_STRETCH
+} DragState;
+
+typedef struct {
+ /* Pointer position in canvas coordinates. */
+ int pointer_x, pointer_y;
+
+ /* Icon top, left, and size in canvas coordinates. */
+ int icon_x, icon_y;
+ guint icon_size;
+} StretchState;
+
+typedef enum {
+ AXIS_NONE,
+ AXIS_HORIZONTAL,
+ AXIS_VERTICAL
+} Axis;
+
+enum {
+ LABEL_COLOR,
+ LABEL_COLOR_HIGHLIGHT,
+ LABEL_COLOR_ACTIVE,
+ LABEL_COLOR_PRELIGHT,
+ LABEL_INFO_COLOR,
+ LABEL_INFO_COLOR_HIGHLIGHT,
+ LABEL_INFO_COLOR_ACTIVE,
+ LAST_LABEL_COLOR
+};
+
+struct NautilusCanvasContainerDetails {
+ /* List of icons. */
+ GList *icons;
+ GList *new_icons;
+ GList *selection;
+ GHashTable *icon_set;
+
+ /* Currently focused icon for accessibility. */
+ NautilusCanvasIcon *focus;
+ gboolean keyboard_focus;
+
+ /* Starting icon for keyboard rubberbanding. */
+ NautilusCanvasIcon *keyboard_rubberband_start;
+
+ /* Last highlighted drop target. */
+ NautilusCanvasIcon *drop_target;
+
+ /* Rubberbanding status. */
+ NautilusCanvasRubberbandInfo 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.)
+ */
+ guint keyboard_icon_reveal_timer_id;
+ NautilusCanvasIcon *keyboard_icon_to_reveal;
+
+ /* Used to coalesce selection changed signals in some cases */
+ guint selection_changed_id;
+
+ /* If a request is made to reveal an unpositioned icon we remember
+ * it and reveal it once it gets positioned (in relayout).
+ */
+ NautilusCanvasIcon *pending_icon_to_reveal;
+
+ /* Remembered information about the start of the current event. */
+ guint32 button_down_time;
+
+ /* Drag state. Valid only if drag_button is non-zero. */
+ guint drag_button;
+ NautilusCanvasIcon *drag_icon;
+ int drag_x, drag_y;
+ DragState drag_state;
+ gboolean drag_started;
+
+ gboolean icon_selected_on_button_down;
+ gboolean double_clicked;
+ NautilusCanvasIcon *double_click_icon[2]; /* Both clicks in a double click need to be on the same icon */
+ guint double_click_button[2];
+
+ NautilusCanvasIcon *range_selection_base_icon;
+
+ /* Idle ID. */
+ guint idle_id;
+
+ /* Align idle id */
+ guint align_idle_id;
+
+ /* DnD info. */
+ NautilusCanvasDndInfo *dnd_info;
+ NautilusDragInfo *dnd_source_info;
+
+ /* zoom level */
+ int zoom_level;
+
+ /* specific fonts used to draw labels */
+ char *font;
+
+ /* State used so arrow keys don't wander if icons aren't lined up.
+ */
+ int arrow_key_start_x;
+ int arrow_key_start_y;
+ GtkDirectionType arrow_key_direction;
+
+ /* Mode settings. */
+ gboolean single_click_mode;
+
+ /* Set to TRUE after first allocation has been done */
+ gboolean has_been_allocated;
+
+ int size_allocation_count;
+ guint size_allocation_count_id;
+
+ /* a11y items used by canvas items */
+ guint a11y_item_action_idle_handler;
+ GQueue* a11y_item_action_queue;
+
+ eel_boolean_bit in_layout_now : 1;
+ eel_boolean_bit is_loading : 1;
+ eel_boolean_bit is_populating_container : 1;
+ eel_boolean_bit needs_resort : 1;
+ eel_boolean_bit selection_needs_resort : 1;
+};
+
+/* Private functions shared by mutiple files. */
+NautilusCanvasIcon *nautilus_canvas_container_get_icon_by_uri (NautilusCanvasContainer *container,
+ const char *uri);
+void nautilus_canvas_container_select_list_unselect_others (NautilusCanvasContainer *container,
+ GList *icons);
+char * nautilus_canvas_container_get_icon_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *canvas);
+char * nautilus_canvas_container_get_icon_activation_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *canvas);
+char * nautilus_canvas_container_get_icon_drop_target_uri (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *canvas);
+void nautilus_canvas_container_update_icon (NautilusCanvasContainer *container,
+ NautilusCanvasIcon *canvas);
+gboolean nautilus_canvas_container_scroll (NautilusCanvasContainer *container,
+ int delta_x,
+ int delta_y);
+void nautilus_canvas_container_update_scroll_region (NautilusCanvasContainer *container);
diff --git a/src/nautilus-canvas-view-container.c b/src/nautilus-canvas-view-container.c
new file mode 100644
index 000000000..a8b1f8ad6
--- /dev/null
+++ b/src/nautilus-canvas-view-container.c
@@ -0,0 +1,377 @@
+/* fm-icon-container.h - the container widget for file manager icons
+ *
+ * Copyright (C) 2002 Sun Microsystems, Inc.
+ *
+ * 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,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Michael Meeks <michael@ximian.com>
+ */
+
+#include "nautilus-canvas-view-container.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <string.h>
+
+#include "nautilus-canvas-view.h"
+#include "nautilus-enums.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-thumbnails.h"
+
+struct _NautilusCanvasViewContainer
+{
+ NautilusCanvasContainer parent;
+
+ NautilusCanvasView *view;
+};
+
+G_DEFINE_TYPE (NautilusCanvasViewContainer, nautilus_canvas_view_container, NAUTILUS_TYPE_CANVAS_CONTAINER);
+
+static GQuark attribute_none_q;
+
+static NautilusCanvasView *
+get_canvas_view (NautilusCanvasContainer *container)
+{
+ /* Type unsafe comparison for performance */
+ return ((NautilusCanvasViewContainer *) container)->view;
+}
+
+static NautilusIconInfo *
+nautilus_canvas_view_container_get_icon_images (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ int size,
+ gboolean for_drag_accept)
+{
+ NautilusCanvasView *canvas_view;
+ NautilusFile *file;
+ NautilusFileIconFlags flags;
+ NautilusIconInfo *icon_info;
+ gint scale;
+
+ file = (NautilusFile *) data;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+ canvas_view = get_canvas_view (container);
+ g_return_val_if_fail (canvas_view != NULL, NULL);
+
+ flags = NAUTILUS_FILE_ICON_FLAGS_USE_EMBLEMS |
+ NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS;
+
+ if (for_drag_accept)
+ {
+ flags |= NAUTILUS_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT;
+ }
+
+ scale = gtk_widget_get_scale_factor (GTK_WIDGET (canvas_view));
+ icon_info = nautilus_file_get_icon (file, size, scale, flags);
+
+ return icon_info;
+}
+
+static char *
+nautilus_canvas_view_container_get_icon_description (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusFile *file;
+ char *mime_type;
+ const char *description;
+
+ file = NAUTILUS_FILE (data);
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ mime_type = nautilus_file_get_mime_type (file);
+ description = g_content_type_get_description (mime_type);
+ g_free (mime_type);
+ return g_strdup (description);
+}
+
+static void
+nautilus_canvas_view_container_prioritize_thumbnailing (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data)
+{
+ NautilusFile *file;
+ char *uri;
+
+ file = (NautilusFile *) data;
+
+ g_assert (NAUTILUS_IS_FILE (file));
+
+ if (nautilus_file_is_thumbnailing (file))
+ {
+ uri = nautilus_file_get_uri (file);
+ nautilus_thumbnail_prioritize (uri);
+ g_free (uri);
+ }
+}
+
+static GQuark *
+get_quark_from_strv (gchar **value)
+{
+ GQuark *quark;
+ int i;
+
+ quark = g_new0 (GQuark, g_strv_length (value) + 1);
+ for (i = 0; value[i] != NULL; ++i)
+ {
+ quark[i] = g_quark_from_string (value[i]);
+ }
+
+ return quark;
+}
+
+/*
+ * Get the preference for which caption text should appear
+ * beneath icons.
+ */
+static GQuark *
+nautilus_canvas_view_container_get_icon_text_attributes_from_preferences (void)
+{
+ GQuark *attributes;
+ gchar **value;
+
+ value = g_settings_get_strv (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS);
+ attributes = get_quark_from_strv (value);
+ g_strfreev (value);
+
+ /* We don't need to sanity check the attributes list even though it came
+ * from preferences.
+ *
+ * There are 2 ways that the values in the list could be bad.
+ *
+ * 1) The user picks "bad" values. "bad" values are those that result in
+ * there being duplicate attributes in the list.
+ *
+ * 2) Value stored in GConf are tampered with. Its possible physically do
+ * this by pulling the rug underneath GConf and manually editing its
+ * config files. Its also possible to use a third party GConf key
+ * editor and store garbage for the keys in question.
+ *
+ * Thankfully, the Nautilus preferences machinery deals with both of
+ * these cases.
+ *
+ * In the first case, the preferences dialog widgetry prevents
+ * duplicate attributes by making "bad" choices insensitive.
+ *
+ * In the second case, the preferences getter (and also the auto storage) for
+ * string_array values are always valid members of the enumeration associated
+ * with the preference.
+ *
+ * So, no more error checking on attributes is needed here and we can return
+ * a the auto stored value.
+ */
+ return attributes;
+}
+
+static int
+quarkv_length (GQuark *attributes)
+{
+ int i;
+ i = 0;
+ while (attributes[i] != 0)
+ {
+ i++;
+ }
+ return i;
+}
+
+/**
+ * nautilus_canvas_view_get_icon_text_attribute_names:
+ *
+ * Get a list representing which text attributes should be displayed
+ * beneath an icon. The result is dependent on zoom level and possibly
+ * user configuration. Don't free the result.
+ * @view: NautilusCanvasView to query.
+ *
+ **/
+static GQuark *
+nautilus_canvas_view_container_get_icon_text_attribute_names (NautilusCanvasContainer *container,
+ int *len)
+{
+ GQuark *attributes;
+ int piece_count;
+
+ const int pieces_by_level[] =
+ {
+ 1, /* NAUTILUS_ZOOM_LEVEL_SMALL */
+ 2, /* NAUTILUS_ZOOM_LEVEL_STANDARD */
+ 3, /* NAUTILUS_ZOOM_LEVEL_LARGE */
+ 3, /* NAUTILUS_ZOOM_LEVEL_LARGER */
+ };
+
+ piece_count = pieces_by_level[nautilus_canvas_container_get_zoom_level (container)];
+
+ attributes = nautilus_canvas_view_container_get_icon_text_attributes_from_preferences ();
+
+ *len = MIN (piece_count, quarkv_length (attributes));
+
+ return attributes;
+}
+
+/* This callback returns the text, both the editable part, and the
+ * part below that is not editable.
+ */
+static void
+nautilus_canvas_view_container_get_icon_text (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *data,
+ char **editable_text,
+ char **additional_text,
+ gboolean include_invisible)
+{
+ GQuark *attributes;
+ char *text_array[4];
+ int i, j, num_attributes;
+ NautilusCanvasView *canvas_view;
+ NautilusFile *file;
+ gboolean use_additional;
+
+ file = NAUTILUS_FILE (data);
+
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (editable_text != NULL);
+ canvas_view = get_canvas_view (container);
+ g_return_if_fail (canvas_view != NULL);
+
+ use_additional = (additional_text != NULL);
+
+ /* Strip the suffix for nautilus object xml files. */
+ *editable_text = nautilus_file_get_display_name (file);
+
+ if (!use_additional)
+ {
+ return;
+ }
+
+ /* Find out what attributes go below each icon. */
+ attributes = nautilus_canvas_view_container_get_icon_text_attribute_names (container,
+ &num_attributes);
+
+ /* Get the attributes. */
+ j = 0;
+ for (i = 0; i < num_attributes; ++i)
+ {
+ char *text;
+ if (attributes[i] == attribute_none_q)
+ {
+ continue;
+ }
+ text = nautilus_file_get_string_attribute_q (file, attributes[i]);
+ if (text == NULL)
+ {
+ continue;
+ }
+ text_array[j++] = text;
+ }
+ text_array[j] = NULL;
+
+ /* Return them. */
+ if (j == 0)
+ {
+ *additional_text = NULL;
+ }
+ else if (j == 1)
+ {
+ /* Only one item, avoid the strdup + free */
+ *additional_text = text_array[0];
+ }
+ else
+ {
+ *additional_text = g_strjoinv ("\n", text_array);
+
+ for (i = 0; i < j; i++)
+ {
+ g_free (text_array[i]);
+ }
+ }
+
+ g_free (attributes);
+}
+
+static int
+nautilus_canvas_view_container_compare_icons (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *icon_a,
+ NautilusCanvasIconData *icon_b)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = get_canvas_view (container);
+ g_return_val_if_fail (canvas_view != NULL, 0);
+
+ /* Type unsafe comparisons for performance */
+ return nautilus_canvas_view_compare_files (canvas_view,
+ (NautilusFile *) icon_a,
+ (NautilusFile *) icon_b);
+}
+
+static int
+nautilus_canvas_view_container_compare_icons_by_name (NautilusCanvasContainer *container,
+ NautilusCanvasIconData *icon_a,
+ NautilusCanvasIconData *icon_b)
+{
+ return nautilus_file_compare_for_sort
+ (NAUTILUS_FILE (icon_a),
+ NAUTILUS_FILE (icon_b),
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ FALSE, FALSE);
+}
+
+static void
+nautilus_canvas_view_container_class_init (NautilusCanvasViewContainerClass *klass)
+{
+ NautilusCanvasContainerClass *ic_class;
+
+ ic_class = &klass->parent_class;
+
+ attribute_none_q = g_quark_from_static_string ("none");
+
+ ic_class->get_icon_text = nautilus_canvas_view_container_get_icon_text;
+ ic_class->get_icon_images = nautilus_canvas_view_container_get_icon_images;
+ ic_class->get_icon_description = nautilus_canvas_view_container_get_icon_description;
+ ic_class->prioritize_thumbnailing = nautilus_canvas_view_container_prioritize_thumbnailing;
+
+ ic_class->compare_icons = nautilus_canvas_view_container_compare_icons;
+ ic_class->compare_icons_by_name = nautilus_canvas_view_container_compare_icons_by_name;
+}
+
+static void
+nautilus_canvas_view_container_init (NautilusCanvasViewContainer *canvas_container)
+{
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (canvas_container)),
+ GTK_STYLE_CLASS_VIEW);
+}
+
+NautilusCanvasContainer *
+nautilus_canvas_view_container_construct (NautilusCanvasViewContainer *canvas_container,
+ NautilusCanvasView *view)
+{
+ AtkObject *atk_obj;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), NULL);
+
+ canvas_container->view = view;
+ atk_obj = gtk_widget_get_accessible (GTK_WIDGET (canvas_container));
+ atk_object_set_name (atk_obj, _("Icon View"));
+
+ return NAUTILUS_CANVAS_CONTAINER (canvas_container);
+}
+
+NautilusCanvasContainer *
+nautilus_canvas_view_container_new (NautilusCanvasView *view)
+{
+ return nautilus_canvas_view_container_construct
+ (g_object_new (NAUTILUS_TYPE_CANVAS_VIEW_CONTAINER, NULL),
+ view);
+}
diff --git a/src/nautilus-canvas-view-container.h b/src/nautilus-canvas-view-container.h
new file mode 100644
index 000000000..b0e103181
--- /dev/null
+++ b/src/nautilus-canvas-view-container.h
@@ -0,0 +1,35 @@
+
+/* fm-icon-container.h - the container widget for file manager icons
+
+ Copyright (C) 2002 Sun Microsystems, Inc.
+
+ 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,
+ see <http://www.gnu.org/licenses/>.
+
+ Author: Michael Meeks <michael@ximian.com>
+*/
+
+#pragma once
+
+#include "nautilus-canvas-container.h"
+
+#define NAUTILUS_TYPE_CANVAS_VIEW_CONTAINER nautilus_canvas_view_container_get_type()
+
+G_DECLARE_FINAL_TYPE (NautilusCanvasViewContainer, nautilus_canvas_view_container,
+ NAUTILUS, CANVAS_VIEW_CONTAINER,
+ NautilusCanvasContainer)
+
+NautilusCanvasContainer *nautilus_canvas_view_container_construct (NautilusCanvasViewContainer *canvas_container,
+ NautilusCanvasView *view);
+NautilusCanvasContainer *nautilus_canvas_view_container_new (NautilusCanvasView *view);
diff --git a/src/nautilus-canvas-view.c b/src/nautilus-canvas-view.c
new file mode 100644
index 000000000..3679e8053
--- /dev/null
+++ b/src/nautilus-canvas-view.c
@@ -0,0 +1,1646 @@
+/* fm-canvas-view.c - implementation of canvas view of directory.
+ *
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * 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,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: John Sullivan <sullivan@eazel.com>
+ */
+
+#include <config.h>
+
+#include "nautilus-canvas-view.h"
+
+#include "nautilus-canvas-view-container.h"
+#include "nautilus-error-reporting.h"
+#include "nautilus-files-view-dnd.h"
+#include "nautilus-toolbar.h"
+#include "nautilus-view.h"
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include "nautilus-directory.h"
+#include "nautilus-dnd.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-ui-utilities.h"
+#include "nautilus-global-preferences.h"
+#include "nautilus-canvas-container.h"
+#include "nautilus-canvas-dnd.h"
+#include "nautilus-metadata.h"
+#include "nautilus-clipboard.h"
+#include "nautilus-gtk4-helpers.h"
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_CANVAS_VIEW
+#include "nautilus-debug.h"
+
+#include <locale.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+typedef gboolean (*SortCriterionMatchFunc) (NautilusFile *file);
+
+typedef struct
+{
+ const NautilusFileSortType sort_type;
+ const char *metadata_text;
+ const char *action_target_name;
+ const gboolean reverse_order;
+ SortCriterionMatchFunc match_func;
+} SortCriterion;
+
+typedef enum
+{
+ MENU_ITEM_TYPE_STANDARD,
+ MENU_ITEM_TYPE_CHECK,
+ MENU_ITEM_TYPE_RADIO,
+ MENU_ITEM_TYPE_TREE
+} MenuItemType;
+
+struct _NautilusCanvasView
+{
+ NautilusFilesView parent_instance;
+
+ GList *icons_not_positioned;
+
+ guint react_to_canvas_change_idle_id;
+
+ const SortCriterion *sort;
+
+ GtkWidget *canvas_container;
+
+ /* FIXME: Needed for async operations. Suposedly we would use cancellable and gtask,
+ * sadly gtkclipboard doesn't support that.
+ * We follow this pattern for checking validity of the object in the views.
+ * Ideally we would connect to a weak reference and do a cancellable.
+ */
+ gboolean destroyed;
+};
+
+/* Note that the first item in this list is the default sort,
+ * and that the items show up in the menu in the order they
+ * appear in this list.
+ */
+static const SortCriterion sort_criteria[] =
+{
+ {
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ "name",
+ "name",
+ FALSE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ "name",
+ "name-desc",
+ TRUE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_SIZE,
+ "size",
+ "size",
+ TRUE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_TYPE,
+ "type",
+ "type",
+ FALSE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ "modification date",
+ "modification-date",
+ FALSE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ "modification date",
+ "modification-date-desc",
+ TRUE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_ATIME,
+ "access date",
+ "access-date",
+ FALSE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_ATIME,
+ "access date",
+ "access-date-desc",
+ TRUE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_BTIME,
+ "creation date",
+ "creation-date",
+ FALSE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_BTIME,
+ "creation date",
+ "creation-date-desc",
+ TRUE
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_TRASHED_TIME,
+ "trashed",
+ "trash-time",
+ TRUE,
+ nautilus_file_is_in_trash
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE,
+ NULL,
+ "search-relevance",
+ TRUE,
+ nautilus_file_is_in_search
+ },
+ {
+ NAUTILUS_FILE_SORT_BY_RECENCY,
+ NULL,
+ "recency",
+ TRUE,
+ nautilus_file_is_in_recent
+ }
+};
+
+G_DEFINE_TYPE (NautilusCanvasView, nautilus_canvas_view, NAUTILUS_TYPE_FILES_VIEW);
+
+static void nautilus_canvas_view_set_directory_sort_by (NautilusCanvasView *canvas_view,
+ NautilusFile *file,
+ const SortCriterion *sort);
+static void nautilus_canvas_view_update_click_mode (NautilusCanvasView *canvas_view);
+static void nautilus_canvas_view_reveal_selection (NautilusFilesView *view);
+static const SortCriterion *get_sort_criterion_by_metadata_text (const char *metadata_text,
+ gboolean reversed);
+static const SortCriterion *get_sort_criterion_by_sort_type (NautilusFileSortType sort_type,
+ gboolean reversed);
+static const SortCriterion *get_default_sort_order (NautilusFile *file);
+static void nautilus_canvas_view_clear (NautilusFilesView *view);
+static void on_clipboard_owner_changed (GtkClipboard *clipboard,
+ GdkEvent *event,
+ gpointer user_data);
+
+static void
+nautilus_canvas_view_destroy (GtkWidget *object)
+{
+ NautilusCanvasView *canvas_view;
+ GtkClipboard *clipboard;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (object);
+
+ nautilus_canvas_view_clear (NAUTILUS_FILES_VIEW (object));
+
+ if (canvas_view->react_to_canvas_change_idle_id != 0)
+ {
+ g_source_remove (canvas_view->react_to_canvas_change_idle_id);
+ canvas_view->react_to_canvas_change_idle_id = 0;
+ }
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ g_signal_handlers_disconnect_by_func (clipboard,
+ on_clipboard_owner_changed,
+ canvas_view);
+
+ if (canvas_view->icons_not_positioned)
+ {
+ nautilus_file_list_free (canvas_view->icons_not_positioned);
+ canvas_view->icons_not_positioned = NULL;
+ }
+
+ GTK_WIDGET_CLASS (nautilus_canvas_view_parent_class)->destroy (object);
+}
+
+static NautilusCanvasContainer *
+get_canvas_container (NautilusCanvasView *canvas_view)
+{
+ return NAUTILUS_CANVAS_CONTAINER (canvas_view->canvas_container);
+}
+
+NautilusCanvasContainer *
+nautilus_canvas_view_get_canvas_container (NautilusCanvasView *canvas_view)
+{
+ return get_canvas_container (canvas_view);
+}
+
+static void
+update_sort_criterion (NautilusCanvasView *canvas_view,
+ const SortCriterion *sort,
+ gboolean set_metadata)
+{
+ NautilusFile *file;
+ const SortCriterion *overrided_sort_criterion;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (canvas_view));
+
+ /* Make sure we use the default one and not one that the user used previously
+ * of the change to not allow sorting on search and recent, or the
+ * case that the user or some app modified directly the metadata */
+ if (nautilus_file_is_in_search (file) || nautilus_file_is_in_recent (file))
+ {
+ overrided_sort_criterion = get_default_sort_order (file);
+ }
+ else if (sort != NULL && canvas_view->sort != sort)
+ {
+ overrided_sort_criterion = sort;
+ if (set_metadata)
+ {
+ /* Store the new sort setting. */
+ nautilus_canvas_view_set_directory_sort_by (canvas_view,
+ file,
+ sort);
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ canvas_view->sort = overrided_sort_criterion;
+}
+
+static void
+list_covers (NautilusCanvasIconData *data,
+ gpointer callback_data)
+{
+ GSList **file_list;
+
+ file_list = callback_data;
+
+ *file_list = g_slist_prepend (*file_list, data);
+}
+
+static void
+unref_cover (NautilusCanvasIconData *data,
+ gpointer callback_data)
+{
+ nautilus_file_unref (NAUTILUS_FILE (data));
+}
+
+static void
+nautilus_canvas_view_clear (NautilusFilesView *view)
+{
+ NautilusCanvasContainer *canvas_container;
+ GSList *file_list;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ canvas_container = get_canvas_container (NAUTILUS_CANVAS_VIEW (view));
+ if (!canvas_container)
+ {
+ return;
+ }
+
+ /* Clear away the existing icons. */
+ file_list = NULL;
+ nautilus_canvas_container_for_each (canvas_container, list_covers, &file_list);
+ nautilus_canvas_container_clear (canvas_container);
+ g_slist_foreach (file_list, (GFunc) unref_cover, NULL);
+ g_slist_free (file_list);
+}
+
+static void
+nautilus_canvas_view_remove_file (NautilusFilesView *view,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusCanvasView *canvas_view;
+
+ /* This used to assert that 'directory == nautilus_files_view_get_model (view)', but that
+ * resulted in a lot of crash reports (bug #352592). I don't see how that trace happens.
+ * It seems that somehow we get a files_changed event sent to the view from a directory
+ * that isn't the model, but the code disables the monitor and signal callback handlers when
+ * changing directories. Maybe we can get some more information when this happens.
+ * Further discussion in bug #368178.
+ */
+ if (directory != nautilus_files_view_get_model (view))
+ {
+ char *file_uri, *dir_uri, *model_uri;
+ file_uri = nautilus_file_get_uri (file);
+ dir_uri = nautilus_directory_get_uri (directory);
+ model_uri = nautilus_directory_get_uri (nautilus_files_view_get_model (view));
+ g_warning ("nautilus_canvas_view_remove_file() - directory not canvas view model, shouldn't happen.\n"
+ "file: %p:%s, dir: %p:%s, model: %p:%s, view loading: %d\n"
+ "If you see this, please add this info to http://bugzilla.gnome.org/show_bug.cgi?id=368178",
+ file, file_uri, directory, dir_uri, nautilus_files_view_get_model (view), model_uri, nautilus_files_view_get_loading (view));
+ g_free (file_uri);
+ g_free (dir_uri);
+ g_free (model_uri);
+ }
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+
+ if (nautilus_canvas_container_remove (get_canvas_container (canvas_view),
+ NAUTILUS_CANVAS_ICON_DATA (file)))
+ {
+ nautilus_file_unref (file);
+ }
+}
+
+static void
+nautilus_canvas_view_add_files (NautilusFilesView *view,
+ GList *files)
+{
+ NautilusCanvasView *canvas_view;
+ NautilusCanvasContainer *canvas_container;
+ GList *l;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+ canvas_container = get_canvas_container (canvas_view);
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ if (nautilus_canvas_container_add (canvas_container,
+ NAUTILUS_CANVAS_ICON_DATA (l->data)))
+ {
+ nautilus_file_ref (NAUTILUS_FILE (l->data));
+ }
+ }
+}
+
+static void
+nautilus_canvas_view_file_changed (NautilusFilesView *view,
+ NautilusFile *file,
+ NautilusDirectory *directory)
+{
+ NautilusCanvasView *canvas_view;
+
+ g_assert (directory == nautilus_files_view_get_model (view));
+
+ g_return_if_fail (view != NULL);
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+
+ nautilus_canvas_container_request_update
+ (get_canvas_container (canvas_view),
+ NAUTILUS_CANVAS_ICON_DATA (file));
+}
+
+static const SortCriterion *
+nautilus_canvas_view_get_directory_sort_by (NautilusCanvasView *canvas_view,
+ NautilusFile *file)
+{
+ const SortCriterion *default_sort;
+ g_autofree char *sort_by = NULL;
+ gboolean reversed;
+
+ default_sort = get_default_sort_order (file);
+ g_return_val_if_fail (default_sort != NULL, NULL);
+
+ sort_by = nautilus_file_get_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
+ default_sort->metadata_text);
+
+ reversed = nautilus_file_get_boolean_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+ default_sort->reverse_order);
+
+ return get_sort_criterion_by_metadata_text (sort_by, reversed);
+}
+
+static const SortCriterion *
+get_default_sort_order (NautilusFile *file)
+{
+ NautilusFileSortType sort_type;
+ gboolean reversed;
+
+ sort_type = nautilus_file_get_default_sort_type (file, &reversed);
+
+ return get_sort_criterion_by_sort_type (sort_type, reversed);
+}
+
+static void
+nautilus_canvas_view_set_directory_sort_by (NautilusCanvasView *canvas_view,
+ NautilusFile *file,
+ const SortCriterion *sort)
+{
+ const SortCriterion *default_sort_criterion;
+
+ default_sort_criterion = get_default_sort_order (file);
+ g_return_if_fail (default_sort_criterion != NULL);
+
+ nautilus_file_set_metadata
+ (file, NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY,
+ default_sort_criterion->metadata_text,
+ sort->metadata_text);
+ nautilus_file_set_boolean_metadata (file,
+ NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED,
+ default_sort_criterion->reverse_order,
+ sort->reverse_order);
+}
+
+static const SortCriterion *
+get_sort_criterion_by_metadata_text (const char *metadata_text,
+ gboolean reversed)
+{
+ guint i;
+
+ /* Figure out what the new sort setting should be. */
+ for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++)
+ {
+ if (g_strcmp0 (sort_criteria[i].metadata_text, metadata_text) == 0
+ && reversed == sort_criteria[i].reverse_order)
+ {
+ return &sort_criteria[i];
+ }
+ }
+ return &sort_criteria[0];
+}
+
+static const SortCriterion *
+get_sort_criterion_by_action_target_name (const char *action_target_name)
+{
+ guint i;
+ /* Figure out what the new sort setting should be. */
+ for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++)
+ {
+ if (g_strcmp0 (sort_criteria[i].action_target_name, action_target_name) == 0)
+ {
+ return &sort_criteria[i];
+ }
+ }
+ return NULL;
+}
+
+static const SortCriterion *
+get_sort_criterion_by_sort_type (NautilusFileSortType sort_type,
+ gboolean reversed)
+{
+ guint i;
+
+ /* Figure out what the new sort setting should be. */
+ for (i = 0; i < G_N_ELEMENTS (sort_criteria); i++)
+ {
+ if (sort_type == sort_criteria[i].sort_type
+ && reversed == sort_criteria[i].reverse_order)
+ {
+ return &sort_criteria[i];
+ }
+ }
+
+ return &sort_criteria[0];
+}
+
+static NautilusCanvasZoomLevel
+get_default_zoom_level (NautilusCanvasView *canvas_view)
+{
+ NautilusCanvasZoomLevel default_zoom_level;
+
+ default_zoom_level = g_settings_get_enum (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL);
+
+ return CLAMP (default_zoom_level, NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL, NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER);
+}
+
+static void
+nautilus_canvas_view_begin_loading (NautilusFilesView *view)
+{
+ NautilusCanvasView *canvas_view;
+ NautilusFile *file;
+ char *uri;
+ const SortCriterion *sort;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+ file = nautilus_files_view_get_directory_as_file (view);
+ uri = nautilus_file_get_uri (file);
+
+ g_free (uri);
+
+ /* Set the sort mode.
+ * It's OK not to resort the icons because the
+ * container doesn't have any icons at this point.
+ */
+ sort = nautilus_canvas_view_get_directory_sort_by (canvas_view, file);
+ update_sort_criterion (canvas_view, sort, FALSE);
+
+ /* We could have changed to the trash directory or to searching, and then
+ * we need to update the menus */
+ nautilus_files_view_update_context_menus (view);
+ nautilus_files_view_update_toolbar_menus (view);
+}
+
+static void
+on_clipboard_contents_received (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ gpointer user_data)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (user_data);
+
+ if (canvas_view->destroyed)
+ {
+ /* We've been destroyed since call */
+ g_object_unref (canvas_view);
+ return;
+ }
+
+ if (nautilus_clipboard_is_cut_from_selection_data (selection_data))
+ {
+ GList *uris;
+ GList *files;
+
+ uris = nautilus_clipboard_get_uri_list_from_selection_data (selection_data);
+ files = nautilus_file_list_from_uri_list (uris);
+ nautilus_canvas_container_set_highlighted_for_clipboard (get_canvas_container (canvas_view),
+ files);
+
+ nautilus_file_list_free (files);
+ g_list_free_full (uris, g_free);
+ }
+ else
+ {
+ nautilus_canvas_container_set_highlighted_for_clipboard (get_canvas_container (canvas_view),
+ NULL);
+ }
+
+ g_object_unref (canvas_view);
+}
+
+static void
+update_clipboard_status (NautilusCanvasView *view)
+{
+ g_object_ref (view); /* Need to keep the object alive until we get the reply */
+ gtk_clipboard_request_contents (nautilus_clipboard_get (GTK_WIDGET (view)),
+ nautilus_clipboard_get_atom (),
+ on_clipboard_contents_received,
+ view);
+}
+
+static void
+on_clipboard_owner_changed (GtkClipboard *clipboard,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ update_clipboard_status (NAUTILUS_CANVAS_VIEW (user_data));
+}
+
+static void
+nautilus_canvas_view_end_loading (NautilusFilesView *view,
+ gboolean all_files_seen)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+ update_clipboard_status (canvas_view);
+}
+
+static NautilusCanvasZoomLevel
+nautilus_canvas_view_get_zoom_level (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE);
+
+ return nautilus_canvas_container_get_zoom_level (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)));
+}
+
+static void
+nautilus_canvas_view_zoom_to_level (NautilusFilesView *view,
+ gint new_level)
+{
+ NautilusCanvasView *canvas_view;
+ NautilusCanvasContainer *canvas_container;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+ g_return_if_fail (new_level >= NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL &&
+ new_level <= NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER);
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+ canvas_container = get_canvas_container (canvas_view);
+ if (nautilus_canvas_container_get_zoom_level (canvas_container) == new_level)
+ {
+ return;
+ }
+
+ nautilus_canvas_container_set_zoom_level (canvas_container, new_level);
+ g_action_group_change_action_state (nautilus_files_view_get_action_group (view),
+ "zoom-to-level", g_variant_new_int32 (new_level));
+
+ nautilus_files_view_update_toolbar_menus (view);
+}
+
+static void
+nautilus_canvas_view_bump_zoom_level (NautilusFilesView *view,
+ int zoom_increment)
+{
+ NautilusCanvasZoomLevel new_level;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+ if (!nautilus_files_view_supports_zooming (view))
+ {
+ return;
+ }
+
+ new_level = nautilus_canvas_view_get_zoom_level (view) + zoom_increment;
+
+ if (new_level >= NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL &&
+ new_level <= NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER)
+ {
+ nautilus_canvas_view_zoom_to_level (view, new_level);
+ }
+}
+
+static void
+nautilus_canvas_view_restore_standard_zoom_level (NautilusFilesView *view)
+{
+ nautilus_canvas_view_zoom_to_level (view, NAUTILUS_CANVAS_ZOOM_LEVEL_LARGE);
+}
+
+static gboolean
+nautilus_canvas_view_can_zoom_in (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), FALSE);
+
+ return nautilus_canvas_view_get_zoom_level (view)
+ < NAUTILUS_CANVAS_ZOOM_LEVEL_LARGER;
+}
+
+static gboolean
+nautilus_canvas_view_can_zoom_out (NautilusFilesView *view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), FALSE);
+
+ return nautilus_canvas_view_get_zoom_level (view)
+ > NAUTILUS_CANVAS_ZOOM_LEVEL_SMALL;
+}
+
+static gfloat
+nautilus_canvas_view_get_zoom_level_percentage (NautilusFilesView *view)
+{
+ guint icon_size;
+ NautilusCanvasZoomLevel zoom_level;
+
+ zoom_level = nautilus_canvas_view_get_zoom_level (view);
+ icon_size = nautilus_canvas_container_get_icon_size_for_zoom_level (zoom_level);
+
+ return (gfloat) icon_size / NAUTILUS_CANVAS_ICON_SIZE_LARGE;
+}
+
+static gboolean
+nautilus_canvas_view_is_zoom_level_default (NautilusFilesView *view)
+{
+ guint icon_size;
+ NautilusCanvasZoomLevel zoom_level;
+
+ zoom_level = nautilus_canvas_view_get_zoom_level (view);
+ icon_size = nautilus_canvas_container_get_icon_size_for_zoom_level (zoom_level);
+
+ return icon_size == NAUTILUS_CANVAS_ICON_SIZE_LARGE;
+}
+
+static gboolean
+nautilus_canvas_view_is_empty (NautilusFilesView *view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ return nautilus_canvas_container_is_empty
+ (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)));
+}
+
+static GList *
+nautilus_canvas_view_get_selection (NautilusFilesView *view)
+{
+ GList *list;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), NULL);
+
+ list = nautilus_canvas_container_get_selection
+ (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)));
+ nautilus_file_list_ref (list);
+ return list;
+}
+
+static void
+action_sort_order_changed (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ const gchar *target_name;
+ const SortCriterion *sort_criterion;
+
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (user_data));
+
+ target_name = g_variant_get_string (value, NULL);
+ sort_criterion = get_sort_criterion_by_action_target_name (target_name);
+
+ g_assert (sort_criterion != NULL);
+
+ update_sort_criterion (user_data, sort_criterion, TRUE);
+
+ nautilus_canvas_container_sort (get_canvas_container (user_data));
+ nautilus_canvas_view_reveal_selection (NAUTILUS_FILES_VIEW (user_data));
+
+ g_simple_action_set_state (action, value);
+}
+
+static void
+action_zoom_to_level (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ NautilusFilesView *view;
+ NautilusCanvasZoomLevel zoom_level;
+
+ g_assert (NAUTILUS_IS_FILES_VIEW (user_data));
+
+ view = NAUTILUS_FILES_VIEW (user_data);
+ zoom_level = g_variant_get_int32 (state);
+ nautilus_canvas_view_zoom_to_level (view, zoom_level);
+
+ g_simple_action_set_state (G_SIMPLE_ACTION (action), state);
+ if (g_settings_get_enum (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL) != zoom_level)
+ {
+ g_settings_set_enum (nautilus_icon_view_preferences,
+ NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL,
+ zoom_level);
+ }
+}
+
+const GActionEntry canvas_view_entries[] =
+{
+ { "sort", NULL, "s", "'name'", action_sort_order_changed },
+ { "zoom-to-level", NULL, NULL, "1", action_zoom_to_level }
+};
+
+static void
+update_sort_action_state_hint (NautilusCanvasView *canvas_view)
+{
+ NautilusFile *file;
+ GVariantBuilder builder;
+ GActionGroup *action_group;
+ GAction *action;
+ GVariant *state_hint;
+ gint idx;
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (canvas_view));
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+
+ for (idx = 0; idx < G_N_ELEMENTS (sort_criteria); idx++)
+ {
+ if (sort_criteria[idx].match_func == NULL ||
+ (file != NULL && sort_criteria[idx].match_func (file)))
+ {
+ g_variant_builder_add (&builder, "s", sort_criteria[idx].action_target_name);
+ }
+ }
+
+ state_hint = g_variant_builder_end (&builder);
+
+ action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (canvas_view));
+ action = g_action_map_lookup_action (G_ACTION_MAP (action_group), "sort");
+ g_simple_action_set_state_hint (G_SIMPLE_ACTION (action), state_hint);
+
+ g_variant_unref (state_hint);
+}
+
+static gboolean
+showing_recent_directory (NautilusFilesView *view)
+{
+ NautilusFile *file;
+
+ file = nautilus_files_view_get_directory_as_file (view);
+ if (file != NULL)
+ {
+ return nautilus_file_is_in_recent (file);
+ }
+ return FALSE;
+}
+
+static gboolean
+showing_search_directory (NautilusFilesView *view)
+{
+ NautilusFile *file;
+
+ file = nautilus_files_view_get_directory_as_file (view);
+ if (file != NULL)
+ {
+ return nautilus_file_is_in_search (file);
+ }
+ return FALSE;
+}
+
+static void
+nautilus_canvas_view_update_actions_state (NautilusFilesView *view)
+{
+ GActionGroup *view_action_group;
+ GVariant *sort_state;
+ GAction *action;
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+
+ NAUTILUS_FILES_VIEW_CLASS (nautilus_canvas_view_parent_class)->update_actions_state (view);
+
+ view_action_group = nautilus_files_view_get_action_group (view);
+
+ /* When we change the sort action state, even using the same value, it triggers
+ * the sort action changed handler, which reveals the selection, since we expect
+ * the selection to be visible when the user changes the sort order. But we may
+ * need to update the actions state for others reason than an actual sort change,
+ * so we need to prevent to trigger the sort action changed handler for those cases.
+ * To achieve this, check if the action state value actually changed before setting
+ * it
+ */
+ sort_state = g_action_group_get_action_state (view_action_group, "sort");
+
+ if (g_strcmp0 (g_variant_get_string (sort_state, NULL),
+ canvas_view->sort->action_target_name) != 0)
+ {
+ g_action_group_change_action_state (view_action_group,
+ "sort",
+ g_variant_new_string (canvas_view->sort->action_target_name));
+ }
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), "sort");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ !showing_recent_directory (view) &&
+ !showing_search_directory (view));
+
+ update_sort_action_state_hint (canvas_view);
+
+ g_variant_unref (sort_state);
+}
+
+static void
+nautilus_canvas_view_select_all (NautilusFilesView *view)
+{
+ NautilusCanvasContainer *canvas_container;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ canvas_container = get_canvas_container (NAUTILUS_CANVAS_VIEW (view));
+ nautilus_canvas_container_select_all (canvas_container);
+}
+
+static void
+nautilus_canvas_view_select_first (NautilusFilesView *view)
+{
+ NautilusCanvasContainer *canvas_container;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ canvas_container = get_canvas_container (NAUTILUS_CANVAS_VIEW (view));
+ nautilus_canvas_container_select_first (canvas_container);
+}
+
+static void
+nautilus_canvas_view_reveal_selection (NautilusFilesView *view)
+{
+ g_autolist (NautilusFile) selection = NULL;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+ /* Make sure at least one of the selected items is scrolled into view */
+ if (selection != NULL)
+ {
+ /* Update the icon ordering to reveal the rigth selection */
+ nautilus_canvas_container_layout_now (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)));
+ nautilus_canvas_container_reveal
+ (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)),
+ selection->data);
+ }
+}
+
+static GdkRectangle *
+get_rectangle_for_data (NautilusFilesView *view,
+ NautilusCanvasIconData *data)
+{
+ NautilusCanvasContainer *container;
+ GdkRectangle *rectangle;
+
+ container = get_canvas_container (NAUTILUS_CANVAS_VIEW (view));
+ rectangle = nautilus_canvas_container_get_icon_bounding_box (container, data);
+ if (rectangle != NULL)
+ {
+ GtkWidget *context_widget;
+ GtkAdjustment *vadjustment;
+ GtkAdjustment *hadjustment;
+
+ context_widget = nautilus_files_view_get_content_widget (view);
+ vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (context_widget));
+ hadjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (context_widget));
+
+ rectangle->x -= gtk_adjustment_get_value (hadjustment);
+ rectangle->y -= gtk_adjustment_get_value (vadjustment);
+ }
+ return rectangle;
+}
+
+static GdkRectangle *
+nautilus_canvas_view_compute_rename_popover_pointing_to (NautilusFilesView *view)
+{
+ g_autolist (NautilusFile) selection = NULL;
+ NautilusCanvasIconData *data;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), NULL);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ g_return_val_if_fail (selection != NULL, NULL);
+
+ /* We only allow renaming one item at once */
+ data = NAUTILUS_CANVAS_ICON_DATA (selection->data);
+
+ return get_rectangle_for_data (view, data);
+}
+
+static GdkRectangle *
+nautilus_canvas_view_reveal_for_selection_context_menu (NautilusFilesView *view)
+{
+ g_autolist (NautilusFile) selection = NULL;
+ NautilusCanvasContainer *container;
+ NautilusCanvasIconData *data;
+
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (view), NULL);
+
+ selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+ g_return_val_if_fail (selection != NULL, NULL);
+
+ container = get_canvas_container (NAUTILUS_CANVAS_VIEW (view));
+
+ /* Update the icon ordering to reveal the rigth selection */
+ nautilus_canvas_container_layout_now (container);
+
+ /* Get the data of the focused item, if selected. Otherwise, get the
+ * data of the last selected item.*/
+ data = nautilus_canvas_container_get_focused_icon (container);
+ if (data == NULL || g_list_find (selection, NAUTILUS_FILE (data)) == NULL)
+ {
+ selection = g_list_last (selection);
+ data = NAUTILUS_CANVAS_ICON_DATA (selection->data);
+ }
+
+ nautilus_canvas_container_reveal (container, data);
+
+ return get_rectangle_for_data (view, data);
+}
+
+static void
+nautilus_canvas_view_set_selection (NautilusFilesView *view,
+ GList *selection)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ nautilus_canvas_container_set_selection
+ (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)), selection);
+}
+
+static void
+nautilus_canvas_view_invert_selection (NautilusFilesView *view)
+{
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ nautilus_canvas_container_invert_selection
+ (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)));
+}
+
+static void
+nautilus_canvas_view_widget_to_file_operation_position (NautilusFilesView *view,
+ GdkPoint *position)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (view));
+
+ nautilus_canvas_container_widget_to_file_operation_position
+ (get_canvas_container (NAUTILUS_CANVAS_VIEW (view)), position);
+}
+
+static void
+canvas_container_activate_callback (NautilusCanvasContainer *container,
+ GList *file_list,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+ g_assert (container == get_canvas_container (canvas_view));
+
+ nautilus_files_view_activate_files (NAUTILUS_FILES_VIEW (canvas_view),
+ file_list,
+ 0, TRUE);
+}
+
+/* this is called in one of these cases:
+ * - we activate with enter holding shift
+ * - we activate with space holding shift
+ * - we double click an canvas holding shift
+ * - we middle click an canvas
+ *
+ * If we don't open in new windows by default, the behavior should be
+ * - middle click, shift + activate -> open in new tab
+ * - shift + double click -> open in new window
+ *
+ * If we open in new windows by default, the behaviour should be
+ * - middle click, or shift + activate, or shift + double-click -> close parent
+ */
+static void
+canvas_container_activate_alternate_callback (NautilusCanvasContainer *container,
+ GList *file_list,
+ NautilusCanvasView *canvas_view)
+{
+ GdkEvent *event;
+ GdkEventButton *button_event;
+ GdkEventKey *key_event;
+ gboolean open_in_tab, open_in_window;
+ NautilusOpenFlags flags;
+
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+ g_assert (container == get_canvas_container (canvas_view));
+
+ flags = 0;
+ event = gtk_get_current_event ();
+ open_in_tab = FALSE;
+ open_in_window = FALSE;
+
+ if (event->type == GDK_BUTTON_PRESS ||
+ event->type == GDK_BUTTON_RELEASE ||
+ event->type == GDK_2BUTTON_PRESS ||
+ event->type == GDK_3BUTTON_PRESS)
+ {
+ button_event = (GdkEventButton *) event;
+ open_in_window = ((button_event->state & GDK_SHIFT_MASK) != 0);
+ open_in_tab = !open_in_window;
+ }
+ else if (event->type == GDK_KEY_PRESS ||
+ event->type == GDK_KEY_RELEASE)
+ {
+ key_event = (GdkEventKey *) event;
+ open_in_tab = ((key_event->state & GDK_SHIFT_MASK) != 0);
+ }
+
+ if (open_in_tab)
+ {
+ flags |= NAUTILUS_OPEN_FLAG_NEW_TAB;
+ flags |= NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE;
+ }
+
+ if (open_in_window)
+ {
+ flags |= NAUTILUS_OPEN_FLAG_NEW_WINDOW;
+ }
+
+ DEBUG ("Activate alternate, open in tab %d, new window %d\n",
+ open_in_tab, open_in_window);
+
+ nautilus_files_view_activate_files (NAUTILUS_FILES_VIEW (canvas_view),
+ file_list,
+ flags,
+ TRUE);
+}
+
+static void
+band_select_started_callback (NautilusCanvasContainer *container,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+ g_assert (container == get_canvas_container (canvas_view));
+
+ nautilus_files_view_start_batching_selection_changes (NAUTILUS_FILES_VIEW (canvas_view));
+}
+
+static void
+band_select_ended_callback (NautilusCanvasContainer *container,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+ g_assert (container == get_canvas_container (canvas_view));
+
+ nautilus_files_view_stop_batching_selection_changes (NAUTILUS_FILES_VIEW (canvas_view));
+}
+
+int
+nautilus_canvas_view_compare_files (NautilusCanvasView *canvas_view,
+ NautilusFile *a,
+ NautilusFile *b)
+{
+ return nautilus_file_compare_for_sort
+ (a, b, canvas_view->sort->sort_type,
+ /* Use type-unsafe cast for performance */
+ nautilus_files_view_should_sort_directories_first ((NautilusFilesView *) canvas_view),
+ canvas_view->sort->reverse_order);
+}
+
+static int
+compare_files (NautilusFilesView *canvas_view,
+ NautilusFile *a,
+ NautilusFile *b)
+{
+ return nautilus_canvas_view_compare_files ((NautilusCanvasView *) canvas_view, a, b);
+}
+
+static void
+selection_changed_callback (NautilusCanvasContainer *container,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+ g_assert (container == get_canvas_container (canvas_view));
+
+ nautilus_files_view_notify_selection_changed (NAUTILUS_FILES_VIEW (canvas_view));
+}
+
+static void
+canvas_container_context_click_selection_callback (NautilusCanvasContainer *container,
+ const GdkEvent *event,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+
+ nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (canvas_view),
+ event);
+}
+
+static void
+canvas_container_context_click_background_callback (NautilusCanvasContainer *container,
+ const GdkEvent *event,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+
+ nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (canvas_view),
+ event);
+}
+
+static char *
+get_icon_uri_callback (NautilusCanvasContainer *container,
+ NautilusFile *file,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+
+ return nautilus_file_get_uri (file);
+}
+
+static char *
+get_icon_activation_uri_callback (NautilusCanvasContainer *container,
+ NautilusFile *file,
+ NautilusCanvasView *canvas_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_CONTAINER (container));
+ g_assert (NAUTILUS_IS_FILE (file));
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (canvas_view));
+
+ return nautilus_file_get_activation_uri (file);
+}
+
+static char *
+get_icon_drop_target_uri_callback (NautilusCanvasContainer *container,
+ NautilusFile *file,
+ NautilusCanvasView *canvas_view)
+{
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (container), NULL);
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+ g_return_val_if_fail (NAUTILUS_IS_CANVAS_VIEW (canvas_view), NULL);
+
+ return nautilus_file_get_uri (file);
+}
+
+/* Preferences changed callbacks */
+static void
+nautilus_canvas_view_click_policy_changed (NautilusFilesView *directory_view)
+{
+ g_assert (NAUTILUS_IS_CANVAS_VIEW (directory_view));
+
+ nautilus_canvas_view_update_click_mode (NAUTILUS_CANVAS_VIEW (directory_view));
+}
+
+static void
+image_display_policy_changed_callback (gpointer callback_data)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (callback_data);
+
+ nautilus_canvas_container_request_update_all (get_canvas_container (canvas_view));
+}
+
+static void
+text_attribute_names_changed_callback (gpointer callback_data)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (callback_data);
+
+ nautilus_canvas_container_request_update_all (get_canvas_container (canvas_view));
+}
+
+static void
+default_sort_order_changed_callback (gpointer callback_data)
+{
+ NautilusCanvasView *canvas_view;
+ NautilusFile *file;
+ const SortCriterion *sort;
+ NautilusCanvasContainer *canvas_container;
+
+ g_return_if_fail (NAUTILUS_IS_CANVAS_VIEW (callback_data));
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (callback_data);
+
+ file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (canvas_view));
+ sort = nautilus_canvas_view_get_directory_sort_by (canvas_view, file);
+ update_sort_criterion (canvas_view, sort, FALSE);
+
+ canvas_container = get_canvas_container (canvas_view);
+ g_return_if_fail (NAUTILUS_IS_CANVAS_CONTAINER (canvas_container));
+
+ nautilus_canvas_container_request_update_all (canvas_container);
+}
+
+static void
+nautilus_canvas_view_sort_directories_first_changed (NautilusFilesView *directory_view)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (directory_view);
+
+ nautilus_canvas_container_sort (get_canvas_container (canvas_view));
+}
+
+static void
+nautilus_canvas_view_preview_selection_event (NautilusFilesView *directory_view,
+ GtkDirectionType direction)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (directory_view);
+
+ nautilus_canvas_container_preview_selection_event (get_canvas_container (canvas_view),
+ direction);
+}
+
+static char *
+canvas_view_get_container_uri (NautilusCanvasContainer *container,
+ NautilusFilesView *view)
+{
+ return nautilus_files_view_get_uri (view);
+}
+
+static void
+canvas_view_move_copy_items (NautilusCanvasContainer *container,
+ const GList *item_uris,
+ const char *target_dir,
+ int copy_action,
+ NautilusFilesView *view)
+{
+ nautilus_clipboard_clear_if_colliding_uris (GTK_WIDGET (view),
+ item_uris);
+ nautilus_files_view_move_copy_items (view, item_uris, target_dir,
+ copy_action);
+}
+
+static void
+nautilus_canvas_view_update_click_mode (NautilusCanvasView *canvas_view)
+{
+ NautilusCanvasContainer *canvas_container;
+ int click_mode;
+
+ canvas_container = get_canvas_container (canvas_view);
+ g_assert (canvas_container != NULL);
+
+ click_mode = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_CLICK_POLICY);
+
+ nautilus_canvas_container_set_single_click_mode (canvas_container,
+ click_mode == NAUTILUS_CLICK_POLICY_SINGLE);
+}
+
+static void
+canvas_container_longpress_gesture_pressed_callback (GtkGestureLongPress *gesture,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ GdkEventSequence *event_sequence;
+ const GdkEvent *event;
+ NautilusCanvasView *view = NAUTILUS_CANVAS_VIEW (user_data);
+
+ event_sequence = gtk_gesture_get_last_updated_sequence (GTK_GESTURE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), event_sequence);
+
+ if (nautilus_view_get_selection (NAUTILUS_VIEW (view)))
+ {
+ nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (view),
+ event);
+ }
+ else
+ {
+ nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (view),
+ event);
+ }
+}
+
+static void
+initialize_canvas_container (NautilusCanvasView *canvas_view,
+ NautilusCanvasContainer *canvas_container)
+{
+ GtkWidget *content_widget;
+ GtkGesture *longpress_gesture;
+
+ content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (canvas_view));
+ canvas_view->canvas_container = GTK_WIDGET (canvas_container);
+ g_object_add_weak_pointer (G_OBJECT (canvas_container),
+ (gpointer *) &canvas_view->canvas_container);
+
+ longpress_gesture = gtk_gesture_long_press_new (GTK_WIDGET (content_widget));
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (longpress_gesture),
+ GTK_PHASE_CAPTURE);
+ gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (longpress_gesture),
+ TRUE);
+ g_signal_connect (longpress_gesture, "pressed",
+ (GCallback) canvas_container_longpress_gesture_pressed_callback,
+ canvas_view);
+
+ gtk_widget_set_can_focus (GTK_WIDGET (canvas_container), TRUE);
+
+ g_signal_connect_object (canvas_container, "activate",
+ G_CALLBACK (canvas_container_activate_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "activate-alternate",
+ G_CALLBACK (canvas_container_activate_alternate_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "band-select-started",
+ G_CALLBACK (band_select_started_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "band-select-ended",
+ G_CALLBACK (band_select_ended_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "context-click-selection",
+ G_CALLBACK (canvas_container_context_click_selection_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "context-click-background",
+ G_CALLBACK (canvas_container_context_click_background_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "selection-changed",
+ G_CALLBACK (selection_changed_callback), canvas_view, 0);
+ /* FIXME: many of these should move into fm-canvas-container as virtual methods */
+ g_signal_connect_object (canvas_container, "get-icon-uri",
+ G_CALLBACK (get_icon_uri_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "get-icon-activation-uri",
+ G_CALLBACK (get_icon_activation_uri_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "get-icon-drop-target-uri",
+ G_CALLBACK (get_icon_drop_target_uri_callback), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "move-copy-items",
+ G_CALLBACK (canvas_view_move_copy_items), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "get-container-uri",
+ G_CALLBACK (canvas_view_get_container_uri), canvas_view, 0);
+
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (content_widget),
+ GTK_WIDGET (canvas_container));
+
+ nautilus_canvas_view_update_click_mode (canvas_view);
+ nautilus_canvas_container_set_zoom_level (canvas_container,
+ get_default_zoom_level (canvas_view));
+
+ gtk_widget_show (GTK_WIDGET (canvas_container));
+}
+
+static void
+canvas_view_handle_uri_list (NautilusCanvasContainer *container,
+ const char *item_uris,
+ const char *target_uri,
+ GdkDragAction action,
+ NautilusCanvasView *view)
+{
+ nautilus_files_view_handle_uri_list_drop (NAUTILUS_FILES_VIEW (view),
+ item_uris, target_uri, action);
+}
+
+/* Handles an URL received from Mozilla */
+static void
+canvas_view_handle_netscape_url (NautilusCanvasContainer *container,
+ const char *encoded_url,
+ const char *target_uri,
+ GdkDragAction action,
+ NautilusCanvasView *view)
+{
+ nautilus_files_view_handle_netscape_url_drop (NAUTILUS_FILES_VIEW (view),
+ encoded_url, target_uri, action);
+}
+
+static void
+canvas_view_handle_text (NautilusCanvasContainer *container,
+ const char *text,
+ const char *target_uri,
+ GdkDragAction action,
+ NautilusCanvasView *view)
+{
+ nautilus_files_view_handle_text_drop (NAUTILUS_FILES_VIEW (view),
+ text, target_uri, action);
+}
+
+static void
+canvas_view_handle_raw (NautilusCanvasContainer *container,
+ const char *raw_data,
+ int length,
+ const char *target_uri,
+ const char *direct_save_uri,
+ GdkDragAction action,
+ NautilusCanvasView *view)
+{
+ nautilus_files_view_handle_raw_drop (NAUTILUS_FILES_VIEW (view),
+ raw_data, length, target_uri, direct_save_uri, action);
+}
+
+static void
+canvas_view_handle_hover (NautilusCanvasContainer *container,
+ const char *target_uri,
+ NautilusCanvasView *view)
+{
+ nautilus_files_view_handle_hover (NAUTILUS_FILES_VIEW (view), target_uri);
+}
+
+static char *
+canvas_view_get_first_visible_file (NautilusFilesView *view)
+{
+ NautilusFile *file;
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+
+ file = NAUTILUS_FILE (nautilus_canvas_container_get_first_visible_icon (get_canvas_container (canvas_view)));
+
+ if (file)
+ {
+ return nautilus_file_get_uri (file);
+ }
+
+ return NULL;
+}
+
+static void
+canvas_view_scroll_to_file (NautilusFilesView *view,
+ const char *uri)
+{
+ NautilusFile *file;
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (view);
+
+ if (uri != NULL)
+ {
+ /* Only if existing, since we don't want to add the file to
+ * the directory if it has been removed since then */
+ file = nautilus_file_get_existing_by_uri (uri);
+ if (file != NULL)
+ {
+ nautilus_canvas_container_scroll_to_canvas (get_canvas_container (canvas_view),
+ NAUTILUS_CANVAS_ICON_DATA (file));
+ nautilus_file_unref (file);
+ }
+ }
+}
+
+static guint
+nautilus_canvas_view_get_id (NautilusFilesView *view)
+{
+ return NAUTILUS_VIEW_GRID_ID;
+}
+
+static void
+nautilus_canvas_view_dispose (GObject *object)
+{
+ NautilusCanvasView *canvas_view;
+
+ canvas_view = NAUTILUS_CANVAS_VIEW (object);
+ canvas_view->destroyed = TRUE;
+
+ g_signal_handlers_disconnect_by_func (nautilus_preferences,
+ default_sort_order_changed_callback,
+ canvas_view);
+ g_signal_handlers_disconnect_by_func (nautilus_preferences,
+ image_display_policy_changed_callback,
+ canvas_view);
+
+ g_signal_handlers_disconnect_by_func (nautilus_icon_view_preferences,
+ text_attribute_names_changed_callback,
+ canvas_view);
+
+
+ G_OBJECT_CLASS (nautilus_canvas_view_parent_class)->dispose (object);
+}
+
+static void
+nautilus_canvas_view_class_init (NautilusCanvasViewClass *klass)
+{
+ NautilusFilesViewClass *nautilus_files_view_class;
+ GObjectClass *oclass;
+
+ nautilus_files_view_class = NAUTILUS_FILES_VIEW_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->dispose = nautilus_canvas_view_dispose;
+
+ GTK_WIDGET_CLASS (klass)->destroy = nautilus_canvas_view_destroy;
+
+ nautilus_files_view_class->add_files = nautilus_canvas_view_add_files;
+ nautilus_files_view_class->begin_loading = nautilus_canvas_view_begin_loading;
+ nautilus_files_view_class->bump_zoom_level = nautilus_canvas_view_bump_zoom_level;
+ nautilus_files_view_class->can_zoom_in = nautilus_canvas_view_can_zoom_in;
+ nautilus_files_view_class->can_zoom_out = nautilus_canvas_view_can_zoom_out;
+ nautilus_files_view_class->get_zoom_level_percentage = nautilus_canvas_view_get_zoom_level_percentage;
+ nautilus_files_view_class->is_zoom_level_default = nautilus_canvas_view_is_zoom_level_default;
+ nautilus_files_view_class->clear = nautilus_canvas_view_clear;
+ nautilus_files_view_class->end_loading = nautilus_canvas_view_end_loading;
+ nautilus_files_view_class->file_changed = nautilus_canvas_view_file_changed;
+ nautilus_files_view_class->compute_rename_popover_pointing_to = nautilus_canvas_view_compute_rename_popover_pointing_to;
+ nautilus_files_view_class->get_selection = nautilus_canvas_view_get_selection;
+ nautilus_files_view_class->get_selection_for_file_transfer = nautilus_canvas_view_get_selection;
+ nautilus_files_view_class->is_empty = nautilus_canvas_view_is_empty;
+ nautilus_files_view_class->remove_file = nautilus_canvas_view_remove_file;
+ nautilus_files_view_class->restore_standard_zoom_level = nautilus_canvas_view_restore_standard_zoom_level;
+ nautilus_files_view_class->reveal_selection = nautilus_canvas_view_reveal_selection;
+ nautilus_files_view_class->select_all = nautilus_canvas_view_select_all;
+ nautilus_files_view_class->select_first = nautilus_canvas_view_select_first;
+ nautilus_files_view_class->set_selection = nautilus_canvas_view_set_selection;
+ nautilus_files_view_class->invert_selection = nautilus_canvas_view_invert_selection;
+ nautilus_files_view_class->compare_files = compare_files;
+ nautilus_files_view_class->click_policy_changed = nautilus_canvas_view_click_policy_changed;
+ nautilus_files_view_class->update_actions_state = nautilus_canvas_view_update_actions_state;
+ nautilus_files_view_class->sort_directories_first_changed = nautilus_canvas_view_sort_directories_first_changed;
+ nautilus_files_view_class->widget_to_file_operation_position = nautilus_canvas_view_widget_to_file_operation_position;
+ nautilus_files_view_class->get_view_id = nautilus_canvas_view_get_id;
+ nautilus_files_view_class->get_first_visible_file = canvas_view_get_first_visible_file;
+ nautilus_files_view_class->scroll_to_file = canvas_view_scroll_to_file;
+ nautilus_files_view_class->reveal_for_selection_context_menu = nautilus_canvas_view_reveal_for_selection_context_menu;
+ nautilus_files_view_class->preview_selection_event = nautilus_canvas_view_preview_selection_event;
+}
+
+static void
+nautilus_canvas_view_init (NautilusCanvasView *canvas_view)
+{
+ NautilusCanvasContainer *canvas_container;
+ GActionGroup *view_action_group;
+ GtkClipboard *clipboard;
+
+ canvas_view->sort = &sort_criteria[0];
+ canvas_view->destroyed = FALSE;
+
+ canvas_container = nautilus_canvas_view_container_new (canvas_view);
+ initialize_canvas_container (canvas_view, canvas_container);
+
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER,
+ G_CALLBACK (default_sort_order_changed_callback),
+ canvas_view);
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER,
+ G_CALLBACK (default_sort_order_changed_callback),
+ canvas_view);
+ g_signal_connect_swapped (nautilus_preferences,
+ "changed::" NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS,
+ G_CALLBACK (image_display_policy_changed_callback),
+ canvas_view);
+
+ g_signal_connect_swapped (nautilus_icon_view_preferences,
+ "changed::" NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS,
+ G_CALLBACK (text_attribute_names_changed_callback),
+ canvas_view);
+
+ g_signal_connect_object (canvas_container, "handle-uri-list",
+ G_CALLBACK (canvas_view_handle_uri_list), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "handle-netscape-url",
+ G_CALLBACK (canvas_view_handle_netscape_url), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "handle-text",
+ G_CALLBACK (canvas_view_handle_text), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "handle-raw",
+ G_CALLBACK (canvas_view_handle_raw), canvas_view, 0);
+ g_signal_connect_object (canvas_container, "handle-hover",
+ G_CALLBACK (canvas_view_handle_hover), canvas_view, 0);
+
+ /* React to clipboard changes */
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ g_signal_connect (clipboard, "owner-change",
+ G_CALLBACK (on_clipboard_owner_changed), canvas_view);
+
+ view_action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (canvas_view));
+ g_action_map_add_action_entries (G_ACTION_MAP (view_action_group),
+ canvas_view_entries,
+ G_N_ELEMENTS (canvas_view_entries),
+ canvas_view);
+ /* Keep the action synced with the actual value, so the toolbar can poll it */
+ g_action_group_change_action_state (nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (canvas_view)),
+ "zoom-to-level", g_variant_new_int32 (get_default_zoom_level (canvas_view)));
+}
+
+NautilusFilesView *
+nautilus_canvas_view_new (NautilusWindowSlot *slot)
+{
+ return g_object_new (NAUTILUS_TYPE_CANVAS_VIEW,
+ "window-slot", slot,
+ NULL);
+}
diff --git a/src/nautilus-canvas-view.h b/src/nautilus-canvas-view.h
new file mode 100644
index 000000000..4e8f1ac8b
--- /dev/null
+++ b/src/nautilus-canvas-view.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* nautilus-canvas-view.h - interface for canvas view of directory.
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * 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,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: John Sullivan <sullivan@eazel.com>
+ *
+ */
+
+#pragma once
+
+#include "nautilus-files-view.h"
+
+#include "nautilus-types.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_CANVAS_VIEW nautilus_canvas_view_get_type()
+
+G_DECLARE_FINAL_TYPE (NautilusCanvasView, nautilus_canvas_view, NAUTILUS, CANVAS_VIEW, NautilusFilesView)
+
+int nautilus_canvas_view_compare_files (NautilusCanvasView *canvas_view,
+ NautilusFile *a,
+ NautilusFile *b);
+
+NautilusFilesView * nautilus_canvas_view_new (NautilusWindowSlot *slot);
+
+NautilusCanvasContainer * nautilus_canvas_view_get_canvas_container (NautilusCanvasView *view);
+
+G_END_DECLS
diff --git a/src/nautilus-debug.c b/src/nautilus-debug.c
index bbf76567c..d189a6f9a 100644
--- a/src/nautilus-debug.c
+++ b/src/nautilus-debug.c
@@ -41,6 +41,7 @@ static GDebugKey keys[] =
{ "DBus", NAUTILUS_DEBUG_DBUS },
{ "DirectoryView", NAUTILUS_DEBUG_DIRECTORY_VIEW },
{ "File", NAUTILUS_DEBUG_FILE },
+ { "CanvasContainer", NAUTILUS_DEBUG_CANVAS_CONTAINER },
{ "IconView", NAUTILUS_DEBUG_GRID_VIEW },
{ "ListView", NAUTILUS_DEBUG_LIST_VIEW },
{ "Mime", NAUTILUS_DEBUG_MIME },
diff --git a/src/nautilus-debug.h b/src/nautilus-debug.h
index 9322a8d62..c277f37a5 100644
--- a/src/nautilus-debug.h
+++ b/src/nautilus-debug.h
@@ -35,18 +35,19 @@ typedef enum {
NAUTILUS_DEBUG_DBUS = 1 << 4,
NAUTILUS_DEBUG_DIRECTORY_VIEW = 1 << 5,
NAUTILUS_DEBUG_FILE = 1 << 6,
- NAUTILUS_DEBUG_GRID_VIEW = 1 << 7,
- NAUTILUS_DEBUG_LIST_VIEW = 1 << 8,
- NAUTILUS_DEBUG_MIME = 1 << 9,
- NAUTILUS_DEBUG_PLACES = 1 << 10,
- NAUTILUS_DEBUG_PREVIEWER = 1 << 11,
- NAUTILUS_DEBUG_SMCLIENT = 1 << 12,
- NAUTILUS_DEBUG_WINDOW = 1 << 13,
- NAUTILUS_DEBUG_UNDO = 1 << 14,
- NAUTILUS_DEBUG_SEARCH = 1 << 15,
- NAUTILUS_DEBUG_SEARCH_HIT = 1 << 16,
- NAUTILUS_DEBUG_THUMBNAILS = 1 << 17,
- NAUTILUS_DEBUG_TAG_MANAGER = 1 << 18,
+ NAUTILUS_DEBUG_CANVAS_CONTAINER = 1 << 7,
+ NAUTILUS_DEBUG_GRID_VIEW = 1 << 8,
+ NAUTILUS_DEBUG_LIST_VIEW = 1 << 9,
+ NAUTILUS_DEBUG_MIME = 1 << 10,
+ NAUTILUS_DEBUG_PLACES = 1 << 11,
+ NAUTILUS_DEBUG_PREVIEWER = 1 << 12,
+ NAUTILUS_DEBUG_SMCLIENT = 1 << 13,
+ NAUTILUS_DEBUG_WINDOW = 1 << 14,
+ NAUTILUS_DEBUG_UNDO = 1 << 15,
+ NAUTILUS_DEBUG_SEARCH = 1 << 16,
+ NAUTILUS_DEBUG_SEARCH_HIT = 1 << 17,
+ NAUTILUS_DEBUG_THUMBNAILS = 1 << 18,
+ NAUTILUS_DEBUG_TAG_MANAGER = 1 << 19,
} DebugFlags;
void nautilus_debug_set_flags (DebugFlags flags);
diff --git a/src/nautilus-dnd.c b/src/nautilus-dnd.c
index 7b7a1ba2b..067bc27ec 100644
--- a/src/nautilus-dnd.c
+++ b/src/nautilus-dnd.c
@@ -32,6 +32,7 @@
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include "nautilus-file-utilities.h"
+#include "nautilus-canvas-dnd.h"
#include <src/nautilus-list-view-dnd.h>
#include <stdio.h>
#include <string.h>
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index 5fff557eb..93270c9bf 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -51,6 +51,7 @@
#include "nautilus-application.h"
#include "nautilus-batch-rename-dialog.h"
#include "nautilus-batch-rename-utilities.h"
+#include "nautilus-canvas-view.h"
#include "nautilus-clipboard.h"
#include "nautilus-compress-dialog-controller.h"
#include "nautilus-directory.h"
diff --git a/src/nautilus-selection-canvas-item.c b/src/nautilus-selection-canvas-item.c
new file mode 100644
index 000000000..b54cb68ed
--- /dev/null
+++ b/src/nautilus-selection-canvas-item.c
@@ -0,0 +1,554 @@
+/* Nautilus - Canvas item for floating selection.
+ *
+ * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
+ * Copyright (C) 2011 Red Hat Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Federico Mena <federico@nuclecu.unam.mx>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ */
+
+#include <config.h>
+
+#include "nautilus-selection-canvas-item.h"
+
+#include <math.h>
+
+enum
+{
+ PROP_X1 = 1,
+ PROP_Y1,
+ PROP_X2,
+ PROP_Y2,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL };
+
+typedef struct
+{
+ /*< public >*/
+ int x0, y0, x1, y1;
+} Rect;
+
+struct _NautilusSelectionCanvasItemDetails
+{
+ Rect last_update_rect;
+ Rect last_outline_update_rect;
+ int last_outline_update_width;
+
+ double x1, y1, x2, y2; /* Corners of item */
+};
+
+G_DEFINE_TYPE (NautilusSelectionCanvasItem, nautilus_selection_canvas_item, EEL_TYPE_CANVAS_ITEM);
+
+static void
+nautilus_selection_canvas_item_draw (EelCanvasItem *item,
+ cairo_t *cr,
+ cairo_region_t *region)
+{
+ NautilusSelectionCanvasItem *self;
+ double x1, y1, x2, y2;
+ int cx1, cy1, cx2, cy2;
+ double i2w_dx, i2w_dy;
+ GtkStyleContext *context;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (item);
+
+ /* Get canvas pixel coordinates */
+ i2w_dx = 0.0;
+ i2w_dy = 0.0;
+ eel_canvas_item_i2w (item, &i2w_dx, &i2w_dy);
+
+ x1 = self->priv->x1 + i2w_dx;
+ y1 = self->priv->y1 + i2w_dy;
+ x2 = self->priv->x2 + i2w_dx;
+ y2 = self->priv->y2 + i2w_dy;
+
+ eel_canvas_w2c (item->canvas, x1, y1, &cx1, &cy1);
+ eel_canvas_w2c (item->canvas, x2, y2, &cx2, &cy2);
+
+ if (cx2 <= cx1 || cy2 <= cy1)
+ {
+ return;
+ }
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (item->canvas));
+
+ gtk_style_context_save (context);
+
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+
+ cairo_save (cr);
+
+ gtk_render_background (context, cr,
+ cx1, cy1,
+ cx2 - cx1,
+ cy2 - cy1);
+ gtk_render_frame (context, cr,
+ cx1, cy1,
+ cx2 - cx1,
+ cy2 - cy1);
+
+ cairo_restore (cr);
+
+ gtk_style_context_restore (context);
+}
+
+static double
+nautilus_selection_canvas_item_point (EelCanvasItem *item,
+ double x,
+ double y,
+ int cx,
+ int cy,
+ EelCanvasItem **actual_item)
+{
+ NautilusSelectionCanvasItem *self;
+ double x1, y1, x2, y2;
+ double hwidth;
+ double dx, dy;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (item);
+ *actual_item = item;
+
+ /* Find the bounds for the rectangle plus its outline width */
+
+ x1 = self->priv->x1;
+ y1 = self->priv->y1;
+ x2 = self->priv->x2;
+ y2 = self->priv->y2;
+
+ hwidth = (1.0 / item->canvas->pixels_per_unit) / 2.0;
+
+ x1 -= hwidth;
+ y1 -= hwidth;
+ x2 += hwidth;
+ y2 += hwidth;
+
+ /* Is point inside rectangle (which can be hollow if it has no fill set)? */
+
+ if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2))
+ {
+ return 0.0;
+ }
+
+ /* Point is outside rectangle */
+
+ if (x < x1)
+ {
+ dx = x1 - x;
+ }
+ else if (x > x2)
+ {
+ dx = x - x2;
+ }
+ else
+ {
+ dx = 0.0;
+ }
+
+ if (y < y1)
+ {
+ dy = y1 - y;
+ }
+ else if (y > y2)
+ {
+ dy = y - y2;
+ }
+ else
+ {
+ dy = 0.0;
+ }
+
+ return sqrt (dx * dx + dy * dy);
+}
+
+static void
+request_redraw_borders (EelCanvas *canvas,
+ Rect *update_rect,
+ int width)
+{
+ /* Top */
+ eel_canvas_request_redraw (canvas,
+ update_rect->x0, update_rect->y0,
+ update_rect->x1, update_rect->y0 + width);
+ /* Bottom */
+ eel_canvas_request_redraw (canvas,
+ update_rect->x0, update_rect->y1 - width,
+ update_rect->x1, update_rect->y1);
+ /* Left */
+ eel_canvas_request_redraw (canvas,
+ update_rect->x0, update_rect->y0,
+ update_rect->x0 + width, update_rect->y1);
+ /* Right */
+ eel_canvas_request_redraw (canvas,
+ update_rect->x1 - width, update_rect->y0,
+ update_rect->x1, update_rect->y1);
+}
+
+static Rect make_rect (int x0,
+ int y0,
+ int x1,
+ int y1);
+
+static int
+rect_empty (const Rect *src)
+{
+ return (src->x1 <= src->x0 || src->y1 <= src->y0);
+}
+
+static gboolean
+rects_intersect (Rect r1,
+ Rect r2)
+{
+ if (r1.x0 >= r2.x1)
+ {
+ return FALSE;
+ }
+ if (r2.x0 >= r1.x1)
+ {
+ return FALSE;
+ }
+ if (r1.y0 >= r2.y1)
+ {
+ return FALSE;
+ }
+ if (r2.y0 >= r1.y1)
+ {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+diff_rects_guts (Rect ra,
+ Rect rb,
+ int *count,
+ Rect result[4])
+{
+ if (ra.x0 < rb.x0)
+ {
+ result[(*count)++] = make_rect (ra.x0, ra.y0, rb.x0, ra.y1);
+ }
+ if (ra.y0 < rb.y0)
+ {
+ result[(*count)++] = make_rect (ra.x0, ra.y0, ra.x1, rb.y0);
+ }
+ if (ra.x1 < rb.x1)
+ {
+ result[(*count)++] = make_rect (ra.x1, rb.y0, rb.x1, rb.y1);
+ }
+ if (ra.y1 < rb.y1)
+ {
+ result[(*count)++] = make_rect (rb.x0, ra.y1, rb.x1, rb.y1);
+ }
+}
+
+static void
+diff_rects (Rect r1,
+ Rect r2,
+ int *count,
+ Rect result[4])
+{
+ g_assert (count != NULL);
+ g_assert (result != NULL);
+
+ *count = 0;
+
+ if (rects_intersect (r1, r2))
+ {
+ diff_rects_guts (r1, r2, count, result);
+ diff_rects_guts (r2, r1, count, result);
+ }
+ else
+ {
+ if (!rect_empty (&r1))
+ {
+ result[(*count)++] = r1;
+ }
+ if (!rect_empty (&r2))
+ {
+ result[(*count)++] = r2;
+ }
+ }
+}
+
+static Rect
+make_rect (int x0,
+ int y0,
+ int x1,
+ int y1)
+{
+ Rect r;
+
+ r.x0 = x0;
+ r.y0 = y0;
+ r.x1 = x1;
+ r.y1 = y1;
+ return r;
+}
+
+static void
+nautilus_selection_canvas_item_update (EelCanvasItem *item,
+ double i2w_dx,
+ double i2w_dy,
+ gint flags)
+{
+ NautilusSelectionCanvasItem *self;
+ NautilusSelectionCanvasItemDetails *priv;
+ double x1, y1, x2, y2;
+ int cx1, cy1, cx2, cy2;
+ int repaint_rects_count, i;
+ GtkStyleContext *context;
+ GtkBorder border;
+ Rect update_rect, repaint_rects[4];
+
+ if (EEL_CANVAS_ITEM_CLASS (nautilus_selection_canvas_item_parent_class)->update)
+ {
+ (*EEL_CANVAS_ITEM_CLASS (nautilus_selection_canvas_item_parent_class)->update)(item, i2w_dx, i2w_dy, flags);
+ }
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (item);
+ priv = self->priv;
+
+ x1 = priv->x1 + i2w_dx;
+ y1 = priv->y1 + i2w_dy;
+ x2 = priv->x2 + i2w_dx;
+ y2 = priv->y2 + i2w_dy;
+
+ eel_canvas_w2c (item->canvas, x1, y1, &cx1, &cy1);
+ eel_canvas_w2c (item->canvas, x2, y2, &cx2, &cy2);
+
+ update_rect = make_rect (cx1, cy1, cx2 + 1, cy2 + 1);
+ diff_rects (update_rect, priv->last_update_rect,
+ &repaint_rects_count, repaint_rects);
+ for (i = 0; i < repaint_rects_count; i++)
+ {
+ eel_canvas_request_redraw (item->canvas,
+ repaint_rects[i].x0, repaint_rects[i].y0,
+ repaint_rects[i].x1, repaint_rects[i].y1);
+ }
+
+ priv->last_update_rect = update_rect;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (item->canvas));
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+ gtk_style_context_get_border (context, GTK_STATE_FLAG_NORMAL, &border);
+ gtk_style_context_restore (context);
+
+ cx1 -= border.left;
+ cy1 -= border.top;
+ cx2 += border.right;
+ cy2 += border.bottom;
+
+ update_rect = make_rect (cx1, cy1, cx2, cy2);
+ request_redraw_borders (item->canvas, &update_rect,
+ border.left + border.top + border.right + border.bottom);
+ request_redraw_borders (item->canvas, &priv->last_outline_update_rect,
+ priv->last_outline_update_width);
+ priv->last_outline_update_rect = update_rect;
+ priv->last_outline_update_width = border.left + border.top + border.right + border.bottom;
+
+ item->x1 = cx1;
+ item->y1 = cy1;
+ item->x2 = cx2;
+ item->y2 = cy2;
+}
+
+static void
+nautilus_selection_canvas_item_translate (EelCanvasItem *item,
+ double dx,
+ double dy)
+{
+ NautilusSelectionCanvasItem *self;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (item);
+
+ self->priv->x1 += dx;
+ self->priv->y1 += dy;
+ self->priv->x2 += dx;
+ self->priv->y2 += dy;
+}
+
+static void
+nautilus_selection_canvas_item_bounds (EelCanvasItem *item,
+ double *x1,
+ double *y1,
+ double *x2,
+ double *y2)
+{
+ NautilusSelectionCanvasItem *self;
+ GtkStyleContext *context;
+ GtkBorder border;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (item);
+ context = gtk_widget_get_style_context (GTK_WIDGET (item->canvas));
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+ gtk_style_context_get_border (context, GTK_STATE_FLAG_NORMAL, &border);
+ gtk_style_context_restore (context);
+
+ *x1 = self->priv->x1 - (border.left / item->canvas->pixels_per_unit) / 2.0;
+ *y1 = self->priv->y1 - (border.top / item->canvas->pixels_per_unit) / 2.0;
+ *x2 = self->priv->x2 + (border.right / item->canvas->pixels_per_unit) / 2.0;
+ *y2 = self->priv->y2 + (border.bottom / item->canvas->pixels_per_unit) / 2.0;
+}
+
+static void
+nautilus_selection_canvas_item_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EelCanvasItem *item;
+ NautilusSelectionCanvasItem *self;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (object);
+ item = EEL_CANVAS_ITEM (object);
+
+ switch (param_id)
+ {
+ case PROP_X1:
+ {
+ self->priv->x1 = g_value_get_double (value);
+
+ eel_canvas_item_request_update (item);
+ }
+ break;
+
+ case PROP_Y1:
+ {
+ self->priv->y1 = g_value_get_double (value);
+
+ eel_canvas_item_request_update (item);
+ }
+ break;
+
+ case PROP_X2:
+ {
+ self->priv->x2 = g_value_get_double (value);
+
+ eel_canvas_item_request_update (item);
+ }
+ break;
+
+ case PROP_Y2:
+ {
+ self->priv->y2 = g_value_get_double (value);
+
+ eel_canvas_item_request_update (item);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_selection_canvas_item_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ NautilusSelectionCanvasItem *self;
+
+ self = NAUTILUS_SELECTION_CANVAS_ITEM (object);
+
+ switch (param_id)
+ {
+ case PROP_X1:
+ {
+ g_value_set_double (value, self->priv->x1);
+ }
+ break;
+
+ case PROP_Y1:
+ {
+ g_value_set_double (value, self->priv->y1);
+ }
+ break;
+
+ case PROP_X2:
+ {
+ g_value_set_double (value, self->priv->x2);
+ }
+ break;
+
+ case PROP_Y2:
+ {
+ g_value_set_double (value, self->priv->y2);
+ }
+ break;
+
+ default:
+ {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ }
+ break;
+ }
+}
+
+static void
+nautilus_selection_canvas_item_class_init (NautilusSelectionCanvasItemClass *klass)
+{
+ EelCanvasItemClass *item_class;
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ item_class = EEL_CANVAS_ITEM_CLASS (klass);
+
+ gobject_class->set_property = nautilus_selection_canvas_item_set_property;
+ gobject_class->get_property = nautilus_selection_canvas_item_get_property;
+
+ item_class->draw = nautilus_selection_canvas_item_draw;
+ item_class->point = nautilus_selection_canvas_item_point;
+ item_class->update = nautilus_selection_canvas_item_update;
+ item_class->bounds = nautilus_selection_canvas_item_bounds;
+ item_class->translate = nautilus_selection_canvas_item_translate;
+
+ properties[PROP_X1] =
+ g_param_spec_double ("x1", NULL, NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+ properties[PROP_Y1] =
+ g_param_spec_double ("y1", NULL, NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+ properties[PROP_X2] =
+ g_param_spec_double ("x2", NULL, NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+ properties[PROP_Y2] =
+ g_param_spec_double ("y2", NULL, NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+ g_type_class_add_private (klass, sizeof (NautilusSelectionCanvasItemDetails));
+}
+
+static void
+nautilus_selection_canvas_item_init (NautilusSelectionCanvasItem *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NAUTILUS_TYPE_SELECTION_CANVAS_ITEM,
+ NautilusSelectionCanvasItemDetails);
+}
diff --git a/src/nautilus-selection-canvas-item.h b/src/nautilus-selection-canvas-item.h
new file mode 100644
index 000000000..6c13fe2a8
--- /dev/null
+++ b/src/nautilus-selection-canvas-item.h
@@ -0,0 +1,62 @@
+
+/* Nautilus - Canvas item for floating selection.
+ *
+ * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
+ * Copyright (C) 2011 Red Hat Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Federico Mena <federico@nuclecu.unam.mx>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ */
+
+#pragma once
+
+#include <eel/eel-canvas.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_SELECTION_CANVAS_ITEM nautilus_selection_canvas_item_get_type()
+#define NAUTILUS_SELECTION_CANVAS_ITEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_SELECTION_CANVAS_ITEM, NautilusSelectionCanvasItem))
+#define NAUTILUS_SELECTION_CANVAS_ITEM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_SELECTION_CANVAS_ITEM, NautilusSelectionCanvasItemClass))
+#define NAUTILUS_IS_SELECTION_CANVAS_ITEM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_SELECTION_CANVAS_ITEM))
+#define NAUTILUS_IS_SELECTION_CANVAS_ITEM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_SELECTION_CANVAS_ITEM))
+#define NAUTILUS_SELECTION_CANVAS_ITEM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_SELECTION_CANVAS_ITEM, NautilusSelectionCanvasItemClass))
+
+typedef struct _NautilusSelectionCanvasItem NautilusSelectionCanvasItem;
+typedef struct _NautilusSelectionCanvasItemClass NautilusSelectionCanvasItemClass;
+typedef struct _NautilusSelectionCanvasItemDetails NautilusSelectionCanvasItemDetails;
+
+struct _NautilusSelectionCanvasItem {
+ EelCanvasItem item;
+ NautilusSelectionCanvasItemDetails *priv;
+ gpointer user_data;
+};
+
+struct _NautilusSelectionCanvasItemClass {
+ EelCanvasItemClass parent_class;
+};
+
+/* GObject */
+GType nautilus_selection_canvas_item_get_type (void);
+
+void nautilus_selection_canvas_item_fade_out (NautilusSelectionCanvasItem *self,
+ guint transition_time);
+
+G_END_DECLS \ No newline at end of file
diff --git a/src/resources/css/Adwaita.css b/src/resources/css/Adwaita.css
index 0e2f302f0..968794388 100644
--- a/src/resources/css/Adwaita.css
+++ b/src/resources/css/Adwaita.css
@@ -8,6 +8,18 @@
opacity: 0.50;
}
+.nautilus-canvas-item {
+ border-radius: 5px;
+}
+
+.nautilus-canvas-item.dim-label {
+ color: mix (@theme_fg_color, @theme_bg_color, 0.50);
+}
+
+.nautilus-canvas-item.dim-label:selected {
+ color: mix (@theme_selected_fg_color, @theme_selected_bg_color, 0.20);
+}
+
/* Toolbar */
/* Here we use the .button background-image colors from Adwaita, but ligthen them,