nautilus-icon-container.c 195 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
enum {
	ACTION_ACTIVATE,
138
	ACTION_MENU,
139
140
141
142
143
144
145
146
147
148
	LAST_ACTION
};

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

static GType         nautilus_icon_container_accessible_get_type (void);

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

190

191
static gpointer accessible_parent_class;
192
193
194
195
196

static GQuark accessible_private_data_quark = 0;

static const char *nautilus_icon_container_accessible_action_names[] = {
	"activate",
197
	"menu",
198
199
200
201
202
	NULL
};

static const char *nautilus_icon_container_accessible_action_descriptions[] = {
	"Activate selected items",
203
	"Popup context menu",
204
205
206
	NULL
};

207
GNOME_CLASS_BOILERPLATE (NautilusIconContainer, nautilus_icon_container,
208
			 EelCanvas, EEL_TYPE_CANVAS)
209

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

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

251
static guint signals[LAST_SIGNAL];
Ettore Perazzoli's avatar
Ettore Perazzoli committed
252

253
/* Functions dealing with NautilusIcons.  */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
254
255

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

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

280
	if (icon->x == x && icon->y == y) {
281
		return;
282
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
283

284
	container = NAUTILUS_ICON_CONTAINER (EEL_CANVAS_ITEM (icon->item)->canvas);
285

286
287
288
289
	if (icon == get_icon_being_renamed (container)) {
		end_renaming_mode (container, TRUE);
	}

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

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

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

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

349
static void
350
351
icon_get_size (NautilusIconContainer *container,
	       NautilusIcon *icon,
352
	       guint *size)
353
{
354
355
356
357
358
	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)
359
360
			       * icon->scale_x, NAUTILUS_ICON_SIZE_SMALLEST);
	}
361
362
}

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

378
379
	icon_get_size (container, icon, &old_size);
	if (icon_size == old_size) {
380
		return;
381
	}
382

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

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

403
404
405
static void
emit_stretch_started (NautilusIconContainer *container, NautilusIcon *icon)
{
406
	g_signal_emit (container,
407
			 signals[ICON_STRETCH_STARTED], 0,
408
409
410
411
412
413
			 icon->data);
}

