nautilus-icon-container.c 219 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>
44
#include <eel/eel-editable-label.h>
Darin Adler's avatar
Darin Adler committed
45
#include <eel/eel-marshal.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
46
#include <eel/eel-string.h>
47
#include <eel/eel-canvas-rect-ellipse.h>
48
#include <libgnomeui/gnome-icon-item.h>
49
#include <gdk/gdkkeysyms.h>
50
#include <gtk/gtkaccessible.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
51
#include <gtk/gtklayout.h>
52 53
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
54
#include <glib/gi18n.h>
55
#include <libgnome/gnome-macros.h>
56 57
#include <stdio.h>
#include <string.h>
58

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

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

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

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

73 74
#define CONTEXT_MENU_TIMEOUT_INTERVAL 500

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

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

87
/* Maximum size (pixels) allowed for icons at the standard zoom level. */
88
#define MINIMUM_IMAGE_SIZE 24
89 90
#define MAXIMUM_IMAGE_SIZE 96
#define MAXIMUM_EMBLEM_SIZE 48
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 void          nautilus_icon_container_update_visible_icons   (NautilusIconContainer *container);
207 208
static void          reveal_icon                                    (NautilusIconContainer *container,
								     NautilusIcon *icon);
209

210

211
static gpointer accessible_parent_class;
212 213 214 215 216

static GQuark accessible_private_data_quark = 0;

static const char *nautilus_icon_container_accessible_action_names[] = {
	"activate",
217
	"menu",
218 219 220 221 222
	NULL
};

static const char *nautilus_icon_container_accessible_action_descriptions[] = {
	"Activate selected items",
223
	"Popup context menu",
224 225 226
	NULL
};

227
GNOME_CLASS_BOILERPLATE (NautilusIconContainer, nautilus_icon_container,
228
			 EelCanvas, EEL_TYPE_CANVAS)
229

230
/* The NautilusIconContainer signals.  */
231
enum {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
232
	ACTIVATE,
233
	ACTIVATE_ALTERNATE,
234 235
	BAND_SELECT_STARTED,
	BAND_SELECT_ENDED,
236
	BUTTON_PRESS,
237
	CAN_ACCEPT_ITEM,
238
	CONTEXT_CLICK_BACKGROUND,
239
	CONTEXT_CLICK_SELECTION,
240
	MIDDLE_CLICK,
241
	GET_CONTAINER_URI,
242
	GET_ICON_URI,
243
	GET_ICON_DROP_TARGET_URI,
244 245
	GET_STORED_ICON_POSITION,
	ICON_POSITION_CHANGED,
246
	ICON_TEXT_CHANGED,
247 248
	ICON_STRETCH_STARTED,
	ICON_STRETCH_ENDED,
249
	RENAMING_ICON,
250
	LAYOUT_CHANGED,
Pavel Cisler's avatar
Pavel Cisler committed
251
	MOVE_COPY_ITEMS,
252
	HANDLE_NETSCAPE_URL,
253
	HANDLE_URI_LIST,
254
	HANDLE_TEXT,
255
	PREVIEW,
256 257 258 259
	SELECTION_CHANGED,
	ICON_ADDED,
	ICON_REMOVED,
	CLEARED,
260
	START_INTERACTIVE_SEARCH,
Ettore Perazzoli's avatar
Ettore Perazzoli committed
261 262
	LAST_SIGNAL
};
263 264 265 266 267 268 269 270 271

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

272
static guint signals[LAST_SIGNAL];
Ettore Perazzoli's avatar
Ettore Perazzoli committed
273

274
/* Functions dealing with NautilusIcons.  */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
275 276

