4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2001, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
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 */
32 #include <gtk/gtkinvisible.h>
43 #include "gui_support.h"
53 /* The number of pixels between the bottom of the image and the top
58 /* The size of the border around the icon which is used when winking */
62 #define GRID_STEP_FINE 2
63 #define GRID_STEP_MED 16
64 #define GRID_STEP_COARSE 32
66 /* Used for the text colours (only) in the icons */
67 GdkColor text_fg_col
, text_bg_col
;
69 /* Style that all the icons should use. NULL => regenerate from text_fg/bg */
70 GtkStyle
*pinicon_style
= NULL
;
73 guchar
*name
; /* Leaf name */
77 static Pinboard
*current_pinboard
= NULL
;
78 static gint loading_pinboard
= 0; /* Non-zero => loading */
79 static gboolean tmp_icon_selected
= FALSE
;
82 static Icon
*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 gboolean pinboard_drag_in_progress
= FALSE
;
102 /* Used when dragging icons around... */
103 static gboolean pinboard_modified
= FALSE
;
111 TextBgType o_text_bg
= TEXT_BG_SOLID
;
112 gboolean o_clamp_icons
= TRUE
;
113 static int o_grid_step
= GRID_STEP_COARSE
;
114 static int old_x
, old_y
; /* For dragging (mouse start) */
115 static int icon_old_x
, icon_old_y
; /* For dragging (icon start) */
117 /* Static prototypes */
118 static void set_size_and_shape(Icon
*icon
, int *rwidth
, int *rheight
);
119 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, Icon
*icon
);
120 static void mask_wink_border(Icon
*icon
, GdkColor
*alpha
);
121 static gint
end_wink(gpointer data
);
122 static gboolean
button_release_event(GtkWidget
*widget
,
123 GdkEventButton
*event
,
125 static gboolean
root_property_event(GtkWidget
*widget
,
126 GdkEventProperty
*event
,
128 static gboolean
root_button_press(GtkWidget
*widget
,
129 GdkEventButton
*event
,
131 static gboolean
enter_notify(GtkWidget
*widget
,
132 GdkEventCrossing
*event
,
134 static gboolean
button_press_event(GtkWidget
*widget
,
135 GdkEventButton
*event
,
137 static gint
icon_motion_notify(GtkWidget
*widget
,
138 GdkEventMotion
*event
,
140 static char *pin_from_file(guchar
*line
);
141 static gboolean
add_root_handlers(void);
142 static void pinboard_save(void);
143 static GdkFilterReturn
proxy_filter(GdkXEvent
*xevent
,
146 static void icon_destroyed(GtkWidget
*widget
, Icon
*icon
);
147 static void snap_to_grid(int *x
, int *y
);
148 static void offset_from_centre(Icon
*icon
,
149 int width
, int height
,
151 static void offset_to_centre(Icon
*icon
,
152 int width
, int height
,
154 static gboolean
drag_motion(GtkWidget
*widget
,
155 GdkDragContext
*context
,
160 static void drag_set_pinicon_dest(Icon
*icon
);
161 static void drag_leave(GtkWidget
*widget
,
162 GdkDragContext
*context
,
165 static void forward_root_clicks(void);
166 static void change_number_selected(int delta
);
167 static gint
lose_selection(GtkWidget
*widget
, GdkEventSelection
*event
);
168 static void selection_get(GtkWidget
*widget
,
169 GtkSelectionData
*selection_data
,
173 static gboolean
bg_drag_motion(GtkWidget
*widget
,
174 GdkDragContext
*context
,
179 static void drag_end(GtkWidget
*widget
,
180 GdkDragContext
*context
,
182 static void reshape_icon(Icon
*icon
);
183 static void reshape_all(void);
184 static void menu_closed(GtkWidget
*widget
);
185 static void edit_icon(gpointer data
, guint action
, GtkWidget
*widget
);
186 static void show_location(gpointer data
, guint action
, GtkWidget
*widget
);
187 static void pin_help(gpointer data
, guint action
, GtkWidget
*widget
);
188 static void pin_remove(gpointer data
, guint action
, GtkWidget
*widget
);
189 static void show_pinboard_menu(GdkEventButton
*event
, Icon
*icon
);
191 static void pinboard_check_options(void);
193 static GtkItemFactoryEntry menu_def
[] = {
194 {N_("ROX-Filer Help"), NULL
, menu_rox_help
, 0, NULL
},
195 {N_("ROX-Filer Options..."), NULL
, menu_show_options
, 0, NULL
},
196 {N_("Open Home Directory"), NULL
, open_home
, 0, NULL
},
197 {"", NULL
, NULL
, 0, "<Separator>"},
198 {N_("Edit Icon"), NULL
, edit_icon
, 0, NULL
},
199 {N_("Show Location"), NULL
, show_location
, 0, NULL
},
200 {N_("Show Help"), NULL
, pin_help
, 0, NULL
},
201 {N_("Remove Item(s)"), NULL
, pin_remove
, 0, NULL
},
204 static GtkWidget
*pinboard_menu
; /* The popup pinboard menu */
206 /****************************************************************
207 * EXTERNAL INTERFACE *
208 ****************************************************************/
210 void pinboard_init(void)
214 option_add_string("pinboard_fg_colour", "#000", NULL
);
215 option_add_string("pinboard_bg_colour", "#ddd", NULL
);
217 option_add_int("pinboard_text_bg", TEXT_BG_SOLID
, NULL
);
218 option_add_int("pinboard_clamp_icons", 1, NULL
);
219 option_add_int("pinboard_grid_step", GRID_STEP_COARSE
, NULL
);
220 option_add_notify(pinboard_check_options
);
222 style
= gtk_widget_get_default_style();
224 gdk_color_parse(option_get_static_string("pinboard_fg_colour"),
226 gdk_color_parse(option_get_static_string("pinboard_bg_colour"),
229 pinboard_menu
= menu_create(menu_def
,
230 sizeof(menu_def
) / sizeof(*menu_def
),
232 gtk_signal_connect(GTK_OBJECT(pinboard_menu
), "unmap_event",
233 GTK_SIGNAL_FUNC(menu_closed
), NULL
);
236 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
237 * and make it the current pinboard.
238 * Any existing pinned items are removed. You must call this
239 * at least once before using the pinboard. NULL disables the
242 void pinboard_activate(guchar
*name
)
244 Pinboard
*old_board
= current_pinboard
;
245 guchar
*path
, *slash
;
247 /* Treat an empty name the same as NULL */
259 if (number_of_windows
< 1 && gtk_main_level() > 0)
264 if (!add_root_handlers())
266 delayed_error(PROJECT
, _("Another application is already "
267 "managing the pinboard!"));
273 slash
= strchr(name
, '/');
276 if (access(name
, F_OK
))
277 path
= NULL
; /* File does not (yet) exist */
279 path
= g_strdup(name
);
285 leaf
= g_strconcat("pb_", name
, NULL
);
286 path
= choices_find_path_load(leaf
, "ROX-Filer");
290 current_pinboard
= g_new(Pinboard
, 1);
291 current_pinboard
->name
= g_strdup(name
);
292 current_pinboard
->icons
= NULL
;
297 parse_file(path
, pin_from_file
);
301 pinboard_pin(home_dir
, "Home", 4, 4, TRUE
);
305 /* Add a new icon to the background.
306 * 'path' should be an absolute pathname.
307 * 'x' and 'y' are the coordinates of the point in the middle of the text
308 * if 'corner' is FALSE, and as the top-left corner of where the icon
309 * image should be if it is TRUE.
310 * 'name' is the name to use. If NULL then the leafname of path is used.
312 void pinboard_pin(guchar
*path
, guchar
*name
, int x
, int y
, gboolean corner
)
317 g_return_if_fail(path
!= NULL
);
318 g_return_if_fail(current_pinboard
!= NULL
);
320 icon
= g_new(Icon
, 1);
321 icon
->type
= ICON_PINBOARD
;
322 icon
->selected
= FALSE
;
323 icon
->src_path
= g_strdup(path
);
324 icon
->path
= icon_convert_path(path
);
329 icon_hash_path(icon
);
331 dir_stat(icon
->path
, &icon
->item
, FALSE
);
335 name
= strrchr(icon
->path
, '/');
342 icon
->item
.leafname
= g_strdup(name
);
344 icon
->win
= gtk_window_new(GTK_WINDOW_DIALOG
);
345 gtk_window_set_wmclass(GTK_WINDOW(icon
->win
), "ROX-Pinboard", PROJECT
);
347 icon
->widget
= gtk_drawing_area_new();
348 gtk_container_add(GTK_CONTAINER(icon
->win
), icon
->widget
);
349 drag_set_pinicon_dest(icon
);
350 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "drag_data_get",
351 drag_data_get
, NULL
);
353 gtk_widget_realize(icon
->win
);
354 gtk_widget_realize(icon
->widget
);
356 set_size_and_shape(icon
, &width
, &height
);
359 /* Convert from icon-corner coordinates to center coordinates */
360 MaskedPixmap
*image
= icon
->item
.image
;
361 x
+= (image
->width
>> 1);
362 y
+= height
- (icon
->widget
->style
->font
->descent
>> 1);
364 snap_to_grid(&x
, &y
);
365 offset_from_centre(icon
, width
, height
, &x
, &y
);
366 gtk_widget_set_uposition(icon
->win
, x
, y
);
367 /* Set the correct position in the icon */
368 offset_to_centre(icon
, width
, height
, &x
, &y
);
372 make_panel_window(icon
->win
->window
);
374 gtk_widget_add_events(icon
->widget
,
375 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
376 GDK_BUTTON1_MOTION_MASK
| GDK_ENTER_NOTIFY_MASK
|
377 GDK_BUTTON2_MOTION_MASK
| GDK_BUTTON3_MOTION_MASK
);
378 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "enter-notify-event",
379 GTK_SIGNAL_FUNC(enter_notify
), icon
);
380 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "button-press-event",
381 GTK_SIGNAL_FUNC(button_press_event
), icon
);
382 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "button-release-event",
383 GTK_SIGNAL_FUNC(button_release_event
), icon
);
384 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "motion-notify-event",
385 GTK_SIGNAL_FUNC(icon_motion_notify
), icon
);
386 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "expose-event",
387 GTK_SIGNAL_FUNC(draw_icon
), icon
);
388 gtk_signal_connect(GTK_OBJECT(icon
->win
), "destroy",
389 GTK_SIGNAL_FUNC(icon_destroyed
), icon
);
391 current_pinboard
->icons
= g_list_prepend(current_pinboard
->icons
,
393 gtk_widget_show_all(icon
->win
);
394 gdk_window_lower(icon
->win
->window
);
396 if (!loading_pinboard
)
400 /* Remove an icon from the pinboard */
401 void pinboard_unpin(Icon
*icon
)
403 g_return_if_fail(icon
!= NULL
);
405 gtk_widget_destroy(icon
->win
);
409 /* Unpin all selected items */
410 void pinboard_unpin_selection(void)
414 g_return_if_fail(current_pinboard
!= NULL
);
416 if (number_selected
== 0)
418 delayed_error(PROJECT
,
419 _("You should first select some pinned icons to "
420 "unpin. Hold down the Ctrl key to select some icons."));
424 next
= current_pinboard
->icons
;
427 Icon
*icon
= (Icon
*) next
->data
;
432 gtk_widget_destroy(icon
->win
);
438 /* Put a border around the icon, briefly.
439 * If icon is NULL then cancel any existing wink.
440 * The icon will automatically unhighlight unless timeout is FALSE,
441 * in which case you must call this function again (with NULL or another
442 * icon) to remove the highlight.
444 void pinboard_wink_item(Icon
*icon
, gboolean timeout
)
446 if (current_wink_icon
== icon
)
449 if (current_wink_icon
)
451 mask_wink_border(current_wink_icon
, &mask_transp
);
452 if (wink_timeout
!= -1)
453 gtk_timeout_remove(wink_timeout
);
456 current_wink_icon
= icon
;
458 if (current_wink_icon
)
460 mask_wink_border(current_wink_icon
, &mask_solid
);
462 wink_timeout
= gtk_timeout_add(300, end_wink
, NULL
);
468 /* Remove everything on the current pinboard and disables the pinboard.
469 * Does not change any files. Does not change number_of_windows.
471 void pinboard_clear(void)
475 g_return_if_fail(current_pinboard
!= NULL
);
477 next
= current_pinboard
->icons
;
480 Icon
*icon
= (Icon
*) next
->data
;
484 gtk_widget_destroy(icon
->win
);
487 g_free(current_pinboard
->name
);
488 g_free(current_pinboard
);
489 current_pinboard
= NULL
;
491 release_xdnd_proxy(GDK_ROOT_WINDOW());
492 gdk_window_remove_filter(GDK_ROOT_PARENT(), proxy_filter
, NULL
);
493 gdk_window_set_user_data(GDK_ROOT_PARENT(), NULL
);
496 /* Return the single selected icon, or NULL */
497 Icon
*pinboard_selected_icon(void)
502 g_return_val_if_fail(current_pinboard
!= NULL
, NULL
);
504 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
506 Icon
*icon
= (Icon
*) next
->data
;
511 return NULL
; /* >1 icon selected */
520 void pinboard_clear_selection(void)
522 pinboard_select_only(NULL
);
525 /* Set whether an icon is selected or not */
526 void pinboard_set_selected(Icon
*icon
, gboolean selected
)
528 g_return_if_fail(icon
!= NULL
);
530 if (icon
->selected
== selected
)
534 change_number_selected(+1);
536 change_number_selected(-1);
538 icon
->selected
= selected
;
539 gtk_widget_queue_draw(icon
->win
);
542 /* Return a list of all the selected icons.
543 * g_list_free() the result.
545 GList
*pinboard_get_selected(void)
548 GList
*selected
= NULL
;
550 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
552 Icon
*i
= (Icon
*) next
->data
;
555 selected
= g_list_append(selected
, i
);
561 /* Clear the selection and then select this icon.
562 * Doesn't release and claim the selection unnecessarily.
563 * If icon is NULL, then just clears the selection.
565 void pinboard_select_only(Icon
*icon
)
569 g_return_if_fail(current_pinboard
!= NULL
);
572 pinboard_set_selected(icon
, TRUE
);
574 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
576 Icon
*i
= (Icon
*) next
->data
;
578 if (i
->selected
&& i
!= icon
)
579 pinboard_set_selected(i
, FALSE
);
584 /****************************************************************
585 * INTERNAL FUNCTIONS *
586 ****************************************************************/
588 static void pinboard_check_options(void)
590 int old_text_bg
= o_text_bg
;
593 o_text_bg
= option_get_int("pinboard_text_bg");
594 o_grid_step
= option_get_int("pinboard_grid_step");
595 o_clamp_icons
= option_get_int("pinboard_clamp_icons");
597 gdk_color_parse(option_get_static_string("pinboard_fg_colour"), &n_fg
);
598 gdk_color_parse(option_get_static_string("pinboard_bg_colour"), &n_bg
);
600 if (o_text_bg
!= old_text_bg
||
601 gdk_color_equal(&n_fg
, &text_fg_col
) == 0 ||
602 gdk_color_equal(&n_bg
, &text_bg_col
) == 0)
604 memcpy(&text_fg_col
, &n_fg
, sizeof(GdkColor
));
605 memcpy(&text_bg_col
, &n_bg
, sizeof(GdkColor
));
609 gtk_style_unref(pinicon_style
);
610 pinicon_style
= NULL
;
613 if (current_pinboard
)
618 /* Icon's size, shape or appearance has changed - update the display */
619 static void reshape_icon(Icon
*icon
)
621 int x
= icon
->x
, y
= icon
->y
;
624 set_size_and_shape(icon
, &width
, &height
);
625 gdk_window_resize(icon
->win
->window
, width
, height
);
626 offset_from_centre(icon
, width
, height
, &x
, &y
);
627 gtk_widget_set_uposition(icon
->win
, x
, y
);
628 gtk_widget_queue_draw(icon
->win
);
631 /* See if the file the icon points to has changed. Update the icon
634 void pinboard_icon_may_update(Icon
*icon
)
636 MaskedPixmap
*image
= icon
->item
.image
;
637 int flags
= icon
->item
.flags
;
641 dir_restat(icon
->path
, &icon
->item
, FALSE
);
643 if (icon
->item
.image
!= image
|| icon
->item
.flags
!= flags
)
649 static gint
end_wink(gpointer data
)
651 pinboard_wink_item(NULL
, FALSE
);
655 /* Make the wink border solid or transparent */
656 static void mask_wink_border(Icon
*icon
, GdkColor
*alpha
)
660 gdk_window_get_size(icon
->widget
->window
, &width
, &height
);
662 gdk_gc_set_foreground(mask_gc
, alpha
);
663 gdk_draw_rectangle(icon
->mask
, mask_gc
, FALSE
,
664 0, 0, width
- 1, height
- 1);
665 gdk_draw_rectangle(icon
->mask
, mask_gc
, FALSE
,
666 1, 1, width
- 3, height
- 3);
668 gtk_widget_shape_combine_mask(icon
->win
, icon
->mask
, 0, 0);
670 gtk_widget_draw(icon
->widget
, NULL
);
673 #define TEXT_AT(dx, dy) \
674 gdk_draw_string(icon->mask, font, mask_gc, \
675 text_x + dx, y + dy, \
678 /* Updates the name_width field and resizes and masks the window.
679 * Also sets the style to pinicon_style, generating it if needed.
680 * Returns the new width and height.
682 static void set_size_and_shape(Icon
*icon
, int *rwidth
, int *rheight
)
687 MaskedPixmap
*image
= icon
->item
.image
;
688 DirItem
*item
= &icon
->item
;
693 pinicon_style
= gtk_style_copy(icon
->widget
->style
);
694 memcpy(&pinicon_style
->fg
[GTK_STATE_NORMAL
],
695 &text_fg_col
, sizeof(GdkColor
));
696 memcpy(&pinicon_style
->bg
[GTK_STATE_NORMAL
],
697 &text_bg_col
, sizeof(GdkColor
));
699 gtk_widget_set_style(icon
->widget
, pinicon_style
);
701 font
= pinicon_style
->font
;
702 font_height
= font
->ascent
+ font
->descent
;
703 item
->name_width
= gdk_string_width(font
, item
->leafname
);
705 width
= MAX(image
->width
, item
->name_width
+ 2) +
707 height
= image
->height
+ GAP
+ (font_height
+ 2) + 2 * WINK_FRAME
;
708 gtk_widget_set_usize(icon
->win
, width
, height
);
711 gdk_pixmap_unref(icon
->mask
);
712 icon
->mask
= gdk_pixmap_new(icon
->win
->window
, width
, height
, 1);
714 mask_gc
= gdk_gc_new(icon
->mask
);
716 /* Clear the mask to transparent */
717 gdk_gc_set_foreground(mask_gc
, &mask_transp
);
718 gdk_draw_rectangle(icon
->mask
, mask_gc
, TRUE
, 0, 0, width
, height
);
720 gdk_gc_set_foreground(mask_gc
, &mask_solid
);
721 /* Make the icon area solid */
724 gdk_draw_pixmap(icon
->mask
, mask_gc
, image
->mask
,
726 (width
- image
->width
) >> 1,
733 gdk_draw_rectangle(icon
->mask
, mask_gc
, TRUE
,
734 (width
- image
->width
) >> 1,
740 gdk_gc_set_function(mask_gc
, GDK_OR
);
741 if (item
->flags
& ITEM_FLAG_SYMLINK
)
743 gdk_draw_pixmap(icon
->mask
, mask_gc
, im_symlink
->mask
,
744 0, 0, /* Source x,y */
745 (width
- image
->width
) >> 1, /* Dest x */
746 WINK_FRAME
, /* Dest y */
749 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
751 /* Note: Both mount state pixmaps must have the same mask */
752 gdk_draw_pixmap(icon
->mask
, mask_gc
, im_mounted
->mask
,
753 0, 0, /* Source x,y */
754 (width
- image
->width
) >> 1, /* Dest x */
755 WINK_FRAME
, /* Dest y */
758 gdk_gc_set_function(mask_gc
, GDK_COPY
);
760 /* Mask off an area for the text (from o_text_bg) */
762 text_x
= (width
- item
->name_width
) >> 1;
763 text_y
= WINK_FRAME
+ image
->height
+ GAP
+ 1;
765 if (o_text_bg
== TEXT_BG_SOLID
)
767 gdk_draw_rectangle(icon
->mask
, mask_gc
, TRUE
,
768 (width
- (item
->name_width
+ 2)) >> 1,
769 WINK_FRAME
+ image
->height
+ GAP
,
770 item
->name_width
+ 2, font_height
+ 2);
774 int y
= text_y
+ font
->ascent
;
778 if (o_text_bg
== TEXT_BG_OUTLINE
)
791 gtk_widget_shape_combine_mask(icon
->win
, icon
->mask
, 0, 0);
797 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, Icon
*icon
)
799 GdkFont
*font
= icon
->widget
->style
->font
;
803 DirItem
*item
= &icon
->item
;
804 MaskedPixmap
*image
= item
->image
;
806 GdkGC
*gc
= widget
->style
->black_gc
;
807 GtkStateType state
= icon
->selected
? GTK_STATE_SELECTED
810 font_height
= font
->ascent
+ font
->descent
;
812 gdk_window_get_size(widget
->window
, &width
, &height
);
813 image_x
= (width
- image
->width
) >> 1;
815 /* TODO: If the shape extension is missing we might need to set
816 * the clip mask here...
818 gdk_draw_pixmap(widget
->window
, gc
,
826 if (item
->flags
& ITEM_FLAG_SYMLINK
)
828 gdk_gc_set_clip_origin(gc
, image_x
, WINK_FRAME
);
829 gdk_gc_set_clip_mask(gc
, im_symlink
->mask
);
830 gdk_draw_pixmap(widget
->window
, gc
,
832 0, 0, /* Source x,y */
833 image_x
, WINK_FRAME
, /* Dest x,y */
835 gdk_gc_set_clip_mask(gc
, NULL
);
836 gdk_gc_set_clip_origin(gc
, 0, 0);
838 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
840 MaskedPixmap
*mp
= item
->flags
& ITEM_FLAG_MOUNTED
844 gdk_gc_set_clip_origin(gc
, image_x
, WINK_FRAME
);
845 gdk_gc_set_clip_mask(gc
, mp
->mask
);
846 gdk_draw_pixmap(widget
->window
, gc
,
848 0, 0, /* Source x,y */
849 image_x
, WINK_FRAME
, /* Dest x,y */
851 gdk_gc_set_clip_mask(gc
, NULL
);
852 gdk_gc_set_clip_origin(gc
, 0, 0);
855 text_x
= (width
- item
->name_width
) >> 1;
856 text_y
= WINK_FRAME
+ image
->height
+ GAP
+ 1;
858 if (o_text_bg
!= TEXT_BG_NONE
)
860 gtk_paint_flat_box(widget
->style
, widget
->window
,
863 NULL
, widget
, "text",
866 item
->name_width
+ 2,
870 gtk_paint_string(widget
->style
, widget
->window
,
872 NULL
, widget
, "text",
874 text_y
+ font
->ascent
,
877 if (current_wink_icon
== icon
)
879 gdk_draw_rectangle(icon
->widget
->window
,
880 icon
->widget
->style
->white_gc
,
882 0, 0, width
- 1, height
- 1);
883 gdk_draw_rectangle(icon
->widget
->window
,
884 icon
->widget
->style
->black_gc
,
886 1, 1, width
- 3, height
- 3);
892 static gboolean
root_property_event(GtkWidget
*widget
,
893 GdkEventProperty
*event
,
896 if (event
->atom
== win_button_proxy
&&
897 event
->state
== GDK_PROPERTY_NEW_VALUE
)
899 /* Setup forwarding on the new proxy window, if possible */
900 forward_root_clicks();
906 static gboolean
root_button_press(GtkWidget
*widget
,
907 GdkEventButton
*event
,
912 action
= bind_lookup_bev(BIND_PINBOARD
, event
);
916 case ACT_CLEAR_SELECTION
:
917 pinboard_clear_selection();
921 show_pinboard_menu(event
, NULL
);
926 g_warning("Unsupported action : %d\n", action
);
933 static gboolean
enter_notify(GtkWidget
*widget
,
934 GdkEventCrossing
*event
,
937 pinboard_icon_may_update(icon
);
942 static void perform_action(Icon
*icon
, GdkEventButton
*event
)
946 action
= bind_lookup_bev(BIND_PINBOARD_ICON
, event
);
952 pinboard_wink_item(icon
, TRUE
);
953 run_diritem(icon
->path
, &icon
->item
, NULL
, FALSE
);
957 pinboard_wink_item(icon
, TRUE
);
958 run_diritem(icon
->path
, &icon
->item
, NULL
, TRUE
);
962 show_pinboard_menu(event
, icon
);
965 old_x
= event
->x_root
;
966 old_y
= event
->y_root
;
967 icon_old_x
= icon
->x
;
968 icon_old_y
= icon
->y
;
969 dnd_motion_start(MOTION_REPOSITION
);
971 case ACT_PRIME_AND_SELECT
:
973 pinboard_select_only(icon
);
974 dnd_motion_start(MOTION_READY_FOR_DND
);
976 case ACT_PRIME_AND_TOGGLE
:
977 pinboard_set_selected(icon
, !icon
->selected
);
978 dnd_motion_start(MOTION_READY_FOR_DND
);
980 case ACT_PRIME_FOR_DND
:
981 pinboard_wink_item(icon
, TRUE
);
982 dnd_motion_start(MOTION_READY_FOR_DND
);
984 case ACT_TOGGLE_SELECTED
:
985 pinboard_set_selected(icon
, !icon
->selected
);
987 case ACT_SELECT_EXCL
:
988 pinboard_select_only(icon
);
993 g_warning("Unsupported action : %d\n", action
);
998 static gboolean
button_release_event(GtkWidget
*widget
,
999 GdkEventButton
*event
,
1002 if (pinboard_modified
)
1005 if (dnd_motion_release(event
))
1008 perform_action(icon
, event
);
1013 static gboolean
button_press_event(GtkWidget
*widget
,
1014 GdkEventButton
*event
,
1017 if (dnd_motion_press(widget
, event
))
1018 perform_action(icon
, event
);
1023 /* Return a text/uri-list of all the icons in the list */
1024 static guchar
*create_uri_list(GList
*list
)
1030 tmp
= g_string_new(NULL
);
1031 leader
= g_strdup_printf("file://%s", our_host_name());
1033 for (; list
; list
= list
->next
)
1035 Icon
*icon
= (Icon
*) list
->data
;
1037 g_string_append(tmp
, leader
);
1038 g_string_append(tmp
, icon
->path
);
1039 g_string_append(tmp
, "\r\n");
1044 g_string_free(tmp
, FALSE
);
1049 static void start_drag(Icon
*icon
, GdkEventMotion
*event
)
1051 GtkWidget
*widget
= icon
->widget
;
1054 if (!icon
->selected
)
1056 tmp_icon_selected
= TRUE
;
1057 pinboard_select_only(icon
);
1060 selected
= pinboard_get_selected();
1061 g_return_if_fail(selected
!= NULL
);
1063 pinboard_drag_in_progress
= TRUE
;
1065 if (selected
->next
== NULL
)
1066 drag_one_item(widget
, event
, icon
->path
, &icon
->item
);
1071 uri_list
= create_uri_list(selected
);
1072 drag_selection(widget
, event
, uri_list
);
1076 g_list_free(selected
);
1079 /* An icon is being dragged around... */
1080 static gint
icon_motion_notify(GtkWidget
*widget
,
1081 GdkEventMotion
*event
,
1088 if (motion_state
== MOTION_READY_FOR_DND
)
1090 if (dnd_motion_moved(event
))
1091 start_drag(icon
, event
);
1094 else if (motion_state
!= MOTION_REPOSITION
)
1097 /* How far the pointer has moved since the drag started */
1098 dx
= event
->x_root
- old_x
;
1099 dy
= event
->y_root
- old_y
;
1101 x
= icon_old_x
+ dx
;
1102 y
= icon_old_y
+ dy
;
1104 snap_to_grid(&x
, &y
);
1106 if (icon
->x
== x
&& icon
->y
== y
)
1111 gdk_window_get_size(icon
->win
->window
, &width
, &height
);
1112 offset_from_centre(icon
, width
, height
, &x
, &y
);
1114 gdk_window_move(icon
->win
->window
, x
, y
);
1116 /* Store the fixed position for the center of the icon */
1117 offset_to_centre(icon
, width
, height
, &x
, &y
);
1121 pinboard_modified
= TRUE
;
1126 /* Called for each line in the pinboard file while loading a new board */
1127 static char *pin_from_file(guchar
*line
)
1129 guchar
*leaf
= NULL
;
1136 end
= strchr(line
+ 1, '>');
1138 return _("Missing '>' in icon label");
1140 leaf
= g_strndup(line
+ 1, end
- line
- 1);
1144 while (isspace(*line
))
1147 return _("Missing ',' after icon label");
1151 if (sscanf(line
, " %d , %d , %n", &x
, &y
, &n
) < 2)
1152 return NULL
; /* Ignore format errors */
1154 pinboard_pin(line
+ n
, leaf
, x
, y
, FALSE
);
1161 /* Make sure that clicks and drops on the root window come to us...
1162 * False if an error occurred (ie, someone else is using it).
1164 static gboolean
add_root_handlers(void)
1168 if (!proxy_invisible
)
1170 GtkTargetEntry target_table
[] =
1172 {"text/uri-list", 0, TARGET_URI_LIST
},
1173 {"STRING", 0, TARGET_STRING
},
1176 win_button_proxy
= gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY",
1178 proxy_invisible
= gtk_invisible_new();
1179 gtk_widget_show(proxy_invisible
);
1181 gdk_window_add_filter(proxy_invisible->window,
1182 proxy_filter, NULL);
1185 gtk_signal_connect(GTK_OBJECT(proxy_invisible
),
1186 "property_notify_event",
1187 GTK_SIGNAL_FUNC(root_property_event
), NULL
);
1188 gtk_signal_connect(GTK_OBJECT(proxy_invisible
),
1189 "button_press_event",
1190 GTK_SIGNAL_FUNC(root_button_press
), NULL
);
1192 /* Drag and drop handlers */
1193 drag_set_pinboard_dest(proxy_invisible
);
1194 gtk_signal_connect(GTK_OBJECT(proxy_invisible
), "drag_motion",
1195 GTK_SIGNAL_FUNC(bg_drag_motion
),
1198 /* The proxy window is also used to hold the selection... */
1199 gtk_signal_connect(GTK_OBJECT(proxy_invisible
),
1200 "selection_clear_event",
1201 GTK_SIGNAL_FUNC(lose_selection
),
1204 gtk_signal_connect(GTK_OBJECT(proxy_invisible
),
1206 GTK_SIGNAL_FUNC(selection_get
), NULL
);
1208 gtk_selection_add_targets(proxy_invisible
,
1209 GDK_SELECTION_PRIMARY
,
1211 sizeof(target_table
) / sizeof(*target_table
));
1214 root
= gdk_window_lookup(GDK_ROOT_WINDOW());
1216 root
= gdk_window_foreign_new(GDK_ROOT_WINDOW());
1218 if (!setup_xdnd_proxy(GDK_ROOT_WINDOW(), proxy_invisible
->window
))
1221 /* Forward events from the root window to our proxy window */
1222 gdk_window_add_filter(GDK_ROOT_PARENT(), proxy_filter
, NULL
);
1223 gdk_window_set_user_data(GDK_ROOT_PARENT(), proxy_invisible
);
1224 gdk_window_set_events(GDK_ROOT_PARENT(),
1225 gdk_window_get_events(GDK_ROOT_PARENT()) |
1226 GDK_PROPERTY_CHANGE_MASK
);
1228 forward_root_clicks();
1233 /* See if the window manager is offering to forward root window clicks.
1234 * If so, grab them. Otherwise, do nothing.
1235 * Call this whenever the _WIN_DESKTOP_BUTTON_PROXY property changes.
1237 static void forward_root_clicks(void)
1239 click_proxy_gdk_window
= find_click_proxy_window();
1240 if (!click_proxy_gdk_window
)
1243 /* Events on the wm's proxy are dealt with by our proxy widget */
1244 gdk_window_set_user_data(click_proxy_gdk_window
, proxy_invisible
);
1245 gdk_window_add_filter(click_proxy_gdk_window
, proxy_filter
, NULL
);
1247 /* The proxy window for clicks sends us button press events with
1248 * SubstructureNotifyMask. We need StructureNotifyMask to receive
1249 * DestroyNotify events, too.
1251 XSelectInput(GDK_DISPLAY(),
1252 GDK_WINDOW_XWINDOW(click_proxy_gdk_window
),
1253 SubstructureNotifyMask
| StructureNotifyMask
);
1256 /* Write the current state of the pinboard to the current pinboard file */
1257 static void pinboard_save(void)
1259 guchar
*save
= NULL
;
1260 GString
*tmp
= NULL
;
1263 guchar
*save_new
= NULL
;
1265 g_return_if_fail(current_pinboard
!= NULL
);
1267 pinboard_modified
= FALSE
;
1269 if (strchr(current_pinboard
->name
, '/'))
1270 save
= g_strdup(current_pinboard
->name
);
1275 leaf
= g_strconcat("pb_", current_pinboard
->name
, NULL
);
1276 save
= choices_find_path_save(leaf
, "ROX-Filer", TRUE
);
1283 save_new
= g_strconcat(save
, ".new", NULL
);
1284 file
= fopen(save_new
, "wb");
1288 tmp
= g_string_new(NULL
);
1289 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
1291 Icon
*icon
= (Icon
*) next
->data
;
1293 g_string_sprintf(tmp
, "<%s>, %d, %d, %s\n",
1294 icon
->item
.leafname
, icon
->x
, icon
->y
, icon
->src_path
);
1295 if (fwrite(tmp
->str
, 1, tmp
->len
, file
) < tmp
->len
)
1307 if (rename(save_new
, save
))
1312 delayed_error(_("Error saving pinboard"), g_strerror(errno
));
1317 g_string_free(tmp
, TRUE
);
1324 * Filter that translates proxied events from virtual root windows into normal
1325 * Gdk events for the proxy_invisible widget. Stolen from gmc.
1327 * Also gets events from the root window.
1329 static GdkFilterReturn
proxy_filter(GdkXEvent
*xevent
,
1334 GdkWindow
*proxy
= proxy_invisible
->window
;
1338 switch (xev
->type
) {
1341 /* Translate button events into events that come from
1342 * the proxy window, so that we can catch them as a
1343 * signal from the invisible widget.
1345 if (xev
->type
== ButtonPress
)
1346 event
->button
.type
= GDK_BUTTON_PRESS
;
1348 event
->button
.type
= GDK_BUTTON_RELEASE
;
1350 gdk_window_ref(proxy
);
1352 event
->button
.window
= proxy
;
1353 event
->button
.send_event
= xev
->xbutton
.send_event
;
1354 event
->button
.time
= xev
->xbutton
.time
;
1355 event
->button
.x_root
= xev
->xbutton
.x_root
;
1356 event
->button
.y_root
= xev
->xbutton
.y_root
;
1357 event
->button
.x
= xev
->xbutton
.x
;
1358 event
->button
.y
= xev
->xbutton
.y
;
1359 event
->button
.state
= xev
->xbutton
.state
;
1360 event
->button
.button
= xev
->xbutton
.button
;
1362 return GDK_FILTER_TRANSLATE
;
1365 /* XXX: I have no idea why this helps, but it does! */
1366 /* The proxy window was destroyed (i.e. the window
1367 * manager died), so we have to cope with it
1369 if (((GdkEventAny
*) event
)->window
== proxy
)
1370 gdk_window_destroy_notify(proxy
);
1372 return GDK_FILTER_REMOVE
;
1378 return GDK_FILTER_CONTINUE
;
1381 /* Does not save the new state */
1382 static void icon_destroyed(GtkWidget
*widget
, Icon
*icon
)
1384 g_return_if_fail(icon
!= NULL
);
1386 icon_unhash_path(icon
);
1389 change_number_selected(-1);
1391 if (current_wink_icon
== icon
)
1392 current_wink_icon
= NULL
;
1394 gdk_pixmap_unref(icon
->mask
);
1395 dir_item_clear(&icon
->item
);
1396 g_free(icon
->src_path
);
1400 if (current_pinboard
)
1401 current_pinboard
->icons
=
1402 g_list_remove(current_pinboard
->icons
, icon
);
1405 static void snap_to_grid(int *x
, int *y
)
1407 *x
= ((*x
+ o_grid_step
/ 2) / o_grid_step
) * o_grid_step
;
1408 *y
= ((*y
+ o_grid_step
/ 2) / o_grid_step
) * o_grid_step
;
1411 /* Convert (x,y) from a centre point to a window position */
1412 static void offset_from_centre(Icon
*icon
,
1413 int width
, int height
,
1417 *y
-= height
- (icon
->widget
->style
->font
->descent
>> 1);
1418 *x
= CLAMP(*x
, 0, screen_width
- (o_clamp_icons
? width
: 0));
1419 *y
= CLAMP(*y
, 0, screen_height
- (o_clamp_icons
? height
: 0));
1422 /* Convert (x,y) from a window position to a centre point */
1423 static void offset_to_centre(Icon
*icon
,
1424 int width
, int height
,
1428 *y
+= height
- (icon
->widget
->style
->font
->descent
>> 1);
1431 /* Same as drag_set_dest(), but for pinboard icons */
1432 static void drag_set_pinicon_dest(Icon
*icon
)
1434 GtkObject
*obj
= GTK_OBJECT(icon
->widget
);
1436 make_drop_target(icon
->widget
, 0);
1438 gtk_signal_connect(obj
, "drag_motion",
1439 GTK_SIGNAL_FUNC(drag_motion
), icon
);
1440 gtk_signal_connect(obj
, "drag_leave",
1441 GTK_SIGNAL_FUNC(drag_leave
), icon
);
1442 gtk_signal_connect(obj
, "drag_end",
1443 GTK_SIGNAL_FUNC(drag_end
), icon
);
1446 /* Called during the drag when the mouse is in a widget registered
1447 * as a drop target. Returns TRUE if we can accept the drop.
1449 static gboolean
drag_motion(GtkWidget
*widget
,
1450 GdkDragContext
*context
,
1456 GdkDragAction action
= context
->suggested_action
;
1458 DirItem
*item
= &icon
->item
;
1460 if (gtk_drag_get_source_widget(context
) == widget
)
1461 goto out
; /* Can't drag something to itself! */
1464 goto out
; /* Can't drag a selection to itself */
1466 type
= dnd_motion_item(context
, &item
);
1471 /* We actually must pretend to accept the drop, even if the
1472 * directory isn't writeable, so that the spring-opening
1476 /* Don't allow drops to non-writeable directories */
1477 if (option_get_int("dnd_spring_open") == FALSE
&&
1478 type
== drop_dest_dir
&&
1479 access(icon
->path
, W_OK
) != 0)
1484 g_dataset_set_data(context
, "drop_dest_type", type
);
1487 gdk_drag_status(context
, action
, time
);
1488 g_dataset_set_data_full(context
, "drop_dest_path",
1489 g_strdup(icon
->path
), g_free
);
1490 if (type
== drop_dest_dir
)
1491 dnd_spring_load(context
);
1493 pinboard_wink_item(icon
, FALSE
);
1496 return type
!= NULL
;
1499 static void drag_leave(GtkWidget
*widget
,
1500 GdkDragContext
*context
,
1504 pinboard_wink_item(NULL
, FALSE
);
1508 /* When changing the 'selected' attribute of an icon, call this
1509 * to update the global counter and claim or release the primary
1510 * selection as needed.
1512 static void change_number_selected(int delta
)
1516 g_return_if_fail(delta
!= 0);
1517 g_return_if_fail(number_selected
+ delta
>= 0);
1519 if (number_selected
== 0)
1521 time
= gdk_event_get_time(gtk_get_current_event());
1523 gtk_selection_owner_set(proxy_invisible
,
1524 GDK_SELECTION_PRIMARY
,
1528 number_selected
+= delta
;
1530 if (number_selected
== 0)
1532 time
= gdk_event_get_time(gtk_get_current_event());
1534 gtk_selection_owner_set(NULL
,
1535 GDK_SELECTION_PRIMARY
,
1540 /* Called when another application wants the contents of our selection */
1541 static void selection_get(GtkWidget
*widget
,
1542 GtkSelectionData
*selection_data
,
1549 guchar
*leader
= NULL
;
1551 str
= g_string_new(NULL
);
1553 if (info
== TARGET_URI_LIST
)
1554 leader
= g_strdup_printf("file://%s", our_host_name());
1556 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
1558 Icon
*icon
= (Icon
*) next
->data
;
1560 if (!icon
->selected
)
1564 g_string_append(str
, leader
);
1565 g_string_append(str
, icon
->path
);
1566 g_string_append_c(str
, ' ');
1571 gtk_selection_data_set(selection_data
,
1572 gdk_atom_intern("STRING", FALSE
),
1575 str
->len
? str
->len
- 1 : 0);
1577 g_string_free(str
, TRUE
);
1580 /* Called when another application takes the selection away from us */
1581 static gint
lose_selection(GtkWidget
*widget
, GdkEventSelection
*event
)
1583 /* 'lock' number_selected so that we don't send any events */
1585 pinboard_clear_selection();
1591 static gboolean
bg_drag_motion(GtkWidget
*widget
,
1592 GdkDragContext
*context
,
1598 /* Dragging from the pinboard to the pinboard is not allowed */
1599 if (pinboard_drag_in_progress
)
1602 gdk_drag_status(context
, context
->suggested_action
, time
);
1606 static void drag_end(GtkWidget
*widget
,
1607 GdkDragContext
*context
,
1610 pinboard_drag_in_progress
= FALSE
;
1611 if (tmp_icon_selected
)
1613 pinboard_clear_selection();
1614 tmp_icon_selected
= FALSE
;
1618 /* Something which affects all the icons has changed - reshape
1619 * and redraw all of them.
1621 static void reshape_all(void)
1625 g_return_if_fail(current_pinboard
!= NULL
);
1627 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
1629 Icon
*icon
= (Icon
*) next
->data
;
1634 /* Display the pinboard menu. Set icon to NULL if no particular icon
1637 static void show_pinboard_menu(GdkEventButton
*event
, Icon
*icon
)
1642 /* Remove the previous appmenu used on this menu */
1648 tmp_icon_selected
= FALSE
;
1651 pinboard_select_only(icon
);
1652 tmp_icon_selected
= TRUE
;
1656 icons
= pinboard_get_selected();
1658 pos
[0] = event
->x_root
;
1659 pos
[1] = event
->y_root
;
1663 menu_set_items_shaded(pinboard_menu
,
1664 icons
->next
? TRUE
: FALSE
, 4, 3);
1666 menu_set_items_shaded(pinboard_menu
, FALSE
, 7, 1);
1668 /* Check for app-specific menu */
1671 Icon
*icon
= (Icon
*) icons
->data
;
1672 appmenu_add(icon
->path
, &icon
->item
, pinboard_menu
);
1676 menu_set_items_shaded(pinboard_menu
, TRUE
, 4, 4);
1678 gtk_menu_popup(GTK_MENU(pinboard_menu
), NULL
, NULL
, position_menu
,
1679 (gpointer
) pos
, event
->button
, event
->time
);
1682 static void pin_help(gpointer data
, guint action
, GtkWidget
*widget
)
1686 icon
= pinboard_selected_icon();
1689 show_item_help(icon
->path
, &icon
->item
);
1691 delayed_error(PROJECT
,
1692 _("You must first select a single pinned icon to get "
1696 static void pin_remove(gpointer data
, guint action
, GtkWidget
*widget
)
1698 pinboard_unpin_selection();
1701 static void menu_closed(GtkWidget
*widget
)
1705 if (tmp_icon_selected
)
1707 pinboard_clear_selection();
1708 tmp_icon_selected
= FALSE
;
1712 /* Show where this item is stored */
1713 static void show_location(gpointer data
, guint action
, GtkWidget
*widget
)
1717 icon
= pinboard_selected_icon();
1720 open_to_show(icon
->path
);
1723 delayed_error(PROJECT
,
1724 _("Select a single item, then use this to find out "
1725 "where it is in the filesystem."));
1729 static void rename_cb(Icon
*icon
)
1736 static void edit_icon(gpointer data
, guint action
, GtkWidget
*widget
)
1740 icon
= pinboard_selected_icon();
1743 show_rename_box(icon
->widget
, icon
, rename_cb
);
1746 delayed_error(PROJECT
,
1747 _("First, select a single item to edit"));