static void
emit_stretch_ended (NautilusIconContainer *container, NautilusIcon *icon)
{
414
	g_signal_emit (container,
415
			 signals[ICON_STRETCH_ENDED], 0,
416
417
418
			 icon->data);
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
419
static void
420
421
icon_toggle_selected (NautilusIconContainer *container,
		      NautilusIcon *icon)
422
{		
423
	end_renaming_mode (container, TRUE);
424

425
	icon->is_selected = !icon->is_selected;
426
	eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
427
428
			     "highlighted_for_selection", (gboolean) icon->is_selected,
			     NULL);
429
430
431
432
433
434

	/* 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;
435
		nautilus_icon_canvas_item_set_show_stretch_handles (icon->item, FALSE);
436
437
438
439
440
441
442
443
444
		/* 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);
		}
		
445
		emit_stretch_ended (container, icon);
446
	}
447
448
449
450
451

	/* 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
452
453
}

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

467
	icon_toggle_selected (container, icon);
468
	g_assert (select == icon->is_selected);
469
	return TRUE;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
470
471
472
}

static void
473
icon_get_bounding_box (NautilusIcon *icon,
474
475
		       int *x1_return, int *y1_return,
		       int *x2_return, int *y2_return)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
476
477
478
{
	double x1, y1, x2, y2;

479
	eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
Ettore Perazzoli's avatar
Ettore Perazzoli committed
480
481
482
483
484
485
486
487
				      &x1, &y1, &x2, &y2);

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

488
/* Utility functions for NautilusIconContainer.  */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
489

490
gboolean
Pavel Cisler's avatar
Pavel Cisler committed
491
492
nautilus_icon_container_scroll (NautilusIconContainer *container,
				int delta_x, int delta_y)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
493
494
{
	GtkAdjustment *hadj, *vadj;
495
	int old_h_value, old_v_value;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
496

497
498
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
499

500
501
502
503
504
505
506
507
	/* 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
508
509
	eel_gtk_adjustment_set_value (hadj, hadj->value + delta_x);
	eel_gtk_adjustment_set_value (vadj, vadj->value + delta_y);
510
511
512

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

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

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

559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
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
587
static void
588
589
reveal_icon (NautilusIconContainer *container,
	     NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
590
{
591
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
592
593
	GtkAllocation *allocation;
	GtkAdjustment *hadj, *vadj;
594
	ArtIRect bounds;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
595

Mike Engber's avatar
   
Mike Engber committed
596
597
598
599
600
601
602
	if (!icon_is_positioned (icon)) {
		set_pending_icon_to_reveal (container, icon);
		return;
	}
	
	set_pending_icon_to_reveal (container, NULL);

603
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
604
605
	allocation = &GTK_WIDGET (container)->allocation;

606
607
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
608

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

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

Mike Engber's avatar
   
Mike Engber committed
625
626
627
628
629
630
631
632
633
634
635
636
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);
	}
}

637
static gboolean
638
keyboard_icon_reveal_timeout_callback (gpointer data)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
639
{
640
641
	NautilusIconContainer *container;
	NautilusIcon *icon;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
642

643
	container = NAUTILUS_ICON_CONTAINER (data);
644
645
646
	icon = container->details->keyboard_icon_to_reveal;

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

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

	return FALSE;
}

static void
665
unschedule_keyboard_icon_reveal (NautilusIconContainer *container)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
666
{
667
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
668

669
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
670

671
	if (details->keyboard_icon_reveal_timer_id != 0) {
672
		g_source_remove (details->keyboard_icon_reveal_timer_id);
673
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
674
675
676
}

static void
677
678
schedule_keyboard_icon_reveal (NautilusIconContainer *container,
			       NautilusIcon *icon)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
679
{
680
	NautilusIconContainerDetails *details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
681

682
	details = container->details;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
683

684
	unschedule_keyboard_icon_reveal (container);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
685

686
687
	details->keyboard_icon_to_reveal = icon;
	details->keyboard_icon_reveal_timer_id
688
689
690
		= g_timeout_add (KEYBOARD_ICON_REVEAL_TIMEOUT,
				 keyboard_icon_reveal_timeout_callback,
				 container);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
691
692
693
}

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

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

	if (icon == container->details->keyboard_focus) {
		return;
714
	}
715
716
717
718
719

	clear_keyboard_focus (container);

	container->details->keyboard_focus = icon;

720
	eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
721
722
			       "highlighted_as_keyboard_focus", 1,
			       NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
723
724
}

725
726
727
728
729
730
731
732
733
734
735
736
737
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;
}

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

751
752
753
754
755
756
757
758
/* 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;
}

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

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

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

814
	reset_scroll_region = container->details->reset_scroll_region_trigger
815
816
		|| nautilus_icon_container_is_empty (container)
		|| nautilus_icon_container_is_auto_layout (container);
817
818
819
820
821
822
823
824
825
826
		
	/* 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;
	}

827
828
829
830
831
832
833
834
835
836
	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;
	}
837

838
839
	y2 += CONTAINER_PAD_BOTTOM;

840
	if (reset_scroll_region) {
841
842
		eel_canvas_set_scroll_region
			(EEL_CANVAS (container),
843
			 x1, y1, x2, y2);
844
	} else {
845
846
		canvas_set_scroll_region_include_visible_area
			(EEL_CANVAS (container),
847
			 x1, y1, x2, y2);
848
	}
849

850
851
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
852

853
	/* Scroll by 1/4 icon each time you click. */
854
855
856
857
858
859
860
861
862
863
	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
864

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

872
static int
873
compare_icons (gconstpointer a, gconstpointer b, gpointer icon_container)
874
{
875
	NautilusIconContainerClass *klass;
876
877
878
879
	const NautilusIcon *icon_a, *icon_b;

	icon_a = a;
	icon_b = b;
880
	klass  = NAUTILUS_ICON_CONTAINER_GET_CLASS (icon_container);
881

882
	return klass->compare_icons (icon_container, icon_a->data, icon_b->data);
883
884
}

885
static void
886
sort_icons (NautilusIconContainer *container,
887
	    GList                **icons)
888
{
889
890
891
892
893
	NautilusIconContainerClass *klass;

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

894
	*icons = g_list_sort_with_data (*icons, compare_icons, container);
895
896
}

897
898
899
900
static void
resort (NautilusIconContainer *container)
{
	sort_icons (container, &container->details->icons);
901
902
}

903
#if 0
904
905
static double
get_grid_width (NautilusIconContainer *container)
Darin Adler's avatar
Darin Adler committed
906
{
Dave Camp's avatar
Dave Camp committed
907
908
909
910
911
	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
912
}
913
#endif
914
915
typedef struct {
	double width;
916
	double height;
917
918
919
920
	double x_offset;
	double y_offset;
} IconPositions;

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

935
	/* FIXME: Should layout differently when in RTL base mode */
Darin Adler's avatar
Darin Adler committed
936
937

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

943
		position = &g_array_index (positions, IconPositions, i++);
944
945
946
947
948
949
		
		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
950
951
952

		icon_set_position
			(icon,
953
			 x + position->x_offset,
954
			 y + y_offset);
Darin Adler's avatar
Darin Adler committed
955

956
		x += position->width;
Darin Adler's avatar
Darin Adler committed
957
958
959
	}
}

960
961
static void
lay_down_icons_horizontal (NautilusIconContainer *container,
962
963
			   GList *icons,
			   double start_y)
Darin Adler's avatar
Darin Adler committed
964
965
{
	GList *p, *line_start;
966
	NautilusIcon *icon;
967
	double canvas_width, y, canvas_height;
968