nautilus-icon-container.c 222 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

Alexander Larsson's avatar
 
Alexander Larsson committed
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"
Darin Adler's avatar
Darin Adler committed
36
#include "nautilus-marshal.h"
37 38
#include <atk/atkaction.h>
#include <eel/eel-accessibility.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
39
#include <eel/eel-background.h>
40
#include <eel/eel-vfs-extensions.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
41 42 43
#include <eel/eel-gdk-pixbuf-extensions.h>
#include <eel/eel-gnome-extensions.h>
#include <eel/eel-gtk-extensions.h>
Alexander Larsson's avatar
Alexander Larsson committed
44
#include <eel/eel-art-extensions.h>
45
#include <eel/eel-editable-label.h>
Darin Adler's avatar
Darin Adler committed
46
#include <eel/eel-marshal.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
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>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
52
#include <gtk/gtklayout.h>
53 54
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
55
#include <glib/gi18n.h>
56
#include <libgnome/gnome-macros.h>
57 58
#include <stdio.h>
#include <string.h>
59

Alexander Larsson's avatar
Alexander Larsson committed
60 61
#define TAB_NAVIGATION_DISABLED

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

65 66 67
/* Initial unpositioned icon value */
#define ICON_UNPOSITIONED_VALUE -1

68 69 70
/* 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.
71
 */
72
#define KEYBOARD_ICON_REVEAL_TIMEOUT 10
Ettore Perazzoli's avatar
Ettore Perazzoli committed
73

74 75
#define CONTEXT_MENU_TIMEOUT_INTERVAL 500

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

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

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

Darin Adler's avatar
Darin Adler committed
92 93 94 95 96 97 98 99 100 101 102
#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

103
#define STANDARD_ICON_GRID_WIDTH 155
104

Dave Camp's avatar
Dave Camp committed
105 106
#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 111
#define SNAP_SIZE_X 		78
#define SNAP_SIZE_Y 		20
112

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

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

123 124 125 126 127 128 129 130 131
#define DEFAULT_NORMAL_ICON_RENDER_MODE 0
#define DEFAULT_PRELIGHT_ICON_RENDER_MODE 1
#define DEFAULT_NORMAL_ICON_SATURATION 255
#define DEFAULT_PRELIGHT_ICON_SATURATION 255
#define DEFAULT_NORMAL_ICON_BRIGHTNESS 255
#define DEFAULT_PRELIGHT_ICON_BRIGHTNESS 255
#define DEFAULT_NORMAL_ICON_LIGHTEN 0
#define DEFAULT_PRELIGHT_ICON_LIGHTEN 0

132 133 134
#define MINIMUM_EMBEDDED_TEXT_RECT_WIDTH       20
#define MINIMUM_EMBEDDED_TEXT_RECT_HEIGHT      20

135 136 137 138 139
/* 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

140 141 142
/* From nautilus-icon-canvas-item.c */
#define MAX_TEXT_WIDTH_BESIDE 90

143 144
#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)
145 146 147 148 149 150

#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)
151

152 153 154
/* Copied from NautilusIconContainer */
#define NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT 5000

155 156
enum {
	ACTION_ACTIVATE,
157
	ACTION_MENU,
158 159 160 161 162 163 164 165 166 167
	LAST_ACTION
};

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

static GType         nautilus_icon_container_accessible_get_type (void);

168
static void          activate_selected_items                        (NautilusIconContainer *container);
169 170
static void          activate_selected_items_alternate              (NautilusIconContainer *container,
								     NautilusIcon          *icon);
171 172 173 174 175 176 177
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);
178
static gboolean      all_selected                                   (NautilusIconContainer *container);
Alexander Larsson's avatar
Alexander Larsson committed
179
static gboolean      has_selection                                  (NautilusIconContainer *container);
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
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,
202 203
								     gconstpointer          client,
								     gboolean               large_text);
