nautilus-icon-container.c 207 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 <libgnome/gnome-i18n.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
/* From nautilus-icon-canvas-item.c */
#define MAX_TEXT_WIDTH_BESIDE 90

127
128
#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)
129
130
131
132
133
134

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

136
137
138
/* Copied from NautilusIconContainer */
#define NAUTILUS_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT 5000

139
140
enum {
	ACTION_ACTIVATE,
141
	ACTION_MENU,
142
143
144
145
146
147
148
149
150
151
	LAST_ACTION
};

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

static GType         nautilus_icon_container_accessible_get_type (void);

152
static void          activate_selected_items                        (NautilusIconContainer *container);
153
154
static void          activate_selected_items_alternate              (NautilusIconContainer *container,
								     NautilusIcon          *icon);
155
156
157
158
159
160
161
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);
162
static gboolean      all_selected                                   (NautilusIconContainer *container);
Alexander Larsson's avatar
Alexander Larsson committed
163
static gboolean      has_selection                                  (NautilusIconContainer *container);
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
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,
								     gconstpointer          client);
187
188
189
static void          handle_vadjustment_changed                     (GtkAdjustment         *adjustment,
								     NautilusIconContainer *container);
static void          nautilus_icon_container_prioritize_thumbnailing_for_visible_icons (NautilusIconContainer *container);
190
191
static void          reveal_icon                                    (NautilusIconContainer *container,
								     NautilusIcon *icon);
192

193

194
static gpointer accessible_parent_class;
195
196
197
198
199

static GQuark accessible_private_data_quark = 0;

static const char *nautilus_icon_container_accessible_action_names[] = {
	"activate",
200
	"menu",
201
202
203
204
205
	NULL
};

static const char *nautilus_icon_container_accessible_action_descriptions[] = {
	"Activate selected items",
206
	"Popup context menu",
207
208
209
	NULL
};

210
GNOME_CLASS_BOILERPLATE (NautilusIconContainer, nautilus_icon_container,
211
			 EelCanvas, EEL_TYPE_CANVAS)
212

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

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

255
static guint signals[LAST_SIGNAL];
Ettore Perazzoli's avatar
Ettore Perazzoli committed
256

257
/* Functions dealing with NautilusIcons.  */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
258
259

