nautilus-icon-container.c 211 KB
Newer Older
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2

3
/* nautilus-icon-container.c - Icon container widget.
Ettore Perazzoli's avatar
Ettore Perazzoli committed
4

Elliot Lee's avatar
Elliot Lee committed
5
   Copyright (C) 1999, 2000 Free Software Foundation
6
   Copyright (C) 2000, 2001 Eazel, Inc.
7
   Copyright (C) 2002, 2003 Red Hat, Inc.
8
   
Ettore Perazzoli's avatar
Ettore Perazzoli committed
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   The Gnome Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The Gnome Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Gnome Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

24
   Authors: Ettore Perazzoli <ettore@gnu.org>,
25
   Darin Adler <darin@bentspoon.com>
Ettore Perazzoli's avatar
Ettore Perazzoli committed
26 27 28
*/

#include <config.h>
29
#include <math.h>
30
#include "nautilus-icon-container.h"
31

32
#include "nautilus-global-preferences.h"
33
#include "nautilus-icon-private.h"
34
#include "nautilus-lib-self-check-functions.h"
Darin Adler's avatar
Darin Adler committed
35
#include "nautilus-marshal.h"
36 37
#include <atk/atkaction.h>
#include <eel/eel-accessibility.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
38
#include <eel/eel-background.h>
39
#include <eel/eel-vfs-extensions.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
40 41 42
#include <eel/eel-gdk-pixbuf-extensions.h>
#include <eel/eel-gnome-extensions.h>
#include <eel/eel-gtk-extensions.h>
43
#include <eel/eel-editable-label.h>
Darin Adler's avatar
Darin Adler committed
44
#include <eel/eel-marshal.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
45
#include <eel/eel-string.h>
46
#include <eel/eel-canvas-rect-ellipse.h>
47
#include <libgnomeui/gnome-icon-item.h>
48
#include <gdk/gdkkeysyms.h>
49
#include <gtk/gtkaccessible.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
50
#include <gtk/gtklayout.h>
51 52
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
53
#include <glib/gi18n.h>
54
#include <libgnome/gnome-macros.h>
55 56
#include <stdio.h>
#include <string.h>
57

Alexander Larsson's avatar
Alexander Larsson committed
58 59
#define TAB_NAVIGATION_DISABLED

Ettore Perazzoli's avatar
Ettore Perazzoli committed
60 61 62
/* Interval for updating the rubberband selection, in milliseconds.  */
#define RUBBERBAND_TIMEOUT_INTERVAL 10

63 64 65
/* Initial unpositioned icon value */
#define ICON_UNPOSITIONED_VALUE -1

66 67 68
/* Timeout for making the icon currently selected for keyboard operation visible.
 * If this is 0, you can get into trouble with extra scrolling after holding
 * down the arrow key for awhile when there are many items.
69
 */
70
#define KEYBOARD_ICON_REVEAL_TIMEOUT 10
Ettore Perazzoli's avatar
Ettore Perazzoli committed
71

72 73
#define CONTEXT_MENU_TIMEOUT_INTERVAL 500

74 75 76
/* Maximum amount of milliseconds the mouse button is allowed to stay down
 * and still be considered a click.
 */
77 78
#define MAX_CLICK_TIME 1500

79 80 81
/* Button assignments. */
#define DRAG_BUTTON 1
#define RUBBERBAND_BUTTON 1
82
#define MIDDLE_BUTTON 2
83
#define CONTEXTUAL_MENU_BUTTON 3
84
#define DRAG_MENU_BUTTON 2
85

86
/* Maximum size (pixels) allowed for icons at the standard zoom level. */
87
#define MINIMUM_IMAGE_SIZE 24
88 89
#define MAXIMUM_IMAGE_SIZE 96
#define MAXIMUM_EMBLEM_SIZE 48
90

Darin Adler's avatar
Darin Adler committed
91 92 93 94 95 96 97 98 99 100 101
#define ICON_PAD_LEFT 4
#define ICON_PAD_RIGHT 4
#define ICON_BASE_WIDTH 96

