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 desktop background */
32 #include <gtk/gtkinvisible.h>
43 #include "gui_support.h"
50 /* The number of pixels between the bottom of the image and the top
55 /* The size of the border around the icon which is used when winking */
59 #define GRID_STEP_FINE 2
60 #define GRID_STEP_MED 16
61 #define GRID_STEP_COARSE 32
63 static Icon
*current_wink_icon
= NULL
;
64 static gint wink_timeout
;
66 /* Used for the text colours (only) in the icons */
67 static GdkColor text_fg_col
, text_bg_col
;
69 /* Style that all the icons should use. NULL => regenerate from text_fg/bg */
70 static GtkStyle
*pinicon_style
= NULL
;
72 Pinboard
*current_pinboard
= NULL
;
73 static gint loading_pinboard
= 0; /* Non-zero => loading */
75 static GdkColor mask_solid
= {1, 1, 1, 1};
76 static GdkColor mask_transp
= {0, 0, 0, 0};
77 static GdkGC
*mask_gc
= NULL
;
79 /* Proxy window for DnD and clicks on the desktop */
80 static GtkWidget
*proxy_invisible
;
82 /* The window (owned by the wm) which root clicks are forwarded to.
83 * NULL if wm does not support forwarding clicks.
85 static GdkWindow
*click_proxy_gdk_window
= NULL
;
86 static GdkAtom win_button_proxy
; /* _WIN_DESKTOP_BUTTON_PROXY */
88 static gboolean pinboard_drag_in_progress
= FALSE
;
90 /* Used when dragging icons around... */
91 static gboolean pinboard_modified
= FALSE
;
99 TextBgType o_text_bg
= TEXT_BG_SOLID
;
100 gboolean o_clamp_icons
= TRUE
;
101 static int o_grid_step
= GRID_STEP_COARSE
;
102 static int old_x
, old_y
; /* For dragging (mouse start) */
103 static int icon_old_x
, icon_old_y
; /* For dragging (icon start) */
105 /* Static prototypes */
106 static void set_size_and_shape(Icon
*icon
, int *rwidth
, int *rheight
);
107 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, Icon
*icon
);
108 static void mask_wink_border(Icon
*icon
, GdkColor
*alpha
);
109 static gint
end_wink(gpointer data
);
110 static gboolean
button_release_event(GtkWidget
*widget
,
111 GdkEventButton
*event
,
113 static gboolean
root_property_event(GtkWidget
*widget
,
114 GdkEventProperty
*event
,
116 static gboolean
root_button_press(GtkWidget
*widget
,
117 GdkEventButton
*event
,
119 static gboolean
enter_notify(GtkWidget
*widget
,
120 GdkEventCrossing
*event
,
122 static gboolean
button_press_event(GtkWidget
*widget
,
123 GdkEventButton
*event
,
125 static gint
icon_motion_notify(GtkWidget
*widget
,
126 GdkEventMotion
*event
,
128 static char *pin_from_file(guchar
*line
);
129 static gboolean
add_root_handlers(void);
130 static GdkFilterReturn
proxy_filter(GdkXEvent
*xevent
,
133 static void snap_to_grid(int *x
, int *y
);
134 static void offset_from_centre(Icon
*icon
,
135 int width
, int height
,
137 static void offset_to_centre(Icon
*icon
,
138 int width
, int height
,
140 static gboolean
drag_motion(GtkWidget
*widget
,
141 GdkDragContext
*context
,
146 static void drag_set_pinicon_dest(Icon
*icon
);
147 static void drag_leave(GtkWidget
*widget
,
148 GdkDragContext
*context
,
151 static void forward_root_clicks(void);
152 static gboolean
bg_drag_motion(GtkWidget
*widget
,
153 GdkDragContext
*context
,
158 static void drag_end(GtkWidget
*widget
,
159 GdkDragContext
*context
,
161 static void reshape_all(void);
162 static void pinboard_check_options(void);
164 /****************************************************************
165 * EXTERNAL INTERFACE *
166 ****************************************************************/
168 void pinboard_init(void)
170 option_add_string("pinboard_fg_colour", "#000", NULL
);
171 option_add_string("pinboard_bg_colour", "#ddd", NULL
);
173 option_add_int("pinboard_text_bg", TEXT_BG_SOLID
, NULL
);
174 option_add_int("pinboard_clamp_icons", 1, NULL
);
175 option_add_int("pinboard_grid_step", GRID_STEP_COARSE
, NULL
);
176 option_add_notify(pinboard_check_options
);
178 gdk_color_parse(option_get_static_string("pinboard_fg_colour"),
180 gdk_color_parse(option_get_static_string("pinboard_bg_colour"),
185 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
186 * and make it the current pinboard.
187 * Any existing pinned items are removed. You must call this
188 * at least once before using the pinboard. NULL disables the
191 void pinboard_activate(guchar
*name
)
193 Pinboard
*old_board
= current_pinboard
;
194 guchar
*path
, *slash
;
196 /* Treat an empty name the same as NULL */
208 if (number_of_windows
< 1 && gtk_main_level() > 0)
213 if (!add_root_handlers())
215 delayed_error(_("Another application is already "
216 "managing the pinboard!"));
222 slash
= strchr(name
, '/');
225 if (access(name
, F_OK
))
226 path
= NULL
; /* File does not (yet) exist */
228 path
= g_strdup(name
);
234 leaf
= g_strconcat("pb_", name
, NULL
);
235 path
= choices_find_path_load(leaf
, PROJECT
);
239 current_pinboard
= g_new(Pinboard
, 1);
240 current_pinboard
->name
= g_strdup(name
);
241 current_pinboard
->icons
= NULL
;
246 parse_file(path
, pin_from_file
);
250 pinboard_pin(home_dir
, "Home", 4, 4, TRUE
);
254 /* Add a new icon to the background.
255 * 'path' should be an absolute pathname.
256 * 'x' and 'y' are the coordinates of the point in the middle of the text
257 * if 'corner' is FALSE, and as the top-left corner of where the icon
258 * image should be if it is TRUE.
259 * 'name' is the name to use. If NULL then the leafname of path is used.
261 void pinboard_pin(guchar
*path
, guchar
*name
, int x
, int y
, gboolean corner
)
266 g_return_if_fail(path
!= NULL
);
267 g_return_if_fail(current_pinboard
!= NULL
);
269 icon
= g_new(Icon
, 1);
271 icon
->selected
= FALSE
;
272 icon
->src_path
= g_strdup(path
);
273 icon
->path
= icon_convert_path(path
);
279 icon_hash_path(icon
);
281 diritem_stat(icon
->path
, &icon
->item
, FALSE
);
285 name
= strrchr(icon
->path
, '/');
292 icon
->item
.leafname
= g_strdup(name
);
294 icon
->win
= gtk_window_new(GTK_WINDOW_DIALOG
);
295 gtk_window_set_wmclass(GTK_WINDOW(icon
->win
), "ROX-Pinboard", PROJECT
);
297 icon
->widget
= gtk_drawing_area_new();
298 gtk_widget_set_name(icon
->widget
, "pinboard-icon");
300 icon
->layout
= gtk_widget_create_pango_layout(icon
->widget
, NULL
);
301 pango_layout_set_width(icon
->layout
, 140 * PANGO_SCALE
);
303 gtk_container_add(GTK_CONTAINER(icon
->win
), icon
->widget
);
304 drag_set_pinicon_dest(icon
);
305 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "drag_data_get",
306 GTK_SIGNAL_FUNC(drag_data_get
), NULL
);
308 gtk_widget_realize(icon
->win
);
309 gtk_widget_realize(icon
->widget
);
311 set_size_and_shape(icon
, &width
, &height
);
314 /* Convert from icon-corner coordinates to center coordinates */
315 MaskedPixmap
*image
= icon
->item
.image
;
317 offset_to_centre(icon
, image
->width
>> 1, height
, &x
, &y
);
319 snap_to_grid(&x
, &y
);
320 offset_from_centre(icon
, width
, height
, &x
, &y
);
321 gtk_widget_set_uposition(icon
->win
, x
, y
);
322 /* Set the correct position in the icon */
323 offset_to_centre(icon
, width
, height
, &x
, &y
);
327 make_panel_window(icon
->win
->window
);
329 gtk_widget_add_events(icon
->widget
,
330 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
331 GDK_BUTTON1_MOTION_MASK
| GDK_ENTER_NOTIFY_MASK
|
332 GDK_BUTTON2_MOTION_MASK
| GDK_BUTTON3_MOTION_MASK
);
333 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "enter-notify-event",
334 GTK_SIGNAL_FUNC(enter_notify
), icon
);
335 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "button-press-event",
336 GTK_SIGNAL_FUNC(button_press_event
), icon
);
337 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "button-release-event",
338 GTK_SIGNAL_FUNC(button_release_event
), icon
);
339 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "motion-notify-event",
340 GTK_SIGNAL_FUNC(icon_motion_notify
), icon
);
341 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "expose-event",
342 GTK_SIGNAL_FUNC(draw_icon
), icon
);
343 gtk_signal_connect_object(GTK_OBJECT(icon
->win
), "destroy",
344 GTK_SIGNAL_FUNC(icon_destroyed
), (gpointer
) icon
);
346 current_pinboard
->icons
= g_list_prepend(current_pinboard
->icons
,
349 gtk_widget_show_all(icon
->win
);
350 gdk_window_lower(icon
->win
->window
);
352 if (!loading_pinboard
)
356 /* Remove an icon from the pinboard */
357 void pinboard_unpin(Icon
*icon
)
359 g_return_if_fail(icon
!= NULL
);
361 gtk_widget_destroy(icon
->win
);
365 /* Put a border around the icon, briefly.
366 * If icon is NULL then cancel any existing wink.
367 * The icon will automatically unhighlight unless timeout is FALSE,
368 * in which case you must call this function again (with NULL or another
369 * icon) to remove the highlight.
371 void pinboard_wink_item(Icon
*icon
, gboolean timeout
)
373 if (current_wink_icon
== icon
)
376 if (current_wink_icon
)
378 mask_wink_border(current_wink_icon
, &mask_transp
);
379 if (wink_timeout
!= -1)
380 gtk_timeout_remove(wink_timeout
);
383 current_wink_icon
= icon
;
385 if (current_wink_icon
)
387 mask_wink_border(current_wink_icon
, &mask_solid
);
389 wink_timeout
= gtk_timeout_add(300, end_wink
, NULL
);
395 /* Remove everything on the current pinboard and disables the pinboard.
396 * Does not change any files. Does not change number_of_windows.
398 void pinboard_clear(void)
402 g_return_if_fail(current_pinboard
!= NULL
);
404 next
= current_pinboard
->icons
;
407 Icon
*icon
= (Icon
*) next
->data
;
411 gtk_widget_destroy(icon
->win
);
414 g_free(current_pinboard
->name
);
415 g_free(current_pinboard
);
416 current_pinboard
= NULL
;
418 release_xdnd_proxy(GDK_ROOT_WINDOW());
419 gdk_window_remove_filter(GDK_ROOT_PARENT(), proxy_filter
, NULL
);
420 gdk_window_set_user_data(GDK_ROOT_PARENT(), NULL
);
423 /* Icon's size, shape or appearance has changed - update the display */
424 void pinboard_reshape_icon(Icon
*icon
)
426 int x
= icon
->x
, y
= icon
->y
;
429 set_size_and_shape(icon
, &width
, &height
);
430 gdk_window_resize(icon
->win
->window
, width
, height
);
431 offset_from_centre(icon
, width
, height
, &x
, &y
);
432 gtk_widget_set_uposition(icon
->win
, x
, y
);
433 gtk_widget_queue_draw(icon
->win
);
437 /****************************************************************
438 * INTERNAL FUNCTIONS *
439 ****************************************************************/
441 static void pinboard_check_options(void)
443 int old_text_bg
= o_text_bg
;
446 o_text_bg
= option_get_int("pinboard_text_bg");
447 o_grid_step
= option_get_int("pinboard_grid_step");
448 o_clamp_icons
= option_get_int("pinboard_clamp_icons");
450 gdk_color_parse(option_get_static_string("pinboard_fg_colour"), &n_fg
);
451 gdk_color_parse(option_get_static_string("pinboard_bg_colour"), &n_bg
);
453 if (o_text_bg
!= old_text_bg
||
454 gdk_color_equal(&n_fg
, &text_fg_col
) == 0 ||
455 gdk_color_equal(&n_bg
, &text_bg_col
) == 0)
457 memcpy(&text_fg_col
, &n_fg
, sizeof(GdkColor
));
458 memcpy(&text_bg_col
, &n_bg
, sizeof(GdkColor
));
462 gtk_style_unref(pinicon_style
);
463 pinicon_style
= NULL
;
466 if (current_pinboard
)
471 static gint
end_wink(gpointer data
)
473 pinboard_wink_item(NULL
, FALSE
);
477 /* Make the wink border solid or transparent */
478 static void mask_wink_border(Icon
*icon
, GdkColor
*alpha
)
480 if (!current_pinboard
)
483 gdk_gc_set_foreground(mask_gc
, alpha
);
484 gdk_draw_rectangle(icon
->mask
, mask_gc
, FALSE
,
485 0, 0, icon
->width
- 1, icon
->height
- 1);
486 gdk_draw_rectangle(icon
->mask
, mask_gc
, FALSE
,
487 1, 1, icon
->width
- 3, icon
->height
- 3);
489 gtk_widget_shape_combine_mask(icon
->win
, icon
->mask
, 0, 0);
491 gtk_widget_draw(icon
->widget
, NULL
);
494 #define TEXT_AT(dx, dy) \
495 gdk_draw_string(icon->mask, font, mask_gc, \
496 text_x + dx, y + dy, \
499 /* Updates the name_width and layout fields, and resizes and masks the window.
500 * Also sets the style to pinicon_style, generating it if needed.
501 * Returns the new width and height.
503 static void set_size_and_shape(Icon
*icon
, int *rwidth
, int *rheight
)
507 MaskedPixmap
*image
= icon
->item
.image
;
508 int iwidth
= image
->width
;
509 int iheight
= image
->height
;
510 DirItem
*item
= &icon
->item
;
518 pinicon_style
= gtk_style_copy(icon
->widget
->style
);
519 memcpy(&pinicon_style
->fg
[GTK_STATE_NORMAL
],
520 &text_fg_col
, sizeof(GdkColor
));
521 memcpy(&pinicon_style
->bg
[GTK_STATE_NORMAL
],
522 &text_bg_col
, sizeof(GdkColor
));
524 gtk_widget_set_style(icon
->widget
, pinicon_style
);
527 font
= pinicon_style
->font
;
528 font_height
= font
->ascent
+ font
->descent
;
529 item
->name_width
= gdk_string_measure(font
, item
->leafname
);
532 PangoRectangle logical
;
533 pango_layout_set_text(icon
->layout
, icon
->item
.leafname
, -1);
534 pango_layout_get_pixel_extents(icon
->layout
, NULL
, &logical
);
536 item
->name_width
= logical
.width
- logical
.x
;
537 font_height
= logical
.height
- logical
.y
;
541 width
= MAX(iwidth
, item
->name_width
+ 2) + 2 * WINK_FRAME
;
542 height
= iheight
+ GAP
+ (font_height
+ 2) + 2 * WINK_FRAME
;
543 gtk_widget_set_usize(icon
->win
, width
, height
);
545 icon
->height
= height
;
548 gdk_pixmap_unref(icon
->mask
);
549 icon
->mask
= gdk_pixmap_new(icon
->win
->window
, width
, height
, 1);
551 mask_gc
= gdk_gc_new(icon
->mask
);
553 /* Clear the mask to transparent */
554 gdk_gc_set_foreground(mask_gc
, &mask_transp
);
555 gdk_draw_rectangle(icon
->mask
, mask_gc
, TRUE
, 0, 0, width
, height
);
557 gdk_gc_set_foreground(mask_gc
, &mask_solid
);
558 /* Make the icon area solid */
561 gdk_draw_pixmap(icon
->mask
, mask_gc
, image
->mask
,
563 (width
- iwidth
) >> 1,
570 gdk_draw_rectangle(icon
->mask
, mask_gc
, TRUE
,
571 (width
- iwidth
) >> 1,
577 gdk_gc_set_function(mask_gc
, GDK_OR
);
578 if (item
->flags
& ITEM_FLAG_SYMLINK
)
580 gdk_draw_pixmap(icon
->mask
, mask_gc
, im_symlink
->mask
,
581 0, 0, /* Source x,y */
582 (width
- iwidth
) >> 1, /* Dest x */
583 WINK_FRAME
, /* Dest y */
586 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
588 /* Note: Both mount state pixmaps must have the same mask */
589 gdk_draw_pixmap(icon
->mask
, mask_gc
, im_mounted
->mask
,
590 0, 0, /* Source x,y */
591 (width
- iwidth
) >> 1, /* Dest x */
592 WINK_FRAME
, /* Dest y */
595 gdk_gc_set_function(mask_gc
, GDK_COPY
);
597 /* Mask off an area for the text (from o_text_bg) */
599 text_x
= (width
- item
->name_width
) >> 1;
600 text_y
= WINK_FRAME
+ iheight
+ GAP
+ 1;
603 if (o_text_bg
== TEXT_BG_SOLID
)
606 gdk_draw_rectangle(icon
->mask
, mask_gc
, TRUE
,
607 (width
- (item
->name_width
+ 2)) >> 1,
608 WINK_FRAME
+ iheight
+ GAP
,
609 item
->name_width
+ 2, font_height
+ 2);
614 int y
= text_y
+ font
->ascent
;
618 if (o_text_bg
== TEXT_BG_OUTLINE
)
632 gtk_widget_shape_combine_mask(icon
->win
, icon
->mask
, 0, 0);
638 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, Icon
*icon
)
641 GdkFont
*font
= icon
->widget
->style
->font
;
644 DirItem
*item
= &icon
->item
;
645 MaskedPixmap
*image
= item
->image
;
646 int iwidth
= image
->width
;
647 int iheight
= image
->height
;
649 GdkGC
*gc
= widget
->style
->black_gc
;
650 GtkStateType state
= icon
->selected
? GTK_STATE_SELECTED
653 image_x
= (icon
->width
- iwidth
) >> 1;
655 /* TODO: If the shape extension is missing we might need to set
656 * the clip mask here...
658 gdk_draw_pixmap(widget
->window
, gc
,
666 if (item
->flags
& ITEM_FLAG_SYMLINK
)
668 gdk_gc_set_clip_origin(gc
, image_x
, WINK_FRAME
);
669 gdk_gc_set_clip_mask(gc
, im_symlink
->mask
);
670 gdk_draw_pixmap(widget
->window
, gc
,
672 0, 0, /* Source x,y */
673 image_x
, WINK_FRAME
, /* Dest x,y */
675 gdk_gc_set_clip_mask(gc
, NULL
);
676 gdk_gc_set_clip_origin(gc
, 0, 0);
678 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
680 MaskedPixmap
*mp
= item
->flags
& ITEM_FLAG_MOUNTED
684 gdk_gc_set_clip_origin(gc
, image_x
, WINK_FRAME
);
685 gdk_gc_set_clip_mask(gc
, mp
->mask
);
686 gdk_draw_pixmap(widget
->window
, gc
,
688 0, 0, /* Source x,y */
689 image_x
, WINK_FRAME
, /* Dest x,y */
691 gdk_gc_set_clip_mask(gc
, NULL
);
692 gdk_gc_set_clip_origin(gc
, 0, 0);
695 text_x
= (icon
->width
- item
->name_width
) >> 1;
696 text_y
= WINK_FRAME
+ iheight
+ GAP
+ 1;
698 if (o_text_bg
!= TEXT_BG_NONE
)
701 PangoRectangle logical
;
704 pango_layout_get_pixel_extents(icon
->layout
, NULL
, &logical
);
705 font_height
= logical
.height
- logical
.y
;
707 int font_height
= font
->ascent
+ font
->descent
;
710 gtk_paint_flat_box(widget
->style
, widget
->window
,
713 NULL
, widget
, "text",
716 item
->name_width
+ 2,
721 gtk_paint_layout(widget
->style
, widget
->window
,
723 FALSE
, NULL
, widget
, "text",
728 gtk_paint_string(widget
->style
, widget
->window
,
730 NULL
, widget
, "text",
732 text_y
+ font
->ascent
,
736 if (current_wink_icon
== icon
)
738 gdk_draw_rectangle(icon
->widget
->window
,
739 icon
->widget
->style
->white_gc
,
741 0, 0, icon
->width
- 1, icon
->height
- 1);
742 gdk_draw_rectangle(icon
->widget
->window
,
743 icon
->widget
->style
->black_gc
,
745 1, 1, icon
->width
- 3, icon
->height
- 3);
751 static gboolean
root_property_event(GtkWidget
*widget
,
752 GdkEventProperty
*event
,
755 if (event
->atom
== win_button_proxy
&&
756 event
->state
== GDK_PROPERTY_NEW_VALUE
)
758 /* Setup forwarding on the new proxy window, if possible */
759 forward_root_clicks();
765 static gboolean
root_button_press(GtkWidget
*widget
,
766 GdkEventButton
*event
,
771 action
= bind_lookup_bev(BIND_PINBOARD
, event
);
775 case ACT_CLEAR_SELECTION
:
776 icon_select_only(NULL
);
780 icon_show_menu(event
, NULL
, NULL
);
785 g_warning("Unsupported action : %d\n", action
);
792 static gboolean
enter_notify(GtkWidget
*widget
,
793 GdkEventCrossing
*event
,
796 icon_may_update(icon
);
801 static void perform_action(Icon
*icon
, GdkEventButton
*event
)
805 action
= bind_lookup_bev(BIND_PINBOARD_ICON
, event
);
811 pinboard_wink_item(icon
, TRUE
);
812 run_diritem(icon
->path
, &icon
->item
, NULL
, NULL
, FALSE
);
816 pinboard_wink_item(icon
, TRUE
);
817 run_diritem(icon
->path
, &icon
->item
, NULL
, NULL
, TRUE
);
821 icon_show_menu(event
, icon
, NULL
);
824 old_x
= event
->x_root
;
825 old_y
= event
->y_root
;
826 icon_old_x
= icon
->x
;
827 icon_old_y
= icon
->y
;
828 dnd_motion_start(MOTION_REPOSITION
);
830 case ACT_PRIME_AND_SELECT
:
832 icon_select_only(icon
);
833 dnd_motion_start(MOTION_READY_FOR_DND
);
835 case ACT_PRIME_AND_TOGGLE
:
836 icon_set_selected(icon
, !icon
->selected
);
837 dnd_motion_start(MOTION_READY_FOR_DND
);
839 case ACT_PRIME_FOR_DND
:
840 dnd_motion_start(MOTION_READY_FOR_DND
);
842 case ACT_TOGGLE_SELECTED
:
843 icon_set_selected(icon
, !icon
->selected
);
845 case ACT_SELECT_EXCL
:
846 icon_select_only(icon
);
851 g_warning("Unsupported action : %d\n", action
);
856 static gboolean
button_release_event(GtkWidget
*widget
,
857 GdkEventButton
*event
,
860 if (pinboard_modified
)
863 if (dnd_motion_release(event
))
866 perform_action(icon
, event
);
871 static gboolean
button_press_event(GtkWidget
*widget
,
872 GdkEventButton
*event
,
875 if (dnd_motion_press(widget
, event
))
876 perform_action(icon
, event
);
881 /* Return a text/uri-list of all the icons in the list.
882 * TODO: Use code in icon.c instead.
884 static guchar
*create_uri_list(GList
*list
)
890 tmp
= g_string_new(NULL
);
891 leader
= g_strdup_printf("file://%s", our_host_name_for_dnd());
893 for (; list
; list
= list
->next
)
895 Icon
*icon
= (Icon
*) list
->data
;
897 g_string_append(tmp
, leader
);
898 g_string_append(tmp
, icon
->path
);
899 g_string_append(tmp
, "\r\n");
904 g_string_free(tmp
, FALSE
);
909 static void start_drag(Icon
*icon
, GdkEventMotion
*event
)
911 GtkWidget
*widget
= icon
->widget
;
915 tmp_icon_selected
= TRUE
;
916 icon_select_only(icon
);
919 g_return_if_fail(icon_selection
!= NULL
);
921 pinboard_drag_in_progress
= TRUE
;
923 if (icon_selection
->next
== NULL
)
924 drag_one_item(widget
, event
, icon
->path
, &icon
->item
);
929 uri_list
= create_uri_list(icon_selection
);
930 drag_selection(widget
, event
, uri_list
);
935 /* An icon is being dragged around... */
936 static gint
icon_motion_notify(GtkWidget
*widget
,
937 GdkEventMotion
*event
,
944 if (motion_state
== MOTION_READY_FOR_DND
)
946 if (dnd_motion_moved(event
))
947 start_drag(icon
, event
);
950 else if (motion_state
!= MOTION_REPOSITION
)
953 /* How far the pointer has moved since the drag started */
954 dx
= event
->x_root
- old_x
;
955 dy
= event
->y_root
- old_y
;
960 snap_to_grid(&x
, &y
);
962 if (icon
->x
== x
&& icon
->y
== y
)
967 gdk_window_get_size(icon
->win
->window
, &width
, &height
);
968 offset_from_centre(icon
, width
, height
, &x
, &y
);
970 gdk_window_move(icon
->win
->window
, x
, y
);
972 /* Store the fixed position for the center of the icon */
973 offset_to_centre(icon
, width
, height
, &x
, &y
);
977 pinboard_modified
= TRUE
;
982 /* Called for each line in the pinboard file while loading a new board */
983 static char *pin_from_file(guchar
*line
)
992 end
= strchr(line
+ 1, '>');
994 return _("Missing '>' in icon label");
996 leaf
= g_strndup(line
+ 1, end
- line
- 1);
1000 while (isspace(*line
))
1003 return _("Missing ',' after icon label");
1007 if (sscanf(line
, " %d , %d , %n", &x
, &y
, &n
) < 2)
1008 return NULL
; /* Ignore format errors */
1010 pinboard_pin(line
+ n
, leaf
, x
, y
, FALSE
);
1017 /* Make sure that clicks and drops on the root window come to us...
1018 * False if an error occurred (ie, someone else is using it).
1020 static gboolean
add_root_handlers(void)
1024 if (!proxy_invisible
)
1026 win_button_proxy
= gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY",
1028 proxy_invisible
= gtk_invisible_new();
1029 gtk_widget_show(proxy_invisible
);
1031 gdk_window_add_filter(proxy_invisible->window,
1032 proxy_filter, NULL);
1035 gtk_signal_connect(GTK_OBJECT(proxy_invisible
),
1036 "property_notify_event",
1037 GTK_SIGNAL_FUNC(root_property_event
), NULL
);
1038 gtk_signal_connect(GTK_OBJECT(proxy_invisible
),
1039 "button_press_event",
1040 GTK_SIGNAL_FUNC(root_button_press
), NULL
);
1042 /* Drag and drop handlers */
1043 drag_set_pinboard_dest(proxy_invisible
);
1044 gtk_signal_connect(GTK_OBJECT(proxy_invisible
), "drag_motion",
1045 GTK_SIGNAL_FUNC(bg_drag_motion
),
1049 root
= gdk_window_lookup(GDK_ROOT_WINDOW());
1051 root
= gdk_window_foreign_new(GDK_ROOT_WINDOW());
1053 if (!setup_xdnd_proxy(GDK_ROOT_WINDOW(), proxy_invisible
->window
))
1056 /* Forward events from the root window to our proxy window */
1057 gdk_window_add_filter(GDK_ROOT_PARENT(), proxy_filter
, NULL
);
1058 gdk_window_set_user_data(GDK_ROOT_PARENT(), proxy_invisible
);
1059 gdk_window_set_events(GDK_ROOT_PARENT(),
1060 gdk_window_get_events(GDK_ROOT_PARENT()) |
1061 GDK_PROPERTY_CHANGE_MASK
);
1063 forward_root_clicks();
1068 /* See if the window manager is offering to forward root window clicks.
1069 * If so, grab them. Otherwise, do nothing.
1070 * Call this whenever the _WIN_DESKTOP_BUTTON_PROXY property changes.
1072 static void forward_root_clicks(void)
1074 click_proxy_gdk_window
= find_click_proxy_window();
1075 if (!click_proxy_gdk_window
)
1078 /* Events on the wm's proxy are dealt with by our proxy widget */
1079 gdk_window_set_user_data(click_proxy_gdk_window
, proxy_invisible
);
1080 gdk_window_add_filter(click_proxy_gdk_window
, proxy_filter
, NULL
);
1082 /* The proxy window for clicks sends us button press events with
1083 * SubstructureNotifyMask. We need StructureNotifyMask to receive
1084 * DestroyNotify events, too.
1086 XSelectInput(GDK_DISPLAY(),
1087 GDK_WINDOW_XWINDOW(click_proxy_gdk_window
),
1088 SubstructureNotifyMask
| StructureNotifyMask
);
1091 /* Write the current state of the pinboard to the current pinboard file */
1092 void pinboard_save(void)
1094 guchar
*save
= NULL
;
1095 GString
*tmp
= NULL
;
1098 guchar
*save_new
= NULL
;
1100 g_return_if_fail(current_pinboard
!= NULL
);
1102 pinboard_modified
= FALSE
;
1104 if (strchr(current_pinboard
->name
, '/'))
1105 save
= g_strdup(current_pinboard
->name
);
1110 leaf
= g_strconcat("pb_", current_pinboard
->name
, NULL
);
1111 save
= choices_find_path_save(leaf
, PROJECT
, TRUE
);
1118 save_new
= g_strconcat(save
, ".new", NULL
);
1119 file
= fopen(save_new
, "wb");
1123 tmp
= g_string_new(NULL
);
1124 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
1126 Icon
*icon
= (Icon
*) next
->data
;
1128 g_string_sprintf(tmp
, "<%s>, %d, %d, %s\n",
1129 icon
->item
.leafname
, icon
->x
, icon
->y
, icon
->src_path
);
1130 if (fwrite(tmp
->str
, 1, tmp
->len
, file
) < tmp
->len
)
1142 if (rename(save_new
, save
))
1147 delayed_error(_("Could not save pinboard: %s"), g_strerror(errno
));
1152 g_string_free(tmp
, TRUE
);
1159 * Filter that translates proxied events from virtual root windows into normal
1160 * Gdk events for the proxy_invisible widget. Stolen from gmc.
1162 * Also gets events from the root window.
1164 static GdkFilterReturn
proxy_filter(GdkXEvent
*xevent
,
1169 GdkWindow
*proxy
= proxy_invisible
->window
;
1173 switch (xev
->type
) {
1176 /* Translate button events into events that come from
1177 * the proxy window, so that we can catch them as a
1178 * signal from the invisible widget.
1180 if (xev
->type
== ButtonPress
)
1181 event
->button
.type
= GDK_BUTTON_PRESS
;
1183 event
->button
.type
= GDK_BUTTON_RELEASE
;
1185 gdk_window_ref(proxy
);
1187 event
->button
.window
= proxy
;
1188 event
->button
.send_event
= xev
->xbutton
.send_event
;
1189 event
->button
.time
= xev
->xbutton
.time
;
1190 event
->button
.x_root
= xev
->xbutton
.x_root
;
1191 event
->button
.y_root
= xev
->xbutton
.y_root
;
1192 event
->button
.x
= xev
->xbutton
.x
;
1193 event
->button
.y
= xev
->xbutton
.y
;
1194 event
->button
.state
= xev
->xbutton
.state
;
1195 event
->button
.button
= xev
->xbutton
.button
;
1197 event
->button
.axes
= NULL
;
1200 return GDK_FILTER_TRANSLATE
;
1203 /* XXX: I have no idea why this helps, but it does! */
1204 /* The proxy window was destroyed (i.e. the window
1205 * manager died), so we have to cope with it
1207 if (((GdkEventAny
*) event
)->window
== proxy
)
1208 gdk_window_destroy_notify(proxy
);
1210 return GDK_FILTER_REMOVE
;
1216 return GDK_FILTER_CONTINUE
;
1219 static void snap_to_grid(int *x
, int *y
)
1221 *x
= ((*x
+ o_grid_step
/ 2) / o_grid_step
) * o_grid_step
;
1222 *y
= ((*y
+ o_grid_step
/ 2) / o_grid_step
) * o_grid_step
;
1225 /* Convert (x,y) from a centre point to a window position */
1226 static void offset_from_centre(Icon
*icon
,
1227 int width
, int height
,
1233 *y
+= (icon
->widget
->style
->font
->descent
>> 1);
1235 *x
= CLAMP(*x
, 0, screen_width
- (o_clamp_icons
? width
: 0));
1236 *y
= CLAMP(*y
, 0, screen_height
- (o_clamp_icons
? height
: 0));
1239 /* Convert (x,y) from a window position to a centre point */
1240 static void offset_to_centre(Icon
*icon
,
1241 int width
, int height
,
1248 *y
-= (icon
->widget
->style
->font
->descent
>> 1);
1252 /* Same as drag_set_dest(), but for pinboard icons */
1253 static void drag_set_pinicon_dest(Icon
*icon
)
1255 GtkObject
*obj
= GTK_OBJECT(icon
->widget
);
1257 make_drop_target(icon
->widget
, 0);
1259 gtk_signal_connect(obj
, "drag_motion",
1260 GTK_SIGNAL_FUNC(drag_motion
), icon
);
1261 gtk_signal_connect(obj
, "drag_leave",
1262 GTK_SIGNAL_FUNC(drag_leave
), icon
);
1263 gtk_signal_connect(obj
, "drag_end",
1264 GTK_SIGNAL_FUNC(drag_end
), icon
);
1267 /* Called during the drag when the mouse is in a widget registered
1268 * as a drop target. Returns TRUE if we can accept the drop.
1270 static gboolean
drag_motion(GtkWidget
*widget
,
1271 GdkDragContext
*context
,
1277 GdkDragAction action
= context
->suggested_action
;
1279 DirItem
*item
= &icon
->item
;
1281 if (gtk_drag_get_source_widget(context
) == widget
)
1282 goto out
; /* Can't drag something to itself! */
1285 goto out
; /* Can't drag a selection to itself */
1287 type
= dnd_motion_item(context
, &item
);
1292 /* We actually must pretend to accept the drop, even if the
1293 * directory isn't writeable, so that the spring-opening
1297 /* Don't allow drops to non-writeable directories */
1298 if (option_get_int("dnd_spring_open") == FALSE
&&
1299 type
== drop_dest_dir
&&
1300 access(icon
->path
, W_OK
) != 0)
1305 g_dataset_set_data(context
, "drop_dest_type", type
);
1308 gdk_drag_status(context
, action
, time
);
1309 g_dataset_set_data_full(context
, "drop_dest_path",
1310 g_strdup(icon
->path
), g_free
);
1311 if (type
== drop_dest_dir
)
1312 dnd_spring_load(context
, NULL
);
1314 pinboard_wink_item(icon
, FALSE
);
1317 return type
!= NULL
;
1320 static void drag_leave(GtkWidget
*widget
,
1321 GdkDragContext
*context
,
1325 pinboard_wink_item(NULL
, FALSE
);
1329 static gboolean
bg_drag_motion(GtkWidget
*widget
,
1330 GdkDragContext
*context
,
1336 /* Dragging from the pinboard to the pinboard is not allowed */
1337 if (pinboard_drag_in_progress
)
1340 gdk_drag_status(context
, context
->suggested_action
, time
);
1344 static void drag_end(GtkWidget
*widget
,
1345 GdkDragContext
*context
,
1348 pinboard_drag_in_progress
= FALSE
;
1349 if (tmp_icon_selected
)
1351 icon_select_only(NULL
);
1352 tmp_icon_selected
= FALSE
;
1356 /* Something which affects all the icons has changed - reshape
1357 * and redraw all of them.
1359 static void reshape_all(void)
1363 g_return_if_fail(current_pinboard
!= NULL
);
1365 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
1367 Icon
*icon
= (Icon
*) next
->data
;
1368 pinboard_reshape_icon(icon
);