204 205
static void          handle_vadjustment_changed                     (GtkAdjustment         *adjustment,
								     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 209
static void          reveal_icon                                    (NautilusIconContainer *container,
								     NautilusIcon *icon);
210

211 212 213 214
static void	     nautilus_icon_container_set_rtl_positions (NautilusIconContainer *container);
static double	     get_mirror_x_position                     (NautilusIconContainer *container,
								NautilusIcon *icon,
								double x);
215

216
static gpointer accessible_parent_class;
217 218 219 220 221

static GQuark accessible_private_data_quark = 0;

static const char *nautilus_icon_container_accessible_action_names[] = {
	"activate",
222
	"menu",
223 224 225 226 227
	NULL
};

static const char *nautilus_icon_container_accessible_action_descriptions[] = {
	"Activate selected items",
228
	"Popup context menu",
229 230 231
	NULL
};

232
GNOME_CLASS_BOILERPLATE (NautilusIconContainer, nautilus_icon_container,
233
			 EelCanvas, EEL_TYPE_CANVAS)
234

235
/* The NautilusIconContainer signals.  */
236
enum {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
237
	ACTIVATE,
238
	ACTIVATE_ALTERNATE,
239 240
	BAND_SELECT_STARTED,
	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 250
	GET_STORED_ICON_POSITION,
	ICON_POSITION_CHANGED,
251
	ICON_TEXT_CHANGED,
252 253
	ICON_STRETCH_STARTED,
	ICON_STRETCH_ENDED,
254
	RENAMING_ICON,
255
	LAYOUT_CHANGED,
Pavel Cisler's avatar
Pavel Cisler committed
256
	MOVE_COPY_ITEMS,
257
	HANDLE_NETSCAPE_URL,
258
	HANDLE_URI_LIST,
259
	HANDLE_TEXT,
260
	PREVIEW,
261 262 263 264
	SELECTION_CHANGED,
	ICON_ADDED,
	ICON_REMOVED,
	CLEARED,
265
	START_INTERACTIVE_SEARCH,
Ettore Perazzoli's avatar
Ettore Perazzoli committed
266 267
	LAST_SIGNAL
};
268 269 270 271 272 273 274 275 276

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

277
static guint signals[LAST_SIGNAL];
Ettore Perazzoli's avatar
Ettore Perazzoli committed
278

279
/* Functions dealing with NautilusIcons.  */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
280 281

static void
282
icon_free (NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
283
{
284
	/* Destroy this canvas item; the parent will unref it. */
285
	gtk_object_destroy (GTK_OBJECT (icon->item));
286
	g_free (icon);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
287 288
}

Mike Engber's avatar
 
Mike Engber committed
289 290 291 292 293 294
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
295
static void
296
icon_set_position (NautilusIcon *icon,
297
		   double x, double y)
298 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;
Alexander Larsson's avatar
Alexander Larsson committed
304
	EelDRect icon_bounds;
305

306
	if (icon->x == x && icon->y == y) {
307
		return;
308
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
309

310
	container = NAUTILUS_ICON_CONTAINER (EEL_CANVAS_ITEM (icon->item)->canvas);
311

312 313 314 315
	if (icon == get_icon_being_renamed (container)) {
		end_renaming_mode (container, TRUE);
	}

316
	if (nautilus_icon_container_get_is_fixed_size (container)) {
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
		/*  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 ();
334
		pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
335
		/* Clip the position of the icon within our desktop bounds */
336 337 338 339
		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;
340

341 342
		icon_bounds = nautilus_icon_canvas_item_get_icon_rectangle (icon->item);
		width = icon_bounds.x1 - icon_bounds.x0;
343
				
344 345 346 347 348 349 350 351 352 353 354 355 356 357
		if (nautilus_icon_container_is_layout_rtl(container)) {
			if (x + width < left + DESKTOP_ICON_SAFETY_PAD) {
				x = left + DESKTOP_ICON_SAFETY_PAD - width;
			}
			if (x + width > right) {
				x = right - width;
			}
		} else {
			if (x > right - DESKTOP_ICON_SAFETY_PAD) {
				x = right - DESKTOP_ICON_SAFETY_PAD;
			}
			if (x < left) {
				x = left;
			}
358
		}
359
		if (y > bottom - DESKTOP_ICON_SAFETY_PAD) {
360
			y = bottom - DESKTOP_ICON_SAFETY_PAD;
361
		}
362 363
		if (y < top) {
			y = top;
364
		}		
365 366
	}

367 368 369 370 371 372
	if (icon->x == ICON_UNPOSITIONED_VALUE) {
		icon->x = 0;
	}
	if (icon->y == ICON_UNPOSITIONED_VALUE) {
		icon->y = 0;
	}
373
	
374
	eel_canvas_item_move (EEL_CANVAS_ITEM (icon->item),
375 376
				x - icon->x,
				y - icon->y);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
377 378 379 380 381

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

382
static void
383 384
icon_get_size (NautilusIconContainer *container,
	       NautilusIcon *icon,
385
	       guint *size)
386
{
387 388
	if (size != NULL) {
		*size = MAX (nautilus_get_icon_size_for_zoom_level (container->details->zoom_level)
389
			       * icon->scale, NAUTILUS_ICON_SIZE_SMALLEST);
390
	}
391 392
}

Darin Adler's avatar
Darin Adler committed
393 394 395 396 397
/* 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.
 */
398
static void
399 400
icon_set_size (NautilusIconContainer *container,
	       NautilusIcon *icon,
401
	       guint icon_size,
402
	       gboolean snap,
403
	       gboolean update_position)
404
{
405
	guint old_size;
406 407
	double scale;

408 409
	icon_get_size (container, icon, &old_size);
	if (icon_size == old_size) {
410
		return;
411
	}
412

413
	scale = (double) icon_size /
414 415
		nautilus_get_icon_size_for_zoom_level
		(container->details->zoom_level);
416
	nautilus_icon_container_move_icon (container, icon,
Darin Adler's avatar
Darin Adler committed
417
					   icon->x, icon->y,
418
					   scale, FALSE,
419
					   snap, update_position);
420 421
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
422
static void
423
icon_raise (NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
424
{
425
	EelCanvasItem *item, *band;
426
	
427
	item = EEL_CANVAS_ITEM (icon->item);
428
	band = NAUTILUS_ICON_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
429
	
430
	eel_canvas_item_send_behind (item, band);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
431 432
}

433 434 435
static void
emit_stretch_started (NautilusIconContainer *container, NautilusIcon *icon)
{
436
	g_signal_emit (container,
437
			 signals[ICON_STRETCH_STARTED], 0,
438 439 440 441 442 443
			 icon->data);
}

static void
emit_stretch_ended (NautilusIconContainer *container, NautilusIcon *icon)
{
444
	g_signal_emit (container,
445
			 signals[ICON_STRETCH_ENDED], 0,
446 447 448
			 icon->data);
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
449
static void
450 451
icon_toggle_selected (NautilusIconContainer *container,
		      NautilusIcon *icon)
452
{		
453
	end_renaming_mode (container, TRUE);
454

455
	icon->is_selected = !icon->is_selected;
456
	eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
457 458
			     "highlighted_for_selection", (gboolean) icon->is_selected,
			     NULL);
459 460 461 462 463 464

	/* 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;
465
		nautilus_icon_canvas_item_set_show_stretch_handles (icon->item, FALSE);
466 467 468 469 470
		/* snap the icon if necessary */
		if (container->details->keep_aligned) {
			nautilus_icon_container_move_icon (container,
							   icon,
							   icon->x, icon->y,
471
							   icon->scale,
472 473 474
							   FALSE, TRUE, TRUE);
		}
		
475
		emit_stretch_ended (container, icon);
476
	}
477 478 479 480 481

	/* 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
482 483
}

484
/* Select an icon. Return TRUE if selection has changed. */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
485
static gboolean
486 487
icon_set_selected (NautilusIconContainer *container,
		   NautilusIcon *icon,
488
		   gboolean select)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
489
{
490 491 492 493
	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
494
		return FALSE;
495
	}
496

497
	icon_toggle_selected (container, icon);
498
	g_assert (select == icon->is_selected);
499
	return TRUE;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
500 501 502
}

static void
503
icon_get_bounding_box (NautilusIcon *icon,
504 505
		       int *x1_return, int *y1_return,
		       int *x2_return, int *y2_return)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
506 507 508
{
	double x1, y1, x2, y2;

509
	eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
Ettore Perazzoli's avatar
Ettore Perazzoli committed
510 511 512 513 514 515 516 517
				      &x1, &y1, &x2, &y2);

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

518
/* Utility functions for NautilusIconContainer.  */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
519

520
gboolean
Pavel Cisler's avatar
Pavel Cisler committed
521 522
nautilus_icon_container_scroll (NautilusIconContainer *container,
				int delta_x, int delta_y)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
523 524
{
	GtkAdjustment *hadj, *vadj;
525
	int old_h_value, old_v_value;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
526

527 528
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
529

530 531 532 533 534 535 536 537
	/* 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
538 539
	eel_gtk_adjustment_set_value (hadj, hadj->value + delta_x);
	eel_gtk_adjustment_set_value (vadj, vadj->value + delta_y);
540 541 542

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

Mike Engber's avatar
 
Mike Engber committed
545
static void
546 547
pending_icon_to_reveal_destroy_callback (NautilusIconCanvasItem *item,
					 NautilusIconContainer *container)
Mike Engber's avatar
 
Mike Engber committed
548
{
549
	g_assert (NAUTILUS_IS_ICON_CONTAINER (container));
Mike Engber's avatar
 
Mike Engber committed
550 551
	g_assert (container->details->pending_icon_to_reveal != NULL);
	g_assert (container->details->pending_icon_to_reveal->item == item);
552

Mike Engber's avatar
 
Mike Engber committed
553 554 555 556 557 558 559 560 561 562 563 564
	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)
{
565
	NautilusIcon *old_icon;
Mike Engber's avatar
 
Mike Engber committed
566
	
567
	old_icon = container->details->pending_icon_to_reveal;
Mike Engber's avatar
 
Mike Engber committed
568
	
569
	if (icon == old_icon) {
Mike Engber's avatar
 
Mike Engber committed
570 571 572
		return;
	}
	
573 574 575 576 577
	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
578 579 580
	}
	
	if (icon != NULL) {
581
		g_signal_connect (icon->item, "destroy",
582 583
				  G_CALLBACK (pending_icon_to_reveal_destroy_callback),
				  container);
Mike Engber's avatar
 
Mike Engber committed
584 585 586 587 588
	}
	
	container->details->pending_icon_to_reveal = icon;
}

589
static void
Alexander Larsson's avatar
Alexander Larsson committed
590
item_get_canvas_bounds (EelCanvasItem *item, EelIRect *bounds)
591
{
Alexander Larsson's avatar
Alexander Larsson committed
592
	EelDRect world_rect;
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
	
	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
617
static void
618 619
reveal_icon (NautilusIconContainer *container,
	     NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
620
{
621
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
622 623
	GtkAllocation *allocation;
	GtkAdjustment *hadj, *vadj;
Alexander Larsson's avatar
Alexander Larsson committed
624
	EelIRect bounds;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
625

Mike Engber's avatar
 
Mike Engber committed
626 627 628 629 630 631 632
	if (!icon_is_positioned (icon)) {
		set_pending_icon_to_reveal (container, icon);
		return;
	}
	
	set_pending_icon_to_reveal (container, NULL);

633
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
634 635
	allocation = &GTK_WIDGET (container)->allocation;

636 637
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
638

639
	item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), &bounds);
640
	if (bounds.y0 < vadj->value) {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
641
		eel_gtk_adjustment_set_value (vadj, bounds.y0);
642
	} else if (bounds.y1 > vadj->value + allocation->height) {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
643
		eel_gtk_adjustment_set_value
644
			(vadj, bounds.y1 - allocation->height);
645
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
646

647
	if (bounds.x0 < hadj->value) {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
648
		eel_gtk_adjustment_set_value (hadj, bounds.x0);
649
	} else if (bounds.x1 > hadj->value + allocation->width) {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
650
		eel_gtk_adjustment_set_value
651
			(hadj, bounds.x1 - allocation->width);
652
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
653 654
}

Mike Engber's avatar
 
Mike Engber committed
655 656 657 658 659 660 661 662 663 664 665 666
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);
	}
}

667
static gboolean
668
keyboard_icon_reveal_timeout_callback (gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
669
{
670 671
	NautilusIconContainer *container;
	NautilusIcon *icon;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
672

673
	container = NAUTILUS_ICON_CONTAINER (data);
674 675 676
	icon = container->details->keyboard_icon_to_reveal;

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

Darin Adler's avatar
Darin Adler committed
678
	/* Only reveal the icon if it's still the keyboard focus or if
679 680 681 682
	 * 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 
683
	 * (see bugzilla.gnome.org 40612).
684 685 686 687
	 */
	if (icon == container->details->keyboard_focus
	    || icon->is_selected) {
		reveal_icon (container, icon);
688
	}
689
	container->details->keyboard_icon_reveal_timer_id = 0;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
690 691 692 693 694

	return FALSE;
}

static void
695
unschedule_keyboard_icon_reveal (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
696
{
697
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
698

699
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
700

701
	if (details->keyboard_icon_reveal_timer_id != 0) {
702
		g_source_remove (details->keyboard_icon_reveal_timer_id);
703
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
704 705 706
}

static void
707 708
schedule_keyboard_icon_reveal (NautilusIconContainer *container,
			       NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
709
{
710
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
711

712
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
713

714
	unschedule_keyboard_icon_reveal (container);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
715

716 717
	details->keyboard_icon_to_reveal = icon;
	details->keyboard_icon_reveal_timer_id
718 719 720
		= g_timeout_add (KEYBOARD_ICON_REVEAL_TIMEOUT,
				 keyboard_icon_reveal_timeout_callback,
				 container);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
721 722 723
}

static void
724
clear_keyboard_focus (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
725
{
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 730 731
				       NULL);
	}
	
732 733 734
	container->details->keyboard_focus = NULL;
}

Darin Adler's avatar
Darin Adler committed
735
/* Set @icon as the icon currently selected for keyboard operations. */
736
static void
737 738
set_keyboard_focus (NautilusIconContainer *container,
		    NautilusIcon *icon)
739 740 741 742 743
{
	g_assert (icon != NULL);

	if (icon == container->details->keyboard_focus) {
		return;
744
	}
745 746 747 748 749

	clear_keyboard_focus (container);

	container->details->keyboard_focus = icon;

750
	eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
751 752
			       "highlighted_as_keyboard_focus", 1,
			       NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
753 754
}

755 756 757 758 759 760 761 762 763 764 765 766 767
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;
}

768 769 770 771 772
static void
get_all_icon_bounds (NautilusIconContainer *container,
		     double *x1, double *y1,
		     double *x2, double *y2)
{
773
	/* FIXME bugzilla.gnome.org 42477: Do we have to do something about the rubberband
774 775
	 * here? Any other non-icon items?
	 */
776 777
	eel_canvas_item_get_bounds
		(EEL_CANVAS (container)->root,
778 779
		 x1, y1, x2, y2);
}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
780

781 782 783 784 785 786 787 788
/* 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;
}

789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815
/* 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);
}

816 817
void
nautilus_icon_container_update_scroll_region (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
818
{
819
	double x1, y1, x2, y2;
820
	double pixels_per_unit;
821 822
	GtkAdjustment *hadj, *vadj;
	float step_increment;
823
	GtkAllocation *allocation;
824
	gboolean reset_scroll_region;
825 826

	if (nautilus_icon_container_get_is_fixed_size (container)) {
827
		pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
828
		
829
		/* Set the scroll region to the size of the container allocation */
830