4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, 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 */
33 #include <gtk/gtkinvisible.h>
45 #include "gui_support.h"
52 /* The number of pixels between the bottom of the image and the top
57 /* The size of the border around the icon which is used when winking */
61 #define GRID_STEP_FINE 2
62 #define GRID_STEP_MED 16
63 #define GRID_STEP_COARSE 32
65 static Icon
*current_wink_icon
= NULL
;
66 static gint wink_timeout
;
68 /* Used for the text colours (only) in the icons */
69 static GdkColor text_fg_col
, text_bg_col
;
71 /* Style that all the icons should use. NULL => regenerate from text_fg/bg */
72 static GtkStyle
*pinicon_style
= NULL
;
74 Pinboard
*current_pinboard
= NULL
;
75 static gint loading_pinboard
= 0; /* Non-zero => loading */
77 static GdkColor mask_solid
= {1, 1, 1, 1};
78 static GdkColor mask_transp
= {0, 0, 0, 0};
79 static GdkGC
*mask_gc
= NULL
;
81 /* Proxy window for DnD and clicks on the desktop */
82 static GtkWidget
*proxy_invisible
;
84 /* The window (owned by the wm) which root clicks are forwarded to.
85 * NULL if wm does not support forwarding clicks.
87 static GdkWindow
*click_proxy_gdk_window
= NULL
;
88 static GdkAtom win_button_proxy
; /* _WIN_DESKTOP_BUTTON_PROXY */
90 /* The Icon that was used to start the current drag, if any */
91 Icon
*pinboard_drag_in_progress
= NULL
;
93 /* Used when dragging icons around... */
94 static gboolean pinboard_modified
= FALSE
;
102 TextBgType o_text_bg
= TEXT_BG_SOLID
;
103 gboolean o_clamp_icons
= TRUE
;
104 static int o_grid_step
= GRID_STEP_COARSE
;
105 static int old_x
, old_y
; /* For dragging (mouse start) */
106 static int icon_old_x
, icon_old_y
; /* For dragging (icon start) */
108 /* Static prototypes */
109 static void set_size_and_shape(Icon
*icon
, int *rwidth
, int *rheight
);
110 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, Icon
*icon
);
111 static void mask_wink_border(Icon
*icon
, GdkColor
*alpha
);
112 static gint
end_wink(gpointer data
);
113 static gboolean
button_release_event(GtkWidget
*widget
,
114 GdkEventButton
*event
,
116 static gboolean
root_property_event(GtkWidget
*widget
,
117 GdkEventProperty
*event
,
119 static gboolean
root_button_press(GtkWidget
*widget
,
120 GdkEventButton
*event
,
122 static gboolean
enter_notify(GtkWidget
*widget
,
123 GdkEventCrossing
*event
,
125 static gboolean
button_press_event(GtkWidget
*widget
,
126 GdkEventButton
*event
,
128 static gint
icon_motion_notify(GtkWidget
*widget
,
129 GdkEventMotion
*event
,
131 static char *pin_from_file(guchar
*line
);
132 static gboolean
add_root_handlers(void);
133 static GdkFilterReturn
proxy_filter(GdkXEvent
*xevent
,
136 static void snap_to_grid(int *x
, int *y
);
137 static void offset_from_centre(Icon
*icon
,
138 int width
, int height
,
140 static void offset_to_centre(Icon
*icon
,
141 int width
, int height
,
143 static gboolean
drag_motion(GtkWidget
*widget
,
144 GdkDragContext
*context
,
149 static void drag_set_pinicon_dest(Icon
*icon
);
150 static void drag_leave(GtkWidget
*widget
,
151 GdkDragContext
*context
,
154 static void forward_root_clicks(void);
155 static gboolean
bg_drag_motion(GtkWidget
*widget
,
156 GdkDragContext
*context
,
161 static gboolean
bg_drag_leave(GtkWidget
*widget
,
162 GdkDragContext
*context
,
165 static void bg_expose(GdkRectangle
*area
);
166 static void drag_end(GtkWidget
*widget
,
167 GdkDragContext
*context
,
169 static void reshape_all(void);
170 static void pinboard_check_options(void);
171 static void pinboard_load_from_xml(xmlDocPtr doc
);
173 /****************************************************************
174 * EXTERNAL INTERFACE *
175 ****************************************************************/
177 void pinboard_init(void)
179 option_add_string("pinboard_fg_colour", "#000", NULL
);
180 option_add_string("pinboard_bg_colour", "#ddd", NULL
);
182 option_add_int("pinboard_text_bg", TEXT_BG_SOLID
, NULL
);
183 option_add_int("pinboard_clamp_icons", 1, NULL
);
184 option_add_int("pinboard_grid_step", GRID_STEP_COARSE
, NULL
);
185 option_add_notify(pinboard_check_options
);
187 gdk_color_parse(option_get_static_string("pinboard_fg_colour"),
189 gdk_color_parse(option_get_static_string("pinboard_bg_colour"),
194 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
195 * and make it the current pinboard.
196 * Any existing pinned items are removed. You must call this
197 * at least once before using the pinboard. NULL disables the
200 void pinboard_activate(guchar
*name
)
202 Pinboard
*old_board
= current_pinboard
;
203 guchar
*path
, *slash
;
205 /* Treat an empty name the same as NULL */
217 if (number_of_windows
< 1 && gtk_main_level() > 0)
222 if (!add_root_handlers())
224 delayed_error(_("Another application is already "
225 "managing the pinboard!"));
231 slash
= strchr(name
, '/');
234 if (access(name
, F_OK
))
235 path
= NULL
; /* File does not (yet) exist */
237 path
= g_strdup(name
);
243 leaf
= g_strconcat("pb_", name
, NULL
);
244 path
= choices_find_path_load(leaf
, PROJECT
);
248 current_pinboard
= g_new(Pinboard
, 1);
249 current_pinboard
->name
= g_strdup(name
);
250 current_pinboard
->icons
= NULL
;
256 doc
= xmlParseFile(path
);
259 pinboard_load_from_xml(doc
);
264 parse_file(path
, pin_from_file
);
265 delayed_error(_("Your old pinboard file has been "
266 "converted to the new XML format."));
272 pinboard_pin(home_dir
, "Home",
274 4 + ICON_HEIGHT
/ 2);
278 /* Add a new icon to the background.
279 * 'path' should be an absolute pathname.
280 * 'x' and 'y' are the coordinates of the point in the middle of the text
281 * if 'corner' is FALSE, and as the top-left corner of where the icon
282 * image should be if it is TRUE.
283 * 'name' is the name to use. If NULL then the leafname of path is used.
285 * names are in UTF-8.
287 void pinboard_pin(guchar
*path
, guchar
*name
, int x
, int y
)
292 g_return_if_fail(path
!= NULL
);
293 g_return_if_fail(current_pinboard
!= NULL
);
295 icon
= g_new(Icon
, 1);
297 icon
->selected
= FALSE
;
298 icon
->src_path
= g_strdup(path
);
299 icon
->path
= icon_convert_path(path
);
305 icon_hash_path(icon
);
309 name
= strrchr(icon
->path
, '/');
320 loc_name
= from_utf8(name
);
321 icon
->item
= diritem_new(loc_name
);
325 icon
->item
= diritem_new(name
);
327 diritem_restat(icon
->path
, icon
->item
);
329 icon
->win
= gtk_window_new(GTK_WINDOW_DIALOG
);
330 gtk_window_set_wmclass(GTK_WINDOW(icon
->win
), "ROX-Pinboard", PROJECT
);
332 icon
->widget
= gtk_drawing_area_new();
333 gtk_widget_set_name(icon
->widget
, "pinboard-icon");
335 icon
->layout
= gtk_widget_create_pango_layout(icon
->widget
, NULL
);
336 pango_layout_set_width(icon
->layout
, 140 * PANGO_SCALE
);
338 gtk_container_add(GTK_CONTAINER(icon
->win
), icon
->widget
);
339 drag_set_pinicon_dest(icon
);
340 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "drag_data_get",
341 GTK_SIGNAL_FUNC(drag_data_get
), NULL
);
343 gtk_widget_realize(icon
->win
);
344 gtk_widget_realize(icon
->widget
);
346 set_size_and_shape(icon
, &width
, &height
);
347 snap_to_grid(&x
, &y
);
348 offset_from_centre(icon
, width
, height
, &x
, &y
);
349 gtk_widget_set_uposition(icon
->win
, x
, y
);
350 /* Set the correct position in the icon */
351 offset_to_centre(icon
, width
, height
, &x
, &y
);
355 make_panel_window(icon
->win
);
357 /* TODO: Use gdk function when it supports this type */
359 GdkAtom desktop_type
;
361 desktop_type
= gdk_atom_intern("_NET_WM_WINDOW_TYPE_DESKTOP",
363 gdk_property_change(icon
->win
->window
,
364 gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE
),
365 gdk_atom_intern("ATOM", FALSE
), 32,
366 GDK_PROP_MODE_REPLACE
, (guchar
*) &desktop_type
, 1);
369 gtk_widget_add_events(icon
->widget
,
370 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
371 GDK_BUTTON1_MOTION_MASK
| GDK_ENTER_NOTIFY_MASK
|
372 GDK_BUTTON2_MOTION_MASK
| GDK_BUTTON3_MOTION_MASK
);
373 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "enter-notify-event",
374 GTK_SIGNAL_FUNC(enter_notify
), icon
);
375 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "button-press-event",
376 GTK_SIGNAL_FUNC(button_press_event
), icon
);
377 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "button-release-event",
378 GTK_SIGNAL_FUNC(button_release_event
), icon
);
379 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "motion-notify-event",
380 GTK_SIGNAL_FUNC(icon_motion_notify
), icon
);
381 gtk_signal_connect(GTK_OBJECT(icon
->widget
), "expose-event",
382 GTK_SIGNAL_FUNC(draw_icon
), icon
);
383 gtk_signal_connect_object(GTK_OBJECT(icon
->win
), "destroy",
384 GTK_SIGNAL_FUNC(icon_destroyed
), (gpointer
) icon
);
386 current_pinboard
->icons
= g_list_prepend(current_pinboard
->icons
,
389 gtk_widget_show_all(icon
->win
);
390 gdk_window_lower(icon
->win
->window
);
392 if (!loading_pinboard
)
396 /* Remove an icon from the pinboard */
397 void pinboard_unpin(Icon
*icon
)
399 g_return_if_fail(icon
!= NULL
);
401 gtk_widget_destroy(icon
->win
);
405 /* Put a border around the icon, briefly.
406 * If icon is NULL then cancel any existing wink.
407 * The icon will automatically unhighlight unless timeout is FALSE,
408 * in which case you must call this function again (with NULL or another
409 * icon) to remove the highlight.
411 void pinboard_wink_item(Icon
*icon
, gboolean timeout
)
413 if (current_wink_icon
== icon
)
416 if (current_wink_icon
)
418 mask_wink_border(current_wink_icon
, &mask_transp
);
419 if (wink_timeout
!= -1)
420 gtk_timeout_remove(wink_timeout
);
423 current_wink_icon
= icon
;
425 if (current_wink_icon
)
427 mask_wink_border(current_wink_icon
, &mask_solid
);
429 wink_timeout
= gtk_timeout_add(300, end_wink
, NULL
);
435 /* Remove everything on the current pinboard and disables the pinboard.
436 * Does not change any files. Does not change number_of_windows.
438 void pinboard_clear(void)
442 g_return_if_fail(current_pinboard
!= NULL
);
444 next
= current_pinboard
->icons
;
447 Icon
*icon
= (Icon
*) next
->data
;
451 gtk_widget_destroy(icon
->win
);
454 g_free(current_pinboard
->name
);
455 g_free(current_pinboard
);
456 current_pinboard
= NULL
;
458 release_xdnd_proxy(GDK_ROOT_WINDOW());
459 gdk_window_remove_filter(GDK_ROOT_PARENT(), proxy_filter
, NULL
);
460 gdk_window_set_user_data(GDK_ROOT_PARENT(), NULL
);
463 /* Icon's size, shape or appearance has changed - update the display */
464 void pinboard_reshape_icon(Icon
*icon
)
466 int x
= icon
->x
, y
= icon
->y
;
469 set_size_and_shape(icon
, &width
, &height
);
470 gdk_window_resize(icon
->win
->window
, width
, height
);
471 offset_from_centre(icon
, width
, height
, &x
, &y
);
472 gtk_widget_set_uposition(icon
->win
, x
, y
);
473 gtk_widget_queue_draw(icon
->win
);
477 /****************************************************************
478 * INTERNAL FUNCTIONS *
479 ****************************************************************/
481 static void pinboard_check_options(void)
483 int old_text_bg
= o_text_bg
;
486 o_text_bg
= option_get_int("pinboard_text_bg");
487 o_grid_step
= option_get_int("pinboard_grid_step");
488 o_clamp_icons
= option_get_int("pinboard_clamp_icons");
490 gdk_color_parse(option_get_static_string("pinboard_fg_colour"), &n_fg
);
491 gdk_color_parse(option_get_static_string("pinboard_bg_colour"), &n_bg
);
493 if (o_text_bg
!= old_text_bg
||
494 gdk_color_equal(&n_fg
, &text_fg_col
) == 0 ||
495 gdk_color_equal(&n_bg
, &text_bg_col
) == 0)
497 memcpy(&text_fg_col
, &n_fg
, sizeof(GdkColor
));
498 memcpy(&text_bg_col
, &n_bg
, sizeof(GdkColor
));
502 gtk_style_unref(pinicon_style
);
503 pinicon_style
= NULL
;
506 if (current_pinboard
)
511 static gint
end_wink(gpointer data
)
513 pinboard_wink_item(NULL
, FALSE
);
517 /* Make the wink border solid or transparent */
518 static void mask_wink_border(Icon
*icon
, GdkColor
*alpha
)
520 if (!current_pinboard
)
523 gdk_gc_set_foreground(mask_gc
, alpha
);
524 gdk_draw_rectangle(icon
->mask
, mask_gc
, FALSE
,
525 0, 0, icon
->width
- 1, icon
->height
- 1);
526 gdk_draw_rectangle(icon
->mask
, mask_gc
, FALSE
,
527 1, 1, icon
->width
- 3, icon
->height
- 3);
529 gtk_widget_shape_combine_mask(icon
->win
, icon
->mask
, 0, 0);
531 gtk_widget_draw(icon
->widget
, NULL
);
534 #define TEXT_AT(dx, dy) \
535 gdk_draw_string(icon->mask, font, mask_gc, \
536 text_x + dx, y + dy, \
539 /* Updates the name_width and layout fields, and resizes and masks the window.
540 * Also sets the style to pinicon_style, generating it if needed.
541 * Returns the new width and height.
543 static void set_size_and_shape(Icon
*icon
, int *rwidth
, int *rheight
)
547 MaskedPixmap
*image
= icon
->item
->image
;
548 int iwidth
= image
->width
;
549 int iheight
= image
->height
;
550 DirItem
*item
= icon
->item
;
558 pinicon_style
= gtk_style_copy(icon
->widget
->style
);
559 memcpy(&pinicon_style
->fg
[GTK_STATE_NORMAL
],
560 &text_fg_col
, sizeof(GdkColor
));
561 memcpy(&pinicon_style
->bg
[GTK_STATE_NORMAL
],
562 &text_bg_col
, sizeof(GdkColor
));
564 gtk_widget_set_style(icon
->widget
, pinicon_style
);
567 font
= pinicon_style
->font
;
568 font_height
= font
->ascent
+ font
->descent
;
569 icon
->name_width
= gdk_string_measure(font
, item
->leafname
);
572 PangoRectangle logical
;
573 pango_layout_set_text(icon
->layout
, icon
->item
->leafname
, -1);
574 pango_layout_get_pixel_extents(icon
->layout
, NULL
, &logical
);
576 icon
->name_width
= logical
.width
- logical
.x
;
577 font_height
= logical
.height
- logical
.y
;
581 width
= MAX(iwidth
, icon
->name_width
+ 2) + 2 * WINK_FRAME
;
582 height
= iheight
+ GAP
+ (font_height
+ 2) + 2 * WINK_FRAME
;
583 gtk_widget_set_usize(icon
->win
, width
, height
);
585 icon
->height
= height
;
588 gdk_pixmap_unref(icon
->mask
);
589 icon
->mask
= gdk_pixmap_new(icon
->win
->window
, width
, height
, 1);
591 mask_gc
= gdk_gc_new(icon
->mask
);
593 /* Clear the mask to transparent */
594 gdk_gc_set_foreground(mask_gc
, &mask_transp
);
595 gdk_draw_rectangle(icon
->mask
, mask_gc
, TRUE
, 0, 0, width
, height
);
597 gdk_gc_set_foreground(mask_gc
, &mask_solid
);
598 /* Make the icon area solid */
601 gdk_draw_pixmap(icon
->mask
, mask_gc
, image
->mask
,
603 (width
- iwidth
) >> 1,
610 gdk_draw_rectangle(icon
->mask
, mask_gc
, TRUE
,
611 (width
- iwidth
) >> 1,
617 gdk_gc_set_function(mask_gc
, GDK_OR
);
618 if (item
->flags
& ITEM_FLAG_SYMLINK
)
620 gdk_draw_pixmap(icon
->mask
, mask_gc
, im_symlink
->mask
,
621 0, 0, /* Source x,y */
622 (width
- iwidth
) >> 1, /* Dest x */
623 WINK_FRAME
, /* Dest y */
626 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
628 /* Note: Both mount state pixmaps must have the same mask */
629 gdk_draw_pixmap(icon
->mask
, mask_gc
, im_mounted
->mask
,
630 0, 0, /* Source x,y */
631 (width
- iwidth
) >> 1, /* Dest x */
632 WINK_FRAME
, /* Dest y */
635 gdk_gc_set_function(mask_gc
, GDK_COPY
);
637 /* Mask off an area for the text (from o_text_bg) */
639 text_x
= (width
- icon
->name_width
) >> 1;
640 text_y
= WINK_FRAME
+ iheight
+ GAP
+ 1;
643 if (o_text_bg
== TEXT_BG_SOLID
)
646 gdk_draw_rectangle(icon
->mask
, mask_gc
, TRUE
,
647 (width
- (icon
->name_width
+ 2)) >> 1,
648 WINK_FRAME
+ iheight
+ GAP
,
649 icon
->name_width
+ 2, font_height
+ 2);
654 int y
= text_y
+ font
->ascent
;
658 if (o_text_bg
== TEXT_BG_OUTLINE
)
672 gtk_widget_shape_combine_mask(icon
->win
, icon
->mask
, 0, 0);
678 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, Icon
*icon
)
681 GdkFont
*font
= icon
->widget
->style
->font
;
684 DirItem
*item
= icon
->item
;
685 MaskedPixmap
*image
= item
->image
;
686 int iwidth
= image
->width
;
687 int iheight
= image
->height
;
689 GdkGC
*gc
= widget
->style
->black_gc
;
690 GtkStateType state
= icon
->selected
? GTK_STATE_SELECTED
693 image_x
= (icon
->width
- iwidth
) >> 1;
695 /* TODO: If the shape extension is missing we might need to set
696 * the clip mask here...
698 gdk_draw_pixmap(widget
->window
, gc
,
706 if (item
->flags
& ITEM_FLAG_SYMLINK
)
708 gdk_gc_set_clip_origin(gc
, image_x
, WINK_FRAME
);
709 gdk_gc_set_clip_mask(gc
, im_symlink
->mask
);
710 gdk_draw_pixmap(widget
->window
, gc
,
712 0, 0, /* Source x,y */
713 image_x
, WINK_FRAME
, /* Dest x,y */
715 gdk_gc_set_clip_mask(gc
, NULL
);
716 gdk_gc_set_clip_origin(gc
, 0, 0);
718 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
720 MaskedPixmap
*mp
= item
->flags
& ITEM_FLAG_MOUNTED
724 gdk_gc_set_clip_origin(gc
, image_x
, WINK_FRAME
);
725 gdk_gc_set_clip_mask(gc
, mp
->mask
);
726 gdk_draw_pixmap(widget
->window
, gc
,
728 0, 0, /* Source x,y */
729 image_x
, WINK_FRAME
, /* Dest x,y */
731 gdk_gc_set_clip_mask(gc
, NULL
);
732 gdk_gc_set_clip_origin(gc
, 0, 0);
735 text_x
= (icon
->width
- icon
->name_width
) >> 1;
736 text_y
= WINK_FRAME
+ iheight
+ GAP
+ 1;
738 if (o_text_bg
!= TEXT_BG_NONE
)
741 PangoRectangle logical
;
744 pango_layout_get_pixel_extents(icon
->layout
, NULL
, &logical
);
745 font_height
= logical
.height
- logical
.y
;
747 int font_height
= font
->ascent
+ font
->descent
;
750 gtk_paint_flat_box(widget
->style
, widget
->window
,
753 NULL
, widget
, "text",
756 icon
->name_width
+ 2,
761 gtk_paint_layout(widget
->style
, widget
->window
,
763 FALSE
, NULL
, widget
, "text",
768 gtk_paint_string(widget
->style
, widget
->window
,
770 NULL
, widget
, "text",
772 text_y
+ font
->ascent
,
776 if (current_wink_icon
== icon
)
778 gdk_draw_rectangle(icon
->widget
->window
,
779 icon
->widget
->style
->white_gc
,
781 0, 0, icon
->width
- 1, icon
->height
- 1);
782 gdk_draw_rectangle(icon
->widget
->window
,
783 icon
->widget
->style
->black_gc
,
785 1, 1, icon
->width
- 3, icon
->height
- 3);
791 static gboolean
root_property_event(GtkWidget
*widget
,
792 GdkEventProperty
*event
,
795 if (event
->atom
== win_button_proxy
&&
796 event
->state
== GDK_PROPERTY_NEW_VALUE
)
798 /* Setup forwarding on the new proxy window, if possible */
799 forward_root_clicks();
805 static gboolean
root_button_press(GtkWidget
*widget
,
806 GdkEventButton
*event
,
811 action
= bind_lookup_bev(BIND_PINBOARD
, event
);
815 case ACT_CLEAR_SELECTION
:
816 icon_select_only(NULL
);
820 icon_show_menu(event
, NULL
, NULL
);
825 g_warning("Unsupported action : %d\n", action
);
832 static gboolean
enter_notify(GtkWidget
*widget
,
833 GdkEventCrossing
*event
,
836 icon_may_update(icon
);
841 static void perform_action(Icon
*icon
, GdkEventButton
*event
)
845 action
= bind_lookup_bev(BIND_PINBOARD_ICON
, event
);
851 pinboard_wink_item(icon
, TRUE
);
852 if (event
->type
== GDK_2BUTTON_PRESS
)
853 icon_set_selected(icon
, FALSE
);
854 run_diritem(icon
->path
, icon
->item
, NULL
, NULL
, FALSE
);
858 pinboard_wink_item(icon
, TRUE
);
859 if (event
->type
== GDK_2BUTTON_PRESS
)
860 icon_set_selected(icon
, FALSE
);
861 run_diritem(icon
->path
, icon
->item
, NULL
, NULL
, TRUE
);
865 icon_show_menu(event
, icon
, NULL
);
868 old_x
= event
->x_root
;
869 old_y
= event
->y_root
;
870 icon_old_x
= icon
->x
;
871 icon_old_y
= icon
->y
;
872 dnd_motion_start(MOTION_REPOSITION
);
874 case ACT_PRIME_AND_SELECT
:
876 icon_select_only(icon
);
877 dnd_motion_start(MOTION_READY_FOR_DND
);
879 case ACT_PRIME_AND_TOGGLE
:
880 icon_set_selected(icon
, !icon
->selected
);
881 dnd_motion_start(MOTION_READY_FOR_DND
);
883 case ACT_PRIME_FOR_DND
:
884 dnd_motion_start(MOTION_READY_FOR_DND
);
886 case ACT_TOGGLE_SELECTED
:
887 icon_set_selected(icon
, !icon
->selected
);
889 case ACT_SELECT_EXCL
:
890 icon_select_only(icon
);
895 g_warning("Unsupported action : %d\n", action
);
900 static gboolean
button_release_event(GtkWidget
*widget
,
901 GdkEventButton
*event
,
904 if (pinboard_modified
)
907 if (dnd_motion_release(event
))
910 perform_action(icon
, event
);
915 static gboolean
button_press_event(GtkWidget
*widget
,
916 GdkEventButton
*event
,
919 if (dnd_motion_press(widget
, event
))
920 perform_action(icon
, event
);
925 /* Return a text/uri-list of all the icons in the list.
926 * TODO: Use code in icon.c instead.
928 static guchar
*create_uri_list(GList
*list
)
934 tmp
= g_string_new(NULL
);
935 leader
= g_strdup_printf("file://%s", our_host_name_for_dnd());
937 for (; list
; list
= list
->next
)
939 Icon
*icon
= (Icon
*) list
->data
;
941 g_string_append(tmp
, leader
);
942 g_string_append(tmp
, icon
->path
);
943 g_string_append(tmp
, "\r\n");
948 g_string_free(tmp
, FALSE
);
953 static void start_drag(Icon
*icon
, GdkEventMotion
*event
)
955 GtkWidget
*widget
= icon
->widget
;
959 tmp_icon_selected
= TRUE
;
960 icon_select_only(icon
);
963 g_return_if_fail(icon_selection
!= NULL
);
965 pinboard_drag_in_progress
= icon
;
967 if (icon_selection
->next
== NULL
)
968 drag_one_item(widget
, event
, icon
->path
, icon
->item
, NULL
);
973 uri_list
= create_uri_list(icon_selection
);
974 drag_selection(widget
, event
, uri_list
);
979 /* An icon is being dragged around... */
980 static gint
icon_motion_notify(GtkWidget
*widget
,
981 GdkEventMotion
*event
,
988 if (motion_state
== MOTION_READY_FOR_DND
)
990 if (dnd_motion_moved(event
))
991 start_drag(icon
, event
);
994 else if (motion_state
!= MOTION_REPOSITION
)
997 /* How far the pointer has moved since the drag started */
998 dx
= event
->x_root
- old_x
;
999 dy
= event
->y_root
- old_y
;
1001 x
= icon_old_x
+ dx
;
1002 y
= icon_old_y
+ dy
;
1004 snap_to_grid(&x
, &y
);
1006 if (icon
->x
== x
&& icon
->y
== y
)
1011 gdk_window_get_size(icon
->win
->window
, &width
, &height
);
1012 offset_from_centre(icon
, width
, height
, &x
, &y
);
1014 gdk_window_move(icon
->win
->window
, x
, y
);
1016 /* Store the fixed position for the center of the icon */
1017 offset_to_centre(icon
, width
, height
, &x
, &y
);
1021 pinboard_modified
= TRUE
;
1026 /* Create one pinboard icon for each icon in the doc */
1027 static void pinboard_load_from_xml(xmlDocPtr doc
)
1029 xmlNodePtr node
, root
;
1030 char *tmp
, *label
, *path
;
1033 root
= xmlDocGetRootElement(doc
);
1035 for (node
= root
->xmlChildrenNode
; node
; node
= node
->next
)
1037 if (node
->type
!= XML_ELEMENT_NODE
)
1039 if (strcmp(node
->name
, "icon") != 0)
1042 tmp
= xmlGetProp(node
, "x");
1048 tmp
= xmlGetProp(node
, "y");
1054 label
= xmlGetProp(node
, "label");
1056 label
= g_strdup("<missing label>");
1057 path
= xmlNodeGetContent(node
);
1059 path
= g_strdup("<missing path>");
1061 pinboard_pin(path
, label
, x
, y
);
1068 /* Called for each line in the pinboard file while loading a new board.
1069 * Only used for old-format files when converting to XML.
1071 static char *pin_from_file(guchar
*line
)
1073 guchar
*leaf
= NULL
;
1074 gchar
*u8_path
, *u8_leaf
;
1081 end
= strchr(line
+ 1, '>');
1083 return _("Missing '>' in icon label");
1085 leaf
= g_strndup(line
+ 1, end
- line
- 1);
1089 while (isspace(*line
))
1092 return _("Missing ',' after icon label");
1096 if (sscanf(line
, " %d , %d , %n", &x
, &y
, &n
) < 2)
1097 return NULL
; /* Ignore format errors */
1099 u8_path
= to_utf8(line
+ n
);
1100 u8_leaf
= to_utf8(leaf
);
1103 pinboard_pin(u8_path
, u8_leaf
, x
, y
);
1111 /* Make sure that clicks and drops on the root window come to us...
1112 * False if an error occurred (ie, someone else is using it).
1114 static gboolean
add_root_handlers(void)
1118 if (!proxy_invisible
)
1120 win_button_proxy
= gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY",
1122 proxy_invisible
= gtk_invisible_new();
1123 gtk_widget_show(proxy_invisible
);
1125 gdk_window_add_filter(proxy_invisible->window,
1126 proxy_filter, NULL);
1129 gtk_signal_connect(GTK_OBJECT(proxy_invisible
),
1130 "property_notify_event",
1131 GTK_SIGNAL_FUNC(root_property_event
), NULL
);
1132 gtk_signal_connect(GTK_OBJECT(proxy_invisible
),
1133 "button_press_event",
1134 GTK_SIGNAL_FUNC(root_button_press
), NULL
);
1136 /* Drag and drop handlers */
1137 drag_set_pinboard_dest(proxy_invisible
);
1138 gtk_signal_connect(GTK_OBJECT(proxy_invisible
), "drag_motion",
1139 GTK_SIGNAL_FUNC(bg_drag_motion
),
1141 gtk_signal_connect(GTK_OBJECT(proxy_invisible
), "drag_leave",
1142 GTK_SIGNAL_FUNC(bg_drag_leave
),
1146 root
= gdk_window_lookup(GDK_ROOT_WINDOW());
1148 root
= gdk_window_foreign_new(GDK_ROOT_WINDOW());
1150 if (!setup_xdnd_proxy(GDK_ROOT_WINDOW(), proxy_invisible
->window
))
1153 /* Forward events from the root window to our proxy window */
1154 gdk_window_add_filter(GDK_ROOT_PARENT(), proxy_filter
, NULL
);
1155 gdk_window_set_user_data(GDK_ROOT_PARENT(), proxy_invisible
);
1156 gdk_window_set_events(GDK_ROOT_PARENT(),
1157 gdk_window_get_events(GDK_ROOT_PARENT()) |
1159 GDK_PROPERTY_CHANGE_MASK
);
1161 forward_root_clicks();
1166 /* See if the window manager is offering to forward root window clicks.
1167 * If so, grab them. Otherwise, do nothing.
1168 * Call this whenever the _WIN_DESKTOP_BUTTON_PROXY property changes.
1170 static void forward_root_clicks(void)
1172 click_proxy_gdk_window
= find_click_proxy_window();
1173 if (!click_proxy_gdk_window
)
1176 /* Events on the wm's proxy are dealt with by our proxy widget */
1177 gdk_window_set_user_data(click_proxy_gdk_window
, proxy_invisible
);
1178 gdk_window_add_filter(click_proxy_gdk_window
, proxy_filter
, NULL
);
1180 /* The proxy window for clicks sends us button press events with
1181 * SubstructureNotifyMask. We need StructureNotifyMask to receive
1182 * DestroyNotify events, too.
1184 XSelectInput(GDK_DISPLAY(),
1185 GDK_WINDOW_XWINDOW(click_proxy_gdk_window
),
1186 SubstructureNotifyMask
| StructureNotifyMask
);
1189 /* Write the current state of the pinboard to the current pinboard file */
1190 void pinboard_save(void)
1192 guchar
*save
= NULL
;
1193 guchar
*save_new
= NULL
;
1195 xmlDocPtr doc
= NULL
;
1198 g_return_if_fail(current_pinboard
!= NULL
);
1200 pinboard_modified
= FALSE
;
1202 if (strchr(current_pinboard
->name
, '/'))
1203 save
= g_strdup(current_pinboard
->name
);
1208 leaf
= g_strconcat("pb_", current_pinboard
->name
, NULL
);
1209 save
= choices_find_path_save(leaf
, PROJECT
, TRUE
);
1216 doc
= xmlNewDoc("1.0");
1217 xmlDocSetRootElement(doc
, xmlNewDocNode(doc
, NULL
, "pinboard", NULL
));
1219 root
= xmlDocGetRootElement(doc
);
1221 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
1224 Icon
*icon
= (Icon
*) next
->data
;
1227 tree
= xmlNewTextChild(root
, NULL
, "icon", icon
->src_path
);
1229 tmp
= g_strdup_printf("%d", icon
->x
);
1230 xmlSetProp(tree
, "x", tmp
);
1233 tmp
= g_strdup_printf("%d", icon
->y
);
1234 xmlSetProp(tree
, "y", tmp
);
1240 u8
= to_utf8(icon
->item
->leafname
);
1241 xmlSetProp(tree
, "label", u8
);
1245 xmlSetProp(tree
, "label", icon
->item
->leafname
);
1249 save_new
= g_strconcat(save
, ".new", NULL
);
1250 if (save_xml_file(doc
, save_new
) || rename(save_new
, save
))
1251 delayed_error(_("Error saving pinboard %s: %s"),
1252 save
, g_strerror(errno
));
1261 * Filter that translates proxied events from virtual root windows into normal
1262 * Gdk events for the proxy_invisible widget. Stolen from gmc.
1264 * Also gets events from the root window.
1266 static GdkFilterReturn
proxy_filter(GdkXEvent
*xevent
,
1271 GdkWindow
*proxy
= proxy_invisible
->window
;
1276 switch (xev
->type
) {
1279 /* Translate button events into events that come from
1280 * the proxy window, so that we can catch them as a
1281 * signal from the invisible widget.
1283 if (xev
->type
== ButtonPress
)
1284 event
->button
.type
= GDK_BUTTON_PRESS
;
1286 event
->button
.type
= GDK_BUTTON_RELEASE
;
1288 gdk_window_ref(proxy
);
1290 event
->button
.window
= proxy
;
1291 event
->button
.send_event
= xev
->xbutton
.send_event
;
1292 event
->button
.time
= xev
->xbutton
.time
;
1293 event
->button
.x_root
= xev
->xbutton
.x_root
;
1294 event
->button
.y_root
= xev
->xbutton
.y_root
;
1295 event
->button
.x
= xev
->xbutton
.x
;
1296 event
->button
.y
= xev
->xbutton
.y
;
1297 event
->button
.state
= xev
->xbutton
.state
;
1298 event
->button
.button
= xev
->xbutton
.button
;
1300 event
->button
.axes
= NULL
;
1303 return GDK_FILTER_TRANSLATE
;
1306 area
.x
= xev
->xexpose
.x
;
1307 area
.y
= xev
->xexpose
.y
;
1308 area
.width
= xev
->xexpose
.width
;
1309 area
.height
= xev
->xexpose
.height
;
1311 return GDK_FILTER_REMOVE
;
1314 /* XXX: I have no idea why this helps, but it does! */
1315 /* The proxy window was destroyed (i.e. the window
1316 * manager died), so we have to cope with it
1318 if (((GdkEventAny
*) event
)->window
== proxy
)
1319 gdk_window_destroy_notify(proxy
);
1321 return GDK_FILTER_REMOVE
;
1327 return GDK_FILTER_CONTINUE
;
1330 static void snap_to_grid(int *x
, int *y
)
1332 *x
= ((*x
+ o_grid_step
/ 2) / o_grid_step
) * o_grid_step
;
1333 *y
= ((*y
+ o_grid_step
/ 2) / o_grid_step
) * o_grid_step
;
1336 /* Convert (x,y) from a centre point to a window position */
1337 static void offset_from_centre(Icon
*icon
,
1338 int width
, int height
,
1343 *x
= CLAMP(*x
, 0, screen_width
- (o_clamp_icons
? width
: 0));
1344 *y
= CLAMP(*y
, 0, screen_height
- (o_clamp_icons
? height
: 0));
1347 /* Convert (x,y) from a window position to a centre point */
1348 static void offset_to_centre(Icon
*icon
,
1349 int width
, int height
,
1356 /* Same as drag_set_dest(), but for pinboard icons */
1357 static void drag_set_pinicon_dest(Icon
*icon
)
1359 GtkObject
*obj
= GTK_OBJECT(icon
->widget
);
1361 make_drop_target(icon
->widget
, 0);
1363 gtk_signal_connect(obj
, "drag_motion",
1364 GTK_SIGNAL_FUNC(drag_motion
), icon
);
1365 gtk_signal_connect(obj
, "drag_leave",
1366 GTK_SIGNAL_FUNC(drag_leave
), icon
);
1367 gtk_signal_connect(obj
, "drag_end",
1368 GTK_SIGNAL_FUNC(drag_end
), icon
);
1371 /* Called during the drag when the mouse is in a widget registered
1372 * as a drop target. Returns TRUE if we can accept the drop.
1374 static gboolean
drag_motion(GtkWidget
*widget
,
1375 GdkDragContext
*context
,
1381 GdkDragAction action
= context
->suggested_action
;
1383 DirItem
*item
= icon
->item
;
1385 if (gtk_drag_get_source_widget(context
) == widget
)
1386 goto out
; /* Can't drag something to itself! */
1389 goto out
; /* Can't drag a selection to itself */
1391 type
= dnd_motion_item(context
, &item
);
1396 /* We actually must pretend to accept the drop, even if the
1397 * directory isn't writeable, so that the spring-opening
1401 /* Don't allow drops to non-writeable directories */
1402 if (option_get_int("dnd_spring_open") == FALSE
&&
1403 type
== drop_dest_dir
&&
1404 access(icon
->path
, W_OK
) != 0)
1409 g_dataset_set_data(context
, "drop_dest_type", type
);
1412 gdk_drag_status(context
, action
, time
);
1413 g_dataset_set_data_full(context
, "drop_dest_path",
1414 g_strdup(icon
->path
), g_free
);
1415 if (type
== drop_dest_dir
)
1416 dnd_spring_load(context
, NULL
);
1418 pinboard_wink_item(icon
, FALSE
);
1421 return type
!= NULL
;
1424 static gboolean pinboard_shadow
= FALSE
;
1425 static gint shadow_x
, shadow_y
;
1426 #define SHADOW_SIZE (ICON_WIDTH)
1428 static void bg_expose(GdkRectangle
*area
)
1430 GdkWindow
*root
= GDK_ROOT_PARENT();
1431 static GdkGC
*shadow_gc
= NULL
;
1432 static GdkColor white
, black
;
1434 if (!pinboard_shadow
)
1436 /* XXX: Should just disable the events */
1444 cm
= gdk_window_get_colormap(root
);
1445 shadow_gc
= gdk_gc_new(root
);
1446 gdk_color_white(cm
, &white
);
1447 gdk_color_black(cm
, &black
);
1450 gdk_gc_set_clip_rectangle(shadow_gc
, area
);
1451 gdk_gc_set_foreground(shadow_gc
, &white
);
1452 gdk_draw_rectangle(root
, shadow_gc
, FALSE
, shadow_x
, shadow_y
,
1453 SHADOW_SIZE
, SHADOW_SIZE
);
1454 gdk_gc_set_foreground(shadow_gc
, &black
);
1455 gdk_draw_rectangle(root
, shadow_gc
, FALSE
, shadow_x
+ 1, shadow_y
+ 1,
1456 SHADOW_SIZE
- 2, SHADOW_SIZE
- 2);
1457 gdk_gc_set_clip_rectangle(shadow_gc
, NULL
);
1460 /* Draw a 'shadow' under an icon being dragged, showing where
1463 static void pinboard_set_shadow(gboolean on
)
1465 GdkWindow
*root
= GDK_ROOT_PARENT();
1467 if (pinboard_shadow
)
1469 gdk_window_clear_area_e(root
, shadow_x
, shadow_y
,
1470 SHADOW_SIZE
+ 1, SHADOW_SIZE
+ 1);
1475 int old_x
= shadow_x
, old_y
= shadow_y
;
1477 gdk_window_get_pointer(root
, &shadow_x
, &shadow_y
, NULL
);
1478 snap_to_grid(&shadow_x
, &shadow_y
);
1479 shadow_x
-= SHADOW_SIZE
/ 2;
1480 shadow_y
-= SHADOW_SIZE
/ 2;
1483 if (pinboard_shadow
&& shadow_x
== old_x
&& shadow_y
== old_y
)
1486 gdk_window_clear_area_e(root
, shadow_x
, shadow_y
,
1487 SHADOW_SIZE
+ 1, SHADOW_SIZE
+ 1);
1490 pinboard_shadow
= on
;
1493 /* Called when dragging some pinboard icons finishes */
1494 void pinboard_move_icons(void)
1496 int x
= shadow_x
, y
= shadow_y
;
1497 Icon
*icon
= pinboard_drag_in_progress
;
1500 g_return_if_fail(icon
!= NULL
);
1502 x
+= SHADOW_SIZE
/ 2;
1503 y
+= SHADOW_SIZE
/ 2;
1504 snap_to_grid(&x
, &y
);
1506 if (icon
->x
== x
&& icon
->y
== y
)
1511 gdk_window_get_size(icon
->win
->window
, &width
, &height
);
1512 offset_from_centre(icon
, width
, height
, &x
, &y
);
1514 gdk_window_move(icon
->win
->window
, x
, y
);
1519 static void drag_leave(GtkWidget
*widget
,
1520 GdkDragContext
*context
,
1524 pinboard_wink_item(NULL
, FALSE
);
1528 static gboolean
bg_drag_leave(GtkWidget
*widget
,
1529 GdkDragContext
*context
,
1533 pinboard_set_shadow(FALSE
);
1538 static gboolean
bg_drag_motion(GtkWidget
*widget
,
1539 GdkDragContext
*context
,
1545 /* Dragging from the pinboard to the pinboard is not allowed */
1547 if (!provides(context
, text_uri_list
))
1550 pinboard_set_shadow(TRUE
);
1552 gdk_drag_status(context
, context
->suggested_action
, time
);
1556 static void drag_end(GtkWidget
*widget
,
1557 GdkDragContext
*context
,
1560 pinboard_drag_in_progress
= NULL
;
1561 if (tmp_icon_selected
)
1563 icon_select_only(NULL
);
1564 tmp_icon_selected
= FALSE
;
1568 /* Something which affects all the icons has changed - reshape
1569 * and redraw all of them.
1571 static void reshape_all(void)
1575 g_return_if_fail(current_pinboard
!= NULL
);
1577 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
1579 Icon
*icon
= (Icon
*) next
->data
;
1580 pinboard_reshape_icon(icon
);