2008-02-29 Cosimo Cecchi <cosimoc@gnome.org>
[nautilus.git] / libnautilus-private / nautilus-icon-container.c
blob20bc2527519e43ea51681bb2fb79d9c5630e3cd6
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
3 /* nautilus-icon-container.c - Icon container widget.
5 Copyright (C) 1999, 2000 Free Software Foundation
6 Copyright (C) 2000, 2001 Eazel, Inc.
7 Copyright (C) 2002, 2003 Red Hat, Inc.
9 The Gnome Library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version.
14 The Gnome Library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Library General Public License for more details.
19 You should have received a copy of the GNU Library General Public
20 License along with the Gnome Library; see the file COPYING.LIB. If not,
21 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 Boston, MA 02111-1307, USA.
24 Authors: Ettore Perazzoli <ettore@gnu.org>,
25 Darin Adler <darin@bentspoon.com>
28 #include <config.h>
29 #include <math.h>
30 #include "nautilus-icon-container.h"
32 #include "nautilus-debug-log.h"
33 #include "nautilus-global-preferences.h"
34 #include "nautilus-icon-private.h"
35 #include "nautilus-lib-self-check-functions.h"
36 #include "nautilus-marshal.h"
37 #include <atk/atkaction.h>
38 #include <eel/eel-accessibility.h>
39 #include <eel/eel-background.h>
40 #include <eel/eel-vfs-extensions.h>
41 #include <eel/eel-gdk-pixbuf-extensions.h>
42 #include <eel/eel-gnome-extensions.h>
43 #include <eel/eel-gtk-extensions.h>
44 #include <eel/eel-art-extensions.h>
45 #include <eel/eel-editable-label.h>
46 #include <eel/eel-marshal.h>
47 #include <eel/eel-string.h>
48 #include <eel/eel-canvas-rect-ellipse.h>
49 #include <libgnomeui/gnome-icon-item.h>
50 #include <gdk/gdkkeysyms.h>
51 #include <gtk/gtkaccessible.h>
52 #include <gtk/gtklayout.h>
53 #include <gtk/gtkmain.h>
54 #include <gtk/gtksignal.h>
55 #include <glib/gi18n.h>
56 #include <libgnome/gnome-macros.h>
57 #include <stdio.h>
58 #include <string.h>
60 #define TAB_NAVIGATION_DISABLED
62 /* Interval for updating the rubberband selection, in milliseconds. */
63 #define RUBBERBAND_TIMEOUT_INTERVAL 10
65 /* Initial unpositioned icon value */
66 #define ICON_UNPOSITIONED_VALUE -1
68 /* Timeout for making the icon currently selected for keyboard operation visible.
69 * If this is 0, you can get into trouble with extra scrolling after holding
70 * down the arrow key for awhile when there are many items.
72 #define KEYBOARD_ICON_REVEAL_TIMEOUT 10
74 #define CONTEXT_MENU_TIMEOUT_INTERVAL 500
76 /* Maximum amount of milliseconds the mouse button is allowed to stay down
77 * and still be considered a click.
79 #define MAX_CLICK_TIME 1500
81 /* Button assignments. */
82 #define DRAG_BUTTON 1
83 #define RUBBERBAND_BUTTON 1
84 #define MIDDLE_BUTTON 2
85 #define CONTEXTUAL_MENU_BUTTON 3
86 #define DRAG_MENU_BUTTON 2
88 /* Maximum size (pixels) allowed for icons at the standard zoom level. */
89 #define MINIMUM_IMAGE_SIZE 24
90 #define MAXIMUM_IMAGE_SIZE 96
92 #define ICON_PAD_LEFT 4
93 #define ICON_PAD_RIGHT 4
94 #define ICON_BASE_WIDTH 96
96 #define ICON_PAD_TOP 4
97 #define ICON_PAD_BOTTOM 4
99 #define CONTAINER_PAD_LEFT 4
100 #define CONTAINER_PAD_TOP 4
101 #define CONTAINER_PAD_BOTTOM 4
103 #define STANDARD_ICON_GRID_WIDTH 155
105 #define TEXT_BESIDE_ICON_GRID_WIDTH 205
107 /* Desktop layout mode defines */
108 #define DESKTOP_PAD_HORIZONTAL 10
109 #define DESKTOP_PAD_VERTICAL 10
110 #define SNAP_SIZE_X 78
111 #define SNAP_SIZE_Y 20
113 /* Value used to protect against icons being dragged outside of the desktop bounds */
114 #define DESKTOP_ICON_SAFETY_PAD 10
116 #define DEFAULT_SELECTION_BOX_ALPHA 0x40
117 #define DEFAULT_HIGHLIGHT_ALPHA 0xff
118 #define DEFAULT_NORMAL_ALPHA 0xff
119 #define DEFAULT_PRELIGHT_ALPHA 0xff
120 #define DEFAULT_LIGHT_INFO_COLOR 0xAAAAFD
121 #define DEFAULT_DARK_INFO_COLOR 0x33337F
123 #define DEFAULT_NORMAL_ICON_RENDER_MODE 0
124 #define DEFAULT_PRELIGHT_ICON_RENDER_MODE 1
125 #define DEFAULT_NORMAL_ICON_SATURATION 255
126 #define DEFAULT_PRELIGHT_ICON_SATURATION 255
127 #define DEFAULT_NORMAL_ICON_BRIGHTNESS 255
128 #define DEFAULT_PRELIGHT_ICON_BRIGHTNESS 255
129 #define DEFAULT_NORMAL_ICON_LIGHTEN 0
130 #define DEFAULT_PRELIGHT_ICON_LIGHTEN 0
132 #define MINIMUM_EMBEDDED_TEXT_RECT_WIDTH 20
133 #define MINIMUM_EMBEDDED_TEXT_RECT_HEIGHT 20
135 /* If icon size is bigger than this, request large embedded text.
136 * Its selected so that the non-large text should fit in "normal" icon sizes
138 #define ICON_SIZE_FOR_LARGE_EMBEDDED_TEXT 55
140 /* From nautilus-icon-canvas-item.c */
141 #define MAX_TEXT_WIDTH_BESIDE 90
143 #define SNAP_HORIZONTAL(func,x) ((func ((double)((x) - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X) * SNAP_SIZE_X) + DESKTOP_PAD_HORIZONTAL)
144 #define SNAP_VERTICAL(func, y) ((func ((double)((y) - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y) * SNAP_SIZE_Y) + DESKTOP_PAD_VERTICAL)
146 #define SNAP_NEAREST_HORIZONTAL(x) SNAP_HORIZONTAL (eel_round, x)
147 #define SNAP_NEAREST_VERTICAL(y) SNAP_VERTICAL (eel_round, y)
149 #define SNAP_CEIL_HORIZONTAL(x) SNAP_HORIZONTAL (ceil, x)
150 #define SNAP_CEIL_VERTICAL(y) SNAP_VERTICAL (ceil, y)
152 /* Copied from NautilusIconContainer */
153 #define NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT 5000
155 enum {
156 ACTION_ACTIVATE,
157 ACTION_MENU,
158 LAST_ACTION
161 typedef struct {
162 GList *selection;
163 char *action_descriptions[LAST_ACTION];
164 } NautilusIconContainerAccessiblePrivate;
166 static GType nautilus_icon_container_accessible_get_type (void);
168 static void activate_selected_items (NautilusIconContainer *container);
169 static void activate_selected_items_alternate (NautilusIconContainer *container,
170 NautilusIcon *icon);
171 static void nautilus_icon_container_theme_changed (gpointer user_data);
172 static void compute_stretch (StretchState *start,
173 StretchState *current);
174 static NautilusIcon *get_first_selected_icon (NautilusIconContainer *container);
175 static NautilusIcon *get_nth_selected_icon (NautilusIconContainer *container,
176 int index);
177 static gboolean has_multiple_selection (NautilusIconContainer *container);
178 static gboolean all_selected (NautilusIconContainer *container);
179 static gboolean has_selection (NautilusIconContainer *container);
180 static void icon_destroy (NautilusIconContainer *container,
181 NautilusIcon *icon);
182 static void end_renaming_mode (NautilusIconContainer *container,
183 gboolean commit);
184 static NautilusIcon *get_icon_being_renamed (NautilusIconContainer *container);
185 static void finish_adding_new_icons (NautilusIconContainer *container);
186 static void update_label_color (EelBackground *background,
187 NautilusIconContainer *icon_container);
188 static void icon_get_bounding_box (NautilusIcon *icon,
189 int *x1_return,
190 int *y1_return,
191 int *x2_return,
192 int *y2_return);
193 static gboolean is_renaming (NautilusIconContainer *container);
194 static gboolean is_renaming_pending (NautilusIconContainer *container);
195 static void process_pending_icon_to_rename (NautilusIconContainer *container);
196 static void setup_label_gcs (NautilusIconContainer *container);
197 static void nautilus_icon_container_stop_monitor_top_left (NautilusIconContainer *container,
198 NautilusIconData *data,
199 gconstpointer client);
200 static void nautilus_icon_container_start_monitor_top_left (NautilusIconContainer *container,
201 NautilusIconData *data,
202 gconstpointer client,
203 gboolean large_text);
204 static void handle_vadjustment_changed (GtkAdjustment *adjustment,
205 NautilusIconContainer *container);
206 static GList * nautilus_icon_container_get_selected_icons (NautilusIconContainer *container);
207 static void nautilus_icon_container_update_visible_icons (NautilusIconContainer *container);
208 static void reveal_icon (NautilusIconContainer *container,
209 NautilusIcon *icon);
211 static void nautilus_icon_container_set_rtl_positions (NautilusIconContainer *container);
212 static double get_mirror_x_position (NautilusIconContainer *container,
213 NautilusIcon *icon,
214 double x);
216 static gpointer accessible_parent_class;
218 static GQuark accessible_private_data_quark = 0;
220 static const char *nautilus_icon_container_accessible_action_names[] = {
221 "activate",
222 "menu",
223 NULL
226 static const char *nautilus_icon_container_accessible_action_descriptions[] = {
227 "Activate selected items",
228 "Popup context menu",
229 NULL
232 GNOME_CLASS_BOILERPLATE (NautilusIconContainer, nautilus_icon_container,
233 EelCanvas, EEL_TYPE_CANVAS)
235 /* The NautilusIconContainer signals. */
236 enum {
237 ACTIVATE,
238 ACTIVATE_ALTERNATE,
239 BAND_SELECT_STARTED,
240 BAND_SELECT_ENDED,
241 BUTTON_PRESS,
242 CAN_ACCEPT_ITEM,
243 CONTEXT_CLICK_BACKGROUND,
244 CONTEXT_CLICK_SELECTION,
245 MIDDLE_CLICK,
246 GET_CONTAINER_URI,
247 GET_ICON_URI,
248 GET_ICON_DROP_TARGET_URI,
249 GET_STORED_ICON_POSITION,
250 ICON_POSITION_CHANGED,
251 ICON_TEXT_CHANGED,
252 ICON_STRETCH_STARTED,
253 ICON_STRETCH_ENDED,
254 RENAMING_ICON,
255 LAYOUT_CHANGED,
256 MOVE_COPY_ITEMS,
257 HANDLE_NETSCAPE_URL,
258 HANDLE_URI_LIST,
259 HANDLE_TEXT,
260 PREVIEW,
261 SELECTION_CHANGED,
262 ICON_ADDED,
263 ICON_REMOVED,
264 CLEARED,
265 START_INTERACTIVE_SEARCH,
266 LAST_SIGNAL
269 typedef struct {
270 int **icon_grid;
271 int *grid_memory;
272 int num_rows;
273 int num_columns;
274 gboolean tight;
275 } PlacementGrid;
277 static guint signals[LAST_SIGNAL];
279 /* Functions dealing with NautilusIcons. */
281 static void
282 icon_free (NautilusIcon *icon)
284 /* Destroy this canvas item; the parent will unref it. */
285 gtk_object_destroy (GTK_OBJECT (icon->item));
286 g_free (icon);
289 static gboolean
290 icon_is_positioned (const NautilusIcon *icon)
292 return icon->x != ICON_UNPOSITIONED_VALUE && icon->y != ICON_UNPOSITIONED_VALUE;
295 static void
296 icon_set_position (NautilusIcon *icon,
297 double x, double y)
299 NautilusIconContainer *container;
300 double pixels_per_unit;
301 int left, top, right, bottom;
302 int width;
303 int container_x, container_y, container_width, container_height;
304 EelDRect icon_bounds;
306 if (icon->x == x && icon->y == y) {
307 return;
310 container = NAUTILUS_ICON_CONTAINER (EEL_CANVAS_ITEM (icon->item)->canvas);
312 if (icon == get_icon_being_renamed (container)) {
313 end_renaming_mode (container, TRUE);
316 if (nautilus_icon_container_get_is_fixed_size (container)) {
317 /* FIXME: This should be:
319 container_x = GTK_WIDGET (container)->allocation.x;
320 container_y = GTK_WIDGET (container)->allocation.y;
321 container_width = GTK_WIDGET (container)->allocation.width;
322 container_height = GTK_WIDGET (container)->allocation.height;
324 But for some reason the widget allocation is sometimes not done
325 at startup, and the allocation is then only 45x60. which is
326 really bad.
328 For now, we have a cheesy workaround:
330 container_x = 0;
331 container_y = 0;
332 container_width = gdk_screen_width ();
333 container_height = gdk_screen_height ();
334 pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
335 /* Clip the position of the icon within our desktop bounds */
336 left = container_x / pixels_per_unit;
337 top = container_y / pixels_per_unit;
338 right = left + container_width / pixels_per_unit;
339 bottom = top + container_height / pixels_per_unit;
341 icon_bounds = nautilus_icon_canvas_item_get_icon_rectangle (icon->item);
342 width = icon_bounds.x1 - icon_bounds.x0;
344 if (nautilus_icon_container_is_layout_rtl(container)) {
345 if (x + width < left + DESKTOP_ICON_SAFETY_PAD) {
346 x = left + DESKTOP_ICON_SAFETY_PAD - width;
348 if (x + width > right) {
349 x = right - width;
351 } else {
352 if (x > right - DESKTOP_ICON_SAFETY_PAD) {
353 x = right - DESKTOP_ICON_SAFETY_PAD;
355 if (x < left) {
356 x = left;
359 if (y > bottom - DESKTOP_ICON_SAFETY_PAD) {
360 y = bottom - DESKTOP_ICON_SAFETY_PAD;
362 if (y < top) {
363 y = top;
367 if (icon->x == ICON_UNPOSITIONED_VALUE) {
368 icon->x = 0;
370 if (icon->y == ICON_UNPOSITIONED_VALUE) {
371 icon->y = 0;
374 eel_canvas_item_move (EEL_CANVAS_ITEM (icon->item),
375 x - icon->x,
376 y - icon->y);
378 icon->x = x;
379 icon->y = y;
382 static void
383 icon_get_size (NautilusIconContainer *container,
384 NautilusIcon *icon,
385 guint *size)
387 if (size != NULL) {
388 *size = MAX (nautilus_get_icon_size_for_zoom_level (container->details->zoom_level)
389 * icon->scale, NAUTILUS_ICON_SIZE_SMALLEST);
393 /* The icon_set_size function is used by the stretching user
394 * interface, which currently stretches in a way that keeps the aspect
395 * ratio. Later we might have a stretching interface that stretches Y
396 * separate from X and we will change this around.
398 static void
399 icon_set_size (NautilusIconContainer *container,
400 NautilusIcon *icon,
401 guint icon_size,
402 gboolean snap,
403 gboolean update_position)
405 guint old_size;
406 double scale;
408 icon_get_size (container, icon, &old_size);
409 if (icon_size == old_size) {
410 return;
413 scale = (double) icon_size /
414 nautilus_get_icon_size_for_zoom_level
415 (container->details->zoom_level);
416 nautilus_icon_container_move_icon (container, icon,
417 icon->x, icon->y,
418 scale, FALSE,
419 snap, update_position);
422 static void
423 icon_raise (NautilusIcon *icon)
425 EelCanvasItem *item, *band;
427 item = EEL_CANVAS_ITEM (icon->item);
428 band = NAUTILUS_ICON_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
430 eel_canvas_item_send_behind (item, band);
433 static void
434 emit_stretch_started (NautilusIconContainer *container, NautilusIcon *icon)
436 g_signal_emit (container,
437 signals[ICON_STRETCH_STARTED], 0,
438 icon->data);
441 static void
442 emit_stretch_ended (NautilusIconContainer *container, NautilusIcon *icon)
444 g_signal_emit (container,
445 signals[ICON_STRETCH_ENDED], 0,
446 icon->data);
449 static void
450 icon_toggle_selected (NautilusIconContainer *container,
451 NautilusIcon *icon)
453 end_renaming_mode (container, TRUE);
455 icon->is_selected = !icon->is_selected;
456 eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
457 "highlighted_for_selection", (gboolean) icon->is_selected,
458 NULL);
460 /* If the icon is deselected, then get rid of the stretch handles.
461 * No harm in doing the same if the item is newly selected.
463 if (icon == container->details->stretch_icon) {
464 container->details->stretch_icon = NULL;
465 nautilus_icon_canvas_item_set_show_stretch_handles (icon->item, FALSE);
466 /* snap the icon if necessary */
467 if (container->details->keep_aligned) {
468 nautilus_icon_container_move_icon (container,
469 icon,
470 icon->x, icon->y,
471 icon->scale,
472 FALSE, TRUE, TRUE);
475 emit_stretch_ended (container, icon);
478 /* Raise each newly-selected icon to the front as it is selected. */
479 if (icon->is_selected) {
480 icon_raise (icon);
484 /* Select an icon. Return TRUE if selection has changed. */
485 static gboolean
486 icon_set_selected (NautilusIconContainer *container,
487 NautilusIcon *icon,
488 gboolean select)
490 g_assert (select == FALSE || select == TRUE);
491 g_assert (icon->is_selected == FALSE || icon->is_selected == TRUE);
493 if (select == icon->is_selected) {
494 return FALSE;
497 icon_toggle_selected (container, icon);
498 g_assert (select == icon->is_selected);
499 return TRUE;
502 static void
503 icon_get_bounding_box (NautilusIcon *icon,
504 int *x1_return, int *y1_return,
505 int *x2_return, int *y2_return)
507 double x1, y1, x2, y2;
509 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
510 &x1, &y1, &x2, &y2);
512 *x1_return = x1;
513 *y1_return = y1;
514 *x2_return = x2;
515 *y2_return = y2;
518 /* Utility functions for NautilusIconContainer. */
520 gboolean
521 nautilus_icon_container_scroll (NautilusIconContainer *container,
522 int delta_x, int delta_y)
524 GtkAdjustment *hadj, *vadj;
525 int old_h_value, old_v_value;
527 hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
528 vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
530 /* Store the old ajustment values so we can tell if we
531 * ended up actually scrolling. We may not have in a case
532 * where the resulting value got pinned to the adjustment
533 * min or max.
535 old_h_value = hadj->value;
536 old_v_value = vadj->value;
538 eel_gtk_adjustment_set_value (hadj, hadj->value + delta_x);
539 eel_gtk_adjustment_set_value (vadj, vadj->value + delta_y);
541 /* return TRUE if we did scroll */
542 return hadj->value != old_h_value || vadj->value != old_v_value;
545 static void
546 pending_icon_to_reveal_destroy_callback (NautilusIconCanvasItem *item,
547 NautilusIconContainer *container)
549 g_assert (NAUTILUS_IS_ICON_CONTAINER (container));
550 g_assert (container->details->pending_icon_to_reveal != NULL);
551 g_assert (container->details->pending_icon_to_reveal->item == item);
553 container->details->pending_icon_to_reveal = NULL;
556 static NautilusIcon*
557 get_pending_icon_to_reveal (NautilusIconContainer *container)
559 return container->details->pending_icon_to_reveal;
562 static void
563 set_pending_icon_to_reveal (NautilusIconContainer *container, NautilusIcon *icon)
565 NautilusIcon *old_icon;
567 old_icon = container->details->pending_icon_to_reveal;
569 if (icon == old_icon) {
570 return;
573 if (old_icon != NULL) {
574 g_signal_handlers_disconnect_by_func
575 (old_icon->item,
576 G_CALLBACK (pending_icon_to_reveal_destroy_callback),
577 container);
580 if (icon != NULL) {
581 g_signal_connect (icon->item, "destroy",
582 G_CALLBACK (pending_icon_to_reveal_destroy_callback),
583 container);
586 container->details->pending_icon_to_reveal = icon;
589 static void
590 item_get_canvas_bounds (EelCanvasItem *item, EelIRect *bounds)
592 EelDRect world_rect;
594 eel_canvas_item_get_bounds (item,
595 &world_rect.x0,
596 &world_rect.y0,
597 &world_rect.x1,
598 &world_rect.y1);
599 eel_canvas_item_i2w (item->parent,
600 &world_rect.x0,
601 &world_rect.y0);
602 eel_canvas_item_i2w (item->parent,
603 &world_rect.x1,
604 &world_rect.y1);
605 eel_canvas_w2c (item->canvas,
606 world_rect.x0,
607 world_rect.y0,
608 &bounds->x0,
609 &bounds->y0);
610 eel_canvas_w2c (item->canvas,
611 world_rect.x1,
612 world_rect.y1,
613 &bounds->x1,
614 &bounds->y1);
617 static void
618 reveal_icon (NautilusIconContainer *container,
619 NautilusIcon *icon)
621 NautilusIconContainerDetails *details;
622 GtkAllocation *allocation;
623 GtkAdjustment *hadj, *vadj;
624 EelIRect bounds;
626 if (!icon_is_positioned (icon)) {
627 set_pending_icon_to_reveal (container, icon);
628 return;
631 set_pending_icon_to_reveal (container, NULL);
633 details = container->details;
634 allocation = &GTK_WIDGET (container)->allocation;
636 hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
637 vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
639 item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), &bounds);
640 if (bounds.y0 < vadj->value) {
641 eel_gtk_adjustment_set_value (vadj, bounds.y0);
642 } else if (bounds.y1 > vadj->value + allocation->height) {
643 eel_gtk_adjustment_set_value
644 (vadj, bounds.y1 - allocation->height);
647 if (bounds.x0 < hadj->value) {
648 eel_gtk_adjustment_set_value (hadj, bounds.x0);
649 } else if (bounds.x1 > hadj->value + allocation->width) {
650 eel_gtk_adjustment_set_value
651 (hadj, bounds.x1 - allocation->width);
655 static void
656 process_pending_icon_to_reveal (NautilusIconContainer *container)
658 NautilusIcon *pending_icon_to_reveal;
660 pending_icon_to_reveal = get_pending_icon_to_reveal (container);
662 if (pending_icon_to_reveal != NULL) {
663 reveal_icon (container, pending_icon_to_reveal);
667 static gboolean
668 keyboard_icon_reveal_timeout_callback (gpointer data)
670 NautilusIconContainer *container;
671 NautilusIcon *icon;
673 container = NAUTILUS_ICON_CONTAINER (data);
674 icon = container->details->keyboard_icon_to_reveal;
676 g_assert (icon != NULL);
678 /* Only reveal the icon if it's still the keyboard focus or if
679 * it's still selected. Someone originally thought we should
680 * cancel this reveal if the user manages to sneak a direct
681 * scroll in before the timeout fires, but we later realized
682 * this wouldn't actually be an improvement
683 * (see bugzilla.gnome.org 40612).
685 if (icon == container->details->keyboard_focus
686 || icon->is_selected) {
687 reveal_icon (container, icon);
689 container->details->keyboard_icon_reveal_timer_id = 0;
691 return FALSE;
694 static void
695 unschedule_keyboard_icon_reveal (NautilusIconContainer *container)
697 NautilusIconContainerDetails *details;
699 details = container->details;
701 if (details->keyboard_icon_reveal_timer_id != 0) {
702 g_source_remove (details->keyboard_icon_reveal_timer_id);
706 static void
707 schedule_keyboard_icon_reveal (NautilusIconContainer *container,
708 NautilusIcon *icon)
710 NautilusIconContainerDetails *details;
712 details = container->details;
714 unschedule_keyboard_icon_reveal (container);
716 details->keyboard_icon_to_reveal = icon;
717 details->keyboard_icon_reveal_timer_id
718 = g_timeout_add (KEYBOARD_ICON_REVEAL_TIMEOUT,
719 keyboard_icon_reveal_timeout_callback,
720 container);
723 static void
724 clear_keyboard_focus (NautilusIconContainer *container)
726 if (container->details->keyboard_focus != NULL) {
727 eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
728 "highlighted_as_keyboard_focus", 0,
729 NULL);
732 container->details->keyboard_focus = NULL;
735 /* Set @icon as the icon currently selected for keyboard operations. */
736 static void
737 set_keyboard_focus (NautilusIconContainer *container,
738 NautilusIcon *icon)
740 g_assert (icon != NULL);
742 if (icon == container->details->keyboard_focus) {
743 return;
746 clear_keyboard_focus (container);
748 container->details->keyboard_focus = icon;
750 eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
751 "highlighted_as_keyboard_focus", 1,
752 NULL);
755 static void
756 set_keyboard_rubberband_start (NautilusIconContainer *container,
757 NautilusIcon *icon)
759 container->details->keyboard_rubberband_start = icon;
762 static void
763 clear_keyboard_rubberband_start (NautilusIconContainer *container)
765 container->details->keyboard_rubberband_start = NULL;
768 static void
769 get_all_icon_bounds (NautilusIconContainer *container,
770 double *x1, double *y1,
771 double *x2, double *y2)
773 /* FIXME bugzilla.gnome.org 42477: Do we have to do something about the rubberband
774 * here? Any other non-icon items?
776 eel_canvas_item_get_bounds
777 (EEL_CANVAS (container)->root,
778 x1, y1, x2, y2);
781 /* Don't preserve visible white space the next time the scroll region
782 * is recomputed when the container is not empty. */
783 void
784 nautilus_icon_container_reset_scroll_region (NautilusIconContainer *container)
786 container->details->reset_scroll_region_trigger = TRUE;
789 /* Set a new scroll region without eliminating any of the currently-visible area. */
790 static void
791 canvas_set_scroll_region_include_visible_area (EelCanvas *canvas,
792 double x1, double y1,
793 double x2, double y2)
795 double old_x1, old_y1, old_x2, old_y2;
796 double old_scroll_x, old_scroll_y;
797 double height, width;
799 eel_canvas_get_scroll_region (canvas, &old_x1, &old_y1, &old_x2, &old_y2);
801 width = (GTK_WIDGET (canvas)->allocation.width) / canvas->pixels_per_unit;
802 height = (GTK_WIDGET (canvas)->allocation.height) / canvas->pixels_per_unit;
804 old_scroll_x = gtk_layout_get_hadjustment (GTK_LAYOUT (canvas))->value;
805 old_scroll_y = gtk_layout_get_vadjustment (GTK_LAYOUT (canvas))->value;
807 x1 = MIN (x1, old_x1 + old_scroll_x);
808 y1 = MIN (y1, old_y1 + old_scroll_y);
809 x2 = MAX (x2, old_x1 + old_scroll_x + width);
810 y2 = MAX (y2, old_y1 + old_scroll_y + height);
812 eel_canvas_set_scroll_region
813 (canvas, x1, y1, x2, y2);
816 void
817 nautilus_icon_container_update_scroll_region (NautilusIconContainer *container)
819 double x1, y1, x2, y2;
820 double pixels_per_unit;
821 GtkAdjustment *hadj, *vadj;
822 float step_increment;
823 GtkAllocation *allocation;
824 gboolean reset_scroll_region;
826 if (nautilus_icon_container_get_is_fixed_size (container)) {
827 pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
829 /* Set the scroll region to the size of the container allocation */
830 allocation = &GTK_WIDGET (container)->allocation;
831 eel_canvas_set_scroll_region
832 (EEL_CANVAS (container),
833 (double) - container->details->left_margin / pixels_per_unit,
834 (double) - container->details->top_margin / pixels_per_unit,
835 ((double) (allocation->width - 1)
836 - container->details->left_margin
837 - container->details->right_margin)
838 / pixels_per_unit,
839 ((double) (allocation->height - 1)
840 - container->details->top_margin
841 - container->details->bottom_margin)
842 / pixels_per_unit);
843 return;
846 reset_scroll_region = container->details->reset_scroll_region_trigger
847 || nautilus_icon_container_is_empty (container)
848 || nautilus_icon_container_is_auto_layout (container);
850 /* The trigger is only cleared when container is non-empty, so
851 * callers can reliably reset the scroll region when an item
852 * is added even if extraneous relayouts are called when the
853 * window is still empty.
855 if (!nautilus_icon_container_is_empty (container)) {
856 container->details->reset_scroll_region_trigger = FALSE;
859 get_all_icon_bounds (container, &x1, &y1, &x2, &y2);
861 /* Auto-layout assumes a 0, 0 scroll origin */
862 if (nautilus_icon_container_is_auto_layout (container)) {
863 x1 = 0;
864 y1 = 0;
865 } else {
866 x1 -= CONTAINER_PAD_LEFT;
867 y1 -= CONTAINER_PAD_TOP;
870 y2 += CONTAINER_PAD_BOTTOM;
872 if (reset_scroll_region) {
873 eel_canvas_set_scroll_region
874 (EEL_CANVAS (container),
875 x1, y1, x2, y2);
876 } else {
877 canvas_set_scroll_region_include_visible_area
878 (EEL_CANVAS (container),
879 x1, y1, x2, y2);
882 hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
883 vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
885 /* Scroll by 1/4 icon each time you click. */
886 step_increment = nautilus_get_icon_size_for_zoom_level
887 (container->details->zoom_level) / 4;
888 if (hadj->step_increment != step_increment) {
889 hadj->step_increment = step_increment;
890 gtk_adjustment_changed (hadj);
892 if (vadj->step_increment != step_increment) {
893 vadj->step_increment = step_increment;
894 gtk_adjustment_changed (vadj);
897 /* Now that we have a new scroll region, clamp the
898 * adjustments so we are within the valid scroll area.
900 eel_gtk_adjustment_clamp_value (hadj);
901 eel_gtk_adjustment_clamp_value (vadj);
904 * In RTL mode, when displayed force horizontal scrollbar to the
905 * right side.
908 if (nautilus_icon_container_is_layout_rtl(container))
909 gtk_adjustment_set_value (hadj, hadj->upper - hadj->page_size);
912 static int
913 compare_icons (gconstpointer a, gconstpointer b, gpointer icon_container)
915 NautilusIconContainerClass *klass;
916 const NautilusIcon *icon_a, *icon_b;
918 icon_a = a;
919 icon_b = b;
920 klass = NAUTILUS_ICON_CONTAINER_GET_CLASS (icon_container);
922 return klass->compare_icons (icon_container, icon_a->data, icon_b->data);
925 static void
926 sort_icons (NautilusIconContainer *container,
927 GList **icons)
929 NautilusIconContainerClass *klass;
931 klass = NAUTILUS_ICON_CONTAINER_GET_CLASS (container);
932 g_return_if_fail (klass->compare_icons != NULL);
934 *icons = g_list_sort_with_data (*icons, compare_icons, container);
937 static void
938 resort (NautilusIconContainer *container)
940 sort_icons (container, &container->details->icons);
943 #if 0
944 static double
945 get_grid_width (NautilusIconContainer *container)
947 if (container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) {
948 return TEXT_BESIDE_ICON_GRID_WIDTH;
949 } else {
950 return STANDARD_ICON_GRID_WIDTH;
953 #endif
954 typedef struct {
955 double width;
956 double height;
957 double x_offset;
958 double y_offset;
959 } IconPositions;
961 static void
962 lay_down_one_line (NautilusIconContainer *container,
963 GList *line_start,
964 GList *line_end,
965 double y,
966 double max_height,
967 GArray *positions)
969 GList *p;
970 NautilusIcon *icon;
971 double x, y_offset;
972 IconPositions *position;
973 int i;
974 gboolean is_rtl;
976 is_rtl = nautilus_icon_container_is_layout_rtl (container);
978 /* Lay out the icons along the baseline. */
979 x = ICON_PAD_LEFT;
980 i = 0;
981 for (p = line_start; p != line_end; p = p->next) {
982 icon = p->data;
984 position = &g_array_index (positions, IconPositions, i++);
986 if (container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) {
987 y_offset = (max_height - position->height) / 2;
988 } else {
989 y_offset = position->y_offset;
992 icon_set_position
993 (icon,
994 is_rtl ? get_mirror_x_position (container, icon, x + position->x_offset) : x + position->x_offset,
995 y + y_offset);
997 icon->saved_ltr_x = is_rtl ? get_mirror_x_position (container, icon, icon->x) : icon->x;
999 x += position->width;
1003 static void
1004 lay_down_icons_horizontal (NautilusIconContainer *container,
1005 GList *icons,
1006 double start_y)
1008 GList *p, *line_start;
1009 NautilusIcon *icon;
1010 double canvas_width, y, canvas_height;
1011 GArray *positions;
1012 IconPositions *position;
1013 EelDRect bounds;
1014 EelDRect icon_bounds;
1015 EelDRect text_bounds;
1016 EelCanvasItem *item;
1017 double max_height_above, max_height_below;
1018 double height_above, height_below;
1019 double line_width;
1020 gboolean gridded_layout;
1021 double grid_width;
1022 double max_text_width, max_icon_width;
1023 int icon_width;
1024 int i;
1026 g_assert (NAUTILUS_IS_ICON_CONTAINER (container));
1028 if (icons == NULL) {
1029 return;
1032 positions = g_array_new (FALSE, FALSE, sizeof (IconPositions));
1034 /* Lay out icons a line at a time. */
1035 canvas_width = CANVAS_WIDTH(container);
1036 canvas_height = CANVAS_HEIGHT(container);
1038 max_icon_width = max_text_width = 0.0;
1040 if (container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) {
1041 /* Would it be worth caching these bounds for the next loop? */
1042 for (p = icons; p != NULL; p = p->next) {
1043 icon = p->data;
1045 icon_bounds = nautilus_icon_canvas_item_get_icon_rectangle (icon->item);
1046 max_icon_width = MAX (max_icon_width, ceil (icon_bounds.x1 - icon_bounds.x0));
1048 text_bounds = nautilus_icon_canvas_item_get_text_rectangle (icon->item);
1049 max_text_width = MAX (max_text_width, ceil (text_bounds.x1 - text_bounds.x0));
1052 grid_width = max_icon_width + max_text_width + ICON_PAD_LEFT + ICON_PAD_RIGHT;
1053 } else {
1054 grid_width = STANDARD_ICON_GRID_WIDTH;
1057 gridded_layout = !nautilus_icon_container_is_tighter_layout (container);
1059 line_width = container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE ? ICON_PAD_LEFT : 0;
1060 line_start = icons;
1061 y = start_y;
1062 i = 0;
1064 max_height_above = 0;
1065 max_height_below = 0;
1066 for (p = icons; p != NULL; p = p->next) {
1067 icon = p->data;
1069 /* Get the width of the icon. */
1070 item = EEL_CANVAS_ITEM (icon->item);
1072 /* Assume it's only one level hierarchy to avoid costly affine calculations */
1073 eel_canvas_item_get_bounds (item,
1074 &bounds.x0, &bounds.y0,
1075 &bounds.x1, &bounds.y1);
1077 icon_bounds = nautilus_icon_canvas_item_get_icon_rectangle (icon->item);
1078 text_bounds = nautilus_icon_canvas_item_get_text_rectangle (icon->item);
1080 if (gridded_layout) {
1081 icon_width = ceil ((bounds.x1 - bounds.x0)/grid_width) * grid_width;
1084 } else {
1085 icon_width = (bounds.x1 - bounds.x0) + ICON_PAD_RIGHT + 8; /* 8 pixels extra for fancy selection box */
1088 /* Calculate size above/below baseline */
1089 height_above = icon_bounds.y1 - bounds.y0;
1090 height_below = bounds.y1 - icon_bounds.y1;
1092 /* If this icon doesn't fit, it's time to lay out the line that's queued up. */
1093 if (line_start != p && line_width + icon_width > canvas_width ) {
1094 if (container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) {
1095 y += ICON_PAD_TOP;
1096 } else {
1097 /* Advance to the baseline. */
1098 y += ICON_PAD_TOP + max_height_above;
1101 lay_down_one_line (container, line_start, p, y, max_height_above, positions);
1103 if (container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) {
1104 y += max_height_above + max_height_below + ICON_PAD_BOTTOM;
1105 } else {
1106 /* Advance to next line. */
1107 y += max_height_below + ICON_PAD_BOTTOM;
1110 line_width = container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE ? ICON_PAD_LEFT : 0;
1111 line_start = p;
1112 i = 0;
1114 max_height_above = height_above;
1115 max_height_below = height_below;
1116 } else {
1117 if (height_above > max_height_above) {
1118 max_height_above = height_above;
1120 if (height_below > max_height_below) {
1121 max_height_below = height_below;
1125 g_array_set_size (positions, i + 1);
1126 position = &g_array_index (positions, IconPositions, i++);
1127 position->width = icon_width;
1128 position->height = icon_bounds.y1 - icon_bounds.y0;
1130 if (container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) {
1131 if (gridded_layout) {
1132 position->x_offset = max_icon_width + ICON_PAD_LEFT + ICON_PAD_RIGHT - (icon_bounds.x1 - icon_bounds.x0);
1133 } else {
1134 position->x_offset = icon_width - ((icon_bounds.x1 - icon_bounds.x0) + (text_bounds.x1 - text_bounds.x0));
1136 position->y_offset = 0;
1137 } else {
1138 position->x_offset = (icon_width - (icon_bounds.x1 - icon_bounds.x0)) / 2;
1139 position->y_offset = icon_bounds.y0 - icon_bounds.y1;
1142 /* Add this icon. */
1143 line_width += icon_width;
1146 /* Lay down that last line of icons. */
1147 if (line_start != NULL) {
1148 if (container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) {
1149 y += ICON_PAD_TOP;
1150 } else {
1151 /* Advance to the baseline. */
1152 y += ICON_PAD_TOP + max_height_above;
1155 lay_down_one_line (container, line_start, NULL, y, max_height_above, positions);
1157 /* Advance to next line. */
1158 y += max_height_below + ICON_PAD_BOTTOM;
1161 g_array_free (positions, TRUE);
1164 static void
1165 snap_position (NautilusIconContainer *container,
1166 NautilusIcon *icon,
1167 int *x, int *y)
1169 int center_x;
1170 int baseline_y;
1171 int icon_width;
1172 int icon_height;
1173 EelDRect icon_position;
1175 icon_position = nautilus_icon_canvas_item_get_icon_rectangle (icon->item);
1176 icon_width = icon_position.x1 - icon_position.x0;
1177 icon_height = icon_position.y1 - icon_position.y0;
1179 if (nautilus_icon_container_is_layout_rtl (container))
1180 *x = get_mirror_x_position (container, icon, *x);
1182 if (*x + icon_width / 2 < DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X) {
1183 *x = DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X - icon_width / 2;
1186 if (*y + icon_height < DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y) {
1187 *y = DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y - icon_height;
1190 center_x = *x + icon_width / 2;
1191 *x = SNAP_NEAREST_HORIZONTAL (center_x) - (icon_width / 2);
1192 if (nautilus_icon_container_is_layout_rtl (container)) {
1193 *x = get_mirror_x_position (container, icon, *x);
1197 /* Find the grid position vertically and place on the proper baseline */
1198 baseline_y = *y + icon_height;
1199 baseline_y = SNAP_NEAREST_VERTICAL (baseline_y);
1200 *y = baseline_y - icon_height;
1203 static int
1204 compare_icons_by_position (gconstpointer a, gconstpointer b)
1206 NautilusIcon *icon_a, *icon_b;
1207 int x1, y1, x2, y2;
1208 int center_a;
1209 int center_b;
1211 icon_a = (NautilusIcon*)a;
1212 icon_b = (NautilusIcon*)b;
1214 icon_get_bounding_box (icon_a, &x1, &y1, &x2, &y2);
1215 center_a = x1 + (x2 - x1) / 2;
1216 icon_get_bounding_box (icon_b, &x1, &y1, &x2, &y2);
1217 center_b = x1 + (x2 - x1) / 2;
1219 return center_a == center_b ?
1220 icon_a->y - icon_b->y :
1221 center_a - center_b;
1224 static PlacementGrid *
1225 placement_grid_new (NautilusIconContainer *container, gboolean tight)
1227 PlacementGrid *grid;
1228 int width, height;
1229 int num_columns;
1230 int num_rows;
1231 int i;
1233 /* Get container dimensions */
1234 width = CANVAS_WIDTH(container);
1235 height = CANVAS_HEIGHT(container);
1237 num_columns = width / SNAP_SIZE_X;
1238 num_rows = height / SNAP_SIZE_Y;
1240 if (num_columns == 0 || num_rows == 0) {
1241 return NULL;
1244 grid = g_new0 (PlacementGrid, 1);
1245 grid->tight = tight;
1246 grid->num_columns = num_columns;
1247 grid->num_rows = num_rows;
1249 grid->grid_memory = g_new0 (int, (num_rows * num_columns));
1250 grid->icon_grid = g_new0 (int *, num_columns);
1252 for (i = 0; i < num_columns; i++) {
1253 grid->icon_grid[i] = grid->grid_memory + (i * num_rows);
1256 return grid;
1259 static void
1260 placement_grid_free (PlacementGrid *grid)
1262 g_free (grid->icon_grid);
1263 g_free (grid->grid_memory);
1264 g_free (grid);
1267 static gboolean
1268 placement_grid_position_is_free (PlacementGrid *grid, EelIRect pos)
1270 int x, y;
1272 g_return_val_if_fail (pos.x0 >= 0 && pos.x0 < grid->num_columns, TRUE);
1273 g_return_val_if_fail (pos.y0 >= 0 && pos.y0 < grid->num_rows, TRUE);
1274 g_return_val_if_fail (pos.x1 >= 0 && pos.x1 < grid->num_columns, TRUE);
1275 g_return_val_if_fail (pos.y1 >= 0 && pos.y1 < grid->num_rows, TRUE);
1277 for (x = pos.x0; x <= pos.x1; x++) {
1278 for (y = pos.y0; y <= pos.y1; y++) {
1279 if (grid->icon_grid[x][y] != 0) {
1280 return FALSE;
1285 return TRUE;
1288 static void
1289 placement_grid_mark (PlacementGrid *grid, EelIRect pos)
1291 int x, y;
1293 g_return_if_fail (pos.x0 >= 0 && pos.x0 < grid->num_columns);
1294 g_return_if_fail (pos.y0 >= 0 && pos.y0 < grid->num_rows);
1295 g_return_if_fail (pos.x1 >= 0 && pos.x1 < grid->num_columns);
1296 g_return_if_fail (pos.y1 >= 0 && pos.y1 < grid->num_rows);
1298 for (x = pos.x0; x <= pos.x1; x++) {
1299 for (y = pos.y0; y <= pos.y1; y++) {
1300 grid->icon_grid[x][y] = 1;
1305 static void
1306 canvas_position_to_grid_position (PlacementGrid *grid,
1307 EelIRect canvas_position,
1308 EelIRect *grid_position)
1310 /* The first causes minimal moving around during a snap, but
1311 * can end up with partially overlapping icons. The second one won't
1312 * allow any overlapping, but can cause more movement to happen
1313 * during a snap. */
1314 if (grid->tight) {
1315 grid_position->x0 = ceil ((double)(canvas_position.x0 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
1316 grid_position->y0 = ceil ((double)(canvas_position.y0 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
1317 grid_position->x1 = floor ((double)(canvas_position.x1 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
1318 grid_position->y1 = floor ((double)(canvas_position.y1 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
1319 } else {
1320 grid_position->x0 = floor ((double)(canvas_position.x0 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
1321 grid_position->y0 = floor ((double)(canvas_position.y0 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
1322 grid_position->x1 = floor ((double)(canvas_position.x1 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
1323 grid_position->y1 = floor ((double)(canvas_position.y1 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
1326 grid_position->x0 = CLAMP (grid_position->x0, 0, grid->num_columns - 1);
1327 grid_position->y0 = CLAMP (grid_position->y0, 0, grid->num_rows - 1);
1328 grid_position->x1 = CLAMP (grid_position->x1, grid_position->x0, grid->num_columns - 1);
1329 grid_position->y1 = CLAMP (grid_position->y1, grid_position->y0, grid->num_rows - 1);
1332 static void
1333 placement_grid_mark_icon (PlacementGrid *grid, NautilusIcon *icon)
1335 EelIRect icon_pos;
1336 EelIRect grid_pos;
1338 icon_get_bounding_box (icon,
1339 &icon_pos.x0, &icon_pos.y0,
1340 &icon_pos.x1, &icon_pos.y1);
1341 canvas_position_to_grid_position (grid,
1342 icon_pos,
1343 &grid_pos);
1344 placement_grid_mark (grid, grid_pos);
1347 static void
1348 find_empty_location (NautilusIconContainer *container,
1349 PlacementGrid *grid,
1350 NautilusIcon *icon,
1351 int start_x,
1352 int start_y,
1353 int *x,
1354 int *y)
1356 double icon_width, icon_height;
1357 int canvas_width;
1358 int canvas_height;
1359 EelIRect icon_position;
1360 EelDRect pixbuf_rect;
1361 gboolean collision;
1363 /* Get container dimensions */
1364 canvas_width = CANVAS_WIDTH(container);
1365 canvas_height = CANVAS_HEIGHT(container);
1367 icon_get_bounding_box (icon,
1368 &icon_position.x0, &icon_position.y0,
1369 &icon_position.x1, &icon_position.y1);
1370 icon_width = icon_position.x1 - icon_position.x0;
1371 icon_height = icon_position.y1 - icon_position.y0;
1373 pixbuf_rect = nautilus_icon_canvas_item_get_icon_rectangle (icon->item);
1375 /* Start the icon on a grid location */
1376 snap_position (container, icon, &start_x, &start_y);
1378 icon_position.x0 = start_x;
1379 icon_position.y0 = start_y;
1380 icon_position.x1 = icon_position.x0 + icon_width;
1381 icon_position.y1 = icon_position.y0 + icon_height;
1383 do {
1384 EelIRect grid_position;
1386 collision = FALSE;
1388 canvas_position_to_grid_position (grid,
1389 icon_position,
1390 &grid_position);
1392 if (!placement_grid_position_is_free (grid, grid_position)) {
1393 icon_position.y0 += SNAP_SIZE_Y;
1394 icon_position.y1 = icon_position.y0 + icon_height;
1396 if (icon_position.y1 + DESKTOP_PAD_VERTICAL > canvas_height) {
1397 /* Move to the next column */
1398 icon_position.y0 = DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y - (pixbuf_rect.y1 - pixbuf_rect.y0);
1399 while (icon_position.y0 < DESKTOP_PAD_VERTICAL) {
1400 icon_position.y0 += SNAP_SIZE_Y;
1402 icon_position.y1 = icon_position.y0 + icon_height;
1404 icon_position.x0 += SNAP_SIZE_X;
1405 icon_position.x1 = icon_position.x0 + icon_width;
1408 collision = TRUE;
1410 } while (collision && (icon_position.x1 < canvas_width));
1412 *x = icon_position.x0;
1413 *y = icon_position.y0;
1416 static void
1417 align_icons (NautilusIconContainer *container)
1419 GList *unplaced_icons;
1420 GList *l;
1421 PlacementGrid *grid;
1423 unplaced_icons = g_list_copy (container->details->icons);
1425 unplaced_icons = g_list_sort (unplaced_icons,
1426 compare_icons_by_position);
1428 if (nautilus_icon_container_is_layout_rtl (container)) {
1429 unplaced_icons = g_list_reverse (unplaced_icons);
1432 grid = placement_grid_new (container, TRUE);
1434 if (!grid) {
1435 return;
1438 for (l = unplaced_icons; l != NULL; l = l->next) {
1439 NautilusIcon *icon;
1440 int x, y;
1442 icon = l->data;
1443 x = icon->saved_ltr_x;
1444 y = icon->y;
1445 find_empty_location (container, grid,
1446 icon, x, y, &x, &y);
1448 icon_set_position (icon, x, y);
1449 icon->saved_ltr_x = icon->x;
1450 placement_grid_mark_icon (grid, icon);
1453 g_list_free (unplaced_icons);
1455 placement_grid_free (grid);
1457 if (nautilus_icon_container_is_layout_rtl (container)) {
1458 nautilus_icon_container_set_rtl_positions (container);
1462 static double
1463 get_mirror_x_position (NautilusIconContainer *container, NautilusIcon *icon, double x)
1465 EelDRect icon_bounds;
1467 icon_bounds = nautilus_icon_canvas_item_get_icon_rectangle (icon->item);
1469 return CANVAS_WIDTH(container) - x - (icon_bounds.x1 - icon_bounds.x0);
1472 static void
1473 nautilus_icon_container_set_rtl_positions (NautilusIconContainer *container)
1475 GList *l;
1476 NautilusIcon *icon;
1477 double x;
1479 if (!container->details->icons) {
1480 return;
1483 for (l = container->details->icons; l != NULL; l = l->next) {
1484 icon = l->data;
1485 x = get_mirror_x_position (container, icon, icon->saved_ltr_x);
1486 icon_set_position (icon, x, icon->y);
1490 static void
1491 lay_down_icons_vertical (NautilusIconContainer *container, GList *icons)
1493 GList *p, *placed_icons, *unplaced_icons;
1494 int total, new_length, placed;
1495 NautilusIcon *icon;
1496 int width, height, max_width, column_width, icon_width, icon_height;
1497 int x, y, x1, x2, y1, y2;
1498 EelDRect icon_rect;
1500 /* Get container dimensions */
1501 width = CANVAS_WIDTH(container);
1502 height = CANVAS_HEIGHT(container);
1504 /* Determine which icons have and have not been placed */
1505 placed_icons = NULL;
1506 unplaced_icons = NULL;
1508 total = g_list_length (container->details->icons);
1509 new_length = g_list_length (icons);
1510 placed = total - new_length;
1511 if (placed > 0) {
1512 PlacementGrid *grid;
1513 /* Add only placed icons in list */
1514 for (p = container->details->icons; p != NULL; p = p->next) {
1515 icon = p->data;
1516 if (icon_is_positioned (icon)) {
1517 icon_set_position(icon, icon->saved_ltr_x, icon->y);
1518 placed_icons = g_list_prepend (placed_icons, icon);
1519 } else {
1520 icon->x = 0;
1521 icon->y = 0;
1522 unplaced_icons = g_list_prepend (unplaced_icons, icon);
1525 placed_icons = g_list_reverse (placed_icons);
1526 unplaced_icons = g_list_reverse (unplaced_icons);
1528 grid = placement_grid_new (container, FALSE);
1530 if (grid) {
1531 for (p = placed_icons; p != NULL; p = p->next) {
1532 placement_grid_mark_icon
1533 (grid, (NautilusIcon*)p->data);
1536 /* Place unplaced icons in the best locations */
1537 for (p = unplaced_icons; p != NULL; p = p->next) {
1538 icon = p->data;
1540 icon_rect = nautilus_icon_canvas_item_get_icon_rectangle (icon->item);
1542 /* Start the icon in the first column */
1543 x = DESKTOP_PAD_HORIZONTAL + (SNAP_SIZE_X / 2) - ((icon_rect.x1 - icon_rect.x0) / 2);
1544 y = DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y - (icon_rect.y1 - icon_rect.y0);
1546 find_empty_location (container,
1547 grid,
1548 icon,
1549 x, y,
1550 &x, &y);
1552 icon_set_position (icon, x, y);
1553 icon->saved_ltr_x = x;
1554 placement_grid_mark_icon (grid, icon);
1557 placement_grid_free (grid);
1560 g_list_free (placed_icons);
1561 g_list_free (unplaced_icons);
1562 } else {
1563 /* There are no placed icons. Just lay them down using our rules */
1564 x = DESKTOP_PAD_HORIZONTAL;
1566 while (icons != NULL) {
1567 int center_x;
1568 int baseline;
1569 gboolean should_snap;
1571 should_snap = !(container->details->tighter_layout && !container->details->keep_aligned);
1573 y = DESKTOP_PAD_VERTICAL;
1575 max_width = 0;
1577 /* Calculate max width for column */
1578 for (p = icons; p != NULL; p = p->next) {
1579 icon = p->data;
1580 icon_get_bounding_box (icon, &x1, &y1, &x2, &y2);
1582 icon_width = x2 - x1;
1583 icon_height = y2 - y1;
1585 if (should_snap) {
1586 /* Snap the baseline to a grid position */
1587 icon_rect = nautilus_icon_canvas_item_get_icon_rectangle (icon->item);
1588 baseline = y + (icon_rect.y1 - icon_rect.y0);
1589 baseline = SNAP_CEIL_VERTICAL (baseline);
1590 y = baseline - (icon_rect.y1 - icon_rect.y0);
1593 /* Check and see if we need to move to a new column */
1594 if (y != DESKTOP_PAD_VERTICAL && y > height - icon_height) {
1595 break;
1598 if (max_width < icon_width) {
1599 max_width = icon_width;
1602 y += icon_height + DESKTOP_PAD_VERTICAL;
1605 y = DESKTOP_PAD_VERTICAL;
1607 center_x = x + max_width / 2;
1608 column_width = max_width;
1609 if (should_snap) {
1610 /* Find the grid column to center on */
1611 center_x = SNAP_CEIL_HORIZONTAL (center_x);
1612 column_width = (center_x - x) + (max_width / 2);
1615 /* Lay out column */
1616 for (p = icons; p != NULL; p = p->next) {
1617 icon = p->data;
1618 icon_get_bounding_box (icon, &x1, &y1, &x2, &y2);
1620 icon_height = y2 - y1;
1622 icon_rect = nautilus_icon_canvas_item_get_icon_rectangle (icon->item);
1624 if (should_snap) {
1625 baseline = y + (icon_rect.y1 - icon_rect.y0);
1626 baseline = SNAP_CEIL_VERTICAL (baseline);
1627 y = baseline - (icon_rect.y1 - icon_rect.y0);
1630 /* Check and see if we need to move to a new column */
1631 if (y != DESKTOP_PAD_VERTICAL && y > height - icon_height &&
1632 /* Make sure we lay out at least one icon per column, to make progress */
1633 p != icons) {
1634 x += column_width + DESKTOP_PAD_HORIZONTAL;
1635 break;
1638 icon_set_position (icon,
1639 center_x - (icon_rect.x1 - icon_rect.x0) / 2,
1642 icon->saved_ltr_x = icon->x;
1643 y += icon_height + DESKTOP_PAD_VERTICAL;
1645 icons = p;
1649 /* These modes are special. We freeze all of our positions
1650 * after we do the layout.
1652 /* FIXME bugzilla.gnome.org 42478:
1653 * This should not be tied to the direction of layout.
1654 * It should be a separate switch.
1656 nautilus_icon_container_freeze_icon_positions (container);
1660 static void
1661 lay_down_icons (NautilusIconContainer *container, GList *icons, double start_y)
1663 switch (container->details->layout_mode)
1665 case NAUTILUS_ICON_LAYOUT_L_R_T_B:
1666 case NAUTILUS_ICON_LAYOUT_R_L_T_B:
1667 lay_down_icons_horizontal (container, icons, start_y);
1668 break;
1670 case NAUTILUS_ICON_LAYOUT_T_B_L_R:
1671 case NAUTILUS_ICON_LAYOUT_T_B_R_L:
1672 lay_down_icons_vertical (container, icons);
1673 break;
1675 default:
1676 g_assert_not_reached ();
1680 static void
1681 redo_layout_internal (NautilusIconContainer *container)
1683 finish_adding_new_icons (container);
1685 /* Don't do any re-laying-out during stretching. Later we
1686 * might add smart logic that does this and leaves room for
1687 * the stretched icon, but if we do it we want it to be fast
1688 * and only re-lay-out when it's really needed.
1690 if (container->details->auto_layout
1691 && container->details->drag_state != DRAG_STATE_STRETCH) {
1692 resort (container);
1693 lay_down_icons (container, container->details->icons, 0);
1696 if (nautilus_icon_container_is_layout_rtl (container)) {
1697 nautilus_icon_container_set_rtl_positions (container);
1700 nautilus_icon_container_update_scroll_region (container);
1702 process_pending_icon_to_reveal (container);
1703 process_pending_icon_to_rename (container);
1704 nautilus_icon_container_update_visible_icons (container);
1707 static gboolean
1708 redo_layout_callback (gpointer callback_data)
1710 NautilusIconContainer *container;
1712 container = NAUTILUS_ICON_CONTAINER (callback_data);
1713 redo_layout_internal (container);
1714 container->details->idle_id = 0;
1716 return FALSE;
1719 static void
1720 unschedule_redo_layout (NautilusIconContainer *container)
1722 if (container->details->idle_id != 0) {
1723 g_source_remove (container->details->idle_id);
1724 container->details->idle_id = 0;
1728 static void
1729 schedule_redo_layout (NautilusIconContainer *container)
1731 if (container->details->idle_id == 0
1732 && container->details->has_been_allocated) {
1733 container->details->idle_id = g_idle_add
1734 (redo_layout_callback, container);
1738 static void
1739 redo_layout (NautilusIconContainer *container)
1741 unschedule_redo_layout (container);
1742 redo_layout_internal (container);
1745 static void
1746 reload_icon_positions (NautilusIconContainer *container)
1748 GList *p, *no_position_icons;
1749 NautilusIcon *icon;
1750 gboolean have_stored_position;
1751 NautilusIconPosition position;
1752 EelDRect bounds;
1753 double bottom;
1754 EelCanvasItem *item;
1756 g_assert (!container->details->auto_layout);
1758 resort (container);
1760 no_position_icons = NULL;
1762 /* Place all the icons with positions. */
1763 bottom = 0;
1764 for (p = container->details->icons; p != NULL; p = p->next) {
1765 icon = p->data;
1767 have_stored_position = FALSE;
1768 g_signal_emit (container,
1769 signals[GET_STORED_ICON_POSITION], 0,
1770 icon->data,
1771 &position,
1772 &have_stored_position);
1773 if (have_stored_position) {
1774 icon_set_position (icon, position.x, position.y);
1775 item = EEL_CANVAS_ITEM (icon->item);
1776 eel_canvas_item_get_bounds (item,
1777 &bounds.x0,
1778 &bounds.y0,
1779 &bounds.x1,
1780 &bounds.y1);
1781 eel_canvas_item_i2w (item->parent,
1782 &bounds.x0,
1783 &bounds.y0);
1784 eel_canvas_item_i2w (item->parent,
1785 &bounds.x1,
1786 &bounds.y1);
1787 if (bounds.y1 > bottom) {
1788 bottom = bounds.y1;
1790 } else {
1791 no_position_icons = g_list_prepend (no_position_icons, icon);
1794 no_position_icons = g_list_reverse (no_position_icons);
1796 /* Place all the other icons. */
1797 lay_down_icons (container, no_position_icons, bottom + ICON_PAD_BOTTOM);
1798 g_list_free (no_position_icons);
1801 /* Container-level icon handling functions. */
1803 static gboolean
1804 button_event_modifies_selection (GdkEventButton *event)
1806 return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
1809 /* invalidate the cached label sizes for all the icons */
1810 static void
1811 invalidate_label_sizes (NautilusIconContainer *container)
1813 GList *p;
1814 NautilusIcon *icon;
1816 for (p = container->details->icons; p != NULL; p = p->next) {
1817 icon = p->data;
1819 nautilus_icon_canvas_item_invalidate_label_size (icon->item);
1823 static gboolean
1824 select_range (NautilusIconContainer *container,
1825 NautilusIcon *icon1,
1826 NautilusIcon *icon2)
1828 gboolean selection_changed;
1829 GList *p;
1830 NautilusIcon *icon;
1831 NautilusIcon *unmatched_icon;
1832 gboolean select;
1834 selection_changed = FALSE;
1836 unmatched_icon = NULL;
1837 select = FALSE;
1838 for (p = container->details->icons; p != NULL; p = p->next) {
1839 icon = p->data;
1841 if (unmatched_icon == NULL) {
1842 if (icon == icon1) {
1843 unmatched_icon = icon2;
1844 select = TRUE;
1845 } else if (icon == icon2) {
1846 unmatched_icon = icon1;
1847 select = TRUE;
1851 selection_changed |= icon_set_selected
1852 (container, icon, select);
1854 if (unmatched_icon != NULL && icon == unmatched_icon) {
1855 select = FALSE;
1860 if (selection_changed && icon2 != NULL) {
1861 AtkObject *atk_object = eel_accessibility_for_object (icon2->item);
1862 atk_focus_tracker_notify (atk_object);
1864 return selection_changed;
1868 static gboolean
1869 select_one_unselect_others (NautilusIconContainer *container,
1870 NautilusIcon *icon_to_select)
1872 gboolean selection_changed;
1873 GList *p;
1874 NautilusIcon *icon;
1876 selection_changed = FALSE;
1878 for (p = container->details->icons; p != NULL; p = p->next) {
1879 icon = p->data;
1881 selection_changed |= icon_set_selected
1882 (container, icon, icon == icon_to_select);
1885 if (selection_changed && icon_to_select != NULL) {
1886 AtkObject *atk_object = eel_accessibility_for_object (icon_to_select->item);
1887 atk_focus_tracker_notify (atk_object);
1888 reveal_icon (container, icon_to_select);
1890 return selection_changed;
1893 static gboolean
1894 unselect_all (NautilusIconContainer *container)
1896 return select_one_unselect_others (container, NULL);
1899 void
1900 nautilus_icon_container_move_icon (NautilusIconContainer *container,
1901 NautilusIcon *icon,
1902 int x, int y,
1903 double scale,
1904 gboolean raise,
1905 gboolean snap,
1906 gboolean update_position)
1908 NautilusIconContainerDetails *details;
1909 gboolean emit_signal;
1910 NautilusIconPosition position;
1912 details = container->details;
1914 emit_signal = FALSE;
1916 if (icon == get_icon_being_renamed (container)) {
1917 end_renaming_mode (container, TRUE);
1920 if (scale != icon->scale) {
1921 icon->scale = scale;
1922 nautilus_icon_container_update_icon (container, icon);
1923 if (update_position) {
1924 redo_layout (container);
1925 emit_signal = TRUE;
1929 if (!details->auto_layout) {
1930 if (details->keep_aligned && snap) {
1931 snap_position (container, icon, &x, &y);
1934 if (x != icon->x || y != icon->y) {
1935 icon_set_position (icon, x, y);
1936 emit_signal = update_position;
1939 icon->saved_ltr_x = nautilus_icon_container_is_layout_rtl (container) ? get_mirror_x_position (container, icon, icon->x) : icon->x;
1942 if (emit_signal) {
1943 position.x = icon->saved_ltr_x;
1944 position.y = icon->y;
1945 position.scale = scale;
1946 g_signal_emit (container,
1947 signals[ICON_POSITION_CHANGED], 0,
1948 icon->data, &position);
1951 if (raise) {
1952 icon_raise (icon);
1955 /* FIXME bugzilla.gnome.org 42474:
1956 * Handling of the scroll region is inconsistent here. In
1957 * the scale-changing case, redo_layout is called, which updates the
1958 * scroll region appropriately. In other cases, it's up to the
1959 * caller to make sure the scroll region is updated. This could
1960 * lead to hard-to-track-down bugs.
1964 /* Implementation of rubberband selection. */
1965 static void
1966 rubberband_select (NautilusIconContainer *container,
1967 const EelDRect *previous_rect,
1968 const EelDRect *current_rect)
1970 GList *p;
1971 gboolean selection_changed, is_in, canvas_rect_calculated;
1972 NautilusIcon *icon;
1973 EelIRect canvas_rect;
1974 EelCanvas *canvas;
1976 selection_changed = FALSE;
1977 canvas_rect_calculated = FALSE;
1979 for (p = container->details->icons; p != NULL; p = p->next) {
1980 icon = p->data;
1982 if (!canvas_rect_calculated) {
1983 /* Only do this calculation once, since all the canvas items
1984 * we are interating are in the same coordinate space
1986 canvas = EEL_CANVAS_ITEM (icon->item)->canvas;
1987 eel_canvas_w2c (canvas,
1988 current_rect->x0,
1989 current_rect->y0,
1990 &canvas_rect.x0,
1991 &canvas_rect.y0);
1992 eel_canvas_w2c (canvas,
1993 current_rect->x1,
1994 current_rect->y1,
1995 &canvas_rect.x1,
1996 &canvas_rect.y1);
1997 canvas_rect_calculated = TRUE;
2000 is_in = nautilus_icon_canvas_item_hit_test_rectangle (icon->item, canvas_rect);
2002 g_assert (icon->was_selected_before_rubberband == FALSE
2003 || icon->was_selected_before_rubberband == TRUE);
2004 selection_changed |= icon_set_selected
2005 (container, icon,
2006 is_in ^ icon->was_selected_before_rubberband);
2009 if (selection_changed) {
2010 g_signal_emit (container,
2011 signals[SELECTION_CHANGED], 0);
2015 static int
2016 rubberband_timeout_callback (gpointer data)
2018 NautilusIconContainer *container;
2019 GtkWidget *widget;
2020 NautilusIconRubberbandInfo *band_info;
2021 int x, y;
2022 double x1, y1, x2, y2;
2023 double world_x, world_y;
2024 int x_scroll, y_scroll;
2025 int adj_y;
2026 gboolean adj_changed;
2028 EelDRect selection_rect;
2030 widget = GTK_WIDGET (data);
2031 container = NAUTILUS_ICON_CONTAINER (data);
2032 band_info = &container->details->rubberband_info;
2034 g_assert (band_info->timer_id != 0);
2035 g_assert (EEL_IS_CANVAS_RECT (band_info->selection_rectangle) ||
2036 EEL_IS_CANVAS_RECT (band_info->selection_rectangle));
2038 adj_changed = FALSE;
2039 adj_y = gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (container)));
2040 if (adj_y != band_info->last_adj_y) {
2041 band_info->last_adj_y = adj_y;
2042 adj_changed = TRUE;
2045 gtk_widget_get_pointer (widget, &x, &y);
2047 if (x < 0) {
2048 x_scroll = x;
2049 x = 0;
2050 } else if (x >= widget->allocation.width) {
2051 x_scroll = x - widget->allocation.width + 1;
2052 x = widget->allocation.width - 1;
2053 } else {
2054 x_scroll = 0;
2057 if (y < 0) {
2058 y_scroll = y;
2059 y = 0;
2060 } else if (y >= widget->allocation.height) {
2061 y_scroll = y - widget->allocation.height + 1;
2062 y = widget->allocation.height - 1;
2063 } else {
2064 y_scroll = 0;
2067 if (y_scroll == 0 && x_scroll == 0
2068 && (int) band_info->prev_x == x && (int) band_info->prev_y == y && !adj_changed) {
2069 return TRUE;
2072 nautilus_icon_container_scroll (container, x_scroll, y_scroll);
2074 /* Remember to convert from widget to scrolled window coords */
2075 eel_canvas_window_to_world (EEL_CANVAS (container),
2076 x + gtk_adjustment_get_value (gtk_layout_get_hadjustment (GTK_LAYOUT (container))),
2077 y + gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (container))),
2078 &world_x, &world_y);
2080 if (world_x < band_info->start_x) {
2081 x1 = world_x;
2082 x2 = band_info->start_x;
2083 } else {
2084 x1 = band_info->start_x;
2085 x2 = world_x;
2088 if (world_y < band_info->start_y) {
2089 y1 = world_y;
2090 y2 = band_info->start_y;
2091 } else {
2092 y1 = band_info->start_y;
2093 y2 = world_y;
2096 /* Don't let the area of the selection rectangle be empty.
2097 * Aside from the fact that it would be funny when the rectangle disappears,
2098 * this also works around a crash in libart that happens sometimes when a
2099 * zero height rectangle is passed.
2101 x2 = MAX (x1 + 1, x2);
2102 y2 = MAX (y1 + 1, y2);
2104 eel_canvas_item_set
2105 (band_info->selection_rectangle,
2106 "x1", x1, "y1", y1,
2107 "x2", x2, "y2", y2,
2108 NULL);
2110 selection_rect.x0 = x1;
2111 selection_rect.y0 = y1;
2112 selection_rect.x1 = x2;
2113 selection_rect.y1 = y2;
2115 rubberband_select (container,
2116 &band_info->prev_rect,
2117 &selection_rect);
2119 band_info->prev_x = x;
2120 band_info->prev_y = y;
2122 band_info->prev_rect = selection_rect;
2124 return TRUE;
2127 static void
2128 start_rubberbanding (NautilusIconContainer *container,
2129 GdkEventButton *event)
2131 AtkObject *accessible;
2132 NautilusIconContainerDetails *details;
2133 NautilusIconRubberbandInfo *band_info;
2134 guint fill_color, outline_color;
2135 GdkColor *fill_color_gdk;
2136 guchar fill_color_alpha;
2137 GList *p;
2138 NautilusIcon *icon;
2140 details = container->details;
2141 band_info = &details->rubberband_info;
2143 g_signal_emit (container,
2144 signals[BAND_SELECT_STARTED], 0);
2146 for (p = details->icons; p != NULL; p = p->next) {
2147 icon = p->data;
2148 icon->was_selected_before_rubberband = icon->is_selected;
2151 eel_canvas_window_to_world
2152 (EEL_CANVAS (container), event->x, event->y,
2153 &band_info->start_x, &band_info->start_y);
2155 gtk_widget_style_get (GTK_WIDGET (container),
2156 "selection_box_color", &fill_color_gdk,
2157 "selection_box_alpha", &fill_color_alpha,
2158 NULL);
2160 if (!fill_color_gdk) {
2161 fill_color_gdk = gdk_color_copy (&GTK_WIDGET (container)->style->base[GTK_STATE_SELECTED]);
2164 fill_color = eel_gdk_color_to_rgb (fill_color_gdk) << 8 | fill_color_alpha;
2166 gdk_color_free (fill_color_gdk);
2168 outline_color = fill_color | 255;
2170 band_info->selection_rectangle = eel_canvas_item_new
2171 (eel_canvas_root
2172 (EEL_CANVAS (container)),
2173 EEL_TYPE_CANVAS_RECT,
2174 "x1", band_info->start_x,
2175 "y1", band_info->start_y,
2176 "x2", band_info->start_x,
2177 "y2", band_info->start_y,
2178 "fill_color_rgba", fill_color,
2179 "outline_color_rgba", outline_color,
2180 "width_pixels", 1,
2181 NULL);
2183 accessible = atk_gobject_accessible_for_object
2184 (G_OBJECT (band_info->selection_rectangle));
2185 atk_object_set_name (accessible, "selection");
2186 atk_object_set_description (accessible, _("The selection rectangle"));
2188 band_info->prev_x = event->x - gtk_adjustment_get_value (gtk_layout_get_hadjustment (GTK_LAYOUT (container)));
2189 band_info->prev_y = event->y - gtk_adjustment_get_value (gtk_layout_get_vadjustment (GTK_LAYOUT (container)));
2191 band_info->active = TRUE;
2193 if (band_info->timer_id == 0) {
2194 band_info->timer_id = g_timeout_add
2195 (RUBBERBAND_TIMEOUT_INTERVAL,
2196 rubberband_timeout_callback,
2197 container);
2200 eel_canvas_item_grab (band_info->selection_rectangle,
2201 (GDK_POINTER_MOTION_MASK
2202 | GDK_BUTTON_RELEASE_MASK
2203 | GDK_SCROLL_MASK),
2204 NULL, event->time);
2207 static void
2208 stop_rubberbanding (NautilusIconContainer *container,
2209 guint32 time)
2211 NautilusIconRubberbandInfo *band_info;
2212 GList *icons;
2214 band_info = &container->details->rubberband_info;
2216 g_assert (band_info->timer_id != 0);
2217 g_source_remove (band_info->timer_id);
2218 band_info->timer_id = 0;
2220 band_info->active = FALSE;
2222 /* Destroy this canvas item; the parent will unref it. */
2223 eel_canvas_item_ungrab (band_info->selection_rectangle, time);
2224 gtk_object_destroy (GTK_OBJECT (band_info->selection_rectangle));
2225 band_info->selection_rectangle = NULL;
2227 /* if only one item has been selected, use it as range
2228 * selection base (cf. handle_icon_button_press) */
2229 icons = nautilus_icon_container_get_selected_icons (container);
2230 if (g_list_length (icons) == 1) {
2231 container->details->range_selection_base_icon = icons->data;
2233 g_list_free (icons);
2235 g_signal_emit (container,
2236 signals[BAND_SELECT_ENDED], 0);
2239 /* Keyboard navigation. */
2241 typedef gboolean (* IsBetterIconFunction) (NautilusIconContainer *container,
2242 NautilusIcon *start_icon,
2243 NautilusIcon *best_so_far,
2244 NautilusIcon *candidate,
2245 void *data);
2247 static NautilusIcon *
2248 find_best_icon (NautilusIconContainer *container,
2249 NautilusIcon *start_icon,
2250 IsBetterIconFunction function,
2251 void *data)
2253 GList *p;
2254 NautilusIcon *best, *candidate;
2256 best = NULL;
2257 for (p = container->details->icons; p != NULL; p = p->next) {
2258 candidate = p->data;
2260 if (candidate != start_icon) {
2261 if ((* function) (container, start_icon, best, candidate, data)) {
2262 best = candidate;
2266 return best;
2269 static NautilusIcon *
2270 find_best_selected_icon (NautilusIconContainer *container,
2271 NautilusIcon *start_icon,
2272 IsBetterIconFunction function,
2273 void *data)
2275 GList *p;
2276 NautilusIcon *best, *candidate;
2278 best = NULL;
2279 for (p = container->details->icons; p != NULL; p = p->next) {
2280 candidate = p->data;
2282 if (candidate != start_icon && candidate->is_selected) {
2283 if ((* function) (container, start_icon, best, candidate, data)) {
2284 best = candidate;
2288 return best;
2291 static int
2292 compare_icons_by_uri (NautilusIconContainer *container,
2293 NautilusIcon *icon_a,
2294 NautilusIcon *icon_b)
2296 char *uri_a, *uri_b;
2297 int result;
2299 g_assert (NAUTILUS_IS_ICON_CONTAINER (container));
2300 g_assert (icon_a != NULL);
2301 g_assert (icon_b != NULL);
2302 g_assert (icon_a != icon_b);
2304 uri_a = nautilus_icon_container_get_icon_uri (container, icon_a);
2305 uri_b = nautilus_icon_container_get_icon_uri (container, icon_b);
2306 result = strcmp (uri_a, uri_b);
2307 g_assert (result != 0);
2308 g_free (uri_a);
2309 g_free (uri_b);
2311 return result;
2314 static int
2315 get_cmp_point_x (NautilusIconContainer *container,
2316 EelDRect icon_rect)
2318 if (container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) {
2319 if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL) {
2320 return icon_rect.x0;
2321 } else {
2322 return icon_rect.x1;
2324 } else {
2325 return (icon_rect.x0 + icon_rect.x1) / 2;
2329 static int
2330 get_cmp_point_y (NautilusIconContainer *container,
2331 EelDRect icon_rect)
2333 if (container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) {
2334 return (icon_rect.y0 + icon_rect.y1)/2;
2335 } else {
2336 return icon_rect.y1;
2340 static int
2341 compare_icons_horizontal_first (NautilusIconContainer *container,
2342 NautilusIcon *icon_a,
2343 NautilusIcon *icon_b)
2345 EelDRect world_rect;
2346 int ax, ay, bx, by;
2348 world_rect = nautilus_icon_canvas_item_get_icon_rectangle (icon_a->item);
2349 eel_canvas_w2c
2350 (EEL_CANVAS (container),
2351 get_cmp_point_x (container, world_rect),
2352 get_cmp_point_y (container, world_rect),
2353 &ax,
2354 &ay);
2355 world_rect = nautilus_icon_canvas_item_get_icon_rectangle (icon_b->item);
2356 eel_canvas_w2c
2357 (EEL_CANVAS (container),
2358 get_cmp_point_x (container, world_rect),
2359 get_cmp_point_y (container, world_rect),
2360 &bx,
2361 &by);
2363 if (ax < bx) {
2364 return -1;
2366 if (ax > bx) {
2367 return +1;
2369 if (ay < by) {
2370 return -1;
2372 if (ay > by) {
2373 return +1;
2375 return compare_icons_by_uri (container, icon_a, icon_b);
2378 static int
2379 compare_icons_vertical_first_reverse_horizontal (NautilusIconContainer *container,
2380 NautilusIcon *icon_a,
2381 NautilusIcon *icon_b)
2383 EelDRect world_rect;
2384 int ax, ay, bx, by;
2386 world_rect = nautilus_icon_canvas_item_get_icon_rectangle (icon_a->item);
2387 eel_canvas_w2c
2388 (EEL_CANVAS (container),
2389 get_cmp_point_x (container, world_rect),
2390 get_cmp_point_y (container, world_rect),
2391 &ax,
2392 &ay);
2393 world_rect = nautilus_icon_canvas_item_get_icon_rectangle (icon_b->item);
2394 eel_canvas_w2c
2395 (EEL_CANVAS (container),
2396 get_cmp_point_x (container, world_rect),
2397 get_cmp_point_y (container, world_rect),
2398 &bx,
2399 &by);
2401 if (ay < by) {
2402 return -1;
2404 if (ay > by) {
2405 return +1;
2407 if (ax < bx) {
2408 return (nautilus_icon_container_is_layout_rtl (container) ? -1 : +1);
2410 if (ax > bx) {
2411 return (nautilus_icon_container_is_layout_rtl (container) ? +1 : -1);
2413 return compare_icons_by_uri (container, icon_a, icon_b);
2417 static int
2418 compare_icons_vertical_first (NautilusIconContainer *container,
2419 NautilusIcon *icon_a,
2420 NautilusIcon *icon_b)
2422 EelDRect world_rect;
2423 int ax, ay, bx, by;
2425 world_rect = nautilus_icon_canvas_item_get_icon_rectangle (icon_a->item);
2426 eel_canvas_w2c
2427 (EEL_CANVAS (container),
2428 get_cmp_point_x (container, world_rect),
2429 get_cmp_point_y (container, world_rect),
2430 &ax,
2431 &ay);
2432 world_rect = nautilus_icon_canvas_item_get_icon_rectangle (icon_b->item);
2433 eel_canvas_w2c
2434 (EEL_CANVAS (container),
2435 get_cmp_point_x (container, world_rect),
2436 get_cmp_point_y (container, world_rect),
2437 &bx,
2438 &by);
2440 if (ay < by) {
2441 return -1;
2443 if (ay > by) {
2444 return +1;
2446 if (ax < bx) {
2447 return -1;
2449 if (ax > bx) {
2450 return +1;
2452 return compare_icons_by_uri (container, icon_a, icon_b);
2455 static gboolean
2456 leftmost_in_top_row (NautilusIconContainer *container,
2457 NautilusIcon *start_icon,
2458 NautilusIcon *best_so_far,
2459 NautilusIcon *candidate,
2460 void *data)
2462 if (best_so_far == NULL) {
2463 return TRUE;
2465 return compare_icons_vertical_first (container, best_so_far, candidate) > 0;
2468 static gboolean
2469 rightmost_in_bottom_row (NautilusIconContainer *container,
2470 NautilusIcon *start_icon,
2471 NautilusIcon *best_so_far,
2472 NautilusIcon *candidate,
2473 void *data)
2475 if (best_so_far == NULL) {
2476 return TRUE;
2478 return compare_icons_vertical_first (container, best_so_far, candidate) < 0;
2481 static int
2482 compare_with_start_row (NautilusIconContainer *container,
2483 NautilusIcon *icon)
2485 EelCanvasItem *item;
2487 item = EEL_CANVAS_ITEM (icon->item);
2489 if (container->details->arrow_key_start < item->y1) {
2490 return -1;
2492 if (container->details->arrow_key_start > item->y2) {
2493 return +1;
2495 return 0;
2498 static int
2499 compare_with_start_column (NautilusIconContainer *container,
2500 NautilusIcon *icon)
2502 EelCanvasItem *item;
2504 item = EEL_CANVAS_ITEM (icon->item);
2506 if (container->details->arrow_key_start < item->x1) {
2507 return -1;
2509 if (container->details->arrow_key_start > item->x2) {
2510 return +1;
2512 return 0;
2515 static gboolean
2516 same_row_right_side_leftmost (NautilusIconContainer *container,
2517 NautilusIcon *start_icon,
2518 NautilusIcon *best_so_far,
2519 NautilusIcon *candidate,
2520 void *data)
2522 /* Candidates not on the start row do not qualify. */
2523 if (compare_with_start_row (container, candidate) != 0) {
2524 return FALSE;
2527 /* Candidates that are farther right lose out. */
2528 if (best_so_far != NULL) {
2529 if (compare_icons_horizontal_first (container,
2530 best_so_far,
2531 candidate) < 0) {
2532 return FALSE;
2536 /* Candidate to the left of the start do not qualify. */
2537 if (compare_icons_horizontal_first (container,
2538 candidate,
2539 start_icon) <= 0) {
2540 return FALSE;
2543 return TRUE;
2546 static gboolean
2547 same_row_left_side_rightmost (NautilusIconContainer *container,
2548 NautilusIcon *start_icon,
2549 NautilusIcon *best_so_far,
2550 NautilusIcon *candidate,
2551 void *data)
2553 /* Candidates not on the start row do not qualify. */
2554 if (compare_with_start_row (container, candidate) != 0) {
2555 return FALSE;
2558 /* Candidates that are farther left lose out. */
2559 if (best_so_far != NULL) {
2560 if (compare_icons_horizontal_first (container,
2561 best_so_far,
2562 candidate) > 0) {
2563 return FALSE;
2567 /* Candidate to the right of the start do not qualify. */
2568 if (compare_icons_horizontal_first (container,
2569 candidate,
2570 start_icon) >= 0) {
2571 return FALSE;
2574 return TRUE;
2577 static gboolean
2578 same_column_above_lowest (NautilusIconContainer *container,
2579 NautilusIcon *start_icon,
2580 NautilusIcon *best_so_far,
2581 NautilusIcon *candidate,
2582 void *data)
2584 /* Candidates not on the start column do not qualify. */
2585 if (compare_with_start_column (container, candidate) != 0) {
2586 return FALSE;
2589 /* Candidates that are higher lose out. */
2590 if (best_so_far != NULL) {
2591 if (compare_icons_vertical_first (container,
2592 best_so_far,
2593 candidate) > 0) {
2594 return FALSE;
2598 /* Candidates below the start do not qualify. */
2599 if (compare_icons_vertical_first (container,
2600 candidate,
2601 start_icon) >= 0) {
2602 return FALSE;
2605 return TRUE;
2608 static gboolean
2609 same_column_below_highest (NautilusIconContainer *container,
2610 NautilusIcon *start_icon,
2611 NautilusIcon *best_so_far,
2612 NautilusIcon *candidate,
2613 void *data)
2615 EelCanvasItem *item;
2617 item = EEL_CANVAS_ITEM (candidate->item);
2619 /* Candidates above or on the start row do not qualify. */
2620 if (container->details->arrow_key_start_y >= item->y1) {
2621 return FALSE;
2624 if (best_so_far != NULL) {
2625 /* Candidates on the start column are preferred. */
2626 if (compare_with_start_column (container, candidate) != 0 &&
2627 compare_with_start_column (container, best_so_far) == 0) {
2628 return FALSE;
2630 /* Candidates that are lower or to the left lose out. */
2631 if (compare_icons_vertical_first_reverse_horizontal (container, best_so_far, candidate) <= 0) {
2632 return FALSE;
2636 return TRUE;
2639 static gboolean
2640 closest_in_90_degrees (NautilusIconContainer *container,
2641 NautilusIcon *start_icon,
2642 NautilusIcon *best_so_far,
2643 NautilusIcon *candidate,
2644 void *data)
2646 EelDRect world_rect;
2647 int x, y;
2648 int dx, dy;
2649 int dist;
2650 int *best_dist;
2653 world_rect = nautilus_icon_canvas_item_get_icon_rectangle (candidate->item);
2654 eel_canvas_w2c
2655 (EEL_CANVAS (container),
2656 get_cmp_point_x (container, world_rect),
2657 get_cmp_point_y (container, world_rect),
2659 &y);
2661 dx = x - container->details->arrow_key_start_x;
2662 dy = y - container->details->arrow_key_start_y;
2664 switch (container->details->arrow_key_direction) {
2665 case GTK_DIR_UP:
2666 if (dy > 0 ||
2667 ABS(dx) > ABS(dy)) {
2668 return FALSE;
2670 break;
2671 case GTK_DIR_DOWN:
2672 if (dy < 0 ||
2673 ABS(dx) > ABS(dy)) {
2674 return FALSE;
2676 break;
2677 case GTK_DIR_LEFT:
2678 if (dx > 0 ||
2679 ABS(dy) > ABS(dx)) {
2680 return FALSE;
2682 break;
2683 case GTK_DIR_RIGHT:
2684 if (dx < 0 ||
2685 ABS(dy) > ABS(dx)) {
2686 return FALSE;
2688 break;
2689 default:
2690 g_assert_not_reached();
2693 dist = dx*dx + dy*dy;
2694 best_dist = data;
2696 if (best_so_far == NULL) {
2697 *best_dist = dist;
2698 return TRUE;
2701 if (dist < *best_dist) {
2702 *best_dist = dist;
2703 return TRUE;
2706 return FALSE;
2709 static EelDRect
2710 get_rubberband (NautilusIcon *icon1,
2711 NautilusIcon *icon2)
2713 EelDRect rect1;
2714 EelDRect rect2;
2715 EelDRect ret;
2717 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon1->item),
2718 &rect1.x0, &rect1.y0,
2719 &rect1.x1, &rect1.y1);
2720 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon2->item),
2721 &rect2.x0, &rect2.y0,
2722 &rect2.x1, &rect2.y1);
2724 eel_drect_union (&ret, &rect1, &rect2);
2726 return ret;
2729 static void
2730 keyboard_move_to (NautilusIconContainer *container,
2731 NautilusIcon *icon,
2732 NautilusIcon *from,
2733 GdkEventKey *event)
2735 if (icon == NULL) {
2736 return;
2739 if ((event->state & GDK_CONTROL_MASK) != 0) {
2740 /* Move the keyboard focus. Use Control modifier
2741 * rather than Alt to avoid Sawfish conflict.
2743 set_keyboard_focus (container, icon);
2744 container->details->keyboard_rubberband_start = NULL;
2745 } else if ((event->state & GDK_SHIFT_MASK) != 0) {
2746 /* Do rubberband selection */
2747 EelDRect rect;
2749 if (from && !container->details->keyboard_rubberband_start) {
2750 set_keyboard_rubberband_start (container, from);
2753 select_one_unselect_others (container, icon);
2754 set_keyboard_focus (container, icon);
2756 if (icon && container->details->keyboard_rubberband_start && container->details->keyboard_rubberband_start != icon) {
2757 rect = get_rubberband (container->details->keyboard_rubberband_start,
2758 icon);
2759 rubberband_select (container, NULL, &rect);
2761 } else {
2762 /* Select icons and get rid of the special keyboard focus. */
2763 clear_keyboard_focus (container);
2764 clear_keyboard_rubberband_start (container);
2766 container->details->range_selection_base_icon = icon;
2767 if (select_one_unselect_others (container, icon)) {
2768 g_signal_emit (container,
2769 signals[SELECTION_CHANGED], 0);
2772 schedule_keyboard_icon_reveal (container, icon);
2775 static void
2776 keyboard_home (NautilusIconContainer *container,
2777 GdkEventKey *event)
2779 NautilusIcon *from;
2780 NautilusIcon *to;
2782 /* Home selects the first icon.
2783 * Control-Home sets the keyboard focus to the first icon.
2786 from = find_best_selected_icon (container, NULL,
2787 rightmost_in_bottom_row,
2788 NULL);
2789 to = find_best_icon (container, NULL, leftmost_in_top_row, NULL);
2791 container->details->arrow_key_axis = AXIS_NONE;
2792 keyboard_move_to (container, to, from, event);
2795 static void
2796 keyboard_end (NautilusIconContainer *container,
2797 GdkEventKey *event)
2799 NautilusIcon *to;
2800 NautilusIcon *from;
2802 /* End selects the last icon.
2803 * Control-End sets the keyboard focus to the last icon.
2805 from = find_best_selected_icon (container, NULL,
2806 leftmost_in_top_row,
2807 NULL);
2808 to = find_best_icon (container, NULL, rightmost_in_bottom_row, NULL);
2810 container->details->arrow_key_axis = AXIS_NONE;
2811 keyboard_move_to (container, to, from, event);
2814 static void
2815 record_arrow_key_start (NautilusIconContainer *container,
2816 NautilusIcon *icon,
2817 GtkDirectionType direction)
2819 EelDRect world_rect;
2821 world_rect = nautilus_icon_canvas_item_get_icon_rectangle (icon->item);
2822 eel_canvas_w2c
2823 (EEL_CANVAS (container),
2824 get_cmp_point_x (container, world_rect),
2825 get_cmp_point_y (container, world_rect),
2826 &container->details->arrow_key_start_x,
2827 &container->details->arrow_key_start_y);
2829 container->details->arrow_key_direction = direction;
2831 switch (container->details->arrow_key_direction) {
2832 case GTK_DIR_UP:
2833 case GTK_DIR_DOWN:
2834 container->details->arrow_key_axis = AXIS_VERTICAL;
2835 container->details->arrow_key_start = container->details->arrow_key_start_x;
2836 break;
2837 case GTK_DIR_LEFT:
2838 case GTK_DIR_RIGHT:
2839 container->details->arrow_key_axis = AXIS_HORIZONTAL;
2840 container->details->arrow_key_start = container->details->arrow_key_start_y;
2841 break;
2842 default:
2843 g_assert_not_reached();
2847 static void
2848 keyboard_arrow_key (NautilusIconContainer *container,
2849 GdkEventKey *event,
2850 GtkDirectionType direction,
2851 IsBetterIconFunction better_start,
2852 IsBetterIconFunction empty_start,
2853 IsBetterIconFunction better_destination,
2854 IsBetterIconFunction better_destination_manual)
2856 NautilusIcon *from;
2857 NautilusIcon *to;
2858 int data;
2860 /* Chose the icon to start with.
2861 * If we have a keyboard focus, start with it.
2862 * Otherwise, use the single selected icon.
2863 * If there's multiple selection, use the icon farthest toward the end.
2866 from = container->details->keyboard_focus;
2868 if (from == NULL) {
2869 if (has_multiple_selection (container)) {
2870 if (all_selected (container)) {
2871 from = find_best_selected_icon
2872 (container, NULL,
2873 empty_start, NULL);
2874 } else {
2875 from = find_best_selected_icon
2876 (container, NULL,
2877 better_start, NULL);
2879 } else {
2880 from = get_first_selected_icon (container);
2884 /* If there's no icon, select the icon farthest toward the end.
2885 * If there is an icon, select the next icon based on the arrow direction.
2887 if (from == NULL) {
2888 container->details->arrow_key_axis = AXIS_NONE;
2889 to = from = find_best_icon
2890 (container, NULL,
2891 empty_start, NULL);
2893 } else {
2894 record_arrow_key_start (container, from, direction);
2896 to = find_best_icon
2897 (container, from,
2898 container->details->auto_layout ? better_destination : better_destination_manual,
2899 &data);
2902 keyboard_move_to (container, to, from, event);
2905 static void
2906 keyboard_right (NautilusIconContainer *container,
2907 GdkEventKey *event)
2909 /* Right selects the next icon in the same row.
2910 * Control-Right sets the keyboard focus to the next icon in the same row.
2912 keyboard_arrow_key (container,
2913 event,
2914 GTK_DIR_RIGHT,
2915 rightmost_in_bottom_row,
2916 leftmost_in_top_row,
2917 same_row_right_side_leftmost,
2918 closest_in_90_degrees);
2921 static void
2922 keyboard_left (NautilusIconContainer *container,
2923 GdkEventKey *event)
2925 /* Left selects the next icon in the same row.
2926 * Control-Left sets the keyboard focus to the next icon in the same row.
2928 keyboard_arrow_key (container,
2929 event,
2930 GTK_DIR_LEFT,
2931 leftmost_in_top_row,
2932 rightmost_in_bottom_row,
2933 same_row_left_side_rightmost,
2934 closest_in_90_degrees);
2937 static void
2938 keyboard_down (NautilusIconContainer *container,
2939 GdkEventKey *event)
2941 /* Down selects the next icon in the same column.
2942 * Control-Down sets the keyboard focus to the next icon in the same column.
2944 keyboard_arrow_key (container,
2945 event,
2946 GTK_DIR_DOWN,
2947 rightmost_in_bottom_row,
2948 leftmost_in_top_row,
2949 same_column_below_highest,
2950 closest_in_90_degrees);
2953 static void
2954 keyboard_up (NautilusIconContainer *container,
2955 GdkEventKey *event)
2957 /* Up selects the next icon in the same column.
2958 * Control-Up sets the keyboard focus to the next icon in the same column.
2960 keyboard_arrow_key (container,
2961 event,
2962 GTK_DIR_UP,
2963 leftmost_in_top_row,
2964 rightmost_in_bottom_row,
2965 same_column_above_lowest,
2966 closest_in_90_degrees);
2969 static void
2970 keyboard_space (NautilusIconContainer *container,
2971 GdkEventKey *event)
2973 NautilusIcon *icon;
2975 /* Control-space toggles the selection state of the current icon. */
2976 if ((event->state & GDK_CONTROL_MASK) != 0) {
2977 if (container->details->keyboard_focus != NULL) {
2978 icon_toggle_selected (container, container->details->keyboard_focus);
2979 g_signal_emit (container, signals[SELECTION_CHANGED], 0);
2980 if (container->details->keyboard_focus->is_selected) {
2981 container->details->range_selection_base_icon = container->details->keyboard_focus;
2983 } else {
2984 icon = find_best_selected_icon (container,
2985 NULL,
2986 leftmost_in_top_row,
2987 NULL);
2988 if (icon == NULL) {
2989 icon = find_best_icon (container,
2990 NULL,
2991 leftmost_in_top_row,
2992 NULL);
2994 if (icon != NULL) {
2995 set_keyboard_focus (container, icon);
2998 } else if ((event->state & GDK_SHIFT_MASK) != 0) {
2999 activate_selected_items_alternate (container, NULL);
3000 } else {
3001 activate_selected_items (container);
3005 /* look for the first icon that matches the longest part of a given
3006 * search pattern
3008 typedef struct {
3009 gunichar *name;
3010 int last_match_length;
3011 } BestNameMatch;
3013 #ifndef TAB_NAVIGATION_DISABLED
3014 static void
3015 select_previous_or_next_icon (NautilusIconContainer *container,
3016 gboolean next,
3017 GdkEventKey *event)
3019 NautilusIcon *icon;
3020 const GList *item;
3022 item = NULL;
3023 /* Chose the icon to start with.
3024 * If we have a keyboard focus, start with it.
3025 * Otherwise, use the single selected icon.
3027 icon = container->details->keyboard_focus;
3028 if (icon == NULL) {
3029 icon = get_first_selected_icon (container);
3032 if (icon != NULL) {
3033 /* must have at least @icon in the list */
3034 g_assert (container->details->icons != NULL);
3035 item = g_list_find (container->details->icons, icon);
3036 g_assert (item != NULL);
3038 item = next ? item->next : item->prev;
3039 if (item == NULL) {
3040 item = next ? g_list_first (container->details->icons) : g_list_last (container->details->icons);
3043 } else if (container->details->icons != NULL) {
3044 /* no selection yet, pick the first or last item to select */
3045 item = next ? g_list_first (container->details->icons) : g_list_last (container->details->icons);
3048 icon = (item != NULL) ? item->data : NULL;
3050 if (icon != NULL) {
3051 keyboard_move_to (container, icon, NULL, event);
3054 #endif
3056 /* GtkObject methods. */
3058 static void
3059 destroy (GtkObject *object)
3061 NautilusIconContainer *container;
3063 container = NAUTILUS_ICON_CONTAINER (object);
3065 nautilus_icon_container_clear (container);
3067 if (container->details->rubberband_info.timer_id != 0) {
3068 g_source_remove (container->details->rubberband_info.timer_id);
3069 container->details->rubberband_info.timer_id = 0;
3072 if (container->details->idle_id != 0) {
3073 g_source_remove (container->details->idle_id);
3074 container->details->idle_id = 0;
3077 if (container->details->stretch_idle_id != 0) {
3078 g_source_remove (container->details->stretch_idle_id);
3079 container->details->stretch_idle_id = 0;
3082 if (container->details->align_idle_id != 0) {
3083 g_source_remove (container->details->align_idle_id);
3084 container->details->align_idle_id = 0;
3088 /* destroy interactive search dialog */
3089 if (container->details->search_window) {
3090 gtk_widget_destroy (container->details->search_window);
3091 container->details->search_window = NULL;
3092 container->details->search_entry = NULL;
3093 if (container->details->typeselect_flush_timeout) {
3094 g_source_remove (container->details->typeselect_flush_timeout);
3095 container->details->typeselect_flush_timeout = 0;
3100 GTK_OBJECT_CLASS (parent_class)->destroy (object);
3103 static void
3104 finalize (GObject *object)
3106 NautilusIconContainerDetails *details;
3108 details = NAUTILUS_ICON_CONTAINER (object)->details;
3110 eel_preferences_remove_callback (NAUTILUS_PREFERENCES_THEME,
3111 nautilus_icon_container_theme_changed,
3112 object);
3114 g_hash_table_destroy (details->icon_set);
3115 details->icon_set = NULL;
3117 g_free (details->font);
3119 if (details->a11y_item_action_queue != NULL) {
3120 while (!g_queue_is_empty (details->a11y_item_action_queue)) {
3121 g_free (g_queue_pop_head (details->a11y_item_action_queue));
3123 g_queue_free (details->a11y_item_action_queue);
3125 if (details->a11y_item_action_idle_handler != 0) {
3126 g_source_remove (details->a11y_item_action_idle_handler);
3129 g_free (details);
3131 G_OBJECT_CLASS (parent_class)->finalize (object);
3134 /* GtkWidget methods. */
3136 static void
3137 size_request (GtkWidget *widget,
3138 GtkRequisition *requisition)
3140 GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition);
3141 requisition->width = 1;
3142 requisition->height = 1;
3145 static void
3146 size_allocate (GtkWidget *widget,
3147 GtkAllocation *allocation)
3149 NautilusIconContainer *container;
3150 gboolean need_layout_redone;
3152 container = NAUTILUS_ICON_CONTAINER (widget);
3154 need_layout_redone = !container->details->has_been_allocated;
3156 if (allocation->width != widget->allocation.width) {
3157 need_layout_redone = TRUE;
3160 GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
3162 container->details->has_been_allocated = TRUE;
3164 if (need_layout_redone) {
3165 redo_layout (container);
3169 static void
3170 realize (GtkWidget *widget)
3172 GtkWindow *window;
3173 GdkBitmap *stipple;
3174 GtkAdjustment *vadj;
3176 GTK_WIDGET_CLASS (parent_class)->realize (widget);
3178 /* Set up DnD. */
3179 nautilus_icon_dnd_init (NAUTILUS_ICON_CONTAINER (widget), NULL);
3181 setup_label_gcs (NAUTILUS_ICON_CONTAINER (widget));
3183 /* make us the focused widget */
3184 g_assert (GTK_IS_WINDOW (gtk_widget_get_toplevel (widget)));
3185 window = GTK_WINDOW (gtk_widget_get_toplevel (widget));
3186 gtk_window_set_focus (window, widget);
3188 stipple = eel_stipple_bitmap_for_screen (
3189 gdk_drawable_get_screen (GDK_DRAWABLE (widget->window)));
3191 nautilus_icon_dnd_set_stipple (NAUTILUS_ICON_CONTAINER (widget), stipple);
3193 vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (widget));
3194 g_signal_connect (vadj, "value_changed",
3195 G_CALLBACK (handle_vadjustment_changed), widget);
3199 static void
3200 unrealize (GtkWidget *widget)
3202 int i;
3203 GtkWindow *window;
3204 NautilusIconContainer *container;
3206 container = NAUTILUS_ICON_CONTAINER (widget);
3208 g_assert (GTK_IS_WINDOW (gtk_widget_get_toplevel (widget)));
3209 window = GTK_WINDOW (gtk_widget_get_toplevel (widget));
3210 gtk_window_set_focus (window, NULL);
3212 for (i = 0; i < LAST_LABEL_COLOR; i++) {
3213 if (container->details->label_gcs [i]) {
3214 g_object_unref (container->details->label_gcs [i]);
3215 container->details->label_gcs [i] = NULL;
3219 nautilus_icon_dnd_fini (container);
3221 if (container->details->typeselect_flush_timeout) {
3222 g_source_remove (container->details->typeselect_flush_timeout);
3223 container->details->typeselect_flush_timeout = 0;
3226 GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
3229 static void
3230 style_set (GtkWidget *widget,
3231 GtkStyle *previous_style)
3233 NautilusIconContainer *container;
3234 gboolean frame_text;
3236 container = NAUTILUS_ICON_CONTAINER (widget);
3238 gtk_widget_style_get (GTK_WIDGET (container),
3239 "frame_text", &frame_text,
3240 NULL);
3242 container->details->use_drop_shadows = container->details->drop_shadows_requested && !frame_text;
3244 nautilus_icon_container_theme_changed (NAUTILUS_ICON_CONTAINER (widget));
3246 if (GTK_WIDGET_REALIZED (widget)) {
3247 invalidate_label_sizes (container);
3248 nautilus_icon_container_request_update_all (container);
3251 GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style);
3254 static gboolean
3255 button_press_event (GtkWidget *widget,
3256 GdkEventButton *event)
3258 NautilusIconContainer *container;
3259 gboolean selection_changed;
3260 gboolean return_value;
3261 gboolean clicked_on_icon;
3263 container = NAUTILUS_ICON_CONTAINER (widget);
3264 container->details->button_down_time = event->time;
3266 /* Forget about the old keyboard selection now that we've started mousing. */
3267 clear_keyboard_focus (container);
3268 clear_keyboard_rubberband_start (container);
3270 /* Forget about where we began with the arrow keys now that we're mousing. */
3271 container->details->arrow_key_axis = AXIS_NONE;
3273 /* Invoke the canvas event handler and see if an item picks up the event. */
3274 clicked_on_icon = GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
3276 /* Move focus to icon container, unless we're still renaming (to avoid exiting
3277 * renaming mode)
3279 if (!GTK_WIDGET_HAS_FOCUS (widget) && !(is_renaming (container) || is_renaming_pending (container))) {
3280 gtk_widget_grab_focus (widget);
3283 if (clicked_on_icon) {
3284 return TRUE;
3287 /* An item didn't take the press, so it's a background press.
3288 * We ignore double clicks on the desktop for now.
3290 if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS) {
3291 return TRUE;
3294 if ((event->button == DRAG_BUTTON || event->button == MIDDLE_BUTTON) &&
3295 event->type == GDK_BUTTON_PRESS) {
3296 /* Clear the last click icon for double click */
3297 container->details->double_click_icon[1] = container->details->double_click_icon[0];
3298 container->details->double_click_icon[0] = NULL;
3301 /* Button 1 does rubber banding. */
3302 if (event->button == RUBBERBAND_BUTTON) {
3303 if (! button_event_modifies_selection (event)) {
3304 selection_changed = unselect_all (container);
3305 if (selection_changed) {
3306 g_signal_emit (container,
3307 signals[SELECTION_CHANGED], 0);
3311 start_rubberbanding (container, event);
3312 return TRUE;
3315 /* Prevent multi-button weirdness such as bug 6181 */
3316 if (container->details->rubberband_info.active) {
3317 return TRUE;
3320 /* Button 2 may be passed to the window manager. */
3321 if (event->button == MIDDLE_BUTTON) {
3322 selection_changed = unselect_all (container);
3323 if (selection_changed) {
3324 g_signal_emit (container, signals[SELECTION_CHANGED], 0);
3326 g_signal_emit (widget, signals[MIDDLE_CLICK], 0, event);
3327 return TRUE;
3330 /* Button 3 does a contextual menu. */
3331 if (event->button == CONTEXTUAL_MENU_BUTTON) {
3332 end_renaming_mode (container, TRUE);
3333 selection_changed = unselect_all (container);
3334 if (selection_changed) {
3335 g_signal_emit (container, signals[SELECTION_CHANGED], 0);
3337 g_signal_emit (widget, signals[CONTEXT_CLICK_BACKGROUND], 0, event);
3338 return TRUE;
3341 /* Otherwise, we emit a button_press message. */
3342 g_signal_emit (widget,
3343 signals[BUTTON_PRESS], 0, event,
3344 &return_value);
3345 return return_value;
3348 static void
3349 nautilus_icon_container_did_not_drag (NautilusIconContainer *container,
3350 GdkEventButton *event)
3352 NautilusIconContainerDetails *details;
3353 gboolean selection_changed;
3354 static gint64 last_click_time = 0;
3355 static gint click_count = 0;
3356 gint double_click_time;
3357 gint64 current_time;
3359 details = container->details;
3361 if (details->icon_selected_on_button_down &&
3362 ((event->state & GDK_CONTROL_MASK) != 0 ||
3363 (event->state & GDK_SHIFT_MASK) == 0)) {
3364 if (button_event_modifies_selection (event)) {
3365 details->range_selection_base_icon = NULL;
3366 icon_toggle_selected (container, details->drag_icon);
3367 g_signal_emit (container,
3368 signals[SELECTION_CHANGED], 0);
3369 } else {
3370 details->range_selection_base_icon = details->drag_icon;
3371 selection_changed = select_one_unselect_others
3372 (container, details->drag_icon);
3374 if (selection_changed) {
3375 g_signal_emit (container,
3376 signals[SELECTION_CHANGED], 0);
3381 if (details->drag_icon != NULL &&
3382 details->single_click_mode) {
3383 /* Determine click count */
3384 g_object_get (G_OBJECT (gtk_widget_get_settings (GTK_WIDGET (container))),
3385 "gtk-double-click-time", &double_click_time,
3386 NULL);
3387 current_time = eel_get_system_time ();
3388 if (current_time - last_click_time < double_click_time * 1000) {
3389 click_count++;
3390 } else {
3391 click_count = 0;
3394 /* Stash time for next compare */
3395 last_click_time = current_time;
3397 /* If single-click mode, activate the selected icons, unless modifying
3398 * the selection or pressing for a very long time, or double clicking.
3402 if (click_count == 0 &&
3403 event->time - details->button_down_time < MAX_CLICK_TIME &&
3404 ! button_event_modifies_selection (event)) {
3406 /* It's a tricky UI issue whether this should activate
3407 * just the clicked item (as if it were a link), or all
3408 * the selected items (as if you were issuing an "activate
3409 * selection" command). For now, we're trying the activate
3410 * entire selection version to see how it feels. Note that
3411 * NautilusList goes the other way because its "links" seem
3412 * much more link-like.
3414 if (event->button == MIDDLE_BUTTON) {
3415 activate_selected_items_alternate (container, NULL);
3416 } else {
3417 activate_selected_items (container);
3423 static void
3424 clear_drag_state (NautilusIconContainer *container)
3426 container->details->drag_icon = NULL;
3427 container->details->drag_state = DRAG_STATE_INITIAL;
3430 static gboolean
3431 start_stretching (NautilusIconContainer *container)
3433 NautilusIconContainerDetails *details;
3434 NautilusIcon *icon;
3435 EelDPoint world_point;
3436 GtkWidget *toplevel;
3437 GtkCornerType corner;
3438 GdkCursor *cursor;
3440 details = container->details;
3441 icon = details->stretch_icon;
3443 /* Check if we hit the stretch handles. */
3444 world_point.x = details->drag_x;
3445 world_point.y = details->drag_y;
3446 if (!nautilus_icon_canvas_item_hit_test_stretch_handles (icon->item, world_point, &corner)) {
3447 return FALSE;
3450 switch (corner) {
3451 case GTK_CORNER_TOP_LEFT:
3452 cursor = gdk_cursor_new (GDK_TOP_LEFT_CORNER);
3453 break;
3454 case GTK_CORNER_BOTTOM_LEFT:
3455 cursor = gdk_cursor_new (GDK_BOTTOM_LEFT_CORNER);
3456 break;
3457 case GTK_CORNER_TOP_RIGHT:
3458 cursor = gdk_cursor_new (GDK_TOP_RIGHT_CORNER);
3459 break;
3460 case GTK_CORNER_BOTTOM_RIGHT:
3461 cursor = gdk_cursor_new (GDK_BOTTOM_RIGHT_CORNER);
3462 break;
3463 default:
3464 cursor = NULL;
3465 break;
3467 /* Set up the dragging. */
3468 details->drag_state = DRAG_STATE_STRETCH;
3469 eel_canvas_w2c (EEL_CANVAS (container),
3470 details->drag_x,
3471 details->drag_y,
3472 &details->stretch_start.pointer_x,
3473 &details->stretch_start.pointer_y);
3474 eel_canvas_w2c (EEL_CANVAS (container),
3475 icon->x, icon->y,
3476 &details->stretch_start.icon_x,
3477 &details->stretch_start.icon_y);
3478 icon_get_size (container, icon,
3479 &details->stretch_start.icon_size);
3481 eel_canvas_item_grab (EEL_CANVAS_ITEM (icon->item),
3482 (GDK_POINTER_MOTION_MASK
3483 | GDK_BUTTON_RELEASE_MASK),
3484 cursor,
3485 GDK_CURRENT_TIME);
3486 if (cursor)
3487 gdk_cursor_unref (cursor);
3489 /* Ensure the window itself is focused.. */
3490 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (container));
3491 if (toplevel != NULL && GTK_WIDGET_REALIZED (toplevel)) {
3492 eel_gdk_window_focus (toplevel->window, GDK_CURRENT_TIME);
3495 return TRUE;
3498 static gboolean
3499 update_stretch_at_idle (NautilusIconContainer *container)
3501 NautilusIconContainerDetails *details;
3502 NautilusIcon *icon;
3503 double world_x, world_y;
3504 StretchState stretch_state;
3506 details = container->details;
3507 icon = details->stretch_icon;
3509 if (icon == NULL) {
3510 container->details->stretch_idle_id = 0;
3511 return FALSE;
3514 eel_canvas_w2c (EEL_CANVAS (container),
3515 details->world_x, details->world_y,
3516 &stretch_state.pointer_x, &stretch_state.pointer_y);
3518 compute_stretch (&details->stretch_start,
3519 &stretch_state);
3521 eel_canvas_c2w (EEL_CANVAS (container),
3522 stretch_state.icon_x, stretch_state.icon_y,
3523 &world_x, &world_y);
3525 icon_set_position (icon, world_x, world_y);
3526 icon_set_size (container, icon, stretch_state.icon_size, FALSE, FALSE);
3528 container->details->stretch_idle_id = 0;
3530 return FALSE;
3533 static void
3534 continue_stretching (NautilusIconContainer *container,
3535 double world_x, double world_y)
3538 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
3540 container->details->world_x = world_x;
3541 container->details->world_y = world_y;
3543 if (container->details->stretch_idle_id == 0) {
3544 container->details->stretch_idle_id = g_idle_add ((GtkFunction) update_stretch_at_idle, container);
3548 static gboolean
3549 keyboard_stretching (NautilusIconContainer *container,
3550 GdkEventKey *event)
3552 NautilusIcon *icon;
3553 guint size;
3555 icon = container->details->stretch_icon;
3557 if (icon == NULL || !icon->is_selected) {
3558 return FALSE;
3561 icon_get_size (container, icon, &size);
3563 switch (event->keyval) {
3564 case GDK_equal:
3565 case GDK_plus:
3566 case GDK_KP_Add:
3567 icon_set_size (container, icon, size + 5, FALSE, FALSE);
3568 break;
3569 case GDK_minus:
3570 case GDK_KP_Subtract:
3571 icon_set_size (container, icon, size - 5, FALSE, FALSE);
3572 break;
3573 case GDK_0:
3574 case GDK_KP_0:
3575 nautilus_icon_container_move_icon (container, icon,
3576 icon->x, icon->y,
3577 1.0,
3578 FALSE, TRUE, TRUE);
3579 break;
3582 return TRUE;
3585 static void
3586 ungrab_stretch_icon (NautilusIconContainer *container)
3588 eel_canvas_item_ungrab (EEL_CANVAS_ITEM (container->details->stretch_icon->item),
3589 GDK_CURRENT_TIME);
3592 static void
3593 end_stretching (NautilusIconContainer *container,
3594 double world_x, double world_y)
3596 NautilusIconPosition position;
3597 NautilusIcon *icon;
3599 continue_stretching (container, world_x, world_y);
3600 ungrab_stretch_icon (container);
3602 /* now that we're done stretching, update the icon's position */
3604 icon = container->details->drag_icon;
3605 if (nautilus_icon_container_is_layout_rtl (container)) {
3606 position.x = icon->saved_ltr_x = get_mirror_x_position (container, icon, icon->x);
3607 } else {
3608 position.x = icon->x;
3610 position.y = icon->y;
3611 position.scale = icon->scale;
3612 g_signal_emit (container,
3613 signals[ICON_POSITION_CHANGED], 0,
3614 icon->data, &position);
3616 clear_drag_state (container);
3617 redo_layout (container);
3620 static gboolean
3621 undo_stretching (NautilusIconContainer *container)
3623 NautilusIcon *stretched_icon;
3625 stretched_icon = container->details->stretch_icon;
3627 if (stretched_icon == NULL) {
3628 return FALSE;
3631 if (container->details->drag_state == DRAG_STATE_STRETCH) {
3632 ungrab_stretch_icon (container);
3633 clear_drag_state (container);
3635 nautilus_icon_canvas_item_set_show_stretch_handles
3636 (stretched_icon->item, FALSE);
3638 icon_set_position (stretched_icon,
3639 container->details->stretch_initial_x,
3640 container->details->stretch_initial_y);
3641 icon_set_size (container,
3642 stretched_icon,
3643 container->details->stretch_initial_size,
3644 TRUE,
3645 TRUE);
3647 container->details->stretch_icon = NULL;
3648 emit_stretch_ended (container, stretched_icon);
3649 redo_layout (container);
3651 return TRUE;
3654 static gboolean
3655 button_release_event (GtkWidget *widget,
3656 GdkEventButton *event)
3658 NautilusIconContainer *container;
3659 NautilusIconContainerDetails *details;
3660 double world_x, world_y;
3662 container = NAUTILUS_ICON_CONTAINER (widget);
3663 details = container->details;
3665 if (event->button == RUBBERBAND_BUTTON && details->rubberband_info.active) {
3666 stop_rubberbanding (container, event->time);
3667 return TRUE;
3670 if (event->button == details->drag_button) {
3671 details->drag_button = 0;
3673 switch (details->drag_state) {
3674 case DRAG_STATE_MOVE_OR_COPY:
3675 if (!details->drag_started) {
3676 nautilus_icon_container_did_not_drag (container, event);
3677 } else {
3678 nautilus_icon_dnd_end_drag (container);
3679 nautilus_debug_log (FALSE, NAUTILUS_DEBUG_LOG_DOMAIN_USER,
3680 "end drag from icon container");
3682 break;
3683 case DRAG_STATE_STRETCH:
3684 eel_canvas_window_to_world
3685 (EEL_CANVAS (container), event->x, event->y, &world_x, &world_y);
3686 end_stretching (container, world_x, world_y);
3687 break;
3688 default:
3689 break;
3692 clear_drag_state (container);
3693 return TRUE;
3696 return GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event);
3699 static int
3700 motion_notify_event (GtkWidget *widget,
3701 GdkEventMotion *event)
3703 NautilusIconContainer *container;
3704 NautilusIconContainerDetails *details;
3705 double world_x, world_y;
3706 int canvas_x, canvas_y;
3707 GdkDragAction actions;
3709 container = NAUTILUS_ICON_CONTAINER (widget);
3710 details = container->details;
3712 if (details->drag_button != 0) {
3713 switch (details->drag_state) {
3714 case DRAG_STATE_MOVE_OR_COPY:
3715 if (details->drag_started) {
3716 break;
3719 eel_canvas_window_to_world
3720 (EEL_CANVAS (container), event->x, event->y, &world_x, &world_y);
3722 if (gtk_drag_check_threshold (widget,
3723 details->drag_x,
3724 details->drag_y,
3725 world_x,
3726 world_y)) {
3727 details->drag_started = TRUE;
3728 details->drag_state = DRAG_STATE_MOVE_OR_COPY;
3730 end_renaming_mode (container, TRUE);
3732 eel_canvas_w2c (EEL_CANVAS (container),
3733 details->drag_x,
3734 details->drag_y,
3735 &canvas_x,
3736 &canvas_y);
3738 actions = GDK_ACTION_COPY
3739 | GDK_ACTION_LINK
3740 | GDK_ACTION_ASK;
3742 if (container->details->drag_allow_moves) {
3743 actions |= GDK_ACTION_MOVE;
3746 nautilus_icon_dnd_begin_drag (container,
3747 actions,
3748 details->drag_button,
3749 event,
3750 canvas_x,
3751 canvas_y);
3752 nautilus_debug_log (FALSE, NAUTILUS_DEBUG_LOG_DOMAIN_USER,
3753 "begin drag from icon container");
3755 break;
3756 case DRAG_STATE_STRETCH:
3757 eel_canvas_window_to_world
3758 (EEL_CANVAS (container), event->x, event->y, &world_x, &world_y);
3759 continue_stretching (container, world_x, world_y);
3760 break;
3761 default:
3762 break;
3766 return GTK_WIDGET_CLASS (parent_class)->motion_notify_event (widget, event);
3769 static void
3770 nautilus_icon_container_search_position_func (NautilusIconContainer *container,
3771 GtkWidget *search_dialog)
3773 gint x, y;
3774 gint cont_x, cont_y;
3775 gint cont_width, cont_height;
3776 GdkWindow *cont_window;
3777 GdkScreen *screen;
3778 GtkRequisition requisition;
3779 gint monitor_num;
3780 GdkRectangle monitor;
3783 cont_window = GTK_WIDGET (container)->window;
3784 screen = gdk_drawable_get_screen (cont_window);
3786 monitor_num = gdk_screen_get_monitor_at_window (screen, cont_window);
3787 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
3789 gtk_widget_realize (search_dialog);
3791 gdk_window_get_origin (cont_window, &cont_x, &cont_y);
3792 gdk_drawable_get_size (cont_window, &cont_width, &cont_height);
3793 gtk_widget_size_request (search_dialog, &requisition);
3795 if (cont_x + cont_width - requisition.width > gdk_screen_get_width (screen)) {
3796 x = gdk_screen_get_width (screen) - requisition.width;
3797 } else if (cont_x + cont_width - requisition.width < 0) {
3798 x = 0;
3799 } else {
3800 x = cont_x + cont_width - requisition.width;
3803 if (cont_y + cont_height > gdk_screen_get_height (screen)) {
3804 y = gdk_screen_get_height (screen) - requisition.height;
3805 } else if (cont_y + cont_height < 0) { /* isn't really possible ... */
3806 y = 0;
3807 } else {
3808 y = cont_y + cont_height;
3811 gtk_window_move (GTK_WINDOW (search_dialog), x, y);
3814 static gboolean
3815 nautilus_icon_container_real_search_enable_popdown (gpointer data)
3817 NautilusIconContainer *container = (NautilusIconContainer *)data;
3819 container->details->disable_popdown = FALSE;
3821 g_object_unref (container);
3823 return FALSE;
3826 static void
3827 nautilus_icon_container_search_enable_popdown (GtkWidget *widget,
3828 gpointer data)
3830 NautilusIconContainer *container = (NautilusIconContainer *) data;
3832 g_object_ref (container);
3833 g_timeout_add (200, nautilus_icon_container_real_search_enable_popdown, data);
3836 static void
3837 nautilus_icon_container_search_disable_popdown (GtkEntry *entry,
3838 GtkMenu *menu,
3839 gpointer data)
3841 NautilusIconContainer *container = (NautilusIconContainer *) data;
3843 container->details->disable_popdown = TRUE;
3844 g_signal_connect (menu, "hide",
3845 G_CALLBACK (nautilus_icon_container_search_enable_popdown),
3846 data);
3849 /* Cut and paste from gtkwindow.c */
3850 static void
3851 send_focus_change (GtkWidget *widget, gboolean in)
3853 GdkEvent *fevent;
3855 fevent = gdk_event_new (GDK_FOCUS_CHANGE);
3857 g_object_ref (widget);
3859 if (in) {
3860 GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
3861 } else {
3862 GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
3865 fevent->focus_change.type = GDK_FOCUS_CHANGE;
3866 fevent->focus_change.window = g_object_ref (widget->window);
3867 fevent->focus_change.in = in;
3869 gtk_widget_event (widget, fevent);
3871 g_object_notify (G_OBJECT (widget), "has-focus");
3873 g_object_unref (widget);
3874 gdk_event_free (fevent);
3877 static void
3878 nautilus_icon_container_search_dialog_hide (GtkWidget *search_dialog,
3879 NautilusIconContainer *container)
3881 if (container->details->disable_popdown) {
3882 return;
3885 if (container->details->search_entry_changed_id) {
3886 g_signal_handler_disconnect (container->details->search_entry,
3887 container->details->search_entry_changed_id);
3888 container->details->search_entry_changed_id = 0;
3890 if (container->details->typeselect_flush_timeout) {
3891 g_source_remove (container->details->typeselect_flush_timeout);
3892 container->details->typeselect_flush_timeout = 0;
3895 /* send focus-in event */
3896 send_focus_change (GTK_WIDGET (container->details->search_entry), FALSE);
3897 gtk_widget_hide (search_dialog);
3898 gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
3901 static gboolean
3902 nautilus_icon_container_search_entry_flush_timeout (NautilusIconContainer *container)
3904 nautilus_icon_container_search_dialog_hide (container->details->search_window, container);
3906 return TRUE;
3909 /* Because we're visible but offscreen, we just set a flag in the preedit
3910 * callback.
3912 static void
3913 nautilus_icon_container_search_preedit_changed (GtkIMContext *im_context,
3914 NautilusIconContainer *container)
3916 container->details->imcontext_changed = 1;
3917 if (container->details->typeselect_flush_timeout) {
3918 g_source_remove (container->details->typeselect_flush_timeout);
3919 container->details->typeselect_flush_timeout =
3920 g_timeout_add (NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
3921 (GSourceFunc) nautilus_icon_container_search_entry_flush_timeout,
3922 container);
3926 static void
3927 nautilus_icon_container_search_activate (GtkEntry *entry,
3928 NautilusIconContainer *container)
3930 nautilus_icon_container_search_dialog_hide (container->details->search_window,
3931 container);
3933 activate_selected_items (container);
3936 static gboolean
3937 nautilus_icon_container_search_delete_event (GtkWidget *widget,
3938 GdkEventAny *event,
3939 NautilusIconContainer *container)
3941 g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
3943 nautilus_icon_container_search_dialog_hide (widget, container);
3945 return TRUE;
3948 static gboolean
3949 nautilus_icon_container_search_button_press_event (GtkWidget *widget,
3950 GdkEventButton *event,
3951 NautilusIconContainer *container)
3953 g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
3955 nautilus_icon_container_search_dialog_hide (widget, container);
3957 if (event->window == GTK_LAYOUT (container)->bin_window) {
3958 button_press_event (GTK_WIDGET (container), event);
3961 return TRUE;
3964 static gboolean
3965 nautilus_icon_container_search_iter (NautilusIconContainer *container,
3966 const char *key, gint n)
3968 GList *p;
3969 NautilusIcon *icon;
3970 const char *name;
3971 int count;
3972 char *normalized_key, *case_normalized_key;
3973 char *normalized_name, *case_normalized_name;
3975 g_return_val_if_fail (key != NULL, FALSE);
3976 g_return_val_if_fail (n >= 1, FALSE);
3978 normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
3979 if (!normalized_key) {
3980 return FALSE;
3982 case_normalized_key = g_utf8_casefold (normalized_key, -1);
3983 g_free (normalized_key);
3984 if (!case_normalized_key) {
3985 return FALSE;
3988 icon = NULL;
3989 count = 0;
3990 for (p = container->details->icons; p != NULL && count != n; p = p->next) {
3991 icon = p->data;
3992 name = nautilus_icon_canvas_item_get_editable_text (icon->item);
3994 /* This can happen if a key event is handled really early while
3995 * loading the icon container, before the items have all been
3996 * updated once.
3998 if (!name) {
3999 continue;
4002 normalized_name = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
4003 if (!normalized_name) {
4004 continue;
4006 case_normalized_name = g_utf8_casefold (normalized_name, -1);
4007 g_free (normalized_name);
4008 if (!case_normalized_name) {
4009 continue;
4012 if (strncmp (case_normalized_key, case_normalized_name,
4013 strlen (case_normalized_key)) == 0) {
4014 count++;
4017 g_free (case_normalized_name);
4020 g_free (case_normalized_key);
4022 if (count == n) {
4023 if (select_one_unselect_others (container, icon)) {
4024 g_signal_emit (container, signals[SELECTION_CHANGED], 0);
4026 schedule_keyboard_icon_reveal (container, icon);
4028 return TRUE;
4031 return FALSE;
4034 static void
4035 nautilus_icon_container_search_move (GtkWidget *window,
4036 NautilusIconContainer *container,
4037 gboolean up)
4039 gboolean ret;
4040 gint len;
4041 gint count = 0;
4042 const gchar *text;
4044 text = gtk_entry_get_text (GTK_ENTRY (container->details->search_entry));
4046 g_return_if_fail (text != NULL);
4048 if (container->details->selected_iter == 0) {
4049 return;
4052 if (up && container->details->selected_iter == 1) {
4053 return;
4056 len = strlen (text);
4058 if (len < 1) {
4059 return;
4062 /* search */
4063 unselect_all (container);
4065 ret = nautilus_icon_container_search_iter (container, text,
4066 up?((container->details->selected_iter) - 1):((container->details->selected_iter + 1)));
4068 if (ret) {
4069 /* found */
4070 container->details->selected_iter += up?(-1):(1);
4071 } else {
4072 /* return to old iter */
4073 count = 0;
4074 nautilus_icon_container_search_iter (container, text,
4075 container->details->selected_iter);
4079 static gboolean
4080 nautilus_icon_container_search_scroll_event (GtkWidget *widget,
4081 GdkEventScroll *event,
4082 NautilusIconContainer *container)
4084 gboolean retval = FALSE;
4086 if (event->direction == GDK_SCROLL_UP) {
4087 nautilus_icon_container_search_move (widget, container, TRUE);
4088 retval = TRUE;
4089 } else if (event->direction == GDK_SCROLL_DOWN) {
4090 nautilus_icon_container_search_move (widget, container, FALSE);
4091 retval = TRUE;
4094 /* renew the flush timeout */
4095 if (retval && container->details->typeselect_flush_timeout) {
4096 g_source_remove (container->details->typeselect_flush_timeout);
4097 container->details->typeselect_flush_timeout =
4098 g_timeout_add (NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
4099 (GSourceFunc) nautilus_icon_container_search_entry_flush_timeout,
4100 container);
4103 return retval;
4106 static gboolean
4107 nautilus_icon_container_search_key_press_event (GtkWidget *widget,
4108 GdkEventKey *event,
4109 NautilusIconContainer *container)
4111 gboolean retval = FALSE;
4113 g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
4114 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), FALSE);
4116 /* close window and cancel the search */
4117 if (event->keyval == GDK_Escape || event->keyval == GDK_Tab) {
4118 nautilus_icon_container_search_dialog_hide (widget, container);
4119 return TRUE;
4122 /* select previous matching iter */
4123 if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up) {
4124 nautilus_icon_container_search_move (widget, container, TRUE);
4125 retval = TRUE;
4128 if (((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
4129 && (event->keyval == GDK_g || event->keyval == GDK_G)) {
4130 nautilus_icon_container_search_move (widget, container, TRUE);
4131 retval = TRUE;
4134 /* select next matching iter */
4135 if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down) {
4136 nautilus_icon_container_search_move (widget, container, FALSE);
4137 retval = TRUE;
4140 if (((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == GDK_CONTROL_MASK)
4141 && (event->keyval == GDK_g || event->keyval == GDK_G)) {
4142 nautilus_icon_container_search_move (widget, container, FALSE);
4143 retval = TRUE;
4146 /* renew the flush timeout */
4147 if (retval && container->details->typeselect_flush_timeout) {
4148 g_source_remove (container->details->typeselect_flush_timeout);
4149 container->details->typeselect_flush_timeout =
4150 g_timeout_add (NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
4151 (GSourceFunc) nautilus_icon_container_search_entry_flush_timeout,
4152 container);
4155 return retval;
4158 static void
4159 nautilus_icon_container_search_init (GtkWidget *entry,
4160 NautilusIconContainer *container)
4162 gint ret;
4163 gint len;
4164 const gchar *text;
4166 g_return_if_fail (GTK_IS_ENTRY (entry));
4167 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
4169 text = gtk_entry_get_text (GTK_ENTRY (entry));
4170 len = strlen (text);
4172 /* search */
4173 unselect_all (container);
4174 if (container->details->typeselect_flush_timeout)
4176 g_source_remove (container->details->typeselect_flush_timeout);
4177 container->details->typeselect_flush_timeout =
4178 g_timeout_add (NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
4179 (GSourceFunc) nautilus_icon_container_search_entry_flush_timeout,
4180 container);
4183 if (len < 1) {
4184 return;
4187 ret = nautilus_icon_container_search_iter (container, text, 1);
4189 if (ret) {
4190 container->details->selected_iter = 1;
4194 static void
4195 nautilus_icon_container_ensure_interactive_directory (NautilusIconContainer *container)
4197 GtkWidget *frame, *vbox, *toplevel;
4199 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (container));
4201 if (container->details->search_window != NULL) {
4202 return;
4205 container->details->search_window = gtk_window_new (GTK_WINDOW_POPUP);
4207 gtk_window_set_modal (GTK_WINDOW (container->details->search_window), TRUE);
4208 g_signal_connect (container->details->search_window, "delete_event",
4209 G_CALLBACK (nautilus_icon_container_search_delete_event),
4210 container);
4211 g_signal_connect (container->details->search_window, "key_press_event",
4212 G_CALLBACK (nautilus_icon_container_search_key_press_event),
4213 container);
4214 g_signal_connect (container->details->search_window, "button_press_event",
4215 G_CALLBACK (nautilus_icon_container_search_button_press_event),
4216 container);
4217 g_signal_connect (container->details->search_window, "scroll_event",
4218 G_CALLBACK (nautilus_icon_container_search_scroll_event),
4219 container);
4221 frame = gtk_frame_new (NULL);
4222 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
4223 gtk_widget_show (frame);
4224 gtk_container_add (GTK_CONTAINER (container->details->search_window), frame);
4226 vbox = gtk_vbox_new (FALSE, 0);
4227 gtk_widget_show (vbox);
4228 gtk_container_add (GTK_CONTAINER (frame), vbox);
4229 gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
4231 /* add entry */
4232 container->details->search_entry = gtk_entry_new ();
4233 gtk_widget_show (container->details->search_entry);
4234 g_signal_connect (container->details->search_entry, "populate_popup",
4235 G_CALLBACK (nautilus_icon_container_search_disable_popdown),
4236 container);
4237 g_signal_connect (container->details->search_entry, "activate",
4238 G_CALLBACK (nautilus_icon_container_search_activate),
4239 container);
4240 g_signal_connect (GTK_ENTRY (container->details->search_entry)->im_context,
4241 "preedit-changed",
4242 G_CALLBACK (nautilus_icon_container_search_preedit_changed),
4243 container);
4244 gtk_container_add (GTK_CONTAINER (vbox), container->details->search_entry);
4246 gtk_widget_realize (container->details->search_entry);
4249 /* Pops up the interactive search entry. If keybinding is TRUE then the user
4250 * started this by typing the start_interactive_search keybinding. Otherwise, it came from
4252 static gboolean
4253 nautilus_icon_container_real_start_interactive_search (NautilusIconContainer *container,
4254 gboolean keybinding)
4256 /* We only start interactive search if we have focus. If one of our
4257 * children have focus, we don't want to start the search.
4259 GtkWidgetClass *entry_parent_class;
4261 if (container->details->search_window != NULL &&
4262 GTK_WIDGET_VISIBLE (container->details->search_window)) {
4263 return TRUE;
4266 if (!GTK_WIDGET_HAS_FOCUS (container)) {
4267 return FALSE;
4270 nautilus_icon_container_ensure_interactive_directory (container);
4272 if (keybinding) {
4273 gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
4276 /* done, show it */
4277 nautilus_icon_container_search_position_func (container, container->details->search_window);
4278 gtk_widget_show (container->details->search_window);
4279 if (container->details->search_entry_changed_id == 0) {
4280 container->details->search_entry_changed_id =
4281 g_signal_connect (container->details->search_entry, "changed",
4282 G_CALLBACK (nautilus_icon_container_search_init),
4283 container);
4286 container->details->typeselect_flush_timeout =
4287 g_timeout_add (NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
4288 (GSourceFunc) nautilus_icon_container_search_entry_flush_timeout,
4289 container);
4291 /* Grab focus will select all the text. We don't want that to happen, so we
4292 * call the parent instance and bypass the selection change. This is probably
4293 * really non-kosher. */
4294 entry_parent_class = g_type_class_peek_parent (GTK_ENTRY_GET_CLASS (container->details->search_entry));
4295 (entry_parent_class->grab_focus) (container->details->search_entry);
4297 /* send focus-in event */
4298 send_focus_change (container->details->search_entry, TRUE);
4300 /* search first matching iter */
4301 nautilus_icon_container_search_init (container->details->search_entry, container);
4303 return TRUE;
4306 static gboolean
4307 nautilus_icon_container_start_interactive_search (NautilusIconContainer *container)
4309 return nautilus_icon_container_real_start_interactive_search (container, TRUE);
4312 static gboolean
4313 handle_popups (NautilusIconContainer *container,
4314 GdkEventKey *event,
4315 const char *signal)
4317 GdkEventButton button_event = { 0 };
4319 g_signal_emit_by_name (container, signal, &button_event);
4321 return TRUE;
4324 static int
4325 key_press_event (GtkWidget *widget,
4326 GdkEventKey *event)
4328 NautilusIconContainer *container;
4329 gboolean handled;
4331 container = NAUTILUS_ICON_CONTAINER (widget);
4332 handled = FALSE;
4334 if (is_renaming (container) || is_renaming_pending (container)) {
4335 switch (event->keyval) {
4336 case GDK_Return:
4337 case GDK_KP_Enter:
4338 end_renaming_mode (container, TRUE);
4339 handled = TRUE;
4340 break;
4341 case GDK_Escape:
4342 end_renaming_mode (container, FALSE);
4343 handled = TRUE;
4344 break;
4345 default:
4346 break;
4348 } else {
4349 switch (event->keyval) {
4350 case GDK_Home:
4351 case GDK_KP_Home:
4352 keyboard_home (container, event);
4353 handled = TRUE;
4354 break;
4355 case GDK_End:
4356 case GDK_KP_End:
4357 keyboard_end (container, event);
4358 handled = TRUE;
4359 break;
4360 case GDK_Left:
4361 case GDK_KP_Left:
4362 keyboard_left (container, event);
4363 handled = TRUE;
4364 break;
4365 case GDK_Up:
4366 case GDK_KP_Up:
4367 /* Don't eat Alt-Up, as that is used for alt-shift-Up */
4368 if ((event->state & GDK_MOD1_MASK) == 0) {
4369 keyboard_up (container, event);
4370 handled = TRUE;
4372 break;
4373 case GDK_Right:
4374 case GDK_KP_Right:
4375 keyboard_right (container, event);
4376 handled = TRUE;
4377 break;
4378 case GDK_Down:
4379 case GDK_KP_Down:
4380 /* Don't eat Alt-Down, as that is used for Open */
4381 if ((event->state & GDK_MOD1_MASK) == 0) {
4382 keyboard_down (container, event);
4383 handled = TRUE;
4385 break;
4386 case GDK_space:
4387 keyboard_space (container, event);
4388 handled = TRUE;
4389 break;
4390 #ifndef TAB_NAVIGATION_DISABLED
4391 case GDK_Tab:
4392 case GDK_ISO_Left_Tab:
4393 select_previous_or_next_icon (container,
4394 (event->state & GDK_SHIFT_MASK) == 0, event);
4395 handled = TRUE;
4396 break;
4397 #endif
4398 case GDK_Return:
4399 case GDK_KP_Enter:
4400 if ((event->state & GDK_SHIFT_MASK) != 0) {
4401 activate_selected_items_alternate (container, NULL);
4402 } else {
4403 activate_selected_items (container);
4406 handled = TRUE;
4407 break;
4408 case GDK_Escape:
4409 handled = undo_stretching (container);
4410 break;
4411 case GDK_plus:
4412 case GDK_minus:
4413 case GDK_equal:
4414 case GDK_KP_Add:
4415 case GDK_KP_Subtract:
4416 case GDK_0:
4417 case GDK_KP_0:
4418 if (event->state & GDK_CONTROL_MASK) {
4419 handled = keyboard_stretching (container, event);
4421 break;
4422 case GDK_F10:
4423 /* handle Ctrl+F10 because we want to display the
4424 * background popup even if something is selected.
4425 * The other cases are handled by popup_menu().
4427 if (event->state & GDK_CONTROL_MASK) {
4428 handled = handle_popups (container, event,
4429 "context_click_background");
4431 break;
4432 default:
4433 break;
4437 if (!handled) {
4438 handled = GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
4441 /* We pass the event to the search_entry. If its text changes, then we
4442 * start the typeahead find capabilities.
4443 * Copied from NautilusIconContainer */
4444 if (!handled &&
4445 event->keyval != GDK_slash /* don't steal slash key event, used for "go to" */ &&
4446 event->keyval != GDK_BackSpace &&
4447 event->keyval != GDK_Delete) {
4448 GdkEvent *new_event;
4449 char *old_text;
4450 const char *new_text;
4451 gboolean retval;
4452 GdkScreen *screen;
4453 gboolean text_modified;
4454 gulong popup_menu_id;
4456 nautilus_icon_container_ensure_interactive_directory (container);
4458 /* Make a copy of the current text */
4459 old_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (container->details->search_entry)));
4460 new_event = gdk_event_copy ((GdkEvent *) event);
4461 ((GdkEventKey *) new_event)->window = container->details->search_entry->window;
4462 gtk_widget_realize (container->details->search_window);
4464 popup_menu_id = g_signal_connect (container->details->search_entry,
4465 "popup_menu", G_CALLBACK (gtk_true), NULL);
4467 /* Move the entry off screen */
4468 screen = gtk_widget_get_screen (GTK_WIDGET (container));
4469 gtk_window_move (GTK_WINDOW (container->details->search_window),
4470 gdk_screen_get_width (screen) + 1,
4471 gdk_screen_get_height (screen) + 1);
4472 gtk_widget_show (container->details->search_window);
4474 /* Send the event to the window. If the preedit_changed signal is emitted
4475 * during this event, we will set priv->imcontext_changed */
4476 container->details->imcontext_changed = FALSE;
4477 retval = gtk_widget_event (container->details->search_entry, new_event);
4478 gtk_widget_hide (container->details->search_window);
4480 g_signal_handler_disconnect (container->details->search_entry,
4481 popup_menu_id);
4483 /* We check to make sure that the entry tried to handle the text, and that
4484 * the text has changed. */
4485 new_text = gtk_entry_get_text (GTK_ENTRY (container->details->search_entry));
4486 text_modified = strcmp (old_text, new_text) != 0;
4487 g_free (old_text);
4488 if (container->details->imcontext_changed || /* we're in a preedit */
4489 (retval && text_modified)) { /* ...or the text was modified */
4490 if (nautilus_icon_container_real_start_interactive_search (container, FALSE)) {
4491 gtk_widget_grab_focus (GTK_WIDGET (container));
4492 return TRUE;
4493 } else {
4494 gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
4495 return FALSE;
4500 return handled;
4503 static gboolean
4504 popup_menu (GtkWidget *widget)
4506 NautilusIconContainer *container;
4508 container = NAUTILUS_ICON_CONTAINER (widget);
4510 if (has_selection (container)) {
4511 handle_popups (container, NULL,
4512 "context_click_selection");
4513 } else {
4514 handle_popups (container, NULL,
4515 "context_click_background");
4518 return TRUE;
4521 static void
4522 draw_canvas_background (EelCanvas *canvas,
4523 int x, int y, int width, int height)
4525 /* Don't chain up to the parent to avoid clearing and redrawing */
4529 static gboolean
4530 expose_event (GtkWidget *widget,
4531 GdkEventExpose *event)
4533 /* g_warning ("Expose Icon Container %p '%d,%d: %d,%d'",
4534 widget,
4535 event->area.x, event->area.y,
4536 event->area.width, event->area.height); */
4538 return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
4541 static AtkObject *
4542 get_accessible (GtkWidget *widget)
4544 AtkObject *accessible;
4546 if ((accessible = eel_accessibility_get_atk_object (widget))) {
4547 return accessible;
4550 accessible = g_object_new
4551 (nautilus_icon_container_accessible_get_type (), NULL);
4553 return eel_accessibility_set_atk_object_return (widget, accessible);
4556 static void
4557 grab_notify_cb (GtkWidget *widget,
4558 gboolean was_grabbed)
4560 NautilusIconContainer *container;
4562 container = NAUTILUS_ICON_CONTAINER (widget);
4564 if (container->details->rubberband_info.active &&
4565 !was_grabbed) {
4566 /* we got a (un)grab-notify during rubberband.
4567 * This happens when a new modal dialog shows
4568 * up (e.g. authentication or an error). Stop
4569 * the rubberbanding so that we can handle the
4570 * dialog. */
4571 stop_rubberbanding (container,
4572 GDK_CURRENT_TIME);
4577 /* Initialization. */
4579 static void
4580 nautilus_icon_container_class_init (NautilusIconContainerClass *class)
4582 GtkWidgetClass *widget_class;
4583 EelCanvasClass *canvas_class;
4584 GtkBindingSet *binding_set;
4586 G_OBJECT_CLASS (class)->finalize = finalize;
4587 GTK_OBJECT_CLASS (class)->destroy = destroy;
4589 /* Signals. */
4591 signals[SELECTION_CHANGED]
4592 = g_signal_new ("selection_changed",
4593 G_TYPE_FROM_CLASS (class),
4594 G_SIGNAL_RUN_LAST,
4595 G_STRUCT_OFFSET (NautilusIconContainerClass,
4596 selection_changed),
4597 NULL, NULL,
4598 g_cclosure_marshal_VOID__VOID,
4599 G_TYPE_NONE, 0);
4600 signals[BUTTON_PRESS]
4601 = g_signal_new ("button_press",
4602 G_TYPE_FROM_CLASS (class),
4603 G_SIGNAL_RUN_LAST,
4604 G_STRUCT_OFFSET (NautilusIconContainerClass,
4605 button_press),
4606 NULL, NULL,
4607 nautilus_marshal_BOOLEAN__POINTER,
4608 G_TYPE_BOOLEAN, 1,
4609 GDK_TYPE_EVENT);
4610 signals[ACTIVATE]
4611 = g_signal_new ("activate",
4612 G_TYPE_FROM_CLASS (class),
4613 G_SIGNAL_RUN_LAST,
4614 G_STRUCT_OFFSET (NautilusIconContainerClass,
4615 activate),
4616 NULL, NULL,
4617 g_cclosure_marshal_VOID__POINTER,
4618 G_TYPE_NONE, 1,
4619 G_TYPE_POINTER);
4620 signals[ACTIVATE_ALTERNATE]
4621 = g_signal_new ("activate_alternate",
4622 G_TYPE_FROM_CLASS (class),
4623 G_SIGNAL_RUN_LAST,
4624 G_STRUCT_OFFSET (NautilusIconContainerClass,
4625 activate_alternate),
4626 NULL, NULL,
4627 g_cclosure_marshal_VOID__POINTER,
4628 G_TYPE_NONE, 1,
4629 G_TYPE_POINTER);
4630 signals[CONTEXT_CLICK_SELECTION]
4631 = g_signal_new ("context_click_selection",
4632 G_TYPE_FROM_CLASS (class),
4633 G_SIGNAL_RUN_LAST,
4634 G_STRUCT_OFFSET (NautilusIconContainerClass,
4635 context_click_selection),
4636 NULL, NULL,
4637 g_cclosure_marshal_VOID__POINTER,
4638 G_TYPE_NONE, 1,
4639 G_TYPE_POINTER);
4640 signals[CONTEXT_CLICK_BACKGROUND]
4641 = g_signal_new ("context_click_background",
4642 G_TYPE_FROM_CLASS (class),
4643 G_SIGNAL_RUN_LAST,
4644 G_STRUCT_OFFSET (NautilusIconContainerClass,
4645 context_click_background),
4646 NULL, NULL,
4647 g_cclosure_marshal_VOID__POINTER,
4648 G_TYPE_NONE, 1,
4649 G_TYPE_POINTER);
4650 signals[MIDDLE_CLICK]
4651 = g_signal_new ("middle_click",
4652 G_TYPE_FROM_CLASS (class),
4653 G_SIGNAL_RUN_LAST,
4654 G_STRUCT_OFFSET (NautilusIconContainerClass,
4655 middle_click),
4656 NULL, NULL,
4657 g_cclosure_marshal_VOID__POINTER,
4658 G_TYPE_NONE, 1,
4659 G_TYPE_POINTER);
4660 signals[ICON_POSITION_CHANGED]
4661 = g_signal_new ("icon_position_changed",
4662 G_TYPE_FROM_CLASS (class),
4663 G_SIGNAL_RUN_LAST,
4664 G_STRUCT_OFFSET (NautilusIconContainerClass,
4665 icon_position_changed),
4666 NULL, NULL,
4667 nautilus_marshal_VOID__POINTER_POINTER,
4668 G_TYPE_NONE, 2,
4669 G_TYPE_POINTER,
4670 G_TYPE_POINTER);
4671 signals[ICON_TEXT_CHANGED]
4672 = g_signal_new ("icon_text_changed",
4673 G_TYPE_FROM_CLASS (class),
4674 G_SIGNAL_RUN_LAST,
4675 G_STRUCT_OFFSET (NautilusIconContainerClass,
4676 icon_text_changed),
4677 NULL, NULL,
4678 nautilus_marshal_VOID__POINTER_STRING,
4679 G_TYPE_NONE, 2,
4680 G_TYPE_POINTER,
4681 G_TYPE_STRING);
4682 signals[ICON_STRETCH_STARTED]
4683 = g_signal_new ("icon_stretch_started",
4684 G_TYPE_FROM_CLASS (class),
4685 G_SIGNAL_RUN_LAST,
4686 G_STRUCT_OFFSET (NautilusIconContainerClass,
4687 icon_stretch_started),
4688 NULL, NULL,
4689 g_cclosure_marshal_VOID__POINTER,
4690 G_TYPE_NONE, 1,
4691 G_TYPE_POINTER);
4692 signals[ICON_STRETCH_ENDED]
4693 = g_signal_new ("icon_stretch_ended",
4694 G_TYPE_FROM_CLASS (class),
4695 G_SIGNAL_RUN_LAST,
4696 G_STRUCT_OFFSET (NautilusIconContainerClass,
4697 icon_stretch_ended),
4698 NULL, NULL,
4699 g_cclosure_marshal_VOID__POINTER,
4700 G_TYPE_NONE, 1,
4701 G_TYPE_POINTER);
4702 signals[RENAMING_ICON]
4703 = g_signal_new ("renaming_icon",
4704 G_TYPE_FROM_CLASS (class),
4705 G_SIGNAL_RUN_LAST,
4706 G_STRUCT_OFFSET (NautilusIconContainerClass,
4707 renaming_icon),
4708 NULL, NULL,
4709 g_cclosure_marshal_VOID__POINTER,
4710 G_TYPE_NONE, 1,
4711 G_TYPE_POINTER);
4712 signals[GET_ICON_URI]
4713 = g_signal_new ("get_icon_uri",
4714 G_TYPE_FROM_CLASS (class),
4715 G_SIGNAL_RUN_LAST,
4716 G_STRUCT_OFFSET (NautilusIconContainerClass,
4717 get_icon_uri),
4718 NULL, NULL,
4719 eel_marshal_STRING__POINTER,
4720 G_TYPE_STRING, 1,
4721 G_TYPE_POINTER);
4722 signals[GET_ICON_DROP_TARGET_URI]
4723 = g_signal_new ("get_icon_drop_target_uri",
4724 G_TYPE_FROM_CLASS (class),
4725 G_SIGNAL_RUN_LAST,
4726 G_STRUCT_OFFSET (NautilusIconContainerClass,
4727 get_icon_drop_target_uri),
4728 NULL, NULL,
4729 eel_marshal_STRING__POINTER,
4730 G_TYPE_STRING, 1,
4731 G_TYPE_POINTER);
4732 signals[MOVE_COPY_ITEMS]
4733 = g_signal_new ("move_copy_items",
4734 G_TYPE_FROM_CLASS (class),
4735 G_SIGNAL_RUN_LAST,
4736 G_STRUCT_OFFSET (NautilusIconContainerClass,
4737 move_copy_items),
4738 NULL, NULL,
4739 nautilus_marshal_VOID__POINTER_POINTER_POINTER_ENUM_INT_INT,
4740 G_TYPE_NONE, 6,
4741 G_TYPE_POINTER,
4742 G_TYPE_POINTER,
4743 G_TYPE_POINTER,
4744 GDK_TYPE_DRAG_ACTION,
4745 G_TYPE_INT,
4746 G_TYPE_INT);
4747 signals[HANDLE_NETSCAPE_URL]
4748 = g_signal_new ("handle_netscape_url",
4749 G_TYPE_FROM_CLASS (class),
4750 G_SIGNAL_RUN_LAST,
4751 G_STRUCT_OFFSET (NautilusIconContainerClass,
4752 handle_netscape_url),
4753 NULL, NULL,
4754 nautilus_marshal_VOID__STRING_STRING_ENUM_INT_INT,
4755 G_TYPE_NONE, 5,
4756 G_TYPE_STRING,
4757 G_TYPE_STRING,
4758 GDK_TYPE_DRAG_ACTION,
4759 G_TYPE_INT,
4760 G_TYPE_INT);
4761 signals[HANDLE_URI_LIST]
4762 = g_signal_new ("handle_uri_list",
4763 G_TYPE_FROM_CLASS (class),
4764 G_SIGNAL_RUN_LAST,
4765 G_STRUCT_OFFSET (NautilusIconContainerClass,
4766 handle_uri_list),
4767 NULL, NULL,
4768 nautilus_marshal_VOID__STRING_STRING_ENUM_INT_INT,
4769 G_TYPE_NONE, 5,
4770 G_TYPE_STRING,
4771 G_TYPE_STRING,
4772 GDK_TYPE_DRAG_ACTION,
4773 G_TYPE_INT,
4774 G_TYPE_INT);
4775 signals[HANDLE_TEXT]
4776 = g_signal_new ("handle_text",
4777 G_TYPE_FROM_CLASS (class),
4778 G_SIGNAL_RUN_LAST,
4779 G_STRUCT_OFFSET (NautilusIconContainerClass,
4780 handle_text),
4781 NULL, NULL,
4782 nautilus_marshal_VOID__STRING_STRING_ENUM_INT_INT,
4783 G_TYPE_NONE, 5,
4784 G_TYPE_STRING,
4785 G_TYPE_STRING,
4786 GDK_TYPE_DRAG_ACTION,
4787 G_TYPE_INT,
4788 G_TYPE_INT);
4789 signals[GET_CONTAINER_URI]
4790 = g_signal_new ("get_container_uri",
4791 G_TYPE_FROM_CLASS (class),
4792 G_SIGNAL_RUN_LAST,
4793 G_STRUCT_OFFSET (NautilusIconContainerClass,
4794 get_container_uri),
4795 NULL, NULL,
4796 eel_marshal_STRING__VOID,
4797 G_TYPE_STRING, 0);
4798 signals[CAN_ACCEPT_ITEM]
4799 = g_signal_new ("can_accept_item",
4800 G_TYPE_FROM_CLASS (class),
4801 G_SIGNAL_RUN_LAST,
4802 G_STRUCT_OFFSET (NautilusIconContainerClass,
4803 can_accept_item),
4804 NULL, NULL,
4805 eel_marshal_INT__POINTER_STRING,
4806 G_TYPE_INT, 2,
4807 G_TYPE_POINTER,
4808 G_TYPE_STRING);
4809 signals[GET_STORED_ICON_POSITION]
4810 = g_signal_new ("get_stored_icon_position",
4811 G_TYPE_FROM_CLASS (class),
4812 G_SIGNAL_RUN_LAST,
4813 G_STRUCT_OFFSET (NautilusIconContainerClass,
4814 get_stored_icon_position),
4815 NULL, NULL,
4816 eel_marshal_BOOLEAN__POINTER_POINTER,
4817 G_TYPE_BOOLEAN, 2,
4818 G_TYPE_POINTER,
4819 G_TYPE_POINTER);
4820 signals[LAYOUT_CHANGED]
4821 = g_signal_new ("layout_changed",
4822 G_TYPE_FROM_CLASS (class),
4823 G_SIGNAL_RUN_LAST,
4824 G_STRUCT_OFFSET (NautilusIconContainerClass,
4825 layout_changed),
4826 NULL, NULL,
4827 g_cclosure_marshal_VOID__VOID,
4828 G_TYPE_NONE, 0);
4829 signals[PREVIEW]
4830 = g_signal_new ("preview",
4831 G_TYPE_FROM_CLASS (class),
4832 G_SIGNAL_RUN_LAST,
4833 G_STRUCT_OFFSET (NautilusIconContainerClass,
4834 preview),
4835 NULL, NULL,
4836 nautilus_marshal_INT__POINTER_BOOLEAN,
4837 G_TYPE_INT, 2,
4838 G_TYPE_POINTER,
4839 G_TYPE_BOOLEAN);
4840 signals[BAND_SELECT_STARTED]
4841 = g_signal_new ("band_select_started",
4842 G_TYPE_FROM_CLASS (class),
4843 G_SIGNAL_RUN_LAST,
4844 G_STRUCT_OFFSET (NautilusIconContainerClass,
4845 band_select_started),
4846 NULL, NULL,
4847 g_cclosure_marshal_VOID__VOID,
4848 G_TYPE_NONE, 0);
4849 signals[BAND_SELECT_ENDED]
4850 = g_signal_new ("band_select_ended",
4851 G_TYPE_FROM_CLASS (class),
4852 G_SIGNAL_RUN_LAST,
4853 G_STRUCT_OFFSET (NautilusIconContainerClass,
4854 band_select_ended),
4855 NULL, NULL,
4856 g_cclosure_marshal_VOID__VOID,
4857 G_TYPE_NONE, 0);
4858 signals[ICON_ADDED]
4859 = g_signal_new ("icon_added",
4860 G_TYPE_FROM_CLASS (class),
4861 G_SIGNAL_RUN_LAST,
4862 G_STRUCT_OFFSET (NautilusIconContainerClass,
4863 icon_added),
4864 NULL, NULL,
4865 g_cclosure_marshal_VOID__POINTER,
4866 G_TYPE_NONE, 1, G_TYPE_POINTER);
4867 signals[ICON_REMOVED]
4868 = g_signal_new ("icon_removed",
4869 G_TYPE_FROM_CLASS (class),
4870 G_SIGNAL_RUN_LAST,
4871 G_STRUCT_OFFSET (NautilusIconContainerClass,
4872 icon_removed),
4873 NULL, NULL,
4874 g_cclosure_marshal_VOID__POINTER,
4875 G_TYPE_NONE, 1, G_TYPE_POINTER);
4877 signals[CLEARED]
4878 = g_signal_new ("cleared",
4879 G_TYPE_FROM_CLASS (class),
4880 G_SIGNAL_RUN_LAST,
4881 G_STRUCT_OFFSET (NautilusIconContainerClass,
4882 cleared),
4883 NULL, NULL,
4884 g_cclosure_marshal_VOID__VOID,
4885 G_TYPE_NONE, 0);
4887 signals[START_INTERACTIVE_SEARCH]
4888 = g_signal_new ("start_interactive_search",
4889 G_TYPE_FROM_CLASS (class),
4890 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
4891 G_STRUCT_OFFSET (NautilusIconContainerClass,
4892 start_interactive_search),
4893 NULL, NULL,
4894 nautilus_marshal_BOOLEAN__VOID,
4895 G_TYPE_BOOLEAN, 0);
4897 /* GtkWidget class. */
4899 widget_class = GTK_WIDGET_CLASS (class);
4900 widget_class->size_request = size_request;
4901 widget_class->size_allocate = size_allocate;
4902 widget_class->realize = realize;
4903 widget_class->unrealize = unrealize;
4904 widget_class->button_press_event = button_press_event;
4905 widget_class->button_release_event = button_release_event;
4906 widget_class->motion_notify_event = motion_notify_event;
4907 widget_class->key_press_event = key_press_event;
4908 widget_class->popup_menu = popup_menu;
4909 widget_class->get_accessible = get_accessible;
4910 widget_class->style_set = style_set;
4911 widget_class->expose_event = expose_event;
4912 widget_class->grab_notify = grab_notify_cb;
4914 canvas_class = EEL_CANVAS_CLASS (class);
4915 canvas_class->draw_background = draw_canvas_background;
4917 class->start_interactive_search = nautilus_icon_container_start_interactive_search;
4919 gtk_widget_class_install_style_property (widget_class,
4920 g_param_spec_boolean ("frame_text",
4921 "Frame Text",
4922 "Draw a frame around unselected text",
4923 FALSE,
4924 G_PARAM_READABLE));
4926 gtk_widget_class_install_style_property (widget_class,
4927 g_param_spec_boxed ("selection_box_color",
4928 "Selection Box Color",
4929 "Color of the selection box",
4930 GDK_TYPE_COLOR,
4931 G_PARAM_READABLE));
4932 gtk_widget_class_install_style_property (widget_class,
4933 g_param_spec_uchar ("selection_box_alpha",
4934 "Selection Box Alpha",
4935 "Opacity of the selection box",
4936 0, 0xff,
4937 DEFAULT_SELECTION_BOX_ALPHA,
4938 G_PARAM_READABLE));
4940 gtk_widget_class_install_style_property (widget_class,
4941 g_param_spec_uchar ("highlight_alpha",
4942 "Highlight Alpha",
4943 "Opacity of the highlight for selected icons",
4944 0, 0xff,
4945 DEFAULT_HIGHLIGHT_ALPHA,
4946 G_PARAM_READABLE));
4947 gtk_widget_class_install_style_property (widget_class,
4948 g_param_spec_uchar ("normal_alpha",
4949 "Normal Alpha",
4950 "Opacity of the normal icons if frame_text is set",
4951 0, 0xff,
4952 DEFAULT_NORMAL_ALPHA,
4953 G_PARAM_READABLE));
4954 gtk_widget_class_install_style_property (widget_class,
4955 g_param_spec_uchar ("prelight_alpha",
4956 "Prelight Alpha",
4957 "Opacity of the prelight icons if frame_text is set",
4958 0, 0xff,
4959 DEFAULT_PRELIGHT_ALPHA,
4960 G_PARAM_READABLE));
4961 gtk_widget_class_install_style_property (widget_class,
4962 g_param_spec_boxed ("light_info_color",
4963 "Light Info Color",
4964 "Color used for information text against a dark background",
4965 GDK_TYPE_COLOR,
4966 G_PARAM_READABLE));
4967 gtk_widget_class_install_style_property (widget_class,
4968 g_param_spec_boxed ("dark_info_color",
4969 "Dark Info Color",
4970 "Color used for information text against a light background",
4971 GDK_TYPE_COLOR,
4972 G_PARAM_READABLE));
4974 gtk_widget_class_install_style_property (widget_class,
4975 g_param_spec_uint ("normal_icon_render_mode",
4976 "Normal Icon Render Mode",
4977 "Mode of normal icons being rendered (0=normal, 1=spotlight, 2=colorize, 3=colorize-monochromely)",
4978 0, 3,
4979 DEFAULT_NORMAL_ICON_RENDER_MODE,
4980 G_PARAM_READABLE));
4981 gtk_widget_class_install_style_property (widget_class,
4982 g_param_spec_uint ("prelight_icon_render_mode",
4983 "Prelight Icon Render Mode",
4984 "Mode of prelight icons being rendered (0=normal, 1=spotlight, 2=colorize, 3=colorize-monochromely)",
4985 0, 3,
4986 DEFAULT_PRELIGHT_ICON_RENDER_MODE,
4987 G_PARAM_READABLE));
4988 gtk_widget_class_install_style_property (widget_class,
4989 g_param_spec_boxed ("normal_icon_color",
4990 "Icon Normal Color",
4991 "Color used for colorizing icons in normal state (default base[NORMAL])",
4992 GDK_TYPE_COLOR,
4993 G_PARAM_READABLE));
4994 gtk_widget_class_install_style_property (widget_class,
4995 g_param_spec_boxed ("prelight_icon_color",
4996 "Icon Prelight Color",
4997 "Color used for colorizing prelighted icons (default base[PRELIGHT])",
4998 GDK_TYPE_COLOR,
4999 G_PARAM_READABLE));
5000 gtk_widget_class_install_style_property (widget_class,
5001 g_param_spec_uint ("normal_icon_saturation",
5002 "Normal Icon Saturation",
5003 "Saturation of icons in normal state",
5004 0, 255,
5005 DEFAULT_NORMAL_ICON_SATURATION,
5006 G_PARAM_READABLE));
5007 gtk_widget_class_install_style_property (widget_class,
5008 g_param_spec_uint ("prelight_icon_saturation",
5009 "Prelight Icon Saturation",
5010 "Saturation of icons in prelight state",
5011 0, 255,
5012 DEFAULT_PRELIGHT_ICON_SATURATION,
5013 G_PARAM_READABLE));
5014 gtk_widget_class_install_style_property (widget_class,
5015 g_param_spec_uint ("normal_icon_brightness",
5016 "Normal Icon Brightness",
5017 "Brightness of icons in normal state",
5018 0, 255,
5019 DEFAULT_NORMAL_ICON_BRIGHTNESS,
5020 G_PARAM_READABLE));
5021 gtk_widget_class_install_style_property (widget_class,
5022 g_param_spec_uint ("prelight_icon_brightness",
5023 "Prelight Icon Brightness",
5024 "Brightness of icons in prelight state",
5025 0, 255,
5026 DEFAULT_PRELIGHT_ICON_BRIGHTNESS,
5027 G_PARAM_READABLE));
5028 gtk_widget_class_install_style_property (widget_class,
5029 g_param_spec_uint ("normal_icon_lighten",
5030 "Normal Icon Lighten",
5031 "Lighten icons in normal state",
5032 0, 255,
5033 DEFAULT_NORMAL_ICON_LIGHTEN,
5034 G_PARAM_READABLE));
5035 gtk_widget_class_install_style_property (widget_class,
5036 g_param_spec_uint ("prelight_icon_lighten",
5037 "Prelight Icon Lighten",
5038 "Lighten icons in prelight state",
5039 0, 255,
5040 DEFAULT_PRELIGHT_ICON_LIGHTEN,
5041 G_PARAM_READABLE));
5042 gtk_widget_class_install_style_property (widget_class,
5043 g_param_spec_boolean ("activate_prelight_icon_label",
5044 "Activate Prelight Icon Label",
5045 "Whether icon labels should make use of its prelight color in prelight state",
5046 FALSE,
5047 G_PARAM_READABLE));
5050 binding_set = gtk_binding_set_by_class (class);
5052 gtk_binding_entry_add_signal (binding_set, GDK_f, GDK_CONTROL_MASK, "start_interactive_search", 0);
5053 gtk_binding_entry_add_signal (binding_set, GDK_F, GDK_CONTROL_MASK, "start_interactive_search", 0);
5056 static void
5057 update_selected (NautilusIconContainer *container)
5059 GList *node;
5060 NautilusIcon *icon;
5062 for (node = container->details->icons; node != NULL; node = node->next) {
5063 icon = node->data;
5064 if (icon->is_selected) {
5065 eel_canvas_item_request_update (EEL_CANVAS_ITEM (icon->item));
5070 static gboolean
5071 handle_focus_in_event (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
5073 update_selected (NAUTILUS_ICON_CONTAINER (widget));
5075 return FALSE;
5078 static gboolean
5079 handle_focus_out_event (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
5081 /* End renaming and commit change. */
5082 end_renaming_mode (NAUTILUS_ICON_CONTAINER (widget), TRUE);
5083 update_selected (NAUTILUS_ICON_CONTAINER (widget));
5085 return FALSE;
5088 static void
5089 nautilus_icon_container_instance_init (NautilusIconContainer *container)
5091 NautilusIconContainerDetails *details;
5092 EelBackground *background;
5094 details = g_new0 (NautilusIconContainerDetails, 1);
5096 details->icon_set = g_hash_table_new (g_direct_hash, g_direct_equal);
5098 details->zoom_level = NAUTILUS_ZOOM_LEVEL_STANDARD;
5100 details->font_size_table[NAUTILUS_ZOOM_LEVEL_SMALLEST] = -2 * PANGO_SCALE;
5101 details->font_size_table[NAUTILUS_ZOOM_LEVEL_SMALLER] = -2 * PANGO_SCALE;
5102 details->font_size_table[NAUTILUS_ZOOM_LEVEL_SMALL] = -0 * PANGO_SCALE;
5103 details->font_size_table[NAUTILUS_ZOOM_LEVEL_STANDARD] = 0 * PANGO_SCALE;
5104 details->font_size_table[NAUTILUS_ZOOM_LEVEL_LARGE] = 0 * PANGO_SCALE;
5105 details->font_size_table[NAUTILUS_ZOOM_LEVEL_LARGER] = 0 * PANGO_SCALE;
5106 details->font_size_table[NAUTILUS_ZOOM_LEVEL_LARGEST] = 0 * PANGO_SCALE;
5108 container->details = details;
5110 /* when the background changes, we must set up the label text color */
5111 background = eel_get_widget_background (GTK_WIDGET (container));
5113 g_signal_connect_object (background, "appearance_changed",
5114 G_CALLBACK (update_label_color), container, 0);
5116 g_signal_connect (container, "focus-in-event",
5117 G_CALLBACK (handle_focus_in_event), NULL);
5118 g_signal_connect (container, "focus-out-event",
5119 G_CALLBACK (handle_focus_out_event), NULL);
5121 eel_background_set_use_base (background, TRUE);
5123 /* read in theme-dependent data */
5124 nautilus_icon_container_theme_changed (container);
5125 eel_preferences_add_callback (NAUTILUS_PREFERENCES_THEME,
5126 nautilus_icon_container_theme_changed,
5127 container);
5130 typedef struct {
5131 NautilusIconContainer *container;
5132 GdkEventButton *event;
5133 } ContextMenuParameters;
5135 /* NautilusIcon event handling. */
5137 /* Conceptually, pressing button 1 together with CTRL or SHIFT toggles
5138 * selection of a single icon without affecting the other icons;
5139 * without CTRL or SHIFT, it selects a single icon and un-selects all
5140 * the other icons. But in this latter case, the de-selection should
5141 * only happen when the button is released if the icon is already
5142 * selected, because the user might select multiple icons and drag all
5143 * of them by doing a simple click-drag.
5146 static gboolean
5147 handle_icon_button_press (NautilusIconContainer *container,
5148 NautilusIcon *icon,
5149 GdkEventButton *event)
5151 NautilusIconContainerDetails *details;
5153 details = container->details;
5155 if (event->type == GDK_3BUTTON_PRESS) {
5156 return TRUE;
5159 if (details->single_click_mode &&
5160 event->type == GDK_2BUTTON_PRESS) {
5161 /* Don't care about double clicks in single click mode */
5162 return TRUE;
5165 if (event->button != DRAG_BUTTON
5166 && event->button != CONTEXTUAL_MENU_BUTTON
5167 && event->button != DRAG_MENU_BUTTON) {
5168 return TRUE;
5171 if ((event->button == DRAG_BUTTON || event->button == MIDDLE_BUTTON) &&
5172 event->type == GDK_BUTTON_PRESS) {
5173 /* The next double click has to be on this icon */
5174 details->double_click_icon[1] = details->double_click_icon[0];
5175 details->double_click_icon[0] = icon;
5177 if (event->type == GDK_2BUTTON_PRESS &&
5178 (event->button == DRAG_BUTTON || event->button == MIDDLE_BUTTON)) {
5179 /* Double clicking does not trigger a D&D action. */
5180 details->drag_button = 0;
5181 details->drag_icon = NULL;
5183 if (icon == details->double_click_icon[1]) {
5184 if (!button_event_modifies_selection (event)) {
5185 if (event->button == MIDDLE_BUTTON) {
5186 activate_selected_items_alternate (container, NULL);
5187 } else {
5188 activate_selected_items (container);
5190 } else if (event->button == DRAG_BUTTON &&
5191 (event->state & GDK_SHIFT_MASK) != 0) {
5192 activate_selected_items_alternate (container, icon);
5195 return TRUE;
5197 if (event->button == DRAG_BUTTON
5198 || event->button == DRAG_MENU_BUTTON) {
5199 details->drag_button = event->button;
5200 details->drag_icon = icon;
5201 details->drag_x = event->x;
5202 details->drag_y = event->y;
5203 details->drag_state = DRAG_STATE_MOVE_OR_COPY;
5204 details->drag_started = FALSE;
5206 /* Check to see if this is a click on the stretch handles.
5207 * If so, it won't modify the selection.
5209 if (icon == container->details->stretch_icon) {
5210 if (start_stretching (container)) {
5211 return TRUE;
5216 /* Modify the selection as appropriate. Selection is modified
5217 * the same way for contextual menu as it would be without.
5219 details->icon_selected_on_button_down = icon->is_selected;
5221 if ((event->button == DRAG_BUTTON || event->button == MIDDLE_BUTTON) &&
5222 (event->state & GDK_CONTROL_MASK) == 0 &&
5223 (event->state & GDK_SHIFT_MASK) != 0) {
5224 NautilusIcon *start_icon;
5226 start_icon = details->range_selection_base_icon;
5227 if (start_icon == NULL || !start_icon->is_selected) {
5228 start_icon = icon;
5229 details->range_selection_base_icon = icon;
5231 if (select_range (container, start_icon, icon)) {
5232 g_signal_emit (container,
5233 signals[SELECTION_CHANGED], 0);
5235 } else if (!details->icon_selected_on_button_down) {
5236 details->range_selection_base_icon = icon;
5237 if (button_event_modifies_selection (event)) {
5238 icon_toggle_selected (container, icon);
5239 g_signal_emit (container,
5240 signals[SELECTION_CHANGED], 0);
5241 } else {
5242 select_one_unselect_others (container, icon);
5243 g_signal_emit (container,
5244 signals[SELECTION_CHANGED], 0);
5248 if (event->button == CONTEXTUAL_MENU_BUTTON) {
5249 g_signal_emit (container,
5250 signals[CONTEXT_CLICK_SELECTION], 0,
5251 event);
5255 return TRUE;
5258 static int
5259 item_event_callback (EelCanvasItem *item,
5260 GdkEvent *event,
5261 gpointer data)
5263 NautilusIconContainer *container;
5264 NautilusIconContainerDetails *details;
5265 NautilusIcon *icon;
5267 container = NAUTILUS_ICON_CONTAINER (data);
5268 details = container->details;
5270 icon = NAUTILUS_ICON_CANVAS_ITEM (item)->user_data;
5271 g_return_val_if_fail (icon != NULL, FALSE);
5273 switch (event->type) {
5274 case GDK_BUTTON_PRESS:
5275 case GDK_2BUTTON_PRESS:
5276 if (handle_icon_button_press (container, icon, &event->button)) {
5277 /* Stop the event from being passed along further. Returning
5278 * TRUE ain't enough.
5280 return TRUE;
5282 return FALSE;
5283 default:
5284 return FALSE;
5288 GtkWidget *
5289 nautilus_icon_container_new (void)
5291 return gtk_widget_new (nautilus_icon_container_get_type (), NULL);
5294 /* Clear all of the icons in the container. */
5295 void
5296 nautilus_icon_container_clear (NautilusIconContainer *container)
5298 NautilusIconContainerDetails *details;
5299 NautilusIcon *icon;
5300 GList *p;
5302 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
5304 details = container->details;
5306 if (details->icons == NULL) {
5307 return;
5310 end_renaming_mode (container, TRUE);
5312 clear_keyboard_focus (container);
5313 clear_keyboard_rubberband_start (container);
5314 unschedule_keyboard_icon_reveal (container);
5315 set_pending_icon_to_reveal (container, NULL);
5316 details->stretch_icon = NULL;
5317 details->drop_target = NULL;
5319 for (p = details->icons; p != NULL; p = p->next) {
5320 icon = p->data;
5321 if (icon->is_monitored) {
5322 nautilus_icon_container_stop_monitor_top_left (container,
5323 icon->data,
5324 icon);
5326 icon_free (p->data);
5328 g_list_free (details->icons);
5329 details->icons = NULL;
5330 g_list_free (details->new_icons);
5331 details->new_icons = NULL;
5333 g_hash_table_destroy (details->icon_set);
5334 details->icon_set = g_hash_table_new (g_direct_hash, g_direct_equal);
5336 nautilus_icon_container_update_scroll_region (container);
5339 gboolean
5340 nautilus_icon_container_is_empty (NautilusIconContainer *container)
5342 return container->details->icons == NULL;
5345 NautilusIconData *
5346 nautilus_icon_container_get_first_visible_icon (NautilusIconContainer *container)
5348 GList *l;
5349 NautilusIcon *icon, *best_icon;
5350 GtkAdjustment *vadj;
5351 double x, y;
5352 double x1, y1, x2, y2;
5353 double best_y1;
5355 vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
5357 eel_canvas_c2w (EEL_CANVAS (container),
5358 0, vadj->value,
5359 &x, &y);
5361 l = container->details->icons;
5362 best_icon = NULL;
5363 best_y1 = 0;
5364 while (l != NULL) {
5365 icon = l->data;
5367 if (icon_is_positioned (icon)) {
5368 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
5369 &x1, &y1, &x2, &y2);
5370 if (y2 > y) {
5371 if (best_icon != NULL) {
5372 if (best_y1 > y1) {
5373 best_icon = icon;
5374 best_y1 = y1;
5376 } else {
5377 best_icon = icon;
5378 best_y1 = y1;
5383 l = l->next;
5386 return best_icon ? best_icon->data : NULL;
5389 /* puts the icon at the top of the screen */
5390 void
5391 nautilus_icon_container_scroll_to_icon (NautilusIconContainer *container,
5392 NautilusIconData *data)
5394 GList *l;
5395 NautilusIcon *icon;
5396 GtkAdjustment *vadj;
5397 int x, y;
5398 double x1, y1, x2, y2;
5399 EelCanvasItem *item;
5401 vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
5403 /* We need to force a relayout now if there are updates queued
5404 * since we need the final positions */
5405 nautilus_icon_container_layout_now (container);
5407 l = container->details->icons;
5408 while (l != NULL) {
5409 icon = l->data;
5411 if (icon->data == data &&
5412 icon_is_positioned (icon)) {
5413 item = EEL_CANVAS_ITEM (icon->item);
5414 eel_canvas_item_get_bounds (item,
5415 &x1, &y1, &x2, &y2);
5416 eel_canvas_item_i2w (item->parent,
5417 &x1,
5418 &y1);
5419 eel_canvas_w2c (item->canvas,
5420 x1, y1,
5421 &x, &y);
5423 y -= ICON_PAD_TOP;
5425 y = MAX (0, y);
5427 eel_gtk_adjustment_set_value (vadj, y);
5430 l = l->next;
5434 /* Call a function for all the icons. */
5435 typedef struct {
5436 NautilusIconCallback callback;
5437 gpointer callback_data;
5438 } CallbackAndData;
5440 static void
5441 call_icon_callback (gpointer data, gpointer callback_data)
5443 NautilusIcon *icon;
5444 CallbackAndData *callback_and_data;
5446 icon = data;
5447 callback_and_data = callback_data;
5448 (* callback_and_data->callback) (icon->data, callback_and_data->callback_data);
5451 void
5452 nautilus_icon_container_for_each (NautilusIconContainer *container,
5453 NautilusIconCallback callback,
5454 gpointer callback_data)
5456 CallbackAndData callback_and_data;
5458 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
5460 callback_and_data.callback = callback;
5461 callback_and_data.callback_data = callback_data;
5463 g_list_foreach (container->details->icons,
5464 call_icon_callback, &callback_and_data);
5467 /* utility routine to remove a single icon from the container */
5469 static void
5470 icon_destroy (NautilusIconContainer *container,
5471 NautilusIcon *icon)
5473 NautilusIconContainerDetails *details;
5474 gboolean was_selected;
5475 NautilusIcon *icon_to_focus;
5476 GList *item;
5478 details = container->details;
5480 item = g_list_find (details->icons, icon);
5481 item = item->next ? item->next : item->prev;
5482 icon_to_focus = (item != NULL) ? item->data : NULL;
5484 details->icons = g_list_remove (details->icons, icon);
5485 details->new_icons = g_list_remove (details->new_icons, icon);
5486 g_hash_table_remove (details->icon_set, icon->data);
5488 was_selected = icon->is_selected;
5490 if (details->keyboard_focus == icon ||
5491 details->keyboard_focus == NULL) {
5492 if (icon_to_focus != NULL) {
5493 set_keyboard_focus (container, icon_to_focus);
5494 } else {
5495 clear_keyboard_focus (container);
5499 if (details->keyboard_rubberband_start == icon) {
5500 clear_keyboard_rubberband_start (container);
5503 if (details->keyboard_icon_to_reveal == icon) {
5504 unschedule_keyboard_icon_reveal (container);
5506 if (details->drag_icon == icon) {
5507 clear_drag_state (container);
5509 if (details->drop_target == icon) {
5510 details->drop_target = NULL;
5512 if (details->range_selection_base_icon == icon) {
5513 details->range_selection_base_icon = NULL;
5515 if (details->pending_icon_to_reveal == icon) {
5516 set_pending_icon_to_reveal (container, NULL);
5518 if (details->stretch_icon == icon) {
5519 details->stretch_icon = NULL;
5522 if (icon->is_monitored) {
5523 nautilus_icon_container_stop_monitor_top_left (container,
5524 icon->data,
5525 icon);
5527 icon_free (icon);
5529 if (was_selected) {
5530 g_signal_emit (container,
5531 signals[SELECTION_CHANGED], 0);
5535 /* activate any selected items in the container */
5536 static void
5537 activate_selected_items (NautilusIconContainer *container)
5539 GList *selection;
5541 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
5543 selection = nautilus_icon_container_get_selection (container);
5544 if (selection != NULL) {
5545 g_signal_emit (container,
5546 signals[ACTIVATE], 0,
5547 selection);
5549 g_list_free (selection);
5552 static void
5553 activate_selected_items_alternate (NautilusIconContainer *container,
5554 NautilusIcon *icon)
5556 GList *selection;
5558 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
5560 if (icon != NULL) {
5561 selection = g_list_prepend (NULL, icon->data);
5562 } else {
5563 selection = nautilus_icon_container_get_selection (container);
5565 if (selection != NULL) {
5566 g_signal_emit (container,
5567 signals[ACTIVATE_ALTERNATE], 0,
5568 selection);
5570 g_list_free (selection);
5573 static NautilusIcon *
5574 get_icon_being_renamed (NautilusIconContainer *container)
5576 NautilusIcon *rename_icon;
5578 if (!is_renaming (container)) {
5579 return NULL;
5582 g_assert (!has_multiple_selection (container));
5584 rename_icon = get_first_selected_icon (container);
5585 g_assert (rename_icon != NULL);
5587 return rename_icon;
5590 static NautilusIconInfo *
5591 nautilus_icon_container_get_icon_images (NautilusIconContainer *container,
5592 NautilusIconData *data,
5593 int size,
5594 GList **emblem_pixbufs,
5595 char **embedded_text,
5596 gboolean for_drag_accept,
5597 gboolean need_large_embeddded_text,
5598 gboolean *embedded_text_needs_loading,
5599 gboolean *has_open_window)
5601 NautilusIconContainerClass *klass;
5603 klass = NAUTILUS_ICON_CONTAINER_GET_CLASS (container);
5604 g_return_val_if_fail (klass->get_icon_images != NULL, NULL);
5606 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);
5610 static void
5611 nautilus_icon_container_get_icon_text (NautilusIconContainer *container,
5612 NautilusIconData *data,
5613 char **editable_text,
5614 char **additional_text)
5616 NautilusIconContainerClass *klass;
5618 klass = NAUTILUS_ICON_CONTAINER_GET_CLASS (container);
5619 g_return_if_fail (klass->get_icon_text != NULL);
5621 klass->get_icon_text (container, data, editable_text, additional_text);
5624 static void
5625 nautilus_icon_container_freeze_updates (NautilusIconContainer *container)
5627 NautilusIconContainerClass *klass;
5629 klass = NAUTILUS_ICON_CONTAINER_GET_CLASS (container);
5630 g_return_if_fail (klass->freeze_updates != NULL);
5632 klass->freeze_updates (container);
5635 static void
5636 nautilus_icon_container_unfreeze_updates (NautilusIconContainer *container)
5638 NautilusIconContainerClass *klass;
5640 klass = NAUTILUS_ICON_CONTAINER_GET_CLASS (container);
5641 g_return_if_fail (klass->unfreeze_updates != NULL);
5643 klass->unfreeze_updates (container);
5646 static void
5647 nautilus_icon_container_start_monitor_top_left (NautilusIconContainer *container,
5648 NautilusIconData *data,
5649 gconstpointer client,
5650 gboolean large_text)
5652 NautilusIconContainerClass *klass;
5654 klass = NAUTILUS_ICON_CONTAINER_GET_CLASS (container);
5655 g_return_if_fail (klass->start_monitor_top_left != NULL);
5657 klass->start_monitor_top_left (container, data, client, large_text);
5660 static void
5661 nautilus_icon_container_stop_monitor_top_left (NautilusIconContainer *container,
5662 NautilusIconData *data,
5663 gconstpointer client)
5665 NautilusIconContainerClass *klass;
5667 klass = NAUTILUS_ICON_CONTAINER_GET_CLASS (container);
5668 g_return_if_fail (klass->stop_monitor_top_left != NULL);
5670 klass->stop_monitor_top_left (container, data, client);
5674 static void
5675 nautilus_icon_container_prioritize_thumbnailing (NautilusIconContainer *container,
5676 NautilusIcon *icon)
5678 NautilusIconContainerClass *klass;
5680 klass = NAUTILUS_ICON_CONTAINER_GET_CLASS (container);
5681 g_return_if_fail (klass->prioritize_thumbnailing != NULL);
5683 klass->prioritize_thumbnailing (container, icon->data);
5686 static void
5687 nautilus_icon_container_update_visible_icons (NautilusIconContainer *container)
5689 GtkAdjustment *vadj;
5690 double min_y, max_y;
5691 double x0, y0, x1, y1;
5692 GList *node;
5693 NautilusIcon *icon;
5696 vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
5698 min_y = vadj->value;
5699 max_y = min_y + GTK_WIDGET (container)->allocation.height;
5701 eel_canvas_c2w (EEL_CANVAS (container),
5702 0, min_y, NULL, &min_y);
5703 eel_canvas_c2w (EEL_CANVAS (container),
5704 0, max_y, NULL, &max_y);
5706 /* Do the iteration in reverse to get the render-order from top to
5707 * bottom for the prioritized thumbnails.
5709 for (node = g_list_last (container->details->icons); node != NULL; node = node->prev) {
5710 icon = node->data;
5712 if (icon_is_positioned (icon)) {
5713 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
5714 &x0,
5715 &y0,
5716 &x1,
5717 &y1);
5718 eel_canvas_item_i2w (EEL_CANVAS_ITEM (icon->item)->parent,
5719 &x0,
5720 &y0);
5721 eel_canvas_item_i2w (EEL_CANVAS_ITEM (icon->item)->parent,
5722 &x1,
5723 &y1);
5724 if (y1 >= min_y && y0 <= max_y) {
5725 nautilus_icon_canvas_item_set_is_visible (icon->item, TRUE);
5726 nautilus_icon_container_prioritize_thumbnailing (container,
5727 icon);
5728 } else {
5729 nautilus_icon_canvas_item_set_is_visible (icon->item, FALSE);
5735 static void
5736 handle_vadjustment_changed (GtkAdjustment *adjustment,
5737 NautilusIconContainer *container)
5739 nautilus_icon_container_update_visible_icons (container);
5742 void
5743 nautilus_icon_container_update_icon (NautilusIconContainer *container,
5744 NautilusIcon *icon)
5746 NautilusIconContainerDetails *details;
5747 guint icon_size;
5748 guint min_image_size, max_image_size;
5749 NautilusIconInfo *icon_info;
5750 GdkPoint *attach_points;
5751 int n_attach_points;
5752 gboolean has_embedded_text_rect;
5753 GdkPixbuf *pixbuf;
5754 GList *emblem_pixbufs;
5755 char *editable_text, *additional_text;
5756 char *embedded_text;
5757 GdkRectangle embedded_text_rect;
5758 gboolean large_embedded_text;
5759 gboolean embedded_text_needs_loading;
5760 gboolean has_open_window;
5762 if (icon == NULL) {
5763 return;
5766 details = container->details;
5768 /* compute the maximum size based on the scale factor */
5769 min_image_size = MINIMUM_IMAGE_SIZE * EEL_CANVAS (container)->pixels_per_unit;
5770 max_image_size = MAX (MAXIMUM_IMAGE_SIZE * EEL_CANVAS (container)->pixels_per_unit, NAUTILUS_ICON_MAXIMUM_SIZE);
5772 /* Get the appropriate images for the file. */
5773 icon_get_size (container, icon, &icon_size);
5775 icon_size = MAX (icon_size, min_image_size);
5776 icon_size = MIN (icon_size, max_image_size);
5778 /* Get the icons. */
5779 emblem_pixbufs = NULL;
5780 embedded_text = NULL;
5781 large_embedded_text = icon_size > ICON_SIZE_FOR_LARGE_EMBEDDED_TEXT;
5782 icon_info = nautilus_icon_container_get_icon_images (container, icon->data, icon_size,
5783 &emblem_pixbufs,
5784 &embedded_text,
5785 icon == details->drop_target,
5786 large_embedded_text, &embedded_text_needs_loading,
5787 &has_open_window);
5790 pixbuf = nautilus_icon_info_get_pixbuf (icon_info);
5791 nautilus_icon_info_get_attach_points (icon_info, &attach_points, &n_attach_points);
5792 has_embedded_text_rect = nautilus_icon_info_get_embedded_rect (icon_info,
5793 &embedded_text_rect);
5795 if (has_embedded_text_rect && embedded_text_needs_loading) {
5796 icon->is_monitored = TRUE;
5797 nautilus_icon_container_start_monitor_top_left (container, icon->data, icon, large_embedded_text);
5800 icon_size = MAX (nautilus_get_icon_size_for_zoom_level (container->details->zoom_level)
5801 * icon->scale, NAUTILUS_ICON_SIZE_SMALLEST);
5803 nautilus_icon_container_get_icon_text (container,
5804 icon->data,
5805 &editable_text,
5806 &additional_text);
5808 /* If name of icon being renamed was changed from elsewhere, end renaming mode.
5809 * Alternatively, we could replace the characters in the editable text widget
5810 * with the new name, but that could cause timing problems if the user just
5811 * happened to be typing at that moment.
5813 if (icon == get_icon_being_renamed (container) &&
5814 eel_strcmp (editable_text,
5815 nautilus_icon_canvas_item_get_editable_text (icon->item)) != 0) {
5816 end_renaming_mode (container, FALSE);
5819 eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
5820 "editable_text", editable_text,
5821 "additional_text", additional_text,
5822 "highlighted_for_drop", icon == details->drop_target,
5823 NULL);
5825 nautilus_icon_canvas_item_set_image (icon->item, pixbuf);
5826 nautilus_icon_canvas_item_set_attach_points (icon->item, attach_points, n_attach_points);
5827 nautilus_icon_canvas_item_set_emblems (icon->item, emblem_pixbufs);
5828 nautilus_icon_canvas_item_set_embedded_text_rect (icon->item, &embedded_text_rect);
5829 nautilus_icon_canvas_item_set_embedded_text (icon->item, embedded_text);
5831 /* Let the pixbufs go. */
5832 g_object_unref (pixbuf);
5833 eel_gdk_pixbuf_list_free (emblem_pixbufs);
5835 g_free (editable_text);
5836 g_free (additional_text);
5838 g_object_unref (icon_info);
5841 static gboolean
5842 assign_icon_position (NautilusIconContainer *container,
5843 NautilusIcon *icon)
5845 gboolean have_stored_position;
5846 NautilusIconPosition position;
5848 /* Get the stored position. */
5849 have_stored_position = FALSE;
5850 position.scale = 1.0;
5851 g_signal_emit (container,
5852 signals[GET_STORED_ICON_POSITION], 0,
5853 icon->data,
5854 &position,
5855 &have_stored_position);
5856 icon->scale = position.scale;
5857 if (!container->details->auto_layout) {
5858 if (have_stored_position) {
5859 icon_set_position (icon, position.x, position.y);
5860 icon->saved_ltr_x = icon->x;
5861 } else {
5862 return FALSE;
5865 return TRUE;
5868 static void
5869 finish_adding_icon (NautilusIconContainer *container,
5870 NautilusIcon *icon)
5872 nautilus_icon_container_update_icon (container, icon);
5873 eel_canvas_item_show (EEL_CANVAS_ITEM (icon->item));
5875 g_signal_connect_object (icon->item, "event",
5876 G_CALLBACK (item_event_callback), container, 0);
5878 g_signal_emit (container, signals[ICON_ADDED], 0, icon->data);
5881 static void
5882 finish_adding_new_icons (NautilusIconContainer *container)
5884 GList *p, *new_icons, *no_position_icons, *semi_position_icons;
5885 NautilusIcon *icon;
5886 double bottom;
5888 new_icons = container->details->new_icons;
5889 container->details->new_icons = NULL;
5891 /* Position most icons (not unpositioned manual-layout icons). */
5892 new_icons = g_list_reverse (new_icons);
5893 no_position_icons = semi_position_icons = NULL;
5894 for (p = new_icons; p != NULL; p = p->next) {
5895 icon = p->data;
5896 if (assign_icon_position (container, icon)) {
5897 if (!container->details->auto_layout && icon->has_lazy_position) {
5898 semi_position_icons = g_list_prepend (semi_position_icons, icon);
5900 } else {
5901 no_position_icons = g_list_prepend (no_position_icons, icon);
5904 finish_adding_icon (container, icon);
5906 g_list_free (new_icons);
5908 if (semi_position_icons != NULL) {
5909 PlacementGrid *grid;
5911 g_assert (!container->details->auto_layout);
5913 semi_position_icons = g_list_reverse (semi_position_icons);
5915 /* This is currently only used on the desktop.
5916 * Thus, we pass FALSE for tight, like lay_down_icons_tblr */
5917 grid = placement_grid_new (container, FALSE);
5919 for (p = container->details->icons; p != NULL; p = p->next) {
5920 icon = p->data;
5922 if (icon_is_positioned (icon) && !icon->has_lazy_position) {
5923 placement_grid_mark_icon (grid, icon);
5927 for (p = semi_position_icons; p != NULL; p = p->next) {
5928 NautilusIcon *icon;
5929 int x, y;
5931 icon = p->data;
5932 x = icon->x;
5933 y = icon->y;
5935 find_empty_location (container, grid,
5936 icon, x, y, &x, &y);
5938 icon_set_position (icon, x, y);
5940 placement_grid_mark_icon (grid, icon);
5942 /* ensure that next time we run this code, the formerly semi-positioned
5943 * icons are treated as being positioned. */
5944 icon->has_lazy_position = FALSE;
5947 placement_grid_free (grid);
5949 g_list_free (semi_position_icons);
5952 /* Position the unpositioned manual layout icons. */
5953 if (no_position_icons != NULL) {
5954 g_assert (!container->details->auto_layout);
5956 sort_icons (container, &no_position_icons);
5957 get_all_icon_bounds (container, NULL, NULL, NULL, &bottom);
5958 lay_down_icons (container, no_position_icons, bottom + ICON_PAD_BOTTOM);
5959 g_list_free (no_position_icons);
5964 * nautilus_icon_container_add:
5965 * @container: A NautilusIconContainer
5966 * @data: Icon data.
5967 * @has_lazy_position: Whether the saved icon position should only be used
5968 * if the previous icon position is free. If the position
5969 * is occupied, another position near the last one will
5970 * be used.
5972 * Add icon to represent @data to container.
5973 * Returns FALSE if there was already such an icon.
5975 gboolean
5976 nautilus_icon_container_add (NautilusIconContainer *container,
5977 NautilusIconData *data,
5978 gboolean has_lazy_position)
5980 NautilusIconContainerDetails *details;
5981 NautilusIcon *icon;
5982 EelCanvasItem *band, *item;
5984 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), FALSE);
5985 g_return_val_if_fail (data != NULL, FALSE);
5987 details = container->details;
5989 if (g_hash_table_lookup (details->icon_set, data) != NULL) {
5990 return FALSE;
5993 /* Create the new icon, including the canvas item. */
5994 icon = g_new0 (NautilusIcon, 1);
5995 icon->data = data;
5996 icon->x = ICON_UNPOSITIONED_VALUE;
5997 icon->y = ICON_UNPOSITIONED_VALUE;
5998 icon->has_lazy_position = has_lazy_position;
5999 icon->scale = 1.0;
6000 icon->item = NAUTILUS_ICON_CANVAS_ITEM
6001 (eel_canvas_item_new (EEL_CANVAS_GROUP (EEL_CANVAS (container)->root),
6002 nautilus_icon_canvas_item_get_type (),
6003 "visible", FALSE,
6004 NULL));
6005 icon->item->user_data = icon;
6007 /* Make sure the icon is under the selection_rectangle */
6008 item = EEL_CANVAS_ITEM (icon->item);
6009 band = NAUTILUS_ICON_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
6010 if (band) {
6011 eel_canvas_item_send_behind (item, band);
6014 /* Put it on both lists. */
6015 details->icons = g_list_prepend (details->icons, icon);
6016 details->new_icons = g_list_prepend (details->new_icons, icon);
6018 g_hash_table_insert (details->icon_set, data, icon);
6020 /* Run an idle function to add the icons. */
6021 schedule_redo_layout (container);
6023 return TRUE;
6026 void
6027 nautilus_icon_container_layout_now (NautilusIconContainer *container)
6029 if (container->details->idle_id != 0) {
6030 unschedule_redo_layout (container);
6031 redo_layout_internal (container);
6036 * nautilus_icon_container_remove:
6037 * @container: A NautilusIconContainer.
6038 * @data: Icon data.
6040 * Remove the icon with this data.
6042 gboolean
6043 nautilus_icon_container_remove (NautilusIconContainer *container,
6044 NautilusIconData *data)
6046 NautilusIcon *icon;
6048 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), FALSE);
6049 g_return_val_if_fail (data != NULL, FALSE);
6051 end_renaming_mode (container, FALSE);
6053 icon = g_hash_table_lookup (container->details->icon_set, data);
6055 if (icon == NULL) {
6056 return FALSE;
6059 icon_destroy (container, icon);
6060 schedule_redo_layout (container);
6062 g_signal_emit (container, signals[ICON_REMOVED], 0, icon);
6064 return TRUE;
6068 * nautilus_icon_container_request_update:
6069 * @container: A NautilusIconContainer.
6070 * @data: Icon data.
6072 * Update the icon with this data.
6074 void
6075 nautilus_icon_container_request_update (NautilusIconContainer *container,
6076 NautilusIconData *data)
6078 NautilusIcon *icon;
6080 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
6081 g_return_if_fail (data != NULL);
6083 icon = g_hash_table_lookup (container->details->icon_set, data);
6085 if (icon != NULL) {
6086 nautilus_icon_container_update_icon (container, icon);
6087 schedule_redo_layout (container);
6091 /* zooming */
6093 NautilusZoomLevel
6094 nautilus_icon_container_get_zoom_level (NautilusIconContainer *container)
6096 return container->details->zoom_level;
6099 void
6100 nautilus_icon_container_set_zoom_level (NautilusIconContainer *container, int new_level)
6102 NautilusIconContainerDetails *details;
6103 int pinned_level;
6104 double pixels_per_unit;
6106 details = container->details;
6108 end_renaming_mode (container, TRUE);
6110 pinned_level = new_level;
6111 if (pinned_level < NAUTILUS_ZOOM_LEVEL_SMALLEST) {
6112 pinned_level = NAUTILUS_ZOOM_LEVEL_SMALLEST;
6113 } else if (pinned_level > NAUTILUS_ZOOM_LEVEL_LARGEST) {
6114 pinned_level = NAUTILUS_ZOOM_LEVEL_LARGEST;
6117 if (pinned_level == details->zoom_level) {
6118 return;
6121 details->zoom_level = pinned_level;
6123 pixels_per_unit = (double) nautilus_get_icon_size_for_zoom_level (pinned_level)
6124 / NAUTILUS_ICON_SIZE_STANDARD;
6125 eel_canvas_set_pixels_per_unit (EEL_CANVAS (container), pixels_per_unit);
6127 invalidate_label_sizes (container);
6128 nautilus_icon_container_request_update_all (container);
6132 * nautilus_icon_container_request_update_all:
6133 * For each icon, synchronizes the displayed information (image, text) with the
6134 * information from the model.
6136 * @container: An icon container.
6138 void
6139 nautilus_icon_container_request_update_all (NautilusIconContainer *container)
6141 GList *node;
6142 NautilusIcon *icon;
6144 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
6146 for (node = container->details->icons; node != NULL; node = node->next) {
6147 icon = node->data;
6148 nautilus_icon_container_update_icon (container, icon);
6151 redo_layout (container);
6155 * nautilus_icon_container_reveal:
6156 * Change scroll position as necessary to reveal the specified item.
6158 void
6159 nautilus_icon_container_reveal (NautilusIconContainer *container, NautilusIconData *data)
6161 NautilusIcon *icon;
6163 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
6164 g_return_if_fail (data != NULL);
6166 icon = g_hash_table_lookup (container->details->icon_set, data);
6168 if (icon != NULL) {
6169 reveal_icon (container, icon);
6174 * nautilus_icon_container_get_selection:
6175 * @container: An icon container.
6177 * Get a list of the icons currently selected in @container.
6179 * Return value: A GList of the programmer-specified data associated to each
6180 * selected icon, or NULL if no icon is selected. The caller is expected to
6181 * free the list when it is not needed anymore.
6183 GList *
6184 nautilus_icon_container_get_selection (NautilusIconContainer *container)
6186 GList *list, *p;
6188 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), NULL);
6190 list = NULL;
6191 for (p = container->details->icons; p != NULL; p = p->next) {
6192 NautilusIcon *icon;
6194 icon = p->data;
6195 if (icon->is_selected) {
6196 list = g_list_prepend (list, icon->data);
6200 return g_list_reverse (list);
6203 static GList *
6204 nautilus_icon_container_get_selected_icons (NautilusIconContainer *container)
6206 GList *list, *p;
6208 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), NULL);
6210 list = NULL;
6211 for (p = container->details->icons; p != NULL; p = p->next) {
6212 NautilusIcon *icon;
6214 icon = p->data;
6215 if (icon->is_selected) {
6216 list = g_list_prepend (list, icon);
6220 return g_list_reverse (list);
6223 /* Returns an array of GdkPoints of locations of the icons. */
6224 static GArray *
6225 nautilus_icon_container_get_icon_locations (NautilusIconContainer *container,
6226 GList *icons)
6228 GArray *result;
6229 GList *node;
6230 int index;
6232 result = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
6233 result = g_array_set_size (result, g_list_length (icons));
6235 for (index = 0, node = icons; node != NULL; index++, node = node->next) {
6236 g_array_index (result, GdkPoint, index).x =
6237 ((NautilusIcon *)node->data)->x;
6238 g_array_index (result, GdkPoint, index).y =
6239 ((NautilusIcon *)node->data)->y;
6242 return result;
6246 * nautilus_icon_container_get_selected_icon_locations:
6247 * @container: An icon container widget.
6249 * Returns an array of GdkPoints of locations of the selected icons.
6251 GArray *
6252 nautilus_icon_container_get_selected_icon_locations (NautilusIconContainer *container)
6254 GArray *result;
6255 GList *icons;
6257 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), NULL);
6259 icons = nautilus_icon_container_get_selected_icons (container);
6260 result = nautilus_icon_container_get_icon_locations (container, icons);
6261 g_list_free (icons);
6263 return result;
6267 * nautilus_icon_container_select_all:
6268 * @container: An icon container widget.
6270 * Select all the icons in @container at once.
6272 void
6273 nautilus_icon_container_select_all (NautilusIconContainer *container)
6275 gboolean selection_changed;
6276 GList *p;
6277 NautilusIcon *icon;
6279 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
6281 selection_changed = FALSE;
6283 for (p = container->details->icons; p != NULL; p = p->next) {
6284 icon = p->data;
6286 selection_changed |= icon_set_selected (container, icon, TRUE);
6289 if (selection_changed) {
6290 g_signal_emit (container,
6291 signals[SELECTION_CHANGED], 0);
6296 * nautilus_icon_container_set_selection:
6297 * @container: An icon container widget.
6298 * @selection: A list of NautilusIconData *.
6300 * Set the selection to exactly the icons in @container which have
6301 * programmer data matching one of the items in @selection.
6303 void
6304 nautilus_icon_container_set_selection (NautilusIconContainer *container,
6305 GList *selection)
6307 gboolean selection_changed;
6308 GHashTable *hash;
6309 GList *p;
6310 NautilusIcon *icon;
6312 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
6314 selection_changed = FALSE;
6316 hash = g_hash_table_new (NULL, NULL);
6317 for (p = selection; p != NULL; p = p->next) {
6318 g_hash_table_insert (hash, p->data, p->data);
6320 for (p = container->details->icons; p != NULL; p = p->next) {
6321 icon = p->data;
6323 selection_changed |= icon_set_selected
6324 (container, icon,
6325 g_hash_table_lookup (hash, icon->data) != NULL);
6327 g_hash_table_destroy (hash);
6329 if (selection_changed) {
6330 g_signal_emit (container,
6331 signals[SELECTION_CHANGED], 0);
6336 * nautilus_icon_container_select_list_unselect_others.
6337 * @container: An icon container widget.
6338 * @selection: A list of NautilusIcon *.
6340 * Set the selection to exactly the icons in @selection.
6342 void
6343 nautilus_icon_container_select_list_unselect_others (NautilusIconContainer *container,
6344 GList *selection)
6346 gboolean selection_changed;
6347 GHashTable *hash;
6348 GList *p;
6349 NautilusIcon *icon;
6351 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
6353 selection_changed = FALSE;
6355 hash = g_hash_table_new (NULL, NULL);
6356 for (p = selection; p != NULL; p = p->next) {
6357 g_hash_table_insert (hash, p->data, p->data);
6359 for (p = container->details->icons; p != NULL; p = p->next) {
6360 icon = p->data;
6362 selection_changed |= icon_set_selected
6363 (container, icon,
6364 g_hash_table_lookup (hash, icon) != NULL);
6366 g_hash_table_destroy (hash);
6368 if (selection_changed) {
6369 g_signal_emit (container,
6370 signals[SELECTION_CHANGED], 0);
6375 * nautilus_icon_container_unselect_all:
6376 * @container: An icon container widget.
6378 * Deselect all the icons in @container.
6380 void
6381 nautilus_icon_container_unselect_all (NautilusIconContainer *container)
6383 if (unselect_all (container)) {
6384 g_signal_emit (container,
6385 signals[SELECTION_CHANGED], 0);
6390 * nautilus_icon_container_get_icon_by_uri:
6391 * @container: An icon container widget.
6392 * @uri: The uri of an icon to find.
6394 * Locate an icon, given the URI. The URI must match exactly.
6395 * Later we may have to have some way of figuring out if the
6396 * URI specifies the same object that does not require an exact match.
6398 NautilusIcon *
6399 nautilus_icon_container_get_icon_by_uri (NautilusIconContainer *container,
6400 const char *uri)
6402 NautilusIconContainerDetails *details;
6403 GList *p;
6405 /* Eventually, we must avoid searching the entire icon list,
6406 but it's OK for now.
6407 A hash table mapping uri to icon is one possibility.
6410 details = container->details;
6412 for (p = details->icons; p != NULL; p = p->next) {
6413 NautilusIcon *icon;
6414 char *icon_uri;
6415 gboolean is_match;
6417 icon = p->data;
6419 icon_uri = nautilus_icon_container_get_icon_uri
6420 (container, icon);
6421 is_match = strcmp (uri, icon_uri) == 0;
6422 g_free (icon_uri);
6424 if (is_match) {
6425 return icon;
6429 return NULL;
6432 static NautilusIcon *
6433 get_nth_selected_icon (NautilusIconContainer *container, int index)
6435 GList *p;
6436 NautilusIcon *icon;
6437 int selection_count;
6439 g_return_val_if_fail (index > 0, NULL);
6441 /* Find the nth selected icon. */
6442 selection_count = 0;
6443 for (p = container->details->icons; p != NULL; p = p->next) {
6444 icon = p->data;
6445 if (icon->is_selected) {
6446 if (++selection_count == index) {
6447 return icon;
6451 return NULL;
6454 static NautilusIcon *
6455 get_first_selected_icon (NautilusIconContainer *container)
6457 return get_nth_selected_icon (container, 1);
6460 static gboolean
6461 has_multiple_selection (NautilusIconContainer *container)
6463 return get_nth_selected_icon (container, 2) != NULL;
6466 static gboolean
6467 all_selected (NautilusIconContainer *container)
6469 GList *p;
6470 NautilusIcon *icon;
6472 for (p = container->details->icons; p != NULL; p = p->next) {
6473 icon = p->data;
6474 if (!icon->is_selected) {
6475 return FALSE;
6478 return TRUE;
6481 static gboolean
6482 has_selection (NautilusIconContainer *container)
6484 return get_nth_selected_icon (container, 1) != NULL;
6488 * nautilus_icon_container_show_stretch_handles:
6489 * @container: An icon container widget.
6491 * Makes stretch handles visible on the first selected icon.
6493 void
6494 nautilus_icon_container_show_stretch_handles (NautilusIconContainer *container)
6496 NautilusIconContainerDetails *details;
6497 NautilusIcon *icon;
6498 guint initial_size;
6500 icon = get_first_selected_icon (container);
6501 if (icon == NULL) {
6502 return;
6505 /* Check if it already has stretch handles. */
6506 details = container->details;
6507 if (details->stretch_icon == icon) {
6508 return;
6511 /* Get rid of the existing stretch handles and put them on the new icon. */
6512 if (details->stretch_icon != NULL) {
6513 nautilus_icon_canvas_item_set_show_stretch_handles
6514 (details->stretch_icon->item, FALSE);
6515 ungrab_stretch_icon (container);
6516 emit_stretch_ended (container, details->stretch_icon);
6518 nautilus_icon_canvas_item_set_show_stretch_handles (icon->item, TRUE);
6519 details->stretch_icon = icon;
6521 icon_get_size (container, icon, &initial_size);
6523 /* only need to keep size in one dimension, since they are constrained to be the same */
6524 container->details->stretch_initial_x = icon->x;
6525 container->details->stretch_initial_y = icon->y;
6526 container->details->stretch_initial_size = initial_size;
6528 emit_stretch_started (container, icon);
6532 * nautilus_icon_container_has_stretch_handles
6533 * @container: An icon container widget.
6535 * Returns true if the first selected item has stretch handles.
6537 gboolean
6538 nautilus_icon_container_has_stretch_handles (NautilusIconContainer *container)
6540 NautilusIcon *icon;
6542 icon = get_first_selected_icon (container);
6543 if (icon == NULL) {
6544 return FALSE;
6547 return icon == container->details->stretch_icon;
6551 * nautilus_icon_container_is_stretched
6552 * @container: An icon container widget.
6554 * Returns true if the any selected item is stretched to a size other than 1.0.
6556 gboolean
6557 nautilus_icon_container_is_stretched (NautilusIconContainer *container)
6559 GList *p;
6560 NautilusIcon *icon;
6562 for (p = container->details->icons; p != NULL; p = p->next) {
6563 icon = p->data;
6564 if (icon->is_selected && icon->scale != 1.0) {
6565 return TRUE;
6568 return FALSE;
6572 * nautilus_icon_container_unstretch
6573 * @container: An icon container widget.
6575 * Gets rid of any icon stretching.
6577 void
6578 nautilus_icon_container_unstretch (NautilusIconContainer *container)
6580 GList *p;
6581 NautilusIcon *icon;
6583 for (p = container->details->icons; p != NULL; p = p->next) {
6584 icon = p->data;
6585 if (icon->is_selected) {
6586 nautilus_icon_container_move_icon (container, icon,
6587 icon->x, icon->y,
6588 1.0,
6589 FALSE, TRUE, TRUE);
6594 static void
6595 compute_stretch (StretchState *start,
6596 StretchState *current)
6598 gboolean right, bottom;
6599 int x_stretch, y_stretch;
6601 /* FIXME bugzilla.gnome.org 45390: This doesn't correspond to
6602 * the way the handles are drawn.
6604 /* Figure out which handle we are dragging. */
6605 right = start->pointer_x > start->icon_x + (int) start->icon_size / 2;
6606 bottom = start->pointer_y > start->icon_y + (int) start->icon_size / 2;
6608 /* Figure out how big we should stretch. */
6609 x_stretch = start->pointer_x - current->pointer_x;
6610 y_stretch = start->pointer_y - current->pointer_y;
6611 if (right) {
6612 x_stretch = - x_stretch;
6614 if (bottom) {
6615 y_stretch = - y_stretch;
6617 current->icon_size = MAX ((int) start->icon_size + MIN (x_stretch, y_stretch),
6618 (int) NAUTILUS_ICON_SIZE_SMALLEST);
6620 /* Figure out where the corner of the icon should be. */
6621 current->icon_x = start->icon_x;
6622 if (!right) {
6623 current->icon_x += start->icon_size - current->icon_size;
6625 current->icon_y = start->icon_y;
6626 if (!bottom) {
6627 current->icon_y += start->icon_size - current->icon_size;
6631 char *
6632 nautilus_icon_container_get_icon_uri (NautilusIconContainer *container,
6633 NautilusIcon *icon)
6635 char *uri;
6637 uri = NULL;
6638 g_signal_emit (container,
6639 signals[GET_ICON_URI], 0,
6640 icon->data,
6641 &uri);
6642 return uri;
6645 char *
6646 nautilus_icon_container_get_icon_drop_target_uri (NautilusIconContainer *container,
6647 NautilusIcon *icon)
6649 char *uri;
6651 uri = NULL;
6652 g_signal_emit (container,
6653 signals[GET_ICON_DROP_TARGET_URI], 0,
6654 icon->data,
6655 &uri);
6656 return uri;
6659 /* Call to reset the scroll region only if the container is not empty,
6660 * to avoid having the flag linger until the next file is added.
6662 static void
6663 reset_scroll_region_if_not_empty (NautilusIconContainer *container)
6665 if (!nautilus_icon_container_is_empty (container)) {
6666 nautilus_icon_container_reset_scroll_region (container);
6670 /* Switch from automatic layout to manual or vice versa.
6671 * If we switch to manual layout, we restore the icon positions from the
6672 * last manual layout.
6674 void
6675 nautilus_icon_container_set_auto_layout (NautilusIconContainer *container,
6676 gboolean auto_layout)
6678 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
6679 g_return_if_fail (auto_layout == FALSE || auto_layout == TRUE);
6681 if (container->details->auto_layout == auto_layout) {
6682 return;
6685 reset_scroll_region_if_not_empty (container);
6686 container->details->auto_layout = auto_layout;
6688 if (!auto_layout) {
6689 reload_icon_positions (container);
6690 nautilus_icon_container_freeze_icon_positions (container);
6693 redo_layout (container);
6695 g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
6699 /* Toggle the tighter layout boolean. */
6700 void
6701 nautilus_icon_container_set_tighter_layout (NautilusIconContainer *container,
6702 gboolean tighter_layout)
6704 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
6705 g_return_if_fail (tighter_layout == FALSE || tighter_layout == TRUE);
6707 if (container->details->tighter_layout == tighter_layout) {
6708 return;
6711 container->details->tighter_layout = tighter_layout;
6713 if (container->details->auto_layout) {
6714 invalidate_label_sizes (container);
6715 redo_layout (container);
6717 g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
6718 } else {
6719 /* in manual layout, label sizes still change, even though
6720 * the icons don't move.
6722 invalidate_label_sizes (container);
6723 nautilus_icon_container_request_update_all (container);
6727 gboolean
6728 nautilus_icon_container_is_keep_aligned (NautilusIconContainer *container)
6730 return container->details->keep_aligned;
6733 static gboolean
6734 align_icons_callback (gpointer callback_data)
6736 NautilusIconContainer *container;
6738 container = NAUTILUS_ICON_CONTAINER (callback_data);
6739 align_icons (container);
6740 container->details->align_idle_id = 0;
6742 return FALSE;
6745 static void
6746 unschedule_align_icons (NautilusIconContainer *container)
6748 if (container->details->align_idle_id != 0) {
6749 g_source_remove (container->details->align_idle_id);
6750 container->details->align_idle_id = 0;
6754 static void
6755 schedule_align_icons (NautilusIconContainer *container)
6757 if (container->details->align_idle_id == 0
6758 && container->details->has_been_allocated) {
6759 container->details->align_idle_id = g_idle_add
6760 (align_icons_callback, container);
6764 void
6765 nautilus_icon_container_set_keep_aligned (NautilusIconContainer *container,
6766 gboolean keep_aligned)
6768 if (container->details->keep_aligned != keep_aligned) {
6769 container->details->keep_aligned = keep_aligned;
6771 if (keep_aligned && !container->details->auto_layout) {
6772 schedule_align_icons (container);
6773 } else {
6774 unschedule_align_icons (container);
6779 void
6780 nautilus_icon_container_set_layout_mode (NautilusIconContainer *container,
6781 NautilusIconLayoutMode mode)
6783 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
6785 container->details->layout_mode = mode;
6787 redo_layout (container);
6789 g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
6792 void
6793 nautilus_icon_container_set_label_position (NautilusIconContainer *container,
6794 NautilusIconLabelPosition position)
6796 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
6798 if (container->details->label_position != position) {
6799 container->details->label_position = position;
6801 invalidate_label_sizes (container);
6802 nautilus_icon_container_request_update_all (container);
6804 schedule_redo_layout (container);
6808 /* Switch from automatic to manual layout, freezing all the icons in their
6809 * current positions instead of restoring icon positions from the last manual
6810 * layout as set_auto_layout does.
6812 void
6813 nautilus_icon_container_freeze_icon_positions (NautilusIconContainer *container)
6815 gboolean changed;
6816 GList *p;
6817 NautilusIcon *icon;
6818 NautilusIconPosition position;
6820 changed = container->details->auto_layout;
6821 container->details->auto_layout = FALSE;
6823 for (p = container->details->icons; p != NULL; p = p->next) {
6824 icon = p->data;
6826 position.x = icon->saved_ltr_x;
6827 position.y = icon->y;
6828 position.scale = icon->scale;
6829 g_signal_emit (container, signals[ICON_POSITION_CHANGED], 0,
6830 icon->data, &position);
6833 if (changed) {
6834 g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
6838 /* Re-sort, switching to automatic layout if it was in manual layout. */
6839 void
6840 nautilus_icon_container_sort (NautilusIconContainer *container)
6842 gboolean changed;
6844 changed = !container->details->auto_layout;
6845 container->details->auto_layout = TRUE;
6847 reset_scroll_region_if_not_empty (container);
6848 redo_layout (container);
6850 if (changed) {
6851 g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
6855 gboolean
6856 nautilus_icon_container_is_auto_layout (NautilusIconContainer *container)
6858 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), FALSE);
6860 return container->details->auto_layout;
6863 gboolean
6864 nautilus_icon_container_is_tighter_layout (NautilusIconContainer *container)
6866 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), FALSE);
6868 return container->details->tighter_layout;
6871 static void
6872 pending_icon_to_rename_destroy_callback (NautilusIconCanvasItem *item, NautilusIconContainer *container)
6874 g_assert (container->details->pending_icon_to_rename != NULL);
6875 g_assert (container->details->pending_icon_to_rename->item == item);
6876 container->details->pending_icon_to_rename = NULL;
6879 static NautilusIcon*
6880 get_pending_icon_to_rename (NautilusIconContainer *container)
6882 return container->details->pending_icon_to_rename;
6885 static void
6886 set_pending_icon_to_rename (NautilusIconContainer *container, NautilusIcon *icon)
6888 NautilusIcon *old_icon;
6890 old_icon = container->details->pending_icon_to_rename;
6892 if (icon == old_icon) {
6893 return;
6896 if (old_icon != NULL) {
6897 g_signal_handlers_disconnect_by_func
6898 (old_icon->item,
6899 G_CALLBACK (pending_icon_to_rename_destroy_callback),
6900 container);
6903 if (icon != NULL) {
6904 g_signal_connect (icon->item, "destroy",
6905 G_CALLBACK (pending_icon_to_rename_destroy_callback), container);
6908 container->details->pending_icon_to_rename = icon;
6911 static void
6912 process_pending_icon_to_rename (NautilusIconContainer *container)
6914 NautilusIcon *pending_icon_to_rename;
6916 pending_icon_to_rename = get_pending_icon_to_rename (container);
6918 if (pending_icon_to_rename != NULL) {
6919 if (pending_icon_to_rename->is_selected && !has_multiple_selection (container)) {
6920 nautilus_icon_container_start_renaming_selected_item (container, FALSE);
6921 } else {
6922 set_pending_icon_to_rename (container, NULL);
6927 static gboolean
6928 is_renaming_pending (NautilusIconContainer *container)
6930 return get_pending_icon_to_rename (container) != NULL;
6933 static gboolean
6934 is_renaming (NautilusIconContainer *container)
6936 return container->details->renaming;
6940 * nautilus_icon_container_start_renaming_selected_item
6941 * @container: An icon container widget.
6942 * @select_all: Whether the whole file should initially be selected, or
6943 * only its basename (i.e. everything except its extension).
6945 * Displays the edit name widget on the first selected icon
6947 void
6948 nautilus_icon_container_start_renaming_selected_item (NautilusIconContainer *container,
6949 gboolean select_all)
6951 NautilusIconContainerDetails *details;
6952 NautilusIcon *icon;
6953 EelDRect icon_rect;
6954 EelDRect text_rect;
6955 PangoContext *context;
6956 PangoFontDescription *desc;
6957 const char *editable_text;
6958 int x, y, width;
6959 int start_offset, end_offset;
6961 /* Check if it already in renaming mode. */
6962 details = container->details;
6963 if (details->renaming) {
6964 return;
6967 /* Find selected icon */
6968 icon = get_first_selected_icon (container);
6969 if (icon == NULL) {
6970 return;
6973 g_assert (!has_multiple_selection (container));
6976 if (!icon_is_positioned (icon)) {
6977 set_pending_icon_to_rename (container, icon);
6978 return;
6981 set_pending_icon_to_rename (container, NULL);
6983 /* Make a copy of the original editable text for a later compare */
6984 editable_text = nautilus_icon_canvas_item_get_editable_text (icon->item);
6986 /* This could conceivably be NULL if a rename was triggered really early. */
6987 if (editable_text == NULL) {
6988 return;
6991 details->original_text = g_strdup (editable_text);
6993 /* Freeze updates so files added while renaming don't cause rename to loose focus, bug #318373 */
6994 nautilus_icon_container_freeze_updates (container);
6996 /* Create text renaming widget, if it hasn't been created already.
6997 * We deal with the broken icon text item widget by keeping it around
6998 * so its contents can still be cut and pasted as part of the clipboard
7000 if (details->rename_widget == NULL) {
7001 details->rename_widget = eel_editable_label_new ("Test text");
7002 eel_editable_label_set_line_wrap (EEL_EDITABLE_LABEL (details->rename_widget), TRUE);
7003 eel_editable_label_set_line_wrap_mode (EEL_EDITABLE_LABEL (details->rename_widget), PANGO_WRAP_WORD_CHAR);
7004 eel_editable_label_set_draw_outline (EEL_EDITABLE_LABEL (details->rename_widget), TRUE);
7005 eel_editable_label_set_justify (EEL_EDITABLE_LABEL (details->rename_widget), GTK_JUSTIFY_CENTER);
7006 gtk_misc_set_padding (GTK_MISC (details->rename_widget), 1, 1);
7007 gtk_layout_put (GTK_LAYOUT (container),
7008 details->rename_widget, 0, 0);
7011 /* Set the right font */
7012 if (details->font) {
7013 desc = pango_font_description_from_string (details->font);
7014 } else {
7015 context = gtk_widget_get_pango_context (GTK_WIDGET (container));
7016 desc = pango_font_description_copy (pango_context_get_font_description (context));
7017 pango_font_description_set_size (desc,
7018 pango_font_description_get_size (desc) +
7019 container->details->font_size_table [container->details->zoom_level]);
7021 eel_editable_label_set_font_description (EEL_EDITABLE_LABEL (details->rename_widget),
7022 desc);
7023 pango_font_description_free (desc);
7025 icon_rect = nautilus_icon_canvas_item_get_icon_rectangle (icon->item);
7026 text_rect = nautilus_icon_canvas_item_get_text_rectangle (icon->item);
7028 width = nautilus_icon_canvas_item_get_max_text_width (icon->item);
7031 if (details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) {
7032 eel_canvas_w2c (EEL_CANVAS_ITEM (icon->item)->canvas,
7033 text_rect.x0,
7034 text_rect.y0,
7035 &x, &y);
7036 } else {
7037 eel_canvas_w2c (EEL_CANVAS_ITEM (icon->item)->canvas,
7038 (icon_rect.x0 + icon_rect.x1) / 2,
7039 icon_rect.y1,
7040 &x, &y);
7041 x = x - width / 2 - 1;
7044 gtk_layout_move (GTK_LAYOUT (container),
7045 details->rename_widget,
7046 x, y);
7048 gtk_widget_set_size_request (details->rename_widget,
7049 width, -1);
7050 eel_editable_label_set_text (EEL_EDITABLE_LABEL (details->rename_widget),
7051 editable_text);
7052 if (select_all) {
7053 start_offset = 0;
7054 end_offset = -1;
7055 } else {
7056 eel_filename_get_rename_region (editable_text, &start_offset, &end_offset);
7058 eel_editable_label_select_region (EEL_EDITABLE_LABEL (details->rename_widget),
7059 start_offset,
7060 end_offset);
7061 gtk_widget_show (details->rename_widget);
7063 gtk_widget_grab_focus (details->rename_widget);
7065 g_signal_emit (container,
7066 signals[RENAMING_ICON], 0,
7067 GTK_EDITABLE (details->rename_widget));
7069 nautilus_icon_container_update_icon (container, icon);
7071 /* We are in renaming mode */
7072 details->renaming = TRUE;
7073 nautilus_icon_canvas_item_set_renaming (icon->item, TRUE);
7076 static void
7077 end_renaming_mode (NautilusIconContainer *container, gboolean commit)
7079 NautilusIcon *icon;
7080 const char *changed_text;
7082 set_pending_icon_to_rename (container, NULL);
7084 icon = get_icon_being_renamed (container);
7085 if (icon == NULL) {
7086 return;
7089 /* We are not in renaming mode */
7090 container->details->renaming = FALSE;
7091 nautilus_icon_canvas_item_set_renaming (icon->item, FALSE);
7093 nautilus_icon_container_unfreeze_updates (container);
7095 if (commit) {
7096 set_pending_icon_to_reveal (container, icon);
7099 gtk_widget_grab_focus (GTK_WIDGET (container));
7101 if (commit) {
7102 /* Verify that text has been modified before signalling change. */
7103 changed_text = eel_editable_label_get_text (EEL_EDITABLE_LABEL (container->details->rename_widget));
7104 if (strcmp (container->details->original_text, changed_text) != 0) {
7105 g_signal_emit (container,
7106 signals[ICON_TEXT_CHANGED], 0,
7107 icon->data,
7108 changed_text);
7112 gtk_widget_hide (container->details->rename_widget);
7114 g_free (container->details->original_text);
7118 /* emit preview signal, called by the canvas item */
7119 gboolean
7120 nautilus_icon_container_emit_preview_signal (NautilusIconContainer *icon_container,
7121 NautilusIcon *icon,
7122 gboolean start_flag)
7124 gboolean result;
7126 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (icon_container), FALSE);
7127 g_return_val_if_fail (icon != NULL, FALSE);
7128 g_return_val_if_fail (start_flag == FALSE || start_flag == TRUE, FALSE);
7130 result = FALSE;
7131 g_signal_emit (icon_container,
7132 signals[PREVIEW], 0,
7133 icon->data,
7134 start_flag,
7135 &result);
7137 return result;
7140 gboolean
7141 nautilus_icon_container_has_stored_icon_positions (NautilusIconContainer *container)
7143 GList *p;
7144 NautilusIcon *icon;
7145 gboolean have_stored_position;
7146 NautilusIconPosition position;
7148 for (p = container->details->icons; p != NULL; p = p->next) {
7149 icon = p->data;
7151 have_stored_position = FALSE;
7152 g_signal_emit (container,
7153 signals[GET_STORED_ICON_POSITION], 0,
7154 icon->data,
7155 &position,
7156 &have_stored_position);
7157 if (have_stored_position) {
7158 return TRUE;
7161 return FALSE;
7164 void
7165 nautilus_icon_container_set_single_click_mode (NautilusIconContainer *container,
7166 gboolean single_click_mode)
7168 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
7170 container->details->single_click_mode = single_click_mode;
7174 /* update the label color when the background changes */
7176 GdkGC *
7177 nautilus_icon_container_get_label_color_and_gc (NautilusIconContainer *container,
7178 GdkColor **color,
7179 gboolean is_name,
7180 gboolean is_highlight,
7181 gboolean is_prelit)
7183 int idx;
7185 if (is_name) {
7186 if (is_highlight) {
7187 if (GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (container))) {
7188 idx = LABEL_COLOR_HIGHLIGHT;
7189 } else {
7190 idx = LABEL_COLOR_ACTIVE;
7192 } else {
7193 if (is_prelit) {
7194 idx = LABEL_COLOR_PRELIGHT;
7195 } else {
7196 idx = LABEL_COLOR;
7199 } else {
7200 if (is_highlight) {
7201 if (GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (container))) {
7202 idx = LABEL_INFO_COLOR_HIGHLIGHT;
7203 } else {
7204 idx = LABEL_INFO_COLOR_ACTIVE;
7206 } else {
7207 idx = LABEL_INFO_COLOR;
7211 if (color) {
7212 *color = &container->details->label_colors [idx];
7215 return container->details->label_gcs [idx];
7218 static void
7219 setup_gc_with_fg (NautilusIconContainer *container, int idx, guint32 color)
7221 GdkGC *gc;
7222 GdkColor gcolor;
7224 gcolor = eel_gdk_rgb_to_color (color);
7225 container->details->label_colors [idx] = gcolor;
7227 gc = gdk_gc_new (GTK_LAYOUT (container)->bin_window);
7228 gdk_gc_set_rgb_fg_color (gc, &gcolor);
7230 if (container->details->label_gcs [idx]) {
7231 g_object_unref (container->details->label_gcs [idx]);
7234 container->details->label_gcs [idx] = gc;
7237 static void
7238 setup_label_gcs (NautilusIconContainer *container)
7240 EelBackground *background;
7241 GtkWidget *widget;
7242 GdkColor *light_info_color, *dark_info_color;
7243 guint light_info_value, dark_info_value;
7244 gboolean frame_text;
7246 if (!GTK_WIDGET_REALIZED (container))
7247 return;
7249 widget = GTK_WIDGET (container);
7251 g_assert (NAUTILUS_IS_ICON_CONTAINER (container));
7253 background = eel_get_widget_background (GTK_WIDGET (container));
7255 /* read the info colors from the current theme; use a reasonable default if undefined */
7256 gtk_widget_style_get (GTK_WIDGET (container),
7257 "light_info_color", &light_info_color,
7258 "dark_info_color", &dark_info_color,
7259 NULL);
7261 if (light_info_color) {
7262 light_info_value = eel_gdk_color_to_rgb (light_info_color);
7263 gdk_color_free (light_info_color);
7264 } else {
7265 light_info_value = DEFAULT_LIGHT_INFO_COLOR;
7268 if (dark_info_color) {
7269 dark_info_value = eel_gdk_color_to_rgb (dark_info_color);
7270 gdk_color_free (dark_info_color);
7271 } else {
7272 dark_info_value = DEFAULT_DARK_INFO_COLOR;
7275 setup_gc_with_fg (container, LABEL_COLOR_HIGHLIGHT, eel_gdk_color_to_rgb (&widget->style->text[GTK_STATE_SELECTED]));
7276 setup_gc_with_fg (container, LABEL_COLOR_ACTIVE, eel_gdk_color_to_rgb (&widget->style->text[GTK_STATE_ACTIVE]));
7277 setup_gc_with_fg (container, LABEL_COLOR_PRELIGHT, eel_gdk_color_to_rgb (&widget->style->text[GTK_STATE_PRELIGHT]));
7278 setup_gc_with_fg (container,
7279 LABEL_INFO_COLOR_HIGHLIGHT,
7280 eel_gdk_color_is_dark (&GTK_WIDGET (container)->style->base[GTK_STATE_SELECTED]) ? light_info_value : dark_info_value);
7281 setup_gc_with_fg (container,
7282 LABEL_INFO_COLOR_ACTIVE,
7283 eel_gdk_color_is_dark (&GTK_WIDGET (container)->style->base[GTK_STATE_ACTIVE]) ? light_info_value : dark_info_value);
7285 /* If NautilusIconContainer::frame_text is set, we can safely
7286 * use the foreground color from the theme, because it will
7287 * always be displayed against the gtk background */
7288 gtk_widget_style_get (widget,
7289 "frame_text", &frame_text,
7290 NULL);
7292 if (frame_text || !eel_background_is_set(background)) {
7293 setup_gc_with_fg (container, LABEL_COLOR,
7294 eel_gdk_color_to_rgb (&widget->style->text[GTK_STATE_NORMAL]));
7295 setup_gc_with_fg (container,
7296 LABEL_INFO_COLOR,
7297 eel_gdk_color_is_dark (&widget->style->base[GTK_STATE_NORMAL]) ? light_info_value : dark_info_value);
7298 } else {
7299 if (container->details->use_drop_shadows || eel_background_is_dark (background)) {
7300 setup_gc_with_fg (container, LABEL_COLOR, 0xEFEFEF);
7301 setup_gc_with_fg (container,
7302 LABEL_INFO_COLOR,
7303 light_info_value);
7304 } else { /* converse */
7305 setup_gc_with_fg (container, LABEL_COLOR, 0x000000);
7306 setup_gc_with_fg (container,
7307 LABEL_INFO_COLOR,
7308 dark_info_value);
7313 static void
7314 update_label_color (EelBackground *background,
7315 NautilusIconContainer *container)
7317 g_assert (EEL_IS_BACKGROUND (background));
7319 setup_label_gcs (container);
7323 /* Return if the icon container is a fixed size */
7324 gboolean
7325 nautilus_icon_container_get_is_fixed_size (NautilusIconContainer *container)
7327 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), FALSE);
7329 return container->details->is_fixed_size;
7332 /* Set the icon container to be a fixed size */
7333 void
7334 nautilus_icon_container_set_is_fixed_size (NautilusIconContainer *container,
7335 gboolean is_fixed_size)
7337 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
7339 container->details->is_fixed_size = is_fixed_size;
7342 gboolean
7343 nautilus_icon_container_get_is_desktop (NautilusIconContainer *container)
7345 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), FALSE);
7347 return container->details->is_desktop;
7350 void
7351 nautilus_icon_container_set_is_desktop (NautilusIconContainer *container,
7352 gboolean is_desktop)
7354 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
7356 container->details->is_desktop = is_desktop;
7359 void
7360 nautilus_icon_container_set_margins (NautilusIconContainer *container,
7361 int left_margin,
7362 int right_margin,
7363 int top_margin,
7364 int bottom_margin)
7366 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
7368 container->details->left_margin = left_margin;
7369 container->details->right_margin = right_margin;
7370 container->details->top_margin = top_margin;
7371 container->details->bottom_margin = bottom_margin;
7373 /* redo layout of icons as the margins have changed */
7374 schedule_redo_layout (container);
7377 void
7378 nautilus_icon_container_set_use_drop_shadows (NautilusIconContainer *container,
7379 gboolean use_drop_shadows)
7381 gboolean frame_text;
7383 gtk_widget_style_get (GTK_WIDGET (container),
7384 "frame_text", &frame_text,
7385 NULL);
7387 if (container->details->drop_shadows_requested == use_drop_shadows) {
7388 return;
7391 container->details->drop_shadows_requested = use_drop_shadows;
7392 container->details->use_drop_shadows = use_drop_shadows && !frame_text;
7393 gtk_widget_queue_draw (GTK_WIDGET (container));
7396 /* handle theme changes */
7398 static void
7399 nautilus_icon_container_theme_changed (gpointer user_data)
7401 NautilusIconContainer *container;
7402 GtkStyle *style;
7403 GdkColor *prelight_icon_color, *normal_icon_color;
7404 guchar highlight_alpha, normal_alpha, prelight_alpha;
7406 container = NAUTILUS_ICON_CONTAINER (user_data);
7408 /* load the highlight color */
7409 gtk_widget_style_get (GTK_WIDGET (container),
7410 "highlight_alpha", &highlight_alpha,
7411 NULL);
7413 style = GTK_WIDGET (container)->style;
7415 container->details->highlight_color_rgba =
7416 EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_SELECTED].red >> 8,
7417 style->base[GTK_STATE_SELECTED].green >> 8,
7418 style->base[GTK_STATE_SELECTED].blue >> 8,
7419 highlight_alpha);
7420 container->details->active_color_rgba =
7421 EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_ACTIVE].red >> 8,
7422 style->base[GTK_STATE_ACTIVE].green >> 8,
7423 style->base[GTK_STATE_ACTIVE].blue >> 8,
7424 highlight_alpha);
7426 /* load the prelight icon color */
7427 gtk_widget_style_get (GTK_WIDGET (container),
7428 "prelight_icon_color", &prelight_icon_color,
7429 NULL);
7431 if (prelight_icon_color) {
7432 container->details->prelight_icon_color_rgba =
7433 EEL_RGBA_COLOR_PACK (prelight_icon_color->red >> 8,
7434 prelight_icon_color->green >> 8,
7435 prelight_icon_color->blue >> 8,
7436 255);
7437 } else { /* if not defined by rc, set to default value */
7438 container->details->prelight_icon_color_rgba =
7439 EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_PRELIGHT].red >> 8,
7440 style->base[GTK_STATE_PRELIGHT].green >> 8,
7441 style->base[GTK_STATE_PRELIGHT].blue >> 8,
7442 255);
7446 /* load the normal icon color */
7447 gtk_widget_style_get (GTK_WIDGET (container),
7448 "normal_icon_color", &normal_icon_color,
7449 NULL);
7451 if (normal_icon_color) {
7452 container->details->normal_icon_color_rgba =
7453 EEL_RGBA_COLOR_PACK (normal_icon_color->red >> 8,
7454 normal_icon_color->green >> 8,
7455 normal_icon_color->blue >> 8,
7456 255);
7457 } else { /* if not defined by rc, set to default value */
7458 container->details->normal_icon_color_rgba =
7459 EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_NORMAL].red >> 8,
7460 style->base[GTK_STATE_NORMAL].green >> 8,
7461 style->base[GTK_STATE_NORMAL].blue >> 8,
7462 255);
7466 /* load the normal color */
7467 gtk_widget_style_get (GTK_WIDGET (container),
7468 "normal_alpha", &normal_alpha,
7469 NULL);
7471 container->details->normal_color_rgba =
7472 EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_NORMAL].red >> 8,
7473 style->base[GTK_STATE_NORMAL].green >> 8,
7474 style->base[GTK_STATE_NORMAL].blue >> 8,
7475 normal_alpha);
7478 /* load the prelight color */
7479 gtk_widget_style_get (GTK_WIDGET (container),
7480 "prelight_alpha", &prelight_alpha,
7481 NULL);
7483 container->details->prelight_color_rgba =
7484 EEL_RGBA_COLOR_PACK (style->base[GTK_STATE_PRELIGHT].red >> 8,
7485 style->base[GTK_STATE_PRELIGHT].green >> 8,
7486 style->base[GTK_STATE_PRELIGHT].blue >> 8,
7487 prelight_alpha);
7490 setup_label_gcs (container);
7493 void
7494 nautilus_icon_container_set_font (NautilusIconContainer *container,
7495 const char *font)
7497 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
7499 if (eel_strcmp (container->details->font, font) == 0) {
7500 return;
7503 g_free (container->details->font);
7504 container->details->font = g_strdup (font);
7506 invalidate_label_sizes (container);
7507 nautilus_icon_container_request_update_all (container);
7508 gtk_widget_queue_draw (GTK_WIDGET (container));
7511 void
7512 nautilus_icon_container_set_font_size_table (NautilusIconContainer *container,
7513 const int font_size_table[NAUTILUS_ZOOM_LEVEL_LARGEST + 1])
7515 int old_font_size;
7516 int i;
7518 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
7519 g_return_if_fail (font_size_table != NULL);
7521 old_font_size = container->details->font_size_table[container->details->zoom_level];
7523 for (i = 0; i <= NAUTILUS_ZOOM_LEVEL_LARGEST; i++) {
7524 if (container->details->font_size_table[i] != font_size_table[i]) {
7525 container->details->font_size_table[i] = font_size_table[i];
7529 if (old_font_size != container->details->font_size_table[container->details->zoom_level]) {
7530 invalidate_label_sizes (container);
7531 nautilus_icon_container_request_update_all (container);
7536 * nautilus_icon_container_get_icon_description
7537 * @container: An icon container widget.
7538 * @data: Icon data
7540 * Gets the description for the icon. This function may return NULL.
7542 char*
7543 nautilus_icon_container_get_icon_description (NautilusIconContainer *container,
7544 NautilusIconData *data)
7546 NautilusIconContainerClass *klass;
7548 klass = NAUTILUS_ICON_CONTAINER_GET_CLASS (container);
7550 if (klass->get_icon_description) {
7551 return klass->get_icon_description (container, data);
7552 } else {
7553 return NULL;
7557 gboolean
7558 nautilus_icon_container_get_allow_moves (NautilusIconContainer *container)
7560 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), FALSE);
7562 return container->details->drag_allow_moves;
7565 void
7566 nautilus_icon_container_set_allow_moves (NautilusIconContainer *container,
7567 gboolean allow_moves)
7569 g_return_if_fail (NAUTILUS_IS_ICON_CONTAINER (container));
7571 container->details->drag_allow_moves = allow_moves;
7574 /* NautilusIconContainerAccessible */
7576 static NautilusIconContainerAccessiblePrivate *
7577 accessible_get_priv (AtkObject *accessible)
7579 NautilusIconContainerAccessiblePrivate *priv;
7581 priv = g_object_get_qdata (G_OBJECT (accessible),
7582 accessible_private_data_quark);
7584 return priv;
7587 /* AtkAction interface */
7589 static gboolean
7590 nautilus_icon_container_accessible_do_action (AtkAction *accessible, int i)
7592 GtkWidget *widget;
7593 NautilusIconContainer *container;
7594 GList *selection;
7596 g_return_val_if_fail (i < LAST_ACTION, FALSE);
7598 widget = GTK_ACCESSIBLE (accessible)->widget;
7599 if (!widget) {
7600 return FALSE;
7603 container = NAUTILUS_ICON_CONTAINER (widget);
7604 switch (i) {
7605 case ACTION_ACTIVATE :
7606 selection = nautilus_icon_container_get_selection (container);
7608 if (selection) {
7609 g_signal_emit_by_name (container, "activate", selection);
7610 g_list_free (selection);
7612 break;
7613 case ACTION_MENU :
7614 handle_popups (container, NULL,"context_click_background");
7615 break;
7616 default :
7617 g_warning ("Invalid action passed to NautilusIconContainerAccessible::do_action");
7618 return FALSE;
7620 return TRUE;
7623 static int
7624 nautilus_icon_container_accessible_get_n_actions (AtkAction *accessible)
7626 return LAST_ACTION;
7629 static const char *
7630 nautilus_icon_container_accessible_action_get_description (AtkAction *accessible,
7631 int i)
7633 NautilusIconContainerAccessiblePrivate *priv;
7635 g_return_val_if_fail (i < LAST_ACTION, NULL);
7637 priv = accessible_get_priv (ATK_OBJECT (accessible));
7639 if (priv->action_descriptions[i]) {
7640 return priv->action_descriptions[i];
7641 } else {
7642 return nautilus_icon_container_accessible_action_descriptions[i];
7646 static const char *
7647 nautilus_icon_container_accessible_action_get_name (AtkAction *accessible, int i)
7649 g_return_val_if_fail (i < LAST_ACTION, NULL);
7651 return nautilus_icon_container_accessible_action_names[i];
7654 static const char *
7655 nautilus_icon_container_accessible_action_get_keybinding (AtkAction *accessible,
7656 int i)
7658 g_return_val_if_fail (i < LAST_ACTION, NULL);
7660 return NULL;
7663 static gboolean
7664 nautilus_icon_container_accessible_action_set_description (AtkAction *accessible,
7665 int i,
7666 const char *description)
7668 NautilusIconContainerAccessiblePrivate *priv;
7670 g_return_val_if_fail (i < LAST_ACTION, FALSE);
7672 priv = accessible_get_priv (ATK_OBJECT (accessible));
7674 if (priv->action_descriptions[i]) {
7675 g_free (priv->action_descriptions[i]);
7677 priv->action_descriptions[i] = g_strdup (description);
7679 return FALSE;
7682 static void
7683 nautilus_icon_container_accessible_action_interface_init (AtkActionIface *iface)
7685 iface->do_action = nautilus_icon_container_accessible_do_action;
7686 iface->get_n_actions = nautilus_icon_container_accessible_get_n_actions;
7687 iface->get_description = nautilus_icon_container_accessible_action_get_description;
7688 iface->get_name = nautilus_icon_container_accessible_action_get_name;
7689 iface->get_keybinding = nautilus_icon_container_accessible_action_get_keybinding;
7690 iface->set_description = nautilus_icon_container_accessible_action_set_description;
7693 /* AtkSelection interface */
7695 static void
7696 nautilus_icon_container_accessible_update_selection (AtkObject *accessible)
7698 NautilusIconContainer *container;
7699 NautilusIconContainerAccessiblePrivate *priv;
7700 GList *l;
7701 NautilusIcon *icon;
7703 container = NAUTILUS_ICON_CONTAINER (GTK_ACCESSIBLE (accessible)->widget);
7705 priv = accessible_get_priv (accessible);
7707 if (priv->selection) {
7708 g_list_free (priv->selection);
7709 priv->selection = NULL;
7712 for (l = container->details->icons; l != NULL; l = l->next) {
7713 icon = l->data;
7714 if (icon->is_selected) {
7715 priv->selection = g_list_prepend (priv->selection,
7716 icon);
7720 priv->selection = g_list_reverse (priv->selection);
7723 static void
7724 nautilus_icon_container_accessible_selection_changed_cb (NautilusIconContainer *container,
7725 gpointer data)
7727 g_signal_emit_by_name (data, "selection_changed");
7730 static void
7731 nautilus_icon_container_accessible_icon_added_cb (NautilusIconContainer *container,
7732 NautilusIconData *icon_data,
7733 gpointer data)
7735 NautilusIcon *icon;
7736 AtkObject *atk_parent;
7737 AtkObject *atk_child;
7738 int index;
7740 icon = g_hash_table_lookup (container->details->icon_set, icon_data);
7741 if (icon) {
7742 atk_parent = ATK_OBJECT (data);
7743 atk_child = atk_gobject_accessible_for_object
7744 (G_OBJECT (icon->item));
7745 index = g_list_index (container->details->icons, icon);
7747 g_signal_emit_by_name (atk_parent, "children_changed::add",
7748 index, atk_child, NULL);
7752 static void
7753 nautilus_icon_container_accessible_icon_removed_cb (NautilusIconContainer *container,
7754 NautilusIconData *icon_data,
7755 gpointer data)
7757 NautilusIcon *icon;
7758 AtkObject *atk_parent;
7759 AtkObject *atk_child;
7760 int index;
7762 icon = g_hash_table_lookup (container->details->icon_set, icon_data);
7763 if (icon) {
7764 atk_parent = ATK_OBJECT (data);
7765 atk_child = atk_gobject_accessible_for_object
7766 (G_OBJECT (icon->item));
7767 index = g_list_index (container->details->icons, icon);
7769 g_signal_emit_by_name (atk_parent, "children_changed::remove",
7770 index, atk_child, NULL);
7774 static void
7775 nautilus_icon_container_accessible_cleared_cb (NautilusIconContainer *container,
7776 gpointer data)
7778 g_signal_emit_by_name (data, "children_changed", 0, NULL, NULL);
7782 static gboolean
7783 nautilus_icon_container_accessible_add_selection (AtkSelection *accessible,
7784 int i)
7786 GtkWidget *widget;
7787 NautilusIconContainer *container;
7788 GList *l;
7789 GList *selection;
7790 NautilusIcon *icon;
7792 widget = GTK_ACCESSIBLE (accessible)->widget;
7793 if (!widget) {
7794 return FALSE;
7797 container = NAUTILUS_ICON_CONTAINER (widget);
7799 l = g_list_nth (container->details->icons, i);
7800 if (l) {
7801 icon = l->data;
7803 selection = nautilus_icon_container_get_selection (container);
7804 selection = g_list_prepend (selection,
7805 icon->data);
7806 nautilus_icon_container_set_selection (container, selection);
7808 g_list_free (selection);
7809 return TRUE;
7812 return FALSE;
7815 static gboolean
7816 nautilus_icon_container_accessible_clear_selection (AtkSelection *accessible)
7818 GtkWidget *widget;
7819 NautilusIconContainer *container;
7821 widget = GTK_ACCESSIBLE (accessible)->widget;
7822 if (!widget) {
7823 return FALSE;
7826 container = NAUTILUS_ICON_CONTAINER (widget);
7828 nautilus_icon_container_unselect_all (container);
7830 return TRUE;
7833 static AtkObject *
7834 nautilus_icon_container_accessible_ref_selection (AtkSelection *accessible,
7835 int i)
7837 AtkObject *atk_object;
7838 NautilusIconContainerAccessiblePrivate *priv;
7839 GList *item;
7840 NautilusIcon *icon;
7842 nautilus_icon_container_accessible_update_selection (ATK_OBJECT (accessible));
7843 priv = accessible_get_priv (ATK_OBJECT (accessible));
7845 item = (g_list_nth (priv->selection, i));
7847 if (item) {
7848 icon = item->data;
7849 atk_object = atk_gobject_accessible_for_object (G_OBJECT (icon->item));
7850 if (atk_object) {
7851 g_object_ref (atk_object);
7854 return atk_object;
7855 } else {
7856 return NULL;
7860 static int
7861 nautilus_icon_container_accessible_get_selection_count (AtkSelection *accessible)
7863 int count;
7864 NautilusIconContainerAccessiblePrivate *priv;
7866 nautilus_icon_container_accessible_update_selection (ATK_OBJECT (accessible));
7867 priv = accessible_get_priv (ATK_OBJECT (accessible));
7869 count = g_list_length (priv->selection);
7871 return count;
7874 static gboolean
7875 nautilus_icon_container_accessible_is_child_selected (AtkSelection *accessible,
7876 int i)
7878 NautilusIconContainer *container;
7879 GList *l;
7880 NautilusIcon *icon;
7881 GtkWidget *widget;
7883 widget = GTK_ACCESSIBLE (accessible)->widget;
7884 if (!widget) {
7885 return FALSE;
7888 container = NAUTILUS_ICON_CONTAINER (widget);
7890 l = g_list_nth (container->details->icons, i);
7891 if (l) {
7892 icon = l->data;
7893 return icon->is_selected;
7895 return FALSE;
7898 static gboolean
7899 nautilus_icon_container_accessible_remove_selection (AtkSelection *accessible,
7900 int i)
7902 NautilusIconContainer *container;
7903 NautilusIconContainerAccessiblePrivate *priv;
7904 GList *l;
7905 GList *selection;
7906 NautilusIcon *icon;
7907 GtkWidget *widget;
7909 widget = GTK_ACCESSIBLE (accessible)->widget;
7910 if (!widget) {
7911 return FALSE;
7914 nautilus_icon_container_accessible_update_selection (ATK_OBJECT (accessible));
7915 priv = accessible_get_priv (ATK_OBJECT (accessible));
7917 container = NAUTILUS_ICON_CONTAINER (widget);
7919 l = g_list_nth (priv->selection, i);
7920 if (l) {
7921 icon = l->data;
7923 selection = nautilus_icon_container_get_selection (container);
7924 selection = g_list_remove (selection, icon->data);
7925 nautilus_icon_container_set_selection (container, selection);
7927 g_list_free (selection);
7928 return TRUE;
7931 return FALSE;
7934 static gboolean
7935 nautilus_icon_container_accessible_select_all_selection (AtkSelection *accessible)
7937 NautilusIconContainer *container;
7938 GtkWidget *widget;
7940 widget = GTK_ACCESSIBLE (accessible)->widget;
7941 if (!widget) {
7942 return FALSE;
7945 container = NAUTILUS_ICON_CONTAINER (widget);
7947 nautilus_icon_container_select_all (container);
7949 return TRUE;
7952 void
7953 nautilus_icon_container_widget_to_file_operation_position (NautilusIconContainer *container,
7954 GdkPoint *position)
7956 double x, y;
7958 g_return_if_fail (position != NULL);
7960 x = position->x;
7961 y = position->y;
7963 eel_canvas_window_to_world (EEL_CANVAS (container), x, y, &x, &y);
7965 position->x = (int) x;
7966 position->y = (int) y;
7968 /* ensure that we end up in the middle of the icon */
7969 position->x -= nautilus_get_icon_size_for_zoom_level (container->details->zoom_level) / 2;
7970 position->y -= nautilus_get_icon_size_for_zoom_level (container->details->zoom_level) / 2;
7973 static void
7974 nautilus_icon_container_accessible_selection_interface_init (AtkSelectionIface *iface)
7976 iface->add_selection = nautilus_icon_container_accessible_add_selection;
7977 iface->clear_selection = nautilus_icon_container_accessible_clear_selection;
7978 iface->ref_selection = nautilus_icon_container_accessible_ref_selection;
7979 iface->get_selection_count = nautilus_icon_container_accessible_get_selection_count;
7980 iface->is_child_selected = nautilus_icon_container_accessible_is_child_selected;
7981 iface->remove_selection = nautilus_icon_container_accessible_remove_selection;
7982 iface->select_all_selection = nautilus_icon_container_accessible_select_all_selection;
7986 static gint
7987 nautilus_icon_container_accessible_get_n_children (AtkObject *accessible)
7989 NautilusIconContainer *container;
7990 GtkWidget *widget;
7991 gint i;
7993 widget = GTK_ACCESSIBLE (accessible)->widget;
7994 if (!widget) {
7995 return FALSE;
7998 container = NAUTILUS_ICON_CONTAINER (widget);
8000 i = g_hash_table_size (container->details->icon_set);
8001 if (container->details->rename_widget) {
8002 i++;
8004 return i;
8007 static AtkObject*
8008 nautilus_icon_container_accessible_ref_child (AtkObject *accessible, int i)
8010 AtkObject *atk_object;
8011 NautilusIconContainer *container;
8012 GList *item;
8013 NautilusIcon *icon;
8014 GtkWidget *widget;
8016 widget = GTK_ACCESSIBLE (accessible)->widget;
8017 if (!widget) {
8018 return NULL;
8021 container = NAUTILUS_ICON_CONTAINER (widget);
8023 item = (g_list_nth (container->details->icons, i));
8025 if (item) {
8026 icon = item->data;
8028 atk_object = atk_gobject_accessible_for_object (G_OBJECT (icon->item));
8029 g_object_ref (atk_object);
8031 return atk_object;
8032 } else {
8033 if (i == g_list_length (container->details->icons)) {
8034 if (container->details->rename_widget) {
8035 atk_object = gtk_widget_get_accessible (container->details->rename_widget);
8036 g_object_ref (atk_object);
8038 return atk_object;
8041 return NULL;
8045 static void
8046 nautilus_icon_container_accessible_initialize (AtkObject *accessible,
8047 gpointer data)
8049 NautilusIconContainer *container;
8050 NautilusIconContainerAccessiblePrivate *priv;
8052 if (ATK_OBJECT_CLASS (accessible_parent_class)->initialize) {
8053 ATK_OBJECT_CLASS (accessible_parent_class)->initialize (accessible, data);
8056 priv = g_new0 (NautilusIconContainerAccessiblePrivate, 1);
8057 g_object_set_qdata (G_OBJECT (accessible),
8058 accessible_private_data_quark,
8059 priv);
8061 if (GTK_IS_ACCESSIBLE (accessible)) {
8062 nautilus_icon_container_accessible_update_selection
8063 (ATK_OBJECT (accessible));
8065 container = NAUTILUS_ICON_CONTAINER (GTK_ACCESSIBLE (accessible)->widget);
8066 g_signal_connect (G_OBJECT (container), "selection_changed",
8067 G_CALLBACK (nautilus_icon_container_accessible_selection_changed_cb),
8068 accessible);
8069 g_signal_connect (G_OBJECT (container), "icon_added",
8070 G_CALLBACK (nautilus_icon_container_accessible_icon_added_cb),
8071 accessible);
8072 g_signal_connect (G_OBJECT (container), "icon_removed",
8073 G_CALLBACK (nautilus_icon_container_accessible_icon_removed_cb),
8074 accessible);
8075 g_signal_connect (G_OBJECT (container), "cleared",
8076 G_CALLBACK (nautilus_icon_container_accessible_cleared_cb),
8077 accessible);
8081 static void
8082 nautilus_icon_container_accessible_finalize (GObject *object)
8084 NautilusIconContainerAccessiblePrivate *priv;
8085 int i;
8087 priv = accessible_get_priv (ATK_OBJECT (object));
8088 if (priv->selection) {
8089 g_list_free (priv->selection);
8092 for (i = 0; i < LAST_ACTION; i++) {
8093 if (priv->action_descriptions[i]) {
8094 g_free (priv->action_descriptions[i]);
8098 g_free (priv);
8100 G_OBJECT_CLASS (accessible_parent_class)->finalize (object);
8103 static void
8104 nautilus_icon_container_accessible_class_init (AtkObjectClass *klass)
8106 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
8108 accessible_parent_class = g_type_class_peek_parent (klass);
8110 gobject_class->finalize = nautilus_icon_container_accessible_finalize;
8112 klass->get_n_children = nautilus_icon_container_accessible_get_n_children;
8113 klass->ref_child = nautilus_icon_container_accessible_ref_child;
8114 klass->initialize = nautilus_icon_container_accessible_initialize;
8116 accessible_private_data_quark = g_quark_from_static_string ("icon-container-accessible-private-data");
8119 static GType
8120 nautilus_icon_container_accessible_get_type (void)
8122 static GType type = 0;
8124 if (!type) {
8125 static GInterfaceInfo atk_action_info = {
8126 (GInterfaceInitFunc) nautilus_icon_container_accessible_action_interface_init,
8127 (GInterfaceFinalizeFunc) NULL,
8128 NULL
8131 static GInterfaceInfo atk_selection_info = {
8132 (GInterfaceInitFunc) nautilus_icon_container_accessible_selection_interface_init,
8133 (GInterfaceFinalizeFunc) NULL,
8134 NULL
8137 type = eel_accessibility_create_derived_type
8138 ("NautilusIconContainerAccessible",
8139 EEL_TYPE_CANVAS,
8140 nautilus_icon_container_accessible_class_init);
8142 g_type_add_interface_static (type, ATK_TYPE_ACTION,
8143 &atk_action_info);
8144 g_type_add_interface_static (type, ATK_TYPE_SELECTION,
8145 &atk_selection_info);
8148 return type;
8151 #if ! defined (NAUTILUS_OMIT_SELF_CHECK)
8153 static char *
8154 check_compute_stretch (int icon_x, int icon_y, int icon_size,
8155 int start_pointer_x, int start_pointer_y,
8156 int end_pointer_x, int end_pointer_y)
8158 StretchState start, current;
8160 start.icon_x = icon_x;
8161 start.icon_y = icon_y;
8162 start.icon_size = icon_size;
8163 start.pointer_x = start_pointer_x;
8164 start.pointer_y = start_pointer_y;
8165 current.pointer_x = end_pointer_x;
8166 current.pointer_y = end_pointer_y;
8168 compute_stretch (&start, &current);
8170 return g_strdup_printf ("%d,%d:%d",
8171 current.icon_x,
8172 current.icon_y,
8173 current.icon_size);
8176 void
8177 nautilus_self_check_icon_container (void)
8179 EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 16, 0, 0, 0, 0), "0,0:16");
8180 EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 16, 16, 16, 17, 17), "0,0:17");
8181 EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 16, 16, 16, 17, 16), "0,0:16");
8182 EEL_CHECK_STRING_RESULT (check_compute_stretch (100, 100, 64, 105, 105, 40, 40), "35,35:129");
8185 gboolean
8186 nautilus_icon_container_is_layout_rtl (NautilusIconContainer *container)
8188 g_return_val_if_fail (NAUTILUS_IS_ICON_CONTAINER (container), 0);
8190 return container->details->layout_mode == NAUTILUS_ICON_LAYOUT_T_B_R_L ||
8191 container->details->layout_mode == NAUTILUS_ICON_LAYOUT_R_L_T_B;
8194 #endif /* ! NAUTILUS_OMIT_SELF_CHECK */