static void
277
icon_free (NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
278
{
279
	/* Destroy this canvas item; the parent will unref it. */
280
	gtk_object_destroy (GTK_OBJECT (icon->item));
281
	g_free (icon);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
282 283
}

Mike Engber's avatar
 
Mike Engber committed
284 285 286 287 288 289
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
290
static void
291
icon_set_position (NautilusIcon *icon,
292
		   double x, double y)
293 294
{	
	NautilusIconContainer *container;
295
	double pixels_per_unit;	
296 297 298
	int left, top, right, bottom;
	int width, height;
	int x1, y1, x2, y2;
299 300
	int container_x, container_y, container_width, container_height;

301
	if (icon->x == x && icon->y == y) {
302
		return;
303
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
304

305
	container = NAUTILUS_ICON_CONTAINER (EEL_CANVAS_ITEM (icon->item)->canvas);
306

307 308 309 310
	if (icon == get_icon_being_renamed (container)) {
		end_renaming_mode (container, TRUE);
	}

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

		icon_get_bounding_box (icon, &x1, &y1, &x2, &y2);
		width = x2 - x1;
		height = y2 - y1;
339
				
340
		if (x > right - DESKTOP_ICON_SAFETY_PAD) {
341
			x = right - DESKTOP_ICON_SAFETY_PAD;
342
		}
343
		
344 345
		if (x < left) {
			x = left;
346
		}
347
		if (y > bottom - DESKTOP_ICON_SAFETY_PAD) {
348
			y = bottom - DESKTOP_ICON_SAFETY_PAD;
349
		}
350 351
		if (y < top) {
			y = top;
352
		}		
353 354
	}

355 356 357 358 359 360
	if (icon->x == ICON_UNPOSITIONED_VALUE) {
		icon->x = 0;
	}
	if (icon->y == ICON_UNPOSITIONED_VALUE) {
		icon->y = 0;
	}
361
	
362
	eel_canvas_item_move (EEL_CANVAS_ITEM (icon->item),
363 364
				x - icon->x,
				y - icon->y);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
365 366 367 368 369

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

370
static void
371 372
icon_get_size (NautilusIconContainer *container,
	       NautilusIcon *icon,
373
	       guint *size)
374
{
375 376
	if (size != NULL) {
		*size = MAX (nautilus_get_icon_size_for_zoom_level (container->details->zoom_level)
377
			       * icon->scale, NAUTILUS_ICON_SIZE_SMALLEST);
378
	}
379 380
}

Darin Adler's avatar
Darin Adler committed
381 382 383 384 385
/* 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.
 */
386
static void
387 388
icon_set_size (NautilusIconContainer *container,
	       NautilusIcon *icon,
389
	       guint icon_size,
390
	       gboolean snap,
391
	       gboolean update_position)
392
{
393
	guint old_size;
394 395
	double scale;

396 397
	icon_get_size (container, icon, &old_size);
	if (icon_size == old_size) {
398
		return;
399
	}
400

401
	scale = (double) icon_size /
402 403
		nautilus_get_icon_size_for_zoom_level
		(container->details->zoom_level);
404
	nautilus_icon_container_move_icon (container, icon,
Darin Adler's avatar
Darin Adler committed
405
					   icon->x, icon->y,
406
					   scale, FALSE,
407
					   snap, update_position);
408 409
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
410
static void
411
icon_raise (NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
412
{
413
	EelCanvasItem *item, *band;
414
	
415
	item = EEL_CANVAS_ITEM (icon->item);
416
	band = NAUTILUS_ICON_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
417
	
418
	eel_canvas_item_send_behind (item, band);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
419 420
}

421 422 423
static void
emit_stretch_started (NautilusIconContainer *container, NautilusIcon *icon)
{
424
	g_signal_emit (container,
425
			 signals[ICON_STRETCH_STARTED], 0,
426 427 428 429 430 431
			 icon->data);
}

static void
emit_stretch_ended (NautilusIconContainer *container, NautilusIcon *icon)
{
432
	g_signal_emit (container,
433
			 signals[ICON_STRETCH_ENDED], 0,
434 435 436
			 icon->data);
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
437
static void
438 439
icon_toggle_selected (NautilusIconContainer *container,
		      NautilusIcon *icon)
440
{		
441
	end_renaming_mode (container, TRUE);
442

443
	icon->is_selected = !icon->is_selected;
444
	eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
445 446
			     "highlighted_for_selection", (gboolean) icon->is_selected,
			     NULL);
447 448 449 450 451 452

	/* 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;
453
		nautilus_icon_canvas_item_set_show_stretch_handles (icon->item, FALSE);
454 455 456 457 458
		/* snap the icon if necessary */
		if (container->details->keep_aligned) {
			nautilus_icon_container_move_icon (container,
							   icon,
							   icon->x, icon->y,
459
							   icon->scale,
460 461 462
							   FALSE, TRUE, TRUE);
		}
		
463
		emit_stretch_ended (container, icon);
464
	}
465 466 467 468 469

	/* 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
470 471
}

472
/* Select an icon. Return TRUE if selection has changed. */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
473
static gboolean
474 475
icon_set_selected (NautilusIconContainer *container,
		   NautilusIcon *icon,
476
		   gboolean select)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
477
{
478 479 480 481
	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
482
		return FALSE;
483
	}
484

485
	icon_toggle_selected (container, icon);
486
	g_assert (select == icon->is_selected);
487
	return TRUE;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
488 489 490
}

static void
491
icon_get_bounding_box (NautilusIcon *icon,
492 493
		       int *x1_return, int *y1_return,
		       int *x2_return, int *y2_return)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
494 495 496
{
	double x1, y1, x2, y2;

497
	eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
Ettore Perazzoli's avatar
Ettore Perazzoli committed
498 499 500 501 502 503 504 505
				      &x1, &y1, &x2, &y2);

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

506
/* Utility functions for NautilusIconContainer.  */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
507

508
gboolean
Pavel Cisler's avatar
Pavel Cisler committed
509 510
nautilus_icon_container_scroll (NautilusIconContainer *container,
				int delta_x, int delta_y)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
511 512
{
	GtkAdjustment *hadj, *vadj;
513
	int old_h_value, old_v_value;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
514

515 516
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
517

518 519 520 521 522 523 524 525
	/* 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
526 527
	eel_gtk_adjustment_set_value (hadj, hadj->value + delta_x);
	eel_gtk_adjustment_set_value (vadj, vadj->value + delta_y);
528 529 530

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

Mike Engber's avatar
 
Mike Engber committed
533
static void
534 535
pending_icon_to_reveal_destroy_callback (NautilusIconCanvasItem *item,
					 NautilusIconContainer *container)
Mike Engber's avatar
 
Mike Engber committed
536
{
537
	g_assert (NAUTILUS_IS_ICON_CONTAINER (container));
Mike Engber's avatar
 
Mike Engber committed
538 539
	g_assert (container->details->pending_icon_to_reveal != NULL);
	g_assert (container->details->pending_icon_to_reveal->item == item);
540

Mike Engber's avatar
 
Mike Engber committed
541 542 543 544 545 546 547 548 549 550 551 552
	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)
{
553
	NautilusIcon *old_icon;
Mike Engber's avatar
 
Mike Engber committed
554
	
555
	old_icon = container->details->pending_icon_to_reveal;
Mike Engber's avatar
 
Mike Engber committed
556
	
557
	if (icon == old_icon) {
Mike Engber's avatar
 
Mike Engber committed
558 559 560
		return;
	}
	
561 562 563 564 565
	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
566 567 568
	}
	
	if (icon != NULL) {
569
		g_signal_connect (icon->item, "destroy",
570 571
				  G_CALLBACK (pending_icon_to_reveal_destroy_callback),
				  container);
Mike Engber's avatar
 
Mike Engber committed
572 573 574 575 576
	}
	
	container->details->pending_icon_to_reveal = icon;
}

577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
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
605
static void
606 607
reveal_icon (NautilusIconContainer *container,
	     NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
608
{
609
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
610 611
	GtkAllocation *allocation;
	GtkAdjustment *hadj, *vadj;
612
	ArtIRect bounds;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
613

Mike Engber's avatar
 
Mike Engber committed
614 615 616 617 618 619 620
	if (!icon_is_positioned (icon)) {
		set_pending_icon_to_reveal (container, icon);
		return;
	}
	
	set_pending_icon_to_reveal (container, NULL);

621
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
622 623
	allocation = &GTK_WIDGET (container)->allocation;

624 625
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
626

627
	item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), &bounds);
628
	if (bounds.y0 < vadj->value) {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
629
		eel_gtk_adjustment_set_value (vadj, bounds.y0);
630
	} else if (bounds.y1 > vadj->value + allocation->height) {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
631
		eel_gtk_adjustment_set_value
632
			(vadj, bounds.y1 - allocation->height);
633
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
634

635
	if (bounds.x0 < hadj->value) {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
636
		eel_gtk_adjustment_set_value (hadj, bounds.x0);
637
	} else if (bounds.x1 > hadj->value + allocation->width) {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
638
		eel_gtk_adjustment_set_value
639
			(hadj, bounds.x1 - allocation->width);
640
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
641 642
}

Mike Engber's avatar
 
Mike Engber committed
643 644 645 646 647 648 649 650 651 652 653 654
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);
	}
}

655
static gboolean
656
keyboard_icon_reveal_timeout_callback (gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
657
{
658 659
	NautilusIconContainer *container;
	NautilusIcon *icon;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
660

661
	container = NAUTILUS_ICON_CONTAINER (data);
662 663 664
	icon = container->details->keyboard_icon_to_reveal;

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

Darin Adler's avatar
Darin Adler committed
666
	/* Only reveal the icon if it's still the keyboard focus or if
667 668 669 670
	 * 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 
671
	 * (see bugzilla.gnome.org 40612).
672 673 674 675
	 */
	if (icon == container->details->keyboard_focus
	    || icon->is_selected) {
		reveal_icon (container, icon);
676
	}
677
	container->details->keyboard_icon_reveal_timer_id = 0;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
678 679 680 681 682

	return FALSE;
}

static void
683
unschedule_keyboard_icon_reveal (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
684
{
685
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
686

687
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
688

689
	if (details->keyboard_icon_reveal_timer_id != 0) {
690
		g_source_remove (details->keyboard_icon_reveal_timer_id);
691
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
692 693 694
}

static void
695 696
schedule_keyboard_icon_reveal (NautilusIconContainer *container,
			       NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
697
{
698
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
699

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

702
	unschedule_keyboard_icon_reveal (container);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
703

704 705
	details->keyboard_icon_to_reveal = icon;
	details->keyboard_icon_reveal_timer_id
706 707 708
		= g_timeout_add (KEYBOARD_ICON_REVEAL_TIMEOUT,
				 keyboard_icon_reveal_timeout_callback,
				 container);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
709 710 711
}

static void
712
clear_keyboard_focus (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
713
{
714
        if (container->details->keyboard_focus != NULL) {
715
		eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
716
				       "highlighted_as_keyboard_focus", 0,
717 718 719
				       NULL);
	}
	
720 721 722
	container->details->keyboard_focus = NULL;
}

Darin Adler's avatar
Darin Adler committed
723
/* Set @icon as the icon currently selected for keyboard operations. */
724
static void
725 726
set_keyboard_focus (NautilusIconContainer *container,
		    NautilusIcon *icon)
727 728 729 730 731
{
	g_assert (icon != NULL);

	if (icon == container->details->keyboard_focus) {
		return;
732
	}
733 734 735 736 737

	clear_keyboard_focus (container);

	container->details->keyboard_focus = icon;

738
	eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
739 740
			       "highlighted_as_keyboard_focus", 1,
			       NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
741 742
}

743 744 745 746 747 748 749 750 751 752 753 754 755
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;
}

756 757 758 759 760
static void
get_all_icon_bounds (NautilusIconContainer *container,
		     double *x1, double *y1,
		     double *x2, double *y2)
{
761
	/* FIXME bugzilla.gnome.org 42477: Do we have to do something about the rubberband
762 763
	 * here? Any other non-icon items?
	 */
764 765
	eel_canvas_item_get_bounds
		(EEL_CANVAS (container)->root,
766 767
		 x1, y1, x2, y2);
}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
768

769 770 771 772 773 774 775 776
/* 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;
}

777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803
/* 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);
}

804 805
void
nautilus_icon_container_update_scroll_region (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
806
{
807
	double x1, y1, x2, y2;
808
	double pixels_per_unit;
809 810
	GtkAdjustment *hadj, *vadj;
	float step_increment;
811
	GtkAllocation *allocation;
812
	gboolean reset_scroll_region;
813 814

	if (nautilus_icon_container_get_is_fixed_size (container)) {
815
		pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
816
		
817
		/* Set the scroll region to the size of the container allocation */
818 819 820
		allocation = &GTK_WIDGET (container)->allocation;
		eel_canvas_set_scroll_region
			(EEL_CANVAS (container),
821 822 823
			 (double) - container->details->left_margin / pixels_per_unit,
			 (double) - container->details->top_margin / pixels_per_unit,
			 ((double) (allocation->width - 1)
824
			 - container->details->left_margin
825 826 827
			 - container->details->right_margin)
			 / pixels_per_unit,
			 ((double) (allocation->height - 1)
828
			 - container->details->top_margin
829 830
			 - container->details->bottom_margin)
			 / pixels_per_unit);
831