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 */
32 #include <gtk/gtkinvisible.h>
34 #include <libxml/parser.h>
45 #include "gui_support.h"
54 static gboolean tmp_icon_selected
= FALSE
; /* When dragging */
57 guchar
*name
; /* Leaf name */
62 #define IS_PIN_ICON(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), pin_icon_get_type())
64 typedef struct _PinIconClass PinIconClass
;
65 typedef struct _PinIcon PinIcon
;
67 struct _PinIconClass
{
74 int x
, y
, width
, height
;
76 PangoLayout
*layout
; /* The label */
78 GtkWidget
*widget
; /* The drawing area */
82 /* The number of pixels between the bottom of the image and the top
87 /* The size of the border around the icon which is used when winking */
91 #define GRID_STEP_FINE 2
92 #define GRID_STEP_MED 16
93 #define GRID_STEP_COARSE 32
95 static PinIcon
*current_wink_icon
= NULL
;
96 static gint wink_timeout
;
98 /* Used for the text colours (only) in the icons */
99 static GdkColor text_fg_col
, text_bg_col
;
101 /* Style that all the icons should use. NULL => regenerate from text_fg/bg */
102 static GtkStyle
*pinicon_style
= NULL
;
104 Pinboard
*current_pinboard
= NULL
;
105 static gint loading_pinboard
= 0; /* Non-zero => loading */
107 static GdkColor mask_solid
= {1, 1, 1, 1};
108 static GdkColor mask_transp
= {0, 0, 0, 0};
109 static GdkGC
*mask_gc
= NULL
;
111 /* Proxy window for DnD and clicks on the desktop */
112 static GtkWidget
*proxy_invisible
;
114 /* The window (owned by the wm) which root clicks are forwarded to.
115 * NULL if wm does not support forwarding clicks.
117 static GdkWindow
*click_proxy_gdk_window
= NULL
;
118 static GdkAtom win_button_proxy
; /* _WIN_DESKTOP_BUTTON_PROXY */
120 /* The Icon that was used to start the current drag, if any */
121 Icon
*pinboard_drag_in_progress
= NULL
;
123 /* Used when dragging icons around... */
124 static gboolean pinboard_modified
= FALSE
;
132 static Option o_pinboard_clamp_icons
, o_pinboard_grid_step
;
133 static Option o_pinboard_fg_colour
, o_pinboard_bg_colour
;
135 /* Static prototypes */
136 static GType
pin_icon_get_type(void);
137 static void set_size_and_shape(PinIcon
*pi
, int *rwidth
, int *rheight
);
138 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, PinIcon
*pi
);
139 static void mask_wink_border(PinIcon
*pi
, GdkColor
*alpha
);
140 static gint
end_wink(gpointer data
);
141 static gboolean
button_release_event(GtkWidget
*widget
,
142 GdkEventButton
*event
,
144 static gboolean
root_property_event(GtkWidget
*widget
,
145 GdkEventProperty
*event
,
147 static gboolean
root_button_press(GtkWidget
*widget
,
148 GdkEventButton
*event
,
150 static gboolean
enter_notify(GtkWidget
*widget
,
151 GdkEventCrossing
*event
,
153 static gboolean
button_press_event(GtkWidget
*widget
,
154 GdkEventButton
*event
,
156 static gint
icon_motion_notify(GtkWidget
*widget
,
157 GdkEventMotion
*event
,
159 static const char *pin_from_file(gchar
*line
);
160 static gboolean
add_root_handlers(void);
161 static GdkFilterReturn
proxy_filter(GdkXEvent
*xevent
,
164 static void snap_to_grid(int *x
, int *y
);
165 static void offset_from_centre(PinIcon
*pi
,
166 int width
, int height
,
168 static void offset_to_centre(PinIcon
*pi
,
169 int width
, int height
,
171 static gboolean
drag_motion(GtkWidget
*widget
,
172 GdkDragContext
*context
,
177 static void drag_set_pinicon_dest(PinIcon
*pi
);
178 static void drag_leave(GtkWidget
*widget
,
179 GdkDragContext
*context
,
182 static void forward_root_clicks(void);
183 static gboolean
bg_drag_motion(GtkWidget
*widget
,
184 GdkDragContext
*context
,
189 static gboolean
bg_drag_leave(GtkWidget
*widget
,
190 GdkDragContext
*context
,
193 static void bg_expose(GdkRectangle
*area
);
194 static void drag_end(GtkWidget
*widget
,
195 GdkDragContext
*context
,
197 static void reshape_all(void);
198 static void pinboard_check_options(void);
199 static void pinboard_load_from_xml(xmlDocPtr doc
);
200 static void pinboard_clear(void);
201 static void pinboard_save(void);
202 static PinIcon
*pin_icon_new(const char *pathname
, const char *name
);
203 static void pin_icon_destroyed(PinIcon
*pi
);
204 static void pin_icon_set_tip(PinIcon
*pi
);
205 static void pinboard_show_menu(GdkEventButton
*event
, PinIcon
*pi
);
206 static void merge_alpha(GdkPixbuf
*src
, GdkBitmap
*mask
, int x
, int y
);
208 /****************************************************************
209 * EXTERNAL INTERFACE *
210 ****************************************************************/
212 void pinboard_init(void)
214 option_add_string(&o_pinboard_fg_colour
, "pinboard_fg_colour", "#000");
215 option_add_string(&o_pinboard_bg_colour
, "pinboard_bg_colour", "#ddd");
217 option_add_int(&o_pinboard_clamp_icons
, "pinboard_clamp_icons", 1);
218 option_add_int(&o_pinboard_grid_step
, "pinboard_grid_step",
220 option_add_notify(pinboard_check_options
);
222 gdk_color_parse(o_pinboard_fg_colour
.value
, &text_fg_col
);
223 gdk_color_parse(o_pinboard_bg_colour
.value
, &text_bg_col
);
226 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
227 * and make it the current pinboard.
228 * Any existing pinned items are removed. You must call this
229 * at least once before using the pinboard. NULL disables the
232 void pinboard_activate(const gchar
*name
)
234 Pinboard
*old_board
= current_pinboard
;
235 guchar
*path
, *slash
;
237 /* Treat an empty name the same as NULL */
246 if (number_of_windows
< 1 && gtk_main_level() > 0)
251 if (!add_root_handlers())
253 delayed_error(_("Another application is already "
254 "managing the pinboard!"));
260 slash
= strchr(name
, '/');
263 if (access(name
, F_OK
))
264 path
= NULL
; /* File does not (yet) exist */
266 path
= g_strdup(name
);
272 leaf
= g_strconcat("pb_", name
, NULL
);
273 path
= choices_find_path_load(leaf
, PROJECT
);
277 current_pinboard
= g_new(Pinboard
, 1);
278 current_pinboard
->name
= g_strdup(name
);
279 current_pinboard
->icons
= NULL
;
285 doc
= xmlParseFile(path
);
288 pinboard_load_from_xml(doc
);
293 parse_file(path
, pin_from_file
);
294 info_message(_("Your old pinboard file has been "
295 "converted to the new XML format."));
301 pinboard_pin(home_dir
, "Home",
303 4 + ICON_HEIGHT
/ 2);
307 const char *pinboard_get_name(void)
309 g_return_val_if_fail(current_pinboard
!= NULL
, NULL
);
311 return current_pinboard
->name
;
314 /* Add a new icon to the background.
315 * 'path' should be an absolute pathname.
316 * 'x' and 'y' are the coordinates of the point in the middle of the text
317 * if 'corner' is FALSE, and as the top-left corner of where the icon
318 * image should be if it is TRUE.
319 * 'name' is the name to use. If NULL then the leafname of path is used.
321 * name and path are in UTF-8 for Gtk+-2.0 only.
323 void pinboard_pin(const gchar
*path
, const gchar
*name
, int x
, int y
)
329 g_return_if_fail(path
!= NULL
);
330 g_return_if_fail(current_pinboard
!= NULL
);
332 pi
= pin_icon_new(path
, name
);
337 /* Window takes the initial ref of Icon */
338 pi
->win
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
339 gtk_window_set_wmclass(GTK_WINDOW(pi
->win
), "ROX-Pinboard", PROJECT
);
341 pi
->widget
= gtk_drawing_area_new();
342 gtk_widget_set_name(pi
->widget
, "pinboard-icon");
344 pi
->layout
= gtk_widget_create_pango_layout(pi
->widget
, NULL
);
345 pango_layout_set_width(pi
->layout
, 140 * PANGO_SCALE
);
347 gtk_container_add(GTK_CONTAINER(pi
->win
), pi
->widget
);
348 drag_set_pinicon_dest(pi
);
349 g_signal_connect(pi
->widget
, "drag_data_get",
350 G_CALLBACK(drag_data_get
), NULL
);
352 gtk_widget_realize(pi
->win
);
353 gtk_widget_realize(pi
->widget
);
355 set_size_and_shape(pi
, &width
, &height
);
356 snap_to_grid(&x
, &y
);
357 offset_from_centre(pi
, width
, height
, &x
, &y
);
358 gtk_window_move(GTK_WINDOW(pi
->win
), x
, y
);
359 /* Set the correct position in the icon */
360 offset_to_centre(pi
, width
, height
, &x
, &y
);
364 make_panel_window(pi
->win
);
366 /* TODO: Use gdk function when it supports this type */
368 GdkAtom desktop_type
;
370 desktop_type
= gdk_atom_intern("_NET_WM_WINDOW_TYPE_DESKTOP",
372 gdk_property_change(pi
->win
->window
,
373 gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE
),
374 gdk_atom_intern("ATOM", FALSE
), 32,
375 GDK_PROP_MODE_REPLACE
, (guchar
*) &desktop_type
, 1);
378 gtk_widget_add_events(pi
->widget
,
379 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
380 GDK_BUTTON1_MOTION_MASK
| GDK_ENTER_NOTIFY_MASK
|
381 GDK_BUTTON2_MOTION_MASK
| GDK_BUTTON3_MOTION_MASK
);
382 g_signal_connect(pi
->widget
, "enter-notify-event",
383 G_CALLBACK(enter_notify
), pi
);
384 g_signal_connect(pi
->widget
, "button-press-event",
385 G_CALLBACK(button_press_event
), pi
);
386 g_signal_connect(pi
->widget
, "button-release-event",
387 G_CALLBACK(button_release_event
), pi
);
388 g_signal_connect(pi
->widget
, "motion-notify-event",
389 G_CALLBACK(icon_motion_notify
), pi
);
390 g_signal_connect(pi
->widget
, "expose-event",
391 G_CALLBACK(draw_icon
), pi
);
392 g_signal_connect_swapped(pi
->win
, "destroy",
393 G_CALLBACK(pin_icon_destroyed
), pi
);
395 current_pinboard
->icons
= g_list_prepend(current_pinboard
->icons
,
397 pin_icon_set_tip(pi
);
398 gtk_widget_show_all(pi
->win
);
399 gdk_window_lower(pi
->win
->window
);
401 if (!loading_pinboard
)
405 /* Remove an icon from the pinboard */
406 /* XXX: use destroy */
407 void pinboard_unpin(PinIcon
*pi
)
409 g_return_if_fail(pi
!= NULL
);
411 gtk_widget_destroy(pi
->win
);
415 /* Put a border around the icon, briefly.
416 * If icon is NULL then cancel any existing wink.
417 * The icon will automatically unhighlight unless timeout is FALSE,
418 * in which case you must call this function again (with NULL or another
419 * icon) to remove the highlight.
421 static void pinboard_wink_item(PinIcon
*pi
, gboolean timeout
)
423 if (current_wink_icon
== pi
)
426 if (current_wink_icon
)
428 mask_wink_border(current_wink_icon
, &mask_transp
);
429 if (wink_timeout
!= -1)
430 gtk_timeout_remove(wink_timeout
);
433 current_wink_icon
= pi
;
435 if (current_wink_icon
)
437 mask_wink_border(current_wink_icon
, &mask_solid
);
439 wink_timeout
= gtk_timeout_add(300, end_wink
, NULL
);
445 /* Icon's size, shape or appearance has changed - update the display */
446 void pinboard_reshape_icon(Icon
*icon
)
448 PinIcon
*pi
= (PinIcon
*) icon
;
449 int x
= pi
->x
, y
= pi
->y
;
452 set_size_and_shape(pi
, &width
, &height
);
453 gdk_window_resize(pi
->win
->window
, width
, height
);
454 offset_from_centre(pi
, width
, height
, &x
, &y
);
455 gtk_window_move(GTK_WINDOW(pi
->win
), x
, y
);
456 gtk_widget_queue_draw(pi
->win
);
460 /****************************************************************
461 * INTERNAL FUNCTIONS *
462 ****************************************************************/
464 static void pinboard_check_options(void)
468 gdk_color_parse(o_pinboard_fg_colour
.value
, &n_fg
);
469 gdk_color_parse(o_pinboard_bg_colour
.value
, &n_bg
);
471 if (gdk_color_equal(&n_fg
, &text_fg_col
) == 0 ||
472 gdk_color_equal(&n_bg
, &text_bg_col
) == 0)
474 memcpy(&text_fg_col
, &n_fg
, sizeof(GdkColor
));
475 memcpy(&text_bg_col
, &n_bg
, sizeof(GdkColor
));
479 g_object_unref(G_OBJECT(pinicon_style
));
480 pinicon_style
= NULL
;
483 if (current_pinboard
)
488 static gint
end_wink(gpointer data
)
490 pinboard_wink_item(NULL
, FALSE
);
494 /* Make the wink border solid or transparent */
495 static void mask_wink_border(PinIcon
*pi
, GdkColor
*alpha
)
497 if (!current_pinboard
)
500 gdk_gc_set_foreground(mask_gc
, alpha
);
501 gdk_draw_rectangle(pi
->mask
, mask_gc
, FALSE
,
502 0, 0, pi
->width
- 1, pi
->height
- 1);
503 gdk_draw_rectangle(pi
->mask
, mask_gc
, FALSE
,
504 1, 1, pi
->width
- 3, pi
->height
- 3);
506 gtk_widget_shape_combine_mask(pi
->win
, pi
->mask
, 0, 0);
508 gtk_widget_queue_draw(pi
->widget
);
509 gdk_window_process_updates(pi
->widget
->window
, TRUE
);
512 /* Updates the name_width and layout fields, and resizes and masks the window.
513 * Also sets the style to pinicon_style, generating it if needed.
514 * Returns the new width and height.
516 static void set_size_and_shape(PinIcon
*pi
, int *rwidth
, int *rheight
)
520 Icon
*icon
= (Icon
*) pi
;
521 MaskedPixmap
*image
= icon
->item
->image
;
522 int iwidth
= image
->width
;
523 int iheight
= image
->height
;
524 DirItem
*item
= icon
->item
;
529 pinicon_style
= gtk_style_copy(pi
->widget
->style
);
530 memcpy(&pinicon_style
->fg
[GTK_STATE_NORMAL
],
531 &text_fg_col
, sizeof(GdkColor
));
532 memcpy(&pinicon_style
->bg
[GTK_STATE_NORMAL
],
533 &text_bg_col
, sizeof(GdkColor
));
535 gtk_widget_set_style(pi
->widget
, pinicon_style
);
538 PangoRectangle logical
;
539 pango_layout_set_text(pi
->layout
, icon
->item
->leafname
, -1);
540 pango_layout_get_pixel_extents(pi
->layout
, NULL
, &logical
);
542 pi
->name_width
= logical
.width
- logical
.x
;
543 font_height
= logical
.height
- logical
.y
;
546 width
= MAX(iwidth
, pi
->name_width
+ 2) + 2 * WINK_FRAME
;
547 height
= iheight
+ GAP
+ (font_height
+ 2) + 2 * WINK_FRAME
;
548 gtk_widget_set_size_request(pi
->win
, width
, height
);
553 g_object_unref(pi
->mask
);
554 pi
->mask
= gdk_pixmap_new(pi
->win
->window
, width
, height
, 1);
556 mask_gc
= gdk_gc_new(pi
->mask
);
558 /* Clear the mask to transparent */
559 gdk_gc_set_foreground(mask_gc
, &mask_transp
);
560 gdk_draw_rectangle(pi
->mask
, mask_gc
, TRUE
, 0, 0, width
, height
);
562 gdk_gc_set_foreground(mask_gc
, &mask_solid
);
564 /* Make the icon area solid.
565 * Note that the highlighted version must have same alpha...
567 merge_alpha(image
->src_pixbuf
, pi
->mask
,
568 (width
- iwidth
) >> 1, WINK_FRAME
);
570 if (item
->flags
& ITEM_FLAG_SYMLINK
)
572 merge_alpha(im_symlink
->src_pixbuf
, pi
->mask
,
573 (width
- iwidth
) >> 1, WINK_FRAME
);
575 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
577 /* Note: Both mount state pixmaps must have the same mask */
578 merge_alpha(im_mounted
->src_pixbuf
, pi
->mask
,
579 (width
- iwidth
) >> 1, WINK_FRAME
);
582 /* Mask off an area for the text */
584 text_x
= (width
- pi
->name_width
) >> 1;
585 text_y
= WINK_FRAME
+ iheight
+ GAP
+ 1;
587 gdk_draw_rectangle(pi
->mask
, mask_gc
, TRUE
,
588 (width
- (pi
->name_width
+ 2)) >> 1,
589 WINK_FRAME
+ iheight
+ GAP
,
590 pi
->name_width
+ 2, font_height
+ 2);
592 gtk_widget_shape_combine_mask(pi
->win
, pi
->mask
, 0, 0);
598 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, PinIcon
*pi
)
601 Icon
*icon
= (Icon
*) pi
;
602 DirItem
*item
= icon
->item
;
603 MaskedPixmap
*image
= item
->image
;
604 int iwidth
= image
->width
;
605 int iheight
= image
->height
;
607 GtkStateType state
= icon
->selected
? GTK_STATE_SELECTED
609 PangoRectangle logical
;
611 image_x
= (pi
->width
- iwidth
) >> 1;
613 gdk_pixbuf_render_to_drawable_alpha(image
->pixbuf
,
616 image_x
, WINK_FRAME
, /* dest */
618 GDK_PIXBUF_ALPHA_FULL
, 128, /* (unused) */
619 GDK_RGB_DITHER_NORMAL
, 0, 0);
621 if (item
->flags
& ITEM_FLAG_SYMLINK
)
623 gdk_pixbuf_render_to_drawable_alpha(im_symlink
->pixbuf
,
628 GDK_PIXBUF_ALPHA_FULL
, 128, /* (unused) */
629 GDK_RGB_DITHER_NORMAL
, 0, 0);
631 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
633 MaskedPixmap
*mp
= item
->flags
& ITEM_FLAG_MOUNTED
637 gdk_pixbuf_render_to_drawable_alpha(mp
->pixbuf
,
642 GDK_PIXBUF_ALPHA_FULL
, 128, /* (unused) */
643 GDK_RGB_DITHER_NORMAL
, 0, 0);
646 text_x
= (pi
->width
- pi
->name_width
) >> 1;
647 text_y
= WINK_FRAME
+ iheight
+ GAP
+ 1;
649 pango_layout_get_pixel_extents(pi
->layout
, NULL
, &logical
);
651 gtk_paint_flat_box(widget
->style
, widget
->window
,
654 NULL
, widget
, "text",
658 logical
.height
- logical
.y
+ 2);
660 gtk_paint_layout(widget
->style
, widget
->window
,
662 FALSE
, NULL
, widget
, "text",
667 if (current_wink_icon
== pi
)
669 gdk_draw_rectangle(pi
->widget
->window
,
670 pi
->widget
->style
->white_gc
,
672 0, 0, pi
->width
- 1, pi
->height
- 1);
673 gdk_draw_rectangle(pi
->widget
->window
,
674 pi
->widget
->style
->black_gc
,
676 1, 1, pi
->width
- 3, pi
->height
- 3);
682 static gboolean
root_property_event(GtkWidget
*widget
,
683 GdkEventProperty
*event
,
686 if (event
->atom
== win_button_proxy
&&
687 event
->state
== GDK_PROPERTY_NEW_VALUE
)
689 /* Setup forwarding on the new proxy window, if possible */
690 forward_root_clicks();
696 static gboolean
root_button_press(GtkWidget
*widget
,
697 GdkEventButton
*event
,
702 action
= bind_lookup_bev(BIND_PINBOARD
, event
);
706 case ACT_CLEAR_SELECTION
:
707 icon_select_only(NULL
);
711 pinboard_show_menu(event
, NULL
);
716 g_warning("Unsupported action : %d\n", action
);
723 static gboolean
enter_notify(GtkWidget
*widget
,
724 GdkEventCrossing
*event
,
727 icon_may_update((Icon
*) pi
);
732 static void perform_action(PinIcon
*pi
, GdkEventButton
*event
)
735 Icon
*icon
= (Icon
*) pi
;
737 action
= bind_lookup_bev(BIND_PINBOARD_ICON
, event
);
743 pinboard_wink_item(pi
, TRUE
);
744 if (event
->type
== GDK_2BUTTON_PRESS
)
745 icon_set_selected(icon
, FALSE
);
746 run_diritem(icon
->path
, icon
->item
, NULL
, NULL
, FALSE
);
750 pinboard_wink_item(pi
, TRUE
);
751 if (event
->type
== GDK_2BUTTON_PRESS
)
752 icon_set_selected(icon
, FALSE
);
753 run_diritem(icon
->path
, icon
->item
, NULL
, NULL
, TRUE
);
757 pinboard_show_menu(event
, pi
);
759 case ACT_PRIME_AND_SELECT
:
761 icon_select_only(icon
);
762 dnd_motion_start(MOTION_READY_FOR_DND
);
764 case ACT_PRIME_AND_TOGGLE
:
765 icon_set_selected(icon
, !icon
->selected
);
766 dnd_motion_start(MOTION_READY_FOR_DND
);
768 case ACT_PRIME_FOR_DND
:
769 dnd_motion_start(MOTION_READY_FOR_DND
);
771 case ACT_TOGGLE_SELECTED
:
772 icon_set_selected(icon
, !icon
->selected
);
774 case ACT_SELECT_EXCL
:
775 icon_select_only(icon
);
780 g_warning("Unsupported action : %d\n", action
);
785 static gboolean
button_release_event(GtkWidget
*widget
,
786 GdkEventButton
*event
,
789 if (pinboard_modified
)
792 if (dnd_motion_release(event
))
795 perform_action(pi
, event
);
800 static gboolean
button_press_event(GtkWidget
*widget
,
801 GdkEventButton
*event
,
804 if (dnd_motion_press(widget
, event
))
805 perform_action(pi
, event
);
810 static void start_drag(PinIcon
*pi
, GdkEventMotion
*event
)
812 GtkWidget
*widget
= pi
->widget
;
813 Icon
*icon
= (Icon
*) pi
;
817 tmp_icon_selected
= TRUE
;
818 icon_select_only(icon
);
821 g_return_if_fail(icon_selection
!= NULL
);
823 pinboard_drag_in_progress
= icon
;
825 if (icon_selection
->next
== NULL
)
826 drag_one_item(widget
, event
, icon
->path
, icon
->item
, NULL
);
831 uri_list
= icon_create_uri_list();
832 drag_selection(widget
, event
, uri_list
);
837 /* An icon is being dragged around... */
838 static gint
icon_motion_notify(GtkWidget
*widget
,
839 GdkEventMotion
*event
,
842 if (motion_state
== MOTION_READY_FOR_DND
)
844 if (dnd_motion_moved(event
))
845 start_drag(pi
, event
);
852 /* Create one pinboard icon for each icon in the doc */
853 static void pinboard_load_from_xml(xmlDocPtr doc
)
855 xmlNodePtr node
, root
;
856 char *tmp
, *label
, *path
;
859 root
= xmlDocGetRootElement(doc
);
861 for (node
= root
->xmlChildrenNode
; node
; node
= node
->next
)
863 if (node
->type
!= XML_ELEMENT_NODE
)
865 if (strcmp(node
->name
, "icon") != 0)
868 tmp
= xmlGetProp(node
, "x");
874 tmp
= xmlGetProp(node
, "y");
880 label
= xmlGetProp(node
, "label");
882 label
= g_strdup("<missing label>");
883 path
= xmlNodeGetContent(node
);
885 path
= g_strdup("<missing path>");
887 pinboard_pin(path
, label
, x
, y
);
894 /* Called for each line in the pinboard file while loading a new board.
895 * Only used for old-format files when converting to XML.
897 static const char *pin_from_file(gchar
*line
)
906 end
= strchr(line
+ 1, '>');
908 return _("Missing '>' in icon label");
910 leaf
= g_strndup(line
+ 1, end
- line
- 1);
914 while (isspace(*line
))
917 return _("Missing ',' after icon label");
921 if (sscanf(line
, " %d , %d , %n", &x
, &y
, &n
) < 2)
922 return NULL
; /* Ignore format errors */
924 pinboard_pin(line
+ n
, leaf
, x
, y
);
931 /* Make sure that clicks and drops on the root window come to us...
932 * False if an error occurred (ie, someone else is using it).
934 static gboolean
add_root_handlers(void)
938 if (!proxy_invisible
)
940 win_button_proxy
= gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY",
942 proxy_invisible
= gtk_invisible_new();
943 gtk_widget_show(proxy_invisible
);
945 gdk_window_add_filter(proxy_invisible->window,
949 g_signal_connect(proxy_invisible
, "property_notify_event",
950 G_CALLBACK(root_property_event
), NULL
);
951 g_signal_connect(proxy_invisible
, "button_press_event",
952 G_CALLBACK(root_button_press
), NULL
);
954 /* Drag and drop handlers */
955 drag_set_pinboard_dest(proxy_invisible
);
956 g_signal_connect(proxy_invisible
, "drag_motion",
957 G_CALLBACK(bg_drag_motion
), NULL
);
958 g_signal_connect(proxy_invisible
, "drag_leave",
959 G_CALLBACK(bg_drag_leave
), NULL
);
962 root
= gdk_window_lookup(GDK_ROOT_WINDOW());
964 root
= gdk_window_foreign_new(GDK_ROOT_WINDOW());
966 if (!setup_xdnd_proxy(GDK_ROOT_WINDOW(), proxy_invisible
->window
))
969 /* Forward events from the root window to our proxy window */
970 gdk_window_add_filter(gdk_get_default_root_window(),
972 gdk_window_set_user_data(gdk_get_default_root_window(),
974 gdk_window_set_events(gdk_get_default_root_window(),
975 gdk_window_get_events(gdk_get_default_root_window()) |
977 GDK_PROPERTY_CHANGE_MASK
);
979 forward_root_clicks();
984 /* See if the window manager is offering to forward root window clicks.
985 * If so, grab them. Otherwise, do nothing.
986 * Call this whenever the _WIN_DESKTOP_BUTTON_PROXY property changes.
988 static void forward_root_clicks(void)
990 click_proxy_gdk_window
= find_click_proxy_window();
991 if (!click_proxy_gdk_window
)
994 /* Events on the wm's proxy are dealt with by our proxy widget */
995 gdk_window_set_user_data(click_proxy_gdk_window
, proxy_invisible
);
996 gdk_window_add_filter(click_proxy_gdk_window
, proxy_filter
, NULL
);
998 /* The proxy window for clicks sends us button press events with
999 * SubstructureNotifyMask. We need StructureNotifyMask to receive
1000 * DestroyNotify events, too.
1002 XSelectInput(GDK_DISPLAY(),
1003 GDK_WINDOW_XWINDOW(click_proxy_gdk_window
),
1004 SubstructureNotifyMask
| StructureNotifyMask
);
1007 /* Write the current state of the pinboard to the current pinboard file */
1008 static void pinboard_save(void)
1010 guchar
*save
= NULL
;
1011 guchar
*save_new
= NULL
;
1013 xmlDocPtr doc
= NULL
;
1016 g_return_if_fail(current_pinboard
!= NULL
);
1018 pinboard_modified
= FALSE
;
1020 if (strchr(current_pinboard
->name
, '/'))
1021 save
= g_strdup(current_pinboard
->name
);
1026 leaf
= g_strconcat("pb_", current_pinboard
->name
, NULL
);
1027 save
= choices_find_path_save(leaf
, PROJECT
, TRUE
);
1034 doc
= xmlNewDoc("1.0");
1035 xmlDocSetRootElement(doc
, xmlNewDocNode(doc
, NULL
, "pinboard", NULL
));
1037 root
= xmlDocGetRootElement(doc
);
1039 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
1042 PinIcon
*pi
= (PinIcon
*) next
->data
;
1043 Icon
*icon
= (Icon
*) pi
;
1046 tree
= xmlNewTextChild(root
, NULL
, "icon", icon
->src_path
);
1048 tmp
= g_strdup_printf("%d", pi
->x
);
1049 xmlSetProp(tree
, "x", tmp
);
1052 tmp
= g_strdup_printf("%d", pi
->y
);
1053 xmlSetProp(tree
, "y", tmp
);
1056 xmlSetProp(tree
, "label", icon
->item
->leafname
);
1059 save_new
= g_strconcat(save
, ".new", NULL
);
1060 if (save_xml_file(doc
, save_new
) || rename(save_new
, save
))
1061 delayed_error(_("Error saving pinboard %s: %s"),
1062 save
, g_strerror(errno
));
1071 * Filter that translates proxied events from virtual root windows into normal
1072 * Gdk events for the proxy_invisible widget. Stolen from gmc.
1074 * Also gets events from the root window.
1076 static GdkFilterReturn
proxy_filter(GdkXEvent
*xevent
,
1081 GdkWindow
*proxy
= proxy_invisible
->window
;
1086 switch (xev
->type
) {
1089 /* Translate button events into events that come from
1090 * the proxy window, so that we can catch them as a
1091 * signal from the invisible widget.
1093 if (xev
->type
== ButtonPress
)
1094 event
->button
.type
= GDK_BUTTON_PRESS
;
1096 event
->button
.type
= GDK_BUTTON_RELEASE
;
1098 g_object_ref(proxy
);
1100 event
->button
.window
= proxy
;
1101 event
->button
.send_event
= xev
->xbutton
.send_event
;
1102 event
->button
.time
= xev
->xbutton
.time
;
1103 event
->button
.x_root
= xev
->xbutton
.x_root
;
1104 event
->button
.y_root
= xev
->xbutton
.y_root
;
1105 event
->button
.x
= xev
->xbutton
.x
;
1106 event
->button
.y
= xev
->xbutton
.y
;
1107 event
->button
.state
= xev
->xbutton
.state
;
1108 event
->button
.button
= xev
->xbutton
.button
;
1109 event
->button
.axes
= NULL
;
1111 return GDK_FILTER_TRANSLATE
;
1114 area
.x
= xev
->xexpose
.x
;
1115 area
.y
= xev
->xexpose
.y
;
1116 area
.width
= xev
->xexpose
.width
;
1117 area
.height
= xev
->xexpose
.height
;
1119 return GDK_FILTER_REMOVE
;
1122 /* XXX: I have no idea why this helps, but it does! */
1123 /* The proxy window was destroyed (i.e. the window
1124 * manager died), so we have to cope with it
1126 if (((GdkEventAny
*) event
)->window
== proxy
)
1127 gdk_window_destroy_notify(proxy
);
1129 return GDK_FILTER_REMOVE
;
1135 return GDK_FILTER_CONTINUE
;
1138 static void snap_to_grid(int *x
, int *y
)
1140 int step
= o_pinboard_grid_step
.int_value
;
1142 *x
= ((*x
+ step
/ 2) / step
) * step
;
1143 *y
= ((*y
+ step
/ 2) / step
) * step
;
1146 /* Convert (x,y) from a centre point to a window position */
1147 static void offset_from_centre(PinIcon
*pi
,
1148 int width
, int height
,
1151 gboolean clamp
= o_pinboard_clamp_icons
.int_value
;
1155 *x
= CLAMP(*x
, 0, screen_width
- (clamp
? width
: 0));
1156 *y
= CLAMP(*y
, 0, screen_height
- (clamp
? height
: 0));
1159 /* Convert (x,y) from a window position to a centre point */
1160 static void offset_to_centre(PinIcon
*pi
,
1161 int width
, int height
,
1168 /* Same as drag_set_dest(), but for pinboard icons */
1169 static void drag_set_pinicon_dest(PinIcon
*pi
)
1171 GtkObject
*obj
= GTK_OBJECT(pi
->widget
);
1173 make_drop_target(pi
->widget
, 0);
1175 g_signal_connect(obj
, "drag_motion", G_CALLBACK(drag_motion
), pi
);
1176 g_signal_connect(obj
, "drag_leave", G_CALLBACK(drag_leave
), pi
);
1177 g_signal_connect(obj
, "drag_end", G_CALLBACK(drag_end
), pi
);
1180 /* Called during the drag when the mouse is in a widget registered
1181 * as a drop target. Returns TRUE if we can accept the drop.
1183 static gboolean
drag_motion(GtkWidget
*widget
,
1184 GdkDragContext
*context
,
1190 GdkDragAction action
= context
->suggested_action
;
1192 Icon
*icon
= (Icon
*) pi
;
1193 DirItem
*item
= icon
->item
;
1195 if (gtk_drag_get_source_widget(context
) == widget
)
1196 goto out
; /* Can't drag something to itself! */
1199 goto out
; /* Can't drag a selection to itself */
1201 type
= dnd_motion_item(context
, &item
);
1206 /* We actually must pretend to accept the drop, even if the
1207 * directory isn't writeable, so that the spring-opening
1211 /* Don't allow drops to non-writeable directories */
1212 if (o_dnd_spring_open
.int_value
== FALSE
&&
1213 type
== drop_dest_dir
&&
1214 access(icon
->path
, W_OK
) != 0)
1219 g_dataset_set_data(context
, "drop_dest_type", type
);
1222 gdk_drag_status(context
, action
, time
);
1223 g_dataset_set_data_full(context
, "drop_dest_path",
1224 g_strdup(icon
->path
), g_free
);
1225 if (type
== drop_dest_dir
)
1226 dnd_spring_load(context
, NULL
);
1228 pinboard_wink_item(pi
, FALSE
);
1231 return type
!= NULL
;
1234 static gboolean pinboard_shadow
= FALSE
;
1235 static gint shadow_x
, shadow_y
;
1236 #define SHADOW_SIZE (ICON_WIDTH)
1238 static void bg_expose(GdkRectangle
*area
)
1241 static GdkGC
*shadow_gc
= NULL
;
1242 static GdkColor white
, black
;
1244 if (!pinboard_shadow
)
1246 /* XXX: Should just disable the events */
1250 root
= gdk_get_default_root_window(); /* XXX */
1257 white
.red
= white
.green
= white
.blue
= 0xffff;
1258 black
.red
= black
.green
= black
.blue
= 0;
1260 cm
= gdk_drawable_get_colormap(root
);
1261 shadow_gc
= gdk_gc_new(root
);
1263 gdk_colormap_alloc_colors(cm
, &white
, 1, FALSE
, TRUE
, &success
);
1264 gdk_colormap_alloc_colors(cm
, &black
, 1, FALSE
, TRUE
, &success
);
1267 gdk_gc_set_clip_rectangle(shadow_gc
, area
);
1268 gdk_gc_set_foreground(shadow_gc
, &white
);
1269 gdk_draw_rectangle(root
, shadow_gc
, FALSE
, shadow_x
, shadow_y
,
1270 SHADOW_SIZE
, SHADOW_SIZE
);
1271 gdk_gc_set_foreground(shadow_gc
, &black
);
1272 gdk_draw_rectangle(root
, shadow_gc
, FALSE
, shadow_x
+ 1, shadow_y
+ 1,
1273 SHADOW_SIZE
- 2, SHADOW_SIZE
- 2);
1274 gdk_gc_set_clip_rectangle(shadow_gc
, NULL
);
1277 /* Draw a 'shadow' under an icon being dragged, showing where
1280 static void pinboard_set_shadow(gboolean on
)
1284 root
= gdk_get_default_root_window();
1286 if (pinboard_shadow
)
1288 gdk_window_clear_area_e(root
, shadow_x
, shadow_y
,
1289 SHADOW_SIZE
+ 1, SHADOW_SIZE
+ 1);
1294 int old_x
= shadow_x
, old_y
= shadow_y
;
1296 gdk_window_get_pointer(root
, &shadow_x
, &shadow_y
, NULL
);
1297 snap_to_grid(&shadow_x
, &shadow_y
);
1298 shadow_x
-= SHADOW_SIZE
/ 2;
1299 shadow_y
-= SHADOW_SIZE
/ 2;
1302 if (pinboard_shadow
&& shadow_x
== old_x
&& shadow_y
== old_y
)
1305 gdk_window_clear_area_e(root
, shadow_x
, shadow_y
,
1306 SHADOW_SIZE
+ 1, SHADOW_SIZE
+ 1);
1309 pinboard_shadow
= on
;
1312 /* Called when dragging some pinboard icons finishes */
1313 void pinboard_move_icons(void)
1315 int x
= shadow_x
, y
= shadow_y
;
1316 PinIcon
*pi
= (PinIcon
*) pinboard_drag_in_progress
;
1319 g_return_if_fail(pi
!= NULL
);
1321 x
+= SHADOW_SIZE
/ 2;
1322 y
+= SHADOW_SIZE
/ 2;
1323 snap_to_grid(&x
, &y
);
1325 if (pi
->x
== x
&& pi
->y
== y
)
1330 gdk_drawable_get_size(pi
->win
->window
, &width
, &height
);
1331 offset_from_centre(pi
, width
, height
, &x
, &y
);
1333 gdk_window_move(pi
->win
->window
, x
, y
);
1338 static void drag_leave(GtkWidget
*widget
,
1339 GdkDragContext
*context
,
1343 pinboard_wink_item(NULL
, FALSE
);
1347 static gboolean
bg_drag_leave(GtkWidget
*widget
,
1348 GdkDragContext
*context
,
1352 pinboard_set_shadow(FALSE
);
1357 static gboolean
bg_drag_motion(GtkWidget
*widget
,
1358 GdkDragContext
*context
,
1364 /* Dragging from the pinboard to the pinboard is not allowed */
1366 if (!provides(context
, text_uri_list
))
1369 pinboard_set_shadow(TRUE
);
1371 gdk_drag_status(context
,
1372 context
->suggested_action
== GDK_ACTION_ASK
1373 ? GDK_ACTION_LINK
: context
->suggested_action
,
1378 static void drag_end(GtkWidget
*widget
,
1379 GdkDragContext
*context
,
1382 pinboard_drag_in_progress
= NULL
;
1383 if (tmp_icon_selected
)
1385 icon_select_only(NULL
);
1386 tmp_icon_selected
= FALSE
;
1390 /* Something which affects all the icons has changed - reshape
1391 * and redraw all of them.
1393 static void reshape_all(void)
1397 g_return_if_fail(current_pinboard
!= NULL
);
1399 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
1401 Icon
*icon
= (Icon
*) next
->data
;
1402 pinboard_reshape_icon(icon
);
1406 /* Turns off the pinboard. Does not call gtk_main_quit. */
1407 static void pinboard_clear(void)
1411 g_return_if_fail(current_pinboard
!= NULL
);
1413 next
= current_pinboard
->icons
;
1416 PinIcon
*pi
= (PinIcon
*) next
->data
;
1420 gtk_widget_destroy(pi
->win
);
1423 g_free(current_pinboard
->name
);
1424 g_free(current_pinboard
);
1425 current_pinboard
= NULL
;
1427 release_xdnd_proxy(GDK_ROOT_WINDOW());
1428 gdk_window_remove_filter(gdk_get_default_root_window(),
1429 proxy_filter
, NULL
);
1430 gdk_window_set_user_data(gdk_get_default_root_window(), NULL
);
1432 number_of_windows
--;
1435 static gpointer parent_class
;
1437 static void pin_icon_destroy(Icon
*icon
)
1439 PinIcon
*pi
= (PinIcon
*) icon
;
1441 g_return_if_fail(pi
->win
!= NULL
);
1443 gtk_widget_destroy(pi
->win
);
1446 static void pin_icon_finalize(GObject
*icon
)
1448 PinIcon
*pi
= (PinIcon
*) icon
;
1452 g_object_unref(G_OBJECT(pi
->layout
));
1456 ((GObjectClass
*) parent_class
)->finalize(icon
);
1459 static void pinboard_remove_items(void)
1461 g_return_if_fail(icon_selection
!= NULL
);
1463 while (icon_selection
)
1464 icon_destroy((Icon
*) icon_selection
->data
);
1469 static void pin_icon_update(Icon
*icon
)
1471 pinboard_reshape_icon(icon
);
1475 static gboolean
pin_icon_same_group(Icon
*icon
, Icon
*other
)
1477 return IS_PIN_ICON(other
);
1480 static void pin_icon_class_init(gpointer gclass
, gpointer data
)
1482 IconClass
*icon
= (IconClass
*) gclass
;
1484 parent_class
= g_type_class_peek_parent(gclass
);
1486 ((GObjectClass
*) icon
)->finalize
= pin_icon_finalize
;
1488 icon
->destroy
= pin_icon_destroy
;
1489 icon
->redraw
= pinboard_reshape_icon
;
1490 icon
->update
= pin_icon_update
;
1491 icon
->remove_items
= pinboard_remove_items
;
1492 icon
->same_group
= pin_icon_same_group
;
1495 static void pin_icon_init(GTypeInstance
*object
, gpointer gclass
)
1497 PinIcon
*pi
= (PinIcon
*) object
;
1503 static GType
pin_icon_get_type(void)
1505 static GType type
= 0;
1509 static const GTypeInfo info
=
1511 sizeof (PinIconClass
),
1512 NULL
, /* base_init */
1513 NULL
, /* base_finalise */
1514 pin_icon_class_init
,
1515 NULL
, /* class_finalise */
1516 NULL
, /* class_data */
1518 0, /* n_preallocs */
1522 type
= g_type_register_static(icon_get_type(),
1523 "PinIcon", &info
, 0);
1529 static PinIcon
*pin_icon_new(const char *pathname
, const char *name
)
1534 pi
= g_object_new(pin_icon_get_type(), NULL
);
1537 icon_set_path(icon
, pathname
, name
);
1542 /* Called when the window widget is somehow destroyed */
1543 static void pin_icon_destroyed(PinIcon
*pi
)
1545 g_return_if_fail(pi
->win
!= NULL
);
1549 pinboard_wink_item(NULL
, FALSE
);
1551 if (pinboard_drag_in_progress
== (Icon
*) pi
)
1552 pinboard_drag_in_progress
= NULL
;
1554 if (current_pinboard
)
1555 current_pinboard
->icons
=
1556 g_list_remove(current_pinboard
->icons
, pi
);
1561 /* Set the tooltip */
1562 static void pin_icon_set_tip(PinIcon
*pi
)
1566 Icon
*icon
= (Icon
*) pi
;
1568 g_return_if_fail(pi
!= NULL
);
1570 ai
= appinfo_get(icon
->path
, icon
->item
);
1572 if (ai
&& ((node
= appinfo_get_section(ai
, "Summary"))))
1575 str
= xmlNodeListGetString(node
->doc
,
1576 node
->xmlChildrenNode
, 1);
1579 gtk_tooltips_set_tip(tooltips
, pi
->win
, str
, NULL
);
1584 gtk_tooltips_set_tip(tooltips
, pi
->widget
, NULL
, NULL
);
1590 static void pinboard_show_menu(GdkEventButton
*event
, PinIcon
*pi
)
1594 pos
[0] = event
->x_root
;
1595 pos
[1] = event
->y_root
;
1598 icon_prepare_menu((Icon
*) pi
);
1600 gtk_menu_popup(GTK_MENU(icon_menu
), NULL
, NULL
,
1602 (gpointer
) pos
, event
->button
, event
->time
);
1605 /* Render the mask of 'src' onto 'mask' at (x, y) */
1606 static void merge_alpha(GdkPixbuf
*src
, GdkBitmap
*mask
, int x
, int y
)
1611 GdkBitmap
*src_mask
;
1613 gc
= gdk_gc_new(mask
);
1615 gdk_gc_set_foreground(gc
, &color
);
1617 gdk_pixbuf_render_pixmap_and_mask(src
, &pixmap
, &src_mask
, 10);
1618 g_object_unref(pixmap
);
1622 gdk_gc_set_function(gc
, GDK_OR
);
1623 gdk_draw_drawable(mask
, gc
, src_mask
, 0, 0, x
, y
, -1, -1);
1624 g_object_unref(src_mask
);
1627 gdk_draw_rectangle(mask
, gc
, TRUE
, x
, y
,
1628 gdk_pixbuf_get_width(src
), gdk_pixbuf_get_height(src
));