r345: Fixed a bug that prevented i18n from working.
[rox-filer/dt.git] / ROX-Filer / src / pinboard.c
blob544a0c5a7da9eeb30a069fdd0bf4cce78fd6c96f
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"
47 #include "mount.h"
49 /* The number of pixels between the bottom of the image and the top
50 * of the text.
52 #define GAP 4
54 /* The size of the border around the icon which is used when winking */
55 #define WINK_FRAME 2
57 enum
59 TARGET_STRING,
60 TARGET_URI_LIST,
63 struct _PinIcon {
64 GtkWidget *win, *paper;
65 GdkBitmap *mask;
66 guchar *path;
67 DirItem item;
68 int x, y;
69 gboolean selected;
72 struct _Pinboard {
73 guchar *name; /* Leaf name */
74 GList *icons;
77 extern int collection_menu_button;
79 static Pinboard *current_pinboard = NULL;
80 static gint loading_pinboard = 0; /* Non-zero => loading */
82 static PinIcon *current_wink_icon = NULL;
83 static gint wink_timeout;
85 static gint number_selected = 0;
87 static GdkColor mask_solid = {1, 1, 1, 1};
88 static GdkColor mask_transp = {0, 0, 0, 0};
89 static GdkGC *mask_gc = NULL;
91 /* Proxy window for DnD and clicks on the desktop */
92 static GtkWidget *proxy_invisible;
94 /* The window (owned by the wm) which root clicks are forwarded to.
95 * NULL if wm does not support forwarding clicks.
97 static GdkWindow *click_proxy_gdk_window = NULL;
98 static GdkAtom win_button_proxy; /* _WIN_DESKTOP_BUTTON_PROXY */
100 static gint drag_start_x, drag_start_y;
101 /* If the drag type is not DRAG_NONE then there is a grab in progress */
102 typedef enum {
103 DRAG_NONE,
104 DRAG_MOVE_ICON, /* When you hold down Adjust on an icon */
105 DRAG_MAY_COPY, /* When you hold down Select on an icon, but */
106 /* before the drag has actually started. */
107 } PinDragType;
108 static PinDragType pin_drag_type = DRAG_NONE;
110 /* This is TRUE while the user is dragging from a pinned icon.
111 * We use it to prevent dragging from the pinboard to itself.
113 static gboolean pinboard_drag_in_progress = FALSE;
115 /* Options bits */
116 static GtkWidget *create_options();
117 static void update_options();
118 static void set_options();
119 static void save_options();
120 static char *text_bg(char *data);
122 static OptionsSection options =
124 N_("Pinboard options"),
125 create_options,
126 update_options,
127 set_options,
128 save_options
131 typedef enum {TEXT_BG_SOLID, TEXT_BG_OUTLINE, TEXT_BG_NONE} TextBgType;
132 TextBgType o_text_bg = TEXT_BG_SOLID;
134 /* Static prototypes */
135 static void set_size_and_shape(PinIcon *icon, int *rwidth, int *rheight);
136 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *icon);
137 static void mask_wink_border(PinIcon *icon, GdkColor *alpha);
138 static gint end_wink(gpointer data);
139 static gboolean button_release_event(GtkWidget *widget,
140 GdkEventButton *event,
141 PinIcon *icon);
142 static gboolean root_property_event(GtkWidget *widget,
143 GdkEventProperty *event,
144 gpointer data);
145 static gboolean root_button_press(GtkWidget *widget,
146 GdkEventButton *event,
147 gpointer data);
148 static gboolean enter_notify(GtkWidget *widget,
149 GdkEventCrossing *event,
150 PinIcon *icon);
151 static gboolean button_press_event(GtkWidget *widget,
152 GdkEventButton *event,
153 PinIcon *icon);
154 static gint icon_motion_notify(GtkWidget *widget,
155 GdkEventMotion *event,
156 PinIcon *icon);
157 static char *pin_from_file(guchar *line);
158 static gboolean add_root_handlers(void);
159 static void pinboard_save(void);
160 static GdkFilterReturn proxy_filter(GdkXEvent *xevent,
161 GdkEvent *event,
162 gpointer data);
163 static void icon_destroyed(GtkWidget *widget, PinIcon *icon);
164 static void snap_to_grid(int *x, int *y);
165 static void offset_from_centre(PinIcon *icon,
166 int width, int height,
167 int *x, int *y);
168 static gboolean drag_motion(GtkWidget *widget,
169 GdkDragContext *context,
170 gint x,
171 gint y,
172 guint time,
173 PinIcon *icon);
174 static void drag_set_pinicon_dest(PinIcon *icon);
175 static void drag_leave(GtkWidget *widget,
176 GdkDragContext *context,
177 guint32 time,
178 PinIcon *icon);
179 static void icon_may_update(PinIcon *icon);
180 static void forward_root_clicks(void);
181 static void change_number_selected(int delta);
182 static gint lose_selection(GtkWidget *widget, GdkEventSelection *event);
183 static void selection_get(GtkWidget *widget,
184 GtkSelectionData *selection_data,
185 guint info,
186 guint time,
187 gpointer data);
188 static gboolean bg_drag_motion(GtkWidget *widget,
189 GdkDragContext *context,
190 gint x,
191 gint y,
192 guint time,
193 gpointer data);
194 static void drag_end(GtkWidget *widget,
195 GdkDragContext *context,
196 FilerWindow *filer_window);
200 /****************************************************************
201 * EXTERNAL INTERFACE *
202 ****************************************************************/
204 void pinboard_init(void)
206 options_sections = g_slist_prepend(options_sections, &options);
207 option_register("pinboard_text_bg", text_bg);
210 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
211 * and make it the current pinboard.
212 * Any existing pinned items are removed. You must call this
213 * at least once before using the pinboard. NULL disables the
214 * pinboard.
216 void pinboard_activate(guchar *name)
218 Pinboard *old_board = current_pinboard;
219 guchar *path, *slash;
221 /* Treat an empty name the same as NULL */
222 if (!*name)
223 name = NULL;
225 if (old_board)
227 pinboard_clear();
228 number_of_windows--;
231 if (!name)
233 if (number_of_windows < 1 && gtk_main_level() > 0)
234 gtk_main_quit();
235 return;
238 if (!add_root_handlers())
240 delayed_error(PROJECT, _("Another application is already "
241 "managing the pinboard!"));
242 return;
245 number_of_windows++;
247 slash = strchr(name, '/');
248 if (slash)
249 path = g_strdup(name);
250 else
252 guchar *leaf;
254 leaf = g_strconcat("pb_", name, NULL);
255 path = choices_find_path_load(leaf, "ROX-Filer");
256 g_free(leaf);
259 current_pinboard = g_new(Pinboard, 1);
260 current_pinboard->name = g_strdup(name);
261 current_pinboard->icons = NULL;
263 if (path)
265 loading_pinboard++;
266 parse_file(path, pin_from_file);
267 g_free(path);
268 loading_pinboard--;
272 /* Add a new icon to the background.
273 * 'path' should be an absolute pathname.
274 * 'x' and 'y' are the coordinates of the point in the middle of the text.
275 * 'name' is the name to use. If NULL then the leafname of path is used.
277 void pinboard_pin(guchar *path, guchar *name, int x, int y)
279 PinIcon *icon;
280 int path_len;
281 int width, height;
283 g_return_if_fail(path != NULL);
284 g_return_if_fail(current_pinboard != NULL);
286 path_len = strlen(path);
287 while (path_len > 0 && path[path_len - 1] == '/')
288 path_len--;
290 icon = g_new(PinIcon, 1);
291 icon->selected = FALSE;
292 icon->path = g_strndup(path, path_len);
293 icon->mask = NULL;
294 snap_to_grid(&x, &y);
295 icon->x = x;
296 icon->y = y;
298 dir_stat(icon->path, &icon->item);
300 if (!name)
302 name = strrchr(icon->path, '/');
303 if (name)
304 name++;
305 else
306 name = icon->path; /* Shouldn't happen */
309 icon->item.leafname = g_strdup(name);
311 icon->win = gtk_window_new(GTK_WINDOW_DIALOG);
312 gtk_window_set_wmclass(GTK_WINDOW(icon->win), "ROX-Pinboard", PROJECT);
314 icon->paper = gtk_drawing_area_new();
315 gtk_container_add(GTK_CONTAINER(icon->win), icon->paper);
316 drag_set_pinicon_dest(icon);
317 gtk_signal_connect(GTK_OBJECT(icon->paper), "drag_data_get",
318 drag_data_get, NULL);
320 gtk_widget_realize(icon->win);
321 if (override_redirect)
323 gdk_window_lower(icon->win->window);
324 gdk_window_set_override_redirect(icon->win->window, TRUE);
326 else
327 make_panel_window(icon->win->window);
329 set_size_and_shape(icon, &width, &height);
330 offset_from_centre(icon, width, height, &x, &y);
331 gtk_widget_set_uposition(icon->win, x, y);
333 gtk_widget_add_events(icon->paper,
334 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
335 GDK_BUTTON1_MOTION_MASK | GDK_ENTER_NOTIFY_MASK |
336 GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK);
337 gtk_signal_connect(GTK_OBJECT(icon->paper), "enter-notify-event",
338 GTK_SIGNAL_FUNC(enter_notify), icon);
339 gtk_signal_connect(GTK_OBJECT(icon->paper), "button-press-event",
340 GTK_SIGNAL_FUNC(button_press_event), icon);
341 gtk_signal_connect(GTK_OBJECT(icon->paper), "button-release-event",
342 GTK_SIGNAL_FUNC(button_release_event), icon);
343 gtk_signal_connect(GTK_OBJECT(icon->paper), "motion-notify-event",
344 GTK_SIGNAL_FUNC(icon_motion_notify), icon);
345 gtk_signal_connect(GTK_OBJECT(icon->paper), "expose-event",
346 GTK_SIGNAL_FUNC(draw_icon), icon);
347 gtk_signal_connect(GTK_OBJECT(icon->win), "destroy",
348 GTK_SIGNAL_FUNC(icon_destroyed), icon);
350 current_pinboard->icons = g_list_prepend(current_pinboard->icons,
351 icon);
352 gtk_widget_show_all(icon->win);
354 if (!loading_pinboard)
355 pinboard_save();
358 /* Remove an icon from the pinboard */
359 void pinboard_unpin(PinIcon *icon)
361 g_return_if_fail(icon != NULL);
363 gtk_widget_destroy(icon->win);
364 pinboard_save();
367 /* Unpin all selected items */
368 void pinboard_unpin_selection(void)
370 GList *next;
372 g_return_if_fail(current_pinboard != NULL);
374 if (number_selected == 0)
376 delayed_error(PROJECT,
377 _("You should first select some pinned icons to "
378 "unpin. Hold down the Ctrl key to select some icons."));
379 return;
382 next = current_pinboard->icons;
383 while (next)
385 PinIcon *icon = (PinIcon *) next->data;
387 next = next->next;
389 if (icon->selected)
390 gtk_widget_destroy(icon->win);
393 pinboard_save();
396 /* Put a border around the icon, briefly.
397 * If icon is NULL then cancel any existing wink.
398 * The icon will automatically unhighlight unless timeout is FALSE,
399 * in which case you must call this function again (with NULL or another
400 * icon) to remove the highlight.
402 void pinboard_wink_item(PinIcon *icon, gboolean timeout)
404 if (current_wink_icon == icon)
405 return;
407 if (current_wink_icon)
409 mask_wink_border(current_wink_icon, &mask_transp);
410 if (wink_timeout != -1)
411 gtk_timeout_remove(wink_timeout);
414 current_wink_icon = icon;
416 if (current_wink_icon)
418 mask_wink_border(current_wink_icon, &mask_solid);
419 if (timeout)
420 wink_timeout = gtk_timeout_add(300, end_wink, NULL);
421 else
422 wink_timeout = -1;
426 /* Remove everything on the current pinboard and disables the pinboard.
427 * Does not change any files. Does not change number_of_windows.
429 void pinboard_clear(void)
431 GList *next;
433 g_return_if_fail(current_pinboard != NULL);
435 next = current_pinboard->icons;
436 while (next)
438 PinIcon *icon = (PinIcon *) next->data;
440 next = next->next;
442 gtk_widget_destroy(icon->win);
445 g_free(current_pinboard->name);
446 g_free(current_pinboard);
447 current_pinboard = NULL;
449 release_xdnd_proxy(GDK_ROOT_WINDOW());
450 gdk_window_remove_filter(GDK_ROOT_PARENT(), proxy_filter, NULL);
451 gdk_window_set_user_data(GDK_ROOT_PARENT(), NULL);
454 /* If path is on the pinboard then it may have changed... check! */
455 void pinboard_may_update(guchar *path)
457 GList *next;
459 if (!current_pinboard)
460 return;
462 for (next = current_pinboard->icons; next; next = next->next)
464 PinIcon *icon = (PinIcon *) next->data;
466 if (strcmp(icon->path, path) == 0)
467 icon_may_update(icon);
471 /* Return the single selected icon, or NULL */
472 PinIcon *pinboard_selected_icon(void)
474 GList *next;
475 PinIcon *found = NULL;
477 g_return_val_if_fail(current_pinboard != NULL, NULL);
479 for (next = current_pinboard->icons; next; next = next->next)
481 PinIcon *icon = (PinIcon *) next->data;
483 if (icon->selected)
485 if (found)
486 return NULL; /* >1 icon selected */
487 else
488 found = icon;
492 return found;
495 /* Display the help for this application/item */
496 void pinboard_show_help(PinIcon *icon)
498 g_return_if_fail(icon != NULL);
500 show_item_help(icon->path, &icon->item);
503 void pinboard_clear_selection(void)
505 pinboard_select_only(NULL);
508 gboolean pinboard_is_selected(PinIcon *icon)
510 g_return_val_if_fail(icon != NULL, FALSE);
512 return icon->selected;
515 /* Set whether an icon is selected or not */
516 void pinboard_set_selected(PinIcon *icon, gboolean selected)
518 g_return_if_fail(icon != NULL);
520 if (icon->selected == selected)
521 return;
523 if (selected)
524 change_number_selected(+1);
525 else
526 change_number_selected(-1);
528 icon->selected = selected;
529 gtk_widget_queue_draw(icon->win);
532 /* Return a list of all the selected icons.
533 * g_list_free() the result.
535 GList *pinboard_get_selected(void)
537 GList *next;
538 GList *selected = NULL;
540 for (next = current_pinboard->icons; next; next = next->next)
542 PinIcon *i = (PinIcon *) next->data;
544 if (i->selected)
545 selected = g_list_append(selected, i);
548 return selected;
551 /* Clear the selection and then select this icon.
552 * Doesn't release and claim the selection unnecessarily.
553 * If icon is NULL, then just clears the selection.
555 void pinboard_select_only(PinIcon *icon)
557 GList *next;
559 g_return_if_fail(current_pinboard != NULL);
561 if (icon)
562 pinboard_set_selected(icon, TRUE);
564 for (next = current_pinboard->icons; next; next = next->next)
566 PinIcon *i = (PinIcon *) next->data;
568 if (i->selected && i != icon)
569 pinboard_set_selected(i, FALSE);
574 /****************************************************************
575 * INTERNAL FUNCTIONS *
576 ****************************************************************/
578 /* See if the file the icon points to has changed. Update the icon
579 * if so.
581 static void icon_may_update(PinIcon *icon)
583 MaskedPixmap *image = icon->item.image;
584 int flags = icon->item.flags;
586 pixmap_ref(image);
587 mount_update(FALSE);
588 dir_restat(icon->path, &icon->item);
590 if (icon->item.image != image || icon->item.flags != flags)
592 int x = icon->x, y = icon->y;
593 int width, height;
595 set_size_and_shape(icon, &width, &height);
596 gdk_window_resize(icon->win->window, width, height);
597 offset_from_centre(icon, width, height, &x, &y);
598 gtk_widget_set_uposition(icon->win, x, y);
599 gtk_widget_queue_draw(icon->win);
602 pixmap_unref(image);
605 static gint end_wink(gpointer data)
607 pinboard_wink_item(NULL, FALSE);
608 return FALSE;
611 /* Make the wink border solid or transparent */
612 static void mask_wink_border(PinIcon *icon, GdkColor *alpha)
614 int width, height;
616 gdk_window_get_size(icon->paper->window, &width, &height);
618 gdk_gc_set_foreground(mask_gc, alpha);
619 gdk_draw_rectangle(icon->mask, mask_gc, FALSE,
620 0, 0, width - 1, height - 1);
621 gdk_draw_rectangle(icon->mask, mask_gc, FALSE,
622 1, 1, width - 3, height - 3);
624 gtk_widget_shape_combine_mask(icon->win, icon->mask, 0, 0);
626 gtk_widget_draw(icon->paper, NULL);
629 #define TEXT_AT(dx, dy) \
630 gdk_draw_string(icon->mask, font, mask_gc, \
631 text_x + dx, y + dy, \
632 item->leafname);
634 /* Updates the name_width field and resizes and masks the window.
635 * Returns the new width and height.
637 static void set_size_and_shape(PinIcon *icon, int *rwidth, int *rheight)
639 int width, height;
640 GdkFont *font = icon->win->style->font;
641 int font_height;
642 MaskedPixmap *image = icon->item.image;
643 DirItem *item = &icon->item;
644 int text_x, text_y;
646 font_height = font->ascent + font->descent;
647 item->name_width = gdk_string_width(font, item->leafname);
649 width = MAX(image->width, item->name_width + 2) +
650 2 * WINK_FRAME;
651 height = image->height + GAP + (font_height + 2) + 2 * WINK_FRAME;
652 gtk_widget_set_usize(icon->win, width, height);
654 if (icon->mask)
655 gdk_pixmap_unref(icon->mask);
656 icon->mask = gdk_pixmap_new(icon->win->window, width, height, 1);
657 if (!mask_gc)
658 mask_gc = gdk_gc_new(icon->mask);
660 /* Clear the mask to transparent */
661 gdk_gc_set_foreground(mask_gc, &mask_transp);
662 gdk_draw_rectangle(icon->mask, mask_gc, TRUE, 0, 0, width, height);
664 gdk_gc_set_foreground(mask_gc, &mask_solid);
665 /* Make the icon area solid */
666 if (image->mask)
668 gdk_draw_pixmap(icon->mask, mask_gc, image->mask,
669 0, 0,
670 (width - image->width) >> 1,
671 WINK_FRAME,
672 image->width,
673 image->height);
675 else
677 gdk_draw_rectangle(icon->mask, mask_gc, TRUE,
678 (width - image->width) >> 1,
679 WINK_FRAME,
680 image->width,
681 image->height);
684 gdk_gc_set_function(mask_gc, GDK_OR);
685 if (item->flags & ITEM_FLAG_SYMLINK)
687 gdk_draw_pixmap(icon->mask, mask_gc, im_symlink->mask,
688 0, 0, /* Source x,y */
689 (width - image->width) >> 1, /* Dest x */
690 WINK_FRAME, /* Dest y */
691 -1, -1);
693 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
695 /* Note: Both mount state pixmaps must have the same mask */
696 gdk_draw_pixmap(icon->mask, mask_gc, im_mounted->mask,
697 0, 0, /* Source x,y */
698 (width - image->width) >> 1, /* Dest x */
699 WINK_FRAME, /* Dest y */
700 -1, -1);
702 gdk_gc_set_function(mask_gc, GDK_COPY);
704 /* Mask off an area for the text (from o_text_bg) */
706 text_x = (width - item->name_width) >> 1;
707 text_y = WINK_FRAME + image->height + GAP + 1;
709 if (o_text_bg == TEXT_BG_SOLID)
711 gdk_draw_rectangle(icon->mask, mask_gc, TRUE,
712 (width - (item->name_width + 2)) >> 1,
713 WINK_FRAME + image->height + GAP,
714 item->name_width + 2, font_height + 2);
716 else
718 int y = text_y + font->ascent;
720 TEXT_AT(0, 0);
722 if (o_text_bg == TEXT_BG_OUTLINE)
724 TEXT_AT(1, 0);
725 TEXT_AT(1, 1);
726 TEXT_AT(0, 1);
727 TEXT_AT(-1, 1);
728 TEXT_AT(-1, 0);
729 TEXT_AT(-1, -1);
730 TEXT_AT(0, -1);
731 TEXT_AT(1, -1);
735 gtk_widget_shape_combine_mask(icon->win, icon->mask, 0, 0);
737 *rwidth = width;
738 *rheight = height;
741 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *icon)
743 GdkFont *font = icon->win->style->font;
744 int font_height;
745 int width, height;
746 int text_x, text_y;
747 DirItem *item = &icon->item;
748 MaskedPixmap *image = item->image;
749 int image_x;
750 GdkGC *gc = widget->style->black_gc;
751 GtkStateType state = icon->selected ? GTK_STATE_SELECTED
752 : GTK_STATE_NORMAL;
754 font_height = font->ascent + font->descent;
756 gdk_window_get_size(widget->window, &width, &height);
757 image_x = (width - image->width) >> 1;
759 /* TODO: If the shape extension is missing we might need to set
760 * the clip mask here...
762 gdk_draw_pixmap(widget->window, gc,
763 image->pixmap,
764 0, 0,
765 image_x,
766 WINK_FRAME,
767 image->width,
768 image->height);
770 if (item->flags & ITEM_FLAG_SYMLINK)
772 gdk_gc_set_clip_origin(gc, image_x, WINK_FRAME);
773 gdk_gc_set_clip_mask(gc, im_symlink->mask);
774 gdk_draw_pixmap(widget->window, gc,
775 im_symlink->pixmap,
776 0, 0, /* Source x,y */
777 image_x, WINK_FRAME, /* Dest x,y */
778 -1, -1);
779 gdk_gc_set_clip_mask(gc, NULL);
780 gdk_gc_set_clip_origin(gc, 0, 0);
782 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
784 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
785 ? im_mounted
786 : im_unmounted;
788 gdk_gc_set_clip_origin(gc, image_x, WINK_FRAME);
789 gdk_gc_set_clip_mask(gc, mp->mask);
790 gdk_draw_pixmap(widget->window, gc,
791 mp->pixmap,
792 0, 0, /* Source x,y */
793 image_x, WINK_FRAME, /* Dest x,y */
794 -1, -1);
795 gdk_gc_set_clip_mask(gc, NULL);
796 gdk_gc_set_clip_origin(gc, 0, 0);
799 text_x = (width - item->name_width) >> 1;
800 text_y = WINK_FRAME + image->height + GAP + 1;
802 if (o_text_bg != TEXT_BG_NONE)
804 gtk_paint_flat_box(widget->style, widget->window,
805 state,
806 GTK_SHADOW_NONE,
807 NULL, widget, "text",
808 text_x - 1,
809 text_y - 1,
810 item->name_width + 2,
811 font_height + 2);
814 gtk_paint_string(widget->style, widget->window,
815 state,
816 NULL, widget, "text",
817 text_x,
818 text_y + font->ascent,
819 item->leafname);
821 if (current_wink_icon == icon)
823 gdk_draw_rectangle(icon->paper->window,
824 icon->paper->style->white_gc,
825 FALSE,
826 0, 0, width - 1, height - 1);
827 gdk_draw_rectangle(icon->paper->window,
828 icon->paper->style->black_gc,
829 FALSE,
830 1, 1, width - 3, height - 3);
833 return FALSE;
836 #define OTHER_BUTTONS (GDK_BUTTON2_MASK | GDK_BUTTON3_MASK | \
837 GDK_BUTTON4_MASK | GDK_BUTTON5_MASK)
839 static gboolean button_release_event(GtkWidget *widget,
840 GdkEventButton *event,
841 PinIcon *icon)
843 int b = event->button;
844 DirItem *item = &icon->item;
846 if (pin_drag_type == DRAG_NONE)
847 return TRUE; /* Already done a drag? */
849 gtk_grab_remove(widget);
851 if (pin_drag_type == DRAG_MOVE_ICON)
852 pinboard_save();
853 else if (b == 1 && pin_drag_type == DRAG_MAY_COPY)
855 /* Could have been a copy, but the user didn't move
856 * far enough. Open the item instead.
858 pinboard_wink_item(icon, TRUE);
860 run_diritem(icon->path, item, NULL,
861 (event->state & GDK_SHIFT_MASK) != 0);
864 pin_drag_type = DRAG_NONE;
866 return TRUE;
869 static gboolean root_property_event(GtkWidget *widget,
870 GdkEventProperty *event,
871 gpointer data)
873 if (event->atom == win_button_proxy &&
874 event->state == GDK_PROPERTY_NEW_VALUE)
876 /* Setup forwarding on the new proxy window, if possible */
877 forward_root_clicks();
880 return FALSE;
883 static gboolean root_button_press(GtkWidget *widget,
884 GdkEventButton *event,
885 gpointer data)
887 if (event->button == collection_menu_button)
888 show_pinboard_menu(event, NULL);
889 else
890 pinboard_clear_selection();
892 return TRUE;
895 static gboolean enter_notify(GtkWidget *widget,
896 GdkEventCrossing *event,
897 PinIcon *icon)
899 icon_may_update(icon);
901 return FALSE;
904 static gboolean button_press_event(GtkWidget *widget,
905 GdkEventButton *event,
906 PinIcon *icon)
908 int b = event->button;
910 if (pin_drag_type != DRAG_NONE)
911 return TRUE;
913 if (b == collection_menu_button)
914 show_pinboard_menu(event, icon);
915 else if (b < 4)
917 if (event->state & GDK_CONTROL_MASK)
919 pin_drag_type = DRAG_NONE;
920 pinboard_set_selected(icon, !icon->selected);
922 else
924 drag_start_x = event->x_root;
925 drag_start_y = event->y_root;
926 gtk_grab_add(widget);
928 if (b == 1)
929 pin_drag_type = DRAG_MAY_COPY;
930 else
931 pin_drag_type = DRAG_MOVE_ICON;
935 return TRUE;
938 /* Return a text/uri-list of all the icons in the list */
939 static guchar *create_uri_list(GList *list)
941 GString *tmp;
942 guchar *retval;
943 guchar *leader;
945 tmp = g_string_new(NULL);
946 leader = g_strdup_printf("file://%s", o_no_hostnames
947 ? ""
948 : our_host_name());
950 for (; list; list = list->next)
952 PinIcon *icon = (PinIcon *) list->data;
954 g_string_append(tmp, leader);
955 g_string_append(tmp, icon->path);
956 g_string_append(tmp, "\r\n");
959 g_free(leader);
960 retval = tmp->str;
961 g_string_free(tmp, FALSE);
963 return retval;
966 static void start_drag(PinIcon *icon, GdkEventMotion *event)
968 GtkWidget *widget = icon->paper;
969 GList *selected;
971 if (!icon->selected)
972 pinboard_select_only(icon);
974 selected = pinboard_get_selected();
975 g_return_if_fail(selected != NULL);
977 pinboard_drag_in_progress = TRUE;
979 if (selected->next == NULL)
980 drag_one_item(widget, event, icon->path, &icon->item, FALSE);
981 else
983 guchar *uri_list;
985 uri_list = create_uri_list(selected);
986 drag_selection(widget, event, uri_list);
987 g_free(uri_list);
990 g_list_free(selected);
993 /* An icon is being dragged around... */
994 static gint icon_motion_notify(GtkWidget *widget,
995 GdkEventMotion *event,
996 PinIcon *icon)
998 int x = event->x_root;
999 int y = event->y_root;
1001 if (pin_drag_type == DRAG_MOVE_ICON)
1003 int width, height;
1005 snap_to_grid(&x, &y);
1006 icon->x = x;
1007 icon->y = y;
1008 gdk_window_get_size(icon->win->window, &width, &height);
1009 offset_from_centre(icon, width, height, &x, &y);
1011 gdk_window_move(icon->win->window, x, y);
1013 else if (pin_drag_type == DRAG_MAY_COPY)
1015 int dx = x - drag_start_x;
1016 int dy = y - drag_start_y;
1018 if (ABS(dx) > 3 || ABS(dy) > 3)
1020 pin_drag_type = DRAG_NONE;
1021 gtk_grab_remove(widget);
1022 start_drag(icon, event);
1026 return TRUE;
1029 /* Called for each line in the pinboard file while loading a new board */
1030 static char *pin_from_file(guchar *line)
1032 int x, y, n;
1034 if (sscanf(line, " %d , %d , %n", &x, &y, &n) < 2)
1035 return NULL; /* Ignore format errors */
1037 pinboard_pin(line + n, NULL, x, y);
1039 return NULL;
1042 /* Make sure that clicks and drops on the root window come to us...
1043 * False if an error occurred (ie, someone else is using it).
1045 static gboolean add_root_handlers(void)
1047 GdkWindow *root;
1049 if (!proxy_invisible)
1051 GtkTargetEntry target_table[] =
1053 {"text/uri-list", 0, TARGET_URI_LIST},
1054 {"STRING", 0, TARGET_STRING},
1057 win_button_proxy = gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY",
1058 FALSE);
1059 proxy_invisible = gtk_invisible_new();
1060 gtk_widget_show(proxy_invisible);
1062 gdk_window_add_filter(proxy_invisible->window,
1063 proxy_filter, NULL);
1066 gtk_signal_connect(GTK_OBJECT(proxy_invisible),
1067 "property_notify_event",
1068 GTK_SIGNAL_FUNC(root_property_event), NULL);
1069 gtk_signal_connect(GTK_OBJECT(proxy_invisible),
1070 "button_press_event",
1071 GTK_SIGNAL_FUNC(root_button_press), NULL);
1073 /* Drag and drop handlers */
1074 drag_set_pinboard_dest(proxy_invisible);
1075 gtk_signal_connect(GTK_OBJECT(proxy_invisible), "drag_motion",
1076 GTK_SIGNAL_FUNC(bg_drag_motion),
1077 NULL);
1079 /* The proxy window is also used to hold the selection... */
1080 gtk_signal_connect(GTK_OBJECT(proxy_invisible),
1081 "selection_clear_event",
1082 GTK_SIGNAL_FUNC(lose_selection),
1083 NULL);
1085 gtk_signal_connect(GTK_OBJECT(proxy_invisible),
1086 "selection_get",
1087 GTK_SIGNAL_FUNC(selection_get), NULL);
1089 gtk_selection_add_targets(proxy_invisible,
1090 GDK_SELECTION_PRIMARY,
1091 target_table,
1092 sizeof(target_table) / sizeof(*target_table));
1095 root = gdk_window_lookup(GDK_ROOT_WINDOW());
1096 if (!root)
1097 root = gdk_window_foreign_new(GDK_ROOT_WINDOW());
1099 if (!setup_xdnd_proxy(GDK_ROOT_WINDOW(), proxy_invisible->window))
1100 return FALSE;
1102 /* Forward events from the root window to our proxy window */
1103 gdk_window_add_filter(GDK_ROOT_PARENT(), proxy_filter, NULL);
1104 gdk_window_set_user_data(GDK_ROOT_PARENT(), proxy_invisible);
1105 gdk_window_set_events(GDK_ROOT_PARENT(),
1106 gdk_window_get_events(GDK_ROOT_PARENT()) |
1107 GDK_PROPERTY_CHANGE_MASK);
1109 forward_root_clicks();
1111 return TRUE;
1114 /* See if the window manager is offering to forward root window clicks.
1115 * If so, grab them. Otherwise, do nothing.
1116 * Call this whenever the _WIN_DESKTOP_BUTTON_PROXY property changes.
1118 static void forward_root_clicks(void)
1120 click_proxy_gdk_window = find_click_proxy_window();
1121 if (!click_proxy_gdk_window)
1122 return;
1124 /* Events on the wm's proxy are dealt with by our proxy widget */
1125 gdk_window_set_user_data(click_proxy_gdk_window, proxy_invisible);
1126 gdk_window_add_filter(click_proxy_gdk_window, proxy_filter, NULL);
1128 /* The proxy window for clicks sends us button press events with
1129 * SubstructureNotifyMask. We need StructureNotifyMask to receive
1130 * DestroyNotify events, too.
1132 XSelectInput(GDK_DISPLAY(),
1133 GDK_WINDOW_XWINDOW(click_proxy_gdk_window),
1134 SubstructureNotifyMask | StructureNotifyMask);
1137 /* Write the current state of the pinboard to the current pinboard file */
1138 static void pinboard_save(void)
1140 guchar *save;
1141 guchar *leaf;
1142 GString *tmp;
1143 FILE *file = NULL;
1144 GList *next;
1145 guchar *save_new = NULL;
1147 g_return_if_fail(current_pinboard != NULL);
1149 leaf = g_strconcat("pb_", current_pinboard->name, NULL);
1150 save = choices_find_path_save(leaf, "ROX-Filer", TRUE);
1151 g_free(leaf);
1153 if (!save)
1154 return;
1156 save_new = g_strconcat(save, ".new", NULL);
1157 file = fopen(save_new, "wb");
1158 if (!file)
1159 goto err;
1161 tmp = g_string_new(NULL);
1162 for (next = current_pinboard->icons; next; next = next->next)
1164 PinIcon *icon = (PinIcon *) next->data;
1166 g_string_sprintf(tmp, "%d, %d, %s\n",
1167 icon->x, icon->y, icon->path);
1168 if (fwrite(tmp->str, 1, tmp->len, file) < tmp->len)
1169 goto err;
1172 g_string_free(tmp, TRUE);
1174 if (fclose(file))
1176 file = NULL;
1177 goto err;
1180 file = NULL;
1182 if (rename(save_new, save))
1183 goto err;
1185 goto out;
1186 err:
1187 delayed_error(_("Error saving pinboard"), g_strerror(errno));
1188 out:
1189 if (file)
1190 fclose(file);
1191 g_free(save_new);
1195 * Filter that translates proxied events from virtual root windows into normal
1196 * Gdk events for the proxy_invisible widget. Stolen from gmc.
1198 * Also gets events from the root window.
1200 static GdkFilterReturn proxy_filter(GdkXEvent *xevent,
1201 GdkEvent *event,
1202 gpointer data)
1204 XEvent *xev;
1205 GdkWindow *proxy = proxy_invisible->window;
1207 xev = xevent;
1209 switch (xev->type) {
1210 case ButtonPress:
1211 case ButtonRelease:
1212 /* Translate button events into events that come from
1213 * the proxy window, so that we can catch them as a
1214 * signal from the invisible widget.
1216 if (xev->type == ButtonPress)
1217 event->button.type = GDK_BUTTON_PRESS;
1218 else
1219 event->button.type = GDK_BUTTON_RELEASE;
1221 gdk_window_ref(proxy);
1223 event->button.window = proxy;
1224 event->button.send_event = xev->xbutton.send_event;
1225 event->button.time = xev->xbutton.time;
1226 event->button.x_root = xev->xbutton.x_root;
1227 event->button.y_root = xev->xbutton.y_root;
1228 event->button.x = xev->xbutton.x;
1229 event->button.y = xev->xbutton.y;
1230 event->button.state = xev->xbutton.state;
1231 event->button.button = xev->xbutton.button;
1233 return GDK_FILTER_TRANSLATE;
1235 case DestroyNotify:
1236 /* XXX: I have no idea why this helps, but it does! */
1237 /* The proxy window was destroyed (i.e. the window
1238 * manager died), so we have to cope with it
1240 if (((GdkEventAny *) event)->window == proxy)
1241 gdk_window_destroy_notify(proxy);
1243 return GDK_FILTER_REMOVE;
1245 default:
1246 break;
1249 return GDK_FILTER_CONTINUE;
1252 /* Does not save the new state */
1253 static void icon_destroyed(GtkWidget *widget, PinIcon *icon)
1255 g_return_if_fail(icon != NULL);
1257 if (icon->selected)
1258 change_number_selected(-1);
1260 gdk_pixmap_unref(icon->mask);
1261 dir_item_clear(&icon->item);
1262 g_free(icon->path);
1263 g_free(icon);
1265 if (current_pinboard)
1266 current_pinboard->icons =
1267 g_list_remove(current_pinboard->icons, icon);
1270 #define GRID_STEP 32
1272 static void snap_to_grid(int *x, int *y)
1274 *x = ((*x + GRID_STEP / 2) / GRID_STEP) * GRID_STEP;
1275 *y = ((*y + GRID_STEP / 2) / GRID_STEP) * GRID_STEP;
1278 /* Convert (x,y) from a centre point to a window position */
1279 static void offset_from_centre(PinIcon *icon,
1280 int width, int height,
1281 int *x, int *y)
1283 *x -= width >> 1;
1284 *y -= height - (icon->paper->style->font->descent >> 1);
1285 *x = CLAMP(*x, 0, screen_width - width);
1286 *y = CLAMP(*y, 0, screen_height - height);
1289 /* Same as drag_set_dest(), but for pinboard icons */
1290 static void drag_set_pinicon_dest(PinIcon *icon)
1292 GtkObject *obj = GTK_OBJECT(icon->paper);
1294 make_drop_target(icon->paper);
1296 gtk_signal_connect(obj, "drag_motion",
1297 GTK_SIGNAL_FUNC(drag_motion), icon);
1298 gtk_signal_connect(obj, "drag_leave",
1299 GTK_SIGNAL_FUNC(drag_leave), icon);
1300 gtk_signal_connect(obj, "drag_end",
1301 GTK_SIGNAL_FUNC(drag_end), icon);
1304 gtk_signal_connect(obj, "drag_end",
1305 GTK_SIGNAL_FUNC(drag_end), filer_window);
1309 /* Called during the drag when the mouse is in a widget registered
1310 * as a drop target. Returns TRUE if we can accept the drop.
1312 static gboolean drag_motion(GtkWidget *widget,
1313 GdkDragContext *context,
1314 gint x,
1315 gint y,
1316 guint time,
1317 PinIcon *icon)
1319 GdkDragAction action = context->suggested_action;
1320 char *type = NULL;
1321 DirItem *item = &icon->item;
1323 if (gtk_drag_get_source_widget(context) == widget)
1324 goto out; /* Can't drag something to itself! */
1326 if (icon->selected)
1327 goto out; /* Can't drag a selection to itself */
1329 if (provides(context, _rox_run_action))
1331 /* This is a special internal type. The user is dragging
1332 * to an executable item to set the run action.
1335 if (item->flags & (ITEM_FLAG_APPDIR | ITEM_FLAG_EXEC_FILE))
1336 type = drop_dest_prog;
1337 else
1338 goto out;
1341 else
1343 /* If we didn't drop onto a directory, application or
1344 * executable file then give up.
1346 if (item->base_type != TYPE_DIRECTORY
1347 && !(item->flags & ITEM_FLAG_EXEC_FILE))
1348 goto out;
1351 if (item->base_type == TYPE_DIRECTORY &&
1352 !(item->flags & ITEM_FLAG_APPDIR))
1354 if (provides(context, text_uri_list) ||
1355 provides(context, XdndDirectSave0))
1356 type = drop_dest_dir;
1358 else
1360 if (provides(context, text_uri_list) ||
1361 provides(context, application_octet_stream))
1362 type = drop_dest_prog;
1365 out:
1367 #if 0
1368 /* We actually must pretend to accept the drop, even if the
1369 * directory isn't writeable, so that the spring-opening
1370 * thing works.
1373 /* Don't allow drops to non-writeable directories */
1374 if (type == drop_dest_dir && access(icon->path, W_OK) != 0)
1375 type = NULL;
1376 #endif
1378 g_dataset_set_data(context, "drop_dest_type", type);
1379 if (type)
1381 gdk_drag_status(context, action, time);
1382 g_dataset_set_data_full(context, "drop_dest_path",
1383 g_strdup(icon->path), g_free);
1384 if (type == drop_dest_dir)
1385 dnd_spring_load(context);
1387 pinboard_wink_item(icon, FALSE);
1390 return type != NULL;
1393 static void drag_leave(GtkWidget *widget,
1394 GdkDragContext *context,
1395 guint32 time,
1396 PinIcon *icon)
1398 pinboard_wink_item(NULL, FALSE);
1399 dnd_spring_abort();
1402 /* When changing the 'selected' attribute of an icon, call this
1403 * to update the global counter and claim or release the primary
1404 * selection as needed.
1406 static void change_number_selected(int delta)
1408 guint32 time;
1410 g_return_if_fail(delta != 0);
1411 g_return_if_fail(number_selected + delta >= 0);
1413 if (number_selected == 0)
1415 time = gdk_event_get_time(gtk_get_current_event());
1417 gtk_selection_owner_set(proxy_invisible,
1418 GDK_SELECTION_PRIMARY,
1419 time);
1422 number_selected += delta;
1424 if (number_selected == 0)
1426 time = gdk_event_get_time(gtk_get_current_event());
1428 gtk_selection_owner_set(NULL,
1429 GDK_SELECTION_PRIMARY,
1430 time);
1434 /* Called when another application wants the contents of our selection */
1435 static void selection_get(GtkWidget *widget,
1436 GtkSelectionData *selection_data,
1437 guint info,
1438 guint time,
1439 gpointer data)
1441 GString *str;
1442 GList *next;
1443 guchar *leader = NULL;
1445 str = g_string_new(NULL);
1447 if (info == TARGET_URI_LIST)
1448 leader = g_strdup_printf("file://%s",
1449 o_no_hostnames ? "" : our_host_name());
1451 for (next = current_pinboard->icons; next; next = next->next)
1453 PinIcon *icon = (PinIcon *) next->data;
1455 if (!icon->selected)
1456 continue;
1458 if (leader)
1459 g_string_append(str, leader);
1460 g_string_append(str, icon->path);
1461 g_string_append_c(str, ' ');
1464 g_free(leader);
1466 gtk_selection_data_set(selection_data,
1467 gdk_atom_intern("STRING", FALSE),
1469 str->str,
1470 str->len ? str->len - 1 : 0);
1472 g_string_free(str, TRUE);
1475 /* Called when another application takes the selection away from us */
1476 static gint lose_selection(GtkWidget *widget, GdkEventSelection *event)
1478 /* 'lock' number_selected so that we don't send any events */
1479 number_selected++;
1480 pinboard_clear_selection();
1481 number_selected--;
1483 return TRUE;
1486 static gboolean bg_drag_motion(GtkWidget *widget,
1487 GdkDragContext *context,
1488 gint x,
1489 gint y,
1490 guint time,
1491 gpointer data)
1493 /* Dragging from the pinboard to the pinboard is not allowed */
1494 if (pinboard_drag_in_progress)
1495 return FALSE;
1497 gdk_drag_status(context, context->suggested_action, time);
1498 return TRUE;
1501 static void drag_end(GtkWidget *widget,
1502 GdkDragContext *context,
1503 FilerWindow *filer_window)
1505 pinboard_drag_in_progress = FALSE;
1506 pinboard_clear_selection();
1509 /* OPTIONS BITS */
1511 static GtkToggleButton *radio_bg_none;
1512 static GtkToggleButton *radio_bg_outline;
1513 static GtkToggleButton *radio_bg_solid;
1515 /* Build up some option widgets to go in the options dialog, but don't
1516 * fill them in yet.
1518 static GtkWidget *create_options(void)
1520 GtkWidget *vbox, *tog;
1522 vbox = gtk_vbox_new(FALSE, 0);
1523 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
1525 tog = gtk_radio_button_new_with_label( NULL, _("No background"));
1526 radio_bg_none = GTK_TOGGLE_BUTTON(tog);
1527 OPTION_TIP(tog, "The text is drawn directly on the desktop "
1528 "background.");
1529 gtk_box_pack_start(GTK_BOX(vbox), tog, FALSE, TRUE, 0);
1531 tog = gtk_radio_button_new_with_label(
1532 gtk_radio_button_group(GTK_RADIO_BUTTON(tog)),
1533 _("Outlined text"));
1534 radio_bg_outline = GTK_TOGGLE_BUTTON(tog);
1535 OPTION_TIP(tog, "The text has a thin outline around each letter.");
1536 gtk_box_pack_start(GTK_BOX(vbox), tog, FALSE, TRUE, 0);
1538 tog = gtk_radio_button_new_with_label(
1539 gtk_radio_button_group(GTK_RADIO_BUTTON(tog)),
1540 _("Rectangular background slab"));
1541 radio_bg_solid = GTK_TOGGLE_BUTTON(tog);
1542 OPTION_TIP(tog, "The text is drawn on a solid rectangle.");
1543 gtk_box_pack_start(GTK_BOX(vbox), tog, FALSE, TRUE, 0);
1545 return vbox;
1548 /* Reflect current state by changing the widgets in the options box */
1549 static void update_options()
1551 if (o_text_bg == TEXT_BG_SOLID)
1552 gtk_toggle_button_set_active(radio_bg_solid, TRUE);
1553 else if (o_text_bg == TEXT_BG_OUTLINE)
1554 gtk_toggle_button_set_active(radio_bg_outline, TRUE);
1555 else
1556 gtk_toggle_button_set_active(radio_bg_none, TRUE);
1559 /* Set current values by reading the states of the widgets in the options box */
1560 static void set_options()
1562 TextBgType old = o_text_bg;
1564 if (gtk_toggle_button_get_active(radio_bg_none))
1565 o_text_bg = TEXT_BG_NONE;
1566 else if (gtk_toggle_button_get_active(radio_bg_outline))
1567 o_text_bg = TEXT_BG_OUTLINE;
1568 else
1569 o_text_bg = TEXT_BG_SOLID;
1571 if (o_text_bg != old && current_pinboard)
1573 guchar *name;
1575 name = g_strdup(current_pinboard->name);
1576 pinboard_activate(name);
1577 g_free(name);
1581 static void save_options()
1583 option_write("pinboard_text_bg",
1584 o_text_bg == TEXT_BG_NONE ? "None" :
1585 o_text_bg == TEXT_BG_OUTLINE ? "Outline" :
1586 "Solid");
1589 static char *text_bg(char *data)
1591 if (g_strcasecmp(data, "None") == 0)
1592 o_text_bg = TEXT_BG_NONE;
1593 else if (g_strcasecmp(data, "Outline") == 0)
1594 o_text_bg = TEXT_BG_OUTLINE;
1595 else if (g_strcasecmp(data, "Solid") == 0)
1596 o_text_bg = TEXT_BG_SOLID;
1597 else
1598 return _("Unknown pinboard text background type");
1600 return NULL;