static void
260
icon_free (NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
261
{
262
	/* Destroy this canvas item; the parent will unref it. */
263
	gtk_object_destroy (GTK_OBJECT (icon->item));
264
	g_free (icon);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
265
266
}

Mike Engber's avatar
   
Mike Engber committed
267
268
269
270
271
272
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
273
static void
274
icon_set_position (NautilusIcon *icon,
275
		   double x, double y)
276
277
{	
	NautilusIconContainer *container;
278
	double pixels_per_unit;	
279
280
281
	int left, top, right, bottom;
	int width, height;
	int x1, y1, x2, y2;
282
283
	int container_x, container_y, container_width, container_height;

284
	if (icon->x == x && icon->y == y) {
285
		return;
286
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
287

288
	container = NAUTILUS_ICON_CONTAINER (EEL_CANVAS_ITEM (icon->item)->canvas);
289

290
291
292
293
	if (icon == get_icon_being_renamed (container)) {
		end_renaming_mode (container, TRUE);
	}

294
	if (nautilus_icon_container_get_is_fixed_size (container)) {
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
		/*  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 ();
312
		pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
313
		/* Clip the position of the icon within our desktop bounds */
314
315
316
317
		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;
318
319
320
321

		icon_get_bounding_box (icon, &x1, &y1, &x2, &y2);
		width = x2 - x1;
		height = y2 - y1;
322
				
323
		if (x > right - DESKTOP_ICON_SAFETY_PAD) {
324
			x = right - DESKTOP_ICON_SAFETY_PAD;
325
		}
326
		
327
328
		if (x < left) {
			x = left;
329
		}
330
		if (y > bottom - DESKTOP_ICON_SAFETY_PAD) {
331
			y = bottom - DESKTOP_ICON_SAFETY_PAD;
332
		}
333
334
		if (y < top) {
			y = top;
335
		}		
336
337
	}

338
339
340
341
342
343
344
	if (icon->x == ICON_UNPOSITIONED_VALUE) {
		icon->x = 0;
	}
	if (icon->y == ICON_UNPOSITIONED_VALUE) {
		icon->y = 0;
	}
	
345
	eel_canvas_item_move (EEL_CANVAS_ITEM (icon->item),
346
347
				x - icon->x,
				y - icon->y);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
348
349
350
351
352

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

353
static void
354
355
icon_get_size (NautilusIconContainer *container,
	       NautilusIcon *icon,
356
	       guint *size)
357
{
358
359
360
361
362
	g_assert (fabs (icon->scale_x - icon->scale_y) <= 0.001);
		
	/* ALEX TODO: Bogus. Should only have one scale, not _x and _y */
	if (size != NULL) {
		*size = MAX (nautilus_get_icon_size_for_zoom_level (container->details->zoom_level)
363
364
			       * icon->scale_x, NAUTILUS_ICON_SIZE_SMALLEST);
	}
365
366
}

Darin Adler's avatar
Darin Adler committed
367
368
369
370
371
/* 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.
 */
372
static void
373
374
icon_set_size (NautilusIconContainer *container,
	       NautilusIcon *icon,
375
	       guint icon_size,
376
	       gboolean snap,
377
	       gboolean update_position)
378
{
379
	guint old_size;
380
381
	double scale;

382
383
	icon_get_size (container, icon, &old_size);
	if (icon_size == old_size) {
384
		return;
385
	}
386

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

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

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

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

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

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

	/* 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;
439
		nautilus_icon_canvas_item_set_show_stretch_handles (icon->item, FALSE);
440
441
442
443
444
445
446
447
448
		/* snap the icon if necessary */
		if (container->details->keep_aligned) {
			nautilus_icon_container_move_icon (container,
							   icon,
							   icon->x, icon->y,
							   icon->scale_x, icon->scale_y,
							   FALSE, TRUE, TRUE);
		}
		
449
		emit_stretch_ended (container, icon);
450
	}
451
452
453
454
455

	/* 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
456
457
}

458
/* Select an icon. Return TRUE if selection has changed. */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
459
static gboolean
460
461
icon_set_selected (NautilusIconContainer *container,
		   NautilusIcon *icon,
462
		   gboolean select)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
463
{
464
465
466
467
	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
468
		return FALSE;
469
	}
470

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

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

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

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

492
/* Utility functions for NautilusIconContainer.  */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
493

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

501
502
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
503

504
505
506
507
508
509
510
511
	/* 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
512
513
	eel_gtk_adjustment_set_value (hadj, hadj->value + delta_x);
	eel_gtk_adjustment_set_value (vadj, vadj->value + delta_y);
514
515
516

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

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

Mike Engber's avatar
   
Mike Engber committed
527
528
529
530
531
532
533
534
535
536
537
538
	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)
{
539
	NautilusIcon *old_icon;
Mike Engber's avatar
   
Mike Engber committed
540
	
541
	old_icon = container->details->pending_icon_to_reveal;
Mike Engber's avatar
   
Mike Engber committed
542
	
543
	if (icon == old_icon) {
Mike Engber's avatar
   
Mike Engber committed
544
545
546
		return;
	}
	
547
548
549
550
551
	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
552
553
554
	}
	
	if (icon != NULL) {
555
		g_signal_connect (icon->item, "destroy",
556
557
				  G_CALLBACK (pending_icon_to_reveal_destroy_callback),
				  container);
Mike Engber's avatar
   
Mike Engber committed
558
559
560
561
562
	}
	
	container->details->pending_icon_to_reveal = icon;
}

563
564
565
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
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
591
static void
592
593
reveal_icon (NautilusIconContainer *container,
	     NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
594
{
595
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
596
597
	GtkAllocation *allocation;
	GtkAdjustment *hadj, *vadj;
598
	ArtIRect bounds;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
599

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

607
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
608
609
	allocation = &GTK_WIDGET (container)->allocation;

610
611
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
612

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

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

Mike Engber's avatar
   
Mike Engber committed
629
630
631
632
633
634
635
636
637
638
639
640
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);
	}
}

641
static gboolean
642
keyboard_icon_reveal_timeout_callback (gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
643
{
644
645
	NautilusIconContainer *container;
	NautilusIcon *icon;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
646

647
	container = NAUTILUS_ICON_CONTAINER (data);
648
649
650
	icon = container->details->keyboard_icon_to_reveal;

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

Darin Adler's avatar
Darin Adler committed
652
	/* Only reveal the icon if it's still the keyboard focus or if
653
654
655
656
	 * 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 
657
	 * (see bugzilla.gnome.org 40612).
658
659
660
661
	 */
	if (icon == container->details->keyboard_focus
	    || icon->is_selected) {
		reveal_icon (container, icon);
662
	}
663
	container->details->keyboard_icon_reveal_timer_id = 0;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
664
665
666
667
668

	return FALSE;
}

static void
669
unschedule_keyboard_icon_reveal (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
670
{
671
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
672

673
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
674

675
	if (details->keyboard_icon_reveal_timer_id != 0) {
676
		g_source_remove (details->keyboard_icon_reveal_timer_id);
677
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
678
679
680
}

static void
681
682
schedule_keyboard_icon_reveal (NautilusIconContainer *container,
			       NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
683
{
684
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
685

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

688
	unschedule_keyboard_icon_reveal (container);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
689

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

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

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

	if (icon == container->details->keyboard_focus) {
		return;
718
	}
719
720
721
722
723

	clear_keyboard_focus (container);

	container->details->keyboard_focus = icon;

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

729
730
731
732
733
734
735
736
737
738
739
740
741
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;
}

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

755
756
757
758
759
760
761
762
/* 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;
}

763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
/* 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);
}

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

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

818
	reset_scroll_region = container->details->reset_scroll_region_trigger
819
820
		|| nautilus_icon_container_is_empty (container)
		|| nautilus_icon_container_is_auto_layout (container);
821
822
823
824
825
826
827
828
829
830
		
	/* The trigger is only cleared when container is non-empty, so
	 * callers can reliably reset the scroll region when an item
	 * is added even if extraneous relayouts are called when the
	 * window is still empty.
	 */
	if (!nautilus_icon_container_is_empty (container)) {
		container->details->reset_scroll_region_trigger = FALSE;
	}

831
832
833
834
835
836
837
838
839
840
	get_all_icon_bounds (container, &x1, &y1, &x2, &y2);	

	/* Auto-layout assumes a 0, 0 scroll origin */
	if (nautilus_icon_container_is_auto_layout (container)) {
		x1 = 0;
		y1 = 0;
	} else {
		x1 -= CONTAINER_PAD_LEFT;
		y1 -= CONTAINER_PAD_TOP;
	}
841

842
843
	y2 += CONTAINER_PAD_BOTTOM;

844
	if (reset_scroll_region) {
845
846
		eel_canvas_set_scroll_region
			(EEL_CANVAS (container),
847
			 x1, y1, x2, y2);
848
	} else {
849
850
		canvas_set_scroll_region_include_visible_area
			(EEL_CANVAS (container),
851
			 x1, y1, x2, y2);
852
	}
853

854
855
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
856

857
	/* Scroll by 1/4 icon each time you click. */
858
859
860
861
862
863
864
865
866
867
	step_increment = nautilus_get_icon_size_for_zoom_level
		(container->details->zoom_level) / 4;
	if (hadj->step_increment != step_increment) {
		hadj->step_increment = step_increment;
		gtk_adjustment_changed (hadj);
	}
	if (vadj->step_increment != step_increment) {
		vadj->step_increment = step_increment;
		gtk_adjustment_changed (vadj);
	}
Darin Adler's avatar
Darin Adler committed
868

869
	/* Now that we have a new scroll region, clamp the
870
         * adjustments so we are within the valid scroll area.
871
	 */
Ramiro Estrugo's avatar
Ramiro Estrugo committed
872
873
	eel_gtk_adjustment_clamp_value (hadj);
	eel_gtk_adjustment_clamp_value (vadj);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
874
875
}

876
static int
877
compare_icons (gconstpointer a, gconstpointer b, gpointer icon_container)
878
{
879
	NautilusIconContainerClass *klass;
880
881
882
883
	const NautilusIcon *icon_a, *icon_b;

	icon_a = a;
	icon_b = b;
884
	klass  = NAUTILUS_ICON_CONTAINER_GET_CLASS (icon_container);
885

886
	return klass->compare_icons (icon_container, icon_a->data, icon_b->data);
887
888
}

889
static void
890
sort_icons (NautilusIconContainer *container,
891
	    GList                **icons)
892
{
893
894
895
896
897
	NautilusIconContainerClass *klass;

	klass = NAUTILUS_ICON_CONTAINER_GET_CLASS (container);
	g_return_if_fail (klass->compare_icons != NULL);

898
	*icons = g_list_sort_with_data (*icons, compare_icons, container);
899
900
}

901
902
903
904
static void
resort (NautilusIconContainer *container)
{
	sort_icons (container, &container->details->icons);
905
906
}

907
#if 0
908
909
static double
get_grid_width (NautilusIconContainer *container)
Darin Adler's avatar
Darin Adler committed
910
{
Dave Camp's avatar
Dave Camp committed
911
912
913
914
915
	if (container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) {
		return TEXT_BESIDE_ICON_GRID_WIDTH;
	} else {
		return STANDARD_ICON_GRID_WIDTH;
	}
Darin Adler's avatar
Darin Adler committed
916
}
917
#endif
918
919
typedef struct {
	double width;
920
	double height;
921
922
923
924
	double x_offset;
	double y_offset;
} IconPositions;

925
static void
Darin Adler's avatar
Darin Adler committed
926
927
928
lay_down_one_line (NautilusIconContainer *container,
		   GList *line_start,
		   GList *line_end,
929
		   double y,
930
		   double max_height,
931
		   GArray *positions)
932
933
{
	GList *p;
Darin Adler's avatar
Darin Adler committed
934
	NautilusIcon *icon;
935
	double x, y_offset;
936
937
	IconPositions *position;
	int i;
Darin Adler's avatar
Darin Adler committed
938

939
	/* FIXME: Should layout differently when in RTL base mode */
Darin Adler's avatar
Darin Adler committed
940
941

	/* Lay out the icons along the baseline. */
942
	x = ICON_PAD_LEFT;
943
	i = 0;
Darin Adler's avatar
Darin Adler committed
944
945
946
	for (p = line_start; p != line_end; p = p->next) {
		icon = p->data;

947
		position = &g_array_index (positions, IconPositions, i++);
948
949
950
951
952
953
		
		if (container->details->label_position == NAUTILUS_ICON_LABEL_POSITION_BESIDE) {
			y_offset = (max_height - position->height) / 2;
		} else {
			y_offset = position->y_offset;
		}
Darin Adler's avatar
Darin Adler committed
954
955
956

		icon_set_position
			(icon,
957
			 x + position->x_offset,
958
			 y + y_offset);
Darin Adler's avatar
Darin Adler committed
959

960
		x += position->width;
Darin Adler's avatar
Darin Adler committed
961
962
963
	}
}

964
965
static void
lay_down_icons_horizontal (NautilusIconContainer *container,
966
967
			   GList *icons,
			   double start_y)
Darin Adler's avatar
Darin Adler committed
968
969
{
	GList *p, *line_start;
970
	NautilusIcon *icon;