#define ICON_PAD_TOP 4
#define ICON_PAD_BOTTOM 4

#define CONTAINER_PAD_LEFT 4
#define CONTAINER_PAD_TOP 4
#define CONTAINER_PAD_BOTTOM 4

102
#define STANDARD_ICON_GRID_WIDTH 155
103

Dave Camp's avatar
Dave Camp committed
104 105
#define TEXT_BESIDE_ICON_GRID_WIDTH 205

106
/* Desktop layout mode defines */
107
#define DESKTOP_PAD_HORIZONTAL 	10
108
#define DESKTOP_PAD_VERTICAL 	10
109 110
#define SNAP_SIZE_X 		78
#define SNAP_SIZE_Y 		20
111

112 113 114
/* Value used to protect against icons being dragged outside of the desktop bounds */
#define DESKTOP_ICON_SAFETY_PAD 10

115 116
#define DEFAULT_SELECTION_BOX_ALPHA 0x40
#define DEFAULT_HIGHLIGHT_ALPHA 0xff
117
#define DEFAULT_NORMAL_ALPHA 0xff
118 119 120
#define DEFAULT_LIGHT_INFO_COLOR 0xAAAAFD
#define DEFAULT_DARK_INFO_COLOR  0x33337F

121 122 123
#define MINIMUM_EMBEDDED_TEXT_RECT_WIDTH       20
#define MINIMUM_EMBEDDED_TEXT_RECT_HEIGHT      20

124 125 126 127 128
/* If icon size is bigger than this, request large embedded text.
 * Its selected so that the non-large text should fit in "normal" icon sizes
 */
#define ICON_SIZE_FOR_LARGE_EMBEDDED_TEXT 55

129 130 131
/* From nautilus-icon-canvas-item.c */
#define MAX_TEXT_WIDTH_BESIDE 90

132 133
#define SNAP_HORIZONTAL(func,x) ((func ((double)((x) - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X) * SNAP_SIZE_X) + DESKTOP_PAD_HORIZONTAL)
#define SNAP_VERTICAL(func, y) ((func ((double)((y) - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y) * SNAP_SIZE_Y) + DESKTOP_PAD_VERTICAL)
134 135 136 137 138 139

#define SNAP_NEAREST_HORIZONTAL(x) SNAP_HORIZONTAL (eel_round, x)
#define SNAP_NEAREST_VERTICAL(y) SNAP_VERTICAL (eel_round, y)

#define SNAP_CEIL_HORIZONTAL(x) SNAP_HORIZONTAL (ceil, x)
#define SNAP_CEIL_VERTICAL(y) SNAP_VERTICAL (ceil, y)
140

141 142 143
/* Copied from NautilusIconContainer */
#define NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT 5000

144 145
enum {
	ACTION_ACTIVATE,
146
	ACTION_MENU,
147 148 149 150 151 152 153 154 155 156
	LAST_ACTION
};

typedef struct {
	GList *selection;
	char *action_descriptions[LAST_ACTION];
} NautilusIconContainerAccessiblePrivate;

static GType         nautilus_icon_container_accessible_get_type (void);

157
static void          activate_selected_items                        (NautilusIconContainer *container);
158 159
static void          activate_selected_items_alternate              (NautilusIconContainer *container,
								     NautilusIcon          *icon);
160 161 162 163 164 165 166
static void          nautilus_icon_container_theme_changed          (gpointer               user_data);
static void          compute_stretch                                (StretchState          *start,
								     StretchState          *current);
static NautilusIcon *get_first_selected_icon                        (NautilusIconContainer *container);
static NautilusIcon *get_nth_selected_icon                          (NautilusIconContainer *container,
								     int                    index);
static gboolean      has_multiple_selection                         (NautilusIconContainer *container);
167
static gboolean      all_selected                                   (NautilusIconContainer *container);
Alexander Larsson's avatar
Alexander Larsson committed
168
static gboolean      has_selection                                  (NautilusIconContainer *container);
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
static void          icon_destroy                                   (NautilusIconContainer *container,
								     NautilusIcon          *icon);
