r339: The menu key bindings are now only saved if they actually changed.
[rox-filer.git] / ROX-Filer / src / pinboard.c
blob1676ec6aa8d944c92b6e7384529c0e26b696d971
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@users.sourceforge.net>.
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 background */
24 #include "config.h"
26 #include <string.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <gtk/gtk.h>
30 #include <gdk/gdkx.h>
31 #include <gtk/gtkinvisible.h>
33 #include "global.h"
35 #include "pinboard.h"
36 #include "main.h"
37 #include "dnd.h"
38 #include "pixmaps.h"
39 #include "type.h"
40 #include "choices.h"
41 #include "support.h"
42 #include "gui_support.h"
43 #include "run.h"
44 #include "menu.h"
45 #include "options.h"
46 #include "dir.h"
48 /* The number of pixels between the bottom of the image and the top
49 * of the text.
51 #define GAP 4
53 /* The size of the border around the icon which is used when winking */
54 #define WINK_FRAME 2
56 enum
58 TARGET_STRING,
59 TARGET_URI_LIST,
62 struct _PinIcon {
63 GtkWidget *win, *paper;
64 GdkBitmap *mask;
65 guchar *path;
66 DirItem item;
67 int x, y;
68 gboolean selected;
71 struct _Pinboard {
72 guchar *name; /* Leaf name */
73 GList *icons;
76 extern int collection_menu_button;
78 static Pinboard *current_pinboard = NULL;
79 static gint loading_pinboard = 0; /* Non-zero => loading */
81 static PinIcon *current_wink_icon = NULL;
82 static gint wink_timeout;
84 static gint number_selected = 0;
86 static GdkColor mask_solid = {1, 1, 1, 1};
87 static GdkColor mask_transp = {0, 0, 0, 0};
88 static GdkGC *mask_gc = NULL;
90 /* Proxy window for DnD and clicks on the desktop */
91 static GtkWidget *proxy_invisible;
93 /* The window (owned by the wm) which root clicks are forwarded to.
94 * NULL if wm does not support forwarding clicks.
96 static GdkWindow *click_proxy_gdk_window = NULL;
97 static GdkAtom win_button_proxy; /* _WIN_DESKTOP_BUTTON_PROXY */
99 static gint drag_start_x, drag_start_y;
100 /* If the drag type is not DRAG_NONE then there is a grab in progress */
101 typedef enum {
102 DRAG_NONE,
103 DRAG_MOVE_ICON, /* When you hold down Adjust on an icon */
104 DRAG_MAY_COPY, /* When you hold down Select on an icon, but */
105 /* before the drag has actually started. */
106 } PinDragType;
107 static PinDragType pin_drag_type = DRAG_NONE;
109 /* This is TRUE while the user is dragging from a pinned icon.
110 * We use it to prevent dragging from the pinboard to itself.
112 static gboolean pinboard_drag_in_progress = FALSE;
114 /* Options bits */
115 static GtkWidget *create_options();
116 static void update_options();
117 static void set_options();
118 static void save_options();
119 static char *text_bg(char *data);
121 static OptionsSection options =
123 N_("Pinboard options"),
124 create_options,
125 update_options,
126 set_options,
127 save_options
130 typedef enum {TEXT_BG_SOLID, TEXT_BG_OUTLINE, TEXT_BG_NONE} TextBgType;
131 TextBgType o_text_bg = TEXT_BG_SOLID;
133 /* Static prototypes */
134 static void set_size_and_shape(PinIcon *icon, int *rwidth, int *rheight);
135 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *icon);
136 static void mask_wink_border(PinIcon *icon, GdkColor *alpha);
137 static gint end_wink(gpointer data);
138 static gboolean button_release_event(GtkWidget *widget,
139 GdkEventButton *event,
140 PinIcon *icon);
141 static gboolean root_property_event(GtkWidget *widget,
142 GdkEventProperty *event,
143 gpointer data);
144 static gboolean root_button_press(GtkWidget *widget,
145 GdkEventButton *event,
146 gpointer data);
147 static gboolean enter_notify(GtkWidget *widget,
148 GdkEventCrossing *event,
149 PinIcon *icon);
150 static gboolean button_press_event(GtkWidget *widget,
151 GdkEventButton *event,
152 PinIcon *icon);
153 static gint icon_motion_notify(GtkWidget *widget,
154 GdkEventMotion *event,
155 PinIcon *icon);
156 static char *pin_from_file(guchar *line);
157 static gboolean add_root_handlers(void);
158 static void pinboard_save(void);
159 static GdkFilterReturn proxy_filter(GdkXEvent *xevent,
160 GdkEvent *event,
161 gpointer data);
162 static void icon_destroyed(GtkWidget *widget, PinIcon *icon);
163 static void snap_to_grid(int *x, int *y);
164 static void offset_from_centre(PinIcon *icon,
165 int width, int height,
166 int *x, int *y);
167 static gboolean drag_motion(GtkWidget *widget,
168 GdkDragContext *context,
169 gint x,
170 gint y,
171 guint time,
172 PinIcon *icon);
173 static void drag_set_pinicon_dest(PinIcon *icon);
174 static void drag_leave(GtkWidget *widget,
175 GdkDragContext *context,
176 guint32 time,
177 PinIcon *icon);
178 static void icon_may_update(PinIcon *icon);
179 static void forward_root_clicks(void);
180 static void change_number_selected(int delta);
181 static gint lose_selection(GtkWidget *widget, GdkEventSelection *event);
182 static void selection_get(GtkWidget *widget,
183 GtkSelectionData *selection_data,
184 guint info,
185 guint time,
186 gpointer data);
187 static gboolean bg_drag_motion(GtkWidget *widget,
188 GdkDragContext *context,
189 gint x,
190 gint y,
191 guint time,
192 gpointer data);
193 static void drag_end(GtkWidget *widget,
194 GdkDragContext *context,
195 FilerWindow *filer_window);
199 /****************************************************************
200 * EXTERNAL INTERFACE *
201 ****************************************************************/
203 void pinboard_init(void)
205 options_sections = g_slist_prepend(options_sections, &options);
206 option_register("pinboard_text_bg", text_bg);
209 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
210 * and make it the current pinboard.
211 * Any existing pinned items are removed. You must call this
212 * at least once before using the pinboard. NULL disables the
213 * pinboard.
215 void pinboard_activate(guchar *name)
217 Pinboard *old_board = current_pinboard;
218 guchar *path, *slash;
220 /* Treat an empty name the same as NULL */
221 if (!*name)
222 name = NULL;
224 if (old_board)
226 pinboard_clear();
227 number_of_windows--;
230 if (!name)
232 if (number_of_windows < 1 && gtk_main_level() > 0)
233 gtk_main_quit();
234 return;
237 if (!add_root_handlers())
239 delayed_error(PROJECT, _("Another application is already "
240 "managing the pinboard!"));
241 return;
244 number_of_windows++;
246 slash = strchr(name, '/');
247 if (slash)
248 path = g_strdup(name);
249 else
251 guchar *leaf;
253 leaf = g_strconcat("pb_", name, NULL);
254 path = choices_find_path_load(leaf, "ROX-Filer");
255 g_free(leaf);
258 current_pinboard = g_new(Pinboard, 1);
259 current_pinboard->name = g_strdup(name);
260 current_pinboard->icons = NULL;
262 if (path)
264 loading_pinboard++;
265 parse_file(path, pin_from_file);
266 g_free(path);
267 loading_pinboard--;
271 /* Add a new icon to the background.
272 * 'path' should be an absolute pathname.
273 * 'x' and 'y' are the coordinates of the point in the middle of the text.
274 * 'name' is the name to use. If NULL then the leafname of path is used.
276 void pinboard_pin(guchar *path, guchar *name, int x, int y)
278 PinIcon *icon;
279 int path_len;
280 int width, height;
282 g_return_if_fail(path != NULL);
283 g_return_if_fail(current_pinboard != NULL);
285 path_len = strlen(path);
286 while (path_len > 0 && path[path_len - 1] == '/')
287 path_len--;
289 icon = g_new(PinIcon, 1);
290 icon->selected = FALSE;
291 icon->path = g_strndup(path, path_len);
292 icon->mask = NULL;
293 snap_to_grid(&x, &y);
294 icon->x = x;
295 icon->y = y;
297 dir_stat(icon->path, &icon->item);
299 if (!name)
301 name = strrchr(icon->path, '/');
302 if (name)
303 name++;
304 else
305 name = icon->path; /* Shouldn't happen */
308 icon->item.leafname = g_strdup(name);
310 icon->win = gtk_window_new(GTK_WINDOW_DIALOG);
311 gtk_window_set_wmclass(GTK_WINDOW(icon->win), "ROX-Pinboard", PROJECT);
313 icon->paper = gtk_drawing_area_new();
314 gtk_container_add(GTK_CONTAINER(icon->win), icon->paper);
315 drag_set_pinicon_dest(icon);
316 gtk_signal_connect(GTK_OBJECT(icon->paper), "drag_data_get",
317 drag_data_get, NULL);
319 gtk_widget_realize(icon->win);
320 if (override_redirect)
322 gdk_window_lower(icon->win->window);
323 gdk_window_set_override_redirect(icon->win->window, TRUE);
325 else
326 make_panel_window(icon->win->window);
328 set_size_and_shape(icon, &width, &height);
329 offset_from_centre(icon, width, height, &x, &y);
330 gtk_widget_set_uposition(icon->win, x, y);
332 gtk_widget_add_events(icon->paper,
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->paper), "enter-notify-event",
337 GTK_SIGNAL_FUNC(enter_notify), icon);
338 gtk_signal_connect(GTK_OBJECT(icon->paper), "button-press-event",
339 GTK_SIGNAL_FUNC(button_press_event), icon);
340 gtk_signal_connect(GTK_OBJECT(icon->paper), "button-release-event",
341 GTK_SIGNAL_FUNC(button_release_event), icon);
342 gtk_signal_connect(GTK_OBJECT(icon->paper), "motion-notify-event",
343 GTK_SIGNAL_FUNC(icon_motion_notify), icon);
344 gtk_signal_connect(GTK_OBJECT(icon->paper), "expose-event",
345 GTK_SIGNAL_FUNC(draw_icon), icon);
346 gtk_signal_connect(GTK_OBJECT(icon->win), "destroy",
347 GTK_SIGNAL_FUNC(icon_destroyed), icon);
349 current_pinboard->icons = g_list_prepend(current_pinboard->icons,
350 icon);
351 gtk_widget_show_all(icon->win);
353 if (!loading_pinboard)
354 pinboard_save();
357 /* Remove an icon from the pinboard */
358 void pinboard_unpin(PinIcon *icon)
360 g_return_if_fail(icon != NULL);
362 gtk_widget_destroy(icon->win);
363 pinboard_save();
366 /* Unpin all selected items */
367 void pinboard_unpin_selection(void)
369 GList *next;
371 g_return_if_fail(current_pinboard != NULL);
373 if (number_selected == 0)
375 delayed_error(PROJECT,
376 _("You should first select some pinned icons to "
377 "unpin. Hold down the Ctrl key to select some icons."));
378 return;
381 next = current_pinboard->icons;
382 while (next)
384 PinIcon *icon = (PinIcon *) next->data;
386 next = next->next;
388 if (icon->selected)
389 gtk_widget_destroy(icon->win);
392 pinboard_save();
395 /* Put a border around the icon, briefly.
396 * If icon is NULL then cancel any existing wink.
397 * The icon will automatically unhighlight unless timeout is FALSE,
398 * in which case you must call this function again (with NULL or another
399 * icon) to remove the highlight.
401 void pinboard_wink_item(PinIcon *icon, gboolean timeout)
403 if (current_wink_icon == icon)
404 return;
406 if (current_wink_icon)
408 mask_wink_border(current_wink_icon, &mask_transp);
409 if (wink_timeout != -1)
410 gtk_timeout_remove(wink_timeout);
413 current_wink_icon = icon;
415 if (current_wink_icon)
417 mask_wink_border(current_wink_icon, &mask_solid);
418 if (timeout)
419 wink_timeout = gtk_timeout_add(300, end_wink, NULL);
420 else
421 wink_timeout = -1;
425 /* Remove everything on the current pinboard and disables the pinboard.
426 * Does not change any files. Does not change number_of_windows.
428 void pinboard_clear(void)
430 GList *next;
432 g_return_if_fail(current_pinboard != NULL);
434 next = current_pinboard->icons;
435 while (next)
437 PinIcon *icon = (PinIcon *) next->data;
439 next = next->next;
441 gtk_widget_destroy(icon->win);
444 g_free(current_pinboard->name);
445 g_free(current_pinboard);
446 current_pinboard = NULL;
448 release_xdnd_proxy(GDK_ROOT_WINDOW());
449 gdk_window_remove_filter(GDK_ROOT_PARENT(), proxy_filter, NULL);
450 gdk_window_set_user_data(GDK_ROOT_PARENT(), NULL);
453 /* If path is on the pinboard then it may have changed... check! */
454 void pinboard_may_update(guchar *path)
456 GList *next;
458 if (!current_pinboard)
459 return;
461 for (next = current_pinboard->icons; next; next = next->next)
463 PinIcon *icon = (PinIcon *) next->data;
465 if (strcmp(icon->path, path) == 0)
466 icon_may_update(icon);
470 /* Return the single selected icon, or NULL */
471 PinIcon *pinboard_selected_icon(void)
473 GList *next;
474 PinIcon *found = NULL;
476 g_return_val_if_fail(current_pinboard != NULL, NULL);
478 for (next = current_pinboard->icons; next; next = next->next)
480 PinIcon *icon = (PinIcon *) next->data;
482 if (icon->selected)
484 if (found)
485 return NULL; /* >1 icon selected */
486 else
487 found = icon;
491 return found;
494 /* Display the help for this application/item */
495 void pinboard_show_help(PinIcon *icon)
497 g_return_if_fail(icon != NULL);
499 show_item_help(icon->path, &icon->item);
502 void pinboard_clear_selection(void)
504 pinboard_select_only(NULL);
507 gboolean pinboard_is_selected(PinIcon *icon)
509 g_return_val_if_fail(icon != NULL, FALSE);
511 return icon->selected;
514 /* Set whether an icon is selected or not */
515 void pinboard_set_selected(PinIcon *icon, gboolean selected)
517 g_return_if_fail(icon != NULL);
519 if (icon->selected == selected)
520 return;
522 if (selected)
523 change_number_selected(+1);
524 else
525 change_number_selected(-1);
527 icon->selected = selected;
528 gtk_widget_queue_draw(icon->win);
531 /* Return a list of all the selected icons.
532 * g_list_free() the result.
534 GList *pinboard_get_selected(void)
536 GList *next;
537 GList *selected = NULL;
539 for (next = current_pinboard->icons; next; next = next->next)
541 PinIcon *i = (PinIcon *) next->data;
543 if (i->selected)
544 selected = g_list_append(selected, i);
547 return selected;
550 /* Clear the selection and then select this icon.
551 * Doesn't release and claim the selection unnecessarily.
552 * If icon is NULL, then just clears the selection.
554 void pinboard_select_only(PinIcon *icon)
556 GList *next;
558 g_return_if_fail(current_pinboard != NULL);
560 if (icon)
561 pinboard_set_selected(icon, TRUE);
563 for (next = current_pinboard->icons; next; next = next->next)
565 PinIcon *i = (PinIcon *) next->data;
567 if (i->selected && i != icon)
568 pinboard_set_selected(i, FALSE);
573 /****************************************************************
574 * INTERNAL FUNCTIONS *
575 ****************************************************************/
577 /* See if the file the icon points to has changed. Update the icon
578 * if so.
580 static void icon_may_update(PinIcon *icon)
582 MaskedPixmap *image = icon->item.image;
584 pixmap_ref(image);
585 dir_restat(icon->path, &icon->item);
587 if (icon->item.image != image)
589 int x = icon->x, y = icon->y;
590 int width, height;
592 set_size_and_shape(icon, &width, &height);
593 gdk_window_resize(icon->win->window, width, height);
594 offset_from_centre(icon, width, height, &x, &y);
595 gtk_widget_set_uposition(icon->win, x, y);
596 gtk_widget_queue_draw(icon->win);
599 pixmap_unref(image);
602 static gint end_wink(gpointer data)
604 pinboard_wink_item(NULL, FALSE);
605 return FALSE;
608 /* Make the wink border solid or transparent */
609 static void mask_wink_border(PinIcon *icon, GdkColor *alpha)
611 int width, height;
613 gdk_window_get_size(icon->paper->window, &width, &height);
615 gdk_gc_set_foreground(mask_gc, alpha);
616 gdk_draw_rectangle(icon->mask, mask_gc, FALSE,
617 0, 0, width - 1, height - 1);
618 gdk_draw_rectangle(icon->mask, mask_gc, FALSE,
619 1, 1, width - 3, height - 3);
621 gtk_widget_shape_combine_mask(icon->win, icon->mask, 0, 0);
623 gtk_widget_draw(icon->paper, NULL);
626 #define TEXT_AT(dx, dy) \
627 gdk_draw_string(icon->mask, font, mask_gc, \
628 text_x + dx, y + dy, \
629 item->leafname);
631 /* Updates the name_width field and resizes and masks the window.
632 * Returns the new width and height.
634 static void set_size_and_shape(PinIcon *icon, int *rwidth, int *rheight)
636 int width, height;
637 GdkFont *font = icon->win->style->font;
638 int font_height;
639 MaskedPixmap *image = icon->item.image;
640 DirItem *item = &icon->item;
641 int text_x, text_y;
643 font_height = font->ascent + font->descent;
644 item->name_width = gdk_string_width(font, item->leafname);
646 width = MAX(image->width, item->name_width + 2) +
647 2 * WINK_FRAME;
648 height = image->height + GAP + (font_height + 2) + 2 * WINK_FRAME;
649 gtk_widget_set_usize(icon->win, width, height);
651 if (icon->mask)
652 gdk_pixmap_unref(icon->mask);
653 icon->mask = gdk_pixmap_new(icon->win->window, width, height, 1);
654 if (!mask_gc)
655 mask_gc = gdk_gc_new(icon->mask);
657 /* Clear the mask to transparent */
658 gdk_gc_set_foreground(mask_gc, &mask_transp);
659 gdk_draw_rectangle(icon->mask, mask_gc, TRUE, 0, 0, width, height);
661 gdk_gc_set_foreground(mask_gc, &mask_solid);
662 /* Make the icon area solid */
663 if (image->mask)
665 gdk_draw_pixmap(icon->mask, mask_gc, image->mask,
666 0, 0,
667 (width - image->width) >> 1,
668 WINK_FRAME,
669 image->width,
670 image->height);
672 else
674 gdk_draw_rectangle(icon->mask, mask_gc, TRUE,
675 (width - image->width) >> 1,
676 WINK_FRAME,
677 image->width,
678 image->height);
681 gdk_gc_set_function(mask_gc, GDK_OR);
682 if (item->flags & ITEM_FLAG_SYMLINK)
684 gdk_draw_pixmap(icon->mask, mask_gc, im_symlink->mask,
685 0, 0, /* Source x,y */
686 (width - image->width) >> 1, /* Dest x */
687 WINK_FRAME, /* Dest y */
688 -1, -1);
690 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
692 /* Note: Both mount state pixmaps must have the same mask */
693 gdk_draw_pixmap(icon->mask, mask_gc, im_mounted->mask,
694 0, 0, /* Source x,y */
695 (width - image->width) >> 1, /* Dest x */
696 WINK_FRAME, /* Dest y */
697 -1, -1);
699 gdk_gc_set_function(mask_gc, GDK_COPY);
701 /* Mask off an area for the text (from o_text_bg) */
703 text_x = (width - item->name_width) >> 1;
704 text_y = WINK_FRAME + image->height + GAP + 1;
706 if (o_text_bg == TEXT_BG_SOLID)
708 gdk_draw_rectangle(icon->mask, mask_gc, TRUE,
709 (width - (item->name_width + 2)) >> 1,
710 WINK_FRAME + image->height + GAP,
711 item->name_width + 2, font_height + 2);
713 else
715 int y = text_y + font->ascent;
717 TEXT_AT(0, 0);
719 if (o_text_bg == TEXT_BG_OUTLINE)
721 TEXT_AT(1, 0);
722 TEXT_AT(1, 1);
723 TEXT_AT(0, 1);
724 TEXT_AT(-1, 1);
725 TEXT_AT(-1, 0);
726 TEXT_AT(-1, -1);
727 TEXT_AT(0, -1);
728 TEXT_AT(1, -1);
732 gtk_widget_shape_combine_mask(icon->win, icon->mask, 0, 0);
734 *rwidth = width;
735 *rheight = height;
738 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *icon)
740 GdkFont *font = icon->win->style->font;
741 int font_height;
742 int width, height;
743 int text_x, text_y;
744 DirItem *item = &icon->item;
745 MaskedPixmap *image = item->image;
746 int image_x;
747 GdkGC *gc = widget->style->black_gc;
748 GtkStateType state = icon->selected ? GTK_STATE_SELECTED
749 : GTK_STATE_NORMAL;
751 font_height = font->ascent + font->descent;
753 gdk_window_get_size(widget->window, &width, &height);
754 image_x = (width - image->width) >> 1;
756 /* TODO: If the shape extension is missing we might need to set
757 * the clip mask here...
759 gdk_draw_pixmap(widget->window, gc,
760 image->pixmap,
761 0, 0,
762 image_x,
763 WINK_FRAME,
764 image->width,
765 image->height);
767 if (item->flags & ITEM_FLAG_SYMLINK)
769 gdk_gc_set_clip_origin(gc, image_x, WINK_FRAME);
770 gdk_gc_set_clip_mask(gc, im_symlink->mask);
771 gdk_draw_pixmap(widget->window, gc,
772 im_symlink->pixmap,
773 0, 0, /* Source x,y */
774 image_x, WINK_FRAME, /* Dest x,y */
775 -1, -1);
776 gdk_gc_set_clip_mask(gc, NULL);
777 gdk_gc_set_clip_origin(gc, 0, 0);
779 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
781 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
782 ? im_mounted
783 : im_unmounted;
785 gdk_gc_set_clip_origin(gc, image_x, WINK_FRAME);
786 gdk_gc_set_clip_mask(gc, mp->mask);
787 gdk_draw_pixmap(widget->window, gc,
788 mp->pixmap,
789 0, 0, /* Source x,y */
790 image_x, WINK_FRAME, /* Dest x,y */
791 -1, -1);
792 gdk_gc_set_clip_mask(gc, NULL);
793 gdk_gc_set_clip_origin(gc, 0, 0);
796 text_x = (width - item->name_width) >> 1;
797 text_y = WINK_FRAME + image->height + GAP + 1;
799 if (o_text_bg != TEXT_BG_NONE)
801 gtk_paint_flat_box(widget->style, widget->window,
802 state,
803 GTK_SHADOW_NONE,
804 NULL, widget, "text",
805 text_x - 1,
806 text_y - 1,
807 item->name_width + 2,
808 font_height + 2);
811 gtk_paint_string(widget->style, widget->window,
812 state,
813 NULL, widget, "text",
814 text_x,
815 text_y + font->ascent,
816 item->leafname);
818 if (current_wink_icon == icon)
820 gdk_draw_rectangle(icon->paper->window,
821 icon->paper->style->white_gc,
822 FALSE,
823 0, 0, width - 1, height - 1);
824 gdk_draw_rectangle(icon->paper->window,
825 icon->paper->style->black_gc,
826 FALSE,
827 1, 1, width - 3, height - 3);
830 return FALSE;
833 #define OTHER_BUTTONS (GDK_BUTTON2_MASK | GDK_BUTTON3_MASK | \
834 GDK_BUTTON4_MASK | GDK_BUTTON5_MASK)
836 static gboolean button_release_event(GtkWidget *widget,
837 GdkEventButton *event,
838 PinIcon *icon)
840 int b = event->button;
841 DirItem *item = &icon->item;
843 if (pin_drag_type == DRAG_NONE)
844 return TRUE; /* Already done a drag? */
846 gtk_grab_remove(widget);
848 if (pin_drag_type == DRAG_MOVE_ICON)
849 pinboard_save();
850 else if (b == 1 && pin_drag_type == DRAG_MAY_COPY)
852 /* Could have been a copy, but the user didn't move
853 * far enough. Open the item instead.
855 pinboard_wink_item(icon, TRUE);
857 run_diritem(icon->path, item, NULL,
858 (event->state & GDK_SHIFT_MASK) != 0);
861 pin_drag_type = DRAG_NONE;
863 return TRUE;
866 static gboolean root_property_event(GtkWidget *widget,
867 GdkEventProperty *event,
868 gpointer data)
870 if (event->atom == win_button_proxy &&
871 event->state == GDK_PROPERTY_NEW_VALUE)
873 /* Setup forwarding on the new proxy window, if possible */
874 forward_root_clicks();
877 return FALSE;
880 static gboolean root_button_press(GtkWidget *widget,
881 GdkEventButton *event,
882 gpointer data)
884 if (event->button == collection_menu_button)
885 show_pinboard_menu(event, NULL);
886 else
887 pinboard_clear_selection();
889 return TRUE;
892 static gboolean enter_notify(GtkWidget *widget,
893 GdkEventCrossing *event,
894 PinIcon *icon)
896 icon_may_update(icon);
898 return FALSE;
901 static gboolean button_press_event(GtkWidget *widget,
902 GdkEventButton *event,
903 PinIcon *icon)
905 int b = event->button;
907 if (pin_drag_type != DRAG_NONE)
908 return TRUE;
910 if (b == collection_menu_button)
911 show_pinboard_menu(event, icon);
912 else if (b < 4)
914 if (event->state & GDK_CONTROL_MASK)
916 pin_drag_type = DRAG_NONE;
917 pinboard_set_selected(icon, !icon->selected);
919 else
921 drag_start_x = event->x_root;
922 drag_start_y = event->y_root;
923 gtk_grab_add(widget);
925 if (b == 1)
926 pin_drag_type = DRAG_MAY_COPY;
927 else
928 pin_drag_type = DRAG_MOVE_ICON;
932 return TRUE;
935 /* Return a text/uri-list of all the icons in the list */
936 static guchar *create_uri_list(GList *list)
938 GString *tmp;
939 guchar *retval;
940 guchar *leader;
942 tmp = g_string_new(NULL);
943 leader = g_strdup_printf("file://%s", o_no_hostnames
944 ? ""
945 : our_host_name());
947 for (; list; list = list->next)
949 PinIcon *icon = (PinIcon *) list->data;
951 g_string_append(tmp, leader);
952 g_string_append(tmp, icon->path);
953 g_string_append(tmp, "\r\n");
956 g_free(leader);
957 retval = tmp->str;
958 g_string_free(tmp, FALSE);
960 return retval;
963 static void start_drag(PinIcon *icon, GdkEventMotion *event)
965 GtkWidget *widget = icon->paper;
966 GList *selected;
968 if (!icon->selected)
969 pinboard_select_only(icon);
971 selected = pinboard_get_selected();
972 g_return_if_fail(selected != NULL);
974 pinboard_drag_in_progress = TRUE;
976 if (selected->next == NULL)
977 drag_one_item(widget, event, icon->path, &icon->item, FALSE);
978 else
980 guchar *uri_list;
982 uri_list = create_uri_list(selected);
983 drag_selection(widget, event, uri_list);
984 g_free(uri_list);
987 g_list_free(selected);
990 /* An icon is being dragged around... */
991 static gint icon_motion_notify(GtkWidget *widget,
992 GdkEventMotion *event,
993 PinIcon *icon)
995 int x = event->x_root;
996 int y = event->y_root;
998 if (pin_drag_type == DRAG_MOVE_ICON)
1000 int width, height;
1002 snap_to_grid(&x, &y);
1003 icon->x = x;
1004 icon->y = y;
1005 gdk_window_get_size(icon->win->window, &width, &height);
1006 offset_from_centre(icon, width, height, &x, &y);
1008 gdk_window_move(icon->win->window, x, y);
1010 else if (pin_drag_type == DRAG_MAY_COPY)
1012 int dx = x - drag_start_x;
1013 int dy = y - drag_start_y;
1015 if (ABS(dx) > 3 || ABS(dy) > 3)
1017 pin_drag_type = DRAG_NONE;
1018 gtk_grab_remove(widget);
1019 start_drag(icon, event);
1023 return TRUE;
1026 /* Called for each line in the pinboard file while loading a new board */
1027 static char *pin_from_file(guchar *line)
1029 int x, y, n;
1031 if (sscanf(line, " %d , %d , %n", &x, &y, &n) < 2)
1032 return NULL; /* Ignore format errors */
1034 pinboard_pin(line + n, NULL, x, y);
1036 return NULL;
1039 /* Make sure that clicks and drops on the root window come to us...
1040 * False if an error occurred (ie, someone else is using it).
1042 static gboolean add_root_handlers(void)
1044 GdkWindow *root;
1046 if (!proxy_invisible)
1048 GtkTargetEntry target_table[] =
1050 {"text/uri-list", 0, TARGET_URI_LIST},
1051 {"STRING", 0, TARGET_STRING},
1054 win_button_proxy = gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY",
1055 FALSE);
1056 proxy_invisible = gtk_invisible_new();
1057 gtk_widget_show(proxy_invisible);
1059 gdk_window_add_filter(proxy_invisible->window,
1060 proxy_filter, NULL);
1063 gtk_signal_connect(GTK_OBJECT(proxy_invisible),
1064 "property_notify_event",
1065 GTK_SIGNAL_FUNC(root_property_event), NULL);
1066 gtk_signal_connect(GTK_OBJECT(proxy_invisible),
1067 "button_press_event",
1068 GTK_SIGNAL_FUNC(root_button_press), NULL);
1070 /* Drag and drop handlers */
1071 drag_set_pinboard_dest(proxy_invisible);
1072 gtk_signal_connect(GTK_OBJECT(proxy_invisible), "drag_motion",
1073 GTK_SIGNAL_FUNC(bg_drag_motion),
1074 NULL);
1076 /* The proxy window is also used to hold the selection... */
1077 gtk_signal_connect(GTK_OBJECT(proxy_invisible),
1078 "selection_clear_event",
1079 GTK_SIGNAL_FUNC(lose_selection),
1080 NULL);
1082 gtk_signal_connect(GTK_OBJECT(proxy_invisible),
1083 "selection_get",
1084 GTK_SIGNAL_FUNC(selection_get), NULL);
1086 gtk_selection_add_targets(proxy_invisible,
1087 GDK_SELECTION_PRIMARY,
1088 target_table,
1089 sizeof(target_table) / sizeof(*target_table));
1092 root = gdk_window_lookup(GDK_ROOT_WINDOW());
1093 if (!root)
1094 root = gdk_window_foreign_new(GDK_ROOT_WINDOW());
1096 if (!setup_xdnd_proxy(GDK_ROOT_WINDOW(), proxy_invisible->window))
1097 return FALSE;
1099 /* Forward events from the root window to our proxy window */
1100 gdk_window_add_filter(GDK_ROOT_PARENT(), proxy_filter, NULL);
1101 gdk_window_set_user_data(GDK_ROOT_PARENT(), proxy_invisible);
1102 gdk_window_set_events(GDK_ROOT_PARENT(),
1103 gdk_window_get_events(GDK_ROOT_PARENT()) |
1104 GDK_PROPERTY_CHANGE_MASK);
1106 forward_root_clicks();
1108 return TRUE;
1111 /* See if the window manager is offering to forward root window clicks.
1112 * If so, grab them. Otherwise, do nothing.
1113 * Call this whenever the _WIN_DESKTOP_BUTTON_PROXY property changes.
1115 static void forward_root_clicks(void)
1117 click_proxy_gdk_window = find_click_proxy_window();
1118 if (!click_proxy_gdk_window)
1119 return;
1121 /* Events on the wm's proxy are dealt with by our proxy widget */
1122 gdk_window_set_user_data(click_proxy_gdk_window, proxy_invisible);
1123 gdk_window_add_filter(click_proxy_gdk_window, proxy_filter, NULL);
1125 /* The proxy window for clicks sends us button press events with
1126 * SubstructureNotifyMask. We need StructureNotifyMask to receive
1127 * DestroyNotify events, too.
1129 XSelectInput(GDK_DISPLAY(),
1130 GDK_WINDOW_XWINDOW(click_proxy_gdk_window),
1131 SubstructureNotifyMask | StructureNotifyMask);
1134 /* Write the current state of the pinboard to the current pinboard file */
1135 static void pinboard_save(void)
1137 guchar *save;
1138 guchar *leaf;
1139 GString *tmp;
1140 FILE *file = NULL;
1141 GList *next;
1142 guchar *save_new = NULL;
1144 g_return_if_fail(current_pinboard != NULL);
1146 leaf = g_strconcat("pb_", current_pinboard->name, NULL);
1147 save = choices_find_path_save(leaf, "ROX-Filer", TRUE);
1148 g_free(leaf);
1150 if (!save)
1151 return;
1153 save_new = g_strconcat(save, ".new", NULL);
1154 file = fopen(save_new, "wb");
1155 if (!file)
1156 goto err;
1158 tmp = g_string_new(NULL);
1159 for (next = current_pinboard->icons; next; next = next->next)
1161 PinIcon *icon = (PinIcon *) next->data;
1163 g_string_sprintf(tmp, "%d, %d, %s\n",
1164 icon->x, icon->y, icon->path);
1165 if (fwrite(tmp->str, 1, tmp->len, file) < tmp->len)
1166 goto err;
1169 g_string_free(tmp, TRUE);
1171 if (fclose(file))
1173 file = NULL;
1174 goto err;
1177 file = NULL;
1179 if (rename(save_new, save))
1180 goto err;
1182 goto out;
1183 err:
1184 delayed_error(_("Error saving pinboard"), g_strerror(errno));
1185 out:
1186 if (file)
1187 fclose(file);
1188 g_free(save_new);
1192 * Filter that translates proxied events from virtual root windows into normal
1193 * Gdk events for the proxy_invisible widget. Stolen from gmc.
1195 * Also gets events from the root window.
1197 static GdkFilterReturn proxy_filter(GdkXEvent *xevent,
1198 GdkEvent *event,
1199 gpointer data)
1201 XEvent *xev;
1202 GdkWindow *proxy = proxy_invisible->window;
1204 xev = xevent;
1206 switch (xev->type) {
1207 case ButtonPress:
1208 case ButtonRelease:
1209 /* Translate button events into events that come from
1210 * the proxy window, so that we can catch them as a
1211 * signal from the invisible widget.
1213 if (xev->type == ButtonPress)
1214 event->button.type = GDK_BUTTON_PRESS;
1215 else
1216 event->button.type = GDK_BUTTON_RELEASE;
1218 gdk_window_ref(proxy);
1220 event->button.window = proxy;
1221 event->button.send_event = xev->xbutton.send_event;
1222 event->button.time = xev->xbutton.time;
1223 event->button.x_root = xev->xbutton.x_root;
1224 event->button.y_root = xev->xbutton.y_root;
1225 event->button.x = xev->xbutton.x;
1226 event->button.y = xev->xbutton.y;
1227 event->button.state = xev->xbutton.state;
1228 event->button.button = xev->xbutton.button;
1230 return GDK_FILTER_TRANSLATE;
1232 case DestroyNotify:
1233 /* XXX: I have no idea why this helps, but it does! */
1234 /* The proxy window was destroyed (i.e. the window
1235 * manager died), so we have to cope with it
1237 if (((GdkEventAny *) event)->window == proxy)
1238 gdk_window_destroy_notify(proxy);
1240 return GDK_FILTER_REMOVE;
1242 default:
1243 break;
1246 return GDK_FILTER_CONTINUE;
1249 /* Does not save the new state */
1250 static void icon_destroyed(GtkWidget *widget, PinIcon *icon)
1252 g_return_if_fail(icon != NULL);
1254 if (icon->selected)
1255 change_number_selected(-1);
1257 gdk_pixmap_unref(icon->mask);
1258 dir_item_clear(&icon->item);
1259 g_free(icon->path);
1260 g_free(icon);
1262 if (current_pinboard)
1263 current_pinboard->icons =
1264 g_list_remove(current_pinboard->icons, icon);
1267 #define GRID_STEP 32
1269 static void snap_to_grid(int *x, int *y)
1271 *x = ((*x + GRID_STEP / 2) / GRID_STEP) * GRID_STEP;
1272 *y = ((*y + GRID_STEP / 2) / GRID_STEP) * GRID_STEP;
1275 /* Convert (x,y) from a centre point to a window position */
1276 static void offset_from_centre(PinIcon *icon,
1277 int width, int height,
1278 int *x, int *y)
1280 *x -= width >> 1;
1281 *y -= height - (icon->paper->style->font->descent >> 1);
1282 *x = CLAMP(*x, 0, screen_width - width);
1283 *y = CLAMP(*y, 0, screen_height - height);
1286 /* Same as drag_set_dest(), but for pinboard icons */
1287 static void drag_set_pinicon_dest(PinIcon *icon)
1289 GtkObject *obj = GTK_OBJECT(icon->paper);
1291 make_drop_target(icon->paper);
1293 gtk_signal_connect(obj, "drag_motion",
1294 GTK_SIGNAL_FUNC(drag_motion), icon);
1295 gtk_signal_connect(obj, "drag_leave",
1296 GTK_SIGNAL_FUNC(drag_leave), icon);
1297 gtk_signal_connect(obj, "drag_end",
1298 GTK_SIGNAL_FUNC(drag_end), icon);
1301 gtk_signal_connect(obj, "drag_end",
1302 GTK_SIGNAL_FUNC(drag_end), filer_window);
1306 /* Called during the drag when the mouse is in a widget registered
1307 * as a drop target. Returns TRUE if we can accept the drop.
1309 static gboolean drag_motion(GtkWidget *widget,
1310 GdkDragContext *context,
1311 gint x,
1312 gint y,
1313 guint time,
1314 PinIcon *icon)
1316 GdkDragAction action = context->suggested_action;
1317 char *type = NULL;
1318 DirItem *item = &icon->item;
1320 if (gtk_drag_get_source_widget(context) == widget)
1321 goto out; /* Can't drag something to itself! */
1323 if (icon->selected)
1324 goto out; /* Can't drag a selection to itself */
1326 if (provides(context, _rox_run_action))
1328 /* This is a special internal type. The user is dragging
1329 * to an executable item to set the run action.
1332 if (item->flags & (ITEM_FLAG_APPDIR | ITEM_FLAG_EXEC_FILE))
1333 type = drop_dest_prog;
1334 else
1335 goto out;
1338 else
1340 /* If we didn't drop onto a directory, application or
1341 * executable file then give up.
1343 if (item->base_type != TYPE_DIRECTORY
1344 && !(item->flags & ITEM_FLAG_EXEC_FILE))
1345 goto out;
1348 if (item->base_type == TYPE_DIRECTORY &&
1349 !(item->flags & ITEM_FLAG_APPDIR))
1351 if (provides(context, text_uri_list) ||
1352 provides(context, XdndDirectSave0))
1353 type = drop_dest_dir;
1355 else
1357 if (provides(context, text_uri_list) ||
1358 provides(context, application_octet_stream))
1359 type = drop_dest_prog;
1362 out:
1364 #if 0
1365 /* We actually must pretend to accept the drop, even if the
1366 * directory isn't writeable, so that the spring-opening
1367 * thing works.
1370 /* Don't allow drops to non-writeable directories */
1371 if (type == drop_dest_dir && access(icon->path, W_OK) != 0)
1372 type = NULL;
1373 #endif
1375 g_dataset_set_data(context, "drop_dest_type", type);
1376 if (type)
1378 gdk_drag_status(context, action, time);
1379 g_dataset_set_data_full(context, "drop_dest_path",
1380 g_strdup(icon->path), g_free);
1381 if (type == drop_dest_dir)
1382 dnd_spring_load(context);
1384 pinboard_wink_item(icon, FALSE);
1387 return type != NULL;
1390 static void drag_leave(GtkWidget *widget,
1391 GdkDragContext *context,
1392 guint32 time,
1393 PinIcon *icon)
1395 pinboard_wink_item(NULL, FALSE);
1396 dnd_spring_abort();
1399 /* When changing the 'selected' attribute of an icon, call this
1400 * to update the global counter and claim or release the primary
1401 * selection as needed.
1403 static void change_number_selected(int delta)
1405 guint32 time;
1407 g_return_if_fail(delta != 0);
1408 g_return_if_fail(number_selected + delta >= 0);
1410 if (number_selected == 0)
1412 time = gdk_event_get_time(gtk_get_current_event());
1414 gtk_selection_owner_set(proxy_invisible,
1415 GDK_SELECTION_PRIMARY,
1416 time);
1419 number_selected += delta;
1421 if (number_selected == 0)
1423 time = gdk_event_get_time(gtk_get_current_event());
1425 gtk_selection_owner_set(NULL,
1426 GDK_SELECTION_PRIMARY,
1427 time);
1431 /* Called when another application wants the contents of our selection */
1432 static void selection_get(GtkWidget *widget,
1433 GtkSelectionData *selection_data,
1434 guint info,
1435 guint time,
1436 gpointer data)
1438 GString *str;
1439 GList *next;
1440 guchar *leader = NULL;
1442 str = g_string_new(NULL);
1444 if (info == TARGET_URI_LIST)
1445 leader = g_strdup_printf("file://%s",
1446 o_no_hostnames ? "" : our_host_name());
1448 for (next = current_pinboard->icons; next; next = next->next)
1450 PinIcon *icon = (PinIcon *) next->data;
1452 if (!icon->selected)
1453 continue;
1455 if (leader)
1456 g_string_append(str, leader);
1457 g_string_append(str, icon->path);
1458 g_string_append_c(str, ' ');
1461 g_free(leader);
1463 gtk_selection_data_set(selection_data,
1464 gdk_atom_intern("STRING", FALSE),
1466 str->str,
1467 str->len ? str->len - 1 : 0);
1469 g_string_free(str, TRUE);
1472 /* Called when another application takes the selection away from us */
1473 static gint lose_selection(GtkWidget *widget, GdkEventSelection *event)
1475 /* 'lock' number_selected so that we don't send any events */
1476 number_selected++;
1477 pinboard_clear_selection();
1478 number_selected--;
1480 return TRUE;
1483 static gboolean bg_drag_motion(GtkWidget *widget,
1484 GdkDragContext *context,
1485 gint x,
1486 gint y,
1487 guint time,
1488 gpointer data)
1490 /* Dragging from the pinboard to the pinboard is not allowed */
1491 if (pinboard_drag_in_progress)
1492 return FALSE;
1494 gdk_drag_status(context, context->suggested_action, time);
1495 return TRUE;
1498 static void drag_end(GtkWidget *widget,
1499 GdkDragContext *context,
1500 FilerWindow *filer_window)
1502 pinboard_drag_in_progress = FALSE;
1503 pinboard_clear_selection();
1506 /* OPTIONS BITS */
1508 static GtkToggleButton *radio_bg_none;
1509 static GtkToggleButton *radio_bg_outline;
1510 static GtkToggleButton *radio_bg_solid;
1512 /* Build up some option widgets to go in the options dialog, but don't
1513 * fill them in yet.
1515 static GtkWidget *create_options(void)
1517 GtkWidget *vbox, *tog;
1519 vbox = gtk_vbox_new(FALSE, 0);
1520 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
1522 tog = gtk_radio_button_new_with_label( NULL, _("No background"));
1523 radio_bg_none = GTK_TOGGLE_BUTTON(tog);
1524 OPTION_TIP(tog, "The text is drawn directly on the desktop "
1525 "background.");
1526 gtk_box_pack_start(GTK_BOX(vbox), tog, FALSE, TRUE, 0);
1528 tog = gtk_radio_button_new_with_label(
1529 gtk_radio_button_group(GTK_RADIO_BUTTON(tog)),
1530 _("Outlined text"));
1531 radio_bg_outline = GTK_TOGGLE_BUTTON(tog);
1532 OPTION_TIP(tog, "The text has a thin outline around each letter.");
1533 gtk_box_pack_start(GTK_BOX(vbox), tog, FALSE, TRUE, 0);
1535 tog = gtk_radio_button_new_with_label(
1536 gtk_radio_button_group(GTK_RADIO_BUTTON(tog)),
1537 _("Rectangular background slab"));
1538 radio_bg_solid = GTK_TOGGLE_BUTTON(tog);
1539 OPTION_TIP(tog, "The text is drawn on a solid rectangle.");
1540 gtk_box_pack_start(GTK_BOX(vbox), tog, FALSE, TRUE, 0);
1542 return vbox;
1545 /* Reflect current state by changing the widgets in the options box */
1546 static void update_options()
1548 if (o_text_bg == TEXT_BG_SOLID)
1549 gtk_toggle_button_set_active(radio_bg_solid, TRUE);
1550 else if (o_text_bg == TEXT_BG_OUTLINE)
1551 gtk_toggle_button_set_active(radio_bg_outline, TRUE);
1552 else
1553 gtk_toggle_button_set_active(radio_bg_none, TRUE);
1556 /* Set current values by reading the states of the widgets in the options box */
1557 static void set_options()
1559 TextBgType old = o_text_bg;
1561 if (gtk_toggle_button_get_active(radio_bg_none))
1562 o_text_bg = TEXT_BG_NONE;
1563 else if (gtk_toggle_button_get_active(radio_bg_outline))
1564 o_text_bg = TEXT_BG_OUTLINE;
1565 else
1566 o_text_bg = TEXT_BG_SOLID;
1568 if (o_text_bg != old && current_pinboard)
1570 guchar *name;
1572 name = g_strdup(current_pinboard->name);
1573 pinboard_activate(name);
1574 g_free(name);
1578 static void save_options()
1580 option_write("pinboard_text_bg",
1581 o_text_bg == TEXT_BG_NONE ? "None" :
1582 o_text_bg == TEXT_BG_OUTLINE ? "Outline" :
1583 "Solid");
1586 static char *text_bg(char *data)
1588 if (g_strcasecmp(data, "None") == 0)
1589 o_text_bg = TEXT_BG_NONE;
1590 else if (g_strcasecmp(data, "Outline") == 0)
1591 o_text_bg = TEXT_BG_OUTLINE;
1592 else if (g_strcasecmp(data, "Solid") == 0)
1593 o_text_bg = TEXT_BG_SOLID;
1594 else
1595 return _("Unknown pinboard text background type");
1597 return NULL;