r729: Pinboard background stuff doesn't work for Gtk+ 2.0. Always use
[rox-filer.git] / ROX-Filer / src / pinboard.c
blobdec7e4b79257dd57562d548060353f5cda2a434e
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2001, 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 <gtk/gtk.h>
31 #include <gdk/gdkx.h>
32 #include <gtk/gtkinvisible.h>
34 #include "global.h"
36 #include "pinboard.h"
37 #include "main.h"
38 #include "dnd.h"
39 #include "pixmaps.h"
40 #include "type.h"
41 #include "choices.h"
42 #include "support.h"
43 #include "gui_support.h"
44 #include "options.h"
45 #include "dir.h"
46 #include "bind.h"
47 #include "icon.h"
48 #include "run.h"
50 /* The number of pixels between the bottom of the image and the top
51 * of the text.
53 #define GAP 4
55 /* The size of the border around the icon which is used when winking */
56 #define WINK_FRAME 2
58 /* Grid sizes */
59 #define GRID_STEP_FINE 2
60 #define GRID_STEP_MED 16
61 #define GRID_STEP_COARSE 32
63 static Icon *current_wink_icon = NULL;
64 static gint wink_timeout;
66 /* Used for the text colours (only) in the icons */
67 static GdkColor text_fg_col, text_bg_col;
69 /* Style that all the icons should use. NULL => regenerate from text_fg/bg */
70 static GtkStyle *pinicon_style = NULL;
72 Pinboard *current_pinboard = NULL;
73 static gint loading_pinboard = 0; /* Non-zero => loading */
75 static GdkColor mask_solid = {1, 1, 1, 1};
76 static GdkColor mask_transp = {0, 0, 0, 0};
77 static GdkGC *mask_gc = NULL;
79 /* Proxy window for DnD and clicks on the desktop */
80 static GtkWidget *proxy_invisible;
82 /* The window (owned by the wm) which root clicks are forwarded to.
83 * NULL if wm does not support forwarding clicks.
85 static GdkWindow *click_proxy_gdk_window = NULL;
86 static GdkAtom win_button_proxy; /* _WIN_DESKTOP_BUTTON_PROXY */
88 static gboolean pinboard_drag_in_progress = FALSE;
90 /* Used when dragging icons around... */
91 static gboolean pinboard_modified = FALSE;
93 typedef enum {
94 TEXT_BG_NONE = 0,
95 TEXT_BG_OUTLINE = 1,
96 TEXT_BG_SOLID = 2,
97 } TextBgType;
99 TextBgType o_text_bg = TEXT_BG_SOLID;
100 gboolean o_clamp_icons = TRUE;
101 static int o_grid_step = GRID_STEP_COARSE;
102 static int old_x, old_y; /* For dragging (mouse start) */
103 static int icon_old_x, icon_old_y; /* For dragging (icon start) */
105 /* Static prototypes */
106 static void set_size_and_shape(Icon *icon, int *rwidth, int *rheight);
107 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, Icon *icon);
108 static void mask_wink_border(Icon *icon, GdkColor *alpha);
109 static gint end_wink(gpointer data);
110 static gboolean button_release_event(GtkWidget *widget,
111 GdkEventButton *event,
112 Icon *icon);
113 static gboolean root_property_event(GtkWidget *widget,
114 GdkEventProperty *event,
115 gpointer data);
116 static gboolean root_button_press(GtkWidget *widget,
117 GdkEventButton *event,
118 gpointer data);
119 static gboolean enter_notify(GtkWidget *widget,
120 GdkEventCrossing *event,
121 Icon *icon);
122 static gboolean button_press_event(GtkWidget *widget,
123 GdkEventButton *event,
124 Icon *icon);
125 static gint icon_motion_notify(GtkWidget *widget,
126 GdkEventMotion *event,
127 Icon *icon);
128 static char *pin_from_file(guchar *line);
129 static gboolean add_root_handlers(void);
130 static GdkFilterReturn proxy_filter(GdkXEvent *xevent,
131 GdkEvent *event,
132 gpointer data);
133 static void snap_to_grid(int *x, int *y);
134 static void offset_from_centre(Icon *icon,
135 int width, int height,
136 int *x, int *y);
137 static void offset_to_centre(Icon *icon,
138 int width, int height,
139 int *x, int *y);
140 static gboolean drag_motion(GtkWidget *widget,
141 GdkDragContext *context,
142 gint x,
143 gint y,
144 guint time,
145 Icon *icon);
146 static void drag_set_pinicon_dest(Icon *icon);
147 static void drag_leave(GtkWidget *widget,
148 GdkDragContext *context,
149 guint32 time,
150 Icon *icon);
151 static void forward_root_clicks(void);
152 static gboolean bg_drag_motion(GtkWidget *widget,
153 GdkDragContext *context,
154 gint x,
155 gint y,
156 guint time,
157 gpointer data);
158 static void drag_end(GtkWidget *widget,
159 GdkDragContext *context,
160 Icon *icon);
161 static void reshape_all(void);
162 static void pinboard_check_options(void);
164 /****************************************************************
165 * EXTERNAL INTERFACE *
166 ****************************************************************/
168 void pinboard_init(void)
170 GtkStyle *style;
172 option_add_string("pinboard_fg_colour", "#000", NULL);
173 option_add_string("pinboard_bg_colour", "#ddd", NULL);
175 option_add_int("pinboard_text_bg", TEXT_BG_SOLID, NULL);
176 option_add_int("pinboard_clamp_icons", 1, NULL);
177 option_add_int("pinboard_grid_step", GRID_STEP_COARSE, NULL);
178 option_add_notify(pinboard_check_options);
180 style = gtk_widget_get_default_style();
182 gdk_color_parse(option_get_static_string("pinboard_fg_colour"),
183 &text_fg_col);
184 gdk_color_parse(option_get_static_string("pinboard_bg_colour"),
185 &text_bg_col);
189 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
190 * and make it the current pinboard.
191 * Any existing pinned items are removed. You must call this
192 * at least once before using the pinboard. NULL disables the
193 * pinboard.
195 void pinboard_activate(guchar *name)
197 Pinboard *old_board = current_pinboard;
198 guchar *path, *slash;
200 /* Treat an empty name the same as NULL */
201 if (!*name)
202 name = NULL;
204 if (old_board)
206 pinboard_clear();
207 number_of_windows--;
210 if (!name)
212 if (number_of_windows < 1 && gtk_main_level() > 0)
213 gtk_main_quit();
214 return;
217 if (!add_root_handlers())
219 delayed_error(PROJECT, _("Another application is already "
220 "managing the pinboard!"));
221 return;
224 number_of_windows++;
226 slash = strchr(name, '/');
227 if (slash)
229 if (access(name, F_OK))
230 path = NULL; /* File does not (yet) exist */
231 else
232 path = g_strdup(name);
234 else
236 guchar *leaf;
238 leaf = g_strconcat("pb_", name, NULL);
239 path = choices_find_path_load(leaf, "ROX-Filer");
240 g_free(leaf);
243 current_pinboard = g_new(Pinboard, 1);
244 current_pinboard->name = g_strdup(name);
245 current_pinboard->icons = NULL;
247 loading_pinboard++;
248 if (path)
250 parse_file(path, pin_from_file);
251 g_free(path);
253 else
254 pinboard_pin(home_dir, "Home", 4, 4, TRUE);
255 loading_pinboard--;
258 /* Add a new icon to the background.
259 * 'path' should be an absolute pathname.
260 * 'x' and 'y' are the coordinates of the point in the middle of the text
261 * if 'corner' is FALSE, and as the top-left corner of where the icon
262 * image should be if it is TRUE.
263 * 'name' is the name to use. If NULL then the leafname of path is used.
265 void pinboard_pin(guchar *path, guchar *name, int x, int y, gboolean corner)
267 Icon *icon;
268 int width, height;
270 g_return_if_fail(path != NULL);
271 g_return_if_fail(current_pinboard != NULL);
273 icon = g_new(Icon, 1);
274 icon->panel = NULL;
275 icon->selected = FALSE;
276 icon->src_path = g_strdup(path);
277 icon->path = icon_convert_path(path);
278 icon->mask = NULL;
279 icon->x = x;
280 icon->y = y;
281 icon->socket = NULL;
283 icon_hash_path(icon);
285 dir_stat(icon->path, &icon->item, FALSE);
287 if (!name)
289 name = strrchr(icon->path, '/');
290 if (name && name[1])
291 name++;
292 else
293 name = icon->path;
296 icon->item.leafname = g_strdup(name);
298 icon->win = gtk_window_new(GTK_WINDOW_DIALOG);
299 gtk_window_set_wmclass(GTK_WINDOW(icon->win), "ROX-Pinboard", PROJECT);
301 icon->widget = gtk_drawing_area_new();
302 #ifdef GTK2
303 icon->layout = gtk_widget_create_pango_layout(icon->widget, NULL);
304 pango_layout_set_width(icon->layout, 140 * PANGO_SCALE);
305 #endif
306 gtk_container_add(GTK_CONTAINER(icon->win), icon->widget);
307 drag_set_pinicon_dest(icon);
308 gtk_signal_connect(GTK_OBJECT(icon->widget), "drag_data_get",
309 GTK_SIGNAL_FUNC(drag_data_get), NULL);
311 gtk_widget_realize(icon->win);
312 gtk_widget_realize(icon->widget);
314 set_size_and_shape(icon, &width, &height);
315 if (corner)
317 /* Convert from icon-corner coordinates to center coordinates */
318 MaskedPixmap *image = icon->item.image;
319 x += (PIXMAP_WIDTH(image->pixmap) >> 1);
320 y += height - (icon->widget->style->font->descent >> 1);
322 snap_to_grid(&x, &y);
323 offset_from_centre(icon, width, height, &x, &y);
324 gtk_widget_set_uposition(icon->win, x, y);
325 /* Set the correct position in the icon */
326 offset_to_centre(icon, width, height, &x, &y);
327 icon->x = x;
328 icon->y = y;
330 make_panel_window(icon->win->window);
332 gtk_widget_add_events(icon->widget,
333 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
334 GDK_BUTTON1_MOTION_MASK | GDK_ENTER_NOTIFY_MASK |
335 GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK);
336 gtk_signal_connect(GTK_OBJECT(icon->widget), "enter-notify-event",
337 GTK_SIGNAL_FUNC(enter_notify), icon);
338 gtk_signal_connect(GTK_OBJECT(icon->widget), "button-press-event",
339 GTK_SIGNAL_FUNC(button_press_event), icon);
340 gtk_signal_connect(GTK_OBJECT(icon->widget), "button-release-event",
341 GTK_SIGNAL_FUNC(button_release_event), icon);
342 gtk_signal_connect(GTK_OBJECT(icon->widget), "motion-notify-event",
343 GTK_SIGNAL_FUNC(icon_motion_notify), icon);
344 gtk_signal_connect(GTK_OBJECT(icon->widget), "expose-event",
345 GTK_SIGNAL_FUNC(draw_icon), icon);
346 gtk_signal_connect_object(GTK_OBJECT(icon->win), "destroy",
347 GTK_SIGNAL_FUNC(icon_destroyed), (gpointer) icon);
349 current_pinboard->icons = g_list_prepend(current_pinboard->icons,
350 icon);
351 icon_set_tip(icon);
352 gtk_widget_show_all(icon->win);
353 gdk_window_lower(icon->win->window);
355 if (!loading_pinboard)
356 pinboard_save();
359 /* Remove an icon from the pinboard */
360 void pinboard_unpin(Icon *icon)
362 g_return_if_fail(icon != NULL);
364 gtk_widget_destroy(icon->win);
365 pinboard_save();
368 /* Put a border around the icon, briefly.
369 * If icon is NULL then cancel any existing wink.
370 * The icon will automatically unhighlight unless timeout is FALSE,
371 * in which case you must call this function again (with NULL or another
372 * icon) to remove the highlight.
374 void pinboard_wink_item(Icon *icon, gboolean timeout)
376 if (current_wink_icon == icon)
377 return;
379 if (current_wink_icon)
381 mask_wink_border(current_wink_icon, &mask_transp);
382 if (wink_timeout != -1)
383 gtk_timeout_remove(wink_timeout);
386 current_wink_icon = icon;
388 if (current_wink_icon)
390 mask_wink_border(current_wink_icon, &mask_solid);
391 if (timeout)
392 wink_timeout = gtk_timeout_add(300, end_wink, NULL);
393 else
394 wink_timeout = -1;
398 /* Remove everything on the current pinboard and disables the pinboard.
399 * Does not change any files. Does not change number_of_windows.
401 void pinboard_clear(void)
403 GList *next;
405 g_return_if_fail(current_pinboard != NULL);
407 next = current_pinboard->icons;
408 while (next)
410 Icon *icon = (Icon *) next->data;
412 next = next->next;
414 gtk_widget_destroy(icon->win);
417 g_free(current_pinboard->name);
418 g_free(current_pinboard);
419 current_pinboard = NULL;
421 release_xdnd_proxy(GDK_ROOT_WINDOW());
422 gdk_window_remove_filter(GDK_ROOT_PARENT(), proxy_filter, NULL);
423 gdk_window_set_user_data(GDK_ROOT_PARENT(), NULL);
426 /* Icon's size, shape or appearance has changed - update the display */
427 void pinboard_reshape_icon(Icon *icon)
429 int x = icon->x, y = icon->y;
430 int width, height;
432 set_size_and_shape(icon, &width, &height);
433 gdk_window_resize(icon->win->window, width, height);
434 offset_from_centre(icon, width, height, &x, &y);
435 gtk_widget_set_uposition(icon->win, x, y);
436 gtk_widget_queue_draw(icon->win);
440 /****************************************************************
441 * INTERNAL FUNCTIONS *
442 ****************************************************************/
444 static void pinboard_check_options(void)
446 int old_text_bg = o_text_bg;
447 GdkColor n_fg, n_bg;
449 o_text_bg = option_get_int("pinboard_text_bg");
450 o_grid_step = option_get_int("pinboard_grid_step");
451 o_clamp_icons = option_get_int("pinboard_clamp_icons");
453 gdk_color_parse(option_get_static_string("pinboard_fg_colour"), &n_fg);
454 gdk_color_parse(option_get_static_string("pinboard_bg_colour"), &n_bg);
456 if (o_text_bg != old_text_bg ||
457 gdk_color_equal(&n_fg, &text_fg_col) == 0 ||
458 gdk_color_equal(&n_bg, &text_bg_col) == 0)
460 memcpy(&text_fg_col, &n_fg, sizeof(GdkColor));
461 memcpy(&text_bg_col, &n_bg, sizeof(GdkColor));
463 if (pinicon_style)
465 gtk_style_unref(pinicon_style);
466 pinicon_style = NULL;
469 if (current_pinboard)
470 reshape_all();
474 static gint end_wink(gpointer data)
476 pinboard_wink_item(NULL, FALSE);
477 return FALSE;
480 /* Make the wink border solid or transparent */
481 static void mask_wink_border(Icon *icon, GdkColor *alpha)
483 int width, height;
485 if (!current_pinboard)
486 return;
488 gdk_window_get_size(icon->widget->window, &width, &height);
490 gdk_gc_set_foreground(mask_gc, alpha);
491 gdk_draw_rectangle(icon->mask, mask_gc, FALSE,
492 0, 0, width - 1, height - 1);
493 gdk_draw_rectangle(icon->mask, mask_gc, FALSE,
494 1, 1, width - 3, height - 3);
496 gtk_widget_shape_combine_mask(icon->win, icon->mask, 0, 0);
498 gtk_widget_draw(icon->widget, NULL);
501 #define TEXT_AT(dx, dy) \
502 gdk_draw_string(icon->mask, font, mask_gc, \
503 text_x + dx, y + dy, \
504 item->leafname);
506 /* Updates the name_width and layout fields, and resizes and masks the window.
507 * Also sets the style to pinicon_style, generating it if needed.
508 * Returns the new width and height.
510 static void set_size_and_shape(Icon *icon, int *rwidth, int *rheight)
512 int width, height;
513 int font_height;
514 MaskedPixmap *image = icon->item.image;
515 int iwidth = PIXMAP_WIDTH(image->pixmap);
516 int iheight = PIXMAP_HEIGHT(image->pixmap);
517 DirItem *item = &icon->item;
518 int text_x, text_y;
520 if (!pinicon_style)
522 pinicon_style = gtk_style_copy(icon->widget->style);
523 memcpy(&pinicon_style->fg[GTK_STATE_NORMAL],
524 &text_fg_col, sizeof(GdkColor));
525 memcpy(&pinicon_style->bg[GTK_STATE_NORMAL],
526 &text_bg_col, sizeof(GdkColor));
528 gtk_widget_set_style(icon->widget, pinicon_style);
530 #ifdef GTK2
532 PangoRectangle logical;
533 pango_layout_set_text(icon->layout, icon->item.leafname, -1);
534 pango_layout_get_pixel_extents(icon->layout, NULL, &logical);
536 item->name_width = logical.width - logical.x;
537 font_height = logical.height - logical.y;
539 #else
541 GdkFont *font;
542 font = pinicon_style->font;
543 font_height = font->ascent + font->descent;
544 item->name_width = gdk_string_width(font, item->leafname);
546 #endif
548 width = MAX(iwidth, item->name_width + 2) + 2 * WINK_FRAME;
549 height = iheight + GAP + (font_height + 2) + 2 * WINK_FRAME;
550 gtk_widget_set_usize(icon->win, width, height);
552 if (icon->mask)
553 gdk_pixmap_unref(icon->mask);
554 icon->mask = gdk_pixmap_new(icon->win->window, width, height, 1);
555 if (!mask_gc)
556 mask_gc = gdk_gc_new(icon->mask);
558 /* Clear the mask to transparent */
559 gdk_gc_set_foreground(mask_gc, &mask_transp);
560 gdk_draw_rectangle(icon->mask, mask_gc, TRUE, 0, 0, width, height);
562 gdk_gc_set_foreground(mask_gc, &mask_solid);
563 /* Make the icon area solid */
564 if (image->mask)
566 gdk_draw_pixmap(icon->mask, mask_gc, image->mask,
567 0, 0,
568 (width - iwidth) >> 1,
569 WINK_FRAME,
570 PIXMAP_WIDTH(image->pixmap),
571 PIXMAP_HEIGHT(image->pixmap));
573 else
575 gdk_draw_rectangle(icon->mask, mask_gc, TRUE,
576 (width - iwidth) >> 1,
577 WINK_FRAME,
578 iwidth,
579 iheight);
582 gdk_gc_set_function(mask_gc, GDK_OR);
583 if (item->flags & ITEM_FLAG_SYMLINK)
585 gdk_draw_pixmap(icon->mask, mask_gc, im_symlink->mask,
586 0, 0, /* Source x,y */
587 (width - iwidth) >> 1, /* Dest x */
588 WINK_FRAME, /* Dest y */
589 -1, -1);
591 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
593 /* Note: Both mount state pixmaps must have the same mask */
594 gdk_draw_pixmap(icon->mask, mask_gc, im_mounted->mask,
595 0, 0, /* Source x,y */
596 (width - iwidth) >> 1, /* Dest x */
597 WINK_FRAME, /* Dest y */
598 -1, -1);
600 gdk_gc_set_function(mask_gc, GDK_COPY);
602 /* Mask off an area for the text (from o_text_bg) */
604 text_x = (width - item->name_width) >> 1;
605 text_y = WINK_FRAME + iheight + GAP + 1;
607 #ifndef GTK2
608 if (o_text_bg == TEXT_BG_SOLID)
610 #endif
611 gdk_draw_rectangle(icon->mask, mask_gc, TRUE,
612 (width - (item->name_width + 2)) >> 1,
613 WINK_FRAME + iheight + GAP,
614 item->name_width + 2, font_height + 2);
615 #ifndef GTK2
617 else
619 int y = text_y + font->ascent;
621 TEXT_AT(0, 0);
623 if (o_text_bg == TEXT_BG_OUTLINE)
625 TEXT_AT(1, 0);
626 TEXT_AT(1, 1);
627 TEXT_AT(0, 1);
628 TEXT_AT(-1, 1);
629 TEXT_AT(-1, 0);
630 TEXT_AT(-1, -1);
631 TEXT_AT(0, -1);
632 TEXT_AT(1, -1);
635 #endif
637 gtk_widget_shape_combine_mask(icon->win, icon->mask, 0, 0);
639 *rwidth = width;
640 *rheight = height;
643 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, Icon *icon)
645 #ifndef GTK2
646 GdkFont *font = icon->widget->style->font;
647 #endif
648 int width, height;
649 int text_x, text_y;
650 DirItem *item = &icon->item;
651 MaskedPixmap *image = item->image;
652 int iwidth = PIXMAP_WIDTH(image->pixmap);
653 int iheight = PIXMAP_HEIGHT(image->pixmap);
654 int image_x;
655 GdkGC *gc = widget->style->black_gc;
656 GtkStateType state = icon->selected ? GTK_STATE_SELECTED
657 : GTK_STATE_NORMAL;
659 gdk_window_get_size(widget->window, &width, &height);
660 image_x = (width - iwidth) >> 1;
662 /* TODO: If the shape extension is missing we might need to set
663 * the clip mask here...
665 gdk_draw_pixmap(widget->window, gc,
666 image->pixmap,
667 0, 0,
668 image_x,
669 WINK_FRAME,
670 iwidth,
671 iheight);
673 if (item->flags & ITEM_FLAG_SYMLINK)
675 gdk_gc_set_clip_origin(gc, image_x, WINK_FRAME);
676 gdk_gc_set_clip_mask(gc, im_symlink->mask);
677 gdk_draw_pixmap(widget->window, gc,
678 im_symlink->pixmap,
679 0, 0, /* Source x,y */
680 image_x, WINK_FRAME, /* Dest x,y */
681 -1, -1);
682 gdk_gc_set_clip_mask(gc, NULL);
683 gdk_gc_set_clip_origin(gc, 0, 0);
685 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
687 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
688 ? im_mounted
689 : im_unmounted;
691 gdk_gc_set_clip_origin(gc, image_x, WINK_FRAME);
692 gdk_gc_set_clip_mask(gc, mp->mask);
693 gdk_draw_pixmap(widget->window, gc,
694 mp->pixmap,
695 0, 0, /* Source x,y */
696 image_x, WINK_FRAME, /* Dest x,y */
697 -1, -1);
698 gdk_gc_set_clip_mask(gc, NULL);
699 gdk_gc_set_clip_origin(gc, 0, 0);
702 text_x = (width - item->name_width) >> 1;
703 text_y = WINK_FRAME + iheight + GAP + 1;
705 if (o_text_bg != TEXT_BG_NONE)
707 #ifdef GTK2
708 PangoRectangle logical;
709 int font_height;
711 pango_layout_get_pixel_extents(icon->layout, NULL, &logical);
712 font_height = logical.height - logical.y;
713 #else
714 int font_height = font->ascent + font->descent;
715 #endif
717 gtk_paint_flat_box(widget->style, widget->window,
718 state,
719 GTK_SHADOW_NONE,
720 NULL, widget, "text",
721 text_x - 1,
722 text_y - 1,
723 item->name_width + 2,
724 font_height + 2);
727 #ifdef GTK2
728 gtk_paint_layout(widget->style, widget->window,
729 state,
730 FALSE, NULL, widget, "text",
731 text_x,
732 text_y,
733 icon->layout);
734 #else
735 gtk_paint_string(widget->style, widget->window,
736 state,
737 NULL, widget, "text",
738 text_x,
739 text_y + font->ascent,
740 item->leafname);
741 #endif
743 if (current_wink_icon == icon)
745 gdk_draw_rectangle(icon->widget->window,
746 icon->widget->style->white_gc,
747 FALSE,
748 0, 0, width - 1, height - 1);
749 gdk_draw_rectangle(icon->widget->window,
750 icon->widget->style->black_gc,
751 FALSE,
752 1, 1, width - 3, height - 3);
755 return FALSE;
758 static gboolean root_property_event(GtkWidget *widget,
759 GdkEventProperty *event,
760 gpointer data)
762 if (event->atom == win_button_proxy &&
763 event->state == GDK_PROPERTY_NEW_VALUE)
765 /* Setup forwarding on the new proxy window, if possible */
766 forward_root_clicks();
769 return FALSE;
772 static gboolean root_button_press(GtkWidget *widget,
773 GdkEventButton *event,
774 gpointer data)
776 BindAction action;
778 action = bind_lookup_bev(BIND_PINBOARD, event);
780 switch (action)
782 case ACT_CLEAR_SELECTION:
783 icon_select_only(NULL);
784 break;
785 case ACT_POPUP_MENU:
786 dnd_motion_ungrab();
787 icon_show_menu(event, NULL, NULL);
788 break;
789 case ACT_IGNORE:
790 break;
791 default:
792 g_warning("Unsupported action : %d\n", action);
793 break;
796 return TRUE;
799 static gboolean enter_notify(GtkWidget *widget,
800 GdkEventCrossing *event,
801 Icon *icon)
803 icon_may_update(icon);
805 return FALSE;
808 static void perform_action(Icon *icon, GdkEventButton *event)
810 BindAction action;
812 action = bind_lookup_bev(BIND_PINBOARD_ICON, event);
814 switch (action)
816 case ACT_OPEN_ITEM:
817 dnd_motion_ungrab();
818 pinboard_wink_item(icon, TRUE);
819 run_diritem(icon->path, &icon->item, NULL, FALSE);
820 break;
821 case ACT_EDIT_ITEM:
822 dnd_motion_ungrab();
823 pinboard_wink_item(icon, TRUE);
824 run_diritem(icon->path, &icon->item, NULL, TRUE);
825 break;
826 case ACT_POPUP_MENU:
827 dnd_motion_ungrab();
828 icon_show_menu(event, icon, NULL);
829 break;
830 case ACT_MOVE_ICON:
831 old_x = event->x_root;
832 old_y = event->y_root;
833 icon_old_x = icon->x;
834 icon_old_y = icon->y;
835 dnd_motion_start(MOTION_REPOSITION);
836 break;
837 case ACT_PRIME_AND_SELECT:
838 if (!icon->selected)
839 icon_select_only(icon);
840 dnd_motion_start(MOTION_READY_FOR_DND);
841 break;
842 case ACT_PRIME_AND_TOGGLE:
843 icon_set_selected(icon, !icon->selected);
844 dnd_motion_start(MOTION_READY_FOR_DND);
845 break;
846 case ACT_PRIME_FOR_DND:
847 dnd_motion_start(MOTION_READY_FOR_DND);
848 break;
849 case ACT_TOGGLE_SELECTED:
850 icon_set_selected(icon, !icon->selected);
851 break;
852 case ACT_SELECT_EXCL:
853 icon_select_only(icon);
854 break;
855 case ACT_IGNORE:
856 break;
857 default:
858 g_warning("Unsupported action : %d\n", action);
859 break;
863 static gboolean button_release_event(GtkWidget *widget,
864 GdkEventButton *event,
865 Icon *icon)
867 if (pinboard_modified)
868 pinboard_save();
870 if (dnd_motion_release(event))
871 return TRUE;
873 perform_action(icon, event);
875 return TRUE;
878 static gboolean button_press_event(GtkWidget *widget,
879 GdkEventButton *event,
880 Icon *icon)
882 if (dnd_motion_press(widget, event))
883 perform_action(icon, event);
885 return TRUE;
888 /* Return a text/uri-list of all the icons in the list.
889 * TODO: Use code in icon.c instead.
891 static guchar *create_uri_list(GList *list)
893 GString *tmp;
894 guchar *retval;
895 guchar *leader;
897 tmp = g_string_new(NULL);
898 leader = g_strdup_printf("file://%s", our_host_name());
900 for (; list; list = list->next)
902 Icon *icon = (Icon *) list->data;
904 g_string_append(tmp, leader);
905 g_string_append(tmp, icon->path);
906 g_string_append(tmp, "\r\n");
909 g_free(leader);
910 retval = tmp->str;
911 g_string_free(tmp, FALSE);
913 return retval;
916 static void start_drag(Icon *icon, GdkEventMotion *event)
918 GtkWidget *widget = icon->widget;
920 if (!icon->selected)
922 tmp_icon_selected = TRUE;
923 icon_select_only(icon);
926 g_return_if_fail(icon_selection != NULL);
928 pinboard_drag_in_progress = TRUE;
930 if (icon_selection->next == NULL)
931 drag_one_item(widget, event, icon->path, &icon->item);
932 else
934 guchar *uri_list;
936 uri_list = create_uri_list(icon_selection);
937 drag_selection(widget, event, uri_list);
938 g_free(uri_list);
942 /* An icon is being dragged around... */
943 static gint icon_motion_notify(GtkWidget *widget,
944 GdkEventMotion *event,
945 Icon *icon)
947 int x, y;
948 int width, height;
949 int dx,dy;
951 if (motion_state == MOTION_READY_FOR_DND)
953 if (dnd_motion_moved(event))
954 start_drag(icon, event);
955 return TRUE;
957 else if (motion_state != MOTION_REPOSITION)
958 return FALSE;
960 /* How far the pointer has moved since the drag started */
961 dx = event->x_root - old_x;
962 dy = event->y_root - old_y;
964 x = icon_old_x + dx;
965 y = icon_old_y + dy;
967 snap_to_grid(&x, &y);
969 if (icon->x == x && icon->y == y)
970 return TRUE;
972 icon->x = x;
973 icon->y = y;
974 gdk_window_get_size(icon->win->window, &width, &height);
975 offset_from_centre(icon, width, height, &x, &y);
977 gdk_window_move(icon->win->window, x, y);
979 /* Store the fixed position for the center of the icon */
980 offset_to_centre(icon, width, height, &x, &y);
981 icon->x = x;
982 icon->y = y;
984 pinboard_modified = TRUE;
986 return TRUE;
989 /* Called for each line in the pinboard file while loading a new board */
990 static char *pin_from_file(guchar *line)
992 guchar *leaf = NULL;
993 int x, y, n;
995 if (*line == '<')
997 guchar *end;
999 end = strchr(line + 1, '>');
1000 if (!end)
1001 return _("Missing '>' in icon label");
1003 leaf = g_strndup(line + 1, end - line - 1);
1005 line = end + 1;
1007 while (isspace(*line))
1008 line++;
1009 if (*line != ',')
1010 return _("Missing ',' after icon label");
1011 line++;
1014 if (sscanf(line, " %d , %d , %n", &x, &y, &n) < 2)
1015 return NULL; /* Ignore format errors */
1017 pinboard_pin(line + n, leaf, x, y, FALSE);
1019 g_free(leaf);
1021 return NULL;
1024 /* Make sure that clicks and drops on the root window come to us...
1025 * False if an error occurred (ie, someone else is using it).
1027 static gboolean add_root_handlers(void)
1029 GdkWindow *root;
1031 if (!proxy_invisible)
1033 win_button_proxy = gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY",
1034 FALSE);
1035 proxy_invisible = gtk_invisible_new();
1036 gtk_widget_show(proxy_invisible);
1038 gdk_window_add_filter(proxy_invisible->window,
1039 proxy_filter, NULL);
1042 gtk_signal_connect(GTK_OBJECT(proxy_invisible),
1043 "property_notify_event",
1044 GTK_SIGNAL_FUNC(root_property_event), NULL);
1045 gtk_signal_connect(GTK_OBJECT(proxy_invisible),
1046 "button_press_event",
1047 GTK_SIGNAL_FUNC(root_button_press), NULL);
1049 /* Drag and drop handlers */
1050 drag_set_pinboard_dest(proxy_invisible);
1051 gtk_signal_connect(GTK_OBJECT(proxy_invisible), "drag_motion",
1052 GTK_SIGNAL_FUNC(bg_drag_motion),
1053 NULL);
1056 root = gdk_window_lookup(GDK_ROOT_WINDOW());
1057 if (!root)
1058 root = gdk_window_foreign_new(GDK_ROOT_WINDOW());
1060 if (!setup_xdnd_proxy(GDK_ROOT_WINDOW(), proxy_invisible->window))
1061 return FALSE;
1063 /* Forward events from the root window to our proxy window */
1064 gdk_window_add_filter(GDK_ROOT_PARENT(), proxy_filter, NULL);
1065 gdk_window_set_user_data(GDK_ROOT_PARENT(), proxy_invisible);
1066 gdk_window_set_events(GDK_ROOT_PARENT(),
1067 gdk_window_get_events(GDK_ROOT_PARENT()) |
1068 GDK_PROPERTY_CHANGE_MASK);
1070 forward_root_clicks();
1072 return TRUE;
1075 /* See if the window manager is offering to forward root window clicks.
1076 * If so, grab them. Otherwise, do nothing.
1077 * Call this whenever the _WIN_DESKTOP_BUTTON_PROXY property changes.
1079 static void forward_root_clicks(void)
1081 click_proxy_gdk_window = find_click_proxy_window();
1082 if (!click_proxy_gdk_window)
1083 return;
1085 /* Events on the wm's proxy are dealt with by our proxy widget */
1086 gdk_window_set_user_data(click_proxy_gdk_window, proxy_invisible);
1087 gdk_window_add_filter(click_proxy_gdk_window, proxy_filter, NULL);
1089 /* The proxy window for clicks sends us button press events with
1090 * SubstructureNotifyMask. We need StructureNotifyMask to receive
1091 * DestroyNotify events, too.
1093 XSelectInput(GDK_DISPLAY(),
1094 GDK_WINDOW_XWINDOW(click_proxy_gdk_window),
1095 SubstructureNotifyMask | StructureNotifyMask);
1098 /* Write the current state of the pinboard to the current pinboard file */
1099 void pinboard_save(void)
1101 guchar *save = NULL;
1102 GString *tmp = NULL;
1103 FILE *file = NULL;
1104 GList *next;
1105 guchar *save_new = NULL;
1107 g_return_if_fail(current_pinboard != NULL);
1109 pinboard_modified = FALSE;
1111 if (strchr(current_pinboard->name, '/'))
1112 save = g_strdup(current_pinboard->name);
1113 else
1115 guchar *leaf;
1117 leaf = g_strconcat("pb_", current_pinboard->name, NULL);
1118 save = choices_find_path_save(leaf, "ROX-Filer", TRUE);
1119 g_free(leaf);
1122 if (!save)
1123 return;
1125 save_new = g_strconcat(save, ".new", NULL);
1126 file = fopen(save_new, "wb");
1127 if (!file)
1128 goto err;
1130 tmp = g_string_new(NULL);
1131 for (next = current_pinboard->icons; next; next = next->next)
1133 Icon *icon = (Icon *) next->data;
1135 g_string_sprintf(tmp, "<%s>, %d, %d, %s\n",
1136 icon->item.leafname, icon->x, icon->y, icon->src_path);
1137 if (fwrite(tmp->str, 1, tmp->len, file) < tmp->len)
1138 goto err;
1141 if (fclose(file))
1143 file = NULL;
1144 goto err;
1147 file = NULL;
1149 if (rename(save_new, save))
1150 goto err;
1152 goto out;
1153 err:
1154 delayed_error(_("Error saving pinboard"), g_strerror(errno));
1155 out:
1156 if (file)
1157 fclose(file);
1158 if (tmp)
1159 g_string_free(tmp, TRUE);
1161 g_free(save_new);
1162 g_free(save);
1166 * Filter that translates proxied events from virtual root windows into normal
1167 * Gdk events for the proxy_invisible widget. Stolen from gmc.
1169 * Also gets events from the root window.
1171 static GdkFilterReturn proxy_filter(GdkXEvent *xevent,
1172 GdkEvent *event,
1173 gpointer data)
1175 XEvent *xev;
1176 GdkWindow *proxy = proxy_invisible->window;
1178 xev = xevent;
1180 switch (xev->type) {
1181 case ButtonPress:
1182 case ButtonRelease:
1183 /* Translate button events into events that come from
1184 * the proxy window, so that we can catch them as a
1185 * signal from the invisible widget.
1187 if (xev->type == ButtonPress)
1188 event->button.type = GDK_BUTTON_PRESS;
1189 else
1190 event->button.type = GDK_BUTTON_RELEASE;
1192 gdk_window_ref(proxy);
1194 event->button.window = proxy;
1195 event->button.send_event = xev->xbutton.send_event;
1196 event->button.time = xev->xbutton.time;
1197 event->button.x_root = xev->xbutton.x_root;
1198 event->button.y_root = xev->xbutton.y_root;
1199 event->button.x = xev->xbutton.x;
1200 event->button.y = xev->xbutton.y;
1201 event->button.state = xev->xbutton.state;
1202 event->button.button = xev->xbutton.button;
1203 #ifdef GTK2
1204 event->button.axes = NULL;
1205 #endif
1207 return GDK_FILTER_TRANSLATE;
1209 case DestroyNotify:
1210 /* XXX: I have no idea why this helps, but it does! */
1211 /* The proxy window was destroyed (i.e. the window
1212 * manager died), so we have to cope with it
1214 if (((GdkEventAny *) event)->window == proxy)
1215 gdk_window_destroy_notify(proxy);
1217 return GDK_FILTER_REMOVE;
1219 default:
1220 break;
1223 return GDK_FILTER_CONTINUE;
1226 static void snap_to_grid(int *x, int *y)
1228 *x = ((*x + o_grid_step / 2) / o_grid_step) * o_grid_step;
1229 *y = ((*y + o_grid_step / 2) / o_grid_step) * o_grid_step;
1232 /* Convert (x,y) from a centre point to a window position */
1233 static void offset_from_centre(Icon *icon,
1234 int width, int height,
1235 int *x, int *y)
1237 *x -= width >> 1;
1238 *y -= height - (icon->widget->style->font->descent >> 1);
1239 *x = CLAMP(*x, 0, screen_width - (o_clamp_icons ? width : 0));
1240 *y = CLAMP(*y, 0, screen_height - (o_clamp_icons ? height : 0));
1243 /* Convert (x,y) from a window position to a centre point */
1244 static void offset_to_centre(Icon *icon,
1245 int width, int height,
1246 int *x, int *y)
1248 *x += width >> 1;
1249 *y += height - (icon->widget->style->font->descent >> 1);
1252 /* Same as drag_set_dest(), but for pinboard icons */
1253 static void drag_set_pinicon_dest(Icon *icon)
1255 GtkObject *obj = GTK_OBJECT(icon->widget);
1257 make_drop_target(icon->widget, 0);
1259 gtk_signal_connect(obj, "drag_motion",
1260 GTK_SIGNAL_FUNC(drag_motion), icon);
1261 gtk_signal_connect(obj, "drag_leave",
1262 GTK_SIGNAL_FUNC(drag_leave), icon);
1263 gtk_signal_connect(obj, "drag_end",
1264 GTK_SIGNAL_FUNC(drag_end), icon);
1267 /* Called during the drag when the mouse is in a widget registered
1268 * as a drop target. Returns TRUE if we can accept the drop.
1270 static gboolean drag_motion(GtkWidget *widget,
1271 GdkDragContext *context,
1272 gint x,
1273 gint y,
1274 guint time,
1275 Icon *icon)
1277 GdkDragAction action = context->suggested_action;
1278 char *type = NULL;
1279 DirItem *item = &icon->item;
1281 if (gtk_drag_get_source_widget(context) == widget)
1282 goto out; /* Can't drag something to itself! */
1284 if (icon->selected)
1285 goto out; /* Can't drag a selection to itself */
1287 type = dnd_motion_item(context, &item);
1289 if (!item)
1290 type = NULL;
1291 out:
1292 /* We actually must pretend to accept the drop, even if the
1293 * directory isn't writeable, so that the spring-opening
1294 * thing works.
1297 /* Don't allow drops to non-writeable directories */
1298 if (option_get_int("dnd_spring_open") == FALSE &&
1299 type == drop_dest_dir &&
1300 access(icon->path, W_OK) != 0)
1302 type = NULL;
1305 g_dataset_set_data(context, "drop_dest_type", type);
1306 if (type)
1308 gdk_drag_status(context, action, time);
1309 g_dataset_set_data_full(context, "drop_dest_path",
1310 g_strdup(icon->path), g_free);
1311 if (type == drop_dest_dir)
1312 dnd_spring_load(context);
1314 pinboard_wink_item(icon, FALSE);
1317 return type != NULL;
1320 static void drag_leave(GtkWidget *widget,
1321 GdkDragContext *context,
1322 guint32 time,
1323 Icon *icon)
1325 pinboard_wink_item(NULL, FALSE);
1326 dnd_spring_abort();
1329 static gboolean bg_drag_motion(GtkWidget *widget,
1330 GdkDragContext *context,
1331 gint x,
1332 gint y,
1333 guint time,
1334 gpointer data)
1336 /* Dragging from the pinboard to the pinboard is not allowed */
1337 if (pinboard_drag_in_progress)
1338 return FALSE;
1340 gdk_drag_status(context, context->suggested_action, time);
1341 return TRUE;
1344 static void drag_end(GtkWidget *widget,
1345 GdkDragContext *context,
1346 Icon *icon)
1348 pinboard_drag_in_progress = FALSE;
1349 if (tmp_icon_selected)
1351 icon_select_only(NULL);
1352 tmp_icon_selected = FALSE;
1356 /* Something which affects all the icons has changed - reshape
1357 * and redraw all of them.
1359 static void reshape_all(void)
1361 GList *next;
1363 g_return_if_fail(current_pinboard != NULL);
1365 for (next = current_pinboard->icons; next; next = next->next)
1367 Icon *icon = (Icon *) next->data;
1368 pinboard_reshape_icon(icon);