static void          end_renaming_mode                              (NautilusIconContainer *container,
								     gboolean               commit);
static NautilusIcon *get_icon_being_renamed                         (NautilusIconContainer *container);
static void          finish_adding_new_icons                        (NautilusIconContainer *container);
static void          update_label_color                             (EelBackground         *background,
								     NautilusIconContainer *icon_container);
static void          icon_get_bounding_box                          (NautilusIcon          *icon,
								     int                   *x1_return,
								     int                   *y1_return,
								     int                   *x2_return,
								     int                   *y2_return);
static gboolean      is_renaming                                    (NautilusIconContainer *container);
static gboolean      is_renaming_pending                            (NautilusIconContainer *container);
static void          process_pending_icon_to_rename                 (NautilusIconContainer *container);
static void          setup_label_gcs                                (NautilusIconContainer *container);
static void          nautilus_icon_container_stop_monitor_top_left  (NautilusIconContainer *container,
								     NautilusIconData      *data,
								     gconstpointer          client);
static void          nautilus_icon_container_start_monitor_top_left (NautilusIconContainer *container,
								     NautilusIconData      *data,
191 192
								     gconstpointer          client,
								     gboolean               large_text);
193 194
static void          handle_vadjustment_changed                     (GtkAdjustment         *adjustment,
								     NautilusIconContainer *container);
195
static void          nautilus_icon_container_update_visible_icons   (NautilusIconContainer *container);
196 197
static void          reveal_icon                                    (NautilusIconContainer *container,
								     NautilusIcon *icon);
198

199

200
static gpointer accessible_parent_class;
201 202 203 204 205

static GQuark accessible_private_data_quark = 0;

static const char *nautilus_icon_container_accessible_action_names[] = {
	"activate",
206
	"menu",
207 208 209 210 211
	NULL
};

static const char *nautilus_icon_container_accessible_action_descriptions[] = {
	"Activate selected items",
212
	"Popup context menu",
213 214 215
	NULL
};

216
GNOME_CLASS_BOILERPLATE (NautilusIconContainer, nautilus_icon_container,
217
			 EelCanvas, EEL_TYPE_CANVAS)
218

219
/* The NautilusIconContainer signals.  */
220
enum {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
221
	ACTIVATE,
222
	ACTIVATE_ALTERNATE,
223 224
	BAND_SELECT_STARTED,
	BAND_SELECT_ENDED,
225
	BUTTON_PRESS,
226
	CAN_ACCEPT_ITEM,
227
	CONTEXT_CLICK_BACKGROUND,
228
	CONTEXT_CLICK_SELECTION,
229
	MIDDLE_CLICK,
230
	GET_CONTAINER_URI,
231
	GET_ICON_URI,
232
	GET_ICON_DROP_TARGET_URI,
233 234
	GET_STORED_ICON_POSITION,
	ICON_POSITION_CHANGED,
235
	ICON_TEXT_CHANGED,
236 237
	ICON_STRETCH_STARTED,
	ICON_STRETCH_ENDED,
238
	RENAMING_ICON,
239
	LAYOUT_CHANGED,
Pavel Cisler's avatar
Pavel Cisler committed
240
	MOVE_COPY_ITEMS,
241
	HANDLE_URL,
242
	HANDLE_URI_LIST,
243
	HANDLE_TEXT,
244
	PREVIEW,
245 246 247 248
	SELECTION_CHANGED,
	ICON_ADDED,
	ICON_REMOVED,
	CLEARED,
249
	START_INTERACTIVE_SEARCH,
Ettore Perazzoli's avatar
Ettore Perazzoli committed
250 251
	LAST_SIGNAL
};
252 253 254 255 256 257 258 259 260

typedef struct {
	int **icon_grid;
	int *grid_memory;
	int num_rows;
	int num_columns;
	gboolean tight;
} PlacementGrid;

261
static guint signals[LAST_SIGNAL];
Ettore Perazzoli's avatar
Ettore Perazzoli committed
262

