diff options
Diffstat (limited to 'libcaja-private/caja-icon-container.c')
-rw-r--r-- | libcaja-private/caja-icon-container.c | 10612 |
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, ×tamp, &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, ¤t); + + 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 */ |