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"
48 /* The number of pixels between the bottom of the image and the top
53 /* The size of the border around the icon which is used when winking */
63 GtkWidget
*win
, *paper
;
72 guchar
*name
; /* Leaf name */
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 */
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. */
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
;
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"),
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
,
141 static gboolean
root_property_event(GtkWidget
*widget
,
142 GdkEventProperty
*event
,
144 static gboolean
root_button_press(GtkWidget
*widget
,
145 GdkEventButton
*event
,
147 static gboolean
enter_notify(GtkWidget
*widget
,
148 GdkEventCrossing
*event
,
150 static gboolean
button_press_event(GtkWidget
*widget
,
151 GdkEventButton
*event
,
153 static gint
icon_motion_notify(GtkWidget
*widget
,
154 GdkEventMotion
*event
,
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
,
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
,
167 static gboolean
drag_motion(GtkWidget
*widget
,
168 GdkDragContext
*context
,
173 static void drag_set_pinicon_dest(PinIcon
*icon
);
174 static void drag_leave(GtkWidget
*widget
,
175 GdkDragContext
*context
,
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
,
187 static gboolean
bg_drag_motion(GtkWidget
*widget
,
188 GdkDragContext
*context
,
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
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 */
232 if (number_of_windows
< 1 && gtk_main_level() > 0)
237 if (!add_root_handlers())
239 delayed_error(PROJECT
, _("Another application is already "
240 "managing the pinboard!"));
246 slash
= strchr(name
, '/');
248 path
= g_strdup(name
);
253 leaf
= g_strconcat("pb_", name
, NULL
);
254 path
= choices_find_path_load(leaf
, "ROX-Filer");
258 current_pinboard
= g_new(Pinboard
, 1);
259 current_pinboard
->name
= g_strdup(name
);
260 current_pinboard
->icons
= NULL
;
265 parse_file(path
, pin_from_file
);
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
)
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] == '/')
289 icon
= g_new(PinIcon
, 1);
290 icon
->selected
= FALSE
;
291 icon
->path
= g_strndup(path
, path_len
);
293 snap_to_grid(&x
, &y
);
297 dir_stat(icon
->path
, &icon
->item
);
301 name
= strrchr(icon
->path
, '/');
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
);
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
,
351 gtk_widget_show_all(icon
->win
);
353 if (!loading_pinboard
)
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
);
366 /* Unpin all selected items */
367 void pinboard_unpin_selection(void)
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."));
381 next
= current_pinboard
->icons
;
384 PinIcon
*icon
= (PinIcon
*) next
->data
;
389 gtk_widget_destroy(icon
->win
);
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
)
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
);
419 wink_timeout
= gtk_timeout_add(300, end_wink
, NULL
);
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)
432 g_return_if_fail(current_pinboard
!= NULL
);
434 next
= current_pinboard
->icons
;
437 PinIcon
*icon
= (PinIcon
*) next
->data
;
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
)
458 if (!current_pinboard
)
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)
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
;
485 return NULL
; /* >1 icon selected */
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
)
523 change_number_selected(+1);
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)
537 GList
*selected
= NULL
;
539 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
541 PinIcon
*i
= (PinIcon
*) next
->data
;
544 selected
= g_list_append(selected
, i
);
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
)
558 g_return_if_fail(current_pinboard
!= NULL
);
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
580 static void icon_may_update(PinIcon
*icon
)
582 MaskedPixmap
*image
= icon
->item
.image
;
585 dir_restat(icon
->path
, &icon
->item
);
587 if (icon
->item
.image
!= image
)
589 int x
= icon
->x
, y
= icon
->y
;
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
);
602 static gint
end_wink(gpointer data
)
604 pinboard_wink_item(NULL
, FALSE
);
608 /* Make the wink border solid or transparent */
609 static void mask_wink_border(PinIcon
*icon
, GdkColor
*alpha
)
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, \
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
)
637 GdkFont
*font
= icon
->win
->style
->font
;
639 MaskedPixmap
*image
= icon
->item
.image
;
640 DirItem
*item
= &icon
->item
;
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) +
648 height
= image
->height
+ GAP
+ (font_height
+ 2) + 2 * WINK_FRAME
;
649 gtk_widget_set_usize(icon
->win
, width
, height
);
652 gdk_pixmap_unref(icon
->mask
);
653 icon
->mask
= gdk_pixmap_new(icon
->win
->window
, width
, height
, 1);
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 */
665 gdk_draw_pixmap(icon
->mask
, mask_gc
, image
->mask
,
667 (width
- image
->width
) >> 1,
674 gdk_draw_rectangle(icon
->mask
, mask_gc
, TRUE
,
675 (width
- image
->width
) >> 1,
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 */
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 */
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);
715 int y
= text_y
+ font
->ascent
;
719 if (o_text_bg
== TEXT_BG_OUTLINE
)
732 gtk_widget_shape_combine_mask(icon
->win
, icon
->mask
, 0, 0);
738 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, PinIcon
*icon
)
740 GdkFont
*font
= icon
->win
->style
->font
;
744 DirItem
*item
= &icon
->item
;
745 MaskedPixmap
*image
= item
->image
;
747 GdkGC
*gc
= widget
->style
->black_gc
;
748 GtkStateType state
= icon
->selected
? GTK_STATE_SELECTED
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
,
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
,
773 0, 0, /* Source x,y */
774 image_x
, WINK_FRAME
, /* Dest x,y */
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
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
,
789 0, 0, /* Source x,y */
790 image_x
, WINK_FRAME
, /* Dest x,y */
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
,
804 NULL
, widget
, "text",
807 item
->name_width
+ 2,
811 gtk_paint_string(widget
->style
, widget
->window
,
813 NULL
, widget
, "text",
815 text_y
+ font
->ascent
,
818 if (current_wink_icon
== icon
)
820 gdk_draw_rectangle(icon
->paper
->window
,
821 icon
->paper
->style
->white_gc
,
823 0, 0, width
- 1, height
- 1);
824 gdk_draw_rectangle(icon
->paper
->window
,
825 icon
->paper
->style
->black_gc
,
827 1, 1, width
- 3, height
- 3);
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
,
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
)
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
;
866 static gboolean
root_property_event(GtkWidget
*widget
,
867 GdkEventProperty
*event
,
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();
880 static gboolean
root_button_press(GtkWidget
*widget
,
881 GdkEventButton
*event
,
884 if (event
->button
== collection_menu_button
)
885 show_pinboard_menu(event
, NULL
);
887 pinboard_clear_selection();
892 static gboolean
enter_notify(GtkWidget
*widget
,
893 GdkEventCrossing
*event
,
896 icon_may_update(icon
);
901 static gboolean
button_press_event(GtkWidget
*widget
,
902 GdkEventButton
*event
,
905 int b
= event
->button
;
907 if (pin_drag_type
!= DRAG_NONE
)
910 if (b
== collection_menu_button
)
911 show_pinboard_menu(event
, icon
);
914 if (event
->state
& GDK_CONTROL_MASK
)
916 pin_drag_type
= DRAG_NONE
;
917 pinboard_set_selected(icon
, !icon
->selected
);
921 drag_start_x
= event
->x_root
;
922 drag_start_y
= event
->y_root
;
923 gtk_grab_add(widget
);
926 pin_drag_type
= DRAG_MAY_COPY
;
928 pin_drag_type
= DRAG_MOVE_ICON
;
935 /* Return a text/uri-list of all the icons in the list */
936 static guchar
*create_uri_list(GList
*list
)
942 tmp
= g_string_new(NULL
);
943 leader
= g_strdup_printf("file://%s", o_no_hostnames
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");
958 g_string_free(tmp
, FALSE
);
963 static void start_drag(PinIcon
*icon
, GdkEventMotion
*event
)
965 GtkWidget
*widget
= icon
->paper
;
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
);
982 uri_list
= create_uri_list(selected
);
983 drag_selection(widget
, event
, 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
,
995 int x
= event
->x_root
;
996 int y
= event
->y_root
;
998 if (pin_drag_type
== DRAG_MOVE_ICON
)
1002 snap_to_grid(&x
, &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
);
1026 /* Called for each line in the pinboard file while loading a new board */
1027 static char *pin_from_file(guchar
*line
)
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
);
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)
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",
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
),
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
),
1082 gtk_signal_connect(GTK_OBJECT(proxy_invisible
),
1084 GTK_SIGNAL_FUNC(selection_get
), NULL
);
1086 gtk_selection_add_targets(proxy_invisible
,
1087 GDK_SELECTION_PRIMARY
,
1089 sizeof(target_table
) / sizeof(*target_table
));
1092 root
= gdk_window_lookup(GDK_ROOT_WINDOW());
1094 root
= gdk_window_foreign_new(GDK_ROOT_WINDOW());
1096 if (!setup_xdnd_proxy(GDK_ROOT_WINDOW(), proxy_invisible
->window
))
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();
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
)
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)
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
);
1153 save_new
= g_strconcat(save
, ".new", NULL
);
1154 file
= fopen(save_new
, "wb");
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
)
1169 g_string_free(tmp
, TRUE
);
1179 if (rename(save_new
, save
))
1184 delayed_error(_("Error saving pinboard"), g_strerror(errno
));
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
,
1202 GdkWindow
*proxy
= proxy_invisible
->window
;
1206 switch (xev
->type
) {
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
;
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
;
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
;
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
);
1255 change_number_selected(-1);
1257 gdk_pixmap_unref(icon
->mask
);
1258 dir_item_clear(&icon
->item
);
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
,
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
,
1316 GdkDragAction action
= context
->suggested_action
;
1318 DirItem
*item
= &icon
->item
;
1320 if (gtk_drag_get_source_widget(context
) == widget
)
1321 goto out
; /* Can't drag something to itself! */
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
;
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
))
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
;
1357 if (provides(context
, text_uri_list
) ||
1358 provides(context
, application_octet_stream
))
1359 type
= drop_dest_prog
;
1365 /* We actually must pretend to accept the drop, even if the
1366 * directory isn't writeable, so that the spring-opening
1370 /* Don't allow drops to non-writeable directories */
1371 if (type
== drop_dest_dir
&& access(icon
->path
, W_OK
) != 0)
1375 g_dataset_set_data(context
, "drop_dest_type", 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
,
1395 pinboard_wink_item(NULL
, FALSE
);
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
)
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
,
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
,
1431 /* Called when another application wants the contents of our selection */
1432 static void selection_get(GtkWidget
*widget
,
1433 GtkSelectionData
*selection_data
,
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
)
1456 g_string_append(str
, leader
);
1457 g_string_append(str
, icon
->path
);
1458 g_string_append_c(str
, ' ');
1463 gtk_selection_data_set(selection_data
,
1464 gdk_atom_intern("STRING", FALSE
),
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 */
1477 pinboard_clear_selection();
1483 static gboolean
bg_drag_motion(GtkWidget
*widget
,
1484 GdkDragContext
*context
,
1490 /* Dragging from the pinboard to the pinboard is not allowed */
1491 if (pinboard_drag_in_progress
)
1494 gdk_drag_status(context
, context
->suggested_action
, time
);
1498 static void drag_end(GtkWidget
*widget
,
1499 GdkDragContext
*context
,
1500 FilerWindow
*filer_window
)
1502 pinboard_drag_in_progress
= FALSE
;
1503 pinboard_clear_selection();
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
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 "
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);
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
);
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
;
1566 o_text_bg
= TEXT_BG_SOLID
;
1568 if (o_text_bg
!= old
&& current_pinboard
)
1572 name
= g_strdup(current_pinboard
->name
);
1573 pinboard_activate(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" :
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
;
1595 return _("Unknown pinboard text background type");