263
/* Functions dealing with NautilusIcons.  */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
264 265

static void
266
icon_free (NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
267
{
268
	/* Destroy this canvas item; the parent will unref it. */
269
	gtk_object_destroy (GTK_OBJECT (icon->item));
270
	g_free (icon);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
271 272
}

Mike Engber's avatar
 
Mike Engber committed
273 274 275 276 277 278
static gboolean
icon_is_positioned (const NautilusIcon *icon)
{
	return icon->x != ICON_UNPOSITIONED_VALUE && icon->y != ICON_UNPOSITIONED_VALUE;
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
279
static void
280
icon_set_position (NautilusIcon *icon,
281
		   double x, double y)
282 283
{	
	NautilusIconContainer *container;
284
	double pixels_per_unit;	
285 286 287
	int left, top, right, bottom;
	int width, height;
	int x1, y1, x2, y2;
288 289
	int container_x, container_y, container_width, container_height;

290
	if (icon->x == x && icon->y == y) {
291
		return;
292
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
293

294
	container = NAUTILUS_ICON_CONTAINER (EEL_CANVAS_ITEM (icon->item)->canvas);
295

296 297 298 299
	if (icon == get_icon_being_renamed (container)) {
		end_renaming_mode (container, TRUE);
	}

300
	if (nautilus_icon_container_get_is_fixed_size (container)) {
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
		/*  FIXME: This should be:
		    
		container_x = GTK_WIDGET (container)->allocation.x;
		container_y = GTK_WIDGET (container)->allocation.y;
		container_width = GTK_WIDGET (container)->allocation.width;
		container_height = GTK_WIDGET (container)->allocation.height;

		But for some reason the widget allocation is sometimes not done
		at startup, and the allocation is then only 45x60. which is
		really bad.

		For now, we have a cheesy workaround:
		*/
		container_x = 0;
		container_y = 0;
		container_width = gdk_screen_width ();
		container_height = gdk_screen_height ();
318
		pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
319
		/* Clip the position of the icon within our desktop bounds */
320 321 322 323
		left = container_x / pixels_per_unit;
		top =  container_y / pixels_per_unit;
		right = left + container_width / pixels_per_unit;
		bottom = top + container_height / pixels_per_unit;
324 325 326 327

		icon_get_bounding_box (icon, &x1, &y1, &x2, &y2);
		width = x2 - x1;
		height = y2 - y1;
328
				
329
		if (x > right - DESKTOP_ICON_SAFETY_PAD) {
330
			x = right - DESKTOP_ICON_SAFETY_PAD;
331
		}
332
		
333 334
		if (x < left) {
			x = left;
335
		}
336
		if (y > bottom - DESKTOP_ICON_SAFETY_PAD) {
337
			y = bottom - DESKTOP_ICON_SAFETY_PAD;
338
		}
339 340
		if (y < top) {
			y = top;
341
		}		
342 343
	}

344 345 346 347 348 349 350
	if (icon->x == ICON_UNPOSITIONED_VALUE) {
		icon->x = 0;
	}
	if (icon->y == ICON_UNPOSITIONED_VALUE) {
		icon->y = 0;
	}
	
351
	eel_canvas_item_move (EEL_CANVAS_ITEM (icon->item),
352 353
				x - icon->x,
				y - icon->y);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
354 355 356 357 358

	icon->x = x;
	icon->y = y;
}

359
static void
360 361
icon_get_size (NautilusIconContainer *container,
	       NautilusIcon *icon,
362
	       guint *size)
363
{
364 365
	if (size != NULL) {
		*size = MAX (nautilus_get_icon_size_for_zoom_level (container->details->zoom_level)
366
			       * icon->scale, NAUTILUS_ICON_SIZE_SMALLEST);
367
	}
368 369
}

Darin Adler's avatar
Darin Adler committed
370 371 372 373 374
/* The icon_set_size function is used by the stretching user
 * interface, which currently stretches in a way that keeps the aspect
 * ratio. Later we might have a stretching interface that stretches Y
 * separate from X and we will change this around.
 */
375
static void
376 377
icon_set_size (NautilusIconContainer *container,
	       NautilusIcon *icon,
378
	       guint icon_size,
379
	       gboolean snap,
380
	       gboolean update_position)
381
{
382
	guint old_size;
383 384
	double scale;

385 386
	icon_get_size (container, icon, &old_size);
	if (icon_size == old_size) {
387
		return;
388
	}
389

390
	scale = (double) icon_size /
391 392
		nautilus_get_icon_size_for_zoom_level
		(container->details->zoom_level);
393
	nautilus_icon_container_move_icon (container, icon,
Darin Adler's avatar
Darin Adler committed
394
					   icon->x, icon->y,
395
					   scale, FALSE,
396
					   snap, update_position);
397 398
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
399
static void
400
icon_raise (NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
401
{
402
	EelCanvasItem *item, *band;
403
	
404
	item = EEL_CANVAS_ITEM (icon->item);
405
	band = NAUTILUS_ICON_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
406
	
407
	eel_canvas_item_send_behind (item, band);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
408 409
}

410 411 412
static void
emit_stretch_started (NautilusIconContainer *container, NautilusIcon *icon)
{
413
	g_signal_emit (container,
414
			 signals[ICON_STRETCH_STARTED], 0,
415 416 417 418 419 420
			 icon->data);
}

static void
emit_stretch_ended (NautilusIconContainer *container, NautilusIcon *icon)
{
421
	g_signal_emit (container,
422
			 signals[ICON_STRETCH_ENDED], 0,
423 424 425
			 icon->data);
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
426
static void
427 428
icon_toggle_selected (NautilusIconContainer *container,
		      NautilusIcon *icon)
429
{		
430
	end_renaming_mode (container, TRUE);
431

432
	icon->is_selected = !icon->is_selected;
433
	eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
434 435
			     "highlighted_for_selection", (gboolean) icon->is_selected,
			     NULL);
436 437 438 439 440 441

	/* If the icon is deselected, then get rid of the stretch handles.
	 * No harm in doing the same if the item is newly selected.
	 */
	if (icon == container->details->stretch_icon) {
		container->details->stretch_icon = NULL;
442
		nautilus_icon_canvas_item_set_show_stretch_handles (icon->item, FALSE);
443 444 445 446 447
		/* snap the icon if necessary */
		if (container->details->keep_aligned) {
			nautilus_icon_container_move_icon (container,
							   icon,
							   icon->x, icon->y,
448
							   icon->scale,
449 450 451
							   FALSE, TRUE, TRUE);
		}
		
452
		emit_stretch_ended (container, icon);
453
	}
454 455 456 457 458

	/* Raise each newly-selected icon to the front as it is selected. */
	if (icon->is_selected) {
		icon_raise (icon);
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
459 460
}

461
/* Select an icon. Return TRUE if selection has changed. */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
462
static gboolean
463 464
icon_set_selected (NautilusIconContainer *container,
		   NautilusIcon *icon,
465
		   gboolean select)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
466
{
467 468 469 470
	g_assert (select == FALSE || select == TRUE);
	g_assert (icon->is_selected == FALSE || icon->is_selected == TRUE);

	if (select == icon->is_selected) {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
471
		return FALSE;
472
	}
473

474
	icon_toggle_selected (container, icon);
475
	g_assert (select == icon->is_selected);
476
	return TRUE;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
477 478 479
}

static void
480
icon_get_bounding_box (NautilusIcon *icon,
481 482
		       int *x1_return, int *y1_return,
		       int *x2_return, int *y2_return)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
483 484 485
{
	double x1, y1, x2, y2;

486
	eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
Ettore Perazzoli's avatar
Ettore Perazzoli committed
487 488 489 490 491 492 493 494
				      &x1, &y1, &x2, &y2);

	*x1_return = x1;
	*y1_return = y1;
	*x2_return = x2;
	*y2_return = y2;
}

495
/* Utility functions for NautilusIconContainer.  */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
496

497
gboolean
Pavel Cisler's avatar
Pavel Cisler committed
498 499
nautilus_icon_container_scroll (NautilusIconContainer *container,
				int delta_x, int delta_y)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
500 501
{
	GtkAdjustment *hadj, *vadj;
502
	int old_h_value, old_v_value;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
503

504 505
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
506

507 508 509 510 511 512 513 514
	/* Store the old ajustment values so we can tell if we
	 * ended up actually scrolling. We may not have in a case
	 * where the resulting value got pinned to the adjustment
	 * min or max.
	 */
	old_h_value = hadj->value;
	old_v_value = vadj->value;
	
Ramiro Estrugo's avatar
Ramiro Estrugo committed
515 516
	eel_gtk_adjustment_set_value (hadj, hadj->value + delta_x);
	eel_gtk_adjustment_set_value (vadj, vadj->value + delta_y);
517 518 519

	/* return TRUE if we did scroll */
	return hadj->value != old_h_value || vadj->value != old_v_value;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
520 521
}

Mike Engber's avatar
 
Mike Engber committed
522
static void
523 524
pending_icon_to_reveal_destroy_callback (NautilusIconCanvasItem *item,
					 NautilusIconContainer *container)
Mike Engber's avatar
 
Mike Engber committed
525
{
526
	g_assert (NAUTILUS_IS_ICON_CONTAINER (container));
Mike Engber's avatar
 
Mike Engber committed
527 528
	g_assert (container->details->pending_icon_to_reveal != NULL);
	g_assert (container->details->pending_icon_to_reveal->item == item);
529

Mike Engber's avatar
 
Mike Engber committed
530 531 532 533 534 535 536 537 538 539 540 541
	container->details->pending_icon_to_reveal = NULL;
}

static NautilusIcon*
get_pending_icon_to_reveal (NautilusIconContainer *container)
{
	return container->details->pending_icon_to_reveal;
}

static void
set_pending_icon_to_reveal (NautilusIconContainer *container, NautilusIcon *icon)
{
542
	NautilusIcon *old_icon;
Mike Engber's avatar
 
Mike Engber committed
543
	
544
	old_icon = container->details->pending_icon_to_reveal;
Mike Engber's avatar
 
Mike Engber committed
545
	
546
	if (icon == old_icon) {
Mike Engber's avatar
 
Mike Engber committed
547 548 549
		return;
	}
	
550 551 552 553 554
	if (old_icon != NULL) {
		g_signal_handlers_disconnect_by_func
			(old_icon->item,
			 G_CALLBACK (pending_icon_to_reveal_destroy_callback),
			 container);
Mike Engber's avatar
 
Mike Engber committed
555 556 557
	}
	
	if (icon != NULL) {
558
		g_signal_connect (icon->item, "destroy",
559 560
				  G_CALLBACK (pending_icon_to_reveal_destroy_callback),
				  container);
Mike Engber's avatar
 
Mike Engber committed
561 562 563 564 565
	}
	
	container->details->pending_icon_to_reveal = icon;
}

566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
static void
item_get_canvas_bounds (EelCanvasItem *item, ArtIRect *bounds)
{
	ArtDRect world_rect;
	
	eel_canvas_item_get_bounds (item,
				    &world_rect.x0,
				    &world_rect.y0,
				    &world_rect.x1,
				    &world_rect.y1);
	eel_canvas_item_i2w (item->parent,
			     &world_rect.x0,
			     &world_rect.y0);
	eel_canvas_item_i2w (item->parent,
			     &world_rect.x1,
			     &world_rect.y1);
	eel_canvas_w2c (item->canvas,
			world_rect.x0,
			world_rect.y0,
			&bounds->x0,
			&bounds->y0);
	eel_canvas_w2c (item->canvas,
			world_rect.x1,
			world_rect.y1,
			&bounds->x1,
			&bounds->y1);
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
594
static void
595 596
reveal_icon (NautilusIconContainer *container,
	     NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
597
{
598
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
599 600
	GtkAllocation *allocation;
	GtkAdjustment *hadj, *vadj;
601
	ArtIRect bounds;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
602

Mike Engber's avatar
 
Mike Engber committed
603 604 605 606 607 608 609
	if (!icon_is_positioned (icon)) {
		set_pending_icon_to_reveal (container, icon);
		return;
	}
	
	set_pending_icon_to_reveal (container, NULL);

610
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
611 612
	allocation = &GTK_WIDGET (container)->allocation;

613 614
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
615

616
	item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), &bounds);
617
	if (bounds.y0 < vadj->value) {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
618
		eel_gtk_adjustment_set_value (vadj, bounds.y0);
619
	} else if (bounds.y1 > vadj->value + allocation->height) {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
620
		eel_gtk_adjustment_set_value
621
			(vadj, bounds.y1 - allocation->height);
622
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
623

624
	if (bounds.x0 < hadj->value) {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
625
		eel_gtk_adjustment_set_value (hadj, bounds.x0);
626
	} else if (bounds.x1 > hadj->value + allocation->width) {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
627
		eel_gtk_adjustment_set_value
628
			(hadj, bounds.x1 - allocation->width);
629
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
630 631
}

Mike Engber's avatar
 
Mike Engber committed
632 633 634 635 636 637 638 639 640 641 642 643
static void
process_pending_icon_to_reveal (NautilusIconContainer *container)
{
	NautilusIcon *pending_icon_to_reveal;
	
	pending_icon_to_reveal = get_pending_icon_to_reveal (container);
	
	if (pending_icon_to_reveal != NULL) {
		reveal_icon (container, pending_icon_to_reveal);
	}
}

644
static gboolean
645
keyboard_icon_reveal_timeout_callback (gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
646
{
647 648
	NautilusIconContainer *container;
	NautilusIcon *icon;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
649

650
	container = NAUTILUS_ICON_CONTAINER (data);
651 652 653
	icon = container->details->keyboard_icon_to_reveal;

	g_assert (icon != NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
654

Darin Adler's avatar
Darin Adler committed
655
	/* Only reveal the icon if it's still the keyboard focus or if
656 657 658 659
	 * it's still selected. Someone originally thought we should
	 * cancel this reveal if the user manages to sneak a direct
	 * scroll in before the timeout fires, but we later realized
	 * this wouldn't actually be an improvement 
660
	 * (see bugzilla.gnome.org 40612).
661 662 663 664
	 */
	if (icon == container->details->keyboard_focus
	    || icon->is_selected) {
		reveal_icon (container, icon);
665
	}
666
	container->details->keyboard_icon_reveal_timer_id = 0;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
667 668 669 670 671

	return FALSE;
}

static void
672
unschedule_keyboard_icon_reveal (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
673
{
674
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
675

676
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
677

678
	if (details->keyboard_icon_reveal_timer_id != 0) {
679
		g_source_remove (details->keyboard_icon_reveal_timer_id);
680
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
681 682 683
}

static void
684 685
schedule_keyboard_icon_reveal (NautilusIconContainer *container,
			       NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
686
{
687
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
688

689
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
690

691
	unschedule_keyboard_icon_reveal (container);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
692

693 694
	details->keyboard_icon_to_reveal = icon;
	details->keyboard_icon_reveal_timer_id
695 696 697
		= g_timeout_add (KEYBOARD_ICON_REVEAL_TIMEOUT,
				 keyboard_icon_reveal_timeout_callback,
				 container);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
698 699 700
}

static void
701
clear_keyboard_focus (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
702
{
703
        if (container->details->keyboard_focus != NULL) {
704
		eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
705
				       "highlighted_as_keyboard_focus", 0,
706 707 708
				       NULL);
	}
	
709 710 711
	container->details->keyboard_focus = NULL;
}

Darin Adler's avatar
Darin Adler committed
712
/* Set @icon as the icon currently selected for keyboard operations. */
713
static void
714 715
set_keyboard_focus (NautilusIconContainer *container,
		    NautilusIcon *icon)
716 717 718 719 720
{
	g_assert (icon != NULL);

	if (icon == container->details->keyboard_focus) {
		return;
721
	}
722 723 724 725 726

	clear_keyboard_focus (container);

	container->details->keyboard_focus = icon;

727
	eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
728 729
			       "highlighted_as_keyboard_focus", 1,
			       NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
730 731
}

732 733 734 735 736 737 738 739 740 741 742 743 744
static void
set_keyboard_rubberband_start (NautilusIconContainer *container,
			       NautilusIcon *icon)
{
	container->details->keyboard_rubberband_start = icon;
}

static void
clear_keyboard_rubberband_start (NautilusIconContainer *container)
{
	container->details->keyboard_rubberband_start = NULL;
}

745 746 747 748 749
static void
get_all_icon_bounds (NautilusIconContainer *container,
		     double *x1, double *y1,
		     double *x2, double *y2)
{
750
	/* FIXME bugzilla.gnome.org 42477: Do we have to do something about the rubberband
751 752
	 * here? Any other non-icon items?
	 */
753 754
	eel_canvas_item_get_bounds
		(EEL_CANVAS (container)->root,
755 756
		 x1, y1, x2, y2);
}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
757

758 759 760 761 762 763 764 765
/* Don't preserve visible white space the next time the scroll region
 * is recomputed when the container is not empty. */
void
nautilus_icon_container_reset_scroll_region (NautilusIconContainer *container)
{
	container->details->reset_scroll_region_trigger = TRUE;
}

766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792
/* Set a new scroll region without eliminating any of the currently-visible area. */
static void
canvas_set_scroll_region_include_visible_area (EelCanvas *canvas,
					       double x1, double y1,
					       double x2, double y2)
{
	double old_x1, old_y1, old_x2, old_y2;
	double old_scroll_x, old_scroll_y;
	double height, width;
	
	eel_canvas_get_scroll_region (canvas, &old_x1, &old_y1, &old_x2, &old_y2);

	width = (GTK_WIDGET (canvas)->allocation.width) / canvas->pixels_per_unit;
	height = (GTK_WIDGET (canvas)->allocation.height) / canvas->pixels_per_unit;

	old_scroll_x = gtk_layout_get_hadjustment (GTK_LAYOUT (canvas))->value;
	old_scroll_y = gtk_layout_get_vadjustment (GTK_LAYOUT (canvas))->value;

	x1 = MIN (x1, old_x1 + old_scroll_x);
	y1 = MIN (y1, old_y1 + old_scroll_y);
	x2 = MAX (x2, old_x1 + old_scroll_x + width);
	y2 = MAX (y2, old_y1 + old_scroll_y + height);

	eel_canvas_set_scroll_region
		(canvas, x1, y1, x2, y2);
}

793 794
void
nautilus_icon_container_update_scroll_region (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
795
{
796
	double x1, y1, x2, y2;
797
	double pixels_per_unit;
798 799
	GtkAdjustment *hadj, *vadj;
	float step_increment;
800
	GtkAllocation *allocation;
801
	gboolean reset_scroll_region;
802 803

	if (nautilus_icon_container_get_is_fixed_size (container)) {
804
		pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
805
		
806
		/* Set the scroll region to the size of the container allocation */
807 808 809
		allocation = &GTK_WIDGET (container)->allocation;
		eel_canvas_set_scroll_region
			(EEL_CANVAS (container),
810 811 812
			 (double) - container->details->left_margin / pixels_per_unit,
			 (double) - container->details->top_margin / pixels_per_unit,
			 ((double) (allocation->width - 1)
813
			 - container->details->left_margin
814 815 816
			 - container->details->right_margin)
			 / pixels_per_unit,
			 ((double) (allocation->height - 1)
817
			 - container->details->top_margin
818 819
			 - container->details->bottom_margin)
			 / pixels_per_unit);
820 821
		return;
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
822

823
	reset_scroll_region = container->details->reset_scroll_region_trigger
824 825
		|| nautilus_icon_container_is_empty (container)
		|| nautilus_icon_container_is_auto_layout (container);
826 827 828 829 830 831 832