From f0ed057f7a27cacdf9cfa80432e64046685f113a Mon Sep 17 00:00:00 2001 From: Ondrej Holy Date: Fri, 11 Feb 2022 14:31:50 +0100 Subject: Revert "general: Remove canvas view" This reverts commit 2d1deaac2dd12b0ba16446bfbf3498b266e60338. --- eel/eel-canvas.c | 4153 ++++++++++++++++++++++ eel/eel-canvas.h | 497 +++ eel/meson.build | 2 + po/POTFILES.in | 6 + src/meson.build | 13 + src/nautilus-canvas-container.c | 6360 ++++++++++++++++++++++++++++++++++ src/nautilus-canvas-container.h | 295 ++ src/nautilus-canvas-dnd.c | 1833 ++++++++++ src/nautilus-canvas-dnd.h | 56 + src/nautilus-canvas-item.c | 2760 +++++++++++++++ src/nautilus-canvas-item.h | 90 + src/nautilus-canvas-private.h | 223 ++ src/nautilus-canvas-view-container.c | 377 ++ src/nautilus-canvas-view-container.h | 35 + src/nautilus-canvas-view.c | 1646 +++++++++ src/nautilus-canvas-view.h | 45 + src/nautilus-debug.c | 1 + src/nautilus-debug.h | 25 +- src/nautilus-dnd.c | 1 + src/nautilus-files-view.c | 1 + src/nautilus-selection-canvas-item.c | 554 +++ src/nautilus-selection-canvas-item.h | 62 + src/resources/css/Adwaita.css | 12 + 23 files changed, 19035 insertions(+), 12 deletions(-) create mode 100644 eel/eel-canvas.c create mode 100644 eel/eel-canvas.h create mode 100644 src/nautilus-canvas-container.c create mode 100644 src/nautilus-canvas-container.h create mode 100644 src/nautilus-canvas-dnd.c create mode 100644 src/nautilus-canvas-dnd.h create mode 100644 src/nautilus-canvas-item.c create mode 100644 src/nautilus-canvas-item.h create mode 100644 src/nautilus-canvas-private.h create mode 100644 src/nautilus-canvas-view-container.c create mode 100644 src/nautilus-canvas-view-container.h create mode 100644 src/nautilus-canvas-view.c create mode 100644 src/nautilus-canvas-view.h create mode 100644 src/nautilus-selection-canvas-item.c create mode 100644 src/nautilus-selection-canvas-item.h 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 . + */ +/* + * @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 + * Raph Levien + */ + +/* + * 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 + +#include +#include +#include +#include +#include +#include +#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 . + */ +/* + @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 + * Raph Levien + */ + +#pragma once + +#include +#include +#include +#include + +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 . + * + * Authors: Ettore Perazzoli , + * Darin Adler + */ + +#include "nautilus-canvas-container.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + + Authors: Ettore Perazzoli , Darin Adler +*/ + +#pragma once + +#include + +#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 . + * + * Authors: Ettore Perazzoli , + * Darin Adler , + * Andy Hertzfeld + * Pavel Cisler + * + * + * XDS support: Benedikt Meurer (adapted by Amos Brocco ) + * + */ + + +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nautilus-file-utilities.h" +#include "nautilus-file-changes-queue.h" +#include +#include + +#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 . + + Authors: Ettore Perazzoli , + Darin Adler , + Andy Hertzfeld +*/ + +#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 + * + * 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 . + */ + +#include +#include +#include "nautilus-canvas-item.h" + +#include + +#include "nautilus-file-utilities.h" +#include "nautilus-global-preferences.h" +#include "nautilus-canvas-private.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 + * + * 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 . + */ + +#pragma once + +#include +#include + +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 . + + Author: Ettore Perazzoli +*/ + +#pragma once + +#include +#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 . + * + * Author: Michael Meeks + */ + +#include "nautilus-canvas-view-container.h" + +#include +#include +#include +#include + +#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 . + + Author: Michael Meeks +*/ + +#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 . + * + * Authors: John Sullivan + */ + +#include + +#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 +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include + +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 . + * + * Authors: John Sullivan + * + */ + +#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 #include #include "nautilus-file-utilities.h" +#include "nautilus-canvas-dnd.h" #include #include #include 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 . + * + * Authors: Federico Mena + * Cosimo Cecchi + */ + +#include + +#include "nautilus-selection-canvas-item.h" + +#include + +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 . + * + * Authors: Federico Mena + * Cosimo Cecchi + */ + +#pragma once + +#include + +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, -- cgit v1.2.1