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, &timestamp, &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, &current);
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