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"
55 static gboolean tmp_icon_selected
= FALSE
; /* When dragging */
58 guchar
*name
; /* Leaf name */
62 BackdropStyle backdrop_style
;
63 gchar
*backdrop
; /* Pathname */
65 GtkWidget
*window
; /* Screen-sized window */
69 #define IS_PIN_ICON(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), pin_icon_get_type())
71 typedef struct _PinIconClass PinIconClass
;
72 typedef struct _PinIcon PinIcon
;
74 struct _PinIconClass
{
81 int x
, y
, width
, height
;
83 PangoLayout
*layout
; /* The label */
85 GtkWidget
*widget
; /* The drawing area */
88 /* The number of pixels between the bottom of the image and the top
93 /* The size of the border around the icon which is used when winking */
97 #define GRID_STEP_FINE 2
98 #define GRID_STEP_MED 16
99 #define GRID_STEP_COARSE 32
101 static PinIcon
*current_wink_icon
= NULL
;
102 static gint wink_timeout
;
104 /* Used for the text colours (only) in the icons */
105 static GdkColor text_fg_col
, text_bg_col
;
107 /* Style that all the icons should use. NULL => regenerate from text_fg/bg */
108 static GtkStyle
*pinicon_style
= NULL
;
110 Pinboard
*current_pinboard
= NULL
;
111 static gint loading_pinboard
= 0; /* Non-zero => loading */
113 /* The Icon that was used to start the current drag, if any */
114 Icon
*pinboard_drag_in_progress
= NULL
;
122 static Option o_pinboard_clamp_icons
, o_pinboard_grid_step
;
123 static Option o_pinboard_fg_colour
, o_pinboard_bg_colour
;
125 /* Static prototypes */
126 static GType
pin_icon_get_type(void);
127 static void set_size_and_style(PinIcon
*pi
);
128 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, PinIcon
*pi
);
129 static gint
end_wink(gpointer data
);
130 static gboolean
button_release_event(GtkWidget
*widget
,
131 GdkEventButton
*event
,
133 static gboolean
enter_notify(GtkWidget
*widget
,
134 GdkEventCrossing
*event
,
136 static gboolean
button_press_event(GtkWidget
*widget
,
137 GdkEventButton
*event
,
139 static gint
icon_motion_notify(GtkWidget
*widget
,
140 GdkEventMotion
*event
,
142 static const char *pin_from_file(gchar
*line
);
143 static void snap_to_grid(int *x
, int *y
);
144 static void offset_from_centre(PinIcon
*pi
,
145 int width
, int height
,
147 static void offset_to_centre(PinIcon
*pi
,
148 int width
, int height
,
150 static gboolean
drag_motion(GtkWidget
*widget
,
151 GdkDragContext
*context
,
156 static void drag_set_pinicon_dest(PinIcon
*pi
);
157 static void drag_leave(GtkWidget
*widget
,
158 GdkDragContext
*context
,
161 static gboolean
bg_drag_motion(GtkWidget
*widget
,
162 GdkDragContext
*context
,
167 static gboolean
bg_drag_leave(GtkWidget
*widget
,
168 GdkDragContext
*context
,
171 static void bg_expose(GtkWidget
*window
, GdkEventExpose
*event
, gpointer data
);
172 static void drag_end(GtkWidget
*widget
,
173 GdkDragContext
*context
,
175 static void reshape_all(void);
176 static void pinboard_check_options(void);
177 static void pinboard_load_from_xml(xmlDocPtr doc
);
178 static void pinboard_clear(void);
179 static void pinboard_save(void);
180 static PinIcon
*pin_icon_new(const char *pathname
, const char *name
);
181 static void pin_icon_destroyed(PinIcon
*pi
);
182 static void pin_icon_set_tip(PinIcon
*pi
);
183 static void pinboard_show_menu(GdkEventButton
*event
, PinIcon
*pi
);
184 static void create_pinboard_window(Pinboard
*pinboard
);
185 static void reload_backdrop(Pinboard
*pinboard
);
186 static void set_backdrop(Pinboard
*pinboard
, const gchar
*image
,
187 const gchar
*path
, BackdropStyle style
);
189 /****************************************************************
190 * EXTERNAL INTERFACE *
191 ****************************************************************/
193 void pinboard_init(void)
195 option_add_string(&o_pinboard_fg_colour
, "pinboard_fg_colour", "#000");
196 option_add_string(&o_pinboard_bg_colour
, "pinboard_bg_colour", "#ddd");
198 option_add_int(&o_pinboard_clamp_icons
, "pinboard_clamp_icons", 1);
199 option_add_int(&o_pinboard_grid_step
, "pinboard_grid_step",
201 option_add_notify(pinboard_check_options
);
203 gdk_color_parse(o_pinboard_fg_colour
.value
, &text_fg_col
);
204 gdk_color_parse(o_pinboard_bg_colour
.value
, &text_bg_col
);
207 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
208 * and make it the current pinboard.
209 * Any existing pinned items are removed. You must call this
210 * at least once before using the pinboard. NULL disables the
213 void pinboard_activate(const gchar
*name
)
215 Pinboard
*old_board
= current_pinboard
;
216 guchar
*path
, *slash
;
218 /* Treat an empty name the same as NULL */
227 if (number_of_windows
< 1 && gtk_main_level() > 0)
234 slash
= strchr(name
, '/');
237 if (access(name
, F_OK
))
238 path
= NULL
; /* File does not (yet) exist */
240 path
= g_strdup(name
);
246 leaf
= g_strconcat("pb_", name
, NULL
);
247 path
= choices_find_path_load(leaf
, PROJECT
);
251 current_pinboard
= g_new(Pinboard
, 1);
252 current_pinboard
->name
= g_strdup(name
);
253 current_pinboard
->icons
= NULL
;
254 current_pinboard
->window
= NULL
;
255 current_pinboard
->backdrop
= NULL
;
256 current_pinboard
->backdrop_style
= BACKDROP_NONE
;
258 create_pinboard_window(current_pinboard
);
264 doc
= xmlParseFile(path
);
267 pinboard_load_from_xml(doc
);
269 reload_backdrop(current_pinboard
);
273 parse_file(path
, pin_from_file
);
274 info_message(_("Your old pinboard file has been "
275 "converted to the new XML format."));
281 pinboard_pin(home_dir
, "Home",
283 4 + ICON_HEIGHT
/ 2);
287 const char *pinboard_get_name(void)
289 g_return_val_if_fail(current_pinboard
!= NULL
, NULL
);
291 return current_pinboard
->name
;
294 /* Add a new icon to the background.
295 * 'path' should be an absolute pathname.
296 * 'x' and 'y' are the coordinates of the point in the middle of the text
297 * if 'corner' is FALSE, and as the top-left corner of where the icon
298 * image should be if it is TRUE.
299 * 'name' is the name to use. If NULL then the leafname of path is used.
301 * name and path are in UTF-8 for Gtk+-2.0 only.
303 void pinboard_pin(const gchar
*path
, const gchar
*name
, int x
, int y
)
308 g_return_if_fail(path
!= NULL
);
309 g_return_if_fail(current_pinboard
!= NULL
);
311 pi
= pin_icon_new(path
, name
);
316 /* Box takes the initial ref of Icon */
317 pi
->win
= gtk_event_box_new();
319 pi
->widget
= gtk_drawing_area_new();
321 pi
->layout
= gtk_widget_create_pango_layout(pi
->widget
, NULL
);
322 pango_layout_set_width(pi
->layout
, 140 * PANGO_SCALE
);
324 gtk_container_add(GTK_CONTAINER(pi
->win
), pi
->widget
);
325 drag_set_pinicon_dest(pi
);
326 g_signal_connect(pi
->widget
, "drag_data_get",
327 G_CALLBACK(drag_data_get
), NULL
);
329 gtk_fixed_put(GTK_FIXED(current_pinboard
->fixed
), pi
->win
, x
, y
);
330 gtk_widget_realize(pi
->win
);
331 gtk_widget_realize(pi
->widget
);
333 set_size_and_style(pi
);
334 snap_to_grid(&x
, &y
);
335 offset_from_centre(pi
, pi
->width
, pi
->height
, &x
, &y
);
336 gtk_fixed_move(GTK_FIXED(current_pinboard
->fixed
), pi
->win
, x
, y
);
337 /* Set the correct position in the icon */
338 offset_to_centre(pi
, pi
->width
, pi
->height
, &x
, &y
);
342 gtk_widget_add_events(pi
->widget
,
343 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
344 GDK_BUTTON1_MOTION_MASK
| GDK_ENTER_NOTIFY_MASK
|
345 GDK_BUTTON2_MOTION_MASK
| GDK_BUTTON3_MOTION_MASK
);
346 g_signal_connect(pi
->widget
, "enter-notify-event",
347 G_CALLBACK(enter_notify
), pi
);
348 g_signal_connect(pi
->widget
, "button-press-event",
349 G_CALLBACK(button_press_event
), pi
);
350 g_signal_connect(pi
->widget
, "button-release-event",
351 G_CALLBACK(button_release_event
), pi
);
352 g_signal_connect(pi
->widget
, "motion-notify-event",
353 G_CALLBACK(icon_motion_notify
), pi
);
354 g_signal_connect(pi
->widget
, "expose-event",
355 G_CALLBACK(draw_icon
), pi
);
356 g_signal_connect_swapped(pi
->win
, "destroy",
357 G_CALLBACK(pin_icon_destroyed
), pi
);
359 current_pinboard
->icons
= g_list_prepend(current_pinboard
->icons
, pi
);
360 pin_icon_set_tip(pi
);
361 gtk_widget_show_all(pi
->win
);
363 if (!loading_pinboard
)
367 /* Remove an icon from the pinboard */
368 /* XXX: use destroy */
369 void pinboard_unpin(PinIcon
*pi
)
371 g_return_if_fail(pi
!= NULL
);
373 gtk_widget_destroy(pi
->win
);
377 /* Put a border around the icon, briefly.
378 * If icon is NULL then cancel any existing wink.
379 * The icon will automatically unhighlight unless timeout is FALSE,
380 * in which case you must call this function again (with NULL or another
381 * icon) to remove the highlight.
383 static void pinboard_wink_item(PinIcon
*pi
, gboolean timeout
)
385 PinIcon
*old
= current_wink_icon
;
390 current_wink_icon
= pi
;
394 gtk_widget_queue_draw(old
->widget
);
395 gdk_window_process_updates(old
->widget
->window
, TRUE
);
397 if (wink_timeout
!= -1)
398 gtk_timeout_remove(wink_timeout
);
403 gtk_widget_queue_draw(pi
->widget
);
404 gdk_window_process_updates(pi
->widget
->window
, TRUE
);
407 wink_timeout
= gtk_timeout_add(300, end_wink
, NULL
);
413 /* Icon's size, shape or appearance has changed - update the display */
414 void pinboard_reshape_icon(Icon
*icon
)
416 PinIcon
*pi
= (PinIcon
*) icon
;
417 int x
= pi
->x
, y
= pi
->y
;
419 set_size_and_style(pi
);
420 gdk_window_resize(pi
->win
->window
, pi
->width
, pi
->height
);
421 offset_from_centre(pi
, pi
->width
, pi
->height
, &x
, &y
);
422 gtk_fixed_move(GTK_FIXED(current_pinboard
->fixed
), pi
->win
, x
, y
);
423 gtk_widget_queue_draw(current_pinboard
->fixed
);
426 /* Sets 'app' as the new backdrop handler, and loads 'image'.
427 * If image is NULL, will run program to get image.
429 void pinboard_set_backdrop_from_program(const gchar
*app
, const gchar
*image
,
437 style
= BACKDROP_PROGRAM
;
439 item
= diritem_new("");
440 diritem_restat(app
, item
, NULL
);
441 ai
= appinfo_get(app
, item
);
444 can_set
= ai
&& xml_get_section(ai
, ROX_NS
, "CanSetBackdrop") != NULL
;
449 set_backdrop(current_pinboard
, image
, app
, style
);
452 delayed_error(_("This program does not know how to "
453 "manage ROX-Filer's backdrop image."));
458 /* Use this icon / program as the backdrop for the current pinboard.
459 * NULL removes the backdrop. If type is BACKDROP_NONE then will try
460 * to work it out ourself.
461 * If no pinboard is in use, activates 'Default'.
463 void pinboard_set_backdrop(DirItem
*item
, const gchar
*path
)
465 BackdropStyle type
= BACKDROP_NONE
;
467 if (path
== NULL
|| item
== NULL
)
469 /* Remove backdrop */
471 if (current_pinboard
&& current_pinboard
->backdrop
)
472 set_backdrop(current_pinboard
,
473 NULL
, NULL
, BACKDROP_NONE
);
475 delayed_error(_("No backdrop image is currently "
476 "set. Use the 'Use for Backdrop' menu "
477 "item to set an image (or program) for "
482 if (item
->flags
& ITEM_FLAG_APPDIR
)
484 pinboard_set_backdrop_from_program(path
, NULL
, BACKDROP_NONE
);
488 if (type
== BACKDROP_NONE
&& item
->base_type
== TYPE_FILE
)
491 i
= get_choice(_("Set backdrop"),
492 _("How should this image be displayed?"),
494 _("Centred"), _("_Scaled"), _("Tiled"));
497 type
= i
== 1 ? BACKDROP_CENTRE
:
498 i
== 2 ? BACKDROP_SCALE
:
502 if (type
== BACKDROP_NONE
)
504 delayed_error(_("Only files and certain applications can be "
505 "used to set the background image."));
509 if (!current_pinboard
)
511 pinboard_activate("Default");
512 delayed_error(_("No pinboard was in use... "
513 "the 'Default' pinboard has been selected. "
514 "Use 'rox -p=Default' to turn it on in "
518 set_backdrop(current_pinboard
, NULL
, path
, type
);
521 /****************************************************************
522 * INTERNAL FUNCTIONS *
523 ****************************************************************/
525 static void pinboard_check_options(void)
529 gdk_color_parse(o_pinboard_fg_colour
.value
, &n_fg
);
530 gdk_color_parse(o_pinboard_bg_colour
.value
, &n_bg
);
532 if (gdk_color_equal(&n_fg
, &text_fg_col
) == 0 ||
533 gdk_color_equal(&n_bg
, &text_bg_col
) == 0)
535 memcpy(&text_fg_col
, &n_fg
, sizeof(GdkColor
));
536 memcpy(&text_bg_col
, &n_bg
, sizeof(GdkColor
));
540 g_object_unref(G_OBJECT(pinicon_style
));
541 pinicon_style
= NULL
;
544 if (current_pinboard
)
549 static gint
end_wink(gpointer data
)
551 pinboard_wink_item(NULL
, FALSE
);
555 /* Updates the width, height, name_width and layout fields, and resizes the
556 * window. Also sets the style to pinicon_style, generating it if needed.
558 static void set_size_and_style(PinIcon
*pi
)
562 Icon
*icon
= (Icon
*) pi
;
563 MaskedPixmap
*image
= icon
->item
->image
;
564 int iwidth
= image
->width
;
565 int iheight
= image
->height
;
569 pinicon_style
= gtk_style_copy(pi
->widget
->style
);
570 memcpy(&pinicon_style
->fg
[GTK_STATE_NORMAL
],
571 &text_fg_col
, sizeof(GdkColor
));
572 memcpy(&pinicon_style
->bg
[GTK_STATE_NORMAL
],
573 &text_bg_col
, sizeof(GdkColor
));
577 gtk_widget_set_style(pi
->widget
, pinicon_style
);
581 PangoRectangle logical
;
582 pango_layout_set_text(pi
->layout
, icon
->item
->leafname
, -1);
583 pango_layout_get_pixel_extents(pi
->layout
, NULL
, &logical
);
585 pi
->name_width
= logical
.width
- logical
.x
;
586 font_height
= logical
.height
- logical
.y
;
589 width
= MAX(iwidth
, pi
->name_width
+ 2) + 2 * WINK_FRAME
;
590 height
= iheight
+ GAP
+ (font_height
+ 2) + 2 * WINK_FRAME
;
591 gtk_widget_set_size_request(pi
->win
, width
, height
);
596 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, PinIcon
*pi
)
599 Icon
*icon
= (Icon
*) pi
;
600 DirItem
*item
= icon
->item
;
601 MaskedPixmap
*image
= item
->image
;
602 int iwidth
= image
->width
;
603 int iheight
= image
->height
;
605 GtkStateType state
= icon
->selected
? GTK_STATE_SELECTED
607 PangoRectangle logical
;
609 image_x
= (pi
->width
- iwidth
) >> 1;
611 gdk_pixbuf_render_to_drawable_alpha(
612 icon
->selected
? image
->pixbuf_lit
: image
->pixbuf
,
615 image_x
, WINK_FRAME
, /* dest */
617 GDK_PIXBUF_ALPHA_FULL
, 128, /* (unused) */
618 GDK_RGB_DITHER_NORMAL
, 0, 0);
620 if (item
->flags
& ITEM_FLAG_SYMLINK
)
622 gdk_pixbuf_render_to_drawable_alpha(im_symlink
->pixbuf
,
627 GDK_PIXBUF_ALPHA_FULL
, 128, /* (unused) */
628 GDK_RGB_DITHER_NORMAL
, 0, 0);
630 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
632 MaskedPixmap
*mp
= item
->flags
& ITEM_FLAG_MOUNTED
636 gdk_pixbuf_render_to_drawable_alpha(mp
->pixbuf
,
641 GDK_PIXBUF_ALPHA_FULL
, 128, /* (unused) */
642 GDK_RGB_DITHER_NORMAL
, 0, 0);
645 text_x
= (pi
->width
- pi
->name_width
) >> 1;
646 text_y
= WINK_FRAME
+ iheight
+ GAP
+ 1;
648 pango_layout_get_pixel_extents(pi
->layout
, NULL
, &logical
);
650 gtk_paint_flat_box(widget
->style
, widget
->window
,
653 NULL
, widget
, "text",
657 logical
.height
- logical
.y
+ 2);
659 gtk_paint_layout(widget
->style
, widget
->window
,
661 FALSE
, NULL
, widget
, "text",
666 if (current_wink_icon
== pi
)
668 gdk_draw_rectangle(pi
->widget
->window
,
669 pi
->widget
->style
->white_gc
,
671 0, 0, pi
->width
- 1, pi
->height
- 1);
672 gdk_draw_rectangle(pi
->widget
->window
,
673 pi
->widget
->style
->black_gc
,
675 1, 1, pi
->width
- 3, pi
->height
- 3);
681 static gboolean
enter_notify(GtkWidget
*widget
,
682 GdkEventCrossing
*event
,
685 icon_may_update((Icon
*) pi
);
690 static void perform_action(PinIcon
*pi
, GdkEventButton
*event
)
693 Icon
*icon
= (Icon
*) pi
;
695 action
= bind_lookup_bev(pi
? BIND_PINBOARD_ICON
: BIND_PINBOARD
,
698 /* Actions that can happen with or without an icon */
701 case ACT_CLEAR_SELECTION
:
702 icon_select_only(NULL
);
706 pinboard_show_menu(event
, pi
);
714 g_return_if_fail(pi
!= NULL
);
720 pinboard_wink_item(pi
, TRUE
);
721 if (event
->type
== GDK_2BUTTON_PRESS
)
722 icon_set_selected(icon
, FALSE
);
723 run_diritem(icon
->path
, icon
->item
, NULL
, NULL
, FALSE
);
727 pinboard_wink_item(pi
, TRUE
);
728 if (event
->type
== GDK_2BUTTON_PRESS
)
729 icon_set_selected(icon
, FALSE
);
730 run_diritem(icon
->path
, icon
->item
, NULL
, NULL
, TRUE
);
732 case ACT_PRIME_AND_SELECT
:
734 icon_select_only(icon
);
735 dnd_motion_start(MOTION_READY_FOR_DND
);
737 case ACT_PRIME_AND_TOGGLE
:
738 icon_set_selected(icon
, !icon
->selected
);
739 dnd_motion_start(MOTION_READY_FOR_DND
);
741 case ACT_PRIME_FOR_DND
:
742 dnd_motion_start(MOTION_READY_FOR_DND
);
744 case ACT_TOGGLE_SELECTED
:
745 icon_set_selected(icon
, !icon
->selected
);
747 case ACT_SELECT_EXCL
:
748 icon_select_only(icon
);
751 g_warning("Unsupported action : %d\n", action
);
756 static void forward_to_root(GdkEventButton
*event
)
760 if (event
->type
== GDK_BUTTON_PRESS
)
762 xev
.type
= ButtonPress
;
763 XUngrabPointer(gdk_display
, event
->time
);
766 xev
.type
= ButtonRelease
;
768 xev
.window
= gdk_x11_get_default_root_xwindow();
769 xev
.root
= xev
.window
;
770 xev
.subwindow
= None
;
771 xev
.time
= event
->time
;
774 xev
.x_root
= event
->x_root
;
775 xev
.y_root
= event
->y_root
;
776 xev
.state
= event
->state
;
777 xev
.button
= event
->button
;
778 xev
.same_screen
= True
;
780 XSendEvent(gdk_display
, xev
.window
, False
,
781 ButtonPressMask
| ButtonReleaseMask
, (XEvent
*) &xev
);
784 /* pi is NULL if this is a root event */
785 static gboolean
button_release_event(GtkWidget
*widget
,
786 GdkEventButton
*event
,
789 if (event
->button
== 2)
790 forward_to_root(event
);
791 else if (dnd_motion_release(event
))
794 perform_action(pi
, event
);
799 /* pi is NULL if this is a root event */
800 static gboolean
button_press_event(GtkWidget
*widget
,
801 GdkEventButton
*event
,
804 if (event
->button
== 2)
805 forward_to_root(event
);
806 else if (dnd_motion_press(widget
, event
))
807 perform_action(pi
, event
);
812 static void start_drag(PinIcon
*pi
, GdkEventMotion
*event
)
814 GtkWidget
*widget
= pi
->widget
;
815 Icon
*icon
= (Icon
*) pi
;
819 tmp_icon_selected
= TRUE
;
820 icon_select_only(icon
);
823 g_return_if_fail(icon_selection
!= NULL
);
825 pinboard_drag_in_progress
= icon
;
827 if (icon_selection
->next
== NULL
)
828 drag_one_item(widget
, event
, icon
->path
, icon
->item
, NULL
);
833 uri_list
= icon_create_uri_list();
834 drag_selection(widget
, event
, uri_list
);
839 /* An icon is being dragged around... */
840 static gint
icon_motion_notify(GtkWidget
*widget
,
841 GdkEventMotion
*event
,
844 if (motion_state
== MOTION_READY_FOR_DND
)
846 if (dnd_motion_moved(event
))
847 start_drag(pi
, event
);
854 static void backdrop_from_xml(xmlNode
*node
)
858 g_free(current_pinboard
->backdrop
);
859 current_pinboard
->backdrop
= xmlNodeGetContent(node
);
861 style
= xmlGetProp(node
, "style");
865 current_pinboard
->backdrop_style
=
866 g_strcasecmp(style
, "Tiled") == 0 ? BACKDROP_TILE
:
867 g_strcasecmp(style
, "Scaled") == 0 ? BACKDROP_SCALE
:
868 g_strcasecmp(style
, "Centred") == 0 ? BACKDROP_CENTRE
:
869 g_strcasecmp(style
, "Program") == 0 ? BACKDROP_PROGRAM
:
874 current_pinboard
->backdrop_style
= BACKDROP_TILE
;
877 /* Create one pinboard icon for each icon in the doc */
878 static void pinboard_load_from_xml(xmlDocPtr doc
)
880 xmlNodePtr node
, root
;
881 char *tmp
, *label
, *path
;
884 root
= xmlDocGetRootElement(doc
);
886 for (node
= root
->xmlChildrenNode
; node
; node
= node
->next
)
888 if (node
->type
!= XML_ELEMENT_NODE
)
890 if (strcmp(node
->name
, "backdrop") == 0)
892 backdrop_from_xml(node
);
895 if (strcmp(node
->name
, "icon") != 0)
898 tmp
= xmlGetProp(node
, "x");
904 tmp
= xmlGetProp(node
, "y");
910 label
= xmlGetProp(node
, "label");
912 label
= g_strdup("<missing label>");
913 path
= xmlNodeGetContent(node
);
915 path
= g_strdup("<missing path>");
917 pinboard_pin(path
, label
, x
, y
);
924 /* Called for each line in the pinboard file while loading a new board.
925 * Only used for old-format files when converting to XML.
927 static const char *pin_from_file(gchar
*line
)
936 end
= strchr(line
+ 1, '>');
938 return _("Missing '>' in icon label");
940 leaf
= g_strndup(line
+ 1, end
- line
- 1);
944 while (isspace(*line
))
947 return _("Missing ',' after icon label");
951 if (sscanf(line
, " %d , %d , %n", &x
, &y
, &n
) < 2)
952 return NULL
; /* Ignore format errors */
954 pinboard_pin(line
+ n
, leaf
, x
, y
);
961 /* Write the current state of the pinboard to the current pinboard file */
962 static void pinboard_save(void)
965 guchar
*save_new
= NULL
;
967 xmlDocPtr doc
= NULL
;
970 g_return_if_fail(current_pinboard
!= NULL
);
972 if (strchr(current_pinboard
->name
, '/'))
973 save
= g_strdup(current_pinboard
->name
);
978 leaf
= g_strconcat("pb_", current_pinboard
->name
, NULL
);
979 save
= choices_find_path_save(leaf
, PROJECT
, TRUE
);
986 doc
= xmlNewDoc("1.0");
987 xmlDocSetRootElement(doc
, xmlNewDocNode(doc
, NULL
, "pinboard", NULL
));
989 root
= xmlDocGetRootElement(doc
);
991 if (current_pinboard
->backdrop
)
993 BackdropStyle style
= current_pinboard
->backdrop_style
;
996 tree
= xmlNewTextChild(root
, NULL
, "backdrop",
997 current_pinboard
->backdrop
);
998 xmlSetProp(tree
, "style",
999 style
== BACKDROP_TILE
? "Tiled" :
1000 style
== BACKDROP_CENTRE
? "Centred" :
1001 style
== BACKDROP_SCALE
? "Scaled" :
1005 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
1008 PinIcon
*pi
= (PinIcon
*) next
->data
;
1009 Icon
*icon
= (Icon
*) pi
;
1012 tree
= xmlNewTextChild(root
, NULL
, "icon", icon
->src_path
);
1014 tmp
= g_strdup_printf("%d", pi
->x
);
1015 xmlSetProp(tree
, "x", tmp
);
1018 tmp
= g_strdup_printf("%d", pi
->y
);
1019 xmlSetProp(tree
, "y", tmp
);
1022 xmlSetProp(tree
, "label", icon
->item
->leafname
);
1025 save_new
= g_strconcat(save
, ".new", NULL
);
1026 if (save_xml_file(doc
, save_new
) || rename(save_new
, save
))
1027 delayed_error(_("Error saving pinboard %s: %s"),
1028 save
, g_strerror(errno
));
1036 static void snap_to_grid(int *x
, int *y
)
1038 int step
= o_pinboard_grid_step
.int_value
;
1040 *x
= ((*x
+ step
/ 2) / step
) * step
;
1041 *y
= ((*y
+ step
/ 2) / step
) * step
;
1044 /* Convert (x,y) from a centre point to a window position */
1045 static void offset_from_centre(PinIcon
*pi
,
1046 int width
, int height
,
1049 gboolean clamp
= o_pinboard_clamp_icons
.int_value
;
1053 *x
= CLAMP(*x
, 0, screen_width
- (clamp
? width
: 0));
1054 *y
= CLAMP(*y
, 0, screen_height
- (clamp
? height
: 0));
1057 /* Convert (x,y) from a window position to a centre point */
1058 static void offset_to_centre(PinIcon
*pi
,
1059 int width
, int height
,
1066 /* Same as drag_set_dest(), but for pinboard icons */
1067 static void drag_set_pinicon_dest(PinIcon
*pi
)
1069 GtkObject
*obj
= GTK_OBJECT(pi
->widget
);
1071 make_drop_target(pi
->widget
, 0);
1073 g_signal_connect(obj
, "drag_motion", G_CALLBACK(drag_motion
), pi
);
1074 g_signal_connect(obj
, "drag_leave", G_CALLBACK(drag_leave
), pi
);
1075 g_signal_connect(obj
, "drag_end", G_CALLBACK(drag_end
), pi
);
1078 /* Called during the drag when the mouse is in a widget registered
1079 * as a drop target. Returns TRUE if we can accept the drop.
1081 static gboolean
drag_motion(GtkWidget
*widget
,
1082 GdkDragContext
*context
,
1088 GdkDragAction action
= context
->suggested_action
;
1090 Icon
*icon
= (Icon
*) pi
;
1091 DirItem
*item
= icon
->item
;
1093 if (gtk_drag_get_source_widget(context
) == widget
)
1094 goto out
; /* Can't drag something to itself! */
1097 goto out
; /* Can't drag a selection to itself */
1099 type
= dnd_motion_item(context
, &item
);
1104 /* We actually must pretend to accept the drop, even if the
1105 * directory isn't writeable, so that the spring-opening
1109 /* Don't allow drops to non-writeable directories */
1110 if (o_dnd_spring_open
.int_value
== FALSE
&&
1111 type
== drop_dest_dir
&&
1112 access(icon
->path
, W_OK
) != 0)
1117 g_dataset_set_data(context
, "drop_dest_type", type
);
1120 gdk_drag_status(context
, action
, time
);
1121 g_dataset_set_data_full(context
, "drop_dest_path",
1122 g_strdup(icon
->path
), g_free
);
1123 if (type
== drop_dest_dir
)
1124 dnd_spring_load(context
, NULL
);
1126 pinboard_wink_item(pi
, FALSE
);
1129 gdk_drag_status(context
, 0, time
);
1131 /* Always return TRUE to stop the pinboard getting the events */
1135 static gboolean pinboard_shadow
= FALSE
;
1136 static gint shadow_x
, shadow_y
;
1137 #define SHADOW_SIZE (ICON_WIDTH)
1139 static void bg_expose(GtkWidget
*window
, GdkEventExpose
*event
, gpointer data
)
1141 GdkRectangle
*area
= &event
->area
;
1142 static GdkGC
*shadow_gc
= NULL
;
1143 static GdkColor white
, black
;
1145 if (!pinboard_shadow
)
1147 /* XXX: Should just disable the events */
1156 white
.red
= white
.green
= white
.blue
= 0xffff;
1157 black
.red
= black
.green
= black
.blue
= 0;
1159 cm
= gtk_widget_get_colormap(window
);
1160 shadow_gc
= gdk_gc_new(window
->window
);
1162 gdk_colormap_alloc_colors(cm
, &white
, 1, FALSE
, TRUE
, &success
);
1163 gdk_colormap_alloc_colors(cm
, &black
, 1, FALSE
, TRUE
, &success
);
1166 gdk_gc_set_clip_rectangle(shadow_gc
, area
);
1167 gdk_gc_set_foreground(shadow_gc
, &white
);
1168 gdk_draw_rectangle(window
->window
, shadow_gc
, FALSE
,
1170 SHADOW_SIZE
, SHADOW_SIZE
);
1171 gdk_gc_set_foreground(shadow_gc
, &black
);
1172 gdk_draw_rectangle(window
->window
, shadow_gc
, FALSE
,
1173 shadow_x
+ 1, shadow_y
+ 1,
1174 SHADOW_SIZE
- 2, SHADOW_SIZE
- 2);
1175 gdk_gc_set_clip_rectangle(shadow_gc
, NULL
);
1178 /* Draw a 'shadow' under an icon being dragged, showing where
1181 static void pinboard_set_shadow(gboolean on
)
1183 if (pinboard_shadow
)
1185 gdk_window_clear_area_e(current_pinboard
->window
->window
,
1187 SHADOW_SIZE
+ 1, SHADOW_SIZE
+ 1);
1192 int old_x
= shadow_x
, old_y
= shadow_y
;
1194 gdk_window_get_pointer(current_pinboard
->window
->window
,
1195 &shadow_x
, &shadow_y
, NULL
);
1196 snap_to_grid(&shadow_x
, &shadow_y
);
1197 shadow_x
-= SHADOW_SIZE
/ 2;
1198 shadow_y
-= SHADOW_SIZE
/ 2;
1201 if (pinboard_shadow
&& shadow_x
== old_x
&& shadow_y
== old_y
)
1204 gdk_window_clear_area_e(current_pinboard
->window
->window
,
1206 SHADOW_SIZE
+ 1, SHADOW_SIZE
+ 1);
1209 pinboard_shadow
= on
;
1212 /* Called when dragging some pinboard icons finishes */
1213 void pinboard_move_icons(void)
1215 int x
= shadow_x
, y
= shadow_y
;
1216 PinIcon
*pi
= (PinIcon
*) pinboard_drag_in_progress
;
1219 g_return_if_fail(pi
!= NULL
);
1221 x
+= SHADOW_SIZE
/ 2;
1222 y
+= SHADOW_SIZE
/ 2;
1223 snap_to_grid(&x
, &y
);
1225 if (pi
->x
== x
&& pi
->y
== y
)
1230 gdk_drawable_get_size(pi
->win
->window
, &width
, &height
);
1231 offset_from_centre(pi
, width
, height
, &x
, &y
);
1233 gdk_window_move(pi
->win
->window
, x
, y
);
1238 static void drag_leave(GtkWidget
*widget
,
1239 GdkDragContext
*context
,
1243 pinboard_wink_item(NULL
, FALSE
);
1247 static gboolean
bg_drag_leave(GtkWidget
*widget
,
1248 GdkDragContext
*context
,
1252 pinboard_set_shadow(FALSE
);
1256 static gboolean
bg_drag_motion(GtkWidget
*widget
,
1257 GdkDragContext
*context
,
1263 /* Dragging from the pinboard to the pinboard is not allowed */
1265 if (!provides(context
, text_uri_list
))
1268 pinboard_set_shadow(TRUE
);
1270 gdk_drag_status(context
,
1271 context
->suggested_action
== GDK_ACTION_ASK
1272 ? GDK_ACTION_LINK
: context
->suggested_action
,
1277 static void drag_end(GtkWidget
*widget
,
1278 GdkDragContext
*context
,
1281 pinboard_drag_in_progress
= NULL
;
1282 if (tmp_icon_selected
)
1284 icon_select_only(NULL
);
1285 tmp_icon_selected
= FALSE
;
1289 /* Something which affects all the icons has changed - reshape
1290 * and redraw all of them.
1292 static void reshape_all(void)
1296 g_return_if_fail(current_pinboard
!= NULL
);
1298 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
1300 Icon
*icon
= (Icon
*) next
->data
;
1301 pinboard_reshape_icon(icon
);
1305 /* Turns off the pinboard. Does not call gtk_main_quit. */
1306 static void pinboard_clear(void)
1310 g_return_if_fail(current_pinboard
!= NULL
);
1312 next
= current_pinboard
->icons
;
1315 PinIcon
*pi
= (PinIcon
*) next
->data
;
1319 gtk_widget_destroy(pi
->win
);
1322 gtk_widget_destroy(current_pinboard
->window
);
1324 g_free(current_pinboard
->name
);
1325 g_free(current_pinboard
);
1326 current_pinboard
= NULL
;
1328 number_of_windows
--;
1331 static gpointer parent_class
;
1333 static void pin_icon_destroy(Icon
*icon
)
1335 PinIcon
*pi
= (PinIcon
*) icon
;
1337 g_return_if_fail(pi
->win
!= NULL
);
1339 gtk_widget_destroy(pi
->win
);
1342 static void pin_icon_finalize(GObject
*icon
)
1344 PinIcon
*pi
= (PinIcon
*) icon
;
1348 g_object_unref(G_OBJECT(pi
->layout
));
1352 ((GObjectClass
*) parent_class
)->finalize(icon
);
1355 static void pinboard_remove_items(void)
1357 g_return_if_fail(icon_selection
!= NULL
);
1359 while (icon_selection
)
1360 icon_destroy((Icon
*) icon_selection
->data
);
1365 static void pin_icon_update(Icon
*icon
)
1367 pinboard_reshape_icon(icon
);
1371 static gboolean
pin_icon_same_group(Icon
*icon
, Icon
*other
)
1373 return IS_PIN_ICON(other
);
1376 static void pin_icon_class_init(gpointer gclass
, gpointer data
)
1378 IconClass
*icon
= (IconClass
*) gclass
;
1380 parent_class
= g_type_class_peek_parent(gclass
);
1382 ((GObjectClass
*) icon
)->finalize
= pin_icon_finalize
;
1384 icon
->destroy
= pin_icon_destroy
;
1385 icon
->redraw
= pinboard_reshape_icon
;
1386 icon
->update
= pin_icon_update
;
1387 icon
->remove_items
= pinboard_remove_items
;
1388 icon
->same_group
= pin_icon_same_group
;
1391 static void pin_icon_init(GTypeInstance
*object
, gpointer gclass
)
1393 PinIcon
*pi
= (PinIcon
*) object
;
1398 static GType
pin_icon_get_type(void)
1400 static GType type
= 0;
1404 static const GTypeInfo info
=
1406 sizeof (PinIconClass
),
1407 NULL
, /* base_init */
1408 NULL
, /* base_finalise */
1409 pin_icon_class_init
,
1410 NULL
, /* class_finalise */
1411 NULL
, /* class_data */
1413 0, /* n_preallocs */
1417 type
= g_type_register_static(icon_get_type(),
1418 "PinIcon", &info
, 0);
1424 static PinIcon
*pin_icon_new(const char *pathname
, const char *name
)
1429 pi
= g_object_new(pin_icon_get_type(), NULL
);
1432 icon_set_path(icon
, pathname
, name
);
1437 /* Called when the window widget is somehow destroyed */
1438 static void pin_icon_destroyed(PinIcon
*pi
)
1440 g_return_if_fail(pi
->win
!= NULL
);
1444 pinboard_wink_item(NULL
, FALSE
);
1446 if (pinboard_drag_in_progress
== (Icon
*) pi
)
1447 pinboard_drag_in_progress
= NULL
;
1449 if (current_pinboard
)
1450 current_pinboard
->icons
=
1451 g_list_remove(current_pinboard
->icons
, pi
);
1456 /* Set the tooltip */
1457 static void pin_icon_set_tip(PinIcon
*pi
)
1461 Icon
*icon
= (Icon
*) pi
;
1463 g_return_if_fail(pi
!= NULL
);
1465 ai
= appinfo_get(icon
->path
, icon
->item
);
1467 if (ai
&& ((node
= xml_get_section(ai
, NULL
, "Summary"))))
1470 str
= xmlNodeListGetString(node
->doc
,
1471 node
->xmlChildrenNode
, 1);
1474 gtk_tooltips_set_tip(tooltips
, pi
->win
, str
, NULL
);
1479 gtk_tooltips_set_tip(tooltips
, pi
->widget
, NULL
, NULL
);
1485 static void pinboard_show_menu(GdkEventButton
*event
, PinIcon
*pi
)
1489 pos
[0] = event
->x_root
;
1490 pos
[1] = event
->y_root
;
1493 icon_prepare_menu((Icon
*) pi
);
1494 gtk_widget_show(icon_menu_remove_backdrop
);
1495 gtk_widget_set_sensitive(GTK_BIN(icon_menu_remove_backdrop
)->child
,
1496 current_pinboard
->backdrop
!= NULL
);
1498 gtk_menu_popup(GTK_MENU(icon_menu
), NULL
, NULL
,
1500 (gpointer
) pos
, event
->button
, event
->time
);
1503 static void create_pinboard_window(Pinboard
*pinboard
)
1507 g_return_if_fail(pinboard
->window
== NULL
);
1509 win
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
1510 pinboard
->window
= win
;
1511 pinboard
->fixed
= gtk_fixed_new();
1512 gtk_container_add(GTK_CONTAINER(win
), pinboard
->fixed
);
1514 gtk_window_set_wmclass(GTK_WINDOW(win
), "ROX-Pinboard", PROJECT
);
1515 gtk_widget_set_name(win
, "rox-pinboard");
1517 gtk_widget_set_size_request(win
, screen_width
, screen_height
);
1518 gtk_widget_realize(win
);
1519 gtk_window_move(GTK_WINDOW(win
), 0, 0);
1520 make_panel_window(win
);
1522 /* TODO: Use gdk function when it supports this type */
1524 GdkAtom desktop_type
;
1526 desktop_type
= gdk_atom_intern("_NET_WM_WINDOW_TYPE_DESKTOP",
1528 gdk_property_change(win
->window
,
1529 gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE
),
1530 gdk_atom_intern("ATOM", FALSE
), 32,
1531 GDK_PROP_MODE_REPLACE
, (guchar
*) &desktop_type
, 1);
1534 gtk_widget_add_events(win
,
1535 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
1537 g_signal_connect(win
, "button-press-event",
1538 G_CALLBACK(button_press_event
), NULL
);
1539 g_signal_connect(win
, "button-release-event",
1540 G_CALLBACK(button_release_event
), NULL
);
1541 g_signal_connect(win
, "expose_event", G_CALLBACK(bg_expose
), NULL
);
1543 /* Drag and drop handlers */
1544 drag_set_pinboard_dest(win
);
1545 g_signal_connect(win
, "drag_motion", G_CALLBACK(bg_drag_motion
), NULL
);
1546 g_signal_connect(win
, "drag_leave", G_CALLBACK(bg_drag_leave
), NULL
);
1548 gtk_widget_show_all(win
);
1549 gdk_window_lower(win
->window
);
1552 static void reload_backdrop(Pinboard
*pinboard
)
1556 if (pinboard
->backdrop
&& pinboard
->backdrop_style
== BACKDROP_PROGRAM
)
1558 const char *argv
[] = {NULL
, "--backdrop", NULL
};
1560 argv
[0] = make_path(pinboard
->backdrop
, "AppRun")->str
;
1562 /* Run the program. It'll send us a SOAP message and we'll
1563 * get back here with a different style and image.
1565 rox_spawn(NULL
, argv
);
1569 /* Note: Copying a style does not ref the pixmaps! */
1571 style
= gtk_style_copy(gtk_widget_get_style(pinboard
->window
));
1573 style
->bg_pixmap
[GTK_STATE_NORMAL
] = NULL
;
1575 if (pinboard
->backdrop
)
1578 GError
*error
= NULL
;
1580 pixbuf
= gdk_pixbuf_new_from_file(pinboard
->backdrop
, &error
);
1583 delayed_error(_("Error loading backdrop image:\n%s\n"
1584 "Backdrop removed."),
1586 g_error_free(error
);
1587 set_backdrop(pinboard
, NULL
, NULL
, BACKDROP_NONE
);
1593 if (pinboard
->backdrop_style
== BACKDROP_SCALE
)
1595 GdkPixbuf
*old
= pixbuf
;
1597 pixbuf
= gdk_pixbuf_scale_simple(old
,
1598 screen_width
, screen_height
,
1601 g_object_unref(old
);
1603 else if (pinboard
->backdrop_style
== BACKDROP_CENTRE
)
1605 GdkPixbuf
*old
= pixbuf
;
1606 int x
, y
, width
, height
;
1608 width
= gdk_pixbuf_get_width(pixbuf
);
1609 height
= gdk_pixbuf_get_height(pixbuf
);
1611 pixbuf
= gdk_pixbuf_new(
1612 gdk_pixbuf_get_colorspace(pixbuf
), 0,
1613 8, screen_width
, screen_height
);
1614 gdk_pixbuf_fill(pixbuf
, 0);
1616 x
= (screen_width
- width
) / 2;
1617 y
= (screen_height
- height
) / 2;
1621 gdk_pixbuf_composite(old
, pixbuf
,
1623 MIN(screen_width
, width
),
1624 MIN(screen_height
, height
),
1626 GDK_INTERP_NEAREST
, 255);
1627 g_object_unref(old
);
1630 gdk_pixbuf_render_pixmap_and_mask(pixbuf
,
1632 g_object_unref(pixbuf
);
1634 style
->bg_pixmap
[GTK_STATE_NORMAL
] = pixmap
;
1638 gtk_widget_set_style(pinboard
->window
, style
);
1640 g_object_unref(style
);
1641 gtk_widget_queue_draw(pinboard
->window
);
1644 /* If image is NULL (normal case), (path,style) is both displayed and saved.
1645 * Otherwise, (image, style) is displayed and (path, BACKDROP_PROGRAM) saved.
1647 static void set_backdrop(Pinboard
*pinboard
, const gchar
*image
,
1648 const gchar
*path
, BackdropStyle style
)
1652 g_return_if_fail(pinboard
!= NULL
);
1654 g_free(pinboard
->backdrop
);
1655 pinboard
->backdrop
= NULL
;
1657 /* Set the image to display... */
1658 tmp
= g_strdup(image
? image
: path
);
1659 pinboard
->backdrop
= tmp
;
1660 pinboard
->backdrop_style
= style
;
1661 reload_backdrop(pinboard
);
1664 /* Set the stored values... */
1666 pinboard
->backdrop
= g_strdup(path
);
1668 pinboard
->backdrop
= NULL
;
1670 pinboard
->backdrop_style
= image
? BACKDROP_PROGRAM
: style
;