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

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

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

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

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

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

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

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

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

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

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

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

72
73
#define CONTEXT_MENU_TIMEOUT_INTERVAL 500

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

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

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

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

#define ICON_PAD_TOP 4
#define ICON_PAD_BOTTOM 4

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

102
#define STANDARD_ICON_GRID_WIDTH 155
103

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

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

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

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

121
122
123
#define MINIMUM_EMBEDDED_TEXT_RECT_WIDTH       20
#define MINIMUM_EMBEDDED_TEXT_RECT_HEIGHT      20

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

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

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

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

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

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

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

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

static GType         nautilus_icon_container_accessible_get_type (void);

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

199

200
static gpointer accessible_parent_class;
201
202
203
204
205

static GQuark accessible_private_data_quark = 0;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	return FALSE;
}

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

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

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

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

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

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

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

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

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

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

	clear_keyboard_focus (container);

	container->details->keyboard_focus = icon;

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

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

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

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

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

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

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

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

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

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

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

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

823
	reset_scroll_region = container->details->reset_scroll_region_trigger
824
825
		|| nautilus_icon_container_is_empty (container)
		|| nautilus_icon_container_is_auto_layout (container);
826
827
828
829
830
831
832
833
834
835
		
	/* 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;
	}

836
837
838
839
840
841
842
843
844
845
	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;
	}
846

847
848
	y2 += CONTAINER_PAD_BOTTOM;

849
	if (reset_scroll_region) {
850
851
		eel_canvas_set_scroll_region
			(EEL_CANVAS (container),
852
			 x1, y1, x2, y2);
853
	} else {
854
855
		canvas_set_scroll_region_include_visible_area
			(EEL_CANVAS (container),
856
			 x1, y1, x2, y2);
857
	}
858

859
860
	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (container));
	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (container));
861

862
	/* Scroll by 1/4 icon each time you click. */
863
864
865
866
867
868
869
870
871
872
	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
873

874
	/* Now that we have a new scroll region, clamp the
875
         * adjustments so we are within the valid scroll area.
876
	 */
Ramiro Estrugo's avatar
Ramiro Estrugo committed
877
878
	eel_gtk_adjustment_clamp_value (hadj);
	eel_gtk_adjustment_clamp_value (vadj);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
879
880
}

881
static int
882
compare_icons (gconstpointer a, gconstpointer b, gpointer icon_container)
883
{
884
	NautilusIconContainerClass *klass;
885
886
887
888
	const NautilusIcon *icon_a, *icon_b;

	icon_a = a;
	icon_b = b;
889
	klass  = NAUTILUS_ICON_CONTAINER_GET_CLASS (icon_container);
890

891
	return klass->compare_icons (icon_container, icon_a->data, icon_b->data);
892
893
}

894
static void
895
sort_icons (NautilusIconContainer *container,
896
	    GList                **icons)
897
{
898
899
900
901
902
	NautilusIconContainerClass *klass;

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

903
	*icons = g_list_sort_with_data (*icons, compare_icons, container);
904
905
}

906
907
908
909
static void
resort (NautilusIconContainer *container)
{
	sort_icons (container, &container->details->icons);
910
911
}

912
#if 0
913
914
static double
get_grid_width (NautilusIconContainer *container)
Darin Adler's avatar
Darin Adler committed
915
{
Dave Camp's avatar
Dave Camp committed
916
917
918
919
920
	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
921
}
922
#endif
923
924
typedef struct {
	double width;
925
	double height;
926
927
928
929
	double x_offset;
	double y_offset;
} IconPositions;

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

944
	/* FIXME: Should layout differently when in RTL base mode */
Darin Adler's avatar
Darin Adler committed
945
946

	/* Lay out the icons along the baseline. */
947
	x = ICON_PAD_LEFT;