summaryrefslogtreecommitdiff
path: root/libcaja-private/caja-icon-container.c
diff options
context:
space:
mode:
Diffstat (limited to 'libcaja-private/caja-icon-container.c')
-rw-r--r--libcaja-private/caja-icon-container.c10612
1 files changed, 10612 insertions, 0 deletions
diff --git a/libcaja-private/caja-icon-container.c b/libcaja-private/caja-icon-container.c
new file mode 100644
index 00000000..2be07d1e
--- /dev/null
+++ b/libcaja-private/caja-icon-container.c
@@ -0,0 +1,10612 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-icon-container.c - Icon container widget.
+
+ Copyright (C) 1999, 2000 Free Software Foundation
+ Copyright (C) 2000, 2001 Eazel, Inc.
+ Copyright (C) 2002, 2003 Red Hat, Inc.
+
+ The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ettore Perazzoli <[email protected]>,
+ Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+#include <math.h>
+#include "caja-icon-container.h"
+
+#include "caja-debug-log.h"
+#include "caja-global-preferences.h"
+#include "caja-icon-private.h"
+#include "caja-lib-self-check-functions.h"
+#include "caja-marshal.h"
+#include <atk/atkaction.h>
+#include <eel/eel-accessibility.h>
+#include <eel/eel-background.h>
+#include <eel/eel-vfs-extensions.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-mate-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-art-extensions.h>
+#include <eel/eel-editable-label.h>
+#include <eel/eel-marshal.h>
+#include <eel/eel-string.h>
+#include <eel/eel-preferences.h>
+#include <eel/eel-enumeration.h>
+#include <eel/eel-canvas-rect-ellipse.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <string.h>
+
+#define TAB_NAVIGATION_DISABLED
+
+/* Interval for updating the rubberband selection, in milliseconds. */
+#define RUBBERBAND_TIMEOUT_INTERVAL 10
+
+/* 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_BASE_WIDTH 96
+
+#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
+
+#define STANDARD_ICON_GRID_WIDTH 155
+
+#define TEXT_BESIDE_ICON_GRID_WIDTH 205
+
+/* Desktop layout mode defines */
+#define DESKTOP_PAD_HORIZONTAL 10
+#define DESKTOP_PAD_VERTICAL 10
+#define SNAP_SIZE_X 78
+#define SNAP_SIZE_Y 20
+
+#define DEFAULT_SELECTION_BOX_ALPHA 0x40
+#define DEFAULT_HIGHLIGHT_ALPHA 0xff
+#define DEFAULT_NORMAL_ALPHA 0xff
+#define DEFAULT_PRELIGHT_ALPHA 0xff
+#define DEFAULT_LIGHT_INFO_COLOR 0xAAAAFD
+#define DEFAULT_DARK_INFO_COLOR 0x33337F
+
+#define DEFAULT_NORMAL_ICON_RENDER_MODE 0
+#define DEFAULT_PRELIGHT_ICON_RENDER_MODE 1
+#define DEFAULT_NORMAL_ICON_SATURATION 255
+#define DEFAULT_PRELIGHT_ICON_SATURATION 255
+#define DEFAULT_NORMAL_ICON_BRIGHTNESS 255
+#define DEFAULT_PRELIGHT_ICON_BRIGHTNESS 255
+#define DEFAULT_NORMAL_ICON_LIGHTEN 0
+#define DEFAULT_PRELIGHT_ICON_LIGHTEN 0
+
+#define MINIMUM_EMBEDDED_TEXT_RECT_WIDTH 20
+#define MINIMUM_EMBEDDED_TEXT_RECT_HEIGHT 20
+
+/* If icon size is bigger than this, request large embedded text.
+ * Its selected so that the non-large text should fit in "normal" icon sizes
+ */
+#define ICON_SIZE_FOR_LARGE_EMBEDDED_TEXT 55
+
+/* From caja-icon-canvas-item.c */
+#define MAX_TEXT_WIDTH_BESIDE 90
+
+#define SNAP_HORIZONTAL(func,x) ((func ((double)((x) - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X) * SNAP_SIZE_X) + DESKTOP_PAD_HORIZONTAL)
+#define SNAP_VERTICAL(func, y) ((func ((double)((y) - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y) * SNAP_SIZE_Y) + DESKTOP_PAD_VERTICAL)
+
+#define SNAP_NEAREST_HORIZONTAL(x) SNAP_HORIZONTAL (eel_round, x)
+#define SNAP_NEAREST_VERTICAL(y) SNAP_VERTICAL (eel_round, y)
+
+#define SNAP_CEIL_HORIZONTAL(x) SNAP_HORIZONTAL (ceil, x)
+#define SNAP_CEIL_VERTICAL(y) SNAP_VERTICAL (ceil, y)
+
+/* Copied from CajaIconContainer */
+#define CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT 5
+
+/* Copied from CajaFile */
+#define UNDEFINED_TIME ((time_t) (-1))
+
+enum
+{
+ ACTION_ACTIVATE,
+ ACTION_MENU,
+ LAST_ACTION
+};
+
+typedef struct
+{
+ GList *selection;
+ char *action_descriptions[LAST_ACTION];
+} CajaIconContainerAccessiblePrivate;
+
+static GType caja_icon_container_accessible_get_type (void);
+
+static void activate_selected_items (CajaIconContainer *container);
+static void activate_selected_items_alternate (CajaIconContainer *container,
+ CajaIcon *icon);
+static void caja_icon_container_theme_changed (gpointer user_data);
+static void compute_stretch (StretchState *start,
+ StretchState *current);
+static CajaIcon *get_first_selected_icon (CajaIconContainer *container);
+static CajaIcon *get_nth_selected_icon (CajaIconContainer *container,
+ int index);
+static gboolean has_multiple_selection (CajaIconContainer *container);
+static gboolean all_selected (CajaIconContainer *container);
+static gboolean has_selection (CajaIconContainer *container);
+static void icon_destroy (CajaIconContainer *container,
+ CajaIcon *icon);
+static void end_renaming_mode (CajaIconContainer *container,
+ gboolean commit);
+static CajaIcon *get_icon_being_renamed (CajaIconContainer *container);
+static void finish_adding_new_icons (CajaIconContainer *container);
+static void update_label_color (EelBackground *background,
+ CajaIconContainer *icon_container);
+static inline void icon_get_bounding_box (CajaIcon *icon,
+ int *x1_return,
+ int *y1_return,
+ int *x2_return,
+ int *y2_return,
+ CajaIconCanvasItemBoundsUsage usage);
+static gboolean is_renaming (CajaIconContainer *container);
+static gboolean is_renaming_pending (CajaIconContainer *container);
+static void process_pending_icon_to_rename (CajaIconContainer *container);
+static void setup_label_gcs (CajaIconContainer *container);
+static void caja_icon_container_stop_monitor_top_left (CajaIconContainer *container,
+ CajaIconData *data,
+ gconstpointer client);
+static void caja_icon_container_start_monitor_top_left (CajaIconContainer *container,
+ CajaIconData *data,
+ gconstpointer client,
+ gboolean large_text);
+static void handle_hadjustment_changed (GtkAdjustment *adjustment,
+ CajaIconContainer *container);
+static void handle_vadjustment_changed (GtkAdjustment *adjustment,
+ CajaIconContainer *container);
+static GList * caja_icon_container_get_selected_icons (CajaIconContainer *container);
+static void caja_icon_container_update_visible_icons (CajaIconContainer *container);
+static void reveal_icon (CajaIconContainer *container,
+ CajaIcon *icon);
+
+static void caja_icon_container_set_rtl_positions (CajaIconContainer *container);
+static double get_mirror_x_position (CajaIconContainer *container,
+ CajaIcon *icon,
+ double x);
+
+static int compare_icons_horizontal (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b);
+
+static int compare_icons_vertical (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b);
+
+static void store_layout_timestamps_now (CajaIconContainer *container);
+
+static gpointer accessible_parent_class;
+
+static GQuark accessible_private_data_quark = 0;
+
+static const char *caja_icon_container_accessible_action_names[] =
+{
+ "activate",
+ "menu",
+ NULL
+};
+
+static const char *caja_icon_container_accessible_action_descriptions[] =
+{
+ "Activate selected items",
+ "Popup context menu",
+ NULL
+};
+
+G_DEFINE_TYPE (CajaIconContainer, caja_icon_container, EEL_TYPE_CANVAS);
+
+/* The CajaIconContainer signals. */
+enum
+{
+ ACTIVATE,
+ ACTIVATE_ALTERNATE,
+ BAND_SELECT_STARTED,
+ BAND_SELECT_ENDED,
+ BUTTON_PRESS,
+ CAN_ACCEPT_ITEM,
+ CONTEXT_CLICK_BACKGROUND,
+ CONTEXT_CLICK_SELECTION,
+ MIDDLE_CLICK,
+ GET_CONTAINER_URI,
+ GET_ICON_URI,
+ GET_ICON_DROP_TARGET_URI,
+ GET_STORED_ICON_POSITION,
+ ICON_POSITION_CHANGED,
+ GET_STORED_LAYOUT_TIMESTAMP,
+ STORE_LAYOUT_TIMESTAMP,
+ ICON_TEXT_CHANGED,
+ ICON_STRETCH_STARTED,
+ ICON_STRETCH_ENDED,
+ RENAMING_ICON,
+ LAYOUT_CHANGED,
+ MOVE_COPY_ITEMS,
+ HANDLE_NETSCAPE_URL,
+ HANDLE_URI_LIST,
+ HANDLE_TEXT,
+ HANDLE_RAW,
+ PREVIEW,
+ SELECTION_CHANGED,
+ ICON_ADDED,
+ ICON_REMOVED,
+ CLEARED,
+ START_INTERACTIVE_SEARCH,
+ 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 CajaIcons. */
+
+static void
+icon_free (CajaIcon *icon)
+{
+ /* Destroy this canvas item; the parent will unref it. */
+ gtk_object_destroy (GTK_OBJECT (icon->item));
+ g_free (icon);
+}
+
+static gboolean
+icon_is_positioned (const CajaIcon *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 (CajaIcon *icon,
+ double x, double y)
+{
+ CajaIconContainer *container;
+ double pixels_per_unit;
+ int container_left, container_top, container_right, container_bottom;
+ int x1, x2, y1, y2;
+ int container_x, container_y, container_width, container_height;
+ EelDRect icon_bounds;
+ int item_width, item_height;
+ int height_above, height_below, width_left, width_right;
+ int min_x, max_x, min_y, max_y;
+
+ if (icon->x == x && icon->y == y)
+ {
+ return;
+ }
+
+ container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (icon->item)->canvas);
+
+ if (icon == get_icon_being_renamed (container))
+ {
+ end_renaming_mode (container, TRUE);
+ }
+
+ if (caja_icon_container_get_is_fixed_size (container))
+ {
+ /* FIXME: This should be:
+
+ container_x = GTK_WIDGET (container)->allocation.x;
+ container_y = GTK_WIDGET (container)->allocation.y;
+ container_width = GTK_WIDGET (container)->allocation.width;
+ container_height = GTK_WIDGET (container)->allocation.height;
+
+ But for some reason the widget allocation is sometimes not done
+ at startup, and the allocation is then only 45x60. which is
+ really bad.
+
+ For now, we have a cheesy workaround:
+ */
+ container_x = 0;
+ container_y = 0;
+ container_width = gdk_screen_width () - container_x
+ - container->details->left_margin
+ - container->details->right_margin;
+ container_height = gdk_screen_height () - container_y
+ - container->details->top_margin
+ - container->details->bottom_margin;
+ pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
+ /* Clip the position of the icon within our desktop bounds */
+ container_left = container_x / pixels_per_unit;
+ container_top = container_y / pixels_per_unit;
+ container_right = container_left + container_width / pixels_per_unit;
+ container_bottom = container_top + container_height / pixels_per_unit;
+
+ icon_get_bounding_box (icon, &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+ item_width = x2 - x1;
+ item_height = y2 - y1;
+
+ icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+
+ /* determine icon rectangle relative to item rectangle */
+ height_above = icon_bounds.y0 - y1;
+ height_below = y2 - icon_bounds.y1;
+ width_left = icon_bounds.x0 - x1;
+ width_right = x2 - icon_bounds.x1;
+
+ min_x = container_left + DESKTOP_PAD_HORIZONTAL + width_left;
+ max_x = container_right - DESKTOP_PAD_HORIZONTAL - item_width + width_left;
+ x = CLAMP (x, min_x, max_x);
+
+ min_y = container_top + height_above + DESKTOP_PAD_VERTICAL;
+ max_y = container_bottom - DESKTOP_PAD_VERTICAL - item_height + height_above;
+ y = CLAMP (y, min_y, max_y);
+ }
+
+ 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 void
+icon_get_size (CajaIconContainer *container,
+ CajaIcon *icon,
+ guint *size)
+{
+ if (size != NULL)
+ {
+ *size = MAX (caja_get_icon_size_for_zoom_level (container->details->zoom_level)
+ * icon->scale, CAJA_ICON_SIZE_SMALLEST);
+ }
+}
+
+/* The icon_set_size function is used by the stretching user
+ * interface, which currently stretches in a way that keeps the aspect
+ * ratio. Later we might have a stretching interface that stretches Y
+ * separate from X and we will change this around.
+ */
+static void
+icon_set_size (CajaIconContainer *container,
+ CajaIcon *icon,
+ guint icon_size,
+ gboolean snap,
+ gboolean update_position)
+{
+ guint old_size;
+ double scale;
+
+ icon_get_size (container, icon, &old_size);
+ if (icon_size == old_size)
+ {
+ return;
+ }
+
+ scale = (double) icon_size /
+ caja_get_icon_size_for_zoom_level
+ (container->details->zoom_level);
+ caja_icon_container_move_icon (container, icon,
+ icon->x, icon->y,
+ scale, FALSE,
+ snap, update_position);
+}
+
+static void
+icon_raise (CajaIcon *icon)
+{
+ EelCanvasItem *item, *band;
+
+ item = EEL_CANVAS_ITEM (icon->item);
+ band = CAJA_ICON_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
+
+ eel_canvas_item_send_behind (item, band);
+}
+
+static void
+emit_stretch_started (CajaIconContainer *container, CajaIcon *icon)
+{
+ g_signal_emit (container,
+ signals[ICON_STRETCH_STARTED], 0,
+ icon->data);
+}
+
+static void
+emit_stretch_ended (CajaIconContainer *container, CajaIcon *icon)
+{
+ g_signal_emit (container,
+ signals[ICON_STRETCH_ENDED], 0,
+ icon->data);
+}
+
+static void
+icon_toggle_selected (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ end_renaming_mode (container, TRUE);
+
+ icon->is_selected = !icon->is_selected;
+ eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
+ "highlighted_for_selection", (gboolean) icon->is_selected,
+ NULL);
+
+ /* If the icon is deselected, then get rid of the stretch handles.
+ * No harm in doing the same if the item is newly selected.
+ */
+ if (icon == container->details->stretch_icon)
+ {
+ container->details->stretch_icon = NULL;
+ caja_icon_canvas_item_set_show_stretch_handles (icon->item, FALSE);
+ /* snap the icon if necessary */
+ if (container->details->keep_aligned)
+ {
+ caja_icon_container_move_icon (container,
+ icon,
+ icon->x, icon->y,
+ icon->scale,
+ FALSE, TRUE, TRUE);
+ }
+
+ emit_stretch_ended (container, icon);
+ }
+
+ /* 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 (CajaIconContainer *container,
+ CajaIcon *icon,
+ gboolean select)
+{
+ g_assert (select == FALSE || select == TRUE);
+ g_assert (icon->is_selected == FALSE || icon->is_selected == TRUE);
+
+ 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 (CajaIcon *icon,
+ int *x1_return, int *y1_return,
+ int *x2_return, int *y2_return,
+ CajaIconCanvasItemBoundsUsage 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)
+ {
+ caja_icon_canvas_item_get_bounds_for_layout (icon->item,
+ &x1, &y1, &x2, &y2);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ caja_icon_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 CajaIconContainer. */
+
+gboolean
+caja_icon_container_scroll (CajaIconContainer *container,
+ int delta_x, int delta_y)
+{
+ GtkAdjustment *hadj, *vadj;
+ int old_h_value, old_v_value;
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (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);
+
+ eel_gtk_adjustment_set_value (hadj, gtk_adjustment_get_value (hadj) + delta_x);
+ eel_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 (CajaIconCanvasItem *item,
+ CajaIconContainer *container)
+{
+ g_assert (CAJA_IS_ICON_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 CajaIcon*
+get_pending_icon_to_reveal (CajaIconContainer *container)
+{
+ return container->details->pending_icon_to_reveal;
+}
+
+static void
+set_pending_icon_to_reveal (CajaIconContainer *container, CajaIcon *icon)
+{
+ CajaIcon *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,
+ gboolean safety_pad)
+{
+ 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);
+ if (safety_pad)
+ {
+ 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 (CajaIconContainer *container,
+ CajaIcon *icon,
+ EelIRect *bounds,
+ gboolean safety_pad)
+{
+ GList *p;
+ CajaIcon *one_icon;
+ EelIRect one_bounds;
+
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), bounds, safety_pad);
+
+ 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, safety_pad);
+ 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, safety_pad);
+ bounds->y0 = MIN (bounds->y0, one_bounds.y0);
+ bounds->y1 = MAX (bounds->y1, one_bounds.y1);
+ }
+ }
+
+
+}
+
+static void
+reveal_icon (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ CajaIconContainerDetails *details;
+ 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);
+
+ details = container->details;
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
+
+ if (caja_icon_container_is_auto_layout (container))
+ {
+ /* ensure that we reveal the entire row/column */
+ icon_get_row_and_column_bounds (container, icon, &bounds, TRUE);
+ }
+ else
+ {
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), &bounds, TRUE);
+ }
+ if (bounds.y0 < gtk_adjustment_get_value (vadj))
+ {
+ eel_gtk_adjustment_set_value (vadj, bounds.y0);
+ }
+ else if (bounds.y1 > gtk_adjustment_get_value (vadj) + allocation.height)
+ {
+ eel_gtk_adjustment_set_value
+ (vadj, bounds.y1 - allocation.height);
+ }
+
+ if (bounds.x0 < gtk_adjustment_get_value (hadj))
+ {
+ eel_gtk_adjustment_set_value (hadj, bounds.x0);
+ }
+ else if (bounds.x1 > gtk_adjustment_get_value (hadj) + allocation.width)
+ {
+ eel_gtk_adjustment_set_value
+ (hadj, bounds.x1 - allocation.width);
+ }
+}
+
+static void
+process_pending_icon_to_reveal (CajaIconContainer *container)
+{
+ CajaIcon *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)
+{
+ CajaIconContainer *container;
+ CajaIcon *icon;
+
+ container = CAJA_ICON_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->keyboard_focus
+ || icon->is_selected)
+ {
+ reveal_icon (container, icon);
+ }
+ container->details->keyboard_icon_reveal_timer_id = 0;
+
+ return FALSE;
+}
+
+static void
+unschedule_keyboard_icon_reveal (CajaIconContainer *container)
+{
+ CajaIconContainerDetails *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 (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ CajaIconContainerDetails *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
+clear_keyboard_focus (CajaIconContainer *container)
+{
+ if (container->details->keyboard_focus != NULL)
+ {
+ eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
+ "highlighted_as_keyboard_focus", 0,
+ NULL);
+ }
+
+ container->details->keyboard_focus = NULL;
+}
+
+static void inline
+emit_atk_focus_tracker_notify (CajaIcon *icon)
+{
+ AtkObject *atk_object = eel_accessibility_for_object (icon->item);
+ atk_focus_tracker_notify (atk_object);
+}
+
+/* Set @icon as the icon currently selected for keyboard operations. */
+static void
+set_keyboard_focus (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ g_assert (icon != NULL);
+
+ if (icon == container->details->keyboard_focus)
+ {
+ return;
+ }
+
+ clear_keyboard_focus (container);
+
+ container->details->keyboard_focus = icon;
+
+ eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
+ "highlighted_as_keyboard_focus", 1,
+ NULL);
+
+ emit_atk_focus_tracker_notify (icon);
+}
+
+static void
+set_keyboard_rubberband_start (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ container->details->keyboard_rubberband_start = icon;
+}
+
+static void
+clear_keyboard_rubberband_start (CajaIconContainer *container)
+{
+ container->details->keyboard_rubberband_start = NULL;
+}
+
+/* carbon-copy of eel_canvas_group_bounds(), but
+ * for CajaIconContainerItems 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,
+ CajaIconCanvasItemBoundsUsage 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 (child->flags & EEL_CANVAS_ITEM_VISIBLE)
+ {
+ set = TRUE;
+ if (!CAJA_IS_ICON_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)
+ {
+ caja_icon_canvas_item_get_bounds_for_layout (CAJA_ICON_CANVAS_ITEM (child),
+ &minx, &miny, &maxx, &maxy);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ caja_icon_canvas_item_get_bounds_for_entire_item (CAJA_ICON_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 (!(child->flags & EEL_CANVAS_ITEM_VISIBLE))
+ continue;
+
+ if (!CAJA_IS_ICON_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)
+ {
+ caja_icon_canvas_item_get_bounds_for_layout (CAJA_ICON_CANVAS_ITEM (child),
+ &tx1, &ty1, &tx2, &ty2);
+ }
+ else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
+ {
+ caja_icon_canvas_item_get_bounds_for_entire_item (CAJA_ICON_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 (CajaIconContainer *container,
+ double *x1, double *y1,
+ double *x2, double *y2,
+ CajaIconCanvasItemBoundsUsage 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);
+}
+
+/* Don't preserve visible white space the next time the scroll region
+ * is recomputed when the container is not empty. */
+void
+caja_icon_container_reset_scroll_region (CajaIconContainer *container)
+{
+ container->details->reset_scroll_region_trigger = TRUE;
+}
+
+/* Set a new scroll region without eliminating any of the currently-visible area. */
+static void
+canvas_set_scroll_region_include_visible_area (EelCanvas *canvas,
+ double x1, double y1,
+ double x2, double y2)
+{
+ double old_x1, old_y1, old_x2, old_y2;
+ double old_scroll_x, old_scroll_y;
+ double height, width;
+ GtkAllocation allocation;
+
+ eel_canvas_get_scroll_region (canvas, &old_x1, &old_y1, &old_x2, &old_y2);
+ gtk_widget_get_allocation (GTK_WIDGET (canvas), &allocation);
+
+ width = (allocation.width) / canvas->pixels_per_unit;
+ height = (allocation.height) / canvas->pixels_per_unit;
+
+ old_scroll_x = gtk_adjustment_get_value (GTK_ADJUSTMENT (gtk_layout_get_hadjustment (GTK_LAYOUT (canvas))));
+ old_scroll_y = gtk_adjustment_get_value (GTK_ADJUSTMENT (gtk_layout_get_vadjustment (GTK_LAYOUT (canvas))));
+
+ x1 = MIN (x1, old_x1 + old_scroll_x);
+ y1 = MIN (y1, old_y1 + old_scroll_y);
+ x2 = MAX (x2, old_x1 + old_scroll_x + width);
+ y2 = MAX (y2, old_y1 + old_scroll_y + height);
+
+ eel_canvas_set_scroll_region
+ (canvas, x1, y1, x2, y2);
+}
+
+void
+caja_icon_container_update_scroll_region (CajaIconContainer *container)
+{
+ double x1, y1, x2, y2;
+ double pixels_per_unit;
+ GtkAdjustment *hadj, *vadj;
+ float step_increment;
+ gboolean reset_scroll_region;
+ GtkAllocation allocation;
+
+ pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
+
+ if (caja_icon_container_get_is_fixed_size (container))
+ {
+ /* Set the scroll region to the size of the container allocation */
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ eel_canvas_set_scroll_region
+ (EEL_CANVAS (container),
+ (double) - container->details->left_margin / pixels_per_unit,
+ (double) - container->details->top_margin / pixels_per_unit,
+ ((double) (allocation.width - 1)
+ - container->details->left_margin
+ - container->details->right_margin)
+ / pixels_per_unit,
+ ((double) (allocation.height - 1)
+ - container->details->top_margin
+ - container->details->bottom_margin)
+ / pixels_per_unit);
+ return;
+ }
+
+ reset_scroll_region = container->details->reset_scroll_region_trigger
+ || caja_icon_container_is_empty (container)
+ || caja_icon_container_is_auto_layout (container);
+
+ /* The trigger is only cleared when container is non-empty, so
+ * callers can reliably reset the scroll region when an item
+ * is added even if extraneous relayouts are called when the
+ * window is still empty.
+ */
+ if (!caja_icon_container_is_empty (container))
+ {
+ container->details->reset_scroll_region_trigger = FALSE;
+ }
+
+ 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.
+ * For horizontal layouts, we add a bottom border.
+ * Vertical layout is used by the compact view so the end
+ * depends on the RTL setting.
+ */
+ if (caja_icon_container_is_layout_vertical (container))
+ {
+ if (caja_icon_container_is_layout_rtl (container))
+ {
+ x1 -= ICON_PAD_LEFT + CONTAINER_PAD_LEFT;
+ }
+ else
+ {
+ x2 += ICON_PAD_RIGHT + CONTAINER_PAD_RIGHT;
+ }
+ }
+ else
+ {
+ 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 */
+ if (caja_icon_container_is_auto_layout (container))
+ {
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ x1 = MIN (x1, 0);
+ x2 = MAX (x2, allocation.width / pixels_per_unit);
+ y1 = 0;
+ }
+ else
+ {
+ /* Otherwise we add the padding that is at the start of the
+ layout */
+ if (caja_icon_container_is_layout_rtl (container))
+ {
+ x2 += ICON_PAD_RIGHT + CONTAINER_PAD_RIGHT;
+ }
+ else
+ {
+ x1 -= ICON_PAD_LEFT + CONTAINER_PAD_LEFT;
+ }
+ y1 -= ICON_PAD_TOP + CONTAINER_PAD_TOP;
+ }
+
+ x2 -= 1;
+ x2 = MAX(x1, x2);
+
+ y2 -= 1;
+ y2 = MAX(y1, y2);
+
+ if (reset_scroll_region)
+ {
+ eel_canvas_set_scroll_region
+ (EEL_CANVAS (container),
+ x1, y1, x2, y2);
+ }
+ else
+ {
+ canvas_set_scroll_region_include_visible_area
+ (EEL_CANVAS (container),
+ x1, y1, x2, y2);
+ }
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
+
+ /* Scroll by 1/4 icon each time you click. */
+ step_increment = caja_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);
+ gtk_adjustment_changed (hadj);
+ }
+ if (gtk_adjustment_get_step_increment (vadj) != step_increment)
+ {
+ gtk_adjustment_set_step_increment (vadj, step_increment);
+ gtk_adjustment_changed (vadj);
+ }
+
+ /* Now that we have a new scroll region, clamp the
+ * adjustments so we are within the valid scroll area.
+ */
+ eel_gtk_adjustment_clamp_value (hadj);
+ eel_gtk_adjustment_clamp_value (vadj);
+}
+
+static int
+compare_icons (gconstpointer a, gconstpointer b, gpointer icon_container)
+{
+ CajaIconContainerClass *klass;
+ const CajaIcon *icon_a, *icon_b;
+
+ icon_a = a;
+ icon_b = b;
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (icon_container);
+
+ return klass->compare_icons (icon_container, icon_a->data, icon_b->data);
+}
+
+static void
+sort_icons (CajaIconContainer *container,
+ GList **icons)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_assert (klass->compare_icons != NULL);
+
+ *icons = g_list_sort_with_data (*icons, compare_icons, container);
+}
+
+static void
+resort (CajaIconContainer *container)
+{
+ sort_icons (container, &container->details->icons);
+}
+
+#if 0
+static double
+get_grid_width (CajaIconContainer *container)
+{
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ return TEXT_BESIDE_ICON_GRID_WIDTH;
+ }
+ else
+ {
+ return STANDARD_ICON_GRID_WIDTH;
+ }
+}
+#endif
+typedef struct
+{
+ double width;
+ double height;
+ double x_offset;
+ double y_offset;
+} IconPositions;
+
+static void
+lay_down_one_line (CajaIconContainer *container,
+ GList *line_start,
+ GList *line_end,
+ double y,
+ double max_height,
+ GArray *positions,
+ gboolean whole_text)
+{
+ GList *p;
+ CajaIcon *icon;
+ double x, y_offset;
+ IconPositions *position;
+ int i;
+ gboolean is_rtl;
+
+ is_rtl = caja_icon_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++);
+
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ y_offset = (max_height - position->height) / 2;
+ }
+ else
+ {
+ y_offset = position->y_offset;
+ }
+
+ icon_set_position
+ (icon,
+ is_rtl ? get_mirror_x_position (container, icon, x + position->x_offset) : x + position->x_offset,
+ y + y_offset);
+ caja_icon_canvas_item_set_entire_text (icon->item, whole_text);
+
+ icon->saved_ltr_x = is_rtl ? get_mirror_x_position (container, icon, icon->x) : icon->x;
+
+ x += position->width;
+ }
+}
+
+static void
+lay_down_one_column (CajaIconContainer *container,
+ GList *line_start,
+ GList *line_end,
+ double x,
+ double y_start,
+ double y_iter,
+ GArray *positions)
+{
+ GList *p;
+ CajaIcon *icon;
+ double y;
+ IconPositions *position;
+ int i;
+ gboolean is_rtl;
+
+ is_rtl = caja_icon_container_is_layout_rtl (container);
+
+ /* Lay out the icons along the baseline. */
+ y = y_start;
+ i = 0;
+ for (p = line_start; p != line_end; p = p->next)
+ {
+ icon = p->data;
+
+ position = &g_array_index (positions, IconPositions, i++);
+
+ icon_set_position
+ (icon,
+ is_rtl ? get_mirror_x_position (container, icon, x + position->x_offset) : x + position->x_offset,
+ y + position->y_offset);
+
+ icon->saved_ltr_x = is_rtl ? get_mirror_x_position (container, icon, icon->x) : icon->x;
+
+ y += y_iter;
+ }
+}
+
+static void
+lay_down_icons_horizontal (CajaIconContainer *container,
+ GList *icons,
+ double start_y)
+{
+ GList *p, *line_start;
+ CajaIcon *icon;
+ double canvas_width, y, canvas_height;
+ GArray *positions;
+ IconPositions *position;
+ EelDRect bounds;
+ EelDRect icon_bounds;
+ EelDRect text_bounds;
+ double max_height_above, max_height_below;
+ double height_above, height_below;
+ double line_width;
+ gboolean gridded_layout;
+ double grid_width;
+ double max_text_width, max_icon_width;
+ int icon_width;
+ int i;
+ GtkAllocation allocation;
+
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+
+ 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);
+ canvas_height = CANVAS_HEIGHT(container, allocation);
+
+ max_icon_width = max_text_width = 0.0;
+
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ /* Would it be worth caching these bounds for the next loop? */
+ for (p = icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ max_icon_width = MAX (max_icon_width, ceil (icon_bounds.x1 - icon_bounds.x0));
+
+ text_bounds = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
+ max_text_width = MAX (max_text_width, ceil (text_bounds.x1 - text_bounds.x0));
+ }
+
+ grid_width = max_icon_width + max_text_width + ICON_PAD_LEFT + ICON_PAD_RIGHT;
+ }
+ else
+ {
+ grid_width = STANDARD_ICON_GRID_WIDTH;
+ }
+
+ gridded_layout = !caja_icon_container_is_tighter_layout (container);
+
+ line_width = container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE ? ICON_PAD_LEFT : 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 */
+ caja_icon_canvas_item_get_bounds_for_layout (icon->item,
+ &bounds.x0, &bounds.y0,
+ &bounds.x1, &bounds.y1);
+
+ icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ text_bounds = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
+
+ if (gridded_layout)
+ {
+ icon_width = ceil ((bounds.x1 - bounds.x0)/grid_width) * grid_width;
+
+
+ }
+ else
+ {
+ icon_width = (bounds.x1 - bounds.x0) + ICON_PAD_RIGHT + 8; /* 8 pixels extra for fancy selection box */
+ }
+
+ /* Calculate size above/below baseline */
+ 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 )
+ {
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ y += ICON_PAD_TOP;
+ }
+ else
+ {
+ /* 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);
+
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ y += max_height_above + max_height_below + ICON_PAD_BOTTOM;
+ }
+ else
+ {
+ /* Advance to next line. */
+ y += max_height_below + ICON_PAD_BOTTOM;
+ }
+
+ line_width = container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE ? ICON_PAD_LEFT : 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;
+
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ if (gridded_layout)
+ {
+ position->x_offset = max_icon_width + ICON_PAD_LEFT + ICON_PAD_RIGHT - (icon_bounds.x1 - icon_bounds.x0);
+ }
+ else
+ {
+ position->x_offset = icon_width - ((icon_bounds.x1 - icon_bounds.x0) + (text_bounds.x1 - text_bounds.x0));
+ }
+ position->y_offset = 0;
+ }
+ else
+ {
+ 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)
+ {
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ y += ICON_PAD_TOP;
+ }
+ else
+ {
+ /* Advance to the baseline. */
+ y += ICON_PAD_TOP + max_height_above;
+ }
+
+ lay_down_one_line (container, line_start, NULL, y, max_height_above, positions, TRUE);
+
+ /* Advance to next line. */
+ y += max_height_below + ICON_PAD_BOTTOM;
+ }
+
+ g_array_free (positions, TRUE);
+}
+
+static void
+get_max_icon_dimensions (GList *icon_start,
+ GList *icon_end,
+ double *max_icon_width,
+ double *max_icon_height,
+ double *max_text_width,
+ double *max_text_height,
+ double *max_bounds_height)
+{
+ CajaIcon *icon;
+ EelDRect icon_bounds;
+ EelDRect text_bounds;
+ GList *p;
+ double y1, y2;
+
+ *max_icon_width = *max_text_width = 0.0;
+ *max_icon_height = *max_text_height = 0.0;
+ *max_bounds_height = 0.0;
+
+ /* Would it be worth caching these bounds for the next loop? */
+ for (p = icon_start; p != icon_end; p = p->next)
+ {
+ icon = p->data;
+
+ icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ *max_icon_width = MAX (*max_icon_width, ceil (icon_bounds.x1 - icon_bounds.x0));
+ *max_icon_height = MAX (*max_icon_height, ceil (icon_bounds.y1 - icon_bounds.y0));
+
+ text_bounds = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
+ *max_text_width = MAX (*max_text_width, ceil (text_bounds.x1 - text_bounds.x0));
+ *max_text_height = MAX (*max_text_height, ceil (text_bounds.y1 - text_bounds.y0));
+
+ caja_icon_canvas_item_get_bounds_for_layout (icon->item,
+ NULL, &y1,
+ NULL, &y2);
+ *max_bounds_height = MAX (*max_bounds_height, y2 - y1);
+ }
+}
+
+/* column-wise layout. At the moment, this only works with label-beside-icon (used by "Compact View"). */
+static void
+lay_down_icons_vertical (CajaIconContainer *container,
+ GList *icons,
+ double start_y)
+{
+ GList *p, *line_start;
+ CajaIcon *icon;
+ double canvas_width, x, canvas_height;
+ GArray *positions;
+ IconPositions *position;
+ EelDRect icon_bounds;
+ EelDRect text_bounds;
+ EelCanvasItem *item;
+ GtkAllocation allocation;
+
+ double line_height;
+
+ double max_height;
+ double max_height_with_borders;
+ double max_width;
+ double max_width_in_column;
+
+ double max_bounds_height;
+ double max_bounds_height_with_borders;
+
+ double max_text_width, max_icon_width;
+ double max_text_height, max_icon_height;
+ int height;
+ int i;
+
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+ g_assert (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE);
+
+ if (icons == NULL)
+ {
+ return;
+ }
+
+ positions = g_array_new (FALSE, FALSE, sizeof (IconPositions));
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+
+ /* Lay out icons a column at a time. */
+ canvas_width = CANVAS_WIDTH(container, allocation);
+ canvas_height = CANVAS_HEIGHT(container, allocation);
+
+ max_icon_width = max_text_width = 0.0;
+ max_icon_height = max_text_height = 0.0;
+ max_bounds_height = 0.0;
+
+ get_max_icon_dimensions (icons, NULL,
+ &max_icon_width, &max_icon_height,
+ &max_text_width, &max_text_height,
+ &max_bounds_height);
+
+ max_width = max_icon_width + max_text_width;
+ max_height = MAX (max_icon_height, max_text_height);
+ max_height_with_borders = ICON_PAD_TOP + max_height;
+
+ max_bounds_height_with_borders = ICON_PAD_TOP + max_bounds_height;
+
+ line_height = ICON_PAD_TOP;
+ line_start = icons;
+ x = 0;
+ i = 0;
+
+ max_width_in_column = 0.0;
+
+ for (p = icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ item = EEL_CANVAS_ITEM (icon->item);
+
+ /* If this icon doesn't fit, it's time to lay out the column that's queued up. */
+
+ /* We use the bounds height here, since for wrapping we also want to consider
+ * overlapping emblems at the bottom. We may wrap a little bit too early since
+ * the icon with the max. bounds height may actually not be in the last row, but
+ * it is better than visual glitches
+ */
+ if (line_start != p && line_height + (max_bounds_height_with_borders-1) >= canvas_height )
+ {
+ x += ICON_PAD_LEFT;
+
+ /* correctly set (per-column) width */
+ if (!container->details->all_columns_same_width)
+ {
+ for (i = 0; i < (int) positions->len; i++)
+ {
+ position = &g_array_index (positions, IconPositions, i);
+ position->width = max_width_in_column;
+ }
+ }
+
+ lay_down_one_column (container, line_start, p, x, CONTAINER_PAD_TOP, max_height_with_borders, positions);
+
+ /* Advance to next column. */
+ if (container->details->all_columns_same_width)
+ {
+ x += max_width + ICON_PAD_RIGHT;
+ }
+ else
+ {
+ x += max_width_in_column + ICON_PAD_RIGHT;
+ }
+
+ line_height = ICON_PAD_TOP;
+ line_start = p;
+ i = 0;
+
+ max_width_in_column = 0;
+ }
+
+ icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ text_bounds = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
+
+ max_width_in_column = MAX (max_width_in_column,
+ ceil (icon_bounds.x1 - icon_bounds.x0) +
+ ceil (text_bounds.x1 - text_bounds.x0));
+
+ g_array_set_size (positions, i + 1);
+ position = &g_array_index (positions, IconPositions, i++);
+ if (container->details->all_columns_same_width)
+ {
+ position->width = max_width;
+ }
+ position->height = max_height;
+ position->y_offset = ICON_PAD_TOP;
+ position->x_offset = ICON_PAD_LEFT;
+
+ position->x_offset += max_icon_width - ceil (icon_bounds.x1 - icon_bounds.x0);
+
+ height = MAX (ceil (icon_bounds.y1 - icon_bounds.y0), ceil(text_bounds.y1 - text_bounds.y0));
+ position->y_offset += (max_height - height) / 2;
+
+ /* Add this icon. */
+ line_height += max_height_with_borders;
+ }
+
+ /* Lay down that last column of icons. */
+ if (line_start != NULL)
+ {
+ x += ICON_PAD_LEFT;
+ lay_down_one_column (container, line_start, NULL, x, CONTAINER_PAD_TOP, max_height_with_borders, positions);
+ }
+
+ g_array_free (positions, TRUE);
+}
+
+static void
+snap_position (CajaIconContainer *container,
+ CajaIcon *icon,
+ int *x, int *y)
+{
+ int center_x;
+ int baseline_y;
+ int icon_width;
+ int icon_height;
+ int total_width;
+ int total_height;
+ EelDRect icon_position;
+ GtkAllocation allocation;
+
+ icon_position = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ icon_width = icon_position.x1 - icon_position.x0;
+ icon_height = icon_position.y1 - icon_position.y0;
+
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ total_width = CANVAS_WIDTH (container, allocation);
+ total_height = CANVAS_HEIGHT (container, allocation);
+
+ if (caja_icon_container_is_layout_rtl (container))
+ *x = get_mirror_x_position (container, icon, *x);
+
+ if (*x + icon_width / 2 < DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X)
+ {
+ *x = DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X - icon_width / 2;
+ }
+
+ if (*x + icon_width / 2 > total_width - (DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X))
+ {
+ *x = total_width - (DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X + (icon_width / 2));
+ }
+
+ if (*y + icon_height < DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y)
+ {
+ *y = DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y - icon_height;
+ }
+
+ if (*y + icon_height > total_height - (DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y))
+ {
+ *y = total_height - (DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y + (icon_height / 2));
+ }
+
+ center_x = *x + icon_width / 2;
+ *x = SNAP_NEAREST_HORIZONTAL (center_x) - (icon_width / 2);
+ if (caja_icon_container_is_layout_rtl (container))
+ {
+ *x = get_mirror_x_position (container, icon, *x);
+ }
+
+
+ /* Find the grid position vertically and place on the proper baseline */
+ baseline_y = *y + icon_height;
+ baseline_y = SNAP_NEAREST_VERTICAL (baseline_y);
+ *y = baseline_y - icon_height;
+}
+
+static int
+compare_icons_by_position (gconstpointer a, gconstpointer b)
+{
+ CajaIcon *icon_a, *icon_b;
+ int x1, y1, x2, y2;
+ int center_a;
+ int center_b;
+
+ icon_a = (CajaIcon*)a;
+ icon_b = (CajaIcon*)b;
+
+ icon_get_bounding_box (icon_a, &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_DISPLAY);
+ center_a = x1 + (x2 - x1) / 2;
+ icon_get_bounding_box (icon_b, &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_DISPLAY);
+ center_b = x1 + (x2 - x1) / 2;
+
+ return center_a == center_b ?
+ icon_a->y - icon_b->y :
+ center_a - center_b;
+}
+
+static PlacementGrid *
+placement_grid_new (CajaIconContainer *container, gboolean tight)
+{
+ PlacementGrid *grid;
+ int width, height;
+ int num_columns;
+ int num_rows;
+ int i;
+ GtkAllocation allocation;
+
+ /* Get container dimensions */
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ width = CANVAS_WIDTH(container, allocation);
+ height = CANVAS_HEIGHT(container, allocation);
+
+ num_columns = width / SNAP_SIZE_X;
+ num_rows = height / SNAP_SIZE_Y;
+
+ if (num_columns == 0 || num_rows == 0)
+ {
+ return NULL;
+ }
+
+ grid = g_new0 (PlacementGrid, 1);
+ grid->tight = tight;
+ grid->num_columns = num_columns;
+ grid->num_rows = num_rows;
+
+ grid->grid_memory = g_new0 (int, (num_rows * num_columns));
+ grid->icon_grid = g_new0 (int *, num_columns);
+
+ for (i = 0; i < num_columns; i++)
+ {
+ grid->icon_grid[i] = grid->grid_memory + (i * num_rows);
+ }
+
+ return grid;
+}
+
+static void
+placement_grid_free (PlacementGrid *grid)
+{
+ g_free (grid->icon_grid);
+ g_free (grid->grid_memory);
+ g_free (grid);
+}
+
+static gboolean
+placement_grid_position_is_free (PlacementGrid *grid, EelIRect pos)
+{
+ int x, y;
+
+ g_assert (pos.x0 >= 0 && pos.x0 < grid->num_columns);
+ g_assert (pos.y0 >= 0 && pos.y0 < grid->num_rows);
+ g_assert (pos.x1 >= 0 && pos.x1 < grid->num_columns);
+ g_assert (pos.y1 >= 0 && pos.y1 < grid->num_rows);
+
+ for (x = pos.x0; x <= pos.x1; x++)
+ {
+ for (y = pos.y0; y <= pos.y1; y++)
+ {
+ if (grid->icon_grid[x][y] != 0)
+ {
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+placement_grid_mark (PlacementGrid *grid, EelIRect pos)
+{
+ int x, y;
+
+ g_assert (pos.x0 >= 0 && pos.x0 < grid->num_columns);
+ g_assert (pos.y0 >= 0 && pos.y0 < grid->num_rows);
+ g_assert (pos.x1 >= 0 && pos.x1 < grid->num_columns);
+ g_assert (pos.y1 >= 0 && pos.y1 < grid->num_rows);
+
+ for (x = pos.x0; x <= pos.x1; x++)
+ {
+ for (y = pos.y0; y <= pos.y1; y++)
+ {
+ grid->icon_grid[x][y] = 1;
+ }
+ }
+}
+
+static void
+canvas_position_to_grid_position (PlacementGrid *grid,
+ EelIRect canvas_position,
+ EelIRect *grid_position)
+{
+ /* The first causes minimal moving around during a snap, but
+ * can end up with partially overlapping icons. The second one won't
+ * allow any overlapping, but can cause more movement to happen
+ * during a snap. */
+ if (grid->tight)
+ {
+ grid_position->x0 = ceil ((double)(canvas_position.x0 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
+ grid_position->y0 = ceil ((double)(canvas_position.y0 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
+ grid_position->x1 = floor ((double)(canvas_position.x1 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
+ grid_position->y1 = floor ((double)(canvas_position.y1 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
+ }
+ else
+ {
+ grid_position->x0 = floor ((double)(canvas_position.x0 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
+ grid_position->y0 = floor ((double)(canvas_position.y0 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
+ grid_position->x1 = floor ((double)(canvas_position.x1 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
+ grid_position->y1 = floor ((double)(canvas_position.y1 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
+ }
+
+ grid_position->x0 = CLAMP (grid_position->x0, 0, grid->num_columns - 1);
+ grid_position->y0 = CLAMP (grid_position->y0, 0, grid->num_rows - 1);
+ grid_position->x1 = CLAMP (grid_position->x1, grid_position->x0, grid->num_columns - 1);
+ grid_position->y1 = CLAMP (grid_position->y1, grid_position->y0, grid->num_rows - 1);
+}
+
+static void
+placement_grid_mark_icon (PlacementGrid *grid, CajaIcon *icon)
+{
+ EelIRect icon_pos;
+ EelIRect grid_pos;
+
+ icon_get_bounding_box (icon,
+ &icon_pos.x0, &icon_pos.y0,
+ &icon_pos.x1, &icon_pos.y1,
+ BOUNDS_USAGE_FOR_LAYOUT);
+ canvas_position_to_grid_position (grid,
+ icon_pos,
+ &grid_pos);
+ placement_grid_mark (grid, grid_pos);
+}
+
+static void
+find_empty_location (CajaIconContainer *container,
+ PlacementGrid *grid,
+ CajaIcon *icon,
+ int start_x,
+ int start_y,
+ int *x,
+ int *y)
+{
+ double icon_width, icon_height;
+ int canvas_width;
+ int canvas_height;
+ int height_for_bound_check;
+ EelIRect icon_position;
+ EelDRect pixbuf_rect;
+ gboolean collision;
+ GtkAllocation allocation;
+
+ /* Get container dimensions */
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ canvas_width = CANVAS_WIDTH(container, allocation);
+ canvas_height = CANVAS_HEIGHT(container, allocation);
+
+ icon_get_bounding_box (icon,
+ &icon_position.x0, &icon_position.y0,
+ &icon_position.x1, &icon_position.y1,
+ BOUNDS_USAGE_FOR_LAYOUT);
+ icon_width = icon_position.x1 - icon_position.x0;
+ icon_height = icon_position.y1 - icon_position.y0;
+
+ icon_get_bounding_box (icon,
+ NULL, &icon_position.y0,
+ NULL, &icon_position.y1,
+ BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+ height_for_bound_check = icon_position.y1 - icon_position.y0;
+
+ pixbuf_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+
+ /* Start the icon on a grid location */
+ snap_position (container, icon, &start_x, &start_y);
+
+ icon_position.x0 = start_x;
+ icon_position.y0 = start_y;
+ icon_position.x1 = icon_position.x0 + icon_width;
+ icon_position.y1 = icon_position.y0 + icon_height;
+
+ do
+ {
+ EelIRect grid_position;
+ gboolean need_new_column;
+
+ collision = FALSE;
+
+ canvas_position_to_grid_position (grid,
+ icon_position,
+ &grid_position);
+
+ need_new_column = icon_position.y0 + height_for_bound_check + DESKTOP_PAD_VERTICAL > canvas_height;
+
+ if (need_new_column ||
+ !placement_grid_position_is_free (grid, grid_position))
+ {
+ icon_position.y0 += SNAP_SIZE_Y;
+ icon_position.y1 = icon_position.y0 + icon_height;
+
+ if (need_new_column)
+ {
+ /* Move to the next column */
+ icon_position.y0 = DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y - (pixbuf_rect.y1 - pixbuf_rect.y0);
+ while (icon_position.y0 < DESKTOP_PAD_VERTICAL)
+ {
+ icon_position.y0 += SNAP_SIZE_Y;
+ }
+ icon_position.y1 = icon_position.y0 + icon_height;
+
+ icon_position.x0 += SNAP_SIZE_X;
+ icon_position.x1 = icon_position.x0 + icon_width;
+ }
+
+ collision = TRUE;
+ }
+ }
+ while (collision && (icon_position.x1 < canvas_width));
+
+ *x = icon_position.x0;
+ *y = icon_position.y0;
+}
+
+static void
+align_icons (CajaIconContainer *container)
+{
+ GList *unplaced_icons;
+ GList *l;
+ PlacementGrid *grid;
+
+ unplaced_icons = g_list_copy (container->details->icons);
+
+ unplaced_icons = g_list_sort (unplaced_icons,
+ compare_icons_by_position);
+
+ if (caja_icon_container_is_layout_rtl (container))
+ {
+ unplaced_icons = g_list_reverse (unplaced_icons);
+ }
+
+ grid = placement_grid_new (container, TRUE);
+
+ if (!grid)
+ {
+ return;
+ }
+
+ for (l = unplaced_icons; l != NULL; l = l->next)
+ {
+ CajaIcon *icon;
+ int x, y;
+
+ icon = l->data;
+ x = icon->saved_ltr_x;
+ y = icon->y;
+ find_empty_location (container, grid,
+ icon, x, y, &x, &y);
+
+ icon_set_position (icon, x, y);
+ icon->saved_ltr_x = icon->x;
+ placement_grid_mark_icon (grid, icon);
+ }
+
+ g_list_free (unplaced_icons);
+
+ placement_grid_free (grid);
+
+ if (caja_icon_container_is_layout_rtl (container))
+ {
+ caja_icon_container_set_rtl_positions (container);
+ }
+}
+
+static double
+get_mirror_x_position (CajaIconContainer *container, CajaIcon *icon, double x)
+{
+ EelDRect icon_bounds;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+
+ return CANVAS_WIDTH(container, allocation) - x - (icon_bounds.x1 - icon_bounds.x0);
+}
+
+static void
+caja_icon_container_set_rtl_positions (CajaIconContainer *container)
+{
+ GList *l;
+ CajaIcon *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_vertical_desktop (CajaIconContainer *container, GList *icons)
+{
+ GList *p, *placed_icons, *unplaced_icons;
+ int total, new_length, placed;
+ CajaIcon *icon;
+ int width, height, max_width, column_width, icon_width, icon_height;
+ int x, y, x1, x2, y1, y2;
+ EelDRect icon_rect;
+ GtkAllocation allocation;
+
+ /* Get container dimensions */
+ gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
+ width = CANVAS_WIDTH(container, allocation);
+ height = CANVAS_HEIGHT(container, allocation);
+
+ /* Determine which icons have and have not been placed */
+ placed_icons = NULL;
+ unplaced_icons = NULL;
+
+ total = g_list_length (container->details->icons);
+ new_length = g_list_length (icons);
+ placed = total - new_length;
+ if (placed > 0)
+ {
+ PlacementGrid *grid;
+ /* Add only placed icons in list */
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ if (icon_is_positioned (icon))
+ {
+ icon_set_position(icon, icon->saved_ltr_x, icon->y);
+ placed_icons = g_list_prepend (placed_icons, icon);
+ }
+ else
+ {
+ icon->x = 0;
+ icon->y = 0;
+ unplaced_icons = g_list_prepend (unplaced_icons, icon);
+ }
+ }
+ placed_icons = g_list_reverse (placed_icons);
+ unplaced_icons = g_list_reverse (unplaced_icons);
+
+ grid = placement_grid_new (container, FALSE);
+
+ if (grid)
+ {
+ for (p = placed_icons; p != NULL; p = p->next)
+ {
+ placement_grid_mark_icon
+ (grid, (CajaIcon*)p->data);
+ }
+
+ /* Place unplaced icons in the best locations */
+ for (p = unplaced_icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ icon_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+
+ /* Start the icon in the first column */
+ x = DESKTOP_PAD_HORIZONTAL + (SNAP_SIZE_X / 2) - ((icon_rect.x1 - icon_rect.x0) / 2);
+ y = DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y - (icon_rect.y1 - icon_rect.y0);
+
+ find_empty_location (container,
+ grid,
+ icon,
+ x, y,
+ &x, &y);
+
+ icon_set_position (icon, x, y);
+ icon->saved_ltr_x = x;
+ placement_grid_mark_icon (grid, icon);
+ }
+
+ placement_grid_free (grid);
+ }
+
+ g_list_free (placed_icons);
+ g_list_free (unplaced_icons);
+ }
+ else
+ {
+ /* There are no placed icons. Just lay them down using our rules */
+ x = DESKTOP_PAD_HORIZONTAL;
+
+ while (icons != NULL)
+ {
+ int center_x;
+ int baseline;
+ int icon_height_for_bound_check;
+ gboolean should_snap;
+
+ should_snap = !(container->details->tighter_layout && !container->details->keep_aligned);
+
+ y = DESKTOP_PAD_VERTICAL;
+
+ max_width = 0;
+
+ /* Calculate max width for column */
+ for (p = icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ icon_get_bounding_box (icon, &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_LAYOUT);
+ icon_width = x2 - x1;
+ icon_height = y2 - y1;
+
+ icon_get_bounding_box (icon, NULL, &y1, NULL, &y2,
+ BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+ icon_height_for_bound_check = y2 - y1;
+
+ if (should_snap)
+ {
+ /* Snap the baseline to a grid position */
+ icon_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ baseline = y + (icon_rect.y1 - icon_rect.y0);
+ baseline = SNAP_CEIL_VERTICAL (baseline);
+ y = baseline - (icon_rect.y1 - icon_rect.y0);
+ }
+
+ /* Check and see if we need to move to a new column */
+ if (y != DESKTOP_PAD_VERTICAL && y + icon_height_for_bound_check > height)
+ {
+ break;
+ }
+
+ if (max_width < icon_width)
+ {
+ max_width = icon_width;
+ }
+
+ y += icon_height + DESKTOP_PAD_VERTICAL;
+ }
+
+ y = DESKTOP_PAD_VERTICAL;
+
+ center_x = x + max_width / 2;
+ column_width = max_width;
+ if (should_snap)
+ {
+ /* Find the grid column to center on */
+ center_x = SNAP_CEIL_HORIZONTAL (center_x);
+ column_width = (center_x - x) + (max_width / 2);
+ }
+
+ /* Lay out column */
+ for (p = icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ icon_get_bounding_box (icon, &x1, &y1, &x2, &y2,
+ BOUNDS_USAGE_FOR_LAYOUT);
+ icon_height = y2 - y1;
+
+ icon_get_bounding_box (icon, NULL, &y1, NULL, &y2,
+ BOUNDS_USAGE_FOR_ENTIRE_ITEM);
+ icon_height_for_bound_check = y2 - y1;
+
+ icon_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+
+ if (should_snap)
+ {
+ baseline = y + (icon_rect.y1 - icon_rect.y0);
+ baseline = SNAP_CEIL_VERTICAL (baseline);
+ y = baseline - (icon_rect.y1 - icon_rect.y0);
+ }
+
+ /* Check and see if we need to move to a new column */
+ if (y != DESKTOP_PAD_VERTICAL && y > height - icon_height_for_bound_check &&
+ /* Make sure we lay out at least one icon per column, to make progress */
+ p != icons)
+ {
+ x += column_width + DESKTOP_PAD_HORIZONTAL;
+ break;
+ }
+
+ icon_set_position (icon,
+ center_x - (icon_rect.x1 - icon_rect.x0) / 2,
+ y);
+
+ icon->saved_ltr_x = icon->x;
+ y += icon_height + DESKTOP_PAD_VERTICAL;
+ }
+ icons = p;
+ }
+ }
+
+ /* These modes are special. We freeze all of our positions
+ * after we do the layout.
+ */
+ /* FIXME bugzilla.gnome.org 42478:
+ * This should not be tied to the direction of layout.
+ * It should be a separate switch.
+ */
+ caja_icon_container_freeze_icon_positions (container);
+}
+
+
+static void
+lay_down_icons (CajaIconContainer *container, GList *icons, double start_y)
+{
+ switch (container->details->layout_mode)
+ {
+ case CAJA_ICON_LAYOUT_L_R_T_B:
+ case CAJA_ICON_LAYOUT_R_L_T_B:
+ lay_down_icons_horizontal (container, icons, start_y);
+ break;
+
+ case CAJA_ICON_LAYOUT_T_B_L_R:
+ case CAJA_ICON_LAYOUT_T_B_R_L:
+ if (caja_icon_container_get_is_desktop (container))
+ {
+ lay_down_icons_vertical_desktop (container, icons);
+ }
+ else
+ {
+ lay_down_icons_vertical (container, icons, start_y);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+redo_layout_internal (CajaIconContainer *container)
+{
+ finish_adding_new_icons (container);
+
+ /* Don't do any re-laying-out during stretching. Later we
+ * might add smart logic that does this and leaves room for
+ * the stretched icon, but if we do it we want it to be fast
+ * and only re-lay-out when it's really needed.
+ */
+ if (container->details->auto_layout
+ && container->details->drag_state != DRAG_STATE_STRETCH)
+ {
+ resort (container);
+ lay_down_icons (container, container->details->icons, 0);
+ }
+
+ if (caja_icon_container_is_layout_rtl (container))
+ {
+ caja_icon_container_set_rtl_positions (container);
+ }
+
+ caja_icon_container_update_scroll_region (container);
+
+ process_pending_icon_to_reveal (container);
+ process_pending_icon_to_rename (container);
+ caja_icon_container_update_visible_icons (container);
+}
+
+static gboolean
+redo_layout_callback (gpointer callback_data)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_CONTAINER (callback_data);
+ redo_layout_internal (container);
+ container->details->idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+unschedule_redo_layout (CajaIconContainer *container)
+{
+ if (container->details->idle_id != 0)
+ {
+ g_source_remove (container->details->idle_id);
+ container->details->idle_id = 0;
+ }
+}
+
+static void
+schedule_redo_layout (CajaIconContainer *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 (CajaIconContainer *container)
+{
+ unschedule_redo_layout (container);
+ redo_layout_internal (container);
+}
+
+static void
+reload_icon_positions (CajaIconContainer *container)
+{
+ GList *p, *no_position_icons;
+ CajaIcon *icon;
+ gboolean have_stored_position;
+ CajaIconPosition position;
+ EelDRect bounds;
+ double bottom;
+ EelCanvasItem *item;
+
+ g_assert (!container->details->auto_layout);
+
+ resort (container);
+
+ no_position_icons = NULL;
+
+ /* Place all the icons with positions. */
+ bottom = 0;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ have_stored_position = FALSE;
+ g_signal_emit (container,
+ signals[GET_STORED_ICON_POSITION], 0,
+ icon->data,
+ &position,
+ &have_stored_position);
+ if (have_stored_position)
+ {
+ icon_set_position (icon, position.x, position.y);
+ item = EEL_CANVAS_ITEM (icon->item);
+ caja_icon_canvas_item_get_bounds_for_layout (icon->item,
+ &bounds.x0,
+ &bounds.y0,
+ &bounds.x1,
+ &bounds.y1);
+ eel_canvas_item_i2w (item->parent,
+ &bounds.x0,
+ &bounds.y0);
+ eel_canvas_item_i2w (item->parent,
+ &bounds.x1,
+ &bounds.y1);
+ if (bounds.y1 > bottom)
+ {
+ bottom = bounds.y1;
+ }
+ }
+ else
+ {
+ no_position_icons = g_list_prepend (no_position_icons, icon);
+ }
+ }
+ no_position_icons = g_list_reverse (no_position_icons);
+
+ /* Place all the other icons. */
+ lay_down_icons (container, no_position_icons, bottom + ICON_PAD_BOTTOM);
+ g_list_free (no_position_icons);
+}
+
+/* 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 (CajaIconContainer *container)
+{
+ GList *p;
+ CajaIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ caja_icon_canvas_item_invalidate_label_size (icon->item);
+ }
+}
+
+/* invalidate the entire labels (i.e. their attributes) for all the icons */
+static void
+invalidate_labels (CajaIconContainer *container)
+{
+ GList *p;
+ CajaIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ caja_icon_canvas_item_invalidate_label (icon->item);
+ }
+}
+
+static gboolean
+select_range (CajaIconContainer *container,
+ CajaIcon *icon1,
+ CajaIcon *icon2,
+ gboolean unselect_outside_range)
+{
+ gboolean selection_changed;
+ GList *p;
+ CajaIcon *icon;
+ CajaIcon *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;
+ }
+
+ }
+
+ if (selection_changed && icon2 != NULL)
+ {
+ emit_atk_focus_tracker_notify (icon2);
+ }
+ return selection_changed;
+}
+
+
+static gboolean
+select_one_unselect_others (CajaIconContainer *container,
+ CajaIcon *icon_to_select)
+{
+ gboolean selection_changed;
+ GList *p;
+ CajaIcon *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)
+ {
+ emit_atk_focus_tracker_notify (icon_to_select);
+ reveal_icon (container, icon_to_select);
+ }
+ return selection_changed;
+}
+
+static gboolean
+unselect_all (CajaIconContainer *container)
+{
+ return select_one_unselect_others (container, NULL);
+}
+
+void
+caja_icon_container_move_icon (CajaIconContainer *container,
+ CajaIcon *icon,
+ int x, int y,
+ double scale,
+ gboolean raise,
+ gboolean snap,
+ gboolean update_position)
+{
+ CajaIconContainerDetails *details;
+ gboolean emit_signal;
+ CajaIconPosition position;
+
+ details = container->details;
+
+ emit_signal = FALSE;
+
+ if (icon == get_icon_being_renamed (container))
+ {
+ end_renaming_mode (container, TRUE);
+ }
+
+ if (scale != icon->scale)
+ {
+ icon->scale = scale;
+ caja_icon_container_update_icon (container, icon);
+ if (update_position)
+ {
+ redo_layout (container);
+ emit_signal = TRUE;
+ }
+ }
+
+ if (!details->auto_layout)
+ {
+ if (details->keep_aligned && snap)
+ {
+ snap_position (container, icon, &x, &y);
+ }
+
+ if (x != icon->x || y != icon->y)
+ {
+ icon_set_position (icon, x, y);
+ emit_signal = update_position;
+ }
+
+ icon->saved_ltr_x = caja_icon_container_is_layout_rtl (container) ? get_mirror_x_position (container, icon, icon->x) : icon->x;
+ }
+
+ if (emit_signal)
+ {
+ position.x = icon->saved_ltr_x;
+ position.y = icon->y;
+ position.scale = scale;
+ g_signal_emit (container,
+ signals[ICON_POSITION_CHANGED], 0,
+ icon->data, &position);
+ }
+
+ if (raise)
+ {
+ icon_raise (icon);
+ }
+
+ /* FIXME bugzilla.gnome.org 42474:
+ * Handling of the scroll region is inconsistent here. In
+ * the scale-changing case, redo_layout is called, which updates the
+ * scroll region appropriately. In other cases, it's up to the
+ * caller to make sure the scroll region is updated. This could
+ * lead to hard-to-track-down bugs.
+ */
+}
+
+/* Implementation of rubberband selection. */
+static void
+rubberband_select (CajaIconContainer *container,
+ const EelDRect *previous_rect,
+ const EelDRect *current_rect)
+{
+ GList *p;
+ gboolean selection_changed, is_in, canvas_rect_calculated;
+ CajaIcon *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 = caja_icon_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)
+{
+ CajaIconContainer *container;
+ GtkWidget *widget;
+ CajaIconRubberbandInfo *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 = CAJA_ICON_CONTAINER (data);
+ band_info = &container->details->rubberband_info;
+
+ g_assert (band_info->timer_id != 0);
+ g_assert (EEL_IS_CANVAS_RECT (band_info->selection_rectangle) ||
+ EEL_IS_CANVAS_RECT (band_info->selection_rectangle));
+
+ adj_changed = FALSE;
+ gtk_widget_get_allocation (widget, &allocation);
+
+ adj_x = gtk_adjustment_get_value (gtk_layout_get_hadjustment (GTK_LAYOUT (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_layout_get_vadjustment (GTK_LAYOUT (container)));
+ if (adj_y != band_info->last_adj_y)
+ {
+ band_info->last_adj_y = adj_y;
+ adj_changed = TRUE;
+ }
+
+ gtk_widget_get_pointer (widget, &x, &y);
+
+ if (x < 0)
+ {
+ x_scroll = x;
+ x = 0;
+ }
+ else if (x >= allocation.width)
+ {
+ x_scroll = x - allocation.width + 1;
+ x = allocation.width - 1;
+ }
+ else
+ {
+ x_scroll = 0;
+ }
+
+ if (y < 0)
+ {
+ y_scroll = y;
+ y = 0;
+ }
+ else if (y >= allocation.height)
+ {
+ y_scroll = y - allocation.height + 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;
+ }
+
+ caja_icon_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_layout_get_hadjustment (GTK_LAYOUT (container))),
+ y + gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (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,
+ &band_info->prev_rect,
+ &selection_rect);
+
+ band_info->prev_x = x;
+ band_info->prev_y = y;
+
+ band_info->prev_rect = selection_rect;
+
+ return TRUE;
+}
+
+static void
+start_rubberbanding (CajaIconContainer *container,
+ GdkEventButton *event)
+{
+ AtkObject *accessible;
+ CajaIconContainerDetails *details;
+ CajaIconRubberbandInfo *band_info;
+ guint fill_color, outline_color;
+ GdkColor *fill_color_gdk;
+ guchar fill_color_alpha;
+ GList *p;
+ CajaIcon *icon;
+ GtkStyle *style;
+
+ details = container->details;
+ band_info = &details->rubberband_info;
+
+ g_signal_emit (container,
+ signals[BAND_SELECT_STARTED], 0);
+
+ 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);
+
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "selection_box_color", &fill_color_gdk,
+ "selection_box_alpha", &fill_color_alpha,
+ NULL);
+
+ if (!fill_color_gdk)
+ {
+ style = gtk_widget_get_style (GTK_WIDGET (container));
+ fill_color_gdk = gdk_color_copy (&style->base[GTK_STATE_SELECTED]);
+ }
+
+ fill_color = eel_gdk_color_to_rgb (fill_color_gdk) << 8 | fill_color_alpha;
+
+ gdk_color_free (fill_color_gdk);
+
+ outline_color = fill_color | 255;
+
+ band_info->selection_rectangle = eel_canvas_item_new
+ (eel_canvas_root
+ (EEL_CANVAS (container)),
+ EEL_TYPE_CANVAS_RECT,
+ "x1", band_info->start_x,
+ "y1", band_info->start_y,
+ "x2", band_info->start_x,
+ "y2", band_info->start_y,
+ "fill_color_rgba", fill_color,
+ "outline_color_rgba", outline_color,
+ "width_pixels", 1,
+ 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_layout_get_hadjustment (GTK_LAYOUT (container)));
+ band_info->prev_y = event->y - gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (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, event->time);
+}
+
+static void
+stop_rubberbanding (CajaIconContainer *container,
+ guint32 time)
+{
+ CajaIconRubberbandInfo *band_info;
+ GList *icons;
+
+ band_info = &container->details->rubberband_info;
+
+ g_assert (band_info->timer_id != 0);
+ g_source_remove (band_info->timer_id);
+ band_info->timer_id = 0;
+
+ band_info->active = FALSE;
+
+ /* Destroy this canvas item; the parent will unref it. */
+ eel_canvas_item_ungrab (band_info->selection_rectangle, time);
+ gtk_object_destroy (GTK_OBJECT (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 = caja_icon_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 (* IsBetterIconFunction) (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *candidate,
+ void *data);
+
+static CajaIcon *
+find_best_icon (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ IsBetterIconFunction function,
+ void *data)
+{
+ GList *p;
+ CajaIcon *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 CajaIcon *
+find_best_selected_icon (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ IsBetterIconFunction function,
+ void *data)
+{
+ GList *p;
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b)
+{
+ char *uri_a, *uri_b;
+ int result;
+
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+ g_assert (icon_a != NULL);
+ g_assert (icon_b != NULL);
+ g_assert (icon_a != icon_b);
+
+ uri_a = caja_icon_container_get_icon_uri (container, icon_a);
+ uri_b = caja_icon_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 (CajaIconContainer *container,
+ EelDRect icon_rect)
+{
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
+ {
+ return icon_rect.x0;
+ }
+ else
+ {
+ return icon_rect.x1;
+ }
+ }
+ else
+ {
+ return (icon_rect.x0 + icon_rect.x1) / 2;
+ }
+}
+
+static int
+get_cmp_point_y (CajaIconContainer *container,
+ EelDRect icon_rect)
+{
+ if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ return (icon_rect.y0 + icon_rect.y1)/2;
+ }
+ else
+ {
+ return icon_rect.y1;
+ }
+}
+
+
+static int
+compare_icons_horizontal (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ax, bx;
+
+ world_rect = caja_icon_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 = caja_icon_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 (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ay, by;
+
+ world_rect = caja_icon_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 = caja_icon_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 (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ax, ay, bx, by;
+
+ world_rect = caja_icon_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 = caja_icon_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 (CajaIconContainer *container,
+ CajaIcon *icon_a,
+ CajaIcon *icon_b)
+{
+ EelDRect world_rect;
+ int ax, ay, bx, by;
+
+ world_rect = caja_icon_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 = caja_icon_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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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_column_bottommost (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not on the right of the current column */
+ if (compare_with_start_column (container, candidate) >= 0)
+ {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ /* candidate is above best choice, but below the current row */
+ return TRUE;
+ }
+
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+static gboolean
+previous_row_rightmost (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *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
+previous_column_highest (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not before the current column */
+ if (compare_with_start_column (container, candidate) <= 0)
+ {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_horizontal (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ /* candidate is right of the best choice, but left of the current column */
+ return TRUE;
+ }
+
+ if (compare_icons_vertical (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+
+static gboolean
+next_column_highest (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not after the current column */
+ if (compare_with_start_column (container, candidate) >= 0)
+ {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ /* candidate is left of the best choice, but right of the current column */
+ return TRUE;
+ }
+
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) > 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+static gboolean
+previous_column_lowest (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *candidate,
+ void *data)
+{
+ /* sort out icons that are not before the current column */
+ if (compare_with_start_column (container, candidate) <= 0)
+ {
+ return FALSE;
+ }
+
+ if (best_so_far != NULL)
+ {
+ if (compare_icons_horizontal_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ /* candidate is right of the best choice, but left of the current column */
+ return TRUE;
+ }
+
+ if (compare_icons_vertical_first (container,
+ best_so_far,
+ candidate) < 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return best_so_far == NULL;
+}
+
+static gboolean
+last_column_lowest (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *candidate,
+ void *data)
+{
+ if (best_so_far == NULL)
+ {
+ return TRUE;
+ }
+ return compare_icons_horizontal_first (container, best_so_far, candidate) < 0;
+}
+
+static gboolean
+closest_in_90_degrees (CajaIconContainer *container,
+ CajaIcon *start_icon,
+ CajaIcon *best_so_far,
+ CajaIcon *candidate,
+ void *data)
+{
+ EelDRect world_rect;
+ int x, y;
+ int dx, dy;
+ int dist;
+ int *best_dist;
+
+
+ world_rect = caja_icon_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();
+ }
+
+ 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 (CajaIcon *icon1,
+ CajaIcon *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 (CajaIconContainer *container,
+ CajaIcon *icon,
+ CajaIcon *from,
+ GdkEventKey *event)
+{
+ if (icon == NULL)
+ {
+ return;
+ }
+
+ if (event != NULL &&
+ (event->state & GDK_CONTROL_MASK) != 0 &&
+ (event->state & GDK_SHIFT_MASK) == 0)
+ {
+ /* Move the keyboard focus. Use Control modifier
+ * rather than Alt to avoid Sawfish conflict.
+ */
+ set_keyboard_focus (container, icon);
+ container->details->keyboard_rubberband_start = NULL;
+ }
+ else if (event != NULL &&
+ ((event->state & GDK_CONTROL_MASK) != 0 ||
+ !container->details->auto_layout) &&
+ (event->state & GDK_SHIFT_MASK) != 0)
+ {
+ /* Do rubberband selection */
+ EelDRect rect;
+
+ if (from && !container->details->keyboard_rubberband_start)
+ {
+ set_keyboard_rubberband_start (container, from);
+ }
+
+ set_keyboard_focus (container, icon);
+
+ if (icon && container->details->keyboard_rubberband_start)
+ {
+ rect = get_rubberband (container->details->keyboard_rubberband_start,
+ icon);
+ rubberband_select (container, NULL, &rect);
+ }
+ }
+ else if (event != NULL &&
+ (event->state & GDK_CONTROL_MASK) == 0 &&
+ (event->state & GDK_SHIFT_MASK) != 0)
+ {
+ /* Select range */
+ CajaIcon *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;
+ }
+
+ set_keyboard_focus (container, icon);
+
+ if (select_range (container, start_icon, icon, TRUE))
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+ }
+ else
+ {
+ /* Select icons and get rid of the special keyboard focus. */
+ clear_keyboard_focus (container);
+ 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 (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ CajaIcon *from;
+ CajaIcon *to;
+
+ /* Home selects the first icon.
+ * Control-Home sets the keyboard focus to the first icon.
+ */
+
+ 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 (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ CajaIcon *to;
+ CajaIcon *from;
+
+ /* End selects the last icon.
+ * Control-End sets the keyboard focus to the last icon.
+ */
+ from = find_best_selected_icon (container, NULL,
+ leftmost_in_top_row,
+ NULL);
+ to = find_best_icon (container, NULL,
+ caja_icon_container_is_layout_vertical (container) ?
+ last_column_lowest :
+ rightmost_in_bottom_row,
+ NULL);
+
+ keyboard_move_to (container, to, from, event);
+}
+
+static void
+record_arrow_key_start (CajaIconContainer *container,
+ CajaIcon *icon,
+ GtkDirectionType direction)
+{
+ EelDRect world_rect;
+
+ world_rect = caja_icon_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 (CajaIconContainer *container,
+ GdkEventKey *event,
+ GtkDirectionType direction,
+ IsBetterIconFunction better_start,
+ IsBetterIconFunction empty_start,
+ IsBetterIconFunction better_destination,
+ IsBetterIconFunction better_destination_fallback_if_no_a11y,
+ IsBetterIconFunction better_destination_fallback_fallback,
+ IsBetterIconFunction better_destination_manual)
+{
+ CajaIcon *from;
+ CajaIcon *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->keyboard_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,
+ container->details->auto_layout ? better_destination : better_destination_manual,
+ &data);
+
+ /* only wrap around to next/previous row/column if no a11y is used.
+ * Visually impaired people may be easily confused by this.
+ */
+ if (to == NULL &&
+ better_destination_fallback_if_no_a11y != NULL &&
+ ATK_IS_NO_OP_OBJECT (gtk_widget_get_accessible (GTK_WIDGET (container))))
+ {
+ to = find_best_icon
+ (container, from,
+ better_destination_fallback_if_no_a11y,
+ &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 &&
+ container->details->auto_layout &&
+ 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->state & GDK_CONTROL_MASK) != 0 &&
+ (event->state & GDK_SHIFT_MASK) != 0;
+}
+
+static void
+keyboard_right (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterIconFunction no_a11y;
+ IsBetterIconFunction next_column_fallback;
+
+ no_a11y = NULL;
+ if (container->details->auto_layout &&
+ !caja_icon_container_is_layout_vertical (container) &&
+ !is_rectangle_selection_event (event))
+ {
+ no_a11y = next_row_leftmost;
+ }
+
+ next_column_fallback = NULL;
+ if (caja_icon_container_is_layout_vertical (container) &&
+ gtk_widget_get_direction (GTK_WIDGET (container)) != GTK_TEXT_DIR_RTL)
+ {
+ next_column_fallback = next_column_bottommost;
+ }
+
+ /* 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,
+ caja_icon_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_row_right_side_leftmost,
+ no_a11y,
+ next_column_fallback,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_left (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterIconFunction no_a11y;
+ IsBetterIconFunction previous_column_fallback;
+
+ no_a11y = NULL;
+ if (container->details->auto_layout &&
+ !caja_icon_container_is_layout_vertical (container) &&
+ !is_rectangle_selection_event (event))
+ {
+ no_a11y = previous_row_rightmost;
+ }
+
+ previous_column_fallback = NULL;
+ if (caja_icon_container_is_layout_vertical (container) &&
+ gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
+ {
+ previous_column_fallback = previous_column_lowest;
+ }
+
+ /* 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,
+ caja_icon_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_row_left_side_rightmost,
+ no_a11y,
+ previous_column_fallback,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_down (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterIconFunction no_a11y;
+ IsBetterIconFunction next_row_fallback;
+
+ no_a11y = NULL;
+ if (container->details->auto_layout &&
+ caja_icon_container_is_layout_vertical (container) &&
+ !is_rectangle_selection_event (event))
+ {
+ if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
+ {
+ no_a11y = previous_column_highest;
+ }
+ else
+ {
+ no_a11y = next_column_highest;
+ }
+ }
+
+ next_row_fallback = NULL;
+ if (!caja_icon_container_is_layout_vertical (container))
+ {
+ 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,
+ caja_icon_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_column_below_highest,
+ no_a11y,
+ next_row_fallback,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_up (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ IsBetterIconFunction no_a11y;
+
+ no_a11y = NULL;
+ if (container->details->auto_layout &&
+ caja_icon_container_is_layout_vertical (container) &&
+ !is_rectangle_selection_event (event))
+ {
+ if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
+ {
+ no_a11y = next_column_bottommost;
+ }
+ else
+ {
+ no_a11y = previous_column_lowest;
+ }
+ }
+
+ /* 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,
+ caja_icon_container_is_layout_rtl (container) ?
+ rightmost_in_top_row : leftmost_in_top_row,
+ same_column_above_lowest,
+ no_a11y,
+ NULL,
+ closest_in_90_degrees);
+}
+
+static void
+keyboard_space (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ CajaIcon *icon;
+
+ if (!has_selection (container) &&
+ container->details->keyboard_focus != NULL)
+ {
+ keyboard_move_to (container,
+ container->details->keyboard_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->keyboard_focus != NULL)
+ {
+ icon_toggle_selected (container, container->details->keyboard_focus);
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+ if (container->details->keyboard_focus->is_selected)
+ {
+ container->details->range_selection_base_icon = container->details->keyboard_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_keyboard_focus (container, icon);
+ }
+ }
+ }
+ else if ((event->state & GDK_SHIFT_MASK) != 0)
+ {
+ activate_selected_items_alternate (container, NULL);
+ }
+ else
+ {
+ activate_selected_items (container);
+ }
+}
+
+/* look for the first icon that matches the longest part of a given
+ * search pattern
+ */
+typedef struct
+{
+ gunichar *name;
+ int last_match_length;
+} BestNameMatch;
+
+#ifndef TAB_NAVIGATION_DISABLED
+static void
+select_previous_or_next_icon (CajaIconContainer *container,
+ gboolean next,
+ GdkEventKey *event)
+{
+ CajaIcon *icon;
+ const GList *item;
+
+ item = NULL;
+ /* Chose the icon to start with.
+ * If we have a keyboard focus, start with it.
+ * Otherwise, use the single selected icon.
+ */
+ icon = container->details->keyboard_focus;
+ if (icon == NULL)
+ {
+ icon = get_first_selected_icon (container);
+ }
+
+ if (icon != NULL)
+ {
+ /* must have at least @icon in the list */
+ g_assert (container->details->icons != NULL);
+ item = g_list_find (container->details->icons, icon);
+ g_assert (item != NULL);
+
+ item = next ? item->next : item->prev;
+ if (item == NULL)
+ {
+ item = next ? g_list_first (container->details->icons) : g_list_last (container->details->icons);
+ }
+
+ }
+ else if (container->details->icons != NULL)
+ {
+ /* no selection yet, pick the first or last item to select */
+ item = next ? g_list_first (container->details->icons) : g_list_last (container->details->icons);
+ }
+
+ icon = (item != NULL) ? item->data : NULL;
+
+ if (icon != NULL)
+ {
+ keyboard_move_to (container, icon, NULL, event);
+ }
+}
+#endif
+
+/* GtkObject methods. */
+
+static void
+destroy (GtkObject *object)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_CONTAINER (object);
+
+ caja_icon_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->stretch_idle_id != 0)
+ {
+ g_source_remove (container->details->stretch_idle_id);
+ container->details->stretch_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;
+ }
+
+ /* destroy interactive search dialog */
+ if (container->details->search_window)
+ {
+ gtk_widget_destroy (container->details->search_window);
+ container->details->search_window = NULL;
+ container->details->search_entry = NULL;
+ if (container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout = 0;
+ }
+ }
+
+
+ GTK_OBJECT_CLASS (caja_icon_container_parent_class)->destroy (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ CajaIconContainerDetails *details;
+
+ details = CAJA_ICON_CONTAINER (object)->details;
+
+ eel_preferences_remove_callback (CAJA_PREFERENCES_THEME,
+ caja_icon_container_theme_changed,
+ 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 (caja_icon_container_parent_class)->finalize (object);
+}
+
+/* GtkWidget methods. */
+
+static void
+size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ GTK_WIDGET_CLASS (caja_icon_container_parent_class)->size_request (widget, requisition);
+ requisition->width = 1;
+ requisition->height = 1;
+}
+
+static gboolean
+clear_size_allocation_count (gpointer data)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_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)
+{
+ CajaIconContainer *container;
+ gboolean need_layout_redone;
+ GtkAllocation wid_allocation;
+
+ container = CAJA_ICON_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 (caja_icon_container_parent_class)->size_allocate (widget, allocation);
+
+ container->details->has_been_allocated = TRUE;
+
+ if (need_layout_redone)
+ {
+ redo_layout (container);
+ }
+}
+
+static void
+realize (GtkWidget *widget)
+{
+ GdkBitmap *stipple;
+ GtkAdjustment *vadj, *hadj;
+ CajaIconContainer *container;
+
+ GTK_WIDGET_CLASS (caja_icon_container_parent_class)->realize (widget);
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ /* Ensure that the desktop window is native so the background
+ set on it is drawn by X. */
+ if (container->details->is_desktop)
+ {
+ gdk_x11_drawable_get_xid (gtk_layout_get_bin_window (GTK_LAYOUT (widget)));
+ }
+
+ /* Set up DnD. */
+ caja_icon_dnd_init (container, NULL);
+
+ setup_label_gcs (container);
+
+ stipple = eel_stipple_bitmap_for_screen
+ (gdk_drawable_get_screen (GDK_DRAWABLE (gtk_widget_get_window (widget))));
+
+ caja_icon_dnd_set_stipple (container, stipple);
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (widget));
+ g_signal_connect (hadj, "value_changed",
+ G_CALLBACK (handle_hadjustment_changed), widget);
+
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (widget));
+ g_signal_connect (vadj, "value_changed",
+ G_CALLBACK (handle_vadjustment_changed), widget);
+
+}
+
+static void
+unrealize (GtkWidget *widget)
+{
+ int i;
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ for (i = 0; i < LAST_LABEL_COLOR; i++)
+ {
+ if (container->details->label_gcs [i])
+ {
+ g_object_unref (container->details->label_gcs [i]);
+ container->details->label_gcs [i] = NULL;
+ }
+ }
+
+ caja_icon_dnd_fini (container);
+
+ if (container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout = 0;
+ }
+
+ GTK_WIDGET_CLASS (caja_icon_container_parent_class)->unrealize (widget);
+}
+
+static void
+style_set (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ CajaIconContainer *container;
+ gboolean frame_text;
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "frame_text", &frame_text,
+ NULL);
+
+ container->details->use_drop_shadows = container->details->drop_shadows_requested && !frame_text;
+
+ caja_icon_container_theme_changed (CAJA_ICON_CONTAINER (widget));
+
+ if (gtk_widget_get_realized (widget))
+ {
+ invalidate_label_sizes (container);
+ caja_icon_container_request_update_all (container);
+ }
+
+ /* Don't chain up to parent, because that sets the background of the window and we're doing
+ that ourself with some delay, so this would cause flickering */
+}
+
+static gboolean
+button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ CajaIconContainer *container;
+ gboolean selection_changed;
+ gboolean return_value;
+ gboolean clicked_on_icon;
+
+ container = CAJA_ICON_CONTAINER (widget);
+ container->details->button_down_time = event->time;
+
+ /* Forget about the old keyboard selection now that we've started mousing. */
+ clear_keyboard_focus (container);
+ 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 (caja_icon_container_parent_class)->button_press_event (widget, event);
+
+ /* Move focus to icon container, unless we're still renaming (to avoid exiting
+ * renaming mode)
+ */
+ if (!gtk_widget_has_focus (widget) && !(is_renaming (container) || is_renaming_pending (container)))
+ {
+ gtk_widget_grab_focus (widget);
+ }
+
+ if (clicked_on_icon)
+ {
+ return TRUE;
+ }
+
+ 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)
+ {
+ end_renaming_mode (container, TRUE);
+ 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
+caja_icon_container_did_not_drag (CajaIconContainer *container,
+ GdkEventButton *event)
+{
+ CajaIconContainerDetails *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 = eel_get_system_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
+ * CajaList 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 (CajaIconContainer *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 = eel_get_system_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 */
+ return (click_count == 1);
+}
+
+static void
+clear_drag_state (CajaIconContainer *container)
+{
+ container->details->drag_icon = NULL;
+ container->details->drag_state = DRAG_STATE_INITIAL;
+}
+
+static gboolean
+start_stretching (CajaIconContainer *container)
+{
+ CajaIconContainerDetails *details;
+ CajaIcon *icon;
+ EelDPoint world_point;
+ GtkWidget *toplevel;
+ GtkCornerType corner;
+ GdkCursor *cursor;
+
+ details = container->details;
+ icon = details->stretch_icon;
+
+ /* Check if we hit the stretch handles. */
+ world_point.x = details->drag_x;
+ world_point.y = details->drag_y;
+ if (!caja_icon_canvas_item_hit_test_stretch_handles (icon->item, world_point, &corner))
+ {
+ return FALSE;
+ }
+
+ switch (corner)
+ {
+ case GTK_CORNER_TOP_LEFT:
+ cursor = gdk_cursor_new (GDK_TOP_LEFT_CORNER);
+ break;
+ case GTK_CORNER_BOTTOM_LEFT:
+ cursor = gdk_cursor_new (GDK_BOTTOM_LEFT_CORNER);
+ break;
+ case GTK_CORNER_TOP_RIGHT:
+ cursor = gdk_cursor_new (GDK_TOP_RIGHT_CORNER);
+ break;
+ case GTK_CORNER_BOTTOM_RIGHT:
+ cursor = gdk_cursor_new (GDK_BOTTOM_RIGHT_CORNER);
+ break;
+ default:
+ cursor = NULL;
+ break;
+ }
+ /* Set up the dragging. */
+ details->drag_state = DRAG_STATE_STRETCH;
+ eel_canvas_w2c (EEL_CANVAS (container),
+ details->drag_x,
+ details->drag_y,
+ &details->stretch_start.pointer_x,
+ &details->stretch_start.pointer_y);
+ eel_canvas_w2c (EEL_CANVAS (container),
+ icon->x, icon->y,
+ &details->stretch_start.icon_x,
+ &details->stretch_start.icon_y);
+ icon_get_size (container, icon,
+ &details->stretch_start.icon_size);
+
+ eel_canvas_item_grab (EEL_CANVAS_ITEM (icon->item),
+ (GDK_POINTER_MOTION_MASK
+ | GDK_BUTTON_RELEASE_MASK),
+ cursor,
+ GDK_CURRENT_TIME);
+ if (cursor)
+ gdk_cursor_unref (cursor);
+
+ /* Ensure the window itself is focused.. */
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (container));
+ if (toplevel != NULL && gtk_widget_get_realized (toplevel))
+ {
+ eel_gdk_window_focus (gtk_widget_get_window (toplevel), GDK_CURRENT_TIME);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+update_stretch_at_idle (CajaIconContainer *container)
+{
+ CajaIconContainerDetails *details;
+ CajaIcon *icon;
+ double world_x, world_y;
+ StretchState stretch_state;
+
+ details = container->details;
+ icon = details->stretch_icon;
+
+ if (icon == NULL)
+ {
+ container->details->stretch_idle_id = 0;
+ return FALSE;
+ }
+
+ eel_canvas_w2c (EEL_CANVAS (container),
+ details->world_x, details->world_y,
+ &stretch_state.pointer_x, &stretch_state.pointer_y);
+
+ compute_stretch (&details->stretch_start,
+ &stretch_state);
+
+ eel_canvas_c2w (EEL_CANVAS (container),
+ stretch_state.icon_x, stretch_state.icon_y,
+ &world_x, &world_y);
+
+ icon_set_position (icon, world_x, world_y);
+ icon_set_size (container, icon, stretch_state.icon_size, FALSE, FALSE);
+
+ container->details->stretch_idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+continue_stretching (CajaIconContainer *container,
+ double world_x, double world_y)
+{
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ container->details->world_x = world_x;
+ container->details->world_y = world_y;
+
+ if (container->details->stretch_idle_id == 0)
+ {
+ container->details->stretch_idle_id = g_idle_add ((GtkFunction) update_stretch_at_idle, container);
+ }
+}
+
+static gboolean
+keyboard_stretching (CajaIconContainer *container,
+ GdkEventKey *event)
+{
+ CajaIcon *icon;
+ guint size;
+
+ icon = container->details->stretch_icon;
+
+ if (icon == NULL || !icon->is_selected)
+ {
+ return FALSE;
+ }
+
+ icon_get_size (container, icon, &size);
+
+ switch (event->keyval)
+ {
+ case GDK_equal:
+ case GDK_plus:
+ case GDK_KP_Add:
+ icon_set_size (container, icon, size + 5, FALSE, FALSE);
+ break;
+ case GDK_minus:
+ case GDK_KP_Subtract:
+ icon_set_size (container, icon, size - 5, FALSE, FALSE);
+ break;
+ case GDK_0:
+ case GDK_KP_0:
+ caja_icon_container_move_icon (container, icon,
+ icon->x, icon->y,
+ 1.0,
+ FALSE, TRUE, TRUE);
+ break;
+ }
+
+ return TRUE;
+}
+
+static void
+ungrab_stretch_icon (CajaIconContainer *container)
+{
+ eel_canvas_item_ungrab (EEL_CANVAS_ITEM (container->details->stretch_icon->item),
+ GDK_CURRENT_TIME);
+}
+
+static void
+end_stretching (CajaIconContainer *container,
+ double world_x, double world_y)
+{
+ CajaIconPosition position;
+ CajaIcon *icon;
+
+ continue_stretching (container, world_x, world_y);
+ ungrab_stretch_icon (container);
+
+ /* now that we're done stretching, update the icon's position */
+
+ icon = container->details->drag_icon;
+ if (caja_icon_container_is_layout_rtl (container))
+ {
+ position.x = icon->saved_ltr_x = get_mirror_x_position (container, icon, icon->x);
+ }
+ else
+ {
+ position.x = icon->x;
+ }
+ position.y = icon->y;
+ position.scale = icon->scale;
+ g_signal_emit (container,
+ signals[ICON_POSITION_CHANGED], 0,
+ icon->data, &position);
+
+ clear_drag_state (container);
+ redo_layout (container);
+}
+
+static gboolean
+undo_stretching (CajaIconContainer *container)
+{
+ CajaIcon *stretched_icon;
+
+ stretched_icon = container->details->stretch_icon;
+
+ if (stretched_icon == NULL)
+ {
+ return FALSE;
+ }
+
+ if (container->details->drag_state == DRAG_STATE_STRETCH)
+ {
+ ungrab_stretch_icon (container);
+ clear_drag_state (container);
+ }
+ caja_icon_canvas_item_set_show_stretch_handles
+ (stretched_icon->item, FALSE);
+
+ icon_set_position (stretched_icon,
+ container->details->stretch_initial_x,
+ container->details->stretch_initial_y);
+ icon_set_size (container,
+ stretched_icon,
+ container->details->stretch_initial_size,
+ TRUE,
+ TRUE);
+
+ container->details->stretch_icon = NULL;
+ emit_stretch_ended (container, stretched_icon);
+ redo_layout (container);
+
+ return TRUE;
+}
+
+static gboolean
+button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ CajaIconContainer *container;
+ CajaIconContainerDetails *details;
+ double world_x, world_y;
+
+ container = CAJA_ICON_CONTAINER (widget);
+ details = container->details;
+
+ if (event->button == RUBBERBAND_BUTTON && details->rubberband_info.active)
+ {
+ stop_rubberbanding (container, event->time);
+ 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)
+ {
+ caja_icon_container_did_not_drag (container, event);
+ }
+ else
+ {
+ caja_icon_dnd_end_drag (container);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "end drag from icon container");
+ }
+ break;
+ case DRAG_STATE_STRETCH:
+ eel_canvas_window_to_world
+ (EEL_CANVAS (container), event->x, event->y, &world_x, &world_y);
+ end_stretching (container, world_x, world_y);
+ break;
+ default:
+ break;
+ }
+
+ clear_drag_state (container);
+ return TRUE;
+ }
+
+ return GTK_WIDGET_CLASS (caja_icon_container_parent_class)->button_release_event (widget, event);
+}
+
+static int
+motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ CajaIconContainer *container;
+ CajaIconContainerDetails *details;
+ double world_x, world_y;
+ int canvas_x, canvas_y;
+ GdkDragAction actions;
+
+ container = CAJA_ICON_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;
+
+ end_renaming_mode (container, TRUE);
+
+ eel_canvas_w2c (EEL_CANVAS (container),
+ details->drag_x,
+ details->drag_y,
+ &canvas_x,
+ &canvas_y);
+
+ actions = GDK_ACTION_COPY
+ | GDK_ACTION_LINK
+ | GDK_ACTION_ASK;
+
+ if (container->details->drag_allow_moves)
+ {
+ actions |= GDK_ACTION_MOVE;
+ }
+
+ caja_icon_dnd_begin_drag (container,
+ actions,
+ details->drag_button,
+ event,
+ canvas_x,
+ canvas_y);
+ caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
+ "begin drag from icon container");
+ }
+ break;
+ case DRAG_STATE_STRETCH:
+ eel_canvas_window_to_world
+ (EEL_CANVAS (container), event->x, event->y, &world_x, &world_y);
+ continue_stretching (container, world_x, world_y);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return GTK_WIDGET_CLASS (caja_icon_container_parent_class)->motion_notify_event (widget, event);
+}
+
+static void
+caja_icon_container_search_position_func (CajaIconContainer *container,
+ GtkWidget *search_dialog)
+{
+ gint x, y;
+ gint cont_x, cont_y;
+ gint cont_width, cont_height;
+ GdkWindow *cont_window;
+ GdkScreen *screen;
+ GtkRequisition requisition;
+ gint monitor_num;
+ GdkRectangle monitor;
+
+
+ cont_window = gtk_widget_get_window (GTK_WIDGET (container));
+ screen = gdk_drawable_get_screen (cont_window);
+
+ monitor_num = gdk_screen_get_monitor_at_window (screen, cont_window);
+ gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+ gtk_widget_realize (search_dialog);
+
+ gdk_window_get_origin (cont_window, &cont_x, &cont_y);
+
+#if GTK_CHECK_VERSION(3, 0, 0)
+ cont_width = gdk_window_get_width(GDK_WINDOW(cont_window));
+ cont_height = gdk_window_get_height(GDK_WINDOW(cont_window));
+#else
+ gdk_drawable_get_size(cont_window, &cont_width, &cont_height);
+#endif
+
+ gtk_widget_size_request (search_dialog, &requisition);
+
+ if (cont_x + cont_width - requisition.width > gdk_screen_get_width (screen))
+ {
+ x = gdk_screen_get_width (screen) - requisition.width;
+ }
+ else if (cont_x + cont_width - requisition.width < 0)
+ {
+ x = 0;
+ }
+ else
+ {
+ x = cont_x + cont_width - requisition.width;
+ }
+
+ if (cont_y + cont_height > gdk_screen_get_height (screen))
+ {
+ y = gdk_screen_get_height (screen) - requisition.height;
+ }
+ else if (cont_y + cont_height < 0) /* isn't really possible ... */
+ {
+ y = 0;
+ }
+ else
+ {
+ y = cont_y + cont_height;
+ }
+
+ gtk_window_move (GTK_WINDOW (search_dialog), x, y);
+}
+
+static gboolean
+caja_icon_container_real_search_enable_popdown (gpointer data)
+{
+ CajaIconContainer *container = (CajaIconContainer *)data;
+
+ container->details->disable_popdown = FALSE;
+
+ g_object_unref (container);
+
+ return FALSE;
+}
+
+static void
+caja_icon_container_search_enable_popdown (GtkWidget *widget,
+ gpointer data)
+{
+ CajaIconContainer *container = (CajaIconContainer *) data;
+
+ g_object_ref (container);
+ g_timeout_add (200, caja_icon_container_real_search_enable_popdown, data);
+}
+
+static void
+caja_icon_container_search_disable_popdown (GtkEntry *entry,
+ GtkMenu *menu,
+ gpointer data)
+{
+ CajaIconContainer *container = (CajaIconContainer *) data;
+
+ container->details->disable_popdown = TRUE;
+ g_signal_connect (menu, "hide",
+ G_CALLBACK (caja_icon_container_search_enable_popdown),
+ data);
+}
+
+/* Cut and paste from gtkwindow.c */
+static void
+send_focus_change (GtkWidget *widget, gboolean in)
+{
+ GdkEvent *fevent;
+
+ fevent = gdk_event_new (GDK_FOCUS_CHANGE);
+
+ g_object_ref (widget);
+ ((GdkEventFocus *) fevent)->in = in;
+
+ gtk_widget_send_focus_change (widget, fevent);
+
+ fevent->focus_change.type = GDK_FOCUS_CHANGE;
+ fevent->focus_change.window = g_object_ref (gtk_widget_get_window (widget));
+ fevent->focus_change.in = in;
+
+ gtk_widget_event (widget, fevent);
+
+ g_object_notify (G_OBJECT (widget), "has-focus");
+
+ g_object_unref (widget);
+ gdk_event_free (fevent);
+}
+
+static void
+caja_icon_container_search_dialog_hide (GtkWidget *search_dialog,
+ CajaIconContainer *container)
+{
+ if (container->details->disable_popdown)
+ {
+ return;
+ }
+
+ if (container->details->search_entry_changed_id)
+ {
+ g_signal_handler_disconnect (container->details->search_entry,
+ container->details->search_entry_changed_id);
+ container->details->search_entry_changed_id = 0;
+ }
+ if (container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout = 0;
+ }
+
+ /* send focus-in event */
+ send_focus_change (GTK_WIDGET (container->details->search_entry), FALSE);
+ gtk_widget_hide (search_dialog);
+ gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
+}
+
+static gboolean
+caja_icon_container_search_entry_flush_timeout (CajaIconContainer *container)
+{
+ caja_icon_container_search_dialog_hide (container->details->search_window, container);
+
+ return TRUE;
+}
+
+/* Because we're visible but offscreen, we just set a flag in the preedit
+ * callback.
+ */
+static void
+caja_icon_container_search_preedit_changed (GtkEntry *entry,
+ CajaIconContainer *container)
+{
+ container->details->imcontext_changed = 1;
+ if (container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout =
+ g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
+ container);
+ }
+}
+
+static void
+caja_icon_container_search_activate (GtkEntry *entry,
+ CajaIconContainer *container)
+{
+ caja_icon_container_search_dialog_hide (container->details->search_window,
+ container);
+
+ activate_selected_items (container);
+}
+
+static gboolean
+caja_icon_container_search_delete_event (GtkWidget *widget,
+ GdkEventAny *event,
+ CajaIconContainer *container)
+{
+ g_assert (GTK_IS_WIDGET (widget));
+
+ caja_icon_container_search_dialog_hide (widget, container);
+
+ return TRUE;
+}
+
+static gboolean
+caja_icon_container_search_button_press_event (GtkWidget *widget,
+ GdkEventButton *event,
+ CajaIconContainer *container)
+{
+ g_assert (GTK_IS_WIDGET (widget));
+
+ caja_icon_container_search_dialog_hide (widget, container);
+
+ if (event->window == gtk_layout_get_bin_window (GTK_LAYOUT (container)))
+ {
+ button_press_event (GTK_WIDGET (container), event);
+ }
+
+ return TRUE;
+}
+
+static void
+caja_icon_container_get_icon_text (CajaIconContainer *container,
+ CajaIconData *data,
+ char **editable_text,
+ char **additional_text,
+ gboolean include_invisible)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_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
+caja_icon_container_search_iter (CajaIconContainer *container,
+ const char *key, gint n)
+{
+ GList *p;
+ CajaIcon *icon;
+ char *name;
+ int count;
+ char *normalized_key, *case_normalized_key;
+ char *normalized_name, *case_normalized_name;
+
+ g_assert (key != NULL);
+ g_assert (n >= 1);
+
+ normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
+ if (!normalized_key)
+ {
+ return FALSE;
+ }
+ case_normalized_key = g_utf8_casefold (normalized_key, -1);
+ g_free (normalized_key);
+ if (!case_normalized_key)
+ {
+ return FALSE;
+ }
+
+ icon = NULL;
+ name = NULL;
+ count = 0;
+ for (p = container->details->icons; p != NULL && count != n; p = p->next)
+ {
+ icon = p->data;
+ caja_icon_container_get_icon_text (container, icon->data, &name,
+ NULL, TRUE);
+
+ /* This can happen if a key event is handled really early while
+ * loading the icon container, before the items have all been
+ * updated once.
+ */
+ if (!name)
+ {
+ continue;
+ }
+
+ normalized_name = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
+ if (!normalized_name)
+ {
+ continue;
+ }
+ case_normalized_name = g_utf8_casefold (normalized_name, -1);
+ g_free (normalized_name);
+ if (!case_normalized_name)
+ {
+ continue;
+ }
+
+ if (strncmp (case_normalized_key, case_normalized_name,
+ strlen (case_normalized_key)) == 0)
+ {
+ count++;
+ }
+
+ g_free (case_normalized_name);
+ g_free (name);
+ name = NULL;
+ }
+
+ g_free (case_normalized_key);
+
+ if (count == n)
+ {
+ if (select_one_unselect_others (container, icon))
+ {
+ g_signal_emit (container, signals[SELECTION_CHANGED], 0);
+ }
+ schedule_keyboard_icon_reveal (container, icon);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+caja_icon_container_search_move (GtkWidget *window,
+ CajaIconContainer *container,
+ gboolean up)
+{
+ gboolean ret;
+ gint len;
+ gint count = 0;
+ const gchar *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (container->details->search_entry));
+
+ g_assert (text != NULL);
+
+ if (container->details->selected_iter == 0)
+ {
+ return;
+ }
+
+ if (up && container->details->selected_iter == 1)
+ {
+ return;
+ }
+
+ len = strlen (text);
+
+ if (len < 1)
+ {
+ return;
+ }
+
+ /* search */
+ unselect_all (container);
+
+ ret = caja_icon_container_search_iter (container, text,
+ up?((container->details->selected_iter) - 1):((container->details->selected_iter + 1)));
+
+ if (ret)
+ {
+ /* found */
+ container->details->selected_iter += up?(-1):(1);
+ }
+ else
+ {
+ /* return to old iter */
+ count = 0;
+ caja_icon_container_search_iter (container, text,
+ container->details->selected_iter);
+ }
+}
+
+static gboolean
+caja_icon_container_search_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event,
+ CajaIconContainer *container)
+{
+ gboolean retval = FALSE;
+
+ if (event->direction == GDK_SCROLL_UP)
+ {
+ caja_icon_container_search_move (widget, container, TRUE);
+ retval = TRUE;
+ }
+ else if (event->direction == GDK_SCROLL_DOWN)
+ {
+ caja_icon_container_search_move (widget, container, FALSE);
+ retval = TRUE;
+ }
+
+ /* renew the flush timeout */
+ if (retval && container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout =
+ g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
+ container);
+ }
+
+ return retval;
+}
+
+static gboolean
+caja_icon_container_search_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ CajaIconContainer *container)
+{
+ gboolean retval = FALSE;
+
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+
+ /* close window and cancel the search */
+ if (event->keyval == GDK_Escape || event->keyval == GDK_Tab)
+ {
+ caja_icon_container_search_dialog_hide (widget, container);
+ return TRUE;
+ }
+
+ /* close window and activate alternate */
+ if (event->keyval == GDK_Return && event->state & GDK_SHIFT_MASK)
+ {
+ caja_icon_container_search_dialog_hide (widget,
+ container);
+
+ activate_selected_items_alternate (container, NULL);
+ return TRUE;
+ }
+
+ /* select previous matching iter */
+ if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
+ {
+ caja_icon_container_search_move (widget, container, TRUE);
+ retval = TRUE;
+ }
+
+ if (((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
+ && (event->keyval == GDK_g || event->keyval == GDK_G))
+ {
+ caja_icon_container_search_move (widget, container, TRUE);
+ retval = TRUE;
+ }
+
+ /* select next matching iter */
+ if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
+ {
+ caja_icon_container_search_move (widget, container, FALSE);
+ retval = TRUE;
+ }
+
+ if (((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == GDK_CONTROL_MASK)
+ && (event->keyval == GDK_g || event->keyval == GDK_G))
+ {
+ caja_icon_container_search_move (widget, container, FALSE);
+ retval = TRUE;
+ }
+
+ /* renew the flush timeout */
+ if (retval && container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout =
+ g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
+ container);
+ }
+
+ return retval;
+}
+
+static void
+caja_icon_container_search_init (GtkWidget *entry,
+ CajaIconContainer *container)
+{
+ gint ret;
+ gint len;
+ const gchar *text;
+
+ g_assert (GTK_IS_ENTRY (entry));
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ len = strlen (text);
+
+ /* search */
+ unselect_all (container);
+ if (container->details->typeselect_flush_timeout)
+ {
+ g_source_remove (container->details->typeselect_flush_timeout);
+ container->details->typeselect_flush_timeout =
+ g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
+ container);
+ }
+
+ if (len < 1)
+ {
+ return;
+ }
+
+ ret = caja_icon_container_search_iter (container, text, 1);
+
+ if (ret)
+ {
+ container->details->selected_iter = 1;
+ }
+}
+
+static void
+caja_icon_container_ensure_interactive_directory (CajaIconContainer *container)
+{
+ GtkWidget *frame, *vbox, *toplevel;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (container));
+
+ if (container->details->search_window != NULL)
+ {
+ return;
+ }
+
+ container->details->search_window = gtk_window_new (GTK_WINDOW_POPUP);
+
+ gtk_window_set_modal (GTK_WINDOW (container->details->search_window), TRUE);
+ gtk_window_set_type_hint (GTK_WINDOW (container->details->search_window),
+ GDK_WINDOW_TYPE_HINT_COMBO);
+
+ g_signal_connect (container->details->search_window, "delete_event",
+ G_CALLBACK (caja_icon_container_search_delete_event),
+ container);
+ g_signal_connect (container->details->search_window, "key_press_event",
+ G_CALLBACK (caja_icon_container_search_key_press_event),
+ container);
+ g_signal_connect (container->details->search_window, "button_press_event",
+ G_CALLBACK (caja_icon_container_search_button_press_event),
+ container);
+ g_signal_connect (container->details->search_window, "scroll_event",
+ G_CALLBACK (caja_icon_container_search_scroll_event),
+ container);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
+ gtk_widget_show (frame);
+ gtk_container_add (GTK_CONTAINER (container->details->search_window), frame);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
+
+ /* add entry */
+ container->details->search_entry = gtk_entry_new ();
+ gtk_widget_show (container->details->search_entry);
+ g_signal_connect (container->details->search_entry, "populate_popup",
+ G_CALLBACK (caja_icon_container_search_disable_popdown),
+ container);
+ g_signal_connect (container->details->search_entry, "activate",
+ G_CALLBACK (caja_icon_container_search_activate),
+ container);
+ g_signal_connect (container->details->search_entry,
+ "preedit-changed",
+ G_CALLBACK (caja_icon_container_search_preedit_changed),
+ container);
+ gtk_container_add (GTK_CONTAINER (vbox), container->details->search_entry);
+
+ gtk_widget_realize (container->details->search_entry);
+}
+
+/* Pops up the interactive search entry. If keybinding is TRUE then the user
+ * started this by typing the start_interactive_search keybinding. Otherwise, it came from
+ */
+static gboolean
+caja_icon_container_real_start_interactive_search (CajaIconContainer *container,
+ gboolean keybinding)
+{
+ /* We only start interactive search if we have focus. If one of our
+ * children have focus, we don't want to start the search.
+ */
+ GtkWidgetClass *entry_parent_class;
+
+ if (container->details->search_window != NULL &&
+ gtk_widget_get_visible (container->details->search_window))
+ {
+ return TRUE;
+ }
+
+ if (!gtk_widget_has_focus (GTK_WIDGET (container)))
+ {
+ return FALSE;
+ }
+
+ caja_icon_container_ensure_interactive_directory (container);
+
+ if (keybinding)
+ {
+ gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
+ }
+
+ /* done, show it */
+ caja_icon_container_search_position_func (container, container->details->search_window);
+ gtk_widget_show (container->details->search_window);
+ if (container->details->search_entry_changed_id == 0)
+ {
+ container->details->search_entry_changed_id =
+ g_signal_connect (container->details->search_entry, "changed",
+ G_CALLBACK (caja_icon_container_search_init),
+ container);
+ }
+
+ container->details->typeselect_flush_timeout =
+ g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
+ (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
+ container);
+
+ /* Grab focus will select all the text. We don't want that to happen, so we
+ * call the parent instance and bypass the selection change. This is probably
+ * really non-kosher. */
+ entry_parent_class = g_type_class_peek_parent (GTK_ENTRY_GET_CLASS (container->details->search_entry));
+ (entry_parent_class->grab_focus) (container->details->search_entry);
+
+ /* send focus-in event */
+ send_focus_change (container->details->search_entry, TRUE);
+
+ /* search first matching iter */
+ caja_icon_container_search_init (container->details->search_entry, container);
+
+ return TRUE;
+}
+
+static gboolean
+caja_icon_container_start_interactive_search (CajaIconContainer *container)
+{
+ return caja_icon_container_real_start_interactive_search (container, TRUE);
+}
+
+static gboolean
+handle_popups (CajaIconContainer *container,
+ GdkEventKey *event,
+ const char *signal)
+{
+ GdkEventButton button_event = { 0 };
+
+ g_signal_emit_by_name (container, signal, &button_event);
+
+ return TRUE;
+}
+
+static int
+key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ CajaIconContainer *container;
+ gboolean handled;
+
+ container = CAJA_ICON_CONTAINER (widget);
+ handled = FALSE;
+
+ if (is_renaming (container) || is_renaming_pending (container))
+ {
+ switch (event->keyval)
+ {
+ case GDK_Return:
+ case GDK_KP_Enter:
+ end_renaming_mode (container, TRUE);
+ handled = TRUE;
+ break;
+ case GDK_Escape:
+ end_renaming_mode (container, FALSE);
+ handled = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ switch (event->keyval)
+ {
+ case GDK_Home:
+ case GDK_KP_Home:
+ keyboard_home (container, event);
+ handled = TRUE;
+ break;
+ case GDK_End:
+ case GDK_KP_End:
+ keyboard_end (container, event);
+ handled = TRUE;
+ break;
+ case GDK_Left:
+ case GDK_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_Up:
+ case GDK_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_Right:
+ case GDK_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_Down:
+ case GDK_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_space:
+ keyboard_space (container, event);
+ handled = TRUE;
+ break;
+#ifndef TAB_NAVIGATION_DISABLED
+ case GDK_Tab:
+ case GDK_ISO_Left_Tab:
+ select_previous_or_next_icon (container,
+ (event->state & GDK_SHIFT_MASK) == 0, event);
+ handled = TRUE;
+ break;
+#endif
+ case GDK_Return:
+ case GDK_KP_Enter:
+ if ((event->state & GDK_SHIFT_MASK) != 0)
+ {
+ activate_selected_items_alternate (container, NULL);
+ }
+ else
+ {
+ activate_selected_items (container);
+ }
+
+ handled = TRUE;
+ break;
+ case GDK_Escape:
+ handled = undo_stretching (container);
+ break;
+ case GDK_plus:
+ case GDK_minus:
+ case GDK_equal:
+ case GDK_KP_Add:
+ case GDK_KP_Subtract:
+ case GDK_0:
+ case GDK_KP_0:
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ handled = keyboard_stretching (container, event);
+ }
+ break;
+ case GDK_F10:
+ /* handle Ctrl+F10 because we want to display the
+ * background popup even if something is selected.
+ * The other cases are handled by popup_menu().
+ */
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ handled = handle_popups (container, event,
+ "context_click_background");
+ }
+ break;
+ case GDK_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 (caja_icon_container_parent_class)->key_press_event (widget, event);
+ }
+
+ /* We pass the event to the search_entry. If its text changes, then we
+ * start the typeahead find capabilities.
+ * Copied from CajaIconContainer */
+ if (!handled &&
+ event->keyval != GDK_slash /* don't steal slash key event, used for "go to" */ &&
+ event->keyval != GDK_BackSpace &&
+ event->keyval != GDK_Delete)
+ {
+ GdkEvent *new_event;
+ GdkWindow *window;
+ char *old_text;
+ const char *new_text;
+ gboolean retval;
+ GdkScreen *screen;
+ gboolean text_modified;
+ gulong popup_menu_id;
+
+ caja_icon_container_ensure_interactive_directory (container);
+
+ /* Make a copy of the current text */
+ old_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (container->details->search_entry)));
+ new_event = gdk_event_copy ((GdkEvent *) event);
+ window = ((GdkEventKey *) new_event)->window;
+ ((GdkEventKey *) new_event)->window = gtk_widget_get_window (container->details->search_entry);
+ gtk_widget_realize (container->details->search_window);
+
+ popup_menu_id = g_signal_connect (container->details->search_entry,
+ "popup_menu", G_CALLBACK (gtk_true), NULL);
+
+ /* Move the entry off screen */
+ screen = gtk_widget_get_screen (GTK_WIDGET (container));
+ gtk_window_move (GTK_WINDOW (container->details->search_window),
+ gdk_screen_get_width (screen) + 1,
+ gdk_screen_get_height (screen) + 1);
+ gtk_widget_show (container->details->search_window);
+
+ /* Send the event to the window. If the preedit_changed signal is emitted
+ * during this event, we will set priv->imcontext_changed */
+ container->details->imcontext_changed = FALSE;
+ retval = gtk_widget_event (container->details->search_entry, new_event);
+ gtk_widget_hide (container->details->search_window);
+
+ g_signal_handler_disconnect (container->details->search_entry,
+ popup_menu_id);
+
+ /* We check to make sure that the entry tried to handle the text, and that
+ * the text has changed. */
+ new_text = gtk_entry_get_text (GTK_ENTRY (container->details->search_entry));
+ text_modified = strcmp (old_text, new_text) != 0;
+ g_free (old_text);
+ if (container->details->imcontext_changed || /* we're in a preedit */
+ (retval && text_modified)) /* ...or the text was modified */
+ {
+ if (caja_icon_container_real_start_interactive_search (container, FALSE))
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (container));
+ return TRUE;
+ }
+ else
+ {
+ gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
+ return FALSE;
+ }
+ }
+
+ ((GdkEventKey *) new_event)->window = window;
+ gdk_event_free (new_event);
+ }
+
+ return handled;
+}
+
+static gboolean
+popup_menu (GtkWidget *widget)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ if (has_selection (container))
+ {
+ handle_popups (container, NULL,
+ "context_click_selection");
+ }
+ else
+ {
+ handle_popups (container, NULL,
+ "context_click_background");
+ }
+
+ return TRUE;
+}
+
+static void
+draw_canvas_background (EelCanvas *canvas,
+ int x, int y, int width, int height)
+{
+ /* Don't chain up to the parent to avoid clearing and redrawing */
+}
+
+
+static gboolean
+expose_event (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ /* g_warning ("Expose Icon Container %p '%d,%d: %d,%d'",
+ widget,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height); */
+
+ return GTK_WIDGET_CLASS (caja_icon_container_parent_class)->expose_event (widget, event);
+}
+
+static AtkObject *
+get_accessible (GtkWidget *widget)
+{
+ AtkObject *accessible;
+
+ if ((accessible = eel_accessibility_get_atk_object (widget)))
+ {
+ return accessible;
+ }
+
+ accessible = g_object_new
+ (caja_icon_container_accessible_get_type (), NULL);
+
+ return eel_accessibility_set_atk_object_return (widget, accessible);
+}
+
+static void
+grab_notify_cb (GtkWidget *widget,
+ gboolean was_grabbed)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_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,
+ GDK_CURRENT_TIME);
+ }
+}
+
+static void
+text_ellipsis_limit_changed_container_callback (gpointer callback_data)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_CONTAINER (callback_data);
+ invalidate_label_sizes (container);
+ schedule_redo_layout (container);
+}
+
+static GObject*
+caja_icon_container_constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ CajaIconContainer *container;
+ GObject *object;
+
+ object = G_OBJECT_CLASS (caja_icon_container_parent_class)->constructor
+ (type,
+ n_construct_params,
+ construct_params);
+
+ container = CAJA_ICON_CONTAINER (object);
+ if (caja_icon_container_get_is_desktop (container))
+ {
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT,
+ text_ellipsis_limit_changed_container_callback,
+ container, G_OBJECT (container));
+ }
+ else
+ {
+ eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT,
+ text_ellipsis_limit_changed_container_callback,
+ container, G_OBJECT (container));
+ }
+
+ return object;
+}
+
+/* Initialization. */
+
+static void
+caja_icon_container_class_init (CajaIconContainerClass *class)
+{
+ GtkWidgetClass *widget_class;
+ EelCanvasClass *canvas_class;
+ GtkBindingSet *binding_set;
+
+ G_OBJECT_CLASS (class)->constructor = caja_icon_container_constructor;
+ G_OBJECT_CLASS (class)->finalize = finalize;
+ GTK_OBJECT_CLASS (class)->destroy = destroy;
+
+ /* Signals. */
+
+ signals[SELECTION_CHANGED]
+ = g_signal_new ("selection_changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ button_press),
+ NULL, NULL,
+ caja_marshal_BOOLEAN__POINTER,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ activate_alternate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ middle_click),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[ICON_POSITION_CHANGED]
+ = g_signal_new ("icon_position_changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ icon_position_changed),
+ NULL, NULL,
+ caja_marshal_VOID__POINTER_POINTER,
+ G_TYPE_NONE, 2,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+ signals[ICON_TEXT_CHANGED]
+ = g_signal_new ("icon_text_changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ icon_text_changed),
+ NULL, NULL,
+ caja_marshal_VOID__POINTER_STRING,
+ G_TYPE_NONE, 2,
+ G_TYPE_POINTER,
+ G_TYPE_STRING);
+ signals[ICON_STRETCH_STARTED]
+ = g_signal_new ("icon_stretch_started",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ icon_stretch_started),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[ICON_STRETCH_ENDED]
+ = g_signal_new ("icon_stretch_ended",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ icon_stretch_ended),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+ signals[RENAMING_ICON]
+ = g_signal_new ("renaming_icon",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ renaming_icon),
+ 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 (CajaIconContainerClass,
+ get_icon_uri),
+ NULL, NULL,
+ eel_marshal_STRING__POINTER,
+ 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 (CajaIconContainerClass,
+ get_icon_drop_target_uri),
+ NULL, NULL,
+ eel_marshal_STRING__POINTER,
+ 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 (CajaIconContainerClass,
+ move_copy_items),
+ NULL, NULL,
+ caja_marshal_VOID__POINTER_POINTER_POINTER_ENUM_INT_INT,
+ G_TYPE_NONE, 6,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ signals[HANDLE_NETSCAPE_URL]
+ = g_signal_new ("handle_netscape_url",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ handle_netscape_url),
+ NULL, NULL,
+ caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
+ G_TYPE_NONE, 5,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ signals[HANDLE_URI_LIST]
+ = g_signal_new ("handle_uri_list",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ handle_uri_list),
+ NULL, NULL,
+ caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
+ G_TYPE_NONE, 5,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ signals[HANDLE_TEXT]
+ = g_signal_new ("handle_text",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ handle_text),
+ NULL, NULL,
+ caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
+ G_TYPE_NONE, 5,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ signals[HANDLE_RAW]
+ = g_signal_new ("handle_raw",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ handle_raw),
+ NULL, NULL,
+ caja_marshal_VOID__POINTER_INT_STRING_STRING_ENUM_INT_INT,
+ G_TYPE_NONE, 7,
+ G_TYPE_POINTER,
+ G_TYPE_INT,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_DRAG_ACTION,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ signals[GET_CONTAINER_URI]
+ = g_signal_new ("get_container_uri",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ get_container_uri),
+ NULL, NULL,
+ eel_marshal_STRING__VOID,
+ G_TYPE_STRING, 0);
+ signals[CAN_ACCEPT_ITEM]
+ = g_signal_new ("can_accept_item",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ can_accept_item),
+ NULL, NULL,
+ eel_marshal_INT__POINTER_STRING,
+ G_TYPE_INT, 2,
+ G_TYPE_POINTER,
+ G_TYPE_STRING);
+ signals[GET_STORED_ICON_POSITION]
+ = g_signal_new ("get_stored_icon_position",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ get_stored_icon_position),
+ NULL, NULL,
+ eel_marshal_BOOLEAN__POINTER_POINTER,
+ G_TYPE_BOOLEAN, 2,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+ signals[GET_STORED_LAYOUT_TIMESTAMP]
+ = g_signal_new ("get_stored_layout_timestamp",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ get_stored_layout_timestamp),
+ NULL, NULL,
+ eel_marshal_BOOLEAN__POINTER_POINTER,
+ G_TYPE_BOOLEAN, 2,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+ signals[STORE_LAYOUT_TIMESTAMP]
+ = g_signal_new ("store_layout_timestamp",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ store_layout_timestamp),
+ NULL, NULL,
+ eel_marshal_BOOLEAN__POINTER_POINTER,
+ G_TYPE_BOOLEAN, 2,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+ signals[LAYOUT_CHANGED]
+ = g_signal_new ("layout_changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ layout_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[PREVIEW]
+ = g_signal_new ("preview",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ preview),
+ NULL, NULL,
+ caja_marshal_INT__POINTER_BOOLEAN,
+ G_TYPE_INT, 2,
+ G_TYPE_POINTER,
+ G_TYPE_BOOLEAN);
+ signals[BAND_SELECT_STARTED]
+ = g_signal_new ("band_select_started",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ 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 (CajaIconContainerClass,
+ cleared),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[START_INTERACTIVE_SEARCH]
+ = g_signal_new ("start_interactive_search",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (CajaIconContainerClass,
+ start_interactive_search),
+ NULL, NULL,
+ caja_marshal_BOOLEAN__VOID,
+ G_TYPE_BOOLEAN, 0);
+
+ /* GtkWidget class. */
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->size_request = size_request;
+ widget_class->size_allocate = size_allocate;
+ widget_class->realize = realize;
+ widget_class->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->popup_menu = popup_menu;
+ widget_class->get_accessible = get_accessible;
+ widget_class->style_set = style_set;
+ widget_class->expose_event = expose_event;
+ widget_class->grab_notify = grab_notify_cb;
+
+ canvas_class = EEL_CANVAS_CLASS (class);
+ canvas_class->draw_background = draw_canvas_background;
+
+ class->start_interactive_search = caja_icon_container_start_interactive_search;
+
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boolean ("frame_text",
+ "Frame Text",
+ "Draw a frame around unselected text",
+ FALSE,
+ G_PARAM_READABLE));
+
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boxed ("selection_box_color",
+ "Selection Box Color",
+ "Color of the selection box",
+ GDK_TYPE_COLOR,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uchar ("selection_box_alpha",
+ "Selection Box Alpha",
+ "Opacity of the selection box",
+ 0, 0xff,
+ DEFAULT_SELECTION_BOX_ALPHA,
+ G_PARAM_READABLE));
+
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uchar ("highlight_alpha",
+ "Highlight Alpha",
+ "Opacity of the highlight for selected icons",
+ 0, 0xff,
+ DEFAULT_HIGHLIGHT_ALPHA,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uchar ("normal_alpha",
+ "Normal Alpha",
+ "Opacity of the normal icons if frame_text is set",
+ 0, 0xff,
+ DEFAULT_NORMAL_ALPHA,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uchar ("prelight_alpha",
+ "Prelight Alpha",
+ "Opacity of the prelight icons if frame_text is set",
+ 0, 0xff,
+ DEFAULT_PRELIGHT_ALPHA,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boxed ("light_info_color",
+ "Light Info Color",
+ "Color used for information text against a dark background",
+ GDK_TYPE_COLOR,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boxed ("dark_info_color",
+ "Dark Info Color",
+ "Color used for information text against a light background",
+ GDK_TYPE_COLOR,
+ G_PARAM_READABLE));
+
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("normal_icon_render_mode",
+ "Normal Icon Render Mode",
+ "Mode of normal icons being rendered (0=normal, 1=spotlight, 2=colorize, 3=colorize-monochromely)",
+ 0, 3,
+ DEFAULT_NORMAL_ICON_RENDER_MODE,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("prelight_icon_render_mode",
+ "Prelight Icon Render Mode",
+ "Mode of prelight icons being rendered (0=normal, 1=spotlight, 2=colorize, 3=colorize-monochromely)",
+ 0, 3,
+ DEFAULT_PRELIGHT_ICON_RENDER_MODE,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boxed ("normal_icon_color",
+ "Icon Normal Color",
+ "Color used for colorizing icons in normal state (default base[NORMAL])",
+ GDK_TYPE_COLOR,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boxed ("prelight_icon_color",
+ "Icon Prelight Color",
+ "Color used for colorizing prelighted icons (default base[PRELIGHT])",
+ GDK_TYPE_COLOR,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("normal_icon_saturation",
+ "Normal Icon Saturation",
+ "Saturation of icons in normal state",
+ 0, 255,
+ DEFAULT_NORMAL_ICON_SATURATION,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("prelight_icon_saturation",
+ "Prelight Icon Saturation",
+ "Saturation of icons in prelight state",
+ 0, 255,
+ DEFAULT_PRELIGHT_ICON_SATURATION,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("normal_icon_brightness",
+ "Normal Icon Brightness",
+ "Brightness of icons in normal state",
+ 0, 255,
+ DEFAULT_NORMAL_ICON_BRIGHTNESS,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("prelight_icon_brightness",
+ "Prelight Icon Brightness",
+ "Brightness of icons in prelight state",
+ 0, 255,
+ DEFAULT_PRELIGHT_ICON_BRIGHTNESS,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("normal_icon_lighten",
+ "Normal Icon Lighten",
+ "Lighten icons in normal state",
+ 0, 255,
+ DEFAULT_NORMAL_ICON_LIGHTEN,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_uint ("prelight_icon_lighten",
+ "Prelight Icon Lighten",
+ "Lighten icons in prelight state",
+ 0, 255,
+ DEFAULT_PRELIGHT_ICON_LIGHTEN,
+ G_PARAM_READABLE));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boolean ("activate_prelight_icon_label",
+ "Activate Prelight Icon Label",
+ "Whether icon labels should make use of its prelight color in prelight state",
+ FALSE,
+ G_PARAM_READABLE));
+
+
+ binding_set = gtk_binding_set_by_class (class);
+
+ gtk_binding_entry_add_signal (binding_set, GDK_f, GDK_CONTROL_MASK, "start_interactive_search", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_F, GDK_CONTROL_MASK, "start_interactive_search", 0);
+}
+
+static void
+update_selected (CajaIconContainer *container)
+{
+ GList *node;
+ CajaIcon *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 gboolean
+handle_focus_in_event (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
+{
+ update_selected (CAJA_ICON_CONTAINER (widget));
+
+ return FALSE;
+}
+
+static gboolean
+handle_focus_out_event (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
+{
+ /* End renaming and commit change. */
+ end_renaming_mode (CAJA_ICON_CONTAINER (widget), TRUE);
+ update_selected (CAJA_ICON_CONTAINER (widget));
+
+ return FALSE;
+}
+
+
+static int text_ellipsis_limits[CAJA_ZOOM_LEVEL_N_ENTRIES];
+static int desktop_text_ellipsis_limit;
+
+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)
+ {
+ for (p = strs; *p != NULL; p++)
+ {
+ if (sscanf (*p, str, limit))
+ {
+ success = TRUE;
+ }
+ }
+ }
+
+ g_free (str);
+
+ return success;
+}
+
+static void
+text_ellipsis_limit_changed_callback (gpointer callback_data)
+{
+ char **pref;
+ unsigned int i;
+ int one_limit;
+ const EelEnumeration *eenum;
+ const EelEnumerationEntry *entry;
+
+ pref = eel_preferences_get_string_array (CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT);
+
+ /* set default */
+ get_text_ellipsis_limit_for_zoom (pref, NULL, &one_limit);
+ for (i = 0; i < CAJA_ZOOM_LEVEL_N_ENTRIES; i++)
+ {
+ text_ellipsis_limits[i] = one_limit;
+ }
+
+ /* override for each zoom level */
+ eenum = eel_enumeration_lookup ("default_zoom_level");
+ g_assert (eenum != NULL);
+ for (i = 0; i < eel_enumeration_get_length (eenum); i++)
+ {
+ entry = eel_enumeration_get_nth_entry (eenum, i);
+ if (get_text_ellipsis_limit_for_zoom (pref, entry->name, &one_limit))
+ {
+ text_ellipsis_limits[entry->value] = one_limit;
+ }
+ }
+
+ g_strfreev (pref);
+}
+
+static void
+desktop_text_ellipsis_limit_changed_callback (gpointer callback_data)
+{
+ int pref;
+
+ pref = eel_preferences_get_integer (CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT);
+ desktop_text_ellipsis_limit = pref;
+}
+
+static void
+caja_icon_container_init (CajaIconContainer *container)
+{
+ CajaIconContainerDetails *details;
+ EelBackground *background;
+ static gboolean setup_prefs = FALSE;
+
+ details = g_new0 (CajaIconContainerDetails, 1);
+
+ details->icon_set = g_hash_table_new (g_direct_hash, g_direct_equal);
+ details->layout_timestamp = UNDEFINED_TIME;
+
+ details->zoom_level = CAJA_ZOOM_LEVEL_STANDARD;
+
+ details->font_size_table[CAJA_ZOOM_LEVEL_SMALLEST] = -2 * PANGO_SCALE;
+ details->font_size_table[CAJA_ZOOM_LEVEL_SMALLER] = -2 * PANGO_SCALE;
+ details->font_size_table[CAJA_ZOOM_LEVEL_SMALL] = -0 * PANGO_SCALE;
+ details->font_size_table[CAJA_ZOOM_LEVEL_STANDARD] = 0 * PANGO_SCALE;
+ details->font_size_table[CAJA_ZOOM_LEVEL_LARGE] = 0 * PANGO_SCALE;
+ details->font_size_table[CAJA_ZOOM_LEVEL_LARGER] = 0 * PANGO_SCALE;
+ details->font_size_table[CAJA_ZOOM_LEVEL_LARGEST] = 0 * PANGO_SCALE;
+
+ container->details = details;
+
+ /* when the background changes, we must set up the label text color */
+ background = eel_get_widget_background (GTK_WIDGET (container));
+
+ g_signal_connect_object (background, "appearance_changed",
+ G_CALLBACK (update_label_color), container, 0);
+
+ g_signal_connect (container, "focus-in-event",
+ G_CALLBACK (handle_focus_in_event), NULL);
+ g_signal_connect (container, "focus-out-event",
+ G_CALLBACK (handle_focus_out_event), NULL);
+
+ eel_background_set_use_base (background, TRUE);
+
+ /* read in theme-dependent data */
+ caja_icon_container_theme_changed (container);
+ eel_preferences_add_callback (CAJA_PREFERENCES_THEME,
+ caja_icon_container_theme_changed,
+ container);
+
+ if (!setup_prefs)
+ {
+ eel_preferences_add_callback (CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT,
+ text_ellipsis_limit_changed_callback,
+ NULL);
+ text_ellipsis_limit_changed_callback (NULL);
+
+ eel_preferences_add_callback (CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT,
+ desktop_text_ellipsis_limit_changed_callback,
+ NULL);
+ desktop_text_ellipsis_limit_changed_callback (NULL);
+
+ setup_prefs = TRUE;
+ }
+}
+
+typedef struct
+{
+ CajaIconContainer *container;
+ GdkEventButton *event;
+} ContextMenuParameters;
+
+static gboolean
+handle_icon_double_click (CajaIconContainer *container,
+ CajaIcon *icon,
+ GdkEventButton *event)
+{
+ CajaIconContainerDetails *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])
+ {
+ if (!button_event_modifies_selection (event))
+ {
+ activate_selected_items (container);
+ return TRUE;
+ }
+ else if ((event->state & GDK_CONTROL_MASK) == 0 &&
+ (event->state & GDK_SHIFT_MASK) != 0)
+ {
+ activate_selected_items_alternate (container, icon);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* CajaIcon 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_icon_button_press (CajaIconContainer *container,
+ CajaIcon *icon,
+ GdkEventButton *event)
+{
+ CajaIconContainerDetails *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_icon_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;
+
+ /* Check to see if this is a click on the stretch handles.
+ * If so, it won't modify the selection.
+ */
+ if (icon == container->details->stretch_icon)
+ {
+ if (start_stretching (container))
+ {
+ return TRUE;
+ }
+ }
+ }
+
+ /* 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)
+ {
+ CajaIcon *start_icon;
+
+ 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)
+ {
+ 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)
+ {
+ g_signal_emit (container,
+ signals[CONTEXT_CLICK_SELECTION], 0,
+ event);
+ }
+
+
+ return TRUE;
+}
+
+static int
+item_event_callback (EelCanvasItem *item,
+ GdkEvent *event,
+ gpointer data)
+{
+ CajaIconContainer *container;
+ CajaIconContainerDetails *details;
+ CajaIcon *icon;
+
+ container = CAJA_ICON_CONTAINER (data);
+ details = container->details;
+
+ icon = CAJA_ICON_CANVAS_ITEM (item)->user_data;
+ g_assert (icon != NULL);
+
+ switch (event->type)
+ {
+ case GDK_BUTTON_PRESS:
+ if (handle_icon_button_press (container, icon, &event->button))
+ {
+ /* Stop the event from being passed along further. Returning
+ * TRUE ain't enough.
+ */
+ return TRUE;
+ }
+ return FALSE;
+ default:
+ return FALSE;
+ }
+}
+
+GtkWidget *
+caja_icon_container_new (void)
+{
+ return gtk_widget_new (CAJA_TYPE_ICON_CONTAINER, NULL);
+}
+
+/* Clear all of the icons in the container. */
+void
+caja_icon_container_clear (CajaIconContainer *container)
+{
+ CajaIconContainerDetails *details;
+ CajaIcon *icon;
+ GList *p;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ details = container->details;
+ details->layout_timestamp = UNDEFINED_TIME;
+ details->store_layout_timestamps_when_finishing_new_icons = FALSE;
+
+ if (details->icons == NULL)
+ {
+ return;
+ }
+
+ end_renaming_mode (container, TRUE);
+
+ clear_keyboard_focus (container);
+ clear_keyboard_rubberband_start (container);
+ unschedule_keyboard_icon_reveal (container);
+ set_pending_icon_to_reveal (container, NULL);
+ details->stretch_icon = NULL;
+ details->drop_target = NULL;
+
+ for (p = details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ if (icon->is_monitored)
+ {
+ caja_icon_container_stop_monitor_top_left (container,
+ icon->data,
+ icon);
+ }
+ icon_free (p->data);
+ }
+ g_list_free (details->icons);
+ details->icons = NULL;
+ g_list_free (details->new_icons);
+ details->new_icons = NULL;
+
+ g_hash_table_destroy (details->icon_set);
+ details->icon_set = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ caja_icon_container_update_scroll_region (container);
+}
+
+gboolean
+caja_icon_container_is_empty (CajaIconContainer *container)
+{
+ return container->details->icons == NULL;
+}
+
+CajaIconData *
+caja_icon_container_get_first_visible_icon (CajaIconContainer *container)
+{
+ GList *l;
+ CajaIcon *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_layout_get_hadjustment (GTK_LAYOUT (container)));
+ vadj_v = gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (container)));
+ h_page_size = gtk_adjustment_get_page_size (gtk_layout_get_hadjustment (GTK_LAYOUT (container)));
+
+ if (caja_icon_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;
+ if (caja_icon_container_is_layout_vertical (container))
+ {
+ pos = &x1;
+ if (caja_icon_container_is_layout_rtl (container))
+ {
+ compare_lt = TRUE;
+ better_icon = x1 < x + ICON_PAD_LEFT;
+ }
+ else
+ {
+ better_icon = x2 > x + ICON_PAD_LEFT;
+ }
+ }
+ else
+ {
+ 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;
+}
+
+/* puts the icon at the top of the screen */
+void
+caja_icon_container_scroll_to_icon (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ GList *l;
+ CajaIcon *icon;
+ GtkAdjustment *hadj, *vadj;
+ EelCanvasItem *item;
+ EelIRect bounds;
+ GtkAllocation allocation;
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (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 */
+ caja_icon_container_layout_now (container);
+
+ l = container->details->icons;
+ while (l != NULL)
+ {
+ icon = l->data;
+
+ if (icon->data == data &&
+ icon_is_positioned (icon))
+ {
+
+ item = EEL_CANVAS_ITEM (icon->item);
+
+ if (caja_icon_container_is_auto_layout (container))
+ {
+ /* ensure that we reveal the entire row/column */
+ icon_get_row_and_column_bounds (container, icon, &bounds, TRUE);
+ }
+ else
+ {
+ item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), &bounds, TRUE);
+ }
+
+ if (caja_icon_container_is_layout_vertical (container))
+ {
+ if (caja_icon_container_is_layout_rtl (container))
+ {
+ eel_gtk_adjustment_set_value (hadj, bounds.x1 - allocation.width);
+ }
+ else
+ {
+ eel_gtk_adjustment_set_value (hadj, bounds.x0);
+ }
+ }
+ else
+ {
+ eel_gtk_adjustment_set_value (vadj, bounds.y0);
+ }
+ }
+
+ l = l->next;
+ }
+}
+
+/* Call a function for all the icons. */
+typedef struct
+{
+ CajaIconCallback callback;
+ gpointer callback_data;
+} CallbackAndData;
+
+static void
+call_icon_callback (gpointer data, gpointer callback_data)
+{
+ CajaIcon *icon;
+ CallbackAndData *callback_and_data;
+
+ icon = data;
+ callback_and_data = callback_data;
+ (* callback_and_data->callback) (icon->data, callback_and_data->callback_data);
+}
+
+void
+caja_icon_container_for_each (CajaIconContainer *container,
+ CajaIconCallback callback,
+ gpointer callback_data)
+{
+ CallbackAndData callback_and_data;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ callback_and_data.callback = callback;
+ callback_and_data.callback_data = callback_data;
+
+ g_list_foreach (container->details->icons,
+ call_icon_callback, &callback_and_data);
+}
+
+static int
+selection_changed_at_idle_callback (gpointer data)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_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 (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ CajaIconContainerDetails *details;
+ gboolean was_selected;
+ CajaIcon *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);
+ g_hash_table_remove (details->icon_set, icon->data);
+
+ was_selected = icon->is_selected;
+
+ if (details->keyboard_focus == icon ||
+ details->keyboard_focus == NULL)
+ {
+ if (icon_to_focus != NULL)
+ {
+ set_keyboard_focus (container, icon_to_focus);
+ }
+ else
+ {
+ clear_keyboard_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);
+ }
+ if (details->stretch_icon == icon)
+ {
+ details->stretch_icon = NULL;
+ }
+
+ if (icon->is_monitored)
+ {
+ caja_icon_container_stop_monitor_top_left (container,
+ icon->data,
+ icon);
+ }
+ 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 (CajaIconContainer *container)
+{
+ GList *selection;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ selection = caja_icon_container_get_selection (container);
+ if (selection != NULL)
+ {
+ g_signal_emit (container,
+ signals[ACTIVATE], 0,
+ selection);
+ }
+ g_list_free (selection);
+}
+
+static void
+activate_selected_items_alternate (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ GList *selection;
+
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+
+ if (icon != NULL)
+ {
+ selection = g_list_prepend (NULL, icon->data);
+ }
+ else
+ {
+ selection = caja_icon_container_get_selection (container);
+ }
+ if (selection != NULL)
+ {
+ g_signal_emit (container,
+ signals[ACTIVATE_ALTERNATE], 0,
+ selection);
+ }
+ g_list_free (selection);
+}
+
+static CajaIcon *
+get_icon_being_renamed (CajaIconContainer *container)
+{
+ CajaIcon *rename_icon;
+
+ if (!is_renaming (container))
+ {
+ return NULL;
+ }
+
+ g_assert (!has_multiple_selection (container));
+
+ rename_icon = get_first_selected_icon (container);
+ g_assert (rename_icon != NULL);
+
+ return rename_icon;
+}
+
+static CajaIconInfo *
+caja_icon_container_get_icon_images (CajaIconContainer *container,
+ CajaIconData *data,
+ int size,
+ GList **emblem_pixbufs,
+ char **embedded_text,
+ gboolean for_drag_accept,
+ gboolean need_large_embeddded_text,
+ gboolean *embedded_text_needs_loading,
+ gboolean *has_open_window)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_assert (klass->get_icon_images != NULL);
+
+ return klass->get_icon_images (container, data, size, emblem_pixbufs, embedded_text, for_drag_accept, need_large_embeddded_text, embedded_text_needs_loading, has_open_window);
+}
+
+static void
+caja_icon_container_freeze_updates (CajaIconContainer *container)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_assert (klass->freeze_updates != NULL);
+
+ klass->freeze_updates (container);
+}
+
+static void
+caja_icon_container_unfreeze_updates (CajaIconContainer *container)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_assert (klass->unfreeze_updates != NULL);
+
+ klass->unfreeze_updates (container);
+}
+
+static void
+caja_icon_container_start_monitor_top_left (CajaIconContainer *container,
+ CajaIconData *data,
+ gconstpointer client,
+ gboolean large_text)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_assert (klass->start_monitor_top_left != NULL);
+
+ klass->start_monitor_top_left (container, data, client, large_text);
+}
+
+static void
+caja_icon_container_stop_monitor_top_left (CajaIconContainer *container,
+ CajaIconData *data,
+ gconstpointer client)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_return_if_fail (klass->stop_monitor_top_left != NULL);
+
+ klass->stop_monitor_top_left (container, data, client);
+}
+
+
+static void
+caja_icon_container_prioritize_thumbnailing (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+ g_assert (klass->prioritize_thumbnailing != NULL);
+
+ klass->prioritize_thumbnailing (container, icon->data);
+}
+
+static void
+caja_icon_container_update_visible_icons (CajaIconContainer *container)
+{
+ GtkAdjustment *vadj, *hadj;
+ double min_y, max_y;
+ double min_x, max_x;
+ double x0, y0, x1, y1;
+ GList *node;
+ CajaIcon *icon;
+ gboolean visible;
+ GtkAllocation allocation;
+
+ hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
+ vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (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);
+
+ if (caja_icon_container_is_layout_vertical (container))
+ {
+ visible = x1 >= min_x && x0 <= max_x;
+ }
+ else
+ {
+ visible = y1 >= min_y && y0 <= max_y;
+ }
+
+ if (visible)
+ {
+ caja_icon_canvas_item_set_is_visible (icon->item, TRUE);
+ caja_icon_container_prioritize_thumbnailing (container,
+ icon);
+ }
+ else
+ {
+ caja_icon_canvas_item_set_is_visible (icon->item, FALSE);
+ }
+ }
+ }
+}
+
+static void
+handle_vadjustment_changed (GtkAdjustment *adjustment,
+ CajaIconContainer *container)
+{
+ if (!caja_icon_container_is_layout_vertical (container))
+ {
+ caja_icon_container_update_visible_icons (container);
+ }
+}
+
+static void
+handle_hadjustment_changed (GtkAdjustment *adjustment,
+ CajaIconContainer *container)
+{
+ if (caja_icon_container_is_layout_vertical (container))
+ {
+ caja_icon_container_update_visible_icons (container);
+ }
+}
+
+
+void
+caja_icon_container_update_icon (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ CajaIconContainerDetails *details;
+ guint icon_size;
+ guint min_image_size, max_image_size;
+ CajaIconInfo *icon_info;
+ GdkPoint *attach_points;
+ int n_attach_points;
+ gboolean has_embedded_text_rect;
+ GdkPixbuf *pixbuf;
+ GList *emblem_pixbufs;
+ char *editable_text, *additional_text;
+ char *embedded_text;
+ GdkRectangle embedded_text_rect;
+ gboolean large_embedded_text;
+ gboolean embedded_text_needs_loading;
+ gboolean has_open_window;
+
+ 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, CAJA_ICON_MAXIMUM_SIZE);
+
+ /* Get the appropriate images for the file. */
+ if (container->details->forced_icon_size > 0)
+ {
+ icon_size = container->details->forced_icon_size;
+ }
+ else
+ {
+ icon_get_size (container, icon, &icon_size);
+ }
+
+
+ icon_size = MAX (icon_size, min_image_size);
+ icon_size = MIN (icon_size, max_image_size);
+
+ /* Get the icons. */
+ emblem_pixbufs = NULL;
+ embedded_text = NULL;
+ large_embedded_text = icon_size > ICON_SIZE_FOR_LARGE_EMBEDDED_TEXT;
+ icon_info = caja_icon_container_get_icon_images (container, icon->data, icon_size,
+ &emblem_pixbufs,
+ &embedded_text,
+ icon == details->drop_target,
+ large_embedded_text, &embedded_text_needs_loading,
+ &has_open_window);
+
+
+ if (container->details->forced_icon_size > 0)
+ pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, icon_size);
+ else
+ pixbuf = caja_icon_info_get_pixbuf (icon_info);
+ caja_icon_info_get_attach_points (icon_info, &attach_points, &n_attach_points);
+ has_embedded_text_rect = caja_icon_info_get_embedded_rect (icon_info,
+ &embedded_text_rect);
+
+ if (has_embedded_text_rect && embedded_text_needs_loading)
+ {
+ icon->is_monitored = TRUE;
+ caja_icon_container_start_monitor_top_left (container, icon->data, icon, large_embedded_text);
+ }
+
+ caja_icon_container_get_icon_text (container,
+ icon->data,
+ &editable_text,
+ &additional_text,
+ FALSE);
+
+ /* If name of icon being renamed was changed from elsewhere, end renaming mode.
+ * Alternatively, we could replace the characters in the editable text widget
+ * with the new name, but that could cause timing problems if the user just
+ * happened to be typing at that moment.
+ */
+ if (icon == get_icon_being_renamed (container) &&
+ eel_strcmp (editable_text,
+ caja_icon_canvas_item_get_editable_text (icon->item)) != 0)
+ {
+ end_renaming_mode (container, 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);
+
+ caja_icon_canvas_item_set_image (icon->item, pixbuf);
+ caja_icon_canvas_item_set_attach_points (icon->item, attach_points, n_attach_points);
+ caja_icon_canvas_item_set_emblems (icon->item, emblem_pixbufs);
+ caja_icon_canvas_item_set_embedded_text_rect (icon->item, &embedded_text_rect);
+ caja_icon_canvas_item_set_embedded_text (icon->item, embedded_text);
+
+ /* Let the pixbufs go. */
+ g_object_unref (pixbuf);
+ eel_gdk_pixbuf_list_free (emblem_pixbufs);
+
+ g_free (editable_text);
+ g_free (additional_text);
+
+ g_object_unref (icon_info);
+}
+
+static gboolean
+assign_icon_position (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ gboolean have_stored_position;
+ CajaIconPosition position;
+
+ /* Get the stored position. */
+ have_stored_position = FALSE;
+ position.scale = 1.0;
+ g_signal_emit (container,
+ signals[GET_STORED_ICON_POSITION], 0,
+ icon->data,
+ &position,
+ &have_stored_position);
+ icon->scale = position.scale;
+ if (!container->details->auto_layout)
+ {
+ if (have_stored_position)
+ {
+ icon_set_position (icon, position.x, position.y);
+ icon->saved_ltr_x = icon->x;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void
+finish_adding_icon (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ caja_icon_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 void
+finish_adding_new_icons (CajaIconContainer *container)
+{
+ GList *p, *new_icons, *no_position_icons, *semi_position_icons;
+ CajaIcon *icon;
+ double bottom;
+
+ new_icons = container->details->new_icons;
+ container->details->new_icons = NULL;
+
+ /* Position most icons (not unpositioned manual-layout icons). */
+ new_icons = g_list_reverse (new_icons);
+ no_position_icons = semi_position_icons = NULL;
+ for (p = new_icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ if (icon->has_lazy_position)
+ {
+ assign_icon_position (container, icon);
+ semi_position_icons = g_list_prepend (semi_position_icons, icon);
+ }
+ else if (!assign_icon_position (container, icon))
+ {
+ no_position_icons = g_list_prepend (no_position_icons, icon);
+ }
+
+ finish_adding_icon (container, icon);
+ }
+ g_list_free (new_icons);
+
+ if (semi_position_icons != NULL)
+ {
+ PlacementGrid *grid;
+ time_t now;
+ gboolean dummy;
+
+ g_assert (!container->details->auto_layout);
+
+ semi_position_icons = g_list_reverse (semi_position_icons);
+
+ /* This is currently only used on the desktop.
+ * Thus, we pass FALSE for tight, like lay_down_icons_tblr */
+ grid = placement_grid_new (container, FALSE);
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ if (icon_is_positioned (icon) && !icon->has_lazy_position)
+ {
+ placement_grid_mark_icon (grid, icon);
+ }
+ }
+
+ now = time (NULL);
+
+ for (p = semi_position_icons; p != NULL; p = p->next)
+ {
+ CajaIcon *icon;
+ CajaIconPosition position;
+ int x, y;
+
+ icon = p->data;
+ x = icon->x;
+ y = icon->y;
+
+ find_empty_location (container, grid,
+ icon, x, y, &x, &y);
+
+ icon_set_position (icon, x, y);
+
+ position.x = icon->x;
+ position.y = icon->y;
+ position.scale = icon->scale;
+ placement_grid_mark_icon (grid, icon);
+ g_signal_emit (container, signals[ICON_POSITION_CHANGED], 0,
+ icon->data, &position);
+ g_signal_emit (container, signals[STORE_LAYOUT_TIMESTAMP], 0,
+ icon->data, &now, &dummy);
+
+ /* ensure that next time we run this code, the formerly semi-positioned
+ * icons are treated as being positioned. */
+ icon->has_lazy_position = FALSE;
+ }
+
+ placement_grid_free (grid);
+
+ g_list_free (semi_position_icons);
+ }
+
+ /* Position the unpositioned manual layout icons. */
+ if (no_position_icons != NULL)
+ {
+ g_assert (!container->details->auto_layout);
+
+ sort_icons (container, &no_position_icons);
+ if (caja_icon_container_get_is_desktop (container))
+ {
+ lay_down_icons (container, no_position_icons, CONTAINER_PAD_TOP);
+ }
+ else
+ {
+ get_all_icon_bounds (container, NULL, NULL, NULL, &bottom, BOUNDS_USAGE_FOR_LAYOUT);
+ lay_down_icons (container, no_position_icons, bottom + ICON_PAD_BOTTOM);
+ }
+ g_list_free (no_position_icons);
+ }
+
+ if (container->details->store_layout_timestamps_when_finishing_new_icons)
+ {
+ store_layout_timestamps_now (container);
+ container->details->store_layout_timestamps_when_finishing_new_icons = FALSE;
+ }
+}
+
+static gboolean
+is_old_or_unknown_icon_data (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ time_t timestamp;
+ gboolean success;
+
+ if (container->details->layout_timestamp == UNDEFINED_TIME)
+ {
+ /* don't know */
+ return FALSE;
+ }
+
+ g_signal_emit (container,
+ signals[GET_STORED_LAYOUT_TIMESTAMP], 0,
+ data, &timestamp, &success);
+ return (!success || timestamp < container->details->layout_timestamp);
+}
+
+/**
+ * caja_icon_container_add:
+ * @container: A CajaIconContainer
+ * @data: Icon data.
+ *
+ * Add icon to represent @data to container.
+ * Returns FALSE if there was already such an icon.
+ **/
+gboolean
+caja_icon_container_add (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ CajaIconContainerDetails *details;
+ CajaIcon *icon;
+ EelCanvasItem *band, *item;
+
+ g_return_val_if_fail (CAJA_IS_ICON_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 (CajaIcon, 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->has_lazy_position = is_old_or_unknown_icon_data (container, data);
+ icon->scale = 1.0;
+ icon->item = CAJA_ICON_CANVAS_ITEM
+ (eel_canvas_item_new (EEL_CANVAS_GROUP (EEL_CANVAS (container)->root),
+ caja_icon_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 = CAJA_ICON_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);
+
+ /* Run an idle function to add the icons. */
+ schedule_redo_layout (container);
+
+ return TRUE;
+}
+
+void
+caja_icon_container_layout_now (CajaIconContainer *container)
+{
+ 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))));
+}
+
+/**
+ * caja_icon_container_remove:
+ * @container: A CajaIconContainer.
+ * @data: Icon data.
+ *
+ * Remove the icon with this data.
+ **/
+gboolean
+caja_icon_container_remove (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ CajaIcon *icon;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ end_renaming_mode (container, 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;
+}
+
+/**
+ * caja_icon_container_request_update:
+ * @container: A CajaIconContainer.
+ * @data: Icon data.
+ *
+ * Update the icon with this data.
+ **/
+void
+caja_icon_container_request_update (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ CajaIcon *icon;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+ g_return_if_fail (data != NULL);
+
+ icon = g_hash_table_lookup (container->details->icon_set, data);
+
+ if (icon != NULL)
+ {
+ caja_icon_container_update_icon (container, icon);
+ schedule_redo_layout (container);
+ }
+}
+
+/* zooming */
+
+CajaZoomLevel
+caja_icon_container_get_zoom_level (CajaIconContainer *container)
+{
+ return container->details->zoom_level;
+}
+
+void
+caja_icon_container_set_zoom_level (CajaIconContainer *container, int new_level)
+{
+ CajaIconContainerDetails *details;
+ int pinned_level;
+ double pixels_per_unit;
+
+ details = container->details;
+
+ end_renaming_mode (container, TRUE);
+
+ pinned_level = new_level;
+ if (pinned_level < CAJA_ZOOM_LEVEL_SMALLEST)
+ {
+ pinned_level = CAJA_ZOOM_LEVEL_SMALLEST;
+ }
+ else if (pinned_level > CAJA_ZOOM_LEVEL_LARGEST)
+ {
+ pinned_level = CAJA_ZOOM_LEVEL_LARGEST;
+ }
+
+ if (pinned_level == details->zoom_level)
+ {
+ return;
+ }
+
+ details->zoom_level = pinned_level;
+
+ pixels_per_unit = (double) caja_get_icon_size_for_zoom_level (pinned_level)
+ / CAJA_ICON_SIZE_STANDARD;
+ eel_canvas_set_pixels_per_unit (EEL_CANVAS (container), pixels_per_unit);
+
+ invalidate_labels (container);
+ caja_icon_container_request_update_all (container);
+}
+
+/**
+ * caja_icon_container_request_update_all:
+ * For each icon, synchronizes the displayed information (image, text) with the
+ * information from the model.
+ *
+ * @container: An icon container.
+ **/
+void
+caja_icon_container_request_update_all (CajaIconContainer *container)
+{
+ GList *node;
+ CajaIcon *icon;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ for (node = container->details->icons; node != NULL; node = node->next)
+ {
+ icon = node->data;
+ caja_icon_container_update_icon (container, icon);
+ }
+
+ redo_layout (container);
+}
+
+/**
+ * caja_icon_container_reveal:
+ * Change scroll position as necessary to reveal the specified item.
+ */
+void
+caja_icon_container_reveal (CajaIconContainer *container, CajaIconData *data)
+{
+ CajaIcon *icon;
+
+ g_return_if_fail (CAJA_IS_ICON_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);
+ }
+}
+
+/**
+ * caja_icon_container_get_selection:
+ * @container: An icon container.
+ *
+ * Get a list of the icons currently selected in @container.
+ *
+ * Return value: A GList of the programmer-specified data associated to each
+ * selected icon, or NULL if no icon is selected. The caller is expected to
+ * free the list when it is not needed anymore.
+ **/
+GList *
+caja_icon_container_get_selection (CajaIconContainer *container)
+{
+ GList *list, *p;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL);
+
+ list = NULL;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ CajaIcon *icon;
+
+ icon = p->data;
+ if (icon->is_selected)
+ {
+ list = g_list_prepend (list, icon->data);
+ }
+ }
+
+ return g_list_reverse (list);
+}
+
+static GList *
+caja_icon_container_get_selected_icons (CajaIconContainer *container)
+{
+ GList *list, *p;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL);
+
+ list = NULL;
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ CajaIcon *icon;
+
+ icon = p->data;
+ if (icon->is_selected)
+ {
+ list = g_list_prepend (list, icon);
+ }
+ }
+
+ return g_list_reverse (list);
+}
+
+/**
+ * caja_icon_container_invert_selection:
+ * @container: An icon container.
+ *
+ * Inverts the selection in @container.
+ *
+ **/
+void
+caja_icon_container_invert_selection (CajaIconContainer *container)
+{
+ GList *p;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ CajaIcon *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 *
+caja_icon_container_get_icon_locations (CajaIconContainer *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 =
+ ((CajaIcon *)node->data)->x;
+ g_array_index (result, GdkPoint, index).y =
+ ((CajaIcon *)node->data)->y;
+ }
+
+ return result;
+}
+
+/**
+ * caja_icon_container_get_selected_icon_locations:
+ * @container: An icon container widget.
+ *
+ * Returns an array of GdkPoints of locations of the selected icons.
+ **/
+GArray *
+caja_icon_container_get_selected_icon_locations (CajaIconContainer *container)
+{
+ GArray *result;
+ GList *icons;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL);
+
+ icons = caja_icon_container_get_selected_icons (container);
+ result = caja_icon_container_get_icon_locations (container, icons);
+ g_list_free (icons);
+
+ return result;
+}
+
+/**
+ * caja_icon_container_select_all:
+ * @container: An icon container widget.
+ *
+ * Select all the icons in @container at once.
+ **/
+void
+caja_icon_container_select_all (CajaIconContainer *container)
+{
+ gboolean selection_changed;
+ GList *p;
+ CajaIcon *icon;
+
+ g_return_if_fail (CAJA_IS_ICON_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);
+ }
+}
+
+/**
+ * caja_icon_container_set_selection:
+ * @container: An icon container widget.
+ * @selection: A list of CajaIconData *.
+ *
+ * Set the selection to exactly the icons in @container which have
+ * programmer data matching one of the items in @selection.
+ **/
+void
+caja_icon_container_set_selection (CajaIconContainer *container,
+ GList *selection)
+{
+ gboolean selection_changed;
+ GHashTable *hash;
+ GList *p;
+ CajaIcon *icon;
+
+ g_return_if_fail (CAJA_IS_ICON_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->data) != NULL);
+ }
+ g_hash_table_destroy (hash);
+
+ if (selection_changed)
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * caja_icon_container_select_list_unselect_others.
+ * @container: An icon container widget.
+ * @selection: A list of CajaIcon *.
+ *
+ * Set the selection to exactly the icons in @selection.
+ **/
+void
+caja_icon_container_select_list_unselect_others (CajaIconContainer *container,
+ GList *selection)
+{
+ gboolean selection_changed;
+ GHashTable *hash;
+ GList *p;
+ CajaIcon *icon;
+
+ g_return_if_fail (CAJA_IS_ICON_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);
+ }
+}
+
+/**
+ * caja_icon_container_unselect_all:
+ * @container: An icon container widget.
+ *
+ * Deselect all the icons in @container.
+ **/
+void
+caja_icon_container_unselect_all (CajaIconContainer *container)
+{
+ if (unselect_all (container))
+ {
+ g_signal_emit (container,
+ signals[SELECTION_CHANGED], 0);
+ }
+}
+
+/**
+ * caja_icon_container_get_icon_by_uri:
+ * @container: An icon container widget.
+ * @uri: The uri of an icon 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.
+ **/
+CajaIcon *
+caja_icon_container_get_icon_by_uri (CajaIconContainer *container,
+ const char *uri)
+{
+ CajaIconContainerDetails *details;
+ GList *p;
+
+ /* Eventually, we must avoid searching the entire icon list,
+ but it's OK for now.
+ A hash table mapping uri to icon is one possibility.
+ */
+
+ details = container->details;
+
+ for (p = details->icons; p != NULL; p = p->next)
+ {
+ CajaIcon *icon;
+ char *icon_uri;
+ gboolean is_match;
+
+ icon = p->data;
+
+ icon_uri = caja_icon_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 CajaIcon *
+get_nth_selected_icon (CajaIconContainer *container, int index)
+{
+ GList *p;
+ CajaIcon *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 CajaIcon *
+get_first_selected_icon (CajaIconContainer *container)
+{
+ return get_nth_selected_icon (container, 1);
+}
+
+static gboolean
+has_multiple_selection (CajaIconContainer *container)
+{
+ return get_nth_selected_icon (container, 2) != NULL;
+}
+
+static gboolean
+all_selected (CajaIconContainer *container)
+{
+ GList *p;
+ CajaIcon *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 (CajaIconContainer *container)
+{
+ return get_nth_selected_icon (container, 1) != NULL;
+}
+
+/**
+ * caja_icon_container_show_stretch_handles:
+ * @container: An icon container widget.
+ *
+ * Makes stretch handles visible on the first selected icon.
+ **/
+void
+caja_icon_container_show_stretch_handles (CajaIconContainer *container)
+{
+ CajaIconContainerDetails *details;
+ CajaIcon *icon;
+ guint initial_size;
+
+ icon = get_first_selected_icon (container);
+ if (icon == NULL)
+ {
+ return;
+ }
+
+ /* Check if it already has stretch handles. */
+ details = container->details;
+ if (details->stretch_icon == icon)
+ {
+ return;
+ }
+
+ /* Get rid of the existing stretch handles and put them on the new icon. */
+ if (details->stretch_icon != NULL)
+ {
+ caja_icon_canvas_item_set_show_stretch_handles
+ (details->stretch_icon->item, FALSE);
+ ungrab_stretch_icon (container);
+ emit_stretch_ended (container, details->stretch_icon);
+ }
+ caja_icon_canvas_item_set_show_stretch_handles (icon->item, TRUE);
+ details->stretch_icon = icon;
+
+ icon_get_size (container, icon, &initial_size);
+
+ /* only need to keep size in one dimension, since they are constrained to be the same */
+ container->details->stretch_initial_x = icon->x;
+ container->details->stretch_initial_y = icon->y;
+ container->details->stretch_initial_size = initial_size;
+
+ emit_stretch_started (container, icon);
+}
+
+/**
+ * caja_icon_container_has_stretch_handles
+ * @container: An icon container widget.
+ *
+ * Returns true if the first selected item has stretch handles.
+ **/
+gboolean
+caja_icon_container_has_stretch_handles (CajaIconContainer *container)
+{
+ CajaIcon *icon;
+
+ icon = get_first_selected_icon (container);
+ if (icon == NULL)
+ {
+ return FALSE;
+ }
+
+ return icon == container->details->stretch_icon;
+}
+
+/**
+ * caja_icon_container_is_stretched
+ * @container: An icon container widget.
+ *
+ * Returns true if the any selected item is stretched to a size other than 1.0.
+ **/
+gboolean
+caja_icon_container_is_stretched (CajaIconContainer *container)
+{
+ GList *p;
+ CajaIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ if (icon->is_selected && icon->scale != 1.0)
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * caja_icon_container_unstretch
+ * @container: An icon container widget.
+ *
+ * Gets rid of any icon stretching.
+ **/
+void
+caja_icon_container_unstretch (CajaIconContainer *container)
+{
+ GList *p;
+ CajaIcon *icon;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+ if (icon->is_selected)
+ {
+ caja_icon_container_move_icon (container, icon,
+ icon->x, icon->y,
+ 1.0,
+ FALSE, TRUE, TRUE);
+ }
+ }
+}
+
+static void
+compute_stretch (StretchState *start,
+ StretchState *current)
+{
+ gboolean right, bottom;
+ int x_stretch, y_stretch;
+
+ /* FIXME bugzilla.gnome.org 45390: This doesn't correspond to
+ * the way the handles are drawn.
+ */
+ /* Figure out which handle we are dragging. */
+ right = start->pointer_x > start->icon_x + (int) start->icon_size / 2;
+ bottom = start->pointer_y > start->icon_y + (int) start->icon_size / 2;
+
+ /* Figure out how big we should stretch. */
+ x_stretch = start->pointer_x - current->pointer_x;
+ y_stretch = start->pointer_y - current->pointer_y;
+ if (right)
+ {
+ x_stretch = - x_stretch;
+ }
+ if (bottom)
+ {
+ y_stretch = - y_stretch;
+ }
+ current->icon_size = MAX ((int) start->icon_size + MIN (x_stretch, y_stretch),
+ (int) CAJA_ICON_SIZE_SMALLEST);
+
+ /* Figure out where the corner of the icon should be. */
+ current->icon_x = start->icon_x;
+ if (!right)
+ {
+ current->icon_x += start->icon_size - current->icon_size;
+ }
+ current->icon_y = start->icon_y;
+ if (!bottom)
+ {
+ current->icon_y += start->icon_size - current->icon_size;
+ }
+}
+
+char *
+caja_icon_container_get_icon_uri (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ char *uri;
+
+ uri = NULL;
+ g_signal_emit (container,
+ signals[GET_ICON_URI], 0,
+ icon->data,
+ &uri);
+ return uri;
+}
+
+char *
+caja_icon_container_get_icon_drop_target_uri (CajaIconContainer *container,
+ CajaIcon *icon)
+{
+ char *uri;
+
+ uri = NULL;
+ g_signal_emit (container,
+ signals[GET_ICON_DROP_TARGET_URI], 0,
+ icon->data,
+ &uri);
+ return uri;
+}
+
+/* Call to reset the scroll region only if the container is not empty,
+ * to avoid having the flag linger until the next file is added.
+ */
+static void
+reset_scroll_region_if_not_empty (CajaIconContainer *container)
+{
+ if (!caja_icon_container_is_empty (container))
+ {
+ caja_icon_container_reset_scroll_region (container);
+ }
+}
+
+/* Switch from automatic layout to manual or vice versa.
+ * If we switch to manual layout, we restore the icon positions from the
+ * last manual layout.
+ */
+void
+caja_icon_container_set_auto_layout (CajaIconContainer *container,
+ gboolean auto_layout)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+ g_return_if_fail (auto_layout == FALSE || auto_layout == TRUE);
+
+ if (container->details->auto_layout == auto_layout)
+ {
+ return;
+ }
+
+ reset_scroll_region_if_not_empty (container);
+ container->details->auto_layout = auto_layout;
+
+ if (!auto_layout)
+ {
+ reload_icon_positions (container);
+ caja_icon_container_freeze_icon_positions (container);
+ }
+
+ redo_layout (container);
+
+ g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
+}
+
+
+/* Toggle the tighter layout boolean. */
+void
+caja_icon_container_set_tighter_layout (CajaIconContainer *container,
+ gboolean tighter_layout)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+ g_return_if_fail (tighter_layout == FALSE || tighter_layout == TRUE);
+
+ if (container->details->tighter_layout == tighter_layout)
+ {
+ return;
+ }
+
+ container->details->tighter_layout = tighter_layout;
+
+ if (container->details->auto_layout)
+ {
+ invalidate_label_sizes (container);
+ redo_layout (container);
+
+ g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
+ }
+ else
+ {
+ /* in manual layout, label sizes still change, even though
+ * the icons don't move.
+ */
+ invalidate_label_sizes (container);
+ caja_icon_container_request_update_all (container);
+ }
+}
+
+gboolean
+caja_icon_container_is_keep_aligned (CajaIconContainer *container)
+{
+ return container->details->keep_aligned;
+}
+
+static gboolean
+align_icons_callback (gpointer callback_data)
+{
+ CajaIconContainer *container;
+
+ container = CAJA_ICON_CONTAINER (callback_data);
+ align_icons (container);
+ container->details->align_idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+unschedule_align_icons (CajaIconContainer *container)
+{
+ if (container->details->align_idle_id != 0)
+ {
+ g_source_remove (container->details->align_idle_id);
+ container->details->align_idle_id = 0;
+ }
+}
+
+static void
+schedule_align_icons (CajaIconContainer *container)
+{
+ if (container->details->align_idle_id == 0
+ && container->details->has_been_allocated)
+ {
+ container->details->align_idle_id = g_idle_add
+ (align_icons_callback, container);
+ }
+}
+
+void
+caja_icon_container_set_keep_aligned (CajaIconContainer *container,
+ gboolean keep_aligned)
+{
+ if (container->details->keep_aligned != keep_aligned)
+ {
+ container->details->keep_aligned = keep_aligned;
+
+ if (keep_aligned && !container->details->auto_layout)
+ {
+ schedule_align_icons (container);
+ }
+ else
+ {
+ unschedule_align_icons (container);
+ }
+ }
+}
+
+void
+caja_icon_container_set_layout_mode (CajaIconContainer *container,
+ CajaIconLayoutMode mode)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ container->details->layout_mode = mode;
+ invalidate_labels (container);
+
+ redo_layout (container);
+
+ g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
+}
+
+void
+caja_icon_container_set_label_position (CajaIconContainer *container,
+ CajaIconLabelPosition position)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ if (container->details->label_position != position)
+ {
+ container->details->label_position = position;
+
+ invalidate_labels (container);
+ caja_icon_container_request_update_all (container);
+
+ schedule_redo_layout (container);
+ }
+}
+
+/* Switch from automatic to manual layout, freezing all the icons in their
+ * current positions instead of restoring icon positions from the last manual
+ * layout as set_auto_layout does.
+ */
+void
+caja_icon_container_freeze_icon_positions (CajaIconContainer *container)
+{
+ gboolean changed;
+ GList *p;
+ CajaIcon *icon;
+ CajaIconPosition position;
+
+ changed = container->details->auto_layout;
+ container->details->auto_layout = FALSE;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ position.x = icon->saved_ltr_x;
+ position.y = icon->y;
+ position.scale = icon->scale;
+ g_signal_emit (container, signals[ICON_POSITION_CHANGED], 0,
+ icon->data, &position);
+ }
+
+ if (changed)
+ {
+ g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
+ }
+}
+
+/* Re-sort, switching to automatic layout if it was in manual layout. */
+void
+caja_icon_container_sort (CajaIconContainer *container)
+{
+ gboolean changed;
+
+ changed = !container->details->auto_layout;
+ container->details->auto_layout = TRUE;
+
+ reset_scroll_region_if_not_empty (container);
+ redo_layout (container);
+
+ if (changed)
+ {
+ g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
+ }
+}
+
+gboolean
+caja_icon_container_is_auto_layout (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+
+ return container->details->auto_layout;
+}
+
+gboolean
+caja_icon_container_is_tighter_layout (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+
+ return container->details->tighter_layout;
+}
+
+static void
+pending_icon_to_rename_destroy_callback (CajaIconCanvasItem *item, CajaIconContainer *container)
+{
+ g_assert (container->details->pending_icon_to_rename != NULL);
+ g_assert (container->details->pending_icon_to_rename->item == item);
+ container->details->pending_icon_to_rename = NULL;
+}
+
+static CajaIcon*
+get_pending_icon_to_rename (CajaIconContainer *container)
+{
+ return container->details->pending_icon_to_rename;
+}
+
+static void
+set_pending_icon_to_rename (CajaIconContainer *container, CajaIcon *icon)
+{
+ CajaIcon *old_icon;
+
+ old_icon = container->details->pending_icon_to_rename;
+
+ if (icon == old_icon)
+ {
+ return;
+ }
+
+ if (old_icon != NULL)
+ {
+ g_signal_handlers_disconnect_by_func
+ (old_icon->item,
+ G_CALLBACK (pending_icon_to_rename_destroy_callback),
+ container);
+ }
+
+ if (icon != NULL)
+ {
+ g_signal_connect (icon->item, "destroy",
+ G_CALLBACK (pending_icon_to_rename_destroy_callback), container);
+ }
+
+ container->details->pending_icon_to_rename = icon;
+}
+
+static void
+process_pending_icon_to_rename (CajaIconContainer *container)
+{
+ CajaIcon *pending_icon_to_rename;
+
+ pending_icon_to_rename = get_pending_icon_to_rename (container);
+
+ if (pending_icon_to_rename != NULL)
+ {
+ if (pending_icon_to_rename->is_selected && !has_multiple_selection (container))
+ {
+ caja_icon_container_start_renaming_selected_item (container, FALSE);
+ }
+ else
+ {
+ set_pending_icon_to_rename (container, NULL);
+ }
+ }
+}
+
+static gboolean
+is_renaming_pending (CajaIconContainer *container)
+{
+ return get_pending_icon_to_rename (container) != NULL;
+}
+
+static gboolean
+is_renaming (CajaIconContainer *container)
+{
+ return container->details->renaming;
+}
+
+/**
+ * caja_icon_container_start_renaming_selected_item
+ * @container: An icon container widget.
+ * @select_all: Whether the whole file should initially be selected, or
+ * only its basename (i.e. everything except its extension).
+ *
+ * Displays the edit name widget on the first selected icon
+ **/
+void
+caja_icon_container_start_renaming_selected_item (CajaIconContainer *container,
+ gboolean select_all)
+{
+ CajaIconContainerDetails *details;
+ CajaIcon *icon;
+ EelDRect icon_rect;
+ EelDRect text_rect;
+ PangoContext *context;
+ PangoFontDescription *desc;
+ const char *editable_text;
+ int x, y, width;
+ int start_offset, end_offset;
+
+ /* Check if it already in renaming mode, if so - select all */
+ details = container->details;
+ if (details->renaming)
+ {
+ eel_editable_label_select_region (EEL_EDITABLE_LABEL (details->rename_widget),
+ 0,
+ -1);
+ return;
+ }
+
+ /* Find selected icon */
+ icon = get_first_selected_icon (container);
+ if (icon == NULL)
+ {
+ return;
+ }
+
+ g_assert (!has_multiple_selection (container));
+
+
+ if (!icon_is_positioned (icon))
+ {
+ set_pending_icon_to_rename (container, icon);
+ return;
+ }
+
+ set_pending_icon_to_rename (container, NULL);
+
+ /* Make a copy of the original editable text for a later compare */
+ editable_text = caja_icon_canvas_item_get_editable_text (icon->item);
+
+ /* This could conceivably be NULL if a rename was triggered really early. */
+ if (editable_text == NULL)
+ {
+ return;
+ }
+
+ details->original_text = g_strdup (editable_text);
+
+ /* Freeze updates so files added while renaming don't cause rename to loose focus, bug #318373 */
+ caja_icon_container_freeze_updates (container);
+
+ /* Create text renaming widget, if it hasn't been created already.
+ * We deal with the broken icon text item widget by keeping it around
+ * so its contents can still be cut and pasted as part of the clipboard
+ */
+ if (details->rename_widget == NULL)
+ {
+ details->rename_widget = eel_editable_label_new ("Test text");
+ eel_editable_label_set_line_wrap (EEL_EDITABLE_LABEL (details->rename_widget), TRUE);
+ eel_editable_label_set_line_wrap_mode (EEL_EDITABLE_LABEL (details->rename_widget), PANGO_WRAP_WORD_CHAR);
+ eel_editable_label_set_draw_outline (EEL_EDITABLE_LABEL (details->rename_widget), TRUE);
+
+ if (details->label_position != CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ eel_editable_label_set_justify (EEL_EDITABLE_LABEL (details->rename_widget), GTK_JUSTIFY_CENTER);
+ }
+
+ gtk_misc_set_padding (GTK_MISC (details->rename_widget), 1, 1);
+ gtk_layout_put (GTK_LAYOUT (container),
+ details->rename_widget, 0, 0);
+ }
+
+ /* Set the right font */
+ if (details->font)
+ {
+ desc = pango_font_description_from_string (details->font);
+ }
+ else
+ {
+ context = gtk_widget_get_pango_context (GTK_WIDGET (container));
+ desc = pango_font_description_copy (pango_context_get_font_description (context));
+ pango_font_description_set_size (desc,
+ pango_font_description_get_size (desc) +
+ container->details->font_size_table [container->details->zoom_level]);
+ }
+ eel_editable_label_set_font_description (EEL_EDITABLE_LABEL (details->rename_widget),
+ desc);
+ pango_font_description_free (desc);
+
+ icon_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
+ text_rect = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
+
+ if (caja_icon_container_is_layout_vertical (container) &&
+ container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ /* for one-line editables, the width changes dynamically */
+ width = -1;
+ }
+ else
+ {
+ width = caja_icon_canvas_item_get_max_text_width (icon->item);
+ }
+
+ if (details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
+ {
+ eel_canvas_w2c (EEL_CANVAS_ITEM (icon->item)->canvas,
+ text_rect.x0,
+ text_rect.y0,
+ &x, &y);
+ }
+ else
+ {
+ eel_canvas_w2c (EEL_CANVAS_ITEM (icon->item)->canvas,
+ (icon_rect.x0 + icon_rect.x1) / 2,
+ icon_rect.y1,
+ &x, &y);
+ x = x - width / 2 - 1;
+ }
+
+ gtk_layout_move (GTK_LAYOUT (container),
+ details->rename_widget,
+ x, y);
+
+ gtk_widget_set_size_request (details->rename_widget,
+ width, -1);
+ eel_editable_label_set_text (EEL_EDITABLE_LABEL (details->rename_widget),
+ editable_text);
+ if (select_all)
+ {
+ start_offset = 0;
+ end_offset = -1;
+ }
+ else
+ {
+ eel_filename_get_rename_region (editable_text, &start_offset, &end_offset);
+ }
+ eel_editable_label_select_region (EEL_EDITABLE_LABEL (details->rename_widget),
+ start_offset,
+ end_offset);
+ gtk_widget_show (details->rename_widget);
+
+ gtk_widget_grab_focus (details->rename_widget);
+
+ g_signal_emit (container,
+ signals[RENAMING_ICON], 0,
+ GTK_EDITABLE (details->rename_widget));
+
+ caja_icon_container_update_icon (container, icon);
+
+ /* We are in renaming mode */
+ details->renaming = TRUE;
+ caja_icon_canvas_item_set_renaming (icon->item, TRUE);
+}
+
+static void
+end_renaming_mode (CajaIconContainer *container, gboolean commit)
+{
+ CajaIcon *icon;
+ const char *changed_text;
+
+ set_pending_icon_to_rename (container, NULL);
+
+ icon = get_icon_being_renamed (container);
+ if (icon == NULL)
+ {
+ return;
+ }
+
+ /* We are not in renaming mode */
+ container->details->renaming = FALSE;
+ caja_icon_canvas_item_set_renaming (icon->item, FALSE);
+
+ caja_icon_container_unfreeze_updates (container);
+
+ if (commit)
+ {
+ set_pending_icon_to_reveal (container, icon);
+ }
+
+ gtk_widget_grab_focus (GTK_WIDGET (container));
+
+ if (commit)
+ {
+ /* Verify that text has been modified before signalling change. */
+ changed_text = eel_editable_label_get_text (EEL_EDITABLE_LABEL (container->details->rename_widget));
+ if (strcmp (container->details->original_text, changed_text) != 0)
+ {
+ g_signal_emit (container,
+ signals[ICON_TEXT_CHANGED], 0,
+ icon->data,
+ changed_text);
+ }
+ }
+
+ gtk_widget_hide (container->details->rename_widget);
+
+ g_free (container->details->original_text);
+
+}
+
+/* emit preview signal, called by the canvas item */
+gboolean
+caja_icon_container_emit_preview_signal (CajaIconContainer *icon_container,
+ CajaIcon *icon,
+ gboolean start_flag)
+{
+ gboolean result;
+
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (icon_container), FALSE);
+ g_return_val_if_fail (icon != NULL, FALSE);
+ g_return_val_if_fail (start_flag == FALSE || start_flag == TRUE, FALSE);
+
+ result = FALSE;
+ g_signal_emit (icon_container,
+ signals[PREVIEW], 0,
+ icon->data,
+ start_flag,
+ &result);
+
+ return result;
+}
+
+gboolean
+caja_icon_container_has_stored_icon_positions (CajaIconContainer *container)
+{
+ GList *p;
+ CajaIcon *icon;
+ gboolean have_stored_position;
+ CajaIconPosition position;
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ have_stored_position = FALSE;
+ g_signal_emit (container,
+ signals[GET_STORED_ICON_POSITION], 0,
+ icon->data,
+ &position,
+ &have_stored_position);
+ if (have_stored_position)
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+void
+caja_icon_container_set_single_click_mode (CajaIconContainer *container,
+ gboolean single_click_mode)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ container->details->single_click_mode = single_click_mode;
+}
+
+
+/* update the label color when the background changes */
+
+GdkGC *
+caja_icon_container_get_label_color_and_gc (CajaIconContainer *container,
+ GdkColor **color,
+ gboolean is_name,
+ gboolean is_highlight,
+ gboolean is_prelit)
+{
+ int idx;
+
+ if (is_name)
+ {
+ if (is_highlight)
+ {
+ if (gtk_widget_has_focus (GTK_WIDGET (container)))
+ {
+ idx = LABEL_COLOR_HIGHLIGHT;
+ }
+ else
+ {
+ idx = LABEL_COLOR_ACTIVE;
+ }
+ }
+ else
+ {
+ if (is_prelit)
+ {
+ idx = LABEL_COLOR_PRELIGHT;
+ }
+ else
+ {
+ idx = LABEL_COLOR;
+ }
+ }
+ }
+ else
+ {
+ if (is_highlight)
+ {
+ if (gtk_widget_has_focus (GTK_WIDGET (container)))
+ {
+ idx = LABEL_INFO_COLOR_HIGHLIGHT;
+ }
+ else
+ {
+ idx = LABEL_INFO_COLOR_ACTIVE;
+ }
+ }
+ else
+ {
+ idx = LABEL_INFO_COLOR;
+ }
+ }
+
+ if (color)
+ {
+ *color = &container->details->label_colors [idx];
+ }
+
+ return container->details->label_gcs [idx];
+}
+
+static void
+setup_gc_with_fg (CajaIconContainer *container, int idx, guint32 color)
+{
+ GdkGC *gc;
+ GdkColor gcolor;
+
+ gcolor = eel_gdk_rgb_to_color (color);
+ container->details->label_colors [idx] = gcolor;
+
+ gc = gdk_gc_new (gtk_layout_get_bin_window (GTK_LAYOUT (container)));
+ gdk_gc_set_rgb_fg_color (gc, &gcolor);
+
+ if (container->details->label_gcs [idx])
+ {
+ g_object_unref (container->details->label_gcs [idx]);
+ }
+
+ container->details->label_gcs [idx] = gc;
+}
+
+static void
+setup_label_gcs (CajaIconContainer *container)
+{
+ EelBackground *background;
+ GtkWidget *widget;
+ GdkColor *light_info_color, *dark_info_color;
+ guint light_info_value, dark_info_value;
+ gboolean frame_text;
+ GtkStyle *style;
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (container)))
+ return;
+
+ widget = GTK_WIDGET (container);
+
+ g_assert (CAJA_IS_ICON_CONTAINER (container));
+
+ background = eel_get_widget_background (GTK_WIDGET (container));
+
+ /* read the info colors from the current theme; use a reasonable default if undefined */
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "light_info_color", &light_info_color,
+ "dark_info_color", &dark_info_color,
+ NULL);
+ style = gtk_widget_get_style (widget);
+
+ if (light_info_color)
+ {
+ light_info_value = eel_gdk_color_to_rgb (light_info_color);
+ gdk_color_free (light_info_color);
+ }
+ else
+ {
+ light_info_value = DEFAULT_LIGHT_INFO_COLOR;
+ }
+
+ if (dark_info_color)
+ {
+ dark_info_value = eel_gdk_color_to_rgb (dark_info_color);
+ gdk_color_free (dark_info_color);
+ }
+ else
+ {
+ dark_info_value = DEFAULT_DARK_INFO_COLOR;
+ }
+
+ setup_gc_with_fg (container, LABEL_COLOR_HIGHLIGHT, eel_gdk_color_to_rgb (&style->text[GTK_STATE_SELECTED]));
+ setup_gc_with_fg (container, LABEL_COLOR_ACTIVE, eel_gdk_color_to_rgb (&style->text[GTK_STATE_ACTIVE]));
+ setup_gc_with_fg (container, LABEL_COLOR_PRELIGHT, eel_gdk_color_to_rgb (&style->text[GTK_STATE_PRELIGHT]));
+ setup_gc_with_fg (container,
+ LABEL_INFO_COLOR_HIGHLIGHT,
+ eel_gdk_color_is_dark (&style->base[GTK_STATE_SELECTED]) ? light_info_value : dark_info_value);
+ setup_gc_with_fg (container,
+ LABEL_INFO_COLOR_ACTIVE,
+ eel_gdk_color_is_dark (&style->base[GTK_STATE_ACTIVE]) ? light_info_value : dark_info_value);
+
+ /* If CajaIconContainer::frame_text is set, we can safely
+ * use the foreground color from the theme, because it will
+ * always be displayed against the gtk background */
+ gtk_widget_style_get (widget,
+ "frame_text", &frame_text,
+ NULL);
+
+ if (frame_text || !eel_background_is_set(background))
+ {
+ setup_gc_with_fg (container, LABEL_COLOR,
+ eel_gdk_color_to_rgb (&style->text[GTK_STATE_NORMAL]));
+ setup_gc_with_fg (container,
+ LABEL_INFO_COLOR,
+ eel_gdk_color_is_dark (&style->base[GTK_STATE_NORMAL]) ? light_info_value : dark_info_value);
+ }
+ else
+ {
+ if (container->details->use_drop_shadows || eel_background_is_dark (background))
+ {
+ setup_gc_with_fg (container, LABEL_COLOR, 0xEFEFEF);
+ setup_gc_with_fg (container,
+ LABEL_INFO_COLOR,
+ light_info_value);
+ }
+ else /* converse */
+ {
+ setup_gc_with_fg (container, LABEL_COLOR, 0x000000);
+ setup_gc_with_fg (container,
+ LABEL_INFO_COLOR,
+ dark_info_value);
+ }
+ }
+}
+
+static void
+update_label_color (EelBackground *background,
+ CajaIconContainer *container)
+{
+ g_assert (EEL_IS_BACKGROUND (background));
+
+ setup_label_gcs (container);
+}
+
+
+/* Return if the icon container is a fixed size */
+gboolean
+caja_icon_container_get_is_fixed_size (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+
+ return container->details->is_fixed_size;
+}
+
+/* Set the icon container to be a fixed size */
+void
+caja_icon_container_set_is_fixed_size (CajaIconContainer *container,
+ gboolean is_fixed_size)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ container->details->is_fixed_size = is_fixed_size;
+}
+
+gboolean
+caja_icon_container_get_is_desktop (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+
+ return container->details->is_desktop;
+}
+
+void
+caja_icon_container_set_is_desktop (CajaIconContainer *container,
+ gboolean is_desktop)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ container->details->is_desktop = is_desktop;
+}
+
+void
+caja_icon_container_set_margins (CajaIconContainer *container,
+ int left_margin,
+ int right_margin,
+ int top_margin,
+ int bottom_margin)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ container->details->left_margin = left_margin;
+ container->details->right_margin = right_margin;
+ container->details->top_margin = top_margin;
+ container->details->bottom_margin = bottom_margin;
+
+ /* redo layout of icons as the margins have changed */
+ schedule_redo_layout (container);
+}
+
+void
+caja_icon_container_set_use_drop_shadows (CajaIconContainer *container,
+ gboolean use_drop_shadows)
+{
+ gboolean frame_text;
+
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "frame_text", &frame_text,
+ NULL);
+
+ if (container->details->drop_shadows_requested == use_drop_shadows)
+ {
+ return;
+ }
+
+ container->details->drop_shadows_requested = use_drop_shadows;
+ container->details->use_drop_shadows = use_drop_shadows && !frame_text;
+ gtk_widget_queue_draw (GTK_WIDGET (container));
+}
+
+/* handle theme changes */
+
+static void
+caja_icon_container_theme_changed (gpointer user_data)
+{
+ CajaIconContainer *container;
+ GtkStyle *style;
+ GdkColor *prelight_icon_color, *normal_icon_color;
+ guchar highlight_alpha, normal_alpha, prelight_alpha;
+
+ container = CAJA_ICON_CONTAINER (user_data);
+
+ /* load the highlight color */
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "highlight_alpha", &highlight_alpha,
+ NULL);
+
+ style = gtk_widget_get_style (GTK_WIDGET (container));
+
+ container->details->highlight_color_rgba =
+ EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_SELECTED].red >> 8,
+ style->base[GTK_STATE_SELECTED].green >> 8,
+ style->base[GTK_STATE_SELECTED].blue >> 8,
+ highlight_alpha);
+ container->details->active_color_rgba =
+ EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_ACTIVE].red >> 8,
+ style->base[GTK_STATE_ACTIVE].green >> 8,
+ style->base[GTK_STATE_ACTIVE].blue >> 8,
+ highlight_alpha);
+
+ /* load the prelight icon color */
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "prelight_icon_color", &prelight_icon_color,
+ NULL);
+
+ if (prelight_icon_color)
+ {
+ container->details->prelight_icon_color_rgba =
+ EEL_RGBA_COLOR_PACK (prelight_icon_color->red >> 8,
+ prelight_icon_color->green >> 8,
+ prelight_icon_color->blue >> 8,
+ 255);
+ }
+ else /* if not defined by rc, set to default value */
+ {
+ container->details->prelight_icon_color_rgba =
+ EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_PRELIGHT].red >> 8,
+ style->base[GTK_STATE_PRELIGHT].green >> 8,
+ style->base[GTK_STATE_PRELIGHT].blue >> 8,
+ 255);
+ }
+
+
+ /* load the normal icon color */
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "normal_icon_color", &normal_icon_color,
+ NULL);
+
+ if (normal_icon_color)
+ {
+ container->details->normal_icon_color_rgba =
+ EEL_RGBA_COLOR_PACK (normal_icon_color->red >> 8,
+ normal_icon_color->green >> 8,
+ normal_icon_color->blue >> 8,
+ 255);
+ }
+ else /* if not defined by rc, set to default value */
+ {
+ container->details->normal_icon_color_rgba =
+ EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_NORMAL].red >> 8,
+ style->base[GTK_STATE_NORMAL].green >> 8,
+ style->base[GTK_STATE_NORMAL].blue >> 8,
+ 255);
+ }
+
+
+ /* load the normal color */
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "normal_alpha", &normal_alpha,
+ NULL);
+
+ container->details->normal_color_rgba =
+ EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_NORMAL].red >> 8,
+ style->base[GTK_STATE_NORMAL].green >> 8,
+ style->base[GTK_STATE_NORMAL].blue >> 8,
+ normal_alpha);
+
+
+ /* load the prelight color */
+ gtk_widget_style_get (GTK_WIDGET (container),
+ "prelight_alpha", &prelight_alpha,
+ NULL);
+
+ container->details->prelight_color_rgba =
+ EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_PRELIGHT].red >> 8,
+ style->base[GTK_STATE_PRELIGHT].green >> 8,
+ style->base[GTK_STATE_PRELIGHT].blue >> 8,
+ prelight_alpha);
+
+
+ setup_label_gcs (container);
+}
+
+void
+caja_icon_container_set_font (CajaIconContainer *container,
+ const char *font)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ if (eel_strcmp (container->details->font, font) == 0)
+ {
+ return;
+ }
+
+ g_free (container->details->font);
+ container->details->font = g_strdup (font);
+
+ invalidate_labels (container);
+ caja_icon_container_request_update_all (container);
+ gtk_widget_queue_draw (GTK_WIDGET (container));
+}
+
+void
+caja_icon_container_set_font_size_table (CajaIconContainer *container,
+ const int font_size_table[CAJA_ZOOM_LEVEL_LARGEST + 1])
+{
+ int old_font_size;
+ int i;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+ g_return_if_fail (font_size_table != NULL);
+
+ old_font_size = container->details->font_size_table[container->details->zoom_level];
+
+ for (i = 0; i <= CAJA_ZOOM_LEVEL_LARGEST; i++)
+ {
+ if (container->details->font_size_table[i] != font_size_table[i])
+ {
+ container->details->font_size_table[i] = font_size_table[i];
+ }
+ }
+
+ if (old_font_size != container->details->font_size_table[container->details->zoom_level])
+ {
+ invalidate_labels (container);
+ caja_icon_container_request_update_all (container);
+ }
+}
+
+/**
+ * caja_icon_container_get_icon_description
+ * @container: An icon container widget.
+ * @data: Icon data
+ *
+ * Gets the description for the icon. This function may return NULL.
+ **/
+char*
+caja_icon_container_get_icon_description (CajaIconContainer *container,
+ CajaIconData *data)
+{
+ CajaIconContainerClass *klass;
+
+ klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
+
+ if (klass->get_icon_description)
+ {
+ return klass->get_icon_description (container, data);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+gboolean
+caja_icon_container_get_allow_moves (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+
+ return container->details->drag_allow_moves;
+}
+
+void
+caja_icon_container_set_allow_moves (CajaIconContainer *container,
+ gboolean allow_moves)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ container->details->drag_allow_moves = allow_moves;
+}
+
+void
+caja_icon_container_set_forced_icon_size (CajaIconContainer *container,
+ int forced_icon_size)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ if (forced_icon_size != container->details->forced_icon_size)
+ {
+ container->details->forced_icon_size = forced_icon_size;
+
+ invalidate_label_sizes (container);
+ caja_icon_container_request_update_all (container);
+ }
+}
+
+void
+caja_icon_container_set_all_columns_same_width (CajaIconContainer *container,
+ gboolean all_columns_same_width)
+{
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ if (all_columns_same_width != container->details->all_columns_same_width)
+ {
+ container->details->all_columns_same_width = all_columns_same_width;
+
+ invalidate_labels (container);
+ caja_icon_container_request_update_all (container);
+ }
+}
+
+/**
+ * caja_icon_container_set_highlighted_for_clipboard
+ * @container: An icon container widget.
+ * @data: Icon Data associated with all icons that should be highlighted.
+ * Others will be unhighlighted.
+ **/
+void
+caja_icon_container_set_highlighted_for_clipboard (CajaIconContainer *container,
+ GList *clipboard_icon_data)
+{
+ GList *l;
+ CajaIcon *icon;
+ gboolean highlighted_for_clipboard;
+
+ g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
+
+ for (l = container->details->icons; l != NULL; l = l->next)
+ {
+ icon = l->data;
+ highlighted_for_clipboard = (g_list_find (clipboard_icon_data, icon->data) != NULL);
+
+ eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
+ "highlighted-for-clipboard", highlighted_for_clipboard,
+ NULL);
+ }
+
+}
+
+/* CajaIconContainerAccessible */
+
+static CajaIconContainerAccessiblePrivate *
+accessible_get_priv (AtkObject *accessible)
+{
+ CajaIconContainerAccessiblePrivate *priv;
+
+ priv = g_object_get_qdata (G_OBJECT (accessible),
+ accessible_private_data_quark);
+
+ return priv;
+}
+
+/* AtkAction interface */
+
+static gboolean
+caja_icon_container_accessible_do_action (AtkAction *accessible, int i)
+{
+ GtkWidget *widget;
+ CajaIconContainer *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 = CAJA_ICON_CONTAINER (widget);
+ switch (i)
+ {
+ case ACTION_ACTIVATE :
+ selection = caja_icon_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 CajaIconContainerAccessible::do_action");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int
+caja_icon_container_accessible_get_n_actions (AtkAction *accessible)
+{
+ return LAST_ACTION;
+}
+
+static const char *
+caja_icon_container_accessible_action_get_description (AtkAction *accessible,
+ int i)
+{
+ CajaIconContainerAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = accessible_get_priv (ATK_OBJECT (accessible));
+
+ if (priv->action_descriptions[i])
+ {
+ return priv->action_descriptions[i];
+ }
+ else
+ {
+ return caja_icon_container_accessible_action_descriptions[i];
+ }
+}
+
+static const char *
+caja_icon_container_accessible_action_get_name (AtkAction *accessible, int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return caja_icon_container_accessible_action_names[i];
+}
+
+static const char *
+caja_icon_container_accessible_action_get_keybinding (AtkAction *accessible,
+ int i)
+{
+ g_assert (i < LAST_ACTION);
+
+ return NULL;
+}
+
+static gboolean
+caja_icon_container_accessible_action_set_description (AtkAction *accessible,
+ int i,
+ const char *description)
+{
+ CajaIconContainerAccessiblePrivate *priv;
+
+ g_assert (i < LAST_ACTION);
+
+ priv = accessible_get_priv (ATK_OBJECT (accessible));
+
+ if (priv->action_descriptions[i])
+ {
+ g_free (priv->action_descriptions[i]);
+ }
+ priv->action_descriptions[i] = g_strdup (description);
+
+ return FALSE;
+}
+
+static void
+caja_icon_container_accessible_action_interface_init (AtkActionIface *iface)
+{
+ iface->do_action = caja_icon_container_accessible_do_action;
+ iface->get_n_actions = caja_icon_container_accessible_get_n_actions;
+ iface->get_description = caja_icon_container_accessible_action_get_description;
+ iface->get_name = caja_icon_container_accessible_action_get_name;
+ iface->get_keybinding = caja_icon_container_accessible_action_get_keybinding;
+ iface->set_description = caja_icon_container_accessible_action_set_description;
+}
+
+/* AtkSelection interface */
+
+static void
+caja_icon_container_accessible_update_selection (AtkObject *accessible)
+{
+ CajaIconContainer *container;
+ CajaIconContainerAccessiblePrivate *priv;
+ GList *l;
+ CajaIcon *icon;
+
+ container = CAJA_ICON_CONTAINER (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
+
+ priv = accessible_get_priv (accessible);
+
+ if (priv->selection)
+ {
+ g_list_free (priv->selection);
+ priv->selection = NULL;
+ }
+
+ for (l = container->details->icons; l != NULL; l = l->next)
+ {
+ icon = l->data;
+ if (icon->is_selected)
+ {
+ priv->selection = g_list_prepend (priv->selection,
+ icon);
+ }
+ }
+
+ priv->selection = g_list_reverse (priv->selection);
+}
+
+static void
+caja_icon_container_accessible_selection_changed_cb (CajaIconContainer *container,
+ gpointer data)
+{
+ g_signal_emit_by_name (data, "selection_changed");
+}
+
+static void
+caja_icon_container_accessible_icon_added_cb (CajaIconContainer *container,
+ CajaIconData *icon_data,
+ gpointer data)
+{
+ CajaIcon *icon;
+ AtkObject *atk_parent;
+ AtkObject *atk_child;
+ int index;
+
+ 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));
+ index = g_list_index (container->details->icons, icon);
+
+ g_signal_emit_by_name (atk_parent, "children_changed::add",
+ index, atk_child, NULL);
+ }
+}
+
+static void
+caja_icon_container_accessible_icon_removed_cb (CajaIconContainer *container,
+ CajaIconData *icon_data,
+ gpointer data)
+{
+ CajaIcon *icon;
+ AtkObject *atk_parent;
+ AtkObject *atk_child;
+ int index;
+
+ 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));
+ index = g_list_index (container->details->icons, icon);
+
+ g_signal_emit_by_name (atk_parent, "children_changed::remove",
+ index, atk_child, NULL);
+ }
+}
+
+static void
+caja_icon_container_accessible_cleared_cb (CajaIconContainer *container,
+ gpointer data)
+{
+ g_signal_emit_by_name (data, "children_changed", 0, NULL, NULL);
+}
+
+
+static gboolean
+caja_icon_container_accessible_add_selection (AtkSelection *accessible,
+ int i)
+{
+ GtkWidget *widget;
+ CajaIconContainer *container;
+ GList *l;
+ GList *selection;
+ CajaIcon *icon;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ l = g_list_nth (container->details->icons, i);
+ if (l)
+ {
+ icon = l->data;
+
+ selection = caja_icon_container_get_selection (container);
+ selection = g_list_prepend (selection,
+ icon->data);
+ caja_icon_container_set_selection (container, selection);
+
+ g_list_free (selection);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+caja_icon_container_accessible_clear_selection (AtkSelection *accessible)
+{
+ GtkWidget *widget;
+ CajaIconContainer *container;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ caja_icon_container_unselect_all (container);
+
+ return TRUE;
+}
+
+static AtkObject *
+caja_icon_container_accessible_ref_selection (AtkSelection *accessible,
+ int i)
+{
+ AtkObject *atk_object;
+ CajaIconContainerAccessiblePrivate *priv;
+ GList *item;
+ CajaIcon *icon;
+
+ caja_icon_container_accessible_update_selection (ATK_OBJECT (accessible));
+ priv = accessible_get_priv (ATK_OBJECT (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
+caja_icon_container_accessible_get_selection_count (AtkSelection *accessible)
+{
+ int count;
+ CajaIconContainerAccessiblePrivate *priv;
+
+ caja_icon_container_accessible_update_selection (ATK_OBJECT (accessible));
+ priv = accessible_get_priv (ATK_OBJECT (accessible));
+
+ count = g_list_length (priv->selection);
+
+ return count;
+}
+
+static gboolean
+caja_icon_container_accessible_is_child_selected (AtkSelection *accessible,
+ int i)
+{
+ CajaIconContainer *container;
+ GList *l;
+ CajaIcon *icon;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ l = g_list_nth (container->details->icons, i);
+ if (l)
+ {
+ icon = l->data;
+ return icon->is_selected;
+ }
+ return FALSE;
+}
+
+static gboolean
+caja_icon_container_accessible_remove_selection (AtkSelection *accessible,
+ int i)
+{
+ CajaIconContainer *container;
+ CajaIconContainerAccessiblePrivate *priv;
+ GList *l;
+ GList *selection;
+ CajaIcon *icon;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ caja_icon_container_accessible_update_selection (ATK_OBJECT (accessible));
+ priv = accessible_get_priv (ATK_OBJECT (accessible));
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ l = g_list_nth (priv->selection, i);
+ if (l)
+ {
+ icon = l->data;
+
+ selection = caja_icon_container_get_selection (container);
+ selection = g_list_remove (selection, icon->data);
+ caja_icon_container_set_selection (container, selection);
+
+ g_list_free (selection);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+caja_icon_container_accessible_select_all_selection (AtkSelection *accessible)
+{
+ CajaIconContainer *container;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ caja_icon_container_select_all (container);
+
+ return TRUE;
+}
+
+void
+caja_icon_container_widget_to_file_operation_position (CajaIconContainer *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 -= caja_get_icon_size_for_zoom_level (container->details->zoom_level) / 2;
+ position->y -= caja_get_icon_size_for_zoom_level (container->details->zoom_level) / 2;
+}
+
+static void
+caja_icon_container_accessible_selection_interface_init (AtkSelectionIface *iface)
+{
+ iface->add_selection = caja_icon_container_accessible_add_selection;
+ iface->clear_selection = caja_icon_container_accessible_clear_selection;
+ iface->ref_selection = caja_icon_container_accessible_ref_selection;
+ iface->get_selection_count = caja_icon_container_accessible_get_selection_count;
+ iface->is_child_selected = caja_icon_container_accessible_is_child_selected;
+ iface->remove_selection = caja_icon_container_accessible_remove_selection;
+ iface->select_all_selection = caja_icon_container_accessible_select_all_selection;
+}
+
+
+static gint
+caja_icon_container_accessible_get_n_children (AtkObject *accessible)
+{
+ CajaIconContainer *container;
+ GtkWidget *widget;
+ gint i;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return FALSE;
+ }
+
+ container = CAJA_ICON_CONTAINER (widget);
+
+ i = g_hash_table_size (container->details->icon_set);
+ if (container->details->rename_widget)
+ {
+ i++;
+ }
+ return i;
+}
+
+static AtkObject*
+caja_icon_container_accessible_ref_child (AtkObject *accessible, int i)
+{
+ AtkObject *atk_object;
+ CajaIconContainer *container;
+ GList *item;
+ CajaIcon *icon;
+ GtkWidget *widget;
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (!widget)
+ {
+ return NULL;
+ }
+
+ container = CAJA_ICON_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;
+ }
+ else
+ {
+ if (i == g_list_length (container->details->icons))
+ {
+ if (container->details->rename_widget)
+ {
+ atk_object = gtk_widget_get_accessible (container->details->rename_widget);
+ g_object_ref (atk_object);
+
+ return atk_object;
+ }
+ }
+ return NULL;
+ }
+}
+
+static void
+caja_icon_container_accessible_initialize (AtkObject *accessible,
+ gpointer data)
+{
+ CajaIconContainer *container;
+ CajaIconContainerAccessiblePrivate *priv;
+
+ if (ATK_OBJECT_CLASS (accessible_parent_class)->initialize)
+ {
+ ATK_OBJECT_CLASS (accessible_parent_class)->initialize (accessible, data);
+ }
+
+ priv = g_new0 (CajaIconContainerAccessiblePrivate, 1);
+ g_object_set_qdata (G_OBJECT (accessible),
+ accessible_private_data_quark,
+ priv);
+
+ if (GTK_IS_ACCESSIBLE (accessible))
+ {
+ caja_icon_container_accessible_update_selection
+ (ATK_OBJECT (accessible));
+
+ container = CAJA_ICON_CONTAINER (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
+ g_signal_connect (G_OBJECT (container), "selection_changed",
+ G_CALLBACK (caja_icon_container_accessible_selection_changed_cb),
+ accessible);
+ g_signal_connect (G_OBJECT (container), "icon_added",
+ G_CALLBACK (caja_icon_container_accessible_icon_added_cb),
+ accessible);
+ g_signal_connect (G_OBJECT (container), "icon_removed",
+ G_CALLBACK (caja_icon_container_accessible_icon_removed_cb),
+ accessible);
+ g_signal_connect (G_OBJECT (container), "cleared",
+ G_CALLBACK (caja_icon_container_accessible_cleared_cb),
+ accessible);
+ }
+}
+
+static void
+caja_icon_container_accessible_finalize (GObject *object)
+{
+ CajaIconContainerAccessiblePrivate *priv;
+ int i;
+
+ priv = accessible_get_priv (ATK_OBJECT (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_free (priv);
+
+ G_OBJECT_CLASS (accessible_parent_class)->finalize (object);
+}
+
+static void
+caja_icon_container_accessible_class_init (AtkObjectClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ accessible_parent_class = g_type_class_peek_parent (klass);
+
+ gobject_class->finalize = caja_icon_container_accessible_finalize;
+
+ klass->get_n_children = caja_icon_container_accessible_get_n_children;
+ klass->ref_child = caja_icon_container_accessible_ref_child;
+ klass->initialize = caja_icon_container_accessible_initialize;
+
+ accessible_private_data_quark = g_quark_from_static_string ("icon-container-accessible-private-data");
+}
+
+static GType
+caja_icon_container_accessible_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static GInterfaceInfo atk_action_info =
+ {
+ (GInterfaceInitFunc) caja_icon_container_accessible_action_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ static GInterfaceInfo atk_selection_info =
+ {
+ (GInterfaceInitFunc) caja_icon_container_accessible_selection_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ type = eel_accessibility_create_derived_type
+ ("CajaIconContainerAccessible",
+ EEL_TYPE_CANVAS,
+ caja_icon_container_accessible_class_init);
+
+ g_type_add_interface_static (type, ATK_TYPE_ACTION,
+ &atk_action_info);
+ g_type_add_interface_static (type, ATK_TYPE_SELECTION,
+ &atk_selection_info);
+ }
+
+ return type;
+}
+
+#if ! defined (CAJA_OMIT_SELF_CHECK)
+
+static char *
+check_compute_stretch (int icon_x, int icon_y, int icon_size,
+ int start_pointer_x, int start_pointer_y,
+ int end_pointer_x, int end_pointer_y)
+{
+ StretchState start, current;
+
+ start.icon_x = icon_x;
+ start.icon_y = icon_y;
+ start.icon_size = icon_size;
+ start.pointer_x = start_pointer_x;
+ start.pointer_y = start_pointer_y;
+ current.pointer_x = end_pointer_x;
+ current.pointer_y = end_pointer_y;
+
+ compute_stretch (&start, &current);
+
+ return g_strdup_printf ("%d,%d:%d",
+ current.icon_x,
+ current.icon_y,
+ current.icon_size);
+}
+
+void
+caja_self_check_icon_container (void)
+{
+ EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 16, 0, 0, 0, 0), "0,0:16");
+ EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 16, 16, 16, 17, 17), "0,0:17");
+ EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 16, 16, 16, 17, 16), "0,0:16");
+ EEL_CHECK_STRING_RESULT (check_compute_stretch (100, 100, 64, 105, 105, 40, 40), "35,35:129");
+}
+
+gboolean
+caja_icon_container_is_layout_rtl (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), 0);
+
+ return container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_R_L ||
+ container->details->layout_mode == CAJA_ICON_LAYOUT_R_L_T_B;
+}
+
+gboolean
+caja_icon_container_is_layout_vertical (CajaIconContainer *container)
+{
+ g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
+
+ return (container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_L_R ||
+ container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_R_L);
+}
+
+int
+caja_icon_container_get_max_layout_lines_for_pango (CajaIconContainer *container)
+{
+ int limit;
+
+ if (caja_icon_container_get_is_desktop (container))
+ {
+ limit = desktop_text_ellipsis_limit;
+ }
+ else
+ {
+ limit = text_ellipsis_limits[container->details->zoom_level];
+ }
+
+ if (limit <= 0)
+ {
+ return G_MININT;
+ }
+
+ return -limit;
+}
+
+int
+caja_icon_container_get_max_layout_lines (CajaIconContainer *container)
+{
+ int limit;
+
+ if (caja_icon_container_get_is_desktop (container))
+ {
+ limit = desktop_text_ellipsis_limit;
+ }
+ else
+ {
+ limit = text_ellipsis_limits[container->details->zoom_level];
+ }
+
+ if (limit <= 0)
+ {
+ return G_MAXINT;
+ }
+
+ return limit;
+}
+
+void
+caja_icon_container_begin_loading (CajaIconContainer *container)
+{
+ gboolean dummy;
+
+ if (caja_icon_container_get_store_layout_timestamps (container))
+ {
+ container->details->layout_timestamp = UNDEFINED_TIME;
+ g_signal_emit (container,
+ signals[GET_STORED_LAYOUT_TIMESTAMP], 0,
+ NULL, &container->details->layout_timestamp, &dummy);
+ }
+}
+
+static void
+store_layout_timestamps_now (CajaIconContainer *container)
+{
+ CajaIcon *icon;
+ GList *p;
+ gboolean dummy;
+
+ container->details->layout_timestamp = time (NULL);
+ g_signal_emit (container,
+ signals[STORE_LAYOUT_TIMESTAMP], 0,
+ NULL, &container->details->layout_timestamp, &dummy);
+
+ for (p = container->details->icons; p != NULL; p = p->next)
+ {
+ icon = p->data;
+
+ g_signal_emit (container,
+ signals[STORE_LAYOUT_TIMESTAMP], 0,
+ icon->data, &container->details->layout_timestamp, &dummy);
+ }
+}
+
+
+void
+caja_icon_container_end_loading (CajaIconContainer *container,
+ gboolean all_icons_added)
+{
+ if (all_icons_added &&
+ caja_icon_container_get_store_layout_timestamps (container))
+ {
+ if (container->details->new_icons == NULL)
+ {
+ store_layout_timestamps_now (container);
+ }
+ else
+ {
+ container->details->store_layout_timestamps_when_finishing_new_icons = TRUE;
+ }
+ }
+}
+
+gboolean
+caja_icon_container_get_store_layout_timestamps (CajaIconContainer *container)
+{
+ return container->details->store_layout_timestamps;
+}
+
+
+void
+caja_icon_container_set_store_layout_timestamps (CajaIconContainer *container,
+ gboolean store_layout_timestamps)
+{
+ container->details->store_layout_timestamps = store_layout_timestamps;
+}
+
+
+#endif /* ! CAJA_OMIT_SELF_CHECK */