1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2
3 /* caja-icon-container.c - Icon container widget.
4
5 Copyright (C) 1999, 2000 Free Software Foundation
6 Copyright (C) 2000, 2001 Eazel, Inc.
7 Copyright (C) 2002, 2003 Red Hat, Inc.
8
9 The Mate Library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version.
13
14 The Mate Library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Library General Public License for more details.
18
19 You should have received a copy of the GNU Library General Public
20 License along with the Mate Library; see the file COPYING.LIB. If not,
21 write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 Boston, MA 02110-1301, USA.
23
24 Authors: Ettore Perazzoli <ettore@gnu.org>,
25 Darin Adler <darin@bentspoon.com>
26 */
27
28 #include <config.h>
29 #include <math.h>
30 #include <atk/atkaction.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <gtk/gtk.h>
33 #include <gdk/gdkx.h>
34 #include <glib/gi18n.h>
35 #include <stdio.h>
36 #include <string.h>
37
38 #include <eel/eel-accessibility.h>
39 #include <eel/eel-background.h>
40 #include <eel/eel-vfs-extensions.h>
41 #include <eel/eel-gdk-pixbuf-extensions.h>
42 #include <eel/eel-mate-extensions.h>
43 #include <eel/eel-gtk-extensions.h>
44 #include <eel/eel-art-extensions.h>
45 #include <eel/eel-editable-label.h>
46 #include <eel/eel-string.h>
47 #include <eel/eel-canvas.h>
48 #include <eel/eel-canvas-rect-ellipse.h>
49
50 #include "caja-icon-container.h"
51 #include "caja-debug-log.h"
52 #include "caja-global-preferences.h"
53 #include "caja-icon-private.h"
54 #include "caja-lib-self-check-functions.h"
55 #include "caja-marshal.h"
56
57 #define TAB_NAVIGATION_DISABLED
58
59 /* Interval for updating the rubberband selection, in milliseconds. */
60 #define RUBBERBAND_TIMEOUT_INTERVAL 10
61
62 /* Initial unpositioned icon value */
63 #define ICON_UNPOSITIONED_VALUE -1
64
65 /* Timeout for making the icon currently selected for keyboard operation visible.
66 * If this is 0, you can get into trouble with extra scrolling after holding
67 * down the arrow key for awhile when there are many items.
68 */
69 #define KEYBOARD_ICON_REVEAL_TIMEOUT 10
70
71 /* Maximum amount of milliseconds the mouse button is allowed to stay down
72 * and still be considered a click.
73 */
74 #define MAX_CLICK_TIME 1500
75
76 /* Button assignments. */
77 #define DRAG_BUTTON 1
78 #define RUBBERBAND_BUTTON 1
79 #define MIDDLE_BUTTON 2
80 #define CONTEXTUAL_MENU_BUTTON 3
81 #define DRAG_MENU_BUTTON 2
82
83 /* Maximum size (pixels) allowed for icons at the standard zoom level. */
84 #define MINIMUM_IMAGE_SIZE 24
85 #define MAXIMUM_IMAGE_SIZE 96
86
87 #define ICON_PAD_LEFT 4
88 #define ICON_PAD_RIGHT 4
89
90 #define ICON_PAD_TOP 4
91 #define ICON_PAD_BOTTOM 4
92
93 #define CONTAINER_PAD_LEFT 4
94 #define CONTAINER_PAD_RIGHT 4
95 #define CONTAINER_PAD_TOP 4
96 #define CONTAINER_PAD_BOTTOM 4
97
98 #define STANDARD_ICON_GRID_WIDTH 155
99
100 /* Desktop layout mode defines */
101 #define DESKTOP_PAD_HORIZONTAL 10
102 #define DESKTOP_PAD_VERTICAL 10
103 #define SNAP_SIZE_X 78
104 #define SNAP_SIZE_Y 20
105
106 /* If icon size is bigger than this, request large embedded text.
107 * Its selected so that the non-large text should fit in "normal" icon sizes
108 */
109 #define ICON_SIZE_FOR_LARGE_EMBEDDED_TEXT 55
110
111 #define SNAP_HORIZONTAL(func,x) ((func ((double)((x) - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X) * SNAP_SIZE_X) + DESKTOP_PAD_HORIZONTAL)
112 #define SNAP_VERTICAL(func, y) ((func ((double)((y) - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y) * SNAP_SIZE_Y) + DESKTOP_PAD_VERTICAL)
113
114 #define SNAP_NEAREST_HORIZONTAL(x) SNAP_HORIZONTAL (eel_round, x)
115 #define SNAP_NEAREST_VERTICAL(y) SNAP_VERTICAL (eel_round, y)
116
117 #define SNAP_CEIL_HORIZONTAL(x) SNAP_HORIZONTAL (ceil, x)
118 #define SNAP_CEIL_VERTICAL(y) SNAP_VERTICAL (ceil, y)
119
120 /* Copied from CajaIconContainer */
121 #define CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT 5
122
123 /* Copied from CajaFile */
124 #define UNDEFINED_TIME ((time_t) (-1))
125
126 enum
127 {
128 ACTION_ACTIVATE,
129 ACTION_MENU,
130 LAST_ACTION
131 };
132
133 typedef struct
134 {
135 GList *selection;
136 char *action_descriptions[LAST_ACTION];
137 } CajaIconContainerAccessiblePrivate;
138
139 static GType caja_icon_container_accessible_get_type (void);
140
141 typedef struct _CajaIconContainerAccessible CajaIconContainerAccessible;
142 typedef struct _CajaIconContainerAccessibleClass CajaIconContainerAccessibleClass;
143
144 struct _CajaIconContainerAccessible
145 {
146 EelCanvasAccessible parent;
147 };
148
149 struct _CajaIconContainerAccessibleClass
150 {
151 EelCanvasAccessibleClass parent_class;
152 };
153
154 static void activate_selected_items (CajaIconContainer *container);
155 static void activate_selected_items_alternate (CajaIconContainer *container,
156 CajaIcon *icon);
157 static void compute_stretch (StretchState *start,
158 StretchState *current);
159 static CajaIcon *get_first_selected_icon (CajaIconContainer *container);
160 static CajaIcon *get_nth_selected_icon (CajaIconContainer *container,
161 int index);
162 static gboolean has_multiple_selection (CajaIconContainer *container);
163 static gboolean all_selected (CajaIconContainer *container);
164 static gboolean has_selection (CajaIconContainer *container);
165 static void icon_destroy (CajaIconContainer *container,
166 CajaIcon *icon);
167 static void end_renaming_mode (CajaIconContainer *container,
168 gboolean commit);
169 static CajaIcon *get_icon_being_renamed (CajaIconContainer *container);
170 static void finish_adding_new_icons (CajaIconContainer *container);
171 static inline void icon_get_bounding_box (CajaIcon *icon,
172 int *x1_return,
173 int *y1_return,
174 int *x2_return,
175 int *y2_return,
176 CajaIconCanvasItemBoundsUsage usage);
177 static gboolean is_renaming (CajaIconContainer *container);
178 static gboolean is_renaming_pending (CajaIconContainer *container);
179 static void process_pending_icon_to_rename (CajaIconContainer *container);
180 static void caja_icon_container_stop_monitor_top_left (CajaIconContainer *container,
181 CajaIconData *data,
182 gconstpointer client);
183 static void caja_icon_container_start_monitor_top_left (CajaIconContainer *container,
184 CajaIconData *data,
185 gconstpointer client,
186 gboolean large_text);
187 static void handle_hadjustment_changed (GtkAdjustment *adjustment,
188 CajaIconContainer *container);
189 static void handle_vadjustment_changed (GtkAdjustment *adjustment,
190 CajaIconContainer *container);
191 static GList * caja_icon_container_get_selected_icons (CajaIconContainer *container);
192 static void caja_icon_container_update_visible_icons (CajaIconContainer *container);
193 static void reveal_icon (CajaIconContainer *container,
194 CajaIcon *icon);
195
196 static void caja_icon_container_set_rtl_positions (CajaIconContainer *container);
197 static double get_mirror_x_position (CajaIconContainer *container,
198 CajaIcon *icon,
199 double x);
200 static void text_ellipsis_limit_changed_container_callback (gpointer callback_data);
201
202 static int compare_icons_horizontal (CajaIconContainer *container,
203 CajaIcon *icon_a,
204 CajaIcon *icon_b);
205
206 static int compare_icons_vertical (CajaIconContainer *container,
207 CajaIcon *icon_a,
208 CajaIcon *icon_b);
209
210 static void store_layout_timestamps_now (CajaIconContainer *container);
211
212 static gpointer accessible_parent_class;
213
214 static GQuark accessible_private_data_quark = 0;
215
216 static const char *caja_icon_container_accessible_action_names[] =
217 {
218 "activate",
219 "menu",
220 NULL
221 };
222
223 static const char *caja_icon_container_accessible_action_descriptions[] =
224 {
225 "Activate selected items",
226 "Popup context menu",
227 NULL
228 };
229
230 G_DEFINE_TYPE (CajaIconContainer, caja_icon_container, EEL_TYPE_CANVAS);
231
232 /* The CajaIconContainer signals. */
233 enum
234 {
235 ACTIVATE,
236 ACTIVATE_ALTERNATE,
237 BAND_SELECT_STARTED,
238 BAND_SELECT_ENDED,
239 BUTTON_PRESS,
240 CAN_ACCEPT_ITEM,
241 CONTEXT_CLICK_BACKGROUND,
242 CONTEXT_CLICK_SELECTION,
243 MIDDLE_CLICK,
244 GET_CONTAINER_URI,
245 GET_ICON_URI,
246 GET_ICON_DROP_TARGET_URI,
247 GET_STORED_ICON_POSITION,
248 ICON_POSITION_CHANGED,
249 GET_STORED_LAYOUT_TIMESTAMP,
250 STORE_LAYOUT_TIMESTAMP,
251 ICON_TEXT_CHANGED,
252 ICON_STRETCH_STARTED,
253 ICON_STRETCH_ENDED,
254 RENAMING_ICON,
255 LAYOUT_CHANGED,
256 MOVE_COPY_ITEMS,
257 HANDLE_NETSCAPE_URL,
258 HANDLE_URI_LIST,
259 HANDLE_TEXT,
260 HANDLE_RAW,
261 PREVIEW,
262 SELECTION_CHANGED,
263 ICON_ADDED,
264 ICON_REMOVED,
265 CLEARED,
266 START_INTERACTIVE_SEARCH,
267 LAST_SIGNAL
268 };
269
270 typedef struct
271 {
272 int **icon_grid;
273 int *grid_memory;
274 int num_rows;
275 int num_columns;
276 gboolean tight;
277 } PlacementGrid;
278
279 static guint signals[LAST_SIGNAL] = { 0 };
280
281 /* Functions dealing with CajaIcons. */
282
283 static void
icon_free(CajaIcon * icon)284 icon_free (CajaIcon *icon)
285 {
286 /* Destroy this canvas item; the parent will unref it. */
287 eel_canvas_item_destroy (EEL_CANVAS_ITEM (icon->item));
288 g_free (icon);
289 }
290
291 static gboolean
icon_is_positioned(const CajaIcon * icon)292 icon_is_positioned (const CajaIcon *icon)
293 {
294 return icon->x != ICON_UNPOSITIONED_VALUE && icon->y != ICON_UNPOSITIONED_VALUE;
295 }
296
297
298 /* x, y are the top-left coordinates of the icon. */
299 static void
icon_set_position(CajaIcon * icon,double x,double y)300 icon_set_position (CajaIcon *icon,
301 double x, double y)
302 {
303 CajaIconContainer *container;
304 int x1, x2, y1, y2;
305 EelDRect icon_bounds;
306
307 if (icon->x == x && icon->y == y)
308 {
309 return;
310 }
311
312 container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (icon->item)->canvas);
313
314 if (icon == get_icon_being_renamed (container))
315 {
316 end_renaming_mode (container, TRUE);
317 }
318
319 if (caja_icon_container_get_is_fixed_size (container))
320 {
321 double pixels_per_unit;
322 int container_left, container_top, container_right, container_bottom;
323 int container_x, container_y, container_width, container_height;
324 int item_width, item_height;
325 int height_above, width_left;
326 int min_x, max_x, min_y, max_y;
327 int scale;
328
329 /* FIXME: This should be:
330
331 container_x = GTK_WIDGET (container)->allocation.x;
332 container_y = GTK_WIDGET (container)->allocation.y;
333 container_width = GTK_WIDGET (container)->allocation.width;
334 container_height = GTK_WIDGET (container)->allocation.height;
335
336 But for some reason the widget allocation is sometimes not done
337 at startup, and the allocation is then only 45x60. which is
338 really bad.
339
340 For now, we have a cheesy workaround:
341 */
342 scale = gtk_widget_get_scale_factor (GTK_WIDGET (container));
343 container_x = 0;
344 container_y = 0;
345 container_width = WidthOfScreen (gdk_x11_screen_get_xscreen (gdk_screen_get_default ())) / scale - container_x
346 - container->details->left_margin
347 - container->details->right_margin;
348 container_height = HeightOfScreen (gdk_x11_screen_get_xscreen (gdk_screen_get_default ())) / scale - container_y
349 - container->details->top_margin
350 - container->details->bottom_margin;
351 pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
352 /* Clip the position of the icon within our desktop bounds */
353 container_left = container_x / pixels_per_unit;
354 container_top = container_y / pixels_per_unit;
355 container_right = container_left + container_width / pixels_per_unit;
356 container_bottom = container_top + container_height / pixels_per_unit;
357
358 icon_get_bounding_box (icon, &x1, &y1, &x2, &y2,
359 BOUNDS_USAGE_FOR_ENTIRE_ITEM);
360 item_width = x2 - x1;
361 item_height = y2 - y1;
362
363 icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
364
365 /* determine icon rectangle relative to item rectangle */
366 height_above = icon_bounds.y0 - y1;
367 width_left = icon_bounds.x0 - x1;
368
369 min_x = container_left + DESKTOP_PAD_HORIZONTAL + width_left;
370 max_x = container_right - DESKTOP_PAD_HORIZONTAL - item_width + width_left;
371 x = CLAMP (x, min_x, max_x);
372
373 min_y = container_top + height_above + DESKTOP_PAD_VERTICAL;
374 max_y = container_bottom - DESKTOP_PAD_VERTICAL - item_height + height_above;
375 y = CLAMP (y, min_y, max_y);
376 }
377
378 if (icon->x == ICON_UNPOSITIONED_VALUE)
379 {
380 icon->x = 0;
381 }
382 if (icon->y == ICON_UNPOSITIONED_VALUE)
383 {
384 icon->y = 0;
385 }
386
387 eel_canvas_item_move (EEL_CANVAS_ITEM (icon->item),
388 x - icon->x,
389 y - icon->y);
390
391 icon->x = x;
392 icon->y = y;
393 }
394
395 static void
icon_get_size(CajaIconContainer * container,CajaIcon * icon,guint * size)396 icon_get_size (CajaIconContainer *container,
397 CajaIcon *icon,
398 guint *size)
399 {
400 if (size != NULL)
401 {
402 *size = MAX (caja_get_icon_size_for_zoom_level (container->details->zoom_level)
403 * icon->scale, CAJA_ICON_SIZE_SMALLEST);
404 }
405 }
406
407 /* The icon_set_size function is used by the stretching user
408 * interface, which currently stretches in a way that keeps the aspect
409 * ratio. Later we might have a stretching interface that stretches Y
410 * separate from X and we will change this around.
411 */
412 static void
icon_set_size(CajaIconContainer * container,CajaIcon * icon,guint icon_size,gboolean snap,gboolean update_position)413 icon_set_size (CajaIconContainer *container,
414 CajaIcon *icon,
415 guint icon_size,
416 gboolean snap,
417 gboolean update_position)
418 {
419 guint old_size;
420 double scale;
421
422 icon_get_size (container, icon, &old_size);
423 if (icon_size == old_size)
424 {
425 return;
426 }
427
428 scale = (double) icon_size /
429 caja_get_icon_size_for_zoom_level
430 (container->details->zoom_level);
431 caja_icon_container_move_icon (container, icon,
432 icon->x, icon->y,
433 scale, FALSE,
434 snap, update_position);
435 }
436
437 static void
icon_raise(CajaIcon * icon)438 icon_raise (CajaIcon *icon)
439 {
440 EelCanvasItem *item, *band;
441
442 item = EEL_CANVAS_ITEM (icon->item);
443 band = CAJA_ICON_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
444
445 eel_canvas_item_send_behind (item, band);
446 }
447
448 static void
emit_stretch_started(CajaIconContainer * container,CajaIcon * icon)449 emit_stretch_started (CajaIconContainer *container, CajaIcon *icon)
450 {
451 g_signal_emit (container,
452 signals[ICON_STRETCH_STARTED], 0,
453 icon->data);
454 }
455
456 static void
emit_stretch_ended(CajaIconContainer * container,CajaIcon * icon)457 emit_stretch_ended (CajaIconContainer *container, CajaIcon *icon)
458 {
459 g_signal_emit (container,
460 signals[ICON_STRETCH_ENDED], 0,
461 icon->data);
462 }
463
464 static void
icon_toggle_selected(CajaIconContainer * container,CajaIcon * icon)465 icon_toggle_selected (CajaIconContainer *container,
466 CajaIcon *icon)
467 {
468 end_renaming_mode (container, TRUE);
469
470 icon->is_selected = !icon->is_selected;
471 eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
472 "highlighted_for_selection", (gboolean) icon->is_selected,
473 NULL);
474
475 /* If the icon is deselected, then get rid of the stretch handles.
476 * No harm in doing the same if the item is newly selected.
477 */
478 if (icon == container->details->stretch_icon)
479 {
480 container->details->stretch_icon = NULL;
481 caja_icon_canvas_item_set_show_stretch_handles (icon->item, FALSE);
482 /* snap the icon if necessary */
483 if (container->details->keep_aligned)
484 {
485 caja_icon_container_move_icon (container,
486 icon,
487 icon->x, icon->y,
488 icon->scale,
489 FALSE, TRUE, TRUE);
490 }
491
492 emit_stretch_ended (container, icon);
493 }
494
495 /* Raise each newly-selected icon to the front as it is selected. */
496 if (icon->is_selected)
497 {
498 icon_raise (icon);
499 }
500 }
501
502 /* Select an icon. Return TRUE if selection has changed. */
503 static gboolean
icon_set_selected(CajaIconContainer * container,CajaIcon * icon,gboolean select)504 icon_set_selected (CajaIconContainer *container,
505 CajaIcon *icon,
506 gboolean select)
507 {
508 g_assert (select == FALSE || select == TRUE);
509 g_assert (icon->is_selected == FALSE || icon->is_selected == TRUE);
510
511 if (select == icon->is_selected)
512 {
513 return FALSE;
514 }
515
516 icon_toggle_selected (container, icon);
517 g_assert (select == icon->is_selected);
518 return TRUE;
519 }
520
521 static inline void
icon_get_bounding_box(CajaIcon * icon,int * x1_return,int * y1_return,int * x2_return,int * y2_return,CajaIconCanvasItemBoundsUsage usage)522 icon_get_bounding_box (CajaIcon *icon,
523 int *x1_return, int *y1_return,
524 int *x2_return, int *y2_return,
525 CajaIconCanvasItemBoundsUsage usage)
526 {
527 double x1, y1, x2, y2;
528
529 if (usage == BOUNDS_USAGE_FOR_DISPLAY)
530 {
531 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
532 &x1, &y1, &x2, &y2);
533 }
534 else if (usage == BOUNDS_USAGE_FOR_LAYOUT)
535 {
536 caja_icon_canvas_item_get_bounds_for_layout (icon->item,
537 &x1, &y1, &x2, &y2);
538 }
539 else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
540 {
541 caja_icon_canvas_item_get_bounds_for_entire_item (icon->item,
542 &x1, &y1, &x2, &y2);
543 }
544 else
545 {
546 g_assert_not_reached ();
547 }
548
549 if (x1_return != NULL)
550 {
551 *x1_return = x1;
552 }
553
554 if (y1_return != NULL)
555 {
556 *y1_return = y1;
557 }
558
559 if (x2_return != NULL)
560 {
561 *x2_return = x2;
562 }
563
564 if (y2_return != NULL)
565 {
566 *y2_return = y2;
567 }
568 }
569
570 /* Utility functions for CajaIconContainer. */
571
572 gboolean
caja_icon_container_scroll(CajaIconContainer * container,int delta_x,int delta_y)573 caja_icon_container_scroll (CajaIconContainer *container,
574 int delta_x, int delta_y)
575 {
576 GtkAdjustment *hadj, *vadj;
577 int old_h_value, old_v_value;
578
579 hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
580 vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
581
582 /* Store the old ajustment values so we can tell if we
583 * ended up actually scrolling. We may not have in a case
584 * where the resulting value got pinned to the adjustment
585 * min or max.
586 */
587 old_h_value = gtk_adjustment_get_value (hadj);
588 old_v_value = gtk_adjustment_get_value (vadj);
589
590 gtk_adjustment_set_value (hadj, gtk_adjustment_get_value (hadj) + delta_x);
591 gtk_adjustment_set_value (vadj, gtk_adjustment_get_value (vadj) + delta_y);
592
593 /* return TRUE if we did scroll */
594 return gtk_adjustment_get_value (hadj) != old_h_value || gtk_adjustment_get_value (vadj) != old_v_value;
595 }
596
597 static void
pending_icon_to_reveal_destroy_callback(CajaIconCanvasItem * item,CajaIconContainer * container)598 pending_icon_to_reveal_destroy_callback (CajaIconCanvasItem *item,
599 CajaIconContainer *container)
600 {
601 g_assert (CAJA_IS_ICON_CONTAINER (container));
602 g_assert (container->details->pending_icon_to_reveal != NULL);
603 g_assert (container->details->pending_icon_to_reveal->item == item);
604
605 container->details->pending_icon_to_reveal = NULL;
606 }
607
608 static CajaIcon*
get_pending_icon_to_reveal(CajaIconContainer * container)609 get_pending_icon_to_reveal (CajaIconContainer *container)
610 {
611 return container->details->pending_icon_to_reveal;
612 }
613
614 static void
set_pending_icon_to_reveal(CajaIconContainer * container,CajaIcon * icon)615 set_pending_icon_to_reveal (CajaIconContainer *container, CajaIcon *icon)
616 {
617 CajaIcon *old_icon;
618
619 old_icon = container->details->pending_icon_to_reveal;
620
621 if (icon == old_icon)
622 {
623 return;
624 }
625
626 if (old_icon != NULL)
627 {
628 g_signal_handlers_disconnect_by_func
629 (old_icon->item,
630 G_CALLBACK (pending_icon_to_reveal_destroy_callback),
631 container);
632 }
633
634 if (icon != NULL)
635 {
636 g_signal_connect (icon->item, "destroy",
637 G_CALLBACK (pending_icon_to_reveal_destroy_callback),
638 container);
639 }
640
641 container->details->pending_icon_to_reveal = icon;
642 }
643
644 static void
item_get_canvas_bounds(EelCanvasItem * item,EelIRect * bounds,gboolean safety_pad)645 item_get_canvas_bounds (EelCanvasItem *item,
646 EelIRect *bounds,
647 gboolean safety_pad)
648 {
649 EelDRect world_rect;
650
651 eel_canvas_item_get_bounds (item,
652 &world_rect.x0,
653 &world_rect.y0,
654 &world_rect.x1,
655 &world_rect.y1);
656 eel_canvas_item_i2w (item->parent,
657 &world_rect.x0,
658 &world_rect.y0);
659 eel_canvas_item_i2w (item->parent,
660 &world_rect.x1,
661 &world_rect.y1);
662 if (safety_pad)
663 {
664 world_rect.x0 -= ICON_PAD_LEFT + ICON_PAD_RIGHT;
665 world_rect.x1 += ICON_PAD_LEFT + ICON_PAD_RIGHT;
666
667 world_rect.y0 -= ICON_PAD_TOP + ICON_PAD_BOTTOM;
668 world_rect.y1 += ICON_PAD_TOP + ICON_PAD_BOTTOM;
669 }
670
671 eel_canvas_w2c (item->canvas,
672 world_rect.x0,
673 world_rect.y0,
674 &bounds->x0,
675 &bounds->y0);
676 eel_canvas_w2c (item->canvas,
677 world_rect.x1,
678 world_rect.y1,
679 &bounds->x1,
680 &bounds->y1);
681 }
682
683 static void
icon_get_row_and_column_bounds(CajaIconContainer * container,CajaIcon * icon,EelIRect * bounds,gboolean safety_pad)684 icon_get_row_and_column_bounds (CajaIconContainer *container,
685 CajaIcon *icon,
686 EelIRect *bounds,
687 gboolean safety_pad)
688 {
689 GList *p;
690 EelIRect one_bounds;
691 CajaIcon *one_icon = NULL;
692
693 item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), bounds, safety_pad);
694
695 for (p = container->details->icons; p != NULL; p = p->next) {
696 one_icon = p->data;
697
698 if (icon == one_icon) {
699 continue;
700 }
701
702 if (compare_icons_horizontal (container, icon, one_icon) == 0) {
703 item_get_canvas_bounds (EEL_CANVAS_ITEM (one_icon->item), &one_bounds, safety_pad);
704 bounds->x0 = MIN (bounds->x0, one_bounds.x0);
705 bounds->x1 = MAX (bounds->x1, one_bounds.x1);
706 }
707
708 if (compare_icons_vertical (container, icon, one_icon) == 0) {
709 item_get_canvas_bounds (EEL_CANVAS_ITEM (one_icon->item), &one_bounds, safety_pad);
710 bounds->y0 = MIN (bounds->y0, one_bounds.y0);
711 bounds->y1 = MAX (bounds->y1, one_bounds.y1);
712 }
713 }
714
715
716 }
717
718 static void
reveal_icon(CajaIconContainer * container,CajaIcon * icon)719 reveal_icon (CajaIconContainer *container,
720 CajaIcon *icon)
721 {
722 GtkAllocation allocation;
723 GtkAdjustment *hadj, *vadj;
724 EelIRect bounds;
725
726 if (!icon_is_positioned (icon)) {
727 set_pending_icon_to_reveal (container, icon);
728 return;
729 }
730
731 set_pending_icon_to_reveal (container, NULL);
732
733 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
734
735 hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
736 vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
737
738 if (caja_icon_container_is_auto_layout (container)) {
739 /* ensure that we reveal the entire row/column */
740 icon_get_row_and_column_bounds (container, icon, &bounds, TRUE);
741 } else {
742 item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), &bounds, TRUE);
743 }
744 if (bounds.y0 < gtk_adjustment_get_value (vadj)) {
745 gtk_adjustment_set_value (vadj, bounds.y0);
746 } else if (bounds.y1 > gtk_adjustment_get_value (vadj) + allocation.height) {
747 gtk_adjustment_set_value (vadj, bounds.y1 - allocation.height);
748 }
749
750 if (bounds.x0 < gtk_adjustment_get_value (hadj)) {
751 gtk_adjustment_set_value (hadj, bounds.x0);
752 } else if (bounds.x1 > gtk_adjustment_get_value (hadj) + allocation.width) {
753 if (bounds.x1 - allocation.width > bounds.x0) {
754 gtk_adjustment_set_value (hadj, bounds.x0);
755 } else {
756 gtk_adjustment_set_value (hadj, bounds.x1 - allocation.width);
757 }
758 }
759 }
760
761 static void
process_pending_icon_to_reveal(CajaIconContainer * container)762 process_pending_icon_to_reveal (CajaIconContainer *container)
763 {
764 CajaIcon *pending_icon_to_reveal;
765
766 pending_icon_to_reveal = get_pending_icon_to_reveal (container);
767
768 if (pending_icon_to_reveal != NULL) {
769 reveal_icon (container, pending_icon_to_reveal);
770 }
771 }
772
773 static gboolean
keyboard_icon_reveal_timeout_callback(gpointer data)774 keyboard_icon_reveal_timeout_callback (gpointer data)
775 {
776 CajaIconContainer *container;
777 CajaIcon *icon;
778
779 container = CAJA_ICON_CONTAINER (data);
780 icon = container->details->keyboard_icon_to_reveal;
781
782 g_assert (icon != NULL);
783
784 /* Only reveal the icon if it's still the keyboard focus or if
785 * it's still selected. Someone originally thought we should
786 * cancel this reveal if the user manages to sneak a direct
787 * scroll in before the timeout fires, but we later realized
788 * this wouldn't actually be an improvement
789 * (see bugzilla.gnome.org 40612).
790 */
791 if (icon == container->details->keyboard_focus
792 || icon->is_selected) {
793 reveal_icon (container, icon);
794 }
795 container->details->keyboard_icon_reveal_timer_id = 0;
796
797 return FALSE;
798 }
799
800 static void
unschedule_keyboard_icon_reveal(CajaIconContainer * container)801 unschedule_keyboard_icon_reveal (CajaIconContainer *container)
802 {
803 CajaIconContainerDetails *details;
804
805 details = container->details;
806
807 if (details->keyboard_icon_reveal_timer_id != 0) {
808 g_source_remove (details->keyboard_icon_reveal_timer_id);
809 }
810 }
811
812 static void
schedule_keyboard_icon_reveal(CajaIconContainer * container,CajaIcon * icon)813 schedule_keyboard_icon_reveal (CajaIconContainer *container,
814 CajaIcon *icon)
815 {
816 CajaIconContainerDetails *details;
817
818 details = container->details;
819
820 unschedule_keyboard_icon_reveal (container);
821
822 details->keyboard_icon_to_reveal = icon;
823 details->keyboard_icon_reveal_timer_id
824 = g_timeout_add (KEYBOARD_ICON_REVEAL_TIMEOUT,
825 keyboard_icon_reveal_timeout_callback,
826 container);
827 }
828
829 static void
clear_keyboard_focus(CajaIconContainer * container)830 clear_keyboard_focus (CajaIconContainer *container)
831 {
832 if (container->details->keyboard_focus != NULL)
833 {
834 eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
835 "highlighted_as_keyboard_focus", 0,
836 NULL);
837 }
838
839 container->details->keyboard_focus = NULL;
840 }
841
842 static inline void
emit_atk_focus_state_change(CajaIcon * icon,gboolean focused)843 emit_atk_focus_state_change (CajaIcon *icon, gboolean focused)
844 {
845 AtkObject *atk_object = atk_gobject_accessible_for_object (G_OBJECT (icon->item));
846 atk_object_notify_state_change (atk_object, ATK_STATE_FOCUSED, focused);
847 }
848
849 /* Set @icon as the icon currently selected for keyboard operations. */
850 static void
set_keyboard_focus(CajaIconContainer * container,CajaIcon * icon)851 set_keyboard_focus (CajaIconContainer *container,
852 CajaIcon *icon)
853 {
854 g_assert (icon != NULL);
855
856 if (icon == container->details->keyboard_focus)
857 {
858 return;
859 }
860
861 clear_keyboard_focus (container);
862
863 container->details->keyboard_focus = icon;
864
865 eel_canvas_item_set (EEL_CANVAS_ITEM (container->details->keyboard_focus->item),
866 "highlighted_as_keyboard_focus", 1,
867 NULL);
868
869 emit_atk_focus_state_change (icon, TRUE);
870 }
871
872 static void
set_keyboard_rubberband_start(CajaIconContainer * container,CajaIcon * icon)873 set_keyboard_rubberband_start (CajaIconContainer *container,
874 CajaIcon *icon)
875 {
876 container->details->keyboard_rubberband_start = icon;
877 }
878
879 static void
clear_keyboard_rubberband_start(CajaIconContainer * container)880 clear_keyboard_rubberband_start (CajaIconContainer *container)
881 {
882 container->details->keyboard_rubberband_start = NULL;
883 }
884
885 /* carbon-copy of eel_canvas_group_bounds(), but
886 * for CajaIconContainerItems it returns the
887 * bounds for the “entire item”.
888 */
889 static void
get_icon_bounds_for_canvas_bounds(EelCanvasGroup * group,double * x1,double * y1,double * x2,double * y2,CajaIconCanvasItemBoundsUsage usage)890 get_icon_bounds_for_canvas_bounds (EelCanvasGroup *group,
891 double *x1, double *y1,
892 double *x2, double *y2,
893 CajaIconCanvasItemBoundsUsage usage)
894 {
895 EelCanvasItem *child;
896 GList *list;
897 double tx1, ty1, tx2, ty2;
898 double minx, miny, maxx, maxy;
899 int set;
900
901 /* Get the bounds of the first visible item */
902
903 child = NULL; /* Unnecessary but eliminates a warning. */
904
905 set = FALSE;
906
907 for (list = group->item_list; list; list = list->next)
908 {
909 child = list->data;
910
911 if (child->flags & EEL_CANVAS_ITEM_VISIBLE)
912 {
913 set = TRUE;
914 if (!CAJA_IS_ICON_CANVAS_ITEM (child) ||
915 usage == BOUNDS_USAGE_FOR_DISPLAY)
916 {
917 eel_canvas_item_get_bounds (child, &minx, &miny, &maxx, &maxy);
918 }
919 else if (usage == BOUNDS_USAGE_FOR_LAYOUT)
920 {
921 caja_icon_canvas_item_get_bounds_for_layout (CAJA_ICON_CANVAS_ITEM (child),
922 &minx, &miny, &maxx, &maxy);
923 }
924 else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
925 {
926 caja_icon_canvas_item_get_bounds_for_entire_item (CAJA_ICON_CANVAS_ITEM (child),
927 &minx, &miny, &maxx, &maxy);
928 }
929 else
930 {
931 g_assert_not_reached ();
932 }
933 break;
934 }
935 }
936
937 /* If there were no visible items, return an empty bounding box */
938
939 if (!set)
940 {
941 *x1 = *y1 = *x2 = *y2 = 0.0;
942 return;
943 }
944
945 /* Now we can grow the bounds using the rest of the items */
946
947 list = list->next;
948
949 for (; list; list = list->next)
950 {
951 child = list->data;
952
953 if (!(child->flags & EEL_CANVAS_ITEM_VISIBLE))
954 continue;
955
956 if (!CAJA_IS_ICON_CANVAS_ITEM (child) ||
957 usage == BOUNDS_USAGE_FOR_DISPLAY)
958 {
959 eel_canvas_item_get_bounds (child, &tx1, &ty1, &tx2, &ty2);
960 }
961 else if (usage == BOUNDS_USAGE_FOR_LAYOUT)
962 {
963 caja_icon_canvas_item_get_bounds_for_layout (CAJA_ICON_CANVAS_ITEM (child),
964 &tx1, &ty1, &tx2, &ty2);
965 }
966 else if (usage == BOUNDS_USAGE_FOR_ENTIRE_ITEM)
967 {
968 caja_icon_canvas_item_get_bounds_for_entire_item (CAJA_ICON_CANVAS_ITEM (child),
969 &tx1, &ty1, &tx2, &ty2);
970 }
971 else
972 {
973 g_assert_not_reached ();
974 }
975
976 if (tx1 < minx)
977 minx = tx1;
978
979 if (ty1 < miny)
980 miny = ty1;
981
982 if (tx2 > maxx)
983 maxx = tx2;
984
985 if (ty2 > maxy)
986 maxy = ty2;
987 }
988
989 /* Make the bounds be relative to our parent's coordinate system */
990
991 if (EEL_CANVAS_ITEM (group)->parent)
992 {
993 minx += group->xpos;
994 miny += group->ypos;
995 maxx += group->xpos;
996 maxy += group->ypos;
997 }
998
999 if (x1 != NULL)
1000 {
1001 *x1 = minx;
1002 }
1003
1004 if (y1 != NULL)
1005 {
1006 *y1 = miny;
1007 }
1008
1009 if (x2 != NULL)
1010 {
1011 *x2 = maxx;
1012 }
1013
1014 if (y2 != NULL)
1015 {
1016 *y2 = maxy;
1017 }
1018 }
1019
1020 static void
get_all_icon_bounds(CajaIconContainer * container,double * x1,double * y1,double * x2,double * y2,CajaIconCanvasItemBoundsUsage usage)1021 get_all_icon_bounds (CajaIconContainer *container,
1022 double *x1, double *y1,
1023 double *x2, double *y2,
1024 CajaIconCanvasItemBoundsUsage usage)
1025 {
1026 /* FIXME bugzilla.gnome.org 42477: Do we have to do something about the rubberband
1027 * here? Any other non-icon items?
1028 */
1029 get_icon_bounds_for_canvas_bounds (EEL_CANVAS_GROUP (EEL_CANVAS (container)->root),
1030 x1, y1, x2, y2, usage);
1031 }
1032
1033 /* Don't preserve visible white space the next time the scroll region
1034 * is recomputed when the container is not empty. */
1035 void
caja_icon_container_reset_scroll_region(CajaIconContainer * container)1036 caja_icon_container_reset_scroll_region (CajaIconContainer *container)
1037 {
1038 container->details->reset_scroll_region_trigger = TRUE;
1039 }
1040
1041 /* Set a new scroll region without eliminating any of the currently-visible area. */
1042 static void
canvas_set_scroll_region_include_visible_area(EelCanvas * canvas,double x1,double y1,double x2,double y2)1043 canvas_set_scroll_region_include_visible_area (EelCanvas *canvas,
1044 double x1, double y1,
1045 double x2, double y2)
1046 {
1047 double old_x1, old_y1, old_x2, old_y2;
1048 double old_scroll_x, old_scroll_y;
1049 double height, width;
1050 GtkAllocation allocation;
1051
1052 eel_canvas_get_scroll_region (canvas, &old_x1, &old_y1, &old_x2, &old_y2);
1053 gtk_widget_get_allocation (GTK_WIDGET (canvas), &allocation);
1054
1055 width = (allocation.width) / canvas->pixels_per_unit;
1056 height = (allocation.height) / canvas->pixels_per_unit;
1057
1058 old_scroll_x = gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas)));
1059 old_scroll_y = gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas)));
1060
1061 x1 = MIN (x1, old_x1 + old_scroll_x);
1062 y1 = MIN (y1, old_y1 + old_scroll_y);
1063 x2 = MAX (x2, old_x1 + old_scroll_x + width);
1064 y2 = MAX (y2, old_y1 + old_scroll_y + height);
1065
1066 eel_canvas_set_scroll_region
1067 (canvas, x1, y1, x2, y2);
1068 }
1069
1070 void
caja_icon_container_update_scroll_region(CajaIconContainer * container)1071 caja_icon_container_update_scroll_region (CajaIconContainer *container)
1072 {
1073 double x1, y1, x2, y2;
1074 double pixels_per_unit;
1075 GtkAdjustment *hadj, *vadj;
1076 float step_increment;
1077 gboolean reset_scroll_region;
1078 GtkAllocation allocation;
1079
1080 pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit;
1081
1082 if (caja_icon_container_get_is_fixed_size (container))
1083 {
1084 /* Set the scroll region to the size of the container allocation */
1085 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
1086 eel_canvas_set_scroll_region
1087 (EEL_CANVAS (container),
1088 (double) - container->details->left_margin / pixels_per_unit,
1089 (double) - container->details->top_margin / pixels_per_unit,
1090 ((double) (allocation.width - 1)
1091 - container->details->left_margin
1092 - container->details->right_margin)
1093 / pixels_per_unit,
1094 ((double) (allocation.height - 1)
1095 - container->details->top_margin
1096 - container->details->bottom_margin)
1097 / pixels_per_unit);
1098 return;
1099 }
1100
1101 reset_scroll_region = container->details->reset_scroll_region_trigger
1102 || caja_icon_container_is_empty (container)
1103 || caja_icon_container_is_auto_layout (container);
1104
1105 /* The trigger is only cleared when container is non-empty, so
1106 * callers can reliably reset the scroll region when an item
1107 * is added even if extraneous relayouts are called when the
1108 * window is still empty.
1109 */
1110 if (!caja_icon_container_is_empty (container))
1111 {
1112 container->details->reset_scroll_region_trigger = FALSE;
1113 }
1114
1115 get_all_icon_bounds (container, &x1, &y1, &x2, &y2, BOUNDS_USAGE_FOR_ENTIRE_ITEM);
1116
1117 /* Add border at the "end"of the layout (i.e. after the icons), to
1118 * ensure we get some space when scrolled to the end.
1119 * For horizontal layouts, we add a bottom border.
1120 * Vertical layout is used by the compact view so the end
1121 * depends on the RTL setting.
1122 */
1123 if (caja_icon_container_is_layout_vertical (container))
1124 {
1125 if (caja_icon_container_is_layout_rtl (container))
1126 {
1127 x1 -= ICON_PAD_LEFT + CONTAINER_PAD_LEFT;
1128 }
1129 else
1130 {
1131 x2 += ICON_PAD_RIGHT + CONTAINER_PAD_RIGHT;
1132 }
1133 }
1134 else
1135 {
1136 y2 += ICON_PAD_BOTTOM + CONTAINER_PAD_BOTTOM;
1137 }
1138
1139 /* Auto-layout assumes a 0, 0 scroll origin and at least allocation->width.
1140 * Then we lay out to the right or to the left, so
1141 * x can be < 0 and > allocation */
1142 if (caja_icon_container_is_auto_layout (container))
1143 {
1144 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
1145 x1 = MIN (x1, 0);
1146 x2 = MAX (x2, allocation.width / pixels_per_unit);
1147 y1 = 0;
1148 }
1149 else
1150 {
1151 /* Otherwise we add the padding that is at the start of the
1152 layout */
1153 if (caja_icon_container_is_layout_rtl (container))
1154 {
1155 x2 += ICON_PAD_RIGHT + CONTAINER_PAD_RIGHT;
1156 }
1157 else
1158 {
1159 x1 -= ICON_PAD_LEFT + CONTAINER_PAD_LEFT;
1160 }
1161 y1 -= ICON_PAD_TOP + CONTAINER_PAD_TOP;
1162 }
1163
1164 x2 -= 1;
1165 x2 = MAX(x1, x2);
1166
1167 y2 -= 1;
1168 y2 = MAX(y1, y2);
1169
1170 if (reset_scroll_region)
1171 {
1172 eel_canvas_set_scroll_region
1173 (EEL_CANVAS (container),
1174 x1, y1, x2, y2);
1175 }
1176 else
1177 {
1178 canvas_set_scroll_region_include_visible_area
1179 (EEL_CANVAS (container),
1180 x1, y1, x2, y2);
1181 }
1182
1183 hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
1184 vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
1185
1186 /* Scroll by 1/4 icon each time you click. */
1187 step_increment = caja_get_icon_size_for_zoom_level
1188 (container->details->zoom_level) / 4;
1189 if (gtk_adjustment_get_step_increment (hadj) != step_increment)
1190 {
1191 gtk_adjustment_set_step_increment (hadj, step_increment);
1192 }
1193 if (gtk_adjustment_get_step_increment (vadj) != step_increment)
1194 {
1195 gtk_adjustment_set_step_increment (vadj, step_increment);
1196 }
1197 }
1198
1199 static int
compare_icons(gconstpointer a,gconstpointer b,gpointer icon_container)1200 compare_icons (gconstpointer a, gconstpointer b, gpointer icon_container)
1201 {
1202 CajaIconContainerClass *klass;
1203 const CajaIcon *icon_a, *icon_b;
1204
1205 icon_a = a;
1206 icon_b = b;
1207 klass = CAJA_ICON_CONTAINER_GET_CLASS (icon_container);
1208
1209 return klass->compare_icons (icon_container, icon_a->data, icon_b->data);
1210 }
1211
1212 static void
sort_icons(CajaIconContainer * container,GList ** icons)1213 sort_icons (CajaIconContainer *container,
1214 GList **icons)
1215 {
1216 CajaIconContainerClass *klass;
1217
1218 klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
1219 g_assert (klass->compare_icons != NULL);
1220
1221 *icons = g_list_sort_with_data (*icons, compare_icons, container);
1222 }
1223
1224 static void
resort(CajaIconContainer * container)1225 resort (CajaIconContainer *container)
1226 {
1227 sort_icons (container, &container->details->icons);
1228 }
1229
1230 typedef struct
1231 {
1232 double width;
1233 double height;
1234 double x_offset;
1235 double y_offset;
1236 } IconPositions;
1237
1238 static void
lay_down_one_line(CajaIconContainer * container,GList * line_start,GList * line_end,double y,double max_height,GArray * positions,gboolean whole_text)1239 lay_down_one_line (CajaIconContainer *container,
1240 GList *line_start,
1241 GList *line_end,
1242 double y,
1243 double max_height,
1244 GArray *positions,
1245 gboolean whole_text)
1246 {
1247 GList *p;
1248 double x, y_offset;
1249 IconPositions *position;
1250 int i;
1251 gboolean is_rtl;
1252 CajaIcon *icon = NULL;
1253
1254 is_rtl = caja_icon_container_is_layout_rtl (container);
1255
1256 /* Lay out the icons along the baseline. */
1257 x = ICON_PAD_LEFT;
1258 i = 0;
1259 for (p = line_start; p != line_end; p = p->next)
1260 {
1261 icon = p->data;
1262
1263 position = &g_array_index (positions, IconPositions, i++);
1264
1265 if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
1266 {
1267 y_offset = (max_height - position->height) / 2;
1268 }
1269 else
1270 {
1271 y_offset = position->y_offset;
1272 }
1273
1274 icon_set_position
1275 (icon,
1276 is_rtl ? get_mirror_x_position (container, icon, x + position->x_offset) : x + position->x_offset,
1277 y + y_offset);
1278 caja_icon_canvas_item_set_entire_text (icon->item, whole_text);
1279
1280 icon->saved_ltr_x = is_rtl ? get_mirror_x_position (container, icon, icon->x) : icon->x;
1281
1282 x += position->width;
1283 }
1284 }
1285
1286 static void
lay_down_one_column(CajaIconContainer * container,GList * line_start,GList * line_end,double x,double y_start,double y_iter,GArray * positions)1287 lay_down_one_column (CajaIconContainer *container,
1288 GList *line_start,
1289 GList *line_end,
1290 double x,
1291 double y_start,
1292 double y_iter,
1293 GArray *positions)
1294 {
1295 GList *p;
1296 double y;
1297 int i;
1298 gboolean is_rtl;
1299 IconPositions *position = NULL;
1300 CajaIcon *icon = NULL;
1301
1302 is_rtl = caja_icon_container_is_layout_rtl (container);
1303
1304 /* Lay out the icons along the baseline. */
1305 y = y_start;
1306 i = 0;
1307 for (p = line_start; p != line_end; p = p->next)
1308 {
1309 icon = p->data;
1310
1311 position = &g_array_index (positions, IconPositions, i++);
1312
1313 icon_set_position
1314 (icon,
1315 is_rtl ? get_mirror_x_position (container, icon, x + position->x_offset) : x + position->x_offset,
1316 y + position->y_offset);
1317
1318 icon->saved_ltr_x = is_rtl ? get_mirror_x_position (container, icon, icon->x) : icon->x;
1319
1320 y += y_iter;
1321 }
1322 }
1323
1324 static void
lay_down_icons_horizontal(CajaIconContainer * container,GList * icons,double start_y)1325 lay_down_icons_horizontal (CajaIconContainer *container,
1326 GList *icons,
1327 double start_y)
1328 {
1329 GList *p, *line_start;
1330 CajaIcon *icon;
1331 double canvas_width, y;
1332 EelDRect bounds;
1333 EelDRect icon_bounds;
1334 EelDRect text_bounds;
1335 double max_height_above, max_height_below;
1336 double line_width;
1337 gboolean gridded_layout;
1338 double grid_width;
1339 double max_text_width, max_icon_width;
1340 int icon_width;
1341 int i;
1342 GtkAllocation allocation;
1343 GArray *positions;
1344 IconPositions *position = NULL;
1345
1346 g_assert (CAJA_IS_ICON_CONTAINER (container));
1347
1348 if (icons == NULL)
1349 {
1350 return;
1351 }
1352
1353 positions = g_array_new (FALSE, FALSE, sizeof (IconPositions));
1354 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
1355
1356 /* Lay out icons a line at a time. */
1357 canvas_width = CANVAS_WIDTH(container, allocation);
1358 max_icon_width = max_text_width = 0.0;
1359
1360 if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
1361 {
1362 /* Would it be worth caching these bounds for the next loop? */
1363 for (p = icons; p != NULL; p = p->next)
1364 {
1365 icon = p->data;
1366
1367 icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
1368 max_icon_width = MAX (max_icon_width, ceil (icon_bounds.x1 - icon_bounds.x0));
1369
1370 text_bounds = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
1371 max_text_width = MAX (max_text_width, ceil (text_bounds.x1 - text_bounds.x0));
1372 }
1373
1374 grid_width = max_icon_width + max_text_width + ICON_PAD_LEFT + ICON_PAD_RIGHT;
1375 }
1376 else
1377 {
1378 int num_columns;
1379
1380 num_columns = floor(canvas_width / STANDARD_ICON_GRID_WIDTH);
1381 num_columns = fmax(num_columns, 1);
1382 /* Minimum of one column */
1383 grid_width = canvas_width / num_columns - 1;
1384 /* -1 prevents jitter */
1385 }
1386
1387 gridded_layout = !caja_icon_container_is_tighter_layout (container);
1388
1389 line_width = container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE ? ICON_PAD_LEFT : 0;
1390 line_start = icons;
1391 y = start_y + CONTAINER_PAD_TOP;
1392 i = 0;
1393
1394 max_height_above = 0;
1395 max_height_below = 0;
1396 for (p = icons; p != NULL; p = p->next)
1397 {
1398 double height_above, height_below;
1399
1400 icon = p->data;
1401
1402 /* Assume it's only one level hierarchy to avoid costly affine calculations */
1403 caja_icon_canvas_item_get_bounds_for_layout (icon->item,
1404 &bounds.x0, &bounds.y0,
1405 &bounds.x1, &bounds.y1);
1406
1407 icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
1408 text_bounds = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
1409
1410 if (gridded_layout)
1411 {
1412 icon_width = ceil ((bounds.x1 - bounds.x0)/grid_width) * grid_width;
1413
1414
1415 }
1416 else
1417 {
1418 icon_width = (bounds.x1 - bounds.x0) + ICON_PAD_RIGHT + 8; /* 8 pixels extra for fancy selection box */
1419 }
1420
1421 /* Calculate size above/below baseline */
1422 height_above = icon_bounds.y1 - bounds.y0;
1423 height_below = bounds.y1 - icon_bounds.y1;
1424
1425 /* If this icon doesn't fit, it's time to lay out the line that's queued up. */
1426 if (line_start != p && line_width + icon_width >= canvas_width )
1427 {
1428 if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
1429 {
1430 y += ICON_PAD_TOP;
1431 }
1432 else
1433 {
1434 /* Advance to the baseline. */
1435 y += ICON_PAD_TOP + max_height_above;
1436 }
1437
1438 lay_down_one_line (container, line_start, p, y, max_height_above, positions, FALSE);
1439
1440 if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
1441 {
1442 y += max_height_above + max_height_below + ICON_PAD_BOTTOM;
1443 }
1444 else
1445 {
1446 /* Advance to next line. */
1447 y += max_height_below + ICON_PAD_BOTTOM;
1448 }
1449
1450 line_width = container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE ? ICON_PAD_LEFT : 0;
1451 line_start = p;
1452 i = 0;
1453
1454 max_height_above = height_above;
1455 max_height_below = height_below;
1456 }
1457 else
1458 {
1459 if (height_above > max_height_above)
1460 {
1461 max_height_above = height_above;
1462 }
1463 if (height_below > max_height_below)
1464 {
1465 max_height_below = height_below;
1466 }
1467 }
1468
1469 g_array_set_size (positions, i + 1);
1470 position = &g_array_index (positions, IconPositions, i++);
1471 position->width = icon_width;
1472 position->height = icon_bounds.y1 - icon_bounds.y0;
1473
1474 if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
1475 {
1476 if (gridded_layout)
1477 {
1478 position->x_offset = max_icon_width + ICON_PAD_LEFT + ICON_PAD_RIGHT - (icon_bounds.x1 - icon_bounds.x0);
1479 }
1480 else
1481 {
1482 position->x_offset = icon_width - ((icon_bounds.x1 - icon_bounds.x0) + (text_bounds.x1 - text_bounds.x0));
1483 }
1484 position->y_offset = 0;
1485 }
1486 else
1487 {
1488 position->x_offset = (icon_width - (icon_bounds.x1 - icon_bounds.x0)) / 2;
1489 position->y_offset = icon_bounds.y0 - icon_bounds.y1;
1490 }
1491
1492 /* Add this icon. */
1493 line_width += icon_width;
1494 }
1495
1496 /* Lay down that last line of icons. */
1497 if (line_start != NULL)
1498 {
1499 if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
1500 {
1501 y += ICON_PAD_TOP;
1502 }
1503 else
1504 {
1505 /* Advance to the baseline. */
1506 y += ICON_PAD_TOP + max_height_above;
1507 }
1508
1509 lay_down_one_line (container, line_start, NULL, y, max_height_above, positions, TRUE);
1510
1511 /* Advance to next line. */
1512 y += max_height_below + ICON_PAD_BOTTOM;
1513 }
1514
1515 g_array_free (positions, TRUE);
1516 }
1517
1518 static void
get_max_icon_dimensions(GList * icon_start,GList * icon_end,double * max_icon_width,double * max_icon_height,double * max_text_width,double * max_text_height,double * max_bounds_height)1519 get_max_icon_dimensions (GList *icon_start,
1520 GList *icon_end,
1521 double *max_icon_width,
1522 double *max_icon_height,
1523 double *max_text_width,
1524 double *max_text_height,
1525 double *max_bounds_height)
1526 {
1527 EelDRect icon_bounds;
1528 EelDRect text_bounds;
1529 GList *p;
1530 double y1, y2;
1531 CajaIcon *icon = NULL;
1532
1533 *max_icon_width = *max_text_width = 0.0;
1534 *max_icon_height = *max_text_height = 0.0;
1535 *max_bounds_height = 0.0;
1536
1537 /* Would it be worth caching these bounds for the next loop? */
1538 for (p = icon_start; p != icon_end; p = p->next)
1539 {
1540 icon = p->data;
1541
1542 icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
1543 *max_icon_width = MAX (*max_icon_width, ceil (icon_bounds.x1 - icon_bounds.x0));
1544 *max_icon_height = MAX (*max_icon_height, ceil (icon_bounds.y1 - icon_bounds.y0));
1545
1546 text_bounds = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
1547 *max_text_width = MAX (*max_text_width, ceil (text_bounds.x1 - text_bounds.x0));
1548 *max_text_height = MAX (*max_text_height, ceil (text_bounds.y1 - text_bounds.y0));
1549
1550 caja_icon_canvas_item_get_bounds_for_layout (icon->item,
1551 NULL, &y1,
1552 NULL, &y2);
1553 *max_bounds_height = MAX (*max_bounds_height, y2 - y1);
1554 }
1555 }
1556
1557 /* column-wise layout. At the moment, this only works with label-beside-icon (used by "Compact View"). */
1558 static void
lay_down_icons_vertical(CajaIconContainer * container,GList * icons,double start_y)1559 lay_down_icons_vertical (CajaIconContainer *container,
1560 GList *icons,
1561 double start_y)
1562 {
1563 GList *p, *line_start;
1564 double x, canvas_height;
1565 GArray *positions;
1566 IconPositions *position;
1567 EelDRect icon_bounds;
1568 EelDRect text_bounds;
1569 GtkAllocation allocation;
1570
1571 double line_height;
1572
1573 double max_height;
1574 double max_height_with_borders;
1575 double max_width;
1576 double max_width_in_column;
1577
1578 double max_bounds_height;
1579 double max_bounds_height_with_borders;
1580
1581 double max_text_width, max_icon_width;
1582 double max_text_height, max_icon_height;
1583 int i;
1584
1585 CajaIcon *icon = NULL;
1586
1587 g_assert (CAJA_IS_ICON_CONTAINER (container));
1588 g_assert (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE);
1589
1590 if (icons == NULL)
1591 {
1592 return;
1593 }
1594
1595 positions = g_array_new (FALSE, FALSE, sizeof (IconPositions));
1596 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
1597
1598 /* Lay out icons a column at a time. */
1599 canvas_height = CANVAS_HEIGHT(container, allocation);
1600
1601 max_icon_width = max_text_width = 0.0;
1602 max_icon_height = max_text_height = 0.0;
1603 max_bounds_height = 0.0;
1604
1605 get_max_icon_dimensions (icons, NULL,
1606 &max_icon_width, &max_icon_height,
1607 &max_text_width, &max_text_height,
1608 &max_bounds_height);
1609
1610 max_width = max_icon_width + max_text_width;
1611 max_height = MAX (max_icon_height, max_text_height);
1612 max_height_with_borders = ICON_PAD_TOP + max_height;
1613
1614 max_bounds_height_with_borders = ICON_PAD_TOP + max_bounds_height;
1615
1616 line_height = ICON_PAD_TOP;
1617 line_start = icons;
1618 x = 0;
1619 i = 0;
1620
1621 max_width_in_column = 0.0;
1622
1623 for (p = icons; p != NULL; p = p->next)
1624 {
1625 int height;
1626
1627 icon = p->data;
1628
1629 /* If this icon doesn't fit, it's time to lay out the column that's queued up. */
1630
1631 /* We use the bounds height here, since for wrapping we also want to consider
1632 * overlapping emblems at the bottom. We may wrap a little bit too early since
1633 * the icon with the max. bounds height may actually not be in the last row, but
1634 * it is better than visual glitches
1635 */
1636 if (line_start != p && line_height + (max_bounds_height_with_borders-1) >= canvas_height )
1637 {
1638 x += ICON_PAD_LEFT;
1639
1640 /* correctly set (per-column) width */
1641 if (!container->details->all_columns_same_width)
1642 {
1643 for (i = 0; i < (int) positions->len; i++)
1644 {
1645 position = &g_array_index (positions, IconPositions, i);
1646 position->width = max_width_in_column;
1647 }
1648 }
1649
1650 lay_down_one_column (container, line_start, p, x, CONTAINER_PAD_TOP, max_height_with_borders, positions);
1651
1652 /* Advance to next column. */
1653 if (container->details->all_columns_same_width)
1654 {
1655 x += max_width + ICON_PAD_RIGHT;
1656 }
1657 else
1658 {
1659 x += max_width_in_column + ICON_PAD_RIGHT;
1660 }
1661
1662 line_height = ICON_PAD_TOP;
1663 line_start = p;
1664 i = 0;
1665
1666 max_width_in_column = 0;
1667 }
1668
1669 icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
1670 text_bounds = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
1671
1672 max_width_in_column = MAX (max_width_in_column,
1673 ceil (icon_bounds.x1 - icon_bounds.x0) +
1674 ceil (text_bounds.x1 - text_bounds.x0));
1675
1676 g_array_set_size (positions, i + 1);
1677 position = &g_array_index (positions, IconPositions, i++);
1678 if (container->details->all_columns_same_width)
1679 {
1680 position->width = max_width;
1681 }
1682 position->height = max_height;
1683 position->y_offset = ICON_PAD_TOP;
1684 position->x_offset = ICON_PAD_LEFT;
1685
1686 position->x_offset += max_icon_width - ceil (icon_bounds.x1 - icon_bounds.x0);
1687
1688 height = MAX (ceil (icon_bounds.y1 - icon_bounds.y0), ceil(text_bounds.y1 - text_bounds.y0));
1689 position->y_offset += (max_height - height) / 2;
1690
1691 /* Add this icon. */
1692 line_height += max_height_with_borders;
1693 }
1694
1695 /* Lay down that last column of icons. */
1696 if (line_start != NULL)
1697 {
1698 x += ICON_PAD_LEFT;
1699 lay_down_one_column (container, line_start, NULL, x, CONTAINER_PAD_TOP, max_height_with_borders, positions);
1700 }
1701
1702 g_array_free (positions, TRUE);
1703 }
1704
1705 static void
snap_position(CajaIconContainer * container,CajaIcon * icon,int * x,int * y)1706 snap_position (CajaIconContainer *container,
1707 CajaIcon *icon,
1708 int *x, int *y)
1709 {
1710 int center_x;
1711 int baseline_y;
1712 int icon_width;
1713 int icon_height;
1714 int total_width;
1715 int total_height;
1716 EelDRect icon_position;
1717 GtkAllocation allocation;
1718
1719 icon_position = caja_icon_canvas_item_get_icon_rectangle (icon->item);
1720 icon_width = icon_position.x1 - icon_position.x0;
1721 icon_height = icon_position.y1 - icon_position.y0;
1722
1723 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
1724 total_width = CANVAS_WIDTH (container, allocation);
1725 total_height = CANVAS_HEIGHT (container, allocation);
1726
1727 if (caja_icon_container_is_layout_rtl (container))
1728 *x = get_mirror_x_position (container, icon, *x);
1729
1730 if (*x + icon_width / 2 < DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X)
1731 {
1732 *x = DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X - icon_width / 2;
1733 }
1734
1735 if (*x + icon_width / 2 > total_width - (DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X))
1736 {
1737 *x = total_width - (DESKTOP_PAD_HORIZONTAL + SNAP_SIZE_X + (icon_width / 2));
1738 }
1739
1740 if (*y + icon_height < DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y)
1741 {
1742 *y = DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y - icon_height;
1743 }
1744
1745 if (*y + icon_height > total_height - (DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y))
1746 {
1747 *y = total_height - (DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y + (icon_height / 2));
1748 }
1749
1750 center_x = *x + icon_width / 2;
1751 *x = SNAP_NEAREST_HORIZONTAL (center_x) - (icon_width / 2);
1752 if (caja_icon_container_is_layout_rtl (container))
1753 {
1754 *x = get_mirror_x_position (container, icon, *x);
1755 }
1756
1757
1758 /* Find the grid position vertically and place on the proper baseline */
1759 baseline_y = *y + icon_height;
1760 baseline_y = SNAP_NEAREST_VERTICAL (baseline_y);
1761 *y = baseline_y - icon_height;
1762 }
1763
1764 static int
compare_icons_by_position(gconstpointer a,gconstpointer b)1765 compare_icons_by_position (gconstpointer a, gconstpointer b)
1766 {
1767 CajaIcon *icon_a, *icon_b;
1768 int x1, y1, x2, y2;
1769 int center_a;
1770 int center_b;
1771
1772 icon_a = (CajaIcon*)a;
1773 icon_b = (CajaIcon*)b;
1774
1775 icon_get_bounding_box (icon_a, &x1, &y1, &x2, &y2,
1776 BOUNDS_USAGE_FOR_DISPLAY);
1777 center_a = x1 + (x2 - x1) / 2;
1778 icon_get_bounding_box (icon_b, &x1, &y1, &x2, &y2,
1779 BOUNDS_USAGE_FOR_DISPLAY);
1780 center_b = x1 + (x2 - x1) / 2;
1781
1782 return center_a == center_b ?
1783 icon_a->y - icon_b->y :
1784 center_a - center_b;
1785 }
1786
1787 static PlacementGrid *
placement_grid_new(CajaIconContainer * container,gboolean tight)1788 placement_grid_new (CajaIconContainer *container, gboolean tight)
1789 {
1790 PlacementGrid *grid;
1791 int width, height;
1792 int num_columns;
1793 int num_rows;
1794 int i;
1795 GtkAllocation allocation;
1796
1797 /* Get container dimensions */
1798 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
1799 width = CANVAS_WIDTH(container, allocation);
1800 height = CANVAS_HEIGHT(container, allocation);
1801
1802 num_columns = width / SNAP_SIZE_X;
1803 num_rows = height / SNAP_SIZE_Y;
1804
1805 if (num_columns == 0 || num_rows == 0)
1806 {
1807 return NULL;
1808 }
1809
1810 grid = g_new0 (PlacementGrid, 1);
1811 grid->tight = tight;
1812 grid->num_columns = num_columns;
1813 grid->num_rows = num_rows;
1814
1815 grid->grid_memory = g_new0 (int, (num_rows * num_columns));
1816 grid->icon_grid = g_new0 (int *, num_columns);
1817
1818 for (i = 0; i < num_columns; i++)
1819 {
1820 grid->icon_grid[i] = grid->grid_memory + (i * num_rows);
1821 }
1822
1823 return grid;
1824 }
1825
1826 static void
placement_grid_free(PlacementGrid * grid)1827 placement_grid_free (PlacementGrid *grid)
1828 {
1829 g_free (grid->icon_grid);
1830 g_free (grid->grid_memory);
1831 g_free (grid);
1832 }
1833
1834 static gboolean
placement_grid_position_is_free(PlacementGrid * grid,EelIRect pos)1835 placement_grid_position_is_free (PlacementGrid *grid, EelIRect pos)
1836 {
1837 int x, y;
1838
1839 g_assert (pos.x0 >= 0 && pos.x0 < grid->num_columns);
1840 g_assert (pos.y0 >= 0 && pos.y0 < grid->num_rows);
1841 g_assert (pos.x1 >= 0 && pos.x1 < grid->num_columns);
1842 g_assert (pos.y1 >= 0 && pos.y1 < grid->num_rows);
1843
1844 for (x = pos.x0; x <= pos.x1; x++)
1845 {
1846 for (y = pos.y0; y <= pos.y1; y++)
1847 {
1848 if (grid->icon_grid[x][y] != 0)
1849 {
1850 return FALSE;
1851 }
1852 }
1853 }
1854
1855 return TRUE;
1856 }
1857
1858 static void
placement_grid_mark(PlacementGrid * grid,EelIRect pos)1859 placement_grid_mark (PlacementGrid *grid, EelIRect pos)
1860 {
1861 int x, y;
1862
1863 g_assert (pos.x0 >= 0 && pos.x0 < grid->num_columns);
1864 g_assert (pos.y0 >= 0 && pos.y0 < grid->num_rows);
1865 g_assert (pos.x1 >= 0 && pos.x1 < grid->num_columns);
1866 g_assert (pos.y1 >= 0 && pos.y1 < grid->num_rows);
1867
1868 for (x = pos.x0; x <= pos.x1; x++)
1869 {
1870 for (y = pos.y0; y <= pos.y1; y++)
1871 {
1872 grid->icon_grid[x][y] = 1;
1873 }
1874 }
1875 }
1876
1877 static void
canvas_position_to_grid_position(PlacementGrid * grid,EelIRect canvas_position,EelIRect * grid_position)1878 canvas_position_to_grid_position (PlacementGrid *grid,
1879 EelIRect canvas_position,
1880 EelIRect *grid_position)
1881 {
1882 /* The first causes minimal moving around during a snap, but
1883 * can end up with partially overlapping icons. The second one won't
1884 * allow any overlapping, but can cause more movement to happen
1885 * during a snap. */
1886 if (grid->tight)
1887 {
1888 grid_position->x0 = ceil ((double)(canvas_position.x0 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
1889 grid_position->y0 = ceil ((double)(canvas_position.y0 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
1890 grid_position->x1 = floor ((double)(canvas_position.x1 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
1891 grid_position->y1 = floor ((double)(canvas_position.y1 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
1892 }
1893 else
1894 {
1895 grid_position->x0 = floor ((double)(canvas_position.x0 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
1896 grid_position->y0 = floor ((double)(canvas_position.y0 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
1897 grid_position->x1 = floor ((double)(canvas_position.x1 - DESKTOP_PAD_HORIZONTAL) / SNAP_SIZE_X);
1898 grid_position->y1 = floor ((double)(canvas_position.y1 - DESKTOP_PAD_VERTICAL) / SNAP_SIZE_Y);
1899 }
1900
1901 grid_position->x0 = CLAMP (grid_position->x0, 0, grid->num_columns - 1);
1902 grid_position->y0 = CLAMP (grid_position->y0, 0, grid->num_rows - 1);
1903 grid_position->x1 = CLAMP (grid_position->x1, grid_position->x0, grid->num_columns - 1);
1904 grid_position->y1 = CLAMP (grid_position->y1, grid_position->y0, grid->num_rows - 1);
1905 }
1906
1907 static void
placement_grid_mark_icon(PlacementGrid * grid,CajaIcon * icon)1908 placement_grid_mark_icon (PlacementGrid *grid, CajaIcon *icon)
1909 {
1910 EelIRect icon_pos;
1911 EelIRect grid_pos;
1912
1913 icon_get_bounding_box (icon,
1914 &icon_pos.x0, &icon_pos.y0,
1915 &icon_pos.x1, &icon_pos.y1,
1916 BOUNDS_USAGE_FOR_LAYOUT);
1917 canvas_position_to_grid_position (grid,
1918 icon_pos,
1919 &grid_pos);
1920 placement_grid_mark (grid, grid_pos);
1921 }
1922
1923 static void
find_empty_location(CajaIconContainer * container,PlacementGrid * grid,CajaIcon * icon,int start_x,int start_y,int * x,int * y)1924 find_empty_location (CajaIconContainer *container,
1925 PlacementGrid *grid,
1926 CajaIcon *icon,
1927 int start_x,
1928 int start_y,
1929 int *x,
1930 int *y)
1931 {
1932 double icon_width, icon_height;
1933 int canvas_width;
1934 int canvas_height;
1935 int height_for_bound_check;
1936 EelIRect icon_position;
1937 EelDRect pixbuf_rect;
1938 gboolean collision;
1939 GtkAllocation allocation;
1940
1941 /* Get container dimensions */
1942 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
1943 canvas_width = CANVAS_WIDTH(container, allocation);
1944 canvas_height = CANVAS_HEIGHT(container, allocation);
1945
1946 icon_get_bounding_box (icon,
1947 &icon_position.x0, &icon_position.y0,
1948 &icon_position.x1, &icon_position.y1,
1949 BOUNDS_USAGE_FOR_LAYOUT);
1950 icon_width = icon_position.x1 - icon_position.x0;
1951 icon_height = icon_position.y1 - icon_position.y0;
1952
1953 icon_get_bounding_box (icon,
1954 NULL, &icon_position.y0,
1955 NULL, &icon_position.y1,
1956 BOUNDS_USAGE_FOR_ENTIRE_ITEM);
1957 height_for_bound_check = icon_position.y1 - icon_position.y0;
1958
1959 pixbuf_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
1960
1961 /* Start the icon on a grid location */
1962 snap_position (container, icon, &start_x, &start_y);
1963
1964 icon_position.x0 = start_x;
1965 icon_position.y0 = start_y;
1966 icon_position.x1 = icon_position.x0 + icon_width;
1967 icon_position.y1 = icon_position.y0 + icon_height;
1968
1969 do
1970 {
1971 EelIRect grid_position;
1972 gboolean need_new_column;
1973
1974 collision = FALSE;
1975
1976 canvas_position_to_grid_position (grid,
1977 icon_position,
1978 &grid_position);
1979
1980 need_new_column = icon_position.y0 + height_for_bound_check + DESKTOP_PAD_VERTICAL > canvas_height;
1981
1982 if (need_new_column ||
1983 !placement_grid_position_is_free (grid, grid_position))
1984 {
1985 icon_position.y0 += SNAP_SIZE_Y;
1986 icon_position.y1 = icon_position.y0 + icon_height;
1987
1988 if (need_new_column)
1989 {
1990 /* Move to the next column */
1991 icon_position.y0 = DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y - (pixbuf_rect.y1 - pixbuf_rect.y0);
1992 while (icon_position.y0 < DESKTOP_PAD_VERTICAL)
1993 {
1994 icon_position.y0 += SNAP_SIZE_Y;
1995 }
1996 icon_position.y1 = icon_position.y0 + icon_height;
1997
1998 icon_position.x0 += SNAP_SIZE_X;
1999 icon_position.x1 = icon_position.x0 + icon_width;
2000 }
2001
2002 collision = TRUE;
2003 }
2004 }
2005 while (collision && (icon_position.x1 < canvas_width));
2006
2007 *x = icon_position.x0;
2008 *y = icon_position.y0;
2009 }
2010
2011 static void
align_icons(CajaIconContainer * container)2012 align_icons (CajaIconContainer *container)
2013 {
2014 GList *unplaced_icons;
2015 GList *l;
2016 PlacementGrid *grid;
2017
2018 unplaced_icons = g_list_copy (container->details->icons);
2019
2020 unplaced_icons = g_list_sort (unplaced_icons,
2021 compare_icons_by_position);
2022
2023 if (caja_icon_container_is_layout_rtl (container))
2024 {
2025 unplaced_icons = g_list_reverse (unplaced_icons);
2026 }
2027
2028 grid = placement_grid_new (container, TRUE);
2029
2030 if (!grid)
2031 {
2032 g_list_free (unplaced_icons);
2033 return;
2034 }
2035
2036 for (l = unplaced_icons; l != NULL; l = l->next)
2037 {
2038 CajaIcon *icon;
2039 int x, y;
2040
2041 icon = l->data;
2042 x = icon->saved_ltr_x;
2043 y = icon->y;
2044 find_empty_location (container, grid,
2045 icon, x, y, &x, &y);
2046
2047 icon_set_position (icon, x, y);
2048 icon->saved_ltr_x = icon->x;
2049 placement_grid_mark_icon (grid, icon);
2050 }
2051
2052 g_list_free (unplaced_icons);
2053
2054 placement_grid_free (grid);
2055
2056 if (caja_icon_container_is_layout_rtl (container))
2057 {
2058 caja_icon_container_set_rtl_positions (container);
2059 }
2060 }
2061
2062 static double
get_mirror_x_position(CajaIconContainer * container,CajaIcon * icon,double x)2063 get_mirror_x_position (CajaIconContainer *container, CajaIcon *icon, double x)
2064 {
2065 EelDRect icon_bounds;
2066 GtkAllocation allocation;
2067
2068 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
2069 icon_bounds = caja_icon_canvas_item_get_icon_rectangle (icon->item);
2070
2071 return CANVAS_WIDTH(container, allocation) - x - (icon_bounds.x1 - icon_bounds.x0);
2072 }
2073
2074 static void
caja_icon_container_set_rtl_positions(CajaIconContainer * container)2075 caja_icon_container_set_rtl_positions (CajaIconContainer *container)
2076 {
2077 GList *l;
2078 CajaIcon *icon = NULL;
2079
2080 if (!container->details->icons)
2081 {
2082 return;
2083 }
2084
2085 for (l = container->details->icons; l != NULL; l = l->next)
2086 {
2087 double x;
2088
2089 icon = l->data;
2090 x = get_mirror_x_position (container, icon, icon->saved_ltr_x);
2091 icon_set_position (icon, x, icon->y);
2092 }
2093 }
2094
2095 static void
lay_down_icons_vertical_desktop(CajaIconContainer * container,GList * icons)2096 lay_down_icons_vertical_desktop (CajaIconContainer *container, GList *icons)
2097 {
2098 GList *p, *placed_icons, *unplaced_icons;
2099 int total, new_length, placed;
2100 CajaIcon *icon;
2101 int height;
2102 int x, y, x1, x2, y1, y2;
2103 EelDRect icon_rect;
2104 GtkAllocation allocation;
2105
2106 /* Get container dimensions */
2107 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
2108 height = CANVAS_HEIGHT(container, allocation);
2109
2110 /* Determine which icons have and have not been placed */
2111 placed_icons = NULL;
2112 unplaced_icons = NULL;
2113
2114 total = g_list_length (container->details->icons);
2115 new_length = g_list_length (icons);
2116 placed = total - new_length;
2117 if (placed > 0)
2118 {
2119 PlacementGrid *grid;
2120 /* Add only placed icons in list */
2121 for (p = container->details->icons; p != NULL; p = p->next)
2122 {
2123 icon = p->data;
2124 if (icon_is_positioned (icon))
2125 {
2126 icon_set_position(icon, icon->saved_ltr_x, icon->y);
2127 placed_icons = g_list_prepend (placed_icons, icon);
2128 }
2129 else
2130 {
2131 icon->x = 0;
2132 icon->y = 0;
2133 unplaced_icons = g_list_prepend (unplaced_icons, icon);
2134 }
2135 }
2136 placed_icons = g_list_reverse (placed_icons);
2137 unplaced_icons = g_list_reverse (unplaced_icons);
2138
2139 grid = placement_grid_new (container, FALSE);
2140
2141 if (grid)
2142 {
2143 for (p = placed_icons; p != NULL; p = p->next)
2144 {
2145 placement_grid_mark_icon
2146 (grid, (CajaIcon*)p->data);
2147 }
2148
2149 /* Place unplaced icons in the best locations */
2150 for (p = unplaced_icons; p != NULL; p = p->next)
2151 {
2152 icon = p->data;
2153
2154 icon_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
2155
2156 /* Start the icon in the first column */
2157 x = DESKTOP_PAD_HORIZONTAL + (SNAP_SIZE_X / 2) - ((icon_rect.x1 - icon_rect.x0) / 2);
2158 y = DESKTOP_PAD_VERTICAL + SNAP_SIZE_Y - (icon_rect.y1 - icon_rect.y0);
2159
2160 find_empty_location (container,
2161 grid,
2162 icon,
2163 x, y,
2164 &x, &y);
2165
2166 icon_set_position (icon, x, y);
2167 icon->saved_ltr_x = x;
2168 placement_grid_mark_icon (grid, icon);
2169 }
2170
2171 placement_grid_free (grid);
2172 }
2173
2174 g_list_free (placed_icons);
2175 g_list_free (unplaced_icons);
2176 }
2177 else
2178 {
2179 /* There are no placed icons. Just lay them down using our rules */
2180 x = DESKTOP_PAD_HORIZONTAL;
2181
2182 while (icons != NULL)
2183 {
2184 int max_width, column_width, icon_height;
2185 int center_x;
2186 int baseline;
2187 int icon_height_for_bound_check;
2188 gboolean should_snap;
2189
2190 should_snap = !(container->details->tighter_layout && !container->details->keep_aligned);
2191
2192 y = DESKTOP_PAD_VERTICAL;
2193
2194 max_width = 0;
2195
2196 /* Calculate max width for column */
2197 for (p = icons; p != NULL; p = p->next)
2198 {
2199 int icon_width;
2200
2201 icon = p->data;
2202
2203 icon_get_bounding_box (icon, &x1, &y1, &x2, &y2,
2204 BOUNDS_USAGE_FOR_LAYOUT);
2205 icon_width = x2 - x1;
2206 icon_height = y2 - y1;
2207
2208 icon_get_bounding_box (icon, NULL, &y1, NULL, &y2,
2209 BOUNDS_USAGE_FOR_ENTIRE_ITEM);
2210 icon_height_for_bound_check = y2 - y1;
2211
2212 if (should_snap)
2213 {
2214 /* Snap the baseline to a grid position */
2215 icon_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
2216 baseline = y + (icon_rect.y1 - icon_rect.y0);
2217 baseline = SNAP_CEIL_VERTICAL (baseline);
2218 y = baseline - (icon_rect.y1 - icon_rect.y0);
2219 }
2220
2221 /* Check and see if we need to move to a new column */
2222 if (y != DESKTOP_PAD_VERTICAL && y + icon_height_for_bound_check > height)
2223 {
2224 break;
2225 }
2226
2227 if (max_width < icon_width)
2228 {
2229 max_width = icon_width;
2230 }
2231
2232 y += icon_height + DESKTOP_PAD_VERTICAL;
2233 }
2234
2235 y = DESKTOP_PAD_VERTICAL;
2236
2237 center_x = x + max_width / 2;
2238 column_width = max_width;
2239 if (should_snap)
2240 {
2241 /* Find the grid column to center on */
2242 center_x = SNAP_CEIL_HORIZONTAL (center_x);
2243 column_width = (center_x - x) + (max_width / 2);
2244 }
2245
2246 /* Lay out column */
2247 for (p = icons; p != NULL; p = p->next)
2248 {
2249 icon = p->data;
2250 icon_get_bounding_box (icon, &x1, &y1, &x2, &y2,
2251 BOUNDS_USAGE_FOR_LAYOUT);
2252 icon_height = y2 - y1;
2253
2254 icon_get_bounding_box (icon, NULL, &y1, NULL, &y2,
2255 BOUNDS_USAGE_FOR_ENTIRE_ITEM);
2256 icon_height_for_bound_check = y2 - y1;
2257
2258 icon_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
2259
2260 if (should_snap)
2261 {
2262 baseline = y + (icon_rect.y1 - icon_rect.y0);
2263 baseline = SNAP_CEIL_VERTICAL (baseline);
2264 y = baseline - (icon_rect.y1 - icon_rect.y0);
2265 }
2266
2267 /* Check and see if we need to move to a new column */
2268 if (y != DESKTOP_PAD_VERTICAL && y > height - icon_height_for_bound_check &&
2269 /* Make sure we lay out at least one icon per column, to make progress */
2270 p != icons)
2271 {
2272 x += column_width + DESKTOP_PAD_HORIZONTAL;
2273 break;
2274 }
2275
2276 icon_set_position (icon,
2277 center_x - (icon_rect.x1 - icon_rect.x0) / 2,
2278 y);
2279
2280 icon->saved_ltr_x = icon->x;
2281 y += icon_height + DESKTOP_PAD_VERTICAL;
2282 }
2283 icons = p;
2284 }
2285 }
2286
2287 /* These modes are special. We freeze all of our positions
2288 * after we do the layout.
2289 */
2290 /* FIXME bugzilla.gnome.org 42478:
2291 * This should not be tied to the direction of layout.
2292 * It should be a separate switch.
2293 */
2294 caja_icon_container_freeze_icon_positions (container);
2295 }
2296
2297
2298 static void
lay_down_icons(CajaIconContainer * container,GList * icons,double start_y)2299 lay_down_icons (CajaIconContainer *container, GList *icons, double start_y)
2300 {
2301 switch (container->details->layout_mode)
2302 {
2303 case CAJA_ICON_LAYOUT_L_R_T_B:
2304 case CAJA_ICON_LAYOUT_R_L_T_B:
2305 lay_down_icons_horizontal (container, icons, start_y);
2306 break;
2307
2308 case CAJA_ICON_LAYOUT_T_B_L_R:
2309 case CAJA_ICON_LAYOUT_T_B_R_L:
2310 if (caja_icon_container_get_is_desktop (container))
2311 {
2312 lay_down_icons_vertical_desktop (container, icons);
2313 }
2314 else
2315 {
2316 lay_down_icons_vertical (container, icons, start_y);
2317 }
2318 break;
2319
2320 default:
2321 g_assert_not_reached ();
2322 }
2323 }
2324
2325 static void
redo_layout_internal(CajaIconContainer * container)2326 redo_layout_internal (CajaIconContainer *container)
2327 {
2328 finish_adding_new_icons (container);
2329
2330 /* Don't do any re-laying-out during stretching. Later we
2331 * might add smart logic that does this and leaves room for
2332 * the stretched icon, but if we do it we want it to be fast
2333 * and only re-lay-out when it's really needed.
2334 */
2335 if (container->details->auto_layout
2336 && container->details->drag_state != DRAG_STATE_STRETCH)
2337 {
2338 resort (container);
2339 lay_down_icons (container, container->details->icons, 0);
2340 }
2341
2342 if (caja_icon_container_is_layout_rtl (container))
2343 {
2344 caja_icon_container_set_rtl_positions (container);
2345 }
2346
2347 caja_icon_container_update_scroll_region (container);
2348
2349 process_pending_icon_to_reveal (container);
2350 process_pending_icon_to_rename (container);
2351 caja_icon_container_update_visible_icons (container);
2352 }
2353
2354 static gboolean
redo_layout_callback(gpointer callback_data)2355 redo_layout_callback (gpointer callback_data)
2356 {
2357 CajaIconContainer *container;
2358
2359 container = CAJA_ICON_CONTAINER (callback_data);
2360 redo_layout_internal (container);
2361 container->details->idle_id = 0;
2362
2363 return FALSE;
2364 }
2365
2366 static void
unschedule_redo_layout(CajaIconContainer * container)2367 unschedule_redo_layout (CajaIconContainer *container)
2368 {
2369 if (container->details->idle_id != 0)
2370 {
2371 g_source_remove (container->details->idle_id);
2372 container->details->idle_id = 0;
2373 }
2374 }
2375
2376 static void
schedule_redo_layout(CajaIconContainer * container)2377 schedule_redo_layout (CajaIconContainer *container)
2378 {
2379 if (container->details->idle_id == 0
2380 && container->details->has_been_allocated)
2381 {
2382 container->details->idle_id = g_idle_add
2383 (redo_layout_callback, container);
2384 }
2385 }
2386
2387 static void
redo_layout(CajaIconContainer * container)2388 redo_layout (CajaIconContainer *container)
2389 {
2390 unschedule_redo_layout (container);
2391 redo_layout_internal (container);
2392 }
2393
2394 static void
reload_icon_positions(CajaIconContainer * container)2395 reload_icon_positions (CajaIconContainer *container)
2396 {
2397 GList *p, *no_position_icons;
2398 gboolean have_stored_position;
2399 CajaIconPosition position;
2400 EelDRect bounds;
2401 double bottom;
2402 EelCanvasItem *item;
2403 CajaIcon *icon = NULL;
2404
2405 g_assert (!container->details->auto_layout);
2406
2407 resort (container);
2408
2409 no_position_icons = NULL;
2410
2411 /* Place all the icons with positions. */
2412 bottom = 0;
2413 for (p = container->details->icons; p != NULL; p = p->next)
2414 {
2415 icon = p->data;
2416
2417 have_stored_position = FALSE;
2418 g_signal_emit (container,
2419 signals[GET_STORED_ICON_POSITION], 0,
2420 icon->data,
2421 &position,
2422 &have_stored_position);
2423 if (have_stored_position)
2424 {
2425 icon_set_position (icon, position.x, position.y);
2426 item = EEL_CANVAS_ITEM (icon->item);
2427 caja_icon_canvas_item_get_bounds_for_layout (icon->item,
2428 &bounds.x0,
2429 &bounds.y0,
2430 &bounds.x1,
2431 &bounds.y1);
2432 eel_canvas_item_i2w (item->parent,
2433 &bounds.x0,
2434 &bounds.y0);
2435 eel_canvas_item_i2w (item->parent,
2436 &bounds.x1,
2437 &bounds.y1);
2438 if (bounds.y1 > bottom)
2439 {
2440 bottom = bounds.y1;
2441 }
2442 }
2443 else
2444 {
2445 no_position_icons = g_list_prepend (no_position_icons, icon);
2446 }
2447 }
2448 no_position_icons = g_list_reverse (no_position_icons);
2449
2450 /* Place all the other icons. */
2451 lay_down_icons (container, no_position_icons, bottom + ICON_PAD_BOTTOM);
2452 g_list_free (no_position_icons);
2453 }
2454
2455 /* Container-level icon handling functions. */
2456
2457 static gboolean
button_event_modifies_selection(GdkEventButton * event)2458 button_event_modifies_selection (GdkEventButton *event)
2459 {
2460 return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
2461 }
2462
2463 /* invalidate the cached label sizes for all the icons */
2464 static void
invalidate_label_sizes(CajaIconContainer * container)2465 invalidate_label_sizes (CajaIconContainer *container)
2466 {
2467 GList *p;
2468 CajaIcon *icon = NULL;
2469
2470 for (p = container->details->icons; p != NULL; p = p->next)
2471 {
2472 icon = p->data;
2473
2474 caja_icon_canvas_item_invalidate_label_size (icon->item);
2475 }
2476 }
2477
2478 /* invalidate the entire labels (i.e. their attributes) for all the icons */
2479 static void
invalidate_labels(CajaIconContainer * container)2480 invalidate_labels (CajaIconContainer *container)
2481 {
2482 GList *p;
2483 CajaIcon *icon = NULL;
2484
2485 for (p = container->details->icons; p != NULL; p = p->next)
2486 {
2487 icon = p->data;
2488
2489 caja_icon_canvas_item_invalidate_label (icon->item);
2490 }
2491 }
2492
2493 static gboolean
select_range(CajaIconContainer * container,CajaIcon * icon1,CajaIcon * icon2,gboolean unselect_outside_range)2494 select_range (CajaIconContainer *container,
2495 CajaIcon *icon1,
2496 CajaIcon *icon2,
2497 gboolean unselect_outside_range)
2498 {
2499 gboolean selection_changed;
2500 GList *p;
2501 CajaIcon *icon = NULL;
2502 CajaIcon *unmatched_icon;
2503 gboolean select;
2504
2505 selection_changed = FALSE;
2506
2507 unmatched_icon = NULL;
2508 select = FALSE;
2509 for (p = container->details->icons; p != NULL; p = p->next)
2510 {
2511 icon = p->data;
2512
2513 if (unmatched_icon == NULL)
2514 {
2515 if (icon == icon1)
2516 {
2517 unmatched_icon = icon2;
2518 select = TRUE;
2519 }
2520 else if (icon == icon2)
2521 {
2522 unmatched_icon = icon1;
2523 select = TRUE;
2524 }
2525 }
2526
2527 if (select || unselect_outside_range)
2528 {
2529 selection_changed |= icon_set_selected
2530 (container, icon, select);
2531 }
2532
2533 if (unmatched_icon != NULL && icon == unmatched_icon)
2534 {
2535 select = FALSE;
2536 }
2537
2538 }
2539
2540 if (selection_changed && icon2 != NULL)
2541 {
2542 emit_atk_focus_state_change (icon2, TRUE);
2543 }
2544 return selection_changed;
2545 }
2546
2547
2548 static gboolean
select_one_unselect_others(CajaIconContainer * container,CajaIcon * icon_to_select)2549 select_one_unselect_others (CajaIconContainer *container,
2550 CajaIcon *icon_to_select)
2551 {
2552 gboolean selection_changed;
2553 GList *p;
2554 CajaIcon *icon = NULL;
2555
2556 selection_changed = FALSE;
2557
2558 for (p = container->details->icons; p != NULL; p = p->next)
2559 {
2560 icon = p->data;
2561
2562 selection_changed |= icon_set_selected
2563 (container, icon, icon == icon_to_select);
2564 }
2565
2566 if (selection_changed && icon_to_select != NULL)
2567 {
2568 emit_atk_focus_state_change (icon_to_select, TRUE);
2569 reveal_icon (container, icon_to_select);
2570 }
2571 return selection_changed;
2572 }
2573
2574 static gboolean
unselect_all(CajaIconContainer * container)2575 unselect_all (CajaIconContainer *container)
2576 {
2577 return select_one_unselect_others (container, NULL);
2578 }
2579
2580 void
caja_icon_container_move_icon(CajaIconContainer * container,CajaIcon * icon,int x,int y,double scale,gboolean raise,gboolean snap,gboolean update_position)2581 caja_icon_container_move_icon (CajaIconContainer *container,
2582 CajaIcon *icon,
2583 int x, int y,
2584 double scale,
2585 gboolean raise,
2586 gboolean snap,
2587 gboolean update_position)
2588 {
2589 CajaIconContainerDetails *details;
2590 gboolean emit_signal;
2591 CajaIconPosition position;
2592
2593 details = container->details;
2594
2595 emit_signal = FALSE;
2596
2597 if (icon == get_icon_being_renamed (container))
2598 {
2599 end_renaming_mode (container, TRUE);
2600 }
2601
2602 if (scale != icon->scale)
2603 {
2604 icon->scale = scale;
2605 caja_icon_container_update_icon (container, icon);
2606 if (update_position)
2607 {
2608 redo_layout (container);
2609 emit_signal = TRUE;
2610 }
2611 }
2612
2613 if (!details->auto_layout && !details->lock_icons_position)
2614 {
2615 if (details->keep_aligned && snap)
2616 {
2617 snap_position (container, icon, &x, &y);
2618 }
2619
2620 if (x != icon->x || y != icon->y)
2621 {
2622 icon_set_position (icon, x, y);
2623 emit_signal = update_position;
2624 }
2625
2626 icon->saved_ltr_x = caja_icon_container_is_layout_rtl (container) ? get_mirror_x_position (container, icon, icon->x) : icon->x;
2627 }
2628
2629 if (emit_signal)
2630 {
2631 position.x = icon->saved_ltr_x;
2632 position.y = icon->y;
2633 position.scale = scale;
2634 g_signal_emit (container,
2635 signals[ICON_POSITION_CHANGED], 0,
2636 icon->data, &position);
2637 }
2638
2639 if (raise)
2640 {
2641 icon_raise (icon);
2642 }
2643
2644 /* FIXME bugzilla.gnome.org 42474:
2645 * Handling of the scroll region is inconsistent here. In
2646 * the scale-changing case, redo_layout is called, which updates the
2647 * scroll region appropriately. In other cases, it's up to the
2648 * caller to make sure the scroll region is updated. This could
2649 * lead to hard-to-track-down bugs.
2650 */
2651 }
2652
2653 /* Implementation of rubberband selection. */
2654 static void
rubberband_select(CajaIconContainer * container,const EelDRect * previous_rect,const EelDRect * current_rect)2655 rubberband_select (CajaIconContainer *container,
2656 const EelDRect *previous_rect,
2657 const EelDRect *current_rect)
2658 {
2659 GList *p;
2660 gboolean selection_changed, is_in, canvas_rect_calculated;
2661 EelIRect canvas_rect;
2662 EelCanvas *canvas;
2663 CajaIcon *icon = NULL;
2664
2665 selection_changed = FALSE;
2666 canvas_rect_calculated = FALSE;
2667
2668 for (p = container->details->icons; p != NULL; p = p->next)
2669 {
2670 icon = p->data;
2671
2672 if (!canvas_rect_calculated)
2673 {
2674 /* Only do this calculation once, since all the canvas items
2675 * we are interating are in the same coordinate space
2676 */
2677 canvas = EEL_CANVAS_ITEM (icon->item)->canvas;
2678 eel_canvas_w2c (canvas,
2679 current_rect->x0,
2680 current_rect->y0,
2681 &canvas_rect.x0,
2682 &canvas_rect.y0);
2683 eel_canvas_w2c (canvas,
2684 current_rect->x1,
2685 current_rect->y1,
2686 &canvas_rect.x1,
2687 &canvas_rect.y1);
2688 canvas_rect_calculated = TRUE;
2689 }
2690
2691 is_in = caja_icon_canvas_item_hit_test_rectangle (icon->item, canvas_rect);
2692
2693 selection_changed |= icon_set_selected
2694 (container, icon,
2695 is_in ^ icon->was_selected_before_rubberband);
2696 }
2697
2698 if (selection_changed)
2699 {
2700 g_signal_emit (container,
2701 signals[SELECTION_CHANGED], 0);
2702 }
2703 }
2704
2705 static int
rubberband_timeout_callback(gpointer data)2706 rubberband_timeout_callback (gpointer data)
2707 {
2708 CajaIconContainer *container;
2709 GtkWidget *widget;
2710 CajaIconRubberbandInfo *band_info;
2711 int x, y;
2712 double x1, y1, x2, y2;
2713 double world_x, world_y;
2714 int x_scroll, y_scroll;
2715 int adj_x, adj_y;
2716 GdkDisplay *display;
2717 GdkSeat *seat;
2718 gboolean adj_changed;
2719 GtkAllocation allocation;
2720
2721 EelDRect selection_rect;
2722
2723 widget = GTK_WIDGET (data);
2724 container = CAJA_ICON_CONTAINER (data);
2725 band_info = &container->details->rubberband_info;
2726
2727 g_assert (band_info->timer_id != 0);
2728 g_assert (EEL_IS_CANVAS_RECT (band_info->selection_rectangle) ||
2729 EEL_IS_CANVAS_RECT (band_info->selection_rectangle));
2730
2731 adj_changed = FALSE;
2732 gtk_widget_get_allocation (widget, &allocation);
2733
2734 adj_x = gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
2735 if (adj_x != band_info->last_adj_x)
2736 {
2737 band_info->last_adj_x = adj_x;
2738 adj_changed = TRUE;
2739 }
2740
2741 adj_y = gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
2742 if (adj_y != band_info->last_adj_y)
2743 {
2744 band_info->last_adj_y = adj_y;
2745 adj_changed = TRUE;
2746 }
2747 display = gtk_widget_get_display (widget);
2748 seat = gdk_display_get_default_seat (display);
2749
2750 gdk_window_get_device_position (gtk_widget_get_window (widget),
2751 gdk_seat_get_pointer (seat),
2752 &x, &y, NULL);
2753
2754 if (x < 0)
2755 {
2756 x_scroll = x;
2757 x = 0;
2758 }
2759 else if (x >= allocation.width)
2760 {
2761 x_scroll = x - allocation.width + 1;
2762 x = allocation.width - 1;
2763 }
2764 else
2765 {
2766 x_scroll = 0;
2767 }
2768
2769 if (y < 0)
2770 {
2771 y_scroll = y;
2772 y = 0;
2773 }
2774 else if (y >= allocation.height)
2775 {
2776 y_scroll = y - allocation.height + 1;
2777 y = allocation.height - 1;
2778 }
2779 else
2780 {
2781 y_scroll = 0;
2782 }
2783
2784 if (y_scroll == 0 && x_scroll == 0
2785 && (int) band_info->prev_x == x && (int) band_info->prev_y == y && !adj_changed)
2786 {
2787 return TRUE;
2788 }
2789
2790 caja_icon_container_scroll (container, x_scroll, y_scroll);
2791
2792 /* Remember to convert from widget to scrolled window coords */
2793 eel_canvas_window_to_world (EEL_CANVAS (container),
2794 x + gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container))),
2795 y + gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container))),
2796 &world_x, &world_y);
2797
2798 if (world_x < band_info->start_x)
2799 {
2800 x1 = world_x;
2801 x2 = band_info->start_x;
2802 }
2803 else
2804 {
2805 x1 = band_info->start_x;
2806 x2 = world_x;
2807 }
2808
2809 if (world_y < band_info->start_y)
2810 {
2811 y1 = world_y;
2812 y2 = band_info->start_y;
2813 }
2814 else
2815 {
2816 y1 = band_info->start_y;
2817 y2 = world_y;
2818 }
2819
2820 /* Don't let the area of the selection rectangle be empty.
2821 * Aside from the fact that it would be funny when the rectangle disappears,
2822 * this also works around a crash in libart that happens sometimes when a
2823 * zero height rectangle is passed.
2824 */
2825 x2 = MAX (x1 + 1, x2);
2826 y2 = MAX (y1 + 1, y2);
2827
2828 eel_canvas_item_set
2829 (band_info->selection_rectangle,
2830 "x1", x1, "y1", y1,
2831 "x2", x2, "y2", y2,
2832 NULL);
2833
2834 selection_rect.x0 = x1;
2835 selection_rect.y0 = y1;
2836 selection_rect.x1 = x2;
2837 selection_rect.y1 = y2;
2838
2839 rubberband_select (container,
2840 &band_info->prev_rect,
2841 &selection_rect);
2842
2843 band_info->prev_x = x;
2844 band_info->prev_y = y;
2845
2846 band_info->prev_rect = selection_rect;
2847
2848 return TRUE;
2849 }
2850
2851 /*borrowed from Nemo, makes Caja rubberbanding follow same selectors as Nemo and presumably Nautilus */
2852 static void
start_rubberbanding(CajaIconContainer * container,GdkEventButton * event)2853 start_rubberbanding (CajaIconContainer *container,
2854 GdkEventButton *event)
2855 {
2856 AtkObject *accessible;
2857 CajaIconContainerDetails *details;
2858 CajaIconRubberbandInfo *band_info;
2859 GdkRGBA bg_color, border_color;
2860 GdkRGBA *c;
2861 GList *p;
2862 CajaIcon *icon;
2863 GtkStyleContext *context;
2864
2865 details = container->details;
2866 band_info = &details->rubberband_info;
2867
2868 g_signal_emit (container,
2869 signals[BAND_SELECT_STARTED], 0);
2870
2871 for (p = details->icons; p != NULL; p = p->next) {
2872 icon = p->data;
2873 icon->was_selected_before_rubberband = icon->is_selected;
2874 }
2875
2876 eel_canvas_window_to_world
2877 (EEL_CANVAS (container), event->x, event->y,
2878 &band_info->start_x, &band_info->start_y);
2879
2880 context = gtk_widget_get_style_context (GTK_WIDGET (container));
2881 gtk_style_context_save (context);
2882 gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
2883
2884 gtk_style_context_get (context, GTK_STATE_FLAG_NORMAL,
2885 GTK_STYLE_PROPERTY_BACKGROUND_COLOR,
2886 &c, NULL);
2887
2888 bg_color = *c;
2889
2890 gtk_style_context_get (context, GTK_STATE_FLAG_NORMAL,
2891 GTK_STYLE_PROPERTY_BORDER_COLOR,
2892 &c, NULL);
2893
2894 border_color = *c;
2895 gdk_rgba_free (c);
2896
2897 gtk_style_context_restore (context);
2898
2899 band_info->selection_rectangle = eel_canvas_item_new
2900 (eel_canvas_root
2901 (EEL_CANVAS (container)),
2902 EEL_TYPE_CANVAS_RECT,
2903 "x1", band_info->start_x,
2904 "y1", band_info->start_y,
2905 "x2", band_info->start_x,
2906 "y2", band_info->start_y,
2907 "fill_color_rgba", &bg_color,
2908 "outline_color_rgba", &border_color,
2909 "width_pixels", 1,
2910 NULL);
2911
2912 accessible = atk_gobject_accessible_for_object
2913 (G_OBJECT (band_info->selection_rectangle));
2914 atk_object_set_name (accessible, "selection");
2915 atk_object_set_description (accessible, _("The selection rectangle"));
2916
2917 band_info->prev_x = event->x - gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
2918 band_info->prev_y = event->y - gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
2919
2920 band_info->active = TRUE;
2921
2922 if (band_info->timer_id == 0) {
2923 band_info->timer_id = g_timeout_add
2924 (RUBBERBAND_TIMEOUT_INTERVAL,
2925 rubberband_timeout_callback,
2926 container);
2927 }
2928
2929 eel_canvas_item_grab (band_info->selection_rectangle,
2930 (GDK_POINTER_MOTION_MASK
2931 | GDK_BUTTON_RELEASE_MASK
2932 | GDK_SCROLL_MASK),
2933 NULL,
2934 (GdkEvent *)event);
2935 }
2936
2937 static void
stop_rubberbanding(CajaIconContainer * container)2938 stop_rubberbanding (CajaIconContainer *container)
2939 {
2940 CajaIconRubberbandInfo *band_info;
2941 GList *icons;
2942
2943 band_info = &container->details->rubberband_info;
2944
2945 g_assert (band_info->timer_id != 0);
2946 g_source_remove (band_info->timer_id);
2947 band_info->timer_id = 0;
2948
2949 band_info->active = FALSE;
2950
2951 /* Destroy this canvas item; the parent will unref it. */
2952 eel_canvas_item_ungrab (band_info->selection_rectangle);
2953 eel_canvas_item_destroy (band_info->selection_rectangle);
2954 band_info->selection_rectangle = NULL;
2955
2956 /* if only one item has been selected, use it as range
2957 * selection base (cf. handle_icon_button_press) */
2958 icons = caja_icon_container_get_selected_icons (container);
2959 if (g_list_length (icons) == 1)
2960 {
2961 container->details->range_selection_base_icon = icons->data;
2962 }
2963 g_list_free (icons);
2964
2965 g_signal_emit (container,
2966 signals[BAND_SELECT_ENDED], 0);
2967 }
2968
2969 /* Keyboard navigation. */
2970
2971 typedef gboolean (* IsBetterIconFunction) (CajaIconContainer *container,
2972 CajaIcon *start_icon,
2973 CajaIcon *best_so_far,
2974 CajaIcon *candidate,
2975 void *data);
2976
2977 static CajaIcon *
find_best_icon(CajaIconContainer * container,CajaIcon * start_icon,IsBetterIconFunction function,void * data)2978 find_best_icon (CajaIconContainer *container,
2979 CajaIcon *start_icon,
2980 IsBetterIconFunction function,
2981 void *data)
2982 {
2983 GList *p;
2984 CajaIcon *best, *candidate;
2985
2986 best = NULL;
2987 for (p = container->details->icons; p != NULL; p = p->next)
2988 {
2989 candidate = p->data;
2990
2991 if (candidate != start_icon)
2992 {
2993 if ((* function) (container, start_icon, best, candidate, data))
2994 {
2995 best = candidate;
2996 }
2997 }
2998 }
2999 return best;
3000 }
3001
3002 static CajaIcon *
find_best_selected_icon(CajaIconContainer * container,CajaIcon * start_icon,IsBetterIconFunction function,void * data)3003 find_best_selected_icon (CajaIconContainer *container,
3004 CajaIcon *start_icon,
3005 IsBetterIconFunction function,
3006 void *data)
3007 {
3008 GList *p;
3009 CajaIcon *best, *candidate;
3010
3011 best = NULL;
3012 for (p = container->details->icons; p != NULL; p = p->next)
3013 {
3014 candidate = p->data;
3015
3016 if (candidate != start_icon && candidate->is_selected)
3017 {
3018 if ((* function) (container, start_icon, best, candidate, data))
3019 {
3020 best = candidate;
3021 }
3022 }
3023 }
3024 return best;
3025 }
3026
3027 static int
compare_icons_by_uri(CajaIconContainer * container,CajaIcon * icon_a,CajaIcon * icon_b)3028 compare_icons_by_uri (CajaIconContainer *container,
3029 CajaIcon *icon_a,
3030 CajaIcon *icon_b)
3031 {
3032 char *uri_a, *uri_b;
3033 int result;
3034
3035 g_assert (CAJA_IS_ICON_CONTAINER (container));
3036 g_assert (icon_a != NULL);
3037 g_assert (icon_b != NULL);
3038 g_assert (icon_a != icon_b);
3039
3040 uri_a = caja_icon_container_get_icon_uri (container, icon_a);
3041 uri_b = caja_icon_container_get_icon_uri (container, icon_b);
3042 result = strcmp (uri_a, uri_b);
3043 g_assert (result != 0);
3044 g_free (uri_a);
3045 g_free (uri_b);
3046
3047 return result;
3048 }
3049
3050 static int
get_cmp_point_x(CajaIconContainer * container,EelDRect icon_rect)3051 get_cmp_point_x (CajaIconContainer *container,
3052 EelDRect icon_rect)
3053 {
3054 if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
3055 {
3056 if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
3057 {
3058 return icon_rect.x0;
3059 }
3060 else
3061 {
3062 return icon_rect.x1;
3063 }
3064 }
3065 else
3066 {
3067 return (icon_rect.x0 + icon_rect.x1) / 2;
3068 }
3069 }
3070
3071 static int
get_cmp_point_y(CajaIconContainer * container,EelDRect icon_rect)3072 get_cmp_point_y (CajaIconContainer *container,
3073 EelDRect icon_rect)
3074 {
3075 if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
3076 {
3077 return (icon_rect.y0 + icon_rect.y1)/2;
3078 }
3079 else
3080 {
3081 return icon_rect.y1;
3082 }
3083 }
3084
3085
3086 static int
compare_icons_horizontal(CajaIconContainer * container,CajaIcon * icon_a,CajaIcon * icon_b)3087 compare_icons_horizontal (CajaIconContainer *container,
3088 CajaIcon *icon_a,
3089 CajaIcon *icon_b)
3090 {
3091 EelDRect world_rect;
3092 int ax, bx;
3093
3094 world_rect = caja_icon_canvas_item_get_icon_rectangle (icon_a->item);
3095 eel_canvas_w2c
3096 (EEL_CANVAS (container),
3097 get_cmp_point_x (container, world_rect),
3098 get_cmp_point_y (container, world_rect),
3099 &ax,
3100 NULL);
3101 world_rect = caja_icon_canvas_item_get_icon_rectangle (icon_b->item);
3102 eel_canvas_w2c
3103 (EEL_CANVAS (container),
3104 get_cmp_point_x (container, world_rect),
3105 get_cmp_point_y (container, world_rect),
3106 &bx,
3107 NULL);
3108
3109 if (ax < bx)
3110 {
3111 return -1;
3112 }
3113 if (ax > bx)
3114 {
3115 return +1;
3116 }
3117 return 0;
3118 }
3119
3120 static int
compare_icons_vertical(CajaIconContainer * container,CajaIcon * icon_a,CajaIcon * icon_b)3121 compare_icons_vertical (CajaIconContainer *container,
3122 CajaIcon *icon_a,
3123 CajaIcon *icon_b)
3124 {
3125 EelDRect world_rect;
3126 int ay, by;
3127
3128 world_rect = caja_icon_canvas_item_get_icon_rectangle (icon_a->item);
3129 eel_canvas_w2c
3130 (EEL_CANVAS (container),
3131 get_cmp_point_x (container, world_rect),
3132 get_cmp_point_y (container, world_rect),
3133 NULL,
3134 &ay);
3135 world_rect = caja_icon_canvas_item_get_icon_rectangle (icon_b->item);
3136 eel_canvas_w2c
3137 (EEL_CANVAS (container),
3138 get_cmp_point_x (container, world_rect),
3139 get_cmp_point_y (container, world_rect),
3140 NULL,
3141 &by);
3142
3143 if (ay < by)
3144 {
3145 return -1;
3146 }
3147 if (ay > by)
3148 {
3149 return +1;
3150 }
3151 return 0;
3152 }
3153
3154 static int
compare_icons_horizontal_first(CajaIconContainer * container,CajaIcon * icon_a,CajaIcon * icon_b)3155 compare_icons_horizontal_first (CajaIconContainer *container,
3156 CajaIcon *icon_a,
3157 CajaIcon *icon_b)
3158 {
3159 EelDRect world_rect;
3160 int ax, ay, bx, by;
3161
3162 world_rect = caja_icon_canvas_item_get_icon_rectangle (icon_a->item);
3163 eel_canvas_w2c
3164 (EEL_CANVAS (container),
3165 get_cmp_point_x (container, world_rect),
3166 get_cmp_point_y (container, world_rect),
3167 &ax,
3168 &ay);
3169 world_rect = caja_icon_canvas_item_get_icon_rectangle (icon_b->item);
3170 eel_canvas_w2c
3171 (EEL_CANVAS (container),
3172 get_cmp_point_x (container, world_rect),
3173 get_cmp_point_y (container, world_rect),
3174 &bx,
3175 &by);
3176
3177 if (ax < bx)
3178 {
3179 return -1;
3180 }
3181 if (ax > bx)
3182 {
3183 return +1;
3184 }
3185 if (ay < by)
3186 {
3187 return -1;
3188 }
3189 if (ay > by)
3190 {
3191 return +1;
3192 }
3193 return compare_icons_by_uri (container, icon_a, icon_b);
3194 }
3195
3196 static int
compare_icons_vertical_first(CajaIconContainer * container,CajaIcon * icon_a,CajaIcon * icon_b)3197 compare_icons_vertical_first (CajaIconContainer *container,
3198 CajaIcon *icon_a,
3199 CajaIcon *icon_b)
3200 {
3201 EelDRect world_rect;
3202 int ax, ay, bx, by;
3203
3204 world_rect = caja_icon_canvas_item_get_icon_rectangle (icon_a->item);
3205 eel_canvas_w2c
3206 (EEL_CANVAS (container),
3207 get_cmp_point_x (container, world_rect),
3208 get_cmp_point_y (container, world_rect),
3209 &ax,
3210 &ay);
3211 world_rect = caja_icon_canvas_item_get_icon_rectangle (icon_b->item);
3212 eel_canvas_w2c
3213 (EEL_CANVAS (container),
3214 get_cmp_point_x (container, world_rect),
3215 get_cmp_point_y (container, world_rect),
3216 &bx,
3217 &by);
3218
3219 if (ay < by)
3220 {
3221 return -1;
3222 }
3223 if (ay > by)
3224 {
3225 return +1;
3226 }
3227 if (ax < bx)
3228 {
3229 return -1;
3230 }
3231 if (ax > bx)
3232 {
3233 return +1;
3234 }
3235 return compare_icons_by_uri (container, icon_a, icon_b);
3236 }
3237
3238 static gboolean
leftmost_in_top_row(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3239 leftmost_in_top_row (CajaIconContainer *container,
3240 CajaIcon *start_icon,
3241 CajaIcon *best_so_far,
3242 CajaIcon *candidate,
3243 void *data)
3244 {
3245 if (best_so_far == NULL)
3246 {
3247 return TRUE;
3248 }
3249 return compare_icons_vertical_first (container, best_so_far, candidate) > 0;
3250 }
3251
3252 static gboolean
rightmost_in_top_row(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3253 rightmost_in_top_row (CajaIconContainer *container,
3254 CajaIcon *start_icon,
3255 CajaIcon *best_so_far,
3256 CajaIcon *candidate,
3257 void *data)
3258 {
3259 if (best_so_far == NULL)
3260 {
3261 return TRUE;
3262 }
3263 return ((compare_icons_vertical (container, best_so_far, candidate) > 0) &&
3264 (compare_icons_horizontal (container, best_so_far, candidate) < 0));
3265 }
3266
3267 static gboolean
rightmost_in_bottom_row(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3268 rightmost_in_bottom_row (CajaIconContainer *container,
3269 CajaIcon *start_icon,
3270 CajaIcon *best_so_far,
3271 CajaIcon *candidate,
3272 void *data)
3273 {
3274 if (best_so_far == NULL)
3275 {
3276 return TRUE;
3277 }
3278 return compare_icons_vertical_first (container, best_so_far, candidate) < 0;
3279 }
3280
3281 static int
compare_with_start_row(CajaIconContainer * container,CajaIcon * icon)3282 compare_with_start_row (CajaIconContainer *container,
3283 CajaIcon *icon)
3284 {
3285 EelCanvasItem *item;
3286
3287 item = EEL_CANVAS_ITEM (icon->item);
3288
3289 if (container->details->arrow_key_start_y < item->y1)
3290 {
3291 return -1;
3292 }
3293 if (container->details->arrow_key_start_y > item->y2)
3294 {
3295 return +1;
3296 }
3297 return 0;
3298 }
3299
3300 static int
compare_with_start_column(CajaIconContainer * container,CajaIcon * icon)3301 compare_with_start_column (CajaIconContainer *container,
3302 CajaIcon *icon)
3303 {
3304 EelCanvasItem *item;
3305
3306 item = EEL_CANVAS_ITEM (icon->item);
3307
3308 if (container->details->arrow_key_start_x < item->x1)
3309 {
3310 return -1;
3311 }
3312 if (container->details->arrow_key_start_x > item->x2)
3313 {
3314 return +1;
3315 }
3316 return 0;
3317 }
3318
3319 static gboolean
same_row_right_side_leftmost(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3320 same_row_right_side_leftmost (CajaIconContainer *container,
3321 CajaIcon *start_icon,
3322 CajaIcon *best_so_far,
3323 CajaIcon *candidate,
3324 void *data)
3325 {
3326 /* Candidates not on the start row do not qualify. */
3327 if (compare_with_start_row (container, candidate) != 0)
3328 {
3329 return FALSE;
3330 }
3331
3332 /* Candidates that are farther right lose out. */
3333 if (best_so_far != NULL)
3334 {
3335 if (compare_icons_horizontal_first (container,
3336 best_so_far,
3337 candidate) < 0)
3338 {
3339 return FALSE;
3340 }
3341 }
3342
3343 /* Candidate to the left of the start do not qualify. */
3344 if (compare_icons_horizontal_first (container,
3345 candidate,
3346 start_icon) <= 0)
3347 {
3348 return FALSE;
3349 }
3350
3351 return TRUE;
3352 }
3353
3354 static gboolean
same_row_left_side_rightmost(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3355 same_row_left_side_rightmost (CajaIconContainer *container,
3356 CajaIcon *start_icon,
3357 CajaIcon *best_so_far,
3358 CajaIcon *candidate,
3359 void *data)
3360 {
3361 /* Candidates not on the start row do not qualify. */
3362 if (compare_with_start_row (container, candidate) != 0)
3363 {
3364 return FALSE;
3365 }
3366
3367 /* Candidates that are farther left lose out. */
3368 if (best_so_far != NULL)
3369 {
3370 if (compare_icons_horizontal_first (container,
3371 best_so_far,
3372 candidate) > 0)
3373 {
3374 return FALSE;
3375 }
3376 }
3377
3378 /* Candidate to the right of the start do not qualify. */
3379 if (compare_icons_horizontal_first (container,
3380 candidate,
3381 start_icon) >= 0)
3382 {
3383 return FALSE;
3384 }
3385
3386 return TRUE;
3387 }
3388
3389 static gboolean
next_row_leftmost(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3390 next_row_leftmost (CajaIconContainer *container,
3391 CajaIcon *start_icon,
3392 CajaIcon *best_so_far,
3393 CajaIcon *candidate,
3394 void *data)
3395 {
3396 /* sort out icons that are not below the current row */
3397 if (compare_with_start_row (container, candidate) >= 0)
3398 {
3399 return FALSE;
3400 }
3401
3402 if (best_so_far != NULL)
3403 {
3404 if (compare_icons_vertical_first (container,
3405 best_so_far,
3406 candidate) > 0)
3407 {
3408 /* candidate is above best choice, but below the current row */
3409 return TRUE;
3410 }
3411
3412 if (compare_icons_horizontal_first (container,
3413 best_so_far,
3414 candidate) > 0)
3415 {
3416 return TRUE;
3417 }
3418 }
3419
3420 return best_so_far == NULL;
3421 }
3422
3423 static gboolean
next_row_rightmost(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3424 next_row_rightmost (CajaIconContainer *container,
3425 CajaIcon *start_icon,
3426 CajaIcon *best_so_far,
3427 CajaIcon *candidate,
3428 void *data)
3429 {
3430 /* sort out icons that are not below the current row */
3431 if (compare_with_start_row (container, candidate) >= 0)
3432 {
3433 return FALSE;
3434 }
3435
3436 if (best_so_far != NULL)
3437 {
3438 if (compare_icons_vertical_first (container,
3439 best_so_far,
3440 candidate) > 0)
3441 {
3442 /* candidate is above best choice, but below the current row */
3443 return TRUE;
3444 }
3445
3446 if (compare_icons_horizontal_first (container,
3447 best_so_far,
3448 candidate) < 0)
3449 {
3450 return TRUE;
3451 }
3452 }
3453
3454 return best_so_far == NULL;
3455 }
3456
3457 static gboolean
next_column_bottommost(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3458 next_column_bottommost (CajaIconContainer *container,
3459 CajaIcon *start_icon,
3460 CajaIcon *best_so_far,
3461 CajaIcon *candidate,
3462 void *data)
3463 {
3464 /* sort out icons that are not on the right of the current column */
3465 if (compare_with_start_column (container, candidate) >= 0)
3466 {
3467 return FALSE;
3468 }
3469
3470 if (best_so_far != NULL)
3471 {
3472 if (compare_icons_horizontal_first (container,
3473 best_so_far,
3474 candidate) > 0)
3475 {
3476 /* candidate is above best choice, but below the current row */
3477 return TRUE;
3478 }
3479
3480 if (compare_icons_vertical_first (container,
3481 best_so_far,
3482 candidate) < 0)
3483 {
3484 return TRUE;
3485 }
3486 }
3487
3488 return best_so_far == NULL;
3489 }
3490
3491 static gboolean
previous_row_rightmost(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3492 previous_row_rightmost (CajaIconContainer *container,
3493 CajaIcon *start_icon,
3494 CajaIcon *best_so_far,
3495 CajaIcon *candidate,
3496 void *data)
3497 {
3498 /* sort out icons that are not above the current row */
3499 if (compare_with_start_row (container, candidate) <= 0)
3500 {
3501 return FALSE;
3502 }
3503
3504 if (best_so_far != NULL)
3505 {
3506 if (compare_icons_vertical_first (container,
3507 best_so_far,
3508 candidate) < 0)
3509 {
3510 /* candidate is below the best choice, but above the current row */
3511 return TRUE;
3512 }
3513
3514 if (compare_icons_horizontal_first (container,
3515 best_so_far,
3516 candidate) < 0)
3517 {
3518 return TRUE;
3519 }
3520 }
3521
3522 return best_so_far == NULL;
3523 }
3524
3525 static gboolean
same_column_above_lowest(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3526 same_column_above_lowest (CajaIconContainer *container,
3527 CajaIcon *start_icon,
3528 CajaIcon *best_so_far,
3529 CajaIcon *candidate,
3530 void *data)
3531 {
3532 /* Candidates not on the start column do not qualify. */
3533 if (compare_with_start_column (container, candidate) != 0)
3534 {
3535 return FALSE;
3536 }
3537
3538 /* Candidates that are higher lose out. */
3539 if (best_so_far != NULL)
3540 {
3541 if (compare_icons_vertical_first (container,
3542 best_so_far,
3543 candidate) > 0)
3544 {
3545 return FALSE;
3546 }
3547 }
3548
3549 /* Candidates below the start do not qualify. */
3550 if (compare_icons_vertical_first (container,
3551 candidate,
3552 start_icon) >= 0)
3553 {
3554 return FALSE;
3555 }
3556
3557 return TRUE;
3558 }
3559
3560 static gboolean
same_column_below_highest(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3561 same_column_below_highest (CajaIconContainer *container,
3562 CajaIcon *start_icon,
3563 CajaIcon *best_so_far,
3564 CajaIcon *candidate,
3565 void *data)
3566 {
3567 /* Candidates not on the start column do not qualify. */
3568 if (compare_with_start_column (container, candidate) != 0)
3569 {
3570 return FALSE;
3571 }
3572
3573 /* Candidates that are lower lose out. */
3574 if (best_so_far != NULL)
3575 {
3576 if (compare_icons_vertical_first (container,
3577 best_so_far,
3578 candidate) < 0)
3579 {
3580 return FALSE;
3581 }
3582 }
3583
3584 /* Candidates above the start do not qualify. */
3585 if (compare_icons_vertical_first (container,
3586 candidate,
3587 start_icon) <= 0)
3588 {
3589 return FALSE;
3590 }
3591
3592 return TRUE;
3593 }
3594
3595 static gboolean
previous_column_highest(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3596 previous_column_highest (CajaIconContainer *container,
3597 CajaIcon *start_icon,
3598 CajaIcon *best_so_far,
3599 CajaIcon *candidate,
3600 void *data)
3601 {
3602 /* sort out icons that are not before the current column */
3603 if (compare_with_start_column (container, candidate) <= 0)
3604 {
3605 return FALSE;
3606 }
3607
3608 if (best_so_far != NULL)
3609 {
3610 if (compare_icons_horizontal (container,
3611 best_so_far,
3612 candidate) < 0)
3613 {
3614 /* candidate is right of the best choice, but left of the current column */
3615 return TRUE;
3616 }
3617
3618 if (compare_icons_vertical (container,
3619 best_so_far,
3620 candidate) > 0)
3621 {
3622 return TRUE;
3623 }
3624 }
3625
3626 return best_so_far == NULL;
3627 }
3628
3629
3630 static gboolean
next_column_highest(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3631 next_column_highest (CajaIconContainer *container,
3632 CajaIcon *start_icon,
3633 CajaIcon *best_so_far,
3634 CajaIcon *candidate,
3635 void *data)
3636 {
3637 /* sort out icons that are not after the current column */
3638 if (compare_with_start_column (container, candidate) >= 0)
3639 {
3640 return FALSE;
3641 }
3642
3643 if (best_so_far != NULL)
3644 {
3645 if (compare_icons_horizontal_first (container,
3646 best_so_far,
3647 candidate) > 0)
3648 {
3649 /* candidate is left of the best choice, but right of the current column */
3650 return TRUE;
3651 }
3652
3653 if (compare_icons_vertical_first (container,
3654 best_so_far,
3655 candidate) > 0)
3656 {
3657 return TRUE;
3658 }
3659 }
3660
3661 return best_so_far == NULL;
3662 }
3663
3664 static gboolean
previous_column_lowest(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3665 previous_column_lowest (CajaIconContainer *container,
3666 CajaIcon *start_icon,
3667 CajaIcon *best_so_far,
3668 CajaIcon *candidate,
3669 void *data)
3670 {
3671 /* sort out icons that are not before the current column */
3672 if (compare_with_start_column (container, candidate) <= 0)
3673 {
3674 return FALSE;
3675 }
3676
3677 if (best_so_far != NULL)
3678 {
3679 if (compare_icons_horizontal_first (container,
3680 best_so_far,
3681 candidate) < 0)
3682 {
3683 /* candidate is right of the best choice, but left of the current column */
3684 return TRUE;
3685 }
3686
3687 if (compare_icons_vertical_first (container,
3688 best_so_far,
3689 candidate) < 0)
3690 {
3691 return TRUE;
3692 }
3693 }
3694
3695 return best_so_far == NULL;
3696 }
3697
3698 static gboolean
last_column_lowest(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3699 last_column_lowest (CajaIconContainer *container,
3700 CajaIcon *start_icon,
3701 CajaIcon *best_so_far,
3702 CajaIcon *candidate,
3703 void *data)
3704 {
3705 if (best_so_far == NULL)
3706 {
3707 return TRUE;
3708 }
3709 return compare_icons_horizontal_first (container, best_so_far, candidate) < 0;
3710 }
3711
3712 static gboolean
closest_in_90_degrees(CajaIconContainer * container,CajaIcon * start_icon,CajaIcon * best_so_far,CajaIcon * candidate,void * data)3713 closest_in_90_degrees (CajaIconContainer *container,
3714 CajaIcon *start_icon,
3715 CajaIcon *best_so_far,
3716 CajaIcon *candidate,
3717 void *data)
3718 {
3719 EelDRect world_rect;
3720 int x, y;
3721 int dx, dy;
3722 int dist;
3723 int *best_dist;
3724
3725
3726 world_rect = caja_icon_canvas_item_get_icon_rectangle (candidate->item);
3727 eel_canvas_w2c
3728 (EEL_CANVAS (container),
3729 get_cmp_point_x (container, world_rect),
3730 get_cmp_point_y (container, world_rect),
3731 &x,
3732 &y);
3733
3734 dx = x - container->details->arrow_key_start_x;
3735 dy = y - container->details->arrow_key_start_y;
3736
3737 switch (container->details->arrow_key_direction)
3738 {
3739 case GTK_DIR_UP:
3740 if (dy > 0 ||
3741 ABS(dx) > ABS(dy))
3742 {
3743 return FALSE;
3744 }
3745 break;
3746 case GTK_DIR_DOWN:
3747 if (dy < 0 ||
3748 ABS(dx) > ABS(dy))
3749 {
3750 return FALSE;
3751 }
3752 break;
3753 case GTK_DIR_LEFT:
3754 if (dx > 0 ||
3755 ABS(dy) > ABS(dx))
3756 {
3757 return FALSE;
3758 }
3759 break;
3760 case GTK_DIR_RIGHT:
3761 if (dx < 0 ||
3762 ABS(dy) > ABS(dx))
3763 {
3764 return FALSE;
3765 }
3766 break;
3767 default:
3768 g_assert_not_reached();
3769 }
3770
3771 dist = dx*dx + dy*dy;
3772 best_dist = data;
3773
3774 if (best_so_far == NULL)
3775 {
3776 *best_dist = dist;
3777 return TRUE;
3778 }
3779
3780 if (dist < *best_dist)
3781 {
3782 *best_dist = dist;
3783 return TRUE;
3784 }
3785
3786 return FALSE;
3787 }
3788
3789 static EelDRect
get_rubberband(CajaIcon * icon1,CajaIcon * icon2)3790 get_rubberband (CajaIcon *icon1,
3791 CajaIcon *icon2)
3792 {
3793 EelDRect rect1;
3794 EelDRect rect2;
3795 EelDRect ret;
3796
3797 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon1->item),
3798 &rect1.x0, &rect1.y0,
3799 &rect1.x1, &rect1.y1);
3800 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon2->item),
3801 &rect2.x0, &rect2.y0,
3802 &rect2.x1, &rect2.y1);
3803
3804 eel_drect_union (&ret, &rect1, &rect2);
3805
3806 return ret;
3807 }
3808
3809 static void
keyboard_move_to(CajaIconContainer * container,CajaIcon * icon,CajaIcon * from,GdkEventKey * event)3810 keyboard_move_to (CajaIconContainer *container,
3811 CajaIcon *icon,
3812 CajaIcon *from,
3813 GdkEventKey *event)
3814 {
3815 if (icon == NULL)
3816 {
3817 return;
3818 }
3819
3820 if (event != NULL &&
3821 (event->state & GDK_CONTROL_MASK) != 0 &&
3822 (event->state & GDK_SHIFT_MASK) == 0)
3823 {
3824 /* Move the keyboard focus. Use Control modifier
3825 * rather than Alt to avoid Sawfish conflict.
3826 */
3827 set_keyboard_focus (container, icon);
3828 container->details->keyboard_rubberband_start = NULL;
3829 }
3830 else if (event != NULL &&
3831 ((event->state & GDK_CONTROL_MASK) != 0 ||
3832 !container->details->auto_layout) &&
3833 (event->state & GDK_SHIFT_MASK) != 0)
3834 {
3835 /* Do rubberband selection */
3836 EelDRect rect;
3837
3838 if (from && !container->details->keyboard_rubberband_start)
3839 {
3840 set_keyboard_rubberband_start (container, from);
3841 }
3842
3843 set_keyboard_focus (container, icon);
3844
3845 if (icon && container->details->keyboard_rubberband_start)
3846 {
3847 rect = get_rubberband (container->details->keyboard_rubberband_start,
3848 icon);
3849 rubberband_select (container, NULL, &rect);
3850 }
3851 }
3852 else if (event != NULL &&
3853 (event->state & GDK_CONTROL_MASK) == 0 &&
3854 (event->state & GDK_SHIFT_MASK) != 0)
3855 {
3856 /* Select range */
3857 CajaIcon *start_icon;
3858
3859 start_icon = container->details->range_selection_base_icon;
3860 if (start_icon == NULL || !start_icon->is_selected)
3861 {
3862 start_icon = icon;
3863 container->details->range_selection_base_icon = icon;
3864 }
3865
3866 set_keyboard_focus (container, icon);
3867
3868 if (select_range (container, start_icon, icon, TRUE))
3869 {
3870 g_signal_emit (container,
3871 signals[SELECTION_CHANGED], 0);
3872 }
3873 }
3874 else
3875 {
3876 /* Select icons and get rid of the special keyboard focus. */
3877 clear_keyboard_focus (container);
3878 clear_keyboard_rubberband_start (container);
3879
3880 container->details->range_selection_base_icon = icon;
3881 if (select_one_unselect_others (container, icon))
3882 {
3883 g_signal_emit (container,
3884 signals[SELECTION_CHANGED], 0);
3885 }
3886 }
3887 schedule_keyboard_icon_reveal (container, icon);
3888 }
3889
3890 static void
keyboard_home(CajaIconContainer * container,GdkEventKey * event)3891 keyboard_home (CajaIconContainer *container,
3892 GdkEventKey *event)
3893 {
3894 CajaIcon *from;
3895 CajaIcon *to;
3896
3897 /* Home selects the first icon.
3898 * Control-Home sets the keyboard focus to the first icon.
3899 */
3900
3901 from = find_best_selected_icon (container, NULL,
3902 rightmost_in_bottom_row,
3903 NULL);
3904 to = find_best_icon (container, NULL, leftmost_in_top_row, NULL);
3905
3906 keyboard_move_to (container, to, from, event);
3907 }
3908
3909 static void
keyboard_end(CajaIconContainer * container,GdkEventKey * event)3910 keyboard_end (CajaIconContainer *container,
3911 GdkEventKey *event)
3912 {
3913 CajaIcon *to;
3914 CajaIcon *from;
3915
3916 /* End selects the last icon.
3917 * Control-End sets the keyboard focus to the last icon.
3918 */
3919 from = find_best_selected_icon (container, NULL,
3920 leftmost_in_top_row,
3921 NULL);
3922 to = find_best_icon (container, NULL,
3923 caja_icon_container_is_layout_vertical (container) ?
3924 last_column_lowest :
3925 rightmost_in_bottom_row,
3926 NULL);
3927
3928 keyboard_move_to (container, to, from, event);
3929 }
3930
3931 static void
record_arrow_key_start(CajaIconContainer * container,CajaIcon * icon,GtkDirectionType direction)3932 record_arrow_key_start (CajaIconContainer *container,
3933 CajaIcon *icon,
3934 GtkDirectionType direction)
3935 {
3936 EelDRect world_rect;
3937
3938 world_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
3939 eel_canvas_w2c
3940 (EEL_CANVAS (container),
3941 get_cmp_point_x (container, world_rect),
3942 get_cmp_point_y (container, world_rect),
3943 &container->details->arrow_key_start_x,
3944 &container->details->arrow_key_start_y);
3945 container->details->arrow_key_direction = direction;
3946 }
3947
3948 static void
keyboard_arrow_key(CajaIconContainer * container,GdkEventKey * event,GtkDirectionType direction,IsBetterIconFunction better_start,IsBetterIconFunction empty_start,IsBetterIconFunction better_destination,IsBetterIconFunction better_destination_fallback,IsBetterIconFunction better_destination_fallback_fallback,IsBetterIconFunction better_destination_manual)3949 keyboard_arrow_key (CajaIconContainer *container,
3950 GdkEventKey *event,
3951 GtkDirectionType direction,
3952 IsBetterIconFunction better_start,
3953 IsBetterIconFunction empty_start,
3954 IsBetterIconFunction better_destination,
3955 IsBetterIconFunction better_destination_fallback,
3956 IsBetterIconFunction better_destination_fallback_fallback,
3957 IsBetterIconFunction better_destination_manual)
3958 {
3959 CajaIcon *from;
3960 CajaIcon *to;
3961 int data;
3962
3963 /* Chose the icon to start with.
3964 * If we have a keyboard focus, start with it.
3965 * Otherwise, use the single selected icon.
3966 * If there's multiple selection, use the icon farthest toward the end.
3967 */
3968
3969 from = container->details->keyboard_focus;
3970
3971 if (from == NULL)
3972 {
3973 if (has_multiple_selection (container))
3974 {
3975 if (all_selected (container))
3976 {
3977 from = find_best_selected_icon
3978 (container, NULL,
3979 empty_start, NULL);
3980 }
3981 else
3982 {
3983 from = find_best_selected_icon
3984 (container, NULL,
3985 better_start, NULL);
3986 }
3987 }
3988 else
3989 {
3990 from = get_first_selected_icon (container);
3991 }
3992 }
3993
3994 /* If there's no icon, select the icon farthest toward the end.
3995 * If there is an icon, select the next icon based on the arrow direction.
3996 */
3997 if (from == NULL)
3998 {
3999 to = from = find_best_icon
4000 (container, NULL,
4001 empty_start, NULL);
4002 }
4003 else
4004 {
4005 record_arrow_key_start (container, from, direction);
4006
4007 to = find_best_icon
4008 (container, from,
4009 container->details->auto_layout ? better_destination : better_destination_manual,
4010 &data);
4011
4012 /* Wrap around to next/previous row/column */
4013 if (to == NULL &&
4014 better_destination_fallback != NULL) {
4015 to = find_best_icon
4016 (container, from,
4017 better_destination_fallback,
4018 &data);
4019 }
4020
4021 /* With a layout like
4022 * 1 2 3
4023 * 4
4024 * (horizontal layout)
4025 *
4026 * or
4027 *
4028 * 1 4
4029 * 2
4030 * 3
4031 * (vertical layout)
4032 *
4033 * * pressing down for any of 1,2,3 (horizontal layout)
4034 * * pressing right for any of 1,2,3 (vertical layout)
4035 *
4036 * Should select 4.
4037 */
4038 if (to == NULL &&
4039 container->details->auto_layout &&
4040 better_destination_fallback_fallback != NULL)
4041 {
4042 to = find_best_icon
4043 (container, from,
4044 better_destination_fallback_fallback,
4045 &data);
4046 }
4047
4048 if (to == NULL)
4049 {
4050 to = from;
4051 }
4052
4053 }
4054
4055 keyboard_move_to (container, to, from, event);
4056 }
4057
4058 static gboolean
is_rectangle_selection_event(GdkEventKey * event)4059 is_rectangle_selection_event (GdkEventKey *event)
4060 {
4061 return (event->state & GDK_CONTROL_MASK) != 0 &&
4062 (event->state & GDK_SHIFT_MASK) != 0;
4063 }
4064
4065 static void
keyboard_right(CajaIconContainer * container,GdkEventKey * event)4066 keyboard_right (CajaIconContainer *container,
4067 GdkEventKey *event)
4068 {
4069 IsBetterIconFunction fallback;
4070 IsBetterIconFunction next_column_fallback;
4071
4072 fallback = NULL;
4073 if (container->details->auto_layout &&
4074 !caja_icon_container_is_layout_vertical (container) &&
4075 !is_rectangle_selection_event (event))
4076 {
4077 fallback = next_row_leftmost;
4078 }
4079
4080 next_column_fallback = NULL;
4081 if (caja_icon_container_is_layout_vertical (container) &&
4082 gtk_widget_get_direction (GTK_WIDGET (container)) != GTK_TEXT_DIR_RTL)
4083 {
4084 next_column_fallback = next_column_bottommost;
4085 }
4086
4087 /* Right selects the next icon in the same row.
4088 * Control-Right sets the keyboard focus to the next icon in the same row.
4089 */
4090 keyboard_arrow_key (container,
4091 event,
4092 GTK_DIR_RIGHT,
4093 rightmost_in_bottom_row,
4094 caja_icon_container_is_layout_rtl (container) ?
4095 rightmost_in_top_row : leftmost_in_top_row,
4096 same_row_right_side_leftmost,
4097 fallback,
4098 next_column_fallback,
4099 closest_in_90_degrees);
4100 }
4101
4102 static void
keyboard_left(CajaIconContainer * container,GdkEventKey * event)4103 keyboard_left (CajaIconContainer *container,
4104 GdkEventKey *event)
4105 {
4106 IsBetterIconFunction fallback;
4107 IsBetterIconFunction previous_column_fallback;
4108
4109 fallback = NULL;
4110 if (container->details->auto_layout &&
4111 !caja_icon_container_is_layout_vertical (container) &&
4112 !is_rectangle_selection_event (event))
4113 {
4114 fallback = previous_row_rightmost;
4115 }
4116
4117 previous_column_fallback = NULL;
4118 if (caja_icon_container_is_layout_vertical (container) &&
4119 gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
4120 {
4121 previous_column_fallback = previous_column_lowest;
4122 }
4123
4124 /* Left selects the next icon in the same row.
4125 * Control-Left sets the keyboard focus to the next icon in the same row.
4126 */
4127 keyboard_arrow_key (container,
4128 event,
4129 GTK_DIR_LEFT,
4130 rightmost_in_bottom_row,
4131 caja_icon_container_is_layout_rtl (container) ?
4132 rightmost_in_top_row : leftmost_in_top_row,
4133 same_row_left_side_rightmost,
4134 fallback,
4135 previous_column_fallback,
4136 closest_in_90_degrees);
4137 }
4138
4139 static void
keyboard_down(CajaIconContainer * container,GdkEventKey * event)4140 keyboard_down (CajaIconContainer *container,
4141 GdkEventKey *event)
4142 {
4143 IsBetterIconFunction fallback;
4144 IsBetterIconFunction next_row_fallback;
4145
4146 fallback = NULL;
4147 if (container->details->auto_layout &&
4148 caja_icon_container_is_layout_vertical (container) &&
4149 !is_rectangle_selection_event (event))
4150 {
4151 if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
4152 {
4153 fallback = previous_column_highest;
4154 }
4155 else
4156 {
4157 fallback = next_column_highest;
4158 }
4159 }
4160
4161 next_row_fallback = NULL;
4162 if (!caja_icon_container_is_layout_vertical (container))
4163 {
4164 if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
4165 {
4166 next_row_fallback = next_row_leftmost;
4167 }
4168 else
4169 {
4170 next_row_fallback = next_row_rightmost;
4171 }
4172 }
4173
4174 /* Down selects the next icon in the same column.
4175 * Control-Down sets the keyboard focus to the next icon in the same column.
4176 */
4177 keyboard_arrow_key (container,
4178 event,
4179 GTK_DIR_DOWN,
4180 rightmost_in_bottom_row,
4181 caja_icon_container_is_layout_rtl (container) ?
4182 rightmost_in_top_row : leftmost_in_top_row,
4183 same_column_below_highest,
4184 fallback,
4185 next_row_fallback,
4186 closest_in_90_degrees);
4187 }
4188
4189 static void
keyboard_up(CajaIconContainer * container,GdkEventKey * event)4190 keyboard_up (CajaIconContainer *container,
4191 GdkEventKey *event)
4192 {
4193 IsBetterIconFunction fallback;
4194
4195 fallback = NULL;
4196 if (container->details->auto_layout &&
4197 caja_icon_container_is_layout_vertical (container) &&
4198 !is_rectangle_selection_event (event))
4199 {
4200 if (gtk_widget_get_direction (GTK_WIDGET (container)) == GTK_TEXT_DIR_RTL)
4201 {
4202 fallback = next_column_bottommost;
4203 }
4204 else
4205 {
4206 fallback = previous_column_lowest;
4207 }
4208 }
4209
4210 /* Up selects the next icon in the same column.
4211 * Control-Up sets the keyboard focus to the next icon in the same column.
4212 */
4213 keyboard_arrow_key (container,
4214 event,
4215 GTK_DIR_UP,
4216 rightmost_in_bottom_row,
4217 caja_icon_container_is_layout_rtl (container) ?
4218 rightmost_in_top_row : leftmost_in_top_row,
4219 same_column_above_lowest,
4220 fallback,
4221 NULL,
4222 closest_in_90_degrees);
4223 }
4224
4225 static void
keyboard_space(CajaIconContainer * container,GdkEventKey * event)4226 keyboard_space (CajaIconContainer *container,
4227 GdkEventKey *event)
4228 {
4229 CajaIcon *icon;
4230
4231 if (!has_selection (container) &&
4232 container->details->keyboard_focus != NULL)
4233 {
4234 keyboard_move_to (container,
4235 container->details->keyboard_focus,
4236 NULL, NULL);
4237 }
4238 else if ((event->state & GDK_CONTROL_MASK) != 0 &&
4239 (event->state & GDK_SHIFT_MASK) == 0)
4240 {
4241 /* Control-space toggles the selection state of the current icon. */
4242 if (container->details->keyboard_focus != NULL)
4243 {
4244 icon_toggle_selected (container, container->details->keyboard_focus);
4245 g_signal_emit (container, signals[SELECTION_CHANGED], 0);
4246 if (container->details->keyboard_focus->is_selected)
4247 {
4248 container->details->range_selection_base_icon = container->details->keyboard_focus;
4249 }
4250 }
4251 else
4252 {
4253 icon = find_best_selected_icon (container,
4254 NULL,
4255 leftmost_in_top_row,
4256 NULL);
4257 if (icon == NULL)
4258 {
4259 icon = find_best_icon (container,
4260 NULL,
4261 leftmost_in_top_row,
4262 NULL);
4263 }
4264 if (icon != NULL)
4265 {
4266 set_keyboard_focus (container, icon);
4267 }
4268 }
4269 }
4270 else if ((event->state & GDK_SHIFT_MASK) != 0)
4271 {
4272 activate_selected_items_alternate (container, NULL);
4273 }
4274 else
4275 {
4276 activate_selected_items (container);
4277 }
4278 }
4279
4280 /* look for the first icon that matches the longest part of a given
4281 * search pattern
4282 */
4283 typedef struct
4284 {
4285 gunichar *name;
4286 int last_match_length;
4287 } BestNameMatch;
4288
4289 #ifndef TAB_NAVIGATION_DISABLED
4290 static void
select_previous_or_next_icon(CajaIconContainer * container,gboolean next,GdkEventKey * event)4291 select_previous_or_next_icon (CajaIconContainer *container,
4292 gboolean next,
4293 GdkEventKey *event)
4294 {
4295 CajaIcon *icon;
4296 const GList *item;
4297
4298 item = NULL;
4299 /* Chose the icon to start with.
4300 * If we have a keyboard focus, start with it.
4301 * Otherwise, use the single selected icon.
4302 */
4303 icon = container->details->keyboard_focus;
4304 if (icon == NULL)
4305 {
4306 icon = get_first_selected_icon (container);
4307 }
4308
4309 if (icon != NULL)
4310 {
4311 /* must have at least @icon in the list */
4312 g_assert (container->details->icons != NULL);
4313 item = g_list_find (container->details->icons, icon);
4314 g_assert (item != NULL);
4315
4316 item = next ? item->next : item->prev;
4317 if (item == NULL)
4318 {
4319 item = next ? g_list_first (container->details->icons) : g_list_last (container->details->icons);
4320 }
4321
4322 }
4323 else if (container->details->icons != NULL)
4324 {
4325 /* no selection yet, pick the first or last item to select */
4326 item = next ? g_list_first (container->details->icons) : g_list_last (container->details->icons);
4327 }
4328
4329 icon = (item != NULL) ? item->data : NULL;
4330
4331 if (icon != NULL)
4332 {
4333 keyboard_move_to (container, icon, NULL, event);
4334 }
4335 }
4336 #endif
4337
4338 static void
destroy(GtkWidget * object)4339 destroy (GtkWidget *object)
4340 {
4341 CajaIconContainer *container;
4342
4343 container = CAJA_ICON_CONTAINER (object);
4344
4345 caja_icon_container_clear (container);
4346
4347 if (container->details->rubberband_info.timer_id != 0)
4348 {
4349 g_source_remove (container->details->rubberband_info.timer_id);
4350 container->details->rubberband_info.timer_id = 0;
4351 }
4352
4353 if (container->details->idle_id != 0)
4354 {
4355 g_source_remove (container->details->idle_id);
4356 container->details->idle_id = 0;
4357 }
4358
4359 if (container->details->stretch_idle_id != 0)
4360 {
4361 g_source_remove (container->details->stretch_idle_id);
4362 container->details->stretch_idle_id = 0;
4363 }
4364
4365 if (container->details->align_idle_id != 0)
4366 {
4367 g_source_remove (container->details->align_idle_id);
4368 container->details->align_idle_id = 0;
4369 }
4370
4371 if (container->details->selection_changed_id != 0)
4372 {
4373 g_source_remove (container->details->selection_changed_id);
4374 container->details->selection_changed_id = 0;
4375 }
4376
4377 if (container->details->size_allocation_count_id != 0)
4378 {
4379 g_source_remove (container->details->size_allocation_count_id);
4380 container->details->size_allocation_count_id = 0;
4381 }
4382
4383 /* destroy interactive search dialog */
4384 if (container->details->search_window)
4385 {
4386 gtk_widget_destroy (container->details->search_window);
4387 container->details->search_window = NULL;
4388 container->details->search_entry = NULL;
4389 if (container->details->typeselect_flush_timeout)
4390 {
4391 g_source_remove (container->details->typeselect_flush_timeout);
4392 container->details->typeselect_flush_timeout = 0;
4393 }
4394 }
4395
4396 GTK_WIDGET_CLASS (caja_icon_container_parent_class)->destroy (object);
4397 }
4398
4399 static void
finalize(GObject * object)4400 finalize (GObject *object)
4401 {
4402 CajaIconContainerDetails *details;
4403
4404 details = CAJA_ICON_CONTAINER (object)->details;
4405
4406 g_signal_handlers_disconnect_by_func (caja_icon_view_preferences,
4407 text_ellipsis_limit_changed_container_callback,
4408 object);
4409 g_signal_handlers_disconnect_by_func (caja_desktop_preferences,
4410 text_ellipsis_limit_changed_container_callback,
4411 object);
4412
4413 g_hash_table_destroy (details->icon_set);
4414 details->icon_set = NULL;
4415
4416 g_free (details->font);
4417
4418 if (details->a11y_item_action_queue != NULL)
4419 {
4420 while (!g_queue_is_empty (details->a11y_item_action_queue))
4421 {
4422 g_free (g_queue_pop_head (details->a11y_item_action_queue));
4423 }
4424 g_queue_free (details->a11y_item_action_queue);
4425 }
4426 if (details->a11y_item_action_idle_handler != 0)
4427 {
4428 g_source_remove (details->a11y_item_action_idle_handler);
4429 }
4430
4431 g_free (details);
4432
4433 G_OBJECT_CLASS (caja_icon_container_parent_class)->finalize (object);
4434 }
4435
4436 /* GtkWidget methods. */
4437
4438 static gboolean
clear_size_allocation_count(gpointer data)4439 clear_size_allocation_count (gpointer data)
4440 {
4441 CajaIconContainer *container;
4442
4443 container = CAJA_ICON_CONTAINER (data);
4444
4445 container->details->size_allocation_count_id = 0;
4446 container->details->size_allocation_count = 0;
4447
4448 return FALSE;
4449 }
4450
4451 static void
size_allocate(GtkWidget * widget,GtkAllocation * allocation)4452 size_allocate (GtkWidget *widget,
4453 GtkAllocation *allocation)
4454 {
4455 CajaIconContainer *container;
4456 gboolean need_layout_redone;
4457 GtkAllocation wid_allocation;
4458
4459 container = CAJA_ICON_CONTAINER (widget);
4460
4461 need_layout_redone = !container->details->has_been_allocated;
4462 gtk_widget_get_allocation (widget, &wid_allocation);
4463
4464 if (allocation->width != wid_allocation.width)
4465 {
4466 need_layout_redone = TRUE;
4467 }
4468
4469 if (allocation->height != wid_allocation.height)
4470 {
4471 need_layout_redone = TRUE;
4472 }
4473
4474 /* Under some conditions we can end up in a loop when size allocating.
4475 * This happens when the icons don't fit without a scrollbar, but fits
4476 * when a scrollbar is added (bug #129963 for details).
4477 * We keep track of this looping by increasing a counter in size_allocate
4478 * and clearing it in a high-prio idle (the only way to detect the loop is
4479 * done).
4480 * When we've done at more than two iterations (with/without scrollbar)
4481 * we terminate this looping by not redoing the layout when the width
4482 * is wider than the current one (i.e when removing the scrollbar).
4483 */
4484 if (container->details->size_allocation_count_id == 0)
4485 {
4486 container->details->size_allocation_count_id =
4487 g_idle_add_full (G_PRIORITY_HIGH,
4488 clear_size_allocation_count,
4489 container, NULL);
4490 }
4491 container->details->size_allocation_count++;
4492 if (container->details->size_allocation_count > 2 &&
4493 allocation->width >= wid_allocation.width)
4494 {
4495 need_layout_redone = FALSE;
4496 }
4497
4498 GTK_WIDGET_CLASS (caja_icon_container_parent_class)->size_allocate (widget, allocation);
4499
4500 container->details->has_been_allocated = TRUE;
4501
4502 if (need_layout_redone)
4503 {
4504 redo_layout (container);
4505 }
4506 }
4507
4508 static GtkSizeRequestMode
get_request_mode(GtkWidget * widget)4509 get_request_mode (GtkWidget *widget)
4510 {
4511 /* Don't trade size at all, since we get whatever we get anyway. */
4512 return GTK_SIZE_REQUEST_CONSTANT_SIZE;
4513 }
4514
4515 /* We need to implement these since the GtkScrolledWindow uses them
4516 to guess whether to show scrollbars or not, and if we don't report
4517 anything it'll tend to get it wrong causing double calls
4518 to size_allocate (at different sizes) during its size allocation. */
4519 static void
get_prefered_width(GtkWidget * widget,gint * minimum_size,gint * natural_size)4520 get_prefered_width (GtkWidget *widget,
4521 gint *minimum_size,
4522 gint *natural_size)
4523 {
4524 EelCanvasGroup *root;
4525 double x1, x2;
4526 int cx1, cx2;
4527 int width;
4528
4529 root = eel_canvas_root (EEL_CANVAS (widget));
4530 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (root),
4531 &x1, NULL, &x2, NULL);
4532 eel_canvas_w2c (EEL_CANVAS (widget), x1, 0, &cx1, NULL);
4533 eel_canvas_w2c (EEL_CANVAS (widget), x2, 0, &cx2, NULL);
4534
4535 width = cx2 - cx1;
4536 if (natural_size) {
4537 *natural_size = width;
4538 }
4539 if (minimum_size) {
4540 *minimum_size = width;
4541 }
4542 }
4543
4544 static void
get_prefered_height(GtkWidget * widget,gint * minimum_size,gint * natural_size)4545 get_prefered_height (GtkWidget *widget,
4546 gint *minimum_size,
4547 gint *natural_size)
4548 {
4549 EelCanvasGroup *root;
4550 double y1, y2;
4551 int cy1, cy2;
4552 int height;
4553
4554 root = eel_canvas_root (EEL_CANVAS (widget));
4555 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (root),
4556 NULL, &y1, NULL, &y2);
4557 eel_canvas_w2c (EEL_CANVAS (widget), 0, y1, NULL, &cy1);
4558 eel_canvas_w2c (EEL_CANVAS (widget), 0, y2, NULL, &cy2);
4559
4560 height = cy2 - cy1;
4561 if (natural_size) {
4562 *natural_size = height;
4563 }
4564 if (minimum_size) {
4565 *minimum_size = height;
4566 }
4567 }
4568
4569 static gboolean
draw(GtkWidget * widget,cairo_t * cr)4570 draw (GtkWidget *widget, cairo_t *cr)
4571 {
4572 if (!CAJA_ICON_CONTAINER (widget)->details->is_desktop)
4573 {
4574 eel_background_draw (widget, cr);
4575 }
4576
4577 return GTK_WIDGET_CLASS (caja_icon_container_parent_class)->draw (widget,
4578 cr);
4579 }
4580
4581 static void
realize(GtkWidget * widget)4582 realize (GtkWidget *widget)
4583 {
4584 GtkAdjustment *vadj, *hadj;
4585 CajaIconContainer *container;
4586
4587 GTK_WIDGET_CLASS (caja_icon_container_parent_class)->realize (widget);
4588
4589 container = CAJA_ICON_CONTAINER (widget);
4590
4591 /* Set up DnD. */
4592 caja_icon_dnd_init (container);
4593
4594 hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
4595 g_signal_connect (hadj, "value_changed",
4596 G_CALLBACK (handle_hadjustment_changed), widget);
4597
4598 vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
4599 g_signal_connect (vadj, "value_changed",
4600 G_CALLBACK (handle_vadjustment_changed), widget);
4601
4602 }
4603
4604 static void
unrealize(GtkWidget * widget)4605 unrealize (GtkWidget *widget)
4606 {
4607 CajaIconContainer *container;
4608
4609 container = CAJA_ICON_CONTAINER (widget);
4610
4611 caja_icon_dnd_fini (container);
4612
4613 if (container->details->typeselect_flush_timeout)
4614 {
4615 g_source_remove (container->details->typeselect_flush_timeout);
4616 container->details->typeselect_flush_timeout = 0;
4617 }
4618
4619 GTK_WIDGET_CLASS (caja_icon_container_parent_class)->unrealize (widget);
4620 }
4621
4622 static void
style_updated(GtkWidget * widget)4623 style_updated (GtkWidget *widget)
4624 {
4625 CajaIconContainer *container;
4626
4627 container = CAJA_ICON_CONTAINER (widget);
4628 container->details->use_drop_shadows = container->details->drop_shadows_requested;
4629
4630 /* Don't chain up to parent, if this is a desktop container,
4631 * because that resets the background of the window.
4632 */
4633 if (!caja_icon_container_get_is_desktop (container)) {
4634 GTK_WIDGET_CLASS (caja_icon_container_parent_class)->style_updated (widget);
4635 }
4636
4637 if (gtk_widget_get_realized (widget))
4638 {
4639 invalidate_labels (container);
4640 caja_icon_container_request_update_all (container);
4641 }
4642 }
4643
4644 static gboolean
button_press_event(GtkWidget * widget,GdkEventButton * event)4645 button_press_event (GtkWidget *widget,
4646 GdkEventButton *event)
4647 {
4648 CajaIconContainer *container;
4649 gboolean selection_changed;
4650 gboolean return_value;
4651 gboolean clicked_on_icon;
4652
4653 container = CAJA_ICON_CONTAINER (widget);
4654 container->details->button_down_time = event->time;
4655
4656 /* Forget about the old keyboard selection now that we've started mousing. */
4657 clear_keyboard_focus (container);
4658 clear_keyboard_rubberband_start (container);
4659
4660 if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)
4661 {
4662 /* We use our own double-click detection. */
4663 return TRUE;
4664 }
4665
4666 /* Invoke the canvas event handler and see if an item picks up the event. */
4667 clicked_on_icon = GTK_WIDGET_CLASS (caja_icon_container_parent_class)->button_press_event (widget, event);
4668
4669 /* Move focus to icon container, unless we're still renaming (to avoid exiting
4670 * renaming mode)
4671 */
4672 if (!gtk_widget_has_focus (widget) && !(is_renaming (container) || is_renaming_pending (container)))
4673 {
4674 gtk_widget_grab_focus (widget);
4675 }
4676
4677 if (clicked_on_icon)
4678 {
4679 return TRUE;
4680 }
4681
4682 if (event->button == DRAG_BUTTON &&
4683 event->type == GDK_BUTTON_PRESS)
4684 {
4685 /* Clear the last click icon for double click */
4686 container->details->double_click_icon[1] = container->details->double_click_icon[0];
4687 container->details->double_click_icon[0] = NULL;
4688 }
4689
4690 /* Button 1 does rubber banding. */
4691 if (event->button == RUBBERBAND_BUTTON)
4692 {
4693 if (! button_event_modifies_selection (event))
4694 {
4695 selection_changed = unselect_all (container);
4696 if (selection_changed)
4697 {
4698 g_signal_emit (container,
4699 signals[SELECTION_CHANGED], 0);
4700 }
4701 }
4702
4703 start_rubberbanding (container, event);
4704 return TRUE;
4705 }
4706
4707 /* Prevent multi-button weirdness such as bug 6181 */
4708 if (container->details->rubberband_info.active)
4709 {
4710 return TRUE;
4711 }
4712
4713 /* Button 2 may be passed to the window manager. */
4714 if (event->button == MIDDLE_BUTTON)
4715 {
4716 selection_changed = unselect_all (container);
4717 if (selection_changed)
4718 {
4719 g_signal_emit (container, signals[SELECTION_CHANGED], 0);
4720 }
4721 g_signal_emit (widget, signals[MIDDLE_CLICK], 0, event);
4722 return TRUE;
4723 }
4724
4725 /* Button 3 does a contextual menu. */
4726 if (event->button == CONTEXTUAL_MENU_BUTTON)
4727 {
4728 end_renaming_mode (container, TRUE);
4729 selection_changed = unselect_all (container);
4730 if (selection_changed)
4731 {
4732 g_signal_emit (container, signals[SELECTION_CHANGED], 0);
4733 }
4734 g_signal_emit (widget, signals[CONTEXT_CLICK_BACKGROUND], 0, event);
4735 return TRUE;
4736 }
4737
4738 /* Otherwise, we emit a button_press message. */
4739 g_signal_emit (widget,
4740 signals[BUTTON_PRESS], 0, event,
4741 &return_value);
4742 return return_value;
4743 }
4744
4745 static void
caja_icon_container_did_not_drag(CajaIconContainer * container,GdkEventButton * event)4746 caja_icon_container_did_not_drag (CajaIconContainer *container,
4747 GdkEventButton *event)
4748 {
4749 CajaIconContainerDetails *details;
4750 gboolean selection_changed;
4751 static gint64 last_click_time = 0;
4752 static gint click_count = 0;
4753 gint double_click_time;
4754 gint64 current_time;
4755
4756 details = container->details;
4757
4758 if (details->icon_selected_on_button_down &&
4759 ((event->state & GDK_CONTROL_MASK) != 0 ||
4760 (event->state & GDK_SHIFT_MASK) == 0))
4761 {
4762 if (button_event_modifies_selection (event))
4763 {
4764 details->range_selection_base_icon = NULL;
4765 icon_toggle_selected (container, details->drag_icon);
4766 g_signal_emit (container,
4767 signals[SELECTION_CHANGED], 0);
4768 }
4769 else
4770 {
4771 details->range_selection_base_icon = details->drag_icon;
4772 selection_changed = select_one_unselect_others
4773 (container, details->drag_icon);
4774
4775 if (selection_changed)
4776 {
4777 g_signal_emit (container,
4778 signals[SELECTION_CHANGED], 0);
4779 }
4780 }
4781 }
4782
4783 if (details->drag_icon != NULL &&
4784 (details->single_click_mode ||
4785 event->button == MIDDLE_BUTTON))
4786 {
4787 /* Determine click count */
4788 g_object_get (G_OBJECT (gtk_widget_get_settings (GTK_WIDGET (container))),
4789 "gtk-double-click-time", &double_click_time,
4790 NULL);
4791 current_time = g_get_monotonic_time ();
4792 if (current_time - last_click_time < double_click_time * 1000)
4793 {
4794 click_count++;
4795 }
4796 else
4797 {
4798 click_count = 0;
4799 }
4800
4801 /* Stash time for next compare */
4802 last_click_time = current_time;
4803
4804 /* If single-click mode, activate the selected icons, unless modifying
4805 * the selection or pressing for a very long time, or double clicking.
4806 */
4807
4808
4809 if (click_count == 0 &&
4810 event->time - details->button_down_time < MAX_CLICK_TIME &&
4811 ! button_event_modifies_selection (event))
4812 {
4813
4814 /* It's a tricky UI issue whether this should activate
4815 * just the clicked item (as if it were a link), or all
4816 * the selected items (as if you were issuing an "activate
4817 * selection" command). For now, we're trying the activate
4818 * entire selection version to see how it feels. Note that
4819 * CajaList goes the other way because its "links" seem
4820 * much more link-like.
4821 */
4822 if (event->button == MIDDLE_BUTTON)
4823 {
4824 activate_selected_items_alternate (container, NULL);
4825 }
4826 else
4827 {
4828 activate_selected_items (container);
4829 }
4830 }
4831 }
4832 }
4833
4834 static gboolean
clicked_within_double_click_interval(CajaIconContainer * container)4835 clicked_within_double_click_interval (CajaIconContainer *container)
4836 {
4837 static gint64 last_click_time = 0;
4838 static gint click_count = 0;
4839 gint double_click_time;
4840 gint64 current_time;
4841
4842 /* Determine click count */
4843 g_object_get (G_OBJECT (gtk_widget_get_settings (GTK_WIDGET (container))),
4844 "gtk-double-click-time", &double_click_time,
4845 NULL);
4846 current_time = g_get_monotonic_time ();
4847 if (current_time - last_click_time < double_click_time * 1000)
4848 {
4849 click_count++;
4850 }
4851 else
4852 {
4853 click_count = 0;
4854 }
4855
4856 /* Stash time for next compare */
4857 last_click_time = current_time;
4858
4859 /* Only allow double click */
4860 if (click_count == 1) {
4861 click_count = 0;
4862 return TRUE;
4863 } else {
4864 return FALSE;
4865 }
4866 }
4867
4868 static void
clear_drag_state(CajaIconContainer * container)4869 clear_drag_state (CajaIconContainer *container)
4870 {
4871 container->details->drag_icon = NULL;
4872 container->details->drag_state = DRAG_STATE_INITIAL;
4873 }
4874
4875 static gboolean
start_stretching(CajaIconContainer * container,GdkEvent * event)4876 start_stretching (CajaIconContainer *container,
4877 GdkEvent *event)
4878 {
4879 CajaIconContainerDetails *details;
4880 CajaIcon *icon;
4881 EelDPoint world_point;
4882 GtkWidget *toplevel;
4883 GdkDisplay *display;
4884 GtkCornerType corner;
4885 GdkCursor *cursor;
4886
4887 details = container->details;
4888 icon = details->stretch_icon;
4889 display = gtk_widget_get_display (GTK_WIDGET (container));
4890
4891 /* Check if we hit the stretch handles. */
4892 world_point.x = details->drag_x;
4893 world_point.y = details->drag_y;
4894 if (!caja_icon_canvas_item_hit_test_stretch_handles (icon->item, world_point, &corner))
4895 {
4896 return FALSE;
4897 }
4898
4899 switch (corner)
4900 {
4901 case GTK_CORNER_TOP_LEFT:
4902 cursor = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_CORNER);
4903 break;
4904 case GTK_CORNER_BOTTOM_LEFT:
4905 cursor = gdk_cursor_new_for_display (display, GDK_BOTTOM_LEFT_CORNER);
4906 break;
4907 case GTK_CORNER_TOP_RIGHT:
4908 cursor = gdk_cursor_new_for_display (display, GDK_TOP_RIGHT_CORNER);
4909 break;
4910 case GTK_CORNER_BOTTOM_RIGHT:
4911 cursor = gdk_cursor_new_for_display (display, GDK_BOTTOM_RIGHT_CORNER);
4912 break;
4913 default:
4914 cursor = NULL;
4915 break;
4916 }
4917 /* Set up the dragging. */
4918 details->drag_state = DRAG_STATE_STRETCH;
4919 eel_canvas_w2c (EEL_CANVAS (container),
4920 details->drag_x,
4921 details->drag_y,
4922 &details->stretch_start.pointer_x,
4923 &details->stretch_start.pointer_y);
4924 eel_canvas_w2c (EEL_CANVAS (container),
4925 icon->x, icon->y,
4926 &details->stretch_start.icon_x,
4927 &details->stretch_start.icon_y);
4928 icon_get_size (container, icon,
4929 &details->stretch_start.icon_size);
4930
4931 eel_canvas_item_grab (EEL_CANVAS_ITEM (icon->item),
4932 (GDK_POINTER_MOTION_MASK
4933 | GDK_BUTTON_RELEASE_MASK),
4934 cursor,
4935 event);
4936
4937 if (cursor)
4938 g_object_unref (cursor);
4939
4940 /* Ensure the window itself is focused.. */
4941 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (container));
4942 if (toplevel != NULL && gtk_widget_get_realized (toplevel))
4943 {
4944 gdk_window_focus (gtk_widget_get_window (toplevel), GDK_CURRENT_TIME);
4945 }
4946
4947 return TRUE;
4948 }
4949
4950 static gboolean
update_stretch_at_idle(CajaIconContainer * container)4951 update_stretch_at_idle (CajaIconContainer *container)
4952 {
4953 CajaIconContainerDetails *details;
4954 CajaIcon *icon;
4955 double world_x, world_y;
4956 StretchState stretch_state;
4957
4958 details = container->details;
4959 icon = details->stretch_icon;
4960
4961 if (icon == NULL)
4962 {
4963 container->details->stretch_idle_id = 0;
4964 return FALSE;
4965 }
4966
4967 eel_canvas_w2c (EEL_CANVAS (container),
4968 details->world_x, details->world_y,
4969 &stretch_state.pointer_x, &stretch_state.pointer_y);
4970
4971 compute_stretch (&details->stretch_start,
4972 &stretch_state);
4973
4974 eel_canvas_c2w (EEL_CANVAS (container),
4975 stretch_state.icon_x, stretch_state.icon_y,
4976 &world_x, &world_y);
4977
4978 icon_set_position (icon, world_x, world_y);
4979 icon_set_size (container, icon, stretch_state.icon_size, FALSE, FALSE);
4980
4981 container->details->stretch_idle_id = 0;
4982
4983 return FALSE;
4984 }
4985
4986 static void
continue_stretching(CajaIconContainer * container,double world_x,double world_y)4987 continue_stretching (CajaIconContainer *container,
4988 double world_x, double world_y)
4989 {
4990
4991 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
4992
4993 container->details->world_x = world_x;
4994 container->details->world_y = world_y;
4995
4996 if (container->details->stretch_idle_id == 0)
4997 {
4998 container->details->stretch_idle_id = g_idle_add ((GSourceFunc) update_stretch_at_idle, container);
4999 }
5000 }
5001
5002 static gboolean
keyboard_stretching(CajaIconContainer * container,GdkEventKey * event)5003 keyboard_stretching (CajaIconContainer *container,
5004 GdkEventKey *event)
5005 {
5006 CajaIcon *icon;
5007 guint size;
5008
5009 icon = container->details->stretch_icon;
5010
5011 if (icon == NULL || !icon->is_selected)
5012 {
5013 return FALSE;
5014 }
5015
5016 icon_get_size (container, icon, &size);
5017
5018 switch (event->keyval)
5019 {
5020 case GDK_KEY_equal:
5021 case GDK_KEY_plus:
5022 case GDK_KEY_KP_Add:
5023 icon_set_size (container, icon, size + 5, FALSE, FALSE);
5024 break;
5025 case GDK_KEY_minus:
5026 case GDK_KEY_KP_Subtract:
5027 icon_set_size (container, icon, size - 5, FALSE, FALSE);
5028 break;
5029 case GDK_KEY_0:
5030 case GDK_KEY_KP_0:
5031 caja_icon_container_move_icon (container, icon,
5032 icon->x, icon->y,
5033 1.0,
5034 FALSE, TRUE, TRUE);
5035 break;
5036 }
5037
5038 return TRUE;
5039 }
5040
5041 static void
ungrab_stretch_icon(CajaIconContainer * container)5042 ungrab_stretch_icon (CajaIconContainer *container)
5043 {
5044 eel_canvas_item_ungrab (EEL_CANVAS_ITEM (container->details->stretch_icon->item));
5045 }
5046
5047 static void
end_stretching(CajaIconContainer * container,double world_x,double world_y)5048 end_stretching (CajaIconContainer *container,
5049 double world_x, double world_y)
5050 {
5051 CajaIconPosition position;
5052 CajaIcon *icon;
5053
5054 continue_stretching (container, world_x, world_y);
5055 ungrab_stretch_icon (container);
5056
5057 /* now that we're done stretching, update the icon's position */
5058
5059 icon = container->details->drag_icon;
5060 if (caja_icon_container_is_layout_rtl (container))
5061 {
5062 position.x = icon->saved_ltr_x = get_mirror_x_position (container, icon, icon->x);
5063 }
5064 else
5065 {
5066 position.x = icon->x;
5067 }
5068 position.y = icon->y;
5069 position.scale = icon->scale;
5070 g_signal_emit (container,
5071 signals[ICON_POSITION_CHANGED], 0,
5072 icon->data, &position);
5073
5074 clear_drag_state (container);
5075 redo_layout (container);
5076 }
5077
5078 static gboolean
undo_stretching(CajaIconContainer * container)5079 undo_stretching (CajaIconContainer *container)
5080 {
5081 CajaIcon *stretched_icon;
5082
5083 stretched_icon = container->details->stretch_icon;
5084
5085 if (stretched_icon == NULL)
5086 {
5087 return FALSE;
5088 }
5089
5090 if (container->details->drag_state == DRAG_STATE_STRETCH)
5091 {
5092 ungrab_stretch_icon (container);
5093 clear_drag_state (container);
5094 }
5095 caja_icon_canvas_item_set_show_stretch_handles
5096 (stretched_icon->item, FALSE);
5097
5098 icon_set_position (stretched_icon,
5099 container->details->stretch_initial_x,
5100 container->details->stretch_initial_y);
5101 icon_set_size (container,
5102 stretched_icon,
5103 container->details->stretch_initial_size,
5104 TRUE,
5105 TRUE);
5106
5107 container->details->stretch_icon = NULL;
5108 emit_stretch_ended (container, stretched_icon);
5109 redo_layout (container);
5110
5111 return TRUE;
5112 }
5113
5114 static gboolean
button_release_event(GtkWidget * widget,GdkEventButton * event)5115 button_release_event (GtkWidget *widget,
5116 GdkEventButton *event)
5117 {
5118 CajaIconContainer *container;
5119 CajaIconContainerDetails *details;
5120 double world_x, world_y;
5121
5122 container = CAJA_ICON_CONTAINER (widget);
5123 details = container->details;
5124
5125 if (event->button == RUBBERBAND_BUTTON && details->rubberband_info.active)
5126 {
5127 stop_rubberbanding (container);
5128 return TRUE;
5129 }
5130
5131 if (event->button == details->drag_button)
5132 {
5133 details->drag_button = 0;
5134
5135 switch (details->drag_state)
5136 {
5137 case DRAG_STATE_MOVE_OR_COPY:
5138 if (!details->drag_started)
5139 {
5140 caja_icon_container_did_not_drag (container, event);
5141 }
5142 else
5143 {
5144 caja_icon_dnd_end_drag (container);
5145 caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
5146 "end drag from icon container");
5147 }
5148 break;
5149 case DRAG_STATE_STRETCH:
5150 eel_canvas_window_to_world
5151 (EEL_CANVAS (container), event->x, event->y, &world_x, &world_y);
5152 end_stretching (container, world_x, world_y);
5153 break;
5154 default:
5155 break;
5156 }
5157
5158 clear_drag_state (container);
5159 return TRUE;
5160 }
5161
5162 return GTK_WIDGET_CLASS (caja_icon_container_parent_class)->button_release_event (widget, event);
5163 }
5164
5165 static int
motion_notify_event(GtkWidget * widget,GdkEventMotion * event)5166 motion_notify_event (GtkWidget *widget,
5167 GdkEventMotion *event)
5168 {
5169 CajaIconContainer *container;
5170 CajaIconContainerDetails *details;
5171 double world_x, world_y;
5172 int canvas_x, canvas_y;
5173 GdkDragAction actions;
5174
5175 container = CAJA_ICON_CONTAINER (widget);
5176 details = container->details;
5177
5178 if (details->drag_button != 0)
5179 {
5180 switch (details->drag_state)
5181 {
5182 case DRAG_STATE_MOVE_OR_COPY:
5183 if (details->drag_started)
5184 {
5185 break;
5186 }
5187
5188 eel_canvas_window_to_world
5189 (EEL_CANVAS (container), event->x, event->y, &world_x, &world_y);
5190
5191 if (gtk_drag_check_threshold (widget,
5192 details->drag_x,
5193 details->drag_y,
5194 world_x,
5195 world_y))
5196 {
5197 details->drag_started = TRUE;
5198 details->drag_state = DRAG_STATE_MOVE_OR_COPY;
5199
5200 end_renaming_mode (container, TRUE);
5201
5202 eel_canvas_w2c (EEL_CANVAS (container),
5203 details->drag_x,
5204 details->drag_y,
5205 &canvas_x,
5206 &canvas_y);
5207
5208 actions = GDK_ACTION_COPY
5209 | GDK_ACTION_LINK
5210 | GDK_ACTION_ASK;
5211
5212 if (container->details->drag_allow_moves)
5213 {
5214 actions |= GDK_ACTION_MOVE;
5215 }
5216
5217 caja_icon_dnd_begin_drag (container,
5218 actions,
5219 details->drag_button,
5220 event,
5221 canvas_x,
5222 canvas_y);
5223 caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
5224 "begin drag from icon container");
5225 }
5226 break;
5227 case DRAG_STATE_STRETCH:
5228 eel_canvas_window_to_world
5229 (EEL_CANVAS (container), event->x, event->y, &world_x, &world_y);
5230 continue_stretching (container, world_x, world_y);
5231 break;
5232 default:
5233 break;
5234 }
5235 }
5236
5237 return GTK_WIDGET_CLASS (caja_icon_container_parent_class)->motion_notify_event (widget, event);
5238 }
5239
5240 static void
caja_icon_container_search_position_func(CajaIconContainer * container,GtkWidget * search_dialog)5241 caja_icon_container_search_position_func (CajaIconContainer *container,
5242 GtkWidget *search_dialog)
5243 {
5244 gint x, y;
5245 gint cont_x, cont_y;
5246 gint cont_width, cont_height;
5247 gint scale;
5248 GdkWindow *cont_window;
5249 GdkScreen *screen;
5250 GtkRequisition requisition;
5251 GdkMonitor *monitor_num;
5252 GdkRectangle monitor;
5253
5254
5255 cont_window = gtk_widget_get_window (GTK_WIDGET (container));
5256 scale = gtk_widget_get_scale_factor (GTK_WIDGET (container));
5257 screen = gdk_window_get_screen (cont_window);
5258
5259 monitor_num = gdk_display_get_monitor_at_window (gdk_screen_get_display (screen),
5260 cont_window);
5261 gdk_monitor_get_geometry (monitor_num, &monitor);
5262
5263 gtk_widget_realize (search_dialog);
5264
5265 gdk_window_get_origin (cont_window, &cont_x, &cont_y);
5266
5267 cont_width = gdk_window_get_width (cont_window);
5268 cont_height = gdk_window_get_height (cont_window);
5269
5270 gtk_widget_get_preferred_size (search_dialog, &requisition, NULL);
5271
5272 if (cont_x + cont_width - requisition.width > WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale)
5273 {
5274 x = WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale - requisition.width;
5275 }
5276 else if (cont_x + cont_width - requisition.width < 0)
5277 {
5278 x = 0;
5279 }
5280 else
5281 {
5282 x = cont_x + cont_width - requisition.width;
5283 }
5284
5285 if (cont_y + cont_height > HeightOfScreen (gdk_x11_screen_get_xscreen (screen)))
5286 {
5287 y = HeightOfScreen (gdk_x11_screen_get_xscreen (screen)) - requisition.height;
5288 }
5289 else if (cont_y + cont_height < 0) /* isn't really possible ... */
5290 {
5291 y = 0;
5292 }
5293 else
5294 {
5295 y = cont_y + cont_height;
5296 }
5297
5298 gtk_window_move (GTK_WINDOW (search_dialog), x, y);
5299 }
5300
5301 static gboolean
caja_icon_container_real_search_enable_popdown(gpointer data)5302 caja_icon_container_real_search_enable_popdown (gpointer data)
5303 {
5304 CajaIconContainer *container = (CajaIconContainer *)data;
5305
5306 container->details->disable_popdown = FALSE;
5307
5308 g_object_unref (container);
5309
5310 return FALSE;
5311 }
5312
5313 static void
caja_icon_container_search_enable_popdown(GtkWidget * widget,gpointer data)5314 caja_icon_container_search_enable_popdown (GtkWidget *widget,
5315 gpointer data)
5316 {
5317 CajaIconContainer *container = (CajaIconContainer *) data;
5318
5319 g_object_ref (container);
5320 g_timeout_add (200, caja_icon_container_real_search_enable_popdown, data);
5321 }
5322
5323 static void
caja_icon_container_search_disable_popdown(GtkEntry * entry,GtkMenu * menu,gpointer data)5324 caja_icon_container_search_disable_popdown (GtkEntry *entry,
5325 GtkMenu *menu,
5326 gpointer data)
5327 {
5328 CajaIconContainer *container = (CajaIconContainer *) data;
5329
5330 container->details->disable_popdown = TRUE;
5331 g_signal_connect (menu, "hide",
5332 G_CALLBACK (caja_icon_container_search_enable_popdown),
5333 data);
5334 }
5335
5336 /* Cut and paste from gtkwindow.c */
5337 static void
send_focus_change(GtkWidget * widget,gboolean in)5338 send_focus_change (GtkWidget *widget, gboolean in)
5339 {
5340 GdkEvent *fevent;
5341
5342 fevent = gdk_event_new (GDK_FOCUS_CHANGE);
5343
5344 g_object_ref (widget);
5345 ((GdkEventFocus *) fevent)->in = in;
5346
5347 gtk_widget_send_focus_change (widget, fevent);
5348
5349 fevent->focus_change.type = GDK_FOCUS_CHANGE;
5350 fevent->focus_change.window = g_object_ref (gtk_widget_get_window (widget));
5351 fevent->focus_change.in = in;
5352
5353 gtk_widget_event (widget, fevent);
5354
5355 g_object_notify (G_OBJECT (widget), "has-focus");
5356
5357 g_object_unref (widget);
5358 gdk_event_free (fevent);
5359 }
5360
5361 static void
caja_icon_container_search_dialog_hide(GtkWidget * search_dialog,CajaIconContainer * container)5362 caja_icon_container_search_dialog_hide (GtkWidget *search_dialog,
5363 CajaIconContainer *container)
5364 {
5365 if (container->details->disable_popdown)
5366 {
5367 return;
5368 }
5369
5370 if (container->details->search_entry_changed_id)
5371 {
5372 g_signal_handler_disconnect (container->details->search_entry,
5373 container->details->search_entry_changed_id);
5374 container->details->search_entry_changed_id = 0;
5375 }
5376 if (container->details->typeselect_flush_timeout)
5377 {
5378 g_source_remove (container->details->typeselect_flush_timeout);
5379 container->details->typeselect_flush_timeout = 0;
5380 }
5381
5382 /* send focus-in event */
5383 send_focus_change (GTK_WIDGET (container->details->search_entry), FALSE);
5384 gtk_widget_hide (search_dialog);
5385 gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
5386 }
5387
5388 static gboolean
caja_icon_container_search_entry_flush_timeout(CajaIconContainer * container)5389 caja_icon_container_search_entry_flush_timeout (CajaIconContainer *container)
5390 {
5391 caja_icon_container_search_dialog_hide (container->details->search_window, container);
5392
5393 return TRUE;
5394 }
5395
5396 /* Because we're visible but offscreen, we just set a flag in the preedit
5397 * callback.
5398 */
5399 static void
caja_icon_container_search_preedit_changed(GtkEntry * entry,gchar * preedit,CajaIconContainer * container)5400 caja_icon_container_search_preedit_changed (GtkEntry *entry,
5401 gchar *preedit,
5402 CajaIconContainer *container)
5403 {
5404 container->details->imcontext_changed = 1;
5405 if (container->details->typeselect_flush_timeout)
5406 {
5407 g_source_remove (container->details->typeselect_flush_timeout);
5408 container->details->typeselect_flush_timeout =
5409 g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
5410 (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
5411 container);
5412 }
5413 }
5414
5415 static void
caja_icon_container_search_activate(GtkEntry * entry,CajaIconContainer * container)5416 caja_icon_container_search_activate (GtkEntry *entry,
5417 CajaIconContainer *container)
5418 {
5419 caja_icon_container_search_dialog_hide (container->details->search_window,
5420 container);
5421
5422 activate_selected_items (container);
5423 }
5424
5425 static gboolean
caja_icon_container_search_delete_event(GtkWidget * widget,GdkEventAny * event,CajaIconContainer * container)5426 caja_icon_container_search_delete_event (GtkWidget *widget,
5427 GdkEventAny *event,
5428 CajaIconContainer *container)
5429 {
5430 g_assert (GTK_IS_WIDGET (widget));
5431
5432 caja_icon_container_search_dialog_hide (widget, container);
5433
5434 return TRUE;
5435 }
5436
5437 static gboolean
caja_icon_container_search_button_press_event(GtkWidget * widget,GdkEventButton * event,CajaIconContainer * container)5438 caja_icon_container_search_button_press_event (GtkWidget *widget,
5439 GdkEventButton *event,
5440 CajaIconContainer *container)
5441 {
5442 g_assert (GTK_IS_WIDGET (widget));
5443
5444 caja_icon_container_search_dialog_hide (widget, container);
5445
5446 if (event->window == gtk_layout_get_bin_window (GTK_LAYOUT (container)))
5447 {
5448 button_press_event (GTK_WIDGET (container), event);
5449 }
5450
5451 return TRUE;
5452 }
5453
5454 static void
caja_icon_container_get_icon_text(CajaIconContainer * container,CajaIconData * data,char ** editable_text,char ** additional_text,gboolean include_invisible)5455 caja_icon_container_get_icon_text (CajaIconContainer *container,
5456 CajaIconData *data,
5457 char **editable_text,
5458 char **additional_text,
5459 gboolean include_invisible)
5460 {
5461 CajaIconContainerClass *klass;
5462
5463 klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
5464 g_assert (klass->get_icon_text != NULL);
5465
5466 klass->get_icon_text (container, data, editable_text, additional_text, include_invisible);
5467 }
5468
5469 static gboolean
caja_icon_container_search_iter(CajaIconContainer * container,const char * key,gint n)5470 caja_icon_container_search_iter (CajaIconContainer *container,
5471 const char *key, gint n)
5472 {
5473 GList *p;
5474 CajaIcon *icon;
5475 char *name;
5476 int count;
5477 char *normalized_key, *case_normalized_key;
5478 char *normalized_name, *case_normalized_name;
5479
5480 g_assert (key != NULL);
5481 g_assert (n >= 1);
5482
5483 normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
5484 if (!normalized_key)
5485 {
5486 return FALSE;
5487 }
5488 case_normalized_key = g_utf8_casefold (normalized_key, -1);
5489 g_free (normalized_key);
5490 if (!case_normalized_key)
5491 {
5492 return FALSE;
5493 }
5494
5495 icon = NULL;
5496 name = NULL;
5497 count = 0;
5498 for (p = container->details->icons; p != NULL && count != n; p = p->next)
5499 {
5500 icon = p->data;
5501 caja_icon_container_get_icon_text (container, icon->data, &name,
5502 NULL, TRUE);
5503
5504 /* This can happen if a key event is handled really early while
5505 * loading the icon container, before the items have all been
5506 * updated once.
5507 */
5508 if (!name)
5509 {
5510 continue;
5511 }
5512
5513 normalized_name = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
5514 if (!normalized_name)
5515 {
5516 continue;
5517 }
5518 case_normalized_name = g_utf8_casefold (normalized_name, -1);
5519 g_free (normalized_name);
5520 if (!case_normalized_name)
5521 {
5522 continue;
5523 }
5524
5525 if (strncmp (case_normalized_key, case_normalized_name,
5526 strlen (case_normalized_key)) == 0)
5527 {
5528 count++;
5529 }
5530
5531 g_free (case_normalized_name);
5532 g_free (name);
5533 name = NULL;
5534 }
5535
5536 g_free (case_normalized_key);
5537
5538 if (count == n)
5539 {
5540 if (select_one_unselect_others (container, icon))
5541 {
5542 g_signal_emit (container, signals[SELECTION_CHANGED], 0);
5543 }
5544 schedule_keyboard_icon_reveal (container, icon);
5545
5546 return TRUE;
5547 }
5548
5549 return FALSE;
5550 }
5551
5552 static void
caja_icon_container_search_move(GtkWidget * window,CajaIconContainer * container,gboolean up)5553 caja_icon_container_search_move (GtkWidget *window,
5554 CajaIconContainer *container,
5555 gboolean up)
5556 {
5557 gboolean ret;
5558 gint len;
5559 const gchar *text;
5560
5561 text = gtk_entry_get_text (GTK_ENTRY (container->details->search_entry));
5562
5563 g_assert (text != NULL);
5564
5565 if (container->details->selected_iter == 0)
5566 {
5567 return;
5568 }
5569
5570 if (up && container->details->selected_iter == 1)
5571 {
5572 return;
5573 }
5574
5575 len = strlen (text);
5576
5577 if (len < 1)
5578 {
5579 return;
5580 }
5581
5582 /* search */
5583 unselect_all (container);
5584
5585 ret = caja_icon_container_search_iter (container, text,
5586 up?((container->details->selected_iter) - 1):((container->details->selected_iter + 1)));
5587
5588 if (ret)
5589 {
5590 /* found */
5591 container->details->selected_iter += up?(-1):(1);
5592 }
5593 else
5594 {
5595 /* return to old iter */
5596 caja_icon_container_search_iter (container, text,
5597 container->details->selected_iter);
5598 }
5599 }
5600
5601 static gboolean
caja_icon_container_search_scroll_event(GtkWidget * widget,GdkEventScroll * event,CajaIconContainer * container)5602 caja_icon_container_search_scroll_event (GtkWidget *widget,
5603 GdkEventScroll *event,
5604 CajaIconContainer *container)
5605 {
5606 gboolean retval = FALSE;
5607
5608 if (event->direction == GDK_SCROLL_UP)
5609 {
5610 caja_icon_container_search_move (widget, container, TRUE);
5611 retval = TRUE;
5612 }
5613 else if (event->direction == GDK_SCROLL_DOWN)
5614 {
5615 caja_icon_container_search_move (widget, container, FALSE);
5616 retval = TRUE;
5617 }
5618
5619 /* renew the flush timeout */
5620 if (retval && container->details->typeselect_flush_timeout)
5621 {
5622 g_source_remove (container->details->typeselect_flush_timeout);
5623 container->details->typeselect_flush_timeout =
5624 g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
5625 (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
5626 container);
5627 }
5628
5629 return retval;
5630 }
5631
5632 static gboolean
caja_icon_container_search_key_press_event(GtkWidget * widget,GdkEventKey * event,CajaIconContainer * container)5633 caja_icon_container_search_key_press_event (GtkWidget *widget,
5634 GdkEventKey *event,
5635 CajaIconContainer *container)
5636 {
5637 gboolean retval = FALSE;
5638
5639 g_assert (GTK_IS_WIDGET (widget));
5640 g_assert (CAJA_IS_ICON_CONTAINER (container));
5641
5642 /* close window and cancel the search */
5643 if (event->keyval == GDK_KEY_Escape || event->keyval == GDK_KEY_Tab)
5644 {
5645 caja_icon_container_search_dialog_hide (widget, container);
5646 return TRUE;
5647 }
5648
5649 /* close window and activate alternate */
5650 if (event->keyval == GDK_KEY_Return && event->state & GDK_SHIFT_MASK)
5651 {
5652 caja_icon_container_search_dialog_hide (widget,
5653 container);
5654
5655 activate_selected_items_alternate (container, NULL);
5656 return TRUE;
5657 }
5658
5659 /* select previous matching iter */
5660 if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up)
5661 {
5662 caja_icon_container_search_move (widget, container, TRUE);
5663 retval = TRUE;
5664 }
5665
5666 if (((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
5667 && (event->keyval == GDK_KEY_g || event->keyval == GDK_KEY_G))
5668 {
5669 caja_icon_container_search_move (widget, container, TRUE);
5670 retval = TRUE;
5671 }
5672
5673 /* select next matching iter */
5674 if (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down)
5675 {
5676 caja_icon_container_search_move (widget, container, FALSE);
5677 retval = TRUE;
5678 }
5679
5680 if (((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == GDK_CONTROL_MASK)
5681 && (event->keyval == GDK_KEY_g || event->keyval == GDK_KEY_G))
5682 {
5683 caja_icon_container_search_move (widget, container, FALSE);
5684 retval = TRUE;
5685 }
5686
5687 /* renew the flush timeout */
5688 if (retval && container->details->typeselect_flush_timeout)
5689 {
5690 g_source_remove (container->details->typeselect_flush_timeout);
5691 container->details->typeselect_flush_timeout =
5692 g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
5693 (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
5694 container);
5695 }
5696
5697 return retval;
5698 }
5699
5700 static void
caja_icon_container_search_init(GtkWidget * entry,CajaIconContainer * container)5701 caja_icon_container_search_init (GtkWidget *entry,
5702 CajaIconContainer *container)
5703 {
5704 gint ret;
5705 gint len;
5706 const gchar *text;
5707
5708 g_assert (GTK_IS_ENTRY (entry));
5709 g_assert (CAJA_IS_ICON_CONTAINER (container));
5710
5711 text = gtk_entry_get_text (GTK_ENTRY (entry));
5712 len = strlen (text);
5713
5714 /* search */
5715 unselect_all (container);
5716 if (container->details->typeselect_flush_timeout)
5717 {
5718 g_source_remove (container->details->typeselect_flush_timeout);
5719 container->details->typeselect_flush_timeout =
5720 g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
5721 (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
5722 container);
5723 }
5724
5725 if (len < 1)
5726 {
5727 return;
5728 }
5729
5730 ret = caja_icon_container_search_iter (container, text, 1);
5731
5732 if (ret)
5733 {
5734 container->details->selected_iter = 1;
5735 }
5736 }
5737
5738 static void
caja_icon_container_ensure_interactive_directory(CajaIconContainer * container)5739 caja_icon_container_ensure_interactive_directory (CajaIconContainer *container)
5740 {
5741 GtkWidget *frame, *vbox;
5742
5743 if (container->details->search_window != NULL)
5744 {
5745 return;
5746 }
5747
5748 container->details->search_window = gtk_window_new (GTK_WINDOW_POPUP);
5749
5750 gtk_window_set_modal (GTK_WINDOW (container->details->search_window), TRUE);
5751 gtk_window_set_type_hint (GTK_WINDOW (container->details->search_window),
5752 GDK_WINDOW_TYPE_HINT_COMBO);
5753
5754 g_signal_connect (container->details->search_window, "delete_event",
5755 G_CALLBACK (caja_icon_container_search_delete_event),
5756 container);
5757 g_signal_connect (container->details->search_window, "key_press_event",
5758 G_CALLBACK (caja_icon_container_search_key_press_event),
5759 container);
5760 g_signal_connect (container->details->search_window, "button_press_event",
5761 G_CALLBACK (caja_icon_container_search_button_press_event),
5762 container);
5763 g_signal_connect (container->details->search_window, "scroll_event",
5764 G_CALLBACK (caja_icon_container_search_scroll_event),
5765 container);
5766
5767 frame = gtk_frame_new (NULL);
5768 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
5769 gtk_widget_show (frame);
5770 gtk_container_add (GTK_CONTAINER (container->details->search_window), frame);
5771
5772 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
5773 gtk_widget_show (vbox);
5774 gtk_container_add (GTK_CONTAINER (frame), vbox);
5775 gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
5776
5777 /* add entry */
5778 container->details->search_entry = gtk_entry_new ();
5779 gtk_widget_show (container->details->search_entry);
5780 g_signal_connect (container->details->search_entry, "populate_popup",
5781 G_CALLBACK (caja_icon_container_search_disable_popdown),
5782 container);
5783 g_signal_connect (container->details->search_entry, "activate",
5784 G_CALLBACK (caja_icon_container_search_activate),
5785 container);
5786 g_signal_connect (container->details->search_entry,
5787 "preedit-changed",
5788 G_CALLBACK (caja_icon_container_search_preedit_changed),
5789 container);
5790 gtk_container_add (GTK_CONTAINER (vbox), container->details->search_entry);
5791
5792 gtk_widget_realize (container->details->search_entry);
5793 }
5794
5795 /* Pops up the interactive search entry. If keybinding is TRUE then the user
5796 * started this by typing the start_interactive_search keybinding. Otherwise, it came from
5797 */
5798 static gboolean
caja_icon_container_real_start_interactive_search(CajaIconContainer * container,gboolean keybinding)5799 caja_icon_container_real_start_interactive_search (CajaIconContainer *container,
5800 gboolean keybinding)
5801 {
5802 /* We only start interactive search if we have focus. If one of our
5803 * children have focus, we don't want to start the search.
5804 */
5805 GtkWidgetClass *entry_parent_class;
5806
5807 if (container->details->search_window != NULL &&
5808 gtk_widget_get_visible (container->details->search_window))
5809 {
5810 return TRUE;
5811 }
5812
5813 if (!gtk_widget_has_focus (GTK_WIDGET (container)))
5814 {
5815 return FALSE;
5816 }
5817
5818 caja_icon_container_ensure_interactive_directory (container);
5819
5820 if (keybinding)
5821 {
5822 gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
5823 }
5824
5825 /* done, show it */
5826 caja_icon_container_search_position_func (container, container->details->search_window);
5827 gtk_widget_show (container->details->search_window);
5828 if (container->details->search_entry_changed_id == 0)
5829 {
5830 container->details->search_entry_changed_id =
5831 g_signal_connect (container->details->search_entry, "changed",
5832 G_CALLBACK (caja_icon_container_search_init),
5833 container);
5834 }
5835
5836 container->details->typeselect_flush_timeout =
5837 g_timeout_add_seconds (CAJA_ICON_CONTAINER_SEARCH_DIALOG_TIMEOUT,
5838 (GSourceFunc) caja_icon_container_search_entry_flush_timeout,
5839 container);
5840
5841 /* Grab focus will select all the text. We don't want that to happen, so we
5842 * call the parent instance and bypass the selection change. This is probably
5843 * really non-kosher. */
5844 entry_parent_class = g_type_class_peek_parent (GTK_ENTRY_GET_CLASS (container->details->search_entry));
5845 (entry_parent_class->grab_focus) (container->details->search_entry);
5846
5847 /* send focus-in event */
5848 send_focus_change (container->details->search_entry, TRUE);
5849
5850 /* search first matching iter */
5851 caja_icon_container_search_init (container->details->search_entry, container);
5852
5853 return TRUE;
5854 }
5855
5856 static gboolean
caja_icon_container_start_interactive_search(CajaIconContainer * container)5857 caja_icon_container_start_interactive_search (CajaIconContainer *container)
5858 {
5859 return caja_icon_container_real_start_interactive_search (container, TRUE);
5860 }
5861
5862 static gboolean
handle_popups(CajaIconContainer * container,GdkEventKey * event,const char * signal)5863 handle_popups (CajaIconContainer *container,
5864 GdkEventKey *event,
5865 const char *signal)
5866 {
5867 GdkEventButton button_event = { 0 };
5868
5869 g_signal_emit_by_name (container, signal, &button_event);
5870
5871 return TRUE;
5872 }
5873
5874 static int
key_press_event(GtkWidget * widget,GdkEventKey * event)5875 key_press_event (GtkWidget *widget,
5876 GdkEventKey *event)
5877 {
5878 CajaIconContainer *container;
5879 gboolean handled;
5880
5881 container = CAJA_ICON_CONTAINER (widget);
5882 handled = FALSE;
5883
5884 if (is_renaming (container) || is_renaming_pending (container))
5885 {
5886 switch (event->keyval)
5887 {
5888 case GDK_KEY_Return:
5889 case GDK_KEY_KP_Enter:
5890 end_renaming_mode (container, TRUE);
5891 handled = TRUE;
5892 break;
5893 case GDK_KEY_Escape:
5894 end_renaming_mode (container, FALSE);
5895 handled = TRUE;
5896 break;
5897 default:
5898 break;
5899 }
5900 }
5901 else
5902 {
5903 switch (event->keyval)
5904 {
5905 case GDK_KEY_Home:
5906 case GDK_KEY_KP_Home:
5907 keyboard_home (container, event);
5908 handled = TRUE;
5909 break;
5910 case GDK_KEY_End:
5911 case GDK_KEY_KP_End:
5912 keyboard_end (container, event);
5913 handled = TRUE;
5914 break;
5915 case GDK_KEY_Left:
5916 case GDK_KEY_KP_Left:
5917 /* Don't eat Alt-Left, as that is used for history browsing */
5918 if ((event->state & GDK_MOD1_MASK) == 0)
5919 {
5920 keyboard_left (container, event);
5921 handled = TRUE;
5922 }
5923 break;
5924 case GDK_KEY_Up:
5925 case GDK_KEY_KP_Up:
5926 /* Don't eat Alt-Up, as that is used for alt-shift-Up */
5927 if ((event->state & GDK_MOD1_MASK) == 0)
5928 {
5929 keyboard_up (container, event);
5930 handled = TRUE;
5931 }
5932 break;
5933 case GDK_KEY_Right:
5934 case GDK_KEY_KP_Right:
5935 /* Don't eat Alt-Right, as that is used for history browsing */
5936 if ((event->state & GDK_MOD1_MASK) == 0)
5937 {
5938 keyboard_right (container, event);
5939 handled = TRUE;
5940 }
5941 break;
5942 case GDK_KEY_Down:
5943 case GDK_KEY_KP_Down:
5944 /* Don't eat Alt-Down, as that is used for Open */
5945 if ((event->state & GDK_MOD1_MASK) == 0)
5946 {
5947 keyboard_down (container, event);
5948 handled = TRUE;
5949 }
5950 break;
5951 case GDK_KEY_space:
5952 keyboard_space (container, event);
5953 handled = TRUE;
5954 break;
5955 #ifndef TAB_NAVIGATION_DISABLED
5956 case GDK_KEY_Tab:
5957 case GDK_KEY_ISO_Left_Tab:
5958 select_previous_or_next_icon (container,
5959 (event->state & GDK_SHIFT_MASK) == 0, event);
5960 handled = TRUE;
5961 break;
5962 #endif
5963 case GDK_KEY_Return:
5964 case GDK_KEY_KP_Enter:
5965 if ((event->state & GDK_SHIFT_MASK) != 0)
5966 {
5967 activate_selected_items_alternate (container, NULL);
5968 }
5969 else
5970 {
5971 activate_selected_items (container);
5972 }
5973
5974 handled = TRUE;
5975 break;
5976 case GDK_KEY_Escape:
5977 handled = undo_stretching (container);
5978 break;
5979 case GDK_KEY_plus:
5980 case GDK_KEY_minus:
5981 case GDK_KEY_equal:
5982 case GDK_KEY_KP_Add:
5983 case GDK_KEY_KP_Subtract:
5984 case GDK_KEY_0:
5985 case GDK_KEY_KP_0:
5986 if (event->state & GDK_CONTROL_MASK)
5987 {
5988 handled = keyboard_stretching (container, event);
5989 }
5990 break;
5991 case GDK_KEY_F10:
5992 /* handle Ctrl+F10 because we want to display the
5993 * background popup even if something is selected.
5994 * The other cases are handled by popup_menu().
5995 */
5996 if (event->state & GDK_CONTROL_MASK)
5997 {
5998 handled = handle_popups (container, event,
5999 "context_click_background");
6000 }
6001 break;
6002 case GDK_KEY_v:
6003 /* Eat Control + v to not enable type ahead */
6004 if ((event->state & GDK_CONTROL_MASK) != 0)
6005 {
6006 handled = TRUE;
6007 }
6008 break;
6009 default:
6010 break;
6011 }
6012 }
6013
6014 if (!handled)
6015 {
6016 handled = GTK_WIDGET_CLASS (caja_icon_container_parent_class)->key_press_event (widget, event);
6017 }
6018
6019 /* We pass the event to the search_entry. If its text changes, then we
6020 * start the typeahead find capabilities.
6021 * Copied from CajaIconContainer */
6022 if (!handled &&
6023 event->keyval != GDK_KEY_slash /* don't steal slash key event, used for "go to" */ &&
6024 event->keyval != GDK_KEY_BackSpace &&
6025 event->keyval != GDK_KEY_Delete)
6026 {
6027 GdkEvent *new_event;
6028 GdkWindow *window;
6029 char *old_text;
6030 const char *new_text;
6031 gboolean retval;
6032 GdkScreen *screen;
6033 gboolean text_modified;
6034 gulong popup_menu_id;
6035 gint scale;
6036
6037 caja_icon_container_ensure_interactive_directory (container);
6038
6039 /* Make a copy of the current text */
6040 old_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (container->details->search_entry)));
6041 new_event = gdk_event_copy ((GdkEvent *) event);
6042 window = ((GdkEventKey *) new_event)->window;
6043 ((GdkEventKey *) new_event)->window = gtk_widget_get_window (container->details->search_entry);
6044 gtk_widget_realize (container->details->search_window);
6045
6046 popup_menu_id = g_signal_connect (container->details->search_entry,
6047 "popup_menu", G_CALLBACK (gtk_true), NULL);
6048
6049 /* Move the entry off screen */
6050 screen = gtk_widget_get_screen (GTK_WIDGET (container));
6051 scale = gtk_widget_get_scale_factor (GTK_WIDGET (container));
6052 gtk_window_move (GTK_WINDOW (container->details->search_window),
6053 WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale + 1,
6054 HeightOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale + 1);
6055 gtk_widget_show (container->details->search_window);
6056
6057 /* Send the event to the window. If the preedit_changed signal is emitted
6058 * during this event, we will set priv->imcontext_changed */
6059 container->details->imcontext_changed = FALSE;
6060 retval = gtk_widget_event (container->details->search_entry, new_event);
6061 gtk_widget_hide (container->details->search_window);
6062
6063 g_signal_handler_disconnect (container->details->search_entry,
6064 popup_menu_id);
6065
6066 /* We check to make sure that the entry tried to handle the text, and that
6067 * the text has changed. */
6068 new_text = gtk_entry_get_text (GTK_ENTRY (container->details->search_entry));
6069 text_modified = strcmp (old_text, new_text) != 0;
6070 g_free (old_text);
6071 if (container->details->imcontext_changed || /* we're in a preedit */
6072 (retval && text_modified)) /* ...or the text was modified */
6073 {
6074 if (caja_icon_container_real_start_interactive_search (container, FALSE))
6075 {
6076 gtk_widget_grab_focus (GTK_WIDGET (container));
6077 return TRUE;
6078 }
6079 else
6080 {
6081 gtk_entry_set_text (GTK_ENTRY (container->details->search_entry), "");
6082 return FALSE;
6083 }
6084 }
6085
6086 ((GdkEventKey *) new_event)->window = window;
6087 gdk_event_free (new_event);
6088 }
6089
6090 return handled;
6091 }
6092
6093 static gboolean
popup_menu(GtkWidget * widget)6094 popup_menu (GtkWidget *widget)
6095 {
6096 CajaIconContainer *container;
6097
6098 container = CAJA_ICON_CONTAINER (widget);
6099
6100 if (has_selection (container))
6101 {
6102 handle_popups (container, NULL,
6103 "context_click_selection");
6104 }
6105 else
6106 {
6107 handle_popups (container, NULL,
6108 "context_click_background");
6109 }
6110
6111 return TRUE;
6112 }
6113
6114 static void
draw_canvas_background(EelCanvas * canvas,cairo_t * cr)6115 draw_canvas_background (EelCanvas *canvas,
6116 cairo_t *cr)
6117 {
6118 /* Don't chain up to the parent to avoid clearing and redrawing */
6119 }
6120
6121 static void
grab_notify_cb(GtkWidget * widget,gboolean was_grabbed)6122 grab_notify_cb (GtkWidget *widget,
6123 gboolean was_grabbed)
6124 {
6125 CajaIconContainer *container;
6126
6127 container = CAJA_ICON_CONTAINER (widget);
6128
6129 if (container->details->rubberband_info.active &&
6130 !was_grabbed)
6131 {
6132 /* we got a (un)grab-notify during rubberband.
6133 * This happens when a new modal dialog shows
6134 * up (e.g. authentication or an error). Stop
6135 * the rubberbanding so that we can handle the
6136 * dialog. */
6137 stop_rubberbanding (container);
6138 }
6139 }
6140
6141 static void
text_ellipsis_limit_changed_container_callback(gpointer callback_data)6142 text_ellipsis_limit_changed_container_callback (gpointer callback_data)
6143 {
6144 CajaIconContainer *container;
6145
6146 container = CAJA_ICON_CONTAINER (callback_data);
6147 invalidate_label_sizes (container);
6148 schedule_redo_layout (container);
6149 }
6150
6151 static GObject*
caja_icon_container_constructor(GType type,guint n_construct_params,GObjectConstructParam * construct_params)6152 caja_icon_container_constructor (GType type,
6153 guint n_construct_params,
6154 GObjectConstructParam *construct_params)
6155 {
6156 CajaIconContainer *container;
6157 GObject *object;
6158
6159 object = G_OBJECT_CLASS (caja_icon_container_parent_class)->constructor
6160 (type,
6161 n_construct_params,
6162 construct_params);
6163
6164 container = CAJA_ICON_CONTAINER (object);
6165 if (caja_icon_container_get_is_desktop (container))
6166 {
6167 g_signal_connect_swapped (caja_desktop_preferences,
6168 "changed::" CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT,
6169 G_CALLBACK (text_ellipsis_limit_changed_container_callback),
6170 container);
6171 }
6172 else
6173 {
6174 g_signal_connect_swapped (caja_icon_view_preferences,
6175 "changed::" CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT,
6176 G_CALLBACK (text_ellipsis_limit_changed_container_callback),
6177 container);
6178 }
6179
6180 return object;
6181 }
6182
6183 /* Initialization. */
6184
6185 static void
caja_icon_container_class_init(CajaIconContainerClass * class)6186 caja_icon_container_class_init (CajaIconContainerClass *class)
6187 {
6188 GtkWidgetClass *widget_class;
6189 EelCanvasClass *canvas_class;
6190 GtkBindingSet *binding_set;
6191
6192 G_OBJECT_CLASS (class)->constructor = caja_icon_container_constructor;
6193 G_OBJECT_CLASS (class)->finalize = finalize;
6194
6195 GTK_WIDGET_CLASS (class)->destroy = destroy;
6196
6197 /* Signals. */
6198
6199 signals[SELECTION_CHANGED]
6200 = g_signal_new ("selection_changed",
6201 G_TYPE_FROM_CLASS (class),
6202 G_SIGNAL_RUN_LAST,
6203 G_STRUCT_OFFSET (CajaIconContainerClass,
6204 selection_changed),
6205 NULL, NULL,
6206 g_cclosure_marshal_VOID__VOID,
6207 G_TYPE_NONE, 0);
6208 signals[BUTTON_PRESS]
6209 = g_signal_new ("button_press",
6210 G_TYPE_FROM_CLASS (class),
6211 G_SIGNAL_RUN_LAST,
6212 G_STRUCT_OFFSET (CajaIconContainerClass,
6213 button_press),
6214 NULL, NULL,
6215 caja_marshal_BOOLEAN__POINTER,
6216 G_TYPE_BOOLEAN, 1,
6217 GDK_TYPE_EVENT);
6218 signals[ACTIVATE]
6219 = g_signal_new ("activate",
6220 G_TYPE_FROM_CLASS (class),
6221 G_SIGNAL_RUN_LAST,
6222 G_STRUCT_OFFSET (CajaIconContainerClass,
6223 activate),
6224 NULL, NULL,
6225 g_cclosure_marshal_VOID__POINTER,
6226 G_TYPE_NONE, 1,
6227 G_TYPE_POINTER);
6228 signals[ACTIVATE_ALTERNATE]
6229 = g_signal_new ("activate_alternate",
6230 G_TYPE_FROM_CLASS (class),
6231 G_SIGNAL_RUN_LAST,
6232 G_STRUCT_OFFSET (CajaIconContainerClass,
6233 activate_alternate),
6234 NULL, NULL,
6235 g_cclosure_marshal_VOID__POINTER,
6236 G_TYPE_NONE, 1,
6237 G_TYPE_POINTER);
6238 signals[CONTEXT_CLICK_SELECTION]
6239 = g_signal_new ("context_click_selection",
6240 G_TYPE_FROM_CLASS (class),
6241 G_SIGNAL_RUN_LAST,
6242 G_STRUCT_OFFSET (CajaIconContainerClass,
6243 context_click_selection),
6244 NULL, NULL,
6245 g_cclosure_marshal_VOID__POINTER,
6246 G_TYPE_NONE, 1,
6247 G_TYPE_POINTER);
6248 signals[CONTEXT_CLICK_BACKGROUND]
6249 = g_signal_new ("context_click_background",
6250 G_TYPE_FROM_CLASS (class),
6251 G_SIGNAL_RUN_LAST,
6252 G_STRUCT_OFFSET (CajaIconContainerClass,
6253 context_click_background),
6254 NULL, NULL,
6255 g_cclosure_marshal_VOID__POINTER,
6256 G_TYPE_NONE, 1,
6257 G_TYPE_POINTER);
6258 signals[MIDDLE_CLICK]
6259 = g_signal_new ("middle_click",
6260 G_TYPE_FROM_CLASS (class),
6261 G_SIGNAL_RUN_LAST,
6262 G_STRUCT_OFFSET (CajaIconContainerClass,
6263 middle_click),
6264 NULL, NULL,
6265 g_cclosure_marshal_VOID__POINTER,
6266 G_TYPE_NONE, 1,
6267 G_TYPE_POINTER);
6268 signals[ICON_POSITION_CHANGED]
6269 = g_signal_new ("icon_position_changed",
6270 G_TYPE_FROM_CLASS (class),
6271 G_SIGNAL_RUN_LAST,
6272 G_STRUCT_OFFSET (CajaIconContainerClass,
6273 icon_position_changed),
6274 NULL, NULL,
6275 caja_marshal_VOID__POINTER_POINTER,
6276 G_TYPE_NONE, 2,
6277 G_TYPE_POINTER,
6278 G_TYPE_POINTER);
6279 signals[ICON_TEXT_CHANGED]
6280 = g_signal_new ("icon_text_changed",
6281 G_TYPE_FROM_CLASS (class),
6282 G_SIGNAL_RUN_LAST,
6283 G_STRUCT_OFFSET (CajaIconContainerClass,
6284 icon_text_changed),
6285 NULL, NULL,
6286 caja_marshal_VOID__POINTER_STRING,
6287 G_TYPE_NONE, 2,
6288 G_TYPE_POINTER,
6289 G_TYPE_STRING);
6290 signals[ICON_STRETCH_STARTED]
6291 = g_signal_new ("icon_stretch_started",
6292 G_TYPE_FROM_CLASS (class),
6293 G_SIGNAL_RUN_LAST,
6294 G_STRUCT_OFFSET (CajaIconContainerClass,
6295 icon_stretch_started),
6296 NULL, NULL,
6297 g_cclosure_marshal_VOID__POINTER,
6298 G_TYPE_NONE, 1,
6299 G_TYPE_POINTER);
6300 signals[ICON_STRETCH_ENDED]
6301 = g_signal_new ("icon_stretch_ended",
6302 G_TYPE_FROM_CLASS (class),
6303 G_SIGNAL_RUN_LAST,
6304 G_STRUCT_OFFSET (CajaIconContainerClass,
6305 icon_stretch_ended),
6306 NULL, NULL,
6307 g_cclosure_marshal_VOID__POINTER,
6308 G_TYPE_NONE, 1,
6309 G_TYPE_POINTER);
6310 signals[RENAMING_ICON]
6311 = g_signal_new ("renaming_icon",
6312 G_TYPE_FROM_CLASS (class),
6313 G_SIGNAL_RUN_LAST,
6314 G_STRUCT_OFFSET (CajaIconContainerClass,
6315 renaming_icon),
6316 NULL, NULL,
6317 g_cclosure_marshal_VOID__POINTER,
6318 G_TYPE_NONE, 1,
6319 G_TYPE_POINTER);
6320 signals[GET_ICON_URI]
6321 = g_signal_new ("get_icon_uri",
6322 G_TYPE_FROM_CLASS (class),
6323 G_SIGNAL_RUN_LAST,
6324 G_STRUCT_OFFSET (CajaIconContainerClass,
6325 get_icon_uri),
6326 NULL, NULL,
6327 caja_marshal_STRING__POINTER,
6328 G_TYPE_STRING, 1,
6329 G_TYPE_POINTER);
6330 signals[GET_ICON_DROP_TARGET_URI]
6331 = g_signal_new ("get_icon_drop_target_uri",
6332 G_TYPE_FROM_CLASS (class),
6333 G_SIGNAL_RUN_LAST,
6334 G_STRUCT_OFFSET (CajaIconContainerClass,
6335 get_icon_drop_target_uri),
6336 NULL, NULL,
6337 caja_marshal_STRING__POINTER,
6338 G_TYPE_STRING, 1,
6339 G_TYPE_POINTER);
6340 signals[MOVE_COPY_ITEMS]
6341 = g_signal_new ("move_copy_items",
6342 G_TYPE_FROM_CLASS (class),
6343 G_SIGNAL_RUN_LAST,
6344 G_STRUCT_OFFSET (CajaIconContainerClass,
6345 move_copy_items),
6346 NULL, NULL,
6347 caja_marshal_VOID__POINTER_POINTER_POINTER_ENUM_INT_INT,
6348 G_TYPE_NONE, 6,
6349 G_TYPE_POINTER,
6350 G_TYPE_POINTER,
6351 G_TYPE_POINTER,
6352 GDK_TYPE_DRAG_ACTION,
6353 G_TYPE_INT,
6354 G_TYPE_INT);
6355 signals[HANDLE_NETSCAPE_URL]
6356 = g_signal_new ("handle_netscape_url",
6357 G_TYPE_FROM_CLASS (class),
6358 G_SIGNAL_RUN_LAST,
6359 G_STRUCT_OFFSET (CajaIconContainerClass,
6360 handle_netscape_url),
6361 NULL, NULL,
6362 caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
6363 G_TYPE_NONE, 5,
6364 G_TYPE_STRING,
6365 G_TYPE_STRING,
6366 GDK_TYPE_DRAG_ACTION,
6367 G_TYPE_INT,
6368 G_TYPE_INT);
6369 signals[HANDLE_URI_LIST]
6370 = g_signal_new ("handle_uri_list",
6371 G_TYPE_FROM_CLASS (class),
6372 G_SIGNAL_RUN_LAST,
6373 G_STRUCT_OFFSET (CajaIconContainerClass,
6374 handle_uri_list),
6375 NULL, NULL,
6376 caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
6377 G_TYPE_NONE, 5,
6378 G_TYPE_STRING,
6379 G_TYPE_STRING,
6380 GDK_TYPE_DRAG_ACTION,
6381 G_TYPE_INT,
6382 G_TYPE_INT);
6383 signals[HANDLE_TEXT]
6384 = g_signal_new ("handle_text",
6385 G_TYPE_FROM_CLASS (class),
6386 G_SIGNAL_RUN_LAST,
6387 G_STRUCT_OFFSET (CajaIconContainerClass,
6388 handle_text),
6389 NULL, NULL,
6390 caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
6391 G_TYPE_NONE, 5,
6392 G_TYPE_STRING,
6393 G_TYPE_STRING,
6394 GDK_TYPE_DRAG_ACTION,
6395 G_TYPE_INT,
6396 G_TYPE_INT);
6397 signals[HANDLE_RAW]
6398 = g_signal_new ("handle_raw",
6399 G_TYPE_FROM_CLASS (class),
6400 G_SIGNAL_RUN_LAST,
6401 G_STRUCT_OFFSET (CajaIconContainerClass,
6402 handle_raw),
6403 NULL, NULL,
6404 caja_marshal_VOID__POINTER_INT_STRING_STRING_ENUM_INT_INT,
6405 G_TYPE_NONE, 7,
6406 G_TYPE_POINTER,
6407 G_TYPE_INT,
6408 G_TYPE_STRING,
6409 G_TYPE_STRING,
6410 GDK_TYPE_DRAG_ACTION,
6411 G_TYPE_INT,
6412 G_TYPE_INT);
6413 signals[GET_CONTAINER_URI]
6414 = g_signal_new ("get_container_uri",
6415 G_TYPE_FROM_CLASS (class),
6416 G_SIGNAL_RUN_LAST,
6417 G_STRUCT_OFFSET (CajaIconContainerClass,
6418 get_container_uri),
6419 NULL, NULL,
6420 caja_marshal_STRING__VOID,
6421 G_TYPE_STRING, 0);
6422 signals[CAN_ACCEPT_ITEM]
6423 = g_signal_new ("can_accept_item",
6424 G_TYPE_FROM_CLASS (class),
6425 G_SIGNAL_RUN_LAST,
6426 G_STRUCT_OFFSET (CajaIconContainerClass,
6427 can_accept_item),
6428 NULL, NULL,
6429 caja_marshal_INT__POINTER_STRING,
6430 G_TYPE_INT, 2,
6431 G_TYPE_POINTER,
6432 G_TYPE_STRING);
6433 signals[GET_STORED_ICON_POSITION]
6434 = g_signal_new ("get_stored_icon_position",
6435 G_TYPE_FROM_CLASS (class),
6436 G_SIGNAL_RUN_LAST,
6437 G_STRUCT_OFFSET (CajaIconContainerClass,
6438 get_stored_icon_position),
6439 NULL, NULL,
6440 caja_marshal_BOOLEAN__POINTER_POINTER,
6441 G_TYPE_BOOLEAN, 2,
6442 G_TYPE_POINTER,
6443 G_TYPE_POINTER);
6444 signals[GET_STORED_LAYOUT_TIMESTAMP]
6445 = g_signal_new ("get_stored_layout_timestamp",
6446 G_TYPE_FROM_CLASS (class),
6447 G_SIGNAL_RUN_LAST,
6448 G_STRUCT_OFFSET (CajaIconContainerClass,
6449 get_stored_layout_timestamp),
6450 NULL, NULL,
6451 caja_marshal_BOOLEAN__POINTER_POINTER,
6452 G_TYPE_BOOLEAN, 2,
6453 G_TYPE_POINTER,
6454 G_TYPE_POINTER);
6455 signals[STORE_LAYOUT_TIMESTAMP]
6456 = g_signal_new ("store_layout_timestamp",
6457 G_TYPE_FROM_CLASS (class),
6458 G_SIGNAL_RUN_LAST,
6459 G_STRUCT_OFFSET (CajaIconContainerClass,
6460 store_layout_timestamp),
6461 NULL, NULL,
6462 caja_marshal_BOOLEAN__POINTER_POINTER,
6463 G_TYPE_BOOLEAN, 2,
6464 G_TYPE_POINTER,
6465 G_TYPE_POINTER);
6466 signals[LAYOUT_CHANGED]
6467 = g_signal_new ("layout_changed",
6468 G_TYPE_FROM_CLASS (class),
6469 G_SIGNAL_RUN_LAST,
6470 G_STRUCT_OFFSET (CajaIconContainerClass,
6471 layout_changed),
6472 NULL, NULL,
6473 g_cclosure_marshal_VOID__VOID,
6474 G_TYPE_NONE, 0);
6475 signals[PREVIEW]
6476 = g_signal_new ("preview",
6477 G_TYPE_FROM_CLASS (class),
6478 G_SIGNAL_RUN_LAST,
6479 G_STRUCT_OFFSET (CajaIconContainerClass,
6480 preview),
6481 NULL, NULL,
6482 caja_marshal_INT__POINTER_BOOLEAN,
6483 G_TYPE_INT, 2,
6484 G_TYPE_POINTER,
6485 G_TYPE_BOOLEAN);
6486 signals[BAND_SELECT_STARTED]
6487 = g_signal_new ("band_select_started",
6488 G_TYPE_FROM_CLASS (class),
6489 G_SIGNAL_RUN_LAST,
6490 G_STRUCT_OFFSET (CajaIconContainerClass,
6491 band_select_started),
6492 NULL, NULL,
6493 g_cclosure_marshal_VOID__VOID,
6494 G_TYPE_NONE, 0);
6495 signals[BAND_SELECT_ENDED]
6496 = g_signal_new ("band_select_ended",
6497 G_TYPE_FROM_CLASS (class),
6498 G_SIGNAL_RUN_LAST,
6499 G_STRUCT_OFFSET (CajaIconContainerClass,
6500 band_select_ended),
6501 NULL, NULL,
6502 g_cclosure_marshal_VOID__VOID,
6503 G_TYPE_NONE, 0);
6504 signals[ICON_ADDED]
6505 = g_signal_new ("icon_added",
6506 G_TYPE_FROM_CLASS (class),
6507 G_SIGNAL_RUN_LAST,
6508 G_STRUCT_OFFSET (CajaIconContainerClass,
6509 icon_added),
6510 NULL, NULL,
6511 g_cclosure_marshal_VOID__POINTER,
6512 G_TYPE_NONE, 1, G_TYPE_POINTER);
6513 signals[ICON_REMOVED]
6514 = g_signal_new ("icon_removed",
6515 G_TYPE_FROM_CLASS (class),
6516 G_SIGNAL_RUN_LAST,
6517 G_STRUCT_OFFSET (CajaIconContainerClass,
6518 icon_removed),
6519 NULL, NULL,
6520 g_cclosure_marshal_VOID__POINTER,
6521 G_TYPE_NONE, 1, G_TYPE_POINTER);
6522
6523 signals[CLEARED]
6524 = g_signal_new ("cleared",
6525 G_TYPE_FROM_CLASS (class),
6526 G_SIGNAL_RUN_LAST,
6527 G_STRUCT_OFFSET (CajaIconContainerClass,
6528 cleared),
6529 NULL, NULL,
6530 g_cclosure_marshal_VOID__VOID,
6531 G_TYPE_NONE, 0);
6532
6533 signals[START_INTERACTIVE_SEARCH]
6534 = g_signal_new ("start_interactive_search",
6535 G_TYPE_FROM_CLASS (class),
6536 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
6537 G_STRUCT_OFFSET (CajaIconContainerClass,
6538 start_interactive_search),
6539 NULL, NULL,
6540 caja_marshal_BOOLEAN__VOID,
6541 G_TYPE_BOOLEAN, 0);
6542
6543 /* GtkWidget class. */
6544
6545 widget_class = GTK_WIDGET_CLASS (class);
6546 widget_class->size_allocate = size_allocate;
6547 widget_class->get_request_mode = get_request_mode;
6548 widget_class->get_preferred_width = get_prefered_width;
6549 widget_class->get_preferred_height = get_prefered_height;
6550 widget_class->draw = draw;
6551 widget_class->realize = realize;
6552 widget_class->unrealize = unrealize;
6553 widget_class->button_press_event = button_press_event;
6554 widget_class->button_release_event = button_release_event;
6555 widget_class->motion_notify_event = motion_notify_event;
6556 widget_class->key_press_event = key_press_event;
6557 widget_class->popup_menu = popup_menu;
6558 widget_class->style_updated = style_updated;
6559 widget_class->grab_notify = grab_notify_cb;
6560
6561 gtk_widget_class_set_accessible_type (widget_class, caja_icon_container_accessible_get_type ());
6562
6563 canvas_class = EEL_CANVAS_CLASS (class);
6564 canvas_class->draw_background = draw_canvas_background;
6565 class->start_interactive_search = caja_icon_container_start_interactive_search;
6566
6567 gtk_widget_class_install_style_property (widget_class,
6568 g_param_spec_boxed ("selection_box_rgba",
6569 "Selection Box RGBA",
6570 "Color of the selection box",
6571 GDK_TYPE_RGBA,
6572 G_PARAM_READABLE));
6573 gtk_widget_class_install_style_property (widget_class,
6574 g_param_spec_boxed ("light_info_rgba",
6575 "Light Info RGBA",
6576 "Color used for information text against a dark background",
6577 GDK_TYPE_RGBA,
6578 G_PARAM_READABLE));
6579 gtk_widget_class_install_style_property (widget_class,
6580 g_param_spec_boxed ("dark_info_rgba",
6581 "Dark Info RGBA",
6582 "Color used for information text against a light background",
6583 GDK_TYPE_RGBA,
6584 G_PARAM_READABLE));
6585 gtk_widget_class_install_style_property (widget_class,
6586 g_param_spec_boolean ("activate_prelight_icon_label",
6587 "Activate Prelight Icon Label",
6588 "Whether icon labels should make use of its prelight color in prelight state",
6589 FALSE,
6590 G_PARAM_READABLE));
6591
6592
6593 binding_set = gtk_binding_set_by_class (class);
6594
6595 gtk_binding_entry_add_signal (binding_set, GDK_KEY_f, GDK_CONTROL_MASK, "start_interactive_search", 0);
6596 gtk_binding_entry_add_signal (binding_set, GDK_KEY_F, GDK_CONTROL_MASK, "start_interactive_search", 0);
6597 }
6598
6599 static void
update_selected(CajaIconContainer * container)6600 update_selected (CajaIconContainer *container)
6601 {
6602 GList *node;
6603 CajaIcon *icon = NULL;
6604
6605 for (node = container->details->icons; node != NULL; node = node->next)
6606 {
6607 icon = node->data;
6608 if (icon->is_selected)
6609 {
6610 eel_canvas_item_request_update (EEL_CANVAS_ITEM (icon->item));
6611 }
6612 }
6613 }
6614
6615 static gboolean
handle_focus_in_event(GtkWidget * widget,GdkEventFocus * event,gpointer user_data)6616 handle_focus_in_event (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
6617 {
6618 update_selected (CAJA_ICON_CONTAINER (widget));
6619
6620 return FALSE;
6621 }
6622
6623 static gboolean
handle_focus_out_event(GtkWidget * widget,GdkEventFocus * event,gpointer user_data)6624 handle_focus_out_event (GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
6625 {
6626 /* End renaming and commit change. */
6627 end_renaming_mode (CAJA_ICON_CONTAINER (widget), TRUE);
6628 update_selected (CAJA_ICON_CONTAINER (widget));
6629
6630 return FALSE;
6631 }
6632
6633 static void
handle_scale_factor_changed(GObject * object,GParamSpec * pspec,gpointer user_data)6634 handle_scale_factor_changed (GObject *object,
6635 GParamSpec *pspec,
6636 gpointer user_data)
6637 {
6638 invalidate_labels (CAJA_ICON_CONTAINER (object));
6639 caja_icon_container_request_update_all (CAJA_ICON_CONTAINER (object));
6640 }
6641
6642
6643 static int text_ellipsis_limits[CAJA_ZOOM_LEVEL_N_ENTRIES];
6644 static int desktop_text_ellipsis_limit;
6645
6646 static gboolean
get_text_ellipsis_limit_for_zoom(char ** strs,const char * zoom_level,int * limit)6647 get_text_ellipsis_limit_for_zoom (char **strs,
6648 const char *zoom_level,
6649 int *limit)
6650 {
6651 char *str;
6652 gboolean success;
6653
6654 success = FALSE;
6655
6656 /* default */
6657 *limit = 3;
6658
6659 if (zoom_level != NULL)
6660 {
6661 str = g_strdup_printf ("%s:%%d", zoom_level);
6662 }
6663 else
6664 {
6665 str = g_strdup ("%d");
6666 }
6667
6668 if (strs != NULL)
6669 {
6670 char **p;
6671
6672 for (p = strs; *p != NULL; p++)
6673 {
6674 if (sscanf (*p, str, limit))
6675 {
6676 success = TRUE;
6677 }
6678 }
6679 }
6680
6681 g_free (str);
6682
6683 return success;
6684 }
6685
6686 static const char * zoom_level_names[] = {
6687 "smallest",
6688 "smaller",
6689 "small",
6690 "standard",
6691 "large",
6692 "larger",
6693 "largest"
6694 };
6695
6696 static void
text_ellipsis_limit_changed_callback(gpointer callback_data)6697 text_ellipsis_limit_changed_callback (gpointer callback_data)
6698 {
6699 char **pref;
6700 unsigned int i;
6701 int one_limit;
6702
6703 pref = g_settings_get_strv (caja_icon_view_preferences, CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT);
6704
6705 /* set default */
6706 get_text_ellipsis_limit_for_zoom (pref, NULL, &one_limit);
6707 for (i = 0; i < CAJA_ZOOM_LEVEL_N_ENTRIES; i++)
6708 {
6709 text_ellipsis_limits[i] = one_limit;
6710 }
6711
6712 /* override for each zoom level */
6713 for (i = 0; i < G_N_ELEMENTS(zoom_level_names); i++) {
6714 if (get_text_ellipsis_limit_for_zoom (pref,
6715 zoom_level_names[i],
6716 &one_limit)) {
6717 text_ellipsis_limits[i] = one_limit;
6718 }
6719 }
6720
6721 g_strfreev (pref);
6722 }
6723
6724 static void
desktop_text_ellipsis_limit_changed_callback(gpointer callback_data)6725 desktop_text_ellipsis_limit_changed_callback (gpointer callback_data)
6726 {
6727 int pref;
6728
6729 pref = g_settings_get_int (caja_desktop_preferences, CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT);
6730 desktop_text_ellipsis_limit = pref;
6731 }
6732
6733 static void
caja_icon_container_init(CajaIconContainer * container)6734 caja_icon_container_init (CajaIconContainer *container)
6735 {
6736 CajaIconContainerDetails *details;
6737 EelBackground *background;
6738 static gboolean setup_prefs = FALSE;
6739
6740 details = g_new0 (CajaIconContainerDetails, 1);
6741
6742 details->icon_set = g_hash_table_new (g_direct_hash, g_direct_equal);
6743 details->layout_timestamp = UNDEFINED_TIME;
6744
6745 details->zoom_level = CAJA_ZOOM_LEVEL_STANDARD;
6746
6747 details->font_size_table[CAJA_ZOOM_LEVEL_SMALLEST] = -2 * PANGO_SCALE;
6748 details->font_size_table[CAJA_ZOOM_LEVEL_SMALLER] = -2 * PANGO_SCALE;
6749 details->font_size_table[CAJA_ZOOM_LEVEL_SMALL] = -0 * PANGO_SCALE;
6750 details->font_size_table[CAJA_ZOOM_LEVEL_STANDARD] = 0 * PANGO_SCALE;
6751 details->font_size_table[CAJA_ZOOM_LEVEL_LARGE] = 0 * PANGO_SCALE;
6752 details->font_size_table[CAJA_ZOOM_LEVEL_LARGER] = 0 * PANGO_SCALE;
6753 details->font_size_table[CAJA_ZOOM_LEVEL_LARGEST] = 0 * PANGO_SCALE;
6754
6755 container->details = details;
6756
6757 /* when the background changes, we must set up the label text color */
6758 background = eel_get_widget_background (GTK_WIDGET (container));
6759
6760 g_signal_connect (container, "focus-in-event",
6761 G_CALLBACK (handle_focus_in_event), NULL);
6762 g_signal_connect (container, "focus-out-event",
6763 G_CALLBACK (handle_focus_out_event), NULL);
6764
6765 g_signal_connect (container, "notify::scale-factor",
6766 G_CALLBACK (handle_scale_factor_changed), NULL);
6767
6768 eel_background_set_use_base (background, TRUE);
6769
6770 if (!setup_prefs)
6771 {
6772 g_signal_connect_swapped (caja_icon_view_preferences,
6773 "changed::" CAJA_PREFERENCES_ICON_VIEW_TEXT_ELLIPSIS_LIMIT,
6774 G_CALLBACK (text_ellipsis_limit_changed_callback),
6775 NULL);
6776 text_ellipsis_limit_changed_callback (NULL);
6777
6778 g_signal_connect_swapped (caja_icon_view_preferences,
6779 "changed::" CAJA_PREFERENCES_DESKTOP_TEXT_ELLIPSIS_LIMIT,
6780 G_CALLBACK (desktop_text_ellipsis_limit_changed_callback),
6781 NULL);
6782 desktop_text_ellipsis_limit_changed_callback (NULL);
6783
6784 setup_prefs = TRUE;
6785 }
6786 }
6787
6788 typedef struct
6789 {
6790 CajaIconContainer *container;
6791 GdkEventButton *event;
6792 } ContextMenuParameters;
6793
6794 static gboolean
handle_icon_double_click(CajaIconContainer * container,CajaIcon * icon,GdkEventButton * event)6795 handle_icon_double_click (CajaIconContainer *container,
6796 CajaIcon *icon,
6797 GdkEventButton *event)
6798 {
6799 CajaIconContainerDetails *details;
6800
6801 if (event->button != DRAG_BUTTON)
6802 {
6803 return FALSE;
6804 }
6805
6806 details = container->details;
6807
6808 if (!details->single_click_mode &&
6809 clicked_within_double_click_interval (container) &&
6810 details->double_click_icon[0] == details->double_click_icon[1] &&
6811 details->double_click_button[0] == details->double_click_button[1])
6812 {
6813 if (!button_event_modifies_selection (event))
6814 {
6815 activate_selected_items (container);
6816 return TRUE;
6817 }
6818 else if ((event->state & GDK_CONTROL_MASK) == 0 &&
6819 (event->state & GDK_SHIFT_MASK) != 0)
6820 {
6821 activate_selected_items_alternate (container, icon);
6822 return TRUE;
6823 }
6824 }
6825
6826 return FALSE;
6827 }
6828
6829 /* CajaIcon event handling. */
6830
6831 /* Conceptually, pressing button 1 together with CTRL or SHIFT toggles
6832 * selection of a single icon without affecting the other icons;
6833 * without CTRL or SHIFT, it selects a single icon and un-selects all
6834 * the other icons. But in this latter case, the de-selection should
6835 * only happen when the button is released if the icon is already
6836 * selected, because the user might select multiple icons and drag all
6837 * of them by doing a simple click-drag.
6838 */
6839
6840 static gboolean
handle_icon_button_press(CajaIconContainer * container,CajaIcon * icon,GdkEventButton * event)6841 handle_icon_button_press (CajaIconContainer *container,
6842 CajaIcon *icon,
6843 GdkEventButton *event)
6844 {
6845 CajaIconContainerDetails *details;
6846
6847 details = container->details;
6848
6849 if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)
6850 {
6851 return TRUE;
6852 }
6853
6854 if (event->button != DRAG_BUTTON
6855 && event->button != CONTEXTUAL_MENU_BUTTON
6856 && event->button != DRAG_MENU_BUTTON)
6857 {
6858 return TRUE;
6859 }
6860
6861 if ((event->button == DRAG_BUTTON) &&
6862 event->type == GDK_BUTTON_PRESS)
6863 {
6864 /* The next double click has to be on this icon */
6865 details->double_click_icon[1] = details->double_click_icon[0];
6866 details->double_click_icon[0] = icon;
6867
6868 details->double_click_button[1] = details->double_click_button[0];
6869 details->double_click_button[0] = event->button;
6870 }
6871
6872 if (handle_icon_double_click (container, icon, event))
6873 {
6874 /* Double clicking does not trigger a D&D action. */
6875 details->drag_button = 0;
6876 details->drag_icon = NULL;
6877 return TRUE;
6878 }
6879
6880 if (event->button == DRAG_BUTTON
6881 || event->button == DRAG_MENU_BUTTON)
6882 {
6883 details->drag_button = event->button;
6884 details->drag_icon = icon;
6885 details->drag_x = event->x;
6886 details->drag_y = event->y;
6887 details->drag_state = DRAG_STATE_MOVE_OR_COPY;
6888 details->drag_started = FALSE;
6889
6890 /* Check to see if this is a click on the stretch handles.
6891 * If so, it won't modify the selection.
6892 */
6893 if (icon == container->details->stretch_icon)
6894 {
6895 if (start_stretching (container, (GdkEvent *)event))
6896 {
6897 return TRUE;
6898 }
6899 }
6900 }
6901
6902 /* Modify the selection as appropriate. Selection is modified
6903 * the same way for contextual menu as it would be without.
6904 */
6905 details->icon_selected_on_button_down = icon->is_selected;
6906
6907 if ((event->button == DRAG_BUTTON || event->button == MIDDLE_BUTTON) &&
6908 (event->state & GDK_SHIFT_MASK) != 0)
6909 {
6910 CajaIcon *start_icon;
6911
6912 start_icon = details->range_selection_base_icon;
6913 if (start_icon == NULL || !start_icon->is_selected)
6914 {
6915 start_icon = icon;
6916 details->range_selection_base_icon = icon;
6917 }
6918 if (select_range (container, start_icon, icon,
6919 (event->state & GDK_CONTROL_MASK) == 0))
6920 {
6921 g_signal_emit (container,
6922 signals[SELECTION_CHANGED], 0);
6923 }
6924 }
6925 else if (!details->icon_selected_on_button_down)
6926 {
6927 details->range_selection_base_icon = icon;
6928 if (button_event_modifies_selection (event))
6929 {
6930 icon_toggle_selected (container, icon);
6931 g_signal_emit (container,
6932 signals[SELECTION_CHANGED], 0);
6933 }
6934 else
6935 {
6936 select_one_unselect_others (container, icon);
6937 g_signal_emit (container,
6938 signals[SELECTION_CHANGED], 0);
6939 }
6940 }
6941
6942 if (event->button == CONTEXTUAL_MENU_BUTTON)
6943 {
6944 g_signal_emit (container,
6945 signals[CONTEXT_CLICK_SELECTION], 0,
6946 event);
6947 }
6948
6949
6950 return TRUE;
6951 }
6952
6953 static int
item_event_callback(EelCanvasItem * item,GdkEvent * event,gpointer data)6954 item_event_callback (EelCanvasItem *item,
6955 GdkEvent *event,
6956 gpointer data)
6957 {
6958 CajaIconContainer *container;
6959 CajaIcon *icon;
6960
6961 container = CAJA_ICON_CONTAINER (data);
6962
6963 icon = CAJA_ICON_CANVAS_ITEM (item)->user_data;
6964 g_assert (icon != NULL);
6965
6966 switch (event->type)
6967 {
6968 case GDK_BUTTON_PRESS:
6969 if (handle_icon_button_press (container, icon, &event->button))
6970 {
6971 /* Stop the event from being passed along further. Returning
6972 * TRUE ain't enough.
6973 */
6974 return TRUE;
6975 }
6976 return FALSE;
6977 default:
6978 return FALSE;
6979 }
6980 }
6981
6982 GtkWidget *
caja_icon_container_new(void)6983 caja_icon_container_new (void)
6984 {
6985 return gtk_widget_new (CAJA_TYPE_ICON_CONTAINER, NULL);
6986 }
6987
6988 /* Clear all of the icons in the container. */
6989 void
caja_icon_container_clear(CajaIconContainer * container)6990 caja_icon_container_clear (CajaIconContainer *container)
6991 {
6992 CajaIconContainerDetails *details;
6993 GList *p;
6994 CajaIcon *icon = NULL;
6995
6996 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
6997
6998 details = container->details;
6999 details->layout_timestamp = UNDEFINED_TIME;
7000 details->store_layout_timestamps_when_finishing_new_icons = FALSE;
7001
7002 if (details->icons == NULL)
7003 {
7004 return;
7005 }
7006
7007 end_renaming_mode (container, TRUE);
7008
7009 clear_keyboard_focus (container);
7010 clear_keyboard_rubberband_start (container);
7011 unschedule_keyboard_icon_reveal (container);
7012 set_pending_icon_to_reveal (container, NULL);
7013 details->stretch_icon = NULL;
7014 details->drop_target = NULL;
7015
7016 for (p = details->icons; p != NULL; p = p->next)
7017 {
7018 icon = p->data;
7019 if (icon->is_monitored)
7020 {
7021 caja_icon_container_stop_monitor_top_left (container,
7022 icon->data,
7023 icon);
7024 }
7025 icon_free (p->data);
7026 }
7027 g_list_free (details->icons);
7028 details->icons = NULL;
7029 g_list_free (details->new_icons);
7030 details->new_icons = NULL;
7031
7032 g_hash_table_destroy (details->icon_set);
7033 details->icon_set = g_hash_table_new (g_direct_hash, g_direct_equal);
7034
7035 caja_icon_container_update_scroll_region (container);
7036 }
7037
7038 gboolean
caja_icon_container_is_empty(CajaIconContainer * container)7039 caja_icon_container_is_empty (CajaIconContainer *container)
7040 {
7041 return container->details->icons == NULL;
7042 }
7043
7044 CajaIconData *
caja_icon_container_get_first_visible_icon(CajaIconContainer * container)7045 caja_icon_container_get_first_visible_icon (CajaIconContainer *container)
7046 {
7047 GList *l;
7048 CajaIcon *icon, *best_icon;
7049 double x, y;
7050 double x1, y1, x2, y2;
7051 double *pos, best_pos;
7052 double hadj_v, vadj_v, h_page_size;
7053 gboolean better_icon;
7054 gboolean compare_lt;
7055
7056 hadj_v = gtk_adjustment_get_value (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
7057 vadj_v = gtk_adjustment_get_value (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container)));
7058 h_page_size = gtk_adjustment_get_page_size (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container)));
7059
7060 if (caja_icon_container_is_layout_rtl (container))
7061 {
7062 x = hadj_v + h_page_size - ICON_PAD_LEFT - 1;
7063 y = vadj_v;
7064 }
7065 else
7066 {
7067 x = hadj_v;
7068 y = vadj_v;
7069 }
7070
7071 eel_canvas_c2w (EEL_CANVAS (container),
7072 x, y,
7073 &x, &y);
7074
7075 l = container->details->icons;
7076 best_icon = NULL;
7077 best_pos = 0;
7078 while (l != NULL)
7079 {
7080 icon = l->data;
7081
7082 if (icon_is_positioned (icon))
7083 {
7084 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
7085 &x1, &y1, &x2, &y2);
7086
7087 compare_lt = FALSE;
7088 if (caja_icon_container_is_layout_vertical (container))
7089 {
7090 pos = &x1;
7091 if (caja_icon_container_is_layout_rtl (container))
7092 {
7093 compare_lt = TRUE;
7094 better_icon = x1 < x + ICON_PAD_LEFT;
7095 }
7096 else
7097 {
7098 better_icon = x2 > x + ICON_PAD_LEFT;
7099 }
7100 }
7101 else
7102 {
7103 pos = &y1;
7104 better_icon = y2 > y + ICON_PAD_TOP;
7105 }
7106 if (better_icon)
7107 {
7108 if (best_icon == NULL)
7109 {
7110 better_icon = TRUE;
7111 }
7112 else if (compare_lt)
7113 {
7114 better_icon = best_pos < *pos;
7115 }
7116 else
7117 {
7118 better_icon = best_pos > *pos;
7119 }
7120
7121 if (better_icon)
7122 {
7123 best_icon = icon;
7124 best_pos = *pos;
7125 }
7126 }
7127 }
7128
7129 l = l->next;
7130 }
7131
7132 return best_icon ? best_icon->data : NULL;
7133 }
7134
7135 /* puts the icon at the top of the screen */
7136 void
caja_icon_container_scroll_to_icon(CajaIconContainer * container,CajaIconData * data)7137 caja_icon_container_scroll_to_icon (CajaIconContainer *container,
7138 CajaIconData *data)
7139 {
7140 GList *l;
7141 GtkAdjustment *hadj, *vadj;
7142 EelIRect bounds;
7143 GtkAllocation allocation;
7144 CajaIcon *icon = NULL;
7145
7146 hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
7147 vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
7148 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
7149
7150 /* We need to force a relayout now if there are updates queued
7151 * since we need the final positions */
7152 caja_icon_container_layout_now (container);
7153
7154 l = container->details->icons;
7155 while (l != NULL) {
7156 icon = l->data;
7157
7158 if (icon->data == data &&
7159 icon_is_positioned (icon)) {
7160
7161 if (caja_icon_container_is_auto_layout (container)) {
7162 /* ensure that we reveal the entire row/column */
7163 icon_get_row_and_column_bounds (container, icon, &bounds, TRUE);
7164 } else {
7165 item_get_canvas_bounds (EEL_CANVAS_ITEM (icon->item), &bounds, TRUE);
7166 }
7167
7168 if (caja_icon_container_is_layout_vertical (container)) {
7169 if (caja_icon_container_is_layout_rtl (container)) {
7170 gtk_adjustment_set_value (hadj, bounds.x1 - allocation.width);
7171 } else {
7172 gtk_adjustment_set_value (hadj, bounds.x0);
7173 }
7174 } else {
7175 gtk_adjustment_set_value (vadj, bounds.y0);
7176 }
7177 }
7178
7179 l = l->next;
7180 }
7181 }
7182
7183 /* Call a function for all the icons. */
7184 typedef struct
7185 {
7186 CajaIconCallback callback;
7187 gpointer callback_data;
7188 } CallbackAndData;
7189
7190 static void
call_icon_callback(gpointer data,gpointer callback_data)7191 call_icon_callback (gpointer data, gpointer callback_data)
7192 {
7193 CajaIcon *icon;
7194 CallbackAndData *callback_and_data;
7195
7196 icon = data;
7197 callback_and_data = callback_data;
7198 (* callback_and_data->callback) (icon->data, callback_and_data->callback_data);
7199 }
7200
7201 void
caja_icon_container_for_each(CajaIconContainer * container,CajaIconCallback callback,gpointer callback_data)7202 caja_icon_container_for_each (CajaIconContainer *container,
7203 CajaIconCallback callback,
7204 gpointer callback_data)
7205 {
7206 CallbackAndData callback_and_data;
7207
7208 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
7209
7210 callback_and_data.callback = callback;
7211 callback_and_data.callback_data = callback_data;
7212
7213 g_list_foreach (container->details->icons,
7214 call_icon_callback, &callback_and_data);
7215 }
7216
7217 static int
selection_changed_at_idle_callback(gpointer data)7218 selection_changed_at_idle_callback (gpointer data)
7219 {
7220 CajaIconContainer *container;
7221
7222 container = CAJA_ICON_CONTAINER (data);
7223
7224 g_signal_emit (container,
7225 signals[SELECTION_CHANGED], 0);
7226
7227 container->details->selection_changed_id = 0;
7228 return FALSE;
7229 }
7230
7231 /* utility routine to remove a single icon from the container */
7232
7233 static void
icon_destroy(CajaIconContainer * container,CajaIcon * icon)7234 icon_destroy (CajaIconContainer *container,
7235 CajaIcon *icon)
7236 {
7237 CajaIconContainerDetails *details;
7238 gboolean was_selected;
7239 CajaIcon *icon_to_focus;
7240 GList *item;
7241
7242 details = container->details;
7243
7244 item = g_list_find (details->icons, icon);
7245 item = item->next ? item->next : item->prev;
7246 icon_to_focus = (item != NULL) ? item->data : NULL;
7247
7248 details->icons = g_list_remove (details->icons, icon);
7249 details->new_icons = g_list_remove (details->new_icons, icon);
7250 g_hash_table_remove (details->icon_set, icon->data);
7251
7252 was_selected = icon->is_selected;
7253
7254 if (details->keyboard_focus == icon ||
7255 details->keyboard_focus == NULL)
7256 {
7257 if (icon_to_focus != NULL)
7258 {
7259 set_keyboard_focus (container, icon_to_focus);
7260 }
7261 else
7262 {
7263 clear_keyboard_focus (container);
7264 }
7265 }
7266
7267 if (details->keyboard_rubberband_start == icon)
7268 {
7269 clear_keyboard_rubberband_start (container);
7270 }
7271
7272 if (details->keyboard_icon_to_reveal == icon)
7273 {
7274 unschedule_keyboard_icon_reveal (container);
7275 }
7276 if (details->drag_icon == icon)
7277 {
7278 clear_drag_state (container);
7279 }
7280 if (details->drop_target == icon)
7281 {
7282 details->drop_target = NULL;
7283 }
7284 if (details->range_selection_base_icon == icon)
7285 {
7286 details->range_selection_base_icon = NULL;
7287 }
7288 if (details->pending_icon_to_reveal == icon)
7289 {
7290 set_pending_icon_to_reveal (container, NULL);
7291 }
7292 if (details->stretch_icon == icon)
7293 {
7294 details->stretch_icon = NULL;
7295 }
7296
7297 if (icon->is_monitored)
7298 {
7299 caja_icon_container_stop_monitor_top_left (container,
7300 icon->data,
7301 icon);
7302 }
7303 icon_free (icon);
7304
7305 if (was_selected)
7306 {
7307 /* Coalesce multiple removals causing multiple selection_changed events */
7308 details->selection_changed_id = g_idle_add (selection_changed_at_idle_callback, container);
7309 }
7310 }
7311
7312 /* activate any selected items in the container */
7313 static void
activate_selected_items(CajaIconContainer * container)7314 activate_selected_items (CajaIconContainer *container)
7315 {
7316 GList *selection;
7317
7318 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
7319
7320 selection = caja_icon_container_get_selection (container);
7321 if (selection != NULL)
7322 {
7323 g_signal_emit (container,
7324 signals[ACTIVATE], 0,
7325 selection);
7326 }
7327 g_list_free (selection);
7328 }
7329
7330 static void
activate_selected_items_alternate(CajaIconContainer * container,CajaIcon * icon)7331 activate_selected_items_alternate (CajaIconContainer *container,
7332 CajaIcon *icon)
7333 {
7334 GList *selection;
7335
7336 g_assert (CAJA_IS_ICON_CONTAINER (container));
7337
7338 if (icon != NULL)
7339 {
7340 selection = g_list_prepend (NULL, icon->data);
7341 }
7342 else
7343 {
7344 selection = caja_icon_container_get_selection (container);
7345 }
7346 if (selection != NULL)
7347 {
7348 g_signal_emit (container,
7349 signals[ACTIVATE_ALTERNATE], 0,
7350 selection);
7351 }
7352 g_list_free (selection);
7353 }
7354
7355 static CajaIcon *
get_icon_being_renamed(CajaIconContainer * container)7356 get_icon_being_renamed (CajaIconContainer *container)
7357 {
7358 CajaIcon *rename_icon;
7359
7360 if (!is_renaming (container))
7361 {
7362 return NULL;
7363 }
7364
7365 g_assert (!has_multiple_selection (container));
7366
7367 rename_icon = get_first_selected_icon (container);
7368 g_assert (rename_icon != NULL);
7369
7370 return rename_icon;
7371 }
7372
7373 static CajaIconInfo *
caja_icon_container_get_icon_images(CajaIconContainer * container,CajaIconData * data,int size,GList ** emblem_pixbufs,char ** embedded_text,gboolean for_drag_accept,gboolean need_large_embeddded_text,gboolean * embedded_text_needs_loading,gboolean * has_open_window)7374 caja_icon_container_get_icon_images (CajaIconContainer *container,
7375 CajaIconData *data,
7376 int size,
7377 GList **emblem_pixbufs,
7378 char **embedded_text,
7379 gboolean for_drag_accept,
7380 gboolean need_large_embeddded_text,
7381 gboolean *embedded_text_needs_loading,
7382 gboolean *has_open_window)
7383 {
7384 CajaIconContainerClass *klass;
7385
7386 klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
7387 g_assert (klass->get_icon_images != NULL);
7388
7389 return klass->get_icon_images (container, data, size, emblem_pixbufs, embedded_text, for_drag_accept, need_large_embeddded_text, embedded_text_needs_loading, has_open_window);
7390 }
7391
7392 static void
caja_icon_container_freeze_updates(CajaIconContainer * container)7393 caja_icon_container_freeze_updates (CajaIconContainer *container)
7394 {
7395 CajaIconContainerClass *klass;
7396
7397 klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
7398 g_assert (klass->freeze_updates != NULL);
7399
7400 klass->freeze_updates (container);
7401 }
7402
7403 static void
caja_icon_container_unfreeze_updates(CajaIconContainer * container)7404 caja_icon_container_unfreeze_updates (CajaIconContainer *container)
7405 {
7406 CajaIconContainerClass *klass;
7407
7408 klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
7409 g_assert (klass->unfreeze_updates != NULL);
7410
7411 klass->unfreeze_updates (container);
7412 }
7413
7414 static void
caja_icon_container_start_monitor_top_left(CajaIconContainer * container,CajaIconData * data,gconstpointer client,gboolean large_text)7415 caja_icon_container_start_monitor_top_left (CajaIconContainer *container,
7416 CajaIconData *data,
7417 gconstpointer client,
7418 gboolean large_text)
7419 {
7420 CajaIconContainerClass *klass;
7421
7422 klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
7423 g_assert (klass->start_monitor_top_left != NULL);
7424
7425 klass->start_monitor_top_left (container, data, client, large_text);
7426 }
7427
7428 static void
caja_icon_container_stop_monitor_top_left(CajaIconContainer * container,CajaIconData * data,gconstpointer client)7429 caja_icon_container_stop_monitor_top_left (CajaIconContainer *container,
7430 CajaIconData *data,
7431 gconstpointer client)
7432 {
7433 CajaIconContainerClass *klass;
7434
7435 klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
7436 g_return_if_fail (klass->stop_monitor_top_left != NULL);
7437
7438 klass->stop_monitor_top_left (container, data, client);
7439 }
7440
7441
7442 static void
caja_icon_container_prioritize_thumbnailing(CajaIconContainer * container,CajaIcon * icon)7443 caja_icon_container_prioritize_thumbnailing (CajaIconContainer *container,
7444 CajaIcon *icon)
7445 {
7446 CajaIconContainerClass *klass;
7447
7448 klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
7449 g_assert (klass->prioritize_thumbnailing != NULL);
7450
7451 klass->prioritize_thumbnailing (container, icon->data);
7452 }
7453
7454 static void
caja_icon_container_update_visible_icons(CajaIconContainer * container)7455 caja_icon_container_update_visible_icons (CajaIconContainer *container)
7456 {
7457 GtkAdjustment *vadj, *hadj;
7458 double min_y, max_y;
7459 double min_x, max_x;
7460 double x0, y0, x1, y1;
7461 GList *node;
7462 gboolean visible;
7463 GtkAllocation allocation;
7464 CajaIcon *icon = NULL;
7465
7466 hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (container));
7467 vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (container));
7468 gtk_widget_get_allocation (GTK_WIDGET (container), &allocation);
7469
7470 min_x = gtk_adjustment_get_value (hadj);
7471 max_x = min_x + allocation.width;
7472
7473 min_y = gtk_adjustment_get_value (vadj);
7474 max_y = min_y + allocation.height;
7475
7476 eel_canvas_c2w (EEL_CANVAS (container),
7477 min_x, min_y, &min_x, &min_y);
7478 eel_canvas_c2w (EEL_CANVAS (container),
7479 max_x, max_y, &max_x, &max_y);
7480
7481 /* Do the iteration in reverse to get the render-order from top to
7482 * bottom for the prioritized thumbnails.
7483 */
7484 for (node = g_list_last (container->details->icons); node != NULL; node = node->prev)
7485 {
7486 icon = node->data;
7487
7488 if (icon_is_positioned (icon))
7489 {
7490 eel_canvas_item_get_bounds (EEL_CANVAS_ITEM (icon->item),
7491 &x0,
7492 &y0,
7493 &x1,
7494 &y1);
7495 eel_canvas_item_i2w (EEL_CANVAS_ITEM (icon->item)->parent,
7496 &x0,
7497 &y0);
7498 eel_canvas_item_i2w (EEL_CANVAS_ITEM (icon->item)->parent,
7499 &x1,
7500 &y1);
7501
7502 if (caja_icon_container_is_layout_vertical (container))
7503 {
7504 visible = x1 >= min_x && x0 <= max_x;
7505 }
7506 else
7507 {
7508 visible = y1 >= min_y && y0 <= max_y;
7509 }
7510
7511 if (visible)
7512 {
7513 caja_icon_canvas_item_set_is_visible (icon->item, TRUE);
7514 caja_icon_container_prioritize_thumbnailing (container,
7515 icon);
7516 }
7517 else
7518 {
7519 caja_icon_canvas_item_set_is_visible (icon->item, FALSE);
7520 }
7521 }
7522 }
7523 }
7524
7525 static void
handle_vadjustment_changed(GtkAdjustment * adjustment,CajaIconContainer * container)7526 handle_vadjustment_changed (GtkAdjustment *adjustment,
7527 CajaIconContainer *container)
7528 {
7529 if (!caja_icon_container_is_layout_vertical (container))
7530 {
7531 caja_icon_container_update_visible_icons (container);
7532 }
7533 }
7534
7535 static void
handle_hadjustment_changed(GtkAdjustment * adjustment,CajaIconContainer * container)7536 handle_hadjustment_changed (GtkAdjustment *adjustment,
7537 CajaIconContainer *container)
7538 {
7539 if (caja_icon_container_is_layout_vertical (container))
7540 {
7541 caja_icon_container_update_visible_icons (container);
7542 }
7543 }
7544
7545
7546 void
caja_icon_container_update_icon(CajaIconContainer * container,CajaIcon * icon)7547 caja_icon_container_update_icon (CajaIconContainer *container,
7548 CajaIcon *icon)
7549 {
7550 CajaIconContainerDetails *details;
7551 guint icon_size;
7552 guint min_image_size, max_image_size;
7553 CajaIconInfo *icon_info;
7554 GdkPoint *attach_points;
7555 int n_attach_points;
7556 gboolean has_embedded_text_rect;
7557 GdkPixbuf *pixbuf;
7558 GList *emblem_pixbufs;
7559 char *editable_text, *additional_text;
7560 char *embedded_text;
7561 GdkRectangle embedded_text_rect;
7562 gboolean large_embedded_text;
7563 gboolean embedded_text_needs_loading;
7564 gboolean has_open_window;
7565
7566 if (icon == NULL)
7567 {
7568 return;
7569 }
7570
7571 details = container->details;
7572
7573 /* compute the maximum size based on the scale factor */
7574 min_image_size = MINIMUM_IMAGE_SIZE * EEL_CANVAS (container)->pixels_per_unit;
7575 max_image_size = MAX (MAXIMUM_IMAGE_SIZE * EEL_CANVAS (container)->pixels_per_unit, CAJA_ICON_MAXIMUM_SIZE);
7576
7577 /* Get the appropriate images for the file. */
7578 if (container->details->forced_icon_size > 0)
7579 {
7580 icon_size = container->details->forced_icon_size;
7581 }
7582 else
7583 {
7584 icon_get_size (container, icon, &icon_size);
7585 }
7586
7587
7588 icon_size = MAX (icon_size, min_image_size);
7589 icon_size = MIN (icon_size, max_image_size);
7590
7591 /* Get the icons. */
7592 emblem_pixbufs = NULL;
7593 embedded_text = NULL;
7594 large_embedded_text = icon_size > ICON_SIZE_FOR_LARGE_EMBEDDED_TEXT;
7595 icon_info = caja_icon_container_get_icon_images (container, icon->data, icon_size,
7596 &emblem_pixbufs,
7597 &embedded_text,
7598 icon == details->drop_target,
7599 large_embedded_text, &embedded_text_needs_loading,
7600 &has_open_window);
7601
7602
7603 if (container->details->forced_icon_size > 0)
7604 pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, icon_size);
7605 else
7606 pixbuf = caja_icon_info_get_pixbuf (icon_info);
7607 caja_icon_info_get_attach_points (icon_info, &attach_points, &n_attach_points);
7608 has_embedded_text_rect = caja_icon_info_get_embedded_rect (icon_info,
7609 &embedded_text_rect);
7610
7611 if (has_embedded_text_rect && embedded_text_needs_loading)
7612 {
7613 icon->is_monitored = TRUE;
7614 caja_icon_container_start_monitor_top_left (container, icon->data, icon, large_embedded_text);
7615 }
7616
7617 caja_icon_container_get_icon_text (container,
7618 icon->data,
7619 &editable_text,
7620 &additional_text,
7621 FALSE);
7622
7623 /* If name of icon being renamed was changed from elsewhere, end renaming mode.
7624 * Alternatively, we could replace the characters in the editable text widget
7625 * with the new name, but that could cause timing problems if the user just
7626 * happened to be typing at that moment.
7627 */
7628 if (icon == get_icon_being_renamed (container) &&
7629 g_strcmp0 (editable_text,
7630 caja_icon_canvas_item_get_editable_text (icon->item)) != 0)
7631 {
7632 end_renaming_mode (container, FALSE);
7633 }
7634
7635 eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
7636 "editable_text", editable_text,
7637 "additional_text", additional_text,
7638 "highlighted_for_drop", icon == details->drop_target,
7639 NULL);
7640
7641 caja_icon_canvas_item_set_image (icon->item, pixbuf);
7642 caja_icon_canvas_item_set_attach_points (icon->item, attach_points, n_attach_points);
7643 caja_icon_canvas_item_set_emblems (icon->item, emblem_pixbufs);
7644 caja_icon_canvas_item_set_embedded_text_rect (icon->item, &embedded_text_rect);
7645 caja_icon_canvas_item_set_embedded_text (icon->item, embedded_text);
7646
7647 /* Let the pixbufs go. */
7648 g_object_unref (pixbuf);
7649 g_list_free_full (emblem_pixbufs, g_object_unref);
7650
7651 g_free (editable_text);
7652 g_free (additional_text);
7653
7654 g_object_unref (icon_info);
7655 }
7656
7657 static gboolean
assign_icon_position(CajaIconContainer * container,CajaIcon * icon)7658 assign_icon_position (CajaIconContainer *container,
7659 CajaIcon *icon)
7660 {
7661 gboolean have_stored_position;
7662 CajaIconPosition position;
7663
7664 /* Get the stored position. */
7665 have_stored_position = FALSE;
7666 position.scale = 1.0;
7667 g_signal_emit (container,
7668 signals[GET_STORED_ICON_POSITION], 0,
7669 icon->data,
7670 &position,
7671 &have_stored_position);
7672 icon->scale = position.scale;
7673 if (!container->details->auto_layout)
7674 {
7675 if (have_stored_position)
7676 {
7677 icon_set_position (icon, position.x, position.y);
7678 icon->saved_ltr_x = icon->x;
7679 }
7680 else
7681 {
7682 return FALSE;
7683 }
7684 }
7685 return TRUE;
7686 }
7687
7688 static void
finish_adding_icon(CajaIconContainer * container,CajaIcon * icon)7689 finish_adding_icon (CajaIconContainer *container,
7690 CajaIcon *icon)
7691 {
7692 caja_icon_container_update_icon (container, icon);
7693 eel_canvas_item_show (EEL_CANVAS_ITEM (icon->item));
7694
7695 g_signal_connect_object (icon->item, "event",
7696 G_CALLBACK (item_event_callback), container, 0);
7697
7698 g_signal_emit (container, signals[ICON_ADDED], 0, icon->data);
7699 }
7700
7701 static void
finish_adding_new_icons(CajaIconContainer * container)7702 finish_adding_new_icons (CajaIconContainer *container)
7703 {
7704 GList *p, *new_icons, *no_position_icons, *semi_position_icons;
7705 CajaIcon *icon;
7706 double bottom;
7707
7708 new_icons = container->details->new_icons;
7709 container->details->new_icons = NULL;
7710 container->details->is_populating_container =
7711 g_list_length(new_icons) == g_hash_table_size(container->details->icon_set);
7712
7713 /* Position most icons (not unpositioned manual-layout icons). */
7714 new_icons = g_list_reverse (new_icons);
7715 no_position_icons = semi_position_icons = NULL;
7716 for (p = new_icons; p != NULL; p = p->next)
7717 {
7718 icon = p->data;
7719 if (icon->has_lazy_position)
7720 {
7721 assign_icon_position (container, icon);
7722 semi_position_icons = g_list_prepend (semi_position_icons, icon);
7723 }
7724 else if (!assign_icon_position (container, icon))
7725 {
7726 no_position_icons = g_list_prepend (no_position_icons, icon);
7727 }
7728
7729 finish_adding_icon (container, icon);
7730 }
7731 g_list_free (new_icons);
7732
7733 if (semi_position_icons != NULL)
7734 {
7735 PlacementGrid *grid;
7736 time_t now;
7737 gboolean dummy;
7738
7739 g_assert (!container->details->auto_layout);
7740
7741 semi_position_icons = g_list_reverse (semi_position_icons);
7742
7743 /* This is currently only used on the desktop.
7744 * Thus, we pass FALSE for tight, like lay_down_icons_tblr */
7745 grid = placement_grid_new (container, FALSE);
7746
7747 for (p = container->details->icons; p != NULL; p = p->next)
7748 {
7749 icon = p->data;
7750
7751 if (icon_is_positioned (icon) && !icon->has_lazy_position)
7752 {
7753 placement_grid_mark_icon (grid, icon);
7754 }
7755 }
7756
7757 now = time (NULL);
7758
7759 for (p = semi_position_icons; p != NULL; p = p->next)
7760 {
7761 CajaIcon *icon;
7762 CajaIconPosition position;
7763 int x, y;
7764
7765 icon = p->data;
7766 x = icon->x;
7767 y = icon->y;
7768
7769 find_empty_location (container, grid,
7770 icon, x, y, &x, &y);
7771
7772 icon_set_position (icon, x, y);
7773
7774 position.x = icon->x;
7775 position.y = icon->y;
7776 position.scale = icon->scale;
7777 placement_grid_mark_icon (grid, icon);
7778 g_signal_emit (container, signals[ICON_POSITION_CHANGED], 0,
7779 icon->data, &position);
7780 g_signal_emit (container, signals[STORE_LAYOUT_TIMESTAMP], 0,
7781 icon->data, &now, &dummy);
7782
7783 /* ensure that next time we run this code, the formerly semi-positioned
7784 * icons are treated as being positioned. */
7785 icon->has_lazy_position = FALSE;
7786 }
7787
7788 placement_grid_free (grid);
7789
7790 g_list_free (semi_position_icons);
7791 }
7792
7793 /* Position the unpositioned manual layout icons. */
7794 if (no_position_icons != NULL)
7795 {
7796 g_assert (!container->details->auto_layout);
7797
7798 sort_icons (container, &no_position_icons);
7799 if (caja_icon_container_get_is_desktop (container))
7800 {
7801 lay_down_icons (container, no_position_icons, CONTAINER_PAD_TOP);
7802 }
7803 else
7804 {
7805 get_all_icon_bounds (container, NULL, NULL, NULL, &bottom, BOUNDS_USAGE_FOR_LAYOUT);
7806 lay_down_icons (container, no_position_icons, bottom + ICON_PAD_BOTTOM);
7807 }
7808 g_list_free (no_position_icons);
7809 }
7810
7811 if (container->details->store_layout_timestamps_when_finishing_new_icons)
7812 {
7813 store_layout_timestamps_now (container);
7814 container->details->store_layout_timestamps_when_finishing_new_icons = FALSE;
7815 }
7816 }
7817
7818 static gboolean
is_old_or_unknown_icon_data(CajaIconContainer * container,CajaIconData * data)7819 is_old_or_unknown_icon_data (CajaIconContainer *container,
7820 CajaIconData *data)
7821 {
7822 time_t timestamp;
7823 gboolean success;
7824
7825 if (container->details->layout_timestamp == UNDEFINED_TIME)
7826 {
7827 /* don't know */
7828 return FALSE;
7829 }
7830
7831 g_signal_emit (container,
7832 signals[GET_STORED_LAYOUT_TIMESTAMP], 0,
7833 data, ×tamp, &success);
7834 return (!success || timestamp < container->details->layout_timestamp);
7835 }
7836
7837 /**
7838 * caja_icon_container_add:
7839 * @container: A CajaIconContainer
7840 * @data: Icon data.
7841 *
7842 * Add icon to represent @data to container.
7843 * Returns FALSE if there was already such an icon.
7844 **/
7845 gboolean
caja_icon_container_add(CajaIconContainer * container,CajaIconData * data)7846 caja_icon_container_add (CajaIconContainer *container,
7847 CajaIconData *data)
7848 {
7849 CajaIconContainerDetails *details;
7850 CajaIcon *icon;
7851 EelCanvasItem *band, *item;
7852
7853 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
7854 g_return_val_if_fail (data != NULL, FALSE);
7855
7856 details = container->details;
7857
7858 if (g_hash_table_lookup (details->icon_set, data) != NULL)
7859 {
7860 return FALSE;
7861 }
7862
7863 /* Create the new icon, including the canvas item. */
7864 icon = g_new0 (CajaIcon, 1);
7865 icon->data = data;
7866 icon->x = ICON_UNPOSITIONED_VALUE;
7867 icon->y = ICON_UNPOSITIONED_VALUE;
7868
7869 /* Whether the saved icon position should only be used
7870 * if the previous icon position is free. If the position
7871 * is occupied, another position near the last one will
7872 */
7873 icon->has_lazy_position = is_old_or_unknown_icon_data (container, data);
7874 icon->scale = 1.0;
7875 icon->item = CAJA_ICON_CANVAS_ITEM
7876 (eel_canvas_item_new (EEL_CANVAS_GROUP (EEL_CANVAS (container)->root),
7877 caja_icon_canvas_item_get_type (),
7878 "visible", FALSE,
7879 NULL));
7880 icon->item->user_data = icon;
7881
7882 /* Make sure the icon is under the selection_rectangle */
7883 item = EEL_CANVAS_ITEM (icon->item);
7884 band = CAJA_ICON_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle;
7885 if (band)
7886 {
7887 eel_canvas_item_send_behind (item, band);
7888 }
7889
7890 /* Put it on both lists. */
7891 details->icons = g_list_prepend (details->icons, icon);
7892 details->new_icons = g_list_prepend (details->new_icons, icon);
7893
7894 g_hash_table_insert (details->icon_set, data, icon);
7895
7896 /* Run an idle function to add the icons. */
7897 schedule_redo_layout (container);
7898
7899 return TRUE;
7900 }
7901
7902 void
caja_icon_container_layout_now(CajaIconContainer * container)7903 caja_icon_container_layout_now (CajaIconContainer *container)
7904 {
7905 if (container->details->idle_id != 0)
7906 {
7907 unschedule_redo_layout (container);
7908 redo_layout_internal (container);
7909 }
7910
7911 /* Also need to make sure we're properly resized, for instance
7912 * newly added files may trigger a change in the size allocation and
7913 * thus toggle scrollbars on */
7914 gtk_container_check_resize (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (container))));
7915 }
7916
7917 /**
7918 * caja_icon_container_remove:
7919 * @container: A CajaIconContainer.
7920 * @data: Icon data.
7921 *
7922 * Remove the icon with this data.
7923 **/
7924 gboolean
caja_icon_container_remove(CajaIconContainer * container,CajaIconData * data)7925 caja_icon_container_remove (CajaIconContainer *container,
7926 CajaIconData *data)
7927 {
7928 CajaIcon *icon;
7929
7930 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
7931 g_return_val_if_fail (data != NULL, FALSE);
7932
7933 end_renaming_mode (container, FALSE);
7934
7935 icon = g_hash_table_lookup (container->details->icon_set, data);
7936
7937 if (icon == NULL)
7938 {
7939 return FALSE;
7940 }
7941
7942 g_signal_emit (container, signals[ICON_REMOVED], 0, icon);
7943
7944 icon_destroy (container, icon);
7945 schedule_redo_layout (container);
7946
7947 return TRUE;
7948 }
7949
7950 /**
7951 * caja_icon_container_request_update:
7952 * @container: A CajaIconContainer.
7953 * @data: Icon data.
7954 *
7955 * Update the icon with this data.
7956 **/
7957 void
caja_icon_container_request_update(CajaIconContainer * container,CajaIconData * data)7958 caja_icon_container_request_update (CajaIconContainer *container,
7959 CajaIconData *data)
7960 {
7961 CajaIcon *icon;
7962
7963 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
7964 g_return_if_fail (data != NULL);
7965
7966 icon = g_hash_table_lookup (container->details->icon_set, data);
7967
7968 if (icon != NULL)
7969 {
7970 caja_icon_container_update_icon (container, icon);
7971 schedule_redo_layout (container);
7972 }
7973 }
7974
7975 /* zooming */
7976
7977 CajaZoomLevel
caja_icon_container_get_zoom_level(CajaIconContainer * container)7978 caja_icon_container_get_zoom_level (CajaIconContainer *container)
7979 {
7980 return container->details->zoom_level;
7981 }
7982
7983 void
caja_icon_container_set_zoom_level(CajaIconContainer * container,int new_level)7984 caja_icon_container_set_zoom_level (CajaIconContainer *container, int new_level)
7985 {
7986 CajaIconContainerDetails *details;
7987 int pinned_level;
7988 double pixels_per_unit;
7989
7990 details = container->details;
7991
7992 end_renaming_mode (container, TRUE);
7993
7994 pinned_level = new_level;
7995 if (pinned_level < CAJA_ZOOM_LEVEL_SMALLEST)
7996 {
7997 pinned_level = CAJA_ZOOM_LEVEL_SMALLEST;
7998 }
7999 else if (pinned_level > CAJA_ZOOM_LEVEL_LARGEST)
8000 {
8001 pinned_level = CAJA_ZOOM_LEVEL_LARGEST;
8002 }
8003
8004 if (pinned_level == details->zoom_level)
8005 {
8006 return;
8007 }
8008
8009 details->zoom_level = pinned_level;
8010
8011 pixels_per_unit = (double) caja_get_icon_size_for_zoom_level (pinned_level)
8012 / CAJA_ICON_SIZE_STANDARD;
8013 eel_canvas_set_pixels_per_unit (EEL_CANVAS (container), pixels_per_unit);
8014
8015 invalidate_labels (container);
8016 caja_icon_container_request_update_all (container);
8017 }
8018
8019 /**
8020 * caja_icon_container_request_update_all:
8021 * For each icon, synchronizes the displayed information (image, text) with the
8022 * information from the model.
8023 *
8024 * @container: An icon container.
8025 **/
8026 void
caja_icon_container_request_update_all(CajaIconContainer * container)8027 caja_icon_container_request_update_all (CajaIconContainer *container)
8028 {
8029 GList *node;
8030 CajaIcon *icon = NULL;
8031
8032 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
8033
8034 container->details->is_loading = TRUE;
8035 for (node = container->details->icons; node != NULL; node = node->next)
8036 {
8037 icon = node->data;
8038 caja_icon_container_update_icon (container, icon);
8039 }
8040
8041 redo_layout (container);
8042 container->details->is_loading = FALSE;
8043 }
8044
8045 /**
8046 * caja_icon_container_reveal:
8047 * Change scroll position as necessary to reveal the specified item.
8048 */
8049 void
caja_icon_container_reveal(CajaIconContainer * container,CajaIconData * data)8050 caja_icon_container_reveal (CajaIconContainer *container, CajaIconData *data)
8051 {
8052 CajaIcon *icon;
8053
8054 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
8055 g_return_if_fail (data != NULL);
8056
8057 icon = g_hash_table_lookup (container->details->icon_set, data);
8058
8059 if (icon != NULL)
8060 {
8061 reveal_icon (container, icon);
8062 }
8063 }
8064
8065 /**
8066 * caja_icon_container_get_selection:
8067 * @container: An icon container.
8068 *
8069 * Get a list of the icons currently selected in @container.
8070 *
8071 * Return value: A GList of the programmer-specified data associated to each
8072 * selected icon, or NULL if no icon is selected. The caller is expected to
8073 * free the list when it is not needed anymore.
8074 **/
8075 GList *
caja_icon_container_get_selection(CajaIconContainer * container)8076 caja_icon_container_get_selection (CajaIconContainer *container)
8077 {
8078 GList *list, *p;
8079
8080 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL);
8081
8082 list = NULL;
8083 for (p = container->details->icons; p != NULL; p = p->next)
8084 {
8085 CajaIcon *icon;
8086
8087 icon = p->data;
8088 if (icon->is_selected)
8089 {
8090 list = g_list_prepend (list, icon->data);
8091 }
8092 }
8093
8094 return g_list_reverse (list);
8095 }
8096
8097 static GList *
caja_icon_container_get_selected_icons(CajaIconContainer * container)8098 caja_icon_container_get_selected_icons (CajaIconContainer *container)
8099 {
8100 GList *list, *p;
8101
8102 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL);
8103
8104 list = NULL;
8105 for (p = container->details->icons; p != NULL; p = p->next)
8106 {
8107 CajaIcon *icon;
8108
8109 icon = p->data;
8110 if (icon->is_selected)
8111 {
8112 list = g_list_prepend (list, icon);
8113 }
8114 }
8115
8116 return g_list_reverse (list);
8117 }
8118
8119 /**
8120 * caja_icon_container_invert_selection:
8121 * @container: An icon container.
8122 *
8123 * Inverts the selection in @container.
8124 *
8125 **/
8126 void
caja_icon_container_invert_selection(CajaIconContainer * container)8127 caja_icon_container_invert_selection (CajaIconContainer *container)
8128 {
8129 GList *p;
8130
8131 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
8132
8133 for (p = container->details->icons; p != NULL; p = p->next)
8134 {
8135 CajaIcon *icon;
8136
8137 icon = p->data;
8138 icon_toggle_selected (container, icon);
8139 }
8140
8141 g_signal_emit (container, signals[SELECTION_CHANGED], 0);
8142 }
8143
8144
8145 /* Returns an array of GdkPoints of locations of the icons. */
8146 static GArray *
caja_icon_container_get_icon_locations(CajaIconContainer * container,GList * icons)8147 caja_icon_container_get_icon_locations (CajaIconContainer *container,
8148 GList *icons)
8149 {
8150 GArray *result;
8151 GList *node;
8152 int index;
8153
8154 result = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
8155 result = g_array_set_size (result, g_list_length (icons));
8156
8157 for (index = 0, node = icons; node != NULL; index++, node = node->next)
8158 {
8159 g_array_index (result, GdkPoint, index).x =
8160 ((CajaIcon *)node->data)->x;
8161 g_array_index (result, GdkPoint, index).y =
8162 ((CajaIcon *)node->data)->y;
8163 }
8164
8165 return result;
8166 }
8167
8168 /**
8169 * caja_icon_container_get_selected_icon_locations:
8170 * @container: An icon container widget.
8171 *
8172 * Returns an array of GdkPoints of locations of the selected icons.
8173 **/
8174 GArray *
caja_icon_container_get_selected_icon_locations(CajaIconContainer * container)8175 caja_icon_container_get_selected_icon_locations (CajaIconContainer *container)
8176 {
8177 GArray *result;
8178 GList *icons;
8179
8180 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), NULL);
8181
8182 icons = caja_icon_container_get_selected_icons (container);
8183 result = caja_icon_container_get_icon_locations (container, icons);
8184 g_list_free (icons);
8185
8186 return result;
8187 }
8188
8189 /**
8190 * caja_icon_container_select_all:
8191 * @container: An icon container widget.
8192 *
8193 * Select all the icons in @container at once.
8194 **/
8195 void
caja_icon_container_select_all(CajaIconContainer * container)8196 caja_icon_container_select_all (CajaIconContainer *container)
8197 {
8198 gboolean selection_changed;
8199 GList *p;
8200 CajaIcon *icon = NULL;
8201
8202 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
8203
8204 selection_changed = FALSE;
8205
8206 for (p = container->details->icons; p != NULL; p = p->next)
8207 {
8208 icon = p->data;
8209
8210 selection_changed |= icon_set_selected (container, icon, TRUE);
8211 }
8212
8213 if (selection_changed)
8214 {
8215 g_signal_emit (container,
8216 signals[SELECTION_CHANGED], 0);
8217 }
8218 }
8219
8220 /**
8221 * caja_icon_container_set_selection:
8222 * @container: An icon container widget.
8223 * @selection: A list of CajaIconData *.
8224 *
8225 * Set the selection to exactly the icons in @container which have
8226 * programmer data matching one of the items in @selection.
8227 **/
8228 void
caja_icon_container_set_selection(CajaIconContainer * container,GList * selection)8229 caja_icon_container_set_selection (CajaIconContainer *container,
8230 GList *selection)
8231 {
8232 gboolean selection_changed;
8233 GHashTable *hash;
8234 GList *p;
8235 CajaIcon *icon = NULL;
8236
8237 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
8238
8239 selection_changed = FALSE;
8240
8241 hash = g_hash_table_new (NULL, NULL);
8242 for (p = selection; p != NULL; p = p->next)
8243 {
8244 g_hash_table_insert (hash, p->data, p->data);
8245 }
8246 for (p = container->details->icons; p != NULL; p = p->next)
8247 {
8248 icon = p->data;
8249
8250 selection_changed |= icon_set_selected
8251 (container, icon,
8252 g_hash_table_lookup (hash, icon->data) != NULL);
8253 }
8254 g_hash_table_destroy (hash);
8255
8256 if (selection_changed)
8257 {
8258 g_signal_emit (container,
8259 signals[SELECTION_CHANGED], 0);
8260 }
8261 }
8262
8263 /**
8264 * caja_icon_container_select_list_unselect_others.
8265 * @container: An icon container widget.
8266 * @selection: A list of CajaIcon *.
8267 *
8268 * Set the selection to exactly the icons in @selection.
8269 **/
8270 void
caja_icon_container_select_list_unselect_others(CajaIconContainer * container,GList * selection)8271 caja_icon_container_select_list_unselect_others (CajaIconContainer *container,
8272 GList *selection)
8273 {
8274 gboolean selection_changed;
8275 GHashTable *hash;
8276 GList *p;
8277 CajaIcon *icon = NULL;
8278
8279 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
8280
8281 selection_changed = FALSE;
8282
8283 hash = g_hash_table_new (NULL, NULL);
8284 for (p = selection; p != NULL; p = p->next)
8285 {
8286 g_hash_table_insert (hash, p->data, p->data);
8287 }
8288 for (p = container->details->icons; p != NULL; p = p->next)
8289 {
8290 icon = p->data;
8291
8292 selection_changed |= icon_set_selected
8293 (container, icon,
8294 g_hash_table_lookup (hash, icon) != NULL);
8295 }
8296 g_hash_table_destroy (hash);
8297
8298 if (selection_changed)
8299 {
8300 g_signal_emit (container,
8301 signals[SELECTION_CHANGED], 0);
8302 }
8303 }
8304
8305 /**
8306 * caja_icon_container_unselect_all:
8307 * @container: An icon container widget.
8308 *
8309 * Deselect all the icons in @container.
8310 **/
8311 void
caja_icon_container_unselect_all(CajaIconContainer * container)8312 caja_icon_container_unselect_all (CajaIconContainer *container)
8313 {
8314 if (unselect_all (container))
8315 {
8316 g_signal_emit (container,
8317 signals[SELECTION_CHANGED], 0);
8318 }
8319 }
8320
8321 /**
8322 * caja_icon_container_get_icon_by_uri:
8323 * @container: An icon container widget.
8324 * @uri: The uri of an icon to find.
8325 *
8326 * Locate an icon, given the URI. The URI must match exactly.
8327 * Later we may have to have some way of figuring out if the
8328 * URI specifies the same object that does not require an exact match.
8329 **/
8330 CajaIcon *
caja_icon_container_get_icon_by_uri(CajaIconContainer * container,const char * uri)8331 caja_icon_container_get_icon_by_uri (CajaIconContainer *container,
8332 const char *uri)
8333 {
8334 CajaIconContainerDetails *details;
8335 GList *p;
8336
8337 /* Eventually, we must avoid searching the entire icon list,
8338 but it's OK for now.
8339 A hash table mapping uri to icon is one possibility.
8340 */
8341
8342 details = container->details;
8343
8344 for (p = details->icons; p != NULL; p = p->next)
8345 {
8346 CajaIcon *icon;
8347 char *icon_uri;
8348 gboolean is_match;
8349
8350 icon = p->data;
8351
8352 icon_uri = caja_icon_container_get_icon_uri
8353 (container, icon);
8354 is_match = strcmp (uri, icon_uri) == 0;
8355 g_free (icon_uri);
8356
8357 if (is_match)
8358 {
8359 return icon;
8360 }
8361 }
8362
8363 return NULL;
8364 }
8365
8366 static CajaIcon *
get_nth_selected_icon(CajaIconContainer * container,int index)8367 get_nth_selected_icon (CajaIconContainer *container, int index)
8368 {
8369 GList *p;
8370 int selection_count;
8371 CajaIcon *icon = NULL;
8372
8373 g_assert (index > 0);
8374
8375 /* Find the nth selected icon. */
8376 selection_count = 0;
8377 for (p = container->details->icons; p != NULL; p = p->next)
8378 {
8379 icon = p->data;
8380 if (icon->is_selected)
8381 {
8382 if (++selection_count == index)
8383 {
8384 return icon;
8385 }
8386 }
8387 }
8388 return NULL;
8389 }
8390
8391 static CajaIcon *
get_first_selected_icon(CajaIconContainer * container)8392 get_first_selected_icon (CajaIconContainer *container)
8393 {
8394 return get_nth_selected_icon (container, 1);
8395 }
8396
8397 static gboolean
has_multiple_selection(CajaIconContainer * container)8398 has_multiple_selection (CajaIconContainer *container)
8399 {
8400 return get_nth_selected_icon (container, 2) != NULL;
8401 }
8402
8403 static gboolean
all_selected(CajaIconContainer * container)8404 all_selected (CajaIconContainer *container)
8405 {
8406 GList *p;
8407 CajaIcon *icon = NULL;
8408
8409 for (p = container->details->icons; p != NULL; p = p->next)
8410 {
8411 icon = p->data;
8412 if (!icon->is_selected)
8413 {
8414 return FALSE;
8415 }
8416 }
8417 return TRUE;
8418 }
8419
8420 static gboolean
has_selection(CajaIconContainer * container)8421 has_selection (CajaIconContainer *container)
8422 {
8423 return get_nth_selected_icon (container, 1) != NULL;
8424 }
8425
8426 /**
8427 * caja_icon_container_show_stretch_handles:
8428 * @container: An icon container widget.
8429 *
8430 * Makes stretch handles visible on the first selected icon.
8431 **/
8432 void
caja_icon_container_show_stretch_handles(CajaIconContainer * container)8433 caja_icon_container_show_stretch_handles (CajaIconContainer *container)
8434 {
8435 CajaIconContainerDetails *details;
8436 CajaIcon *icon;
8437 guint initial_size;
8438
8439 icon = get_first_selected_icon (container);
8440 if (icon == NULL)
8441 {
8442 return;
8443 }
8444
8445 /* Check if it already has stretch handles. */
8446 details = container->details;
8447 if (details->stretch_icon == icon)
8448 {
8449 return;
8450 }
8451
8452 /* Get rid of the existing stretch handles and put them on the new icon. */
8453 if (details->stretch_icon != NULL)
8454 {
8455 caja_icon_canvas_item_set_show_stretch_handles
8456 (details->stretch_icon->item, FALSE);
8457 ungrab_stretch_icon (container);
8458 emit_stretch_ended (container, details->stretch_icon);
8459 }
8460 caja_icon_canvas_item_set_show_stretch_handles (icon->item, TRUE);
8461 details->stretch_icon = icon;
8462
8463 icon_get_size (container, icon, &initial_size);
8464
8465 /* only need to keep size in one dimension, since they are constrained to be the same */
8466 container->details->stretch_initial_x = icon->x;
8467 container->details->stretch_initial_y = icon->y;
8468 container->details->stretch_initial_size = initial_size;
8469
8470 emit_stretch_started (container, icon);
8471 }
8472
8473 /**
8474 * caja_icon_container_has_stretch_handles
8475 * @container: An icon container widget.
8476 *
8477 * Returns true if the first selected item has stretch handles.
8478 **/
8479 gboolean
caja_icon_container_has_stretch_handles(CajaIconContainer * container)8480 caja_icon_container_has_stretch_handles (CajaIconContainer *container)
8481 {
8482 CajaIcon *icon;
8483
8484 icon = get_first_selected_icon (container);
8485 if (icon == NULL)
8486 {
8487 return FALSE;
8488 }
8489
8490 return icon == container->details->stretch_icon;
8491 }
8492
8493 /**
8494 * caja_icon_container_is_stretched
8495 * @container: An icon container widget.
8496 *
8497 * Returns true if the any selected item is stretched to a size other than 1.0.
8498 **/
8499 gboolean
caja_icon_container_is_stretched(CajaIconContainer * container)8500 caja_icon_container_is_stretched (CajaIconContainer *container)
8501 {
8502 GList *p;
8503 CajaIcon *icon = NULL;
8504
8505 for (p = container->details->icons; p != NULL; p = p->next)
8506 {
8507 icon = p->data;
8508 if (icon->is_selected && icon->scale != 1.0)
8509 {
8510 return TRUE;
8511 }
8512 }
8513 return FALSE;
8514 }
8515
8516 /**
8517 * caja_icon_container_unstretch
8518 * @container: An icon container widget.
8519 *
8520 * Gets rid of any icon stretching.
8521 **/
8522 void
caja_icon_container_unstretch(CajaIconContainer * container)8523 caja_icon_container_unstretch (CajaIconContainer *container)
8524 {
8525 GList *p;
8526 CajaIcon *icon = NULL;
8527
8528 for (p = container->details->icons; p != NULL; p = p->next)
8529 {
8530 icon = p->data;
8531 if (icon->is_selected)
8532 {
8533 caja_icon_container_move_icon (container, icon,
8534 icon->x, icon->y,
8535 1.0,
8536 FALSE, TRUE, TRUE);
8537 }
8538 }
8539 }
8540
8541 static void
compute_stretch(StretchState * start,StretchState * current)8542 compute_stretch (StretchState *start,
8543 StretchState *current)
8544 {
8545 gboolean right, bottom;
8546 int x_stretch, y_stretch;
8547
8548 /* FIXME bugzilla.gnome.org 45390: This doesn't correspond to
8549 * the way the handles are drawn.
8550 */
8551 /* Figure out which handle we are dragging. */
8552 right = start->pointer_x > start->icon_x + (int) start->icon_size / 2;
8553 bottom = start->pointer_y > start->icon_y + (int) start->icon_size / 2;
8554
8555 /* Figure out how big we should stretch. */
8556 x_stretch = start->pointer_x - current->pointer_x;
8557 y_stretch = start->pointer_y - current->pointer_y;
8558 if (right)
8559 {
8560 x_stretch = - x_stretch;
8561 }
8562 if (bottom)
8563 {
8564 y_stretch = - y_stretch;
8565 }
8566 current->icon_size = MAX ((int) start->icon_size + MIN (x_stretch, y_stretch),
8567 (int) CAJA_ICON_SIZE_SMALLEST);
8568
8569 /* Figure out where the corner of the icon should be. */
8570 current->icon_x = start->icon_x;
8571 if (!right)
8572 {
8573 current->icon_x += start->icon_size - current->icon_size;
8574 }
8575 current->icon_y = start->icon_y;
8576 if (!bottom)
8577 {
8578 current->icon_y += start->icon_size - current->icon_size;
8579 }
8580 }
8581
8582 char *
caja_icon_container_get_icon_uri(CajaIconContainer * container,CajaIcon * icon)8583 caja_icon_container_get_icon_uri (CajaIconContainer *container,
8584 CajaIcon *icon)
8585 {
8586 char *uri;
8587
8588 uri = NULL;
8589 g_signal_emit (container,
8590 signals[GET_ICON_URI], 0,
8591 icon->data,
8592 &uri);
8593 return uri;
8594 }
8595
8596 char *
caja_icon_container_get_icon_drop_target_uri(CajaIconContainer * container,CajaIcon * icon)8597 caja_icon_container_get_icon_drop_target_uri (CajaIconContainer *container,
8598 CajaIcon *icon)
8599 {
8600 char *uri;
8601
8602 uri = NULL;
8603 g_signal_emit (container,
8604 signals[GET_ICON_DROP_TARGET_URI], 0,
8605 icon->data,
8606 &uri);
8607 return uri;
8608 }
8609
8610 /* Call to reset the scroll region only if the container is not empty,
8611 * to avoid having the flag linger until the next file is added.
8612 */
8613 static void
reset_scroll_region_if_not_empty(CajaIconContainer * container)8614 reset_scroll_region_if_not_empty (CajaIconContainer *container)
8615 {
8616 if (!caja_icon_container_is_empty (container))
8617 {
8618 caja_icon_container_reset_scroll_region (container);
8619 }
8620 }
8621
8622 /* Switch from automatic layout to manual or vice versa.
8623 * If we switch to manual layout, we restore the icon positions from the
8624 * last manual layout.
8625 */
8626 void
caja_icon_container_set_auto_layout(CajaIconContainer * container,gboolean auto_layout)8627 caja_icon_container_set_auto_layout (CajaIconContainer *container,
8628 gboolean auto_layout)
8629 {
8630 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
8631 g_return_if_fail (auto_layout == FALSE || auto_layout == TRUE);
8632
8633 if (container->details->auto_layout == auto_layout)
8634 {
8635 return;
8636 }
8637
8638 reset_scroll_region_if_not_empty (container);
8639 container->details->auto_layout = auto_layout;
8640
8641 if (!auto_layout)
8642 {
8643 reload_icon_positions (container);
8644 caja_icon_container_freeze_icon_positions (container);
8645 }
8646
8647 redo_layout (container);
8648
8649 g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
8650 }
8651
8652
8653 /* Toggle the tighter layout boolean. */
8654 void
caja_icon_container_set_tighter_layout(CajaIconContainer * container,gboolean tighter_layout)8655 caja_icon_container_set_tighter_layout (CajaIconContainer *container,
8656 gboolean tighter_layout)
8657 {
8658 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
8659 g_return_if_fail (tighter_layout == FALSE || tighter_layout == TRUE);
8660
8661 if (container->details->tighter_layout == tighter_layout)
8662 {
8663 return;
8664 }
8665
8666 container->details->tighter_layout = tighter_layout;
8667
8668 if (container->details->auto_layout)
8669 {
8670 invalidate_label_sizes (container);
8671 redo_layout (container);
8672
8673 g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
8674 }
8675 else
8676 {
8677 /* in manual layout, label sizes still change, even though
8678 * the icons don't move.
8679 */
8680 invalidate_label_sizes (container);
8681 caja_icon_container_request_update_all (container);
8682 }
8683 }
8684
8685 gboolean
caja_icon_container_is_keep_aligned(CajaIconContainer * container)8686 caja_icon_container_is_keep_aligned (CajaIconContainer *container)
8687 {
8688 return container->details->keep_aligned;
8689 }
8690
8691 gboolean
caja_icon_container_is_lock_icons_position(CajaIconContainer * container)8692 caja_icon_container_is_lock_icons_position (CajaIconContainer *container)
8693 {
8694 return container->details->lock_icons_position;
8695 }
8696
8697 static gboolean
align_icons_callback(gpointer callback_data)8698 align_icons_callback (gpointer callback_data)
8699 {
8700 CajaIconContainer *container;
8701
8702 container = CAJA_ICON_CONTAINER (callback_data);
8703 align_icons (container);
8704 container->details->align_idle_id = 0;
8705
8706 return FALSE;
8707 }
8708
8709 static void
unschedule_align_icons(CajaIconContainer * container)8710 unschedule_align_icons (CajaIconContainer *container)
8711 {
8712 if (container->details->align_idle_id != 0)
8713 {
8714 g_source_remove (container->details->align_idle_id);
8715 container->details->align_idle_id = 0;
8716 }
8717 }
8718
8719 static void
schedule_align_icons(CajaIconContainer * container)8720 schedule_align_icons (CajaIconContainer *container)
8721 {
8722 if (container->details->align_idle_id == 0
8723 && container->details->has_been_allocated)
8724 {
8725 container->details->align_idle_id = g_idle_add
8726 (align_icons_callback, container);
8727 }
8728 }
8729
8730 void
caja_icon_container_set_keep_aligned(CajaIconContainer * container,gboolean keep_aligned)8731 caja_icon_container_set_keep_aligned (CajaIconContainer *container,
8732 gboolean keep_aligned)
8733 {
8734 if (container->details->keep_aligned != keep_aligned)
8735 {
8736 container->details->keep_aligned = keep_aligned;
8737
8738 if (keep_aligned && !container->details->auto_layout)
8739 {
8740 schedule_align_icons (container);
8741 }
8742 else
8743 {
8744 unschedule_align_icons (container);
8745 }
8746 }
8747 }
8748
8749 void
caja_icon_container_set_lock_icons_position(CajaIconContainer * container,gboolean lock_icons_position)8750 caja_icon_container_set_lock_icons_position (CajaIconContainer *container,
8751 gboolean lock_icons_position)
8752 {
8753 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
8754
8755 container->details->lock_icons_position = lock_icons_position;
8756 }
8757
8758 void
caja_icon_container_set_layout_mode(CajaIconContainer * container,CajaIconLayoutMode mode)8759 caja_icon_container_set_layout_mode (CajaIconContainer *container,
8760 CajaIconLayoutMode mode)
8761 {
8762 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
8763
8764 container->details->layout_mode = mode;
8765 invalidate_labels (container);
8766
8767 redo_layout (container);
8768
8769 g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
8770 }
8771
8772 void
caja_icon_container_set_label_position(CajaIconContainer * container,CajaIconLabelPosition position)8773 caja_icon_container_set_label_position (CajaIconContainer *container,
8774 CajaIconLabelPosition position)
8775 {
8776 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
8777
8778 if (container->details->label_position != position)
8779 {
8780 container->details->label_position = position;
8781
8782 invalidate_labels (container);
8783 caja_icon_container_request_update_all (container);
8784
8785 schedule_redo_layout (container);
8786 }
8787 }
8788
8789 /* Switch from automatic to manual layout, freezing all the icons in their
8790 * current positions instead of restoring icon positions from the last manual
8791 * layout as set_auto_layout does.
8792 */
8793 void
caja_icon_container_freeze_icon_positions(CajaIconContainer * container)8794 caja_icon_container_freeze_icon_positions (CajaIconContainer *container)
8795 {
8796 gboolean changed;
8797 GList *p;
8798 CajaIcon *icon;
8799 CajaIconPosition position;
8800
8801 changed = container->details->auto_layout;
8802 container->details->auto_layout = FALSE;
8803
8804 for (p = container->details->icons; p != NULL; p = p->next)
8805 {
8806 icon = p->data;
8807
8808 position.x = icon->saved_ltr_x;
8809 position.y = icon->y;
8810 position.scale = icon->scale;
8811 g_signal_emit (container, signals[ICON_POSITION_CHANGED], 0,
8812 icon->data, &position);
8813 }
8814
8815 if (changed)
8816 {
8817 g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
8818 }
8819 }
8820
8821 /* Re-sort, switching to automatic layout if it was in manual layout. */
8822 void
caja_icon_container_sort(CajaIconContainer * container)8823 caja_icon_container_sort (CajaIconContainer *container)
8824 {
8825 gboolean changed;
8826
8827 changed = !container->details->auto_layout;
8828 container->details->auto_layout = TRUE;
8829
8830 reset_scroll_region_if_not_empty (container);
8831 redo_layout (container);
8832
8833 if (changed)
8834 {
8835 g_signal_emit (container, signals[LAYOUT_CHANGED], 0);
8836 }
8837 }
8838
8839 gboolean
caja_icon_container_is_auto_layout(CajaIconContainer * container)8840 caja_icon_container_is_auto_layout (CajaIconContainer *container)
8841 {
8842 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
8843
8844 return container->details->auto_layout;
8845 }
8846
8847 gboolean
caja_icon_container_is_tighter_layout(CajaIconContainer * container)8848 caja_icon_container_is_tighter_layout (CajaIconContainer *container)
8849 {
8850 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
8851
8852 return container->details->tighter_layout;
8853 }
8854
8855 static void
pending_icon_to_rename_destroy_callback(CajaIconCanvasItem * item,CajaIconContainer * container)8856 pending_icon_to_rename_destroy_callback (CajaIconCanvasItem *item, CajaIconContainer *container)
8857 {
8858 g_assert (container->details->pending_icon_to_rename != NULL);
8859 g_assert (container->details->pending_icon_to_rename->item == item);
8860 container->details->pending_icon_to_rename = NULL;
8861 }
8862
8863 static CajaIcon*
get_pending_icon_to_rename(CajaIconContainer * container)8864 get_pending_icon_to_rename (CajaIconContainer *container)
8865 {
8866 return container->details->pending_icon_to_rename;
8867 }
8868
8869 static void
set_pending_icon_to_rename(CajaIconContainer * container,CajaIcon * icon)8870 set_pending_icon_to_rename (CajaIconContainer *container, CajaIcon *icon)
8871 {
8872 CajaIcon *old_icon;
8873
8874 old_icon = container->details->pending_icon_to_rename;
8875
8876 if (icon == old_icon)
8877 {
8878 return;
8879 }
8880
8881 if (old_icon != NULL)
8882 {
8883 g_signal_handlers_disconnect_by_func
8884 (old_icon->item,
8885 G_CALLBACK (pending_icon_to_rename_destroy_callback),
8886 container);
8887 }
8888
8889 if (icon != NULL)
8890 {
8891 g_signal_connect (icon->item, "destroy",
8892 G_CALLBACK (pending_icon_to_rename_destroy_callback), container);
8893 }
8894
8895 container->details->pending_icon_to_rename = icon;
8896 }
8897
8898 static void
process_pending_icon_to_rename(CajaIconContainer * container)8899 process_pending_icon_to_rename (CajaIconContainer *container)
8900 {
8901 CajaIcon *pending_icon_to_rename;
8902
8903 pending_icon_to_rename = get_pending_icon_to_rename (container);
8904
8905 if (pending_icon_to_rename != NULL)
8906 {
8907 if (pending_icon_to_rename->is_selected && !has_multiple_selection (container))
8908 {
8909 caja_icon_container_start_renaming_selected_item (container, FALSE);
8910 }
8911 else
8912 {
8913 set_pending_icon_to_rename (container, NULL);
8914 }
8915 }
8916 }
8917
8918 static gboolean
is_renaming_pending(CajaIconContainer * container)8919 is_renaming_pending (CajaIconContainer *container)
8920 {
8921 return get_pending_icon_to_rename (container) != NULL;
8922 }
8923
8924 static gboolean
is_renaming(CajaIconContainer * container)8925 is_renaming (CajaIconContainer *container)
8926 {
8927 return container->details->renaming;
8928 }
8929
8930 /**
8931 * caja_icon_container_start_renaming_selected_item
8932 * @container: An icon container widget.
8933 * @select_all: Whether the whole file should initially be selected, or
8934 * only its basename (i.e. everything except its extension).
8935 *
8936 * Displays the edit name widget on the first selected icon
8937 **/
8938 void
caja_icon_container_start_renaming_selected_item(CajaIconContainer * container,gboolean select_all)8939 caja_icon_container_start_renaming_selected_item (CajaIconContainer *container,
8940 gboolean select_all)
8941 {
8942 CajaIconContainerDetails *details;
8943 CajaIcon *icon;
8944 EelDRect icon_rect;
8945 EelDRect text_rect;
8946 PangoFontDescription *desc;
8947 const char *editable_text;
8948 int x, y, width;
8949 int start_offset, end_offset;
8950
8951 /* Check if it already in renaming mode, if so - select all */
8952 details = container->details;
8953 if (details->renaming)
8954 {
8955 eel_editable_label_select_region (EEL_EDITABLE_LABEL (details->rename_widget),
8956 0,
8957 -1);
8958 return;
8959 }
8960
8961 /* Find selected icon */
8962 icon = get_first_selected_icon (container);
8963 if (icon == NULL)
8964 {
8965 return;
8966 }
8967
8968 g_assert (!has_multiple_selection (container));
8969
8970
8971 if (!icon_is_positioned (icon))
8972 {
8973 set_pending_icon_to_rename (container, icon);
8974 return;
8975 }
8976
8977 set_pending_icon_to_rename (container, NULL);
8978
8979 /* Make a copy of the original editable text for a later compare */
8980 editable_text = caja_icon_canvas_item_get_editable_text (icon->item);
8981
8982 /* This could conceivably be NULL if a rename was triggered really early. */
8983 if (editable_text == NULL)
8984 {
8985 return;
8986 }
8987
8988 details->original_text = g_strdup (editable_text);
8989
8990 /* Freeze updates so files added while renaming don't cause rename to loose focus, bug #318373 */
8991 caja_icon_container_freeze_updates (container);
8992
8993 /* Create text renaming widget, if it hasn't been created already.
8994 * We deal with the broken icon text item widget by keeping it around
8995 * so its contents can still be cut and pasted as part of the clipboard
8996 */
8997 if (details->rename_widget == NULL)
8998 {
8999 details->rename_widget = eel_editable_label_new ("Test text");
9000 eel_editable_label_set_line_wrap (EEL_EDITABLE_LABEL (details->rename_widget), TRUE);
9001 eel_editable_label_set_line_wrap_mode (EEL_EDITABLE_LABEL (details->rename_widget), PANGO_WRAP_WORD_CHAR);
9002 eel_editable_label_set_draw_outline (EEL_EDITABLE_LABEL (details->rename_widget), TRUE);
9003
9004 if (details->label_position != CAJA_ICON_LABEL_POSITION_BESIDE)
9005 {
9006 eel_editable_label_set_justify (EEL_EDITABLE_LABEL (details->rename_widget), GTK_JUSTIFY_CENTER);
9007 }
9008
9009 gtk_widget_set_margin_start (details->rename_widget, 1);
9010 gtk_widget_set_margin_end (details->rename_widget, 1);
9011 gtk_widget_set_margin_top (details->rename_widget, 1);
9012 gtk_widget_set_margin_bottom (details->rename_widget, 1);
9013 gtk_layout_put (GTK_LAYOUT (container),
9014 details->rename_widget, 0, 0);
9015 }
9016
9017 /* Set the right font */
9018 if (details->font)
9019 {
9020 desc = pango_font_description_from_string (details->font);
9021 }
9022 else
9023 {
9024 PangoContext *context;
9025
9026 context = gtk_widget_get_pango_context (GTK_WIDGET (container));
9027 desc = pango_font_description_copy (pango_context_get_font_description (context));
9028 pango_font_description_set_size (desc,
9029 pango_font_description_get_size (desc) +
9030 container->details->font_size_table [container->details->zoom_level]);
9031 }
9032 eel_editable_label_set_font_description (EEL_EDITABLE_LABEL (details->rename_widget),
9033 desc);
9034 pango_font_description_free (desc);
9035
9036 icon_rect = caja_icon_canvas_item_get_icon_rectangle (icon->item);
9037 text_rect = caja_icon_canvas_item_get_text_rectangle (icon->item, TRUE);
9038
9039 if (caja_icon_container_is_layout_vertical (container) &&
9040 container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
9041 {
9042 /* for one-line editables, the width changes dynamically */
9043 width = -1;
9044 }
9045 else
9046 {
9047 width = caja_icon_canvas_item_get_max_text_width (icon->item);
9048 }
9049
9050 if (details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
9051 {
9052 eel_canvas_w2c (EEL_CANVAS_ITEM (icon->item)->canvas,
9053 text_rect.x0,
9054 text_rect.y0,
9055 &x, &y);
9056 }
9057 else
9058 {
9059 eel_canvas_w2c (EEL_CANVAS_ITEM (icon->item)->canvas,
9060 (icon_rect.x0 + icon_rect.x1) / 2,
9061 icon_rect.y1,
9062 &x, &y);
9063 x = x - width / 2 - 1;
9064 }
9065
9066 gtk_layout_move (GTK_LAYOUT (container),
9067 details->rename_widget,
9068 x, y);
9069
9070 gtk_widget_set_size_request (details->rename_widget,
9071 width, -1);
9072 eel_editable_label_set_text (EEL_EDITABLE_LABEL (details->rename_widget),
9073 editable_text);
9074 if (select_all)
9075 {
9076 start_offset = 0;
9077 end_offset = -1;
9078 }
9079 else
9080 {
9081 eel_filename_get_rename_region (editable_text, &start_offset, &end_offset);
9082 }
9083 gtk_widget_show (details->rename_widget);
9084 gtk_widget_grab_focus (details->rename_widget);
9085
9086 eel_editable_label_select_region (EEL_EDITABLE_LABEL (details->rename_widget),
9087 start_offset,
9088 end_offset);
9089 g_signal_emit (container,
9090 signals[RENAMING_ICON], 0,
9091 GTK_EDITABLE (details->rename_widget));
9092
9093 caja_icon_container_update_icon (container, icon);
9094
9095 /* We are in renaming mode */
9096 details->renaming = TRUE;
9097 caja_icon_canvas_item_set_renaming (icon->item, TRUE);
9098 }
9099
9100 static void
end_renaming_mode(CajaIconContainer * container,gboolean commit)9101 end_renaming_mode (CajaIconContainer *container, gboolean commit)
9102 {
9103 CajaIcon *icon;
9104
9105 set_pending_icon_to_rename (container, NULL);
9106
9107 icon = get_icon_being_renamed (container);
9108 if (icon == NULL)
9109 {
9110 return;
9111 }
9112
9113 /* We are not in renaming mode */
9114 container->details->renaming = FALSE;
9115 caja_icon_canvas_item_set_renaming (icon->item, FALSE);
9116
9117 caja_icon_container_unfreeze_updates (container);
9118
9119 if (commit)
9120 {
9121 set_pending_icon_to_reveal (container, icon);
9122 }
9123
9124 gtk_widget_grab_focus (GTK_WIDGET (container));
9125
9126 if (commit)
9127 {
9128 const char *changed_text;
9129
9130 /* Verify that text has been modified before signalling change. */
9131 changed_text = eel_editable_label_get_text (EEL_EDITABLE_LABEL (container->details->rename_widget));
9132 if (strcmp (container->details->original_text, changed_text) != 0)
9133 {
9134 AtkObject *accessible_icon;
9135
9136 g_signal_emit (container,
9137 signals[ICON_TEXT_CHANGED], 0,
9138 icon->data,
9139 changed_text);
9140
9141 accessible_icon = atk_gobject_accessible_for_object (G_OBJECT(icon->item));
9142 g_object_notify (G_OBJECT(accessible_icon), "accessible-name");
9143 }
9144 }
9145
9146 gtk_widget_hide (container->details->rename_widget);
9147
9148 g_free (container->details->original_text);
9149
9150 }
9151
9152 /* emit preview signal, called by the canvas item */
9153 gboolean
caja_icon_container_emit_preview_signal(CajaIconContainer * icon_container,CajaIcon * icon,gboolean start_flag)9154 caja_icon_container_emit_preview_signal (CajaIconContainer *icon_container,
9155 CajaIcon *icon,
9156 gboolean start_flag)
9157 {
9158 gboolean result;
9159
9160 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (icon_container), FALSE);
9161 g_return_val_if_fail (icon != NULL, FALSE);
9162 g_return_val_if_fail (start_flag == FALSE || start_flag == TRUE, FALSE);
9163
9164 result = FALSE;
9165 g_signal_emit (icon_container,
9166 signals[PREVIEW], 0,
9167 icon->data,
9168 start_flag,
9169 &result);
9170
9171 return result;
9172 }
9173
9174 gboolean
caja_icon_container_has_stored_icon_positions(CajaIconContainer * container)9175 caja_icon_container_has_stored_icon_positions (CajaIconContainer *container)
9176 {
9177 GList *p;
9178 gboolean have_stored_position;
9179 CajaIconPosition position;
9180 CajaIcon *icon = NULL;
9181
9182 for (p = container->details->icons; p != NULL; p = p->next)
9183 {
9184 icon = p->data;
9185
9186 have_stored_position = FALSE;
9187 g_signal_emit (container,
9188 signals[GET_STORED_ICON_POSITION], 0,
9189 icon->data,
9190 &position,
9191 &have_stored_position);
9192 if (have_stored_position)
9193 {
9194 return TRUE;
9195 }
9196 }
9197 return FALSE;
9198 }
9199
9200 void
caja_icon_container_set_single_click_mode(CajaIconContainer * container,gboolean single_click_mode)9201 caja_icon_container_set_single_click_mode (CajaIconContainer *container,
9202 gboolean single_click_mode)
9203 {
9204 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
9205
9206 container->details->single_click_mode = single_click_mode;
9207 }
9208
9209 /* Return if the icon container is a fixed size */
9210 gboolean
caja_icon_container_get_is_fixed_size(CajaIconContainer * container)9211 caja_icon_container_get_is_fixed_size (CajaIconContainer *container)
9212 {
9213 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
9214
9215 return container->details->is_fixed_size;
9216 }
9217
9218 /* Set the icon container to be a fixed size */
9219 void
caja_icon_container_set_is_fixed_size(CajaIconContainer * container,gboolean is_fixed_size)9220 caja_icon_container_set_is_fixed_size (CajaIconContainer *container,
9221 gboolean is_fixed_size)
9222 {
9223 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
9224
9225 container->details->is_fixed_size = is_fixed_size;
9226 }
9227
9228 gboolean
caja_icon_container_get_is_desktop(CajaIconContainer * container)9229 caja_icon_container_get_is_desktop (CajaIconContainer *container)
9230 {
9231 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
9232
9233 return container->details->is_desktop;
9234 }
9235
9236 void
caja_icon_container_set_is_desktop(CajaIconContainer * container,gboolean is_desktop)9237 caja_icon_container_set_is_desktop (CajaIconContainer *container,
9238 gboolean is_desktop)
9239 {
9240 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
9241
9242 container->details->is_desktop = is_desktop;
9243
9244 if (is_desktop) {
9245 GtkStyleContext *context;
9246
9247 context = gtk_widget_get_style_context (GTK_WIDGET (container));
9248 gtk_style_context_add_class (context, "caja-desktop");
9249 }
9250 }
9251
9252 void
caja_icon_container_set_margins(CajaIconContainer * container,int left_margin,int right_margin,int top_margin,int bottom_margin)9253 caja_icon_container_set_margins (CajaIconContainer *container,
9254 int left_margin,
9255 int right_margin,
9256 int top_margin,
9257 int bottom_margin)
9258 {
9259 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
9260
9261 container->details->left_margin = left_margin;
9262 container->details->right_margin = right_margin;
9263 container->details->top_margin = top_margin;
9264 container->details->bottom_margin = bottom_margin;
9265
9266 /* redo layout of icons as the margins have changed */
9267 schedule_redo_layout (container);
9268 }
9269
9270 void
caja_icon_container_set_use_drop_shadows(CajaIconContainer * container,gboolean use_drop_shadows)9271 caja_icon_container_set_use_drop_shadows (CajaIconContainer *container,
9272 gboolean use_drop_shadows)
9273 {
9274 if (container->details->drop_shadows_requested == use_drop_shadows)
9275 {
9276 return;
9277 }
9278
9279 container->details->drop_shadows_requested = use_drop_shadows;
9280 container->details->use_drop_shadows = use_drop_shadows;
9281
9282 gtk_widget_queue_draw (GTK_WIDGET (container));
9283 }
9284
9285 /* handle theme changes */
9286
9287 void
caja_icon_container_set_font(CajaIconContainer * container,const char * font)9288 caja_icon_container_set_font (CajaIconContainer *container,
9289 const char *font)
9290 {
9291 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
9292
9293 if (g_strcmp0 (container->details->font, font) == 0)
9294 {
9295 return;
9296 }
9297
9298 g_free (container->details->font);
9299 container->details->font = g_strdup (font);
9300
9301 invalidate_labels (container);
9302 caja_icon_container_request_update_all (container);
9303 gtk_widget_queue_draw (GTK_WIDGET (container));
9304 }
9305
9306 void
caja_icon_container_set_font_size_table(CajaIconContainer * container,const int font_size_table[CAJA_ZOOM_LEVEL_LARGEST+1])9307 caja_icon_container_set_font_size_table (CajaIconContainer *container,
9308 const int font_size_table[CAJA_ZOOM_LEVEL_LARGEST + 1])
9309 {
9310 int old_font_size;
9311 int i;
9312
9313 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
9314 g_return_if_fail (font_size_table != NULL);
9315
9316 old_font_size = container->details->font_size_table[container->details->zoom_level];
9317
9318 for (i = 0; i <= CAJA_ZOOM_LEVEL_LARGEST; i++)
9319 {
9320 if (container->details->font_size_table[i] != font_size_table[i])
9321 {
9322 container->details->font_size_table[i] = font_size_table[i];
9323 }
9324 }
9325
9326 if (old_font_size != container->details->font_size_table[container->details->zoom_level])
9327 {
9328 invalidate_labels (container);
9329 caja_icon_container_request_update_all (container);
9330 }
9331 }
9332
9333 /**
9334 * caja_icon_container_get_icon_description
9335 * @container: An icon container widget.
9336 * @data: Icon data
9337 *
9338 * Gets the description for the icon. This function may return NULL.
9339 **/
9340 char*
caja_icon_container_get_icon_description(CajaIconContainer * container,CajaIconData * data)9341 caja_icon_container_get_icon_description (CajaIconContainer *container,
9342 CajaIconData *data)
9343 {
9344 CajaIconContainerClass *klass;
9345
9346 klass = CAJA_ICON_CONTAINER_GET_CLASS (container);
9347
9348 if (klass->get_icon_description)
9349 {
9350 return klass->get_icon_description (container, data);
9351 }
9352 else
9353 {
9354 return NULL;
9355 }
9356 }
9357
9358 gboolean
caja_icon_container_get_allow_moves(CajaIconContainer * container)9359 caja_icon_container_get_allow_moves (CajaIconContainer *container)
9360 {
9361 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
9362
9363 return container->details->drag_allow_moves;
9364 }
9365
9366 void
caja_icon_container_set_allow_moves(CajaIconContainer * container,gboolean allow_moves)9367 caja_icon_container_set_allow_moves (CajaIconContainer *container,
9368 gboolean allow_moves)
9369 {
9370 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
9371
9372 container->details->drag_allow_moves = allow_moves;
9373 }
9374
9375 void
caja_icon_container_set_forced_icon_size(CajaIconContainer * container,int forced_icon_size)9376 caja_icon_container_set_forced_icon_size (CajaIconContainer *container,
9377 int forced_icon_size)
9378 {
9379 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
9380
9381 if (forced_icon_size != container->details->forced_icon_size)
9382 {
9383 container->details->forced_icon_size = forced_icon_size;
9384
9385 invalidate_label_sizes (container);
9386 caja_icon_container_request_update_all (container);
9387 }
9388 }
9389
9390 void
caja_icon_container_set_all_columns_same_width(CajaIconContainer * container,gboolean all_columns_same_width)9391 caja_icon_container_set_all_columns_same_width (CajaIconContainer *container,
9392 gboolean all_columns_same_width)
9393 {
9394 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
9395
9396 if (all_columns_same_width != container->details->all_columns_same_width)
9397 {
9398 container->details->all_columns_same_width = all_columns_same_width;
9399
9400 invalidate_labels (container);
9401 caja_icon_container_request_update_all (container);
9402 }
9403 }
9404
9405 /**
9406 * caja_icon_container_set_highlighted_for_clipboard
9407 * @container: An icon container widget.
9408 * @data: Icon Data associated with all icons that should be highlighted.
9409 * Others will be unhighlighted.
9410 **/
9411 void
caja_icon_container_set_highlighted_for_clipboard(CajaIconContainer * container,GList * clipboard_icon_data)9412 caja_icon_container_set_highlighted_for_clipboard (CajaIconContainer *container,
9413 GList *clipboard_icon_data)
9414 {
9415 GList *l;
9416 gboolean highlighted_for_clipboard;
9417 CajaIcon *icon = NULL;
9418
9419 g_return_if_fail (CAJA_IS_ICON_CONTAINER (container));
9420
9421 for (l = container->details->icons; l != NULL; l = l->next)
9422 {
9423 icon = l->data;
9424 highlighted_for_clipboard = (g_list_find (clipboard_icon_data, icon->data) != NULL);
9425
9426 eel_canvas_item_set (EEL_CANVAS_ITEM (icon->item),
9427 "highlighted-for-clipboard", highlighted_for_clipboard,
9428 NULL);
9429 }
9430
9431 }
9432
9433 /* CajaIconContainerAccessible */
9434
9435 static CajaIconContainerAccessiblePrivate *
accessible_get_priv(AtkObject * accessible)9436 accessible_get_priv (AtkObject *accessible)
9437 {
9438 CajaIconContainerAccessiblePrivate *priv;
9439
9440 priv = g_object_get_qdata (G_OBJECT (accessible),
9441 accessible_private_data_quark);
9442
9443 return priv;
9444 }
9445
9446 /* AtkAction interface */
9447
9448 static gboolean
caja_icon_container_accessible_do_action(AtkAction * accessible,int i)9449 caja_icon_container_accessible_do_action (AtkAction *accessible, int i)
9450 {
9451 GtkWidget *widget;
9452 CajaIconContainer *container;
9453 GList *selection;
9454
9455 g_return_val_if_fail (i < LAST_ACTION, FALSE);
9456
9457 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
9458 if (!widget)
9459 {
9460 return FALSE;
9461 }
9462
9463 container = CAJA_ICON_CONTAINER (widget);
9464 switch (i)
9465 {
9466 case ACTION_ACTIVATE :
9467 selection = caja_icon_container_get_selection (container);
9468
9469 if (selection)
9470 {
9471 g_signal_emit_by_name (container, "activate", selection);
9472 g_list_free (selection);
9473 }
9474 break;
9475 case ACTION_MENU :
9476 handle_popups (container, NULL,"context_click_background");
9477 break;
9478 default :
9479 g_warning ("Invalid action passed to CajaIconContainerAccessible::do_action");
9480 return FALSE;
9481 }
9482 return TRUE;
9483 }
9484
9485 static int
caja_icon_container_accessible_get_n_actions(AtkAction * accessible)9486 caja_icon_container_accessible_get_n_actions (AtkAction *accessible)
9487 {
9488 return LAST_ACTION;
9489 }
9490
9491 static const char *
caja_icon_container_accessible_action_get_description(AtkAction * accessible,int i)9492 caja_icon_container_accessible_action_get_description (AtkAction *accessible,
9493 int i)
9494 {
9495 CajaIconContainerAccessiblePrivate *priv;
9496
9497 g_assert (i < LAST_ACTION);
9498
9499 priv = accessible_get_priv (ATK_OBJECT (accessible));
9500
9501 if (priv->action_descriptions[i])
9502 {
9503 return priv->action_descriptions[i];
9504 }
9505 else
9506 {
9507 return caja_icon_container_accessible_action_descriptions[i];
9508 }
9509 }
9510
9511 static const char *
caja_icon_container_accessible_action_get_name(AtkAction * accessible,int i)9512 caja_icon_container_accessible_action_get_name (AtkAction *accessible, int i)
9513 {
9514 g_assert (i < LAST_ACTION);
9515
9516 return caja_icon_container_accessible_action_names[i];
9517 }
9518
9519 static const char *
caja_icon_container_accessible_action_get_keybinding(AtkAction * accessible,int i)9520 caja_icon_container_accessible_action_get_keybinding (AtkAction *accessible,
9521 int i)
9522 {
9523 g_assert (i < LAST_ACTION);
9524
9525 return NULL;
9526 }
9527
9528 static gboolean
caja_icon_container_accessible_action_set_description(AtkAction * accessible,int i,const char * description)9529 caja_icon_container_accessible_action_set_description (AtkAction *accessible,
9530 int i,
9531 const char *description)
9532 {
9533 CajaIconContainerAccessiblePrivate *priv;
9534
9535 g_assert (i < LAST_ACTION);
9536
9537 priv = accessible_get_priv (ATK_OBJECT (accessible));
9538
9539 if (priv->action_descriptions[i])
9540 {
9541 g_free (priv->action_descriptions[i]);
9542 }
9543 priv->action_descriptions[i] = g_strdup (description);
9544
9545 return FALSE;
9546 }
9547
9548 static void
caja_icon_container_accessible_action_interface_init(AtkActionIface * iface)9549 caja_icon_container_accessible_action_interface_init (AtkActionIface *iface)
9550 {
9551 iface->do_action = caja_icon_container_accessible_do_action;
9552 iface->get_n_actions = caja_icon_container_accessible_get_n_actions;
9553 iface->get_description = caja_icon_container_accessible_action_get_description;
9554 iface->get_name = caja_icon_container_accessible_action_get_name;
9555 iface->get_keybinding = caja_icon_container_accessible_action_get_keybinding;
9556 iface->set_description = caja_icon_container_accessible_action_set_description;
9557 }
9558
9559 /* AtkSelection interface */
9560
9561 static void
caja_icon_container_accessible_update_selection(AtkObject * accessible)9562 caja_icon_container_accessible_update_selection (AtkObject *accessible)
9563 {
9564 CajaIconContainer *container;
9565 CajaIconContainerAccessiblePrivate *priv;
9566 GList *l;
9567 CajaIcon *icon = NULL;
9568
9569 container = CAJA_ICON_CONTAINER (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
9570
9571 priv = accessible_get_priv (accessible);
9572
9573 if (priv->selection)
9574 {
9575 g_list_free (priv->selection);
9576 priv->selection = NULL;
9577 }
9578
9579 for (l = container->details->icons; l != NULL; l = l->next)
9580 {
9581 icon = l->data;
9582 if (icon->is_selected)
9583 {
9584 priv->selection = g_list_prepend (priv->selection,
9585 icon);
9586 }
9587 }
9588
9589 priv->selection = g_list_reverse (priv->selection);
9590 }
9591
9592 static void
caja_icon_container_accessible_selection_changed_cb(CajaIconContainer * container,gpointer data)9593 caja_icon_container_accessible_selection_changed_cb (CajaIconContainer *container,
9594 gpointer data)
9595 {
9596 g_signal_emit_by_name (data, "selection_changed");
9597 }
9598
9599 static void
caja_icon_container_accessible_icon_added_cb(CajaIconContainer * container,CajaIconData * icon_data,gpointer data)9600 caja_icon_container_accessible_icon_added_cb (CajaIconContainer *container,
9601 CajaIconData *icon_data,
9602 gpointer data)
9603 {
9604 CajaIcon *icon;
9605
9606 // We don't want to emit children_changed signals during any type of load.
9607 if (container->details->is_loading || container->details->is_populating_container)
9608 return;
9609
9610 icon = g_hash_table_lookup (container->details->icon_set, icon_data);
9611 if (icon)
9612 {
9613 AtkObject *atk_parent;
9614 AtkObject *atk_child;
9615 int index;
9616
9617 atk_parent = ATK_OBJECT (data);
9618 atk_child = atk_gobject_accessible_for_object
9619 (G_OBJECT (icon->item));
9620 index = g_list_index (container->details->icons, icon);
9621
9622 g_signal_emit_by_name (atk_parent, "children_changed::add",
9623 index, atk_child, NULL);
9624 }
9625 }
9626
9627 static void
caja_icon_container_accessible_icon_removed_cb(CajaIconContainer * container,CajaIconData * icon_data,gpointer data)9628 caja_icon_container_accessible_icon_removed_cb (CajaIconContainer *container,
9629 CajaIconData *icon_data,
9630 gpointer data)
9631 {
9632 CajaIcon *icon;
9633
9634 icon = g_hash_table_lookup (container->details->icon_set, icon_data);
9635 if (icon)
9636 {
9637 AtkObject *atk_parent;
9638 AtkObject *atk_child;
9639 int index;
9640
9641 atk_parent = ATK_OBJECT (data);
9642 atk_child = atk_gobject_accessible_for_object
9643 (G_OBJECT (icon->item));
9644 index = g_list_index (container->details->icons, icon);
9645
9646 g_signal_emit_by_name (atk_parent, "children_changed::remove",
9647 index, atk_child, NULL);
9648 }
9649 }
9650
9651 static void
caja_icon_container_accessible_cleared_cb(CajaIconContainer * container,gpointer data)9652 caja_icon_container_accessible_cleared_cb (CajaIconContainer *container,
9653 gpointer data)
9654 {
9655 g_signal_emit_by_name (data, "children_changed", 0, NULL, NULL);
9656 }
9657
9658
9659 static gboolean
caja_icon_container_accessible_add_selection(AtkSelection * accessible,int i)9660 caja_icon_container_accessible_add_selection (AtkSelection *accessible,
9661 int i)
9662 {
9663 GtkWidget *widget;
9664 CajaIconContainer *container;
9665 CajaIcon *icon;
9666
9667 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
9668 if (!widget)
9669 {
9670 return FALSE;
9671 }
9672
9673 container = CAJA_ICON_CONTAINER (widget);
9674
9675 icon = g_list_nth_data (container->details->icons, i);
9676 if (icon)
9677 {
9678 GList *selection;
9679
9680 selection = caja_icon_container_get_selection (container);
9681 selection = g_list_prepend (selection,
9682 icon->data);
9683 caja_icon_container_set_selection (container, selection);
9684
9685 g_list_free (selection);
9686 return TRUE;
9687 }
9688
9689 return FALSE;
9690 }
9691
9692 static gboolean
caja_icon_container_accessible_clear_selection(AtkSelection * accessible)9693 caja_icon_container_accessible_clear_selection (AtkSelection *accessible)
9694 {
9695 GtkWidget *widget;
9696 CajaIconContainer *container;
9697
9698 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
9699 if (!widget)
9700 {
9701 return FALSE;
9702 }
9703
9704 container = CAJA_ICON_CONTAINER (widget);
9705
9706 caja_icon_container_unselect_all (container);
9707
9708 return TRUE;
9709 }
9710
9711 static AtkObject *
caja_icon_container_accessible_ref_selection(AtkSelection * accessible,int i)9712 caja_icon_container_accessible_ref_selection (AtkSelection *accessible,
9713 int i)
9714 {
9715 CajaIconContainerAccessiblePrivate *priv;
9716 CajaIcon *icon;
9717
9718 caja_icon_container_accessible_update_selection (ATK_OBJECT (accessible));
9719 priv = accessible_get_priv (ATK_OBJECT (accessible));
9720
9721 icon = g_list_nth_data (priv->selection, i);
9722 if (icon)
9723 {
9724 AtkObject *atk_object;
9725
9726 atk_object = atk_gobject_accessible_for_object (G_OBJECT (icon->item));
9727 if (atk_object)
9728 {
9729 g_object_ref (atk_object);
9730 }
9731
9732 return atk_object;
9733 }
9734 else
9735 {
9736 return NULL;
9737 }
9738 }
9739
9740 static int
caja_icon_container_accessible_get_selection_count(AtkSelection * accessible)9741 caja_icon_container_accessible_get_selection_count (AtkSelection *accessible)
9742 {
9743 int count;
9744 CajaIconContainerAccessiblePrivate *priv;
9745
9746 caja_icon_container_accessible_update_selection (ATK_OBJECT (accessible));
9747 priv = accessible_get_priv (ATK_OBJECT (accessible));
9748
9749 count = g_list_length (priv->selection);
9750
9751 return count;
9752 }
9753
9754 static gboolean
caja_icon_container_accessible_is_child_selected(AtkSelection * accessible,int i)9755 caja_icon_container_accessible_is_child_selected (AtkSelection *accessible,
9756 int i)
9757 {
9758 CajaIconContainer *container;
9759 CajaIcon *icon;
9760 GtkWidget *widget;
9761
9762 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
9763 if (!widget)
9764 {
9765 return FALSE;
9766 }
9767
9768 container = CAJA_ICON_CONTAINER (widget);
9769
9770 icon = g_list_nth_data (container->details->icons, i);
9771 return icon ? icon->is_selected : FALSE;
9772 }
9773
9774 static gboolean
caja_icon_container_accessible_remove_selection(AtkSelection * accessible,int i)9775 caja_icon_container_accessible_remove_selection (AtkSelection *accessible,
9776 int i)
9777 {
9778 CajaIconContainer *container;
9779 CajaIconContainerAccessiblePrivate *priv;
9780 CajaIcon *icon;
9781 GtkWidget *widget;
9782
9783 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
9784 if (!widget)
9785 {
9786 return FALSE;
9787 }
9788
9789 caja_icon_container_accessible_update_selection (ATK_OBJECT (accessible));
9790 priv = accessible_get_priv (ATK_OBJECT (accessible));
9791
9792 container = CAJA_ICON_CONTAINER (widget);
9793
9794 icon = g_list_nth_data (priv->selection, i);
9795 if (icon)
9796 {
9797 GList *selection;
9798
9799 selection = caja_icon_container_get_selection (container);
9800 selection = g_list_remove (selection, icon->data);
9801 caja_icon_container_set_selection (container, selection);
9802
9803 g_list_free (selection);
9804 return TRUE;
9805 }
9806
9807 return FALSE;
9808 }
9809
9810 static gboolean
caja_icon_container_accessible_select_all_selection(AtkSelection * accessible)9811 caja_icon_container_accessible_select_all_selection (AtkSelection *accessible)
9812 {
9813 CajaIconContainer *container;
9814 GtkWidget *widget;
9815
9816 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
9817 if (!widget)
9818 {
9819 return FALSE;
9820 }
9821
9822 container = CAJA_ICON_CONTAINER (widget);
9823
9824 caja_icon_container_select_all (container);
9825
9826 return TRUE;
9827 }
9828
9829 void
caja_icon_container_widget_to_file_operation_position(CajaIconContainer * container,GdkPoint * position)9830 caja_icon_container_widget_to_file_operation_position (CajaIconContainer *container,
9831 GdkPoint *position)
9832 {
9833 double x, y;
9834
9835 g_return_if_fail (position != NULL);
9836
9837 x = position->x;
9838 y = position->y;
9839
9840 eel_canvas_window_to_world (EEL_CANVAS (container), x, y, &x, &y);
9841
9842 position->x = (int) x;
9843 position->y = (int) y;
9844
9845 /* ensure that we end up in the middle of the icon */
9846 position->x -= caja_get_icon_size_for_zoom_level (container->details->zoom_level) / 2;
9847 position->y -= caja_get_icon_size_for_zoom_level (container->details->zoom_level) / 2;
9848 }
9849
9850 static void
caja_icon_container_accessible_selection_interface_init(AtkSelectionIface * iface)9851 caja_icon_container_accessible_selection_interface_init (AtkSelectionIface *iface)
9852 {
9853 iface->add_selection = caja_icon_container_accessible_add_selection;
9854 iface->clear_selection = caja_icon_container_accessible_clear_selection;
9855 iface->ref_selection = caja_icon_container_accessible_ref_selection;
9856 iface->get_selection_count = caja_icon_container_accessible_get_selection_count;
9857 iface->is_child_selected = caja_icon_container_accessible_is_child_selected;
9858 iface->remove_selection = caja_icon_container_accessible_remove_selection;
9859 iface->select_all_selection = caja_icon_container_accessible_select_all_selection;
9860 }
9861
9862
9863 static gint
caja_icon_container_accessible_get_n_children(AtkObject * accessible)9864 caja_icon_container_accessible_get_n_children (AtkObject *accessible)
9865 {
9866 CajaIconContainer *container;
9867 GtkWidget *widget;
9868 gint i;
9869
9870 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
9871 if (!widget)
9872 {
9873 return FALSE;
9874 }
9875
9876 container = CAJA_ICON_CONTAINER (widget);
9877
9878 i = g_hash_table_size (container->details->icon_set);
9879 if (container->details->rename_widget)
9880 {
9881 i++;
9882 }
9883 return i;
9884 }
9885
9886 static AtkObject*
caja_icon_container_accessible_ref_child(AtkObject * accessible,int i)9887 caja_icon_container_accessible_ref_child (AtkObject *accessible, int i)
9888 {
9889 AtkObject *atk_object;
9890 CajaIconContainer *container;
9891 CajaIcon *icon;
9892 GtkWidget *widget;
9893
9894 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
9895 if (!widget)
9896 {
9897 return NULL;
9898 }
9899
9900 container = CAJA_ICON_CONTAINER (widget);
9901
9902 icon = g_list_nth_data (container->details->icons, i);
9903 if (icon)
9904 {
9905 atk_object = atk_gobject_accessible_for_object (G_OBJECT (icon->item));
9906 g_object_ref (atk_object);
9907
9908 return atk_object;
9909 }
9910 else
9911 {
9912 if (i == g_list_length (container->details->icons))
9913 {
9914 if (container->details->rename_widget)
9915 {
9916 atk_object = gtk_widget_get_accessible (container->details->rename_widget);
9917 g_object_ref (atk_object);
9918
9919 return atk_object;
9920 }
9921 }
9922 return NULL;
9923 }
9924 }
9925
9926 static void
caja_icon_container_accessible_initialize(AtkObject * accessible,gpointer data)9927 caja_icon_container_accessible_initialize (AtkObject *accessible,
9928 gpointer data)
9929 {
9930 CajaIconContainerAccessiblePrivate *priv;
9931
9932 if (ATK_OBJECT_CLASS (accessible_parent_class)->initialize)
9933 {
9934 ATK_OBJECT_CLASS (accessible_parent_class)->initialize (accessible, data);
9935 }
9936
9937 priv = g_new0 (CajaIconContainerAccessiblePrivate, 1);
9938 g_object_set_qdata (G_OBJECT (accessible),
9939 accessible_private_data_quark,
9940 priv);
9941
9942 if (GTK_IS_ACCESSIBLE (accessible))
9943 {
9944 CajaIconContainer *container;
9945
9946 caja_icon_container_accessible_update_selection
9947 (ATK_OBJECT (accessible));
9948
9949 container = CAJA_ICON_CONTAINER (gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)));
9950 g_signal_connect (G_OBJECT (container), "selection_changed",
9951 G_CALLBACK (caja_icon_container_accessible_selection_changed_cb),
9952 accessible);
9953 g_signal_connect (G_OBJECT (container), "icon_added",
9954 G_CALLBACK (caja_icon_container_accessible_icon_added_cb),
9955 accessible);
9956 g_signal_connect (G_OBJECT (container), "icon_removed",
9957 G_CALLBACK (caja_icon_container_accessible_icon_removed_cb),
9958 accessible);
9959 g_signal_connect (G_OBJECT (container), "cleared",
9960 G_CALLBACK (caja_icon_container_accessible_cleared_cb),
9961 accessible);
9962 }
9963 }
9964
9965 static void
caja_icon_container_accessible_finalize(GObject * object)9966 caja_icon_container_accessible_finalize (GObject *object)
9967 {
9968 CajaIconContainerAccessiblePrivate *priv;
9969 int i;
9970
9971 priv = accessible_get_priv (ATK_OBJECT (object));
9972 if (priv->selection)
9973 {
9974 g_list_free (priv->selection);
9975 }
9976
9977 for (i = 0; i < LAST_ACTION; i++)
9978 {
9979 if (priv->action_descriptions[i])
9980 {
9981 g_free (priv->action_descriptions[i]);
9982 }
9983 }
9984
9985 g_free (priv);
9986
9987 G_OBJECT_CLASS (accessible_parent_class)->finalize (object);
9988 }
9989
9990 G_DEFINE_TYPE_WITH_CODE (CajaIconContainerAccessible,
9991 caja_icon_container_accessible,
9992 eel_canvas_accessible_get_type (),
9993 G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION,
9994 caja_icon_container_accessible_action_interface_init)
9995 G_IMPLEMENT_INTERFACE (ATK_TYPE_SELECTION,
9996 caja_icon_container_accessible_selection_interface_init));
9997
9998 static void
caja_icon_container_accessible_init(CajaIconContainerAccessible * accessible)9999 caja_icon_container_accessible_init (CajaIconContainerAccessible *accessible)
10000 {
10001 }
10002
10003 static void
caja_icon_container_accessible_class_init(CajaIconContainerAccessibleClass * klass)10004 caja_icon_container_accessible_class_init (CajaIconContainerAccessibleClass *klass)
10005 {
10006 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
10007 AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
10008
10009 accessible_parent_class = g_type_class_peek_parent (klass);
10010
10011 gobject_class->finalize = caja_icon_container_accessible_finalize;
10012
10013 atk_class->get_n_children = caja_icon_container_accessible_get_n_children;
10014 atk_class->ref_child = caja_icon_container_accessible_ref_child;
10015 atk_class->initialize = caja_icon_container_accessible_initialize;
10016
10017 accessible_private_data_quark = g_quark_from_static_string ("icon-container-accessible-private-data");
10018 }
10019
10020 #if ! defined (CAJA_OMIT_SELF_CHECK)
10021
10022 static char *
check_compute_stretch(int icon_x,int icon_y,int icon_size,int start_pointer_x,int start_pointer_y,int end_pointer_x,int end_pointer_y)10023 check_compute_stretch (int icon_x, int icon_y, int icon_size,
10024 int start_pointer_x, int start_pointer_y,
10025 int end_pointer_x, int end_pointer_y)
10026 {
10027 StretchState start, current;
10028
10029 start.icon_x = icon_x;
10030 start.icon_y = icon_y;
10031 start.icon_size = icon_size;
10032 start.pointer_x = start_pointer_x;
10033 start.pointer_y = start_pointer_y;
10034 current.pointer_x = end_pointer_x;
10035 current.pointer_y = end_pointer_y;
10036
10037 compute_stretch (&start, ¤t);
10038
10039 return g_strdup_printf ("%d,%d:%d",
10040 current.icon_x,
10041 current.icon_y,
10042 current.icon_size);
10043 }
10044
10045 void
caja_self_check_icon_container(void)10046 caja_self_check_icon_container (void)
10047 {
10048 EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 16, 0, 0, 0, 0), "0,0:16");
10049 EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 16, 16, 16, 17, 17), "0,0:17");
10050 EEL_CHECK_STRING_RESULT (check_compute_stretch (0, 0, 16, 16, 16, 17, 16), "0,0:16");
10051 EEL_CHECK_STRING_RESULT (check_compute_stretch (100, 100, 64, 105, 105, 40, 40), "35,35:129");
10052 }
10053 #endif /* ! CAJA_OMIT_SELF_CHECK */
10054
10055 gboolean
caja_icon_container_is_layout_rtl(CajaIconContainer * container)10056 caja_icon_container_is_layout_rtl (CajaIconContainer *container)
10057 {
10058 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), 0);
10059
10060 return container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_R_L ||
10061 container->details->layout_mode == CAJA_ICON_LAYOUT_R_L_T_B;
10062 }
10063
10064 gboolean
caja_icon_container_is_layout_vertical(CajaIconContainer * container)10065 caja_icon_container_is_layout_vertical (CajaIconContainer *container)
10066 {
10067 g_return_val_if_fail (CAJA_IS_ICON_CONTAINER (container), FALSE);
10068
10069 return (container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_L_R ||
10070 container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_R_L);
10071 }
10072
10073 int
caja_icon_container_get_max_layout_lines_for_pango(CajaIconContainer * container)10074 caja_icon_container_get_max_layout_lines_for_pango (CajaIconContainer *container)
10075 {
10076 int limit;
10077
10078 if (caja_icon_container_get_is_desktop (container))
10079 {
10080 limit = desktop_text_ellipsis_limit;
10081 }
10082 else
10083 {
10084 limit = text_ellipsis_limits[container->details->zoom_level];
10085 }
10086
10087 if (limit <= 0)
10088 {
10089 return G_MININT;
10090 }
10091
10092 return -limit;
10093 }
10094
10095 int
caja_icon_container_get_max_layout_lines(CajaIconContainer * container)10096 caja_icon_container_get_max_layout_lines (CajaIconContainer *container)
10097 {
10098 int limit;
10099
10100 if (caja_icon_container_get_is_desktop (container))
10101 {
10102 limit = desktop_text_ellipsis_limit;
10103 }
10104 else
10105 {
10106 limit = text_ellipsis_limits[container->details->zoom_level];
10107 }
10108
10109 if (limit <= 0)
10110 {
10111 return G_MAXINT;
10112 }
10113
10114 return limit;
10115 }
10116
10117 void
caja_icon_container_begin_loading(CajaIconContainer * container)10118 caja_icon_container_begin_loading (CajaIconContainer *container)
10119 {
10120 gboolean dummy;
10121
10122 if (caja_icon_container_get_store_layout_timestamps (container))
10123 {
10124 container->details->layout_timestamp = UNDEFINED_TIME;
10125 g_signal_emit (container,
10126 signals[GET_STORED_LAYOUT_TIMESTAMP], 0,
10127 NULL, &container->details->layout_timestamp, &dummy);
10128 }
10129 }
10130
10131 static void
store_layout_timestamps_now(CajaIconContainer * container)10132 store_layout_timestamps_now (CajaIconContainer *container)
10133 {
10134 GList *p;
10135 gboolean dummy;
10136 CajaIcon *icon = NULL;
10137
10138 container->details->layout_timestamp = time (NULL);
10139 g_signal_emit (container,
10140 signals[STORE_LAYOUT_TIMESTAMP], 0,
10141 NULL, &container->details->layout_timestamp, &dummy);
10142
10143 for (p = container->details->icons; p != NULL; p = p->next)
10144 {
10145 icon = p->data;
10146
10147 g_signal_emit (container,
10148 signals[STORE_LAYOUT_TIMESTAMP], 0,
10149 icon->data, &container->details->layout_timestamp, &dummy);
10150 }
10151 }
10152
10153 void
caja_icon_container_end_loading(CajaIconContainer * container,gboolean all_icons_added)10154 caja_icon_container_end_loading (CajaIconContainer *container,
10155 gboolean all_icons_added)
10156 {
10157 if (all_icons_added &&
10158 caja_icon_container_get_store_layout_timestamps (container))
10159 {
10160 if (container->details->new_icons == NULL)
10161 {
10162 store_layout_timestamps_now (container);
10163 }
10164 else
10165 {
10166 container->details->store_layout_timestamps_when_finishing_new_icons = TRUE;
10167 }
10168 }
10169 }
10170
10171 gboolean
caja_icon_container_get_store_layout_timestamps(CajaIconContainer * container)10172 caja_icon_container_get_store_layout_timestamps (CajaIconContainer *container)
10173 {
10174 return container->details->store_layout_timestamps;
10175 }
10176
10177 void
caja_icon_container_set_store_layout_timestamps(CajaIconContainer * container,gboolean store_layout_timestamps)10178 caja_icon_container_set_store_layout_timestamps (CajaIconContainer *container,
10179 gboolean store_layout_timestamps)
10180 {
10181 container->details->store_layout_timestamps = store_layout_timestamps;
10182 }
10183