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)
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
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 */
31 #include <gtk/gtkinvisible.h>
42 #include "gui_support.h"
49 /* The number of pixels between the bottom of the image and the top
54 /* The size of the border around the icon which is used when winking */
64 GtkWidget
*win
, *paper
;
73 guchar
*name
; /* Leaf name */
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 */
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. */
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
;
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"),
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
,
142 static gboolean
root_property_event(GtkWidget
*widget
,
143 GdkEventProperty
*event
,
145 static gboolean
root_button_press(GtkWidget
*widget
,
146 GdkEventButton
*event
,
148 static gboolean
enter_notify(GtkWidget
*widget
,
149 GdkEventCrossing
*event
,
151 static gboolean
button_press_event(GtkWidget
*widget
,
152 GdkEventButton
*event
,
154 static gint
icon_motion_notify(GtkWidget
*widget
,
155 GdkEventMotion
*event
,
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
,
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
,
168 static gboolean
drag_motion(GtkWidget
*widget
,
169 GdkDragContext
*context
,
174 static void drag_set_pinicon_dest(PinIcon
*icon
);
175 static void drag_leave(GtkWidget
*widget
,
176 GdkDragContext
*context
,
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
,
188 static gboolean
bg_drag_motion(GtkWidget
*widget
,
189 GdkDragContext
*context
,
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
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 */
233 if (number_of_windows
< 1 && gtk_main_level() > 0)
238 if (!add_root_handlers())
240 delayed_error(PROJECT
, _("Another application is already "
241 "managing the pinboard!"));
247 slash
= strchr(name
, '/');
249 path
= g_strdup(name
);
254 leaf
= g_strconcat("pb_", name
, NULL
);
255 path
= choices_find_path_load(leaf
, "ROX-Filer");
259 current_pinboard
= g_new(Pinboard
, 1);
260 current_pinboard
->name
= g_strdup(name
);
261 current_pinboard
->icons
= NULL
;
266 parse_file(path
, pin_from_file
);
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
)
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] == '/')
290 icon
= g_new(PinIcon
, 1);
291 icon
->selected
= FALSE
;
292 icon
->path
= g_strndup(path
, path_len
);
294 snap_to_grid(&x
, &y
);
298 dir_stat(icon
->path
, &icon
->item
);
302 name
= strrchr(icon
->path
, '/');
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
);
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
,
352 gtk_widget_show_all(icon
->win
);
354 if (!loading_pinboard
)
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
);
367 /* Unpin all selected items */
368 void pinboard_unpin_selection(void)
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."));
382 next
= current_pinboard
->icons
;
385 PinIcon
*icon
= (PinIcon
*) next
->data
;
390 gtk_widget_destroy(icon
->win
);
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
)
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
);
420 wink_timeout
= gtk_timeout_add(300, end_wink
, NULL
);
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)
433 g_return_if_fail(current_pinboard
!= NULL
);
435 next
= current_pinboard
->icons
;
438 PinIcon
*icon
= (PinIcon
*) next
->data
;
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
)
459 if (!current_pinboard
)
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)
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
;
486 return NULL
; /* >1 icon selected */
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
)
524 change_number_selected(+1);
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)
538 GList
*selected
= NULL
;
540 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
542 PinIcon
*i
= (PinIcon
*) next
->data
;
545 selected
= g_list_append(selected
, i
);
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
)
559 g_return_if_fail(current_pinboard
!= NULL
);
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
581 static void icon_may_update(PinIcon
*icon
)
583 MaskedPixmap
*image
= icon
->item
.image
;
584 int flags
= icon
->item
.flags
;
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
;
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
);
605 static gint
end_wink(gpointer data
)
607 pinboard_wink_item(NULL
, FALSE
);
611 /* Make the wink border solid or transparent */
612 static void mask_wink_border(PinIcon
*icon
, GdkColor
*alpha
)
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, \
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
)
640 GdkFont
*font
= icon
->win
->style
->font
;
642 MaskedPixmap
*image
= icon
->item
.image
;
643 DirItem
*item
= &icon
->item
;
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) +
651 height
= image
->height
+ GAP
+ (font_height
+ 2) + 2 * WINK_FRAME
;
652 gtk_widget_set_usize(icon
->win
, width
, height
);
655 gdk_pixmap_unref(icon
->mask
);
656 icon
->mask
= gdk_pixmap_new(icon
->win
->window
, width
, height
, 1);
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 */
668 gdk_draw_pixmap(icon
->mask
, mask_gc
, image
->mask
,
670 (width
- image
->width
) >> 1,
677 gdk_draw_rectangle(icon
->mask
, mask_gc
, TRUE
,
678 (width
- image
->width
) >> 1,
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 */
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 */
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);
718 int y
= text_y
+ font
->ascent
;
722 if (o_text_bg
== TEXT_BG_OUTLINE
)
735 gtk_widget_shape_combine_mask(icon
->win
, icon
->mask
, 0, 0);
741 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, PinIcon
*icon
)
743 GdkFont
*font
= icon
->win
->style
->font
;
747 DirItem
*item
= &icon
->item
;
748 MaskedPixmap
*image
= item
->image
;
750 GdkGC
*gc
= widget
->style
->black_gc
;
751 GtkStateType state
= icon
->selected
? GTK_STATE_SELECTED
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
,
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
,
776 0, 0, /* Source x,y */
777 image_x
, WINK_FRAME
, /* Dest x,y */
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
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
,
792 0, 0, /* Source x,y */
793 image_x
, WINK_FRAME
, /* Dest x,y */
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
,
807 NULL
, widget
, "text",
810 item
->name_width
+ 2,
814 gtk_paint_string(widget
->style
, widget
->window
,
816 NULL
, widget
, "text",
818 text_y
+ font
->ascent
,
821 if (current_wink_icon
== icon
)
823 gdk_draw_rectangle(icon
->paper
->window
,
824 icon
->paper
->style
->white_gc
,
826 0, 0, width
- 1, height
- 1);
827 gdk_draw_rectangle(icon
->paper
->window
,
828 icon
->paper
->style
->black_gc
,
830 1, 1, width
- 3, height
- 3);
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
,
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
)
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
;
869 static gboolean
root_property_event(GtkWidget
*widget
,
870 GdkEventProperty
*event
,
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();
883 static gboolean
root_button_press(GtkWidget
*widget
,
884 GdkEventButton
*event
,
887 if (event
->button
== collection_menu_button
)
888 show_pinboard_menu(event
, NULL
);
890 pinboard_clear_selection();
895 static gboolean
enter_notify(GtkWidget
*widget
,
896 GdkEventCrossing
*event
,
899 icon_may_update(icon
);
904 static gboolean
button_press_event(GtkWidget
*widget
,
905 GdkEventButton
*event
,
908 int b
= event
->button
;
910 if (pin_drag_type
!= DRAG_NONE
)
913 if (b
== collection_menu_button
)
914 show_pinboard_menu(event
, icon
);
917 if (event
->state
& GDK_CONTROL_MASK
)
919 pin_drag_type
= DRAG_NONE
;
920 pinboard_set_selected(icon
, !icon
->selected
);
924 drag_start_x
= event
->x_root
;
925 drag_start_y
= event
->y_root
;
926 gtk_grab_add(widget
);
929 pin_drag_type
= DRAG_MAY_COPY
;
931 pin_drag_type
= DRAG_MOVE_ICON
;
938 /* Return a text/uri-list of all the icons in the list */
939 static guchar
*create_uri_list(GList
*list
)
945 tmp
= g_string_new(NULL
);
946 leader
= g_strdup_printf("file://%s", o_no_hostnames
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");
961 g_string_free(tmp
, FALSE
);
966 static void start_drag(PinIcon
*icon
, GdkEventMotion
*event
)
968 GtkWidget
*widget
= icon
->paper
;
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
);
985 uri_list
= create_uri_list(selected
);
986 drag_selection(widget
, event
, 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
,
998 int x
= event
->x_root
;
999 int y
= event
->y_root
;
1001 if (pin_drag_type
== DRAG_MOVE_ICON
)
1005 snap_to_grid(&x
, &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
);
1029 /* Called for each line in the pinboard file while loading a new board */
1030 static char *pin_from_file(guchar
*line
)
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
);
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)
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",
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
),
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
),
1085 gtk_signal_connect(GTK_OBJECT(proxy_invisible
),
1087 GTK_SIGNAL_FUNC(selection_get
), NULL
);
1089 gtk_selection_add_targets(proxy_invisible
,
1090 GDK_SELECTION_PRIMARY
,
1092 sizeof(target_table
) / sizeof(*target_table
));
1095 root
= gdk_window_lookup(GDK_ROOT_WINDOW());
1097 root
= gdk_window_foreign_new(GDK_ROOT_WINDOW());
1099 if (!setup_xdnd_proxy(GDK_ROOT_WINDOW(), proxy_invisible
->window
))
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();
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
)
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)
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
);
1156 save_new
= g_strconcat(save
, ".new", NULL
);
1157 file
= fopen(save_new
, "wb");
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
)
1172 g_string_free(tmp
, TRUE
);
1182 if (rename(save_new
, save
))
1187 delayed_error(_("Error saving pinboard"), g_strerror(errno
));
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
,
1205 GdkWindow
*proxy
= proxy_invisible
->window
;
1209 switch (xev
->type
) {
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
;
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
;
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
;
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
);
1258 change_number_selected(-1);
1260 gdk_pixmap_unref(icon
->mask
);
1261 dir_item_clear(&icon
->item
);
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
,
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
,
1319 GdkDragAction action
= context
->suggested_action
;
1321 DirItem
*item
= &icon
->item
;
1323 if (gtk_drag_get_source_widget(context
) == widget
)
1324 goto out
; /* Can't drag something to itself! */
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
;
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
))
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
;
1360 if (provides(context
, text_uri_list
) ||
1361 provides(context
, application_octet_stream
))
1362 type
= drop_dest_prog
;
1368 /* We actually must pretend to accept the drop, even if the
1369 * directory isn't writeable, so that the spring-opening
1373 /* Don't allow drops to non-writeable directories */
1374 if (type
== drop_dest_dir
&& access(icon
->path
, W_OK
) != 0)
1378 g_dataset_set_data(context
, "drop_dest_type", 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
,
1398 pinboard_wink_item(NULL
, FALSE
);
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
)
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
,
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
,
1434 /* Called when another application wants the contents of our selection */
1435 static void selection_get(GtkWidget
*widget
,
1436 GtkSelectionData
*selection_data
,
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
)
1459 g_string_append(str
, leader
);
1460 g_string_append(str
, icon
->path
);
1461 g_string_append_c(str
, ' ');
1466 gtk_selection_data_set(selection_data
,
1467 gdk_atom_intern("STRING", FALSE
),
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 */
1480 pinboard_clear_selection();
1486 static gboolean
bg_drag_motion(GtkWidget
*widget
,
1487 GdkDragContext
*context
,
1493 /* Dragging from the pinboard to the pinboard is not allowed */
1494 if (pinboard_drag_in_progress
)
1497 gdk_drag_status(context
, context
->suggested_action
, time
);
1501 static void drag_end(GtkWidget
*widget
,
1502 GdkDragContext
*context
,
1503 FilerWindow
*filer_window
)
1505 pinboard_drag_in_progress
= FALSE
;
1506 pinboard_clear_selection();
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
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 "
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);
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
);
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
;
1569 o_text_bg
= TEXT_BG_SOLID
;
1571 if (o_text_bg
!= old
&& current_pinboard
)
1575 name
= g_strdup(current_pinboard
->name
);
1576 pinboard_activate(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" :
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
;
1598 return _("Unknown pinboard text background type");