r1101: Big changes to internationalisation for pinboards and panels to fix problems
[rox-filer.git] / ROX-Filer / src / pinboard.c
blob7c8ed9f7ab62446ab0dafd9065a9ad108eed4bec
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* pinboard.c - icons on the desktop background */
24 #include "config.h"
26 #include <ctype.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <errno.h>
30 #include <parser.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkx.h>
33 #include <gtk/gtkinvisible.h>
34 #include <stdlib.h>
36 #include "global.h"
38 #include "pinboard.h"
39 #include "main.h"
40 #include "dnd.h"
41 #include "pixmaps.h"
42 #include "type.h"
43 #include "choices.h"
44 #include "support.h"
45 #include "gui_support.h"
46 #include "options.h"
47 #include "diritem.h"
48 #include "bind.h"
49 #include "icon.h"
50 #include "run.h"
52 /* The number of pixels between the bottom of the image and the top
53 * of the text.
55 #define GAP 4
57 /* The size of the border around the icon which is used when winking */
58 #define WINK_FRAME 2
60 /* Grid sizes */
61 #define GRID_STEP_FINE 2
62 #define GRID_STEP_MED 16
63 #define GRID_STEP_COARSE 32
65 static Icon *current_wink_icon = NULL;
66 static gint wink_timeout;
68 /* Used for the text colours (only) in the icons */
69 static GdkColor text_fg_col, text_bg_col;
71 /* Style that all the icons should use. NULL => regenerate from text_fg/bg */
72 static GtkStyle *pinicon_style = NULL;
74 Pinboard *current_pinboard = NULL;
75 static gint loading_pinboard = 0; /* Non-zero => loading */
77 static GdkColor mask_solid = {1, 1, 1, 1};
78 static GdkColor mask_transp = {0, 0, 0, 0};
79 static GdkGC *mask_gc = NULL;
81 /* Proxy window for DnD and clicks on the desktop */
82 static GtkWidget *proxy_invisible;
84 /* The window (owned by the wm) which root clicks are forwarded to.
85 * NULL if wm does not support forwarding clicks.
87 static GdkWindow *click_proxy_gdk_window = NULL;
88 static GdkAtom win_button_proxy; /* _WIN_DESKTOP_BUTTON_PROXY */
90 /* The Icon that was used to start the current drag, if any */
91 Icon *pinboard_drag_in_progress = NULL;
93 /* Used when dragging icons around... */
94 static gboolean pinboard_modified = FALSE;
96 typedef enum {
97 TEXT_BG_NONE = 0,
98 TEXT_BG_OUTLINE = 1,
99 TEXT_BG_SOLID = 2,
100 } TextBgType;
102 TextBgType o_text_bg = TEXT_BG_SOLID;
103 gboolean o_clamp_icons = TRUE;
104 static int o_grid_step = GRID_STEP_COARSE;
105 static int old_x, old_y; /* For dragging (mouse start) */
106 static int icon_old_x, icon_old_y; /* For dragging (icon start) */
108 /* Static prototypes */
109 static void set_size_and_shape(Icon *icon, int *rwidth, int *rheight);
110 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, Icon *icon);
111 static void mask_wink_border(Icon *icon, GdkColor *alpha);
112 static gint end_wink(gpointer data);
113 static gboolean button_release_event(GtkWidget *widget,
114 GdkEventButton *event,
115 Icon *icon);
116 static gboolean root_property_event(GtkWidget *widget,
117 GdkEventProperty *event,
118 gpointer data);
119 static gboolean root_button_press(GtkWidget *widget,
120 GdkEventButton *event,
121 gpointer data);
122 static gboolean enter_notify(GtkWidget *widget,
123 GdkEventCrossing *event,
124 Icon *icon);
125 static gboolean button_press_event(GtkWidget *widget,
126 GdkEventButton *event,
127 Icon *icon);
128 static gint icon_motion_notify(GtkWidget *widget,
129 GdkEventMotion *event,
130 Icon *icon);
131 static char *pin_from_file(guchar *line);
132 static gboolean add_root_handlers(void);
133 static GdkFilterReturn proxy_filter(GdkXEvent *xevent,
134 GdkEvent *event,
135 gpointer data);
136 static void snap_to_grid(int *x, int *y);
137 static void offset_from_centre(Icon *icon,
138 int width, int height,
139 int *x, int *y);
140 static void offset_to_centre(Icon *icon,
141 int width, int height,
142 int *x, int *y);
143 static gboolean drag_motion(GtkWidget *widget,
144 GdkDragContext *context,
145 gint x,
146 gint y,
147 guint time,
148 Icon *icon);
149 static void drag_set_pinicon_dest(Icon *icon);
150 static void drag_leave(GtkWidget *widget,
151 GdkDragContext *context,
152 guint32 time,
153 Icon *icon);
154 static void forward_root_clicks(void);
155 static gboolean bg_drag_motion(GtkWidget *widget,
156 GdkDragContext *context,
157 gint x,
158 gint y,
159 guint time,
160 gpointer data);
161 static gboolean bg_drag_leave(GtkWidget *widget,
162 GdkDragContext *context,
163 guint32 time,
164 gpointer data);
165 static void bg_expose(GdkRectangle *area);
166 static void drag_end(GtkWidget *widget,
167 GdkDragContext *context,
168 Icon *icon);
169 static void reshape_all(void);
170 static void pinboard_check_options(void);
171 static void pinboard_load_from_xml(xmlDocPtr doc);
173 /****************************************************************
174 * EXTERNAL INTERFACE *
175 ****************************************************************/
177 void pinboard_init(void)
179 option_add_string("pinboard_fg_colour", "#000", NULL);
180 option_add_string("pinboard_bg_colour", "#ddd", NULL);
182 option_add_int("pinboard_text_bg", TEXT_BG_SOLID, NULL);
183 option_add_int("pinboard_clamp_icons", 1, NULL);
184 option_add_int("pinboard_grid_step", GRID_STEP_COARSE, NULL);
185 option_add_notify(pinboard_check_options);
187 gdk_color_parse(option_get_static_string("pinboard_fg_colour"),
188 &text_fg_col);
189 gdk_color_parse(option_get_static_string("pinboard_bg_colour"),
190 &text_bg_col);
194 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
195 * and make it the current pinboard.
196 * Any existing pinned items are removed. You must call this
197 * at least once before using the pinboard. NULL disables the
198 * pinboard.
200 void pinboard_activate(guchar *name)
202 Pinboard *old_board = current_pinboard;
203 guchar *path, *slash;
205 /* Treat an empty name the same as NULL */
206 if (name && !*name)
207 name = NULL;
209 if (old_board)
211 pinboard_clear();
212 number_of_windows--;
215 if (!name)
217 if (number_of_windows < 1 && gtk_main_level() > 0)
218 gtk_main_quit();
219 return;
222 if (!add_root_handlers())
224 delayed_error(_("Another application is already "
225 "managing the pinboard!"));
226 return;
229 number_of_windows++;
231 slash = strchr(name, '/');
232 if (slash)
234 if (access(name, F_OK))
235 path = NULL; /* File does not (yet) exist */
236 else
237 path = g_strdup(name);
239 else
241 guchar *leaf;
243 leaf = g_strconcat("pb_", name, NULL);
244 path = choices_find_path_load(leaf, PROJECT);
245 g_free(leaf);
248 current_pinboard = g_new(Pinboard, 1);
249 current_pinboard->name = g_strdup(name);
250 current_pinboard->icons = NULL;
252 loading_pinboard++;
253 if (path)
255 xmlDocPtr doc;
256 doc = xmlParseFile(path);
257 if (doc)
259 pinboard_load_from_xml(doc);
260 xmlFreeDoc(doc);
262 else
264 parse_file(path, pin_from_file);
265 delayed_error(_("Your old pinboard file has been "
266 "converted to the new XML format."));
267 pinboard_save();
269 g_free(path);
271 else
272 pinboard_pin(home_dir, "Home",
273 4 + ICON_WIDTH / 2,
274 4 + ICON_HEIGHT / 2);
275 loading_pinboard--;
278 /* Add a new icon to the background.
279 * 'path' should be an absolute pathname.
280 * 'x' and 'y' are the coordinates of the point in the middle of the text
281 * if 'corner' is FALSE, and as the top-left corner of where the icon
282 * image should be if it is TRUE.
283 * 'name' is the name to use. If NULL then the leafname of path is used.
285 * names are in UTF-8.
287 void pinboard_pin(guchar *path, guchar *name, int x, int y)
289 Icon *icon;
290 int width, height;
292 g_return_if_fail(path != NULL);
293 g_return_if_fail(current_pinboard != NULL);
295 icon = g_new(Icon, 1);
296 icon->panel = NULL;
297 icon->selected = FALSE;
298 icon->src_path = g_strdup(path);
299 icon->path = icon_convert_path(path);
300 icon->mask = NULL;
301 icon->x = x;
302 icon->y = y;
303 icon->socket = NULL;
305 icon_hash_path(icon);
307 if (!name)
309 name = strrchr(icon->path, '/');
310 if (name && name[1])
311 name++;
312 else
313 name = icon->path;
316 #ifndef GTK2
318 gchar *loc_name;
320 loc_name = from_utf8(name);
321 icon->item = diritem_new(loc_name);
322 g_free(loc_name);
324 #else
325 icon->item = diritem_new(name);
326 #endif
327 diritem_restat(icon->path, icon->item);
329 icon->win = gtk_window_new(GTK_WINDOW_DIALOG);
330 gtk_window_set_wmclass(GTK_WINDOW(icon->win), "ROX-Pinboard", PROJECT);
332 icon->widget = gtk_drawing_area_new();
333 gtk_widget_set_name(icon->widget, "pinboard-icon");
334 #ifdef GTK2
335 icon->layout = gtk_widget_create_pango_layout(icon->widget, NULL);
336 pango_layout_set_width(icon->layout, 140 * PANGO_SCALE);
337 #endif
338 gtk_container_add(GTK_CONTAINER(icon->win), icon->widget);
339 drag_set_pinicon_dest(icon);
340 gtk_signal_connect(GTK_OBJECT(icon->widget), "drag_data_get",
341 GTK_SIGNAL_FUNC(drag_data_get), NULL);
343 gtk_widget_realize(icon->win);
344 gtk_widget_realize(icon->widget);
346 set_size_and_shape(icon, &width, &height);
347 snap_to_grid(&x, &y);
348 offset_from_centre(icon, width, height, &x, &y);
349 gtk_widget_set_uposition(icon->win, x, y);
350 /* Set the correct position in the icon */
351 offset_to_centre(icon, width, height, &x, &y);
352 icon->x = x;
353 icon->y = y;
355 make_panel_window(icon->win);
357 /* TODO: Use gdk function when it supports this type */
359 GdkAtom desktop_type;
361 desktop_type = gdk_atom_intern("_NET_WM_WINDOW_TYPE_DESKTOP",
362 FALSE);
363 gdk_property_change(icon->win->window,
364 gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE),
365 gdk_atom_intern("ATOM", FALSE), 32,
366 GDK_PROP_MODE_REPLACE, (guchar *) &desktop_type, 1);
369 gtk_widget_add_events(icon->widget,
370 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
371 GDK_BUTTON1_MOTION_MASK | GDK_ENTER_NOTIFY_MASK |
372 GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK);
373 gtk_signal_connect(GTK_OBJECT(icon->widget), "enter-notify-event",
374 GTK_SIGNAL_FUNC(enter_notify), icon);
375 gtk_signal_connect(GTK_OBJECT(icon->widget), "button-press-event",
376 GTK_SIGNAL_FUNC(button_press_event), icon);
377 gtk_signal_connect(GTK_OBJECT(icon->widget), "button-release-event",
378 GTK_SIGNAL_FUNC(button_release_event), icon);
379 gtk_signal_connect(GTK_OBJECT(icon->widget), "motion-notify-event",
380 GTK_SIGNAL_FUNC(icon_motion_notify), icon);
381 gtk_signal_connect(GTK_OBJECT(icon->widget), "expose-event",
382 GTK_SIGNAL_FUNC(draw_icon), icon);
383 gtk_signal_connect_object(GTK_OBJECT(icon->win), "destroy",
384 GTK_SIGNAL_FUNC(icon_destroyed), (gpointer) icon);
386 current_pinboard->icons = g_list_prepend(current_pinboard->icons,
387 icon);
388 icon_set_tip(icon);
389 gtk_widget_show_all(icon->win);
390 gdk_window_lower(icon->win->window);
392 if (!loading_pinboard)
393 pinboard_save();
396 /* Remove an icon from the pinboard */
397 void pinboard_unpin(Icon *icon)
399 g_return_if_fail(icon != NULL);
401 gtk_widget_destroy(icon->win);
402 pinboard_save();
405 /* Put a border around the icon, briefly.
406 * If icon is NULL then cancel any existing wink.
407 * The icon will automatically unhighlight unless timeout is FALSE,
408 * in which case you must call this function again (with NULL or another
409 * icon) to remove the highlight.
411 void pinboard_wink_item(Icon *icon, gboolean timeout)
413 if (current_wink_icon == icon)
414 return;
416 if (current_wink_icon)
418 mask_wink_border(current_wink_icon, &mask_transp);
419 if (wink_timeout != -1)
420 gtk_timeout_remove(wink_timeout);
423 current_wink_icon = icon;
425 if (current_wink_icon)
427 mask_wink_border(current_wink_icon, &mask_solid);
428 if (timeout)
429 wink_timeout = gtk_timeout_add(300, end_wink, NULL);
430 else
431 wink_timeout = -1;
435 /* Remove everything on the current pinboard and disables the pinboard.
436 * Does not change any files. Does not change number_of_windows.
438 void pinboard_clear(void)
440 GList *next;
442 g_return_if_fail(current_pinboard != NULL);
444 next = current_pinboard->icons;
445 while (next)
447 Icon *icon = (Icon *) next->data;
449 next = next->next;
451 gtk_widget_destroy(icon->win);
454 g_free(current_pinboard->name);
455 g_free(current_pinboard);
456 current_pinboard = NULL;
458 release_xdnd_proxy(GDK_ROOT_WINDOW());
459 gdk_window_remove_filter(GDK_ROOT_PARENT(), proxy_filter, NULL);
460 gdk_window_set_user_data(GDK_ROOT_PARENT(), NULL);
463 /* Icon's size, shape or appearance has changed - update the display */
464 void pinboard_reshape_icon(Icon *icon)
466 int x = icon->x, y = icon->y;
467 int width, height;
469 set_size_and_shape(icon, &width, &height);
470 gdk_window_resize(icon->win->window, width, height);
471 offset_from_centre(icon, width, height, &x, &y);
472 gtk_widget_set_uposition(icon->win, x, y);
473 gtk_widget_queue_draw(icon->win);
477 /****************************************************************
478 * INTERNAL FUNCTIONS *
479 ****************************************************************/
481 static void pinboard_check_options(void)
483 int old_text_bg = o_text_bg;
484 GdkColor n_fg, n_bg;
486 o_text_bg = option_get_int("pinboard_text_bg");
487 o_grid_step = option_get_int("pinboard_grid_step");
488 o_clamp_icons = option_get_int("pinboard_clamp_icons");
490 gdk_color_parse(option_get_static_string("pinboard_fg_colour"), &n_fg);
491 gdk_color_parse(option_get_static_string("pinboard_bg_colour"), &n_bg);
493 if (o_text_bg != old_text_bg ||
494 gdk_color_equal(&n_fg, &text_fg_col) == 0 ||
495 gdk_color_equal(&n_bg, &text_bg_col) == 0)
497 memcpy(&text_fg_col, &n_fg, sizeof(GdkColor));
498 memcpy(&text_bg_col, &n_bg, sizeof(GdkColor));
500 if (pinicon_style)
502 gtk_style_unref(pinicon_style);
503 pinicon_style = NULL;
506 if (current_pinboard)
507 reshape_all();
511 static gint end_wink(gpointer data)
513 pinboard_wink_item(NULL, FALSE);
514 return FALSE;
517 /* Make the wink border solid or transparent */
518 static void mask_wink_border(Icon *icon, GdkColor *alpha)
520 if (!current_pinboard)
521 return;
523 gdk_gc_set_foreground(mask_gc, alpha);
524 gdk_draw_rectangle(icon->mask, mask_gc, FALSE,
525 0, 0, icon->width - 1, icon->height - 1);
526 gdk_draw_rectangle(icon->mask, mask_gc, FALSE,
527 1, 1, icon->width - 3, icon->height - 3);
529 gtk_widget_shape_combine_mask(icon->win, icon->mask, 0, 0);
531 gtk_widget_draw(icon->widget, NULL);
534 #define TEXT_AT(dx, dy) \
535 gdk_draw_string(icon->mask, font, mask_gc, \
536 text_x + dx, y + dy, \
537 item->leafname);
539 /* Updates the name_width and layout fields, and resizes and masks the window.
540 * Also sets the style to pinicon_style, generating it if needed.
541 * Returns the new width and height.
543 static void set_size_and_shape(Icon *icon, int *rwidth, int *rheight)
545 int width, height;
546 int font_height;
547 MaskedPixmap *image = icon->item->image;
548 int iwidth = image->width;
549 int iheight = image->height;
550 DirItem *item = icon->item;
551 int text_x, text_y;
552 #ifndef GTK2
553 GdkFont *font;
554 #endif
556 if (!pinicon_style)
558 pinicon_style = gtk_style_copy(icon->widget->style);
559 memcpy(&pinicon_style->fg[GTK_STATE_NORMAL],
560 &text_fg_col, sizeof(GdkColor));
561 memcpy(&pinicon_style->bg[GTK_STATE_NORMAL],
562 &text_bg_col, sizeof(GdkColor));
564 gtk_widget_set_style(icon->widget, pinicon_style);
566 #ifndef GTK2
567 font = pinicon_style->font;
568 font_height = font->ascent + font->descent;
569 icon->name_width = gdk_string_measure(font, item->leafname);
570 #else
572 PangoRectangle logical;
573 pango_layout_set_text(icon->layout, icon->item->leafname, -1);
574 pango_layout_get_pixel_extents(icon->layout, NULL, &logical);
576 icon->name_width = logical.width - logical.x;
577 font_height = logical.height - logical.y;
579 #endif
581 width = MAX(iwidth, icon->name_width + 2) + 2 * WINK_FRAME;
582 height = iheight + GAP + (font_height + 2) + 2 * WINK_FRAME;
583 gtk_widget_set_usize(icon->win, width, height);
584 icon->width = width;
585 icon->height = height;
587 if (icon->mask)
588 gdk_pixmap_unref(icon->mask);
589 icon->mask = gdk_pixmap_new(icon->win->window, width, height, 1);
590 if (!mask_gc)
591 mask_gc = gdk_gc_new(icon->mask);
593 /* Clear the mask to transparent */
594 gdk_gc_set_foreground(mask_gc, &mask_transp);
595 gdk_draw_rectangle(icon->mask, mask_gc, TRUE, 0, 0, width, height);
597 gdk_gc_set_foreground(mask_gc, &mask_solid);
598 /* Make the icon area solid */
599 if (image->mask)
601 gdk_draw_pixmap(icon->mask, mask_gc, image->mask,
602 0, 0,
603 (width - iwidth) >> 1,
604 WINK_FRAME,
605 image->width,
606 image->height);
608 else
610 gdk_draw_rectangle(icon->mask, mask_gc, TRUE,
611 (width - iwidth) >> 1,
612 WINK_FRAME,
613 iwidth,
614 iheight);
617 gdk_gc_set_function(mask_gc, GDK_OR);
618 if (item->flags & ITEM_FLAG_SYMLINK)
620 gdk_draw_pixmap(icon->mask, mask_gc, im_symlink->mask,
621 0, 0, /* Source x,y */
622 (width - iwidth) >> 1, /* Dest x */
623 WINK_FRAME, /* Dest y */
624 -1, -1);
626 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
628 /* Note: Both mount state pixmaps must have the same mask */
629 gdk_draw_pixmap(icon->mask, mask_gc, im_mounted->mask,
630 0, 0, /* Source x,y */
631 (width - iwidth) >> 1, /* Dest x */
632 WINK_FRAME, /* Dest y */
633 -1, -1);
635 gdk_gc_set_function(mask_gc, GDK_COPY);
637 /* Mask off an area for the text (from o_text_bg) */
639 text_x = (width - icon->name_width) >> 1;
640 text_y = WINK_FRAME + iheight + GAP + 1;
642 #ifndef GTK2
643 if (o_text_bg == TEXT_BG_SOLID)
645 #endif
646 gdk_draw_rectangle(icon->mask, mask_gc, TRUE,
647 (width - (icon->name_width + 2)) >> 1,
648 WINK_FRAME + iheight + GAP,
649 icon->name_width + 2, font_height + 2);
650 #ifndef GTK2
652 else
654 int y = text_y + font->ascent;
656 TEXT_AT(0, 0);
658 if (o_text_bg == TEXT_BG_OUTLINE)
660 TEXT_AT(1, 0);
661 TEXT_AT(1, 1);
662 TEXT_AT(0, 1);
663 TEXT_AT(-1, 1);
664 TEXT_AT(-1, 0);
665 TEXT_AT(-1, -1);
666 TEXT_AT(0, -1);
667 TEXT_AT(1, -1);
670 #endif
672 gtk_widget_shape_combine_mask(icon->win, icon->mask, 0, 0);
674 *rwidth = width;
675 *rheight = height;
678 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, Icon *icon)
680 #ifndef GTK2
681 GdkFont *font = icon->widget->style->font;
682 #endif
683 int text_x, text_y;
684 DirItem *item = icon->item;
685 MaskedPixmap *image = item->image;
686 int iwidth = image->width;
687 int iheight = image->height;
688 int image_x;
689 GdkGC *gc = widget->style->black_gc;
690 GtkStateType state = icon->selected ? GTK_STATE_SELECTED
691 : GTK_STATE_NORMAL;
693 image_x = (icon->width - iwidth) >> 1;
695 /* TODO: If the shape extension is missing we might need to set
696 * the clip mask here...
698 gdk_draw_pixmap(widget->window, gc,
699 image->pixmap,
700 0, 0,
701 image_x,
702 WINK_FRAME,
703 iwidth,
704 iheight);
706 if (item->flags & ITEM_FLAG_SYMLINK)
708 gdk_gc_set_clip_origin(gc, image_x, WINK_FRAME);
709 gdk_gc_set_clip_mask(gc, im_symlink->mask);
710 gdk_draw_pixmap(widget->window, gc,
711 im_symlink->pixmap,
712 0, 0, /* Source x,y */
713 image_x, WINK_FRAME, /* Dest x,y */
714 -1, -1);
715 gdk_gc_set_clip_mask(gc, NULL);
716 gdk_gc_set_clip_origin(gc, 0, 0);
718 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
720 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
721 ? im_mounted
722 : im_unmounted;
724 gdk_gc_set_clip_origin(gc, image_x, WINK_FRAME);
725 gdk_gc_set_clip_mask(gc, mp->mask);
726 gdk_draw_pixmap(widget->window, gc,
727 mp->pixmap,
728 0, 0, /* Source x,y */
729 image_x, WINK_FRAME, /* Dest x,y */
730 -1, -1);
731 gdk_gc_set_clip_mask(gc, NULL);
732 gdk_gc_set_clip_origin(gc, 0, 0);
735 text_x = (icon->width - icon->name_width) >> 1;
736 text_y = WINK_FRAME + iheight + GAP + 1;
738 if (o_text_bg != TEXT_BG_NONE)
740 #ifdef GTK2
741 PangoRectangle logical;
742 int font_height;
744 pango_layout_get_pixel_extents(icon->layout, NULL, &logical);
745 font_height = logical.height - logical.y;
746 #else
747 int font_height = font->ascent + font->descent;
748 #endif
750 gtk_paint_flat_box(widget->style, widget->window,
751 state,
752 GTK_SHADOW_NONE,
753 NULL, widget, "text",
754 text_x - 1,
755 text_y - 1,
756 icon->name_width + 2,
757 font_height + 2);
760 #ifdef GTK2
761 gtk_paint_layout(widget->style, widget->window,
762 state,
763 FALSE, NULL, widget, "text",
764 text_x,
765 text_y,
766 icon->layout);
767 #else
768 gtk_paint_string(widget->style, widget->window,
769 state,
770 NULL, widget, "text",
771 text_x,
772 text_y + font->ascent,
773 item->leafname);
774 #endif
776 if (current_wink_icon == icon)
778 gdk_draw_rectangle(icon->widget->window,
779 icon->widget->style->white_gc,
780 FALSE,
781 0, 0, icon->width - 1, icon->height - 1);
782 gdk_draw_rectangle(icon->widget->window,
783 icon->widget->style->black_gc,
784 FALSE,
785 1, 1, icon->width - 3, icon->height - 3);
788 return FALSE;
791 static gboolean root_property_event(GtkWidget *widget,
792 GdkEventProperty *event,
793 gpointer data)
795 if (event->atom == win_button_proxy &&
796 event->state == GDK_PROPERTY_NEW_VALUE)
798 /* Setup forwarding on the new proxy window, if possible */
799 forward_root_clicks();
802 return FALSE;
805 static gboolean root_button_press(GtkWidget *widget,
806 GdkEventButton *event,
807 gpointer data)
809 BindAction action;
811 action = bind_lookup_bev(BIND_PINBOARD, event);
813 switch (action)
815 case ACT_CLEAR_SELECTION:
816 icon_select_only(NULL);
817 break;
818 case ACT_POPUP_MENU:
819 dnd_motion_ungrab();
820 icon_show_menu(event, NULL, NULL);
821 break;
822 case ACT_IGNORE:
823 break;
824 default:
825 g_warning("Unsupported action : %d\n", action);
826 break;
829 return TRUE;
832 static gboolean enter_notify(GtkWidget *widget,
833 GdkEventCrossing *event,
834 Icon *icon)
836 icon_may_update(icon);
838 return FALSE;
841 static void perform_action(Icon *icon, GdkEventButton *event)
843 BindAction action;
845 action = bind_lookup_bev(BIND_PINBOARD_ICON, event);
847 switch (action)
849 case ACT_OPEN_ITEM:
850 dnd_motion_ungrab();
851 pinboard_wink_item(icon, TRUE);
852 if (event->type == GDK_2BUTTON_PRESS)
853 icon_set_selected(icon, FALSE);
854 run_diritem(icon->path, icon->item, NULL, NULL, FALSE);
855 break;
856 case ACT_EDIT_ITEM:
857 dnd_motion_ungrab();
858 pinboard_wink_item(icon, TRUE);
859 if (event->type == GDK_2BUTTON_PRESS)
860 icon_set_selected(icon, FALSE);
861 run_diritem(icon->path, icon->item, NULL, NULL, TRUE);
862 break;
863 case ACT_POPUP_MENU:
864 dnd_motion_ungrab();
865 icon_show_menu(event, icon, NULL);
866 break;
867 case ACT_MOVE_ICON:
868 old_x = event->x_root;
869 old_y = event->y_root;
870 icon_old_x = icon->x;
871 icon_old_y = icon->y;
872 dnd_motion_start(MOTION_REPOSITION);
873 break;
874 case ACT_PRIME_AND_SELECT:
875 if (!icon->selected)
876 icon_select_only(icon);
877 dnd_motion_start(MOTION_READY_FOR_DND);
878 break;
879 case ACT_PRIME_AND_TOGGLE:
880 icon_set_selected(icon, !icon->selected);
881 dnd_motion_start(MOTION_READY_FOR_DND);
882 break;
883 case ACT_PRIME_FOR_DND:
884 dnd_motion_start(MOTION_READY_FOR_DND);
885 break;
886 case ACT_TOGGLE_SELECTED:
887 icon_set_selected(icon, !icon->selected);
888 break;
889 case ACT_SELECT_EXCL:
890 icon_select_only(icon);
891 break;
892 case ACT_IGNORE:
893 break;
894 default:
895 g_warning("Unsupported action : %d\n", action);
896 break;
900 static gboolean button_release_event(GtkWidget *widget,
901 GdkEventButton *event,
902 Icon *icon)
904 if (pinboard_modified)
905 pinboard_save();
907 if (dnd_motion_release(event))
908 return TRUE;
910 perform_action(icon, event);
912 return TRUE;
915 static gboolean button_press_event(GtkWidget *widget,
916 GdkEventButton *event,
917 Icon *icon)
919 if (dnd_motion_press(widget, event))
920 perform_action(icon, event);
922 return TRUE;
925 /* Return a text/uri-list of all the icons in the list.
926 * TODO: Use code in icon.c instead.
928 static guchar *create_uri_list(GList *list)
930 GString *tmp;
931 guchar *retval;
932 guchar *leader;
934 tmp = g_string_new(NULL);
935 leader = g_strdup_printf("file://%s", our_host_name_for_dnd());
937 for (; list; list = list->next)
939 Icon *icon = (Icon *) list->data;
941 g_string_append(tmp, leader);
942 g_string_append(tmp, icon->path);
943 g_string_append(tmp, "\r\n");
946 g_free(leader);
947 retval = tmp->str;
948 g_string_free(tmp, FALSE);
950 return retval;
953 static void start_drag(Icon *icon, GdkEventMotion *event)
955 GtkWidget *widget = icon->widget;
957 if (!icon->selected)
959 tmp_icon_selected = TRUE;
960 icon_select_only(icon);
963 g_return_if_fail(icon_selection != NULL);
965 pinboard_drag_in_progress = icon;
967 if (icon_selection->next == NULL)
968 drag_one_item(widget, event, icon->path, icon->item, NULL);
969 else
971 guchar *uri_list;
973 uri_list = create_uri_list(icon_selection);
974 drag_selection(widget, event, uri_list);
975 g_free(uri_list);
979 /* An icon is being dragged around... */
980 static gint icon_motion_notify(GtkWidget *widget,
981 GdkEventMotion *event,
982 Icon *icon)
984 int x, y;
985 int dx,dy;
986 int width, height;
988 if (motion_state == MOTION_READY_FOR_DND)
990 if (dnd_motion_moved(event))
991 start_drag(icon, event);
992 return TRUE;
994 else if (motion_state != MOTION_REPOSITION)
995 return FALSE;
997 /* How far the pointer has moved since the drag started */
998 dx = event->x_root - old_x;
999 dy = event->y_root - old_y;
1001 x = icon_old_x + dx;
1002 y = icon_old_y + dy;
1004 snap_to_grid(&x, &y);
1006 if (icon->x == x && icon->y == y)
1007 return TRUE;
1009 icon->x = x;
1010 icon->y = y;
1011 gdk_window_get_size(icon->win->window, &width, &height);
1012 offset_from_centre(icon, width, height, &x, &y);
1014 gdk_window_move(icon->win->window, x, y);
1016 /* Store the fixed position for the center of the icon */
1017 offset_to_centre(icon, width, height, &x, &y);
1018 icon->x = x;
1019 icon->y = y;
1021 pinboard_modified = TRUE;
1023 return TRUE;
1026 /* Create one pinboard icon for each icon in the doc */
1027 static void pinboard_load_from_xml(xmlDocPtr doc)
1029 xmlNodePtr node, root;
1030 char *tmp, *label, *path;
1031 int x, y;
1033 root = xmlDocGetRootElement(doc);
1035 for (node = root->xmlChildrenNode; node; node = node->next)
1037 if (node->type != XML_ELEMENT_NODE)
1038 continue;
1039 if (strcmp(node->name, "icon") != 0)
1040 continue;
1042 tmp = xmlGetProp(node, "x");
1043 if (!tmp)
1044 continue;
1045 x = atoi(tmp);
1046 g_free(tmp);
1048 tmp = xmlGetProp(node, "y");
1049 if (!tmp)
1050 continue;
1051 y = atoi(tmp);
1052 g_free(tmp);
1054 label = xmlGetProp(node, "label");
1055 if (!label)
1056 label = g_strdup("<missing label>");
1057 path = xmlNodeGetContent(node);
1058 if (!path)
1059 path = g_strdup("<missing path>");
1061 pinboard_pin(path, label, x, y);
1063 g_free(path);
1064 g_free(label);
1068 /* Called for each line in the pinboard file while loading a new board.
1069 * Only used for old-format files when converting to XML.
1071 static char *pin_from_file(guchar *line)
1073 guchar *leaf = NULL;
1074 gchar *u8_path, *u8_leaf;
1075 int x, y, n;
1077 if (*line == '<')
1079 guchar *end;
1081 end = strchr(line + 1, '>');
1082 if (!end)
1083 return _("Missing '>' in icon label");
1085 leaf = g_strndup(line + 1, end - line - 1);
1087 line = end + 1;
1089 while (isspace(*line))
1090 line++;
1091 if (*line != ',')
1092 return _("Missing ',' after icon label");
1093 line++;
1096 if (sscanf(line, " %d , %d , %n", &x, &y, &n) < 2)
1097 return NULL; /* Ignore format errors */
1099 u8_path = to_utf8(line + n);
1100 u8_leaf = to_utf8(leaf);
1101 g_free(leaf);
1103 pinboard_pin(u8_path, u8_leaf, x, y);
1105 g_free(u8_path);
1106 g_free(u8_leaf);
1108 return NULL;
1111 /* Make sure that clicks and drops on the root window come to us...
1112 * False if an error occurred (ie, someone else is using it).
1114 static gboolean add_root_handlers(void)
1116 GdkWindow *root;
1118 if (!proxy_invisible)
1120 win_button_proxy = gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY",
1121 FALSE);
1122 proxy_invisible = gtk_invisible_new();
1123 gtk_widget_show(proxy_invisible);
1125 gdk_window_add_filter(proxy_invisible->window,
1126 proxy_filter, NULL);
1129 gtk_signal_connect(GTK_OBJECT(proxy_invisible),
1130 "property_notify_event",
1131 GTK_SIGNAL_FUNC(root_property_event), NULL);
1132 gtk_signal_connect(GTK_OBJECT(proxy_invisible),
1133 "button_press_event",
1134 GTK_SIGNAL_FUNC(root_button_press), NULL);
1136 /* Drag and drop handlers */
1137 drag_set_pinboard_dest(proxy_invisible);
1138 gtk_signal_connect(GTK_OBJECT(proxy_invisible), "drag_motion",
1139 GTK_SIGNAL_FUNC(bg_drag_motion),
1140 NULL);
1141 gtk_signal_connect(GTK_OBJECT(proxy_invisible), "drag_leave",
1142 GTK_SIGNAL_FUNC(bg_drag_leave),
1143 NULL);
1146 root = gdk_window_lookup(GDK_ROOT_WINDOW());
1147 if (!root)
1148 root = gdk_window_foreign_new(GDK_ROOT_WINDOW());
1150 if (!setup_xdnd_proxy(GDK_ROOT_WINDOW(), proxy_invisible->window))
1151 return FALSE;
1153 /* Forward events from the root window to our proxy window */
1154 gdk_window_add_filter(GDK_ROOT_PARENT(), proxy_filter, NULL);
1155 gdk_window_set_user_data(GDK_ROOT_PARENT(), proxy_invisible);
1156 gdk_window_set_events(GDK_ROOT_PARENT(),
1157 gdk_window_get_events(GDK_ROOT_PARENT()) |
1158 GDK_EXPOSURE_MASK |
1159 GDK_PROPERTY_CHANGE_MASK);
1161 forward_root_clicks();
1163 return TRUE;
1166 /* See if the window manager is offering to forward root window clicks.
1167 * If so, grab them. Otherwise, do nothing.
1168 * Call this whenever the _WIN_DESKTOP_BUTTON_PROXY property changes.
1170 static void forward_root_clicks(void)
1172 click_proxy_gdk_window = find_click_proxy_window();
1173 if (!click_proxy_gdk_window)
1174 return;
1176 /* Events on the wm's proxy are dealt with by our proxy widget */
1177 gdk_window_set_user_data(click_proxy_gdk_window, proxy_invisible);
1178 gdk_window_add_filter(click_proxy_gdk_window, proxy_filter, NULL);
1180 /* The proxy window for clicks sends us button press events with
1181 * SubstructureNotifyMask. We need StructureNotifyMask to receive
1182 * DestroyNotify events, too.
1184 XSelectInput(GDK_DISPLAY(),
1185 GDK_WINDOW_XWINDOW(click_proxy_gdk_window),
1186 SubstructureNotifyMask | StructureNotifyMask);
1189 /* Write the current state of the pinboard to the current pinboard file */
1190 void pinboard_save(void)
1192 guchar *save = NULL;
1193 guchar *save_new = NULL;
1194 GList *next;
1195 xmlDocPtr doc = NULL;
1196 xmlNodePtr root;
1198 g_return_if_fail(current_pinboard != NULL);
1200 pinboard_modified = FALSE;
1202 if (strchr(current_pinboard->name, '/'))
1203 save = g_strdup(current_pinboard->name);
1204 else
1206 guchar *leaf;
1208 leaf = g_strconcat("pb_", current_pinboard->name, NULL);
1209 save = choices_find_path_save(leaf, PROJECT, TRUE);
1210 g_free(leaf);
1213 if (!save)
1214 return;
1216 doc = xmlNewDoc("1.0");
1217 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "pinboard", NULL));
1219 root = xmlDocGetRootElement(doc);
1221 for (next = current_pinboard->icons; next; next = next->next)
1223 xmlNodePtr tree;
1224 Icon *icon = (Icon *) next->data;
1225 char *tmp;
1227 tree = xmlNewTextChild(root, NULL, "icon", icon->src_path);
1229 tmp = g_strdup_printf("%d", icon->x);
1230 xmlSetProp(tree, "x", tmp);
1231 g_free(tmp);
1233 tmp = g_strdup_printf("%d", icon->y);
1234 xmlSetProp(tree, "y", tmp);
1235 g_free(tmp);
1237 #ifndef GTK2
1239 gchar *u8;
1240 u8 = to_utf8(icon->item->leafname);
1241 xmlSetProp(tree, "label", u8);
1242 g_free(u8);
1244 #else
1245 xmlSetProp(tree, "label", icon->item->leafname);
1246 #endif
1249 save_new = g_strconcat(save, ".new", NULL);
1250 if (save_xml_file(doc, save_new) || rename(save_new, save))
1251 delayed_error(_("Error saving pinboard %s: %s"),
1252 save, g_strerror(errno));
1253 g_free(save_new);
1255 g_free(save);
1256 if (doc)
1257 xmlFreeDoc(doc);
1261 * Filter that translates proxied events from virtual root windows into normal
1262 * Gdk events for the proxy_invisible widget. Stolen from gmc.
1264 * Also gets events from the root window.
1266 static GdkFilterReturn proxy_filter(GdkXEvent *xevent,
1267 GdkEvent *event,
1268 gpointer data)
1270 XEvent *xev;
1271 GdkWindow *proxy = proxy_invisible->window;
1272 GdkRectangle area;
1274 xev = xevent;
1276 switch (xev->type) {
1277 case ButtonPress:
1278 case ButtonRelease:
1279 /* Translate button events into events that come from
1280 * the proxy window, so that we can catch them as a
1281 * signal from the invisible widget.
1283 if (xev->type == ButtonPress)
1284 event->button.type = GDK_BUTTON_PRESS;
1285 else
1286 event->button.type = GDK_BUTTON_RELEASE;
1288 gdk_window_ref(proxy);
1290 event->button.window = proxy;
1291 event->button.send_event = xev->xbutton.send_event;
1292 event->button.time = xev->xbutton.time;
1293 event->button.x_root = xev->xbutton.x_root;
1294 event->button.y_root = xev->xbutton.y_root;
1295 event->button.x = xev->xbutton.x;
1296 event->button.y = xev->xbutton.y;
1297 event->button.state = xev->xbutton.state;
1298 event->button.button = xev->xbutton.button;
1299 #ifdef GTK2
1300 event->button.axes = NULL;
1301 #endif
1303 return GDK_FILTER_TRANSLATE;
1305 case Expose:
1306 area.x = xev->xexpose.x;
1307 area.y = xev->xexpose.y;
1308 area.width = xev->xexpose.width;
1309 area.height = xev->xexpose.height;
1310 bg_expose(&area);
1311 return GDK_FILTER_REMOVE;
1313 case DestroyNotify:
1314 /* XXX: I have no idea why this helps, but it does! */
1315 /* The proxy window was destroyed (i.e. the window
1316 * manager died), so we have to cope with it
1318 if (((GdkEventAny *) event)->window == proxy)
1319 gdk_window_destroy_notify(proxy);
1321 return GDK_FILTER_REMOVE;
1323 default:
1324 break;
1327 return GDK_FILTER_CONTINUE;
1330 static void snap_to_grid(int *x, int *y)
1332 *x = ((*x + o_grid_step / 2) / o_grid_step) * o_grid_step;
1333 *y = ((*y + o_grid_step / 2) / o_grid_step) * o_grid_step;
1336 /* Convert (x,y) from a centre point to a window position */
1337 static void offset_from_centre(Icon *icon,
1338 int width, int height,
1339 int *x, int *y)
1341 *x -= width >> 1;
1342 *y -= height >> 1;
1343 *x = CLAMP(*x, 0, screen_width - (o_clamp_icons ? width : 0));
1344 *y = CLAMP(*y, 0, screen_height - (o_clamp_icons ? height : 0));
1347 /* Convert (x,y) from a window position to a centre point */
1348 static void offset_to_centre(Icon *icon,
1349 int width, int height,
1350 int *x, int *y)
1352 *x += width >> 1;
1353 *y += height >> 1;
1356 /* Same as drag_set_dest(), but for pinboard icons */
1357 static void drag_set_pinicon_dest(Icon *icon)
1359 GtkObject *obj = GTK_OBJECT(icon->widget);
1361 make_drop_target(icon->widget, 0);
1363 gtk_signal_connect(obj, "drag_motion",
1364 GTK_SIGNAL_FUNC(drag_motion), icon);
1365 gtk_signal_connect(obj, "drag_leave",
1366 GTK_SIGNAL_FUNC(drag_leave), icon);
1367 gtk_signal_connect(obj, "drag_end",
1368 GTK_SIGNAL_FUNC(drag_end), icon);
1371 /* Called during the drag when the mouse is in a widget registered
1372 * as a drop target. Returns TRUE if we can accept the drop.
1374 static gboolean drag_motion(GtkWidget *widget,
1375 GdkDragContext *context,
1376 gint x,
1377 gint y,
1378 guint time,
1379 Icon *icon)
1381 GdkDragAction action = context->suggested_action;
1382 char *type = NULL;
1383 DirItem *item = icon->item;
1385 if (gtk_drag_get_source_widget(context) == widget)
1386 goto out; /* Can't drag something to itself! */
1388 if (icon->selected)
1389 goto out; /* Can't drag a selection to itself */
1391 type = dnd_motion_item(context, &item);
1393 if (!item)
1394 type = NULL;
1395 out:
1396 /* We actually must pretend to accept the drop, even if the
1397 * directory isn't writeable, so that the spring-opening
1398 * thing works.
1401 /* Don't allow drops to non-writeable directories */
1402 if (option_get_int("dnd_spring_open") == FALSE &&
1403 type == drop_dest_dir &&
1404 access(icon->path, W_OK) != 0)
1406 type = NULL;
1409 g_dataset_set_data(context, "drop_dest_type", type);
1410 if (type)
1412 gdk_drag_status(context, action, time);
1413 g_dataset_set_data_full(context, "drop_dest_path",
1414 g_strdup(icon->path), g_free);
1415 if (type == drop_dest_dir)
1416 dnd_spring_load(context, NULL);
1418 pinboard_wink_item(icon, FALSE);
1421 return type != NULL;
1424 static gboolean pinboard_shadow = FALSE;
1425 static gint shadow_x, shadow_y;
1426 #define SHADOW_SIZE (ICON_WIDTH)
1428 static void bg_expose(GdkRectangle *area)
1430 GdkWindow *root = GDK_ROOT_PARENT();
1431 static GdkGC *shadow_gc = NULL;
1432 static GdkColor white, black;
1434 if (!pinboard_shadow)
1436 /* XXX: Should just disable the events */
1437 return;
1440 if (!shadow_gc)
1442 GdkColormap *cm;
1444 cm = gdk_window_get_colormap(root);
1445 shadow_gc = gdk_gc_new(root);
1446 gdk_color_white(cm, &white);
1447 gdk_color_black(cm, &black);
1450 gdk_gc_set_clip_rectangle(shadow_gc, area);
1451 gdk_gc_set_foreground(shadow_gc, &white);
1452 gdk_draw_rectangle(root, shadow_gc, FALSE, shadow_x, shadow_y,
1453 SHADOW_SIZE, SHADOW_SIZE);
1454 gdk_gc_set_foreground(shadow_gc, &black);
1455 gdk_draw_rectangle(root, shadow_gc, FALSE, shadow_x + 1, shadow_y + 1,
1456 SHADOW_SIZE - 2, SHADOW_SIZE - 2);
1457 gdk_gc_set_clip_rectangle(shadow_gc, NULL);
1460 /* Draw a 'shadow' under an icon being dragged, showing where
1461 * it will land.
1463 static void pinboard_set_shadow(gboolean on)
1465 GdkWindow *root = GDK_ROOT_PARENT();
1467 if (pinboard_shadow)
1469 gdk_window_clear_area_e(root, shadow_x, shadow_y,
1470 SHADOW_SIZE + 1, SHADOW_SIZE + 1);
1473 if (on)
1475 int old_x = shadow_x, old_y = shadow_y;
1477 gdk_window_get_pointer(root, &shadow_x, &shadow_y, NULL);
1478 snap_to_grid(&shadow_x, &shadow_y);
1479 shadow_x -= SHADOW_SIZE / 2;
1480 shadow_y -= SHADOW_SIZE / 2;
1483 if (pinboard_shadow && shadow_x == old_x && shadow_y == old_y)
1484 return;
1486 gdk_window_clear_area_e(root, shadow_x, shadow_y,
1487 SHADOW_SIZE + 1, SHADOW_SIZE + 1);
1490 pinboard_shadow = on;
1493 /* Called when dragging some pinboard icons finishes */
1494 void pinboard_move_icons(void)
1496 int x = shadow_x, y = shadow_y;
1497 Icon *icon = pinboard_drag_in_progress;
1498 int width, height;
1500 g_return_if_fail(icon != NULL);
1502 x += SHADOW_SIZE / 2;
1503 y += SHADOW_SIZE / 2;
1504 snap_to_grid(&x, &y);
1506 if (icon->x == x && icon->y == y)
1507 return;
1509 icon->x = x;
1510 icon->y = y;
1511 gdk_window_get_size(icon->win->window, &width, &height);
1512 offset_from_centre(icon, width, height, &x, &y);
1514 gdk_window_move(icon->win->window, x, y);
1516 pinboard_save();
1519 static void drag_leave(GtkWidget *widget,
1520 GdkDragContext *context,
1521 guint32 time,
1522 Icon *icon)
1524 pinboard_wink_item(NULL, FALSE);
1525 dnd_spring_abort();
1528 static gboolean bg_drag_leave(GtkWidget *widget,
1529 GdkDragContext *context,
1530 guint32 time,
1531 gpointer data)
1533 pinboard_set_shadow(FALSE);
1534 return TRUE;
1538 static gboolean bg_drag_motion(GtkWidget *widget,
1539 GdkDragContext *context,
1540 gint x,
1541 gint y,
1542 guint time,
1543 gpointer data)
1545 /* Dragging from the pinboard to the pinboard is not allowed */
1547 if (!provides(context, text_uri_list))
1548 return FALSE;
1550 pinboard_set_shadow(TRUE);
1552 gdk_drag_status(context, context->suggested_action, time);
1553 return TRUE;
1556 static void drag_end(GtkWidget *widget,
1557 GdkDragContext *context,
1558 Icon *icon)
1560 pinboard_drag_in_progress = NULL;
1561 if (tmp_icon_selected)
1563 icon_select_only(NULL);
1564 tmp_icon_selected = FALSE;
1568 /* Something which affects all the icons has changed - reshape
1569 * and redraw all of them.
1571 static void reshape_all(void)
1573 GList *next;
1575 g_return_if_fail(current_pinboard != NULL);
1577 for (next = current_pinboard->icons; next; next = next->next)
1579 Icon *icon = (Icon *) next->data;
1580 pinboard_reshape_icon(icon);