1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 
3 /* Caja - Icon canvas item class for icon container.
4  *
5  * Copyright (C) 2000 Eazel, Inc
6  *
7  * Author: Andy Hertzfeld <andy@eazel.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This 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 this library; if not, write to the
21  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  */
24 
25 #include <config.h>
26 #include <math.h>
27 #include <glib/gi18n.h>
28 #include <gdk-pixbuf/gdk-pixbuf.h>
29 #include <gtk/gtk.h>
30 #include <gdk/gdk.h>
31 #include <glib/gi18n.h>
32 #include <atk/atkimage.h>
33 #include <atk/atkcomponent.h>
34 #include <atk/atknoopobject.h>
35 #include <stdio.h>
36 #include <string.h>
37 
38 #include <eel/eel-art-extensions.h>
39 #include <eel/eel-gdk-extensions.h>
40 #include <eel/eel-gdk-pixbuf-extensions.h>
41 #include <eel/eel-glib-extensions.h>
42 #include <eel/eel-graphic-effects.h>
43 #include <eel/eel-gtk-macros.h>
44 #include <eel/eel-string.h>
45 #include <eel/eel-accessibility.h>
46 
47 #include "caja-icon-canvas-item.h"
48 #include "caja-file-utilities.h"
49 #include "caja-global-preferences.h"
50 #include "caja-icon-private.h"
51 
52 #define EMBLEM_SPACING 2
53 
54 /* gap between bottom of icon and start of text box */
55 #define LABEL_OFFSET 1
56 #define LABEL_LINE_SPACING 0
57 
58 #define MAX_TEXT_WIDTH_STANDARD 135
59 #define MAX_TEXT_WIDTH_TIGHTER 80
60 #define MAX_TEXT_WIDTH_BESIDE 90
61 #define MAX_TEXT_WIDTH_BESIDE_TOP_TO_BOTTOM 150
62 
63 #ifndef PANGO_CHECK_VERSION
64 #define PANGO_CHECK_VERSION(major, minor, micro)                          \
65      (PANGO_VERSION_MAJOR > (major) ||                                    \
66      (PANGO_VERSION_MAJOR == (major) && PANGO_VERSION_MINOR > (minor)) || \
67      (PANGO_VERSION_MAJOR == (major) && PANGO_VERSION_MINOR == (minor) && \
68       PANGO_VERSION_MICRO >= (micro)))
69 #endif
70 
71 /* special text height handling
72  * each item has three text height variables:
73  *  + text_height: actual height of the displayed (i.e. on-screen) PangoLayout.
74  *  + text_height_for_layout: height used in icon grid layout algorithms.
75  *       		      “sane amount” of text.
76  *   “sane amount“ as of
77  *      + hard-coded to three lines in text-below-icon mode.
78  *      + unlimited in text-besides-icon mode (see VOODOO-TODO)
79  *
80  *  This layout height is used by grid layout algorithms, even
81  *  though the actually displayed and/or requested text size may be larger
82  *  and overlap adjacent icons, if an icon is selected.
83  *
84  *  + text_height_for_entire_text: height needed to display the entire PangoLayout,
85  *    if it wasn't ellipsized.
86  */
87 
88 /* Private part of the CajaIconCanvasItem structure. */
89 struct _CajaIconCanvasItemPrivate
90 {
91     /* The image, text, font. */
92     double x, y;
93     GdkPixbuf *pixbuf;
94     cairo_surface_t *rendered_surface;
95     GList *emblem_pixbufs;
96     char *editable_text;		/* Text that can be modified by a renaming function */
97     char *additional_text;		/* Text that cannot be modifed, such as file size, etc. */
98     GdkPoint *attach_points;
99     int n_attach_points;
100 
101     /* Size of the text at current font. */
102     int text_dx;
103     int text_width;
104 
105     /* actual size required for rendering the text to display */
106     int text_height;
107     /* actual size that would be required for rendering the entire text if it wasn't ellipsized */
108     int text_height_for_entire_text;
109     /* actual size needed for rendering a “sane amount” of text */
110     int text_height_for_layout;
111 
112     int editable_text_height;
113 
114     /* whether the entire text must always be visible. In that case,
115      * text_height_for_layout will always be equal to text_height.
116      * Used for the last line of a line-wise icon layout. */
117     guint entire_text : 1;
118 
119     /* preview state */
120     guint is_active : 1;
121 
122     /* Highlight state. */
123     guint is_highlighted_for_selection : 1;
124     guint is_highlighted_as_keyboard_focus: 1;
125     guint is_highlighted_for_drop : 1;
126     guint is_highlighted_for_clipboard : 1;
127     guint show_stretch_handles : 1;
128     guint is_prelit : 1;
129 
130     guint rendered_is_active : 1;
131     guint rendered_is_highlighted_for_selection : 1;
132     guint rendered_is_highlighted_for_drop : 1;
133     guint rendered_is_highlighted_for_clipboard : 1;
134     guint rendered_is_prelit : 1;
135     guint rendered_is_focused : 1;
136 
137     guint is_renaming : 1;
138 
139     guint bounds_cached : 1;
140 
141     guint is_visible : 1;
142 
143     GdkRectangle embedded_text_rect;
144     char *embedded_text;
145 
146     /* Cached PangoLayouts. Only used if the icon is visible */
147     PangoLayout *editable_text_layout;
148     PangoLayout *additional_text_layout;
149     PangoLayout *embedded_text_layout;
150 
151     /* Cached rectangle in canvas coordinates */
152     EelIRect canvas_rect;
153     EelIRect text_rect;
154     EelIRect emblem_rect;
155 
156     EelIRect bounds_cache;
157     EelIRect bounds_cache_for_layout;
158     EelIRect bounds_cache_for_entire_item;
159 
160     GdkWindow *cursor_window;
161 
162     /* Accessibility bits */
163     GailTextUtil *text_util;
164 };
165 
166 /* Object argument IDs. */
167 enum
168 {
169     PROP_0,
170     PROP_EDITABLE_TEXT,
171     PROP_ADDITIONAL_TEXT,
172     PROP_HIGHLIGHTED_FOR_SELECTION,
173     PROP_HIGHLIGHTED_AS_KEYBOARD_FOCUS,
174     PROP_HIGHLIGHTED_FOR_DROP,
175     PROP_HIGHLIGHTED_FOR_CLIPBOARD
176 };
177 
178 typedef enum
179 {
180     RIGHT_SIDE,
181     BOTTOM_SIDE,
182     LEFT_SIDE,
183     TOP_SIDE
184 } RectangleSide;
185 
186 typedef struct
187 {
188     CajaIconCanvasItem *icon_item;
189     EelIRect icon_rect;
190     RectangleSide side;
191     int position;
192     int index;
193     GList *emblem;
194 } EmblemLayout;
195 
196 static int click_policy_auto_value;
197 
198 static void caja_icon_canvas_item_text_interface_init (EelAccessibleTextIface *iface);
199 static GType caja_icon_canvas_item_accessible_factory_get_type (void);
200 
201 G_DEFINE_TYPE_WITH_CODE (CajaIconCanvasItem, caja_icon_canvas_item, EEL_TYPE_CANVAS_ITEM,
202                          G_ADD_PRIVATE (CajaIconCanvasItem)
203                          G_IMPLEMENT_INTERFACE (EEL_TYPE_ACCESSIBLE_TEXT,
204                                  caja_icon_canvas_item_text_interface_init));
205 
206 /* private */
207 static void     draw_label_text                      (CajaIconCanvasItem        *item,
208     						      cairo_t                   *cr,
209     						      gboolean                  create_mask,
210     						      EelIRect                  icon_rect);
211 static void     measure_label_text                   (CajaIconCanvasItem        *item);
212 static void     get_icon_canvas_rectangle            (CajaIconCanvasItem        *item,
213     						      EelIRect                  *rect);
214 static void     emblem_layout_reset                  (EmblemLayout              *layout,
215     						      CajaIconCanvasItem        *icon_item,
216     						      EelIRect                  icon_rect,
217     						      gboolean			is_rtl);
218 static gboolean emblem_layout_next                   (EmblemLayout              *layout,
219     						      GdkPixbuf                 **emblem_pixbuf,
220     						      EelIRect                  *emblem_rect,
221     						      gboolean			is_rtl);
222 static void     draw_pixbuf                          (GdkPixbuf                 *pixbuf,
223     						      cairo_t                   *cr,
224     						      int                       x,
225     						      int                       y);
226 static PangoLayout *get_label_layout                 (PangoLayout               **layout,
227     						      CajaIconCanvasItem        *item,
228     						      const char                *text);
229 
230 static gboolean hit_test_stretch_handle              (CajaIconCanvasItem        *item,
231     						      EelIRect                  canvas_rect,
232     						      GtkCornerType *corner);
233 static void      draw_embedded_text                  (CajaIconCanvasItem        *icon_item,
234     						      cairo_t                   *cr,
235     						      int                       x,
236     						      int                       y);
237 
238 static void       caja_icon_canvas_item_ensure_bounds_up_to_date (CajaIconCanvasItem *icon_item);
239 
240 
241 /* Object initialization function for the icon item. */
242 static void
caja_icon_canvas_item_init(CajaIconCanvasItem * icon_item)243 caja_icon_canvas_item_init (CajaIconCanvasItem *icon_item)
244 {
245     static gboolean setup_auto_enums = FALSE;
246 
247     if (!setup_auto_enums)
248     {
249         eel_g_settings_add_auto_enum
250              (caja_preferences,
251              CAJA_PREFERENCES_CLICK_POLICY,
252              &click_policy_auto_value);
253         setup_auto_enums = TRUE;
254     }
255 
256     icon_item->details = caja_icon_canvas_item_get_instance_private (icon_item);
257     caja_icon_canvas_item_invalidate_label_size (icon_item);
258 }
259 
260 static void
caja_icon_canvas_item_finalize(GObject * object)261 caja_icon_canvas_item_finalize (GObject *object)
262 {
263     CajaIconCanvasItemPrivate *details;
264 
265     g_assert (CAJA_IS_ICON_CANVAS_ITEM (object));
266 
267     details = CAJA_ICON_CANVAS_ITEM (object)->details;
268 
269     if (details->cursor_window != NULL)
270     {
271         gdk_window_set_cursor (details->cursor_window, NULL);
272         g_object_unref (details->cursor_window);
273     }
274 
275     if (details->pixbuf != NULL)
276     {
277         g_object_unref (details->pixbuf);
278     }
279 
280     if (details->text_util != NULL)
281     {
282         g_object_unref (details->text_util);
283     }
284 
285     g_list_free_full (details->emblem_pixbufs, g_object_unref);
286     g_free (details->editable_text);
287     g_free (details->additional_text);
288     g_free (details->attach_points);
289 
290     if (details->rendered_surface != NULL)
291     {
292     cairo_surface_destroy (details->rendered_surface);
293     }
294 
295     if (details->editable_text_layout != NULL)
296     {
297         g_object_unref (details->editable_text_layout);
298     }
299 
300     if (details->additional_text_layout != NULL)
301     {
302         g_object_unref (details->additional_text_layout);
303     }
304 
305     if (details->embedded_text_layout != NULL)
306     {
307         g_object_unref (details->embedded_text_layout);
308     }
309 
310     g_free (details->embedded_text);
311 
312     G_OBJECT_CLASS (caja_icon_canvas_item_parent_class)->finalize (object);
313 }
314 
315 /* Currently we require pixbufs in this format (for hit testing).
316  * Perhaps gdk-pixbuf will be changed so it can do the hit testing
317  * and we won't have this requirement any more.
318  */
319 static gboolean
pixbuf_is_acceptable(GdkPixbuf * pixbuf)320 pixbuf_is_acceptable (GdkPixbuf *pixbuf)
321 {
322     return gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB
323            && ((!gdk_pixbuf_get_has_alpha (pixbuf)
324                 && gdk_pixbuf_get_n_channels (pixbuf) == 3)
325                || (gdk_pixbuf_get_has_alpha (pixbuf)
326                    && gdk_pixbuf_get_n_channels (pixbuf) == 4))
327            && gdk_pixbuf_get_bits_per_sample (pixbuf) == 8;
328 }
329 
330 static void
caja_icon_canvas_item_invalidate_bounds_cache(CajaIconCanvasItem * item)331 caja_icon_canvas_item_invalidate_bounds_cache (CajaIconCanvasItem *item)
332 {
333     item->details->bounds_cached = FALSE;
334 }
335 
336 /* invalidate the text width and height cached in the item details. */
337 void
caja_icon_canvas_item_invalidate_label_size(CajaIconCanvasItem * item)338 caja_icon_canvas_item_invalidate_label_size (CajaIconCanvasItem *item)
339 {
340     if (item->details->editable_text_layout != NULL)
341     {
342         pango_layout_context_changed (item->details->editable_text_layout);
343     }
344     if (item->details->additional_text_layout != NULL)
345     {
346         pango_layout_context_changed (item->details->additional_text_layout);
347     }
348     if (item->details->embedded_text_layout != NULL)
349     {
350         pango_layout_context_changed (item->details->embedded_text_layout);
351     }
352     caja_icon_canvas_item_invalidate_bounds_cache (item);
353     item->details->text_width = -1;
354     item->details->text_height = -1;
355     item->details->text_height_for_layout = -1;
356     item->details->text_height_for_entire_text = -1;
357     item->details->editable_text_height = -1;
358 }
359 
360 /* Set property handler for the icon item. */
361 static void
caja_icon_canvas_item_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)362 caja_icon_canvas_item_set_property (GObject        *object,
363                                     guint           property_id,
364                                     const GValue   *value,
365                                     GParamSpec     *pspec)
366 {
367     CajaIconCanvasItem *item;
368     CajaIconCanvasItemPrivate *details;
369     AtkObject *accessible;
370 
371     item = CAJA_ICON_CANVAS_ITEM (object);
372     details = item->details;
373     accessible = atk_gobject_accessible_for_object (G_OBJECT (item));
374 
375     switch (property_id)
376     {
377 
378     case PROP_EDITABLE_TEXT:
379         if (g_strcmp0 (details->editable_text,
380                         g_value_get_string (value)) == 0)
381         {
382             return;
383         }
384 
385         g_free (details->editable_text);
386         details->editable_text = g_strdup (g_value_get_string (value));
387         if (details->text_util)
388         {
389             gail_text_util_text_setup (details->text_util,
390                                        details->editable_text);
391         }
392 
393         caja_icon_canvas_item_invalidate_label_size (item);
394         if (details->editable_text_layout)
395         {
396             g_object_unref (details->editable_text_layout);
397             details->editable_text_layout = NULL;
398         }
399         break;
400 
401     case PROP_ADDITIONAL_TEXT:
402         if (g_strcmp0 (details->additional_text,
403                         g_value_get_string (value)) == 0)
404         {
405             return;
406         }
407 
408         g_free (details->additional_text);
409         details->additional_text = g_strdup (g_value_get_string (value));
410 
411         caja_icon_canvas_item_invalidate_label_size (item);
412         if (details->additional_text_layout)
413         {
414             g_object_unref (details->additional_text_layout);
415             details->additional_text_layout = NULL;
416         }
417         break;
418 
419     case PROP_HIGHLIGHTED_FOR_SELECTION:
420         if (!details->is_highlighted_for_selection == !g_value_get_boolean (value))
421         {
422             return;
423         }
424         details->is_highlighted_for_selection = g_value_get_boolean (value);
425         caja_icon_canvas_item_invalidate_label_size (item);
426 
427         atk_object_notify_state_change (accessible, ATK_STATE_SELECTED,
428                                         details->is_highlighted_for_selection);
429         break;
430 
431     case PROP_HIGHLIGHTED_AS_KEYBOARD_FOCUS:
432         if (!details->is_highlighted_as_keyboard_focus == !g_value_get_boolean (value))
433         {
434             return;
435         }
436         details->is_highlighted_as_keyboard_focus = g_value_get_boolean (value);
437         atk_object_notify_state_change (accessible, ATK_STATE_FOCUSED,
438                                         details->is_highlighted_as_keyboard_focus);
439         break;
440 
441     case PROP_HIGHLIGHTED_FOR_DROP:
442         if (!details->is_highlighted_for_drop == !g_value_get_boolean (value))
443         {
444             return;
445         }
446         details->is_highlighted_for_drop = g_value_get_boolean (value);
447         break;
448 
449     case PROP_HIGHLIGHTED_FOR_CLIPBOARD:
450         if (!details->is_highlighted_for_clipboard == !g_value_get_boolean (value))
451         {
452             return;
453         }
454         details->is_highlighted_for_clipboard = g_value_get_boolean (value);
455         break;
456 
457     default:
458         g_warning ("caja_icons_view_item_item_set_arg on unknown argument");
459         return;
460     }
461 
462     eel_canvas_item_request_update (EEL_CANVAS_ITEM (object));
463 }
464 
465 /* Get property handler for the icon item */
466 static void
caja_icon_canvas_item_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)467 caja_icon_canvas_item_get_property (GObject        *object,
468                                     guint           property_id,
469                                     GValue         *value,
470                                     GParamSpec     *pspec)
471 {
472     CajaIconCanvasItemPrivate *details;
473 
474     details = CAJA_ICON_CANVAS_ITEM (object)->details;
475 
476     switch (property_id)
477     {
478 
479     case PROP_EDITABLE_TEXT:
480         g_value_set_string (value, details->editable_text);
481         break;
482 
483     case PROP_ADDITIONAL_TEXT:
484         g_value_set_string (value, details->additional_text);
485         break;
486 
487     case PROP_HIGHLIGHTED_FOR_SELECTION:
488         g_value_set_boolean (value, details->is_highlighted_for_selection);
489         break;
490 
491     case PROP_HIGHLIGHTED_AS_KEYBOARD_FOCUS:
492         g_value_set_boolean (value, details->is_highlighted_as_keyboard_focus);
493         break;
494 
495     case PROP_HIGHLIGHTED_FOR_DROP:
496         g_value_set_boolean (value, details->is_highlighted_for_drop);
497         break;
498 
499     case PROP_HIGHLIGHTED_FOR_CLIPBOARD:
500         g_value_set_boolean (value, details->is_highlighted_for_clipboard);
501         break;
502 
503     default:
504         g_warning ("invalid property %d", property_id);
505         break;
506     }
507 }
508 
509 static void
get_scaled_icon_size(CajaIconCanvasItem * item,gint * width,gint * height)510 get_scaled_icon_size (CajaIconCanvasItem *item,
511 		      gint *width,
512 		      gint *height)
513 {
514     GdkPixbuf *pixbuf = NULL;
515     gint scale = 1;
516 
517     if (item != NULL) {
518         EelCanvas *canvas;
519 
520         canvas = EEL_CANVAS_ITEM (item)->canvas;
521         scale = gtk_widget_get_scale_factor (GTK_WIDGET (canvas));
522         pixbuf = item->details->pixbuf;
523     }
524 
525     if (width)
526         *width = (pixbuf == NULL) ? 0 : (gdk_pixbuf_get_width (pixbuf) / scale);
527     if (height)
528         *height = (pixbuf == NULL) ? 0 : (gdk_pixbuf_get_height (pixbuf) / scale);
529 }
530 
531 cairo_surface_t *
caja_icon_canvas_item_get_drag_surface(CajaIconCanvasItem * item)532 caja_icon_canvas_item_get_drag_surface (CajaIconCanvasItem *item)
533 {
534     cairo_surface_t *surface;
535 
536     EelCanvas *canvas;
537     int width, height;
538     int pix_width, pix_height;
539     int item_offset_x, item_offset_y;
540     EelIRect icon_rect;
541     EelIRect emblem_rect;
542     GdkPixbuf *emblem_pixbuf;
543     EmblemLayout emblem_layout;
544     double item_x, item_y;
545     gboolean is_rtl;
546     cairo_t *cr;
547     GtkStyleContext *context;
548     cairo_surface_t *drag_surface;
549 
550     g_return_val_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item), NULL);
551 
552     canvas = EEL_CANVAS_ITEM (item)->canvas;
553     context = gtk_widget_get_style_context (GTK_WIDGET (canvas));
554 
555     gtk_style_context_save (context);
556     gtk_style_context_add_class (context, "caja-canvas-item");
557 
558     /* Assume we're updated so canvas item data is right */
559 
560     /* Calculate the offset from the top-left corner of the
561        new image to the item position (where the pixmap is placed) */
562     eel_canvas_world_to_window (canvas,
563                                 item->details->x, item->details->y,
564                                 &item_x, &item_y);
565 
566     item_offset_x = item_x - EEL_CANVAS_ITEM (item)->x1;
567     item_offset_y = item_y - EEL_CANVAS_ITEM (item)->y1;
568 
569     /* Calculate the width of the item */
570     width = EEL_CANVAS_ITEM (item)->x2 - EEL_CANVAS_ITEM (item)->x1;
571     height = EEL_CANVAS_ITEM (item)->y2 - EEL_CANVAS_ITEM (item)->y1;
572 
573     surface = gdk_window_create_similar_surface (gtk_widget_get_window (GTK_WIDGET (canvas)),
574     						 CAIRO_CONTENT_COLOR_ALPHA,
575     						 width, height);
576 
577     cr = cairo_create (surface);
578 
579     drag_surface = gdk_cairo_surface_create_from_pixbuf (item->details->pixbuf,
580                                                          gtk_widget_get_scale_factor (GTK_WIDGET (canvas)),
581                                                          gtk_widget_get_window (GTK_WIDGET (canvas)));
582     gtk_render_icon_surface (context, cr, drag_surface,
583                              item_offset_x, item_offset_y);
584     cairo_surface_destroy (drag_surface);
585 
586     get_scaled_icon_size (item, &pix_width, &pix_height);
587 
588     icon_rect.x0 = item_offset_x;
589     icon_rect.y0 = item_offset_y;
590     icon_rect.x1 = item_offset_x + pix_width;
591     icon_rect.y1 = item_offset_y + pix_height;
592 
593     is_rtl = caja_icon_container_is_layout_rtl (CAJA_ICON_CONTAINER (canvas));
594 
595     emblem_layout_reset (&emblem_layout, item, icon_rect, is_rtl);
596 
597     while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect, is_rtl))
598     {
599         gdk_cairo_set_source_pixbuf (cr, emblem_pixbuf, emblem_rect.x0, emblem_rect.y0);
600         cairo_rectangle (cr, emblem_rect.x0, emblem_rect.y0,
601                          gdk_pixbuf_get_width (emblem_pixbuf),
602                          gdk_pixbuf_get_height (emblem_pixbuf));
603         cairo_fill (cr);
604     }
605 
606     draw_embedded_text (item, cr,
607     			item_offset_x, item_offset_y);
608     draw_label_text (item, cr, FALSE, icon_rect);
609     cairo_destroy (cr);
610 
611     gtk_style_context_restore (context);
612 
613     return surface;
614 
615 }
616 
617 void
caja_icon_canvas_item_set_image(CajaIconCanvasItem * item,GdkPixbuf * image)618 caja_icon_canvas_item_set_image (CajaIconCanvasItem *item,
619                                  GdkPixbuf *image)
620 {
621     CajaIconCanvasItemPrivate *details;
622 
623     g_return_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item));
624     g_return_if_fail (image == NULL || pixbuf_is_acceptable (image));
625 
626     details = item->details;
627     if (details->pixbuf == image)
628     {
629         return;
630     }
631 
632     if (image != NULL)
633     {
634         g_object_ref (image);
635     }
636     if (details->pixbuf != NULL)
637     {
638         g_object_unref (details->pixbuf);
639     }
640     if (details->rendered_surface != NULL)
641     {
642         cairo_surface_destroy (details->rendered_surface);
643         details->rendered_surface = NULL;
644     }
645 
646     details->pixbuf = image;
647 
648     caja_icon_canvas_item_invalidate_bounds_cache (item);
649     eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
650 }
651 
652 void
caja_icon_canvas_item_set_emblems(CajaIconCanvasItem * item,GList * emblem_pixbufs)653 caja_icon_canvas_item_set_emblems (CajaIconCanvasItem *item,
654                                    GList *emblem_pixbufs)
655 {
656     GList *p;
657 
658     g_return_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item));
659 
660     g_assert (item->details->emblem_pixbufs != emblem_pixbufs || emblem_pixbufs == NULL);
661 
662     /* The case where the emblems are identical is fairly common,
663      * so lets take the time to check for it.
664      */
665     if (eel_g_list_equal (item->details->emblem_pixbufs, emblem_pixbufs))
666     {
667         return;
668     }
669 
670     /* Check if they are acceptable. */
671     for (p = emblem_pixbufs; p != NULL; p = p->next)
672     {
673         g_return_if_fail (pixbuf_is_acceptable (p->data));
674     }
675 
676     /* Take in the new list of emblems. */
677     g_list_foreach (emblem_pixbufs, (GFunc) g_object_ref, NULL);
678     g_list_free_full (item->details->emblem_pixbufs, g_object_unref);
679     item->details->emblem_pixbufs = g_list_copy (emblem_pixbufs);
680 
681     caja_icon_canvas_item_invalidate_bounds_cache (item);
682     eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
683 }
684 
685 void
caja_icon_canvas_item_set_attach_points(CajaIconCanvasItem * item,GdkPoint * attach_points,int n_attach_points)686 caja_icon_canvas_item_set_attach_points (CajaIconCanvasItem *item,
687         GdkPoint *attach_points,
688         int n_attach_points)
689 {
690     g_free (item->details->attach_points);
691     item->details->attach_points = NULL;
692     item->details->n_attach_points = 0;
693 
694     if (attach_points != NULL && n_attach_points != 0)
695     {
696         item->details->attach_points = g_memdup (attach_points, n_attach_points * sizeof (GdkPoint));
697         item->details->n_attach_points = n_attach_points;
698     }
699 
700     caja_icon_canvas_item_invalidate_bounds_cache (item);
701 }
702 
703 void
caja_icon_canvas_item_set_embedded_text_rect(CajaIconCanvasItem * item,const GdkRectangle * text_rect)704 caja_icon_canvas_item_set_embedded_text_rect (CajaIconCanvasItem       *item,
705         const GdkRectangle           *text_rect)
706 {
707     item->details->embedded_text_rect = *text_rect;
708 
709     caja_icon_canvas_item_invalidate_bounds_cache (item);
710     eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
711 }
712 
713 void
caja_icon_canvas_item_set_embedded_text(CajaIconCanvasItem * item,const char * text)714 caja_icon_canvas_item_set_embedded_text (CajaIconCanvasItem       *item,
715         const char                   *text)
716 {
717     g_free (item->details->embedded_text);
718     item->details->embedded_text = g_strdup (text);
719 
720     if (item->details->embedded_text_layout != NULL)
721     {
722         if (text != NULL)
723         {
724             pango_layout_set_text (item->details->embedded_text_layout, text, -1);
725         }
726         else
727         {
728             pango_layout_set_text (item->details->embedded_text_layout, "", -1);
729         }
730     }
731 
732     eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
733 }
734 
735 
736 /* Recomputes the bounding box of a icon canvas item.
737  * This is a generic implementation that could be used for any canvas item
738  * class, it has no assumptions about how the item is used.
739  */
740 static void
recompute_bounding_box(CajaIconCanvasItem * icon_item,double i2w_dx,double i2w_dy)741 recompute_bounding_box (CajaIconCanvasItem *icon_item,
742                         double i2w_dx, double i2w_dy)
743 {
744     /* The bounds stored in the item is the same as what get_bounds
745      * returns, except it's in canvas coordinates instead of the item's
746      * parent's coordinates.
747      */
748 
749     EelCanvasItem *item;
750     EelDPoint top_left, bottom_right;
751 
752     item = EEL_CANVAS_ITEM (icon_item);
753 
754     eel_canvas_item_get_bounds (item,
755                                 &top_left.x, &top_left.y,
756                                 &bottom_right.x, &bottom_right.y);
757 
758     top_left.x += i2w_dx;
759     top_left.y += i2w_dy;
760     bottom_right.x += i2w_dx;
761     bottom_right.y += i2w_dy;
762     eel_canvas_w2c_d (item->canvas,
763                       top_left.x, top_left.y,
764                       &item->x1, &item->y1);
765     eel_canvas_w2c_d (item->canvas,
766                       bottom_right.x, bottom_right.y,
767                       &item->x2, &item->y2);
768 }
769 
770 static EelIRect
compute_text_rectangle(const CajaIconCanvasItem * item,EelIRect icon_rectangle,gboolean canvas_coords,CajaIconCanvasItemBoundsUsage usage)771 compute_text_rectangle (const CajaIconCanvasItem *item,
772                         EelIRect icon_rectangle,
773                         gboolean canvas_coords,
774                         CajaIconCanvasItemBoundsUsage usage)
775 {
776     EelIRect text_rectangle;
777     double pixels_per_unit;
778     double text_width, text_height, text_height_for_layout, text_height_for_entire_text, real_text_height, text_dx;
779 
780     pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
781     if (canvas_coords)
782     {
783         text_width = item->details->text_width;
784         text_height = item->details->text_height;
785         text_height_for_layout = item->details->text_height_for_layout;
786         text_height_for_entire_text = item->details->text_height_for_entire_text;
787         text_dx = item->details->text_dx;
788     }
789     else
790     {
791         text_width = item->details->text_width / pixels_per_unit;
792         text_height = item->details->text_height / pixels_per_unit;
793         text_height_for_layout = item->details->text_height_for_layout / pixels_per_unit;
794         text_height_for_entire_text = item->details->text_height_for_entire_text / pixels_per_unit;
795         text_dx = item->details->text_dx / pixels_per_unit;
796     }
797 
798     if (CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas)->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
799     {
800         if (!caja_icon_container_is_layout_rtl (CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas)))
801         {
802             text_rectangle.x0 = icon_rectangle.x1;
803             text_rectangle.x1 = text_rectangle.x0 + text_dx + text_width;
804         }
805         else
806         {
807             text_rectangle.x1 = icon_rectangle.x0;
808             text_rectangle.x0 = text_rectangle.x1 - text_dx - text_width;
809         }
810 
811         /* VOODOO-TODO */
812 #if 0
813         if (for_layout)
814         {
815             /* in this case, we should be more smart and calculate the size according to the maximum
816              * number of lines fitting next to the icon. However, this requires a more complex layout logic.
817              * It would mean that when measuring the label, the icon dimensions must be known already,
818              * and we
819              *   1. start with an unlimited layout
820              *   2. measure how many lines of this layout fit next to the icon
821              *   3. limit the number of lines to the given number of fitting lines
822              */
823             real_text_height = VOODOO();
824         }
825         else
826         {
827 #endif
828             real_text_height = text_height_for_entire_text;
829 #if 0
830         }
831 #endif
832 
833         text_rectangle.y0 = (icon_rectangle.y0 + icon_rectangle.y1) / 2- (int) real_text_height / 2;
834         text_rectangle.y1 = text_rectangle.y0 + real_text_height;
835     }
836     else
837     {
838         text_rectangle.x0 = (icon_rectangle.x0 + icon_rectangle.x1) / 2 - (int) text_width / 2;
839         text_rectangle.y0 = icon_rectangle.y1;
840         text_rectangle.x1 = text_rectangle.x0 + text_width;
841 
842         switch (usage)
843         {
844             case BOUNDS_USAGE_FOR_LAYOUT:
845                 real_text_height = text_height_for_layout;
846                 break;
847             case BOUNDS_USAGE_FOR_ENTIRE_ITEM:
848                 real_text_height = text_height_for_entire_text;
849                 break;
850             case BOUNDS_USAGE_FOR_DISPLAY:
851                 real_text_height = text_height;
852                 break;
853             default:
854                 g_assert_not_reached ();
855                 break;
856         }
857 
858         text_rectangle.y1 = text_rectangle.y0 + real_text_height + LABEL_OFFSET / pixels_per_unit;
859     }
860 
861     return text_rectangle;
862 }
863 
864 static EelIRect
get_current_canvas_bounds(EelCanvasItem * item)865 get_current_canvas_bounds (EelCanvasItem *item)
866 {
867     EelIRect bounds;
868 
869     g_assert (EEL_IS_CANVAS_ITEM (item));
870 
871     bounds.x0 = item->x1;
872     bounds.y0 = item->y1;
873     bounds.x1 = item->x2;
874     bounds.y1 = item->y2;
875 
876     return bounds;
877 }
878 
879 void
caja_icon_canvas_item_update_bounds(CajaIconCanvasItem * item,double i2w_dx,double i2w_dy)880 caja_icon_canvas_item_update_bounds (CajaIconCanvasItem *item,
881                                      double i2w_dx, double i2w_dy)
882 {
883     EelIRect before, after, emblem_rect;
884     EmblemLayout emblem_layout;
885     EelCanvasItem *canvas_item;
886     GdkPixbuf *emblem_pixbuf;
887     gboolean is_rtl;
888 
889     canvas_item = EEL_CANVAS_ITEM (item);
890 
891     /* Compute new bounds. */
892     before = get_current_canvas_bounds (canvas_item);
893     recompute_bounding_box (item, i2w_dx, i2w_dy);
894     after = get_current_canvas_bounds (canvas_item);
895 
896     /* If the bounds didn't change, we are done. */
897     if (eel_irect_equal (before, after))
898     {
899         return;
900     }
901 
902     is_rtl = caja_icon_container_is_layout_rtl (CAJA_ICON_CONTAINER (canvas_item->canvas));
903 
904     /* Update canvas and text rect cache */
905     get_icon_canvas_rectangle (item, &item->details->canvas_rect);
906     item->details->text_rect = compute_text_rectangle (item, item->details->canvas_rect,
907                                TRUE, BOUNDS_USAGE_FOR_DISPLAY);
908 
909     /* Update emblem rect cache */
910     item->details->emblem_rect.x0 = 0;
911     item->details->emblem_rect.x1 = 0;
912     item->details->emblem_rect.y0 = 0;
913     item->details->emblem_rect.y1 = 0;
914     emblem_layout_reset (&emblem_layout, item, item->details->canvas_rect, is_rtl);
915     while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect, is_rtl))
916     {
917         eel_irect_union (&item->details->emblem_rect, &item->details->emblem_rect, &emblem_rect);
918     }
919 
920     /* queue a redraw. */
921     eel_canvas_request_redraw (canvas_item->canvas,
922                                before.x0, before.y0,
923                                before.x1 + 1, before.y1 + 1);
924 }
925 
926 /* Update handler for the icon canvas item. */
927 static void
caja_icon_canvas_item_update(EelCanvasItem * item,double i2w_dx,double i2w_dy,gint flags)928 caja_icon_canvas_item_update (EelCanvasItem *item,
929                               double i2w_dx, double i2w_dy,
930                               gint flags)
931 {
932     caja_icon_canvas_item_update_bounds (CAJA_ICON_CANVAS_ITEM (item), i2w_dx, i2w_dy);
933 
934     eel_canvas_item_request_redraw (EEL_CANVAS_ITEM (item));
935 
936     EEL_CANVAS_ITEM_CLASS (caja_icon_canvas_item_parent_class)->update (item, i2w_dx, i2w_dy, flags);
937 }
938 
939 /* Rendering */
940 static gboolean
in_single_click_mode(void)941 in_single_click_mode (void)
942 {
943     return click_policy_auto_value == CAJA_CLICK_POLICY_SINGLE;
944 }
945 
946 
947 /* Keep these for a bit while we work on performance of draw_or_measure_label_text. */
948 /*
949   #define PERFORMANCE_TEST_DRAW_DISABLE
950   #define PERFORMANCE_TEST_MEASURE_DISABLE
951 */
952 
953 /* This gets the size of the layout from the position of the layout.
954  * This means that if the layout is right aligned we get the full width
955  * of the layout, not just the width of the text snippet on the right side
956  */
957 static void
layout_get_full_size(PangoLayout * layout,int * width,int * height,int * dx)958 layout_get_full_size (PangoLayout *layout,
959                       int         *width,
960                       int         *height,
961                       int         *dx)
962 {
963     PangoRectangle logical_rect;
964     int the_width, total_width;
965 
966     pango_layout_get_extents (layout, NULL, &logical_rect);
967     the_width = (logical_rect.width + PANGO_SCALE / 2) / PANGO_SCALE;
968     total_width = (logical_rect.x + logical_rect.width + PANGO_SCALE / 2) / PANGO_SCALE;
969 
970     if (width != NULL)
971     {
972         *width = the_width;
973     }
974 
975     if (height != NULL)
976     {
977         *height = (logical_rect.height + PANGO_SCALE / 2) / PANGO_SCALE;
978     }
979 
980     if (dx != NULL)
981     {
982         *dx = total_width - the_width;
983     }
984 }
985 
986 static void
layout_get_size_for_layout(PangoLayout * layout,int max_layout_line_count,int height_for_entire_text,int * height_for_layout)987 layout_get_size_for_layout (PangoLayout *layout,
988                             int          max_layout_line_count,
989                             int          height_for_entire_text,
990                             int         *height_for_layout)
991 {
992     PangoRectangle logical_rect;
993 
994     /* only use the first max_layout_line_count lines for the gridded auto layout */
995     if (pango_layout_get_line_count (layout) <= max_layout_line_count)
996     {
997         *height_for_layout = height_for_entire_text;
998     }
999     else
1000     {
1001         PangoLayoutIter *iter;
1002         int i;
1003 
1004         *height_for_layout = 0;
1005         iter = pango_layout_get_iter (layout);
1006         /* VOODOO-TODO, determine number of lines based on the icon size for text besides icon.
1007          * cf. compute_text_rectangle() */
1008         for (i = 0; i < max_layout_line_count; i++)
1009         {
1010             pango_layout_iter_get_line_extents (iter, NULL, &logical_rect);
1011             *height_for_layout += (logical_rect.height + PANGO_SCALE / 2) / PANGO_SCALE;
1012 
1013             if (!pango_layout_iter_next_line (iter))
1014             {
1015                 break;
1016             }
1017 
1018             *height_for_layout += pango_layout_get_spacing (layout);
1019         }
1020         pango_layout_iter_free (iter);
1021     }
1022 }
1023 
1024 #define IS_COMPACT_VIEW(container) \
1025         ((container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_L_R || \
1026 	  container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_R_L) && \
1027 	 container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
1028 
1029 #define TEXT_BACK_PADDING_X 4
1030 #define TEXT_BACK_PADDING_Y 1
1031 
1032 static void
prepare_pango_layout_width(CajaIconCanvasItem * item,PangoLayout * layout)1033 prepare_pango_layout_width (CajaIconCanvasItem *item,
1034                             PangoLayout *layout)
1035 {
1036     if (caja_icon_canvas_item_get_max_text_width (item) < 0)
1037     {
1038         pango_layout_set_width (layout, -1);
1039     }
1040     else
1041     {
1042         pango_layout_set_width (layout, floor (caja_icon_canvas_item_get_max_text_width (item)) * PANGO_SCALE);
1043         pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
1044     }
1045 }
1046 
1047 static void
prepare_pango_layout_for_measure_entire_text(CajaIconCanvasItem * item,PangoLayout * layout)1048 prepare_pango_layout_for_measure_entire_text (CajaIconCanvasItem *item,
1049         PangoLayout *layout)
1050 {
1051     CajaIconContainer *container;
1052 
1053     prepare_pango_layout_width (item, layout);
1054 
1055     container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
1056 
1057     if (IS_COMPACT_VIEW (container))
1058     {
1059         pango_layout_set_height (layout, -1);
1060     }
1061     else
1062     {
1063         pango_layout_set_height (layout, G_MININT);
1064     }
1065 }
1066 
1067 static void
prepare_pango_layout_for_draw(CajaIconCanvasItem * item,PangoLayout * layout)1068 prepare_pango_layout_for_draw (CajaIconCanvasItem *item,
1069                                PangoLayout *layout)
1070 {
1071     CajaIconCanvasItemPrivate *details;
1072     CajaIconContainer *container;
1073     gboolean needs_highlight;
1074 
1075     prepare_pango_layout_width (item, layout);
1076 
1077     container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
1078     details = item->details;
1079 
1080     needs_highlight = details->is_highlighted_for_selection || details->is_highlighted_for_drop;
1081 
1082     if (IS_COMPACT_VIEW (container))
1083     {
1084         pango_layout_set_height (layout, -1);
1085     }
1086     else if (needs_highlight ||
1087              details->is_highlighted_as_keyboard_focus ||
1088              details->entire_text ||
1089              container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
1090     {
1091         /* VOODOO-TODO, cf. compute_text_rectangle() */
1092         pango_layout_set_height (layout, G_MININT);
1093     }
1094     else
1095     {
1096         /* TODO? we might save some resources, when the re-layout is not neccessary in case
1097          * the layout height already fits into max. layout lines. But pango should figure this
1098          * out itself (which it doesn't ATM).
1099          */
1100         pango_layout_set_height (layout,
1101                                  caja_icon_container_get_max_layout_lines_for_pango (container));
1102     }
1103 }
1104 
1105 static void
measure_label_text(CajaIconCanvasItem * item)1106 measure_label_text (CajaIconCanvasItem *item)
1107 {
1108     CajaIconCanvasItemPrivate *details;
1109     CajaIconContainer *container;
1110     gint editable_height, editable_height_for_layout, editable_height_for_entire_text, editable_width, editable_dx;
1111     gint additional_height, additional_width, additional_dx;
1112     PangoLayout *editable_layout;
1113     PangoLayout *additional_layout;
1114     gboolean have_editable, have_additional;
1115 
1116     /* check to see if the cached values are still valid; if so, there's
1117      * no work necessary
1118      */
1119 
1120     if (item->details->text_width >= 0 && item->details->text_height >= 0)
1121     {
1122         return;
1123     }
1124 
1125     details = item->details;
1126 
1127     have_editable = details->editable_text != NULL && details->editable_text[0] != '\0';
1128     have_additional = details->additional_text != NULL && details->additional_text[0] != '\0';
1129 
1130     /* No font or no text, then do no work. */
1131     if (!have_editable && !have_additional)
1132     {
1133         details->text_height = 0;
1134         details->text_height_for_layout = 0;
1135         details->text_height_for_entire_text = 0;
1136         details->text_width = 0;
1137         return;
1138     }
1139 
1140 #ifdef PERFORMANCE_TEST_MEASURE_DISABLE
1141     /* fake out the width */
1142     details->text_width = 80;
1143     details->text_height = 20;
1144     details->text_height_for_layout = 20;
1145     details->text_height_for_entire_text = 20;
1146     return;
1147 #endif
1148 
1149     editable_width = 0;
1150     editable_height = 0;
1151     editable_height_for_layout = 0;
1152     editable_height_for_entire_text = 0;
1153     editable_dx = 0;
1154     additional_width = 0;
1155     additional_height = 0;
1156     additional_dx = 0;
1157 
1158     container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
1159     editable_layout = NULL;
1160     additional_layout = NULL;
1161 
1162     if (have_editable)
1163     {
1164         /* first, measure required text height: editable_height_for_entire_text
1165          * then, measure text height applicable for layout: editable_height_for_layout
1166          * next, measure actually displayed height: editable_height
1167          */
1168         editable_layout = get_label_layout (&details->editable_text_layout, item, details->editable_text);
1169 
1170         prepare_pango_layout_for_measure_entire_text (item, editable_layout);
1171         layout_get_full_size (editable_layout,
1172                               NULL,
1173                               &editable_height_for_entire_text,
1174                               NULL);
1175         layout_get_size_for_layout (editable_layout,
1176                                     caja_icon_container_get_max_layout_lines (container),
1177                                     editable_height_for_entire_text,
1178                                     &editable_height_for_layout);
1179 
1180         prepare_pango_layout_for_draw (item, editable_layout);
1181         layout_get_full_size (editable_layout,
1182                               &editable_width,
1183                               &editable_height,
1184                               &editable_dx);
1185     }
1186 
1187     if (have_additional)
1188     {
1189         additional_layout = get_label_layout (&details->additional_text_layout, item, details->additional_text);
1190         prepare_pango_layout_for_draw (item, additional_layout);
1191         layout_get_full_size (additional_layout,
1192                               &additional_width, &additional_height, &additional_dx);
1193     }
1194 
1195     details->editable_text_height = editable_height;
1196 
1197     if (editable_width > additional_width)
1198     {
1199         details->text_width = editable_width;
1200         details->text_dx = editable_dx;
1201     }
1202     else
1203     {
1204         details->text_width = additional_width;
1205         details->text_dx = additional_dx;
1206     }
1207 
1208     if (have_additional)
1209     {
1210         details->text_height = editable_height + LABEL_LINE_SPACING + additional_height;
1211         details->text_height_for_layout = editable_height_for_layout + LABEL_LINE_SPACING + additional_height;
1212         details->text_height_for_entire_text = editable_height_for_entire_text + LABEL_LINE_SPACING + additional_height;
1213     }
1214     else
1215     {
1216         details->text_height = editable_height;
1217         details->text_height_for_layout = editable_height_for_layout;
1218         details->text_height_for_entire_text = editable_height_for_entire_text;
1219     }
1220 
1221     /* add some extra space for highlighting even when we don't highlight so things won't move */
1222 
1223     /* extra slop for nicer highlighting */
1224     details->text_height += TEXT_BACK_PADDING_Y*2;
1225     details->text_height_for_layout += TEXT_BACK_PADDING_Y*2;
1226     details->text_height_for_entire_text += TEXT_BACK_PADDING_Y*2;
1227     details->editable_text_height += TEXT_BACK_PADDING_Y*2;
1228 
1229     /* extra to make it look nicer */
1230     details->text_width += TEXT_BACK_PADDING_X*2;
1231 
1232     if (editable_layout)
1233     {
1234         g_object_unref (editable_layout);
1235     }
1236 
1237     if (additional_layout)
1238     {
1239         g_object_unref (additional_layout);
1240     }
1241 }
1242 
1243 static void
draw_label_text(CajaIconCanvasItem * item,cairo_t * cr,gboolean create_mask,EelIRect icon_rect)1244 draw_label_text (CajaIconCanvasItem *item,
1245                  cairo_t *cr,
1246                  gboolean create_mask,
1247                  EelIRect icon_rect)
1248 {
1249     CajaIconCanvasItemPrivate *details;
1250     CajaIconContainer *container;
1251     PangoLayout *editable_layout;
1252     PangoLayout *additional_layout;
1253     GtkStyleContext *context;
1254     GtkStateFlags state, base_state;
1255     gboolean have_editable, have_additional;
1256     gboolean needs_highlight, prelight_label, is_rtl_label_beside;
1257     EelIRect text_rect;
1258     int x;
1259     int max_text_width;
1260     gdouble frame_w, frame_h, frame_x, frame_y;
1261     gboolean draw_frame = TRUE;
1262 
1263 #ifdef PERFORMANCE_TEST_DRAW_DISABLE
1264     return;
1265 #endif
1266 
1267     details = item->details;
1268 
1269     measure_label_text (item);
1270     if (details->text_height == 0 ||
1271             details->text_width == 0)
1272     {
1273         return;
1274     }
1275 
1276     container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
1277     context = gtk_widget_get_style_context (GTK_WIDGET (container));
1278 
1279     text_rect = compute_text_rectangle (item, icon_rect, TRUE, BOUNDS_USAGE_FOR_DISPLAY);
1280 
1281     needs_highlight = details->is_highlighted_for_selection || details->is_highlighted_for_drop;
1282     is_rtl_label_beside = caja_icon_container_is_layout_rtl (container) &&
1283                           container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE;
1284 
1285     editable_layout = NULL;
1286     additional_layout = NULL;
1287 
1288     have_editable = details->editable_text != NULL && details->editable_text[0] != '\0';
1289     have_additional = details->additional_text != NULL && details->additional_text[0] != '\0';
1290     g_assert (have_editable || have_additional);
1291 
1292     max_text_width = floor (caja_icon_canvas_item_get_max_text_width (item));
1293 
1294     base_state = gtk_widget_get_state_flags (GTK_WIDGET (container));
1295     base_state &= ~(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_PRELIGHT);
1296     state = base_state;
1297 
1298     gtk_widget_style_get (GTK_WIDGET (container),
1299                           "activate_prelight_icon_label", &prelight_label,
1300                           NULL);
1301 
1302     /* if the icon needs a background, do some set-up */
1303     if (!needs_highlight && have_editable &&
1304         details->text_width > 0 && details->text_height > 0 &&
1305         prelight_label && item->details->is_prelit) {
1306             state |= GTK_STATE_FLAG_PRELIGHT;
1307 
1308             frame_x = text_rect.x0;
1309             frame_y = text_rect.y0;
1310             frame_w = text_rect.x1 - text_rect.x0;
1311             frame_h = text_rect.y1 - text_rect.y0;
1312     } else if (!details->is_renaming) {
1313             /* always draw a background but when renaming where the editing
1314              * area is on top already. The default background will be transparent,
1315              * but drawing it already allows the theme to change that. */
1316 
1317             if (needs_highlight)
1318                 state |= GTK_STATE_FLAG_SELECTED;
1319 
1320             frame_x = is_rtl_label_beside ? text_rect.x0 + item->details->text_dx : text_rect.x0;
1321             frame_y = text_rect.y0;
1322             frame_w = is_rtl_label_beside ? text_rect.x1 - text_rect.x0 - item->details->text_dx : text_rect.x1 - text_rect.x0;
1323             frame_h = text_rect.y1 - text_rect.y0;
1324     } else {
1325             draw_frame = FALSE;
1326     }
1327 
1328     if (draw_frame) {
1329             gtk_style_context_save (context);
1330             gtk_style_context_set_state (context, state);
1331 
1332             gtk_render_frame (context, cr,
1333                               frame_x, frame_y,
1334                               frame_w, frame_h);
1335             gtk_render_background (context, cr,
1336                                    frame_x, frame_y,
1337                                    frame_w, frame_h);
1338 
1339             gtk_style_context_restore (context);
1340     }
1341 
1342     if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
1343     {
1344         x = text_rect.x0 + 2;
1345     }
1346     else
1347     {
1348         x = text_rect.x0 + ((text_rect.x1 - text_rect.x0) - max_text_width) / 2;
1349     }
1350 
1351     if (have_editable &&
1352         !details->is_renaming)
1353     {
1354         state = base_state;
1355 
1356         if (prelight_label && item->details->is_prelit) {
1357                 state |= GTK_STATE_FLAG_PRELIGHT;
1358         }
1359 
1360         if (needs_highlight) {
1361                 state |= GTK_STATE_FLAG_SELECTED;
1362         }
1363 
1364         editable_layout = get_label_layout (&item->details->editable_text_layout, item, item->details->editable_text);
1365         prepare_pango_layout_for_draw (item, editable_layout);
1366 
1367         gtk_style_context_save (context);
1368         gtk_style_context_set_state (context, state);
1369 
1370         gtk_render_layout (context, cr,
1371                            x, text_rect.y0 + TEXT_BACK_PADDING_Y,
1372                            editable_layout);
1373 
1374         gtk_style_context_restore (context);
1375     }
1376 
1377     if (have_additional &&
1378         !details->is_renaming)
1379     {
1380         state = base_state;
1381 
1382         if (needs_highlight) {
1383                 state |= GTK_STATE_FLAG_SELECTED;
1384         }
1385 
1386         additional_layout = get_label_layout (&item->details->additional_text_layout, item, item->details->additional_text);
1387         prepare_pango_layout_for_draw (item, additional_layout);
1388 
1389         gtk_style_context_save (context);
1390         gtk_style_context_set_state (context, state);
1391         gtk_style_context_add_class (context, "dim-label");
1392 
1393         gtk_render_layout (context, cr,
1394                            x, text_rect.y0 + details->editable_text_height + LABEL_LINE_SPACING + TEXT_BACK_PADDING_Y,
1395                            additional_layout);
1396 
1397         gtk_style_context_restore (context);
1398     }
1399 
1400     if (!create_mask && item->details->is_highlighted_as_keyboard_focus)
1401     {
1402         if (needs_highlight) {
1403                 state = GTK_STATE_FLAG_SELECTED;
1404         }
1405 
1406         gtk_style_context_save (context);
1407         gtk_style_context_set_state (context, state);
1408 
1409         gtk_render_focus (context,
1410                           cr,
1411                          text_rect.x0,
1412                          text_rect.y0,
1413                          text_rect.x1 - text_rect.x0,
1414                          text_rect.y1 - text_rect.y0);
1415 
1416         gtk_style_context_restore (context);
1417     }
1418 
1419     if (editable_layout != NULL)
1420     {
1421         g_object_unref (editable_layout);
1422     }
1423 
1424     if (additional_layout != NULL)
1425     {
1426         g_object_unref (additional_layout);
1427     }
1428 }
1429 
1430 void
caja_icon_canvas_item_set_is_visible(CajaIconCanvasItem * item,gboolean visible)1431 caja_icon_canvas_item_set_is_visible (CajaIconCanvasItem       *item,
1432                                       gboolean                      visible)
1433 {
1434     if (item->details->is_visible == visible)
1435         return;
1436 
1437     item->details->is_visible = visible;
1438 
1439     if (!visible)
1440     {
1441         caja_icon_canvas_item_invalidate_label (item);
1442     }
1443 }
1444 
1445 void
caja_icon_canvas_item_invalidate_label(CajaIconCanvasItem * item)1446 caja_icon_canvas_item_invalidate_label (CajaIconCanvasItem     *item)
1447 {
1448     caja_icon_canvas_item_invalidate_label_size (item);
1449 
1450     if (item->details->editable_text_layout)
1451     {
1452         g_object_unref (item->details->editable_text_layout);
1453         item->details->editable_text_layout = NULL;
1454     }
1455 
1456     if (item->details->additional_text_layout)
1457     {
1458         g_object_unref (item->details->additional_text_layout);
1459         item->details->additional_text_layout = NULL;
1460     }
1461 
1462     if (item->details->embedded_text_layout)
1463     {
1464         g_object_unref (item->details->embedded_text_layout);
1465         item->details->embedded_text_layout = NULL;
1466     }
1467 }
1468 
1469 
1470 static GdkPixbuf *
get_knob_pixbuf(void)1471 get_knob_pixbuf (void)
1472 {
1473     GdkPixbuf *knob_pixbuf;
1474 
1475     knob_pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
1476                                             "stock-caja-knob",
1477                                             8, 0, NULL);
1478     if (!knob_pixbuf)
1479     {
1480         char *knob_filename;
1481 
1482         knob_filename = caja_pixmap_file ("knob.png");
1483         knob_pixbuf = gdk_pixbuf_new_from_file (knob_filename, NULL);
1484         g_free (knob_filename);
1485     }
1486 
1487     return knob_pixbuf;
1488 }
1489 
1490 static void
draw_stretch_handles(CajaIconCanvasItem * item,cairo_t * cr,const EelIRect * rect)1491 draw_stretch_handles (CajaIconCanvasItem *item,
1492                       cairo_t *cr,
1493                       const EelIRect *rect)
1494 {
1495     GtkWidget *widget;
1496     GdkPixbuf *knob_pixbuf;
1497     int knob_width, knob_height;
1498     double dash = { 2.0 };
1499     GtkStyleContext *style;
1500     GdkRGBA color;
1501 
1502     if (!item->details->show_stretch_handles)
1503     {
1504         return;
1505     }
1506 
1507     widget = GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas);
1508     style = gtk_widget_get_style_context (widget);
1509 
1510     cairo_save (cr);
1511 
1512     knob_pixbuf = get_knob_pixbuf ();
1513     knob_width = gdk_pixbuf_get_width (knob_pixbuf);
1514     knob_height = gdk_pixbuf_get_height (knob_pixbuf);
1515 
1516     /* first draw the box */
1517     gtk_style_context_get_color (style, GTK_STATE_FLAG_SELECTED, &color);
1518     gdk_cairo_set_source_rgba (cr, &color);
1519 
1520     cairo_set_dash (cr, &dash, 1, 0);
1521     cairo_set_line_width (cr, 1.0);
1522     cairo_rectangle (cr,
1523              rect->x0 + 0.5,
1524              rect->y0 + 0.5,
1525              rect->x1 - rect->x0 - 1,
1526              rect->y1 - rect->y0 - 1);
1527     cairo_stroke (cr);
1528 
1529     cairo_restore (cr);
1530 
1531     /* draw the stretch handles themselves */
1532     draw_pixbuf (knob_pixbuf, cr, rect->x0, rect->y0);
1533     draw_pixbuf (knob_pixbuf, cr, rect->x0, rect->y1 - knob_height);
1534     draw_pixbuf (knob_pixbuf, cr, rect->x1 - knob_width, rect->y0);
1535     draw_pixbuf (knob_pixbuf, cr, rect->x1 - knob_width, rect->y1 - knob_height);
1536 
1537     g_object_unref (knob_pixbuf);
1538 }
1539 
1540 static void
emblem_layout_reset(EmblemLayout * layout,CajaIconCanvasItem * icon_item,EelIRect icon_rect,gboolean is_rtl)1541 emblem_layout_reset (EmblemLayout *layout, CajaIconCanvasItem *icon_item, EelIRect icon_rect, gboolean is_rtl)
1542 {
1543     layout->icon_item = icon_item;
1544     layout->icon_rect = icon_rect;
1545     layout->side = is_rtl ? LEFT_SIDE : RIGHT_SIDE;
1546     layout->position = 0;
1547     layout->index = 0;
1548     layout->emblem = icon_item->details->emblem_pixbufs;
1549 }
1550 
1551 static gboolean
emblem_layout_next(EmblemLayout * layout,GdkPixbuf ** emblem_pixbuf,EelIRect * emblem_rect,gboolean is_rtl)1552 emblem_layout_next (EmblemLayout *layout,
1553                     GdkPixbuf **emblem_pixbuf,
1554                     EelIRect *emblem_rect,
1555                     gboolean is_rtl)
1556 {
1557     GdkPixbuf *pixbuf;
1558     int width, height, x, y;
1559     GdkPoint *attach_points;
1560 
1561     /* Check if we have layed out all of the pixbufs. */
1562     if (layout->emblem == NULL)
1563     {
1564         return FALSE;
1565     }
1566 
1567     /* Get the pixbuf. */
1568     pixbuf = layout->emblem->data;
1569     width = gdk_pixbuf_get_width (pixbuf);
1570     height = gdk_pixbuf_get_height (pixbuf);
1571 
1572 
1573     /* Advance to the next emblem. */
1574     layout->emblem = layout->emblem->next;
1575 
1576     attach_points = layout->icon_item->details->attach_points;
1577     if (attach_points != NULL)
1578     {
1579         if (layout->index >= layout->icon_item->details->n_attach_points)
1580         {
1581             return FALSE;
1582         }
1583 
1584         x = layout->icon_rect.x0 + attach_points[layout->index].x;
1585         y = layout->icon_rect.y0 + attach_points[layout->index].y;
1586 
1587         layout->index += 1;
1588 
1589         /* Return the rectangle and pixbuf. */
1590         *emblem_pixbuf = pixbuf;
1591         emblem_rect->x0 = x - width / 2;
1592         emblem_rect->y0 = y - height / 2;
1593         emblem_rect->x1 = emblem_rect->x0 + width;
1594         emblem_rect->y1 = emblem_rect->y0 + height;
1595 
1596         return TRUE;
1597 
1598     }
1599 
1600     for (;;)
1601     {
1602 
1603         /* Find the side to lay out along. */
1604         switch (layout->side)
1605         {
1606         case RIGHT_SIDE:
1607             x = layout->icon_rect.x1;
1608             y = is_rtl ? layout->icon_rect.y1 : layout->icon_rect.y0;
1609             break;
1610         case BOTTOM_SIDE:
1611             x = is_rtl ? layout->icon_rect.x0 : layout->icon_rect.x1;
1612             y = layout->icon_rect.y1;
1613             break;
1614         case LEFT_SIDE:
1615             x = layout->icon_rect.x0;
1616             y = is_rtl ? layout->icon_rect.y0 : layout->icon_rect.y1;
1617             break;
1618         case TOP_SIDE:
1619             x = is_rtl ? layout->icon_rect.x1 : layout->icon_rect.x0;
1620             y = layout->icon_rect.y0;
1621             break;
1622         default:
1623             g_assert_not_reached ();
1624             x = 0;
1625             y = 0;
1626             break;
1627         }
1628         if (layout->position != 0)
1629         {
1630             switch (layout->side)
1631             {
1632             case RIGHT_SIDE:
1633                 y += (is_rtl ? -1 : 1) * (layout->position + height / 2);
1634                 break;
1635             case BOTTOM_SIDE:
1636                 x += (is_rtl ? 1 : -1 ) * (layout->position + width / 2);
1637                 break;
1638             case LEFT_SIDE:
1639                 y += (is_rtl ? 1 : -1) * (layout->position + height / 2);
1640                 break;
1641             case TOP_SIDE:
1642                 x += (is_rtl ? -1 : 1) * (layout->position + width / 2);
1643                 break;
1644             }
1645         }
1646 
1647         /* Check to see if emblem fits in current side. */
1648         if (x >= layout->icon_rect.x0 && x <= layout->icon_rect.x1
1649                 && y >= layout->icon_rect.y0 && y <= layout->icon_rect.y1)
1650         {
1651 
1652             /* It fits. */
1653 
1654             /* Advance along the side. */
1655             switch (layout->side)
1656             {
1657             case RIGHT_SIDE:
1658             case LEFT_SIDE:
1659                 layout->position += height + EMBLEM_SPACING;
1660                 break;
1661             case BOTTOM_SIDE:
1662             case TOP_SIDE:
1663                 layout->position += width + EMBLEM_SPACING;
1664                 break;
1665             }
1666 
1667             /* Return the rectangle and pixbuf. */
1668             *emblem_pixbuf = pixbuf;
1669             emblem_rect->x0 = x - width / 2;
1670             emblem_rect->y0 = y - height / 2;
1671             emblem_rect->x1 = emblem_rect->x0 + width;
1672             emblem_rect->y1 = emblem_rect->y0 + height;
1673 
1674             return TRUE;
1675         }
1676 
1677         /* It doesn't fit, so move to the next side. */
1678         switch (layout->side)
1679         {
1680         case RIGHT_SIDE:
1681             layout->side = is_rtl ? TOP_SIDE : BOTTOM_SIDE;
1682             break;
1683         case BOTTOM_SIDE:
1684             layout->side = is_rtl ? RIGHT_SIDE : LEFT_SIDE;
1685             break;
1686         case LEFT_SIDE:
1687             layout->side = is_rtl ? BOTTOM_SIDE : TOP_SIDE;
1688             break;
1689         case TOP_SIDE:
1690         default:
1691             return FALSE;
1692         }
1693         layout->position = 0;
1694     }
1695 }
1696 
1697 static void
draw_pixbuf(GdkPixbuf * pixbuf,cairo_t * cr,int x,int y)1698 draw_pixbuf (GdkPixbuf *pixbuf,
1699              cairo_t *cr,
1700              int x, int y)
1701 {
1702     cairo_save (cr);
1703     gdk_cairo_set_source_pixbuf (cr, pixbuf, x, y);
1704     cairo_paint (cr);
1705     cairo_restore (cr);
1706 }
1707 
1708 /* shared code to highlight or dim the passed-in pixbuf */
1709 static cairo_surface_t *
real_map_surface(CajaIconCanvasItem * icon_item)1710 real_map_surface (CajaIconCanvasItem *icon_item)
1711 {
1712     EelCanvas *canvas;
1713     GdkPixbuf *temp_pixbuf, *old_pixbuf;
1714     GdkRGBA color;
1715     GdkRGBA *c;
1716     cairo_surface_t *surface;
1717 
1718     temp_pixbuf = icon_item->details->pixbuf;
1719     canvas = EEL_CANVAS_ITEM(icon_item)->canvas;
1720 
1721     g_object_ref (temp_pixbuf);
1722 
1723     if (icon_item->details->is_prelit ||
1724             icon_item->details->is_highlighted_for_clipboard)
1725     {
1726         old_pixbuf = temp_pixbuf;
1727 
1728         temp_pixbuf = eel_create_spotlight_pixbuf (temp_pixbuf);
1729         g_object_unref (old_pixbuf);
1730 
1731         /* FIXME bugzilla.gnome.org 42471: This hard-wired image is inappropriate to
1732          * this level of code, which shouldn't know that the
1733          * preview is audio, nor should it have an icon
1734          * hard-wired in.
1735          */
1736 
1737         /* if the icon is currently being previewed, superimpose an image to indicate that */
1738         /* audio is the only kind of previewing right now, so this code isn't as general as it could be */
1739         if (icon_item->details->is_active)
1740         {
1741             char *audio_filename;
1742             GdkPixbuf *audio_pixbuf;
1743             int emblem_size;
1744 
1745             emblem_size = caja_icon_get_emblem_size_for_icon_size (gdk_pixbuf_get_width (temp_pixbuf));
1746             /* Load the audio symbol. */
1747             audio_filename = caja_pixmap_file ("audio.svg");
1748             if (audio_filename != NULL)
1749             {
1750                 audio_pixbuf = gdk_pixbuf_new_from_file_at_scale (audio_filename,
1751                                emblem_size, emblem_size,
1752                                TRUE,
1753                                NULL);
1754             }
1755             else
1756             {
1757                 audio_pixbuf = NULL;
1758             }
1759 
1760             /* Composite it onto the icon. */
1761             if (audio_pixbuf != NULL)
1762             {
1763                 gdk_pixbuf_composite
1764                 (audio_pixbuf,
1765                  temp_pixbuf,
1766                  0, 0,
1767                  gdk_pixbuf_get_width (audio_pixbuf),
1768                  gdk_pixbuf_get_height (audio_pixbuf),
1769                  0, 0,
1770                  1.0, 1.0,
1771                  GDK_INTERP_BILINEAR, 0xFF);
1772 
1773                 g_object_unref (audio_pixbuf);
1774             }
1775 
1776             g_free (audio_filename);
1777         }
1778     }
1779 
1780     if (icon_item->details->is_highlighted_for_selection
1781             || icon_item->details->is_highlighted_for_drop)
1782     {
1783         GtkStyleContext *style;
1784 
1785         style = gtk_widget_get_style_context (GTK_WIDGET (canvas));
1786 
1787         if (gtk_widget_has_focus (GTK_WIDGET (canvas))) {
1788                 gtk_style_context_get (style, GTK_STATE_FLAG_SELECTED,
1789                                        GTK_STYLE_PROPERTY_BACKGROUND_COLOR,
1790                                        &c, NULL);
1791         } else {
1792                 gtk_style_context_get (style, GTK_STATE_FLAG_ACTIVE,
1793                                        GTK_STYLE_PROPERTY_BACKGROUND_COLOR,
1794                                        &c, NULL);
1795         }
1796 
1797         color = *c;
1798         gdk_rgba_free (c);
1799 
1800         old_pixbuf = temp_pixbuf;
1801         temp_pixbuf = eel_create_colorized_pixbuf (temp_pixbuf, &color);
1802 
1803         g_object_unref (old_pixbuf);
1804     }
1805 
1806     surface = gdk_cairo_surface_create_from_pixbuf (temp_pixbuf,
1807                                                     gtk_widget_get_scale_factor (GTK_WIDGET (canvas)),
1808                                                     gtk_widget_get_window (GTK_WIDGET (canvas)));
1809     g_object_unref (temp_pixbuf);
1810 
1811     return surface;
1812 }
1813 
1814 static cairo_surface_t *
map_surface(CajaIconCanvasItem * icon_item)1815 map_surface (CajaIconCanvasItem *icon_item)
1816 {
1817     if (!(icon_item->details->rendered_surface != NULL
1818             && icon_item->details->rendered_is_active == icon_item->details->is_active
1819             && icon_item->details->rendered_is_prelit == icon_item->details->is_prelit
1820             && icon_item->details->rendered_is_highlighted_for_selection == icon_item->details->is_highlighted_for_selection
1821             && icon_item->details->rendered_is_highlighted_for_drop == icon_item->details->is_highlighted_for_drop
1822             && icon_item->details->rendered_is_highlighted_for_clipboard == icon_item->details->is_highlighted_for_clipboard
1823             && (icon_item->details->is_highlighted_for_selection && icon_item->details->rendered_is_focused == gtk_widget_has_focus (GTK_WIDGET (EEL_CANVAS_ITEM (icon_item)->canvas)))))
1824     {
1825         if (icon_item->details->rendered_surface != NULL)
1826         {
1827             cairo_surface_destroy (icon_item->details->rendered_surface);
1828         }
1829         icon_item->details->rendered_surface = real_map_surface (icon_item);
1830         icon_item->details->rendered_is_active = icon_item->details->is_active;
1831         icon_item->details->rendered_is_prelit = icon_item->details->is_prelit;
1832         icon_item->details->rendered_is_highlighted_for_selection = icon_item->details->is_highlighted_for_selection;
1833         icon_item->details->rendered_is_highlighted_for_drop = icon_item->details->is_highlighted_for_drop;
1834         icon_item->details->rendered_is_highlighted_for_clipboard = icon_item->details->is_highlighted_for_clipboard;
1835         icon_item->details->rendered_is_focused = gtk_widget_has_focus (GTK_WIDGET (EEL_CANVAS_ITEM (icon_item)->canvas));
1836     }
1837 
1838     cairo_surface_reference (icon_item->details->rendered_surface);
1839 
1840     return icon_item->details->rendered_surface;
1841 }
1842 
1843 static void
draw_embedded_text(CajaIconCanvasItem * item,cairo_t * cr,int x,int y)1844 draw_embedded_text (CajaIconCanvasItem *item,
1845                     cairo_t *cr,
1846                     int x, int y)
1847 {
1848     PangoLayout *layout;
1849     GtkWidget *widget;
1850     GtkStyleContext *style_context;
1851 
1852     if (item->details->embedded_text == NULL ||
1853             item->details->embedded_text_rect.width == 0 ||
1854             item->details->embedded_text_rect.height == 0)
1855     {
1856         return;
1857     }
1858 
1859     widget = GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas);
1860 
1861     if (item->details->embedded_text_layout != NULL)
1862     {
1863         layout = g_object_ref (item->details->embedded_text_layout);
1864     }
1865     else
1866     {
1867         PangoContext *context;
1868         PangoFontDescription *desc;
1869 
1870         context = gtk_widget_get_pango_context (widget);
1871         layout = pango_layout_new (context);
1872         pango_layout_set_text (layout, item->details->embedded_text, -1);
1873 
1874         desc = pango_font_description_from_string ("monospace 6");
1875         pango_layout_set_font_description (layout, desc);
1876         pango_font_description_free (desc);
1877 
1878         if (item->details->is_visible)
1879         {
1880             item->details->embedded_text_layout = g_object_ref (layout);
1881         }
1882     }
1883 
1884     style_context = gtk_widget_get_style_context (widget);
1885     gtk_style_context_save (style_context);
1886     gtk_style_context_add_class (style_context, "icon-embedded-text");
1887 
1888     cairo_save (cr);
1889 
1890     cairo_rectangle (cr,
1891                      x + item->details->embedded_text_rect.x,
1892                      y + item->details->embedded_text_rect.y,
1893                      item->details->embedded_text_rect.width,
1894                      item->details->embedded_text_rect.height);
1895     cairo_clip (cr);
1896 
1897     gtk_render_layout (style_context, cr,
1898                        x + item->details->embedded_text_rect.x,
1899                        y + item->details->embedded_text_rect.y,
1900                        layout);
1901 
1902     gtk_style_context_restore (style_context);
1903     cairo_restore (cr);
1904 }
1905 
1906 /* Draw the icon item for non-anti-aliased mode. */
1907 static void
caja_icon_canvas_item_draw(EelCanvasItem * item,cairo_t * cr,cairo_region_t * region)1908 caja_icon_canvas_item_draw (EelCanvasItem *item,
1909                             cairo_t *cr,
1910                             cairo_region_t *region)
1911 {
1912     CajaIconContainer *container;
1913     CajaIconCanvasItem *icon_item;
1914     CajaIconCanvasItemPrivate *details;
1915     EelIRect icon_rect, emblem_rect;
1916     EmblemLayout emblem_layout;
1917     GdkPixbuf *emblem_pixbuf;
1918     cairo_surface_t *temp_surface;
1919     GtkStyleContext *context;
1920 
1921     container = CAJA_ICON_CONTAINER (item->canvas);
1922     gboolean is_rtl;
1923 
1924     icon_item = CAJA_ICON_CANVAS_ITEM (item);
1925     details = icon_item->details;
1926 
1927     /* Draw the pixbuf. */
1928     if (details->pixbuf == NULL)
1929     {
1930         return;
1931     }
1932 
1933     context = gtk_widget_get_style_context (GTK_WIDGET (container));
1934     gtk_style_context_save (context);
1935     gtk_style_context_add_class (context, "caja-canvas-item");
1936 
1937     icon_rect = icon_item->details->canvas_rect;
1938 
1939     temp_surface = map_surface (icon_item);
1940 
1941     gtk_render_icon_surface (context, cr,
1942                              temp_surface,
1943                              icon_rect.x0, icon_rect.y0);
1944     cairo_surface_destroy (temp_surface);
1945 
1946     draw_embedded_text (icon_item, cr, icon_rect.x0, icon_rect.y0);
1947 
1948     is_rtl = caja_icon_container_is_layout_rtl (CAJA_ICON_CONTAINER (item->canvas));
1949 
1950     /* Draw the emblem pixbufs. */
1951     emblem_layout_reset (&emblem_layout, icon_item, icon_rect, is_rtl);
1952     while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect, is_rtl))
1953     {
1954         draw_pixbuf (emblem_pixbuf, cr, emblem_rect.x0, emblem_rect.y0);
1955     }
1956 
1957     /* Draw stretching handles (if necessary). */
1958     draw_stretch_handles (icon_item, cr, &icon_rect);
1959 
1960     /* Draw the label text. */
1961     draw_label_text (icon_item, cr, FALSE, icon_rect);
1962 
1963     gtk_style_context_restore (context);
1964 }
1965 
1966 #define ZERO_WIDTH_SPACE "\xE2\x80\x8B"
1967 
1968 #define ZERO_OR_THREE_DIGITS(p) \
1969 	(!g_ascii_isdigit (*p) || \
1970 	 (g_ascii_isdigit (*(p+1)) && \
1971 	  g_ascii_isdigit (*(p+2))))
1972 
1973 
1974 static PangoLayout *
create_label_layout(CajaIconCanvasItem * item,const char * text)1975 create_label_layout (CajaIconCanvasItem *item,
1976                      const char *text)
1977 {
1978     PangoLayout *layout;
1979     PangoContext *context;
1980     PangoFontDescription *desc;
1981     CajaIconContainer *container;
1982     EelCanvasItem *canvas_item;
1983     char *zeroified_text;
1984     #if PANGO_CHECK_VERSION (1, 44, 0)
1985     PangoAttrList *attr_list;
1986     #endif
1987 
1988     canvas_item = EEL_CANVAS_ITEM (item);
1989 
1990     container = CAJA_ICON_CONTAINER (canvas_item->canvas);
1991     context = gtk_widget_get_pango_context (GTK_WIDGET (canvas_item->canvas));
1992     layout = pango_layout_new (context);
1993     #if PANGO_CHECK_VERSION (1, 44, 0)
1994     attr_list = pango_attr_list_new ();
1995     #endif
1996 
1997     zeroified_text = NULL;
1998 
1999     if (text != NULL)
2000     {
2001         GString *str;
2002         const char *p;
2003 
2004         str = g_string_new (NULL);
2005 
2006         for (p = text; *p != '\0'; p++)
2007         {
2008             str = g_string_append_c (str, *p);
2009 
2010             if (*p == '_' || *p == '-' || (*p == '.' && ZERO_OR_THREE_DIGITS (p+1)))
2011             {
2012                 /* Ensure that we allow to break after '_' or '.' characters,
2013                  * if they are not likely to be part of a version information, to
2014                  * not break wrapping of foobar-0.0.1.
2015                  * Wrap before IPs and long numbers, though. */
2016                 str = g_string_append (str, ZERO_WIDTH_SPACE);
2017             }
2018         }
2019 
2020         zeroified_text = g_string_free (str, FALSE);
2021     }
2022 
2023     pango_layout_set_text (layout, zeroified_text, -1);
2024     pango_layout_set_auto_dir (layout, FALSE);
2025 
2026     if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
2027     {
2028         if (!caja_icon_container_is_layout_rtl (container))
2029         {
2030             pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
2031         }
2032         else
2033         {
2034             pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
2035         }
2036     }
2037     else
2038     {
2039         pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
2040     }
2041 
2042     pango_layout_set_spacing (layout, LABEL_LINE_SPACING);
2043     pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
2044 
2045     #if PANGO_CHECK_VERSION (1, 44, 0)
2046     pango_attr_list_insert (attr_list, pango_attr_insert_hyphens_new (FALSE));
2047     pango_layout_set_attributes (layout, attr_list);
2048     #endif
2049 
2050     /* Create a font description */
2051     if (container->details->font)
2052     {
2053         desc = pango_font_description_from_string (container->details->font);
2054     }
2055     else
2056     {
2057         desc = pango_font_description_copy (pango_context_get_font_description (context));
2058         pango_font_description_set_size (desc,
2059                                          pango_font_description_get_size (desc) +
2060                                          container->details->font_size_table [container->details->zoom_level]);
2061     }
2062     pango_layout_set_font_description (layout, desc);
2063     pango_font_description_free (desc);
2064     g_free (zeroified_text);
2065     #if PANGO_CHECK_VERSION (1, 44, 0)
2066     pango_attr_list_unref (attr_list);
2067     #endif
2068 
2069     return layout;
2070 }
2071 
2072 static PangoLayout *
get_label_layout(PangoLayout ** layout_cache,CajaIconCanvasItem * item,const char * text)2073 get_label_layout (PangoLayout **layout_cache,
2074                   CajaIconCanvasItem *item,
2075                   const char *text)
2076 {
2077     PangoLayout *layout;
2078 
2079     if (*layout_cache != NULL)
2080     {
2081         return g_object_ref (*layout_cache);
2082     }
2083 
2084     layout = create_label_layout (item, text);
2085 
2086     if (item->details->is_visible)
2087     {
2088         *layout_cache = g_object_ref (layout);
2089     }
2090 
2091     return layout;
2092 }
2093 
2094 
2095 /* handle events */
2096 static int
caja_icon_canvas_item_event(EelCanvasItem * item,GdkEvent * event)2097 caja_icon_canvas_item_event (EelCanvasItem *item, GdkEvent *event)
2098 {
2099     CajaIconCanvasItem *icon_item;
2100     GdkWindow *cursor_window;
2101 
2102     icon_item = CAJA_ICON_CANVAS_ITEM (item);
2103     cursor_window = ((GdkEventAny *) event)->window;
2104 
2105     switch (event->type)
2106     {
2107     case GDK_ENTER_NOTIFY:
2108         if (!icon_item->details->is_prelit)
2109         {
2110             icon_item->details->is_prelit = TRUE;
2111             caja_icon_canvas_item_invalidate_label_size (icon_item);
2112             eel_canvas_item_request_update (item);
2113             eel_canvas_item_send_behind (item,
2114                                          CAJA_ICON_CONTAINER (item->canvas)->details->rubberband_info.selection_rectangle);
2115 
2116             /* show a hand cursor */
2117             if (in_single_click_mode ())
2118             {
2119                 GdkCursor *cursor;
2120 
2121                 cursor = gdk_cursor_new_for_display (gdk_display_get_default(),
2122                                                      GDK_HAND2);
2123                 gdk_window_set_cursor (cursor_window, cursor);
2124                 g_object_unref (cursor);
2125 
2126                 icon_item->details->cursor_window = g_object_ref (cursor_window);
2127             }
2128 
2129             /* FIXME bugzilla.gnome.org 42473:
2130              * We should emit our own signal here,
2131              * not one from the container; it could hook
2132              * up to that signal and emit one of its
2133              * own. Doing it this way hard-codes what
2134              * "user_data" is. Also, the two signals
2135              * should be separate. The "unpreview" signal
2136              * does not have a return value.
2137              */
2138             icon_item->details->is_active = caja_icon_container_emit_preview_signal
2139                                             (CAJA_ICON_CONTAINER (item->canvas),
2140                                              CAJA_ICON_CANVAS_ITEM (item)->user_data,
2141                                              TRUE);
2142         }
2143         return TRUE;
2144 
2145     case GDK_LEAVE_NOTIFY:
2146         if (icon_item->details->is_prelit
2147                 || icon_item->details->is_highlighted_for_drop)
2148         {
2149             /* When leaving, turn of the prelight state and the
2150              * higlighted for drop. The latter gets turned on
2151              * by the drag&drop motion callback.
2152              */
2153             /* FIXME bugzilla.gnome.org 42473:
2154              * We should emit our own signal here,
2155              * not one from the containe; it could hook up
2156              * to that signal and emit one of its
2157              * ownr. Doing it this way hard-codes what
2158              * "user_data" is. Also, the two signals
2159              * should be separate. The "unpreview" signal
2160              * does not have a return value.
2161              */
2162             caja_icon_container_emit_preview_signal
2163             (CAJA_ICON_CONTAINER (item->canvas),
2164              CAJA_ICON_CANVAS_ITEM (item)->user_data,
2165              FALSE);
2166             icon_item->details->is_prelit = FALSE;
2167             icon_item->details->is_active = 0;
2168             icon_item->details->is_highlighted_for_drop = FALSE;
2169             caja_icon_canvas_item_invalidate_label_size (icon_item);
2170             eel_canvas_item_request_update (item);
2171 
2172             /* show default cursor */
2173             gdk_window_set_cursor (cursor_window, NULL);
2174             g_clear_object (&icon_item->details->cursor_window);
2175         }
2176         return TRUE;
2177 
2178     default:
2179         /* Don't eat up other events; icon container might use them. */
2180         return FALSE;
2181     }
2182 }
2183 
2184 static gboolean
hit_test_pixbuf(GdkPixbuf * pixbuf,EelIRect pixbuf_location,EelIRect probe_rect)2185 hit_test_pixbuf (GdkPixbuf *pixbuf, EelIRect pixbuf_location, EelIRect probe_rect)
2186 {
2187     EelIRect relative_rect, pixbuf_rect;
2188     int x, y;
2189     guint8 *pixel;
2190 
2191     /* You can get here without a pixbuf in some strange cases. */
2192     if (pixbuf == NULL)
2193     {
2194         return FALSE;
2195     }
2196 
2197     /* Check to see if it's within the rectangle at all. */
2198     relative_rect.x0 = probe_rect.x0 - pixbuf_location.x0;
2199     relative_rect.y0 = probe_rect.y0 - pixbuf_location.y0;
2200     relative_rect.x1 = probe_rect.x1 - pixbuf_location.x0;
2201     relative_rect.y1 = probe_rect.y1 - pixbuf_location.y0;
2202     pixbuf_rect.x0 = 0;
2203     pixbuf_rect.y0 = 0;
2204     pixbuf_rect.x1 = gdk_pixbuf_get_width (pixbuf);
2205     pixbuf_rect.y1 = gdk_pixbuf_get_height (pixbuf);
2206     eel_irect_intersect (&relative_rect, &relative_rect, &pixbuf_rect);
2207     if (eel_irect_is_empty (&relative_rect))
2208     {
2209         return FALSE;
2210     }
2211 
2212     /* If there's no alpha channel, it's opaque and we have a hit. */
2213     if (!gdk_pixbuf_get_has_alpha (pixbuf))
2214     {
2215         return TRUE;
2216     }
2217     g_assert (gdk_pixbuf_get_n_channels (pixbuf) == 4);
2218 
2219     /* Check the alpha channel of the pixel to see if we have a hit. */
2220     for (x = relative_rect.x0; x < relative_rect.x1; x++)
2221     {
2222         for (y = relative_rect.y0; y < relative_rect.y1; y++)
2223         {
2224             pixel = gdk_pixbuf_get_pixels (pixbuf)
2225                     + y * gdk_pixbuf_get_rowstride (pixbuf)
2226                     + x * 4;
2227             if (pixel[3] > 1)
2228             {
2229                 return TRUE;
2230             }
2231         }
2232     }
2233     return FALSE;
2234 }
2235 
2236 static gboolean
hit_test(CajaIconCanvasItem * icon_item,EelIRect canvas_rect)2237 hit_test (CajaIconCanvasItem *icon_item, EelIRect canvas_rect)
2238 {
2239     CajaIconCanvasItemPrivate *details;
2240     EelIRect emblem_rect;
2241     EmblemLayout emblem_layout;
2242     GdkPixbuf *emblem_pixbuf;
2243     gboolean is_rtl;
2244 
2245     details = icon_item->details;
2246 
2247     /* Quick check to see if the rect hits the icon, text or emblems at all. */
2248     if (!eel_irect_hits_irect (icon_item->details->canvas_rect, canvas_rect)
2249             && (!eel_irect_hits_irect (details->text_rect, canvas_rect))
2250             && (!eel_irect_hits_irect (details->emblem_rect, canvas_rect)))
2251     {
2252         return FALSE;
2253     }
2254 
2255     /* Check for hits in the stretch handles. */
2256     if (hit_test_stretch_handle (icon_item, canvas_rect, NULL))
2257     {
2258         return TRUE;
2259     }
2260 
2261     /* Check for hit in the icon. */
2262     if (eel_irect_hits_irect (icon_item->details->canvas_rect, canvas_rect))
2263     {
2264         return TRUE;
2265     }
2266 
2267     /* Check for hit in the text. */
2268     if (eel_irect_hits_irect (details->text_rect, canvas_rect)
2269             && !icon_item->details->is_renaming)
2270     {
2271         return TRUE;
2272     }
2273 
2274     is_rtl = caja_icon_container_is_layout_rtl (CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (icon_item)->canvas));
2275 
2276     /* Check for hit in the emblem pixbufs. */
2277     emblem_layout_reset (&emblem_layout, icon_item, icon_item->details->canvas_rect, is_rtl);
2278     while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect, is_rtl))
2279     {
2280         if (hit_test_pixbuf (emblem_pixbuf, emblem_rect, canvas_rect))
2281         {
2282             return TRUE;
2283         }
2284     }
2285 
2286     return FALSE;
2287 }
2288 
2289 /* Point handler for the icon canvas item. */
2290 static double
caja_icon_canvas_item_point(EelCanvasItem * item,double x,double y,int cx,int cy,EelCanvasItem ** actual_item)2291 caja_icon_canvas_item_point (EelCanvasItem *item, double x, double y, int cx, int cy,
2292                              EelCanvasItem **actual_item)
2293 {
2294     EelIRect canvas_rect;
2295 
2296     *actual_item = item;
2297     canvas_rect.x0 = cx;
2298     canvas_rect.y0 = cy;
2299     canvas_rect.x1 = cx + 1;
2300     canvas_rect.y1 = cy + 1;
2301     if (hit_test (CAJA_ICON_CANVAS_ITEM (item), canvas_rect))
2302     {
2303         return 0.0;
2304     }
2305     else
2306     {
2307         /* This value means not hit.
2308          * It's kind of arbitrary. Can we do better?
2309          */
2310         return item->canvas->pixels_per_unit * 2 + 10;
2311     }
2312 }
2313 
2314 static void
caja_icon_canvas_item_translate(EelCanvasItem * item,double dx,double dy)2315 caja_icon_canvas_item_translate (EelCanvasItem *item, double dx, double dy)
2316 {
2317     CajaIconCanvasItem *icon_item;
2318     CajaIconCanvasItemPrivate *details;
2319 
2320     icon_item = CAJA_ICON_CANVAS_ITEM (item);
2321     details = icon_item->details;
2322 
2323     details->x += dx;
2324     details->y += dy;
2325 }
2326 
2327 void
caja_icon_canvas_item_get_bounds_for_layout(CajaIconCanvasItem * icon_item,double * x1,double * y1,double * x2,double * y2)2328 caja_icon_canvas_item_get_bounds_for_layout (CajaIconCanvasItem *icon_item,
2329         double *x1, double *y1, double *x2, double *y2)
2330 {
2331     CajaIconCanvasItemPrivate *details;
2332     EelIRect *total_rect;
2333 
2334     details = icon_item->details;
2335 
2336     caja_icon_canvas_item_ensure_bounds_up_to_date (icon_item);
2337     g_assert (details->bounds_cached);
2338 
2339     total_rect = &details->bounds_cache_for_layout;
2340 
2341     /* Return the result. */
2342     if (x1 != NULL)
2343     {
2344         *x1 = (int)details->x + total_rect->x0;
2345     }
2346     if (y1 != NULL)
2347     {
2348         *y1 = (int)details->y + total_rect->y0;
2349     }
2350     if (x2 != NULL)
2351     {
2352         *x2 = (int)details->x + total_rect->x1 + 1;
2353     }
2354     if (y2 != NULL)
2355     {
2356         *y2 = (int)details->y + total_rect->y1 + 1;
2357     }
2358 }
2359 
2360 void
caja_icon_canvas_item_get_bounds_for_entire_item(CajaIconCanvasItem * icon_item,double * x1,double * y1,double * x2,double * y2)2361 caja_icon_canvas_item_get_bounds_for_entire_item (CajaIconCanvasItem *icon_item,
2362         double *x1, double *y1, double *x2, double *y2)
2363 {
2364     CajaIconCanvasItemPrivate *details;
2365     EelIRect *total_rect;
2366 
2367     details = icon_item->details;
2368 
2369     caja_icon_canvas_item_ensure_bounds_up_to_date (icon_item);
2370     g_assert (details->bounds_cached);
2371 
2372     total_rect = &details->bounds_cache_for_entire_item;
2373 
2374     /* Return the result. */
2375     if (x1 != NULL)
2376     {
2377         *x1 = (int)details->x + total_rect->x0;
2378     }
2379     if (y1 != NULL)
2380     {
2381         *y1 = (int)details->y + total_rect->y0;
2382     }
2383     if (x2 != NULL)
2384     {
2385         *x2 = (int)details->x + total_rect->x1 + 1;
2386     }
2387     if (y2 != NULL)
2388     {
2389         *y2 = (int)details->y + total_rect->y1 + 1;
2390     }
2391 }
2392 
2393 /* Bounds handler for the icon canvas item. */
2394 static void
caja_icon_canvas_item_bounds(EelCanvasItem * item,double * x1,double * y1,double * x2,double * y2)2395 caja_icon_canvas_item_bounds (EelCanvasItem *item,
2396                               double *x1, double *y1, double *x2, double *y2)
2397 {
2398     CajaIconCanvasItem *icon_item;
2399     CajaIconCanvasItemPrivate *details;
2400     EelIRect *total_rect;
2401 
2402     icon_item = CAJA_ICON_CANVAS_ITEM (item);
2403     details = icon_item->details;
2404 
2405     g_assert (x1 != NULL);
2406     g_assert (y1 != NULL);
2407     g_assert (x2 != NULL);
2408     g_assert (y2 != NULL);
2409 
2410     caja_icon_canvas_item_ensure_bounds_up_to_date (icon_item);
2411     g_assert (details->bounds_cached);
2412 
2413     total_rect = &details->bounds_cache;
2414 
2415     /* Return the result. */
2416     *x1 = (int)details->x + total_rect->x0;
2417     *y1 = (int)details->y + total_rect->y0;
2418     *x2 = (int)details->x + total_rect->x1 + 1;
2419     *y2 = (int)details->y + total_rect->y1 + 1;
2420 }
2421 
2422 static void
caja_icon_canvas_item_ensure_bounds_up_to_date(CajaIconCanvasItem * icon_item)2423 caja_icon_canvas_item_ensure_bounds_up_to_date (CajaIconCanvasItem *icon_item)
2424 {
2425     CajaIconCanvasItemPrivate *details;
2426     EelIRect icon_rect, emblem_rect, icon_rect_raw;
2427     EelIRect text_rect, text_rect_for_layout, text_rect_for_entire_text;
2428     EelIRect total_rect, total_rect_for_layout, total_rect_for_entire_text;
2429     EelCanvasItem *item;
2430     gint width, height;
2431     EmblemLayout emblem_layout;
2432     GdkPixbuf *emblem_pixbuf;
2433     gboolean is_rtl;
2434 
2435     details = icon_item->details;
2436     item = EEL_CANVAS_ITEM (icon_item);
2437 
2438     if (!details->bounds_cached)
2439     {
2440         double pixels_per_unit;
2441 
2442         measure_label_text (icon_item);
2443 
2444         pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
2445 
2446         /* Compute raw and scaled icon rectangle. */
2447         icon_rect.x0 = 0;
2448         icon_rect.y0 = 0;
2449         icon_rect_raw.x0 = 0;
2450         icon_rect_raw.y0 = 0;
2451 
2452         get_scaled_icon_size (icon_item, &width, &height);
2453 
2454         icon_rect_raw.x1 = icon_rect_raw.x0 + width;
2455         icon_rect_raw.y1 = icon_rect_raw.y0 + height;
2456         icon_rect.x1 = icon_rect_raw.x1 / pixels_per_unit;
2457         icon_rect.y1 = icon_rect_raw.y1 / pixels_per_unit;
2458 
2459         /* Compute text rectangle. */
2460         text_rect = compute_text_rectangle (icon_item, icon_rect, FALSE, BOUNDS_USAGE_FOR_DISPLAY);
2461         text_rect_for_layout = compute_text_rectangle (icon_item, icon_rect, FALSE, BOUNDS_USAGE_FOR_LAYOUT);
2462         text_rect_for_entire_text = compute_text_rectangle (icon_item, icon_rect, FALSE, BOUNDS_USAGE_FOR_ENTIRE_ITEM);
2463 
2464         is_rtl = caja_icon_container_is_layout_rtl (CAJA_ICON_CONTAINER (item->canvas));
2465 
2466         /* Compute total rectangle, adding in emblem rectangles. */
2467         eel_irect_union (&total_rect, &icon_rect, &text_rect);
2468         eel_irect_union (&total_rect_for_layout, &icon_rect, &text_rect_for_layout);
2469         eel_irect_union (&total_rect_for_entire_text, &icon_rect, &text_rect_for_entire_text);
2470         emblem_layout_reset (&emblem_layout, icon_item, icon_rect_raw, is_rtl);
2471         while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect, is_rtl))
2472         {
2473             emblem_rect.x0 = floor (emblem_rect.x0 / pixels_per_unit);
2474             emblem_rect.y0 = floor (emblem_rect.y0 / pixels_per_unit);
2475             emblem_rect.x1 = ceil (emblem_rect.x1 / pixels_per_unit);
2476             emblem_rect.y1 = ceil (emblem_rect.y1 / pixels_per_unit);
2477 
2478             eel_irect_union (&total_rect, &total_rect, &emblem_rect);
2479             eel_irect_union (&total_rect_for_layout, &total_rect_for_layout, &emblem_rect);
2480             eel_irect_union (&total_rect_for_entire_text, &total_rect_for_entire_text, &emblem_rect);
2481         }
2482 
2483         details->bounds_cache = total_rect;
2484         details->bounds_cache_for_layout = total_rect_for_layout;
2485         details->bounds_cache_for_entire_item = total_rect_for_entire_text;
2486         details->bounds_cached = TRUE;
2487     }
2488 }
2489 
2490 /* Get the rectangle of the icon only, in world coordinates. */
2491 EelDRect
caja_icon_canvas_item_get_icon_rectangle(const CajaIconCanvasItem * item)2492 caja_icon_canvas_item_get_icon_rectangle (const CajaIconCanvasItem *item)
2493 {
2494     EelDRect rectangle;
2495     double pixels_per_unit;
2496     gint width, height;
2497 
2498     g_return_val_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item), eel_drect_empty);
2499 
2500     rectangle.x0 = item->details->x;
2501     rectangle.y0 = item->details->y;
2502 
2503     pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
2504     get_scaled_icon_size (CAJA_ICON_CANVAS_ITEM (item), &width, &height);
2505     rectangle.x1 = rectangle.x0 + width / pixels_per_unit;
2506     rectangle.y1 = rectangle.y0 + height / pixels_per_unit;
2507 
2508     eel_canvas_item_i2w (EEL_CANVAS_ITEM (item),
2509                          &rectangle.x0,
2510                          &rectangle.y0);
2511     eel_canvas_item_i2w (EEL_CANVAS_ITEM (item),
2512                          &rectangle.x1,
2513                          &rectangle.y1);
2514 
2515     return rectangle;
2516 }
2517 
2518 EelDRect
caja_icon_canvas_item_get_text_rectangle(CajaIconCanvasItem * item,gboolean for_layout)2519 caja_icon_canvas_item_get_text_rectangle (CajaIconCanvasItem *item,
2520         gboolean for_layout)
2521 {
2522     /* FIXME */
2523     EelIRect icon_rectangle;
2524     EelIRect text_rectangle;
2525     EelDRect ret;
2526     double pixels_per_unit;
2527     gint width, height;
2528 
2529     g_return_val_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item), eel_drect_empty);
2530 
2531     icon_rectangle.x0 = item->details->x;
2532     icon_rectangle.y0 = item->details->y;
2533 
2534     pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit;
2535     get_scaled_icon_size (item, &width, &height);
2536     icon_rectangle.x1 = icon_rectangle.x0 + width / pixels_per_unit;
2537     icon_rectangle.y1 = icon_rectangle.y0 + height / pixels_per_unit;
2538 
2539     measure_label_text (item);
2540 
2541     text_rectangle = compute_text_rectangle (item, icon_rectangle, FALSE,
2542                      for_layout ? BOUNDS_USAGE_FOR_LAYOUT : BOUNDS_USAGE_FOR_DISPLAY);
2543 
2544     ret.x0 = text_rectangle.x0;
2545     ret.y0 = text_rectangle.y0;
2546     ret.x1 = text_rectangle.x1;
2547     ret.y1 = text_rectangle.y1;
2548 
2549     eel_canvas_item_i2w (EEL_CANVAS_ITEM (item),
2550                          &ret.x0,
2551                          &ret.y0);
2552     eel_canvas_item_i2w (EEL_CANVAS_ITEM (item),
2553                          &ret.x1,
2554                          &ret.y1);
2555 
2556     return ret;
2557 }
2558 
2559 /* Get the rectangle of the icon only, in canvas coordinates. */
2560 static void
get_icon_canvas_rectangle(CajaIconCanvasItem * item,EelIRect * rect)2561 get_icon_canvas_rectangle (CajaIconCanvasItem *item,
2562                            EelIRect *rect)
2563 {
2564     gint width, height;
2565 
2566     g_assert (CAJA_IS_ICON_CANVAS_ITEM (item));
2567     g_assert (rect != NULL);
2568 
2569 
2570     eel_canvas_w2c (EEL_CANVAS_ITEM (item)->canvas,
2571                     item->details->x,
2572                     item->details->y,
2573                     &rect->x0,
2574                     &rect->y0);
2575 
2576     get_scaled_icon_size (item, &width, &height);
2577 
2578     rect->x1 = rect->x0 + width;
2579     rect->y1 = rect->y0 + height;
2580 }
2581 
2582 void
caja_icon_canvas_item_set_show_stretch_handles(CajaIconCanvasItem * item,gboolean show_stretch_handles)2583 caja_icon_canvas_item_set_show_stretch_handles (CajaIconCanvasItem *item,
2584         gboolean show_stretch_handles)
2585 {
2586     g_return_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item));
2587     g_return_if_fail (show_stretch_handles == FALSE || show_stretch_handles == TRUE);
2588 
2589     if (!item->details->show_stretch_handles == !show_stretch_handles)
2590     {
2591         return;
2592     }
2593 
2594     item->details->show_stretch_handles = show_stretch_handles;
2595     eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
2596 }
2597 
2598 /* Check if one of the stretch handles was hit. */
2599 static gboolean
hit_test_stretch_handle(CajaIconCanvasItem * item,EelIRect probe_canvas_rect,GtkCornerType * corner)2600 hit_test_stretch_handle (CajaIconCanvasItem *item,
2601                          EelIRect probe_canvas_rect,
2602                          GtkCornerType *corner)
2603 {
2604     EelIRect icon_rect;
2605     GdkPixbuf *knob_pixbuf;
2606     int knob_width, knob_height;
2607     int hit_corner;
2608 
2609     g_assert (CAJA_IS_ICON_CANVAS_ITEM (item));
2610 
2611     /* Make sure there are handles to hit. */
2612     if (!item->details->show_stretch_handles)
2613     {
2614         return FALSE;
2615     }
2616 
2617     /* Quick check to see if the rect hits the icon at all. */
2618     icon_rect = item->details->canvas_rect;
2619     if (!eel_irect_hits_irect (probe_canvas_rect, icon_rect))
2620     {
2621         return FALSE;
2622     }
2623 
2624     knob_pixbuf = get_knob_pixbuf ();
2625     knob_width = gdk_pixbuf_get_width (knob_pixbuf);
2626     knob_height = gdk_pixbuf_get_height (knob_pixbuf);
2627     g_object_unref (knob_pixbuf);
2628 
2629     /* Check for hits in the stretch handles. */
2630     hit_corner = -1;
2631     if (probe_canvas_rect.x0 < icon_rect.x0 + knob_width)
2632     {
2633         if (probe_canvas_rect.y0 < icon_rect.y0 + knob_height)
2634             hit_corner = GTK_CORNER_TOP_LEFT;
2635         else if (probe_canvas_rect.y1 >= icon_rect.y1 - knob_height)
2636             hit_corner = GTK_CORNER_BOTTOM_LEFT;
2637     }
2638     else if (probe_canvas_rect.x1 >= icon_rect.x1 - knob_width)
2639     {
2640         if (probe_canvas_rect.y0 < icon_rect.y0 + knob_height)
2641             hit_corner = GTK_CORNER_TOP_RIGHT;
2642         else if (probe_canvas_rect.y1 >= icon_rect.y1 - knob_height)
2643             hit_corner = GTK_CORNER_BOTTOM_RIGHT;
2644     }
2645     if (corner)
2646         *corner = hit_corner;
2647 
2648     return hit_corner != -1;
2649 }
2650 
2651 gboolean
caja_icon_canvas_item_hit_test_stretch_handles(CajaIconCanvasItem * item,EelDPoint world_point,GtkCornerType * corner)2652 caja_icon_canvas_item_hit_test_stretch_handles (CajaIconCanvasItem *item,
2653         EelDPoint world_point,
2654         GtkCornerType *corner)
2655 {
2656     EelIRect canvas_rect;
2657 
2658     g_return_val_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item), FALSE);
2659 
2660     eel_canvas_w2c (EEL_CANVAS_ITEM (item)->canvas,
2661                     world_point.x,
2662                     world_point.y,
2663                     &canvas_rect.x0,
2664                     &canvas_rect.y0);
2665     canvas_rect.x1 = canvas_rect.x0 + 1;
2666     canvas_rect.y1 = canvas_rect.y0 + 1;
2667     return hit_test_stretch_handle (item, canvas_rect, corner);
2668 }
2669 
2670 /* caja_icon_canvas_item_hit_test_rectangle
2671  *
2672  * Check and see if there is an intersection between the item and the
2673  * canvas rect.
2674  */
2675 gboolean
caja_icon_canvas_item_hit_test_rectangle(CajaIconCanvasItem * item,EelIRect canvas_rect)2676 caja_icon_canvas_item_hit_test_rectangle (CajaIconCanvasItem *item, EelIRect canvas_rect)
2677 {
2678     g_return_val_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item), FALSE);
2679 
2680     return hit_test (item, canvas_rect);
2681 }
2682 
2683 const char *
caja_icon_canvas_item_get_editable_text(CajaIconCanvasItem * icon_item)2684 caja_icon_canvas_item_get_editable_text (CajaIconCanvasItem *icon_item)
2685 {
2686     g_return_val_if_fail (CAJA_IS_ICON_CANVAS_ITEM (icon_item), NULL);
2687 
2688     return icon_item->details->editable_text;
2689 }
2690 
2691 void
caja_icon_canvas_item_set_renaming(CajaIconCanvasItem * item,gboolean state)2692 caja_icon_canvas_item_set_renaming (CajaIconCanvasItem *item, gboolean state)
2693 {
2694     g_return_if_fail (CAJA_IS_ICON_CANVAS_ITEM (item));
2695     g_return_if_fail (state == FALSE || state == TRUE);
2696 
2697     if (!item->details->is_renaming == !state)
2698     {
2699         return;
2700     }
2701 
2702     item->details->is_renaming = state;
2703     eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
2704 }
2705 
2706 double
caja_icon_canvas_item_get_max_text_width(CajaIconCanvasItem * item)2707 caja_icon_canvas_item_get_max_text_width (CajaIconCanvasItem *item)
2708 {
2709     EelCanvasItem *canvas_item;
2710     CajaIconContainer *container;
2711 
2712     canvas_item = EEL_CANVAS_ITEM (item);
2713     container = CAJA_ICON_CONTAINER (canvas_item->canvas);
2714 
2715     if (caja_icon_container_is_tighter_layout (container))
2716     {
2717         return MAX_TEXT_WIDTH_TIGHTER * canvas_item->canvas->pixels_per_unit;
2718     }
2719     else
2720     {
2721 
2722         if (container->details->label_position == CAJA_ICON_LABEL_POSITION_BESIDE)
2723         {
2724             if (container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_L_R ||
2725                     container->details->layout_mode == CAJA_ICON_LAYOUT_T_B_R_L)
2726             {
2727                 if (container->details->all_columns_same_width)
2728                 {
2729                     return MAX_TEXT_WIDTH_BESIDE_TOP_TO_BOTTOM * canvas_item->canvas->pixels_per_unit;
2730                 }
2731                 else
2732                 {
2733                     return -1;
2734                 }
2735             }
2736             else
2737             {
2738                 return MAX_TEXT_WIDTH_BESIDE * canvas_item->canvas->pixels_per_unit;
2739             }
2740         }
2741         else
2742         {
2743             return MAX_TEXT_WIDTH_STANDARD * canvas_item->canvas->pixels_per_unit;
2744         }
2745 
2746 
2747     }
2748 
2749 }
2750 
2751 void
caja_icon_canvas_item_set_entire_text(CajaIconCanvasItem * item,gboolean entire_text)2752 caja_icon_canvas_item_set_entire_text (CajaIconCanvasItem       *item,
2753 					   gboolean                      entire_text)
2754 {
2755 	if (item->details->entire_text != entire_text) {
2756 		item->details->entire_text = entire_text;
2757 
2758 		caja_icon_canvas_item_invalidate_label_size (item);
2759 		eel_canvas_item_request_update (EEL_CANVAS_ITEM (item));
2760 	}
2761 }
2762 
2763 /* Class initialization function for the icon canvas item. */
2764 static void
caja_icon_canvas_item_class_init(CajaIconCanvasItemClass * class)2765 caja_icon_canvas_item_class_init (CajaIconCanvasItemClass *class)
2766 {
2767 	GObjectClass *object_class;
2768 	EelCanvasItemClass *item_class;
2769 
2770 	object_class = G_OBJECT_CLASS (class);
2771 	item_class = EEL_CANVAS_ITEM_CLASS (class);
2772 
2773 	object_class->finalize = caja_icon_canvas_item_finalize;
2774 	object_class->set_property = caja_icon_canvas_item_set_property;
2775 	object_class->get_property = caja_icon_canvas_item_get_property;
2776 
2777         g_object_class_install_property (
2778 		object_class,
2779 		PROP_EDITABLE_TEXT,
2780 		g_param_spec_string ("editable_text",
2781 				     "editable text",
2782 				     "the editable label",
2783 				     "", G_PARAM_READWRITE));
2784 
2785         g_object_class_install_property (
2786 		object_class,
2787 		PROP_ADDITIONAL_TEXT,
2788 		g_param_spec_string ("additional_text",
2789 				     "additional text",
2790 				     "some more text",
2791 				     "", G_PARAM_READWRITE));
2792 
2793         g_object_class_install_property (
2794 		object_class,
2795 		PROP_HIGHLIGHTED_FOR_SELECTION,
2796 		g_param_spec_boolean ("highlighted_for_selection",
2797 				      "highlighted for selection",
2798 				      "whether we are highlighted for a selection",
2799 				      FALSE, G_PARAM_READWRITE));
2800 
2801         g_object_class_install_property (
2802 		object_class,
2803 		PROP_HIGHLIGHTED_AS_KEYBOARD_FOCUS,
2804 		g_param_spec_boolean ("highlighted_as_keyboard_focus",
2805 				      "highlighted as keyboard focus",
2806 				      "whether we are highlighted to render keyboard focus",
2807 				      FALSE, G_PARAM_READWRITE));
2808 
2809 
2810         g_object_class_install_property (
2811 		object_class,
2812 		PROP_HIGHLIGHTED_FOR_DROP,
2813 		g_param_spec_boolean ("highlighted_for_drop",
2814 				      "highlighted for drop",
2815 				      "whether we are highlighted for a D&D drop",
2816 				      FALSE, G_PARAM_READWRITE));
2817 
2818 	g_object_class_install_property (
2819 		object_class,
2820 		PROP_HIGHLIGHTED_FOR_CLIPBOARD,
2821 		g_param_spec_boolean ("highlighted_for_clipboard",
2822 				      "highlighted for clipboard",
2823 				      "whether we are highlighted for a clipboard paste (after we have been cut)",
2824  				      FALSE, G_PARAM_READWRITE));
2825 
2826 	item_class->update = caja_icon_canvas_item_update;
2827 	item_class->draw = caja_icon_canvas_item_draw;
2828 	item_class->point = caja_icon_canvas_item_point;
2829 	item_class->translate = caja_icon_canvas_item_translate;
2830 	item_class->bounds = caja_icon_canvas_item_bounds;
2831 	item_class->event = caja_icon_canvas_item_event;
2832 
2833 	atk_registry_set_factory_type (atk_get_default_registry (),
2834 				       CAJA_TYPE_ICON_CANVAS_ITEM,
2835 				       caja_icon_canvas_item_accessible_factory_get_type ());
2836 }
2837 
2838 static GailTextUtil *
caja_icon_canvas_item_get_text(GObject * text)2839 caja_icon_canvas_item_get_text (GObject *text)
2840 {
2841 	return CAJA_ICON_CANVAS_ITEM (text)->details->text_util;
2842 }
2843 
2844 static void
caja_icon_canvas_item_text_interface_init(EelAccessibleTextIface * iface)2845 caja_icon_canvas_item_text_interface_init (EelAccessibleTextIface *iface)
2846 {
2847 	iface->get_text = caja_icon_canvas_item_get_text;
2848 }
2849 
2850 /* ============================= a11y interfaces =========================== */
2851 
2852 static const char *caja_icon_canvas_item_accessible_action_names[] = {
2853         "open",
2854         "menu",
2855         NULL
2856 };
2857 
2858 static const char *caja_icon_canvas_item_accessible_action_descriptions[] = {
2859         "Open item",
2860         "Popup context menu",
2861         NULL
2862 };
2863 
2864 enum {
2865 	ACTION_OPEN,
2866 	ACTION_MENU,
2867 	LAST_ACTION
2868 };
2869 
2870 typedef struct {
2871         char *action_descriptions[LAST_ACTION];
2872 	char *image_description;
2873 	char *description;
2874 } CajaIconCanvasItemAccessiblePrivate;
2875 
2876 typedef struct {
2877 	CajaIconCanvasItem *item;
2878 	gint action_number;
2879 } CajaIconCanvasItemAccessibleActionContext;
2880 
2881 static GType caja_icon_canvas_item_accessible_get_type (void);
2882 
2883 #define GET_PRIV(o) G_TYPE_INSTANCE_GET_PRIVATE(o, caja_icon_canvas_item_accessible_get_type (), CajaIconCanvasItemAccessiblePrivate);
2884 
2885 /* accessible AtkAction interface */
2886 
2887 static gboolean
caja_icon_canvas_item_accessible_idle_do_action(gpointer data)2888 caja_icon_canvas_item_accessible_idle_do_action (gpointer data)
2889 {
2890     CajaIconCanvasItem *item;
2891     CajaIconCanvasItemAccessibleActionContext *ctx;
2892     CajaIcon *icon;
2893     CajaIconContainer *container;
2894     GList* selection;
2895     GList file_list;
2896     GdkEventButton button_event = { 0 };
2897     gint action_number;
2898 
2899     container = CAJA_ICON_CONTAINER (data);
2900     container->details->a11y_item_action_idle_handler = 0;
2901     while (!g_queue_is_empty (container->details->a11y_item_action_queue))
2902     {
2903         ctx = g_queue_pop_head (container->details->a11y_item_action_queue);
2904         action_number = ctx->action_number;
2905         item = ctx->item;
2906         g_free (ctx);
2907         icon = item->user_data;
2908 
2909         switch (action_number)
2910         {
2911         case ACTION_OPEN:
2912             file_list.data = icon->data;
2913             file_list.next = NULL;
2914             file_list.prev = NULL;
2915             g_signal_emit_by_name (container, "activate", &file_list);
2916             break;
2917         case ACTION_MENU:
2918             selection = caja_icon_container_get_selection (container);
2919             if (selection == NULL ||
2920                     g_list_length (selection) != 1 ||
2921                     selection->data != icon->data)
2922             {
2923                 g_list_free (selection);
2924                 return FALSE;
2925             }
2926             g_list_free (selection);
2927             g_signal_emit_by_name (container, "context_click_selection", &button_event);
2928             break;
2929         default :
2930             g_assert_not_reached ();
2931             break;
2932         }
2933     }
2934     return FALSE;
2935 }
2936 
2937 static gboolean
caja_icon_canvas_item_accessible_do_action(AtkAction * accessible,int i)2938 caja_icon_canvas_item_accessible_do_action (AtkAction *accessible, int i)
2939 {
2940     CajaIconCanvasItem *item;
2941     CajaIconCanvasItemAccessibleActionContext *ctx;
2942     CajaIconContainer *container;
2943 
2944     g_assert (i < LAST_ACTION);
2945 
2946     item = CAJA_ICON_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
2947     if (!item)
2948     {
2949         return FALSE;
2950     }
2951     container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
2952     switch (i)
2953     {
2954     case ACTION_OPEN:
2955     case ACTION_MENU:
2956         if (container->details->a11y_item_action_queue == NULL)
2957         {
2958             container->details->a11y_item_action_queue = g_queue_new ();
2959         }
2960         ctx = g_new (CajaIconCanvasItemAccessibleActionContext, 1);
2961         ctx->action_number = i;
2962         ctx->item = item;
2963         g_queue_push_head (container->details->a11y_item_action_queue, ctx);
2964         if (container->details->a11y_item_action_idle_handler == 0)
2965         {
2966             container->details->a11y_item_action_idle_handler = g_idle_add (caja_icon_canvas_item_accessible_idle_do_action, container);
2967         }
2968         break;
2969     default :
2970         g_warning ("Invalid action passed to CajaIconCanvasItemAccessible::do_action");
2971         return FALSE;
2972     }
2973 
2974     return TRUE;
2975 }
2976 
2977 static int
caja_icon_canvas_item_accessible_get_n_actions(AtkAction * accessible)2978 caja_icon_canvas_item_accessible_get_n_actions (AtkAction *accessible)
2979 {
2980     return LAST_ACTION;
2981 }
2982 
2983 static const char *
caja_icon_canvas_item_accessible_action_get_description(AtkAction * accessible,int i)2984 caja_icon_canvas_item_accessible_action_get_description (AtkAction *accessible,
2985         int i)
2986 {
2987     CajaIconCanvasItemAccessiblePrivate *priv;
2988 
2989     g_assert (i < LAST_ACTION);
2990 
2991     priv = GET_PRIV (accessible);
2992     if (priv->action_descriptions[i])
2993     {
2994         return priv->action_descriptions[i];
2995     }
2996     else
2997     {
2998         return caja_icon_canvas_item_accessible_action_descriptions[i];
2999     }
3000 }
3001 
3002 static const char *
caja_icon_canvas_item_accessible_action_get_name(AtkAction * accessible,int i)3003 caja_icon_canvas_item_accessible_action_get_name (AtkAction *accessible, int i)
3004 {
3005     g_assert (i < LAST_ACTION);
3006 
3007     return caja_icon_canvas_item_accessible_action_names[i];
3008 }
3009 
3010 static const char *
caja_icon_canvas_item_accessible_action_get_keybinding(AtkAction * accessible,int i)3011 caja_icon_canvas_item_accessible_action_get_keybinding (AtkAction *accessible,
3012         int i)
3013 {
3014     g_assert (i < LAST_ACTION);
3015 
3016     return NULL;
3017 }
3018 
3019 static gboolean
caja_icon_canvas_item_accessible_action_set_description(AtkAction * accessible,int i,const char * description)3020 caja_icon_canvas_item_accessible_action_set_description (AtkAction *accessible,
3021         int i,
3022         const char *description)
3023 {
3024     CajaIconCanvasItemAccessiblePrivate *priv;
3025 
3026     g_assert (i < LAST_ACTION);
3027 
3028     priv = GET_PRIV (accessible);
3029 
3030     if (priv->action_descriptions[i])
3031     {
3032         g_free (priv->action_descriptions[i]);
3033     }
3034     priv->action_descriptions[i] = g_strdup (description);
3035 
3036     return TRUE;
3037 }
3038 
3039 static void
caja_icon_canvas_item_accessible_action_interface_init(AtkActionIface * iface)3040 caja_icon_canvas_item_accessible_action_interface_init (AtkActionIface *iface)
3041 {
3042     iface->do_action = caja_icon_canvas_item_accessible_do_action;
3043     iface->get_n_actions = caja_icon_canvas_item_accessible_get_n_actions;
3044     iface->get_description = caja_icon_canvas_item_accessible_action_get_description;
3045     iface->get_keybinding = caja_icon_canvas_item_accessible_action_get_keybinding;
3046     iface->get_name = caja_icon_canvas_item_accessible_action_get_name;
3047     iface->set_description = caja_icon_canvas_item_accessible_action_set_description;
3048 }
3049 
caja_icon_canvas_item_accessible_get_name(AtkObject * accessible)3050 static const gchar* caja_icon_canvas_item_accessible_get_name(AtkObject* accessible)
3051 {
3052     CajaIconCanvasItem* item;
3053 
3054     if (accessible->name)
3055     {
3056         return accessible->name;
3057     }
3058 
3059     item = CAJA_ICON_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
3060 
3061     if (!item)
3062     {
3063         return NULL;
3064     }
3065 
3066     return item->details->editable_text;
3067 }
3068 
caja_icon_canvas_item_accessible_get_description(AtkObject * accessible)3069 static const gchar* caja_icon_canvas_item_accessible_get_description(AtkObject* accessible)
3070 {
3071     CajaIconCanvasItem* item;
3072 
3073     item = CAJA_ICON_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
3074 
3075     if (!item)
3076     {
3077         return NULL;
3078     }
3079 
3080     return item->details->additional_text;
3081 }
3082 
3083 static AtkObject *
caja_icon_canvas_item_accessible_get_parent(AtkObject * accessible)3084 caja_icon_canvas_item_accessible_get_parent (AtkObject *accessible)
3085 {
3086     CajaIconCanvasItem *item;
3087 
3088     item = CAJA_ICON_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
3089     if (!item)
3090     {
3091         return NULL;
3092     }
3093 
3094     return gtk_widget_get_accessible (GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas));
3095 }
3096 
3097 static int
caja_icon_canvas_item_accessible_get_index_in_parent(AtkObject * accessible)3098 caja_icon_canvas_item_accessible_get_index_in_parent (AtkObject *accessible)
3099 {
3100     CajaIconCanvasItem *item;
3101     CajaIconContainer *container;
3102     GList *l;
3103     int i;
3104     CajaIcon *icon = NULL;
3105 
3106     item = CAJA_ICON_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
3107     if (!item)
3108     {
3109         return -1;
3110     }
3111 
3112     container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
3113 
3114     l = container->details->icons;
3115     i = 0;
3116     while (l)
3117     {
3118         icon = l->data;
3119 
3120         if (icon->item == item)
3121         {
3122             return i;
3123         }
3124 
3125         i++;
3126         l = l->next;
3127     }
3128 
3129     return -1;
3130 }
3131 
3132 
caja_icon_canvas_item_accessible_get_image_description(AtkImage * image)3133 static const gchar* caja_icon_canvas_item_accessible_get_image_description(AtkImage* image)
3134 {
3135     CajaIconCanvasItemAccessiblePrivate* priv;
3136     CajaIconCanvasItem* item;
3137     char* description;
3138 
3139     priv = GET_PRIV (image);
3140 
3141     if (priv->image_description)
3142     {
3143         return priv->image_description;
3144     }
3145     else
3146     {
3147         CajaIcon* icon;
3148         CajaIconContainer* container;
3149 
3150         item = CAJA_ICON_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (image)));
3151 
3152         if (item == NULL)
3153         {
3154             return NULL;
3155         }
3156 
3157         icon = item->user_data;
3158         container = CAJA_ICON_CONTAINER(EEL_CANVAS_ITEM(item)->canvas);
3159         description = caja_icon_container_get_icon_description(container, icon->data);
3160         g_free(priv->description);
3161         priv->description = description;
3162 
3163         return priv->description;
3164     }
3165 }
3166 
3167 static void
caja_icon_canvas_item_accessible_get_image_size(AtkImage * image,gint * width,gint * height)3168 caja_icon_canvas_item_accessible_get_image_size
3169 (AtkImage *image,
3170  gint     *width,
3171  gint     *height)
3172 {
3173     CajaIconCanvasItem *item;
3174 
3175     item = CAJA_ICON_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (image)));
3176 
3177     get_scaled_icon_size (item, width, height);
3178 }
3179 
3180 static void
caja_icon_canvas_item_accessible_get_image_position(AtkImage * image,gint * x,gint * y,AtkCoordType coord_type)3181 caja_icon_canvas_item_accessible_get_image_position
3182 (AtkImage		 *image,
3183  gint                    *x,
3184  gint	                 *y,
3185  AtkCoordType	         coord_type)
3186 {
3187     CajaIconCanvasItem *item;
3188     gint x_offset, y_offset, itmp;
3189 
3190     item = CAJA_ICON_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (image)));
3191     if (!item)
3192     {
3193         return;
3194     }
3195     if (!item->details->canvas_rect.x0 && !item->details->canvas_rect.x1)
3196     {
3197         return;
3198     }
3199     else
3200     {
3201         x_offset = 0;
3202         y_offset = 0;
3203         if (item->details->text_width)
3204         {
3205             itmp = item->details->canvas_rect.x0 -
3206                    item->details->text_rect.x0;
3207             if (itmp > x_offset)
3208             {
3209                 x_offset = itmp;
3210             }
3211             itmp = item->details->canvas_rect.y0 -
3212                    item->details->text_rect.y0;
3213             if (itmp > y_offset)
3214             {
3215                 y_offset = itmp;
3216             }
3217         }
3218         if (item->details->emblem_pixbufs)
3219         {
3220             itmp = item->details->canvas_rect.x0 -
3221                    item->details->emblem_rect.x0;
3222             if (itmp > x_offset)
3223             {
3224                 x_offset = itmp;
3225             }
3226             itmp = item->details->canvas_rect.y0 -
3227                    item->details->emblem_rect.y0;
3228             if (itmp > y_offset)
3229             {
3230                 y_offset = itmp;
3231             }
3232         }
3233     }
3234     atk_component_get_extents (ATK_COMPONENT (image), x, y, NULL, NULL, coord_type);
3235     *x += x_offset;
3236     *y += y_offset;
3237 }
3238 
3239 static gboolean
caja_icon_canvas_item_accessible_set_image_description(AtkImage * image,const gchar * description)3240 caja_icon_canvas_item_accessible_set_image_description
3241 (AtkImage    *image,
3242  const gchar *description)
3243 {
3244     CajaIconCanvasItemAccessiblePrivate *priv;
3245 
3246     priv = GET_PRIV (image);
3247 
3248     g_free (priv->image_description);
3249     priv->image_description = g_strdup (description);
3250 
3251     return TRUE;
3252 }
3253 
3254 static void
caja_icon_canvas_item_accessible_image_interface_init(AtkImageIface * iface)3255 caja_icon_canvas_item_accessible_image_interface_init (AtkImageIface *iface)
3256 {
3257     iface->get_image_description = caja_icon_canvas_item_accessible_get_image_description;
3258     iface->set_image_description = caja_icon_canvas_item_accessible_set_image_description;
3259     iface->get_image_size        = caja_icon_canvas_item_accessible_get_image_size;
3260     iface->get_image_position    = caja_icon_canvas_item_accessible_get_image_position;
3261 }
3262 
3263 static gint
caja_icon_canvas_item_accessible_get_offset_at_point(AtkText * text,gint x,gint y,AtkCoordType coords)3264 caja_icon_canvas_item_accessible_get_offset_at_point (AtkText	 *text,
3265         gint           x,
3266         gint           y,
3267         AtkCoordType coords)
3268 {
3269     gint real_x, real_y, real_width, real_height;
3270     CajaIconCanvasItem *item;
3271     gint editable_height;
3272     gint offset = 0;
3273     gint index;
3274     PangoLayout *layout, *editable_layout, *additional_layout;
3275     PangoRectangle rect0;
3276     char *icon_text;
3277     gboolean have_editable;
3278     gboolean have_additional;
3279     gint text_offset, height;
3280 
3281     atk_component_get_extents (ATK_COMPONENT (text), &real_x, &real_y,
3282                                &real_width, &real_height, coords);
3283 
3284     x -= real_x;
3285     y -= real_y;
3286 
3287     item = CAJA_ICON_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
3288 
3289     if (item->details->pixbuf)
3290     {
3291         get_scaled_icon_size (item, NULL, &height);
3292         y -= height;
3293     }
3294     have_editable = item->details->editable_text != NULL &&
3295                     item->details->editable_text[0] != '\0';
3296     have_additional = item->details->additional_text != NULL &&item->details->additional_text[0] != '\0';
3297 
3298     editable_layout = NULL;
3299     additional_layout = NULL;
3300     if (have_editable)
3301     {
3302         editable_layout = get_label_layout (&item->details->editable_text_layout, item, item->details->editable_text);
3303         prepare_pango_layout_for_draw (item, editable_layout);
3304         pango_layout_get_pixel_size (editable_layout, NULL, &editable_height);
3305         if (y >= editable_height &&
3306                 have_additional)
3307         {
3308             prepare_pango_layout_for_draw (item, editable_layout);
3309             additional_layout = get_label_layout (&item->details->additional_text_layout, item, item->details->additional_text);
3310             layout = additional_layout;
3311             icon_text = item->details->additional_text;
3312             y -= editable_height + LABEL_LINE_SPACING;
3313         }
3314         else
3315         {
3316             layout = editable_layout;
3317             icon_text = item->details->editable_text;
3318         }
3319     }
3320     else if (have_additional)
3321     {
3322         additional_layout = get_label_layout (&item->details->additional_text_layout, item, item->details->additional_text);
3323         prepare_pango_layout_for_draw (item, additional_layout);
3324         layout = additional_layout;
3325         icon_text = item->details->additional_text;
3326     }
3327     else
3328     {
3329         return 0;
3330     }
3331 
3332     text_offset = 0;
3333     if (have_editable)
3334     {
3335         pango_layout_index_to_pos (editable_layout, 0, &rect0);
3336         text_offset = PANGO_PIXELS (rect0.x);
3337     }
3338     if (have_additional)
3339     {
3340         gint itmp;
3341 
3342         pango_layout_index_to_pos (additional_layout, 0, &rect0);
3343         itmp = PANGO_PIXELS (rect0.x);
3344         if (itmp < text_offset)
3345         {
3346             text_offset = itmp;
3347         }
3348     }
3349     pango_layout_index_to_pos (layout, 0, &rect0);
3350     x += text_offset;
3351     if (!pango_layout_xy_to_index (layout,
3352                                    x * PANGO_SCALE,
3353                                    y * PANGO_SCALE,
3354                                    &index, NULL))
3355     {
3356         if (x < 0 || y < 0)
3357         {
3358             index = 0;
3359         }
3360         else
3361         {
3362             index = -1;
3363         }
3364     }
3365     if (index == -1)
3366     {
3367         offset = g_utf8_strlen (icon_text, -1);
3368     }
3369     else
3370     {
3371         offset = g_utf8_pointer_to_offset (icon_text, icon_text + index);
3372     }
3373     if (layout == additional_layout)
3374     {
3375         offset += g_utf8_strlen (item->details->editable_text, -1);
3376     }
3377 
3378     if (editable_layout != NULL)
3379     {
3380         g_object_unref (editable_layout);
3381     }
3382 
3383     if (additional_layout != NULL)
3384     {
3385         g_object_unref (additional_layout);
3386     }
3387 
3388     return offset;
3389 }
3390 
3391 static void
caja_icon_canvas_item_accessible_get_character_extents(AtkText * text,gint offset,gint * x,gint * y,gint * width,gint * height,AtkCoordType coords)3392 caja_icon_canvas_item_accessible_get_character_extents (AtkText	   *text,
3393         gint	   offset,
3394         gint	   *x,
3395         gint	   *y,
3396         gint	   *width,
3397         gint	   *height,
3398         AtkCoordType coords)
3399 {
3400     gint pos_x, pos_y;
3401     gint len, byte_offset;
3402     gint editable_height;
3403     gchar *icon_text;
3404     CajaIconCanvasItem *item;
3405     PangoLayout *layout, *editable_layout, *additional_layout;
3406     PangoRectangle rect;
3407     PangoRectangle rect0;
3408     gboolean have_editable;
3409     gint text_offset, pix_height;
3410 
3411     atk_component_get_extents (ATK_COMPONENT (text), &pos_x, &pos_y, NULL, NULL, coords);
3412     item = CAJA_ICON_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
3413 
3414     if (item->details->pixbuf)
3415     {
3416         get_scaled_icon_size (item, NULL, &pix_height);
3417         pos_y += pix_height;
3418     }
3419 
3420     have_editable = item->details->editable_text != NULL &&
3421                     item->details->editable_text[0] != '\0';
3422     if (have_editable)
3423     {
3424         len = g_utf8_strlen (item->details->editable_text, -1);
3425     }
3426     else
3427     {
3428         len = 0;
3429     }
3430 
3431     editable_layout = get_label_layout (&item->details->editable_text_layout, item, item->details->editable_text);
3432     additional_layout = get_label_layout (&item->details->additional_text_layout, item, item->details->additional_text);
3433 
3434     if (offset < len)
3435     {
3436         icon_text = item->details->editable_text;
3437         layout = editable_layout;
3438     }
3439     else
3440     {
3441         offset -= len;
3442         icon_text = item->details->additional_text;
3443         layout = additional_layout;
3444         pos_y += LABEL_LINE_SPACING;
3445         if (have_editable)
3446         {
3447             pango_layout_get_pixel_size (editable_layout, NULL, &editable_height);
3448             pos_y += editable_height;
3449         }
3450     }
3451     byte_offset = g_utf8_offset_to_pointer (icon_text, offset) - icon_text;
3452     pango_layout_index_to_pos (layout, byte_offset, &rect);
3453     text_offset = 0;
3454     if (have_editable)
3455     {
3456         pango_layout_index_to_pos (editable_layout, 0, &rect0);
3457         text_offset = PANGO_PIXELS (rect0.x);
3458     }
3459     if (item->details->additional_text != NULL &&
3460             item->details->additional_text[0] != '\0')
3461     {
3462         gint itmp;
3463 
3464         pango_layout_index_to_pos (additional_layout, 0, &rect0);
3465         itmp = PANGO_PIXELS (rect0.x);
3466         if (itmp < text_offset)
3467         {
3468             text_offset = itmp;
3469         }
3470     }
3471 
3472     g_object_unref (editable_layout);
3473     g_object_unref (additional_layout);
3474 
3475     *x = pos_x + PANGO_PIXELS (rect.x) - text_offset;
3476     *y = pos_y + PANGO_PIXELS (rect.y);
3477     *width = PANGO_PIXELS (rect.width);
3478     *height = PANGO_PIXELS (rect.height);
3479 }
3480 
3481 static void
caja_icon_canvas_item_accessible_text_interface_init(AtkTextIface * iface)3482 caja_icon_canvas_item_accessible_text_interface_init (AtkTextIface *iface)
3483 {
3484     iface->get_text                = eel_accessibility_text_get_text;
3485     iface->get_character_at_offset = eel_accessibility_text_get_character_at_offset;
3486     iface->get_text_before_offset  = eel_accessibility_text_get_text_before_offset;
3487     iface->get_text_at_offset      = eel_accessibility_text_get_text_at_offset;
3488     iface->get_text_after_offset   = eel_accessibility_text_get_text_after_offset;
3489     iface->get_character_count     = eel_accessibility_text_get_character_count;
3490     iface->get_character_extents   = caja_icon_canvas_item_accessible_get_character_extents;
3491     iface->get_offset_at_point     = caja_icon_canvas_item_accessible_get_offset_at_point;
3492 }
3493 
3494 typedef struct {
3495 	EelCanvasItemAccessible parent;
3496 } CajaIconCanvasItemAccessible;
3497 
3498 typedef struct {
3499 	EelCanvasItemAccessibleClass parent_class;
3500 } CajaIconCanvasItemAccessibleClass;
3501 
3502 G_DEFINE_TYPE_WITH_CODE (CajaIconCanvasItemAccessible,
3503 			 caja_icon_canvas_item_accessible,
3504 			 eel_canvas_item_accessible_get_type (),
3505                          G_ADD_PRIVATE (CajaIconCanvasItemAccessible)
3506 			 G_IMPLEMENT_INTERFACE (ATK_TYPE_IMAGE,
3507 						caja_icon_canvas_item_accessible_image_interface_init)
3508 			 G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT,
3509 						caja_icon_canvas_item_accessible_text_interface_init)
3510 			 G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION,
3511 						caja_icon_canvas_item_accessible_action_interface_init));
3512 
3513 static AtkStateSet*
caja_icon_canvas_item_accessible_ref_state_set(AtkObject * accessible)3514 caja_icon_canvas_item_accessible_ref_state_set (AtkObject *accessible)
3515 {
3516 	AtkStateSet *state_set;
3517 	CajaIconCanvasItem *item;
3518 	CajaIconContainer *container;
3519 	CajaIcon *icon;
3520 	GList *l;
3521 	gboolean one_item_selected;
3522 
3523 	state_set = ATK_OBJECT_CLASS (caja_icon_canvas_item_accessible_parent_class)->ref_state_set (accessible);
3524 
3525 	item = CAJA_ICON_CANVAS_ITEM (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible)));
3526 	if (!item) {
3527 		atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
3528 		return state_set;
3529 	}
3530 	container = CAJA_ICON_CONTAINER (EEL_CANVAS_ITEM (item)->canvas);
3531 	if (item->details->is_highlighted_as_keyboard_focus) {
3532 		atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
3533 	} else if (!container->details->keyboard_focus) {
3534 
3535 		one_item_selected = FALSE;
3536 		l = container->details->icons;
3537 		while (l) {
3538 			icon = l->data;
3539 
3540 			if (icon->item == item) {
3541 				if (icon->is_selected) {
3542 					one_item_selected = TRUE;
3543 				} else {
3544 					break;
3545 				}
3546 			} else if (icon->is_selected) {
3547 				one_item_selected = FALSE;
3548 				break;
3549 			}
3550 
3551 			l = l->next;
3552 		}
3553 
3554 		if (one_item_selected) {
3555 			atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
3556 		}
3557 	}
3558 
3559 	return state_set;
3560 }
3561 
3562 static void
caja_icon_canvas_item_accessible_finalize(GObject * object)3563 caja_icon_canvas_item_accessible_finalize (GObject *object)
3564 {
3565 	CajaIconCanvasItemAccessiblePrivate *priv;
3566 	int i;
3567 
3568 	priv = GET_PRIV (object);
3569 
3570 	for (i = 0; i < LAST_ACTION; i++) {
3571 		g_free (priv->action_descriptions[i]);
3572 	}
3573 	g_free (priv->image_description);
3574 	g_free (priv->description);
3575 
3576         G_OBJECT_CLASS (caja_icon_canvas_item_accessible_parent_class)->finalize (object);
3577 }
3578 
3579 static void
caja_icon_canvas_item_accessible_initialize(AtkObject * accessible,gpointer widget)3580 caja_icon_canvas_item_accessible_initialize (AtkObject *accessible,
3581 						 gpointer widget)
3582 {
3583 	ATK_OBJECT_CLASS (caja_icon_canvas_item_accessible_parent_class)->initialize (accessible, widget);
3584 
3585 	atk_object_set_role (accessible, ATK_ROLE_ICON);
3586 }
3587 
3588 static void
caja_icon_canvas_item_accessible_class_init(CajaIconCanvasItemAccessibleClass * klass)3589 caja_icon_canvas_item_accessible_class_init (CajaIconCanvasItemAccessibleClass *klass)
3590 {
3591 	AtkObjectClass *aclass = ATK_OBJECT_CLASS (klass);
3592 	GObjectClass *oclass = G_OBJECT_CLASS (klass);
3593 
3594 	oclass->finalize = caja_icon_canvas_item_accessible_finalize;
3595 
3596 	aclass->initialize = caja_icon_canvas_item_accessible_initialize;
3597 
3598 	aclass->get_name = caja_icon_canvas_item_accessible_get_name;
3599 	aclass->get_description = caja_icon_canvas_item_accessible_get_description;
3600 	aclass->get_parent = caja_icon_canvas_item_accessible_get_parent;
3601 	aclass->get_index_in_parent = caja_icon_canvas_item_accessible_get_index_in_parent;
3602 	aclass->ref_state_set = caja_icon_canvas_item_accessible_ref_state_set;
3603 }
3604 
3605 static void
caja_icon_canvas_item_accessible_init(CajaIconCanvasItemAccessible * self)3606 caja_icon_canvas_item_accessible_init (CajaIconCanvasItemAccessible *self)
3607 {
3608 }
3609 
3610 /* dummy typedef */
3611 typedef AtkObjectFactory      CajaIconCanvasItemAccessibleFactory;
3612 typedef AtkObjectFactoryClass CajaIconCanvasItemAccessibleFactoryClass;
3613 
3614 G_DEFINE_TYPE (CajaIconCanvasItemAccessibleFactory, caja_icon_canvas_item_accessible_factory,
3615 	       ATK_TYPE_OBJECT_FACTORY);
3616 
3617 
3618 static AtkObject *
caja_icon_canvas_item_accessible_factory_create_accessible(GObject * for_object)3619 caja_icon_canvas_item_accessible_factory_create_accessible (GObject *for_object)
3620 {
3621     AtkObject *accessible;
3622     CajaIconCanvasItem *item;
3623     GString *item_text;
3624 
3625     item = CAJA_ICON_CANVAS_ITEM (for_object);
3626     g_assert (item != NULL);
3627 
3628     item_text = g_string_new (NULL);
3629     if (item->details->editable_text)
3630     {
3631         g_string_append (item_text, item->details->editable_text);
3632     }
3633     if (item->details->additional_text)
3634     {
3635         if (item_text->len > 0)
3636             g_string_append_c (item_text, ' ');
3637         g_string_append (item_text, item->details->additional_text);
3638     }
3639     item->details->text_util = gail_text_util_new ();
3640     gail_text_util_text_setup (item->details->text_util,
3641                                item_text->str);
3642     g_string_free (item_text, TRUE);
3643 
3644     accessible = g_object_new (caja_icon_canvas_item_accessible_get_type (), NULL);
3645     atk_object_initialize (accessible, for_object);
3646     return accessible;
3647 }
3648 
3649 static GType
caja_icon_canvas_item_accessible_factory_get_accessible_type(void)3650 caja_icon_canvas_item_accessible_factory_get_accessible_type (void)
3651 {
3652     return caja_icon_canvas_item_accessible_get_type ();
3653 }
3654 
3655 static void
caja_icon_canvas_item_accessible_factory_init(CajaIconCanvasItemAccessibleFactory * self)3656 caja_icon_canvas_item_accessible_factory_init (CajaIconCanvasItemAccessibleFactory *self)
3657 {
3658 }
3659 
3660 static void
caja_icon_canvas_item_accessible_factory_class_init(CajaIconCanvasItemAccessibleFactoryClass * klass)3661 caja_icon_canvas_item_accessible_factory_class_init (CajaIconCanvasItemAccessibleFactoryClass *klass)
3662 {
3663 	klass->create_accessible = caja_icon_canvas_item_accessible_factory_create_accessible;
3664 	klass->get_accessible_type = caja_icon_canvas_item_accessible_factory_get_accessible_type;
3665 }
3666 
3667