/* -*- 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. Authors: Ettore Perazzoli , Darin Adler */ #include #include #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 #include #include #include #include #include #include #include #include #include #if GTK_CHECK_VERSION(3, 0, 0) #include #endif #include #include #include #include #include #include #include #if !GTK_CHECK_VERSION(3, 0, 0) #define gtk_scrollable_get_hadjustment gtk_layout_get_hadjustment #define gtk_scrollable_get_vadjustment gtk_layout_get_vadjustment #define GTK_SCROLLABLE GTK_LAYOUT #endif #if GTK_CHECK_VERSION (3, 0, 0) #define gtk_vbox_new(X,Y) gtk_box_new(GTK_ORIENTATION_VERTICAL,Y) #endif #if !GTK_CHECK_VERSION(3,0,0) #define gtk_widget_get_preferred_size(x,y,z) gtk_widget_size_request(x,y) #endif #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 #if GTK_CHECK_VERSION(3,0,0) #define DEFAULT_LIGHT_INFO_COLOR "#AAAAFD" #define DEFAULT_DARK_INFO_COLOR "#33337F" #else #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 #endif #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); #if GTK_CHECK_VERSION(3, 0, 0) typedef struct _CajaIconContainerAccessible CajaIconContainerAccessible; typedef struct _CajaIconContainerAccessibleClass CajaIconContainerAccessibleClass; struct _CajaIconContainerAccessible { EelCanvasAccessible parent; }; struct _CajaIconContainerAccessibleClass { EelCanvasAccessibleClass parent_class; }; #endif static void activate_selected_items (CajaIconContainer *container); static void activate_selected_items_alternate (CajaIconContainer *container, CajaIcon *icon); #if !GTK_CHECK_VERSION(3, 0, 0) static void caja_icon_container_theme_changed (gpointer user_data); #endif 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); #if !GTK_CHECK_VERSION(3, 0, 0) static void setup_label_gcs (CajaIconContainer *container); #endif 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 void text_ellipsis_limit_changed_container_callback (gpointer callback_data); 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] = { 0 }; /* Functions dealing with CajaIcons. */ static void icon_free (CajaIcon *icon) { /* Destroy this canvas item; the parent will unref it. */ eel_canvas_item_destroy (EEL_CANVAS_ITEM (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, width_left; 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; width_left = icon_bounds.x0 - 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_scrollable_get_hadjustment (GTK_SCROLLABLE (container)); vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)); /* Store the old ajustment values so we can tell if we * ended up actually scrolling. We may not have in a case * where the resulting value got pinned to the adjustment * min or max. */ old_h_value = gtk_adjustment_get_value (hadj); old_v_value = gtk_adjustment_get_value (vadj); #if GTK_CHECK_VERSION (3, 0, 0) gtk_adjustment_set_value (hadj, gtk_adjustment_get_value (hadj) + delta_x); gtk_adjustment_set_value (vadj, gtk_adjustment_get_value (vadj) + delta_y); #else 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); #endif /* 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) { GtkAllocation allocation; GtkAdjustment *hadj, *vadj; EelIRect bounds; if (!icon_is_positioned (icon)) { set_pending_icon_to_reveal (container, icon); return; } set_pending_icon_to_reveal (container, NULL); gtk_widget_get_allocation (GTK_WIDGET (container), &allocation); hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)); vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)); 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)) { #if GTK_CHECK_VERSION (3, 0, 0) gtk_adjustment_set_value (vadj, bounds.y0); } else if (bounds.y1 > gtk_adjustment_get_value (vadj) + allocation.height) { gtk_adjustment_set_value (vadj, bounds.y1 - allocation.height); } if (bounds.x0 < gtk_adjustment_get_value (hadj)) { gtk_adjustment_set_value (hadj, bounds.x0); } else if (bounds.x1 > gtk_adjustment_get_value (hadj) + allocation.width) { if (bounds.x1 - allocation.width > bounds.x0) { gtk_adjustment_set_value (hadj, bounds.x0); } else { gtk_adjustment_set_value (hadj, bounds.x1 - allocation.width); #else 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) { if (bounds.x1 - allocation.width > bounds.x0) { eel_gtk_adjustment_set_value (hadj, bounds.x0); } else { eel_gtk_adjustment_set_value (hadj, bounds.x1 - allocation.width); #endif } } } 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 = atk_gobject_accessible_for_object (G_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_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas))); old_scroll_y = gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (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_scrollable_get_hadjustment (GTK_SCROLLABLE (container)); vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)); /* Scroll by 1/4 icon each time you click. */ step_increment = 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); } if (gtk_adjustment_get_step_increment (vadj) != step_increment) { gtk_adjustment_set_step_increment (vadj, step_increment); } #if !GTK_CHECK_VERSION (3, 0, 0) /* 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); #endif } 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; 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); 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 x, canvas_height; GArray *positions; IconPositions *position; EelDRect icon_bounds; EelDRect text_bounds; 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_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; /* 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) { g_list_free (unplaced_icons); 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 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); 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; #if GTK_CHECK_VERSION(3, 20, 0) GdkDisplay *display; GdkSeat *seat; #endif 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_scrollable_get_hadjustment (GTK_SCROLLABLE (container))); if (adj_x != band_info->last_adj_x) { band_info->last_adj_x = adj_x; adj_changed = TRUE; } adj_y = gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))); if (adj_y != band_info->last_adj_y) { band_info->last_adj_y = adj_y; adj_changed = TRUE; } #if GTK_CHECK_VERSION(3, 20, 0) display = gtk_widget_get_display (widget); seat = gdk_display_get_default_seat (display); gdk_window_get_device_position (gtk_widget_get_window (widget), gdk_seat_get_pointer (seat), &x, &y, NULL); #elif GTK_CHECK_VERSION (3, 0, 0) gdk_window_get_device_position (gtk_widget_get_window (widget), gdk_device_manager_get_client_pointer ( gdk_display_get_device_manager ( gtk_widget_get_display (widget))), &x, &y, NULL); #else gtk_widget_get_pointer (widget, &x, &y); #endif 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_scrollable_get_hadjustment (GTK_SCROLLABLE (container))), y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))), &world_x, &world_y); if (world_x < band_info->start_x) { x1 = world_x; x2 = band_info->start_x; } else { x1 = band_info->start_x; x2 = world_x; } if (world_y < band_info->start_y) { y1 = world_y; y2 = band_info->start_y; } else { y1 = band_info->start_y; y2 = world_y; } /* Don't let the area of the selection rectangle be empty. * Aside from the fact that it would be funny when the rectangle disappears, * this also works around a crash in libart that happens sometimes when a * zero height rectangle is passed. */ x2 = MAX (x1 + 1, x2); y2 = MAX (y1 + 1, y2); eel_canvas_item_set (band_info->selection_rectangle, "x1", x1, "y1", y1, "x2", x2, "y2", y2, NULL); selection_rect.x0 = x1; selection_rect.y0 = y1; selection_rect.x1 = x2; selection_rect.y1 = y2; rubberband_select (container, &band_info->prev_rect, &selection_rect); band_info->prev_x = x; band_info->prev_y = y; band_info->prev_rect = selection_rect; return TRUE; } #if GTK_CHECK_VERSION(3,0,0) /*borrowed from Nemo, makes Caja rubberbanding follow same selectors as Nemo and presumably Nautilus */ static void start_rubberbanding (CajaIconContainer *container, GdkEventButton *event) { AtkObject *accessible; CajaIconContainerDetails *details; CajaIconRubberbandInfo *band_info; GdkRGBA bg_color, border_color; GList *p; CajaIcon *icon; GtkStyleContext *context; 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); context = gtk_widget_get_style_context (GTK_WIDGET (container)); gtk_style_context_save (context); gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND); gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &bg_color); gtk_style_context_get_border_color (context, GTK_STATE_FLAG_NORMAL, &border_color); gtk_style_context_restore (context); 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", &bg_color, "outline_color_rgba", &border_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_scrollable_get_hadjustment (GTK_SCROLLABLE (container))); band_info->prev_y = event->y - gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))); band_info->active = TRUE; if (band_info->timer_id == 0) { band_info->timer_id = g_timeout_add (RUBBERBAND_TIMEOUT_INTERVAL, rubberband_timeout_callback, container); } eel_canvas_item_grab (band_info->selection_rectangle, (GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK), #if GTK_CHECK_VERSION(3, 20, 0) NULL, (GdkEvent *)event); #else NULL, event->time); #endif } #else 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_scrollable_get_hadjustment (GTK_SCROLLABLE (container))); band_info->prev_y = event->y - gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))); band_info->active = TRUE; if (band_info->timer_id == 0) { band_info->timer_id = g_timeout_add (RUBBERBAND_TIMEOUT_INTERVAL, rubberband_timeout_callback, container); } eel_canvas_item_grab (band_info->selection_rectangle, (GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK), NULL, event->time); } #endif static void #if GTK_CHECK_VERSION(3, 20, 0) stop_rubberbanding (CajaIconContainer *container) #else stop_rubberbanding (CajaIconContainer *container, guint32 time) #endif { 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. */ #if GTK_CHECK_VERSION(3, 20, 0) eel_canvas_item_ungrab (band_info->selection_rectangle); #else eel_canvas_item_ungrab (band_info->selection_rectangle, time); #endif eel_canvas_item_destroy (band_info->selection_rectangle); band_info->selection_rectangle = NULL; /* if only one item has been selected, use it as range * selection base (cf. handle_icon_button_press) */ icons = 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 static void #if GTK_CHECK_VERSION(3, 0, 0) destroy (GtkWidget *object) #else destroy (GtkObject *object) #endif { 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; } } #if GTK_CHECK_VERSION(3, 0, 0) GTK_WIDGET_CLASS (caja_icon_container_parent_class)->destroy (object); #else GTK_OBJECT_CLASS (caja_icon_container_parent_class)->destroy (object); #endif } static void finalize (GObject *object) { CajaIconContainerDetails *details; details = CAJA_ICON_CONTAINER (object)->details; g_signal_handlers_disconnect_by_func (caja_icon_view_preferences, text_ellipsis_limit_changed_container_callback, object); g_signal_handlers_disconnect_by_func (caja_desktop_preferences, text_ellipsis_limit_changed_container_callback, object); g_hash_table_destroy (details->icon_set); details->icon_set = NULL; g_free (details->font); if (details->a11y_item_action_queue != NULL) { while (!g_queue_is_empty (details->a11y_item_action_queue)) { g_free (g_queue_pop_head (details->a11y_item_action_queue)); } g_queue_free (details->a11y_item_action_queue); } if (details->a11y_item_action_idle_handler != 0) { g_source_remove (details->a11y_item_action_idle_handler); } g_free (details); G_OBJECT_CLASS (caja_icon_container_parent_class)->finalize (object); } /* GtkWidget methods. */ 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) { GtkAdjustment *vadj, *hadj; CajaIconContainer *container; GTK_WIDGET_CLASS (caja_icon_container_parent_class)->realize (widget); container = CAJA_ICON_CONTAINER (widget); /* Unless GTK 3.21 or later is in use and the desktop must be transparent*/ #if !GTK_CHECK_VERSION(3, 21, 0) /* Ensure that the desktop window is native so the background set on it is drawn by X. */ if (container->details->is_desktop) { #if GTK_CHECK_VERSION (3, 0, 0) gdk_x11_window_get_xid (gtk_layout_get_bin_window (GTK_LAYOUT (widget))); #else gdk_x11_drawable_get_xid (gtk_layout_get_bin_window (GTK_LAYOUT (widget))); #endif } #endif /* Set up DnD. */ caja_icon_dnd_init (container); hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget)); g_signal_connect (hadj, "value_changed", G_CALLBACK (handle_hadjustment_changed), widget); vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget)); g_signal_connect (vadj, "value_changed", G_CALLBACK (handle_vadjustment_changed), widget); } static void unrealize (GtkWidget *widget) { CajaIconContainer *container; container = CAJA_ICON_CONTAINER (widget); 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 #if GTK_CHECK_VERSION(3,0,0) style_updated (GtkWidget *widget) { CajaIconContainer *container; container = CAJA_ICON_CONTAINER (widget); container->details->use_drop_shadows = container->details->drop_shadows_requested; /* Don't chain up to parent, if this is a desktop container, * because that resets the background of the window. */ if (!caja_icon_container_get_is_desktop (container)) { GTK_WIDGET_CLASS (caja_icon_container_parent_class)->style_updated (widget); } if (gtk_widget_get_realized (widget)) { invalidate_labels (container); caja_icon_container_request_update_all (container); } #else 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 */ #endif } 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 */ if (click_count == 1) { click_count = 0; return TRUE; } else { return FALSE; } } static void clear_drag_state (CajaIconContainer *container) { container->details->drag_icon = NULL; container->details->drag_state = DRAG_STATE_INITIAL; } static gboolean #if GTK_CHECK_VERSION(3, 20, 0) start_stretching (CajaIconContainer *container, GdkEvent *event) #else start_stretching (CajaIconContainer *container) #endif { CajaIconContainerDetails *details; CajaIcon *icon; EelDPoint world_point; GtkWidget *toplevel; GdkDisplay *display; GtkCornerType corner; GdkCursor *cursor; details = container->details; icon = details->stretch_icon; display = gtk_widget_get_display (GTK_WIDGET (container)); /* 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_for_display (display, GDK_TOP_LEFT_CORNER); break; case GTK_CORNER_BOTTOM_LEFT: cursor = gdk_cursor_new_for_display (display, GDK_BOTTOM_LEFT_CORNER); break; case GTK_CORNER_TOP_RIGHT: cursor = gdk_cursor_new_for_display (display, GDK_TOP_RIGHT_CORNER); break; case GTK_CORNER_BOTTOM_RIGHT: cursor = gdk_cursor_new_for_display (display, 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, #if GTK_CHECK_VERSION(3, 20, 0) event); #else GDK_CURRENT_TIME); #endif if (cursor) #if GTK_CHECK_VERSION(3,0,0) g_object_unref (cursor); #else gdk_cursor_unref (cursor); #endif /* Ensure the window itself is focused.. */ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (container)); if (toplevel != NULL && gtk_widget_get_realized (toplevel)) { 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 ((GSourceFunc) 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_KEY_equal: case GDK_KEY_plus: case GDK_KEY_KP_Add: icon_set_size (container, icon, size + 5, FALSE, FALSE); break; case GDK_KEY_minus: case GDK_KEY_KP_Subtract: icon_set_size (container, icon, size - 5, FALSE, FALSE); break; case GDK_KEY_0: case GDK_KEY_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) { #if GTK_CHECK_VERSION(3, 20, 0) eel_canvas_item_ungrab (EEL_CANVAS_ITEM (container->details->stretch_icon->item)); #else eel_canvas_item_ungrab (EEL_CANVAS_ITEM (container->details->stretch_icon->item), GDK_CURRENT_TIME); #endif } 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) { #if GTK_CHECK_VERSION(3, 20, 0) stop_rubberbanding (container); #else stop_rubberbanding (container, event->time); #endif 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_window_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); cont_width = gdk_window_get_width (cont_window); cont_height = gdk_window_get_height (cont_window); gtk_widget_get_preferred_size (search_dialog, &requisition, NULL); 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, gchar *preedit, 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; 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 */ 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_KEY_Escape || event->keyval == GDK_KEY_Tab) { caja_icon_container_search_dialog_hide (widget, container); return TRUE; } /* close window and activate alternate */ if (event->keyval == GDK_KEY_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_KEY_Up || event->keyval == GDK_KEY_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_KEY_g || event->keyval == GDK_KEY_G)) { caja_icon_container_search_move (widget, container, TRUE); retval = TRUE; } /* select next matching iter */ if (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_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_KEY_g || event->keyval == GDK_KEY_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; 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_KEY_Return: case GDK_KEY_KP_Enter: end_renaming_mode (container, TRUE); handled = TRUE; break; case GDK_KEY_Escape: end_renaming_mode (container, FALSE); handled = TRUE; break; default: break; } } else { switch (event->keyval) { case GDK_KEY_Home: case GDK_KEY_KP_Home: keyboard_home (container, event); handled = TRUE; break; case GDK_KEY_End: case GDK_KEY_KP_End: keyboard_end (container, event); handled = TRUE; break; case GDK_KEY_Left: case GDK_KEY_KP_Left: /* Don't eat Alt-Left, as that is used for history browsing */ if ((event->state & GDK_MOD1_MASK) == 0) { keyboard_left (container, event); handled = TRUE; } break; case GDK_KEY_Up: case GDK_KEY_KP_Up: /* Don't eat Alt-Up, as that is used for alt-shift-Up */ if ((event->state & GDK_MOD1_MASK) == 0) { keyboard_up (container, event); handled = TRUE; } break; case GDK_KEY_Right: case GDK_KEY_KP_Right: /* Don't eat Alt-Right, as that is used for history browsing */ if ((event->state & GDK_MOD1_MASK) == 0) { keyboard_right (container, event); handled = TRUE; } break; case GDK_KEY_Down: case GDK_KEY_KP_Down: /* Don't eat Alt-Down, as that is used for Open */ if ((event->state & GDK_MOD1_MASK) == 0) { keyboard_down (container, event); handled = TRUE; } break; case GDK_KEY_space: keyboard_space (container, event); handled = TRUE; break; #ifndef TAB_NAVIGATION_DISABLED case GDK_KEY_Tab: case GDK_KEY_ISO_Left_Tab: select_previous_or_next_icon (container, (event->state & GDK_SHIFT_MASK) == 0, event); handled = TRUE; break; #endif case GDK_KEY_Return: case GDK_KEY_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_KEY_Escape: handled = undo_stretching (container); break; case GDK_KEY_plus: case GDK_KEY_minus: case GDK_KEY_equal: case GDK_KEY_KP_Add: case GDK_KEY_KP_Subtract: case GDK_KEY_0: case GDK_KEY_KP_0: if (event->state & GDK_CONTROL_MASK) { handled = keyboard_stretching (container, event); } break; case GDK_KEY_F10: /* handle Ctrl+F10 because we want to display the * background popup even if something is selected. * The other cases are handled by popup_menu(). */ if (event->state & GDK_CONTROL_MASK) { handled = handle_popups (container, event, "context_click_background"); } break; case GDK_KEY_v: /* Eat Control + v to not enable type ahead */ if ((event->state & GDK_CONTROL_MASK) != 0) { handled = TRUE; } break; default: break; } } if (!handled) { handled = GTK_WIDGET_CLASS (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_KEY_slash /* don't steal slash key event, used for "go to" */ && event->keyval != GDK_KEY_BackSpace && event->keyval != GDK_KEY_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; } #if !GTK_CHECK_VERSION(3, 21, 0) static void draw_canvas_background (EelCanvas *canvas, #if GTK_CHECK_VERSION(3,0,0) cairo_t *cr) #else int x, int y, int width, int height) #endif { /* Don't chain up to the parent to avoid clearing and redrawing */ } #endif #if !GTK_CHECK_VERSION(3,0,0) 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); } #endif #if !GTK_CHECK_VERSION(3, 0, 0) 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); } #endif 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. */ #if GTK_CHECK_VERSION(3, 20, 0) stop_rubberbanding (container); #else stop_rubberbanding (container, GDK_CURRENT_TIME); #endif } } 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)) { g_signal_connect_swapped (caja_desktop_preferences, "changed::" CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT, G_CALLBACK (text_ellipsis_limit_changed_container_callback), container); } else { g_signal_connect_swapped (caja_icon_view_preferences, "changed::" CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT, G_CALLBACK (text_ellipsis_limit_changed_container_callback), 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; #if GTK_CHECK_VERSION(3, 0, 0) GTK_WIDGET_CLASS (class)->destroy = destroy; #else GTK_OBJECT_CLASS (class)->destroy = destroy; #endif /* 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, caja_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, caja_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, caja_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, caja_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, caja_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, caja_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, caja_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_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; #if GTK_CHECK_VERSION(3,2,0) gtk_widget_class_set_accessible_type (widget_class, caja_icon_container_accessible_get_type ()); #else widget_class->get_accessible = get_accessible; #endif #if GTK_CHECK_VERSION(3,0,0) widget_class->style_updated = style_updated; #else widget_class->style_set = style_set; widget_class->expose_event = expose_event; #endif widget_class->grab_notify = grab_notify_cb; canvas_class = EEL_CANVAS_CLASS (class); #if !GTK_CHECK_VERSION(3, 21, 0) canvas_class->draw_background = draw_canvas_background; #endif class->start_interactive_search = caja_icon_container_start_interactive_search; #if GTK_CHECK_VERSION(3,0,0) gtk_widget_class_install_style_property (widget_class, g_param_spec_boxed ("selection_box_rgba", "Selection Box RGBA", "Color of the selection box", GDK_TYPE_RGBA, G_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, g_param_spec_boxed ("light_info_rgba", "Light Info RGBA", "Color used for information text against a dark background", GDK_TYPE_RGBA, G_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, g_param_spec_boxed ("dark_info_rgba", "Dark Info RGBA", "Color used for information text against a light background", GDK_TYPE_RGBA, G_PARAM_READABLE)); #else 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)); #endif 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_KEY_f, GDK_CONTROL_MASK, "start_interactive_search", 0); gtk_binding_entry_add_signal (binding_set, GDK_KEY_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 const char * zoom_level_names[] = { "smallest", "smaller", "small", "standard", "large", "larger", "largest" }; static void text_ellipsis_limit_changed_callback (gpointer callback_data) { char **pref; unsigned int i; int one_limit; pref = g_settings_get_strv (caja_icon_view_preferences, 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 */ for (i = 0; i < G_N_ELEMENTS(zoom_level_names); i++) { if (get_text_ellipsis_limit_for_zoom (pref, zoom_level_names[i], &one_limit)) { text_ellipsis_limits[i] = one_limit; } } g_strfreev (pref); } static void desktop_text_ellipsis_limit_changed_callback (gpointer callback_data) { int pref; pref = g_settings_get_int (caja_desktop_preferences, 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)); #if !GTK_CHECK_VERSION(3,0,0) g_signal_connect_object (background, "appearance_changed", G_CALLBACK (update_label_color), container, 0); #endif 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); #if !GTK_CHECK_VERSION(3,0,0) /* read in theme-dependent data */ caja_icon_container_theme_changed (container); #endif if (!setup_prefs) { g_signal_connect_swapped (caja_icon_view_preferences, "changed::" CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT, G_CALLBACK (text_ellipsis_limit_changed_callback), NULL); text_ellipsis_limit_changed_callback (NULL); g_signal_connect_swapped (caja_icon_view_preferences, "changed::" CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT, G_CALLBACK (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 GTK_CHECK_VERSION(3, 20, 0) if (start_stretching (container, (GdkEvent *)event)) { #else if (start_stretching (container)) { #endif 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; CajaIcon *icon; container = CAJA_ICON_CONTAINER (data); 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_scrollable_get_hadjustment (GTK_SCROLLABLE (container))); vadj_v = gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))); h_page_size = gtk_adjustment_get_page_size (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container))); if (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; EelIRect bounds; GtkAllocation allocation; hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)); vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)); gtk_widget_get_allocation (GTK_WIDGET (container), &allocation); /* 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)) { 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)) { #if GTK_CHECK_VERSION (3, 0, 0) gtk_adjustment_set_value (hadj, bounds.x1 - allocation.width); } else { gtk_adjustment_set_value (hadj, bounds.x0); } } else { gtk_adjustment_set_value (vadj, bounds.y0); #else 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); #endif } } 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_scrollable_get_hadjustment (GTK_SCROLLABLE (container)); vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)); gtk_widget_get_allocation (GTK_WIDGET (container), &allocation); min_x = gtk_adjustment_get_value (hadj); max_x = min_x + allocation.width; min_y = gtk_adjustment_get_value (vadj); max_y = min_y + allocation.height; eel_canvas_c2w (EEL_CANVAS (container), min_x, min_y, &min_x, &min_y); eel_canvas_c2w (EEL_CANVAS (container), max_x, max_y, &max_x, &max_y); /* Do the iteration in reverse to get the render-order from top to * bottom for the prioritized thumbnails. */ for (node = g_list_last (container->details->icons); node != NULL; node = node->prev) { icon = node->data; if (icon_is_positioned (icon)) { eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item), &x0, &y0, &x1, &y1); eel_canvas_item_i2w (EEL_CANVAS_ITEM (icon->item)->parent, &x0, &y0); eel_canvas_item_i2w (EEL_CANVAS_ITEM (icon->item)->parent, &x1, &y1); 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) && g_strcmp0 (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); g_list_free_full (emblem_pixbufs, g_object_unref); 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); } #if GTK_CHECK_VERSION (3, 0, 0) gtk_widget_set_margin_start (details->rename_widget, 1); gtk_widget_set_margin_end (details->rename_widget, 1); gtk_widget_set_margin_top (details->rename_widget, 1); gtk_widget_set_margin_bottom (details->rename_widget, 1); #else gtk_misc_set_padding (GTK_MISC (details->rename_widget), 1, 1); #endif 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); } #if GTK_CHECK_VERSION (3, 0, 0) gtk_widget_show (details->rename_widget); gtk_widget_grab_focus (details->rename_widget); #endif eel_editable_label_select_region (EEL_EDITABLE_LABEL (details->rename_widget), start_offset, end_offset); #if !GTK_CHECK_VERSION (3, 0, 0) gtk_widget_show (details->rename_widget); gtk_widget_grab_focus (details->rename_widget); #endif 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; } #if !GTK_CHECK_VERSION(3,0,0) /* update the label color when the background changes */ void caja_icon_container_get_label_color (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]; } } static void setup_gc_with_fg (CajaIconContainer *container, int idx, guint32 color) { container->details->label_colors [idx] = eel_gdk_rgb_to_color (color); } 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); } #endif /* 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; #if GTK_CHECK_VERSION (3, 0, 0) if (is_desktop) { GtkStyleContext *context; context = gtk_widget_get_style_context (GTK_WIDGET (container)); gtk_style_context_add_class (context, "caja-desktop"); } #endif } 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) { #if !GTK_CHECK_VERSION(3,0,0) gboolean frame_text; gtk_widget_style_get (GTK_WIDGET (container), "frame_text", &frame_text, NULL); #endif if (container->details->drop_shadows_requested == use_drop_shadows) { return; } container->details->drop_shadows_requested = use_drop_shadows; #if GTK_CHECK_VERSION(3,0,0) container->details->use_drop_shadows = use_drop_shadows; #else container->details->use_drop_shadows = use_drop_shadows && !frame_text; #endif gtk_widget_queue_draw (GTK_WIDGET (container)); } /* handle theme changes */ #if !GTK_CHECK_VERSION(3,0,0) 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); } #endif void caja_icon_container_set_font (CajaIconContainer *container, const char *font) { g_return_if_fail (CAJA_IS_ICON_CONTAINER (container)); if (g_strcmp0 (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 *selection; CajaIcon *icon; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)); if (!widget) { return FALSE; } container = CAJA_ICON_CONTAINER (widget); icon = g_list_nth_data (container->details->icons, i); if (icon) { 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; CajaIcon *icon; caja_icon_container_accessible_update_selection (ATK_OBJECT (accessible)); priv = accessible_get_priv (ATK_OBJECT (accessible)); icon = g_list_nth_data (priv->selection, i); if (icon) { 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; CajaIcon *icon; GtkWidget *widget; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)); if (!widget) { return FALSE; } container = CAJA_ICON_CONTAINER (widget); icon = g_list_nth_data (container->details->icons, i); return icon ? icon->is_selected : FALSE; } static gboolean caja_icon_container_accessible_remove_selection (AtkSelection *accessible, int i) { CajaIconContainer *container; CajaIconContainerAccessiblePrivate *priv; 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); icon = g_list_nth_data (priv->selection, i); if (icon) { 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; CajaIcon *icon; GtkWidget *widget; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)); if (!widget) { return NULL; } container = CAJA_ICON_CONTAINER (widget); icon = g_list_nth_data (container->details->icons, i); if (icon) { 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); } #if GTK_CHECK_VERSION(3,2,0) static void caja_icon_container_accessible_init (CajaIconContainerAccessible *accessible) { } static void caja_icon_container_accessible_class_init (CajaIconContainerAccessibleClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass); accessible_parent_class = g_type_class_peek_parent (klass); gobject_class->finalize = caja_icon_container_accessible_finalize; atk_class->get_n_children = caja_icon_container_accessible_get_n_children; atk_class->ref_child = caja_icon_container_accessible_ref_child; atk_class->initialize = caja_icon_container_accessible_initialize; accessible_private_data_quark = g_quark_from_static_string ("icon-container-accessible-private-data"); } G_DEFINE_TYPE_WITH_CODE (CajaIconContainerAccessible, caja_icon_container_accessible, eel_canvas_accessible_get_type (), G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, caja_icon_container_accessible_action_interface_init) G_IMPLEMENT_INTERFACE (ATK_TYPE_SELECTION, caja_icon_container_accessible_selection_interface_init)); #else 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; } #endif #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 */